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/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """DeepWork - Framework for enabling AI agents to perform complex, multi-step work tasks."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.7.0"
4
4
  __author__ = "DeepWork Contributors"
5
5
 
6
6
  __all__ = [
deepwork/cli/hook.py CHANGED
@@ -5,7 +5,6 @@ instead of `python -m deepwork.hooks.*`, which works regardless of how
5
5
  deepwork was installed (flake, pipx, uv, etc.).
6
6
 
7
7
  Usage:
8
- deepwork hook rules_check
9
8
  deepwork hook <hook_name>
10
9
 
11
10
  This is meant to be called from hook wrapper scripts (claude_hook.sh, gemini_hook.sh).
@@ -32,14 +31,14 @@ def hook(hook_name: str) -> None:
32
31
  """
33
32
  Run a DeepWork hook by name.
34
33
 
35
- HOOK_NAME: Name of the hook to run (e.g., 'rules_check')
34
+ HOOK_NAME: Name of the hook to run (e.g., 'check_version')
36
35
 
37
36
  This command imports and runs the hook module from deepwork.hooks.{hook_name}.
38
37
  The hook receives stdin input and outputs to stdout, following the hook protocol.
39
38
 
40
39
  Examples:
41
- deepwork hook rules_check
42
- echo '{}' | deepwork hook rules_check
40
+ deepwork hook check_version
41
+ echo '{}' | deepwork hook my_hook
43
42
  """
44
43
  try:
45
44
  # Import the hook module
deepwork/cli/install.py CHANGED
@@ -21,6 +21,39 @@ class InstallError(Exception):
21
21
  pass
22
22
 
23
23
 
24
+ def _install_schemas(schemas_dir: Path, project_path: Path) -> None:
25
+ """
26
+ Install JSON schemas to the project's .deepwork/schemas directory.
27
+
28
+ Args:
29
+ schemas_dir: Path to .deepwork/schemas directory
30
+ project_path: Path to project root (for relative path display)
31
+
32
+ Raises:
33
+ InstallError: If installation fails
34
+ """
35
+ # Find the source schemas directory
36
+ source_schemas_dir = Path(__file__).parent.parent / "schemas"
37
+
38
+ if not source_schemas_dir.exists():
39
+ raise InstallError(
40
+ f"Schemas directory not found at {source_schemas_dir}. "
41
+ "DeepWork installation may be corrupted."
42
+ )
43
+
44
+ # Copy JSON schema files
45
+ try:
46
+ for schema_file in source_schemas_dir.glob("*.json"):
47
+ target_file = schemas_dir / schema_file.name
48
+ shutil.copy(schema_file, target_file)
49
+ fix_permissions(target_file)
50
+ console.print(
51
+ f" [green]✓[/green] Installed schema {schema_file.name} ({target_file.relative_to(project_path)})"
52
+ )
53
+ except Exception as e:
54
+ raise InstallError(f"Failed to install schemas: {e}") from e
55
+
56
+
24
57
  def _inject_standard_job(job_name: str, jobs_dir: Path, project_path: Path) -> None:
25
58
  """
26
59
  Inject a standard job definition into the project.
@@ -88,20 +121,6 @@ def _inject_deepwork_jobs(jobs_dir: Path, project_path: Path) -> None:
88
121
  _inject_standard_job("deepwork_jobs", jobs_dir, project_path)
89
122
 
90
123
 
91
- def _inject_deepwork_rules(jobs_dir: Path, project_path: Path) -> None:
92
- """
93
- Inject the deepwork_rules job definition into the project.
94
-
95
- Args:
96
- jobs_dir: Path to .deepwork/jobs directory
97
- project_path: Path to project root (for relative path display)
98
-
99
- Raises:
100
- InstallError: If injection fails
101
- """
102
- _inject_standard_job("deepwork_rules", jobs_dir, project_path)
103
-
104
-
105
124
  def _create_deepwork_gitignore(deepwork_dir: Path) -> None:
