deepwork 0.4.0__py3-none-any.whl → 0.7.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 (67) hide show
  1. deepwork/__init__.py +1 -1
  2. deepwork/cli/hook.py +3 -4
  3. deepwork/cli/install.py +70 -117
  4. deepwork/cli/main.py +2 -2
  5. deepwork/cli/serve.py +133 -0
  6. deepwork/cli/sync.py +93 -58
  7. deepwork/core/adapters.py +91 -98
  8. deepwork/core/generator.py +19 -386
  9. deepwork/core/hooks_syncer.py +1 -1
  10. deepwork/core/parser.py +270 -1
  11. deepwork/hooks/README.md +0 -44
  12. deepwork/hooks/__init__.py +3 -6
  13. deepwork/hooks/check_version.sh +54 -21
  14. deepwork/mcp/__init__.py +23 -0
  15. deepwork/mcp/quality_gate.py +347 -0
  16. deepwork/mcp/schemas.py +263 -0
  17. deepwork/mcp/server.py +253 -0
  18. deepwork/mcp/state.py +422 -0
  19. deepwork/mcp/tools.py +394 -0
  20. deepwork/schemas/job.schema.json +347 -0
  21. deepwork/schemas/job_schema.py +27 -239
  22. deepwork/standard_jobs/deepwork_jobs/doc_specs/job_spec.md +9 -15
  23. deepwork/standard_jobs/deepwork_jobs/job.yml +146 -46
  24. deepwork/standard_jobs/deepwork_jobs/steps/define.md +100 -33
  25. deepwork/standard_jobs/deepwork_jobs/steps/errata.md +154 -0
  26. deepwork/standard_jobs/deepwork_jobs/steps/fix_jobs.md +207 -0
  27. deepwork/standard_jobs/deepwork_jobs/steps/fix_settings.md +177 -0
  28. deepwork/standard_jobs/deepwork_jobs/steps/implement.md +22 -138
  29. deepwork/standard_jobs/deepwork_jobs/steps/iterate.md +221 -0
  30. deepwork/standard_jobs/deepwork_jobs/steps/learn.md +2 -26
  31. deepwork/standard_jobs/deepwork_jobs/steps/test.md +154 -0
  32. deepwork/standard_jobs/deepwork_jobs/templates/job.yml.template +2 -0
  33. deepwork/templates/claude/AGENTS.md +38 -0
  34. deepwork/templates/claude/settings.json +16 -0
  35. deepwork/templates/claude/skill-deepwork.md.jinja +37 -0
  36. deepwork/templates/gemini/skill-deepwork.md.jinja +37 -0
  37. deepwork-0.7.0.dist-info/METADATA +317 -0
  38. deepwork-0.7.0.dist-info/RECORD +64 -0
  39. deepwork/cli/rules.py +0 -32
  40. deepwork/core/command_executor.py +0 -190
  41. deepwork/core/pattern_matcher.py +0 -271
  42. deepwork/core/rules_parser.py +0 -559
  43. deepwork/core/rules_queue.py +0 -321
  44. deepwork/hooks/rules_check.py +0 -759
  45. deepwork/schemas/rules_schema.py +0 -135
  46. deepwork/standard_jobs/deepwork_jobs/steps/review_job_spec.md +0 -208
  47. deepwork/standard_jobs/deepwork_jobs/templates/doc_spec.md.example +0 -86
  48. deepwork/standard_jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh +0 -38
  49. deepwork/standard_jobs/deepwork_rules/hooks/global_hooks.yml +0 -8
  50. deepwork/standard_jobs/deepwork_rules/hooks/user_prompt_submit.sh +0 -16
  51. deepwork/standard_jobs/deepwork_rules/job.yml +0 -49
  52. deepwork/standard_jobs/deepwork_rules/rules/.gitkeep +0 -13
  53. deepwork/standard_jobs/deepwork_rules/rules/api-documentation-sync.md.example +0 -10
  54. deepwork/standard_jobs/deepwork_rules/rules/readme-documentation.md.example +0 -10
  55. deepwork/standard_jobs/deepwork_rules/rules/security-review.md.example +0 -11
  56. deepwork/standard_jobs/deepwork_rules/rules/skill-md-validation.md +0 -46
  57. deepwork/standard_jobs/deepwork_rules/rules/source-test-pairing.md.example +0 -13
  58. deepwork/standard_jobs/deepwork_rules/steps/define.md +0 -249
  59. deepwork/templates/claude/skill-job-meta.md.jinja +0 -77
  60. deepwork/templates/claude/skill-job-step.md.jinja +0 -251
  61. deepwork/templates/gemini/skill-job-meta.toml.jinja +0 -76
  62. deepwork/templates/gemini/skill-job-step.toml.jinja +0 -162
  63. deepwork-0.4.0.dist-info/METADATA +0 -381
  64. deepwork-0.4.0.dist-info/RECORD +0 -71
  65. {deepwork-0.4.0.dist-info → deepwork-0.7.0.dist-info}/WHEEL +0 -0
  66. {deepwork-0.4.0.dist-info → deepwork-0.7.0.dist-info}/entry_points.txt +0 -0
  67. {deepwork-0.4.0.dist-info → deepwork-0.7.0.dist-info}/licenses/LICENSE.md +0 -0
