deepwork 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. deepwork/__init__.py +25 -0
  2. deepwork/cli/__init__.py +1 -0
  3. deepwork/cli/install.py +290 -0
  4. deepwork/cli/main.py +25 -0
  5. deepwork/cli/sync.py +176 -0
  6. deepwork/core/__init__.py +1 -0
  7. deepwork/core/adapters.py +373 -0
  8. deepwork/core/detector.py +93 -0
  9. deepwork/core/generator.py +290 -0
  10. deepwork/core/hooks_syncer.py +206 -0
  11. deepwork/core/parser.py +310 -0
  12. deepwork/core/policy_parser.py +285 -0
  13. deepwork/hooks/__init__.py +1 -0
  14. deepwork/hooks/evaluate_policies.py +159 -0
  15. deepwork/schemas/__init__.py +1 -0
  16. deepwork/schemas/job_schema.py +212 -0
  17. deepwork/schemas/policy_schema.py +68 -0
  18. deepwork/standard_jobs/deepwork_jobs/job.yml +102 -0
  19. deepwork/standard_jobs/deepwork_jobs/steps/define.md +359 -0
  20. deepwork/standard_jobs/deepwork_jobs/steps/implement.md +435 -0
  21. deepwork/standard_jobs/deepwork_jobs/steps/refine.md +447 -0
  22. deepwork/standard_jobs/deepwork_policy/hooks/capture_work_tree.sh +26 -0
  23. deepwork/standard_jobs/deepwork_policy/hooks/get_changed_files.sh +30 -0
  24. deepwork/standard_jobs/deepwork_policy/hooks/global_hooks.yml +8 -0
  25. deepwork/standard_jobs/deepwork_policy/hooks/policy_stop_hook.sh +72 -0
  26. deepwork/standard_jobs/deepwork_policy/hooks/user_prompt_submit.sh +17 -0
  27. deepwork/standard_jobs/deepwork_policy/job.yml +35 -0
  28. deepwork/standard_jobs/deepwork_policy/steps/define.md +174 -0
  29. deepwork/templates/__init__.py +1 -0
  30. deepwork/templates/claude/command-job-step.md.jinja +210 -0
  31. deepwork/templates/gemini/command-job-step.toml.jinja +169 -0
  32. deepwork/utils/__init__.py +1 -0
  33. deepwork/utils/fs.py +128 -0
  34. deepwork/utils/git.py +164 -0
  35. deepwork/utils/validation.py +31 -0
  36. deepwork/utils/yaml_utils.py +89 -0
  37. deepwork-0.1.0.dist-info/METADATA +389 -0
  38. deepwork-0.1.0.dist-info/RECORD +41 -0
  39. deepwork-0.1.0.dist-info/WHEEL +4 -0
  40. deepwork-0.1.0.dist-info/entry_points.txt +2 -0
  41. deepwork-0.1.0.dist-info/licenses/LICENSE.md +60 -0