106
125
  """
107
126
  Create .gitignore file in .deepwork/ directory.
@@ -149,89 +168,6 @@ def _create_tmp_directory(deepwork_dir: Path) -> None:
149
168
  )
150
169
 
151
170
 
152
- def _create_rules_directory(project_path: Path) -> bool:
153
- """
154
- Create the v2 rules directory structure with example templates.
155
-
156
- Creates .deepwork/rules/ with example rule files that users can customize.
157
- Only creates the directory if it doesn't already exist.
158
-
159
- Args:
160
- project_path: Path to the project root
161
-
162
- Returns:
163
- True if the directory was created, False if it already existed
164
- """
165
- rules_dir = project_path / ".deepwork" / "rules"
166
-
167
- if rules_dir.exists():
168
- return False
169
-
170
- # Create the rules directory
171
- ensure_dir(rules_dir)
172
-
173
- # Copy example rule templates from the deepwork_rules standard job
174
- example_rules_dir = Path(__file__).parent.parent / "standard_jobs" / "deepwork_rules" / "rules"
175
-
176
- if example_rules_dir.exists():
177
- # Copy all .example files
178
- for example_file in example_rules_dir.glob("*.md.example"):
179
- dest_file = rules_dir / example_file.name
180
- shutil.copy(example_file, dest_file)
181
- # Fix permissions for copied rule template
182
- fix_permissions(dest_file)
183
-
184
- # Create a README file explaining the rules system
185
- readme_content = """# DeepWork Rules
186
-
187
- Rules are automated guardrails that trigger when specific files change during
188
- AI agent sessions. They help ensure documentation stays current, security reviews
189
- happen, and team guidelines are followed.
190
-
191
- ## Getting Started
192
-
193
- 1. Copy an example file and rename it (remove the `.example` suffix):
194
- ```
195
- cp readme-documentation.md.example readme-documentation.md
196
- ```
197
-
198
- 2. Edit the file to match your project's patterns
199
-
200
- 3. The rule will automatically trigger when matching files change
201
-
202
- ## Rule Format
203
-
204
- Rules use YAML frontmatter in markdown files:
205
-
206
- ```markdown
207
- ---
208
- name: Rule Name
209
- trigger: "pattern/**/*"
210
- safety: "optional/pattern"
211
- ---
212
- Instructions in markdown here.
213
- ```
214
-
215
- ## Detection Modes
216
-
217
- - **trigger/safety**: Fire when trigger matches, unless safety also matches
218
- - **set**: Bidirectional file correspondence (e.g., source + test)
219
- - **pair**: Directional correspondence (e.g., API code -> docs)
220
-
221
- ## Documentation
222
-
223
- See `doc/rules_syntax.md` in the DeepWork repository for full syntax documentation.
224
-
225
- ## Creating Rules Interactively
226
-
227
- Use `/deepwork_rules.define` to create new rules with guidance.
228
- """
229
- readme_path = rules_dir / "README.md"
230
- readme_path.write_text(readme_content)
231
-
232
- return True
233
-
234
-
235
171
  class DynamicChoice(click.Choice):
236
172
  """A Click Choice that gets its values dynamically from AgentAdapter."""
237
173
 
@@ -346,30 +282,29 @@ def _install_deepwork(platform_name: str | None, project_path: Path) -> None:
346
282
  deepwork_dir = project_path / ".deepwork"
347
283
  jobs_dir = deepwork_dir / "jobs"
348
284
  doc_specs_dir = deepwork_dir / "doc_specs"
285
+ schemas_dir = deepwork_dir / "schemas"
349
286
  ensure_dir(deepwork_dir)
350
287
  ensure_dir(jobs_dir)
351
288
  ensure_dir(doc_specs_dir)
289
+ ensure_dir(schemas_dir)
352
290
  console.print(f" [green]✓[/green] Created {deepwork_dir.relative_to(project_path)}/")
353
291
 
354
- # Step 3b: Inject standard jobs (core job definitions)
292
+ # Step 3b: Install schemas
293
+ console.print("[yellow]→[/yellow] Installing schemas...")
294
+ _install_schemas(schemas_dir, project_path)
295
+
296
+ # Step 3c: Inject standard jobs (core job definitions)
355
297
  console.print("[yellow]→[/yellow] Installing core job definitions...")
356
298
  _inject_deepwork_jobs(jobs_dir, project_path)
357
- _inject_deepwork_rules(jobs_dir, project_path)
358
299
 
359
- # Step 3c: Create .gitignore for temporary files
300
+ # Step 3d: Create .gitignore for temporary files
360
301
  _create_deepwork_gitignore(deepwork_dir)
361
302
  console.print(" [green]✓[/green] Created .deepwork/.gitignore")
362
303
 
363
- # Step 3d: Create tmp directory with .gitkeep file for version control
304
+ # Step 3e: Create tmp directory with .gitkeep file for version control
364
305
  _create_tmp_directory(deepwork_dir)
365
306
  console.print(" [green]✓[/green] Created .deepwork/tmp/.gitkeep")
366
307
 
367
- # Step 3e: Create rules directory with v2 templates
368
- if _create_rules_directory(project_path):
369
- console.print(" [green]✓[/green] Created .deepwork/rules/ with example templates")
370
- else:
371
- console.print(" [dim]•[/dim] .deepwork/rules/ already exists")
372
-
373
308
  # Step 4: Load or create config.yml
374
309
  console.print("[yellow]→[/yellow] Updating configuration...")
375
310
  config_file = deepwork_dir / "config.yml"
@@ -402,7 +337,17 @@ def _install_deepwork(platform_name: str | None, project_path: Path) -> None:
402
337
  save_yaml(config_file, config_data)
403
338
  console.print(f" [green]✓[/green] Updated {config_file.relative_to(project_path)}")
404
339
 
405
- # Step 5: Run sync to generate skills
340
+ # Step 5: Register MCP server for each platform
341
+ console.print("[yellow]→[/yellow] Registering MCP server...")
342
+ for adapter in detected_adapters:
343
+ if adapter.register_mcp_server(project_path):
344
+ console.print(f" [green]✓[/green] Registered MCP server for {adapter.display_name}")
345
+ else:
346
+ console.print(
347
+ f" [dim]•[/dim] MCP server already registered for {adapter.display_name}"
348
+ )
349
+
350
+ # Step 6: Run sync to generate skills
406
351
  console.print()
407
352
  console.print("[yellow]→[/yellow] Running sync to generate skills...")
408
353
  console.print()
@@ -410,18 +355,26 @@ def _install_deepwork(platform_name: str | None, project_path: Path) -> None:
410
355
  from deepwork.cli.sync import sync_skills
411
356
 
412
357
  try:
413
- sync_skills(project_path)
358
+ sync_result = sync_skills(project_path)
414
359
  except Exception as e:
415
360
  raise InstallError(f"Failed to sync skills: {e}") from e
416
361
 
417
- # Success message
362
+ # Success or warning message
418
363
  console.print()
419
364
  platform_names = ", ".join(a.display_name for a in detected_adapters)
420
- console.print(
421
- f"[bold green]✓ DeepWork installed successfully for {platform_names}![/bold green]"
422
- )
423
- console.print()
424
- console.print("[bold]Next steps:[/bold]")
425
- console.print(" 1. Start your agent CLI (ex. [cyan]claude[/cyan] or [cyan]gemini[/cyan])")
426
- console.print(" 2. Define your first job using the command [cyan]/deepwork_jobs.define[/cyan]")
365
+
366
+ if sync_result.has_warnings:
367
+ console.print("[bold yellow]⚠ You should repair your DeepWork install[/bold yellow]")
368
+ console.print()
369
+ console.print("[bold]To fix issues:[/bold]")
370
+ console.print(" 1. Start your agent CLI (ex. [cyan]claude[/cyan] or [cyan]gemini[/cyan])")
371
+ console.print(" 2. Run [cyan]/deepwork repair[/cyan]")
372
+ else:
373
+ console.print(
374
+ f"[bold green]✓ DeepWork installed successfully for {platform_names}![/bold green]"
375
+ )
376
+ console.print()
377
+ console.print("[bold]Next steps:[/bold]")
378
+ console.print(" 1. Start your agent CLI (ex. [cyan]claude[/cyan] or [cyan]gemini[/cyan])")
379
+ console.print(" 2. Define your first job using the command [cyan]/deepwork_jobs[/cyan]")
427
380
  console.print()
deepwork/cli/main.py CHANGED
@@ -16,13 +16,13 @@ def cli() -> None:
16
16
  # Import commands
17
17
  from deepwork.cli.hook import hook # noqa: E402
18
18
  from deepwork.cli.install import install # noqa: E402
19
- from deepwork.cli.rules import rules # noqa: E402
19
+ from deepwork.cli.serve import serve # noqa: E402
20
20
  from deepwork.cli.sync import sync # noqa: E402
21
21
 
22
22
  cli.add_command(install)
23
23
  cli.add_command(sync)
24
24
  cli.add_command(hook)
25
- cli.add_command(rules)
25
+ cli.add_command(serve)
26
26
 
27
27
 
28
28
  if __name__ == "__main__":
deepwork/cli/serve.py ADDED
@@ -0,0 +1,133 @@
1
+ """Serve command for DeepWork MCP server."""
2
+
3
+ from pathlib import Path
4
+
5
+ import click
6
+ from rich.console import Console
7
+
8
+ from deepwork.utils.yaml_utils import load_yaml
9
+
10
+ console = Console()
11
+
12
+
13
+ class ServeError(Exception):
14
+ """Exception raised for serve errors."""
15
+
16
+ pass
17
+
18
+
19
+ def _load_config(project_path: Path) -> dict:
20
+ """Load DeepWork config from project.
21
+
22
+ Args:
23
+ project_path: Path to project root
24
+
25
+ Returns:
26
+ Config dictionary
27
+
28
+ Raises:
29
+ ServeError: If config not found or invalid
30
+ """
31
+ config_file = project_path / ".deepwork" / "config.yml"
32
+ if not config_file.exists():
33
+ raise ServeError(f"DeepWork not installed in {project_path}. Run 'deepwork install' first.")
34
+
35
+ config = load_yaml(config_file)
36
+ if config is None:
37
+ config = {}
38
+
39
+ return config
40
+
41
+
42
+ @click.command()
43
+ @click.option(
44
+ "--path",
45
+ type=click.Path(exists=True, file_okay=False, path_type=Path),
46
+ default=".",
47
+ help="Path to project directory (default: current directory)",
48
+ )
49
+ @click.option(
50
+ "--no-quality-gate",
51
+ is_flag=True,
52
+ default=False,
53
+ help="Disable quality gate evaluation",
54
+ )
55
+ @click.option(
56
+ "--transport",
57
+ type=click.Choice(["stdio", "sse"]),
58
+ default="stdio",
59
+ help="MCP transport protocol (default: stdio)",
60
+ )
61
+ @click.option(
62
+ "--port",
63
+ type=int,
64
+ default=8000,
65
+ help="Port for SSE transport (default: 8000)",
66
+ )
67
+ def serve(
68
+ path: Path,
69
+ no_quality_gate: bool,
70
+ transport: str,
71
+ port: int,
72
+ ) -> None:
73
+ """Start the DeepWork MCP server.
74
+
75
+ Exposes workflow management tools to AI agents via MCP protocol.
76
+ By default uses stdio transport for local integration with Claude Code.
77
+
78
+ Quality gate is enabled by default and uses Claude Code to evaluate
79
+ step outputs against quality criteria.
80
+
81
+ Examples:
82
+
83
+ # Start server for current directory
84
+ deepwork serve
85
+
86
+ # Start with quality gate disabled
87
+ deepwork serve --no-quality-gate
88
+
89
+ # Start for a specific project
90
+ deepwork serve --path /path/to/project
91
+ """
92
+ try:
93
+ _serve_mcp(path, not no_quality_gate, transport, port)
94
+ except ServeError as e:
95
+ console.print(f"[red]Error:[/red] {e}")
96
+ raise click.Abort() from e
97
+ except Exception as e:
98
+ console.print(f"[red]Unexpected error:[/red] {e}")
99
+ raise
100
+
101
+
102
+ def _serve_mcp(
103
+ project_path: Path,
104
+ enable_quality_gate: bool,
105
+ transport: str,
106
+ port: int,
107
+ ) -> None:
108
+ """Start the MCP server.
109
+
110
+ Args:
111
+ project_path: Path to project directory
112
+ enable_quality_gate: Whether to enable quality gate evaluation
113
+ transport: Transport protocol (stdio or sse)
114
+ port: Port for SSE transport
115
+
116
+ Raises:
117
+ ServeError: If server fails to start
118
+ """
119
+ # Validate project has DeepWork installed
120
+ _load_config(project_path)
121
+
122
+ # Create and run server
123
+ from deepwork.mcp.server import create_server
124
+
125
+ server = create_server(
126
+ project_root=project_path,
127
+ enable_quality_gate=enable_quality_gate,
128
+ )
129
+
130
+ if transport == "stdio":
131
+ server.run(transport="stdio")
132
+ else:
133
+ server.run(transport="sse", port=port)
deepwork/cli/sync.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Sync command for DeepWork CLI."""
2
2
 
