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,415 @@
1
+ // templates/core/governance/common.ts
2
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+
5
+ // templates/core/governance/rules.ts
6
+ var event = {
7
+ "file": {
8
+ "path": "{magicDir}/reports/hook-events.jsonl",
9
+ "rotate_bytes": 262144,
10
+ "total_bytes": 524288,
11
+ "note": "MCP Server \u5B95\u673A\u4E0D\u4E22\u4E8B\u4EF6\uFF0C\u91CD\u542F\u540E\u4ECE\u6587\u4EF6\u6062\u590D\u6D88\u8D39"
12
+ },
13
+ "schema": {
14
+ "fields": [
15
+ "ts",
16
+ "hook",
17
+ "decision",
18
+ "cmd",
19
+ "reason",
20
+ "planKeyword",
21
+ "planStatus"
22
+ ],
23
+ "ts_format": "date -u +%Y-%m-%dT%H:%M:%SZ",
24
+ "extra_fields": [
25
+ "anchor_hit",
26
+ "struct_score",
27
+ "override"
28
+ ]
29
+ },
30
+ "daily": {
31
+ "warn_threshold": 10
32
+ }
33
+ };
34
+ var protocol = {
35
+ "exit_codes": {
36
+ "pass": 0,
37
+ "block": 2,
38
+ "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"
39
+ },
40
+ "output": {
41
+ "stdout_json_only": true,
42
+ "stderr_human_text": true,
43
+ "field_separator": "::",
44
+ "magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
45
+ },
46
+ "adapters": {
47
+ "claude": {
48
+ "stdout_form": "plain-text",
49
+ "project_dir_env": "CLAUDE_PROJECT_DIR",
50
+ "magic_dir": ".claude",
51
+ "handlerTypes": [
52
+ "command",
53
+ "mcp_tool"
54
+ ]
55
+ },
56
+ "qoder": {
57
+ "stdout_form": "json",
58
+ "project_dir_env": "QODER_PROJECT_DIR",
59
+ "magic_dir": ".qoder",
60
+ "handlerTypes": [
61
+ "command",
62
+ "http"
63
+ ]
64
+ },
65
+ "codex": {
66
+ "stdout_form": "systemMessage",
67
+ "project_dir_env": "git-toplevel",
68
+ "magic_dir": ".codex",
69
+ "handlerTypes": [
70
+ "command"
71
+ ]
72
+ },
73
+ "vscode": {
74
+ "stdout_form": "plain-text",
75
+ "project_dir_env": "PWD",
76
+ "magic_dir": ".vscode",
77
+ "handlerTypes": [
78
+ "command"
79
+ ]
80
+ },
81
+ "trae": {
82
+ "stdout_form": "plain-text",
83
+ "project_dir_env": "PWD",
84
+ "magic_dir": ".trae",
85
+ "handlerTypes": [
86
+ "command"
87
+ ]
88
+ }
89
+ },
90
+ "event_outputs": {
91
+ "qoder": {
92
+ "SessionStart": "additionalContext",
93
+ "UserPromptSubmit": "additionalContext",
94
+ "PreToolUse": "permissionDecision",
95
+ "Stop": "additionalContext",
96
+ "PostToolUse": "feedback",
97
+ "SubagentStart": "additionalContext",
98
+ "SubagentStop": "additionalContext",
99
+ "PostToolUseFailure": "text",
100
+ "PermissionRequest": "text",
101
+ "SessionEnd": "text",
102
+ "PreCompact": "text",
103
+ "Notification": "text"
104
+ },
105
+ "claude": {
106
+ "SessionStart": "additionalContext",
107
+ "UserPromptSubmit": "additionalContext",
108
+ "PreToolUse": "permissionDecision",
109
+ "Stop": "text",
110
+ "PostToolUse": "feedback",
111
+ "SubagentStart": "text",
112
+ "SubagentStop": "text",
113
+ "PostToolUseFailure": "text",
114
+ "PermissionRequest": "text",
115
+ "SessionEnd": "text",
116
+ "PreCompact": "text",
117
+ "Notification": "text"
118
+ },
119
+ "codex": {
120
+ "Stop": "systemMessage",
121
+ "PostToolUse": "text"
122
+ },
123
+ "vscode": {
124
+ "PostToolUse": "text"
125
+ },
126
+ "trae": {
127
+ "PostToolUse": "text"
128
+ }
129
+ },
130
+ "core": {
131
+ "stdout_form": "json",
132
+ "magic_dir": ".add",
133
+ "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"
134
+ },
135
+ "adapter_defaults": {
136
+ "magic_dir_fallback": ".qoder",
137
+ "probe_magic_dirs": [
138
+ ".claude",
139
+ ".qoder",
140
+ ".vscode",
141
+ ".add",
142
+ ".trae",
143
+ ".codex"
144
+ ]
145
+ }
146
+ };
147
+
148
+ // templates/core/governance/common.ts
149
+ var EXIT_PASS = protocol.exit_codes.pass;
150
+ var EXIT_BLOCK = protocol.exit_codes.block;
151
+ var STATE_SEP = protocol.output.field_separator;
152
+ function readHookInput() {
153
+ if (process.stdin.isTTY) return "{}";
154
+ return readFileSync(0, "utf-8");
155
+ }
156
+ function jsonGet(json, field) {
157
+ const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`);
158
+ const m = re.exec(json);
159
+ return m?.[1] ?? "";
160
+ }
161
+ function projectHash() {
162
+ try {
163
+ return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
164
+ `).digest("hex").slice(0, 8);
165
+ } catch {
166
+ return "default";
167
+ }
168
+ }
169
+ var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
170
+
171
+ // templates/core/governance/post-tool-router.ts
172
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, writeFileSync as writeFileSync2 } from "node:fs";
173
+ import { join as join2, basename, dirname } from "node:path";
174
+
175
+ // templates/core/governance/notify.ts
176
+ import { existsSync as existsSync2, mkdirSync, renameSync, statSync, appendFileSync } from "node:fs";
177
+ import { join } from "node:path";
178
+ function writeHookEvent(hook, decision, cmd, reason, plan = "unknown", status = "none", extra = "", magicDirOverride) {
179
+ const defaults = protocol.adapter_defaults;
180
+ const fallback = defaults?.magic_dir_fallback ?? ".qoder";
181
+ const dir = join(magicDirOverride ?? (process.env.MAGIC_DIR || fallback), "reports");
182
+ try {
183
+ mkdirSync(dir, { recursive: true });
184
+ } catch {
185
+ }
186
+ const file = join(dir, "hook-events.jsonl");
187
+ if (existsSync2(file)) {
188
+ let sz = 0;
189
+ try {
190
+ sz = statSync(file).size;
191
+ } catch {
192
+ sz = 0;
193
+ }
194
+ if (sz > event.file.rotate_bytes) {
195
+ try {
196
+ renameSync(file, `${file}.old`);
197
+ } catch {
198
+ }
199
+ }
200
+ }
201
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
202
+ const extraPart = extra ? `,${extra}` : "";
203
+ const line = `{"ts":"${ts}","hook":"${hook}","decision":"${decision}","cmd":"${cmd}","reason":"${reason}","planKeyword":"${plan}","planStatus":"${status}"${extraPart}}
204
+ `;
205
+ appendFileSync(file, line);
206
+ }
207
+
208
+ // templates/core/governance/audit-bridge.ts
209
+ var AuditBridge = class {
210
+ projectDir;
211
+ magicDir;
212
+ constructor(projectDir, magicDir) {
213
+ this.projectDir = projectDir;
214
+ this.magicDir = magicDir;
215
+ }
216
+ /**
217
+ * 文件写入审计事件(decision="write")。
218
+ * @param filePath 被写入文件路径(绝对/相对均可,MCP 消费端以 cmd 为 targetId)
219
+ */
220
+ emit(filePath) {
221
+ if (!filePath || filePath === "") return;
222
+ const planKeyword = this.extractPlanKeyword(filePath);
223
+ writeHookEvent("post-tool-use", "write", filePath, "\u6587\u4EF6\u5199\u5165\u5BA1\u8BA1\uFF08ADD-7 \u81EA\u52A8\u5316\uFF09", planKeyword, "none", "", this.magicDir);
224
+ }
225
+ /**
226
+ * 从写入路径提取 planKeyword(plans/specs/reviews 目录下命名规范文件):
227
+ * xxx-plan-v1.md / xxx-add-route-v1.md / xxx-review-v1.md → xxx-{suffix}-vN
228
+ * 其余路径 → "unknown"(与拦截事件缺省语义一致)。
229
+ */
230
+ extractPlanKeyword(filePath) {
231
+ void this.projectDir;
232
+ const m = filePath.match(/([^/]+-(?:plan|add-route|review)-v\d+)\.md$/);
233
+ return m ? m[1] : "unknown";
234
+ }
235
+ };
236
+
237
+ // templates/core/governance/post-tool-router.ts
238
+ function extractOutputText(raw) {
239
+ const m1 = raw.match(/"output"\s*:\s*"([^"]*)"/);
240
+ const m2 = raw.match(/"content"\s*:\s*"([^"]*)"/);
241
+ return [m1?.[1] ?? "", m2?.[1] ?? ""].filter(Boolean).join(" ");
242
+ }
243
+ var PostToolRouter = class {
244
+ projectDir;
245
+ magicDir;
246
+ /** 输出聚合(qoder feedback JSON 模式收集;core 直出 stderr) */
247
+ lines = [];
248
+ /** Task 8.1 审计桥接: 文件写入事件面扩展(jsonl 主路径 → MCP 常驻消费落库) */
249
+ auditBridge;
250
+ constructor(projectDir, magicDir) {
251
+ this.projectDir = projectDir;
252
+ this.magicDir = magicDir;
253
+ this.auditBridge = new AuditBridge(projectDir, magicDir);
254
+ }
255
+ // ─────────────────────────── 扩展点 ───────────────────────────
256
+ /** §1 DPS 自动化跳过(core: false;claude: true——claude 版未实现哨兵自动建) */
257
+ shouldSkipDps() {
258
+ return false;
259
+ }
260
+ /** §2a 文档守卫文本(core: 章节完整) */
261
+ docGuardText(filePath) {
262
+ return `[ADD PostToolUse] ADD \u6587\u6863\u5DF2\u5199\u5165: ${filePath}\u3002\u8BF7\u786E\u4FDD\u7AE0\u8282\u5B8C\u6574\u3001\u53CC\u5411\u94FE\u63A5\u9F50\u5168\u3002
263
+ `;
264
+ }
265
+ /** §2 后置段(core: plan_track 自动触发 + devlog 提醒 + schema regen) */
266
+ postDocSections(filePath) {
267
+ if (/\/(specs|plans)\/.*add-route/.test(filePath)) {
268
+ let planName = basename(filePath).replace(/-add-route.*/, "").replace(/-plan-v\d*$/, "");
269
+ if (filePath.includes("add-route")) {
270
+ const planDir = dirname(filePath);
271
+ if (existsSync3(planDir)) {
272
+ try {
273
+ const planFile = readdirSync(planDir).find((f) => f.includes("-plan-v") && f.endsWith(".md"));
274
+ if (planFile) planName = planFile.replace(/\.md$/, "");
275
+ } catch {
276
+ }
277
+ }
278
+ }
279
+ if (planName !== "") {
280
+ this.emitLine(`[ADD PostToolUse] \u{1F4CA} \u81EA\u52A8\u540C\u6B65 PlanRecord: plan_track({ planName: "${planName}" }) \u2014 \u8BF7\u6267\u884C
281
+ `);
282
+ }
283
+ }
284
+ if (/add-route.*\.md$/.test(filePath)) {
285
+ if (existsSync3(filePath)) {
286
+ const content = readFileSync2(filePath, "utf-8");
287
+ const step8 = content.match(/## Step 8[\s\S]*?(?=^## |\Z)/m)?.[0] ?? "";
288
+ const unchecked = (step8.match(/^\- \[ \]/gm) || []).length;
289
+ const checked = (step8.match(/^\- \[x\]/gm) || []).length;
290
+ if (unchecked === 0 && checked > 0) {
291
+ this.emitLine("[ADD PostToolUse] \u26A0\uFE0F Step 8 \u5168\u90E8\u6536\u655B\u5B8C\u6210\uFF01\u8BF7\u5199 devlog\u65E5\u5FD7(\u8D70mcp) \u2192 \u66F4\u65B0 handoff\n");
292
+ }
293
+ }
294
+ }
295
+ if (/templates\/.*\.md$/.test(filePath)) {
296
+ const schemaFile = filePath.replace(/\.md$/, ".schema.json");
297
+ if (existsSync3(schemaFile)) {
298
+ const headings = (readFileSync2(filePath, "utf-8").match(/^## (.+)$/gm) ?? []).slice(0, 20);
299
+ if (headings.length > 0) {
300
+ this.emitLine(`[ADD PostToolUse] \u{1F504} \u6A21\u677F\u5DF2\u4FEE\u6539\uFF0C\u8BF7\u68C0\u67E5 ${schemaFile} \u662F\u5426\u9700\u66F4\u65B0\uFF08\xA7 sections\uFF09
301
+ `);
302
+ this.emitLine(`[ADD PostToolUse] \u6A21\u677F\u6807\u9898: ${headings.join(" ")}
303
+ `);
304
+ }
305
+ }
306
+ }
307
+ }
308
+ /** 输出通道(core: stderr 直出;qoder: 收集后 feedback JSON flush) */
309
+ collectJson() {
310
+ return false;
311
+ }
312
+ /** 输出一行(core: stderr 直出;qoder: 收集聚合) */
313
+ emitLine(text) {
314
+ if (this.collectJson()) {
315
+ this.lines.push(text.endsWith("\n") ? text.trimEnd() : text);
316
+ } else {
317
+ process.stderr.write(text);
318
+ }
319
+ }
320
+ /** 刷新收集的输出(qoder: hookSpecificOutput.feedback JSON——Qoder 文档实证 PostToolUse 专属字段) */
321
+ flushLines() {
322
+ if (this.collectJson() && this.lines.length > 0) {
323
+ process.stdout.write(
324
+ JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", feedback: this.lines.join("\n") } }) + "\n"
325
+ );
326
+ }
327
+ }
328
+ /** 主路由:返回 exit code(0) */
329
+ run(input2, toolName2) {
330
+ if (!this.shouldSkipDps() && input2.includes('"check_dps"')) {
331
+ const toolOutput = extractOutputText(input2);
332
+ const dpsScore = toolOutput.match(/DPS\s*=\s*(\d+)/)?.[1] ?? "";
333
+ const planKeyword = input2.match(/"planKeyword"\s*:\s*"([^"]+)"/)?.[1] ?? "";
334
+ if (dpsScore !== "" && planKeyword !== "") {
335
+ const score = Number(dpsScore);
336
+ if (!Number.isNaN(score) && score >= 80) {
337
+ const sentinel = join2(this.projectDir, this.magicDir, "hitl", `.tongyi-${planKeyword}`);
338
+ if (!existsSync3(sentinel)) {
339
+ try {
340
+ writeFileSync2(sentinel, "");
341
+ this.emitLine(`[ADD PostToolUse] \u2705 DPS=${dpsScore} \u226580, HITL \u81EA\u52A8\u901A\u8FC7 \u2192 ${sentinel}
342
+ `);
343
+ } catch {
344
+ }
345
+ this.emitLine(`[ADD PostToolUse] DPS=${dpsScore} \u226580, \u5DF2\u81EA\u52A8\u5EFA\u54E8\u5175 ${sentinel}
346
+ `);
347
+ } else {
348
+ this.emitLine(`[ADD PostToolUse] DPS=${dpsScore} \u226580, \u54E8\u5175\u5DF2\u5B58\u5728 \u2192 ${sentinel}
349
+ `);
350
+ }
351
+ } else {
352
+ this.emitLine(`[ADD PostToolUse] \u26A0\uFE0F DPS=${dpsScore} <80, \u9700 Review \u540E\u624B\u52A8\u5EFA\u54E8\u5175 .tongyi-${planKeyword}
353
+ `);
354
+ this.emitLine("[ADD PostToolUse] \u4FEE\u590D\u6587\u6863\u540E\u91CD\u65B0 check_dps\uFF0C\u901A\u8FC7\u5373\u81EA\u52A8\u653E\u884C\u3002\n");
355
+ }
356
+ }
357
+ }
358
+ if (this.isFileTool(toolName2)) {
359
+ const filePath = this.extractFilePath(input2);
360
+ if (filePath === "") return 0;
361
+ if (/\.(qoder|claude|add)\/(plans|specs|reviews)\//.test(filePath)) {
362
+ this.emitLine(this.docGuardText(filePath));
363
+ }
364
+ this.postDocSections(filePath);
365
+ this.emitLine(this.emitAuditReminder(filePath));
366
+ this.emitWriteEvent(filePath);
367
+ } else if (toolName2 === "Bash") {
368
+ this.emitLine(this.emitBashDone());
369
+ } else {
370
+ this.applyPatchMatcher(input2);
371
+ }
372
+ this.flushLines();
373
+ return 0;
374
+ }
375
+ // ─────────────────────────── 扩展点(续)───────────────────────────
376
+ /** 文件写工具判定(core: Edit/Write;codex 子类: +SearchReplace + apply_patch 走附加 matcher) */
377
+ isFileTool(toolName2) {
378
+ return toolName2 === "Edit" || toolName2 === "Write";
379
+ }
380
+ /** filePath 提取(core: file_path 字段;codex 子类: file_path ?? path) */
381
+ extractFilePath(input2) {
382
+ return jsonGet(input2, "file_path");
383
+ }
384
+ /** §2e 审计提醒(core: 含 ADD-7;codex 子类: 简化文本) */
385
+ emitAuditReminder(filePath) {
386
+ return `[ADD PostToolUse] \u6587\u4EF6\u5DF2\u5199\u5165: ${filePath}\u3002\u8BF7\u6267\u884C record_dev_operation \u843D\u5E93\u5BA1\u8BA1\uFF08ADD-7\uFF09\u3002
387
+ `;
388
+ }
389
+ /** §2f 审计桥接触发(Task 8.1,扩展点: 默认 AuditBridge.emit;
390
+ * 如有端需关闭/改造事件面在此 override——本轮 5 端全接入,无关闭端) */
391
+ emitWriteEvent(filePath) {
392
+ this.auditBridge.emit(filePath);
393
+ }
394
+ /** §3 Bash 增强(core: lint/tsc;codex 子类: lint/typecheck/test) */
395
+ emitBashDone() {
396
+ return "[ADD PostToolUse] \u547D\u4EE4\u6267\u884C\u5B8C\u6210\u3002\u5982\u6709 lint/tsc \u9519\u8BEF\u8BF7\u4FEE\u590D\u3002\n";
397
+ }
398
+ /** 附加 matcher(core: 无;codex 子类: apply_patch 解析 `*** Add|Update|Delete File:` 逐文件 report) */
399
+ applyPatchMatcher(_input) {
400
+ }
401
+ };
402
+
403
+ // templates/adapters/trae/hooks/post-tool-use.ts
404
+ var TraePostToolRouter = class extends PostToolRouter {
405
+ /** 协议差异封装: MAGIC_DIR 兜底 = ".trae"(原手写版复制 qoder 时误用 ".qoder" 兜底,2026-08-14 修正) */
406
+ constructor(projectDir) {
407
+ super(projectDir, process.env.MAGIC_DIR || ".trae");
408
+ }
409
+ // 当前无 override(段落能力与 core 基线一致);命名子类承载端身份 + 未来演进位
410
+ };
411
+ var PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
412
+ var input = readHookInput();
413
+ var toolName = jsonGet(input, "tool_name");
414
+ if (toolName === "") process.exit(0);
415
+ process.exit(new TraePostToolRouter(PROJECT_DIR).run(input, toolName));
@@ -0,0 +1,224 @@
1
+ // templates/core/governance/pre-compact-guard.ts
2
+ import { writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "node:fs";
3
+
4
+ // templates/core/governance/common.ts
5
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
6
+ import { basename, dirname, join } from "node:path";
7
+ import { spawnSync } from "node:child_process";
8
+ import { createHash } from "node:crypto";
9
+
10
+ // templates/core/governance/rules.ts
11
+ var protocol = {
12
+ "exit_codes": {
13
+ "pass": 0,
14
+ "block": 2,
15
+ "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"
16
+ },
17
+ "output": {
18
+ "stdout_json_only": true,
19
+ "stderr_human_text": true,
20
+ "field_separator": "::",
21
+ "magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
22
+ },
23
+ "adapters": {
24
+ "claude": {
25
+ "stdout_form": "plain-text",
26
+ "project_dir_env": "CLAUDE_PROJECT_DIR",
27
+ "magic_dir": ".claude",
28
+ "handlerTypes": [
29
+ "command",
30
+ "mcp_tool"
31
+ ]
32
+ },
33
+ "qoder": {
34
+ "stdout_form": "json",
35
+ "project_dir_env": "QODER_PROJECT_DIR",
36
+ "magic_dir": ".qoder",
37
+ "handlerTypes": [
38
+ "command",
39
+ "http"
40
+ ]
41
+ },
42
+ "codex": {
43
+ "stdout_form": "systemMessage",
44
+ "project_dir_env": "git-toplevel",
45
+ "magic_dir": ".codex",
46
+ "handlerTypes": [
47
+ "command"
48
+ ]
49
+ },
50
+ "vscode": {
51
+ "stdout_form": "plain-text",
52
+ "project_dir_env": "PWD",
53
+ "magic_dir": ".vscode",
54
+ "handlerTypes": [
55
+ "command"
56
+ ]
57
+ },
58
+ "trae": {
59
+ "stdout_form": "plain-text",
60
+ "project_dir_env": "PWD",
61
+ "magic_dir": ".trae",
62
+ "handlerTypes": [
63
+ "command"
64
+ ]
65
+ }
66
+ },
67
+ "event_outputs": {
68
+ "qoder": {
69
+ "SessionStart": "additionalContext",
70
+ "UserPromptSubmit": "additionalContext",
71
+ "PreToolUse": "permissionDecision",
72
+ "Stop": "additionalContext",
73
+ "PostToolUse": "feedback",
74
+ "SubagentStart": "additionalContext",
75
+ "SubagentStop": "additionalContext",
76
+ "PostToolUseFailure": "text",
77
+ "PermissionRequest": "text",
78
+ "SessionEnd": "text",
79
+ "PreCompact": "text",
80
+ "Notification": "text"
81
+ },
82
+ "claude": {
83
+ "SessionStart": "additionalContext",
84
+ "UserPromptSubmit": "additionalContext",
85
+ "PreToolUse": "permissionDecision",
86
+ "Stop": "text",
87
+ "PostToolUse": "feedback",
88
+ "SubagentStart": "text",
89
+ "SubagentStop": "text",
90
+ "PostToolUseFailure": "text",
91
+ "PermissionRequest": "text",
92
+ "SessionEnd": "text",
93
+ "PreCompact": "text",
94
+ "Notification": "text"
95
+ },
96
+ "codex": {
97
+ "Stop": "systemMessage",
98
+ "PostToolUse": "text"
99
+ },
100
+ "vscode": {
101
+ "PostToolUse": "text"
102
+ },
103
+ "trae": {
104
+ "PostToolUse": "text"
105
+ }
106
+ },
107
+ "core": {
108
+ "stdout_form": "json",
109
+ "magic_dir": ".add",
110
+ "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"
111
+ },
112
+ "adapter_defaults": {
113
+ "magic_dir_fallback": ".qoder",
114
+ "probe_magic_dirs": [
115
+ ".claude",
116
+ ".qoder",
117
+ ".vscode",
118
+ ".add",
119
+ ".trae",
120
+ ".codex"
121
+ ]
122
+ }
123
+ };
124
+
125
+ // templates/core/governance/common.ts
126
+ var EXIT_PASS = protocol.exit_codes.pass;
127
+ var EXIT_BLOCK = protocol.exit_codes.block;
128
+ var STATE_SEP = protocol.output.field_separator;
129
+ function queryPlanStatus() {
130
+ const magicDir = process.env.MAGIC_DIR;
131
+ if (!magicDir) {
132
+ return {
133
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"magicDir \u672A\u6CE8\u5165\u4E14\u65E0\u6CD5\u4ECE\u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC"}',
134
+ exitCode: 3
135
+ };
136
+ }
137
+ const bridge = join(
138
+ process.env.PROJECT_DIR || process.cwd(),
139
+ magicDir,
140
+ "scripts",
141
+ "plan-status-bridge.ts"
142
+ );
143
+ if (!existsSync(bridge)) {
144
+ return {
145
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"plan-status bridge missing"}',
146
+ exitCode: 3
147
+ };
148
+ }
149
+ const r = spawnSync("node", ["--import", "tsx", bridge], {
150
+ encoding: "utf-8",
151
+ timeout: 1e4
152
+ });
153
+ return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
154
+ }
155
+ function detectActiveAdd() {
156
+ const r = queryPlanStatus();
157
+ if (r.exitCode !== 0) {
158
+ let reason = "database status unavailable";
159
+ try {
160
+ const parsed = JSON.parse(r.stdout);
161
+ if (parsed.reason) reason = parsed.reason;
162
+ } catch {
163
+ }
164
+ return `__STATUS_UNAVAILABLE__::${reason}::database::none::none`;
165
+ }
166
+ let snapshot;
167
+ try {
168
+ snapshot = JSON.parse(r.stdout);
169
+ } catch {
170
+ return null;
171
+ }
172
+ if (!(snapshot.availability === "READY" && snapshot.isActive === true)) return null;
173
+ const done = snapshot.progress?.doneTasks ?? 0;
174
+ const total = snapshot.progress?.totalTasks ?? 0;
175
+ const approval = snapshot.approvalStatus ?? "none";
176
+ return `${snapshot.planName}::${done}/${total}::${approval}::none::none`;
177
+ }
178
+ function projectHash() {
179
+ try {
180
+ return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
181
+ `).digest("hex").slice(0, 8);
182
+ } catch {
183
+ return "default";
184
+ }
185
+ }
186
+ var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
187
+
188
+ // templates/core/governance/pre-compact-guard.ts
189
+ var PreCompactGuard = class {
190
+ /** 主入口:返回 exit code(0) */
191
+ run() {
192
+ const hash = projectHash();
193
+ const state = detectActiveAdd();
194
+ if (state !== null) {
195
+ const [plan, step, rounds, handoff, addRoute] = state.split("::");
196
+ try {
197
+ writeFileSync2(
198
+ `/tmp/add_recovery_${hash}`,
199
+ `plan=${plan}
200
+ step=${step}
201
+ rounds=${rounds}
202
+ handoff=${handoff}
203
+ add_route=${addRoute}
204
+ `
205
+ );
206
+ } catch {
207
+ }
208
+ process.stderr.write(`[ADD PreCompact] ADD \u72B6\u6001\u5DF2\u4FDD\u5B58: Plan=${plan}, Step=${step}
209
+ `);
210
+ }
211
+ try {
212
+ unlinkSync2(`/tmp/add_tpl_${hash}`);
213
+ } catch {
214
+ }
215
+ return 0;
216
+ }
217
+ };
218
+
219
+ // templates/adapters/trae/hooks/pre-compact.ts
220
+ var TraePreCompactGuard = class extends PreCompactGuard {
221
+ // 当前无 override(状态保存/恢复清单/tpl 清理与 core 基线一致);命名子类承载端身份 + 未来演进位
222
+ };
223
+ process.env.PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
224
+ process.exit(new TraePreCompactGuard().run());