add-coder 0.3.31 → 0.3.32

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 (74) hide show
  1. package/package.json +1 -1
  2. package/templates/.add-coder-src-hash.json +73 -1
  3. package/templates/adapters/claude/hooks/doc-format-guard.mjs +850 -0
  4. package/templates/adapters/claude/hooks/notification.mjs +256 -0
  5. package/templates/adapters/claude/hooks/permission-denied.mjs +167 -0
  6. package/templates/adapters/claude/hooks/permission-gate.mjs +150 -0
  7. package/templates/adapters/claude/hooks/post-tool-failure.mjs +3 -0
  8. package/templates/adapters/claude/hooks/post-tool-use.mjs +413 -0
  9. package/templates/adapters/claude/hooks/pre-compact.mjs +226 -0
  10. package/templates/adapters/claude/hooks/pre-tool-use.mjs +914 -0
  11. package/templates/adapters/claude/hooks/prompt-submit.mjs +847 -0
  12. package/templates/adapters/claude/hooks/review-checklist.mjs +3 -0
  13. package/templates/adapters/claude/hooks/session-end.mjs +287 -0
  14. package/templates/adapters/claude/hooks/session-start.mjs +566 -0
  15. package/templates/adapters/claude/hooks/stop-check.mjs +505 -0
  16. package/templates/adapters/claude/hooks/stop-failure.mjs +222 -0
  17. package/templates/adapters/claude/hooks/subagent-guard.mjs +224 -0
  18. package/templates/adapters/claude/hooks/subagent-stop.mjs +277 -0
  19. package/templates/adapters/codex/hooks/doc-format-guard.mjs +849 -0
  20. package/templates/adapters/codex/hooks/notification.mjs +254 -0
  21. package/templates/adapters/codex/hooks/permission-gate.mjs +169 -0
  22. package/templates/adapters/codex/hooks/post-tool-failure.mjs +176 -0
  23. package/templates/adapters/codex/hooks/post-tool-use.mjs +575 -0
  24. package/templates/adapters/codex/hooks/pre-compact.mjs +328 -0
  25. package/templates/adapters/codex/hooks/pre-tool-use.mjs +963 -0
  26. package/templates/adapters/codex/hooks/prompt-submit.mjs +623 -0
  27. package/templates/adapters/codex/hooks/review-checklist.mjs +3 -0
  28. package/templates/adapters/codex/hooks/session-end.mjs +389 -0
  29. package/templates/adapters/codex/hooks/session-start.mjs +583 -0
  30. package/templates/adapters/codex/hooks/stop-check.mjs +539 -0
  31. package/templates/adapters/codex/hooks/subagent-guard.mjs +218 -0
  32. package/templates/adapters/codex/hooks/subagent-stop.mjs +381 -0
  33. package/templates/adapters/qoder/hooks/doc-format-guard.mjs +858 -0
  34. package/templates/adapters/qoder/hooks/notification.mjs +269 -0
  35. package/templates/adapters/qoder/hooks/permission-gate.mjs +284 -0
  36. package/templates/adapters/qoder/hooks/post-tool-failure.mjs +294 -0
  37. package/templates/adapters/qoder/hooks/post-tool-use.mjs +535 -0
  38. package/templates/adapters/qoder/hooks/pre-compact.mjs +234 -0
  39. package/templates/adapters/qoder/hooks/pre-tool-use.mjs +948 -0
  40. package/templates/adapters/qoder/hooks/prompt-submit.mjs +789 -0
  41. package/templates/adapters/qoder/hooks/review-checklist.mjs +409 -0
  42. package/templates/adapters/qoder/hooks/session-end.mjs +301 -0
  43. package/templates/adapters/qoder/hooks/session-start.mjs +619 -0
  44. package/templates/adapters/qoder/hooks/stop-check.mjs +567 -0
  45. package/templates/adapters/qoder/hooks/subagent-guard.mjs +219 -0
  46. package/templates/adapters/qoder/hooks/subagent-stop.mjs +298 -0
  47. package/templates/adapters/trae/hooks/doc-format-guard.mjs +849 -0
  48. package/templates/adapters/trae/hooks/notification.mjs +251 -0
  49. package/templates/adapters/trae/hooks/permission-gate.mjs +169 -0
  50. package/templates/adapters/trae/hooks/post-tool-failure.mjs +176 -0
  51. package/templates/adapters/trae/hooks/post-tool-use.mjs +415 -0
  52. package/templates/adapters/trae/hooks/pre-compact.mjs +224 -0
  53. package/templates/adapters/trae/hooks/pre-tool-use.mjs +873 -0
  54. package/templates/adapters/trae/hooks/prompt-submit.mjs +614 -0
  55. package/templates/adapters/trae/hooks/review-checklist.mjs +3 -0
  56. package/templates/adapters/trae/hooks/session-end.mjs +285 -0
  57. package/templates/adapters/trae/hooks/session-start.mjs +555 -0
  58. package/templates/adapters/trae/hooks/stop-check.mjs +502 -0
  59. package/templates/adapters/trae/hooks/subagent-guard.mjs +218 -0
  60. package/templates/adapters/trae/hooks/subagent-stop.mjs +274 -0
  61. package/templates/adapters/vscode/hooks/doc-format-guard.mjs +847 -0
  62. package/templates/adapters/vscode/hooks/notification.mjs +251 -0
  63. package/templates/adapters/vscode/hooks/permission-gate.mjs +169 -0
  64. package/templates/adapters/vscode/hooks/post-tool-failure.mjs +192 -0
  65. package/templates/adapters/vscode/hooks/post-tool-use.mjs +415 -0
  66. package/templates/adapters/vscode/hooks/pre-compact.mjs +224 -0
  67. package/templates/adapters/vscode/hooks/pre-tool-use.mjs +906 -0
  68. package/templates/adapters/vscode/hooks/prompt-submit.mjs +838 -0
  69. package/templates/adapters/vscode/hooks/review-checklist.mjs +3 -0
  70. package/templates/adapters/vscode/hooks/session-end.mjs +285 -0
  71. package/templates/adapters/vscode/hooks/session-start.mjs +560 -0
  72. package/templates/adapters/vscode/hooks/stop-check.mjs +502 -0
  73. package/templates/adapters/vscode/hooks/subagent-guard.mjs +218 -0
  74. package/templates/adapters/vscode/hooks/subagent-stop.mjs +274 -0
