deepwork 0.2.0__py3-none-any.whl → 0.3.1__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 (54) hide show
  1. deepwork/cli/install.py +116 -71
  2. deepwork/cli/sync.py +20 -20
  3. deepwork/core/adapters.py +88 -51
  4. deepwork/core/command_executor.py +173 -0
  5. deepwork/core/generator.py +148 -31
  6. deepwork/core/hooks_syncer.py +51 -25
  7. deepwork/core/parser.py +8 -0
  8. deepwork/core/pattern_matcher.py +271 -0
  9. deepwork/core/rules_parser.py +559 -0
  10. deepwork/core/rules_queue.py +321 -0
  11. deepwork/hooks/README.md +181 -0
  12. deepwork/hooks/__init__.py +77 -1
  13. deepwork/hooks/claude_hook.sh +55 -0
  14. deepwork/hooks/gemini_hook.sh +55 -0
  15. deepwork/hooks/rules_check.py +700 -0
  16. deepwork/hooks/wrapper.py +363 -0
  17. deepwork/schemas/job_schema.py +14 -1
  18. deepwork/schemas/rules_schema.py +135 -0
  19. deepwork/standard_jobs/deepwork_jobs/job.yml +35 -53
  20. deepwork/standard_jobs/deepwork_jobs/steps/define.md +9 -6
  21. deepwork/standard_jobs/deepwork_jobs/steps/implement.md +28 -26
  22. deepwork/standard_jobs/deepwork_jobs/steps/learn.md +2 -2
  23. deepwork/standard_jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh +30 -0
  24. deepwork/standard_jobs/deepwork_rules/hooks/global_hooks.yml +8 -0
  25. deepwork/standard_jobs/deepwork_rules/job.yml +47 -0
  26. deepwork/standard_jobs/deepwork_rules/rules/.gitkeep +13 -0
  27. deepwork/standard_jobs/deepwork_rules/rules/api-documentation-sync.md.example +10 -0
  28. deepwork/standard_jobs/deepwork_rules/rules/readme-documentation.md.example +10 -0
  29. deepwork/standard_jobs/deepwork_rules/rules/security-review.md.example +11 -0
  30. deepwork/standard_jobs/deepwork_rules/rules/skill-md-validation.md +46 -0
  31. deepwork/standard_jobs/deepwork_rules/rules/source-test-pairing.md.example +13 -0
  32. deepwork/standard_jobs/deepwork_rules/steps/define.md +249 -0
  33. deepwork/templates/claude/skill-job-meta.md.jinja +70 -0
  34. deepwork/templates/claude/skill-job-step.md.jinja +198 -0
  35. deepwork/templates/gemini/skill-job-meta.toml.jinja +76 -0
  36. deepwork/templates/gemini/skill-job-step.toml.jinja +147 -0
  37. {deepwork-0.2.0.dist-info → deepwork-0.3.1.dist-info}/METADATA +56 -25
  38. deepwork-0.3.1.dist-info/RECORD +62 -0
  39. deepwork/core/policy_parser.py +0 -295
  40. deepwork/hooks/evaluate_policies.py +0 -376
  41. deepwork/schemas/policy_schema.py +0 -78
  42. deepwork/standard_jobs/deepwork_policy/hooks/capture_prompt_work_tree.sh +0 -27
  43. deepwork/standard_jobs/deepwork_policy/hooks/global_hooks.yml +0 -8
  44. deepwork/standard_jobs/deepwork_policy/hooks/policy_stop_hook.sh +0 -56
  45. deepwork/standard_jobs/deepwork_policy/job.yml +0 -35
  46. deepwork/standard_jobs/deepwork_policy/steps/define.md +0 -195
  47. deepwork/templates/claude/command-job-step.md.jinja +0 -210
  48. deepwork/templates/default_policy.yml +0 -53
  49. deepwork/templates/gemini/command-job-step.toml.jinja +0 -169
  50. deepwork-0.2.0.dist-info/RECORD +0 -49
  51. /deepwork/standard_jobs/{deepwork_policy → deepwork_rules}/hooks/user_prompt_submit.sh +0 -0
  52. {deepwork-0.2.0.dist-info → deepwork-0.3.1.dist-info}/WHEEL +0 -0
  53. {deepwork-0.2.0.dist-info → deepwork-0.3.1.dist-info}/entry_points.txt +0 -0
  54. {deepwork-0.2.0.dist-info → deepwork-0.3.1.dist-info}/licenses/LICENSE.md +0 -0