deepwork/core/adapters.py CHANGED
@@ -55,12 +55,6 @@ class AgentAdapter(ABC):
55
55
  display_name: ClassVar[str]
56
56
  config_dir: ClassVar[str]
57
57
  skills_dir: ClassVar[str] = "skills"
58
- skill_template: ClassVar[str] = "skill-job-step.md.jinja"
59
- meta_skill_template: ClassVar[str] = "skill-job-meta.md.jinja"
60
-
61
- # Instructions for reloading skills after sync (shown to users)
62
- # Subclasses should override with platform-specific instructions.
63
- reload_instructions: ClassVar[str] = "Restart your AI assistant session to use the new skills."
64
58
 
65
59
  # Mapping from generic SkillLifecycleHook to platform-specific event names.
66
60
  # Subclasses should override this to provide platform-specific mappings.
@@ -152,38 +146,6 @@ class AgentAdapter(ABC):
152
146
  raise AdapterError("No project root specified")
153
147
  return root / self.config_dir / self.skills_dir
154
148
 
155
- def get_meta_skill_filename(self, job_name: str) -> str:
156
- """
157
- Get the filename for a job's meta-skill.
158
-
159
- The meta-skill is the primary user interface for a job.
160
- Can be overridden for different file formats.
161
-
162
- Args:
163
- job_name: Name of the job
164
-
165
- Returns:
166
- Meta-skill filename (e.g., "job_name/SKILL.md" for Claude)
167
- """
168
- return f"{job_name}/SKILL.md"
169
-
170
- def get_step_skill_filename(self, job_name: str, step_id: str, exposed: bool = False) -> str:
171
- """
172
- Get the filename for a step skill.
173
-
174
- All step skills use the same filename format. The exposed parameter
175
- is used for template context (user-invocable frontmatter setting).
176
-
177
- Args:
178
- job_name: Name of the job
179
- step_id: ID of the step
180
- exposed: If True, skill is user-invocable (for template context). Default: False.
181
-
182
- Returns:
183
- Skill filename (e.g., "job_name.step_id/SKILL.md" for Claude)
184
- """
185
- return f"{job_name}.{step_id}/SKILL.md"
186
-
187
149
  def detect(self, project_root: Path | None = None) -> bool:
