ccgx-workflow 1.0.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 (212) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +469 -0
  3. package/README.zh-CN.md +466 -0
  4. package/bin/ccg.mjs +2 -0
  5. package/dist/cli.d.mts +1 -0
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.mjs +173 -0
  8. package/dist/index.d.mts +1774 -0
  9. package/dist/index.d.ts +1774 -0
  10. package/dist/index.mjs +2029 -0
  11. package/dist/shared/ccgx-workflow.WgUzkiC3.mjs +5248 -0
  12. package/package.json +129 -0
  13. package/templates/commands/agents/assumptions-analyzer.md +129 -0
  14. package/templates/commands/agents/code-fixer.md +292 -0
  15. package/templates/commands/agents/codebase-mapper.md +152 -0
  16. package/templates/commands/agents/debug-session-manager.md +247 -0
  17. package/templates/commands/agents/debugger.md +111 -0
  18. package/templates/commands/agents/eval-auditor.md +171 -0
  19. package/templates/commands/agents/framework-selector.md +152 -0
  20. package/templates/commands/agents/get-current-datetime.md +29 -0
  21. package/templates/commands/agents/init-architect.md +114 -0
  22. package/templates/commands/agents/integration-checker.md +163 -0
  23. package/templates/commands/agents/interface-auditor.md +170 -0
  24. package/templates/commands/agents/nyquist-auditor.md +131 -0
  25. package/templates/commands/agents/pattern-mapper.md +111 -0
  26. package/templates/commands/agents/phase-runner.md +321 -0
  27. package/templates/commands/agents/plan-checker.md +255 -0
  28. package/templates/commands/agents/planner.md +320 -0
  29. package/templates/commands/agents/team-architect.md +186 -0
  30. package/templates/commands/agents/team-qa.md +121 -0
  31. package/templates/commands/agents/team-reviewer.md +157 -0
  32. package/templates/commands/agents/ui-ux-designer.md +573 -0
  33. package/templates/commands/agents/verifier.md +274 -0
  34. package/templates/commands/analyze.md +210 -0
  35. package/templates/commands/autonomous.md +792 -0
  36. package/templates/commands/cancel.md +132 -0
  37. package/templates/commands/clean-branches.md +117 -0
  38. package/templates/commands/codex-exec.md +404 -0
  39. package/templates/commands/commit.md +151 -0
  40. package/templates/commands/context.md +332 -0
  41. package/templates/commands/debate.md +165 -0
  42. package/templates/commands/debug.md +226 -0
  43. package/templates/commands/enhance.md +64 -0
  44. package/templates/commands/execute.md +380 -0
  45. package/templates/commands/init.md +123 -0
  46. package/templates/commands/optimize.md +217 -0
  47. package/templates/commands/plan.md +373 -0
  48. package/templates/commands/result.md +106 -0
  49. package/templates/commands/review.md +338 -0
  50. package/templates/commands/rollback.md +116 -0
  51. package/templates/commands/spec-impl.md +139 -0
  52. package/templates/commands/spec-init.md +101 -0
  53. package/templates/commands/spec-plan.md +210 -0
  54. package/templates/commands/spec-research.md +152 -0
  55. package/templates/commands/spec-review.md +120 -0
  56. package/templates/commands/status.md +206 -0
  57. package/templates/commands/team-exec.md +265 -0
  58. package/templates/commands/test.md +236 -0
  59. package/templates/commands/verify-work.md +338 -0
  60. package/templates/commands/verify.md +66 -0
  61. package/templates/commands/workflow.md +190 -0
  62. package/templates/commands/worktree.md +128 -0
  63. package/templates/hooks/ccg-context-monitor.js +159 -0
  64. package/templates/hooks/ccg-session-state.cjs +510 -0
  65. package/templates/hooks/ccg-statusline.js +142 -0
  66. package/templates/output-styles/abyss-command.md +56 -0
  67. package/templates/output-styles/abyss-concise.md +89 -0
  68. package/templates/output-styles/abyss-cultivator.md +302 -0
  69. package/templates/output-styles/abyss-ritual.md +70 -0
  70. package/templates/output-styles/engineer-professional.md +89 -0
  71. package/templates/output-styles/laowang-engineer.md +127 -0
  72. package/templates/output-styles/nekomata-engineer.md +120 -0
  73. package/templates/output-styles/ojousama-engineer.md +121 -0
  74. package/templates/prompts/claude/analyzer.md +59 -0
  75. package/templates/prompts/claude/architect.md +54 -0
  76. package/templates/prompts/claude/debugger.md +71 -0
  77. package/templates/prompts/claude/optimizer.md +73 -0
  78. package/templates/prompts/claude/reviewer.md +63 -0
  79. package/templates/prompts/claude/tester.md +69 -0
  80. package/templates/prompts/codex/analyzer.md +58 -0
  81. package/templates/prompts/codex/architect.md +54 -0
  82. package/templates/prompts/codex/debugger.md +74 -0
  83. package/templates/prompts/codex/optimizer.md +81 -0
  84. package/templates/prompts/codex/reviewer.md +73 -0
  85. package/templates/prompts/codex/tester.md +62 -0
  86. package/templates/prompts/gemini/analyzer.md +61 -0
  87. package/templates/prompts/gemini/architect.md +55 -0
  88. package/templates/prompts/gemini/debugger.md +78 -0
  89. package/templates/prompts/gemini/frontend.md +64 -0
  90. package/templates/prompts/gemini/optimizer.md +84 -0
  91. package/templates/prompts/gemini/reviewer.md +80 -0
  92. package/templates/prompts/gemini/tester.md +68 -0
  93. package/templates/rules/ccg-skill-routing.md +83 -0
  94. package/templates/rules/ccg-skills.md +71 -0
  95. package/templates/scripts/ccg-phase-runner-launcher.mjs +467 -0
  96. package/templates/scripts/invoke-model.mjs +949 -0
  97. package/templates/scripts/repatch-gemini-plugin.mjs +194 -0
  98. package/templates/skills/SKILL.md +92 -0
  99. package/templates/skills/domains/ai/SKILL.md +35 -0
  100. package/templates/skills/domains/ai/agent-dev.md +242 -0
  101. package/templates/skills/domains/ai/llm-security.md +288 -0
  102. package/templates/skills/domains/ai/prompt-and-eval.md +279 -0
  103. package/templates/skills/domains/ai/rag-system.md +542 -0
  104. package/templates/skills/domains/architecture/SKILL.md +43 -0
  105. package/templates/skills/domains/architecture/api-design.md +225 -0
  106. package/templates/skills/domains/architecture/caching.md +299 -0
  107. package/templates/skills/domains/architecture/cloud-native.md +285 -0
  108. package/templates/skills/domains/architecture/message-queue.md +329 -0
  109. package/templates/skills/domains/architecture/security-arch.md +297 -0
  110. package/templates/skills/domains/data-engineering/SKILL.md +208 -0
  111. package/templates/skills/domains/development/SKILL.md +47 -0
  112. package/templates/skills/domains/development/cpp.md +246 -0
  113. package/templates/skills/domains/development/go.md +323 -0
  114. package/templates/skills/domains/development/java.md +277 -0
  115. package/templates/skills/domains/development/python.md +288 -0
  116. package/templates/skills/domains/development/rust.md +313 -0
  117. package/templates/skills/domains/development/shell.md +313 -0
  118. package/templates/skills/domains/development/typescript.md +277 -0
  119. package/templates/skills/domains/devops/SKILL.md +40 -0
  120. package/templates/skills/domains/devops/cost-optimization.md +272 -0
  121. package/templates/skills/domains/devops/database.md +217 -0
  122. package/templates/skills/domains/devops/devsecops.md +198 -0
  123. package/templates/skills/domains/devops/git-workflow.md +181 -0
  124. package/templates/skills/domains/devops/observability.md +280 -0
  125. package/templates/skills/domains/devops/performance.md +336 -0
  126. package/templates/skills/domains/devops/testing.md +283 -0
  127. package/templates/skills/domains/frontend-design/SKILL.md +244 -0
  128. package/templates/skills/domains/frontend-design/agents/openai.yaml +4 -0
  129. package/templates/skills/domains/frontend-design/claymorphism/SKILL.md +121 -0
  130. package/templates/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  131. package/templates/skills/domains/frontend-design/component-patterns.md +202 -0
  132. package/templates/skills/domains/frontend-design/engineering.md +287 -0
  133. package/templates/skills/domains/frontend-design/glassmorphism/SKILL.md +142 -0
  134. package/templates/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  135. package/templates/skills/domains/frontend-design/liquid-glass/SKILL.md +139 -0
  136. package/templates/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  137. package/templates/skills/domains/frontend-design/neubrutalism/SKILL.md +145 -0
  138. package/templates/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  139. package/templates/skills/domains/frontend-design/reference/color-and-contrast.md +132 -0
  140. package/templates/skills/domains/frontend-design/reference/interaction-design.md +195 -0
  141. package/templates/skills/domains/frontend-design/reference/motion-design.md +99 -0
  142. package/templates/skills/domains/frontend-design/reference/responsive-design.md +114 -0
  143. package/templates/skills/domains/frontend-design/reference/spatial-design.md +100 -0
  144. package/templates/skills/domains/frontend-design/reference/typography.md +133 -0
  145. package/templates/skills/domains/frontend-design/reference/ux-writing.md +107 -0
  146. package/templates/skills/domains/frontend-design/state-management.md +680 -0
  147. package/templates/skills/domains/frontend-design/ui-aesthetics.md +110 -0
  148. package/templates/skills/domains/frontend-design/ux-principles.md +156 -0
  149. package/templates/skills/domains/infrastructure/SKILL.md +201 -0
  150. package/templates/skills/domains/mobile/SKILL.md +225 -0
  151. package/templates/skills/domains/orchestration/SKILL.md +30 -0
  152. package/templates/skills/domains/orchestration/multi-agent.md +263 -0
  153. package/templates/skills/domains/security/SKILL.md +73 -0
  154. package/templates/skills/domains/security/blue-team.md +436 -0
  155. package/templates/skills/domains/security/code-audit.md +265 -0
  156. package/templates/skills/domains/security/pentest.md +226 -0
  157. package/templates/skills/domains/security/red-team.md +374 -0
  158. package/templates/skills/domains/security/threat-intel.md +372 -0
  159. package/templates/skills/domains/security/vuln-research.md +369 -0
  160. package/templates/skills/impeccable/adapt/SKILL.md +201 -0
  161. package/templates/skills/impeccable/animate/SKILL.md +176 -0
  162. package/templates/skills/impeccable/arrange/SKILL.md +126 -0
  163. package/templates/skills/impeccable/audit/SKILL.md +149 -0
  164. package/templates/skills/impeccable/bolder/SKILL.md +118 -0
  165. package/templates/skills/impeccable/clarify/SKILL.md +185 -0
  166. package/templates/skills/impeccable/colorize/SKILL.md +144 -0
  167. package/templates/skills/impeccable/critique/SKILL.md +203 -0
  168. package/templates/skills/impeccable/critique/reference/cognitive-load.md +106 -0
  169. package/templates/skills/impeccable/critique/reference/heuristics-scoring.md +234 -0
  170. package/templates/skills/impeccable/critique/reference/personas.md +178 -0
  171. package/templates/skills/impeccable/delight/SKILL.md +305 -0
  172. package/templates/skills/impeccable/distill/SKILL.md +123 -0
  173. package/templates/skills/impeccable/extract/SKILL.md +94 -0
  174. package/templates/skills/impeccable/harden/SKILL.md +357 -0
  175. package/templates/skills/impeccable/normalize/SKILL.md +72 -0
  176. package/templates/skills/impeccable/onboard/SKILL.md +248 -0
  177. package/templates/skills/impeccable/optimize/SKILL.md +268 -0
  178. package/templates/skills/impeccable/overdrive/SKILL.md +143 -0
  179. package/templates/skills/impeccable/polish/SKILL.md +205 -0
  180. package/templates/skills/impeccable/quieter/SKILL.md +104 -0
  181. package/templates/skills/impeccable/teach-impeccable/SKILL.md +72 -0
  182. package/templates/skills/impeccable/typeset/SKILL.md +117 -0
  183. package/templates/skills/orchestration/multi-agent/SKILL.md +533 -0
  184. package/templates/skills/run_skill.js +129 -0
  185. package/templates/skills/scrapling/SKILL.md +98 -0
  186. package/templates/skills/scrapling/references/api-quick-ref.md +127 -0
  187. package/templates/skills/scrapling/references/cookie-vault.md +56 -0
  188. package/templates/skills/scrapling/references/maintenance.md +79 -0
  189. package/templates/skills/scrapling/references/site-patterns.md +86 -0
  190. package/templates/skills/scrapling/references/troubleshooting.md +95 -0
  191. package/templates/skills/scrapling/templates/basic_fetch.py +19 -0
  192. package/templates/skills/scrapling/templates/parse_only.py +15 -0
  193. package/templates/skills/scrapling/templates/session_login.py +19 -0
  194. package/templates/skills/scrapling/templates/stealth_cloudflare.py +28 -0
  195. package/templates/skills/tools/extract-learnings/SKILL.md +77 -0
  196. package/templates/skills/tools/forensics/SKILL.md +89 -0
  197. package/templates/skills/tools/gen-docs/SKILL.md +116 -0
  198. package/templates/skills/tools/gen-docs/scripts/doc_generator.js +435 -0
  199. package/templates/skills/tools/health/SKILL.md +121 -0
  200. package/templates/skills/tools/lib/shared.js +98 -0
  201. package/templates/skills/tools/map-codebase/SKILL.md +118 -0
  202. package/templates/skills/tools/override-refusal/SKILL.md +53 -0
  203. package/templates/skills/tools/override-refusal/agents/openai.yaml +4 -0
  204. package/templates/skills/tools/override-refusal/scripts/refusal_rewriter.js +226 -0
  205. package/templates/skills/tools/verify-change/SKILL.md +143 -0
  206. package/templates/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
  207. package/templates/skills/tools/verify-module/SKILL.md +130 -0
  208. package/templates/skills/tools/verify-module/scripts/module_scanner.js +171 -0
  209. package/templates/skills/tools/verify-quality/SKILL.md +163 -0
  210. package/templates/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
  211. package/templates/skills/tools/verify-security/SKILL.md +146 -0
  212. package/templates/skills/tools/verify-security/scripts/security_scanner.js +283 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,2029 @@
