zhuge-workflow 0.1.0

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 (80) hide show
  1. package/dist/index.js +801 -0
  2. package/dist/index.js.map +1 -0
  3. package/package.json +61 -0
  4. package/templates/claude/CLAUDE-ccg.md +258 -0
  5. package/templates/claude/CLAUDE.md +106 -0
  6. package/templates/claude/commands/ccg/_context.md +152 -0
  7. package/templates/claude/commands/ccg/spec-impl.md +161 -0
  8. package/templates/claude/commands/ccg/spec-plan-trellis.md +239 -0
  9. package/templates/claude/commands/ccg/spec-plan.md +225 -0
  10. package/templates/claude/commands/ccg/spec-research.md +113 -0
  11. package/templates/claude/commands/ccg/spec-review.md +127 -0
  12. package/templates/claude/commands/developer/brainstorm.md +5 -0
  13. package/templates/claude/commands/developer/design-checklist.md +81 -0
  14. package/templates/claude/commands/developer/design-doc.md +188 -0
  15. package/templates/claude/commands/developer/requirement-doc.md +150 -0
  16. package/templates/claude/commands/developer/requirement-interrogate.md +71 -0
  17. package/templates/claude/commands/developer/status.md +55 -0
  18. package/templates/claude/rules/bash-style.md +46 -0
  19. package/templates/claude/rules/claude-code-defensive.md +99 -0
  20. package/templates/claude/rules/doc-sync.md +49 -0
  21. package/templates/claude/rules/ops-safety.md +32 -0
  22. package/templates/claude/skills/bash-style/SKILL.md +244 -0
  23. package/templates/claude/skills/brainstorming/SKILL.md +48 -0
  24. package/templates/claude/skills/dotnet-dev/SKILL.md +250 -0
  25. package/templates/claude/skills/dotnet-dev/references/dotnet-style.md +991 -0
  26. package/templates/claude/skills/mcp-builder/LICENSE.txt +202 -0
  27. package/templates/claude/skills/mcp-builder/SKILL.md +328 -0
  28. package/templates/claude/skills/mcp-builder/reference/evaluation.md +602 -0
  29. package/templates/claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
  30. package/templates/claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
  31. package/templates/claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
  32. package/templates/claude/skills/mcp-builder/scripts/connections.py +151 -0
  33. package/templates/claude/skills/mcp-builder/scripts/evaluation.py +373 -0
  34. package/templates/claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  35. package/templates/claude/skills/mcp-builder/scripts/requirements.txt +2 -0
  36. package/templates/claude/skills/ops-safety/SKILL.md +130 -0
  37. package/templates/claude/skills/python-dev/SKILL.md +281 -0
  38. package/templates/claude/skills/skill-creator/LICENSE.txt +202 -0
  39. package/templates/claude/skills/skill-creator/SKILL.md +209 -0
  40. package/templates/claude/skills/skill-creator/scripts/init_skill.py +303 -0
  41. package/templates/claude/skills/skill-creator/scripts/package_skill.py +110 -0
  42. package/templates/claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  43. package/templates/claude/skills/sqlserver-executor/SKILL.md +201 -0
  44. package/templates/claude/skills/sqlserver-executor/assets/config-example.json +26 -0
  45. package/templates/claude/skills/sqlserver-executor/config.json +12 -0
  46. package/templates/claude/skills/sqlserver-executor/scripts/sql_executor.py +404 -0
  47. package/templates/claude/skills/ui-ux-pro-max/SKILL.md +228 -0
  48. package/templates/claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  49. package/templates/claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  50. package/templates/claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  51. package/templates/claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  52. package/templates/claude/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  53. package/templates/claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  54. package/templates/claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  55. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  56. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  57. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  58. package/templates/claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  59. package/templates/claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  60. package/templates/claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  61. package/templates/claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  62. package/templates/claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  63. package/templates/claude/skills/ui-ux-pro-max/data/styles.csv +59 -0
  64. package/templates/claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  65. package/templates/claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  66. package/templates/claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  67. package/templates/claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
  68. package/templates/claude/skills/ui-ux-pro-max/scripts/core.py +238 -0
  69. package/templates/claude/skills/ui-ux-pro-max/scripts/search.py +61 -0
  70. package/templates/claude/skills/webapp-testing/LICENSE.txt +202 -0
  71. package/templates/claude/skills/webapp-testing/SKILL.md +96 -0
  72. package/templates/claude/skills/webapp-testing/examples/console_logging.py +35 -0
  73. package/templates/claude/skills/webapp-testing/examples/element_discovery.py +40 -0
  74. package/templates/claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
  75. package/templates/claude/skills/webapp-testing/scripts/with_server.py +106 -0
  76. package/templates/init/claude-agents/ccg-impl.md +199 -0
  77. package/templates/init/claude-agents/ccg-review.md +146 -0
  78. package/templates/init/claude-agents/dispatch.md +253 -0
  79. package/templates/init/claude-hooks/inject-subagent-context.py +964 -0
  80. package/templates/init/trellis-scripts/task.sh +1326 -0
