Skip to content

Commit 90215c1

Browse files
authored
Merge branch 'main' into add-update-job
2 parents 3ea69e9 + c9bac58 commit 90215c1

14 files changed

Lines changed: 1492 additions & 92 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to DeepWork will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.2] - 2026-01-15
9+
10+
### Added
11+
- Default policy template file created during `deepwork install`
12+
- Generic, commented-out example policies suitable for any project
13+
- Does not include deepwork-specific policies (those remain in the deepwork repo only)
14+
- Preserves existing policy files if already present
15+
816
## [0.1.1] - 2026-01-15
917

1018
### Added
@@ -41,5 +49,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4149

4250
Initial version.
4351

52+
[0.1.2]: https://github.com/anthropics/deepwork/releases/tag/v0.1.2
4453
[0.1.1]: https://github.com/anthropics/deepwork/releases/tag/v0.1.1
4554
[0.1.0]: https://github.com/anthropics/deepwork/releases/tag/v0.1.0

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ This will:
6262
- Generate core DeepWork jobs
6363
- Install DeepWork jobs for your AI assistant
6464
- Configure hooks for your AI assistant to enable policies
65+
- Create a `.deepwork.policy.yml` template file with example policies
6566

6667
## Quick Start
6768

doc/architecture.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ def install(platform: str):
116116
# Inject core job definitions
117117
inject_deepwork_jobs(".deepwork/jobs/")
118118

119+
# Create default policy template (if not exists)
120+
if not exists(".deepwork.policy.yml"):
121+
copy_template("default_policy.yml", ".deepwork.policy.yml")
122+
119123
# Update config (supports multiple platforms)
120124
config = load_yaml(".deepwork/config.yml") or {}
121125
config["version"] = "1.0.0"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "deepwork"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
description = "Framework for enabling AI agents to perform complex, multi-step work tasks"
55
readme = "README.md"
66
requires-python = ">=3.11"

src/deepwork/cli/install.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,48 @@ def _create_deepwork_gitignore(deepwork_dir: Path) -> None:
113113
gitignore_path.write_text(gitignore_content)
114114

115115

116+
def _create_default_policy_file(project_path: Path) -> bool:
117+
"""
118+
Create a default policy file template in the project root.
119+
120+
Only creates the file if it doesn't already exist.
121+
122+
Args:
123+
project_path: Path to the project root
124+
125+
Returns:
126+
True if the file was created, False if it already existed
127+
"""
128+
policy_file = project_path / ".deepwork.policy.yml"
129+
130+
if policy_file.exists():
131+
return False
132+
133+
# Copy the template from the templates directory
134+
template_path = Path(__file__).parent.parent / "templates" / "default_policy.yml"
135+
136+
if template_path.exists():
137+
shutil.copy(template_path, policy_file)
138+
else:
139+
# Fallback: create a minimal template inline
140+
policy_file.write_text(
141+
"""# DeepWork Policy Configuration
142+
#
143+
# Policies are automated guardrails that trigger when specific files change.
144+
# Use /deepwork_policy.define to create new policies interactively.
145+
#
146+
# Format:
147+
# - name: "Policy name"
148+
# trigger: "glob/pattern/**/*"
149+
# safety: "optional/pattern/**/*"
150+
# instructions: |
151+
# Instructions for the AI agent...
152+
"""
153+
)
154+
155+
return True
156+
157+
116158
class DynamicChoice(click.Choice):
117159
"""A Click Choice that gets its values dynamically from AgentAdapter."""
118160

@@ -238,6 +280,12 @@ def _install_deepwork(platform_name: str | None, project_path: Path) -> None:
238280
_create_deepwork_gitignore(deepwork_dir)
239281
console.print(" [green]✓[/green] Created .deepwork/.gitignore")
240282