1
+ import { h as collectSkills } from './shared/ccgx-workflow.WgUzkiC3.mjs';
2
+ export { j as changeLanguage, F as checkForUpdates, H as collectInvocableSkills, G as compareVersions, l as createDefaultConfig, m as createDefaultRouting, I as generateCommandContent, n as getCcgDir, o as getConfigPath, D as getCurrentVersion, E as getLatestVersion, q as getWorkflowById, p as getWorkflowConfigs, c as i18n, e as init, b as initI18n, x as installAceTool, y as installAceToolRs, J as installSkillCommands, t as installWorkflows, B as migrateToV1_4_0, C as needsMigration, K as parseFrontmatter, a as readCcgConfig, s as showMainMenu, A as uninstallAceTool, z as uninstallWorkflows, u as update, k as writeCcgConfig } from './shared/ccgx-workflow.WgUzkiC3.mjs';
3
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, statSync, readdirSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { homedir } from 'node:os';
6
+ import { execSync } from 'node:child_process';
7
+ import 'ansis';
8
+ import 'inquirer';
9
+ import 'ora';
10
+ import 'node:util';
11
+ import 'node:url';
12
+ import 'pathe';
13
+ import 'fs-extra';
14
+ import 'smol-toml';
15
+ import 'i18next';
16
+
17
+ function phaseDir(workdir, phase) {
18
+ return join(workdir, ".context", sanitizePhase(phase));
19
+ }
20
+ function contextPath(workdir, phase) {
21
+ return join(phaseDir(workdir, phase), "CONTEXT.md");
22
+ }
23
+ function summaryPath(workdir, phase) {
24
+ return join(phaseDir(workdir, phase), "SUMMARY.md");
25
+ }
26
+ function sanitizePhase(phase) {
27
+ return phase.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
28
+ }
29
+ function extractFrontmatter(content) {
30
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
31
+ return m ? m[1] : null;
32
+ }
33
+ function parseFrontmatterFields(yaml) {
34
+ const out = {};
35
+ const lines = yaml.split(/\r?\n/);
36
+ let i = 0;
37
+ while (i < lines.length) {
38
+ const line = lines[i];
39
+ if (!line.trim() || line.trim().startsWith("#")) {
40
+ i += 1;
41
+ continue;
42
+ }
43
+ const match = line.match(/^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/);
44
+ if (!match) {
45
+ throw new Error(`phase-context: malformed frontmatter line: ${line}`);
46
+ }
47
+ const key = match[1];
48
+ const rest = match[2].trim();
49
+ if (rest === "") {
50
+ const items = [];
51
+ i += 1;
52
+ while (i < lines.length && /^\s+-\s+/.test(lines[i])) {
53
+ items.push(unquote(lines[i].replace(/^\s+-\s+/, "").trim()));
54
+ i += 1;
55
+ }
56
+ out[key] = items;
57
+ continue;
58
+ }
59
+ if (rest.startsWith("[") && rest.endsWith("]")) {
60
+ const inner = rest.slice(1, -1).trim();
61
+ out[key] = inner ? inner.split(",").map((s) => unquote(s.trim())) : [];
62
+ } else if (rest === "true" || rest === "false") {
63
+ out[key] = rest === "true";
64
+ } else {
65
+ out[key] = unquote(rest);
66
+ }
67
+ i += 1;
68
+ }
69
+ return out;
70
+ }
71
+ function unquote(s) {
72
+ if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
73
+ return s.slice(1, -1);
74
+ }
75
+ return s;
76
+ }
77
+ function serializeScalar(value) {
78
+ if (/[:#\[\],&*!|>'"%@`]/.test(value) || value === "" || /^\s|\s$/.test(value)) {
79
+ return `"${value.replace(/"/g, '\\"')}"`;
80
+ }
81
+ return value;
82
+ }
83
+ function serializeList(values) {
84
+ if (values.length === 0)
85
+ return "[]";
86
+ return `[${values.map(serializeScalar).join(", ")}]`;
87
+ }
88
+ function writeContext(workdir, ctx) {
89
+ const target = contextPath(workdir, ctx.phase);
90
+ mkdirSync(dirname(target), { recursive: true });
91
+ const frontmatter = [
92
+ "---",
93
+ `phase: ${serializeScalar(ctx.phase)}`,
94
+ `plan: ${serializeScalar(ctx.plan)}`,
95
+ `goal: ${serializeScalar(ctx.goal)}`,
96
+ `decisions: ${serializeList(ctx.decisions)}`,
97
+ `constraints: ${serializeList(ctx.constraints)}`,
98
+ `files: ${serializeList(ctx.files)}`,
99
+ `created_at: ${serializeScalar(ctx.createdAt)}`,
100
+ "---",
101
+ ""
102
+ ].join("\n");
103
+ const body = [
104
+ `# Phase Context: ${ctx.phase}`,
105
+ "",
106
+ `**Plan**: ${ctx.plan}`,
107
+ `**Goal**: ${ctx.goal}`,
108
+ "",
109
+ "## Decisions (frozen)",
110
+ ...ctx.decisions.length ? ctx.decisions.map((d) => `- ${d}`) : ["_(none)_"],
111
+ "",
112
+ "## Constraints",
113
+ ...ctx.constraints.length ? ctx.constraints.map((c) => `- ${c}`) : ["_(none)_"],
114
+ "",
115
+ "## Files",
116
+ ...ctx.files.length ? ctx.files.map((f) => `- \`${f}\``) : ["_(none)_"],
117
+ ""
118
+ ].join("\n");
119
+ writeFileSync(target, frontmatter + body, "utf8");
120
+ return target;
121
+ }
122
+ function readContext(workdir, phase) {
123
+ const target = contextPath(workdir, phase);
124
+ if (!existsSync(target))
125
+ return null;
126
+ const content = readFileSync(target, "utf8");
127
+ const fm = extractFrontmatter(content);
128
+ if (!fm)
129
+ return null;
130
+ const fields = parseFrontmatterFields(fm);
131
+ return {
132
+ phase: String(fields.phase ?? phase),
133
+ plan: String(fields.plan ?? ""),
134
+ goal: String(fields.goal ?? ""),
135
+ decisions: toList(fields.decisions),
136
+ constraints: toList(fields.constraints),
137
+ files: toList(fields.files),
138
+ createdAt: String(fields.created_at ?? "")
139
+ };
140
+ }
141
+ function writeSummary(workdir, summary) {
142
+ const target = summaryPath(workdir, summary.phase);
143
+ mkdirSync(dirname(target), { recursive: true });
144
+ const frontmatter = [
145
+ "---",
146
+ `phase: ${serializeScalar(summary.phase)}`,
147
+ `plan: ${serializeScalar(summary.plan)}`,
148
+ `provides: ${serializeList(summary.provides)}`,
149
+ `affects: ${serializeList(summary.affects)}`,
150
+ `key_files: ${serializeList(summary.keyFiles)}`,
151
+ `completed: ${summary.completed ? "true" : "false"}`,
152
+ ...summary.completedAt ? [`completed_at: ${serializeScalar(summary.completedAt)}`] : [],
153
+ ...summary.notes ? [`notes: ${serializeScalar(summary.notes)}`] : [],
154
+ "---",
155
+ ""
156
+ ].join("\n");
157
+ const body = [
158
+ `# Phase Summary: ${summary.phase}`,
159
+ "",
160
+ `**Plan**: ${summary.plan} `,
161
+ `**Status**: ${summary.completed ? "completed" : "in-progress"}`,
162
+ summary.completedAt ? `**Completed**: ${summary.completedAt}` : "",
163
+ "",
164
+ "## Provides",
165
+ ...summary.provides.length ? summary.provides.map((p) => `- ${p}`) : ["_(none)_"],
166
+ "",
167
+ "## Affects",
168
+ ...summary.affects.length ? summary.affects.map((a) => `- ${a}`) : ["_(none)_"],
169
+ "",
170
+ "## Key files",
171
+ ...summary.keyFiles.length ? summary.keyFiles.map((f) => `- \`${f}\``) : ["_(none)_"],
172
+ "",
173
+ summary.notes ? `## Notes
174
+
175
+ ${summary.notes}
176
+ ` : ""
177
+ ].filter(Boolean).join("\n");
178
+ writeFileSync(target, frontmatter + body, "utf8");
179
+ return target;
180
+ }
181
+ function readSummary(workdir, phase) {
182
+ const target = summaryPath(workdir, phase);
183
+ if (!existsSync(target))
184
+ return null;
185
+ const content = readFileSync(target, "utf8");
186
+ const fm = extractFrontmatter(content);
187
+ if (!fm)
188
+ return null;
189
+ const fields = parseFrontmatterFields(fm);
190
+ return {
191
+ phase: String(fields.phase ?? phase),
192
+ plan: String(fields.plan ?? ""),
193
+ provides: toList(fields.provides),
194
+ affects: toList(fields.affects),
195
+ keyFiles: toList(fields.key_files),
196
+ completed: fields.completed === true,
197
+ completedAt: fields.completed_at ? String(fields.completed_at) : void 0,
198
+ notes: fields.notes ? String(fields.notes) : void 0
199
+ };
200
+ }
201
+ function readSummaryFrontmatter(workdir, phase) {
202
+ const target = summaryPath(workdir, phase);
203
+ if (!existsSync(target))
204
+ return null;
205
+ const content = readFileSync(target, "utf8");
206
+ return extractFrontmatter(content);
207
+ }
208
+ function summaryTokenEstimate(frontmatter) {
209
+ return Math.ceil(frontmatter.length / 3.5);
210
+ }
211
+ function toList(value) {
212
+ if (Array.isArray(value))
213
+ return value.map(String);
214
+ if (typeof value === "string" && value)
215
+ return [value];
216
+ return [];
217
+ }
218
+
219
+ const PHASE_HEADER_RE = /^## Phase ([0-9]+(?:\.[0-9]+)?)\s*:\s*(.+?)\s*\(([a-z_]+)\)\s*$/;
220
+ const DEPENDS_ON_RE = /^-\s+\*\*Depends on\*\*\s*:\s*(.+)$/;
221
+ function parseRoadmap(content) {
222
+ const phases = [];
223
+ const lines = content.split(/\r?\n/);
224
+ let current = null;
225
+ const seen = /* @__PURE__ */ new Set();
226
+ const flushCurrent = () => {
227
+ if (current) {
228
+ if (seen.has(current.id))
229
+ throw new Error(`wave-scheduler: duplicate Phase ${current.id} declaration`);
230
+ seen.add(current.id);
231
+ phases.push(current);
232
+ }
233
+ };
234
+ for (const line of lines) {
235
+ const headerMatch = line.match(PHASE_HEADER_RE);
236
+ if (headerMatch) {
237
+ flushCurrent();
238
+ const [, id, name, statusRaw] = headerMatch;
239
+ if (!isPhaseStatus(statusRaw))
240
+ throw new Error(`wave-scheduler: Phase ${id} has illegal status "${statusRaw}"`);
241
+ current = {
242
+ id,
243
+ name: name.trim(),
244
+ status: statusRaw,
245
+ dependsOn: []
246
+ };
247
+ continue;
248
+ }
249
+ if (current) {
250
+ const depMatch = line.match(DEPENDS_ON_RE);
251
+ if (depMatch)
252
+ current.dependsOn = parseDependsOn(depMatch[1]);
253
+ }
254
+ }
255
+ flushCurrent();
256
+ return phases;
257
+ }
258
+ function parseDependsOn(raw) {
259
+ const trimmed = raw.trim().replace(/[。.]+$/, "");
260
+ if (!trimmed || /^\(none\)$/i.test(trimmed))
261
+ return [];
262
+ const out = [];
263
+ for (const tokenRaw of trimmed.split(",")) {
264
+ const token = tokenRaw.trim();
265
+ if (!token)
266
+ continue;
267
+ const rangeMatch = token.match(/^([0-9]+)\s*-\s*([0-9]+)$/);
268
+ if (rangeMatch) {
269
+ const lo = Number(rangeMatch[1]);
270
+ const hi = Number(rangeMatch[2]);
271
+ if (lo > hi)
272
+ throw new Error(`wave-scheduler: malformed range "${token}" (lo > hi)`);
273
+ for (let i = lo; i <= hi; i++)
274
+ out.push(String(i));
275
+ continue;
276
+ }
277
+ const phaseMatch = token.match(/^(?:Phase\s+)?([0-9]+(?:\.[0-9]+)?)$/i);
278
+ if (!phaseMatch)
279
+ throw new Error(`wave-scheduler: cannot parse depends-on token "${token}"`);
280
+ out.push(phaseMatch[1]);
281
+ }
282
+ return out;
283
+ }
284
+ function isPhaseStatus(s) {
285
+ return s === "pending" || s === "in_progress" || s === "completed" || s === "failed" || s === "skipped";
286
+ }
287
+ function buildWaves(phases) {
288
+ if (phases.length === 0)
289
+ return [];
290
+ const idToPhase = /* @__PURE__ */ new Map();
291
+ for (const p of phases)
292
+ idToPhase.set(p.id, p);
293
+ for (const p of phases) {
294
+ for (const dep of p.dependsOn) {
295
+ if (!idToPhase.has(dep))
296
+ throw new Error(`wave-scheduler: Phase ${p.id} depends on unknown Phase ${dep}`);
297
+ }
298
+ }
299
+ const remaining = new Set(phases.map((p) => p.id));
300
+ const remainingDeps = /* @__PURE__ */ new Map();
301
+ for (const p of phases)
302
+ remainingDeps.set(p.id, new Set(p.dependsOn));
303
+ const waves = [];
304
+ while (remaining.size > 0) {
305
+ const ready = [];
306
+ for (const p of phases) {
307
+ if (!remaining.has(p.id))
308
+ continue;
309
+ if ((remainingDeps.get(p.id)?.size ?? 0) === 0)
310
+ ready.push(p.id);
311
+ }
312
+ if (ready.length === 0) {
313
+ const stuck = Array.from(remaining).join(", ");
314
+ throw new Error(`wave-scheduler: dependency cycle detected among phases: ${stuck}`);
315
+ }
316
+ waves.push(ready);
317
+ for (const id of ready) {
318
+ remaining.delete(id);
319
+ for (const set of remainingDeps.values())
320
+ set.delete(id);
321
+ }
322
+ }
323
+ return waves;
324
+ }
325
+ function cascadeSkip(phases) {
326
+ const idToPhase = /* @__PURE__ */ new Map();
327
+ for (const p of phases)
328
+ idToPhase.set(p.id, p);
329
+ const blocked = /* @__PURE__ */ new Set();
330
+ for (const p of phases) {
331
+ if (p.status === "failed" || p.status === "skipped")
332
+ blocked.add(p.id);
333
+ }
334
+ let changed = true;
335
+ while (changed) {
336
+ changed = false;
337
+ for (const p of phases) {
338
+ if (blocked.has(p.id))
339
+ continue;
340
+ for (const dep of p.dependsOn) {
341
+ if (blocked.has(dep)) {
342
+ blocked.add(p.id);
343
+ changed = true;
344
+ break;
345
+ }
346
+ }
347
+ }
348
+ }
349
+ const result = [];
350
+ for (const p of phases) {
351
+ if (blocked.has(p.id) && p.status !== "failed" && p.status !== "skipped")
352
+ result.push(p.id);
353
+ }
354
+ return result;
355
+ }
356
+ function batchByMaxConcurrent(wave, maxConcurrent) {
357
+ if (maxConcurrent < 1)
358
+ throw new Error(`wave-scheduler: maxConcurrent must be \u2265 1, got ${maxConcurrent}`);
359
+ if (wave.length === 0)
360
+ return [];
361
+ const batches = [];
362
+ for (let i = 0; i < wave.length; i += maxConcurrent)
363
+ batches.push(wave.slice(i, i + maxConcurrent));
364
+ return batches;
365
+ }
366
+ function schedule(phases, options = {}) {
367
+ const skipCompleted = options.skipCompleted ?? true;
368
+ const cascaded = cascadeSkip(phases);
369
+ const cascadedSet = new Set(cascaded);
370
+ const schedulable = phases.filter((p) => {
371
+ if (skipCompleted && p.status === "completed")
372
+ return false;
373
+ if (p.status === "failed" || p.status === "skipped")
374
+ return false;
375
+ if (cascadedSet.has(p.id))
376
+ return false;
377
+ return true;
378
+ });
379
+ const completedSet = new Set(
380
+ phases.filter((p) => p.status === "completed").map((p) => p.id)
381
+ );
382
+ const filtered = schedulable.map((p) => ({
383
+ ...p,
384
+ dependsOn: p.dependsOn.filter((d) => !completedSet.has(d))
385
+ }));
386
+ const droppedDeps = /* @__PURE__ */ new Set([...cascadedSet, ...phases.filter((p) => p.status === "failed" || p.status === "skipped").map((p) => p.id)]);
387
+ const finalPhases = filtered.map((p) => ({
388
+ ...p,
389
+ dependsOn: p.dependsOn.filter((d) => !droppedDeps.has(d))
390
+ }));
391
+ const waves = buildWaves(finalPhases);
392
+ let batches;
393
+ if (typeof options.maxConcurrent === "number") {
394
+ const cap = options.maxConcurrent;
395
+ batches = waves.map((w) => batchByMaxConcurrent(w, cap));
396
+ }
397
+ return { waves, skipped: cascaded, batches };
398
+ }
399
+
400
+ function rolePromptFile(role) {
401
+ switch (role) {
402
+ case "architect":
403
+ return "architect.md";
404
+ case "critic":
405
+ return "reviewer.md";
406
+ // reused with adversarial framing
407
+ case "implementer":
408
+ return null;
409
+ case "tester":
410
+ return "tester.md";
411
+ case "writer":
412
+ return null;
413
+ }
414
+ }
415
+ function routeSpecialist(role, layer) {
416
+ const adversarial = role === "critic";
417
+ const promptFile = rolePromptFile(role);
418
+ if (role === "writer") {
419
+ return {
420
+ models: ["claude"],
421
+ promptFiles: [null],
422
+ adversarial: false,
423
+ runnerDecides: false
424
+ };
425
+ }
426
+ if (role === "implementer") {
427
+ if (layer === "fullstack") {
428
+ return {
429
+ models: ["codex", "gemini"],
430
+ promptFiles: [null, null],
431
+ adversarial: false,
432
+ runnerDecides: true
433
+ };
434
+ }
435
+ return {
436
+ models: ["claude"],
437
+ promptFiles: [null],
438
+ adversarial: false,
439
+ runnerDecides: false
440
+ };
441
+ }
442
+ if (layer === "fullstack" && role === "tester") {
443
+ return {
444
+ models: ["codex", "gemini"],
445
+ promptFiles: [promptFile, promptFile],
446
+ adversarial: false,
447
+ runnerDecides: true
448
+ };
449
+ }
450
+ if (layer === "fullstack") {
451
+ return {
452
+ models: ["codex", "gemini"],
453
+ promptFiles: [promptFile, promptFile],
454
+ adversarial,
455
+ runnerDecides: false
456
+ };
457
+ }
458
+ if (layer === "backend") {
459
+ return {
460
+ models: ["codex"],
461
+ promptFiles: [promptFile],
462
+ adversarial,
463
+ runnerDecides: false
464
+ };
465
+ }
466
+ return {
467
+ models: ["gemini"],
468
+ promptFiles: [promptFile],
469
+ adversarial,
470
+ runnerDecides: false
471
+ };
472
+ }
473
+ function parseRoleFlag(args) {
474
+ const m = args.match(/--role[=\s]+([a-z]+)/i);
475
+ if (!m) return null;
476
+ const candidate = m[1].toLowerCase();
477
+ if (!["architect", "critic", "implementer", "tester", "writer"].includes(candidate)) {
478
+ return null;
479
+ }
480
+ return candidate;
481
+ }
482
+ function promptFilePath(model, promptFile) {
483
+ if (promptFile === null || model === "claude") return null;
484
+ return `~/.claude/.ccg/prompts/${model}/${promptFile}`;
485
+ }
486
+
487
+ const DESCRIPTION_SOFT_LIMIT = 80;
488
+ const CONTEXT_BUDGET_THRESHOLD = 8e3;
489
+ function auditSkillDescriptions(skills) {
490
+ const rows = skills.map((s) => ({
491
+ name: s.name,
492
+ category: s.category,
493
+ length: s.description.length,
494
+ overLimit: s.description.length > DESCRIPTION_SOFT_LIMIT,
495
+ description: s.description
496
+ }));
497
+ const totalLength = rows.reduce((sum, r) => sum + r.length, 0);
498
+ const overLimitCount = rows.filter((r) => r.overLimit).length;
499
+ return {
500
+ rows,
501
+ totalLength,
502
+ overLimitCount,
503
+ budgetExceeded: totalLength > CONTEXT_BUDGET_THRESHOLD,
504
+ budgetThreshold: CONTEXT_BUDGET_THRESHOLD
505
+ };
506
+ }
507
+ function auditSkillsDirectory(skillsDir) {
508
+ const skills = collectSkills(skillsDir);
509
+ return auditSkillDescriptions(skills);
510
+ }
511
+ function renderAuditMarkdown(report) {
512
+ const sorted = [...report.rows].sort((a, b) => b.length - a.length);
513
+ const lines = [];
514
+ lines.push("# Skill Description Audit");
515
+ lines.push("");
516
+ lines.push(
517
+ `> Soft limit: ${DESCRIPTION_SOFT_LIMIT} chars per description. Context budget threshold: ${report.budgetThreshold} chars (~1% of 200k token window).`
518
+ );
519
+ lines.push("");
520
+ lines.push(`- Total skills: **${report.rows.length}**`);
521
+ lines.push(`- Total description length: **${report.totalLength}** chars`);
522
+ lines.push(`- Over-limit skills: **${report.overLimitCount}**`);
523
+ lines.push(
524
+ `- Budget status: ${report.budgetExceeded ? "**EXCEEDED** \u26A0\uFE0F" : "within budget \u2705"}`
525
+ );
526
+ lines.push("");
527
+ lines.push("| Skill | Category | Length | Over Limit? |");
528
+ lines.push("|-------|----------|--------|-------------|");
529
+ for (const row of sorted) {
530
+ lines.push(
531
+ `| \`${row.name}\` | ${row.category} | ${row.length} | ${row.overLimit ? "\u26A0\uFE0F YES" : "no"} |`
532
+ );
533
+ }
534
+ if (report.budgetExceeded) {
535
+ lines.push("");
536
+ lines.push(
537
+ `> \u26A0\uFE0F Total description length exceeds 1% context budget threshold (${report.budgetThreshold} chars). Consider shortening over-limit descriptions while preserving trigger keywords.`
538
+ );
539
+ }
540
+ return lines.join("\n");
541
+ }
542
+
543
+ function planChallengerSpawns(input) {
544
+ if (!input.critical) {
545
+ return {
546
+ skipped: true,
547
+ skipReason: "phase not Critical (Critical: false or unset)",
548
+ spawns: [],
549
+ degraded: false
550
+ };
551
+ }
552
+ const desired = desiredAgentsForType(input.phaseType);
553
+ const spawns = [];
554
+ const dropped = [];
555
+ for (const agent of desired) {
556
+ if (agent === "codex:codex-rescue" && !input.plugins.codex) {
557
+ dropped.push(agent);
558
+ continue;
559
+ }
560
+ if (agent === "gemini:gemini-rescue" && !input.plugins.gemini) {
561
+ dropped.push(agent);
562
+ continue;
563
+ }
564
+ spawns.push({
565
+ agent,
566
+ adversarial: true,
567
+ rationale: rationaleFor(agent, input.phaseType)
568
+ });
569
+ }
570
+ const degraded = dropped.length > 0;
571
+ return {
572
+ skipped: false,
573
+ spawns,
574
+ degraded,
575
+ degradeNote: degraded ? `plugin(s) unavailable, dropped: ${dropped.join(", ")}; specialists only` : void 0
576
+ };
577
+ }
578
+ function desiredAgentsForType(type) {
579
+ switch (type) {
580
+ case "backend":
581
+ return ["codex:codex-rescue", "assumptions-analyzer"];
582
+ case "frontend":
583
+ return ["gemini:gemini-rescue", "nyquist-auditor"];
584
+ case "fullstack":
585
+ return [
586
+ "codex:codex-rescue",
587
+ "gemini:gemini-rescue",
588
+ "assumptions-analyzer",
589
+ "nyquist-auditor"
590
+ ];
591
+ case "docs":
592
+ case "generic":
593
+ return ["assumptions-analyzer"];
594
+ }
595
+ }
596
+ function rationaleFor(agent, type) {
597
+ switch (agent) {
598
+ case "codex:codex-rescue":
599
+ return `backend logic adversarial review (${type})`;
600
+ case "gemini:gemini-rescue":
601
+ return `frontend/UX adversarial review (${type})`;
602
+ case "assumptions-analyzer":
603
+ return "plan assumption audit (CCG specialist)";
604
+ case "nyquist-auditor":
605
+ return "boundary / edge-case deep audit (CCG specialist)";
606
+ }
607
+ }
608
+ function parseChallengerSummary(agent, text) {
609
+ const get = (key) => {
610
+ const re = new RegExp(`^\\s*${key}\\s*:\\s*(.+)$`, "mi");
611
+ const m = text.match(re);
612
+ return m ? m[1].trim() : null;
613
+ };
614
+ const statusRaw = get("STATUS");
615
+ if (!statusRaw) {
616
+ throw new Error(`challenger ${agent} summary missing required STATUS field`);
617
+ }
618
+ const statusLower = statusRaw.toLowerCase().split(/\s+/)[0];
619
+ const status = statusLower === "complete" || statusLower === "completed" ? "complete" : statusLower === "error" || statusLower === "failed" ? "error" : (() => {
620
+ throw new Error(
621
+ `challenger ${agent} summary STATUS=${statusRaw} not one of complete|error`
622
+ );
623
+ })();
624
+ const findingsRaw = get("FINDINGS");
625
+ const findings = findingsRaw ? parseFindings(findingsRaw) : [];
626
+ const notes = get("NOTES") ?? "";
627
+ return { agent, status, findings, notes, raw: text };
628
+ }
629
+ function parseFindings(raw) {
630
+ let trimmed = stripJsonFence(raw.trim());
631
+ const direct = tryParseJsonArray(trimmed);
632
+ if (direct !== null) return direct;
633
+ const normalized = normalizeQuotes(trimmed);
634
+ const secondTry = tryParseJsonArray(normalized);
635
+ if (secondTry !== null) return secondTry;
636
+ const blocks = splitTopLevelObjects(trimmed);
637
+ const findings = [];
638
+ for (const block of blocks) {
639
+ const parsedSingle = tryParseJsonObject(stripJsonFence(block)) ?? tryParseJsonObject(normalizeQuotes(block));
640
+ if (parsedSingle) {
641
+ const f2 = normalizeFinding(parsedSingle);
642
+ if (f2) findings.push(f2);
643
+ continue;
644
+ }
645
+ const f = extractFindingViaRegex(block);
646
+ if (f) findings.push(f);
647
+ }
648
+ return findings;
649
+ }
650
+ function stripJsonFence(s) {
651
+ const m = s.match(/^```(?:json|JSON)?\s*\n?([\s\S]*?)\n?```$/);
652
+ if (m) return m[1].trim();
653
+ const inline = s.match(/^```\s*([\s\S]*?)\s*```$/);
654
+ if (inline) return inline[1].trim();
655
+ return s;
656
+ }
657
+ function tryParseJsonArray(s) {
658
+ if (!s.startsWith("[")) return null;
659
+ try {
660
+ const parsed = JSON.parse(s);
661
+ if (Array.isArray(parsed)) {
662
+ return parsed.map(normalizeFinding).filter((f) => f !== null);
663
+ }
664
+ } catch {
665
+ }
666
+ return null;
667
+ }
668
+ function tryParseJsonObject(s) {
669
+ const t = s.trim();
670
+ if (!t.startsWith("{")) return null;
671
+ try {
672
+ const v = JSON.parse(t);
673
+ return typeof v === "object" && v !== null ? v : null;
674
+ } catch {
675
+ return null;
676
+ }
677
+ }
678
+ function normalizeQuotes(s) {
679
+ let out = s.replace(/'/g, '"');
680
+ out = out.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, '$1"$2":');
681
+ return out;
682
+ }
683
+ function splitTopLevelObjects(raw) {
684
+ const out = [];
685
+ let depth = 0;
686
+ let inString = null;
687
+ let escape = false;
688
+ let start = -1;
689
+ for (let i = 0; i < raw.length; i++) {
690
+ const ch = raw[i];
691
+ if (escape) {
692
+ escape = false;
693
+ continue;
694
+ }
695
+ if (inString) {
696
+ if (ch === "\\") {
697
+ escape = true;
698
+ continue;
699
+ }
700
+ if (ch === inString) inString = null;
701
+ continue;
702
+ }
703
+ if (ch === '"' || ch === "'") {
704
+ inString = ch;
705
+ continue;
706
+ }
707
+ if (ch === "{") {
708
+ if (depth === 0) start = i;
709
+ depth++;
710
+ } else if (ch === "}") {
711
+ depth--;
712
+ if (depth === 0 && start >= 0) {
713
+ out.push(raw.slice(start, i + 1));
714
+ start = -1;
715
+ }
716
+ }
717
+ }
718
+ return out;
719
+ }
720
+ function extractFindingViaRegex(block) {
721
+ const sev = block.match(/severity\s*:\s*['"]?(critical|major|info)['"]?/i)?.[1]?.toLowerCase();
722
+ if (!sev) return null;
723
+ const cat = block.match(/category\s*:\s*['"]?([^,'"}\s]+)/i)?.[1] ?? "unknown";
724
+ const msg = block.match(/message\s*:\s*['"]([^'"]*)['"]/i)?.[1] ?? block.match(/message\s*:\s*([^,}]+)/i)?.[1]?.trim() ?? "";
725
+ return {
726
+ severity: sev,
727
+ category: cat.trim(),
728
+ message: msg.trim()
729
+ };
730
+ }
731
+ function normalizeFinding(raw) {
732
+ if (typeof raw !== "object" || raw === null) return null;
733
+ const r = raw;
734
+ const sev = String(r.severity ?? "").toLowerCase();
735
+ if (!["critical", "major", "info"].includes(sev)) return null;
736
+ return {
737
+ severity: sev,
738
+ category: String(r.category ?? "unknown").trim(),
739
+ message: String(r.message ?? "").trim()
740
+ };
741
+ }
742
+ function decideFromSummaries(summaries) {
743
+ if (summaries.length === 0) return "advance";
744
+ const hasError = summaries.some((s) => s.status === "error");
745
+ if (hasError) return "escalate";
746
+ const hasCritical = summaries.some(
747
+ (s) => s.findings.some((f) => f.severity === "critical")
748
+ );
749
+ if (hasCritical) return "revise";
750
+ return "advance";
751
+ }
752
+ function synthesizeRevisionFeedback(summaries) {
753
+ const critical = summaries.flatMap(
754
+ (s) => s.findings.filter((f) => f.severity === "critical").map((f) => ({ from: s.agent, ...f }))
755
+ );
756
+ if (critical.length === 0) return "";
757
+ const lines = [
758
+ "## Challenger \u53CD\u9988\uFF08critical \u5FC5\u4FEE\uFF09",
759
+ "",
760
+ "\u672C phase \u6807\u8BB0 Critical=true\uFF0C\u4E0B\u5217 critical findings \u5FC5\u987B\u5728\u4FEE\u8BA2\u8F6E\u5904\u7406\uFF1A",
761
+ "",
762
+ ...critical.map(
763
+ (c, i) => `${i + 1}. [${c.from}] (${c.category}) ${c.message}`
764
+ ),
765
+ "",
766
+ "\u4FEE\u8BA2\u8981\u6C42\uFF1A\u4EC5\u4FEE\u590D\u4E0A\u8FF0 critical \u9879\uFF0C\u4E0D\u91CD\u505A\u6574\u4E2A phase\uFF1B\u4FDD\u7559\u539F commit \u5386\u53F2\u3002"
767
+ ];
768
+ return lines.join("\n");
769
+ }
770
+
771
+ function pluginRoot(home = homedir()) {
772
+ return join(home, ".claude", "plugins");
773
+ }
774
+ const PLUGIN_MARKERS = ["SKILL.md", "plugin.json", "package.json", "manifest.json"];
775
+ const PLUGIN_PREFIXES = {
776
+ codex: ["codex@", "codex-rescue@", "openai-codex@"],
777
+ gemini: ["gemini@", "gemini-rescue@", "google-gemini@"]
778
+ };
779
+ function detectPlugin(name, homeDir = homedir()) {
780
+ const root = pluginRoot(homeDir);
781
+ const probedPath = root;
782
+ if (!existsSync(root)) {
783
+ return { name, installed: false, probedPath, reason: "missing-dir" };
784
+ }
785
+ let entries = [];
786
+ try {
787
+ const { readdirSync } = require("node:fs");
788
+ entries = readdirSync(root);
789
+ } catch {
790
+ return { name, installed: false, probedPath, reason: "fs-error" };
791
+ }
792
+ const prefixes = PLUGIN_PREFIXES[name];
793
+ for (const entry of entries) {
794
+ const matchesPrefix = prefixes.some((p) => entry.startsWith(p));
795
+ if (!matchesPrefix) continue;
796
+ const dir = join(root, entry);
797
+ try {
798
+ if (!statSync(dir).isDirectory()) continue;
799
+ } catch {
800
+ continue;
801
+ }
802
+ for (const marker of PLUGIN_MARKERS) {
803
+ const markerPath = join(dir, marker);
804
+ if (existsSync(markerPath)) {
805
+ return { name, installed: true, probedPath: dir };
806
+ }
807
+ }
808
+ }
809
+ return { name, installed: false, probedPath, reason: "missing-marker" };
810
+ }
811
+ function detectPluginAvailability(homeDir = homedir()) {
812
+ return {
813
+ codex: detectPlugin("codex", homeDir).installed,
814
+ gemini: detectPlugin("gemini", homeDir).installed
815
+ };
816
+ }
817
+ function bothPluginsInstalled(homeDir = homedir()) {
818
+ const avail = detectPluginAvailability(homeDir);
819
+ return avail.codex && avail.gemini;
820
+ }
821
+
822
+ const ALL_LAYERS = [
823
+ "backend",
824
+ "frontend",
825
+ "fullstack",
826
+ "docs",
827
+ "generic"
828
+ ];
829
+ function isLayer(value) {
830
+ return typeof value === "string" && ALL_LAYERS.includes(value);
831
+ }
832
+ const ROUTING_SCHEMA_VERSION = "4.2.0";
833
+
834
+ const CCG_PROMPT_BASE$1 = "~/.claude/.ccg/prompts";
835
+ function buildBashDirectCommand(plugin) {
836
+ const vendor = plugin === "codex" ? "openai-codex" : "google-gemini";
837
+ const scriptName = `${plugin}-companion.mjs`;
838
+ return `node "$(ls ~/.claude/plugins/cache/${vendor}/${plugin}/*/scripts/${scriptName} | head -1)" task -p "<PROMPT>" --json`;
839
+ }
840
+ function planVerifyWave(tier, layer, plugins, options = {}) {
841
+ if (!["fast", "triple", "debate"].includes(tier)) {
842
+ throw new Error(`planVerifyWave: invalid tier "${tier}"`);
843
+ }
844
+ const useBashDirect = options.useDirectBashInvocation === true;
845
+ const dual = tier === "triple" || tier === "debate";
846
+ const spawns = [];
847
+ let degraded = false;
848
+ const dropped = [];
849
+ const pluginEntry = (plugin, rationale) => {
850
+ const base = {
851
+ agent: `${plugin}:${plugin}-rescue`,
852
+ rationale
853
+ };
854
+ if (useBashDirect) {
855
+ base.invocationMode = "bash-direct";
856
+ base.bashCommand = buildBashDirectCommand(plugin);
857
+ } else {
858
+ base.invocationMode = "agent";
859
+ }
860
+ return base;
861
+ };
862
+ if (dual) {
863
+ if (plugins.codex) {
864
+ spawns.push(pluginEntry("codex", "cross-vendor verify (codex)"));
865
+ } else {
866
+ spawns.push({
867
+ agent: "general-purpose",
868
+ rationale: "codex plugin unavailable \u2014 main-thread fallback (codex/reviewer prompt)",
869
+ ccgPromptFile: `${CCG_PROMPT_BASE$1}/codex/reviewer.md`
870
+ });
871
+ degraded = true;
872
+ dropped.push("codex:codex-rescue");
873
+ }
874
+ if (plugins.gemini) {
875
+ spawns.push(pluginEntry("gemini", "cross-vendor verify (gemini)"));
876
+ } else {
877
+ spawns.push({
878
+ agent: "general-purpose",
879
+ rationale: "gemini plugin unavailable \u2014 main-thread fallback (gemini/reviewer prompt)",
880
+ ccgPromptFile: `${CCG_PROMPT_BASE$1}/gemini/reviewer.md`
881
+ });
882
+ degraded = true;
883
+ dropped.push("gemini:gemini-rescue");
884
+ }
885
+ } else {
886
+ const preferred = layer === "frontend" ? "codex" : "gemini";
887
+ const fallback = preferred === "codex" ? "gemini" : "codex";
888
+ if (plugins[preferred]) {
889
+ spawns.push(pluginEntry(preferred, `cross-vendor verify (${preferred}, layer=${layer})`));
890
+ } else if (plugins[fallback]) {
891
+ spawns.push(pluginEntry(fallback, `verify fallback (${fallback}, preferred ${preferred} unavailable)`));
892
+ degraded = true;
893
+ dropped.push(`${preferred}:${preferred}-rescue`);
894
+ } else {
895
+ spawns.push({
896
+ agent: "general-purpose",
897
+ rationale: "both plugins unavailable \u2014 main-thread reviewer fallback",
898
+ ccgPromptFile: `${CCG_PROMPT_BASE$1}/claude/reviewer.md`
899
+ });
900
+ degraded = true;
901
+ dropped.push("codex:codex-rescue", "gemini:gemini-rescue");
902
+ }
903
+ }
904
+ return {
905
+ mode: dual ? "dual" : "single",
906
+ spawns,
907
+ degraded,
908
+ degradeNote: degraded ? `verify wave plugin(s) unavailable: ${dropped.join(", ")}` : void 0
909
+ };
910
+ }
911
+ function parseVerifyReport(agent, text) {
912
+ let raw;
913
+ try {
914
+ raw = parseChallengerSummary(agent, text);
915
+ } catch (e) {
916
+ return {
917
+ agent,
918
+ status: "error",
919
+ criticals: [],
920
+ majors: [],
921
+ notes: e instanceof Error ? e.message : String(e),
922
+ raw: text
923
+ };
924
+ }
925
+ const criticals = raw.findings.filter((f) => f.severity === "critical");
926
+ const majors = raw.findings.filter((f) => f.severity === "major");
927
+ return {
928
+ agent,
929
+ status: raw.status,
930
+ criticals,
931
+ majors,
932
+ notes: raw.notes,
933
+ raw: text
934
+ };
935
+ }
936
+ function synthesizeVerifyResults(reports) {
937
+ if (!Array.isArray(reports) || reports.length === 0) {
938
+ return "escalate";
939
+ }
940
+ const hasError = reports.some((r) => r.status === "error");
941
+ if (hasError) return "escalate";
942
+ const hasCritical = reports.some((r) => r.criticals.length > 0);
943
+ if (hasCritical) return "revise";
944
+ return "advance";
945
+ }
946
+ function synthesizeVerifyFeedback(reports) {
947
+ const critical = reports.flatMap(
948
+ (r) => r.criticals.map((f) => ({ from: r.agent, ...f }))
949
+ );
950
+ if (critical.length === 0) return "";
951
+ const lines = [
952
+ "## Verify \u53CD\u9988\uFF08critical \u5FC5\u4FEE\uFF09",
953
+ "",
954
+ "verify wave \u6807\u51FA\u4E0B\u5217 critical findings\uFF0C\u4FEE\u8BA2\u8F6E\u5FC5\u987B\u5904\u7406\uFF1A",
955
+ "",
956
+ ...critical.map(
957
+ (c, i) => `${i + 1}. [${c.from}] (${c.category}) ${c.message}`
958
+ ),
959
+ "",
960
+ "\u4FEE\u8BA2\u8981\u6C42\uFF1A\u4EC5\u4FEE\u590D\u4E0A\u8FF0 critical \u9879\uFF0C\u4E0D\u91CD\u505A\u6574\u4E2A phase\uFF1B\u4FDD\u7559\u539F commit \u5386\u53F2\u3002"
961
+ ];
962
+ return lines.join("\n");
963
+ }
964
+
965
+ const ALL_TIERS = ["fast", "triple", "debate"];
966
+ const DEBATE_MAX_ROUNDS = 3;
967
+ const CCG_PROMPT_BASE = "~/.claude/.ccg/prompts";
968
+ function isQualityTier(v) {
969
+ return typeof v === "string" && ALL_TIERS.includes(v);
970
+ }
971
+ function parseQualityFlag(args) {
972
+ if (typeof args !== "string" || args.length === 0) return null;
973
+ const m = args.match(/--quality[=\s]+([a-z]+)/i);
974
+ if (!m) return null;
975
+ const candidate = m[1].toLowerCase();
976
+ return isQualityTier(candidate) ? candidate : null;
977
+ }
978
+ function resolveQualityTier(input) {
979
+ if (input.phaseQuality && isQualityTier(input.phaseQuality)) {
980
+ return { tier: input.phaseQuality, source: "phase-override" };
981
+ }
982
+ const flag = parseQualityFlag(input.cliArgs);
983
+ if (flag) {
984
+ return { tier: flag, source: "cli-flag" };
985
+ }
986
+ return { tier: "triple", source: "default" };
987
+ }
988
+ const PHASE_RUNNER_BUDGET_USD = {
989
+ fast: 1,
990
+ triple: 2,
991
+ debate: 5
992
+ };
993
+ function shellSingleQuote(s) {
994
+ return `'${s.replace(/'/g, `'\\''`)}'`;
995
+ }
996
+ function buildPhaseRunnerBashCommand(phase, _promptText, jobId, options = {}) {
997
+ const tier = options.tier ?? phase.quality ?? "triple";
998
+ if (!isQualityTier(tier)) {
999
+ throw new Error(`buildPhaseRunnerBashCommand: invalid tier "${tier}"`);
1000
+ }
1001
+ const budget = options.maxBudgetUsd ?? PHASE_RUNNER_BUDGET_USD[tier];
1002
+ if (typeof budget !== "number" || budget <= 0 || !Number.isFinite(budget)) {
1003
+ throw new Error(`buildPhaseRunnerBashCommand: invalid maxBudgetUsd ${budget}`);
1004
+ }
1005
+ const resolvedJobId = jobId ?? phase.jobId ?? "<JOB_ID>";
1006
+ const resolvedWorkdir = options.workdir ?? phase.workdir ?? "<WORKDIR>";
1007
+ const promptFile = options.promptFile ?? `.context/jobs/${resolvedJobId}/prompt.txt`;
1008
+ const progressFile = `.context/jobs/${resolvedJobId}/progress.jsonl`;
1009
+ const budgetStr = String(Number(budget.toFixed(4)));
1010
+ const parts = [
1011
+ `claude -p "$(cat ${shellSingleQuote(promptFile)})"`,
1012
+ `--agent ccg/phase-runner`,
1013
+ `--output-format stream-json`,
1014
+ `--verbose`,
1015
+ `--include-partial-messages`,
1016
+ `--max-budget-usd ${budgetStr}`,
1017
+ `--dangerously-skip-permissions`,
1018
+ `--add-dir ${shellSingleQuote(resolvedWorkdir)}`,
1019
+ `> ${shellSingleQuote(progressFile)} 2>&1`
1020
+ ];
1021
+ return parts.join(" ");
1022
+ }
1023
+ const DEFAULT_LAUNCHER_PATH = "~/.claude/.ccg/scripts/ccg-phase-runner-launcher.mjs";
1024
+ function buildPhaseRunnerLauncherCommand(phase, options = {}) {
1025
+ const tier = options.tier ?? phase.quality ?? "triple";
1026
+ if (!isQualityTier(tier)) {
1027
+ throw new Error(`buildPhaseRunnerLauncherCommand: invalid tier "${tier}"`);
1028
+ }
1029
+ const resolvedJobId = options.jobId ?? phase.jobId ?? "<JOB_ID>";
1030
+ const resolvedWorkdir = options.workdir ?? phase.workdir ?? "<WORKDIR>";
1031
+ const promptFile = options.promptFile ?? `.context/jobs/${resolvedJobId}/prompt.txt`;
1032
+ const progressFile = `.context/jobs/${resolvedJobId}/progress.jsonl`;
1033
+ const launcher = options.launcherPath ?? DEFAULT_LAUNCHER_PATH;
1034
+ const graceMs = options.graceMs ?? 5e3;
1035
+ const parts = [
1036
+ `node ${shellSingleQuote(launcher)}`,
1037
+ `--job-id ${shellSingleQuote(resolvedJobId)}`,
1038
+ `--workdir ${shellSingleQuote(resolvedWorkdir)}`,
1039
+ `--prompt-file ${shellSingleQuote(promptFile)}`,
1040
+ `--tier ${shellSingleQuote(tier)}`
1041
+ ];
1042
+ if (typeof options.maxBudgetUsd === "number" && Number.isFinite(options.maxBudgetUsd) && options.maxBudgetUsd > 0) {
1043
+ parts.push(`--max-budget-usd ${String(Number(options.maxBudgetUsd.toFixed(4)))}`);
1044
+ }
1045
+ parts.push(`--grace-ms ${String(graceMs)}`);
1046
+ parts.push(`> ${shellSingleQuote(progressFile)} 2>&1`);
1047
+ return parts.join(" ");
1048
+ }
1049
+ function buildPlanWave(index, phase, plugins) {
1050
+ const spawns = [];
1051
+ let degraded = false;
1052
+ const dropped = [];
1053
+ if (plugins.codex) {
1054
+ spawns.push({
1055
+ agent: "codex:codex-rescue",
1056
+ role: "planner",
1057
+ rationale: `backend / system-design plan path (${phase.phaseType})`
1058
+ });
1059
+ } else {
1060
+ spawns.push({
1061
+ agent: "general-purpose",
1062
+ role: "planner",
1063
+ rationale: "codex plugin unavailable \u2014 main-thread fallback with codex/architect prompt",
1064
+ ccgPromptFile: `${CCG_PROMPT_BASE}/codex/architect.md`
1065
+ });
1066
+ degraded = true;
1067
+ dropped.push("codex:codex-rescue");
1068
+ }
1069
+ if (plugins.gemini) {
1070
+ spawns.push({
1071
+ agent: "gemini:gemini-rescue",
1072
+ role: "planner",
1073
+ rationale: `frontend / UX plan path (${phase.phaseType})`
1074
+ });
1075
+ } else {
1076
+ spawns.push({
1077
+ agent: "general-purpose",
1078
+ role: "planner",
1079
+ rationale: "gemini plugin unavailable \u2014 main-thread fallback with gemini/architect prompt",
1080
+ ccgPromptFile: `${CCG_PROMPT_BASE}/gemini/architect.md`
1081
+ });
1082
+ degraded = true;
1083
+ dropped.push("gemini:gemini-rescue");
1084
+ }
1085
+ spawns.push({
1086
+ agent: "general-purpose",
1087
+ role: "planner",
1088
+ rationale: "claude opus 3rd-perspective plan (lateral diversity)",
1089
+ ccgPromptFile: `${CCG_PROMPT_BASE}/claude/architect.md`
1090
+ });
1091
+ return {
1092
+ kind: "plan",
1093
+ index,
1094
+ spawns,
1095
+ degraded,
1096
+ degradeNote: degraded ? `plan wave plugin(s) unavailable: ${dropped.join(", ")}; fallback to general-purpose` : void 0
1097
+ };
1098
+ }
1099
+ function buildCriticWave(index, phase) {
1100
+ const spawns = [
1101
+ {
1102
+ agent: "assumptions-analyzer",
1103
+ role: "critic",
1104
+ rationale: `assumption / hidden-dep audit (${phase.phaseType})`
1105
+ },
1106
+ {
1107
+ agent: "nyquist-auditor",
1108
+ role: "critic",
1109
+ rationale: `boundary / edge-case audit (${phase.phaseType})`
1110
+ }
1111
+ ];
1112
+ return { kind: "critic", index, spawns, degraded: false };
1113
+ }
1114
+ function buildImplWave(index, phase, options = {}) {
1115
+ const useBashDirect = options.useDirectBashInvocation === true;
1116
+ const useLauncher = useBashDirect && options.useLauncherWiring === true;
1117
+ const entry = {
1118
+ agent: "phase-runner",
1119
+ role: "implementer",
1120
+ rationale: `single strong implementer (${phase.phaseType}); consistency > diversity`
1121
+ };
1122
+ if (useBashDirect) {
1123
+ entry.invocationMode = "bash-direct";
1124
+ if (useLauncher) {
1125
+ entry.bashCommand = buildPhaseRunnerLauncherCommand(phase, {
1126
+ tier: options.tier
1127
+ });
1128
+ } else {
1129
+ entry.bashCommand = buildPhaseRunnerBashCommand(phase, "", phase.jobId, {
1130
+ tier: options.tier
1131
+ });
1132
+ }
1133
+ }
1134
+ return {
1135
+ kind: "impl",
1136
+ index,
1137
+ spawns: [entry],
1138
+ degraded: false
1139
+ };
1140
+ }
1141
+ function verifyWavePlanToWavePlan(vwp, index) {
1142
+ const spawns = vwp.spawns.map((s) => ({
1143
+ agent: s.agent,
1144
+ role: "verifier",
1145
+ rationale: s.rationale,
1146
+ ccgPromptFile: s.ccgPromptFile,
1147
+ // v4.4.2: 透传 invocationMode/bashCommand(之前 adapter drop 这两字段,
1148
+ // 导致 useDirectBashInvocation 仅对直接调 planVerifyWave 的路径生效;
1149
+ // autonomous Step 4.1 走 quality-router 路径时 silent fallback 风险残留)
1150
+ invocationMode: s.invocationMode,
1151
+ bashCommand: s.bashCommand
1152
+ }));
1153
+ return {
1154
+ kind: "verify",
1155
+ index,
1156
+ spawns,
1157
+ degraded: vwp.degraded,
1158
+ degradeNote: vwp.degradeNote
1159
+ };
1160
+ }
1161
+ function buildVerifyWave(index, phase, plugins, tier) {
1162
+ const vwp = planVerifyWave(tier, phase.phaseType, plugins, {
1163
+ useDirectBashInvocation: true
1164
+ });
1165
+ const wavePlan = verifyWavePlanToWavePlan(vwp, index);
1166
+ if (tier === "triple" || tier === "debate") {
1167
+ wavePlan.spawns.push({
1168
+ agent: "interface-auditor",
1169
+ role: "verifier",
1170
+ rationale: `cross-phase interface audit (${phase.phaseType}; SSoT / leftover / magic-string / commit-drift / mock-drift)`
1171
+ });
1172
+ }
1173
+ return wavePlan;
1174
+ }
1175
+ function buildDebateRound(index, round, phase, plugins) {
1176
+ const cycle = (round - 1) % 3;
1177
+ const kind = cycle === 0 ? "propose" : cycle === 1 ? "challenge" : "respond";
1178
+ const layer = phase.phaseType;
1179
+ const proposerSide = layer === "backend" ? ["codex"] : layer === "frontend" ? ["gemini"] : ["codex", "gemini"];
1180
+ const challengerSide = layer === "backend" ? ["gemini"] : layer === "frontend" ? ["codex"] : ["codex", "gemini"];
1181
+ const rawModels = kind === "challenge" ? challengerSide : proposerSide;
1182
+ const promptName = kind === "challenge" ? "reviewer.md" : "architect.md";
1183
+ const spawns = [];
1184
+ let degraded = false;
1185
+ const dropped = [];
1186
+ for (const m of rawModels) {
1187
+ if (plugins[m]) {
1188
+ spawns.push({
1189
+ agent: `${m}:${m}-rescue`,
1190
+ role: "debater",
1191
+ rationale: `debate r${round} ${kind} (${m})`
1192
+ });
1193
+ } else {
1194
+ spawns.push({
1195
+ agent: "general-purpose",
1196
+ role: "debater",
1197
+ rationale: `${m} plugin unavailable \u2014 main-thread fallback (debate r${round} ${kind})`,
1198
+ ccgPromptFile: `${CCG_PROMPT_BASE}/${m}/${promptName}`
1199
+ });
1200
+ degraded = true;
1201
+ dropped.push(`${m}:${m}-rescue`);
1202
+ }
1203
+ }
1204
+ return {
1205
+ kind: "debate",
1206
+ index,
1207
+ round,
1208
+ spawns,
1209
+ degraded,
1210
+ degradeNote: degraded ? `debate r${round} plugin(s) unavailable: ${dropped.join(", ")}` : void 0
1211
+ };
1212
+ }
1213
+ function planWavesForTier(tier, phase, plugins, options = {}) {
1214
+ if (!isQualityTier(tier)) {
1215
+ throw new Error(`planWavesForTier: invalid tier "${tier}"`);
1216
+ }
1217
+ const bothMissing = !plugins.codex && !plugins.gemini;
1218
+ let effective = tier;
1219
+ let degradedTo;
1220
+ let degradeNote;
1221
+ if (tier === "debate" && bothMissing) {
1222
+ effective = "fast";
1223
+ degradedTo = "fast";
1224
+ degradeNote = "debate \u2192 fast: both plugins unavailable; debate loses lateral diversity";
1225
+ } else if (tier === "debate" && (!plugins.codex || !plugins.gemini)) {
1226
+ effective = "triple";
1227
+ degradedTo = "triple";
1228
+ degradeNote = "debate \u2192 triple: one plugin unavailable; debate needs both for adversarial pairing";
1229
+ } else if (tier === "triple" && bothMissing) {
1230
+ effective = "fast";
1231
+ degradedTo = "fast";
1232
+ degradeNote = "triple \u2192 fast: both plugins unavailable; plan/verify diversity collapsed";
1233
+ }
1234
+ const waves = [];
1235
+ let waveIdx = 1;
1236
+ const implOpts = {
1237
+ useDirectBashInvocation: options.useDirectBashInvocation === true,
1238
+ useLauncherWiring: options.useLauncherWiring === true,
1239
+ tier: effective
1240
+ };
1241
+ switch (effective) {
1242
+ case "fast":
1243
+ waves.push(buildImplWave(waveIdx++, phase, implOpts));
1244
+ waves.push(buildVerifyWave(waveIdx++, phase, plugins, "fast"));
1245
+ break;
1246
+ case "triple":
1247
+ waves.push(buildPlanWave(waveIdx++, phase, plugins));
1248
+ waves.push(buildCriticWave(waveIdx++, phase));
1249
+ waves.push(buildImplWave(waveIdx++, phase, implOpts));
1250
+ waves.push(buildVerifyWave(waveIdx++, phase, plugins, "triple"));
1251
+ break;
1252
+ case "debate": {
1253
+ waves.push(buildPlanWave(waveIdx++, phase, plugins));
1254
+ for (let r = 1; r <= DEBATE_MAX_ROUNDS; r++) {
1255
+ waves.push(buildDebateRound(waveIdx++, r, phase, plugins));
1256
+ }
1257
+ waves.push(buildCriticWave(waveIdx++, phase));
1258
+ waves.push(buildImplWave(waveIdx++, phase, implOpts));
1259
+ waves.push(buildVerifyWave(waveIdx++, phase, plugins, "debate"));
1260
+ break;
1261
+ }
1262
+ }
1263
+ const anyWaveDegraded = waves.some((w) => w.degraded);
1264
+ const tierDegraded = degradedTo !== void 0;
1265
+ return {
1266
+ effectiveTier: effective,
1267
+ waves,
1268
+ degraded: tierDegraded || anyWaveDegraded,
1269
+ degradedTo,
1270
+ degradeNote
1271
+ };
1272
+ }
1273
+ function buildQualityPlan(resolveInput, phase, plugins, options = {}) {
1274
+ const { tier, source } = resolveQualityTier({
1275
+ cliArgs: resolveInput.cliArgs,
1276
+ phaseQuality: phase.quality ?? resolveInput.phaseQuality
1277
+ });
1278
+ const planResult = planWavesForTier(tier, phase, plugins, options);
1279
+ return {
1280
+ tier,
1281
+ source,
1282
+ waves: planResult.waves,
1283
+ degraded: planResult.degraded,
1284
+ degradedTo: planResult.degradedTo,
1285
+ degradeNote: planResult.degradeNote
1286
+ };
1287
+ }
1288
+
1289
+ const SIMILARITY_THRESHOLD = 0.7;
1290
+ const MIN_BULLET_LEN = 8;
1291
+ const HIGH_STAKES_KEYWORDS = [
1292
+ "\u67B6\u6784",
1293
+ "\u7834\u574F",
1294
+ "\u7834\u574F\u6027",
1295
+ "\u5B89\u5168",
1296
+ "schema",
1297
+ "\u8FC1\u79FB",
1298
+ "migration",
1299
+ "breaking",
1300
+ "security",
1301
+ "architecture",
1302
+ "auth",
1303
+ "data loss",
1304
+ "\u6570\u636E\u4E22\u5931",
1305
+ "\u4E0D\u517C\u5BB9"
1306
+ ];
1307
+ const MIN_SHARED_TOKENS = 2;
1308
+ const BRIEF_MAX_TOKENS = 500;
1309
+ function splitIntoBullets(text) {
1310
+ if (typeof text !== "string" || text.trim().length === 0) return [];
1311
+ const lines = text.split(/\r?\n/);
1312
+ const bullets = [];
1313
+ let buffer = [];
1314
+ const flush = () => {
1315
+ const joined = buffer.join(" ").trim();
1316
+ if (joined.length >= MIN_BULLET_LEN) bullets.push(joined);
1317
+ buffer = [];
1318
+ };
1319
+ let hasExplicitBullet = false;
1320
+ for (const raw of lines) {
1321
+ const line = raw.trim();
1322
+ if (line.length === 0) {
1323
+ flush();
1324
+ continue;
1325
+ }
1326
+ const bulletMatch = line.match(/^(?:[-*+•]|\d+[.)])\s+(.+)$/);
1327
+ if (bulletMatch) {
1328
+ hasExplicitBullet = true;
1329
+ flush();
1330
+ buffer.push(bulletMatch[1].trim());
1331
+ } else {
1332
+ buffer.push(line);
1333
+ }
1334
+ }
1335
+ flush();
1336
+ if (hasExplicitBullet) return bullets;
1337
+ const fallback = [];
1338
+ for (const raw of lines) {
1339
+ const line = raw.trim();
1340
+ if (line.length >= MIN_BULLET_LEN) fallback.push(line);
1341
+ }
1342
+ return fallback;
1343
+ }
1344
+ function normalizeBullet(s) {
1345
+ return s.toLowerCase().replace(/[,。、:;!?,.:;!?'"`()()【】\[\]{}<>]/g, " ").replace(/\s+/g, " ").replace(/\b(the|a|an|and|or|of|to|in|for|is|are)\b/g, " ").replace(/[的了和与及之而]/g, " ").replace(/\s+/g, " ").trim();
1346
+ }
1347
+ function similarity(a, b) {
1348
+ const tokensA = tokenize(a);
1349
+ const tokensB = tokenize(b);
1350
+ if (tokensA.size === 0 || tokensB.size === 0) return 0;
1351
+ let intersect = 0;
1352
+ for (const t of tokensA) {
1353
+ if (tokensB.has(t)) intersect++;
1354
+ }
1355
+ const union = tokensA.size + tokensB.size - intersect;
1356
+ return union === 0 ? 0 : intersect / union;
1357
+ }
1358
+ function tokenize(s) {
1359
+ const out = /* @__PURE__ */ new Set();
1360
+ const words = s.match(/[a-z0-9]+/g) ?? [];
1361
+ for (const w of words) {
1362
+ if (w.length >= 2) out.add(w);
1363
+ }
1364
+ const chineseChars = s.match(/[一-鿿]/g) ?? [];
1365
+ for (const c of chineseChars) out.add(c);
1366
+ return out;
1367
+ }
1368
+ function flattenContributions(contributions) {
1369
+ const bullets = [];
1370
+ const warnings = [];
1371
+ for (const c of contributions) {
1372
+ if (typeof c.plan !== "string" || c.plan.trim().length === 0) {
1373
+ warnings.push(`plan from ${c.model} empty or non-string; skipped`);
1374
+ continue;
1375
+ }
1376
+ const items = splitIntoBullets(c.plan);
1377
+ if (items.length === 0) {
1378
+ warnings.push(`plan from ${c.model} produced no parseable bullets; skipped`);
1379
+ continue;
1380
+ }
1381
+ for (const b of items) {
1382
+ const norm = normalizeBullet(b);
1383
+ if (norm.length === 0) continue;
1384
+ bullets.push({ source: c.model, raw: b, norm });
1385
+ }
1386
+ }
1387
+ return { bullets, warnings };
1388
+ }
1389
+ function extractConsensus(bullets) {
1390
+ const consensus = [];
1391
+ const consumed = /* @__PURE__ */ new Set();
1392
+ for (let i = 0; i < bullets.length; i++) {
1393
+ if (consumed.has(i)) continue;
1394
+ const cluster = [i];
1395
+ const sources = /* @__PURE__ */ new Set([bullets[i].source]);
1396
+ for (let j = i + 1; j < bullets.length; j++) {
1397
+ if (consumed.has(j)) continue;
1398
+ const sim = similarity(bullets[i].norm, bullets[j].norm);
1399
+ if (sim >= SIMILARITY_THRESHOLD) {
1400
+ cluster.push(j);
1401
+ sources.add(bullets[j].source);
1402
+ }
1403
+ }
1404
+ if (sources.size >= 2) {
1405
+ for (const idx of cluster) consumed.add(idx);
1406
+ consensus.push(bullets[i].raw);
1407
+ }
1408
+ }
1409
+ return { consensus, consumedIndices: consumed };
1410
+ }
1411
+ function extractDivergences(bullets, consumed) {
1412
+ const remaining = bullets.map((b, i) => ({ ...b, idx: i, tokens: tokenize(b.norm) })).filter((b) => !consumed.has(b.idx));
1413
+ if (remaining.length === 0) return [];
1414
+ const parent = remaining.map((_, i) => i);
1415
+ const find = (x) => {
1416
+ while (parent[x] !== x) {
1417
+ parent[x] = parent[parent[x]];
1418
+ x = parent[x];
1419
+ }
1420
+ return x;
1421
+ };
1422
+ const union = (a, b) => {
1423
+ const ra = find(a);
1424
+ const rb = find(b);
1425
+ if (ra !== rb) parent[ra] = rb;
1426
+ };
1427
+ for (let i = 0; i < remaining.length; i++) {
1428
+ for (let j = i + 1; j < remaining.length; j++) {
1429
+ if (remaining[i].source === remaining[j].source) continue;
1430
+ const shared = countSharedTokens(remaining[i].tokens, remaining[j].tokens);
1431
+ if (shared >= MIN_SHARED_TOKENS) {
1432
+ union(i, j);
1433
+ }
1434
+ }
1435
+ }
1436
+ const groups = /* @__PURE__ */ new Map();
1437
+ for (let i = 0; i < remaining.length; i++) {
1438
+ const root = find(i);
1439
+ const arr = groups.get(root) ?? [];
1440
+ arr.push(remaining[i]);
1441
+ groups.set(root, arr);
1442
+ }
1443
+ const divergences = [];
1444
+ const seenRoots = /* @__PURE__ */ new Set();
1445
+ for (let i = 0; i < remaining.length; i++) {
1446
+ const root = find(i);
1447
+ if (seenRoots.has(root)) continue;
1448
+ seenRoots.add(root);
1449
+ const group = groups.get(root);
1450
+ divergences.push({
1451
+ topic: group[0].raw.slice(0, 60),
1452
+ options: group.map((g) => ({ from: g.source, option: g.raw }))
1453
+ });
1454
+ }
1455
+ return divergences;
1456
+ }
1457
+ function countSharedTokens(a, b) {
1458
+ let n = 0;
1459
+ for (const t of a) {
1460
+ if (b.has(t)) n++;
1461
+ }
1462
+ return n;
1463
+ }
1464
+ function extractDecisionRequired(divergences) {
1465
+ const required = [];
1466
+ for (const d of divergences) {
1467
+ const lower = d.topic.toLowerCase();
1468
+ const hit = HIGH_STAKES_KEYWORDS.some((kw) => lower.includes(kw.toLowerCase()));
1469
+ if (hit) required.push(d.topic);
1470
+ }
1471
+ return required;
1472
+ }
1473
+ function aggregatePlans(contributions) {
1474
+ if (!Array.isArray(contributions)) {
1475
+ throw new Error("aggregatePlans: contributions must be array");
1476
+ }
1477
+ if (contributions.length === 0) {
1478
+ return {
1479
+ consensus: [],
1480
+ divergences: [],
1481
+ decision_required: [],
1482
+ warnings: ["no plan contributions"]
1483
+ };
1484
+ }
1485
+ const { bullets, warnings } = flattenContributions(contributions);
1486
+ if (bullets.length === 0) {
1487
+ return {
1488
+ consensus: [],
1489
+ divergences: [],
1490
+ decision_required: [],
1491
+ warnings: [...warnings, "no parseable bullets across all contributions"]
1492
+ };
1493
+ }
1494
+ const { consensus, consumedIndices } = extractConsensus(bullets);
1495
+ const divergences = extractDivergences(bullets, consumedIndices);
1496
+ const decision_required = extractDecisionRequired(divergences);
1497
+ return { consensus, divergences, decision_required, warnings };
1498
+ }
1499
+ function estimateTokens(text) {
1500
+ if (typeof text !== "string" || text.length === 0) return 0;
1501
+ let tokens = 0;
1502
+ const englishWords = text.match(/[a-zA-Z]+/g) ?? [];
1503
+ for (const w of englishWords) {
1504
+ tokens += w.length <= 5 ? 1 : Math.max(1, Math.round(w.length / 4));
1505
+ }
1506
+ const chineseChars = text.match(/[一-鿿]/g) ?? [];
1507
+ tokens += chineseChars.length;
1508
+ const otherCharCount = text.replace(/[a-zA-Z]+/g, "").replace(/[一-鿿]/g, "").length;
1509
+ tokens += Math.ceil(otherCharCount * 0.3);
1510
+ return Math.ceil(tokens);
1511
+ }
1512
+ function serializeBriefForPrompt(brief) {
1513
+ const lines = [];
1514
+ lines.push("## Design Brief\uFF08plan wave \u7EFC\u5408\uFF09");
1515
+ lines.push("");
1516
+ if (brief.consensus.length > 0) {
1517
+ lines.push("### \u5171\u8BC6\u8981\u70B9");
1518
+ for (const c of brief.consensus) lines.push(`- ${truncate(c, 120)}`);
1519
+ lines.push("");
1520
+ }
1521
+ if (brief.divergences.length > 0) {
1522
+ lines.push("### \u5206\u6B67\u4E3B\u9898");
1523
+ for (const d of brief.divergences) {
1524
+ lines.push(`- **${truncate(d.topic, 60)}**`);
1525
+ for (const opt of d.options) {
1526
+ lines.push(` - [${opt.from}] ${truncate(opt.option, 100)}`);
1527
+ }
1528
+ }
1529
+ lines.push("");
1530
+ }
1531
+ if (brief.decision_required.length > 0) {
1532
+ lines.push("### \u5FC5\u51B3\u7B56\u70B9\uFF08high-stakes\uFF0C\u4E3B\u7EBF / \u7528\u6237\u88C1\u5B9A\uFF09");
1533
+ for (const r of brief.decision_required) lines.push(`- ${truncate(r, 80)}`);
1534
+ lines.push("");
1535
+ }
1536
+ if (brief.warnings.length > 0) {
1537
+ lines.push("### \u89E3\u6790\u544A\u8B66");
1538
+ for (const w of brief.warnings) lines.push(`- ${truncate(w, 100)}`);
1539
+ }
1540
+ let out = lines.join("\n").trim();
1541
+ if (estimateTokens(out) > BRIEF_MAX_TOKENS) {
1542
+ let lo = 0;
1543
+ let hi = out.length;
1544
+ const marker = "\n...(truncated)";
1545
+ const markerTokens = estimateTokens(marker);
1546
+ const budget = BRIEF_MAX_TOKENS - markerTokens;
1547
+ while (lo < hi) {
1548
+ const mid = Math.ceil((lo + hi) / 2);
1549
+ if (estimateTokens(out.slice(0, mid)) <= budget) lo = mid;
1550
+ else hi = mid - 1;
1551
+ }
1552
+ out = out.slice(0, lo) + marker;
1553
+ }
1554
+ return out;
1555
+ }
1556
+ function truncate(s, n) {
1557
+ if (typeof s !== "string") return "";
1558
+ return s.length <= n ? s : s.slice(0, n - 3) + "...";
1559
+ }
1560
+ function estimateBriefLength(brief) {
1561
+ return estimateTokens(serializeBriefForPrompt(brief));
1562
+ }
1563
+
1564
+ function runPnpmPack(workdir) {
1565
+ const out = execSync("pnpm pack", {
1566
+ cwd: workdir,
1567
+ encoding: "utf-8",
1568
+ stdio: ["ignore", "pipe", "pipe"]
1569
+ });
1570
+ const lines = out.trim().split(/\r?\n/).filter((l) => l.trim().length > 0);
1571
+ if (lines.length === 0) {
1572
+ throw new Error("pnpm pack produced no output");
1573
+ }
1574
+ const tarballName = lines[lines.length - 1].trim();
1575
+ if (!tarballName.endsWith(".tgz")) {
1576
+ throw new Error(`pnpm pack last line is not a tgz: "${tarballName}"`);
1577
+ }
1578
+ return join(workdir, tarballName);
1579
+ }
1580
+ function auditTarballContents(tarballPath) {
1581
+ const out = execSync(`tar -tzf "${tarballPath}"`, {
1582
+ encoding: "utf-8",
1583
+ stdio: ["ignore", "pipe", "pipe"]
1584
+ });
1585
+ return out.trim().split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
1586
+ }
1587
+ function verifyAllCommandsIncluded(workdir, tarballEntries) {
1588
+ const result = {
1589
+ packageFiles: [],
1590
+ actualCommands: [],
1591
+ missingInPackageJson: [],
1592
+ missingInTarball: []
1593
+ };
1594
+ const pkgPath = join(workdir, "package.json");
1595
+ if (existsSync(pkgPath)) {
1596
+ try {
1597
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1598
+ if (Array.isArray(pkg.files)) result.packageFiles = pkg.files;
1599
+ } catch {
1600
+ }
1601
+ }
1602
+ const cmdDir = join(workdir, "templates", "commands");
1603
+ if (existsSync(cmdDir)) {
1604
+ const files = readdirSync(cmdDir).filter((f) => f.endsWith(".md"));
1605
+ result.actualCommands = files.map((f) => `templates/commands/${f}`);
1606
+ }
1607
+ for (const cmd of result.actualCommands) {
1608
+ const matched = result.packageFiles.some((entry) => {
1609
+ if (entry === cmd) return true;
1610
+ if (entry.endsWith("/") && cmd.startsWith(entry)) return true;
1611
+ if (cmd.startsWith(entry + "/")) return true;
1612
+ return false;
1613
+ });
1614
+ if (!matched) result.missingInPackageJson.push(cmd);
1615
+ }
1616
+ if (tarballEntries) {
1617
+ const tarballSet = new Set(
1618
+ tarballEntries.map((e) => e.replace(/^package\//, "")).filter((e) => e.startsWith("templates/commands/") && e.endsWith(".md"))
1619
+ );
1620
+ for (const cmd of result.actualCommands) {
1621
+ if (!tarballSet.has(cmd)) result.missingInTarball.push(cmd);
1622
+ }
1623
+ }
1624
+ return result;
1625
+ }
1626
+ function runPipelineCheck(opts = {}) {
1627
+ const workdir = opts.workdir ?? process.cwd();
1628
+ const doPnpmPack = opts.doPnpmPack ?? true;
1629
+ const errors = [];
1630
+ const warnings = [];
1631
+ let tarballPath;
1632
+ let tarballEntries;
1633
+ if (doPnpmPack) {
1634
+ try {
1635
+ tarballPath = runPnpmPack(workdir);
1636
+ } catch (e) {
1637
+ errors.push({
1638
+ category: "pack-failed",
1639
+ message: "pnpm pack \u5931\u8D25 \u2014 \u53EF\u80FD\u662F build \u9519\u6216 pnpm \u672A\u88C5",
1640
+ detail: e instanceof Error ? e.message : String(e)
1641
+ });
1642
+ }
1643
+ if (tarballPath) {
1644
+ try {
1645
+ tarballEntries = auditTarballContents(tarballPath);
1646
+ } catch (e) {
1647
+ errors.push({
1648
+ category: "tar-extract-failed",
1649
+ message: "tar -tzf \u5931\u8D25 \u2014 \u53EF\u80FD\u662F tar \u5DE5\u5177\u672A\u88C5\u6216 tarball \u635F\u574F",
1650
+ detail: e instanceof Error ? e.message : String(e)
1651
+ });
1652
+ }
1653
+ }
1654
+ }
1655
+ const verifyResult = verifyAllCommandsIncluded(workdir, tarballEntries);
1656
+ if (verifyResult.packageFiles.length === 0) {
1657
+ const pkgPath = join(workdir, "package.json");
1658
+ if (!existsSync(pkgPath)) {
1659
+ errors.push({
1660
+ category: "package-json-missing",
1661
+ message: `package.json \u4E0D\u5B58\u5728: ${pkgPath}`
1662
+ });
1663
+ } else {
1664
+ errors.push({
1665
+ category: "package-json-invalid",
1666
+ message: "package.json `files` \u6570\u7EC4\u4E3A\u7A7A\u6216\u89E3\u6790\u5931\u8D25"
1667
+ });
1668
+ }
1669
+ }
1670
+ if (verifyResult.missingInPackageJson.length > 0) {
1671
+ const list = verifyResult.missingInPackageJson;
1672
+ errors.push({
1673
+ category: "missing-from-tarball",
1674
+ message: `${list.length} \u4E2A templates/commands/*.md \u6587\u4EF6\u5B58\u5728\u4F46 package.json files \u6F0F\u5217\uFF08\u8FD9\u662F v4.2.0 debate.md \u6F0F register \u540C\u7C7B\u578B\u4E8B\u6545\uFF09\uFF1A${list.slice(0, 5).join(", ")}${list.length > 5 ? `, ...\u8FD8\u6709 ${list.length - 5} \u4E2A` : ""}`,
1675
+ detail: list.join("\n")
1676
+ });
1677
+ }
1678
+ if (verifyResult.missingInTarball.length > 0) {
1679
+ const list = verifyResult.missingInTarball;
1680
+ errors.push({
1681
+ category: "missing-from-tarball",
1682
+ message: `${list.length} \u4E2A templates/commands/*.md \u6587\u4EF6\u5B58\u5728\u4F46 tarball \u5B9E\u9645\u672A\u6253\u5305\uFF1A${list.slice(0, 5).join(", ")}${list.length > 5 ? `, ...\u8FD8\u6709 ${list.length - 5} \u4E2A` : ""}`,
1683
+ detail: list.join("\n")
1684
+ });
1685
+ }
1686
+ return {
1687
+ ok: errors.length === 0,
1688
+ errors,
1689
+ warnings,
1690
+ tarballPath,
1691
+ tarballEntries,
1692
+ missingFromPackageJson: verifyResult.missingInPackageJson
1693
+ };
1694
+ }
1695
+ function renderPipelineReport(report) {
1696
+ const lines = [];
1697
+ lines.push(`## Pipeline Check ${report.ok ? "\u2705" : "\u274C"}`);
1698
+ lines.push("");
1699
+ if (report.tarballPath) {
1700
+ lines.push(`- Tarball: \`${report.tarballPath}\``);
1701
+ }
1702
+ if (report.tarballEntries) {
1703
+ lines.push(`- Tarball \u542B ${report.tarballEntries.length} \u6587\u4EF6`);
1704
+ }
1705
+ lines.push("");
1706
+ if (report.errors.length > 0) {
1707
+ lines.push("### \u274C Errors");
1708
+ for (const err of report.errors) {
1709
+ lines.push(`- [${err.category}] ${err.message}`);
1710
+ }
1711
+ lines.push("");
1712
+ }
1713
+ if (report.warnings.length > 0) {
1714
+ lines.push("### \u26A0\uFE0F Warnings");
1715
+ for (const w of report.warnings) lines.push(`- ${w}`);
1716
+ lines.push("");
1717
+ }
1718
+ if (report.ok) {
1719
+ lines.push("All checks passed. Tarball \u5185\u5BB9\u8DDF templates/commands/ \u4E00\u81F4\u3002");
1720
+ }
1721
+ return lines.join("\n");
1722
+ }
1723
+
1724
+ const KNOWN_SUBAGENT_HINTS = {
1725
+ codex: ["codex:codex-rescue", "codex:setup"],
1726
+ gemini: ["gemini:gemini-rescue", "gemini:setup"],
1727
+ "frontend-design": ["frontend-design:frontend-design"],
1728
+ "code-review": ["code-review:code-review"]
1729
+ };
1730
+ function shortNameOf(pluginKey) {
1731
+ const at = pluginKey.indexOf("@");
1732
+ return at > 0 ? pluginKey.slice(0, at) : pluginKey;
1733
+ }
1734
+ function samplePluginList(homeDir = homedir()) {
1735
+ const warnings = [];
1736
+ const path = join(homeDir, ".claude", "plugins", "installed_plugins.json");
1737
+ if (!existsSync(path)) {
1738
+ return { plugins: [], warnings: [`installed_plugins.json not found at ${path}`] };
1739
+ }
1740
+ let raw;
1741
+ try {
1742
+ raw = JSON.parse(readFileSync(path, "utf-8"));
1743
+ } catch (e) {
1744
+ warnings.push(`installed_plugins.json parse failed: ${e instanceof Error ? e.message : String(e)}`);
1745
+ return { plugins: [], warnings };
1746
+ }
1747
+ if (!raw?.plugins || typeof raw.plugins !== "object") {
1748
+ warnings.push("installed_plugins.json missing `plugins` object");
1749
+ return { plugins: [], warnings };
1750
+ }
1751
+ const out = [];
1752
+ for (const [name, instances] of Object.entries(raw.plugins)) {
1753
+ if (!Array.isArray(instances) || instances.length === 0) continue;
1754
+ const inst = instances[0];
1755
+ const shortName = shortNameOf(name);
1756
+ out.push({
1757
+ name,
1758
+ shortName,
1759
+ version: typeof inst?.version === "string" ? inst.version : "unknown",
1760
+ installPath: typeof inst?.installPath === "string" ? inst.installPath : void 0,
1761
+ subagentTypeHints: KNOWN_SUBAGENT_HINTS[shortName]
1762
+ });
1763
+ }
1764
+ return { plugins: out, warnings };
1765
+ }
1766
+ function inferSkillCategory(relPath) {
1767
+ if (relPath.includes("/tools/") || relPath.startsWith("tools/")) return "tool";
1768
+ if (relPath.includes("/domains/") || relPath.startsWith("domains/")) return "domain";
1769
+ if (relPath.includes("/impeccable/") || relPath.startsWith("impeccable/")) return "impeccable";
1770
+ if (relPath.includes("/orchestration/") || relPath.startsWith("orchestration/")) return "orchestration";
1771
+ return "unknown";
1772
+ }
1773
+ const SKILL_FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
1774
+ function parseFrontmatterField(fm, key) {
1775
+ const re = new RegExp(`^\\s*${key}\\s*:\\s*(.+)$`, "mi");
1776
+ const m = fm.match(re);
1777
+ if (!m) return void 0;
1778
+ return m[1].trim().replace(/^["']|["']$/g, "");
1779
+ }
1780
+ function walkSkills(baseDir, currentDir, out, warnings) {
1781
+ let entries;
1782
+ try {
1783
+ entries = readdirSync(currentDir);
1784
+ } catch (e) {
1785
+ warnings.push(`readdir failed at ${currentDir}: ${e instanceof Error ? e.message : String(e)}`);
1786
+ return;
1787
+ }
1788
+ for (const entry of entries) {
1789
+ const full = join(currentDir, entry);
1790
+ let stat;
1791
+ try {
1792
+ stat = statSync(full);
1793
+ } catch {
1794
+ continue;
1795
+ }
1796
+ if (stat.isDirectory()) {
1797
+ walkSkills(baseDir, full, out, warnings);
1798
+ continue;
1799
+ }
1800
+ if (entry !== "SKILL.md") continue;
1801
+ let content;
1802
+ try {
1803
+ content = readFileSync(full, "utf-8");
1804
+ } catch {
1805
+ continue;
1806
+ }
1807
+ const fmMatch = content.match(SKILL_FRONTMATTER_RE);
1808
+ if (!fmMatch) continue;
1809
+ const fm = fmMatch[1];
1810
+ const name = parseFrontmatterField(fm, "name");
1811
+ const invocableRaw = parseFrontmatterField(fm, "user-invocable");
1812
+ if (!name) continue;
1813
+ const relPath = full.slice(baseDir.length + 1).replace(/\\/g, "/");
1814
+ out.push({
1815
+ name,
1816
+ path: full,
1817
+ userInvocable: invocableRaw === "true",
1818
+ category: inferSkillCategory(relPath)
1819
+ });
1820
+ }
1821
+ }
1822
+ function sampleSkillList(homeDir = homedir()) {
1823
+ const warnings = [];
1824
+ const root = join(homeDir, ".claude", "skills", "ccg");
1825
+ if (!existsSync(root)) {
1826
+ return { skills: [], warnings: [`skills/ccg/ not found at ${root}`] };
1827
+ }
1828
+ const out = [];
1829
+ walkSkills(root, root, out, warnings);
1830
+ const seen = /* @__PURE__ */ new Set();
1831
+ const dedup = out.filter((s) => {
1832
+ const key = `${s.name}\0${s.path}`;
1833
+ if (seen.has(key)) return false;
1834
+ seen.add(key);
1835
+ return true;
1836
+ });
1837
+ return { skills: dedup, warnings };
1838
+ }
1839
+ function classifyMatcher(matcher) {
1840
+ if (matcher === void 0) return "absent";
1841
+ if (matcher === null) return "null";
1842
+ if (typeof matcher === "string") return "string";
1843
+ if (Array.isArray(matcher)) return "array";
1844
+ if (typeof matcher === "object") return "object";
1845
+ return "other";
1846
+ }
1847
+ function sampleHookSchema(homeDir = homedir()) {
1848
+ const warnings = [];
1849
+ const path = join(homeDir, ".claude", "settings.json");
1850
+ if (!existsSync(path)) {
1851
+ return { hooks: [], warnings: [`settings.json not found at ${path}`] };
1852
+ }
1853
+ let raw;
1854
+ try {
1855
+ raw = JSON.parse(readFileSync(path, "utf-8"));
1856
+ } catch (e) {
1857
+ warnings.push(`settings.json parse failed: ${e instanceof Error ? e.message : String(e)}`);
1858
+ return { hooks: [], warnings };
1859
+ }
1860
+ const hooksObj = raw?.hooks;
1861
+ if (!hooksObj || typeof hooksObj !== "object") {
1862
+ return { hooks: [], warnings: ["settings.json no hooks section"] };
1863
+ }
1864
+ const out = [];
1865
+ for (const [event, entries] of Object.entries(hooksObj)) {
1866
+ if (!Array.isArray(entries) || entries.length === 0) continue;
1867
+ const first = entries[0];
1868
+ const matcher = first && typeof first === "object" ? first.matcher : void 0;
1869
+ const cmd = first?.hooks?.[0]?.command;
1870
+ out.push({
1871
+ event,
1872
+ matcherType: classifyMatcher(matcher),
1873
+ hookCount: entries.length,
1874
+ firstCommandPreview: typeof cmd === "string" ? cmd.slice(0, 80) : void 0
1875
+ });
1876
+ }
1877
+ return { hooks: out, warnings };
1878
+ }
1879
+ function samplePackageStructure(workdir) {
1880
+ const warnings = [];
1881
+ const pkgPath = join(workdir, "package.json");
1882
+ if (!existsSync(pkgPath)) {
1883
+ return { info: null, warnings: [`package.json not found at ${pkgPath}`] };
1884
+ }
1885
+ let pkg;
1886
+ try {
1887
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1888
+ } catch (e) {
1889
+ warnings.push(`package.json parse failed: ${e instanceof Error ? e.message : String(e)}`);
1890
+ return { info: null, warnings };
1891
+ }
1892
+ const packageFiles = Array.isArray(pkg.files) ? pkg.files : [];
1893
+ let templateCommands = [];
1894
+ const cmdDir = join(workdir, "templates", "commands");
1895
+ if (existsSync(cmdDir)) {
1896
+ try {
1897
+ templateCommands = readdirSync(cmdDir).filter((f) => f.endsWith(".md")).map((f) => `templates/commands/${f}`);
1898
+ } catch {
1899
+ warnings.push(`readdir templates/commands failed`);
1900
+ }
1901
+ }
1902
+ const missingFromPackageFiles = templateCommands.filter((cmd) => {
1903
+ return !packageFiles.some((entry) => {
1904
+ if (entry === cmd) return true;
1905
+ if (entry.endsWith("/") && cmd.startsWith(entry)) return true;
1906
+ if (cmd.startsWith(`${entry}/`)) return true;
1907
+ return false;
1908
+ });
1909
+ });
1910
+ return {
1911
+ info: { packageFiles, templateCommands, missingFromPackageFiles },
1912
+ warnings
1913
+ };
1914
+ }
1915
+ function sampleAll(opts = {}) {
1916
+ const homeDir = opts.homeDir ?? homedir();
1917
+ const allWarnings = [];
1918
+ const pluginRes = samplePluginList(homeDir);
1919
+ allWarnings.push(...pluginRes.warnings);
1920
+ const skillRes = sampleSkillList(homeDir);
1921
+ allWarnings.push(...skillRes.warnings);
1922
+ const hookRes = sampleHookSchema(homeDir);
1923
+ allWarnings.push(...hookRes.warnings);
1924
+ const result = {
1925
+ sampledAt: (/* @__PURE__ */ new Date()).toISOString(),
1926
+ plugins: pluginRes.plugins,
1927
+ skills: skillRes.skills,
1928
+ hooks: hookRes.hooks,
1929
+ warnings: allWarnings
1930
+ };
1931
+ if (opts.workdir) {
1932
+ const pkgRes = samplePackageStructure(opts.workdir);
1933
+ if (pkgRes.info) result.packageStructure = pkgRes.info;
1934
+ allWarnings.push(...pkgRes.warnings);
1935
+ }
1936
+ return result;
1937
+ }
1938
+ function summarizeGroundTruth(gt) {
1939
+ const lines = [];
1940
+ lines.push(`# Ground Truth (sampled ${gt.sampledAt})`);
1941
+ lines.push("");
1942
+ lines.push(`## Plugins (${gt.plugins.length})`);
1943
+ for (const p of gt.plugins.slice(0, 20)) {
1944
+ const hints = p.subagentTypeHints?.join(", ") ?? "(no hints)";
1945
+ lines.push(`- ${p.name} v${p.version} \u2192 subagent hints: ${hints}`);
1946
+ }
1947
+ if (gt.plugins.length > 20) lines.push(`- ...\u8FD8\u6709 ${gt.plugins.length - 20} \u4E2A`);
1948
+ lines.push("");
1949
+ lines.push(`## Skills (${gt.skills.length}, ${gt.skills.filter((s) => s.userInvocable).length} user-invocable)`);
1950
+ const invocable = gt.skills.filter((s) => s.userInvocable);
1951
+ for (const s of invocable.slice(0, 30)) {
1952
+ lines.push(`- ${s.name} (${s.category})`);
1953
+ }
1954
+ if (invocable.length > 30) lines.push(`- ...\u8FD8\u6709 ${invocable.length - 30} \u4E2A invocable`);
1955
+ lines.push("");
1956
+ lines.push(`## Hooks (${gt.hooks.length} events)`);
1957
+ for (const h of gt.hooks) {
1958
+ lines.push(`- ${h.event}: matcher=${h.matcherType}, ${h.hookCount} entry`);
1959
+ }
1960
+ lines.push("");
1961
+ if (gt.packageStructure) {
1962
+ const ps = gt.packageStructure;
1963
+ lines.push("## Package Structure");
1964
+ lines.push(`- package.json files: ${ps.packageFiles.length} entries`);
1965
+ lines.push(`- templates/commands/*.md: ${ps.templateCommands.length} files`);
1966
+ if (ps.missingFromPackageFiles.length > 0) {
1967
+ lines.push(`- \u26A0\uFE0F ${ps.missingFromPackageFiles.length} commands NOT in package.json files: ${ps.missingFromPackageFiles.join(", ")}`);
1968
+ } else {
1969
+ lines.push("- \u2705 all commands listed in package.json files");
1970
+ }
1971
+ lines.push("");
1972
+ }
1973
+ if (gt.warnings.length > 0) {
1974
+ lines.push(`## Warnings (${gt.warnings.length})`);
1975
+ for (const w of gt.warnings.slice(0, 10)) lines.push(`- ${w}`);
1976
+ if (gt.warnings.length > 10) lines.push(`- ...\u8FD8\u6709 ${gt.warnings.length - 10} \u6761`);
1977
+ }
1978
+ return lines.join("\n");
1979
+ }
1980
+
1981
+ const VALID_CATEGORIES = [
1982
+ "ssot-violation",
1983
+ "leftover",
1984
+ "magic-string-mismatch",
1985
+ "commit-diff-drift",
1986
+ "mock-drift",
1987
+ "alien-files-staged",
1988
+ "unknown"
1989
+ ];
1990
+ function parseInterfaceAuditorReport(text) {
1991
+ let raw;
1992
+ try {
1993
+ raw = parseChallengerSummary("interface-auditor", text);
1994
+ } catch (e) {
1995
+ return {
1996
+ status: "error",
1997
+ findings: [],
1998
+ notes: e instanceof Error ? e.message : String(e),
1999
+ raw: text
2000
+ };
2001
+ }
2002
+ const findings = raw.findings.map(normalizeFindingCategory);
2003
+ return {
2004
+ status: raw.status,
2005
+ findings,
2006
+ notes: raw.notes,
2007
+ raw: text
2008
+ };
2009
+ }
2010
+ function normalizeFindingCategory(f) {
2011
+ const cat = f.category.toLowerCase().trim();
2012
+ const matched = VALID_CATEGORIES.find((c) => c === cat);
2013
+ return {
2014
+ severity: f.severity,
2015
+ category: matched ?? "unknown",
2016
+ message: f.message
2017
+ };
2018
+ }
2019
+ function criticalFindings(report) {
2020
+ return report.findings.filter((f) => f.severity === "critical");
2021
+ }
2022
+ function majorFindings(report) {
2023
+ return report.findings.filter((f) => f.severity === "major");
2024
+ }
2025
+ function hasBlockingFindings(report) {
2026
+ return report.findings.some((f) => f.severity === "critical");
2027
+ }
2028
+
2029
+ export { ALL_LAYERS, CONTEXT_BUDGET_THRESHOLD, DESCRIPTION_SOFT_LIMIT, ROUTING_SCHEMA_VERSION, aggregatePlans, auditSkillDescriptions, auditSkillsDirectory, auditTarballContents, batchByMaxConcurrent, bothPluginsInstalled, buildQualityPlan, buildWaves, cascadeSkip, collectSkills, contextPath, decideFromSummaries, detectPlugin, detectPluginAvailability, estimateBriefLength, estimateTokens, extractFrontmatter, criticalFindings as interfaceAuditCriticals, hasBlockingFindings as interfaceAuditHasBlocking, majorFindings as interfaceAuditMajors, isLayer, parseChallengerSummary, parseDependsOn, parseFrontmatterFields, parseInterfaceAuditorReport, parseQualityFlag, parseRoadmap, parseRoleFlag, parseVerifyReport, phaseDir, planChallengerSpawns, planVerifyWave, planWavesForTier, promptFilePath, readContext, readSummary, readSummaryFrontmatter, renderAuditMarkdown, renderPipelineReport, resolveQualityTier, routeSpecialist, runPipelineCheck, runPnpmPack, sampleAll, sampleHookSchema, samplePackageStructure, samplePluginList, sampleSkillList, sanitizePhase, schedule, serializeBriefForPrompt, summarizeGroundTruth, summaryPath, summaryTokenEstimate, synthesizeRevisionFeedback, synthesizeVerifyFeedback, synthesizeVerifyResults, verifyAllCommandsIncluded, writeContext, writeSummary };