@@ -0,0 +1,363 @@
1
+ """
2
+ Hook wrapper module for cross-platform hook compatibility.
3
+
4
+ This module provides utilities for normalizing hook input/output between
5
+ different AI CLI platforms (Claude Code, Gemini CLI, etc.).
6
+
7
+ The wrapper system allows writing hooks once in Python and running them
8
+ on any supported platform. Platform-specific shell scripts handle the
9
+ input/output translation, while Python hooks work with a normalized format.
10
+
11
+ Normalized Format:
12
+ Input:
13
+ - session_id: str
14
+ - transcript_path: str
15
+ - cwd: str
16
+ - event: str (normalized: 'after_agent', 'before_tool', 'before_prompt')
17
+ - tool_name: str (normalized: 'write_file', 'shell', etc.)
18
+ - tool_input: dict
19
+ - prompt: str (for agent events)
20
+ - raw_input: dict (original platform-specific input)
21
+
22
+ Output:
23
+ - decision: str ('block', 'allow', 'deny')
24
+ - reason: str (explanation for blocking)
25
+ - context: str (additional context to add)
26
+ - raw_output: dict (will be merged into final output)
27
+
28
+ Usage:
29
+ # In a Python hook:
30
+ from deepwork.hooks.wrapper import HookInput, HookOutput, normalize_input, denormalize_output
31
+
32
+ def my_hook(input_data: HookInput) -> HookOutput:
33
+ if should_block:
34
+ return HookOutput(decision='block', reason='Must do X first')
35
+ return HookOutput() # Allow
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import json
41
+ import sys
42
+ from collections.abc import Callable
43
+ from dataclasses import dataclass, field
44
+ from enum import Enum
45
+ from typing import Any
46
+
47
+
48
+ class Platform(str, Enum):
49
+ """Supported AI CLI platforms."""
50
+
51
+ CLAUDE = "claude"
52
+ GEMINI = "gemini"
53
+
54
+
55
+ class NormalizedEvent(str, Enum):
56
+ """Normalized hook event names."""
57
+
58
+ AFTER_AGENT = "after_agent"
59
+ BEFORE_TOOL = "before_tool"
60
+ BEFORE_PROMPT = "before_prompt"
61
+ SESSION_START = "session_start"
62
+ SESSION_END = "session_end"
63
+ AFTER_TOOL = "after_tool"
64
+ BEFORE_MODEL = "before_model"
65
+ AFTER_MODEL = "after_model"
66
+
67
+
68
+ # Event name mappings from platform-specific to normalized
69
+ EVENT_TO_NORMALIZED: dict[Platform, dict[str, NormalizedEvent]] = {
70
+ Platform.CLAUDE: {
71
+ "Stop": NormalizedEvent.AFTER_AGENT,
72
+ "SubagentStop": NormalizedEvent.AFTER_AGENT,
73
+ "PreToolUse": NormalizedEvent.BEFORE_TOOL,
74
+ "PostToolUse": NormalizedEvent.AFTER_TOOL,
75
+ "UserPromptSubmit": NormalizedEvent.BEFORE_PROMPT,
76
+ "SessionStart": NormalizedEvent.SESSION_START,
77
+ "SessionEnd": NormalizedEvent.SESSION_END,
78
+ },
79
+ Platform.GEMINI: {
80
+ "AfterAgent": NormalizedEvent.AFTER_AGENT,
81
+ "BeforeTool": NormalizedEvent.BEFORE_TOOL,
82
+ "AfterTool": NormalizedEvent.AFTER_TOOL,
83
+ "BeforeAgent": NormalizedEvent.BEFORE_PROMPT,
84
+ "SessionStart": NormalizedEvent.SESSION_START,
85
+ "SessionEnd": NormalizedEvent.SESSION_END,
86
+ "BeforeModel": NormalizedEvent.BEFORE_MODEL,
87
+ "AfterModel": NormalizedEvent.AFTER_MODEL,
88
+ },
89
+ }
90
+
91
+ # Normalized event to platform-specific event name
92
+ NORMALIZED_TO_EVENT: dict[Platform, dict[NormalizedEvent, str]] = {
93
+ Platform.CLAUDE: {
94
+ NormalizedEvent.AFTER_AGENT: "Stop",
95
+ NormalizedEvent.BEFORE_TOOL: "PreToolUse",
96
+ NormalizedEvent.AFTER_TOOL: "PostToolUse",
97
+ NormalizedEvent.BEFORE_PROMPT: "UserPromptSubmit",
98
+ NormalizedEvent.SESSION_START: "SessionStart",
99
+ NormalizedEvent.SESSION_END: "SessionEnd",
100
+ },
101
+ Platform.GEMINI: {
102
+ NormalizedEvent.AFTER_AGENT: "AfterAgent",
103
+ NormalizedEvent.BEFORE_TOOL: "BeforeTool",
104
+ NormalizedEvent.AFTER_TOOL: "AfterTool",
105
+ NormalizedEvent.BEFORE_PROMPT: "BeforeAgent",
106
+ NormalizedEvent.SESSION_START: "SessionStart",
107
+ NormalizedEvent.SESSION_END: "SessionEnd",
108
+ NormalizedEvent.BEFORE_MODEL: "BeforeModel",
109
+ NormalizedEvent.AFTER_MODEL: "AfterModel",
110
+ },
111
+ }
112
+
113
+ # Tool name mappings from platform-specific to normalized (snake_case)
114
+ TOOL_TO_NORMALIZED: dict[Platform, dict[str, str]] = {
115
+ Platform.CLAUDE: {
116
+ "Write": "write_file",
117
+ "Edit": "edit_file",
118
+ "Read": "read_file",
119
+ "Bash": "shell",
120
+ "Glob": "glob",
121
+ "Grep": "grep",
122
+ "WebFetch": "web_fetch",
123
+ "WebSearch": "web_search",
124
+ "Task": "task",
125
+ },
126
+ Platform.GEMINI: {
127
+ # Gemini already uses snake_case
128
+ "write_file": "write_file",
129
+ "edit_file": "edit_file",
130
+ "read_file": "read_file",
131
+ "shell": "shell",
132
+ "glob": "glob",
133
+ "grep": "grep",
134
+ "web_fetch": "web_fetch",
135
+ "web_search": "web_search",
136
+ },
137
+ }
138
+
139
+ # Normalized tool names to platform-specific
140
+ NORMALIZED_TO_TOOL: dict[Platform, dict[str, str]] = {
141
+ Platform.CLAUDE: {
142
+ "write_file": "Write",
143
+ "edit_file": "Edit",
144
+ "read_file": "Read",
145
+ "shell": "Bash",
146
+ "glob": "Glob",
147
+ "grep": "Grep",
148
+ "web_fetch": "WebFetch",
149
+ "web_search": "WebSearch",
150
+ "task": "Task",
151
+ },
152
+ Platform.GEMINI: {
153
+ # Gemini already uses snake_case
154
+ "write_file": "write_file",
155
+ "edit_file": "edit_file",
156
+ "read_file": "read_file",
157
+ "shell": "shell",
158
+ "glob": "glob",
159
+ "grep": "grep",
160
+ "web_fetch": "web_fetch",
161
+ "web_search": "web_search",
162
+ },
163
+ }
164
+
165
+
166
+ @dataclass
167
+ class HookInput:
168
+ """Normalized hook input data."""
169
+
170
+ platform: Platform
171
+ event: NormalizedEvent
172
+ session_id: str = ""
173
+ transcript_path: str = ""
174
+ cwd: str = ""
175
+ tool_name: str = ""
176
+ tool_input: dict[str, Any] = field(default_factory=dict)
177
+ tool_response: str = ""
178
+ prompt: str = ""
179
+ raw_input: dict[str, Any] = field(default_factory=dict)
180
+
181
+ @classmethod
182
+ def from_dict(cls, data: dict[str, Any], platform: Platform) -> HookInput:
183
+ """Create HookInput from raw platform-specific input."""
184
+ # Get event name and normalize
185
+ raw_event = data.get("hook_event_name", "")
186
+ event_map = EVENT_TO_NORMALIZED.get(platform, {})
187
+ event = event_map.get(raw_event, NormalizedEvent.AFTER_AGENT)
188
+
189
+ # Get tool name and normalize
190
+ raw_tool = data.get("tool_name", "")
191
+ tool_map = TOOL_TO_NORMALIZED.get(platform, {})
192
+ tool_name = tool_map.get(raw_tool, raw_tool.lower())
193
+
194
+ return cls(
195
+ platform=platform,
196
+ event=event,
197
+ session_id=data.get("session_id", ""),
198
+ transcript_path=data.get("transcript_path", ""),
199
+ cwd=data.get("cwd", ""),
200
+ tool_name=tool_name,
201
+ tool_input=data.get("tool_input", {}),
202
+ tool_response=data.get("tool_response", ""),
203
+ prompt=data.get("prompt", ""),
204
+ raw_input=data,
205
+ )
206
+
207
+
208
+ @dataclass
209
+ class HookOutput:
210
+ """Normalized hook output data."""
211
+
212
+ decision: str = "" # 'block', 'allow', 'deny', '' (empty = allow)
213
+ reason: str = "" # Explanation for blocking
214
+ context: str = "" # Additional context to add
215
+ continue_loop: bool = True # False to terminate agent loop
216
+ stop_reason: str = "" # Message when stopping
217
+ suppress_output: bool = False # Hide from transcript
218
+ raw_output: dict[str, Any] = field(default_factory=dict)
219
+
220
+ def to_dict(self, platform: Platform, event: NormalizedEvent) -> dict[str, Any]:
221
+ """Convert to platform-specific output format."""
222
+ result: dict[str, Any] = {}
223
+
224
+ # Handle decision
225
+ if self.decision:
226
+ if platform == Platform.GEMINI and self.decision == "block":
227
+ # Gemini prefers 'deny'
228
+ result["decision"] = "deny"
229
+ else:
230
+ result["decision"] = self.decision
231
+
232
+ # Handle reason
233
+ if self.reason:
234
+ result["reason"] = self.reason
235
+
236
+ # Handle continue_loop
237
+ if not self.continue_loop:
238
+ result["continue"] = False
239
+ if self.stop_reason:
240
+ result["stopReason"] = self.stop_reason
241
+
242
+ # Handle suppress_output
243
+ if self.suppress_output:
244
+ result["suppressOutput"] = True
245
+
246
+ # Handle context (platform-specific)
247
+ if self.context:
248
+ if platform == Platform.CLAUDE:
249
+ # Claude uses different fields depending on event
250
+ if event == NormalizedEvent.SESSION_START:
251
+ result.setdefault("hookSpecificOutput", {})
252
+ result["hookSpecificOutput"]["hookEventName"] = NORMALIZED_TO_EVENT[platform][
253
+ event
254
+ ]
255
+ result["hookSpecificOutput"]["additionalContext"] = self.context
256
+ else:
257
+ result["systemMessage"] = self.context
258
+ else:
259
+ # Gemini
260
+ result.setdefault("hookSpecificOutput", {})
261
+ result["hookSpecificOutput"]["hookEventName"] = NORMALIZED_TO_EVENT[platform].get(
262
+ event, str(event)
263
+ )
264
+ result["hookSpecificOutput"]["additionalContext"] = self.context
265
+
266
+ # Merge any raw output
267
+ for key, value in self.raw_output.items():
268
+ if key not in result:
269
+ result[key] = value
270
+
271
+ return result
272
+
273
+
274
+ def normalize_input(raw_json: str, platform: Platform) -> HookInput:
275
+ """
276
+ Parse raw JSON input and normalize it.
277
+
278
+ Args:
279
+ raw_json: JSON string from stdin
280
+ platform: Source platform
281
+
282
+ Returns:
283
+ Normalized HookInput
284
+ """
285
+ try:
286
+ data = json.loads(raw_json) if raw_json.strip() else {}
287
+ except json.JSONDecodeError:
288
+ data = {}
289
+
290
+ return HookInput.from_dict(data, platform)
291
+
292
+
293
+ def denormalize_output(output: HookOutput, platform: Platform, event: NormalizedEvent) -> str:
294
+ """
295
+ Convert normalized output to platform-specific JSON.
296
+
297
+ Args:
298
+ output: Normalized HookOutput
299
+ platform: Target platform
300
+ event: The event being processed
301
+
302
+ Returns:
303
+ JSON string for stdout
304
+ """
305
+ result = output.to_dict(platform, event)
306
+ return json.dumps(result) if result else "{}"
307
+
308
+
309
+ def read_stdin() -> str:
310
+ """Read all input from stdin."""
311
+ if sys.stdin.isatty():
312
+ return ""
313
+ try:
314
+ return sys.stdin.read()
315
+ except Exception:
316
+ return ""
317
+
318
+
319
+ def write_stdout(data: str) -> None:
320
+ """Write output to stdout."""
321
+ print(data)
322
+
323
+
324
+ def run_hook(
325
+ hook_fn: Callable[[HookInput], HookOutput],
326
+ platform: Platform,
327
+ ) -> int:
328
+ """
329
+ Run a hook function with normalized input/output.
330
+
331
+ This is the main entry point for Python hooks. It:
332
+ 1. Reads raw input from stdin
333
+ 2. Normalizes the input
334
+ 3. Calls the hook function
335
+ 4. Denormalizes the output
336
+ 5. Writes to stdout
337
+
338
+ Args:
339
+ hook_fn: Function that takes HookInput and returns HookOutput
340
+ platform: The platform calling this hook
341
+
342
+ Returns:
343
+ Exit code (0 for success, 2 for blocking)
344
+ """
345
+ # Read and normalize input
346
+ raw_input = read_stdin()
347
+ hook_input = normalize_input(raw_input, platform)
348
+
349
+ # Call the hook
350
+ try:
351
+ hook_output = hook_fn(hook_input)
352
+ except Exception as e:
353
+ # On error, allow the action but log
354
+ print(f"Hook error: {e}", file=sys.stderr)
355
+ hook_output = HookOutput()
356
+
357
+ # Denormalize and write output
358
+ output_json = denormalize_output(hook_output, platform, hook_input.event)
359
+ write_stdout(output_json)
360
+
361
+ # Always return 0 when using JSON output format
362
+ # The decision field in the JSON controls blocking behavior
363
+ return 0
@@ -3,7 +3,7 @@
3
3
  from typing import Any
4
4
 
5
5
  # Supported lifecycle hook events (generic names, mapped to platform-specific by adapters)
6
- # These values must match CommandLifecycleHook enum in adapters.py
6
+ # These values must match SkillLifecycleHook enum in adapters.py
7
7
  LIFECYCLE_HOOK_EVENTS = ["after_agent", "before_tool", "before_prompt"]
8
8
 
9
9
  # Schema definition for a single hook action (prompt, prompt_file, or script)
@@ -203,6 +203,19 @@ JOB_SCHEMA: dict[str, Any] = {
203
203
  "description": "DEPRECATED: Use hooks.after_agent instead. Stop hooks for quality validation loops.",
204
204
  "items": HOOK_ACTION_SCHEMA,
205
205
  },
206
+ "exposed": {
207
+ "type": "boolean",
208
+ "description": "If true, skill is user-invocable in menus. Default: false (hidden from menus).",
209
+ "default": False,
210
+ },
211
+ "quality_criteria": {
212
+ "type": "array",
213
+ "description": "Declarative quality criteria. Rendered with standard evaluation framing.",
214
+ "items": {
215
+ "type": "string",
216
+ "minLength": 1,
217
+ },
218
+ },
206
219
  },
207
220
  "additionalProperties": False,
208
221
  },
@@ -0,0 +1,135 @@
1
+ """JSON Schema definition for rule definitions (v2 - frontmatter format)."""
2
+
3
+ from typing import Any
4
+
5
+ # Pattern for string or array of strings
6
+ STRING_OR_ARRAY: dict[str, Any] = {
7
+ "oneOf": [
8
+ {"type": "string", "minLength": 1},
9
+ {"type": "array", "items": {"type": "string", "minLength": 1}, "minItems": 1},
10
+ ]
11
+ }
12
+
13
+ # JSON Schema for rule frontmatter (YAML between --- delimiters)
14
+ # Rules are stored as individual .md files in .deepwork/rules/
15
+ RULES_FRONTMATTER_SCHEMA: dict[str, Any] = {
16
+ "$schema": "http://json-schema.org/draft-07/schema#",
17
+ "type": "object",
18
+ "required": ["name", "compare_to"],
19
+ "properties": {
20
+ "name": {
21
+ "type": "string",
22
+ "minLength": 1,
23
+ "description": "Human-friendly name for the rule (displayed in promise tags)",
24
+ },
25
+ # Detection mode: trigger/safety (mutually exclusive with set/pair)
26
+ "trigger": {
27
+ **STRING_OR_ARRAY,
28
+ "description": "Glob pattern(s) for files that trigger this rule",
29
+ },
30
+ "safety": {
31
+ **STRING_OR_ARRAY,
32
+ "description": "Glob pattern(s) that suppress the rule if changed",
33
+ },
34
+ # Detection mode: set (bidirectional correspondence)
35
+ "set": {
36
+ "type": "array",
37
+ "items": {"type": "string", "minLength": 1},
38
+ "minItems": 2,
39
+ "description": "Patterns defining bidirectional file correspondence",
40
+ },
41
+ # Detection mode: pair (directional correspondence)
42
+ "pair": {
43
+ "type": "object",
44
+ "required": ["trigger", "expects"],
45
+ "properties": {
46
+ "trigger": {
47
+ "type": "string",
48
+ "minLength": 1,
49
+ "description": "Pattern that triggers the rule",
50
+ },
51
+ "expects": {
52
+ **STRING_OR_ARRAY,
53
+ "description": "Pattern(s) for expected corresponding files",
54
+ },
55
+ },
56
+ "additionalProperties": False,
57
+ "description": "Directional file correspondence (trigger -> expects)",
58
+ },
59
+ # Detection mode: created (fire when files are created matching patterns)
60
+ "created": {
61
+ **STRING_OR_ARRAY,
62
+ "description": "Glob pattern(s) for newly created files that trigger this rule",
63
+ },
64
+ # Action type: command (default is prompt using markdown body)
65
+ "action": {
66
+ "type": "object",
67
+ "required": ["command"],
68
+ "properties": {
69
+ "command": {
70
+ "type": "string",
71
+ "minLength": 1,
72
+ "description": "Command to run (supports {file}, {files}, {repo_root})",
73
+ },
74
+ "run_for": {
75
+ "type": "string",
76
+ "enum": ["each_match", "all_matches"],
77
+ "default": "each_match",
78
+ "description": "Run command for each file or all files at once",
79
+ },
80
+ },
81
+ "additionalProperties": False,
82
+ "description": "Command action to run instead of prompting",
83
+ },
84
+ # Common options
85
+ "compare_to": {
86
+ "type": "string",
87
+ "enum": ["base", "default_tip", "prompt"],
88
+ "description": "Baseline for detecting file changes",
89
+ },
90
+ },
91
+ "additionalProperties": False,
92
+ # Detection mode must be exactly one of: trigger, set, pair, or created
93
+ "oneOf": [
94
+ {
95
+ "required": ["trigger"],
96
+ "not": {
97
+ "anyOf": [
98
+ {"required": ["set"]},
99
+ {"required": ["pair"]},
100
+ {"required": ["created"]},
101
+ ]
102
+ },
103
+ },
104
+ {
105
+ "required": ["set"],
106
+ "not": {
107
+ "anyOf": [
108
+ {"required": ["trigger"]},
109
+ {"required": ["pair"]},
110
+ {"required": ["created"]},
111
+ ]
112
+ },
113
+ },
114
+ {
115
+ "required": ["pair"],
116
+ "not": {
117
+ "anyOf": [
118
+ {"required": ["trigger"]},
119
+ {"required": ["set"]},
120
+ {"required": ["created"]},
121
+ ]
122
+ },
123
+ },
124
+ {
125
+ "required": ["created"],
126
+ "not": {
127
+ "anyOf": [
128
+ {"required": ["trigger"]},
129
+ {"required": ["set"]},
130
+ {"required": ["pair"]},
131
+ ]
132
+ },
133
+ },
134
+ ],
135
+ }
@@ -1,12 +1,12 @@
1
1
  name: deepwork_jobs
2
- version: "0.4.0"
2
+ version: "0.5.0"
3
3
  summary: "DeepWork job management commands"
4
4
  description: |
5
5
  Core commands for managing DeepWork jobs. These commands help you define new multi-step
6
6
  workflows and learn from running them.
7
7
 
8
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,
9
+ asking structured questions about your workflow, understanding each step's inputs and outputs,
10
10
  and generating all necessary files.
11
11
 
12
12
  The `learn` command reflects on conversations where DeepWork jobs were run, identifies
@@ -22,6 +22,8 @@ changelog:
22
22
  changes: "Added make_new_job.sh script and templates directory; updated instructions to reference templates instead of inline examples"
23
23
  - version: "0.4.0"
24
24
  changes: "Removed implementation_summary and learning_summary outputs; simplified step outputs"
25
+ - version: "0.5.0"
26
+ 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"
25
27
 
26
28
  steps:
27
29
  - id: define
@@ -34,21 +36,15 @@ steps:
34
36
  outputs:
35
37
  - job.yml
36
38
  dependencies: []
37
- hooks:
38
- after_agent:
39
- - prompt: |
40
- Verify the job.yml output meets ALL quality criteria before completing:
41
-
42
- 1. **User Understanding**: Did you fully understand the user's workflow through interactive Q&A?
43
- 2. **Clear Inputs/Outputs**: Does every step have clearly defined inputs and outputs?
44
- 3. **Logical Dependencies**: Do step dependencies make sense and avoid circular references?
45
- 4. **Concise Summary**: Is the summary under 200 characters and descriptive?
46
- 5. **Rich Description**: Does the description provide enough context for future refinement?
47
- 6. **Valid Schema**: Does the job.yml follow the required schema (name, version, summary, steps)?
48
- 7. **File Created**: Has the job.yml file been created in `.deepwork/jobs/[job_name]/job.yml`?
49
-
50
- If ANY criterion is not met, continue working to address it.
51
- If ALL criteria are satisfied, include `<promise>✓ Quality Criteria Met</promise>` in your response.
39
+ quality_criteria:
40
+ - "**User Understanding**: Did the agent fully understand the user's workflow by asking structured questions?"
41
+ - "**Structured Questions Used**: Did the agent ask structured questions (using the AskUserQuestion tool) to gather user input?"
42
+ - "**Clear Inputs/Outputs**: Does every step have clearly defined inputs and outputs?"
43
+ - "**Logical Dependencies**: Do step dependencies make sense and avoid circular references?"
44
+ - "**Concise Summary**: Is the summary under 200 characters and descriptive?"
45
+ - "**Rich Description**: Does the description provide enough context for future refinement?"
46
+ - "**Valid Schema**: Does the job.yml follow the required schema (name, version, summary, steps)?"
47
+ - "**File Created**: Has the job.yml file been created in `.deepwork/jobs/[job_name]/job.yml`?"
52
48
 
53
49
  - id: implement
54
50
  name: "Implement Job Steps"
@@ -61,50 +57,36 @@ steps:
61
57
  - steps/
62
58
  dependencies:
63
59
  - define
64
- hooks:
65
- after_agent:
66
- - prompt: |
67
- Verify the implementation meets ALL quality criteria before completing:
68
-
69
- 1. **Directory Structure**: Is `.deepwork/jobs/[job_name]/` created correctly?
70
- 2. **Complete Instructions**: Are ALL step instruction files complete (not stubs or placeholders)?
71
- 3. **Specific & Actionable**: Are instructions tailored to each step's purpose, not generic?
72
- 4. **Output Examples**: Does each instruction file show what good output looks like?
73
- 5. **Quality Criteria**: Does each instruction file define quality criteria for its outputs?
74
- 6. **Sync Complete**: Has `deepwork sync` been run successfully?
75
- 7. **Commands Available**: Are the slash-commands generated in `.claude/commands/`?
76
- 8. **Policies Considered**: Have you thought about whether policies would benefit this job?
77
- - If relevant policies were identified, did you explain them and offer to run `/deepwork_policy.define`?
78
- - Not every job needs policies - only suggest when genuinely helpful.
79
-
80
- If ANY criterion is not met, continue working to address it.
81
- If ALL criteria are satisfied, include `<promise>✓ Quality Criteria Met</promise>` in your response.
60
+ quality_criteria:
61
+ - "**Directory Structure**: Is `.deepwork/jobs/[job_name]/` created correctly?"
62
+ - "**Complete Instructions**: Are ALL step instruction files complete (not stubs or placeholders)?"
63
+ - "**Specific & Actionable**: Are instructions tailored to each step's purpose, not generic?"
64
+ - "**Output Examples**: Does each instruction file show what good output looks like?"
65
+ - "**Quality Criteria**: Does each instruction file define quality criteria for its outputs?"
66
+ - "**Ask Structured Questions**: Do step instructions that gather user input explicitly use the phrase \"ask structured questions\"?"
67
+ - "**Sync Complete**: Has `deepwork sync` been run successfully?"
68
+ - "**Commands Available**: Are the slash-commands generated in `.claude/commands/`?"
69
+ - "**Rules Considered**: Has the agent thought about whether rules would benefit this job? If relevant rules were identified, did they explain them and offer to run `/deepwork_rules.define`? Not every job needs rules - only suggest when genuinely helpful."
82
70
 
83
71
  - id: learn
84
72
  name: "Learn from Job Execution"
85
73
  description: "Reflect on conversation to improve job instructions and capture learnings"
86
74
  instructions_file: steps/learn.md
75
+ exposed: true
87
76
  inputs:
88
77
  - name: job_name
89
78
  description: "Name of the job that was run (optional - will auto-detect from conversation)"
90
79
  outputs:
91
80
  - AGENTS.md
92
81
  dependencies: []
93
- hooks:
94
- after_agent:
95
- - prompt: |
96
- Verify the learning process meets ALL quality criteria before completing:
97
-
98
- 1. **Conversation Analyzed**: Did you review the conversation for DeepWork job executions?
99
- 2. **Confusion Identified**: Did you identify points of confusion, errors, or inefficiencies?
100
- 3. **Instructions Improved**: Were job instructions updated to address identified issues?
101
- 4. **Instructions Concise**: Are instructions free of redundancy and unnecessary verbosity?
102
- 5. **Shared Content Extracted**: Is lengthy/duplicated content extracted into referenced files?
103
- 6. **Bespoke Learnings Captured**: Were run-specific learnings added to AGENTS.md?
104
- 7. **File References Used**: Do AGENTS.md entries reference other files where appropriate?
105
- 8. **Working Folder Correct**: Is AGENTS.md in the correct working folder for the job?
106
- 9. **Generalizable Separated**: Are generalizable improvements in instructions, not AGENTS.md?
107
- 10. **Sync Complete**: Has `deepwork sync` been run if instructions were modified?
108
-
109
- If ANY criterion is not met, continue working to address it.
110
- If ALL criteria are satisfied, include `<promise>✓ Quality Criteria Met</promise>` in your response.
82
+ quality_criteria:
83
+ - "**Conversation Analyzed**: Did the agent review the conversation for DeepWork job executions?"
84
+ - "**Confusion Identified**: Did the agent identify points of confusion, errors, or inefficiencies?"
85
+ - "**Instructions Improved**: Were job instructions updated to address identified issues?"
86
+ - "**Instructions Concise**: Are instructions free of redundancy and unnecessary verbosity?"
87
+ - "**Shared Content Extracted**: Is lengthy/duplicated content extracted into referenced files?"
88
+ - "**Bespoke Learnings Captured**: Were run-specific learnings added to AGENTS.md?"
89
+ - "**File References Used**: Do AGENTS.md entries reference other files where appropriate?"
90
+ - "**Working Folder Correct**: Is AGENTS.md in the correct working folder for the job?"
91
+ - "**Generalizable Separated**: Are generalizable improvements in instructions, not AGENTS.md?"
92
+ - "**Sync Complete**: Has `deepwork sync` been run if instructions were modified?"