deepwork 0.1.0__py3-none-any.whl → 0.2.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 (31) hide show
  1. deepwork/cli/install.py +48 -0
  2. deepwork/cli/sync.py +9 -0
  3. deepwork/core/adapters.py +17 -0
  4. deepwork/core/policy_parser.py +10 -0
  5. deepwork/hooks/evaluate_policies.py +237 -20
  6. deepwork/schemas/policy_schema.py +10 -0
  7. deepwork/standard_jobs/deepwork_jobs/AGENTS.md +60 -0
  8. deepwork/standard_jobs/deepwork_jobs/job.yml +30 -22
  9. deepwork/standard_jobs/deepwork_jobs/make_new_job.sh +134 -0
  10. deepwork/standard_jobs/deepwork_jobs/steps/define.md +26 -57
  11. deepwork/standard_jobs/deepwork_jobs/steps/implement.md +43 -242
  12. deepwork/standard_jobs/deepwork_jobs/steps/learn.md +288 -0
  13. deepwork/standard_jobs/deepwork_jobs/steps/supplemental_file_references.md +40 -0
  14. deepwork/standard_jobs/deepwork_jobs/templates/agents.md.template +32 -0
  15. deepwork/standard_jobs/deepwork_jobs/templates/job.yml.example +73 -0
  16. deepwork/standard_jobs/deepwork_jobs/templates/job.yml.template +56 -0
  17. deepwork/standard_jobs/deepwork_jobs/templates/step_instruction.md.example +82 -0
  18. deepwork/standard_jobs/deepwork_jobs/templates/step_instruction.md.template +58 -0
  19. deepwork/standard_jobs/deepwork_policy/hooks/{capture_work_tree.sh → capture_prompt_work_tree.sh} +3 -2
  20. deepwork/standard_jobs/deepwork_policy/hooks/policy_stop_hook.sh +3 -19
  21. deepwork/standard_jobs/deepwork_policy/hooks/user_prompt_submit.sh +5 -6
  22. deepwork/standard_jobs/deepwork_policy/steps/define.md +22 -1
  23. deepwork/templates/default_policy.yml +53 -0
  24. {deepwork-0.1.0.dist-info → deepwork-0.2.0.dist-info}/METADATA +52 -128
  25. deepwork-0.2.0.dist-info/RECORD +49 -0
  26. deepwork/standard_jobs/deepwork_jobs/steps/refine.md +0 -447
  27. deepwork/standard_jobs/deepwork_policy/hooks/get_changed_files.sh +0 -30
  28. deepwork-0.1.0.dist-info/RECORD +0 -41
  29. {deepwork-0.1.0.dist-info → deepwork-0.2.0.dist-info}/WHEEL +0 -0
  30. {deepwork-0.1.0.dist-info → deepwork-0.2.0.dist-info}/entry_points.txt +0 -0
  31. {deepwork-0.1.0.dist-info → deepwork-0.2.0.dist-info}/licenses/LICENSE.md +0 -0
deepwork/cli/install.py CHANGED
@@ -113,6 +113,48 @@ def _create_deepwork_gitignore(deepwork_dir: Path) -> None:
113
113
  gitignore_path.write_text(gitignore_content)
114
114
 
115
115
 
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
+
116
158
  class DynamicChoice(click.Choice):
117
159
  """A Click Choice that gets its values dynamically from AgentAdapter."""
118
160
 
@@ -238,6 +280,12 @@ def _install_deepwork(platform_name: str | None, project_path: Path) -> None:
238
280
  _create_deepwork_gitignore(deepwork_dir)
239
281
  console.print(" [green]✓[/green] Created .deepwork/.gitignore")
240
282
 
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
+
241
289
  # Step 4: Load or create config.yml
242
290
  console.print("[yellow]→[/yellow] Updating configuration...")
243
291
  config_file = deepwork_dir / "config.yml"
deepwork/cli/sync.py CHANGED
@@ -117,6 +117,7 @@ def sync_commands(project_path: Path) -> None:
117
117
  # Sync each platform
118
118
  generator = CommandGenerator()
119
119
  stats = {"platforms": 0, "commands": 0, "hooks": 0}
120
+ synced_adapters: list[AgentAdapter] = []
120
121
 
121
122
  for platform_name in platforms:
122
123
  try:
