From 73d5ee11fe2485cf8cea55caa147cd9d9cc7a809 Mon Sep 17 00:00:00 2001 From: Noah Horton Date: Tue, 20 Jan 2026 19:38:35 -0700 Subject: [PATCH] Rename document_type to doc_spec throughout codebase (#92) BREAKING CHANGE: The job.yml field `document_type` has been renamed to `doc_spec`. - Rename OutputSpec.document_type to doc_spec in parser - Rename DocumentTypeDefinition class to DocSpec (with backward compat alias) - Update generator methods: _load_document_type -> _load_doc_spec - Update templates to use doc_spec terminology - Update standard_jobs and documentation - Add 9 new tests for generator doc spec integration - Bump version to 0.5.0 Co-Authored-By: Claude Opus 4.5 --- .claude/skills/deepwork_jobs.define/SKILL.md | 24 +-- .claude/skills/deepwork_jobs.learn/SKILL.md | 4 +- .../deepwork_jobs.review_job_spec/SKILL.md | 8 +- .deepwork/doc_specs/job_spec.md | 6 +- .../jobs/deepwork_jobs/doc_specs/job_spec.md | 6 +- .deepwork/jobs/deepwork_jobs/job.yml | 10 +- .deepwork/jobs/deepwork_jobs/steps/define.md | 12 +- .deepwork/jobs/deepwork_jobs/steps/learn.md | 4 +- .gemini/skills/deepwork_jobs/define.toml | 16 +- .gemini/skills/deepwork_jobs/learn.toml | 4 +- .../skills/deepwork_jobs/review_job_spec.toml | 2 +- CHANGELOG.md | 32 ++- doc/architecture.md | 6 +- doc/doc-specs.md | 10 +- pyproject.toml | 2 +- src/deepwork/core/doc_spec_parser.py | 30 +-- src/deepwork/core/generator.py | 54 +++-- src/deepwork/core/parser.py | 42 ++-- src/deepwork/schemas/job_schema.py | 2 +- .../deepwork_jobs/doc_specs/job_spec.md | 6 +- .../standard_jobs/deepwork_jobs/job.yml | 10 +- .../deepwork_jobs/steps/define.md | 12 +- .../deepwork_jobs/steps/learn.md | 4 +- .../templates/claude/skill-job-step.md.jinja | 22 +- .../gemini/skill-job-step.toml.jinja | 16 +- tests/fixtures/jobs/job_with_doc_spec/job.yml | 18 ++ .../steps/generate_report.md | 0 .../jobs/job_with_document_type/job.yml | 18 -- tests/unit/test_generator.py | 190 +++++++++++++++++- tests/unit/test_parser.py | 50 ++--- uv.lock | 2 +- 31 files changed, 417 insertions(+), 205 deletions(-) create mode 100644 tests/fixtures/jobs/job_with_doc_spec/job.yml rename tests/fixtures/jobs/{job_with_document_type => job_with_doc_spec}/steps/generate_report.md (100%) delete mode 100644 tests/fixtures/jobs/job_with_document_type/job.yml diff --git a/.claude/skills/deepwork_jobs.define/SKILL.md b/.claude/skills/deepwork_jobs.define/SKILL.md index 9f35d46f..4db98176 100644 --- a/.claude/skills/deepwork_jobs.define/SKILL.md +++ b/.claude/skills/deepwork_jobs.define/SKILL.md @@ -15,7 +15,7 @@ hooks: 2. **Structured Questions Used**: Did the agent ask structured questions (using the AskUserQuestion tool) to gather user input? 3. **Document Detection**: For document-oriented workflows, did the agent detect patterns and offer doc spec creation? 4. **doc spec Created (if applicable)**: If a doc spec was needed, was it created in `.deepwork/doc_specs/[doc_spec_name].md` with proper quality criteria? - 5. **doc spec References**: Are document outputs properly linked to their doc specs using `{file, document_type}` format? + 5. **doc spec References**: Are document outputs properly linked to their doc specs using `{file, doc_spec}` format? 6. **Valid Against doc spec**: Does the job.yml conform to the job.yml doc spec quality criteria (valid identifier, semantic version, concise summary, rich description, complete steps, valid dependencies)? 7. **Clear Inputs/Outputs**: Does every step have clearly defined inputs and outputs? 8. **Logical Dependencies**: Do step dependencies make sense and avoid circular references? @@ -84,19 +84,19 @@ Start by asking structured questions to understand what the user wants to accomp **Check for document-focused patterns** in the user's description: - Keywords: "report", "summary", "document", "create", "monthly", "quarterly", "for stakeholders", "for leadership" -- Final deliverable is a specific document type (e.g., "AWS spending report", "competitive analysis", "sprint summary") +- Final deliverable is a specific document (e.g., "AWS spending report", "competitive analysis", "sprint summary") - Recurring documents with consistent structure **If a document-oriented workflow is detected:** -1. Inform the user: "This workflow produces a specific document type. I recommend defining a Document Type Definition (doc spec) first to ensure consistent quality." +1. Inform the user: "This workflow produces a specific document type. I recommend defining a doc spec first to ensure consistent quality." 2. Ask structured questions to understand if they want to: - - Create a doc spec for the document type + - Create a doc spec for this document - Use an existing doc spec (if any exist in `.deepwork/doc_specs/`) - Skip doc spec and proceed with simple outputs -### Step 1.6: Define the Document Type Definition (if needed) +### Step 1.6: Define the Doc Spec (if needed) When creating a doc spec, gather the following information: @@ -170,7 +170,7 @@ When a step produces a document with a doc spec reference, use this format in jo ```yaml outputs: - file: reports/monthly_spending.md - document_type: .deepwork/doc_specs/monthly_aws_report.md + doc_spec: .deepwork/doc_specs/monthly_aws_report.md ``` The doc spec's quality criteria will automatically be included in the generated skill, ensuring consistent document quality. @@ -269,7 +269,7 @@ This creates: (Where `[job_name]` is the name of the NEW job you're creating, e.g., `competitive_research`) -**Document Type Definition**: See `.deepwork/doc_specs/job_spec.md` for the complete specification with quality criteria. +**Doc Spec**: See `.deepwork/doc_specs/job_spec.md` for the complete specification with quality criteria. **Template reference**: See `.deepwork/jobs/deepwork_jobs/templates/job.yml.template` for the standard structure. @@ -475,7 +475,7 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` **Required outputs**: - `job.yml` - **Document Type**: DeepWork Job Specification + **Doc Spec**: DeepWork Job Specification > YAML specification file that defines a multi-step workflow job for AI agents **Definition**: `.deepwork/doc_specs/job_spec.md` **Target Audience**: AI agents executing jobs and developers defining workflows @@ -540,9 +540,9 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` outputs: - filename.md # simple filename - reports/analysis.md # path with directory - # With document type reference: + # With doc spec reference: - file: report.md - document_type: .deepwork/doc_specs/report_type.md + doc_spec: .deepwork/doc_specs/report_type.md dependencies: - previous_step_id # steps that must complete first ``` @@ -651,7 +651,7 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` from_step: research_competitors outputs: - file: positioning_report.md - document_type: .deepwork/doc_specs/positioning_report.md + doc_spec: .deepwork/doc_specs/positioning_report.md dependencies: - research_competitors ``` @@ -675,7 +675,7 @@ Stop hooks will automatically validate your work. The loop continues until all c 2. **Structured Questions Used**: Did the agent ask structured questions (using the AskUserQuestion tool) to gather user input? 3. **Document Detection**: For document-oriented workflows, did the agent detect patterns and offer doc spec creation? 4. **doc spec Created (if applicable)**: If a doc spec was needed, was it created in `.deepwork/doc_specs/[doc_spec_name].md` with proper quality criteria? -5. **doc spec References**: Are document outputs properly linked to their doc specs using `{file, document_type}` format? +5. **doc spec References**: Are document outputs properly linked to their doc specs using `{file, doc_spec}` format? 6. **Valid Against doc spec**: Does the job.yml conform to the job.yml doc spec quality criteria (valid identifier, semantic version, concise summary, rich description, complete steps, valid dependencies)? 7. **Clear Inputs/Outputs**: Does every step have clearly defined inputs and outputs? 8. **Logical Dependencies**: Do step dependencies make sense and avoid circular references? diff --git a/.claude/skills/deepwork_jobs.learn/SKILL.md b/.claude/skills/deepwork_jobs.learn/SKILL.md index 76013c97..149409b1 100644 --- a/.claude/skills/deepwork_jobs.learn/SKILL.md +++ b/.claude/skills/deepwork_jobs.learn/SKILL.md @@ -113,7 +113,7 @@ For each learning identified, determine if it is: - "Quality criteria should include checking for Y" - "Add example of correct output format" -**doc spec-Related** (should improve document type definitions): +**doc spec-Related** (should improve doc spec files): - Improvements to document quality criteria - Changes to document structure or format - Updated audience or frequency information @@ -214,7 +214,7 @@ Review all instruction files for the job and identify content that: If doc spec-related learnings were identified: 1. **Locate the doc spec file** - - Find doc spec references in job.yml outputs (look for `document_type: .deepwork/doc_specs/[doc_spec_name].md`) + - Find doc spec references in job.yml outputs (look for `doc_spec: .deepwork/doc_specs/[doc_spec_name].md`) - doc spec files are at `.deepwork/doc_specs/[doc_spec_name].md` 2. **Update quality_criteria array** diff --git a/.claude/skills/deepwork_jobs.review_job_spec/SKILL.md b/.claude/skills/deepwork_jobs.review_job_spec/SKILL.md index 3aa92898..fb331c00 100644 --- a/.claude/skills/deepwork_jobs.review_job_spec/SKILL.md +++ b/.claude/skills/deepwork_jobs.review_job_spec/SKILL.md @@ -284,7 +284,7 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` **Required outputs**: - `job.yml` - **Document Type**: DeepWork Job Specification + **Doc Spec**: DeepWork Job Specification > YAML specification file that defines a multi-step workflow job for AI agents **Definition**: `.deepwork/doc_specs/job_spec.md` **Target Audience**: AI agents executing jobs and developers defining workflows @@ -349,9 +349,9 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` outputs: - filename.md # simple filename - reports/analysis.md # path with directory - # With document type reference: + # With doc spec reference: - file: report.md - document_type: .deepwork/doc_specs/report_type.md + doc_spec: .deepwork/doc_specs/report_type.md dependencies: - previous_step_id # steps that must complete first ``` @@ -460,7 +460,7 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` from_step: research_competitors outputs: - file: positioning_report.md - document_type: .deepwork/doc_specs/positioning_report.md + doc_spec: .deepwork/doc_specs/positioning_report.md dependencies: - research_competitors ``` diff --git a/.deepwork/doc_specs/job_spec.md b/.deepwork/doc_specs/job_spec.md index 07eaf198..46b32e7d 100644 --- a/.deepwork/doc_specs/job_spec.md +++ b/.deepwork/doc_specs/job_spec.md @@ -73,9 +73,9 @@ steps: outputs: - filename.md # simple filename - reports/analysis.md # path with directory - # With document type reference: + # With doc spec reference: - file: report.md - document_type: .deepwork/doc_specs/report_type.md + doc_spec: .deepwork/doc_specs/report_type.md dependencies: - previous_step_id # steps that must complete first ``` @@ -184,7 +184,7 @@ steps: from_step: research_competitors outputs: - file: positioning_report.md - document_type: .deepwork/doc_specs/positioning_report.md + doc_spec: .deepwork/doc_specs/positioning_report.md dependencies: - research_competitors ``` diff --git a/.deepwork/jobs/deepwork_jobs/doc_specs/job_spec.md b/.deepwork/jobs/deepwork_jobs/doc_specs/job_spec.md index 07eaf198..46b32e7d 100644 --- a/.deepwork/jobs/deepwork_jobs/doc_specs/job_spec.md +++ b/.deepwork/jobs/deepwork_jobs/doc_specs/job_spec.md @@ -73,9 +73,9 @@ steps: outputs: - filename.md # simple filename - reports/analysis.md # path with directory - # With document type reference: + # With doc spec reference: - file: report.md - document_type: .deepwork/doc_specs/report_type.md + doc_spec: .deepwork/doc_specs/report_type.md dependencies: - previous_step_id # steps that must complete first ``` @@ -184,7 +184,7 @@ steps: from_step: research_competitors outputs: - file: positioning_report.md - document_type: .deepwork/doc_specs/positioning_report.md + doc_spec: .deepwork/doc_specs/positioning_report.md dependencies: - research_competitors ``` diff --git a/.deepwork/jobs/deepwork_jobs/job.yml b/.deepwork/jobs/deepwork_jobs/job.yml index 1c74d3f6..715fc65a 100644 --- a/.deepwork/jobs/deepwork_jobs/job.yml +++ b/.deepwork/jobs/deepwork_jobs/job.yml @@ -25,9 +25,9 @@ changelog: - version: "0.5.0" changes: "Standardized on 'ask structured questions' phrasing for user input; Updated quality criteria hooks to verify phrase usage; Added guidance in implement.md to use phrase in generated instructions" - version: "0.6.0" - changes: "Added Document Type Definition (doc spec) support; define.md now detects document-oriented workflows and guides doc spec creation; learn.md now identifies and applies doc spec-related improvements" + changes: "Added doc spec support; define.md now detects document-oriented workflows and guides doc spec creation; learn.md now identifies and applies doc spec-related improvements" - version: "0.7.0" - changes: "Added job.yml doc spec; define step now outputs job.yml with document_type reference for quality validation" + changes: "Added job.yml doc spec; define step now outputs job.yml with doc_spec reference for quality validation" - version: "0.8.0" changes: "Added review_job_spec step between define and implement for doc spec-based quality validation using sub-agent review" - version: "0.9.0" @@ -43,14 +43,14 @@ steps: description: "What complex task or workflow are you trying to accomplish?" outputs: - file: job.yml - document_type: .deepwork/doc_specs/job_spec.md + doc_spec: .deepwork/doc_specs/job_spec.md dependencies: [] quality_criteria: - "**User Understanding**: Did the agent fully understand the user's workflow by asking structured questions?" - "**Structured Questions Used**: Did the agent ask structured questions (using the AskUserQuestion tool) to gather user input?" - "**Document Detection**: For document-oriented workflows, did the agent detect patterns and offer doc spec creation?" - "**doc spec Created (if applicable)**: If a doc spec was needed, was it created in `.deepwork/doc_specs/[doc_spec_name].md` with proper quality criteria?" - - "**doc spec References**: Are document outputs properly linked to their doc specs using `{file, document_type}` format?" + - "**doc spec References**: Are document outputs properly linked to their doc specs using `{file, doc_spec}` format?" - "**Valid Against doc spec**: Does the job.yml conform to the job.yml doc spec quality criteria (valid identifier, semantic version, concise summary, rich description, complete steps, valid dependencies)?" - "**Clear Inputs/Outputs**: Does every step have clearly defined inputs and outputs?" - "**Logical Dependencies**: Do step dependencies make sense and avoid circular references?" @@ -68,7 +68,7 @@ steps: from_step: define outputs: - file: job.yml - document_type: .deepwork/doc_specs/job_spec.md + doc_spec: .deepwork/doc_specs/job_spec.md dependencies: - define quality_criteria: diff --git a/.deepwork/jobs/deepwork_jobs/steps/define.md b/.deepwork/jobs/deepwork_jobs/steps/define.md index 03528dd2..0d5533a1 100644 --- a/.deepwork/jobs/deepwork_jobs/steps/define.md +++ b/.deepwork/jobs/deepwork_jobs/steps/define.md @@ -35,19 +35,19 @@ Start by asking structured questions to understand what the user wants to accomp **Check for document-focused patterns** in the user's description: - Keywords: "report", "summary", "document", "create", "monthly", "quarterly", "for stakeholders", "for leadership" -- Final deliverable is a specific document type (e.g., "AWS spending report", "competitive analysis", "sprint summary") +- Final deliverable is a specific document (e.g., "AWS spending report", "competitive analysis", "sprint summary") - Recurring documents with consistent structure **If a document-oriented workflow is detected:** -1. Inform the user: "This workflow produces a specific document type. I recommend defining a Document Type Definition (doc spec) first to ensure consistent quality." +1. Inform the user: "This workflow produces a specific document type. I recommend defining a doc spec first to ensure consistent quality." 2. Ask structured questions to understand if they want to: - - Create a doc spec for the document type + - Create a doc spec for this document - Use an existing doc spec (if any exist in `.deepwork/doc_specs/`) - Skip doc spec and proceed with simple outputs -### Step 1.6: Define the Document Type Definition (if needed) +### Step 1.6: Define the Doc Spec (if needed) When creating a doc spec, gather the following information: @@ -121,7 +121,7 @@ When a step produces a document with a doc spec reference, use this format in jo ```yaml outputs: - file: reports/monthly_spending.md - document_type: .deepwork/doc_specs/monthly_aws_report.md + doc_spec: .deepwork/doc_specs/monthly_aws_report.md ``` The doc spec's quality criteria will automatically be included in the generated skill, ensuring consistent document quality. @@ -220,7 +220,7 @@ This creates: (Where `[job_name]` is the name of the NEW job you're creating, e.g., `competitive_research`) -**Document Type Definition**: See `.deepwork/doc_specs/job_spec.md` for the complete specification with quality criteria. +**Doc Spec**: See `.deepwork/doc_specs/job_spec.md` for the complete specification with quality criteria. **Template reference**: See `.deepwork/jobs/deepwork_jobs/templates/job.yml.template` for the standard structure. diff --git a/.deepwork/jobs/deepwork_jobs/steps/learn.md b/.deepwork/jobs/deepwork_jobs/steps/learn.md index c360fd19..2ba2703c 100644 --- a/.deepwork/jobs/deepwork_jobs/steps/learn.md +++ b/.deepwork/jobs/deepwork_jobs/steps/learn.md @@ -65,7 +65,7 @@ For each learning identified, determine if it is: - "Quality criteria should include checking for Y" - "Add example of correct output format" -**doc spec-Related** (should improve document type definitions): +**doc spec-Related** (should improve doc spec files): - Improvements to document quality criteria - Changes to document structure or format - Updated audience or frequency information @@ -166,7 +166,7 @@ Review all instruction files for the job and identify content that: If doc spec-related learnings were identified: 1. **Locate the doc spec file** - - Find doc spec references in job.yml outputs (look for `document_type: .deepwork/doc_specs/[doc_spec_name].md`) + - Find doc spec references in job.yml outputs (look for `doc_spec: .deepwork/doc_specs/[doc_spec_name].md`) - doc spec files are at `.deepwork/doc_specs/[doc_spec_name].md` 2. **Update quality_criteria array** diff --git a/.gemini/skills/deepwork_jobs/define.toml b/.gemini/skills/deepwork_jobs/define.toml index a01f9b53..91905d82 100644 --- a/.gemini/skills/deepwork_jobs/define.toml +++ b/.gemini/skills/deepwork_jobs/define.toml @@ -55,19 +55,19 @@ Start by asking structured questions to understand what the user wants to accomp **Check for document-focused patterns** in the user's description: - Keywords: "report", "summary", "document", "create", "monthly", "quarterly", "for stakeholders", "for leadership" -- Final deliverable is a specific document type (e.g., "AWS spending report", "competitive analysis", "sprint summary") +- Final deliverable is a specific document (e.g., "AWS spending report", "competitive analysis", "sprint summary") - Recurring documents with consistent structure **If a document-oriented workflow is detected:** -1. Inform the user: "This workflow produces a specific document type. I recommend defining a Document Type Definition (doc spec) first to ensure consistent quality." +1. Inform the user: "This workflow produces a specific document type. I recommend defining a doc spec first to ensure consistent quality." 2. Ask structured questions to understand if they want to: - - Create a doc spec for the document type + - Create a doc spec for this document - Use an existing doc spec (if any exist in `.deepwork/doc_specs/`) - Skip doc spec and proceed with simple outputs -### Step 1.6: Define the Document Type Definition (if needed) +### Step 1.6: Define the Doc Spec (if needed) When creating a doc spec, gather the following information: @@ -141,7 +141,7 @@ When a step produces a document with a doc spec reference, use this format in jo ```yaml outputs: - file: reports/monthly_spending.md - document_type: .deepwork/doc_specs/monthly_aws_report.md + doc_spec: .deepwork/doc_specs/monthly_aws_report.md ``` The doc spec's quality criteria will automatically be included in the generated skill, ensuring consistent document quality. @@ -240,7 +240,7 @@ This creates: (Where `[job_name]` is the name of the NEW job you're creating, e.g., `competitive_research`) -**Document Type Definition**: See `.deepwork/doc_specs/job_spec.md` for the complete specification with quality criteria. +**Doc Spec**: See `.deepwork/doc_specs/job_spec.md` for the complete specification with quality criteria. **Template reference**: See `.deepwork/jobs/deepwork_jobs/templates/job.yml.template` for the standard structure. @@ -446,7 +446,7 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` **Required outputs**: - `job.yml` - **Document Type**: DeepWork Job Specification + **Doc Spec**: DeepWork Job Specification > YAML specification file that defines a multi-step workflow job for AI agents **Definition**: `.deepwork/doc_specs/job_spec.md` **Target Audience**: AI agents executing jobs and developers defining workflows @@ -471,7 +471,7 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` 2. **Structured Questions Used**: Did the agent ask structured questions (using the AskUserQuestion tool) to gather user input? 3. **Document Detection**: For document-oriented workflows, did the agent detect patterns and offer doc spec creation? 4. **doc spec Created (if applicable)**: If a doc spec was needed, was it created in `.deepwork/doc_specs/[doc_spec_name].md` with proper quality criteria? -5. **doc spec References**: Are document outputs properly linked to their doc specs using `{file, document_type}` format? +5. **doc spec References**: Are document outputs properly linked to their doc specs using `{file, doc_spec}` format? 6. **Valid Against doc spec**: Does the job.yml conform to the job.yml doc spec quality criteria (valid identifier, semantic version, concise summary, rich description, complete steps, valid dependencies)? 7. **Clear Inputs/Outputs**: Does every step have clearly defined inputs and outputs? 8. **Logical Dependencies**: Do step dependencies make sense and avoid circular references? diff --git a/.gemini/skills/deepwork_jobs/learn.toml b/.gemini/skills/deepwork_jobs/learn.toml index 7dabfff1..1d2d2960 100644 --- a/.gemini/skills/deepwork_jobs/learn.toml +++ b/.gemini/skills/deepwork_jobs/learn.toml @@ -85,7 +85,7 @@ For each learning identified, determine if it is: - "Quality criteria should include checking for Y" - "Add example of correct output format" -**doc spec-Related** (should improve document type definitions): +**doc spec-Related** (should improve doc spec files): - Improvements to document quality criteria - Changes to document structure or format - Updated audience or frequency information @@ -186,7 +186,7 @@ Review all instruction files for the job and identify content that: If doc spec-related learnings were identified: 1. **Locate the doc spec file** - - Find doc spec references in job.yml outputs (look for `document_type: .deepwork/doc_specs/[doc_spec_name].md`) + - Find doc spec references in job.yml outputs (look for `doc_spec: .deepwork/doc_specs/[doc_spec_name].md`) - doc spec files are at `.deepwork/doc_specs/[doc_spec_name].md` 2. **Update quality_criteria array** diff --git a/.gemini/skills/deepwork_jobs/review_job_spec.toml b/.gemini/skills/deepwork_jobs/review_job_spec.toml index 77923d8a..b24bfa55 100644 --- a/.gemini/skills/deepwork_jobs/review_job_spec.toml +++ b/.gemini/skills/deepwork_jobs/review_job_spec.toml @@ -263,7 +263,7 @@ Use branch format: `deepwork/deepwork_jobs-[instance]-YYYYMMDD` **Required outputs**: - `job.yml` - **Document Type**: DeepWork Job Specification + **Doc Spec**: DeepWork Job Specification > YAML specification file that defines a multi-step workflow job for AI agents **Definition**: `.deepwork/doc_specs/job_spec.md` **Target Audience**: AI agents executing jobs and developers defining workflows diff --git a/CHANGELOG.md b/CHANGELOG.md index fabeddbc..60406257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to DeepWork will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0] - 2026-01-20 + +### Changed +- **BREAKING**: Renamed `document_type` to `doc_spec` throughout the codebase + - Job.yml field: `document_type` → `doc_spec` (e.g., `outputs: [{file: "report.md", doc_spec: ".deepwork/doc_specs/report.md"}]`) + - Class: `DocumentTypeDefinition` → `DocSpec` (backward compat alias provided) + - Methods: `has_document_type()` → `has_doc_spec()`, `validate_document_type_references()` → `validate_doc_spec_references()` + - Template variables: `has_document_type` → `has_doc_spec`, `document_type` → `doc_spec` + - Internal: `_load_document_type()` → `_load_doc_spec()`, `_doc_type_cache` → `_doc_spec_cache` + +### Added +- Comprehensive tests for generator doc spec integration (9 new tests) + - `test_load_doc_spec_returns_parsed_spec` - Verifies doc spec loading + - `test_load_doc_spec_caches_result` - Verifies caching behavior + - `test_load_doc_spec_returns_none_for_missing_file` - Graceful handling of missing files + - `test_generate_step_skill_with_doc_spec` - End-to-end skill generation with doc spec + - `test_build_step_context_includes_doc_spec_info` - Context building verification + +### Migration Guide +- Update job.yml files: Change `document_type:` to `doc_spec:` in output definitions +- Update any code importing `DocumentTypeDefinition`: Use `DocSpec` instead (alias still works) +- Run `deepwork install` to regenerate skills with updated terminology + ## [0.4.0] - 2026-01-20 ### Added @@ -13,10 +36,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New `src/deepwork/core/doc_spec_parser.py` with parser for frontmatter markdown doc spec files - Doc spec files stored in `.deepwork/doc_specs/` directory with quality criteria and example documents - Auto-creates `.deepwork/doc_specs/` directory during `deepwork install` -- Extended job.yml output schema to support document type references - - Outputs can now be strings (backward compatible) or objects with `file` and optional `document_type` fields - - Example: `outputs: [{file: "report.md", document_type: ".deepwork/doc_specs/monthly_report.md"}]` - - The `document_type` uses the full path to the doc spec file, making references self-documenting +- Extended job.yml output schema to support doc spec references + - Outputs can now be strings (backward compatible) or objects with `file` and optional `doc_spec` fields + - Example: `outputs: [{file: "report.md", doc_spec: ".deepwork/doc_specs/monthly_report.md"}]` + - The `doc_spec` uses the full path to the doc spec file, making references self-documenting - Doc spec-aware skill generation - Step skills now include doc spec quality criteria, target audience, and example documents - Both Claude and Gemini templates updated for doc spec rendering @@ -141,6 +164,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Initial version. +[0.5.0]: https://github.com/anthropics/deepwork/releases/tag/0.5.0 [0.4.0]: https://github.com/anthropics/deepwork/releases/tag/0.4.0 [0.3.1]: https://github.com/anthropics/deepwork/releases/tag/0.3.1 [0.3.0]: https://github.com/anthropics/deepwork/releases/tag/0.3.0 diff --git a/doc/architecture.md b/doc/architecture.md index f11d84db..95617d1f 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -46,7 +46,7 @@ deepwork/ # DeepWork tool repository │ │ ├── detector.py # AI platform detection │ │ ├── generator.py # Command file generation │ │ ├── parser.py # Job definition parsing -│ │ ├── doc_spec_parser.py # Document Type Definition parsing +│ │ ├── doc_spec_parser.py # Doc spec parsing │ │ ├── rules_parser.py # Rule definition parsing │ │ ├── pattern_matcher.py # Variable pattern matching for rules │ │ ├── rules_queue.py # Rule state queue system @@ -295,7 +295,7 @@ my-project/ # User's project (target) ├── .deepwork/ # DeepWork configuration │ ├── config.yml # Platform config │ ├── .gitignore # Ignores tmp/ directory -│ ├── doc_specs/ # Document Type Definitions +│ ├── doc_specs/ # Doc specs (document specifications) │ │ └── monthly_aws_report.md │ ├── rules/ # Rule definitions (v2 format) │ │ ├── source-test-pairing.md @@ -1250,7 +1250,7 @@ Reference doc specs in job.yml outputs: ```yaml outputs: - file: reports/monthly_spending.md - document_type: .deepwork/doc_specs/monthly_aws_report.md + doc_spec: .deepwork/doc_specs/monthly_aws_report.md ``` ### Generated Skills diff --git a/doc/doc-specs.md b/doc/doc-specs.md index 047a4406..ace34612 100644 --- a/doc/doc-specs.md +++ b/doc/doc-specs.md @@ -96,16 +96,16 @@ steps: instructions_file: steps/generate_report.md outputs: - file: reports/aws_spending.md - document_type: .deepwork/doc_specs/monthly_aws_report.md + doc_spec: .deepwork/doc_specs/monthly_aws_report.md ``` -The `document_type` field uses the full path to the doc spec file (starting with `.deepwork`). This makes references self-documenting and allows agents to understand them without additional context. +The `doc_spec` field uses the full path to the doc spec file (starting with `.deepwork`). This makes references self-documenting and allows agents to understand them without additional context. ### What Happens During Sync When you run `deepwork sync`: -1. For outputs with `document_type` references, the doc spec file is loaded from the specified path +1. For outputs with `doc_spec` references, the doc spec file is loaded from the specified path 2. The doc spec information is included in the generated skill 3. The skill includes: - Document name and description @@ -123,7 +123,7 @@ The skill will include a section like: **Required outputs**: - `reports/aws_spending.md` - **Document Type**: Monthly AWS Spending Report + **Doc Spec**: Monthly AWS Spending Report > A Markdown summary of AWS spend across accounts **Definition**: `.deepwork/doc_specs/monthly_aws_report.md` @@ -225,7 +225,7 @@ When updating a doc spec: ```yaml outputs: - file: reports/aws_$(date +%Y_%m).md - document_type: .deepwork/doc_specs/monthly_aws_report.md + doc_spec: .deepwork/doc_specs/monthly_aws_report.md ``` 4. **Sync generates skill** with quality criteria included diff --git a/pyproject.toml b/pyproject.toml index d84e3edb..a76599d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "deepwork" -version = "0.4.0" +version = "0.5.0" description = "Framework for enabling AI agents to perform complex, multi-step work tasks" readme = "README.md" requires-python = ">=3.11" diff --git a/src/deepwork/core/doc_spec_parser.py b/src/deepwork/core/doc_spec_parser.py index d0b1144b..d03a74c7 100644 --- a/src/deepwork/core/doc_spec_parser.py +++ b/src/deepwork/core/doc_spec_parser.py @@ -1,4 +1,4 @@ -"""Doc spec parser for document type definitions.""" +"""Doc spec parser.""" import re from dataclasses import dataclass, field @@ -18,7 +18,7 @@ class DocSpecParseError(Exception): @dataclass class QualityCriterion: - """Represents a single quality criterion for a document type.""" + """Represents a single quality criterion for a doc spec.""" name: str description: str @@ -33,8 +33,8 @@ def from_dict(cls, data: dict[str, Any]) -> "QualityCriterion": @dataclass -class DocumentTypeDefinition: - """Represents a complete document type definition (doc spec).""" +class DocSpec: + """Represents a complete doc spec (document specification).""" # Required fields name: str @@ -55,9 +55,9 @@ class DocumentTypeDefinition: @classmethod def from_dict( cls, data: dict[str, Any], example_document: str = "", source_file: Path | None = None - ) -> "DocumentTypeDefinition": + ) -> "DocSpec": """ - Create DocumentTypeDefinition from dictionary. + Create DocSpec from dictionary. Args: data: Parsed YAML frontmatter data @@ -65,7 +65,7 @@ def from_dict( source_file: Path to the source doc spec file Returns: - DocumentTypeDefinition instance + DocSpec instance """ return cls( name=data["name"], @@ -79,6 +79,10 @@ def from_dict( ) +# Backward compatibility alias +DocumentTypeDefinition = DocSpec + + def _parse_frontmatter_markdown(content: str) -> tuple[dict[str, Any], str]: """ Parse frontmatter from markdown content. @@ -122,7 +126,7 @@ def _parse_frontmatter_markdown(content: str) -> tuple[dict[str, Any], str]: return frontmatter, body -def parse_doc_spec_file(filepath: Path | str) -> DocumentTypeDefinition: +def parse_doc_spec_file(filepath: Path | str) -> DocSpec: """ Parse a doc spec file. @@ -130,7 +134,7 @@ def parse_doc_spec_file(filepath: Path | str) -> DocumentTypeDefinition: filepath: Path to the doc spec file (markdown with YAML frontmatter) Returns: - Parsed DocumentTypeDefinition + Parsed DocSpec Raises: DocSpecParseError: If parsing fails or validation errors occur @@ -159,12 +163,12 @@ def parse_doc_spec_file(filepath: Path | str) -> DocumentTypeDefinition: raise DocSpecParseError(f"Doc spec validation failed: {e}") from e # Create doc spec instance - return DocumentTypeDefinition.from_dict(frontmatter, body, filepath) + return DocSpec.from_dict(frontmatter, body, filepath) def load_doc_specs_from_directory( doc_specs_dir: Path | str, -) -> dict[str, DocumentTypeDefinition]: +) -> dict[str, DocSpec]: """ Load all doc spec files from a directory. @@ -172,7 +176,7 @@ def load_doc_specs_from_directory( doc_specs_dir: Path to the doc_specs directory Returns: - Dictionary mapping doc spec filename (without extension) to DocumentTypeDefinition + Dictionary mapping doc spec filename (without extension) to DocSpec Raises: DocSpecParseError: If any doc spec file fails to parse @@ -185,7 +189,7 @@ def load_doc_specs_from_directory( if not doc_specs_dir.is_dir(): raise DocSpecParseError(f"Doc specs path is not a directory: {doc_specs_dir}") - doc_specs: dict[str, DocumentTypeDefinition] = {} + doc_specs: dict[str, DocSpec] = {} for doc_spec_file in doc_specs_dir.glob("*.md"): # Use stem (filename without extension) as key diff --git a/src/deepwork/core/generator.py b/src/deepwork/core/generator.py index 77eede57..59faa92f 100644 --- a/src/deepwork/core/generator.py +++ b/src/deepwork/core/generator.py @@ -7,8 +7,8 @@ from deepwork.core.adapters import AgentAdapter, SkillLifecycleHook from deepwork.core.doc_spec_parser import ( + DocSpec, DocSpecParseError, - DocumentTypeDefinition, parse_doc_spec_file, ) from deepwork.core.parser import JobDefinition, Step @@ -42,36 +42,34 @@ def __init__(self, templates_dir: Path | str | None = None): if not self.templates_dir.exists(): raise GeneratorError(f"Templates directory not found: {self.templates_dir}") - # Cache for loaded document types (keyed by absolute file path) - self._doc_type_cache: dict[Path, DocumentTypeDefinition] = {} + # Cache for loaded doc specs (keyed by absolute file path) + self._doc_spec_cache: dict[Path, DocSpec] = {} - def _load_document_type( - self, project_root: Path, document_type_path: str - ) -> DocumentTypeDefinition | None: + def _load_doc_spec(self, project_root: Path, doc_spec_path: str) -> DocSpec | None: """ - Load a document type definition by file path with caching. + Load a doc spec by file path with caching. Args: project_root: Path to project root - document_type_path: Relative path to doc spec file (e.g., ".deepwork/doc_specs/report.md") + doc_spec_path: Relative path to doc spec file (e.g., ".deepwork/doc_specs/report.md") Returns: - DocumentTypeDefinition if file exists and parses, None otherwise + DocSpec if file exists and parses, None otherwise """ - full_path = project_root / document_type_path - if full_path in self._doc_type_cache: - return self._doc_type_cache[full_path] + full_path = project_root / doc_spec_path + if full_path in self._doc_spec_cache: + return self._doc_spec_cache[full_path] if not full_path.exists(): return None try: - doc_type = parse_doc_spec_file(full_path) + doc_spec = parse_doc_spec_file(full_path) except DocSpecParseError: return None - self._doc_type_cache[full_path] = doc_type - return doc_type + self._doc_spec_cache[full_path] = doc_spec + return doc_spec def _get_jinja_env(self, adapter: AgentAdapter) -> Environment: """ @@ -164,7 +162,7 @@ def _build_step_context( step: Step to generate context for step_index: Index of step in job (0-based) adapter: Agent adapter for platform-specific hook name mapping - project_root: Optional project root for loading document types + project_root: Optional project root for loading doc specs Returns: Template context dictionary @@ -220,26 +218,26 @@ def _build_step_context( adapter.get_platform_hook_name(SkillLifecycleHook.AFTER_AGENT) or "Stop", [] ) - # Build rich outputs context with document type information + # Build rich outputs context with doc spec information outputs_context = [] for output in step.outputs: output_ctx: dict[str, Any] = { "file": output.file, - "has_document_type": output.has_document_type(), + "has_doc_spec": output.has_doc_spec(), } - if output.has_document_type() and output.document_type and project_root: - doc_type = self._load_document_type(project_root, output.document_type) - if doc_type: - output_ctx["document_type"] = { - "path": output.document_type, - "name": doc_type.name, - "description": doc_type.description, - "target_audience": doc_type.target_audience, + if output.has_doc_spec() and output.doc_spec and project_root: + doc_spec = self._load_doc_spec(project_root, output.doc_spec) + if doc_spec: + output_ctx["doc_spec"] = { + "path": output.doc_spec, + "name": doc_spec.name, + "description": doc_spec.description, + "target_audience": doc_spec.target_audience, "quality_criteria": [ {"name": c.name, "description": c.description} - for c in doc_type.quality_criteria + for c in doc_spec.quality_criteria ], - "example_document": doc_type.example_document, + "example_document": doc_spec.example_document, } outputs_context.append(output_ctx) diff --git a/src/deepwork/core/parser.py b/src/deepwork/core/parser.py index b37dc4a9..b4ae40ab 100644 --- a/src/deepwork/core/parser.py +++ b/src/deepwork/core/parser.py @@ -48,14 +48,14 @@ def from_dict(cls, data: dict[str, Any]) -> "StepInput": @dataclass class OutputSpec: - """Represents a step output specification, optionally with document type reference.""" + """Represents a step output specification, optionally with doc spec reference.""" file: str - document_type: str | None = None + doc_spec: str | None = None - def has_document_type(self) -> bool: - """Check if this output has a document type reference.""" - return self.document_type is not None + def has_doc_spec(self) -> bool: + """Check if this output has a doc spec reference.""" + return self.doc_spec is not None @classmethod def from_dict(cls, data: dict[str, Any] | str) -> "OutputSpec": @@ -64,13 +64,13 @@ def from_dict(cls, data: dict[str, Any] | str) -> "OutputSpec": Supports both formats: - String: "output.md" -> OutputSpec(file="output.md") - - Dict: {"file": "output.md", "document_type": ".deepwork/doc_specs/report.md"} + - Dict: {"file": "output.md", "doc_spec": ".deepwork/doc_specs/report.md"} """ if isinstance(data, str): return cls(file=data) return cls( file=data["file"], - document_type=data.get("document_type"), + doc_spec=data.get("doc_spec"), ) @@ -274,39 +274,39 @@ def validate_file_inputs(self) -> None: f"but '{inp.from_step}' is not in dependencies" ) - def validate_document_type_references(self, project_root: Path) -> None: + def validate_doc_spec_references(self, project_root: Path) -> None: """ - Validate that document type references in outputs point to existing files. + Validate that doc spec references in outputs point to existing files. Args: project_root: Path to the project root directory Raises: - ParseError: If document type references are invalid + ParseError: If doc spec references are invalid """ for step in self.steps: for output in step.outputs: - if output.has_document_type(): - doc_type_file = project_root / output.document_type - if not doc_type_file.exists(): + if output.has_doc_spec(): + doc_spec_file = project_root / output.doc_spec + if not doc_spec_file.exists(): raise ParseError( - f"Step '{step.id}' references non-existent document type " - f"'{output.document_type}'. Expected file at {doc_type_file}" + f"Step '{step.id}' references non-existent doc spec " + f"'{output.doc_spec}'. Expected file at {doc_spec_file}" ) - def get_document_type_references(self) -> list[str]: + def get_doc_spec_references(self) -> list[str]: """ - Get all unique document type file paths referenced in this job's outputs. + Get all unique doc spec file paths referenced in this job's outputs. Returns: List of doc spec file paths (e.g., ".deepwork/doc_specs/report.md") """ - doc_type_refs = set() + doc_spec_refs = set() for step in self.steps: for output in step.outputs: - if output.has_document_type() and output.document_type: - doc_type_refs.add(output.document_type) - return list(doc_type_refs) + if output.has_doc_spec() and output.doc_spec: + doc_spec_refs.add(output.doc_spec) + return list(doc_spec_refs) @classmethod def from_dict(cls, data: dict[str, Any], job_dir: Path) -> "JobDefinition": diff --git a/src/deepwork/schemas/job_schema.py b/src/deepwork/schemas/job_schema.py index 971acfc6..03bb598a 100644 --- a/src/deepwork/schemas/job_schema.py +++ b/src/deepwork/schemas/job_schema.py @@ -178,7 +178,7 @@ "minLength": 1, "description": "Output file path", }, - "document_type": { + "doc_spec": { "type": "string", "pattern": r"^\.deepwork/doc_specs/[a-z][a-z0-9_-]*\.md$", "description": "Path to doc spec file", diff --git a/src/deepwork/standard_jobs/deepwork_jobs/doc_specs/job_spec.md b/src/deepwork/standard_jobs/deepwork_jobs/doc_specs/job_spec.md index 07eaf198..46b32e7d 100644 --- a/src/deepwork/standard_jobs/deepwork_jobs/doc_specs/job_spec.md +++ b/src/deepwork/standard_jobs/deepwork_jobs/doc_specs/job_spec.md @@ -73,9 +73,9 @@ steps: outputs: - filename.md # simple filename - reports/analysis.md # path with directory - # With document type reference: + # With doc spec reference: - file: report.md - document_type: .deepwork/doc_specs/report_type.md + doc_spec: .deepwork/doc_specs/report_type.md dependencies: - previous_step_id # steps that must complete first ``` @@ -184,7 +184,7 @@ steps: from_step: research_competitors outputs: - file: positioning_report.md - document_type: .deepwork/doc_specs/positioning_report.md + doc_spec: .deepwork/doc_specs/positioning_report.md dependencies: - research_competitors ``` diff --git a/src/deepwork/standard_jobs/deepwork_jobs/job.yml b/src/deepwork/standard_jobs/deepwork_jobs/job.yml index 1c74d3f6..715fc65a 100644 --- a/src/deepwork/standard_jobs/deepwork_jobs/job.yml +++ b/src/deepwork/standard_jobs/deepwork_jobs/job.yml @@ -25,9 +25,9 @@ changelog: - version: "0.5.0" changes: "Standardized on 'ask structured questions' phrasing for user input; Updated quality criteria hooks to verify phrase usage; Added guidance in implement.md to use phrase in generated instructions" - version: "0.6.0" - changes: "Added Document Type Definition (doc spec) support; define.md now detects document-oriented workflows and guides doc spec creation; learn.md now identifies and applies doc spec-related improvements" + changes: "Added doc spec support; define.md now detects document-oriented workflows and guides doc spec creation; learn.md now identifies and applies doc spec-related improvements" - version: "0.7.0" - changes: "Added job.yml doc spec; define step now outputs job.yml with document_type reference for quality validation" + changes: "Added job.yml doc spec; define step now outputs job.yml with doc_spec reference for quality validation" - version: "0.8.0" changes: "Added review_job_spec step between define and implement for doc spec-based quality validation using sub-agent review" - version: "0.9.0" @@ -43,14 +43,14 @@ steps: description: "What complex task or workflow are you trying to accomplish?" outputs: - file: job.yml - document_type: .deepwork/doc_specs/job_spec.md + doc_spec: .deepwork/doc_specs/job_spec.md dependencies: [] quality_criteria: - "**User Understanding**: Did the agent fully understand the user's workflow by asking structured questions?" - "**Structured Questions Used**: Did the agent ask structured questions (using the AskUserQuestion tool) to gather user input?" - "**Document Detection**: For document-oriented workflows, did the agent detect patterns and offer doc spec creation?" - "**doc spec Created (if applicable)**: If a doc spec was needed, was it created in `.deepwork/doc_specs/[doc_spec_name].md` with proper quality criteria?" - - "**doc spec References**: Are document outputs properly linked to their doc specs using `{file, document_type}` format?" + - "**doc spec References**: Are document outputs properly linked to their doc specs using `{file, doc_spec}` format?" - "**Valid Against doc spec**: Does the job.yml conform to the job.yml doc spec quality criteria (valid identifier, semantic version, concise summary, rich description, complete steps, valid dependencies)?" - "**Clear Inputs/Outputs**: Does every step have clearly defined inputs and outputs?" - "**Logical Dependencies**: Do step dependencies make sense and avoid circular references?" @@ -68,7 +68,7 @@ steps: from_step: define outputs: - file: job.yml - document_type: .deepwork/doc_specs/job_spec.md + doc_spec: .deepwork/doc_specs/job_spec.md dependencies: - define quality_criteria: diff --git a/src/deepwork/standard_jobs/deepwork_jobs/steps/define.md b/src/deepwork/standard_jobs/deepwork_jobs/steps/define.md index 03528dd2..0d5533a1 100644 --- a/src/deepwork/standard_jobs/deepwork_jobs/steps/define.md +++ b/src/deepwork/standard_jobs/deepwork_jobs/steps/define.md @@ -35,19 +35,19 @@ Start by asking structured questions to understand what the user wants to accomp **Check for document-focused patterns** in the user's description: - Keywords: "report", "summary", "document", "create", "monthly", "quarterly", "for stakeholders", "for leadership" -- Final deliverable is a specific document type (e.g., "AWS spending report", "competitive analysis", "sprint summary") +- Final deliverable is a specific document (e.g., "AWS spending report", "competitive analysis", "sprint summary") - Recurring documents with consistent structure **If a document-oriented workflow is detected:** -1. Inform the user: "This workflow produces a specific document type. I recommend defining a Document Type Definition (doc spec) first to ensure consistent quality." +1. Inform the user: "This workflow produces a specific document type. I recommend defining a doc spec first to ensure consistent quality." 2. Ask structured questions to understand if they want to: - - Create a doc spec for the document type + - Create a doc spec for this document - Use an existing doc spec (if any exist in `.deepwork/doc_specs/`) - Skip doc spec and proceed with simple outputs -### Step 1.6: Define the Document Type Definition (if needed) +### Step 1.6: Define the Doc Spec (if needed) When creating a doc spec, gather the following information: @@ -121,7 +121,7 @@ When a step produces a document with a doc spec reference, use this format in jo ```yaml outputs: - file: reports/monthly_spending.md - document_type: .deepwork/doc_specs/monthly_aws_report.md + doc_spec: .deepwork/doc_specs/monthly_aws_report.md ``` The doc spec's quality criteria will automatically be included in the generated skill, ensuring consistent document quality. @@ -220,7 +220,7 @@ This creates: (Where `[job_name]` is the name of the NEW job you're creating, e.g., `competitive_research`) -**Document Type Definition**: See `.deepwork/doc_specs/job_spec.md` for the complete specification with quality criteria. +**Doc Spec**: See `.deepwork/doc_specs/job_spec.md` for the complete specification with quality criteria. **Template reference**: See `.deepwork/jobs/deepwork_jobs/templates/job.yml.template` for the standard structure. diff --git a/src/deepwork/standard_jobs/deepwork_jobs/steps/learn.md b/src/deepwork/standard_jobs/deepwork_jobs/steps/learn.md index c360fd19..2ba2703c 100644 --- a/src/deepwork/standard_jobs/deepwork_jobs/steps/learn.md +++ b/src/deepwork/standard_jobs/deepwork_jobs/steps/learn.md @@ -65,7 +65,7 @@ For each learning identified, determine if it is: - "Quality criteria should include checking for Y" - "Add example of correct output format" -**doc spec-Related** (should improve document type definitions): +**doc spec-Related** (should improve doc spec files): - Improvements to document quality criteria - Changes to document structure or format - Updated audience or frequency information @@ -166,7 +166,7 @@ Review all instruction files for the job and identify content that: If doc spec-related learnings were identified: 1. **Locate the doc spec file** - - Find doc spec references in job.yml outputs (look for `document_type: .deepwork/doc_specs/[doc_spec_name].md`) + - Find doc spec references in job.yml outputs (look for `doc_spec: .deepwork/doc_specs/[doc_spec_name].md`) - doc spec files are at `.deepwork/doc_specs/[doc_spec_name].md` 2. **Update quality_criteria array** diff --git a/src/deepwork/templates/claude/skill-job-step.md.jinja b/src/deepwork/templates/claude/skill-job-step.md.jinja index f830cc80..8464a116 100644 --- a/src/deepwork/templates/claude/skill-job-step.md.jinja +++ b/src/deepwork/templates/claude/skill-job-step.md.jinja @@ -151,26 +151,26 @@ Use branch format: `deepwork/{{ job_name }}-[instance]-YYYYMMDD` {% for output in outputs %} - `{{ output.file }}`{% if output.file.endswith('/') %} (directory){% endif %} -{% if output.has_document_type and output.document_type %} - **Document Type**: {{ output.document_type.name }} - > {{ output.document_type.description }} - **Definition**: `{{ output.document_type.path }}` -{% if output.document_type.target_audience %} - **Target Audience**: {{ output.document_type.target_audience }} -{% endif %} -{% if output.document_type.quality_criteria %} +{% if output.has_doc_spec and output.doc_spec %} + **Doc Spec**: {{ output.doc_spec.name }} + > {{ output.doc_spec.description }} + **Definition**: `{{ output.doc_spec.path }}` +{% if output.doc_spec.target_audience %} + **Target Audience**: {{ output.doc_spec.target_audience }} +{% endif %} +{% if output.doc_spec.quality_criteria %} **Quality Criteria**: -{% for criterion in output.document_type.quality_criteria %} +{% for criterion in output.doc_spec.quality_criteria %} {{ loop.index }}. **{{ criterion.name }}**: {{ criterion.description }} {% endfor %} {% endif %} -{% if output.document_type.example_document %} +{% if output.doc_spec.example_document %}
Example Document Structure ```markdown - {{ output.document_type.example_document | indent(2) }} + {{ output.doc_spec.example_document | indent(2) }} ```
diff --git a/src/deepwork/templates/gemini/skill-job-step.toml.jinja b/src/deepwork/templates/gemini/skill-job-step.toml.jinja index 747081df..946bec5c 100644 --- a/src/deepwork/templates/gemini/skill-job-step.toml.jinja +++ b/src/deepwork/templates/gemini/skill-job-step.toml.jinja @@ -108,16 +108,16 @@ Use branch format: `deepwork/{{ job_name }}-[instance]-YYYYMMDD` {% for output in outputs %} - `{{ output.file }}`{% if output.file.endswith('/') %} (directory){% endif %} -{% if output.has_document_type and output.document_type %} - **Document Type**: {{ output.document_type.name }} - > {{ output.document_type.description }} - **Definition**: `{{ output.document_type.path }}` -{% if output.document_type.target_audience %} - **Target Audience**: {{ output.document_type.target_audience }} +{% if output.has_doc_spec and output.doc_spec %} + **Doc Spec**: {{ output.doc_spec.name }} + > {{ output.doc_spec.description }} + **Definition**: `{{ output.doc_spec.path }}` +{% if output.doc_spec.target_audience %} + **Target Audience**: {{ output.doc_spec.target_audience }} {% endif %} -{% if output.document_type.quality_criteria %} +{% if output.doc_spec.quality_criteria %} **Quality Criteria**: -{% for criterion in output.document_type.quality_criteria %} +{% for criterion in output.doc_spec.quality_criteria %} {{ loop.index }}. **{{ criterion.name }}**: {{ criterion.description }} {% endfor %} {% endif %} diff --git a/tests/fixtures/jobs/job_with_doc_spec/job.yml b/tests/fixtures/jobs/job_with_doc_spec/job.yml new file mode 100644 index 00000000..b7a6b3ff --- /dev/null +++ b/tests/fixtures/jobs/job_with_doc_spec/job.yml @@ -0,0 +1,18 @@ +name: job_with_doc_spec +version: "1.0.0" +summary: "Job with doc spec output for testing" +description: | + A test job that produces a document with a doc spec reference. + +steps: + - id: generate_report + name: "Generate Report" + description: "Generate a report following the doc spec" + instructions_file: steps/generate_report.md + inputs: + - name: report_title + description: "Title for the report" + outputs: + - file: report.md + doc_spec: .deepwork/doc_specs/valid_report.md + dependencies: [] diff --git a/tests/fixtures/jobs/job_with_document_type/steps/generate_report.md b/tests/fixtures/jobs/job_with_doc_spec/steps/generate_report.md similarity index 100% rename from tests/fixtures/jobs/job_with_document_type/steps/generate_report.md rename to tests/fixtures/jobs/job_with_doc_spec/steps/generate_report.md diff --git a/tests/fixtures/jobs/job_with_document_type/job.yml b/tests/fixtures/jobs/job_with_document_type/job.yml deleted file mode 100644 index 577cc9d3..00000000 --- a/tests/fixtures/jobs/job_with_document_type/job.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: job_with_document_type -version: "1.0.0" -summary: "Job with document type output for testing" -description: | - A test job that produces a document with a document type reference. - -steps: - - id: generate_report - name: "Generate Report" - description: "Generate a report following the document type specification" - instructions_file: steps/generate_report.md - inputs: - - name: report_title - description: "Title for the report" - outputs: - - file: report.md - document_type: .deepwork/doc_specs/valid_report.md - dependencies: [] diff --git a/tests/unit/test_generator.py b/tests/unit/test_generator.py index 34827201..bce2a5f1 100644 --- a/tests/unit/test_generator.py +++ b/tests/unit/test_generator.py @@ -6,7 +6,7 @@ from deepwork.core.adapters import ClaudeAdapter from deepwork.core.generator import GeneratorError, SkillGenerator -from deepwork.core.parser import parse_job_definition +from deepwork.core.parser import Step, parse_job_definition class TestSkillGenerator: @@ -140,7 +140,6 @@ def test_generate_step_skill_raises_for_missing_step( adapter = ClaudeAdapter() # Create a fake step not in the job - from deepwork.core.parser import Step fake_step = Step( id="fake", @@ -267,3 +266,190 @@ def test_generate_all_skills_with_exposed_steps( actual_dirs = [p.parent.name for p in skill_paths] assert actual_dirs == expected_dirs assert all(p.name == "SKILL.md" for p in skill_paths) + + +class TestDocSpecIntegration: + """Tests for doc spec integration in skill generation.""" + + def test_load_doc_spec_returns_parsed_spec(self, fixtures_dir: Path) -> None: + """Test that _load_doc_spec loads and parses doc spec files.""" + generator = SkillGenerator() + + # Load the valid_report doc spec from fixtures + doc_spec = generator._load_doc_spec(fixtures_dir, "doc_specs/valid_report.md") + + assert doc_spec is not None + assert doc_spec.name == "Monthly Report" + assert doc_spec.description == "A monthly summary report" + assert doc_spec.target_audience == "Team leads" + assert len(doc_spec.quality_criteria) == 2 + assert doc_spec.quality_criteria[0].name == "Summary" + + def test_load_doc_spec_caches_result(self, fixtures_dir: Path) -> None: + """Test that doc specs are cached after first load.""" + generator = SkillGenerator() + + # Load same doc spec twice + doc_spec1 = generator._load_doc_spec(fixtures_dir, "doc_specs/valid_report.md") + doc_spec2 = generator._load_doc_spec(fixtures_dir, "doc_specs/valid_report.md") + + # Should be the same cached instance + assert doc_spec1 is doc_spec2 + # Cache should have exactly one entry + assert len(generator._doc_spec_cache) == 1 + + def test_load_doc_spec_returns_none_for_missing_file(self, temp_dir: Path) -> None: + """Test that _load_doc_spec returns None for non-existent file.""" + generator = SkillGenerator() + + result = generator._load_doc_spec(temp_dir, "nonexistent.md") + + assert result is None + + def test_load_doc_spec_returns_none_for_invalid_spec(self, temp_dir: Path) -> None: + """Test that _load_doc_spec returns None for invalid doc spec file.""" + generator = SkillGenerator() + + # Create an invalid doc spec file (missing required fields) + invalid_spec = temp_dir / "invalid.md" + invalid_spec.write_text("""--- +name: "Test" +--- +Body content +""") + + result = generator._load_doc_spec(temp_dir, "invalid.md") + + assert result is None + + def test_generate_step_skill_with_doc_spec(self, fixtures_dir: Path, temp_dir: Path) -> None: + """Test generating skill for step with doc spec-referenced output.""" + # Set up the directory structure so the doc spec can be found + doc_specs_dir = temp_dir / ".deepwork" / "doc_specs" + doc_specs_dir.mkdir(parents=True) + + # Copy the valid_report.md fixture to the expected location + source_doc_spec = fixtures_dir / "doc_specs" / "valid_report.md" + target_doc_spec = doc_specs_dir / "valid_report.md" + target_doc_spec.write_text(source_doc_spec.read_text()) + + # Parse the job with doc spec + job_dir = fixtures_dir / "jobs" / "job_with_doc_spec" + job = parse_job_definition(job_dir) + + generator = SkillGenerator() + adapter = ClaudeAdapter() + + # Generate skill with project_root set to temp_dir so it finds doc specs + skill_path = generator.generate_step_skill( + job, job.steps[0], adapter, temp_dir, project_root=temp_dir + ) + + assert skill_path.exists() + content = skill_path.read_text() + + # Verify doc spec info is injected into the skill + assert "Doc Spec" in content + assert "Monthly Report" in content + assert "A monthly summary report" in content + assert "Target Audience" in content + assert "Team leads" in content + assert "Quality Criteria" in content + assert "Summary" in content + assert "Must include executive summary" in content + + def test_generate_step_skill_without_doc_spec(self, fixtures_dir: Path, temp_dir: Path) -> None: + """Test generating skill for step without doc spec reference.""" + job_dir = fixtures_dir / "jobs" / "simple_job" + job = parse_job_definition(job_dir) + + generator = SkillGenerator() + adapter = ClaudeAdapter() + + skill_path = generator.generate_step_skill(job, job.steps[0], adapter, temp_dir) + + content = skill_path.read_text() + # Should not have doc spec section + assert "Doc Spec:" not in content + + def test_generate_step_skill_with_missing_doc_spec_file( + self, fixtures_dir: Path, temp_dir: Path + ) -> None: + """Test generating skill when doc spec file doesn't exist.""" + # Parse the job with doc spec but don't create the doc spec file + job_dir = fixtures_dir / "jobs" / "job_with_doc_spec" + job = parse_job_definition(job_dir) + + generator = SkillGenerator() + adapter = ClaudeAdapter() + + # Generate skill without the doc spec file present + # This should work but not include doc spec info + skill_path = generator.generate_step_skill( + job, job.steps[0], adapter, temp_dir, project_root=temp_dir + ) + + assert skill_path.exists() + content = skill_path.read_text() + + # Should still generate the skill, just without doc spec details + assert "job_with_doc_spec.generate_report" in content + # Doc spec section should not appear since file is missing + assert "Monthly Report" not in content + + def test_build_step_context_includes_doc_spec_info( + self, fixtures_dir: Path, temp_dir: Path + ) -> None: + """Test that _build_step_context includes doc spec info in outputs.""" + # Set up the directory structure + doc_specs_dir = temp_dir / ".deepwork" / "doc_specs" + doc_specs_dir.mkdir(parents=True) + + source_doc_spec = fixtures_dir / "doc_specs" / "valid_report.md" + target_doc_spec = doc_specs_dir / "valid_report.md" + target_doc_spec.write_text(source_doc_spec.read_text()) + + job_dir = fixtures_dir / "jobs" / "job_with_doc_spec" + job = parse_job_definition(job_dir) + + generator = SkillGenerator() + adapter = ClaudeAdapter() + + context = generator._build_step_context( + job, job.steps[0], 0, adapter, project_root=temp_dir + ) + + # Check outputs context has doc spec info + assert "outputs" in context + assert len(context["outputs"]) == 1 + + output_ctx = context["outputs"][0] + assert output_ctx["file"] == "report.md" + assert output_ctx["has_doc_spec"] is True + assert "doc_spec" in output_ctx + + doc_spec_ctx = output_ctx["doc_spec"] + assert doc_spec_ctx["name"] == "Monthly Report" + assert doc_spec_ctx["description"] == "A monthly summary report" + assert doc_spec_ctx["target_audience"] == "Team leads" + assert len(doc_spec_ctx["quality_criteria"]) == 2 + assert doc_spec_ctx["quality_criteria"][0]["name"] == "Summary" + assert "example_document" in doc_spec_ctx + + def test_build_step_context_without_project_root( + self, fixtures_dir: Path, temp_dir: Path + ) -> None: + """Test that _build_step_context handles missing project_root.""" + job_dir = fixtures_dir / "jobs" / "job_with_doc_spec" + job = parse_job_definition(job_dir) + + generator = SkillGenerator() + adapter = ClaudeAdapter() + + # Build context without project_root - should still work but no doc spec + context = generator._build_step_context(job, job.steps[0], 0, adapter) + + output_ctx = context["outputs"][0] + assert output_ctx["has_doc_spec"] is True # Job still declares it + # But doc_spec info won't be loaded since no project_root + assert "doc_spec" not in output_ctx diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py index 87f10db3..aa1eae7e 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/test_parser.py @@ -54,46 +54,46 @@ class TestOutputSpec: """Tests for OutputSpec dataclass.""" def test_simple_output(self) -> None: - """Test simple output without document type.""" + """Test simple output without doc spec.""" output = OutputSpec(file="output.md") assert output.file == "output.md" - assert output.document_type is None - assert not output.has_document_type() + assert output.doc_spec is None + assert not output.has_doc_spec() - def test_output_with_document_type(self) -> None: - """Test output with document type reference.""" - output = OutputSpec(file="report.md", document_type=".deepwork/doc_specs/monthly_report.md") + def test_output_with_doc_spec(self) -> None: + """Test output with doc spec reference.""" + output = OutputSpec(file="report.md", doc_spec=".deepwork/doc_specs/monthly_report.md") assert output.file == "report.md" - assert output.document_type == ".deepwork/doc_specs/monthly_report.md" - assert output.has_document_type() + assert output.doc_spec == ".deepwork/doc_specs/monthly_report.md" + assert output.has_doc_spec() def test_from_dict_string(self) -> None: """Test creating output from string.""" output = OutputSpec.from_dict("output.md") assert output.file == "output.md" - assert output.document_type is None - assert not output.has_document_type() + assert output.doc_spec is None + assert not output.has_doc_spec() def test_from_dict_simple_object(self) -> None: - """Test creating output from dict without document type.""" + """Test creating output from dict without doc spec.""" data = {"file": "output.md"} output = OutputSpec.from_dict(data) assert output.file == "output.md" - assert output.document_type is None - assert not output.has_document_type() + assert output.doc_spec is None + assert not output.has_doc_spec() - def test_from_dict_with_document_type(self) -> None: - """Test creating output from dict with document type.""" - data = {"file": "report.md", "document_type": ".deepwork/doc_specs/monthly_report.md"} + def test_from_dict_with_doc_spec(self) -> None: + """Test creating output from dict with doc spec.""" + data = {"file": "report.md", "doc_spec": ".deepwork/doc_specs/monthly_report.md"} output = OutputSpec.from_dict(data) assert output.file == "report.md" - assert output.document_type == ".deepwork/doc_specs/monthly_report.md" - assert output.has_document_type() + assert output.doc_spec == ".deepwork/doc_specs/monthly_report.md" + assert output.has_doc_spec() class TestStep: @@ -116,12 +116,12 @@ def test_from_dict_minimal(self) -> None: assert step.instructions_file == "steps/step1.md" assert len(step.outputs) == 1 assert step.outputs[0].file == "output.md" - assert not step.outputs[0].has_document_type() + assert not step.outputs[0].has_doc_spec() assert step.inputs == [] assert step.dependencies == [] - def test_from_dict_with_document_type_output(self) -> None: - """Test creating step with document type-referenced output.""" + def test_from_dict_with_doc_spec_output(self) -> None: + """Test creating step with doc spec-referenced output.""" data = { "id": "step1", "name": "Step 1", @@ -129,17 +129,17 @@ def test_from_dict_with_document_type_output(self) -> None: "instructions_file": "steps/step1.md", "outputs": [ "simple_output.md", - {"file": "report.md", "document_type": ".deepwork/doc_specs/monthly_report.md"}, + {"file": "report.md", "doc_spec": ".deepwork/doc_specs/monthly_report.md"}, ], } step = Step.from_dict(data) assert len(step.outputs) == 2 assert step.outputs[0].file == "simple_output.md" - assert not step.outputs[0].has_document_type() + assert not step.outputs[0].has_doc_spec() assert step.outputs[1].file == "report.md" - assert step.outputs[1].document_type == ".deepwork/doc_specs/monthly_report.md" - assert step.outputs[1].has_document_type() + assert step.outputs[1].doc_spec == ".deepwork/doc_specs/monthly_report.md" + assert step.outputs[1].has_doc_spec() def test_from_dict_with_inputs(self) -> None: """Test creating step with inputs.""" diff --git a/uv.lock b/uv.lock index cd4110a3..564a99f6 100644 --- a/uv.lock +++ b/uv.lock @@ -126,7 +126,7 @@ toml = [ [[package]] name = "deepwork" -version = "0.4.0" +version = "0.5.0" source = { editable = "." } dependencies = [ { name = "click" },