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,614 @@
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/prompt-router.ts
351
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
352
+ import { join as join4 } from "node:path";
353
+ import { spawnSync as spawnSync2 } from "node:child_process";
354
+
355
+ // templates/core/governance/vocabulary.ts
356
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
357
+ import { join as join2 } from "node:path";
358
+ function serializeTrigger(e) {
359
+ return `${e.priority}::${e.regex}::${e.action}`;
360
+ }
361
+ function vocabularyFile() {
362
+ const magicDir = tryResolveMagicDir();
363
+ if (!magicDir) return "";
364
+ return join2(
365
+ process.env.PROJECT_DIR || process.cwd(),
366
+ magicDir,
367
+ "vocabulary",
368
+ "add-governance-vocabulary.md"
369
+ );
370
+ }
371
+ function mapTableRow(line, map) {
372
+ const cells = line.split("|");
373
+ const prio = cells[1]?.trim() ?? "";
374
+ const rawTrigger = cells[2] ?? "";
375
+ const action = cells[3]?.trim() ?? "";
376
+ const trigger = rawTrigger.replace(/`/g, "").replace(/ *\/ */g, "|").trim();
377
+ if (!trigger) return null;
378
+ return map(prio, trigger, action);
379
+ }
380
+ function loadTriggers() {
381
+ const file = vocabularyFile();
382
+ if (!file || !existsSync2(file)) return [];
383
+ const lines = readFileSync2(file, "utf-8").split("\n");
384
+ const out = [];
385
+ let inRange = false;
386
+ for (const line of lines) {
387
+ if (/^## 类别 A: 文档类型/.test(line)) inRange = true;
388
+ if (/^## 类别 [G-Z]/.test(line)) inRange = false;
389
+ if (!inRange) continue;
390
+ if (!/^\| (P0|P1|P2) /.test(line)) continue;
391
+ const entry = mapTableRow(line, (priority, regex, action) => ({ priority, regex, action }));
392
+ if (entry) out.push(entry);
393
+ }
394
+ return out;
395
+ }
396
+ function isDevKeywordLine(regex) {
397
+ return /修\.?bug|fix\.?bug/i.test(regex);
398
+ }
399
+ function matchTrigger(prompt) {
400
+ return loadTriggers().flatMap(({ regex, action }) => {
401
+ if (isDevKeywordLine(regex)) return [];
402
+ try {
403
+ return new RegExp(regex, "i").test(prompt) ? [`[ADD \u89E6\u53D1] ${regex} \u2192 ${action}`] : [];
404
+ } catch {
405
+ return [];
406
+ }
407
+ });
408
+ }
409
+ function loadDevKeywords() {
410
+ return loadTriggers().filter((e) => serializeTrigger(e).includes("\u5F00\u53D1|\u6539\u529F\u80FD")).map((e) => e.regex);
411
+ }
412
+
413
+ // templates/core/governance/notify.ts
414
+ import { existsSync as existsSync3, mkdirSync, renameSync, statSync, appendFileSync } from "node:fs";
415
+ import { join as join3 } from "node:path";
416
+ function writeHookEvent(hook, decision, cmd, reason, plan = "unknown", status = "none", extra = "", magicDirOverride) {
417
+ const defaults = protocol.adapter_defaults;
418
+ const fallback = defaults?.magic_dir_fallback ?? ".qoder";
419
+ const dir = join3(magicDirOverride ?? (process.env.MAGIC_DIR || fallback), "reports");
420
+ try {
421
+ mkdirSync(dir, { recursive: true });
422
+ } catch {
423
+ }
424
+ const file = join3(dir, "hook-events.jsonl");
425
+ if (existsSync3(file)) {
426
+ let sz = 0;
427
+ try {
428
+ sz = statSync(file).size;
429
+ } catch {
430
+ sz = 0;
431
+ }
432
+ if (sz > event.file.rotate_bytes) {
433
+ try {
434
+ renameSync(file, `${file}.old`);
435
+ } catch {
436
+ }
437
+ }
438
+ }
439
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
440
+ const extraPart = extra ? `,${extra}` : "";
441
+ const line = `{"ts":"${ts}","hook":"${hook}","decision":"${decision}","cmd":"${cmd}","reason":"${reason}","planKeyword":"${plan}","planStatus":"${status}"${extraPart}}
442
+ `;
443
+ appendFileSync(file, line);
444
+ }
445
+
446
+ // templates/core/governance/prompt-router.ts
447
+ var PromptRouter = class {
448
+ magicDir;
449
+ constructor(magicDir) {
450
+ this.magicDir = magicDir;
451
+ }
452
+ // ─────────────────────────── 扩展点 ───────────────────────────
453
+ /** prompt 提取(core: jsonGet) */
454
+ extractPrompt(input2) {
455
+ return jsonGet(input2, "prompt");
456
+ }
457
+ /** 验收幂等文本(core 协议: 6 行含 ★ 同步检查) */
458
+ acceptedText() {
459
+ return `[ADD \u9A8C\u6536] \u26A0\uFE0F \u5DF2\u9A8C\u6536\u3002\u8FDB\u5165 Review \u6A21\u5F0F:
460
+ \u2460 \u91CD\u65B0\u68C0\u67E5 checklist [T]/[R] \u9879
461
+ \u2461 \u5BA1\u67E5 audit \u8BB0\u5F55\u5B8C\u6574\u6027
462
+ \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
463
+ \u2463 \u65E0\u5DEE\u5F02 \u2192 \u8BB0\u5F55 'Review \u5DF2\u786E\u8BA4\uFF0C\u65E0\u65B0\u53D1\u73B0'
464
+ \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
465
+ `;
466
+ }
467
+ /** 验收后置处理(core: spawn review-checklist 子进程,输出丢弃) */
468
+ onAccepted(handoff, addRoute) {
469
+ const reviewScript = join4(this.magicDir, "hooks", "review-checklist.mjs");
470
+ if (existsSync4(reviewScript)) {
471
+ spawnSync2(process.execPath, [reviewScript, handoff, addRoute], { stdio: "ignore" });
472
+ }
473
+ }
474
+ /** 开发关键词命中提示(core: 无) */
475
+ onDevKwMatched() {
476
+ }
477
+ /** 开发关键词匹配(core: join("|") 单正则;qoder: 逐个正则 some) */
478
+ devKwMatched(prompt, devKw) {
479
+ try {
480
+ return new RegExp(devKw.join("|"), "i").test(prompt);
481
+ } catch {
482
+ return false;
483
+ }
484
+ }
485
+ /** Layer 2 输出通道(core: stderr) */
486
+ layer2ToStderr() {
487
+ return true;
488
+ }
489
+ /** Layer 3 状态文本(core: 单行) */
490
+ layer3Text(plan, rounds, step, handoff) {
491
+ return `[ADD \u72B6\u6001] Plan: ${plan}, \u8F6E\u6B21: ${rounds}, Step: ${step}, handoff: ${handoff}
492
+ `;
493
+ }
494
+ /** 日报告警文本(core: 含"或检查 hooks 误报") */
495
+ dailyWarnText(noPlan, threshold) {
496
+ 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
497
+ `;
498
+ }
499
+ /** Layer 3 附加注入(core: 无) */
500
+ afterLayer3() {
501
+ }
502
+ /** 日报跳过条件(core: 不跳过;claude: MAGIC_DIR 未设置时跳过) */
503
+ shouldSkipDaily() {
504
+ return false;
505
+ }
506
+ /** 前置注入(core: 无;qoder: 无条件 "ADD workflow active" JSON) */
507
+ preamble(_input) {
508
+ }
509
+ /** Layer 3 输出形态(core: 纯文本逐行;qoder: hookSpecificOutput JSON 包) */
510
+ layer3Json() {
511
+ return false;
512
+ }
513
+ /** 日报并入 Layer3 上下文(core: 独立行输出;qoder: 并入 additionalContext) */
514
+ dailyInContext() {
515
+ return false;
516
+ }
517
+ // ─────────────────────────── 流程固化 ───────────────────────────
518
+ /** 主路由(模板方法):返回 exit code(0 放行) */
519
+ run(input2) {
520
+ const prompt = this.extractPrompt(input2);
521
+ if (prompt === "") return 0;
522
+ this.preamble(input2);
523
+ const matched = matchTrigger(prompt);
524
+ if (matched.length > 0) {
525
+ if (/验收|收敛/i.test(prompt)) {
526
+ const addState = detectActiveAdd();
527
+ if (addState !== null) {
528
+ const handoff2 = addState.split("::")[3] ?? "";
529
+ const addRoute = addState.split("::")[4] ?? "";
530
+ if (isAlreadyAccepted(addRoute, handoff2)) {
531
+ process.stdout.write(this.acceptedText());
532
+ this.onAccepted(handoff2, addRoute);
533
+ return 0;
534
+ }
535
+ }
536
+ }
537
+ for (const m of matched) process.stdout.write(m + "\n");
538
+ return 0;
539
+ }
540
+ const devKw = loadDevKeywords();
541
+ if (devKw.length === 0) return 0;
542
+ if (!this.devKwMatched(prompt, devKw)) return 0;
543
+ this.onDevKwMatched();
544
+ const state = detectActiveAdd();
545
+ if (state === null) {
546
+ 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:
547
+ Step 0: \u6587\u6863\u5148\u884C (Plan \u2192 Review \u2192 Specs)
548
+ Step 3: \u4EE3\u7801\u5B9E\u73B0 + \u5BA1\u8BA1\u690D\u5165
549
+ Step 8: \u6536\u655B\u5224\u65AD
550
+ `;
551
+ if (this.layer2ToStderr()) {
552
+ process.stderr.write(text);
553
+ } else {
554
+ process.stdout.write(text);
555
+ }
556
+ writeHookEvent("prompt-submit", "info", prompt, "\u65E0\u6D3B\u8DC3 ADD Plan \u4E0B\u68C0\u6D4B\u5230\u5F00\u53D1\u4EFB\u52A1", "no-active-plan", "none");
557
+ return 0;
558
+ }
559
+ const plan = state.split("::")[0] ?? "";
560
+ const step = state.split("::")[1] ?? "";
561
+ const rounds = state.split("::")[2] ?? "";
562
+ const handoff = state.split("::")[3] ?? "";
563
+ let block = this.layer3Text(plan, rounds, step, handoff);
564
+ if (!this.shouldSkipDaily()) {
565
+ const HOOK_JSONL = join4(this.magicDir, "reports", "hook-events.jsonl");
566
+ if (existsSync4(HOOK_JSONL)) {
567
+ const today = localIsoSeconds().slice(0, 10);
568
+ const content = readFileSync3(HOOK_JSONL, "utf-8");
569
+ const todayLines = content.split("\n").filter((l) => l.includes(`"ts":"${today}`));
570
+ const total = todayLines.length;
571
+ const noPlan = todayLines.filter((l) => l.includes('"planKeyword":"no-active-plan"')).length;
572
+ if (total > 0) {
573
+ const dailyLine = `[Hook \u6CBB\u7406] \u4ECA\u65E5\u63D0\u793A: ${total} \u6B21 | \u65E0 Plan \u63D0\u793A: ${noPlan} \u6B21`;
574
+ const warnThreshold = event.daily.warn_threshold;
575
+ const warnLine = noPlan >= warnThreshold ? this.dailyWarnText(noPlan, warnThreshold) : "";
576
+ if (this.dailyInContext()) {
577
+ block += `
578
+ ${dailyLine}`;
579
+ if (warnLine) block += `
580
+ ${warnLine.trimEnd()}`;
581
+ } else {
582
+ process.stdout.write(dailyLine + "\n");
583
+ if (warnLine) process.stdout.write(warnLine);
584
+ }
585
+ }
586
+ }
587
+ }
588
+ if (this.layer3Json()) {
589
+ process.stdout.write(
590
+ JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: block.trimEnd() } }) + "\n"
591
+ );
592
+ } else {
593
+ process.stdout.write(block);
594
+ }
595
+ this.afterLayer3();
596
+ return 0;
597
+ }
598
+ };
599
+
600
+ // templates/adapters/trae/hooks/prompt-submit.ts
601
+ var MAGIC_DIR = process.env.MAGIC_DIR || ".trae";
602
+ var TraePromptRouter = class extends PromptRouter {
603
+ /** ③ 验收后置: 输出 review-checklist 占位文本(trae bash 调占位脚本逐字) */
604
+ onAccepted(_handoff, _addRoute) {
605
+ process.stdout.write("[ADD ReviewChecklist] \u8BF7\u5728\u63D0\u4EA4\u524D\u786E\u8BA4 checklist.md \u5168\u90E8\u9879\u5DF2\u52FE\u9009\u3002\n");
606
+ }
607
+ /** ④ Layer 2 输出通道(trae: stdout——bash heredoc 无重定向) */
608
+ layer2ToStderr() {
609
+ return false;
610
+ }
611
+ // 其余(extractPrompt/acceptedText/layer3Text/dailyWarnText/afterLayer3)与 core 基线一致
612
+ };
613
+ var input = readHookInput();
614
+ process.exit(new TraePromptRouter(MAGIC_DIR).run(input));
@@ -0,0 +1,3 @@
1
+ // templates/adapters/trae/hooks/review-checklist.ts
2
+ process.stdout.write("[ADD ReviewChecklist] \u8BF7\u5728\u63D0\u4EA4\u524D\u786E\u8BA4 checklist.md \u5168\u90E8\u9879\u5DF2\u52FE\u9009\u3002\n");
3
+ process.exit(0);