@@ -157,6 +158,7 @@ def sync_commands(project_path: Path) -> None:
157
158
  console.print(f" [red]✗[/red] Failed to sync hooks: {e}")
158
159
 
159
160
  stats["platforms"] += 1
161
+ synced_adapters.append(adapter)
160
162
 
161
163
  # Summary
162
164
  console.print()
@@ -174,3 +176,10 @@ def sync_commands(project_path: Path) -> None:
174
176
 
175
177
  console.print(table)
176
178
  console.print()
179
+
180
+ # Show reload instructions for each synced platform
181
+ if synced_adapters and stats["commands"] > 0:
182
+ console.print("[bold]To use the new commands:[/bold]")
183
+ for adapter in synced_adapters:
184
+ console.print(f" [cyan]{adapter.display_name}:[/cyan] {adapter.reload_instructions}")
185
+ console.print()
deepwork/core/adapters.py CHANGED
@@ -57,6 +57,12 @@ class AgentAdapter(ABC):
57
57
  commands_dir: ClassVar[str] = "commands"
58
58
  command_template: ClassVar[str] = "command-job-step.md.jinja"
59
59
 
60
+ # Instructions for reloading commands after sync (shown to users)
61
+ # Subclasses should override with platform-specific instructions.
62
+ reload_instructions: ClassVar[str] = (
63
+ "Restart your AI assistant session to use the new commands."
64
+ )
65
+
60
66
  # Mapping from generic CommandLifecycleHook to platform-specific event names.
61
67
  # Subclasses should override this to provide platform-specific mappings.
62
68
  hook_name_mapping: ClassVar[dict[CommandLifecycleHook, str]] = {}
@@ -253,6 +259,12 @@ class ClaudeAdapter(AgentAdapter):
253
259
  display_name = "Claude Code"
254
260
  config_dir = ".claude"
255
261
 
262
+ # Claude Code doesn't have a reload command - must restart session
263
+ reload_instructions: ClassVar[str] = (
264
+ "Type 'exit' to leave your current session, then run "
265
+ "'claude --resume' (your history will be maintained)."
266
+ )
267
+
256
268
  # Claude Code uses PascalCase event names
257
269
  hook_name_mapping: ClassVar[dict[CommandLifecycleHook, str]] = {
258
270
  CommandLifecycleHook.AFTER_AGENT: "Stop",
@@ -333,6 +345,11 @@ class GeminiAdapter(AgentAdapter):
333
345
  config_dir = ".gemini"
334
346
  command_template = "command-job-step.toml.jinja"
335
347
 
348
+ # Gemini CLI can reload with /memory refresh
349
+ reload_instructions: ClassVar[str] = (
350
+ "Run '/memory refresh' to reload commands, or restart your Gemini CLI session."
351
+ )
352
+
336
353
  # Gemini CLI does NOT support command-level hooks
337
354
  # Hooks are global/project-level in settings.json, not per-command
338
355
  hook_name_mapping: ClassVar[dict[CommandLifecycleHook, str]] = {}
@@ -17,6 +17,11 @@ class PolicyParseError(Exception):
17
17
  pass
18
18
 
19
19
 
20
+ # Valid compare_to values
21
+ COMPARE_TO_VALUES = frozenset({"base", "default_tip", "prompt"})
22
+ DEFAULT_COMPARE_TO = "base"
23
+
24
+
20
25
  @dataclass
21
26
  class Policy:
22
27
  """Represents a single policy definition."""
@@ -25,6 +30,7 @@ class Policy:
25
30
  triggers: list[str] # Normalized to list
26
31
  safety: list[str] = field(default_factory=list) # Normalized to list, empty if not specified
27
32
  instructions: str = "" # Resolved content (either inline or from file)
33
+ compare_to: str = DEFAULT_COMPARE_TO # What to compare against: base, default_tip, or prompt
28
34
 
29
35
  @classmethod
30
36
  def from_dict(cls, data: dict[str, Any], base_dir: Path | None = None) -> "Policy":
@@ -74,11 +80,15 @@ class Policy:
74
80
  f"Policy '{data['name']}' must have either 'instructions' or 'instructions_file'"
75
81
  )
76
82
 