@@ -0,0 +1,159 @@
1
+ """
2
+ Policy evaluation module for DeepWork hooks.
3
+
4
+ This module is called by the policy_stop_hook.sh script to evaluate which policies
5
+ should fire based on changed files and conversation context.
6
+
7
+ Usage:
8
+ python -m deepwork.hooks.evaluate_policies \
9
+ --policy-file .deepwork.policy.yml \
10
+ --changed-files "file1.py\nfile2.py"
11
+
12
+ The conversation context is read from stdin and checked for <promise> tags
13
+ that indicate policies have already been addressed.
14
+
15
+ Output is JSON suitable for Claude Code Stop hooks:
16
+ {"decision": "block", "reason": "..."} # Block stop, policies need attention
17
+ {} # No policies fired, allow stop
18
+ """
19
+
20
+ import argparse
21
+ import json
22
+ import re
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ from deepwork.core.policy_parser import (
27
+ PolicyParseError,
28
+ evaluate_policies,
29
+ parse_policy_file,
30
+ )
31
+
32
+
33
+ def extract_promise_tags(text: str) -> set[str]:
34
+ """
35
+ Extract policy names from <promise> tags in text.
36
+
37
+ Supported format:
38
+ - <promise>✓ Policy Name</promise>
39
+
40
+ Args:
41
+ text: Text to search for promise tags
42
+
43
+ Returns:
44
+ Set of policy names that have been promised/addressed
45
+ """
46
+ # Match <promise>✓ Policy Name</promise> and extract the policy name
47
+ pattern = r"<promise>✓\s*([^<]+)</promise>"
48
+ matches = re.findall(pattern, text, re.IGNORECASE | re.DOTALL)
49
+ return {m.strip() for m in matches}
50
+
51
+
52
+ def format_policy_message(policies: list) -> str:
53
+ """
54
+ Format triggered policies into a message for the agent.
55
+
56
+ Args:
57
+ policies: List of Policy objects that fired
58
+
59
+ Returns:
60
+ Formatted message with all policy instructions
61
+ """
62
+ lines = ["## DeepWork Policies Triggered", ""]
63
+ lines.append(
64
+ "Comply with the following policies. "
65
+ "To mark a policy as addressed, include `<promise>✓ Policy Name</promise>` "
66
+ "in your response (replace Policy Name with the actual policy name)."
67
+ )
68
+ lines.append("")
69
+
70
+ for policy in policies:
71
+ lines.append(f"### Policy: {policy.name}")
72
+ lines.append("")
73
+ lines.append(policy.instructions.strip())
74
+ lines.append("")
75
+
76
+ return "\n".join(lines)
77
+
78
+
79
+ def main() -> None:
80
+ """Main entry point for policy evaluation CLI."""
81
+ parser = argparse.ArgumentParser(
82
+ description="Evaluate DeepWork policies based on changed files"
83
+ )
84
+ parser.add_argument(
85
+ "--policy-file",
86
+ type=str,
87
+ required=True,
88
+ help="Path to .deepwork.policy.yml file",
89
+ )
90
+ parser.add_argument(
91
+ "--changed-files",
92
+ type=str,
93
+ required=True,
94
+ help="Newline-separated list of changed files",
95
+ )
96
+
97
+ args = parser.parse_args()
98
+
99
+ # Parse changed files (newline-separated)
100
+ changed_files = [f.strip() for f in args.changed_files.split("\n") if f.strip()]
101
+
102
+ if not changed_files:
103
+ # No files changed, nothing to evaluate
104
+ print("{}")
105
+ return
106
+
107
+ # Check if policy file exists
108
+ policy_path = Path(args.policy_file)
109
+ if not policy_path.exists():
110
+ # No policy file, nothing to evaluate
111
+ print("{}")
112
+ return
113
+
114
+ # Read conversation context from stdin (if available)
115
+ conversation_context = ""
116
+ if not sys.stdin.isatty():
117
+ try:
118
+ conversation_context = sys.stdin.read()
119
+ except Exception:
120
+ pass
121
+
122
+ # Extract promise tags from conversation
123
+ promised_policies = extract_promise_tags(conversation_context)
124
+
125
+ # Parse and evaluate policies
126
+ try:
127
+ policies = parse_policy_file(policy_path)
128
+ except PolicyParseError as e:
129
+ # Log error to stderr, return empty result
130
+ print(f"Error parsing policy file: {e}", file=sys.stderr)
131
+ print("{}")
132
+ return
133
+
134
+ if not policies:
135
+ # No policies defined
136
+ print("{}")
137
+ return
138
+
139
+ # Evaluate which policies fire
140
+ fired_policies = evaluate_policies(policies, changed_files, promised_policies)
141
+
142
+ if not fired_policies:
143
+ # No policies fired
144
+ print("{}")
145
+ return
146
+
147
+ # Format output for Claude Code Stop hooks
148
+ # Use "decision": "block" to prevent Claude from stopping
149
+ message = format_policy_message(fired_policies)
150
+ result = {
151
+ "decision": "block",
152
+ "reason": message,
153
+ }
154
+
155
+ print(json.dumps(result))
156
+
157
+
158
+ if __name__ == "__main__":
159
+ main()
@@ -0,0 +1 @@
1
+ """Schema definitions and validation."""
@@ -0,0 +1,212 @@
1
+ """JSON Schema definition for job definitions."""
2
+
3
+ from typing import Any
4
+
5
+ # Supported lifecycle hook events (generic names, mapped to platform-specific by adapters)
6
+ # These values must match CommandLifecycleHook enum in adapters.py
7
+ LIFECYCLE_HOOK_EVENTS = ["after_agent", "before_tool", "before_prompt"]
8
+
9
+ # Schema definition for a single hook action (prompt, prompt_file, or script)
10
+ HOOK_ACTION_SCHEMA: dict[str, Any] = {
11
+ "type": "object",
12
+ "oneOf": [
13
+ {
14
+ "required": ["prompt"],
15
+ "properties": {
16
+ "prompt": {
17
+ "type": "string",
18
+ "minLength": 1,
19
+ "description": "Inline prompt for validation/action",
20
+ },
21
+ },
22
+ "additionalProperties": False,
23
+ },
24
+ {
25
+ "required": ["prompt_file"],
26
+ "properties": {
27
+ "prompt_file": {
28
+ "type": "string",
29
+ "minLength": 1,
30
+ "description": "Path to prompt file (relative to job directory)",
31
+ },
32
+ },
33
+ "additionalProperties": False,
34
+ },
35
+ {
36
+ "required": ["script"],
37
+ "properties": {
38
+ "script": {
39
+ "type": "string",
40
+ "minLength": 1,
41
+ "description": "Path to shell script (relative to job directory)",
42
+ },
43
+ },
44
+ "additionalProperties": False,
45
+ },
46
+ ],
47
+ }
48
+
49
+ # JSON Schema for job.yml files
50
+ JOB_SCHEMA: dict[str, Any] = {
51
+ "$schema": "http://json-schema.org/draft-07/schema#",
52
+ "type": "object",
53
+ "required": ["name", "version", "summary", "steps"],
54
+ "properties": {
55
+ "name": {
56
+ "type": "string",
57
+ "pattern": "^[a-z][a-z0-9_]*$",
58
+ "description": "Job name (lowercase letters, numbers, underscores, must start with letter)",
59
+ },
60
+ "version": {
61
+ "type": "string",
62
+ "pattern": r"^\d+\.\d+\.\d+$",
63
+ "description": "Semantic version (e.g., 1.0.0)",
64
+ },
65
+ "summary": {
66
+ "type": "string",
67
+ "minLength": 1,
68
+ "maxLength": 200,
69
+ "description": "Brief one-line summary of what this job accomplishes",
70
+ },
71
+ "description": {
72
+ "type": "string",
73
+ "minLength": 1,
74
+ "description": "Detailed multi-line description of the job's purpose, process, and goals",
75
+ },
76
+ "changelog": {
77
+ "type": "array",
78
+ "description": "Version history and changes to the job",
79
+ "items": {
80
+ "type": "object",
81
+ "required": ["version", "changes"],
82
+ "properties": {
83
+ "version": {
84
+ "type": "string",
85
+ "pattern": r"^\d+\.\d+\.\d+$",
86
+ "description": "Version number for this change",
87
+ },
88
+ "changes": {
89
+ "type": "string",
90
+ "minLength": 1,
91
+ "description": "Description of changes made in this version",
92
+ },
93
+ },
94
+ "additionalProperties": False,
95
+ },
96
+ },
97
+ "steps": {
98
+ "type": "array",
99
+ "minItems": 1,
100
+ "description": "List of steps in the job",
101
+ "items": {
102
+ "type": "object",
103
+ "required": ["id", "name", "description", "instructions_file", "outputs"],
104
+ "properties": {
105
+ "id": {
106
+ "type": "string",
107
+ "pattern": "^[a-z][a-z0-9_]*$",
108
+ "description": "Step ID (unique within job)",
109
+ },
110
+ "name": {
111
+ "type": "string",
112
+ "minLength": 1,
113
+ "description": "Human-readable step name",
114
+ },
115
+ "description": {
116
+ "type": "string",
117
+ "minLength": 1,
118
+ "description": "Step description",
119
+ },
120
+ "instructions_file": {
121
+ "type": "string",
122
+ "minLength": 1,
123
+ "description": "Path to instructions file (relative to job directory)",
124
+ },
125
+ "inputs": {
126
+ "type": "array",
127
+ "description": "List of inputs (user parameters or files from previous steps)",
128
+ "items": {
129
+ "type": "object",
130
+ "oneOf": [
131
+ {
132
+ "required": ["name", "description"],
133
+ "properties": {
134
+ "name": {
135
+ "type": "string",
136
+ "description": "Input parameter name",
137
+ },
138
+ "description": {
139
+ "type": "string",
140
+ "description": "Input parameter description",
141
+ },
142
+ },
143
+ "additionalProperties": False,
144
+ },
145
+ {
146
+ "required": ["file", "from_step"],
147
+ "properties": {
148
+ "file": {
149
+ "type": "string",
150
+ "description": "File name from previous step",
151
+ },
152
+ "from_step": {
153
+ "type": "string",
154
+ "description": "Step ID that produces this file",
155
+ },
156
+ },
157
+ "additionalProperties": False,
158
+ },
159
+ ],
160
+ },
161
+ },
162
+ "outputs": {
163
+ "type": "array",
164
+ "description": "List of output files/directories",
165
+ "items": {
166
+ "type": "string",
167
+ "minLength": 1,
168
+ },
169
+ },
170
+ "dependencies": {
171
+ "type": "array",
172
+ "description": "List of step IDs this step depends on",
173
+ "items": {
174
+ "type": "string",
175
+ },
176
+ "default": [],
177
+ },
178
+ "hooks": {
179
+ "type": "object",
180
+ "description": "Lifecycle hooks for this step, keyed by event type",
181
+ "properties": {
182
+ "after_agent": {
183
+ "type": "array",
184
+ "description": "Hooks triggered after the agent finishes (quality validation)",
185
+ "items": HOOK_ACTION_SCHEMA,
186
+ },
187
+ "before_tool": {
188
+ "type": "array",
189
+ "description": "Hooks triggered before a tool is used",
190
+ "items": HOOK_ACTION_SCHEMA,
191
+ },
192
+ "before_prompt": {
193
+ "type": "array",
194
+ "description": "Hooks triggered when user submits a prompt",
195
+ "items": HOOK_ACTION_SCHEMA,
196
+ },
197
+ },
198
+ "additionalProperties": False,
199
+ },
200
+ # DEPRECATED: Use hooks.after_agent instead
201
+ "stop_hooks": {
202
+ "type": "array",
203
+ "description": "DEPRECATED: Use hooks.after_agent instead. Stop hooks for quality validation loops.",
204
+ "items": HOOK_ACTION_SCHEMA,
205
+ },
206
+ },
207
+ "additionalProperties": False,
208
+ },
209
+ },
210
+ },
211
+ "additionalProperties": False,
212
+ }
@@ -0,0 +1,68 @@
1
+ """JSON Schema definition for policy definitions."""
2
+
3
+ from typing import Any
4
+
5
+ # JSON Schema for .deepwork.policy.yml files
6
+ # Policies are defined as an array of policy objects
7
+ POLICY_SCHEMA: dict[str, Any] = {
8
+ "$schema": "http://json-schema.org/draft-07/schema#",
9
+ "type": "array",
10
+ "description": "List of policies that trigger based on file changes",
11
+ "items": {
12
+ "type": "object",
13
+ "required": ["name", "trigger"],
14
+ "properties": {
15
+ "name": {
16
+ "type": "string",
17
+ "minLength": 1,
18
+ "description": "Friendly name for the policy",
19
+ },
20
+ "trigger": {
21
+ "oneOf": [
22
+ {
23
+ "type": "string",
24
+ "minLength": 1,
25
+ "description": "Glob pattern for files that trigger this policy",
26
+ },
27
+ {
28
+ "type": "array",
29
+ "items": {"type": "string", "minLength": 1},
30
+ "minItems": 1,
31
+ "description": "List of glob patterns for files that trigger this policy",
32
+ },
33
+ ],
34
+ "description": "Glob pattern(s) for files that, if changed, should trigger this policy",
35
+ },
36
+ "safety": {
37
+ "oneOf": [
38
+ {
39
+ "type": "string",
40
+ "minLength": 1,
41
+ "description": "Glob pattern for safety files",
42
+ },
43
+ {
44
+ "type": "array",
45
+ "items": {"type": "string", "minLength": 1},
46
+ "description": "List of glob patterns for safety files",
47
+ },
48
+ ],
49
+ "description": "Glob pattern(s) for files that, if also changed, mean the policy doesn't need to trigger",
50
+ },
51
+ "instructions": {
52
+ "type": "string",
53
+ "minLength": 1,
54
+ "description": "Instructions to give the agent when this policy triggers",
55
+ },
56
+ "instructions_file": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "description": "Path to a file containing instructions (alternative to inline instructions)",
60
+ },
61
+ },
62
+ "oneOf": [
63
+ {"required": ["instructions"]},
64
+ {"required": ["instructions_file"]},
65
+ ],
66
+ "additionalProperties": False,
67
+ },
68
+ }
@@ -0,0 +1,102 @@
1
+ name: deepwork_jobs
2
+ version: "0.1.0"
3
+ summary: "DeepWork job management commands"
4
+ description: |
5
+ Core commands for managing DeepWork jobs. These commands help you define new multi-step
6
+ workflows and refine existing ones.
7
+
8
+ The `define` command guides you through an interactive process to create a new job by
9
+ asking detailed questions about your workflow, understanding each step's inputs and outputs,
10
+ and generating all necessary files.
11
+
12
+ The `refine` command helps you modify existing jobs safely by understanding what you want
13
+ to change, validating the impact, and ensuring consistency across your workflow.
14
+
15
+ changelog:
16
+ - version: "0.1.0"
17
+ changes: "Initial version"
18
+
19
+ steps:
20
+ - id: define
21
+ name: "Define Job Specification"
22
+ description: "Create the job.yml specification file by understanding workflow requirements"
23
+ instructions_file: steps/define.md
24
+ inputs:
25
+ - name: job_purpose
26
+ description: "What complex task or workflow are you trying to accomplish?"
27
+ outputs:
28
+ - job.yml
29
+ dependencies: []
30
+ hooks:
31
+ after_agent:
32
+ - prompt: |
33
+ Verify the job.yml output meets ALL quality criteria before completing:
34
+
35
+ 1. **User Understanding**: Did you fully understand the user's workflow through interactive Q&A?
36
+ 2. **Clear Inputs/Outputs**: Does every step have clearly defined inputs and outputs?
37
+ 3. **Logical Dependencies**: Do step dependencies make sense and avoid circular references?
38
+ 4. **Concise Summary**: Is the summary under 200 characters and descriptive?
39
+ 5. **Rich Description**: Does the description provide enough context for future refinement?
40
+ 6. **Valid Schema**: Does the job.yml follow the required schema (name, version, summary, steps)?
41
+ 7. **File Created**: Has the job.yml file been created in `.deepwork/jobs/[job_name]/job.yml`?
42
+
43
+ If ANY criterion is not met, continue working to address it.
44
+ If ALL criteria are satisfied, include `<promise>✓ Quality Criteria Met</promise>` in your response.
45
+
46
+ - id: implement
47
+ name: "Implement Job Steps"
48
+ description: "Generate instruction files for each step based on the job.yml specification"
49
+ instructions_file: steps/implement.md
50
+ inputs:
51
+ - file: job.yml
52
+ from_step: define
53
+ outputs:
54
+ - implementation_summary.md
55
+ dependencies:
56
+ - define
57
+ hooks:
58
+ after_agent:
59
+ - prompt: |
60
+ Verify the implementation meets ALL quality criteria before completing:
61
+
62
+ 1. **Directory Structure**: Is `.deepwork/jobs/[job_name]/` created correctly?
63
+ 2. **Complete Instructions**: Are ALL step instruction files complete (not stubs or placeholders)?
64
+ 3. **Specific & Actionable**: Are instructions tailored to each step's purpose, not generic?
65
+ 4. **Output Examples**: Does each instruction file show what good output looks like?
66
+ 5. **Quality Criteria**: Does each instruction file define quality criteria for its outputs?
67
+ 6. **Sync Complete**: Has `deepwork sync` been run successfully?
68
+ 7. **Commands Available**: Are the slash-commands generated in `.claude/commands/`?
69
+ 8. **Summary Created**: Has `implementation_summary.md` been created?
70
+ 9. **Policies Considered**: Have you thought about whether policies would benefit this job?
71
+ - If relevant policies were identified, did you explain them and offer to run `/deepwork_policy.define`?
72
+ - Not every job needs policies - only suggest when genuinely helpful.
73
+
74
+ If ANY criterion is not met, continue working to address it.
75
+ If ALL criteria are satisfied, include `<promise>✓ Quality Criteria Met</promise>` in your response.
76
+
77
+ - id: refine
78
+ name: "Refine Existing Job"
79
+ description: "Modify an existing job definition"
80
+ instructions_file: steps/refine.md
81
+ inputs:
82
+ - name: job_name
83
+ description: "Name of the job to refine"
84
+ outputs:
85
+ - job.yml
86
+ dependencies: []
87
+ hooks:
88
+ after_agent:
89
+ - prompt: |
90
+ Verify the refinement meets ALL quality criteria before completing:
91
+
92
+ 1. **Job Consistency**: Do the changes maintain overall job consistency?
93
+ 2. **Valid Dependencies**: Are all step dependencies logically valid (no circular refs)?
94
+ 3. **Semantic Versioning**: Was the version bumped appropriately (major/minor/patch)?
95
+ 4. **Changelog Updated**: Is the changelog updated with a description of changes?
96
+ 5. **User Understanding**: Does the user understand the impact of the changes?
97
+ 6. **Breaking Changes**: Were any breaking changes clearly communicated?
98
+ 7. **Files Updated**: Are all affected files (job.yml, step files) updated?
99
+ 8. **Sync Complete**: Has `deepwork sync` been run to regenerate commands?
100
+
101
+ If ANY criterion is not met, continue working to address it.
102
+ If ALL criteria are satisfied, include `<promise>✓ Quality Criteria Met</promise>` in your response.