188
150
  """
189
151
  Check if this platform is available in the project.
@@ -260,6 +222,22 @@ class AgentAdapter(ABC):
260
222
  # Default implementation does nothing - subclasses can override
261
223
  return 0
262
224
 
225
+ def register_mcp_server(self, project_path: Path) -> bool:
226
+ """
227
+ Register the DeepWork MCP server with the platform.
228
+
229
+ Args:
230
+ project_path: Path to project root
231
+
232
+ Returns:
233
+ True if server was registered, False if already registered
234
+
235
+ Raises:
236
+ AdapterError: If registration fails
237
+ """
238
+ # Default implementation does nothing - subclasses can override
239
+ return False
240
+
263
241
 
264
242
  def _hook_already_present(hooks: list[dict[str, Any]], script_path: str) -> bool:
265
243
  """Check if a hook with the given script path is already in the list."""
@@ -296,12 +274,6 @@ class ClaudeAdapter(AgentAdapter):
296
274
  display_name = "Claude Code"
297
275
  config_dir = ".claude"
298
276
 
299
- # Claude Code doesn't have a reload command - must restart session
300
- reload_instructions: ClassVar[str] = (
301
- "Type 'exit' to leave your current session, then run "
302
- "'claude --resume' (your history will be maintained)."
303
- )
304
-
305
277
  # Claude Code uses PascalCase event names
306
278
  hook_name_mapping: ClassVar[dict[SkillLifecycleHook, str]] = {
307
279
  SkillLifecycleHook.AFTER_AGENT: "Stop",
@@ -443,13 +415,25 @@ class ClaudeAdapter(AgentAdapter):
443
415
  return True
444
416
  return False
445
417
 
418
+ def _get_settings_template_path(self) -> Path:
419
+ """Get the path to the settings.json template for this adapter."""
420
+ return Path(__file__).parent.parent / "templates" / self.name / "settings.json"
421
+
422
+ def _load_required_permissions(self) -> list[str]:
423
+ """Load required permissions from the settings template file."""
424
+ settings_template = self._get_settings_template_path()
425
+ with open(settings_template, encoding="utf-8") as f:
426
+ template_settings = json.load(f)
427
+ result: list[str] = template_settings["permissions"]["allow"]
428
+ return result
429
+
446
430
  def sync_permissions(self, project_path: Path) -> int:
447
431
  """
448
432
  Sync required permissions to Claude Code settings.json.
449
433
 
