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,583 @@
1
+ // templates/adapters/codex/hooks/session-start.ts
2
+ import { existsSync as existsSync4, readdirSync as readdirSync2 } from "node:fs";
3
+ import { join as join4 } from "node:path";
4
+ import { execSync } from "node:child_process";
5
+
6
+ // templates/core/governance/common.ts
7
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
8
+ import { basename, dirname, join } from "node:path";
9
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
10
+ import { spawnSync } from "node:child_process";
11
+ import { createHash } from "node:crypto";
12
+
13
+ // templates/core/governance/rules.ts
14
+ var protocol = {
15
+ "exit_codes": {
16
+ "pass": 0,
17
+ "block": 2,
18
+ "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"
19
+ },
20
+ "output": {
21
+ "stdout_json_only": true,
22
+ "stderr_human_text": true,
23
+ "field_separator": "::",
24
+ "magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
25
+ },
26
+ "adapters": {
27
+ "claude": {
28
+ "stdout_form": "plain-text",
29
+ "project_dir_env": "CLAUDE_PROJECT_DIR",
30
+ "magic_dir": ".claude",
31
+ "handlerTypes": [
32
+ "command",
33
+ "mcp_tool"
34
+ ]
35
+ },
36
+ "qoder": {
37
+ "stdout_form": "json",
38
+ "project_dir_env": "QODER_PROJECT_DIR",
39
+ "magic_dir": ".qoder",
40
+ "handlerTypes": [
41
+ "command",
42
+ "http"
43
+ ]
44
+ },
45
+ "codex": {
46
+ "stdout_form": "systemMessage",
47
+ "project_dir_env": "git-toplevel",
48
+ "magic_dir": ".codex",
49
+ "handlerTypes": [
50
+ "command"
51
+ ]
52
+ },
53
+ "vscode": {
54
+ "stdout_form": "plain-text",
55
+ "project_dir_env": "PWD",
56
+ "magic_dir": ".vscode",
57
+ "handlerTypes": [
58
+ "command"
59
+ ]
60
+ },
61
+ "trae": {
62
+ "stdout_form": "plain-text",
63
+ "project_dir_env": "PWD",
64
+ "magic_dir": ".trae",
65
+ "handlerTypes": [
66
+ "command"
67
+ ]
68
+ }
69
+ },
70
+ "event_outputs": {
71
+ "qoder": {
72
+ "SessionStart": "additionalContext",
73
+ "UserPromptSubmit": "additionalContext",
74
+ "PreToolUse": "permissionDecision",
75
+ "Stop": "additionalContext",
76
+ "PostToolUse": "feedback",
77
+ "SubagentStart": "additionalContext",
78
+ "SubagentStop": "additionalContext",
79
+ "PostToolUseFailure": "text",
80
+ "PermissionRequest": "text",
81
+ "SessionEnd": "text",
82
+ "PreCompact": "text",
83
+ "Notification": "text"
84
+ },
85
+ "claude": {
86
+ "SessionStart": "additionalContext",
87
+ "UserPromptSubmit": "additionalContext",
88
+ "PreToolUse": "permissionDecision",
89
+ "Stop": "text",
90
+ "PostToolUse": "feedback",
91
+ "SubagentStart": "text",
92
+ "SubagentStop": "text",
93
+ "PostToolUseFailure": "text",
94
+ "PermissionRequest": "text",
95
+ "SessionEnd": "text",
96
+ "PreCompact": "text",
97
+ "Notification": "text"
98
+ },
99
+ "codex": {
100
+ "Stop": "systemMessage",
101
+ "PostToolUse": "text"
102
+ },
103
+ "vscode": {
104
+ "PostToolUse": "text"
105
+ },
106
+ "trae": {
107
+ "PostToolUse": "text"
108
+ }
109
+ },
110
+ "core": {
111
+ "stdout_form": "json",
112
+ "magic_dir": ".add",
113
+ "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"
114
+ },
115
+ "adapter_defaults": {
116
+ "magic_dir_fallback": ".qoder",
117
+ "probe_magic_dirs": [
118
+ ".claude",
119
+ ".qoder",
120
+ ".vscode",
121
+ ".add",
122
+ ".trae",
123
+ ".codex"
124
+ ]
125
+ }
126
+ };
127
+
128
+ // node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
129
+ import path2 from "node:path";
130
+
131
+ // node_modules/.pnpm/locate-path@8.0.0/node_modules/locate-path/index.js
132
+ import process2 from "node:process";
133
+ import path from "node:path";
134
+ import fs, { promises as fsPromises } from "node:fs";
135
+ import { fileURLToPath } from "node:url";
136
+ var typeMappings = {
137
+ directory: "isDirectory",
138
+ file: "isFile"
139
+ };
140
+ function checkType(type) {
141
+ if (type === "both" || Object.hasOwn(typeMappings, type)) {
142
+ return;
143
+ }
144
+ throw new Error(`Invalid type specified: ${type}`);
145
+ }
146
+ var matchType = (type, stat) => type === "both" ? stat.isFile() || stat.isDirectory() : stat[typeMappings[type]]();
147
+ var toPath = (urlOrPath) => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
148
+ function locatePathSync(paths, {
149
+ cwd = process2.cwd(),
150
+ type = "file",
151
+ allowSymlinks = true
152
+ } = {}) {
153
+ checkType(type);
154
+ cwd = toPath(cwd);
155
+ const statFunction = allowSymlinks ? fs.statSync : fs.lstatSync;
156
+ for (const path_ of paths) {
157
+ try {
158
+ const stat = statFunction(path.resolve(cwd, path_), {
159
+ throwIfNoEntry: false
160
+ });
161
+ if (!stat) {
162
+ continue;
163
+ }
164
+ if (matchType(type, stat)) {
165
+ return path_;
166
+ }
167
+ } catch {
168
+ }
169
+ }
170
+ }
171
+
172
+ // node_modules/.pnpm/unicorn-magic@0.3.0/node_modules/unicorn-magic/node.js
173
+ import { promisify } from "node:util";
174
+ import { execFile as execFileCallback, execFileSync as execFileSyncOriginal } from "node:child_process";
175
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
176
+ var execFileOriginal = promisify(execFileCallback);
177
+ function toPath2(urlOrPath) {
178
+ return urlOrPath instanceof URL ? fileURLToPath2(urlOrPath) : urlOrPath;
179
+ }
180
+ var TEN_MEGABYTES_IN_BYTES = 10 * 1024 * 1024;
181
+
182
+ // node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
183
+ var findUpStop = /* @__PURE__ */ Symbol("findUpStop");
184
+ function findUpMultipleSync(name, options = {}) {
185
+ let directory = path2.resolve(toPath2(options.cwd) ?? "");
186
+ const { root } = path2.parse(directory);
187
+ const stopAt = path2.resolve(directory, toPath2(options.stopAt) ?? root);
188
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
189
+ const paths = [name].flat();
190
+ const runMatcher = (locateOptions) => {
191
+ if (typeof name !== "function") {
192
+ return locatePathSync(paths, locateOptions);
193
+ }
194
+ const foundPath = name(locateOptions.cwd);
195
+ if (typeof foundPath === "string") {
196
+ return locatePathSync([foundPath], locateOptions);
197
+ }
198
+ return foundPath;
199
+ };
200
+ const matches = [];
201
+ while (true) {
202
+ const foundPath = runMatcher({ ...options, cwd: directory });
203
+ if (foundPath === findUpStop) {
204
+ break;
205
+ }
206
+ if (foundPath) {
207
+ matches.push(path2.resolve(directory, foundPath));
208
+ }
209
+ if (directory === stopAt || matches.length >= limit) {
210
+ break;
211
+ }
212
+ directory = path2.dirname(directory);
213
+ }
214
+ return matches;
215
+ }
216
+ function findUpSync(name, options = {}) {
217
+ const matches = findUpMultipleSync(name, { ...options, limit: 1 });
218
+ return matches[0];
219
+ }
220
+
221
+ // templates/core/governance/common.ts
222
+ var EXIT_PASS = protocol.exit_codes.pass;
223
+ var EXIT_BLOCK = protocol.exit_codes.block;
224
+ var STATE_SEP = protocol.output.field_separator;
225
+ function tryResolveMagicDir() {
226
+ const injected = process.env.MAGIC_DIR;
227
+ if (injected) return injected;
228
+ const startDir = dirname(fileURLToPath3(import.meta.url));
229
+ const hit = findUpSync((dir) => basename(dir).startsWith(".") ? dir : void 0, {
230
+ cwd: startDir,
231
+ type: "directory"
232
+ });
233
+ return hit ? basename(hit) : "";
234
+ }
235
+ function queryPlanStatus() {
236
+ const magicDir = process.env.MAGIC_DIR;
237
+ if (!magicDir) {
238
+ return {
239
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"magicDir \u672A\u6CE8\u5165\u4E14\u65E0\u6CD5\u4ECE\u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC"}',
240
+ exitCode: 3
241
+ };
242
+ }
243
+ const bridge = join(
244
+ process.env.PROJECT_DIR || process.cwd(),
245
+ magicDir,
246
+ "scripts",
247
+ "plan-status-bridge.ts"
248
+ );
249
+ if (!existsSync(bridge)) {
250
+ return {
251
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"plan-status bridge missing"}',
252
+ exitCode: 3
253
+ };
254
+ }
255
+ const r = spawnSync("node", ["--import", "tsx", bridge], {
256
+ encoding: "utf-8",
257
+ timeout: 1e4
258
+ });
259
+ return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
260
+ }
261
+ function detectActiveAdd() {
262
+ const r = queryPlanStatus();
263
+ if (r.exitCode !== 0) {
264
+ let reason = "database status unavailable";
265
+ try {
266
+ const parsed = JSON.parse(r.stdout);
267
+ if (parsed.reason) reason = parsed.reason;
268
+ } catch {
269
+ }
270
+ return `__STATUS_UNAVAILABLE__::${reason}::database::none::none`;
271
+ }
272
+ let snapshot;
273
+ try {
274
+ snapshot = JSON.parse(r.stdout);
275
+ } catch {
276
+ return null;
277
+ }
278
+ if (!(snapshot.availability === "READY" && snapshot.isActive === true)) return null;
279
+ const done = snapshot.progress?.doneTasks ?? 0;
280
+ const total = snapshot.progress?.totalTasks ?? 0;
281
+ const approval = snapshot.approvalStatus ?? "none";
282
+ return `${snapshot.planName}::${done}/${total}::${approval}::none::none`;
283
+ }
284
+ function projectHash() {
285
+ try {
286
+ return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
287
+ `).digest("hex").slice(0, 8);
288
+ } catch {
289
+ return "default";
290
+ }
291
+ }
292
+ var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
293
+
294
+ // templates/core/governance/session-start-guard.ts
295
+ import { existsSync as existsSync3, readdirSync, statSync } from "node:fs";
296
+ import { join as join3 } from "node:path";
297
+
298
+ // templates/core/governance/preload-templates.ts
299
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
300
+ import { join as join2, dirname as dirname2 } from "node:path";
301
+ import { createHash as createHash2 } from "node:crypto";
302
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
303
+ function stripFrontmatter(lines) {
304
+ const out = [];
305
+ let inFm = false;
306
+ lines.forEach((line, idx) => {
307
+ if (idx === 0 && line === "---") {
308
+ inFm = true;
309
+ return;
310
+ }
311
+ if (inFm && line === "---") {
312
+ inFm = false;
313
+ return;
314
+ }
315
+ if (!inFm) out.push(line);
316
+ });
317
+ return out;
318
+ }
319
+ function projectHash2(projectDir) {
320
+ try {
321
+ return createHash2("md5").update(projectDir).digest("hex").slice(0, 8);
322
+ } catch {
323
+ return "default";
324
+ }
325
+ }
326
+ function parseArgs(argv) {
327
+ const args = { mode: "index", top: 0, mark: false };
328
+ for (let i = 0; i < argv.length; i++) {
329
+ switch (argv[i]) {
330
+ case "--index":
331
+ args.mode = "index";
332
+ break;
333
+ case "--full":
334
+ args.mode = "full";
335
+ break;
336
+ case "--top":
337
+ args.top = Number(argv[++i] ?? "0") || 0;
338
+ break;
339
+ case "--mark":
340
+ args.mark = true;
341
+ break;
342
+ default:
343
+ break;
344
+ }
345
+ }
346
+ return args;
347
+ }
348
+ var PreloadTemplates = class _PreloadTemplates {
349
+ templatesDir;
350
+ tplFlag;
351
+ static TEMPLATES = Object.freeze({
352
+ "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",
353
+ "spec-template.md": "\u529F\u80FD\u89C4\u683C\uFF1AWhy/What Changes/Impact/WHEN-THEN Requirements",
354
+ "tasks-template.md": "\u4EFB\u52A1\u62C6\u5206\uFF1APhase\u2192Task\u2192SubTask\u5C42\u7EA7",
355
+ "checklist-template.md": "\u9A8C\u6536\u6E05\u5355\uFF1A[T]\u7F16\u8BD1\u671F+[R]\u8FD0\u884C\u65F6+ADD\u89C4\u5219\u5408\u89C4",
356
+ "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",
357
+ "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",
358
+ "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",
359
+ "add-route-template.md": "ADD\u6267\u884C\u8DEF\u7EBF\u56FE\uFF08\u8F7B\u91CF\uFF09\uFF1A\u6807\u51C6Step\u4EA7\u51FA\u68C0\u67E5",
360
+ "handoff-single-round-template.md": "\u5355\u8F6E\u4EA4\u63A5\uFF1A9\u7AE0\u8282\uFF08\u542B\u6062\u590D\u4E0A\u4E0B\u6587\u5BA1\u8BA1\u67E5\u8BE2\uFF09",
361
+ "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",
362
+ "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",
363
+ "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",
364
+ "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",
365
+ "prd-incremental-template.md": "\u4EA7\u54C1\u9700\u6C42\u6587\u6863\uFF08\u589E\u91CF\uFF09\uFF1A\u53D8\u66F4\u6458\u8981+diff\u5F0F\u8BB0\u5F55",
366
+ "fix-verification-template.md": "\u4FEE\u590D\u9A8C\u8BC1\u6A21\u677F",
367
+ "report-template.md": "\u4EE3\u7801\u5BA1\u67E5\u62A5\u544A\u6A21\u677F",
368
+ "runtime-report-template.md": "\u8FD0\u884C\u65F6\u62A5\u544A\u6A21\u677F",
369
+ "TERMINOLOGY.md": "\u6A21\u677F\u672F\u8BED\u901F\u67E5"
370
+ });
371
+ static PRIORITY_ORDER = Object.freeze([
372
+ "simple-plan-template.md",
373
+ "spec-template.md",
374
+ "tasks-template.md",
375
+ "checklist-template.md",
376
+ "review-template.md",
377
+ "standard-plan-template.md",
378
+ "add-route-template-heavyweight.md",
379
+ "add-route-template.md",
380
+ "handoff-single-round-template.md",
381
+ "handoff-multi-round-template.md",
382
+ "review-implementation-template.md",
383
+ "review-runtime-template.md",
384
+ "prd-standard-template.md",
385
+ "prd-incremental-template.md",
386
+ "fix-verification-template.md",
387
+ "report-template.md",
388
+ "runtime-report-template.md",
389
+ "TERMINOLOGY.md"
390
+ ]);
391
+ /**
392
+ * 模板目录 find-up 解析(复用 find-up 包,与 src/shared/paths.ts projectRoot 同范式):
393
+ * 从 startDir 向上找第一个 templates 目录(esbuild bundle 内联,产物零依赖):
394
+ * - 产物 <magicDir>/hooks/xxx.mjs → <magicDir>/templates(分发物化)
395
+ * - 源码 templates/core/governance/preload-templates.ts → templates/core/templates(源码模板)
396
+ * 命中条件:目录含至少一个标准模板(防命中仓库根 templates/ 空壳);
397
+ * 目录存在但标准模板缺失 = 物化异常 → 返回 null,由 validate() fail-fast(不继续向上找)。
398
+ * 缺陷修复(2026-08-14 Task 5.1): 原写死 join(dirname, "..", "..", "templates") 固定层级,
399
+ * 产物从 hooks/ 上溯两级到仓库根 templates/(无标准模板)——被 refresh-fixed 反写掩盖,
400
+ * golden 重抓暴露;改 find-up 锚点查找,层级零漂移。
401
+ */
402
+ static findTemplatesDir(startDir) {
403
+ const hit = findUpSync("templates", { cwd: startDir, type: "directory" });
404
+ if (!hit) return null;
405
+ return _PreloadTemplates.PRIORITY_ORDER.some((t) => existsSync2(join2(hit, t))) ? hit : null;
406
+ }
407
+ constructor(templatesDir, tplFlag) {
408
+ const startDir = dirname2(fileURLToPath4(import.meta.url));
409
+ this.templatesDir = templatesDir ?? _PreloadTemplates.findTemplatesDir(startDir) ?? join2(startDir, "..", "templates");
410
+ this.tplFlag = tplFlag ?? `/tmp/add_tpl_${projectHash2(process.env.PROJECT_DIR || process.cwd())}`;
411
+ }
412
+ /** 模板目录存在性校验(fail-fast:目录缺失/标准模板全缺 → 抛错,由 CLI 层转 exit 1) */
413
+ validate() {
414
+ if (!existsSync2(this.templatesDir)) {
415
+ throw new Error(
416
+ `[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`
417
+ );
418
+ }
419
+ const available = _PreloadTemplates.PRIORITY_ORDER.filter(
420
+ (t) => existsSync2(join2(this.templatesDir, t))
421
+ ).length;
422
+ if (available === 0) {
423
+ throw new Error(
424
+ `[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`
425
+ );
426
+ }
427
+ }
428
+ /** 读取模板内容(strip frontmatter) */
429
+ readTemplate(file) {
430
+ const path3 = join2(this.templatesDir, file);
431
+ if (!existsSync2(path3)) return "";
432
+ return stripFrontmatter(readFileSync2(path3, "utf-8").split("\n")).join("\n");
433
+ }
434
+ /** --index 模式输出(对齐 bash output_index 逐字) */
435
+ index() {
436
+ const lines = ["## ADD \u53EF\u7528\u6A21\u677F\u6E05\u5355", "", "| # | \u6A21\u677F\u6587\u4EF6 | \u7528\u9014 |", "|---|---------|------|"];
437
+ let i = 1;
438
+ for (const tmpl of _PreloadTemplates.PRIORITY_ORDER) {
439
+ if (existsSync2(join2(this.templatesDir, tmpl))) {
440
+ lines.push(`| ${i} | ${tmpl} | ${_PreloadTemplates.TEMPLATES[tmpl] ?? "\u6A21\u677F\u6587\u4EF6"} |`);
441
+ i++;
442
+ }
443
+ }
444
+ return lines.join("\n") + "\n";
445
+ }
446
+ /** --full 模式输出(对齐 bash output_full 逐字) */
447
+ full(top) {
448
+ const lines = ["## ADD \u6A21\u677F\u5168\u6587\u5185\u5BB9", ""];
449
+ let count = 0;
450
+ for (const tmpl of _PreloadTemplates.PRIORITY_ORDER) {
451
+ if (!existsSync2(join2(this.templatesDir, tmpl))) continue;
452
+ count++;
453
+ if (top > 0 && count > top) break;
454
+ lines.push("---", `### ${tmpl}`, "", this.readTemplate(tmpl), "");
455
+ }
456
+ return lines.join("\n") + "\n";
457
+ }
458
+ isInjected() {
459
+ return existsSync2(this.tplFlag);
460
+ }
461
+ /** 标记模板已注入(tpl-injected 去重:同会话二次命中不重复注入) */
462
+ markInjected() {
463
+ try {
464
+ writeFileSync2(this.tplFlag, "");
465
+ } catch {
466
+ }
467
+ }
468
+ /** CLI 入口:解析参数 → 校验 → 输出;去重命中时 stderr 提示并 exit 0 */
469
+ run(argv) {
470
+ const { mode, top, mark } = parseArgs(argv);
471
+ this.validate();
472
+ if (mode === "index") {
473
+ process.stdout.write(this.index());
474
+ return 0;
475
+ }
476
+ if (this.isInjected() && !mark) {
477
+ 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");
478
+ return 0;
479
+ }
480
+ process.stdout.write(this.full(top));
481
+ this.markInjected();
482
+ return 0;
483
+ }
484
+ };
485
+
486
+ // templates/core/governance/session-start-guard.ts
487
+ var SessionStartGuard = class {
488
+ projectDir;
489
+ magicDir;
490
+ constructor(projectDir) {
491
+ this.projectDir = projectDir;
492
+ const inferred = tryResolveMagicDir();
493
+ if (inferred && !process.env.MAGIC_DIR) {
494
+ process.env.MAGIC_DIR = inferred;
495
+ }
496
+ this.magicDir = process.env.MAGIC_DIR ?? "";
497
+ }
498
+ /** 主入口:返回 exit code(0) */
499
+ run() {
500
+ const state = detectActiveAdd();
501
+ this.emitState(state);
502
+ this.emitIndex();
503
+ this.emitTodoReminder(state);
504
+ this.emitHitlPending();
505
+ return 0;
506
+ }
507
+ // ─────────────────────────── 扩展点 ───────────────────────────
508
+ /** ① ADD 状态恢复输出(core: 纯文本块) */
509
+ emitState(state) {
510
+ if (state === null) return;
511
+ const [plan, step, rounds, handoff] = state.split("::");
512
+ process.stdout.write(`[ADD SessionStart] \u68C0\u6D4B\u5230\u6D3B\u8DC3 ADD Plan:
513
+ Plan: ${plan}
514
+ \u8F6E\u6B21: ${rounds}
515
+ \u5F53\u524D Step: ${step}
516
+ handoff: ${handoff}
517
+ \u6062\u590D\u547D\u4EE4: query_audit_logs({ planKeyword: '${plan}' })
518
+ `);
519
+ }
520
+ /** ② 模板索引输出(core: 纯文本 index) */
521
+ emitIndex() {
522
+ try {
523
+ process.stdout.write(new PreloadTemplates().index());
524
+ } catch {
525
+ }
526
+ }
527
+ /** ③ 代办刷新(core: 活跃 Plan 时输出提醒;claude: 无此段) */
528
+ emitTodoReminder(state) {
529
+ if (state !== null) {
530
+ const plan = state.split("::")[0] ?? "";
531
+ process.stdout.write(`[\u4EE3\u529E] \u68C0\u6D4B\u5230\u6D3B\u8DC3 Plan: ${plan}\u3002\u5982\u6709\u672A\u52A0\u8F7D\u7684 IDE \u4EE3\u529E\u6E05\u5355\uFF0C\u8BF7\u4ECE tasks.md \xA7IDE JSON \u5237\u65B0 TodoWrite\u3002
532
+ `);
533
+ }
534
+ }
535
+ /** ④ HITL 待审批检测(core: 7 天内 .hitl.md 计数提示;claude: 无此段) */
536
+ emitHitlPending() {
537
+ const PLANS_DIR = this.magicDir ? join3(this.projectDir, this.magicDir, "plans") : "";
538
+ if (PLANS_DIR && existsSync3(PLANS_DIR)) {
539
+ const weekAgo = Date.now() - 7 * 864e5;
540
+ const hitlCount = readdirSync(PLANS_DIR).filter((f) => f.endsWith(".hitl.md")).filter((f) => {
541
+ try {
542
+ return statSync(join3(PLANS_DIR, f)).mtimeMs >= weekAgo;
543
+ } catch {
544
+ return false;
545
+ }
546
+ }).length;
547
+ if (hitlCount > 0) {
548
+ process.stdout.write(`[HITL \u5F85\u5BA1\u6279] \u68C0\u6D4B\u5230 ${hitlCount} \u4E2A\u5F85\u5BA1\u6279 HITL \u63D0\u6848\uFF0C\u8BF7\u68C0\u67E5\u5E76\u5904\u7406
549
+ `);
550
+ }
551
+ }
552
+ }
553
+ };
554
+
555
+ // templates/adapters/codex/hooks/session-start.ts
556
+ var CodexSessionStartGuard = class extends SessionStartGuard {
557
+ /** ② 模板索引: 自定义列表格式(codex bash 原文逐字,非表格) */
558
+ emitIndex() {
559
+ const TEMPLATES_DIR = join4(this.projectDir, this.magicDir, "templates");
560
+ if (existsSync4(TEMPLATES_DIR)) {
561
+ process.stdout.write("## ADD \u53EF\u7528\u6A21\u677F\u6E05\u5355\n");
562
+ try {
563
+ for (const f of readdirSync2(TEMPLATES_DIR)) {
564
+ if (f.endsWith(".md")) process.stdout.write(`- ${f}
565
+ `);
566
+ }
567
+ } catch {
568
+ }
569
+ }
570
+ }
571
+ };
572
+ function resolveProjectDir() {
573
+ try {
574
+ return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
575
+ } catch {
576
+ return process.cwd();
577
+ }
578
+ }
579
+ var PROJECT_DIR = resolveProjectDir();
580
+ process.env.PROJECT_DIR = PROJECT_DIR;
581
+ var MAGIC_DIR = tryResolveMagicDir() || ".codex";
582
+ process.env.MAGIC_DIR = MAGIC_DIR;
583
+ process.exit(new CodexSessionStartGuard(PROJECT_DIR).run());