3
+ from dataclasses import dataclass, field
3
4
  from pathlib import Path
4
5
 
5
6
  import click
@@ -22,6 +23,21 @@ class SyncError(Exception):
22
23
  pass
23
24
 
24
25
 
26
+ @dataclass
27
+ class SyncResult:
28
+ """Result of a sync operation."""
29
+
30
+ platforms_synced: int = 0
31
+ skills_generated: int = 0
32
+ hooks_synced: int = 0
33
+ warnings: list[str] = field(default_factory=list)
34
+
35
+ @property
36
+ def has_warnings(self) -> bool:
37
+ """Return True if there were any warnings during sync."""
38
+ return len(self.warnings) > 0
39
+
40
+
25
41
  @click.command()
26
42
  @click.option(
27
43
  "--path",
@@ -46,13 +62,16 @@ def sync(path: Path) -> None:
46
62
  raise
47
63
 
48
64
 
49
- def sync_skills(project_path: Path) -> None:
65
+ def sync_skills(project_path: Path) -> SyncResult:
50
66
  """
51
67
  Sync skills to all configured platforms.
52
68
 
53
69
  Args:
54
70
  project_path: Path to project directory
55
71
 
72
+ Returns:
73
+ SyncResult with statistics and any warnings
74
+
56
75
  Raises:
57
76
  SyncError: If sync fails
58
77
  """
@@ -80,6 +99,43 @@ def sync_skills(project_path: Path) -> None:
80
99
 
81
100
  console.print("[bold cyan]Syncing DeepWork Skills[/bold cyan]\n")
82
101
 
102
+ # Generate /deepwork skill FIRST for all platforms (before parsing jobs)
103
+ # This ensures the skill is available even if some jobs fail to parse
104
+ generator = SkillGenerator()
105
+ result = SyncResult()
106
+ platform_adapters: list[AgentAdapter] = []
107
+ all_skill_paths_by_platform: dict[str, list[Path]] = {}
108
+
109
+ console.print("[yellow]→[/yellow] Generating /deepwork skill...")
110
+ for platform_name in platforms:
111
+ try:
112
+ adapter_cls = AgentAdapter.get(platform_name)
113
+ except Exception:
114
+ warning = f"Unknown platform '{platform_name}', skipping"
115
+ console.print(f" [yellow]⚠[/yellow] {warning}")
116
+ result.warnings.append(warning)
117
+ continue
118
+
119
+ adapter = adapter_cls(project_path)
120
+ platform_adapters.append(adapter)
121
+
122
+ platform_dir = project_path / adapter.config_dir
123
+ skills_dir = platform_dir / adapter.skills_dir
124
+ ensure_dir(skills_dir)
125
+
126
+ all_skill_paths: list[Path] = []
127
+ try:
128
+ deepwork_skill_path = generator.generate_deepwork_skill(adapter, platform_dir)
129
+ all_skill_paths.append(deepwork_skill_path)
130
+ result.skills_generated += 1
131
+ console.print(f" [green]✓[/green] {adapter.display_name}: deepwork (MCP entry point)")
132
+ except Exception as e:
133
+ warning = f"{adapter.display_name}: Failed to generate /deepwork skill: {e}"
134
+ console.print(f" [red]✗[/red] {warning}")
135
+ result.warnings.append(warning)
136
+
137
+ all_skill_paths_by_platform[platform_name] = all_skill_paths
138
+
83
139
  # Discover jobs
84
140
  jobs_dir = deepwork_dir / "jobs"
85
141
  if not jobs_dir.exists():
@@ -87,7 +143,7 @@ def sync_skills(project_path: Path) -> None:
87
143
  else:
88
144
  job_dirs = [d for d in jobs_dir.iterdir() if d.is_dir() and (d / "job.yml").exists()]
89
145
 
90
- console.print(f"[yellow]→[/yellow] Found {len(job_dirs)} job(s) to sync")
146
+ console.print(f"\n[yellow]→[/yellow] Found {len(job_dirs)} job(s) to sync")
91
147
 
92
148
  # Parse all jobs
93
149
  jobs = []
@@ -98,68 +154,48 @@ def sync_skills(project_path: Path) -> None:
98
154
  jobs.append(job_def)
99
155
  console.print(f" [green]✓[/green] Loaded {job_def.name} v{job_def.version}")
100
156
  except Exception as e:
101
- console.print(f" [red]✗[/red] Failed to load {job_dir.name}: {e}")
157
+ warning = f"Failed to load {job_dir.name}: {e}"
158
+ console.print(f" [red]✗[/red] {warning}")
102
159
  failed_jobs.append((job_dir.name, str(e)))
160
+ result.warnings.append(warning)
103
161
 
104
- # Fail early if any jobs failed to parse
162
+ # Warn about failed jobs but continue (skill already installed)
105
163
  if failed_jobs:
106
164
  console.print()
107
- console.print("[bold red]Sync aborted due to job parsing errors:[/bold red]")
165
+ console.print("[bold yellow]Warning: Some jobs failed to parse:[/bold yellow]")
108
166
  for job_name, error in failed_jobs:
109
167
  console.print(f" • {job_name}: {error}")
110
- raise SyncError(f"Failed to parse {len(failed_jobs)} job(s)")
168
+ console.print(
169
+ "[dim]The /deepwork skill is installed. Fix the job errors and run 'deepwork sync' again.[/dim]"
170
+ )
111
171
 
112
- # Collect hooks from all jobs
172
+ # Collect hooks from jobs (hooks collection is independent of job.yml parsing)
113
173
  job_hooks_list = collect_job_hooks(jobs_dir)
114
174
  if job_hooks_list:
115
- console.print(f"[yellow]→[/yellow] Found {len(job_hooks_list)} job(s) with hooks")
116
-
117
- # Sync each platform
118
- generator = SkillGenerator()
119
- stats = {"platforms": 0, "skills": 0, "hooks": 0}
120
- synced_adapters: list[AgentAdapter] = []
121
-
122
- for platform_name in platforms:
123
- try:
124
- adapter_cls = AgentAdapter.get(platform_name)
125
- except Exception:
126
- console.print(f"[yellow]⚠[/yellow] Unknown platform '{platform_name}', skipping")
127
- continue
175
+ console.print(f"\n[yellow]→[/yellow] Found {len(job_hooks_list)} job(s) with hooks")
128
176
 
129
- adapter = adapter_cls(project_path)
130
- console.print(f"\n[yellow]→[/yellow] Syncing to {adapter.display_name}...")
131
-
132
- platform_dir = project_path / adapter.config_dir
133
- skills_dir = platform_dir / adapter.skills_dir
134
-
135
- # Create skills directory
136
- ensure_dir(skills_dir)
177
+ # Sync hooks and permissions for each platform
178
+ for adapter in platform_adapters:
179
+ console.print(
180
+ f"\n[yellow]→[/yellow] Syncing hooks and permissions to {adapter.display_name}..."
181
+ )
137
182
 
138
- # Generate skills for all jobs
139
- all_skill_paths: list[Path] = []
140
- if jobs:
141
- console.print(" [dim]•[/dim] Generating skills...")
142
- for job in jobs:
143
- try:
144
- job_paths = generator.generate_all_skills(
145
- job, adapter, platform_dir, project_root=project_path
146
- )
147
- all_skill_paths.extend(job_paths)
148
- stats["skills"] += len(job_paths)
149
- console.print(f" [green]✓[/green] {job.name} ({len(job_paths)} skills)")
150
- except Exception as e:
151
- console.print(f" [red]✗[/red] Failed for {job.name}: {e}")
183
+ # NOTE: Job skills (meta-skills and step skills) are no longer generated.
184
+ # The MCP server now handles workflow orchestration directly.
185
+ # Only the /deepwork skill is installed as the entry point.
152
186
 
153
187
  # Sync hooks to platform settings
154
188
  if job_hooks_list:
155
189
  console.print(" [dim]•[/dim] Syncing hooks...")
156
190
  try:
157
191
  hooks_count = sync_hooks_to_platform(project_path, adapter, job_hooks_list)
158
- stats["hooks"] += hooks_count
192
+ result.hooks_synced += hooks_count
159
193
  if hooks_count > 0:
160
194
  console.print(f" [green]✓[/green] Synced {hooks_count} hook(s)")
161
195
  except Exception as e:
162
- console.print(f" [red]✗[/red] Failed to sync hooks: {e}")
196
+ warning = f"Failed to sync hooks: {e}"
197
+ console.print(f" [red]✗[/red] {warning}")
198
+ result.warnings.append(warning)
163
199
 
164
200
  # Sync required permissions to platform settings
165
201
  console.print(" [dim]•[/dim] Syncing permissions...")
@@ -170,9 +206,12 @@ def sync_skills(project_path: Path) -> None:
170
206
  else:
171
207
  console.print(" [dim]•[/dim] Base permissions already configured")
172
208
  except Exception as e:
173
- console.print(f" [red]✗[/red] Failed to sync permissions: {e}")
209
+ warning = f"Failed to sync permissions: {e}"
210
+ console.print(f" [red]✗[/red] {warning}")
211
+ result.warnings.append(warning)
174
212
 
175
213
  # Add skill permissions for generated skills (if adapter supports it)
214
+ all_skill_paths = all_skill_paths_by_platform.get(adapter.name, [])
176
215
  if all_skill_paths and hasattr(adapter, "add_skill_permissions"):
177
216
  try:
178
217
  skill_perms_count = adapter.add_skill_permissions(project_path, all_skill_paths)
@@ -181,10 +220,11 @@ def sync_skills(project_path: Path) -> None:
181
220
  f" [green]✓[/green] Added {skill_perms_count} skill permission(s)"
182
221
  )