283+
# Step 3d: Create default policy file template
284+
if _create_default_policy_file(project_path):
285+
console.print(" [green]✓[/green] Created .deepwork.policy.yml template")
286+
else:
287+
console.print(" [dim]•[/dim] .deepwork.policy.yml already exists")
288+
241289
# Step 4: Load or create config.yml
242290
console.print("[yellow]→[/yellow] Updating configuration...")
243291
config_file = deepwork_dir / "config.yml"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# DeepWork Policy Configuration
2+
#
3+
# Policies are automated guardrails that trigger when specific files change.
4+
# They help ensure documentation stays current, security reviews happen, etc.
5+
#
6+
# Use /deepwork_policy.define to create new policies interactively.
7+
#
8+
# Format:
9+
# - name: "Friendly name for the policy"
10+
# trigger: "glob/pattern/**/*" # or array: ["pattern1", "pattern2"]
11+
# safety: "pattern/**/*" # optional - if these also changed, skip the policy
12+
# compare_to: "base" # optional: "base" (default), "default_tip", or "prompt"
13+
# instructions: |
14+
# Multi-line instructions for the AI agent...
15+
#
16+
# Example policies (uncomment and customize):
17+
#
18+
# - name: "README Documentation"
19+
# trigger: "src/**/*"
20+
# safety: "README.md"
21+
# instructions: |
22+
# Source code has been modified. Please review README.md for accuracy:
23+
# 1. Verify the project overview reflects current functionality
24+
# 2. Check that usage examples are still correct
25+
# 3. Ensure installation/setup instructions remain valid
26+
#
27+
# - name: "API Documentation Sync"
28+
# trigger: "src/api/**/*"
29+
# safety: "docs/api/**/*.md"
30+
# instructions: |
31+
# API code has changed. Please verify that API documentation is up to date:
32+
# - New or changed endpoints
33+
# - Modified request/response schemas
34+
# - Updated authentication requirements
35+
#
36+
# - name: "Security Review for Auth Changes"
37+
# trigger:
38+
# - "src/auth/**/*"
39+
# - "src/security/**/*"
40+
# instructions: |
41+
# Authentication or security code has been changed. Please:
42+
# 1. Review for hardcoded credentials or secrets
43+
# 2. Check input validation on user inputs
44+
# 3. Verify access control logic is correct
45+
#
46+
# - name: "Test Coverage for New Code"
47+
# trigger: "src/**/*.py"
48+
# safety: "tests/**/*.py"
49+
# instructions: |
50+
# New source code was added. Please ensure appropriate test coverage:
51+
# 1. Add unit tests for new functions/methods
52+
# 2. Update integration tests if behavior changed
53+
# 3. Verify all new code paths are tested

tests/integration/test_install_flow.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,58 @@ def test_install_is_idempotent(self, mock_claude_project: Path) -> None:
122122
assert (claude_dir / "deepwork_jobs.define.md").exists()
123123
assert (claude_dir / "deepwork_jobs.learn.md").exists()
124124

125+
def test_install_creates_policy_template(self, mock_claude_project: Path) -> None:
126+
"""Test that install creates a policy template file."""
127+
runner = CliRunner()
128+
129+
result = runner.invoke(
130+
cli,
131+
["install", "--platform", "claude", "--path", str(mock_claude_project)],
132+
catch_exceptions=False,
133+
)
134+
135+
assert result.exit_code == 0
136+
assert ".deepwork.policy.yml template" in result.output
137+
138+
# Verify policy file was created
139+
policy_file = mock_claude_project / ".deepwork.policy.yml"
140+
assert policy_file.exists()
141+
142+
# Verify it's the template (has comment header, no active policies)
143+
content = policy_file.read_text()
144+
assert "# DeepWork Policy Configuration" in content
145+
assert "# Use /deepwork_policy.define" in content
146+
147+
# Verify it does NOT contain deepwork-specific policies
148+
assert "Standard Jobs Source of Truth" not in content
149+
assert "Version and Changelog Update" not in content
150+
assert "pyproject.toml" not in content
151+
152+
def test_install_preserves_existing_policy_file(self, mock_claude_project: Path) -> None:
153+
"""Test that install doesn't overwrite existing policy file."""
154+
runner = CliRunner()
155+
156+
# Create a custom policy file before install
157+
policy_file = mock_claude_project / ".deepwork.policy.yml"
158+
custom_content = """- name: "My Custom Policy"
159+
trigger: "src/**/*"
160+
instructions: |
161+
Custom instructions here.
162+
"""
163+
policy_file.write_text(custom_content)
164+
165+
result = runner.invoke(
166+
cli,
167+
["install", "--platform", "claude", "--path", str(mock_claude_project)],
168+
catch_exceptions=False,
169+
)
170+
171+
assert result.exit_code == 0
172+
assert ".deepwork.policy.yml already exists" in result.output
173+
174+
# Verify original content is preserved
175+
assert policy_file.read_text() == custom_content
176+
125177