@@ -0,0 +1,964 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Multi-Agent Pipeline Context Injection Hook
4
+
5
+ Core Design Philosophy:
6
+ - Dispatch becomes a pure dispatcher, only responsible for "calling subagents"
7
+ - Hook is responsible for injecting all context, subagent works autonomously with complete info
8
+ - Each agent has a dedicated jsonl file defining its context
9
+ - No resume needed, no segmentation, behavior controlled by code not prompt
10
+
11
+ Trigger: PreToolUse (before Task tool call)
12
+
13
+ Context Source: .trellis/.current-task points to task directory
14
+ - implement.jsonl - Implement agent dedicated context
15
+ - check.jsonl - Check agent dedicated context
16
+ - debug.jsonl - Debug agent dedicated context
17
+ - research.jsonl - Research agent dedicated context (optional, usually not needed)
18
+ - cr.jsonl - Code review dedicated context
19
+ - prd.md - Requirements document
20
+ - info.md - Technical design
21
+ - codex-review-output.txt - Code Review results
22
+ """
23
+
24
+ import json
25
+ import os
26
+ import sys
27
+ from pathlib import Path
28
+
29
+ # =============================================================================
30
+ # Path Constants (change here to rename directories)
31
+ # =============================================================================
32
+
33
+ DIR_WORKFLOW = ".trellis"
34
+ DIR_WORKSPACE = "workspace"
35
+ DIR_TASKS = "tasks"
36
+ DIR_SPEC = "spec"
37
+ FILE_CURRENT_TASK = ".current-task"
38
+ FILE_TASK_JSON = "task.json"
39
+
40
+ # Agents that don't update phase (can be called at any time)
41
+ AGENTS_NO_PHASE_UPDATE = {"debug", "research"}
42
+
43
+ # =============================================================================
44
+ # Subagent Constants (change here to rename subagent types)
45
+ # =============================================================================
46
+
47
+ AGENT_IMPLEMENT = "implement"
48
+ AGENT_CHECK = "check"
49
+ AGENT_DEBUG = "debug"
50
+ AGENT_RESEARCH = "research"
51
+ AGENT_CCG_IMPL = "ccg-impl"
52
+ AGENT_CCG_REVIEW = "ccg-review"
53
+
54
+ # Agents that require a task directory
55
+ AGENTS_REQUIRE_TASK = (AGENT_IMPLEMENT, AGENT_CHECK, AGENT_DEBUG, AGENT_CCG_IMPL, AGENT_CCG_REVIEW)
56
+ # All supported agents
57
+ AGENTS_ALL = (AGENT_IMPLEMENT, AGENT_CHECK, AGENT_DEBUG, AGENT_RESEARCH, AGENT_CCG_IMPL, AGENT_CCG_REVIEW)
58
+
59
+
60
+ def find_repo_root(start_path: str) -> str | None:
61
+ """
62
+ Find git repo root from start_path upwards
63
+
64
+ Returns:
65
+ Repo root path, or None if not found
66
+ """
67
+ current = Path(start_path).resolve()
68
+ while current != current.parent:
69
+ if (current / ".git").exists():
70
+ return str(current)
71
+ current = current.parent
72
+ return None
73
+
74
+
75
+ def get_current_task(repo_root: str) -> str | None:
76
+ """
77
+ Read current task directory path from .trellis/.current-task
78
+
79
+ Returns:
80
+ Task directory relative path (relative to repo_root)
81
+ None if not set
82
+ """
83
+ current_task_file = os.path.join(repo_root, DIR_WORKFLOW, FILE_CURRENT_TASK)
84
+ if not os.path.exists(current_task_file):
85
+ return None
86
+
87
+ try:
88
+ with open(current_task_file, "r", encoding="utf-8") as f:
89
+ content = f.read().strip()
90
+ return content if content else None
91
+ except Exception:
92
+ return None
93
+
94
+
95
+ def update_current_phase(repo_root: str, task_dir: str, subagent_type: str) -> None:
96
+ """
97
+ Update current_phase in task.json based on subagent_type.
98
+
99
+ This ensures phase tracking is always accurate, regardless of whether
100
+ dispatch agent remembers to update it.
101
+
102
+ Logic:
103
+ - Read next_action array from task.json
104
+ - Find the next phase whose action matches subagent_type
105
+ - Only move forward, never backward
106
+ - Some agents (debug, research) don't update phase
107
+ """
108
+ if subagent_type in AGENTS_NO_PHASE_UPDATE:
109
+ return
110
+
111
+ task_json_path = os.path.join(repo_root, task_dir, FILE_TASK_JSON)
112
+ if not os.path.exists(task_json_path):
113
+ return
114
+
115
+ try:
116
+ with open(task_json_path, "r", encoding="utf-8") as f:
117
+ task_data = json.load(f)
118
+
119
+ current_phase = task_data.get("current_phase", 0)
120
+ next_actions = task_data.get("next_action", [])
121
+
122
+ # Map action names to subagent types
123
+ # "implement" -> "implement", "check" -> "check", "finish" -> "check"
124
+ # CCG workflow: "ccg-impl" -> "ccg-impl", "ccg-review" -> "ccg-review"
125
+ action_to_agent = {
126
+ "implement": "implement",
127
+ "check": "check",
128
+ "finish": "check", # finish uses check agent
129
+ "ccg-impl": "ccg-impl",
130
+ "ccg-review": "ccg-review",
131
+ }
132
+
133
+ # Find the next phase that matches this subagent_type
134
+ new_phase = None
135
+ for action in next_actions:
136
+ phase_num = action.get("phase", 0)
137
+ action_name = action.get("action", "")
138
+ expected_agent = action_to_agent.get(action_name)
139
+
140
+ # Only consider phases after current_phase
141
+ if phase_num > current_phase and expected_agent == subagent_type:
142
+ new_phase = phase_num
143
+ break
144
+
145
+ if new_phase is not None:
146
+ task_data["current_phase"] = new_phase
147
+
148
+ with open(task_json_path, "w", encoding="utf-8") as f:
149
+ json.dump(task_data, f, indent=2, ensure_ascii=False)
150
+ except Exception:
151
+ # Don't fail the hook if phase update fails
152
+ pass
153
+
154
+
155
+ def read_file_content(base_path: str, file_path: str) -> str | None:
156
+ """Read file content, return None if file doesn't exist"""
157
+ full_path = os.path.join(base_path, file_path)
158
+ if os.path.exists(full_path) and os.path.isfile(full_path):
159
+ try:
160
+ with open(full_path, "r", encoding="utf-8") as f:
161
+ return f.read()
162
+ except Exception:
163
+ return None
164
+ return None
165
+
166
+
167
+ def read_directory_contents(
168
+ base_path: str, dir_path: str, max_files: int = 20
169
+ ) -> list[tuple[str, str]]:
170
+ """
171
+ Read all .md files in a directory
172
+
173
+ Args:
174
+ base_path: Base path (usually repo_root)
175
+ dir_path: Directory relative path
176
+ max_files: Max files to read (prevent huge directories)
177
+
178
+ Returns:
179
+ [(file_path, content), ...]
180
+ """
181
+ full_path = os.path.join(base_path, dir_path)
182
+ if not os.path.exists(full_path) or not os.path.isdir(full_path):
183
+ return []
184
+
185
+ results = []
186
+ try:
187
+ # Only read .md files, sorted by filename
188
+ md_files = sorted(
189
+ [
190
+ f
191
+ for f in os.listdir(full_path)
192
+ if f.endswith(".md") and os.path.isfile(os.path.join(full_path, f))
193
+ ]
194
+ )
195
+
196
+ for filename in md_files[:max_files]:
197
+ file_full_path = os.path.join(full_path, filename)
198
+ relative_path = os.path.join(dir_path, filename)
199
+ try:
200
+ with open(file_full_path, "r", encoding="utf-8") as f:
201
+ content = f.read()
202
+ results.append((relative_path, content))
203
+ except Exception:
204
+ continue
205
+ except Exception:
206
+ pass
207
+
208
+ return results
209
+
210
+
211
+ def read_jsonl_entries(base_path: str, jsonl_path: str) -> list[tuple[str, str]]:
212
+ """
213
+ Read all file/directory contents referenced in jsonl file
214
+
215
+ Schema:
216
+ {"file": "path/to/file.md", "reason": "..."}
217
+ {"file": "path/to/dir/", "type": "directory", "reason": "..."}
218
+
219
+ Returns:
220
+ [(path, content), ...]
221
+ """
222
+ full_path = os.path.join(base_path, jsonl_path)
223
+ if not os.path.exists(full_path):
224
+ return []
225
+
226
+ results = []
227
+ try:
228
+ with open(full_path, "r", encoding="utf-8") as f:
229
+ for line in f:
230
+ line = line.strip()
231
+ if not line:
232
+ continue
233
+ try:
234
+ item = json.loads(line)
235
+ file_path = item.get("file") or item.get("path")
236
+ entry_type = item.get("type", "file")
237
+
238
+ if not file_path:
239
+ continue
240
+
241
+ if entry_type == "directory":
242
+ # Read all .md files in directory
243
+ dir_contents = read_directory_contents(base_path, file_path)
244
+ results.extend(dir_contents)
245
+ else:
246
+ # Read single file
247
+ content = read_file_content(base_path, file_path)
248
+ if content:
249
+ results.append((file_path, content))
250
+ except json.JSONDecodeError:
251
+ continue
252
+ except Exception:
253
+ pass
254
+
255
+ return results
256
+
257
+
258
+ def get_agent_context(repo_root: str, task_dir: str, agent_type: str) -> str:
259
+ """
260
+ Get complete context for specified agent
261
+
262
+ Prioritize agent-specific jsonl, fallback to spec.jsonl if not exists
263
+ """
264
+ context_parts = []
265
+
266
+ # 1. Try agent-specific jsonl
267
+ agent_jsonl = f"{task_dir}/{agent_type}.jsonl"
268
+ agent_entries = read_jsonl_entries(repo_root, agent_jsonl)
269
+
270
+ # 2. If agent-specific jsonl doesn't exist or empty, fallback to spec.jsonl
271
+ if not agent_entries:
272
+ agent_entries = read_jsonl_entries(repo_root, f"{task_dir}/spec.jsonl")
273
+
274
+ # 3. Add all files from jsonl
275
+ for file_path, content in agent_entries:
276
+ context_parts.append(f"=== {file_path} ===\n{content}")
277
+
278
+ return "\n\n".join(context_parts)
279
+
280
+
281
+ def get_implement_context(repo_root: str, task_dir: str) -> str:
282
+ """
283
+ Complete context for Implement Agent
284
+
285
+ Read order:
286
+ 1. All files in implement.jsonl (dev specs)
287
+ 2. prd.md (requirements)
288
+ 3. info.md (technical design)
289
+ """
290
+ context_parts = []
291
+
292
+ # 1. Read implement.jsonl (or fallback to spec.jsonl)
293
+ base_context = get_agent_context(repo_root, task_dir, "implement")
294
+ if base_context:
295
+ context_parts.append(base_context)
296
+
297
+ # 2. Requirements document
298
+ prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
299
+ if prd_content:
300
+ context_parts.append(f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}")
301
+
302
+ # 3. Technical design
303
+ info_content = read_file_content(repo_root, f"{task_dir}/info.md")
304
+ if info_content:
305
+ context_parts.append(
306
+ f"=== {task_dir}/info.md (Technical Design) ===\n{info_content}"
307
+ )
308
+
309
+ return "\n\n".join(context_parts)
310
+
311
+
312
+ def get_check_context(repo_root: str, task_dir: str) -> str:
313
+ """
314
+ Complete context for Check Agent
315
+
316
+ Read order:
317
+ 1. All files in check.jsonl (check specs + dev specs)
318
+ 2. prd.md (for understanding task intent)
319
+ """
320
+ context_parts = []
321
+
322
+ # 1. Read check.jsonl (or fallback to spec.jsonl + hardcoded check files)
323
+ check_entries = read_jsonl_entries(repo_root, f"{task_dir}/check.jsonl")
324
+
325
+ if check_entries:
326
+ for file_path, content in check_entries:
327
+ context_parts.append(f"=== {file_path} ===\n{content}")
328
+ else:
329
+ # Fallback: use hardcoded check files + spec.jsonl
330
+ check_files = [
331
+ (".claude/commands/trellis/finish-work.md", "Finish work checklist"),
332
+ (".claude/commands/trellis/check-cross-layer.md", "Cross-layer check spec"),
333
+ (".claude/commands/trellis/check-backend.md", "Backend check spec"),
334
+ (".claude/commands/trellis/check-frontend.md", "Frontend check spec"),
335
+ ]
336
+ for file_path, description in check_files:
337
+ content = read_file_content(repo_root, file_path)
338
+ if content:
339
+ context_parts.append(f"=== {file_path} ({description}) ===\n{content}")
340
+
341
+ # Add spec.jsonl
342
+ spec_entries = read_jsonl_entries(repo_root, f"{task_dir}/spec.jsonl")
343
+ for file_path, content in spec_entries:
344
+ context_parts.append(f"=== {file_path} (Dev spec) ===\n{content}")
345
+
346
+ # 2. Requirements document (for understanding task intent)
347
+ prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
348
+ if prd_content:
349
+ context_parts.append(
350
+ f"=== {task_dir}/prd.md (Requirements - for understanding intent) ===\n{prd_content}"
351
+ )
352
+
353
+ return "\n\n".join(context_parts)
354
+
355
+
356
+ def get_finish_context(repo_root: str, task_dir: str) -> str:
357
+ """
358
+ Complete context for Finish phase (final check before PR)
359
+
360
+ Read order:
361
+ 1. All files in finish.jsonl (if exists)
362
+ 2. Fallback to finish-work.md only (lightweight final check)
363
+ 3. prd.md (for verifying requirements are met)
364
+ """
365
+ context_parts = []
366
+
367
+ # 1. Try finish.jsonl first
368
+ finish_entries = read_jsonl_entries(repo_root, f"{task_dir}/finish.jsonl")
369
+
370
+ if finish_entries:
371
+ for file_path, content in finish_entries:
372
+ context_parts.append(f"=== {file_path} ===\n{content}")
373
+ else:
374
+ # Fallback: only finish-work.md (lightweight)
375
+ finish_work = read_file_content(
376
+ repo_root, ".claude/commands/trellis/finish-work.md"
377
+ )
378
+ if finish_work:
379
+ context_parts.append(
380
+ f"=== .claude/commands/trellis/finish-work.md (Finish checklist) ===\n{finish_work}"
381
+ )
382
+
383
+ # 2. Requirements document (for verifying requirements are met)
384
+ prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
385
+ if prd_content:
386
+ context_parts.append(
387
+ f"=== {task_dir}/prd.md (Requirements - verify all met) ===\n{prd_content}"
388
+ )
389
+
390
+ return "\n\n".join(context_parts)
391
+
392
+
393
+ def get_debug_context(repo_root: str, task_dir: str) -> str:
394
+ """
395
+ Complete context for Debug Agent
396
+
397
+ Read order:
398
+ 1. All files in debug.jsonl (specs needed for fixing)
399
+ 2. codex-review-output.txt (Codex Review results)
400
+ """
401
+ context_parts = []
402
+
403
+ # 1. Read debug.jsonl (or fallback to spec.jsonl + hardcoded check files)
404
+ debug_entries = read_jsonl_entries(repo_root, f"{task_dir}/debug.jsonl")
405
+
406
+ if debug_entries:
407
+ for file_path, content in debug_entries:
408
+ context_parts.append(f"=== {file_path} ===\n{content}")
409
+ else:
410
+ # Fallback: use spec.jsonl + hardcoded check files
411
+ spec_entries = read_jsonl_entries(repo_root, f"{task_dir}/spec.jsonl")
412
+ for file_path, content in spec_entries:
413
+ context_parts.append(f"=== {file_path} (Dev spec) ===\n{content}")
414
+
415
+ check_files = [
416
+ (".claude/commands/trellis/check-backend.md", "Backend check spec"),
417
+ (".claude/commands/trellis/check-frontend.md", "Frontend check spec"),
418
+ (".claude/commands/trellis/check-cross-layer.md", "Cross-layer check spec"),
419
+ ]
420
+ for file_path, description in check_files:
421
+ content = read_file_content(repo_root, file_path)
422
+ if content:
423
+ context_parts.append(f"=== {file_path} ({description}) ===\n{content}")
424
+
425
+ # 2. Codex review output (if exists)
426
+ codex_output = read_file_content(repo_root, f"{task_dir}/codex-review-output.txt")
427
+ if codex_output:
428
+ context_parts.append(
429
+ f"=== {task_dir}/codex-review-output.txt (Codex Review Results) ===\n{codex_output}"
430
+ )
431
+
432
+ return "\n\n".join(context_parts)
433
+
434
+
435
+ def build_implement_prompt(original_prompt: str, context: str) -> str:
436
+ """Build complete prompt for Implement"""
437
+ return f"""# Implement Agent Task
438
+
439
+ You are the Implement Agent in the Multi-Agent Pipeline.
440
+
441
+ ## Your Context
442
+
443
+ All the information you need has been prepared for you:
444
+
445
+ {context}
446
+
447
+ ---
448
+
449
+ ## Your Task
450
+
451
+ {original_prompt}
452
+
453
+ ---
454
+
455
+ ## Workflow
456
+
457
+ 1. **Understand specs** - All dev specs are injected above, understand them
458
+ 2. **Understand requirements** - Read requirements document and technical design
459
+ 3. **Implement feature** - Implement following specs and design
460
+ 4. **Self-check** - Ensure code quality against check specs
461
+
462
+ ## Important Constraints
463
+
464
+ - Do NOT execute git commit, only code modifications
465
+ - Follow all dev specs injected above
466
+ - Report list of modified/created files when done"""
467
+
468
+
469
+ def build_check_prompt(original_prompt: str, context: str) -> str:
470
+ """Build complete prompt for Check"""
471
+ return f"""# Check Agent Task
472
+
473
+ You are the Check Agent in the Multi-Agent Pipeline (code and cross-layer checker).
474
+
475
+ ## Your Context
476
+
477
+ All check specs and dev specs you need:
478
+
479
+ {context}
480
+
481
+ ---
482
+
483
+ ## Your Task
484
+
485
+ {original_prompt}
486
+
487
+ ---
488
+
489
+ ## Workflow
490
+
491
+ 1. **Get changes** - Run `git diff --name-only` and `git diff` to get code changes
492
+ 2. **Check against specs** - Check item by item against specs above
493
+ 3. **Self-fix** - Fix issues directly, don't just report
494
+ 4. **Run verification** - Run project's lint and typecheck commands
495
+
496
+ ## Important Constraints
497
+
498
+ - Fix issues yourself, don't just report
499
+ - Must execute complete checklist in check specs
500
+ - Pay special attention to impact radius analysis (L1-L5)"""
501
+
502
+
503
+ def build_finish_prompt(original_prompt: str, context: str) -> str:
504
+ """Build complete prompt for Finish (final check before PR)"""
505
+ return f"""# Finish Agent Task
506
+
507
+ You are performing the final check before creating a PR.
508
+
509
+ ## Your Context
510
+
511
+ Finish checklist and requirements:
512
+
513
+ {context}
514
+
515
+ ---
516
+
517
+ ## Your Task
518
+
519
+ {original_prompt}
520
+
521
+ ---
522
+
523
+ ## Workflow
524
+
525
+ 1. **Review changes** - Run `git diff --name-only` to see all changed files
526
+ 2. **Verify requirements** - Check each requirement in prd.md is implemented
527
+ 3. **Run final checks** - Execute finish-work.md checklist
528
+ 4. **Confirm ready** - Ensure code is ready for PR
529
+
530
+ ## Important Constraints
531
+
532
+ - This is a final verification, not a fix phase
533
+ - If critical issues found, report them clearly
534
+ - Verify all acceptance criteria in prd.md are met"""
535
+
536
+
537
+ def build_debug_prompt(original_prompt: str, context: str) -> str:
538
+ """Build complete prompt for Debug"""
539
+ return f"""# Debug Agent Task
540
+
541
+ You are the Debug Agent in the Multi-Agent Pipeline (issue fixer).
542
+
543
+ ## Your Context
544
+
545
+ Dev specs and Codex Review results:
546
+
547
+ {context}
548
+
549
+ ---
550
+
551
+ ## Your Task
552
+
553
+ {original_prompt}
554
+
555
+ ---
556
+
557
+ ## Workflow
558
+
559
+ 1. **Understand issues** - Analyze issues pointed out in Codex Review
560
+ 2. **Locate code** - Find positions that need fixing
561
+ 3. **Fix against specs** - Fix issues following dev specs
562
+ 4. **Verify fixes** - Run typecheck to ensure no new issues
563
+
564
+ ## Important Constraints
565
+
566
+ - Do NOT execute git commit, only code modifications
567
+ - Run typecheck after each fix to verify
568
+ - Report which issues were fixed and which files were modified"""
569
+
570
+
571
+ def get_research_context(repo_root: str, task_dir: str | None) -> str:
572
+ """
573
+ Context for Research Agent
574
+
575
+ Research doesn't need much preset context, only needs:
576
+ 1. Project structure overview (where spec directories are)
577
+ 2. Optional research.jsonl (if there are specific search needs)
578
+ """
579
+ context_parts = []
580
+
581
+ # 1. Project structure overview (uses constants for paths)
582
+ spec_path = f"{DIR_WORKFLOW}/{DIR_SPEC}"
583
+ project_structure = f"""## Project Spec Directory Structure
584
+
585
+ ```
586
+ {spec_path}/
587
+ ├── shared/ # Cross-project common specs (TypeScript, code quality, git)
588
+ ├── frontend/ # Frontend standards
589
+ ├── backend/ # Backend standards
590
+ └── guides/ # Thinking guides (cross-layer, code reuse, etc.)
591
+
592
+ {DIR_WORKFLOW}/big-question/ # Known issues and pitfalls
593
+ ```
594
+
595
+ ## Search Tips
596
+
597
+ - Spec files: `{spec_path}/**/*.md`
598
+ - Known issues: `{DIR_WORKFLOW}/big-question/`
599
+ - Code search: Use Glob and Grep tools
600
+ - Tech solutions: Use mcp__exa__web_search_exa or mcp__exa__get_code_context_exa"""
601
+
602
+ context_parts.append(project_structure)
603
+
604
+ # 2. If task directory exists, try reading research.jsonl (optional)
605
+ if task_dir:
606
+ research_entries = read_jsonl_entries(repo_root, f"{task_dir}/research.jsonl")
607
+ if research_entries:
608
+ context_parts.append(
609
+ "\n## Additional Search Context (from research.jsonl)\n"
610
+ )
611
+ for file_path, content in research_entries:
612
+ context_parts.append(f"=== {file_path} ===\n{content}")
613
+
614
+ return "\n\n".join(context_parts)
615
+
616
+
617
+ def build_research_prompt(original_prompt: str, context: str) -> str:
618
+ """Build complete prompt for Research"""
619
+ return f"""# Research Agent Task
620
+
621
+ You are the Research Agent in the Multi-Agent Pipeline (search researcher).
622
+
623
+ ## Core Principle
624
+
625
+ **You do one thing: find and explain information.**
626
+
627
+ You are a documenter, not a reviewer.
628
+
629
+ ## Project Info
630
+
631
+ {context}
632
+
633
+ ---
634
+
635
+ ## Your Task
636
+
637
+ {original_prompt}
638
+
639
+ ---
640
+
641
+ ## Workflow
642
+
643
+ 1. **Understand query** - Determine search type (internal/external) and scope
644
+ 2. **Plan search** - List search steps for complex queries
645
+ 3. **Execute search** - Execute multiple independent searches in parallel
646
+ 4. **Organize results** - Output structured report
647
+
648
+ ## Search Tools
649
+
650
+ | Tool | Purpose |
651
+ |------|---------|
652
+ | Glob | Search by filename pattern |
653
+ | Grep | Search by content |
654
+ | Read | Read file content |
655
+ | mcp__exa__web_search_exa | External web search |
656
+ | mcp__exa__get_code_context_exa | External code/doc search |
657
+
658
+ ## Strict Boundaries
659
+
660
+ **Only allowed**: Describe what exists, where it is, how it works
661
+
662
+ **Forbidden** (unless explicitly asked):
663
+ - Suggest improvements
664
+ - Criticize implementation
665
+ - Recommend refactoring
666
+ - Modify any files
667
+
668
+ ## Report Format
669
+
670
+ Provide structured search results including:
671
+ - List of files found (with paths)
672
+ - Code pattern analysis (if applicable)
673
+ - Related spec documents
674
+ - External references (if any)"""
675
+
676
+
677
+ def get_ccg_impl_context(repo_root: str, task_dir: str) -> str:
678
+ """
679
+ Complete context for CCG Implement Agent
680
+
681
+ Read order:
682
+ 1. All files in implement.jsonl (includes openspec artifacts)
683
+ 2. prd.md (contains change path and phase number)
684
+ 3. Extract phase-specific tasks from tasks.md
685
+ """
686
+ context_parts = []
687
+
688
+ # 1. Read implement.jsonl (includes specs.md, design.md, tasks.md from openspec)
689
+ base_context = get_agent_context(repo_root, task_dir, "implement")
690
+ if base_context:
691
+ context_parts.append(base_context)
692
+
693
+ # 2. Requirements document (contains change path and phase)
694
+ prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
695
+ if prd_content:
696
+ context_parts.append(f"=== {task_dir}/prd.md (Task Definition) ===\n{prd_content}")
697
+
698
+ # 3. Try to extract phase number and read phase-specific tasks
699
+ # Parse prd.md to get openspec_change and phase_number
700
+ task_json_content = read_file_content(repo_root, f"{task_dir}/{FILE_TASK_JSON}")
701
+ if task_json_content:
702
+ try:
703
+ task_data = json.loads(task_json_content)
704
+ openspec_change = task_data.get("openspec_change")
705
+ phase_number = task_data.get("phase_number")
706
+
707
+ if openspec_change and phase_number:
708
+ # Read and extract phase-specific section from tasks.md
709
+ tasks_md = read_file_content(repo_root, f"{openspec_change}/tasks.md")
710
+ if tasks_md:
711
+ phase_section = extract_phase_section(tasks_md, phase_number)
712
+ if phase_section:
713
+ context_parts.append(
714
+ f"=== Phase {phase_number} Tasks (from {openspec_change}/tasks.md) ===\n{phase_section}"
715
+ )
716
+ except (json.JSONDecodeError, KeyError):
717
+ pass
718
+
719
+ return "\n\n".join(context_parts)
720
+
721
+
722
+ def extract_phase_section(tasks_md: str, phase_number: int) -> str:
723
+ """
724
+ Extract a specific phase section from tasks.md
725
+
726
+ Looks for pattern: ## Phase N: Title
727
+ Returns content until next ## Phase or end of file
728
+ """
729
+ lines = tasks_md.split("\n")
730
+ in_phase = False
731
+ phase_lines = []
732
+ phase_header = f"## Phase {phase_number}"
733
+
734
+ for line in lines:
735
+ if line.startswith(phase_header):
736
+ in_phase = True
737
+ phase_lines.append(line)
738
+ elif in_phase:
739
+ # Check if we hit next phase or progress section
740
+ if line.startswith("## Phase ") or line.startswith("## 进度统计"):
741
+ break
742
+ phase_lines.append(line)
743
+
744
+ return "\n".join(phase_lines).strip()
745
+
746
+
747
+ def build_ccg_impl_prompt(original_prompt: str, context: str) -> str:
748
+ """Build complete prompt for CCG Implement Agent"""
749
+ return f"""# CCG Implement Agent Task
750
+
751
+ You are the CCG Implement Agent - multi-model collaborative implementation.
752
+
753
+ ## Your Context
754
+
755
+ All the information you need has been prepared for you:
756
+
757
+ {context}
758
+
759
+ ---
760
+
761
+ ## Your Task
762
+
763
+ {original_prompt}
764
+
765
+ ---
766
+
767
+ ## Workflow
768
+
769
+ 1. **Parse prd.md** - Extract change path and phase number
770
+ 2. **Read Phase tasks** - Get task list for the specified phase
771
+ 3. **Read specs and design** - Understand requirements and technical decisions
772
+ 4. **Multi-model collaboration**:
773
+ - Route backend tasks to Codex
774
+ - Route frontend tasks to Gemini
775
+ - Request unified diff patch format only
776
+ 5. **Rewrite prototype** - External outputs are reference only, rewrite to production code
777
+ 6. **Side-effect review** - Verify changes don't exceed scope
778
+ 7. **Multi-model review** - Launch Codex AND Gemini in parallel for review
779
+ 8. **Update status** - Mark completed tasks in tasks.md as [x]
780
+
781
+ ## Important Constraints
782
+
783
+ - Do NOT execute git commit, only code modifications
784
+ - External model outputs are prototypes only - NEVER apply directly
785
+ - Request unified diff patch format from external models
786
+ - Follow all dev specs injected above
787
+ - Keep implementation within tasks.md scope - no scope creep
788
+ - Report list of modified/created files when done"""
789
+
790
+
791
+ def get_ccg_review_context(repo_root: str, task_dir: str) -> str:
792
+ """
793
+ Complete context for CCG Review Agent
794
+
795
+ Read order:
796
+ 1. All files in check.jsonl (review specs)
797
+ 2. prd.md (task requirements)
798
+ 3. OpenSpec specs.md if available
799
+ """
800
+ context_parts = []
801
+
802
+ # 1. Read check.jsonl
803
+ check_entries = read_jsonl_entries(repo_root, f"{task_dir}/check.jsonl")
804
+ if check_entries:
805
+ for file_path, content in check_entries:
806
+ context_parts.append(f"=== {file_path} ===\n{content}")
807
+
808
+ # 2. Requirements document
809
+ prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
810
+ if prd_content:
811
+ context_parts.append(f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}")
812
+
813
+ # 3. Try to get OpenSpec specs.md from task.json
814
+ task_json_content = read_file_content(repo_root, f"{task_dir}/{FILE_TASK_JSON}")
815
+ if task_json_content:
816
+ try:
817
+ task_data = json.loads(task_json_content)
818
+ openspec_change = task_data.get("openspec_change")
819
+ if openspec_change:
820
+ specs_content = read_file_content(repo_root, f"{openspec_change}/specs.md")
821
+ if specs_content:
822
+ context_parts.append(
823
+ f"=== {openspec_change}/specs.md (Constraints) ===\n{specs_content}"
824
+ )
825
+ except (json.JSONDecodeError, KeyError):
826
+ pass
827
+
828
+ return "\n\n".join(context_parts)
829
+
830
+
831
+ def build_ccg_review_prompt(original_prompt: str, context: str) -> str:
832
+ """Build complete prompt for CCG Review Agent"""
833
+ return f"""# CCG Review Agent Task
834
+
835
+ You are the CCG Review Agent - dual-model cross-validation code reviewer.
836
+
837
+ ## Your Context
838
+
839
+ All review specs and constraints:
840
+
841
+ {context}
842
+
843
+ ---
844
+
845
+ ## Your Task
846
+
847
+ {original_prompt}
848
+
849
+ ---
850
+
851
+ ## Workflow
852
+
853
+ 1. **Get changes** - Run `git diff --name-only` and `git diff` to get code changes
854
+ 2. **Multi-model review (PARALLEL)** - Launch BOTH Codex AND Gemini simultaneously
855
+ - Codex: spec compliance, logic, security, regression
856
+ - Gemini: patterns, maintainability, integration, alignment
857
+ 3. **Synthesize findings** - Merge and classify by severity (Critical/Warning/Info)
858
+ 4. **Self-fix Critical issues** - Fix issues directly, don't just report
859
+ 5. **Run verification** - Run project's lint and typecheck commands
860
+
861
+ ## Important Constraints
862
+
863
+ - Launch BOTH models in parallel with run_in_background: true
864
+ - Self-fix Critical issues before reporting
865
+ - External model outputs are prototypes only - always rewrite
866
+ - Do NOT execute git commit
867
+ - Report all findings even if fixed"""
868
+
869
+
870
+ def main():
871
+ try:
872
+ input_data = json.load(sys.stdin)
873
+ except json.JSONDecodeError:
874
+ sys.exit(0)
875
+
876
+ tool_name = input_data.get("tool_name", "")
877
+
878
+ if tool_name != "Task":
879
+ sys.exit(0)
880
+
881
+ tool_input = input_data.get("tool_input", {})
882
+ subagent_type = tool_input.get("subagent_type", "")
883
+ original_prompt = tool_input.get("prompt", "")
884
+ cwd = input_data.get("cwd", os.getcwd())
885
+
886
+ # Only handle subagent types we care about
887
+ if subagent_type not in AGENTS_ALL:
888
+ sys.exit(0)
889
+
890
+ # Find repo root
891
+ repo_root = find_repo_root(cwd)
892
+ if not repo_root:
893
+ sys.exit(0)
894
+
895
+ # Get current task directory (research doesn't require it)
896
+ task_dir = get_current_task(repo_root)
897
+
898
+ # implement/check/debug need task directory
899
+ if subagent_type in AGENTS_REQUIRE_TASK:
900
+ if not task_dir:
901
+ sys.exit(0)
902
+ # Check if task directory exists
903
+ task_dir_full = os.path.join(repo_root, task_dir)
904
+ if not os.path.exists(task_dir_full):
905
+ sys.exit(0)
906
+
907
+ # Update current_phase in task.json (system-level enforcement)
908
+ update_current_phase(repo_root, task_dir, subagent_type)
909
+
910
+ # Check for [finish] marker in prompt (check agent with finish context)
911
+ is_finish_phase = "[finish]" in original_prompt.lower()
912
+
913
+ # Get context and build prompt based on subagent type
914
+ if subagent_type == AGENT_IMPLEMENT:
915
+ assert task_dir is not None # validated above
916
+ context = get_implement_context(repo_root, task_dir)
917
+ new_prompt = build_implement_prompt(original_prompt, context)
918
+ elif subagent_type == AGENT_CHECK:
919
+ assert task_dir is not None # validated above
920
+ if is_finish_phase:
921
+ # Finish phase: use finish context (lighter, focused on final verification)
922
+ context = get_finish_context(repo_root, task_dir)
923
+ new_prompt = build_finish_prompt(original_prompt, context)
924
+ else:
925
+ # Regular check phase: use check context (full specs for self-fix loop)
926
+ context = get_check_context(repo_root, task_dir)
927
+ new_prompt = build_check_prompt(original_prompt, context)
928
+ elif subagent_type == AGENT_DEBUG:
929
+ assert task_dir is not None # validated above
930
+ context = get_debug_context(repo_root, task_dir)
931
+ new_prompt = build_debug_prompt(original_prompt, context)
932
+ elif subagent_type == AGENT_RESEARCH:
933
+ # Research can work without task directory
934
+ context = get_research_context(repo_root, task_dir)
935
+ new_prompt = build_research_prompt(original_prompt, context)
936
+ elif subagent_type == AGENT_CCG_IMPL:
937
+ assert task_dir is not None # validated above
938
+ context = get_ccg_impl_context(repo_root, task_dir)
939
+ new_prompt = build_ccg_impl_prompt(original_prompt, context)
940
+ elif subagent_type == AGENT_CCG_REVIEW:
941
+ assert task_dir is not None # validated above
942
+ context = get_ccg_review_context(repo_root, task_dir)
943
+ new_prompt = build_ccg_review_prompt(original_prompt, context)
944
+ else:
945
+ sys.exit(0)
946
+
947
+ if not context:
948
+ sys.exit(0)
949
+
950
+ # Return updated input
951
+ output = {
952
+ "hookSpecificOutput": {
953
+ "hookEventName": "PreToolUse",
954
+ "permissionDecision": "allow",
955
+ "updatedInput": {**tool_input, "prompt": new_prompt},
956
+ }
957
+ }
958
+
959
+ print(json.dumps(output, ensure_ascii=False))
960
+ sys.exit(0)
961
+
962
+
963
+ if __name__ == "__main__":
964
+ main()