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/core/generator.py
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
"""Skill file generator using Jinja2 templates."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
5
4
|
|
|
6
5
|
from jinja2 import Environment, FileSystemLoader, TemplateNotFound
|
|
7
6
|
|
|
8
|
-
from deepwork.core.adapters import AgentAdapter
|
|
9
|
-
from deepwork.
|
|
10
|
-
DocSpec,
|
|
11
|
-
DocSpecParseError,
|
|
12
|
-
parse_doc_spec_file,
|
|
13
|
-
)
|
|
14
|
-
from deepwork.core.parser import JobDefinition, Step
|
|
15
|
-
from deepwork.schemas.job_schema import LIFECYCLE_HOOK_EVENTS
|
|
16
|
-
from deepwork.utils.fs import safe_read, safe_write
|
|
7
|
+
from deepwork.core.adapters import AgentAdapter
|
|
8
|
+
from deepwork.utils.fs import safe_write
|
|
17
9
|
|
|
18
10
|
|
|
19
11
|
class GeneratorError(Exception):
|
|
@@ -42,35 +34,6 @@ class SkillGenerator:
|
|
|
42
34
|
if not self.templates_dir.exists():
|
|
43
35
|
raise GeneratorError(f"Templates directory not found: {self.templates_dir}")
|
|
44
36
|
|
|
45
|
-
# Cache for loaded doc specs (keyed by absolute file path)
|
|
46
|
-
self._doc_spec_cache: dict[Path, DocSpec] = {}
|
|
47
|
-
|
|
48
|
-
def _load_doc_spec(self, project_root: Path, doc_spec_path: str) -> DocSpec | None:
|
|
49
|
-
"""
|
|
50
|
-
Load a doc spec by file path with caching.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
project_root: Path to project root
|
|
54
|
-
doc_spec_path: Relative path to doc spec file (e.g., ".deepwork/doc_specs/report.md")
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
DocSpec if file exists and parses, None otherwise
|
|
58
|
-
"""
|
|
59
|
-
full_path = project_root / doc_spec_path
|
|
60
|
-
if full_path in self._doc_spec_cache:
|
|
61
|
-
return self._doc_spec_cache[full_path]
|
|
62
|
-
|
|
63
|
-
if not full_path.exists():
|
|
64
|
-
return None
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
doc_spec = parse_doc_spec_file(full_path)
|
|
68
|
-
except DocSpecParseError:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
self._doc_spec_cache[full_path] = doc_spec
|
|
72
|
-
return doc_spec
|
|
73
|
-
|
|
74
37
|
def _get_jinja_env(self, adapter: AgentAdapter) -> Environment:
|
|
75
38
|
"""
|
|
76
39
|
Get Jinja2 environment for an adapter.
|
|
@@ -93,308 +56,20 @@ class SkillGenerator:
|
|
|
93
56
|
lstrip_blocks=True,
|
|
94
57
|
)
|
|
95
58
|
|
|
96
|
-
def
|
|
97
|
-
"""
|
|
98
|
-
Check if a step is standalone (disconnected from the main workflow).
|
|
99
|
-
|
|
100
|
-
A standalone step has no dependencies AND no other steps depend on it.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
job: Job definition
|
|
104
|
-
step: Step to check
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
True if step is standalone
|
|
108
|
-
"""
|
|
109
|
-
# Step has dependencies - not standalone
|
|
110
|
-
if step.dependencies:
|
|
111
|
-
return False
|
|
112
|
-
|
|
113
|
-
# Check if any other step depends on this step
|
|
114
|
-
for other_step in job.steps:
|
|
115
|
-
if step.id in other_step.dependencies:
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
return True
|
|
119
|
-
|
|
120
|
-
def _build_hook_context(self, job: JobDefinition, hook_action: Any) -> dict[str, Any]:
|
|
121
|
-
"""
|
|
122
|
-
Build context for a single hook action.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
job: Job definition
|
|
126
|
-
hook_action: HookAction instance
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
Hook context dictionary
|
|
130
|
-
"""
|
|
131
|
-
hook_ctx: dict[str, Any] = {}
|
|
132
|
-
if hook_action.is_prompt():
|
|
133
|
-
hook_ctx["type"] = "prompt"
|
|
134
|
-
hook_ctx["content"] = hook_action.prompt
|
|
135
|
-
elif hook_action.is_prompt_file():
|
|
136
|
-
hook_ctx["type"] = "prompt_file"
|
|
137
|
-
hook_ctx["path"] = hook_action.prompt_file
|
|
138
|
-
# Read the prompt file content
|
|
139
|
-
prompt_file_path = job.job_dir / hook_action.prompt_file
|
|
140
|
-
prompt_content = safe_read(prompt_file_path)
|
|
141
|
-
if prompt_content is None:
|
|
142
|
-
raise GeneratorError(f"Hook prompt file not found: {prompt_file_path}")
|
|
143
|
-
hook_ctx["content"] = prompt_content
|
|
144
|
-
elif hook_action.is_script():
|
|
145
|
-
hook_ctx["type"] = "script"
|
|
146
|
-
hook_ctx["path"] = hook_action.script
|
|
147
|
-
return hook_ctx
|
|
148
|
-
|
|
149
|
-
def _build_step_context(
|
|
150
|
-
self,
|
|
151
|
-
job: JobDefinition,
|
|
152
|
-
step: Step,
|
|
153
|
-
step_index: int,
|
|
154
|
-
adapter: AgentAdapter,
|
|
155
|
-
project_root: Path | None = None,
|
|
156
|
-
) -> dict[str, Any]:
|
|
157
|
-
"""
|
|
158
|
-
Build template context for a step.
|
|
159
|
-
|
|
160
|
-
Args:
|
|
161
|
-
job: Job definition
|
|
162
|
-
step: Step to generate context for
|
|
163
|
-
step_index: Index of step in job (0-based)
|
|
164
|
-
adapter: Agent adapter for platform-specific hook name mapping
|
|
165
|
-
project_root: Optional project root for loading doc specs
|
|
166
|
-
|
|
167
|
-
Returns:
|
|
168
|
-
Template context dictionary
|
|
169
|
-
"""
|
|
170
|
-
# Read step instructions
|
|
171
|
-
instructions_file = job.job_dir / step.instructions_file
|
|
172
|
-
instructions_content = safe_read(instructions_file)
|
|
173
|
-
if instructions_content is None:
|
|
174
|
-
raise GeneratorError(f"Step instructions file not found: {instructions_file}")
|
|
175
|
-
|
|
176
|
-
# Separate user inputs and file inputs
|
|
177
|
-
user_inputs = [
|
|
178
|
-
{"name": inp.name, "description": inp.description}
|
|
179
|
-
for inp in step.inputs
|
|
180
|
-
if inp.is_user_input()
|
|
181
|
-
]
|
|
182
|
-
file_inputs = [
|
|
183
|
-
{"file": inp.file, "from_step": inp.from_step}
|
|
184
|
-
for inp in step.inputs
|
|
185
|
-
if inp.is_file_input()
|
|
186
|
-
]
|
|
187
|
-
|
|
188
|
-
# Check if this is a standalone step
|
|
189
|
-
is_standalone = self._is_standalone_step(job, step)
|
|
190
|
-
|
|
191
|
-
# Determine next and previous steps (only for non-standalone steps)
|
|
192
|
-
next_step = None
|
|
193
|
-
prev_step = None
|
|
194
|
-
if not is_standalone:
|
|
195
|
-
if step_index < len(job.steps) - 1:
|
|
196
|
-
next_step = job.steps[step_index + 1].id
|
|
197
|
-
if step_index > 0:
|
|
198
|
-
prev_step = job.steps[step_index - 1].id
|
|
199
|
-
|
|
200
|
-
# Build hooks context for all lifecycle events
|
|
201
|
-
# Structure: {platform_event_name: [hook_contexts]}
|
|
202
|
-
hooks: dict[str, list[dict[str, Any]]] = {}
|
|
203
|
-
for event in LIFECYCLE_HOOK_EVENTS:
|
|
204
|
-
if event in step.hooks:
|
|
205
|
-
# Get platform-specific event name from adapter
|
|
206
|
-
hook_enum = SkillLifecycleHook(event)
|
|
207
|
-
platform_event_name = adapter.get_platform_hook_name(hook_enum)
|
|
208
|
-
if platform_event_name:
|
|
209
|
-
hook_contexts = [
|
|
210
|
-
self._build_hook_context(job, hook_action)
|
|
211
|
-
for hook_action in step.hooks[event]
|
|
212
|
-
]
|
|
213
|
-
if hook_contexts:
|
|
214
|
-
hooks[platform_event_name] = hook_contexts
|
|
215
|
-
|
|
216
|
-
# Claude Code has separate Stop and SubagentStop events. When a Stop hook
|
|
217
|
-
# is defined, also register it for SubagentStop so it triggers for both
|
|
218
|
-
# the main agent and subagents.
|
|
219
|
-
if "Stop" in hooks:
|
|
220
|
-
hooks["SubagentStop"] = hooks["Stop"]
|
|
221
|
-
|
|
222
|
-
# Backward compatibility: stop_hooks is after_agent hooks
|
|
223
|
-
stop_hooks = hooks.get(
|
|
224
|
-
adapter.get_platform_hook_name(SkillLifecycleHook.AFTER_AGENT) or "Stop", []
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
# Build rich outputs context with doc spec information
|
|
228
|
-
outputs_context = []
|
|
229
|
-
for output in step.outputs:
|
|
230
|
-
output_ctx: dict[str, Any] = {
|
|
231
|
-
"file": output.file,
|
|
232
|
-
"has_doc_spec": output.has_doc_spec(),
|
|
233
|
-
}
|
|
234
|
-
if output.has_doc_spec() and output.doc_spec and project_root:
|
|
235
|
-
doc_spec = self._load_doc_spec(project_root, output.doc_spec)
|
|
236
|
-
if doc_spec:
|
|
237
|
-
output_ctx["doc_spec"] = {
|
|
238
|
-
"path": output.doc_spec,
|
|
239
|
-
"name": doc_spec.name,
|
|
240
|
-
"description": doc_spec.description,
|
|
241
|
-
"target_audience": doc_spec.target_audience,
|
|
242
|
-
"quality_criteria": [
|
|
243
|
-
{"name": c.name, "description": c.description}
|
|
244
|
-
for c in doc_spec.quality_criteria
|
|
245
|
-
],
|
|
246
|
-
"example_document": doc_spec.example_document,
|
|
247
|
-
}
|
|
248
|
-
outputs_context.append(output_ctx)
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
"job_name": job.name,
|
|
252
|
-
"job_version": job.version,
|
|
253
|
-
"job_summary": job.summary,
|
|
254
|
-
"job_description": job.description,
|
|
255
|
-
"step_id": step.id,
|
|
256
|
-
"step_name": step.name,
|
|
257
|
-
"step_description": step.description,
|
|
258
|
-
"step_number": step_index + 1, # 1-based for display
|
|
259
|
-
"total_steps": len(job.steps),
|
|
260
|
-
"instructions_file": step.instructions_file,
|
|
261
|
-
"instructions_content": instructions_content,
|
|
262
|
-
"user_inputs": user_inputs,
|
|
263
|
-
"file_inputs": file_inputs,
|
|
264
|
-
"outputs": outputs_context,
|
|
265
|
-
"dependencies": step.dependencies,
|
|
266
|
-
"next_step": next_step,
|
|
267
|
-
"prev_step": prev_step,
|
|
268
|
-
"is_standalone": is_standalone,
|
|
269
|
-
"hooks": hooks, # New: all hooks by platform event name
|
|
270
|
-
"stop_hooks": stop_hooks, # Backward compat: after_agent hooks only
|
|
271
|
-
"quality_criteria": step.quality_criteria, # Declarative criteria with framing
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
def _build_meta_skill_context(
|
|
275
|
-
self, job: JobDefinition, adapter: AgentAdapter
|
|
276
|
-
) -> dict[str, Any]:
|
|
277
|
-
"""
|
|
278
|
-
Build template context for a job's meta-skill.
|
|
279
|
-
|
|
280
|
-
Args:
|
|
281
|
-
job: Job definition
|
|
282
|
-
adapter: Agent adapter for platform-specific configuration
|
|
283
|
-
|
|
284
|
-
Returns:
|
|
285
|
-
Template context dictionary
|
|
286
|
-
"""
|
|
287
|
-
# Build step info for the meta-skill
|
|
288
|
-
steps_info = []
|
|
289
|
-
for step in job.steps:
|
|
290
|
-
skill_filename = adapter.get_step_skill_filename(job.name, step.id, step.exposed)
|
|
291
|
-
# Extract just the skill name (without path and extension)
|
|
292
|
-
# For Claude: job_name.step_id/SKILL.md -> job_name.step_id
|
|
293
|
-
# For Gemini: job_name/step_id.toml -> job_name:step_id
|
|
294
|
-
if adapter.name == "gemini":
|
|
295
|
-
# Gemini uses colon for namespacing: job_name:step_id
|
|
296
|
-
parts = skill_filename.replace(".toml", "").split("/")
|
|
297
|
-
skill_name = ":".join(parts)
|
|
298
|
-
else:
|
|
299
|
-
# Claude uses directory/SKILL.md format, extract directory name
|
|
300
|
-
# job_name.step_id/SKILL.md -> job_name.step_id
|
|
301
|
-
skill_name = skill_filename.replace("/SKILL.md", "")
|
|
302
|
-
|
|
303
|
-
steps_info.append(
|
|
304
|
-
{
|
|
305
|
-
"id": step.id,
|
|
306
|
-
"name": step.name,
|
|
307
|
-
"description": step.description,
|
|
308
|
-
"command_name": skill_name,
|
|
309
|
-
"dependencies": step.dependencies,
|
|
310
|
-
"exposed": step.exposed,
|
|
311
|
-
}
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
return {
|
|
315
|
-
"job_name": job.name,
|
|
316
|
-
"job_version": job.version,
|
|
317
|
-
"job_summary": job.summary,
|
|
318
|
-
"job_description": job.description,
|
|
319
|
-
"total_steps": len(job.steps),
|
|
320
|
-
"steps": steps_info,
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
def generate_meta_skill(
|
|
59
|
+
def generate_deepwork_skill(
|
|
324
60
|
self,
|
|
325
|
-
job: JobDefinition,
|
|
326
61
|
adapter: AgentAdapter,
|
|
327
62
|
output_dir: Path | str,
|
|
328
63
|
) -> Path:
|
|
329
64
|
"""
|
|
330
|
-
Generate the
|
|
331
|
-
|
|
332
|
-
The meta-skill is the primary user interface for a job, routing
|
|
333
|
-
user intent to the appropriate step.
|
|
334
|
-
|
|
335
|
-
Args:
|
|
336
|
-
job: Job definition
|
|
337
|
-
adapter: Agent adapter for the target platform
|
|
338
|
-
output_dir: Directory to write skill file to
|
|
339
|
-
|
|
340
|
-
Returns:
|
|
341
|
-
Path to generated meta-skill file
|
|
342
|
-
|
|
343
|
-
Raises:
|
|
344
|
-
GeneratorError: If generation fails
|
|
345
|
-
"""
|
|
346
|
-
output_dir = Path(output_dir)
|
|
347
|
-
|
|
348
|
-
# Create skills subdirectory if needed
|
|
349
|
-
skills_dir = output_dir / adapter.skills_dir
|
|
350
|
-
skills_dir.mkdir(parents=True, exist_ok=True)
|
|
351
|
-
|
|
352
|
-
# Build context
|
|
353
|
-
context = self._build_meta_skill_context(job, adapter)
|
|
354
|
-
|
|
355
|
-
# Load and render template
|
|
356
|
-
env = self._get_jinja_env(adapter)
|
|
357
|
-
try:
|
|
358
|
-
template = env.get_template(adapter.meta_skill_template)
|
|
359
|
-
except TemplateNotFound as e:
|
|
360
|
-
raise GeneratorError(f"Meta-skill template not found: {e}") from e
|
|
361
|
-
|
|
362
|
-
try:
|
|
363
|
-
rendered = template.render(**context)
|
|
364
|
-
except Exception as e:
|
|
365
|
-
raise GeneratorError(f"Meta-skill template rendering failed: {e}") from e
|
|
366
|
-
|
|
367
|
-
# Write meta-skill file
|
|
368
|
-
skill_filename = adapter.get_meta_skill_filename(job.name)
|
|
369
|
-
skill_path = skills_dir / skill_filename
|
|
370
|
-
|
|
371
|
-
# Ensure parent directories exist (for Gemini's job_name/index.toml structure)
|
|
372
|
-
skill_path.parent.mkdir(parents=True, exist_ok=True)
|
|
373
|
-
|
|
374
|
-
try:
|
|
375
|
-
safe_write(skill_path, rendered)
|
|
376
|
-
except Exception as e:
|
|
377
|
-
raise GeneratorError(f"Failed to write meta-skill file: {e}") from e
|
|
65
|
+
Generate the global /deepwork skill that instructs agents to use MCP tools.
|
|
378
66
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
def generate_step_skill(
|
|
382
|
-
self,
|
|
383
|
-
job: JobDefinition,
|
|
384
|
-
step: Step,
|
|
385
|
-
adapter: AgentAdapter,
|
|
386
|
-
output_dir: Path | str,
|
|
387
|
-
project_root: Path | str | None = None,
|
|
388
|
-
) -> Path:
|
|
389
|
-
"""
|
|
390
|
-
Generate skill file for a single step.
|
|
67
|
+
This is a single skill that provides the main entry point for DeepWork,
|
|
68
|
+
directing agents to use the MCP server's tools for workflow management.
|
|
391
69
|
|
|
392
70
|
Args:
|
|
393
|
-
job: Job definition
|
|
394
|
-
step: Step to generate skill for
|
|
395
71
|
adapter: Agent adapter for the target platform
|
|
396
72
|
output_dir: Directory to write skill file to
|
|
397
|
-
project_root: Optional project root for loading doc specs (defaults to output_dir)
|
|
398
73
|
|
|
399
74
|
Returns:
|
|
400
75
|
Path to generated skill file
|
|
@@ -403,80 +78,38 @@ class SkillGenerator:
|
|
|
403
78
|
GeneratorError: If generation fails
|
|
404
79
|
"""
|
|
405
80
|
output_dir = Path(output_dir)
|
|
406
|
-
project_root_path = Path(project_root) if project_root else output_dir
|
|
407
81
|
|
|
408
82
|
# Create skills subdirectory if needed
|
|
409
83
|
skills_dir = output_dir / adapter.skills_dir
|
|
410
84
|
skills_dir.mkdir(parents=True, exist_ok=True)
|
|
411
85
|
|
|
412
|
-
# Find step index
|
|
413
|
-
try:
|
|
414
|
-
step_index = next(i for i, s in enumerate(job.steps) if s.id == step.id)
|
|
415
|
-
except StopIteration as e:
|
|
416
|
-
raise GeneratorError(f"Step '{step.id}' not found in job '{job.name}'") from e
|
|
417
|
-
|
|
418
|
-
# Build context (include exposed for template user-invocable setting)
|
|
419
|
-
context = self._build_step_context(job, step, step_index, adapter, project_root_path)
|
|
420
|
-
context["exposed"] = step.exposed
|
|
421
|
-
|
|
422
86
|
# Load and render template
|
|
423
87
|
env = self._get_jinja_env(adapter)
|
|
88
|
+
template_name = "skill-deepwork.md.jinja"
|
|
89
|
+
|
|
424
90
|
try:
|
|
425
|
-
template = env.get_template(
|
|
91
|
+
template = env.get_template(template_name)
|
|
426
92
|
except TemplateNotFound as e:
|
|
427
|
-
raise GeneratorError(f"
|
|
93
|
+
raise GeneratorError(f"DeepWork skill template not found: {e}") from e
|
|
428
94
|
|
|
429
95
|
try:
|
|
430
|
-
rendered = template.render(
|
|
96
|
+
rendered = template.render()
|
|
431
97
|
except Exception as e:
|
|
432
|
-
raise GeneratorError(f"
|
|
98
|
+
raise GeneratorError(f"DeepWork skill template rendering failed: {e}") from e
|
|
433
99
|
|
|
434
100
|
# Write skill file
|
|
435
|
-
|
|
436
|
-
|
|
101
|
+
# Use the adapter's convention for naming
|
|
102
|
+
if adapter.name == "gemini":
|
|
103
|
+
skill_filename = "deepwork/index.toml"
|
|
104
|
+
else:
|
|
105
|
+
skill_filename = "deepwork/SKILL.md"
|
|
437
106
|
|
|
438
|
-
|
|
107
|
+
skill_path = skills_dir / skill_filename
|
|
439
108
|
skill_path.parent.mkdir(parents=True, exist_ok=True)
|
|
440
109
|
|
|
441
110
|
try:
|
|
442
111
|
safe_write(skill_path, rendered)
|
|
443
112
|
except Exception as e:
|
|
444
|
-
raise GeneratorError(f"Failed to write skill file: {e}") from e
|
|
113
|
+
raise GeneratorError(f"Failed to write DeepWork skill file: {e}") from e
|
|
445
114
|
|
|
446
115
|
return skill_path
|
|
447
|
-
|
|
448
|
-
def generate_all_skills(
|
|
449
|
-
self,
|
|
450
|
-
job: JobDefinition,
|
|
451
|
-
adapter: AgentAdapter,
|
|
452
|
-
output_dir: Path | str,
|
|
453
|
-
project_root: Path | str | None = None,
|
|
454
|
-
) -> list[Path]:
|
|
455
|
-
"""
|
|
456
|
-
Generate all skill files for a job: meta-skill and step skills.
|
|
457
|
-
|
|
458
|
-
Args:
|
|
459
|
-
job: Job definition
|
|
460
|
-
adapter: Agent adapter for the target platform
|
|
461
|
-
output_dir: Directory to write skill files to
|
|
462
|
-
project_root: Optional project root for loading doc specs (defaults to output_dir)
|
|
463
|
-
|
|
464
|
-
Returns:
|
|
465
|
-
List of paths to generated skill files (meta-skill first, then steps)
|
|
466
|
-
|
|
467
|
-
Raises:
|
|
468
|
-
GeneratorError: If generation fails
|
|
469
|
-
"""
|
|
470
|
-
skill_paths = []
|
|
471
|
-
project_root_path = Path(project_root) if project_root else Path(output_dir)
|
|
472
|
-
|
|
473
|
-
# Generate meta-skill first (job-level entry point)
|
|
474
|
-
meta_skill_path = self.generate_meta_skill(job, adapter, output_dir)
|
|
475
|
-
skill_paths.append(meta_skill_path)
|
|
476
|
-
|
|
477
|
-
# Generate step skills
|
|
478
|
-
for step in job.steps:
|
|
479
|
-
skill_path = self.generate_step_skill(job, step, adapter, output_dir, project_root_path)
|
|
480
|
-
skill_paths.append(skill_path)
|
|
481
|
-
|
|
482
|
-
return skill_paths
|
deepwork/core/hooks_syncer.py
CHANGED
|
@@ -36,7 +36,7 @@ class HookEntry:
|
|
|
36
36
|
"""
|
|
37
37
|
if self.module:
|
|
38
38
|
# Python module - use deepwork hook CLI for portability
|
|
39
|
-
# Extract hook name from module path (e.g., "deepwork.hooks.
|
|
39
|
+
# Extract hook name from module path (e.g., "deepwork.hooks.my_hook" -> "my_hook")
|
|
40
40
|
hook_name = self.module.rsplit(".", 1)[-1]
|
|
41
41
|
return f"deepwork hook {hook_name}"
|
|
42
42
|
elif self.script:
|