83
+ # Get compare_to (defaults to DEFAULT_COMPARE_TO)
84
+ compare_to = data.get("compare_to", DEFAULT_COMPARE_TO)
85
+
77
86
  return cls(
78
87
  name=data["name"],
79
88
  triggers=triggers,
80
89
  safety=safety,
81
90
  instructions=instructions,
91
+ compare_to=compare_to,
82
92
  )
83
93
 
84
94
 
@@ -6,12 +6,16 @@ should fire based on changed files and conversation context.
6
6
 
7
7
  Usage:
8
8
  python -m deepwork.hooks.evaluate_policies \
9
- --policy-file .deepwork.policy.yml \
10
- --changed-files "file1.py\nfile2.py"
9
+ --policy-file .deepwork.policy.yml
11
10
 
12
11
  The conversation context is read from stdin and checked for <promise> tags
13
12
  that indicate policies have already been addressed.
14
13
 
14
+ Changed files are computed based on each policy's compare_to setting:
15
+ - base: Compare to merge-base with default branch (default)
16
+ - default_tip: Two-dot diff against default branch tip
17
+ - prompt: Compare to state captured at prompt submission
18
+
15
19
  Output is JSON suitable for Claude Code Stop hooks:
16
20
  {"decision": "block", "reason": "..."} # Block stop, policies need attention
17
21
  {} # No policies fired, allow stop
@@ -20,16 +24,223 @@ Output is JSON suitable for Claude Code Stop hooks:
20
24
  import argparse
21
25
  import json
22
26
  import re
27
+ import subprocess
23
28
  import sys
24
29
  from pathlib import Path
25
30
 
26
31
  from deepwork.core.policy_parser import (
32
+ Policy,
27
33
  PolicyParseError,
28
- evaluate_policies,
34
+ evaluate_policy,
29
35
  parse_policy_file,
30
36
  )
31
37
 
32
38
 