450
- Adds permissions for:
451
- - .deepwork/** - full access to deepwork directory
452
- - All deepwork CLI commands (deepwork:*)
434
+ Loads permissions from the settings template file at
435
+ templates/claude/settings.json and merges them into the
436
+ project's .claude/settings.json.
453
437
 
454
438
  Args:
455
439
  project_path: Path to project root
@@ -460,16 +444,7 @@ class ClaudeAdapter(AgentAdapter):
460
444
  Raises:
461
445
  AdapterError: If sync fails
462
446
  """
463
- # Define required permissions for DeepWork functionality
464
- # Uses ./ prefix for paths relative to project root (per Claude Code docs)
465
- required_permissions = [
466
- # Full access to .deepwork directory
467
- "Read(./.deepwork/**)",
468
- "Edit(./.deepwork/**)",
469
- "Write(./.deepwork/**)",
470
- # All deepwork CLI commands
471
- "Bash(deepwork:*)",
472
- ]
447
+ required_permissions = self._load_required_permissions()
473
448
 
474
449
  # Load settings once, add all permissions, then save once
475
450
  settings = self._load_settings(project_path)
@@ -553,6 +528,65 @@ class ClaudeAdapter(AgentAdapter):
553
528
 
554
529
  return None
555
530
 
531
+ def register_mcp_server(self, project_path: Path) -> bool:
532
+ """
533
+ Register the DeepWork MCP server in .mcp.json at project root.
534
+
535
+ Claude Code reads MCP server configurations from .mcp.json (project scope),
536
+ not from settings.json. This method assumes the `deepwork` command is
537
+ available in the user's PATH.
538
+
539
+ Args:
540
+ project_path: Path to project root
541
+
542
+ Returns:
543
+ True if server was registered or updated, False if no changes needed
544
+
545
+ Raises:
546
+ AdapterError: If registration fails
547
+ """
548
+ mcp_file = project_path / ".mcp.json"
549
+
550
+ # Load existing .mcp.json or create new
551
+ existing_config: dict[str, Any] = {}
552
+ if mcp_file.exists():
553
+ try:
554
+ with open(mcp_file, encoding="utf-8") as f:
555
+ existing_config = json.load(f)
556
+ except (json.JSONDecodeError, OSError) as e:
557
+ raise AdapterError(f"Failed to read .mcp.json: {e}") from e
558
+
559
+ # Initialize mcpServers if not present
560
+ if "mcpServers" not in existing_config:
561
+ existing_config["mcpServers"] = {}
562
+
563
+ # Build the new MCP server config
564
+ # Assume deepwork is available in PATH
565
+ new_server_config = {
566
+ "command": "deepwork",
567
+ "args": ["serve", "--path", "."],
568
+ }
569
+
570
+ # Check if already registered with same config
571
+ existing_server = existing_config["mcpServers"].get("deepwork", {})
572
+ if (
573
+ existing_server.get("command") == new_server_config["command"]
574
+ and existing_server.get("args") == new_server_config["args"]
575
+ ):
576
+ return False
577
+
578
+ # Register or update the DeepWork MCP server
579
+ existing_config["mcpServers"]["deepwork"] = new_server_config
580
+
581
+ # Write .mcp.json
582
+ try:
583
+ with open(mcp_file, "w", encoding="utf-8") as f:
584
+ json.dump(existing_config, f, indent=2)
585
+ except OSError as e:
586
+ raise AdapterError(f"Failed to write .mcp.json: {e}") from e
587
+
588
+ return True
589
+
556
590
 
557
591
  class GeminiAdapter(AgentAdapter):
558
592
  """Adapter for Gemini CLI.
@@ -570,52 +604,11 @@ class GeminiAdapter(AgentAdapter):
570
604
  name = "gemini"
571
605
  display_name = "Gemini CLI"
572
606
  config_dir = ".gemini"
573
- skill_template = "skill-job-step.toml.jinja"
574
- meta_skill_template = "skill-job-meta.toml.jinja"
575
-
576
- # Gemini CLI can reload with /memory refresh
577
- reload_instructions: ClassVar[str] = (
578
- "Run '/memory refresh' to reload skills, or restart your Gemini CLI session."
579
- )
580
607
 
581
608
  # Gemini CLI does NOT support skill-level hooks
582
609
  # Hooks are global/project-level in settings.json, not per-skill
583
610
  hook_name_mapping: ClassVar[dict[SkillLifecycleHook, str]] = {}
584
611
 
585
- def get_meta_skill_filename(self, job_name: str) -> str:
586
- """
587
- Get the filename for a Gemini job's meta-skill.
588
-
589
- Gemini uses TOML files and colon namespacing via subdirectories.
590
- For job "my_job", creates: my_job/index.toml
591
-
592
- Args:
593
- job_name: Name of the job
594
-
595
- Returns:
596
- Meta-skill filename path (e.g., "my_job/index.toml")
597
- """
598
- return f"{job_name}/index.toml"
599
-
600
- def get_step_skill_filename(self, job_name: str, step_id: str, exposed: bool = False) -> str:
601
- """
602
- Get the filename for a Gemini step skill.
603
-
604
- Gemini uses TOML files and colon namespacing via subdirectories.
605
- All step skills use the same filename format. The exposed parameter
606
- is used for template context (user-invocable setting).
607
- For job "my_job" and step "step_one", creates: my_job/step_one.toml
608
-
609
- Args:
610
- job_name: Name of the job
611
- step_id: ID of the step
612
- exposed: If True, skill is user-invocable (for template context). Default: False.
613
-
614
- Returns:
615
- Skill filename path (e.g., "my_job/step_one.toml")
616
- """
617
- return f"{job_name}/{step_id}.toml"
618
-
619
612
  def sync_hooks(self, project_path: Path, hooks: dict[str, list[dict[str, Any]]]) -> int:
620
613
  """
621
614
  Sync hooks to Gemini CLI settings.