@@ -0,0 +1,847 @@
1
+ // templates/core/governance/common.ts
2
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
5
+ import { spawnSync } from "node:child_process";
6
+ import { createHash } from "node:crypto";
7
+
8
+ // templates/core/governance/rules.ts
9
+ var event = {
10
+ "file": {
11
+ "path": "{magicDir}/reports/hook-events.jsonl",
12
+ "rotate_bytes": 262144,
13
+ "total_bytes": 524288,
14
+ "note": "MCP Server \u5B95\u673A\u4E0D\u4E22\u4E8B\u4EF6\uFF0C\u91CD\u542F\u540E\u4ECE\u6587\u4EF6\u6062\u590D\u6D88\u8D39"
15
+ },
16
+ "schema": {
17
+ "fields": [
18
+ "ts",
19
+ "hook",
20
+ "decision",
21
+ "cmd",
22
+ "reason",
23
+ "planKeyword",
24
+ "planStatus"
25
+ ],
26
+ "ts_format": "date -u +%Y-%m-%dT%H:%M:%SZ",
27
+ "extra_fields": [
28
+ "anchor_hit",
29
+ "struct_score",
30
+ "override"
31
+ ]
32
+ },
33
+ "daily": {
34
+ "warn_threshold": 10
35
+ }
36
+ };
37
+ var protocol = {
38
+ "exit_codes": {
39
+ "pass": 0,
40
+ "block": 2,
41
+ "note": "\u5192\u70DF\u6821\u9A8C: \u4EA7\u7269\u9000\u51FA\u7801 \u2208 {0,2}\uFF08\u5176\u4F59\u4E3A\u975E\u9884\u671F\uFF0C\u9700\u4FEE\u590D\uFF09"
42
+ },
43
+ "output": {
44
+ "stdout_json_only": true,
45
+ "stderr_human_text": true,
46
+ "field_separator": "::",
47
+ "magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
48
+ },
49
+ "adapters": {
50
+ "claude": {
51
+ "stdout_form": "plain-text",
52
+ "project_dir_env": "CLAUDE_PROJECT_DIR",
53
+ "magic_dir": ".claude",
54
+ "handlerTypes": [
55
+ "command",
56
+ "mcp_tool"
57
+ ]
58
+ },
59
+ "qoder": {
60
+ "stdout_form": "json",
61
+ "project_dir_env": "QODER_PROJECT_DIR",
62
+ "magic_dir": ".qoder",
63
+ "handlerTypes": [
64
+ "command",
65
+ "http"
66
+ ]
67
+ },
68
+ "codex": {
69
+ "stdout_form": "systemMessage",
70
+ "project_dir_env": "git-toplevel",
71
+ "magic_dir": ".codex",
72
+ "handlerTypes": [
73
+ "command"
74
+ ]
75
+ },
76
+ "vscode": {
77
+ "stdout_form": "plain-text",
78
+ "project_dir_env": "PWD",
79
+ "magic_dir": ".vscode",
80
+ "handlerTypes": [
81
+ "command"
82
+ ]
83
+ },
84
+ "trae": {
85
+ "stdout_form": "plain-text",
86
+ "project_dir_env": "PWD",
87
+ "magic_dir": ".trae",
88
+ "handlerTypes": [
89
+ "command"
90
+ ]
91
+ }
92
+ },
93
+ "event_outputs": {
94
+ "qoder": {
95
+ "SessionStart": "additionalContext",
96
+ "UserPromptSubmit": "additionalContext",
97
+ "PreToolUse": "permissionDecision",
98
+ "Stop": "additionalContext",
99
+ "PostToolUse": "feedback",
100
+ "SubagentStart": "additionalContext",
101
+ "SubagentStop": "additionalContext",
102
+ "PostToolUseFailure": "text",
103
+ "PermissionRequest": "text",
104
+ "SessionEnd": "text",
105
+ "PreCompact": "text",
106
+ "Notification": "text"
107
+ },
108
+ "claude": {
109
+ "SessionStart": "additionalContext",
110
+ "UserPromptSubmit": "additionalContext",
111
+ "PreToolUse": "permissionDecision",
112
+ "Stop": "text",
113
+ "PostToolUse": "feedback",
114
+ "SubagentStart": "text",
115
+ "SubagentStop": "text",
116
+ "PostToolUseFailure": "text",
117
+ "PermissionRequest": "text",
118
+ "SessionEnd": "text",
119
+ "PreCompact": "text",
120
+ "Notification": "text"
121
+ },
122
+ "codex": {
123
+ "Stop": "systemMessage",
124
+ "PostToolUse": "text"
125
+ },
126
+ "vscode": {
127
+ "PostToolUse": "text"
128
+ },
129
+ "trae": {
130
+ "PostToolUse": "text"
131
+ }
132
+ },
133
+ "core": {
134
+ "stdout_form": "json",
135
+ "magic_dir": ".add",
136
+ "note": "core \u5165\u53E3\u534F\u8BAE = qoder \u540C\u6784\u53C2\u8003\u5B9E\u73B0\uFF1Badapter \u4EC5\u4FDD\u7559\u672C\u8868\u58F0\u660E\u7684\u79C1\u6709\u5DEE\u5F02"
137
+ },
138
+ "adapter_defaults": {
139
+ "magic_dir_fallback": ".qoder",
140
+ "probe_magic_dirs": [
141
+ ".claude",
142
+ ".qoder",
143
+ ".vscode",
144
+ ".add",
145
+ ".trae",
146
+ ".codex"
147
+ ]
148
+ }
149
+ };
150
+
151
+ // node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
152
+ import path2 from "node:path";
153
+
154
+ // node_modules/.pnpm/locate-path@8.0.0/node_modules/locate-path/index.js
155
+ import process2 from "node:process";
156
+ import path from "node:path";
157
+ import fs, { promises as fsPromises } from "node:fs";
158
+ import { fileURLToPath } from "node:url";
159
+ var typeMappings = {
160
+ directory: "isDirectory",
161
+ file: "isFile"
162
+ };
163
+ function checkType(type) {
164
+ if (type === "both" || Object.hasOwn(typeMappings, type)) {
165
+ return;
166
+ }
167
+ throw new Error(`Invalid type specified: ${type}`);
168
+ }
169
+ var matchType = (type, stat) => type === "both" ? stat.isFile() || stat.isDirectory() : stat[typeMappings[type]]();
170
+ var toPath = (urlOrPath) => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
171
+ function locatePathSync(paths, {
172
+ cwd = process2.cwd(),
173
+ type = "file",
174
+ allowSymlinks = true
175
+ } = {}) {
176
+ checkType(type);
177
+ cwd = toPath(cwd);
178
+ const statFunction = allowSymlinks ? fs.statSync : fs.lstatSync;
179
+ for (const path_ of paths) {
180
+ try {
181
+ const stat = statFunction(path.resolve(cwd, path_), {
182
+ throwIfNoEntry: false
183
+ });
184
+ if (!stat) {
185
+ continue;
186
+ }
187
+ if (matchType(type, stat)) {
188
+ return path_;
189
+ }
190
+ } catch {
191
+ }
192
+ }
193
+ }
194
+
195
+ // node_modules/.pnpm/unicorn-magic@0.3.0/node_modules/unicorn-magic/node.js
196
+ import { promisify } from "node:util";
197
+ import { execFile as execFileCallback, execFileSync as execFileSyncOriginal } from "node:child_process";
198
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
199
+ var execFileOriginal = promisify(execFileCallback);
200
+ function toPath2(urlOrPath) {
201
+ return urlOrPath instanceof URL ? fileURLToPath2(urlOrPath) : urlOrPath;
202
+ }
203
+ var TEN_MEGABYTES_IN_BYTES = 10 * 1024 * 1024;
204
+
205
+ // node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
206
+ var findUpStop = /* @__PURE__ */ Symbol("findUpStop");
207
+ function findUpMultipleSync(name, options = {}) {
208
+ let directory = path2.resolve(toPath2(options.cwd) ?? "");
209
+ const { root } = path2.parse(directory);
210
+ const stopAt = path2.resolve(directory, toPath2(options.stopAt) ?? root);
211
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
212
+ const paths = [name].flat();
213
+ const runMatcher = (locateOptions) => {
214
+ if (typeof name !== "function") {
215
+ return locatePathSync(paths, locateOptions);
216
+ }
217
+ const foundPath = name(locateOptions.cwd);
218
+ if (typeof foundPath === "string") {
219
+ return locatePathSync([foundPath], locateOptions);
220
+ }
221
+ return foundPath;
222
+ };
223
+ const matches = [];
224
+ while (true) {
225
+ const foundPath = runMatcher({ ...options, cwd: directory });
226
+ if (foundPath === findUpStop) {
227
+ break;
228
+ }
229
+ if (foundPath) {
230
+ matches.push(path2.resolve(directory, foundPath));
231
+ }
232
+ if (directory === stopAt || matches.length >= limit) {
233
+ break;
234
+ }
235
+ directory = path2.dirname(directory);
236
+ }
237
+ return matches;
238
+ }
239
+ function findUpSync(name, options = {}) {
240
+ const matches = findUpMultipleSync(name, { ...options, limit: 1 });
241
+ return matches[0];
242
+ }
243
+
244
+ // templates/core/governance/common.ts
245
+ var EXIT_PASS = protocol.exit_codes.pass;
246
+ var EXIT_BLOCK = protocol.exit_codes.block;
247
+ var STATE_SEP = protocol.output.field_separator;
248
+ function readHookInput() {
249
+ if (process.stdin.isTTY) return "{}";
250
+ return readFileSync(0, "utf-8");
251
+ }
252
+ function jsonGet(json, field) {
253
+ const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`);
254
+ const m = re.exec(json);
255
+ return m?.[1] ?? "";
256
+ }
257
+ function tryResolveMagicDir() {
258
+ const injected = process.env.MAGIC_DIR;
259
+ if (injected) return injected;
260
+ const startDir = dirname(fileURLToPath3(import.meta.url));
261
+ const hit = findUpSync((dir) => basename(dir).startsWith(".") ? dir : void 0, {
262
+ cwd: startDir,
263
+ type: "directory"
264
+ });
265
+ return hit ? basename(hit) : "";
266
+ }
267
+ function queryPlanStatus() {
268
+ const magicDir = process.env.MAGIC_DIR;
269
+ if (!magicDir) {
270
+ return {
271
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"magicDir \u672A\u6CE8\u5165\u4E14\u65E0\u6CD5\u4ECE\u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC"}',
272
+ exitCode: 3
273
+ };
274
+ }
275
+ const bridge = join(
276
+ process.env.PROJECT_DIR || process.cwd(),
277
+ magicDir,
278
+ "scripts",
279
+ "plan-status-bridge.ts"
280
+ );
281
+ if (!existsSync(bridge)) {
282
+ return {
283
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"plan-status bridge missing"}',
284
+ exitCode: 3
285
+ };
286
+ }
287
+ const r = spawnSync("node", ["--import", "tsx", bridge], {
288
+ encoding: "utf-8",
289
+ timeout: 1e4
290
+ });
291
+ return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
292
+ }
293
+ function detectActiveAdd() {
294
+ const r = queryPlanStatus();
295
+ if (r.exitCode !== 0) {
296
+ let reason = "database status unavailable";
297
+ try {
298
+ const parsed = JSON.parse(r.stdout);
299
+ if (parsed.reason) reason = parsed.reason;
300
+ } catch {
301
+ }
302
+ return `__STATUS_UNAVAILABLE__::${reason}::database::none::none`;
303
+ }
304
+ let snapshot;
305
+ try {
306
+ snapshot = JSON.parse(r.stdout);
307
+ } catch {
308
+ return null;
309
+ }
310
+ if (!(snapshot.availability === "READY" && snapshot.isActive === true)) return null;
311
+ const done = snapshot.progress?.doneTasks ?? 0;
312
+ const total = snapshot.progress?.totalTasks ?? 0;
313
+ const approval = snapshot.approvalStatus ?? "none";
314
+ return `${snapshot.planName}::${done}/${total}::${approval}::none::none`;
315
+ }
316
+ function localIsoSeconds() {
317
+ const d = /* @__PURE__ */ new Date();
318
+ const pad = (n) => String(n).padStart(2, "0");
319
+ const offsetMin = -d.getTimezoneOffset();
320
+ const sign = offsetMin >= 0 ? "+" : "-";
321
+ const offAbs = Math.abs(offsetMin);
322
+ const off = `${sign}${pad(Math.floor(offAbs / 60))}:${pad(offAbs % 60)}`;
323
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${off}`;
324
+ }
325
+ function projectHash() {
326
+ try {
327
+ return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
328
+ `).digest("hex").slice(0, 8);
329
+ } catch {
330
+ return "default";
331
+ }
332
+ }
333
+ var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
334
+ function isAlreadyAccepted(addRoute, handoff) {
335
+ if (addRoute && existsSync(addRoute)) {
336
+ const content = readFileSync(addRoute, "utf-8");
337
+ const step8 = content.match(/Step 8[\s\S]{0,2000}/)?.[0] ?? "";
338
+ if (/\[x\].*验证并更新项目状态/.test(step8)) {
339
+ if (handoff && existsSync(handoff)) {
340
+ const h = readFileSync(handoff, "utf-8");
341
+ if (/✅.*验收|收敛|全部闭环|全部.*完成/.test(h)) {
342
+ return true;
343
+ }
344
+ }
345
+ }
346
+ }
347
+ return false;
348
+ }
349
+
350
+ // templates/core/governance/preload-templates.ts
351
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
352
+ import { join as join2, dirname as dirname2 } from "node:path";
353
+ import { createHash as createHash2 } from "node:crypto";
354
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
355
+ function stripFrontmatter(lines) {
356
+ const out = [];
357
+ let inFm = false;
358
+ lines.forEach((line, idx) => {
359
+ if (idx === 0 && line === "---") {
360
+ inFm = true;
361
+ return;
362
+ }
363
+ if (inFm && line === "---") {
364
+ inFm = false;
365
+ return;
366
+ }
367
+ if (!inFm) out.push(line);
368
+ });
369
+ return out;
370
+ }
371
+ function projectHash2(projectDir) {
372
+ try {
373
+ return createHash2("md5").update(projectDir).digest("hex").slice(0, 8);
374
+ } catch {
375
+ return "default";
376
+ }
377
+ }
378
+ function parseArgs(argv) {
379
+ const args = { mode: "index", top: 0, mark: false };
380
+ for (let i = 0; i < argv.length; i++) {
381
+ switch (argv[i]) {
382
+ case "--index":
383
+ args.mode = "index";
384
+ break;
385
+ case "--full":
386
+ args.mode = "full";
387
+ break;
388
+ case "--top":
389
+ args.top = Number(argv[++i] ?? "0") || 0;
390
+ break;
391
+ case "--mark":
392
+ args.mark = true;
393
+ break;
394
+ default:
395
+ break;
396
+ }
397
+ }
398
+ return args;
399
+ }
400
+ var PreloadTemplates = class _PreloadTemplates {
401
+ templatesDir;
402
+ tplFlag;
403
+ static TEMPLATES = Object.freeze({
404
+ "simple-plan-template.md": "\u9700\u6C42\u65B9\u6848\uFF08\u7B80\u5355\u7248\uFF09\uFF1A\u516D\u8282\u7ED3\u6784\uFF0C\u5143\u4FE1\u606F+\u80CC\u666F+\u65B9\u6848+\u67B6\u6784+\u5B9E\u65BD+\u9A8C\u6536",
405
+ "spec-template.md": "\u529F\u80FD\u89C4\u683C\uFF1AWhy/What Changes/Impact/WHEN-THEN Requirements",
406
+ "tasks-template.md": "\u4EFB\u52A1\u62C6\u5206\uFF1APhase\u2192Task\u2192SubTask\u5C42\u7EA7",
407
+ "checklist-template.md": "\u9A8C\u6536\u6E05\u5355\uFF1A[T]\u7F16\u8BD1\u671F+[R]\u8FD0\u884C\u65F6+ADD\u89C4\u5219\u5408\u89C4",
408
+ "review-template.md": "\u65B9\u6848\u5BA1\u67E5\uFF08ADD-9\uFF09\uFF1A\u95EE\u9898\u590D\u73B0+\u65B9\u6848\u5BF9\u6BD4+\u51B3\u7B56\u7ED3\u8BBA+\u5F71\u54CD\u8BC4\u4F30",
409
+ "standard-plan-template.md": "\u9700\u6C42\u65B9\u6848\uFF08\u6807\u51C6\u7248\uFF09\uFF1APLAN\u5143\u4FE1\u606F+\u80CC\u666F+\u65B9\u6848+\u67B6\u6784+\u5B9E\u65BDTask+\u9A8C\u6536+\u5173\u8054\u6587\u6863",
410
+ "add-route-template-heavyweight.md": "ADD\u6267\u884C\u8DEF\u7EBF\u56FE\uFF08\u91CD\u578B\uFF09\uFF1A\u6BCFStep\u9A8C\u8BC1\u5E76\u66F4\u65B0\u72B6\u6001+spec_sync\u4EA4\u53C9\u6821\u9A8C",
411
+ "add-route-template.md": "ADD\u6267\u884C\u8DEF\u7EBF\u56FE\uFF08\u8F7B\u91CF\uFF09\uFF1A\u6807\u51C6Step\u4EA7\u51FA\u68C0\u67E5",
412
+ "handoff-single-round-template.md": "\u5355\u8F6E\u4EA4\u63A5\uFF1A9\u7AE0\u8282\uFF08\u542B\u6062\u590D\u4E0A\u4E0B\u6587\u5BA1\u8BA1\u67E5\u8BE2\uFF09",
413
+ "handoff-multi-round-template.md": "\u591A\u8F6E\u4EA4\u63A5\uFF1A\u5168\u5C40\u62D3\u6251+\u6BCF\u8F6E13\u5B50\u7AE0\u8282+\u6536\u655B\u89C4\u5219+\u542F\u52A8\u6A21\u677F",
414
+ "review-implementation-template.md": "\u5B9E\u73B0\u5BA1\u67E5\uFF08ADD-10\uFF09\uFF1A\u683C\u5F0F\u5951\u7EA6+\u6846\u67B6\u7248\u672C+\u6570\u636E\u6A21\u578B+E2E curl",
415
+ "review-runtime-template.md": "\u8FD0\u884C\u65F6\u7EA0\u504F\uFF08ADD-11\uFF09\uFF1A\u53D1\u73B0\u5217\u8868+\u6839\u56E0\u5206\u6790+\u6D41\u7A0B\u6539\u8FDB\u9879",
416
+ "prd-standard-template.md": "\u4EA7\u54C1\u9700\u6C42\u6587\u6863\uFF08\u65B0\u5EFA\uFF09\uFF1A\u80CC\u666F\u76EE\u6807+\u7528\u6237\u573A\u666F+\u529F\u80FD\u9700\u6C42+\u9A8C\u6536\u6807\u51C6",
417
+ "prd-incremental-template.md": "\u4EA7\u54C1\u9700\u6C42\u6587\u6863\uFF08\u589E\u91CF\uFF09\uFF1A\u53D8\u66F4\u6458\u8981+diff\u5F0F\u8BB0\u5F55",
418
+ "fix-verification-template.md": "\u4FEE\u590D\u9A8C\u8BC1\u6A21\u677F",
419
+ "report-template.md": "\u4EE3\u7801\u5BA1\u67E5\u62A5\u544A\u6A21\u677F",
420
+ "runtime-report-template.md": "\u8FD0\u884C\u65F6\u62A5\u544A\u6A21\u677F",
421
+ "TERMINOLOGY.md": "\u6A21\u677F\u672F\u8BED\u901F\u67E5"
422
+ });
423
+ static PRIORITY_ORDER = Object.freeze([
424
+ "simple-plan-template.md",
425
+ "spec-template.md",
426
+ "tasks-template.md",
427
+ "checklist-template.md",
428
+ "review-template.md",
429
+ "standard-plan-template.md",
430
+ "add-route-template-heavyweight.md",
431
+ "add-route-template.md",
432
+ "handoff-single-round-template.md",
433
+ "handoff-multi-round-template.md",
434
+ "review-implementation-template.md",
435
+ "review-runtime-template.md",
436
+ "prd-standard-template.md",
437
+ "prd-incremental-template.md",
438
+ "fix-verification-template.md",
439
+ "report-template.md",
440
+ "runtime-report-template.md",
441
+ "TERMINOLOGY.md"
442
+ ]);
443
+ /**
444
+ * 模板目录 find-up 解析(复用 find-up 包,与 src/shared/paths.ts projectRoot 同范式):
445
+ * 从 startDir 向上找第一个 templates 目录(esbuild bundle 内联,产物零依赖):
446
+ * - 产物 <magicDir>/hooks/xxx.mjs → <magicDir>/templates(分发物化)
447
+ * - 源码 templates/core/governance/preload-templates.ts → templates/core/templates(源码模板)
448
+ * 命中条件:目录含至少一个标准模板(防命中仓库根 templates/ 空壳);
449
+ * 目录存在但标准模板缺失 = 物化异常 → 返回 null,由 validate() fail-fast(不继续向上找)。
450
+ * 缺陷修复(2026-08-14 Task 5.1): 原写死 join(dirname, "..", "..", "templates") 固定层级,
451
+ * 产物从 hooks/ 上溯两级到仓库根 templates/(无标准模板)——被 refresh-fixed 反写掩盖,
452
+ * golden 重抓暴露;改 find-up 锚点查找,层级零漂移。
453
+ */
454
+ static findTemplatesDir(startDir) {
455
+ const hit = findUpSync("templates", { cwd: startDir, type: "directory" });
456
+ if (!hit) return null;
457
+ return _PreloadTemplates.PRIORITY_ORDER.some((t) => existsSync2(join2(hit, t))) ? hit : null;
458
+ }
459
+ constructor(templatesDir, tplFlag) {
460
+ const startDir = dirname2(fileURLToPath4(import.meta.url));
461
+ this.templatesDir = templatesDir ?? _PreloadTemplates.findTemplatesDir(startDir) ?? join2(startDir, "..", "templates");
462
+ this.tplFlag = tplFlag ?? `/tmp/add_tpl_${projectHash2(process.env.PROJECT_DIR || process.cwd())}`;
463
+ }
464
+ /** 模板目录存在性校验(fail-fast:目录缺失/标准模板全缺 → 抛错,由 CLI 层转 exit 1) */
465
+ validate() {
466
+ if (!existsSync2(this.templatesDir)) {
467
+ throw new Error(
468
+ `[ADD preload] \u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728: ${this.templatesDir}\uFF08\u751F\u6210\u6001\u7269\u5316\u7F3A\u5931\uFF0C\u8BF7\u6267\u884C add-coder sync \u540E\u91CD\u8BD5\uFF09`
469
+ );
470
+ }
471
+ const available = _PreloadTemplates.PRIORITY_ORDER.filter(
472
+ (t) => existsSync2(join2(this.templatesDir, t))
473
+ ).length;
474
+ if (available === 0) {
475
+ throw new Error(
476
+ `[ADD preload] \u6A21\u677F\u76EE\u5F55\u4E2D\u672A\u627E\u5230 ADD \u6807\u51C6\u6A21\u677F: ${this.templatesDir}\uFF08\u7F3A\u5931\u6E05\u5355: ${_PreloadTemplates.PRIORITY_ORDER.join(" ")}\uFF09`
477
+ );
478
+ }
479
+ }
480
+ /** 读取模板内容(strip frontmatter) */
481
+ readTemplate(file) {
482
+ const path3 = join2(this.templatesDir, file);
483
+ if (!existsSync2(path3)) return "";
484
+ return stripFrontmatter(readFileSync2(path3, "utf-8").split("\n")).join("\n");
485
+ }
486
+ /** --index 模式输出(对齐 bash output_index 逐字) */
487
+ index() {
488
+ const lines = ["## ADD \u53EF\u7528\u6A21\u677F\u6E05\u5355", "", "| # | \u6A21\u677F\u6587\u4EF6 | \u7528\u9014 |", "|---|---------|------|"];
489
+ let i = 1;
490
+ for (const tmpl of _PreloadTemplates.PRIORITY_ORDER) {
491
+ if (existsSync2(join2(this.templatesDir, tmpl))) {
492
+ lines.push(`| ${i} | ${tmpl} | ${_PreloadTemplates.TEMPLATES[tmpl] ?? "\u6A21\u677F\u6587\u4EF6"} |`);
493
+ i++;
494
+ }
495
+ }
496
+ return lines.join("\n") + "\n";
497
+ }
498
+ /** --full 模式输出(对齐 bash output_full 逐字) */
499
+ full(top) {
500
+ const lines = ["## ADD \u6A21\u677F\u5168\u6587\u5185\u5BB9", ""];
501
+ let count = 0;
502
+ for (const tmpl of _PreloadTemplates.PRIORITY_ORDER) {
503
+ if (!existsSync2(join2(this.templatesDir, tmpl))) continue;
504
+ count++;
505
+ if (top > 0 && count > top) break;
506
+ lines.push("---", `### ${tmpl}`, "", this.readTemplate(tmpl), "");
507
+ }
508
+ return lines.join("\n") + "\n";
509
+ }
510
+ isInjected() {
511
+ return existsSync2(this.tplFlag);
512
+ }
513
+ /** 标记模板已注入(tpl-injected 去重:同会话二次命中不重复注入) */
514
+ markInjected() {
515
+ try {
516
+ writeFileSync2(this.tplFlag, "");
517
+ } catch {
518
+ }
519
+ }
520
+ /** CLI 入口:解析参数 → 校验 → 输出;去重命中时 stderr 提示并 exit 0 */
521
+ run(argv) {
522
+ const { mode, top, mark } = parseArgs(argv);
523
+ this.validate();
524
+ if (mode === "index") {
525
+ process.stdout.write(this.index());
526
+ return 0;
527
+ }
528
+ if (this.isInjected() && !mark) {
529
+ process.stderr.write("[ADD preload] \u6A21\u677F\u5DF2\u5728\u672C\u4F1A\u8BDD\u6CE8\u5165\uFF0C\u8DF3\u8FC7\u91CD\u590D\u6CE8\u5165\uFF08tpl-injected \u6807\u8BB0\u5B58\u5728\uFF09\n");
530
+ return 0;
531
+ }
532
+ process.stdout.write(this.full(top));
533
+ this.markInjected();
534
+ return 0;
535
+ }
536
+ };
537
+
538
+ // templates/core/governance/prompt-router.ts
539
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
540
+ import { join as join5 } from "node:path";
541
+ import { spawnSync as spawnSync2 } from "node:child_process";
542
+
543
+ // templates/core/governance/vocabulary.ts
544
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
545
+ import { join as join3 } from "node:path";
546
+ function serializeTrigger(e) {
547
+ return `${e.priority}::${e.regex}::${e.action}`;
548
+ }
549
+ function vocabularyFile() {
550
+ const magicDir = tryResolveMagicDir();
551
+ if (!magicDir) return "";
552
+ return join3(
553
+ process.env.PROJECT_DIR || process.cwd(),
554
+ magicDir,
555
+ "vocabulary",
556
+ "add-governance-vocabulary.md"
557
+ );
558
+ }
559
+ function mapTableRow(line, map) {
560
+ const cells = line.split("|");
561
+ const prio = cells[1]?.trim() ?? "";
562
+ const rawTrigger = cells[2] ?? "";
563
+ const action = cells[3]?.trim() ?? "";
564
+ const trigger = rawTrigger.replace(/`/g, "").replace(/ *\/ */g, "|").trim();
565
+ if (!trigger) return null;
566
+ return map(prio, trigger, action);
567
+ }
568
+ function loadTriggers() {
569
+ const file = vocabularyFile();
570
+ if (!file || !existsSync3(file)) return [];
571
+ const lines = readFileSync3(file, "utf-8").split("\n");
572
+ const out = [];
573
+ let inRange = false;
574
+ for (const line of lines) {
575
+ if (/^## 类别 A: 文档类型/.test(line)) inRange = true;
576
+ if (/^## 类别 [G-Z]/.test(line)) inRange = false;
577
+ if (!inRange) continue;
578
+ if (!/^\| (P0|P1|P2) /.test(line)) continue;
579
+ const entry = mapTableRow(line, (priority, regex, action) => ({ priority, regex, action }));
580
+ if (entry) out.push(entry);
581
+ }
582
+ return out;
583
+ }
584
+ function isDevKeywordLine(regex) {
585
+ return /修\.?bug|fix\.?bug/i.test(regex);
586
+ }
587
+ function matchTrigger(prompt) {
588
+ return loadTriggers().flatMap(({ regex, action }) => {
589
+ if (isDevKeywordLine(regex)) return [];
590
+ try {
591
+ return new RegExp(regex, "i").test(prompt) ? [`[ADD \u89E6\u53D1] ${regex} \u2192 ${action}`] : [];
592
+ } catch {
593
+ return [];
594
+ }
595
+ });
596
+ }
597
+ function loadDevKeywords() {
598
+ return loadTriggers().filter((e) => serializeTrigger(e).includes("\u5F00\u53D1|\u6539\u529F\u80FD")).map((e) => e.regex);
599
+ }
600
+
601
+ // templates/core/governance/notify.ts
602
+ import { existsSync as existsSync4, mkdirSync, renameSync, statSync, appendFileSync } from "node:fs";
603
+ import { join as join4 } from "node:path";
604
+ function writeHookEvent(hook, decision, cmd, reason, plan = "unknown", status = "none", extra = "", magicDirOverride) {
605
+ const defaults = protocol.adapter_defaults;
606
+ const fallback = defaults?.magic_dir_fallback ?? ".qoder";
607
+ const dir = join4(magicDirOverride ?? (process.env.MAGIC_DIR || fallback), "reports");
608
+ try {
609
+ mkdirSync(dir, { recursive: true });
610
+ } catch {
611
+ }
612
+ const file = join4(dir, "hook-events.jsonl");
613
+ if (existsSync4(file)) {
614
+ let sz = 0;
615
+ try {
616
+ sz = statSync(file).size;
617
+ } catch {
618
+ sz = 0;
619
+ }
620
+ if (sz > event.file.rotate_bytes) {
621
+ try {
622
+ renameSync(file, `${file}.old`);
623
+ } catch {
624
+ }
625
+ }
626
+ }
627
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
628
+ const extraPart = extra ? `,${extra}` : "";
629
+ const line = `{"ts":"${ts}","hook":"${hook}","decision":"${decision}","cmd":"${cmd}","reason":"${reason}","planKeyword":"${plan}","planStatus":"${status}"${extraPart}}
630
+ `;
631
+ appendFileSync(file, line);
632
+ }
633
+
634
+ // templates/core/governance/prompt-router.ts
635
+ var PromptRouter = class {
636
+ magicDir;
637
+ constructor(magicDir) {
638
+ this.magicDir = magicDir;
639
+ }
640
+ // ─────────────────────────── 扩展点 ───────────────────────────
641
+ /** prompt 提取(core: jsonGet) */
642
+ extractPrompt(input2) {
643
+ return jsonGet(input2, "prompt");
644
+ }
645
+ /** 验收幂等文本(core 协议: 6 行含 ★ 同步检查) */
646
+ acceptedText() {
647
+ return `[ADD \u9A8C\u6536] \u26A0\uFE0F \u5DF2\u9A8C\u6536\u3002\u8FDB\u5165 Review \u6A21\u5F0F:
648
+ \u2460 \u91CD\u65B0\u68C0\u67E5 checklist [T]/[R] \u9879
649
+ \u2461 \u5BA1\u67E5 audit \u8BB0\u5F55\u5B8C\u6574\u6027
650
+ \u2462 \u5982\u6709\u5DEE\u5F02 \u2192 Review \u56DE\u6D41\u81F3 handoff\uFF08\u589E\u91CF\u66F4\u65B0\uFF0C\u4E0D\u8986\u76D6\u5DF2\u6709\u7ED3\u8BBA\uFF09
651
+ \u2463 \u65E0\u5DEE\u5F02 \u2192 \u8BB0\u5F55 'Review \u5DF2\u786E\u8BA4\uFF0C\u65E0\u65B0\u53D1\u73B0'
652
+ \u2605 \u540C\u6B65\u68C0\u67E5: \u5982 checklist \u6709\u65B0 cuid \u4F46 handoff \u5BA1\u8BA1\u8868\u7F3A\u5931 \u2192 \u66F4\u65B0 handoff ADD-7 \u8868 + query_audit_logs \u547D\u4EE4
653
+ `;
654
+ }
655
+ /** 验收后置处理(core: spawn review-checklist 子进程,输出丢弃) */
656
+ onAccepted(handoff, addRoute) {
657
+ const reviewScript = join5(this.magicDir, "hooks", "review-checklist.mjs");
658
+ if (existsSync5(reviewScript)) {
659
+ spawnSync2(process.execPath, [reviewScript, handoff, addRoute], { stdio: "ignore" });
660
+ }
661
+ }
662
+ /** 开发关键词命中提示(core: 无) */
663
+ onDevKwMatched() {
664
+ }
665
+ /** 开发关键词匹配(core: join("|") 单正则;qoder: 逐个正则 some) */
666
+ devKwMatched(prompt, devKw) {
667
+ try {
668
+ return new RegExp(devKw.join("|"), "i").test(prompt);
669
+ } catch {
670
+ return false;
671
+ }
672
+ }
673
+ /** Layer 2 输出通道(core: stderr) */
674
+ layer2ToStderr() {
675
+ return true;
676
+ }
677
+ /** Layer 3 状态文本(core: 单行) */
678
+ layer3Text(plan, rounds, step, handoff) {
679
+ return `[ADD \u72B6\u6001] Plan: ${plan}, \u8F6E\u6B21: ${rounds}, Step: ${step}, handoff: ${handoff}
680
+ `;
681
+ }
682
+ /** 日报告警文本(core: 含"或检查 hooks 误报") */
683
+ dailyWarnText(noPlan, threshold) {
684
+ return `[Hook \u26A0\uFE0F] \u65E0 Plan \u63D0\u793A\u5DF2\u8FBE ${noPlan} \u6B21\uFF08\u2265${threshold}\uFF09\uFF0C\u5EFA\u8BAE\u521B\u5EFA Plan \u6216\u68C0\u67E5 hooks \u8BEF\u62A5
685
+ `;
686
+ }
687
+ /** Layer 3 附加注入(core: 无) */
688
+ afterLayer3() {
689
+ }
690
+ /** 日报跳过条件(core: 不跳过;claude: MAGIC_DIR 未设置时跳过) */
691
+ shouldSkipDaily() {
692
+ return false;
693
+ }
694
+ /** 前置注入(core: 无;qoder: 无条件 "ADD workflow active" JSON) */
695
+ preamble(_input) {
696
+ }
697
+ /** Layer 3 输出形态(core: 纯文本逐行;qoder: hookSpecificOutput JSON 包) */
698
+ layer3Json() {
699
+ return false;
700
+ }
701
+ /** 日报并入 Layer3 上下文(core: 独立行输出;qoder: 并入 additionalContext) */
702
+ dailyInContext() {
703
+ return false;
704
+ }
705
+ // ─────────────────────────── 流程固化 ───────────────────────────
706
+ /** 主路由(模板方法):返回 exit code(0 放行) */
707
+ run(input2) {
708
+ const prompt = this.extractPrompt(input2);
709
+ if (prompt === "") return 0;
710
+ this.preamble(input2);
711
+ const matched = matchTrigger(prompt);
712
+ if (matched.length > 0) {
713
+ if (/验收|收敛/i.test(prompt)) {
714
+ const addState = detectActiveAdd();
715
+ if (addState !== null) {
716
+ const handoff2 = addState.split("::")[3] ?? "";
717
+ const addRoute = addState.split("::")[4] ?? "";
718
+ if (isAlreadyAccepted(addRoute, handoff2)) {
719
+ process.stdout.write(this.acceptedText());
720
+ this.onAccepted(handoff2, addRoute);
721
+ return 0;
722
+ }
723
+ }
724
+ }
725
+ for (const m of matched) process.stdout.write(m + "\n");
726
+ return 0;
727
+ }
728
+ const devKw = loadDevKeywords();
729
+ if (devKw.length === 0) return 0;
730
+ if (!this.devKwMatched(prompt, devKw)) return 0;
731
+ this.onDevKwMatched();
732
+ const state = detectActiveAdd();
733
+ if (state === null) {
734
+ const text = `[ADD \u63D0\u793A] \u68C0\u6D4B\u5230\u5F00\u53D1\u4EFB\u52A1\uFF0C\u4F46\u65E0\u6D3B\u8DC3 ADD Plan\u3002\u5EFA\u8BAE\u5148\u6267\u884C add-paradigm SKILL:
735
+ Step 0: \u6587\u6863\u5148\u884C (Plan \u2192 Review \u2192 Specs)
736
+ Step 3: \u4EE3\u7801\u5B9E\u73B0 + \u5BA1\u8BA1\u690D\u5165
737
+ Step 8: \u6536\u655B\u5224\u65AD
738
+ `;
739
+ if (this.layer2ToStderr()) {
740
+ process.stderr.write(text);
741
+ } else {
742
+ process.stdout.write(text);
743
+ }
744
+ writeHookEvent("prompt-submit", "info", prompt, "\u65E0\u6D3B\u8DC3 ADD Plan \u4E0B\u68C0\u6D4B\u5230\u5F00\u53D1\u4EFB\u52A1", "no-active-plan", "none");
745
+ return 0;
746
+ }
747
+ const plan = state.split("::")[0] ?? "";
748
+ const step = state.split("::")[1] ?? "";
749
+ const rounds = state.split("::")[2] ?? "";
750
+ const handoff = state.split("::")[3] ?? "";
751
+ let block = this.layer3Text(plan, rounds, step, handoff);
752
+ if (!this.shouldSkipDaily()) {
753
+ const HOOK_JSONL = join5(this.magicDir, "reports", "hook-events.jsonl");
754
+ if (existsSync5(HOOK_JSONL)) {
755
+ const today = localIsoSeconds().slice(0, 10);
756
+ const content = readFileSync4(HOOK_JSONL, "utf-8");
757
+ const todayLines = content.split("\n").filter((l) => l.includes(`"ts":"${today}`));
758
+ const total = todayLines.length;
759
+ const noPlan = todayLines.filter((l) => l.includes('"planKeyword":"no-active-plan"')).length;
760
+ if (total > 0) {
761
+ const dailyLine = `[Hook \u6CBB\u7406] \u4ECA\u65E5\u63D0\u793A: ${total} \u6B21 | \u65E0 Plan \u63D0\u793A: ${noPlan} \u6B21`;
762
+ const warnThreshold = event.daily.warn_threshold;
763
+ const warnLine = noPlan >= warnThreshold ? this.dailyWarnText(noPlan, warnThreshold) : "";
764
+ if (this.dailyInContext()) {
765
+ block += `
766
+ ${dailyLine}`;
767
+ if (warnLine) block += `
768
+ ${warnLine.trimEnd()}`;
769
+ } else {
770
+ process.stdout.write(dailyLine + "\n");
771
+ if (warnLine) process.stdout.write(warnLine);
772
+ }
773
+ }
774
+ }
775
+ }
776
+ if (this.layer3Json()) {
777
+ process.stdout.write(
778
+ JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: block.trimEnd() } }) + "\n"
779
+ );
780
+ } else {
781
+ process.stdout.write(block);
782
+ }
783
+ this.afterLayer3();
784
+ return 0;
785
+ }
786
+ };
787
+
788
+ // templates/adapters/claude/hooks/lib/claude-env.ts
789
+ function resolveClaudeProjectDir() {
790
+ return process.env.CLAUDE_PROJECT_DIR || process.cwd();
791
+ }
792
+
793
+ // templates/adapters/claude/hooks/prompt-submit.ts
794
+ process.env.PROJECT_DIR = resolveClaudeProjectDir();
795
+ var ClaudePromptRouter = class extends PromptRouter {
796
+ /** ① prompt 提取(对齐 bash grep/sed,无 jq) */
797
+ extractPrompt(input2) {
798
+ return input2.match(/"prompt"\s*:\s*"([^"]*)"/)?.[1] ?? "";
799
+ }
800
+ /** ② 验收幂等文本(claude 4 行,无 ★ 同步检查) */
801
+ acceptedText() {
802
+ return `[ADD \u9A8C\u6536] \u26A0\uFE0F \u5DF2\u9A8C\u6536\u3002\u8FDB\u5165 Review \u6A21\u5F0F:
803
+ \u2460 \u91CD\u65B0\u68C0\u67E5 checklist [T]/[R] \u9879
804
+ \u2461 \u5BA1\u67E5 audit \u8BB0\u5F55\u5B8C\u6574\u6027
805
+ \u2462 \u5982\u6709\u5DEE\u5F02 \u2192 Review \u56DE\u6D41\u81F3 handoff\uFF08\u589E\u91CF\u66F4\u65B0\uFF09
806
+ \u2463 \u65E0\u5DEE\u5F02 \u2192 \u8BB0\u5F55 'Review \u5DF2\u786E\u8BA4\uFF0C\u65E0\u65B0\u53D1\u73B0'
807
+ `;
808
+ }
809
+ /** ③ 验收后置(claude 无 review-checklist 子进程) */
810
+ onAccepted(_handoff, _addRoute) {
811
+ }
812
+ /** ④ 开发关键词命中提示(stderr) */
813
+ onDevKwMatched() {
814
+ process.stderr.write("[ADD PromptSubmit] \u68C0\u6D4B\u5230\u5F00\u53D1\u5173\u952E\u8BCD\n");
815
+ }
816
+ /** ⑤ Layer 2 输出通道(claude: stdout) */
817
+ layer2ToStderr() {
818
+ return false;
819
+ }
820
+ /** ⑥ Layer 3 状态文本(多行块) */
821
+ layer3Text(plan, rounds, step, handoff) {
822
+ return `[ADD \u5F53\u524D\u72B6\u6001]
823
+ Plan: ${plan}
824
+ \u8F6E\u6B21: ${rounds}
825
+ \u5F53\u524D Step: ${step}
826
+ handoff: ${handoff}
827
+ `;
828
+ }
829
+ /** ⑦ 日报告警文本(无"或检查 hooks 误报") */
830
+ dailyWarnText(noPlan, threshold) {
831
+ return `[Hook \u26A0\uFE0F] \u65E0 Plan \u63D0\u793A\u5DF2\u8FBE ${noPlan} \u6B21\uFF08\u2265${threshold}\uFF09\uFF0C\u5EFA\u8BAE\u521B\u5EFA Plan
832
+ `;
833
+ }
834
+ /** ⑧ Layer 3 附加注入: 模板全文(--full --top 5) */
835
+ afterLayer3() {
836
+ try {
837
+ process.stdout.write(new PreloadTemplates().full(5));
838
+ } catch {
839
+ }
840
+ }
841
+ /** ⑨ MAGIC_DIR 未设置时跳过日报(TS 修复: bash 空路径相对检查) */
842
+ shouldSkipDaily() {
843
+ return !process.env.MAGIC_DIR;
844
+ }
845
+ };
846
+ var input = readHookInput();
847
+ process.exit(new ClaudePromptRouter(process.env.MAGIC_DIR || "").run(input));