39
+ def get_default_branch() -> str:
40
+ """
41
+ Get the default branch name (main or master).
42
+
43
+ Returns:
44
+ Default branch name, or "main" if cannot be determined.
45
+ """
46
+ # Try to get the default branch from remote HEAD
47
+ try:
48
+ result = subprocess.run(
49
+ ["git", "symbolic-ref", "refs/remotes/origin/HEAD"],
50
+ capture_output=True,
51
+ text=True,
52
+ check=True,
53
+ )
54
+ # Output is like "refs/remotes/origin/main"
55
+ return result.stdout.strip().split("/")[-1]
56
+ except subprocess.CalledProcessError:
57
+ pass
58
+
59
+ # Try common default branch names
60
+ for branch in ["main", "master"]:
61
+ try:
62
+ subprocess.run(
63
+ ["git", "rev-parse", "--verify", f"origin/{branch}"],
64
+ capture_output=True,
65
+ check=True,
66
+ )
67
+ return branch
68
+ except subprocess.CalledProcessError:
69
+ continue
70
+
71
+ # Fall back to main
72
+ return "main"
73
+
74
+
75
+ def get_changed_files_base() -> list[str]:
76
+ """
77
+ Get files changed relative to the base of the current branch.
78
+
79
+ This finds the merge-base between the current branch and the default branch,
80
+ then returns all files changed since that point.
81
+
82
+ Returns:
83
+ List of changed file paths.
84
+ """
85
+ default_branch = get_default_branch()
86
+
87
+ try:
88
+ # Get the merge-base (where current branch diverged from default)
89
+ result = subprocess.run(
90
+ ["git", "merge-base", "HEAD", f"origin/{default_branch}"],
91
+ capture_output=True,
92
+ text=True,
93
+ check=True,
94
+ )
95
+ merge_base = result.stdout.strip()
96
+
97
+ # Stage all changes so they appear in diff
98
+ subprocess.run(["git", "add", "-A"], capture_output=True, check=False)
99
+
100
+ # Get files changed since merge-base (including staged)
101
+ result = subprocess.run(
102
+ ["git", "diff", "--name-only", merge_base, "HEAD"],
103
+ capture_output=True,
104
+ text=True,
105
+ check=True,
106
+ )
107
+ committed_files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
108
+
109
+ # Also get staged changes not yet committed
110
+ result = subprocess.run(
111
+ ["git", "diff", "--name-only", "--cached"],
112
+ capture_output=True,
113
+ text=True,
114
+ check=False,
115
+ )
116
+ staged_files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
117
+
118
+ # Get untracked files
119
+ result = subprocess.run(
120
+ ["git", "ls-files", "--others", "--exclude-standard"],
121
+ capture_output=True,
122
+ text=True,
123
+ check=False,
124
+ )
125
+ untracked_files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
126
+
127
+ all_files = committed_files | staged_files | untracked_files
128
+ return sorted([f for f in all_files if f])
129
+
130
+ except subprocess.CalledProcessError:
131
+ return []
132
+
133
+
134
+ def get_changed_files_default_tip() -> list[str]:
135
+ """
136
+ Get files changed compared to the tip of the default branch.
137
+
138
+ This does a two-dot diff: what's different between HEAD and origin/default.
139
+
140
+ Returns:
141
+ List of changed file paths.
142
+ """
143
+ default_branch = get_default_branch()
144
+
145
+ try:
146
+ # Stage all changes so they appear in diff
147
+ subprocess.run(["git", "add", "-A"], capture_output=True, check=False)
148
+
149
+ # Two-dot diff against default branch tip
150
+ result = subprocess.run(
151
+ ["git", "diff", "--name-only", f"origin/{default_branch}..HEAD"],
152
+ capture_output=True,
153
+ text=True,
154
+ check=True,
155
+ )
156
+ committed_files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
157
+
158
+ # Also get staged changes not yet committed
159
+ result = subprocess.run(
160
+ ["git", "diff", "--name-only", "--cached"],
161
+ capture_output=True,
162
+ text=True,
163
+ check=False,
164
+ )
165
+ staged_files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
166
+
167
+ # Get untracked files
168
+ result = subprocess.run(
169
+ ["git", "ls-files", "--others", "--exclude-standard"],
170
+ capture_output=True,
171
+ text=True,
172
+ check=False,
173
+ )
174
+ untracked_files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
175
+
176
+ all_files = committed_files | staged_files | untracked_files
177
+ return sorted([f for f in all_files if f])
178
+
179
+ except subprocess.CalledProcessError:
180
+ return []
181
+
182
+
183
+ def get_changed_files_prompt() -> list[str]:
184
+ """
185
+ Get files changed since the prompt was submitted.
186
+
187
+ This compares against the baseline captured by capture_prompt_work_tree.sh.
188
+
189
+ Returns:
190
+ List of changed file paths.
191
+ """
192
+ baseline_path = Path(".deepwork/.last_work_tree")
193
+
194
+ try:
195
+ # Stage all changes so we can see them with --cached
196
+ subprocess.run(["git", "add", "-A"], capture_output=True, check=False)
197
+
198
+ # Get all staged files (includes what was just staged)
199
+ result = subprocess.run(
200
+ ["git", "diff", "--name-only", "--cached"],
201
+ capture_output=True,
202
+ text=True,
203
+ check=False,
204
+ )
205
+ current_files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
206
+ current_files = {f for f in current_files if f}
207
+
208
+ if baseline_path.exists():
209
+ # Read baseline and find new files
210
+ baseline_files = set(baseline_path.read_text().strip().split("\n"))
211
+ baseline_files = {f for f in baseline_files if f}
212
+ # Return files that are in current but not in baseline
213
+ new_files = current_files - baseline_files
214
+ return sorted(new_files)
215
+ else:
216
+ # No baseline, return all current changes
217
+ return sorted(current_files)
218
+
219
+ except (subprocess.CalledProcessError, OSError):
220
+ return []
221
+
222
+
223
+ def get_changed_files_for_mode(mode: str) -> list[str]:
224
+ """
225
+ Get changed files for a specific compare_to mode.
226
+
227
+ Args:
228
+ mode: One of 'base', 'default_tip', or 'prompt'
229
+
230
+ Returns:
231
+ List of changed file paths.
232
+ """
233
+ if mode == "base":
234
+ return get_changed_files_base()
235
+ elif mode == "default_tip":
236
+ return get_changed_files_default_tip()
237
+ elif mode == "prompt":
238
+ return get_changed_files_prompt()
239
+ else:
240
+ # Unknown mode, fall back to base
241
+ return get_changed_files_base()
242
+
243
+
33
244
  def extract_promise_tags(text: str) -> set[str]:
