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,575 @@
1
+ // templates/adapters/codex/hooks/post-tool-use.ts
2
+ import { execSync } from "node:child_process";
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 { fileURLToPath as fileURLToPath3 } from "node:url";
8
+ import { createHash } from "node:crypto";
9
+
10
+ // templates/core/governance/rules.ts
11
+ var event = {
12
+ "file": {
13
+ "path": "{magicDir}/reports/hook-events.jsonl",
14
+ "rotate_bytes": 262144,
15
+ "total_bytes": 524288,
16
+ "note": "MCP Server \u5B95\u673A\u4E0D\u4E22\u4E8B\u4EF6\uFF0C\u91CD\u542F\u540E\u4ECE\u6587\u4EF6\u6062\u590D\u6D88\u8D39"
17
+ },
18
+ "schema": {
19
+ "fields": [
20
+ "ts",
21
+ "hook",
22
+ "decision",
23
+ "cmd",
24
+ "reason",
25
+ "planKeyword",
26
+ "planStatus"
27
+ ],
28
+ "ts_format": "date -u +%Y-%m-%dT%H:%M:%SZ",
29
+ "extra_fields": [
30
+ "anchor_hit",
31
+ "struct_score",
32
+ "override"
33
+ ]
34
+ },
35
+ "daily": {
36
+ "warn_threshold": 10
37
+ }
38
+ };
39
+ var protocol = {
40
+ "exit_codes": {
41
+ "pass": 0,
42
+ "block": 2,
43
+ "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"
44
+ },
45
+ "output": {
46
+ "stdout_json_only": true,
47
+ "stderr_human_text": true,
48
+ "field_separator": "::",
49
+ "magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
50
+ },
51
+ "adapters": {
52
+ "claude": {
53
+ "stdout_form": "plain-text",
54
+ "project_dir_env": "CLAUDE_PROJECT_DIR",
55
+ "magic_dir": ".claude",
56
+ "handlerTypes": [
57
+ "command",
58
+ "mcp_tool"
59
+ ]
60
+ },
61
+ "qoder": {
62
+ "stdout_form": "json",
63
+ "project_dir_env": "QODER_PROJECT_DIR",
64
+ "magic_dir": ".qoder",
65
+ "handlerTypes": [
66
+ "command",
67
+ "http"
68
+ ]
69
+ },
70
+ "codex": {
71
+ "stdout_form": "systemMessage",
72
+ "project_dir_env": "git-toplevel",
73
+ "magic_dir": ".codex",
74
+ "handlerTypes": [
75
+ "command"
76
+ ]
77
+ },
78
+ "vscode": {
79
+ "stdout_form": "plain-text",
80
+ "project_dir_env": "PWD",
81
+ "magic_dir": ".vscode",
82
+ "handlerTypes": [
83
+ "command"
84
+ ]
85
+ },
86
+ "trae": {
87
+ "stdout_form": "plain-text",
88
+ "project_dir_env": "PWD",
89
+ "magic_dir": ".trae",
90
+ "handlerTypes": [
91
+ "command"
92
+ ]
93
+ }
94
+ },
95
+ "event_outputs": {
96
+ "qoder": {
97
+ "SessionStart": "additionalContext",
98
+ "UserPromptSubmit": "additionalContext",
99
+ "PreToolUse": "permissionDecision",
100
+ "Stop": "additionalContext",
101
+ "PostToolUse": "feedback",
102
+ "SubagentStart": "additionalContext",
103
+ "SubagentStop": "additionalContext",
104
+ "PostToolUseFailure": "text",
105
+ "PermissionRequest": "text",
106
+ "SessionEnd": "text",
107
+ "PreCompact": "text",
108
+ "Notification": "text"
109
+ },
110
+ "claude": {
111
+ "SessionStart": "additionalContext",
112
+ "UserPromptSubmit": "additionalContext",
113
+ "PreToolUse": "permissionDecision",
114
+ "Stop": "text",
115
+ "PostToolUse": "feedback",
116
+ "SubagentStart": "text",
117
+ "SubagentStop": "text",
118
+ "PostToolUseFailure": "text",
119
+ "PermissionRequest": "text",
120
+ "SessionEnd": "text",
121
+ "PreCompact": "text",
122
+ "Notification": "text"
123
+ },
124
+ "codex": {
125
+ "Stop": "systemMessage",
126
+ "PostToolUse": "text"
127
+ },
128
+ "vscode": {
129
+ "PostToolUse": "text"
130
+ },
131
+ "trae": {
132
+ "PostToolUse": "text"
133
+ }
134
+ },
135
+ "core": {
136
+ "stdout_form": "json",
137
+ "magic_dir": ".add",
138
+ "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"
139
+ },
140
+ "adapter_defaults": {
141
+ "magic_dir_fallback": ".qoder",
142
+ "probe_magic_dirs": [
143
+ ".claude",
144
+ ".qoder",
145
+ ".vscode",
146
+ ".add",
147
+ ".trae",
148
+ ".codex"
149
+ ]
150
+ }
151
+ };
152
+
153
+ // node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
154
+ import path2 from "node:path";
155
+
156
+ // node_modules/.pnpm/locate-path@8.0.0/node_modules/locate-path/index.js
157
+ import process2 from "node:process";
158
+ import path from "node:path";
159
+ import fs, { promises as fsPromises } from "node:fs";
160
+ import { fileURLToPath } from "node:url";
161
+ var typeMappings = {
162
+ directory: "isDirectory",
163
+ file: "isFile"
164
+ };
165
+ function checkType(type) {
166
+ if (type === "both" || Object.hasOwn(typeMappings, type)) {
167
+ return;
168
+ }
169
+ throw new Error(`Invalid type specified: ${type}`);
170
+ }
171
+ var matchType = (type, stat) => type === "both" ? stat.isFile() || stat.isDirectory() : stat[typeMappings[type]]();
172
+ var toPath = (urlOrPath) => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
173
+ function locatePathSync(paths, {
174
+ cwd = process2.cwd(),
175
+ type = "file",
176
+ allowSymlinks = true
177
+ } = {}) {
178
+ checkType(type);
179
+ cwd = toPath(cwd);
180
+ const statFunction = allowSymlinks ? fs.statSync : fs.lstatSync;
181
+ for (const path_ of paths) {
182
+ try {
183
+ const stat = statFunction(path.resolve(cwd, path_), {
184
+ throwIfNoEntry: false
185
+ });
186
+ if (!stat) {
187
+ continue;
188
+ }
189
+ if (matchType(type, stat)) {
190
+ return path_;
191
+ }
192
+ } catch {
193
+ }
194
+ }
195
+ }
196
+
197
+ // node_modules/.pnpm/unicorn-magic@0.3.0/node_modules/unicorn-magic/node.js
198
+ import { promisify } from "node:util";
199
+ import { execFile as execFileCallback, execFileSync as execFileSyncOriginal } from "node:child_process";
200
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
201
+ var execFileOriginal = promisify(execFileCallback);
202
+ function toPath2(urlOrPath) {
203
+ return urlOrPath instanceof URL ? fileURLToPath2(urlOrPath) : urlOrPath;
204
+ }
205
+ var TEN_MEGABYTES_IN_BYTES = 10 * 1024 * 1024;
206
+
207
+ // node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
208
+ var findUpStop = /* @__PURE__ */ Symbol("findUpStop");
209
+ function findUpMultipleSync(name, options = {}) {
210
+ let directory = path2.resolve(toPath2(options.cwd) ?? "");
211
+ const { root } = path2.parse(directory);
212
+ const stopAt = path2.resolve(directory, toPath2(options.stopAt) ?? root);
213
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
214
+ const paths = [name].flat();
215
+ const runMatcher = (locateOptions) => {
216
+ if (typeof name !== "function") {
217
+ return locatePathSync(paths, locateOptions);
218
+ }
219
+ const foundPath = name(locateOptions.cwd);
220
+ if (typeof foundPath === "string") {
221
+ return locatePathSync([foundPath], locateOptions);
222
+ }
223
+ return foundPath;
224
+ };
225
+ const matches = [];
226
+ while (true) {
227
+ const foundPath = runMatcher({ ...options, cwd: directory });
228
+ if (foundPath === findUpStop) {
229
+ break;
230
+ }
231
+ if (foundPath) {
232
+ matches.push(path2.resolve(directory, foundPath));
233
+ }
234
+ if (directory === stopAt || matches.length >= limit) {
235
+ break;
236
+ }
237
+ directory = path2.dirname(directory);
238
+ }
239
+ return matches;
240
+ }
241
+ function findUpSync(name, options = {}) {
242
+ const matches = findUpMultipleSync(name, { ...options, limit: 1 });
243
+ return matches[0];
244
+ }
245
+
246
+ // templates/core/governance/common.ts
247
+ var EXIT_PASS = protocol.exit_codes.pass;
248
+ var EXIT_BLOCK = protocol.exit_codes.block;
249
+ var STATE_SEP = protocol.output.field_separator;
250
+ function readHookInput() {
251
+ if (process.stdin.isTTY) return "{}";
252
+ return readFileSync(0, "utf-8");
253
+ }
254
+ function jsonGet(json, field) {
255
+ const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`);
256
+ const m = re.exec(json);
257
+ return m?.[1] ?? "";
258
+ }
259
+ function tryResolveMagicDir() {
260
+ const injected = process.env.MAGIC_DIR;
261
+ if (injected) return injected;
262
+ const startDir = dirname(fileURLToPath3(import.meta.url));
263
+ const hit = findUpSync((dir) => basename(dir).startsWith(".") ? dir : void 0, {
264
+ cwd: startDir,
265
+ type: "directory"
266
+ });
267
+ return hit ? basename(hit) : "";
268
+ }
269
+ function projectHash() {
270
+ try {
271
+ return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
272
+ `).digest("hex").slice(0, 8);
273
+ } catch {
274
+ return "default";
275
+ }
276
+ }
277
+ var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
278
+
279
+ // templates/core/governance/post-tool-router.ts
280
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, writeFileSync as writeFileSync2 } from "node:fs";
281
+ import { join as join3, basename as basename2, dirname as dirname2 } from "node:path";
282
+
283
+ // templates/core/governance/notify.ts
284
+ import { existsSync as existsSync2, mkdirSync, renameSync, statSync, appendFileSync } from "node:fs";
285
+ import { join as join2 } from "node:path";
286
+ function writeHookEvent(hook, decision, cmd, reason, plan = "unknown", status = "none", extra = "", magicDirOverride) {
287
+ const defaults = protocol.adapter_defaults;
288
+ const fallback = defaults?.magic_dir_fallback ?? ".qoder";
289
+ const dir = join2(magicDirOverride ?? (process.env.MAGIC_DIR || fallback), "reports");
290
+ try {
291
+ mkdirSync(dir, { recursive: true });
292
+ } catch {
293
+ }
294
+ const file = join2(dir, "hook-events.jsonl");
295
+ if (existsSync2(file)) {
296
+ let sz = 0;
297
+ try {
298
+ sz = statSync(file).size;
299
+ } catch {
300
+ sz = 0;
301
+ }
302
+ if (sz > event.file.rotate_bytes) {
303
+ try {
304
+ renameSync(file, `${file}.old`);
305
+ } catch {
306
+ }
307
+ }
308
+ }
309
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
310
+ const extraPart = extra ? `,${extra}` : "";
311
+ const line = `{"ts":"${ts}","hook":"${hook}","decision":"${decision}","cmd":"${cmd}","reason":"${reason}","planKeyword":"${plan}","planStatus":"${status}"${extraPart}}
312
+ `;
313
+ appendFileSync(file, line);
314
+ }
315
+
316
+ // templates/core/governance/audit-bridge.ts
317
+ var AuditBridge = class {
318
+ projectDir;
319
+ magicDir;
320
+ constructor(projectDir, magicDir) {
321
+ this.projectDir = projectDir;
322
+ this.magicDir = magicDir;
323
+ }
324
+ /**
325
+ * 文件写入审计事件(decision="write")。
326
+ * @param filePath 被写入文件路径(绝对/相对均可,MCP 消费端以 cmd 为 targetId)
327
+ */
328
+ emit(filePath) {
329
+ if (!filePath || filePath === "") return;
330
+ const planKeyword = this.extractPlanKeyword(filePath);
331
+ writeHookEvent("post-tool-use", "write", filePath, "\u6587\u4EF6\u5199\u5165\u5BA1\u8BA1\uFF08ADD-7 \u81EA\u52A8\u5316\uFF09", planKeyword, "none", "", this.magicDir);
332
+ }
333
+ /**
334
+ * 从写入路径提取 planKeyword(plans/specs/reviews 目录下命名规范文件):
335
+ * xxx-plan-v1.md / xxx-add-route-v1.md / xxx-review-v1.md → xxx-{suffix}-vN
336
+ * 其余路径 → "unknown"(与拦截事件缺省语义一致)。
337
+ */
338
+ extractPlanKeyword(filePath) {
339
+ void this.projectDir;
340
+ const m = filePath.match(/([^/]+-(?:plan|add-route|review)-v\d+)\.md$/);
341
+ return m ? m[1] : "unknown";
342
+ }
343
+ };
344
+
345
+ // templates/core/governance/post-tool-router.ts
346
+ function extractOutputText(raw) {
347
+ const m1 = raw.match(/"output"\s*:\s*"([^"]*)"/);
348
+ const m2 = raw.match(/"content"\s*:\s*"([^"]*)"/);
349
+ return [m1?.[1] ?? "", m2?.[1] ?? ""].filter(Boolean).join(" ");
350
+ }
351
+ var PostToolRouter = class {
352
+ projectDir;
353
+ magicDir;
354
+ /** 输出聚合(qoder feedback JSON 模式收集;core 直出 stderr) */
355
+ lines = [];
356
+ /** Task 8.1 审计桥接: 文件写入事件面扩展(jsonl 主路径 → MCP 常驻消费落库) */
357
+ auditBridge;
358
+ constructor(projectDir, magicDir) {
359
+ this.projectDir = projectDir;
360
+ this.magicDir = magicDir;
361
+ this.auditBridge = new AuditBridge(projectDir, magicDir);
362
+ }
363
+ // ─────────────────────────── 扩展点 ───────────────────────────
364
+ /** §1 DPS 自动化跳过(core: false;claude: true——claude 版未实现哨兵自动建) */
365
+ shouldSkipDps() {
366
+ return false;
367
+ }
368
+ /** §2a 文档守卫文本(core: 章节完整) */
369
+ docGuardText(filePath) {
370
+ 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
371
+ `;
372
+ }
373
+ /** §2 后置段(core: plan_track 自动触发 + devlog 提醒 + schema regen) */
374
+ postDocSections(filePath) {
375
+ if (/\/(specs|plans)\/.*add-route/.test(filePath)) {
376
+ let planName = basename2(filePath).replace(/-add-route.*/, "").replace(/-plan-v\d*$/, "");
377
+ if (filePath.includes("add-route")) {
378
+ const planDir = dirname2(filePath);
379
+ if (existsSync3(planDir)) {
380
+ try {
381
+ const planFile = readdirSync(planDir).find((f) => f.includes("-plan-v") && f.endsWith(".md"));
382
+ if (planFile) planName = planFile.replace(/\.md$/, "");
383
+ } catch {
384
+ }
385
+ }
386
+ }
387
+ if (planName !== "") {
388
+ this.emitLine(`[ADD PostToolUse] \u{1F4CA} \u81EA\u52A8\u540C\u6B65 PlanRecord: plan_track({ planName: "${planName}" }) \u2014 \u8BF7\u6267\u884C
389
+ `);
390
+ }
391
+ }
392
+ if (/add-route.*\.md$/.test(filePath)) {
393
+ if (existsSync3(filePath)) {
394
+ const content = readFileSync2(filePath, "utf-8");
395
+ const step8 = content.match(/## Step 8[\s\S]*?(?=^## |\Z)/m)?.[0] ?? "";
396
+ const unchecked = (step8.match(/^\- \[ \]/gm) || []).length;
397
+ const checked = (step8.match(/^\- \[x\]/gm) || []).length;
398
+ if (unchecked === 0 && checked > 0) {
399
+ 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");
400
+ }
401
+ }
402
+ }
403
+ if (/templates\/.*\.md$/.test(filePath)) {
404
+ const schemaFile = filePath.replace(/\.md$/, ".schema.json");
405
+ if (existsSync3(schemaFile)) {
406
+ const headings = (readFileSync2(filePath, "utf-8").match(/^## (.+)$/gm) ?? []).slice(0, 20);
407
+ if (headings.length > 0) {
408
+ 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
409
+ `);
410
+ this.emitLine(`[ADD PostToolUse] \u6A21\u677F\u6807\u9898: ${headings.join(" ")}
411
+ `);
412
+ }
413
+ }
414
+ }
415
+ }
416
+ /** 输出通道(core: stderr 直出;qoder: 收集后 feedback JSON flush) */
417
+ collectJson() {
418
+ return false;
419
+ }
420
+ /** 输出一行(core: stderr 直出;qoder: 收集聚合) */
421
+ emitLine(text) {
422
+ if (this.collectJson()) {
423
+ this.lines.push(text.endsWith("\n") ? text.trimEnd() : text);
424
+ } else {
425
+ process.stderr.write(text);
426
+ }
427
+ }
428
+ /** 刷新收集的输出(qoder: hookSpecificOutput.feedback JSON——Qoder 文档实证 PostToolUse 专属字段) */
429
+ flushLines() {
430
+ if (this.collectJson() && this.lines.length > 0) {
431
+ process.stdout.write(
432
+ JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", feedback: this.lines.join("\n") } }) + "\n"
433
+ );
434
+ }
435
+ }
436
+ /** 主路由:返回 exit code(0) */
437
+ run(input2, toolName2) {
438
+ if (!this.shouldSkipDps() && input2.includes('"check_dps"')) {
439
+ const toolOutput = extractOutputText(input2);
440
+ const dpsScore = toolOutput.match(/DPS\s*=\s*(\d+)/)?.[1] ?? "";
441
+ const planKeyword = input2.match(/"planKeyword"\s*:\s*"([^"]+)"/)?.[1] ?? "";
442
+ if (dpsScore !== "" && planKeyword !== "") {
443
+ const score = Number(dpsScore);
444
+ if (!Number.isNaN(score) && score >= 80) {
445
+ const sentinel = join3(this.projectDir, this.magicDir, "hitl", `.tongyi-${planKeyword}`);
446
+ if (!existsSync3(sentinel)) {
447
+ try {
448
+ writeFileSync2(sentinel, "");
449
+ this.emitLine(`[ADD PostToolUse] \u2705 DPS=${dpsScore} \u226580, HITL \u81EA\u52A8\u901A\u8FC7 \u2192 ${sentinel}
450
+ `);
451
+ } catch {
452
+ }
453
+ this.emitLine(`[ADD PostToolUse] DPS=${dpsScore} \u226580, \u5DF2\u81EA\u52A8\u5EFA\u54E8\u5175 ${sentinel}
454
+ `);
455
+ } else {
456
+ this.emitLine(`[ADD PostToolUse] DPS=${dpsScore} \u226580, \u54E8\u5175\u5DF2\u5B58\u5728 \u2192 ${sentinel}
457
+ `);
458
+ }
459
+ } else {
460
+ this.emitLine(`[ADD PostToolUse] \u26A0\uFE0F DPS=${dpsScore} <80, \u9700 Review \u540E\u624B\u52A8\u5EFA\u54E8\u5175 .tongyi-${planKeyword}
461
+ `);
462
+ this.emitLine("[ADD PostToolUse] \u4FEE\u590D\u6587\u6863\u540E\u91CD\u65B0 check_dps\uFF0C\u901A\u8FC7\u5373\u81EA\u52A8\u653E\u884C\u3002\n");
463
+ }
464
+ }
465
+ }
466
+ if (this.isFileTool(toolName2)) {
467
+ const filePath = this.extractFilePath(input2);
468
+ if (filePath === "") return 0;
469
+ if (/\.(qoder|claude|add)\/(plans|specs|reviews)\//.test(filePath)) {
470
+ this.emitLine(this.docGuardText(filePath));
471
+ }
472
+ this.postDocSections(filePath);
473
+ this.emitLine(this.emitAuditReminder(filePath));
474
+ this.emitWriteEvent(filePath);
475
+ } else if (toolName2 === "Bash") {
476
+ this.emitLine(this.emitBashDone());
477
+ } else {
478
+ this.applyPatchMatcher(input2);
479
+ }
480
+ this.flushLines();
481
+ return 0;
482
+ }
483
+ // ─────────────────────────── 扩展点(续)───────────────────────────
484
+ /** 文件写工具判定(core: Edit/Write;codex 子类: +SearchReplace + apply_patch 走附加 matcher) */
485
+ isFileTool(toolName2) {
486
+ return toolName2 === "Edit" || toolName2 === "Write";
487
+ }
488
+ /** filePath 提取(core: file_path 字段;codex 子类: file_path ?? path) */
489
+ extractFilePath(input2) {
490
+ return jsonGet(input2, "file_path");
491
+ }
492
+ /** §2e 审计提醒(core: 含 ADD-7;codex 子类: 简化文本) */
493
+ emitAuditReminder(filePath) {
494
+ return `[ADD PostToolUse] \u6587\u4EF6\u5DF2\u5199\u5165: ${filePath}\u3002\u8BF7\u6267\u884C record_dev_operation \u843D\u5E93\u5BA1\u8BA1\uFF08ADD-7\uFF09\u3002
495
+ `;
496
+ }
497
+ /** §2f 审计桥接触发(Task 8.1,扩展点: 默认 AuditBridge.emit;
498
+ * 如有端需关闭/改造事件面在此 override——本轮 5 端全接入,无关闭端) */
499
+ emitWriteEvent(filePath) {
500
+ this.auditBridge.emit(filePath);
501
+ }
502
+ /** §3 Bash 增强(core: lint/tsc;codex 子类: lint/typecheck/test) */
503
+ emitBashDone() {
504
+ return "[ADD PostToolUse] \u547D\u4EE4\u6267\u884C\u5B8C\u6210\u3002\u5982\u6709 lint/tsc \u9519\u8BEF\u8BF7\u4FEE\u590D\u3002\n";
505
+ }
506
+ /** 附加 matcher(core: 无;codex 子类: apply_patch 解析 `*** Add|Update|Delete File:` 逐文件 report) */
507
+ applyPatchMatcher(_input) {
508
+ }
509
+ };
510
+
511
+ // templates/adapters/codex/hooks/post-tool-use.ts
512
+ var CodexPostToolRouter = class extends PostToolRouter {
513
+ /** ① codex 文件写工具含 SearchReplace */
514
+ isFileTool(toolName2) {
515
+ return toolName2 === "Edit" || toolName2 === "Write" || toolName2 === "SearchReplace";
516
+ }
517
+ /** ② file_path ?? path(codex 工具输入形态) */
518
+ extractFilePath(input2) {
519
+ const fp = jsonGet(input2, "file_path");
520
+ if (fp !== "") return fp;
521
+ return jsonGet(input2, "path");
522
+ }
523
+ /** ③ 审计提醒(codex bash 原文逐字——无 ADD-7 字样) */
524
+ emitAuditReminder(filePath) {
525
+ return `[ADD PostToolUse] \u6587\u4EF6\u5DF2\u5199\u5165: ${filePath}\uFF1B\u8BF7\u6267\u884C record_dev_operation \u843D\u5E93\u5BA1\u8BA1\u3002
526
+ `;
527
+ }
528
+ /** ④ Bash 增强(codex bash 原文逐字——lint/typecheck/test 闭环) */
529
+ emitBashDone() {
530
+ return "[ADD PostToolUse] Bash \u547D\u4EE4\u5B8C\u6210\uFF1B\u82E5\u4EA7\u751F lint/typecheck/test \u9519\u8BEF\uFF0C\u8BF7\u5728\u672C\u8F6E\u95ED\u73AF\u3002\n";
531
+ }
532
+ /** ⑤ apply_patch: 解析 `*** Add|Update|Delete File:` 行逐文件 report(codex 特有) */
533
+ applyPatchMatcher(input2) {
534
+ let patchCommand = "";
535
+ if (input2.trim() !== "") {
536
+ try {
537
+ const parsed = JSON.parse(input2);
538
+ patchCommand = typeof parsed.tool_input?.command === "string" ? parsed.tool_input.command : "";
539
+ } catch {
540
+ patchCommand = "";
541
+ }
542
+ }
543
+ const patchPaths = [];
544
+ for (const line of patchCommand.split("\n")) {
545
+ const m = line.match(/^\*\*\* (Add|Update|Delete) File: (.+)$/);
546
+ if (m) patchPaths.push(m[2]);
547
+ }
548
+ for (const p of patchPaths) this.reportFile(p);
549
+ }
550
+ /** 对齐 bash _report_file:ADD 文档守卫 + 审计提醒(codex 版) */
551
+ reportFile(filePath) {
552
+ if (filePath === "") return;
553
+ if (/(^|\/)\.codex\/(plans|specs|reviews)\//.test(filePath)) {
554
+ this.emitLine(`[ADD PostToolUse] ADD \u6587\u6863\u5DF2\u5199\u5165: ${filePath}\uFF1B\u8BF7\u68C0\u67E5\u53CC\u5411\u94FE\u63A5\u4E0E Plan/Spec \u72B6\u6001\u3002
555
+ `);
556
+ }
557
+ this.emitLine(`[ADD PostToolUse] \u6587\u4EF6\u5DF2\u5199\u5165: ${filePath}\uFF1B\u8BF7\u6267\u884C record_dev_operation \u843D\u5E93\u5BA1\u8BA1\u3002
558
+ `);
559
+ this.emitWriteEvent(filePath);
560
+ }
561
+ };
562
+ function resolveProjectDir() {
563
+ try {
564
+ return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
565
+ } catch {
566
+ return process.cwd();
567
+ }
568
+ }
569
+ var PROJECT_DIR = resolveProjectDir();
570
+ process.env.PROJECT_DIR = PROJECT_DIR;
571
+ process.env.MAGIC_DIR = tryResolveMagicDir() || ".codex";
572
+ var input = readHookInput();
573
+ var toolName = jsonGet(input, "tool_name");
574
+ if (toolName === "") process.exit(0);
575
+ process.exit(new CodexPostToolRouter(PROJECT_DIR, process.env.MAGIC_DIR).run(input, toolName));