183
222
  except Exception as e:
184
- console.print(f" [red]✗[/red] Failed to sync skill permissions: {e}")
223
+ warning = f"Failed to sync skill permissions: {e}"
224
+ console.print(f" [red]✗[/red] {warning}")
225
+ result.warnings.append(warning)
185
226
 
186
- stats["platforms"] += 1
187
- synced_adapters.append(adapter)
227
+ result.platforms_synced += 1
188
228
 
189
229
  # Summary
190
230
  console.print()
@@ -195,17 +235,12 @@ def sync_skills(project_path: Path) -> None:
195
235
  table.add_column("Metric", style="cyan")
196
236
  table.add_column("Count", style="green")
197
237
 
198
- table.add_row("Platforms synced", str(stats["platforms"]))
199
- table.add_row("Total skills", str(stats["skills"]))
200
- if stats["hooks"] > 0:
201
- table.add_row("Hooks synced", str(stats["hooks"]))
238
+ table.add_row("Platforms synced", str(result.platforms_synced))
239
+ table.add_row("Total skills", str(result.skills_generated))
240
+ if result.hooks_synced > 0:
241
+ table.add_row("Hooks synced", str(result.hooks_synced))
202
242
 
203
243
  console.print(table)
204
244
  console.print()
205
245
 
206
- # Show reload instructions for each synced platform
207
- if synced_adapters and stats["skills"] > 0:
208
- console.print("[bold]To use the new skills:[/bold]")
209
- for adapter in synced_adapters:
210
- console.print(f" [cyan]{adapter.display_name}:[/cyan] {adapter.reload_instructions}")
211
- console.print()
246
+ return result