34
245
  """
35
246
  Extract policy names from <promise> tags in text.
@@ -87,23 +298,9 @@ def main() -> None:
87
298
  required=True,
88
299
  help="Path to .deepwork.policy.yml file",
89
300
  )
90
- parser.add_argument(
91
- "--changed-files",
92
- type=str,
93
- required=True,
94
- help="Newline-separated list of changed files",
95
- )
96
301
 
97
302
  args = parser.parse_args()
98
303
 
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
304
  # Check if policy file exists
108
305
  policy_path = Path(args.policy_file)
109
306
  if not policy_path.exists():
@@ -122,7 +319,7 @@ def main() -> None:
122
319
  # Extract promise tags from conversation
123
320
  promised_policies = extract_promise_tags(conversation_context)
124
321
 
125
- # Parse and evaluate policies
322
+ # Parse policies
126
323
  try:
127
324
  policies = parse_policy_file(policy_path)
128
325
  except PolicyParseError as e:
@@ -136,8 +333,28 @@ def main() -> None:
136
333
  print("{}")
137
334
  return
138
335
 
139
- # Evaluate which policies fire
140
- fired_policies = evaluate_policies(policies, changed_files, promised_policies)
336
+ # Group policies by compare_to mode to minimize git calls
337
+ policies_by_mode: dict[str, list[Policy]] = {}
338
+ for policy in policies:
339
+ mode = policy.compare_to
340
+ if mode not in policies_by_mode:
341
+ policies_by_mode[mode] = []
342
+ policies_by_mode[mode].append(policy)
343
+
344
+ # Get changed files for each mode and evaluate policies
345
+ fired_policies: list[Policy] = []
346
+ for mode, mode_policies in policies_by_mode.items():
347
+ changed_files = get_changed_files_for_mode(mode)
348
+ if not changed_files:
349
+ continue
350
+
351
+ for policy in mode_policies:
352
+ # Skip if already promised
353
+ if policy.name in promised_policies:
354
+ continue
355
+ # Evaluate this policy
356
+ if evaluate_policy(policy, changed_files):
357
+ fired_policies.append(policy)
141
358
 
142
359
  if not fired_policies:
143
360
  # No policies fired
@@ -58,6 +58,16 @@ POLICY_SCHEMA: dict[str, Any] = {
58
58
  "minLength": 1,
59
59
  "description": "Path to a file containing instructions (alternative to inline instructions)",
60
60
  },
61
+ "compare_to": {
62
+ "type": "string",
63
+ "enum": ["base", "default_tip", "prompt"],
64
+ "description": (
65
+ "What to compare against when detecting changed files. "
66
+ "'base' (default) compares to the base of the current branch. "
67
+ "'default_tip' compares to the tip of the default branch. "
68
+ "'prompt' compares to the state at the start of the prompt."
69
+ ),
70
+ },
61
71
  },
62
72
  "oneOf": [
63
73
  {"required": ["instructions"]},
@@ -0,0 +1,60 @@
1
+ # Project Context for deepwork_jobs
2
+
3
+ This is the source of truth for the `deepwork_jobs` standard job.
4
+
5
+ ## Codebase Structure
6
+
7
+ - Source location: `src/deepwork/standard_jobs/deepwork_jobs/`
8
+ - Working copy: `.deepwork/jobs/deepwork_jobs/`
9
+ - Templates: `templates/` directory within each location
10
+
11
+ ## Dual Location Maintenance
12
+
13
+ **Important**: This job exists in two locations that must be kept in sync:
14
+
15
+ 1. **Source of truth**: `src/deepwork/standard_jobs/deepwork_jobs/`
16
+ - This is where changes should be made first
17
+ - Tracked in version control
18
+
19
+ 2. **Working copy**: `.deepwork/jobs/deepwork_jobs/`
20
+ - Must be updated after changes to source
21
+ - Used by `deepwork sync` to generate commands
22
+
23
+ After making changes to the source, copy files to the working copy:
24
+ ```bash
25
+ cp src/deepwork/standard_jobs/deepwork_jobs/job.yml .deepwork/jobs/deepwork_jobs/
26
+ cp src/deepwork/standard_jobs/deepwork_jobs/steps/*.md .deepwork/jobs/deepwork_jobs/steps/
27
+ cp -r src/deepwork/standard_jobs/deepwork_jobs/templates/* .deepwork/jobs/deepwork_jobs/templates/
28
+ ```
29
+
30
+ ## File Organization
31
+
32
+ ```
33
+ deepwork_jobs/
34
+ ├── AGENTS.md # This file
35
+ ├── job.yml # Job definition
36
+ ├── make_new_job.sh # Script to create new job structure
37
+ ├── steps/
38
+ │ ├── define.md # Define step instructions
39
+ │ ├── implement.md # Implement step instructions
40
+ │ ├── learn.md # Learn step instructions
41
+ │ └── supplemental_file_references.md # Reference documentation
42
+ └── templates/
43
+ ├── job.yml.template # Job spec structure
44
+ ├── step_instruction.md.template # Step instruction structure
45
+ ├── agents.md.template # AGENTS.md structure
46
+ ├── job.yml.example # Complete job example
47
+ └── step_instruction.md.example # Complete step example
48
+ ```
49
+
50
+ ## Version Management
51
+
52
+ - Version is tracked in `job.yml`
53
+ - Bump patch version (0.0.x) for instruction improvements
54
+ - Bump minor version (0.x.0) for new features or structural changes
55
+ - Always update changelog when bumping version
56
+
57
+ ## Last Updated
58
+
59
+ - Date: 2026-01-15
60
+ - From conversation about: Adding make_new_job.sh script and templates directory
@@ -1,20 +1,27 @@
1
1
  name: deepwork_jobs
2
- version: "0.1.0"
2
+ version: "0.4.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
- workflows and refine existing ones.
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
9
  asking detailed questions about your workflow, understanding each step's inputs and outputs,
10
10
  and generating all necessary files.
11
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.
12
+ The `learn` command reflects on conversations where DeepWork jobs were run, identifies
13
+ confusion or inefficiencies, and improves job instructions. It also captures bespoke
14
+ learnings specific to the current run into AGENTS.md files in the working folder.
14
15
 
15
16
  changelog:
16
17
  - version: "0.1.0"
17
18
  changes: "Initial version"
19
+ - version: "0.2.0"
20
+ changes: "Replaced refine command with learn command for conversation-driven improvement"
21
+ - version: "0.3.0"
22
+ changes: "Added make_new_job.sh script and templates directory; updated instructions to reference templates instead of inline examples"
23
+ - version: "0.4.0"
24
+ changes: "Removed implementation_summary and learning_summary outputs; simplified step outputs"
18
25
 
19
26
  steps:
20
27
  - id: define
@@ -51,7 +58,7 @@ steps:
51
58
  - file: job.yml
52
59
  from_step: define
53
60
  outputs:
54
- - implementation_summary.md
61
+ - steps/
55
62
  dependencies:
56
63
  - define
57
64
  hooks:
@@ -66,37 +73,38 @@ steps:
66
73
  5. **Quality Criteria**: Does each instruction file define quality criteria for its outputs?
67
74
  6. **Sync Complete**: Has `deepwork sync` been run successfully?
68
75
  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?
76
+ 8. **Policies Considered**: Have you thought about whether policies would benefit this job?
71
77
  - If relevant policies were identified, did you explain them and offer to run `/deepwork_policy.define`?
72
78
  - Not every job needs policies - only suggest when genuinely helpful.
73
79
 
74
80
  If ANY criterion is not met, continue working to address it.
75
81
  If ALL criteria are satisfied, include `<promise>✓ Quality Criteria Met</promise>` in your response.
76
82
 
77
- - id: refine
78
- name: "Refine Existing Job"
79
- description: "Modify an existing job definition"
80
- instructions_file: steps/refine.md
83
+ - id: learn
84
+ name: "Learn from Job Execution"
85
+ description: "Reflect on conversation to improve job instructions and capture learnings"
86
+ instructions_file: steps/learn.md
81
87
  inputs:
82
88
  - name: job_name
83
- description: "Name of the job to refine"
89
+ description: "Name of the job that was run (optional - will auto-detect from conversation)"
84
90
  outputs:
85
- - job.yml
91
+ - AGENTS.md
86
92
  dependencies: []
87
93
  hooks:
88
94
  after_agent:
89
95
  - prompt: |
90
- Verify the refinement meets ALL quality criteria before completing:
96
+ Verify the learning process meets ALL quality criteria before completing:
91
97
 
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?
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?
100
108
 
101
109
  If ANY criterion is not met, continue working to address it.
102
110
  If ALL criteria are satisfied, include `<promise>✓ Quality Criteria Met</promise>` in your response.