126178
class TestCLIEntryPoint:
127179
"""Tests for CLI entry point."""

tests/shell_script_tests/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Shell Script Tests
2+
3+
Automated tests for DeepWork shell scripts, with a focus on validating Claude Code hooks JSON response formats.
4+
5+
## Scripts Tested
6+
7+
| Script | Type | Description |
8+
|--------|------|-------------|
9+
| `policy_stop_hook.sh` | Stop Hook | Evaluates policies and blocks agent stop if policies are triggered |
10+
| `user_prompt_submit.sh` | UserPromptSubmit Hook | Captures work tree state when user submits a prompt |
11+
| `capture_prompt_work_tree.sh` | Helper | Records current git state for `compare_to: prompt` policies |
12+
| `make_new_job.sh` | Utility | Creates directory structure for new DeepWork jobs |
13+
14+
## Claude Code Hooks JSON Format
15+
16+
Hook scripts must return valid JSON responses. The tests enforce these formats:
17+
18+
### Stop Hooks (`hooks.after_agent`)
19+
```json
20+
{} // Allow stop
21+
{"decision": "block", "reason": "..."} // Block stop with reason
22+
```
23+
24+
### UserPromptSubmit Hooks (`hooks.before_prompt`)
25+
```json
26+
{} // No output or empty object (side-effect only hooks)
27+
```
28+
29+
### All Hooks
30+
- Must return valid JSON if producing output
31+
- Non-JSON output on stdout is **not allowed** (stderr is ok)
32+
- Exit code 0 indicates success (even when blocking)
33+
34+
## Running Tests
35+
36+
```bash
37+
# Run all shell script tests
38+
uv run pytest tests/shell_script_tests/ -v
39+
40+
# Run tests for a specific script
41+
uv run pytest tests/shell_script_tests/test_policy_stop_hook.py -v
42+
43+
# Run with coverage
44+
uv run pytest tests/shell_script_tests/ --cov=src/deepwork
45+
```
46+
47+
## Test Structure
48+
49+
```
50+
tests/shell_script_tests/
51+
├── conftest.py # Shared fixtures and helpers
52+
├── test_policy_stop_hook.py # Stop hook blocking/allowing tests
53+
├── test_user_prompt_submit.py # Prompt submission hook tests
54+
├── test_capture_prompt_work_tree.py # Work tree capture tests
55+
├── test_hooks_json_format.py # JSON format validation tests
56+
└── test_make_new_job.py # Job directory creation tests
57+
```
58+
59+
## Shared Fixtures
60+
61+
Available in `conftest.py`:
62+
63+
| Fixture | Description |
64+
|---------|-------------|
65+
| `git_repo` | Basic git repo with initial commit |
66+
| `git_repo_with_policy` | Git repo with a Python file policy |
67+
| `policy_hooks_dir` | Path to policy hooks scripts |
68+
| `jobs_scripts_dir` | Path to job management scripts |
69+
70+
## Adding New Tests
71+
72+
1. Use shared fixtures from `conftest.py` when possible
73+
2. Use `run_shell_script()` helper for running scripts
74+
3. Validate JSON output with `validate_json_output()` and `validate_stop_hook_response()`
75+
4. Test both success and failure cases
76+
5. Verify exit codes (hooks should exit 0 even when blocking)

0 commit comments

Comments
 (0)