Skip to content

Commit f1dc9e7

Browse files
committed
Separate default policy template from deepwork project policy
Create a distinct template policy file for target projects that: - Contains generic, commented-out examples useful for any project - Does NOT include deepwork-specific policies (Version/Changelog, Standard Jobs) The install command now: - Creates .deepwork.policy.yml template in target projects during install - Preserves existing policy files (won't overwrite custom policies)
1 parent 846e738 commit f1dc9e7

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

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."""

0 commit comments

Comments
 (0)