diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f7239cb..83d757b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +## [0.4.2] - 2026-01-24 + +### Changed +- Added closing tag comments to jinja templates for improved readability + +## [0.4.1] - 2026-01-23 + +### Changed +- Disabled prompt-based stop hooks in Claude templates due to upstream bug ([#20221](https://github.com/anthropics/claude-code/issues/20221)) +- Quality validation now uses sub-agent review pattern instead of prompt hooks + ## [0.4.0] - 2026-01-23 ### Added @@ -178,7 +189,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Initial version. -[Unreleased]: https://github.com/anthropics/deepwork/compare/0.4.0...HEAD +[Unreleased]: https://github.com/anthropics/deepwork/compare/0.4.2...HEAD +[0.4.2]: https://github.com/anthropics/deepwork/compare/0.4.1...0.4.2 +[0.4.1]: https://github.com/anthropics/deepwork/compare/0.4.0...0.4.1 [0.4.0]: https://github.com/anthropics/deepwork/compare/0.3.1...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/platforms/claude/hooks.md b/doc/platforms/claude/hooks.md index 6dbda020..516324af 100644 --- a/doc/platforms/claude/hooks.md +++ b/doc/platforms/claude/hooks.md @@ -1,5 +1,5 @@ @@ -9,6 +9,24 @@ Source: https://code.claude.com/docs/en/hooks Claude Code hooks are scripts that execute at specific points in Claude's workflow. They enable intercepting and controlling tool execution, validating user input, and performing custom actions. +## Known Issues + +### Prompt-Based Stop Hooks Not Working + +**IMPORTANT**: Prompt-based hooks (`type: prompt`) for Stop and SubagentStop events do not currently work properly. + +Reference: https://github.com/anthropics/claude-code/issues/20221 + +**Impact**: +- Quality validation loops using prompt hooks will not block the agent as expected +- The agent may stop without the prompt hook properly evaluating the response + +**Workaround**: +- Use command-type hooks (`type: command`) for Stop events if automated validation is needed +- Alternatively, include explicit instructions in the command content directing the agent to use a sub-agent (e.g., Haiku) to review work against quality criteria before completing + +**Future**: If this issue is resolved, prompt-based stop hooks can be re-enabled. Check the GitHub issue for updates. + ## Configuration Hooks are configured in JSON settings files with this precedence (lowest to highest): diff --git a/doc/platforms/claude/hooks_system.md b/doc/platforms/claude/hooks_system.md index eb95fcc5..66a4cbd5 100644 --- a/doc/platforms/claude/hooks_system.md +++ b/doc/platforms/claude/hooks_system.md @@ -1,5 +1,5 @@ @@ -12,6 +12,28 @@ Claude Code supports **command-level hooks** within slash command definitions. T Hooks in command definitions allow per-command quality validation, input preprocessing, and output verification. These hooks are defined in the YAML frontmatter of markdown command files. +## Known Issues + +### Prompt-Based Stop Hooks Not Working + +**IMPORTANT**: Prompt-based hooks (`type: prompt`) for Stop and SubagentStop events do not currently work properly. + +Reference: https://github.com/anthropics/claude-code/issues/20221 + +**Impact**: +- Quality validation loops using prompt hooks will not block the agent as expected +- The agent may finish without the prompt hook properly evaluating the response + +**Workaround**: +For quality validation, instead of using prompt-based stop hooks, include explicit instructions in the command content directing the agent to: +1. Spawn a sub-agent (e.g., using Haiku model) to review work against quality criteria +2. Fix any valid issues raised by the sub-agent +3. Have the sub-agent review again until all valid feedback is handled + +**Command-type hooks still work**: If you need automated validation via Stop hooks, use `type: command` hooks that run shell scripts. + +**Future**: If this issue is resolved, prompt-based stop hooks can be re-enabled. Check the GitHub issue for updates. + ## Command-Level Hook Support Claude Code slash commands (defined in `.md` files) support hooks in the YAML frontmatter: @@ -60,15 +82,16 @@ Triggered when the main agent finishes responding. Use for: - Output verification - Completion criteria checking +**NOTE**: Prompt-based stop hooks (`type: prompt`) do not currently work. See [Known Issues](#prompt-based-stop-hooks-not-working) above. + +For command-type hooks: + ```yaml hooks: Stop: - hooks: - - type: prompt - prompt: | - Verify all acceptance criteria are met. - If met, let the agent finish. - If not met, have the agent keep working until all criteria are satisfied. + - type: command + command: ".deepwork/jobs/my_job/hooks/validate.sh" ``` **Blocking behavior**: Return JSON with `{"decision": "block", "reason": "..."}` or exit code 2 with stderr message. @@ -182,47 +205,56 @@ hooks: ### Prompt Hooks -Use LLM evaluation: +**NOTE**: Prompt hooks for Stop/SubagentStop events do not currently work. See [Known Issues](#prompt-based-stop-hooks-not-working). + +Prompt hooks may work for other event types (e.g., PreToolUse, UserPromptSubmit), but this has not been fully tested. ```yaml +# Example for non-Stop events (untested) hooks: - Stop: + UserPromptSubmit: - hooks: - type: prompt prompt: | - Evaluate whether the response meets all criteria. - If all criteria are met, let the agent finish. - If criteria are not met, have the agent keep working. + Validate the user's prompt before processing. ``` ## Quality Validation Loop Pattern -Claude Code's Stop hooks enable iterative quality validation: +**NOTE**: This pattern using prompt-based Stop hooks does not currently work. See [Known Issues](#prompt-based-stop-hooks-not-working). + +### Alternative: Sub-Agent Review + +Instead of relying on prompt hooks, include explicit instructions in your command content: + +```markdown +## Quality Validation + +Before completing this step, you MUST have your work reviewed. -1. Agent completes its response -2. Stop hook evaluates quality criteria -3. If criteria not met, agent continues working -4. Loop repeats until criteria are satisfied +**Quality Criteria**: +1. All tests pass +2. Code follows style guide +3. Documentation updated + +**Review Process**: +1. Once you believe your work is complete, spawn a sub-agent using Haiku to review your work against the quality criteria above +2. The sub-agent should examine your outputs and verify each criterion is met +3. If the sub-agent identifies valid issues, fix them +4. Have the sub-agent review again until all valid feedback has been addressed +5. Only mark the step complete when the sub-agent confirms all criteria are satisfied +``` -This pattern is unique to Claude Code among DeepWork-supported platforms. +### Command-Type Hooks (Still Work) -### Implementation Example +If you need automated validation, use command-type hooks that run shell scripts: ```yaml hooks: Stop: - hooks: - - type: prompt - prompt: | - ## Quality Criteria - 1. All tests pass - 2. Code follows style guide - 3. Documentation updated - - Review the conversation. If ALL criteria met and - tag present, let the agent finish. - - Otherwise, have the agent keep working until all criteria are satisfied. + - type: command + command: ".deepwork/jobs/my_job/hooks/validate.sh" ``` ## Comparison with Other Platforms @@ -231,18 +263,22 @@ hooks: |---------|-------------|------------| | Command-level hooks | Yes | No | | Global hooks | Yes | Yes | -| Hook types | `command`, `prompt` | `command` only | -| Quality validation loops | Yes (Stop hooks) | No (workarounds only) | +| Hook types | `command`, `prompt`* | `command` only | +| Quality validation loops | Via sub-agent review** | No (workarounds only) | | Per-command customization | Full | None | +*Prompt hooks for Stop/SubagentStop events do not currently work (see [Known Issues](#prompt-based-stop-hooks-not-working)) +**Prompt-based Stop hooks are not working; use sub-agent review pattern instead + ## Implications for DeepWork -Since Claude Code fully supports command-level hooks: +Due to the prompt-based Stop hooks not working: -1. **`stop_hooks` are fully supported** - Quality validation loops work as designed -2. **Job definitions** can use `hooks.after_agent` (maps to Stop) -3. **Platform adapter** implements all hook mappings -4. **Command templates** generate YAML frontmatter with hook configurations +1. **Prompt-based `stop_hooks` are NOT generated** - Templates filter out prompt hooks for Stop events +2. **Quality validation** uses explicit sub-agent review instructions in command content +3. **Command-type hooks still work** - Script-based hooks for Stop events are generated as expected +4. **Job definitions** can still use `hooks.after_agent` for script hooks (maps to Stop) +5. **Platform adapter** implements all hook mappings (but prompt Stop hooks are skipped in templates) ## Environment Variables @@ -256,7 +292,7 @@ Available to hook scripts: ## Limitations -1. **Prompt hooks are evaluated by the model** - May have latency +1. **Prompt hooks for Stop/SubagentStop do not work** - See [Known Issues](#prompt-based-stop-hooks-not-working) 2. **Timeout default is 60 seconds** - Long-running hooks may fail 3. **Multiple hooks run in parallel** - Cannot depend on order 4. **Transcript path is JSONL** - Requires line-by-line parsing diff --git a/pyproject.toml b/pyproject.toml index d84e3edb..98bb5f81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "deepwork" -version = "0.4.0" +version = "0.4.2" 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/templates/claude/AGENTS.md b/src/deepwork/templates/claude/AGENTS.md index daae0cb9..7c4eb976 100644 --- a/src/deepwork/templates/claude/AGENTS.md +++ b/src/deepwork/templates/claude/AGENTS.md @@ -2,7 +2,33 @@ Notes for AI agents working on Claude Code jinja templates. -## Prompt-Based Hooks +## Prompt-Based Stop Hooks - NOT WORKING + +**IMPORTANT: Prompt-based stop hooks (`type: prompt` for Stop/SubagentStop events) do not currently work properly in Claude Code.** + +Reference: https://github.com/anthropics/claude-code/issues/20221 + +### What This Means + +- Do NOT use `type: prompt` hooks for Stop or SubagentStop events in templates +- The quality validation loop pattern that relies on prompt hooks will not function as expected +- Instead, instruct agents to use a sub-agent (e.g., Haiku) to review their work against quality criteria + +### Workaround + +Instead of prompt-based stop hooks, templates should include explicit instructions in the command content directing the agent to: + +1. Have a sub-agent using Haiku review the work against the specified quality criteria +2. Fix any valid issues raised by the sub-agent +3. Have the sub-agent review again until all valid feedback is handled + +### Future Reversal + +If prompt-based stop hooks are fixed in Claude Code (check the issue above for updates), this guidance should be reversed and prompt hooks can be re-enabled in templates. + +## Historical Context (Prompt Hooks - When They Work) + +The following guidance applies IF prompt hooks start working again: When writing prompt-based hooks (e.g., Stop hooks with `type: prompt`): diff --git a/src/deepwork/templates/claude/skill-job-step.md.jinja b/src/deepwork/templates/claude/skill-job-step.md.jinja index 5c29c836..610f6cd8 100644 --- a/src/deepwork/templates/claude/skill-job-step.md.jinja +++ b/src/deepwork/templates/claude/skill-job-step.md.jinja @@ -40,72 +40,51 @@ Template Variables: --- name: {{ job_name }}.{{ step_id }} description: "{{ step_description }}" -{% if not exposed %} +{%- if not exposed %} user-invocable: false -{% endif %} -{% if quality_criteria or hooks %} +{%- endif %}{#- if not exposed #} +{#- + NOTE: Prompt-based stop hooks do not currently work in Claude Code. + See: https://github.com/anthropics/claude-code/issues/20221 + Only command/script hooks are generated here. Prompt hooks are filtered out. + Quality validation is handled via sub-agent review in the instructions section. +#} +{%- if hooks -%} +{%- set has_command_hooks = namespace(value=false) -%} +{%- for event_name, event_hooks in hooks.items() -%} +{%- for hook in event_hooks -%} +{%- if hook.type == "script" -%} +{%- set has_command_hooks.value = true -%} +{%- endif -%}{#- if hook.type == "script" #} +{%- endfor -%}{#- for hook in event_hooks #} +{%- endfor -%}{#- for event_name, event_hooks in hooks.items() #} +{%- if has_command_hooks.value %} hooks: -{% if quality_criteria %} -{% for event_name in ["Stop", "SubagentStop"] %} - {{ event_name }}: - - hooks: - - type: prompt - prompt: | - You must evaluate whether Claude has met all the below quality criteria for the request. - - ## Quality Criteria - -{% for criterion in quality_criteria %} - {{ loop.index }}. {{ criterion }} -{% endfor %} - - ## Instructions - - Review the conversation and determine if ALL quality criteria above have been satisfied. - Look for evidence that each criterion has been addressed. - - If the agent has included `✓ Quality Criteria Met` in their response OR - all criteria appear to be met, let the agent finish. - - If criteria are NOT met AND the promise tag is missing, have the agent keep working - until all criteria are satisfied. -{% endfor %} -{% endif %} -{% for event_name, event_hooks in hooks.items() %} -{% if not (event_name == "Stop" and quality_criteria) and not (event_name == "SubagentStop" and "Stop" in hooks) %} -{# For Stop events, generate both Stop and SubagentStop blocks #} -{% if event_name == "Stop" %} -{% for stop_event in ["Stop", "SubagentStop"] %} +{%- for event_name, event_hooks in hooks.items() %} +{%- set script_hooks = event_hooks | selectattr("type", "equalto", "script") | list %} +{%- if script_hooks -%} +{#- For Stop events, generate both Stop and SubagentStop blocks #} +{%- if event_name == "Stop" %} +{%- for stop_event in ["Stop", "SubagentStop"] %} {{ stop_event }}: - hooks: -{% for hook in event_hooks %} -{% if hook.type == "script" %} +{%- for hook in script_hooks %} - type: command command: ".deepwork/jobs/{{ job_name }}/{{ hook.path }}" -{% else %} - - type: prompt - prompt: | - {{ hook.content | indent(12) }} -{% endif %} -{% endfor %} -{% endfor %} -{% else %} +{%- endfor %}{#- for hook in script_hooks #} +{%- endfor %}{#- for stop_event in ["Stop", "SubagentStop"] #} +{%- elif event_name != "SubagentStop" or "Stop" not in hooks %} {{ event_name }}: - hooks: -{% for hook in event_hooks %} -{% if hook.type == "script" %} +{%- for hook in script_hooks %} - type: command command: ".deepwork/jobs/{{ job_name }}/{{ hook.path }}" -{% else %} - - type: prompt - prompt: | - {{ hook.content | indent(12) }} -{% endif %} -{% endfor %} -{% endif %} -{% endif %} -{% endfor %} -{% endif %} +{%- endfor %}{#- for hook in script_hooks #} +{%- endif %}{#- if event_name == "Stop" #} +{%- endif %}{#- if script_hooks #} +{%- endfor %}{#- for event_name, event_hooks in hooks.items() #} +{%- endif %}{#- if has_command_hooks.value #} +{%- endif %}{#- if hooks #} --- # {{ job_name }}.{{ step_id }} @@ -114,7 +93,7 @@ hooks: **Standalone skill** - can be run anytime {% else %} **Step {{ step_number }}/{{ total_steps }}** in **{{ job_name }}** workflow -{% endif %} +{% endif %}{#- if is_standalone #} > {{ job_summary }} @@ -124,8 +103,8 @@ hooks: Before proceeding, confirm these steps are complete: {% for dep in dependencies %} - `/{{ job_name }}.{{ dep }}` -{% endfor %} -{% endif %} +{% endfor %}{#- for dep in dependencies #} +{% endif %}{#- if dependencies #} ## Instructions @@ -137,7 +116,7 @@ Before proceeding, confirm these steps are complete: ### Job Context {{ job_description }} -{% endif %} +{% endif %}{#- if job_description #} {% if user_inputs or file_inputs %} ## Required Inputs @@ -146,16 +125,16 @@ Before proceeding, confirm these steps are complete: **User Parameters** - Gather from user before starting: {% for input in user_inputs %} - **{{ input.name }}**: {{ input.description }} -{% endfor %} -{% endif %} +{% endfor %}{#- for input in user_inputs #} +{% endif %}{#- if user_inputs #} {% if file_inputs %} **Files from Previous Steps** - Read these first: {% for input in file_inputs %} - `{{ input.file }}` (from `{{ input.from_step }}`) -{% endfor %} -{% endif %} -{% endif %} +{% endfor %}{#- for input in file_inputs #} +{% endif %}{#- if file_inputs #} +{% endif %}{#- if user_inputs or file_inputs #} ## Work Branch @@ -177,13 +156,13 @@ Use branch format: `deepwork/{{ job_name }}-[instance]-YYYYMMDD` **Definition**: `{{ output.doc_spec.path }}` {% if output.doc_spec.target_audience %} **Target Audience**: {{ output.doc_spec.target_audience }} -{% endif %} +{% endif %}{#- if output.doc_spec.target_audience #} {% if output.doc_spec.quality_criteria %} **Quality Criteria**: {% for criterion in output.doc_spec.quality_criteria %} {{ loop.index }}. **{{ criterion.name }}**: {{ criterion.description }} -{% endfor %} -{% endif %} +{% endfor %}{#- for criterion in output.doc_spec.quality_criteria #} +{% endif %}{#- if output.doc_spec.quality_criteria #} {% if output.doc_spec.example_document %}
@@ -194,12 +173,12 @@ Use branch format: `deepwork/{{ job_name }}-[instance]-YYYYMMDD` ```
-{% endif %} -{% endif %} -{% endfor %} +{% endif %}{#- if output.doc_spec.example_document #} +{% endif %}{#- if output.has_doc_spec and output.doc_spec #} +{% endfor %}{#- for output in outputs #} {% else %} No specific file outputs required. -{% endif %} +{% endif %}{#- if outputs #} ## Guardrails @@ -208,27 +187,32 @@ No specific file outputs required. - Do NOT proceed without required inputs; ask the user if any are missing - Do NOT modify files outside the scope of this step's defined outputs -{% if quality_criteria or stop_hooks %} +{% if quality_criteria %} ## Quality Validation -Stop hooks will automatically validate your work. The loop continues until all criteria pass. +**Before completing this step, you MUST have your work reviewed against the quality criteria below.** + +Use a sub-agent (Haiku model) to review your work against these criteria: -{% if quality_criteria %} **Criteria (all must be satisfied)**: -{% for criterion in quality_criteria %} +{% for criterion in quality_criteria -%} {{ loop.index }}. {{ criterion }} -{% endfor %} -{% endif %} - -{% for hook in stop_hooks %} -{% if hook.type == "script" %} +{% endfor %}{#- for criterion in quality_criteria #} +**Review Process**: +1. Once you believe your work is complete, spawn a sub-agent using Haiku to review your work against the quality criteria above +2. The sub-agent should examine your outputs and verify each criterion is met +3. If the sub-agent identifies valid issues, fix them +4. Have the sub-agent review again until all valid feedback has been addressed +5. Only mark the step complete when the sub-agent confirms all criteria are satisfied + +{% endif %}{#- if quality_criteria #} +{% if stop_hooks -%} +{% for hook in stop_hooks -%} +{% if hook.type == "script" -%} **Validation script**: `.deepwork/jobs/{{ job_name }}/{{ hook.path }}` (runs automatically) -{% endif %} -{% endfor %} - -**To complete**: Include `✓ Quality Criteria Met` in your final response only after verifying ALL criteria are satisfied. - -{% endif %} +{% endif -%}{#- if hook.type == "script" #} +{% endfor %}{#- for hook in stop_hooks #} +{% endif %}{#- if stop_hooks #} ## On Completion {% if is_standalone %} @@ -243,8 +227,8 @@ This standalone skill can be re-run anytime. 3. **Continue workflow**: Use Skill tool to invoke `/{{ job_name }}.{{ next_step }}` {% else %} 3. **Workflow complete**: All steps finished. Consider creating a PR to merge the work branch. -{% endif %} -{% endif %} +{% endif %}{#- if next_step #} +{% endif %}{#- if is_standalone #} --- diff --git a/tests/unit/test_stop_hooks.py b/tests/unit/test_stop_hooks.py index f2837d90..96cdeb5b 100644 --- a/tests/unit/test_stop_hooks.py +++ b/tests/unit/test_stop_hooks.py @@ -743,13 +743,17 @@ def job_with_stop_hooks(self, tmp_path: Path) -> JobDefinition: job_dir=job_dir, ) - def test_template_generates_both_stop_and_subagent_stop_for_quality_criteria( + def test_template_generates_subagent_review_for_quality_criteria( self, full_generator: SkillGenerator, job_with_quality_criteria: JobDefinition, tmp_path: Path, ) -> None: - """Test that template generates both Stop and SubagentStop hooks for quality_criteria.""" + """Test that template generates sub-agent review instructions for quality_criteria. + + NOTE: Prompt-based stop hooks don't work in Claude Code (issue #20221). + Instead, quality_criteria generates sub-agent review instructions in content. + """ adapter = ClaudeAdapter() skill_path = full_generator.generate_step_skill( job_with_quality_criteria, @@ -760,28 +764,26 @@ def test_template_generates_both_stop_and_subagent_stop_for_quality_criteria( content = skill_path.read_text() - # Both Stop and SubagentStop should be in the generated file - assert "Stop:" in content, "Stop hook should be in generated skill" - assert "SubagentStop:" in content, "SubagentStop hook should be in generated skill" - - # Both should contain the quality criteria prompt - lines = content.split("\n") - stop_found = False - subagent_stop_found = False - for _i, line in enumerate(lines): - if line.strip().startswith("Stop:"): - stop_found = True - if line.strip().startswith("SubagentStop:"): - subagent_stop_found = True - - assert stop_found and subagent_stop_found, ( - f"Both Stop and SubagentStop should be generated. Content:\n{content[:1000]}" + # Should NOT generate Stop/SubagentStop hooks (prompt hooks disabled) + assert "Stop:" not in content, "Prompt-based Stop hooks should not be generated" + assert "SubagentStop:" not in content, ( + "Prompt-based SubagentStop hooks should not be generated" ) - def test_template_generates_both_stop_and_subagent_stop_for_custom_hooks( + # Should generate sub-agent review instructions in content + assert "## Quality Validation" in content, "Quality Validation section should be generated" + assert "sub-agent" in content.lower(), "Sub-agent review instructions should be present" + assert "Criterion 1 is met" in content, "Quality criteria should be in content" + assert "Criterion 2 is verified" in content, "Quality criteria should be in content" + + def test_template_does_not_generate_prompt_hooks( self, full_generator: SkillGenerator, job_with_stop_hooks: JobDefinition, tmp_path: Path ) -> None: - """Test that template generates both Stop and SubagentStop for custom stop hooks.""" + """Test that template does NOT generate prompt-based stop hooks. + + NOTE: Prompt-based stop hooks don't work in Claude Code (issue #20221). + The template should filter out prompt hooks and not generate them. + """ adapter = ClaudeAdapter() skill_path = full_generator.generate_step_skill( job_with_stop_hooks, @@ -792,9 +794,67 @@ def test_template_generates_both_stop_and_subagent_stop_for_custom_hooks( content = skill_path.read_text() - # Both Stop and SubagentStop should be in the generated file - assert "Stop:" in content, "Stop hook should be in generated skill" - assert "SubagentStop:" in content, "SubagentStop hook should be in generated skill" + # Should NOT generate Stop/SubagentStop hooks for prompt-type hooks + assert "Stop:" not in content, "Prompt-based Stop hooks should not be generated" + assert "SubagentStop:" not in content, ( + "Prompt-based SubagentStop hooks should not be generated" + ) + + # The prompt content should NOT appear in the hooks section + assert "Custom validation prompt" not in content, ( + "Prompt content should not be in generated skill" + ) + + @pytest.fixture + def job_with_script_hooks(self, tmp_path: Path) -> JobDefinition: + """Create job with script-type stop hooks for testing template output.""" + job_dir = tmp_path / "test_job" + job_dir.mkdir() + steps_dir = job_dir / "steps" + steps_dir.mkdir() + (steps_dir / "step1.md").write_text("# Step 1 Instructions") + + return JobDefinition( + name="test_job", + version="1.0.0", + summary="Test job", + description="A test job", + steps=[ + Step( + id="step1", + name="Step 1", + description="First step", + instructions_file="steps/step1.md", + outputs=[OutputSpec(file="output.md")], + hooks={ + "after_agent": [HookAction(script="hooks/validate.sh")], + }, + ), + ], + job_dir=job_dir, + ) + + def test_template_generates_stop_hooks_for_script_type( + self, full_generator: SkillGenerator, job_with_script_hooks: JobDefinition, tmp_path: Path + ) -> None: + """Test that template generates Stop/SubagentStop hooks for script-type hooks. + + Script-type hooks (type: command) still work in Claude Code, so they should be generated. + """ + adapter = ClaudeAdapter() + skill_path = full_generator.generate_step_skill( + job_with_script_hooks, + job_with_script_hooks.steps[0], + adapter, + tmp_path, + ) + + content = skill_path.read_text() + + # Should generate Stop and SubagentStop hooks for script-type hooks + assert "Stop:" in content, "Script-based Stop hooks should be generated" + assert "SubagentStop:" in content, "Script-based SubagentStop hooks should be generated" - # Both should contain the custom prompt - assert "Custom validation prompt" in content, "Custom prompt should be in generated skill" + # Should contain the command type and path + assert "type: command" in content, "Hook should have type: command" + assert "hooks/validate.sh" in content, "Hook path should be in generated skill" diff --git a/uv.lock b/uv.lock index cd4110a3..85982610 100644 --- a/uv.lock +++ b/uv.lock @@ -126,7 +126,7 @@ toml = [ [[package]] name = "deepwork" -version = "0.4.0" +version = "0.4.2" source = { editable = "." } dependencies = [ { name = "click" },