deepwork 0.5.1__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.
- deepwork/__init__.py +1 -1
- deepwork/cli/hook.py +3 -4
- deepwork/cli/install.py +70 -117
- deepwork/cli/main.py +2 -2
- deepwork/cli/serve.py +133 -0
- deepwork/cli/sync.py +93 -58
- deepwork/core/adapters.py +91 -102
- deepwork/core/generator.py +19 -386
- deepwork/core/hooks_syncer.py +1 -1
- deepwork/core/parser.py +270 -1
- deepwork/hooks/README.md +0 -44
- deepwork/hooks/__init__.py +3 -6
- deepwork/hooks/check_version.sh +54 -21
- deepwork/mcp/__init__.py +23 -0
- deepwork/mcp/quality_gate.py +347 -0
- deepwork/mcp/schemas.py +263 -0
- deepwork/mcp/server.py +253 -0
- deepwork/mcp/state.py +422 -0
- deepwork/mcp/tools.py +394 -0
- deepwork/schemas/job.schema.json +347 -0
- deepwork/schemas/job_schema.py +27 -239
- deepwork/standard_jobs/deepwork_jobs/doc_specs/job_spec.md +9 -15
- deepwork/standard_jobs/deepwork_jobs/job.yml +146 -46
- deepwork/standard_jobs/deepwork_jobs/steps/define.md +100 -33
- deepwork/standard_jobs/deepwork_jobs/steps/errata.md +154 -0
- deepwork/standard_jobs/deepwork_jobs/steps/fix_jobs.md +207 -0
- deepwork/standard_jobs/deepwork_jobs/steps/fix_settings.md +177 -0
- deepwork/standard_jobs/deepwork_jobs/steps/implement.md +22 -138
- deepwork/standard_jobs/deepwork_jobs/steps/iterate.md +221 -0
- deepwork/standard_jobs/deepwork_jobs/steps/learn.md +2 -26
- deepwork/standard_jobs/deepwork_jobs/steps/test.md +154 -0
- deepwork/standard_jobs/deepwork_jobs/templates/job.yml.template +2 -0
- deepwork/templates/claude/settings.json +16 -0
- deepwork/templates/claude/skill-deepwork.md.jinja +37 -0
- deepwork/templates/gemini/skill-deepwork.md.jinja +37 -0
- deepwork-0.7.0.dist-info/METADATA +317 -0
- deepwork-0.7.0.dist-info/RECORD +64 -0
- deepwork/cli/rules.py +0 -32
- deepwork/core/command_executor.py +0 -190
- deepwork/core/pattern_matcher.py +0 -271
- deepwork/core/rules_parser.py +0 -559
- deepwork/core/rules_queue.py +0 -321
- deepwork/hooks/rules_check.py +0 -759
- deepwork/schemas/rules_schema.py +0 -135
- deepwork/standard_jobs/deepwork_jobs/steps/review_job_spec.md +0 -208
- deepwork/standard_jobs/deepwork_jobs/templates/doc_spec.md.example +0 -86
- deepwork/standard_jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh +0 -38
- deepwork/standard_jobs/deepwork_rules/hooks/global_hooks.yml +0 -8
- deepwork/standard_jobs/deepwork_rules/hooks/user_prompt_submit.sh +0 -16
- deepwork/standard_jobs/deepwork_rules/job.yml +0 -49
- deepwork/standard_jobs/deepwork_rules/rules/.gitkeep +0 -13
- deepwork/standard_jobs/deepwork_rules/rules/api-documentation-sync.md.example +0 -10
- deepwork/standard_jobs/deepwork_rules/rules/readme-documentation.md.example +0 -10
- deepwork/standard_jobs/deepwork_rules/rules/security-review.md.example +0 -11
- deepwork/standard_jobs/deepwork_rules/rules/skill-md-validation.md +0 -46
- deepwork/standard_jobs/deepwork_rules/rules/source-test-pairing.md.example +0 -13
- deepwork/standard_jobs/deepwork_rules/steps/define.md +0 -249
- deepwork/templates/claude/skill-job-meta.md.jinja +0 -77
- deepwork/templates/claude/skill-job-step.md.jinja +0 -235
- deepwork/templates/gemini/skill-job-meta.toml.jinja +0 -76
- deepwork/templates/gemini/skill-job-step.toml.jinja +0 -162
- deepwork-0.5.1.dist-info/METADATA +0 -381
- deepwork-0.5.1.dist-info/RECORD +0 -72
- {deepwork-0.5.1.dist-info → deepwork-0.7.0.dist-info}/WHEEL +0 -0
- {deepwork-0.5.1.dist-info → deepwork-0.7.0.dist-info}/entry_points.txt +0 -0
- {deepwork-0.5.1.dist-info → deepwork-0.7.0.dist-info}/licenses/LICENSE.md +0 -0
deepwork/__init__.py
CHANGED
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., '
|
|
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
|
|
42
|
-
echo '{}' | deepwork hook
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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.
|
|
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(
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
#
|
|
162
|
+
# Warn about failed jobs but continue (skill already installed)
|
|
105
163
|
if failed_jobs:
|
|
106
164
|
console.print()
|
|
107
|
-
console.print("[bold
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
#
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
223
|
+
warning = f"Failed to sync skill permissions: {e}"
|
|
224
|
+
console.print(f" [red]✗[/red] {warning}")
|
|
225
|
+
result.warnings.append(warning)
|
|
185
226
|
|
|
186
|
-
|
|
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(
|
|
199
|
-
table.add_row("Total skills", str(
|
|
200
|
-
if
|
|
201
|
-
table.add_row("Hooks synced", str(
|
|
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
|
-
|
|
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
|