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,389 @@
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 protocol = {
10
+ "exit_codes": {
11
+ "pass": 0,
12
+ "block": 2,
13
+ "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"
14
+ },
15
+ "output": {
16
+ "stdout_json_only": true,
17
+ "stderr_human_text": true,
18
+ "field_separator": "::",
19
+ "magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
20
+ },
21
+ "adapters": {
22
+ "claude": {
23
+ "stdout_form": "plain-text",
24
+ "project_dir_env": "CLAUDE_PROJECT_DIR",
25
+ "magic_dir": ".claude",
26
+ "handlerTypes": [
27
+ "command",
28
+ "mcp_tool"
29
+ ]
30
+ },
31
+ "qoder": {
32
+ "stdout_form": "json",
33
+ "project_dir_env": "QODER_PROJECT_DIR",
34
+ "magic_dir": ".qoder",
35
+ "handlerTypes": [
36
+ "command",
37
+ "http"
38
+ ]
39
+ },
40
+ "codex": {
41
+ "stdout_form": "systemMessage",
42
+ "project_dir_env": "git-toplevel",
43
+ "magic_dir": ".codex",
44
+ "handlerTypes": [
45
+ "command"
46
+ ]
47
+ },
48
+ "vscode": {
49
+ "stdout_form": "plain-text",
50
+ "project_dir_env": "PWD",
51
+ "magic_dir": ".vscode",
52
+ "handlerTypes": [
53
+ "command"
54
+ ]
55
+ },
56
+ "trae": {
57
+ "stdout_form": "plain-text",
58
+ "project_dir_env": "PWD",
59
+ "magic_dir": ".trae",
60
+ "handlerTypes": [
61
+ "command"
62
+ ]
63
+ }
64
+ },
65
+ "event_outputs": {
66
+ "qoder": {
67
+ "SessionStart": "additionalContext",
68
+ "UserPromptSubmit": "additionalContext",
69
+ "PreToolUse": "permissionDecision",
70
+ "Stop": "additionalContext",
71
+ "PostToolUse": "feedback",
72
+ "SubagentStart": "additionalContext",
73
+ "SubagentStop": "additionalContext",
74
+ "PostToolUseFailure": "text",
75
+ "PermissionRequest": "text",
76
+ "SessionEnd": "text",
77
+ "PreCompact": "text",
78
+ "Notification": "text"
79
+ },
80
+ "claude": {
81
+ "SessionStart": "additionalContext",
82
+ "UserPromptSubmit": "additionalContext",
83
+ "PreToolUse": "permissionDecision",
84
+ "Stop": "text",
85
+ "PostToolUse": "feedback",
86
+ "SubagentStart": "text",
87
+ "SubagentStop": "text",
88
+ "PostToolUseFailure": "text",
89
+ "PermissionRequest": "text",
90
+ "SessionEnd": "text",
91
+ "PreCompact": "text",
92
+ "Notification": "text"
93
+ },
94
+ "codex": {
95
+ "Stop": "systemMessage",
96
+ "PostToolUse": "text"
97
+ },
98
+ "vscode": {
99
+ "PostToolUse": "text"
100
+ },
101
+ "trae": {
102
+ "PostToolUse": "text"
103
+ }
104
+ },
105
+ "core": {
106
+ "stdout_form": "json",
107
+ "magic_dir": ".add",
108
+ "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"
109
+ },
110
+ "adapter_defaults": {
111
+ "magic_dir_fallback": ".qoder",
112
+ "probe_magic_dirs": [
113
+ ".claude",
114
+ ".qoder",
115
+ ".vscode",
116
+ ".add",
117
+ ".trae",
118
+ ".codex"
119
+ ]
120
+ }
121
+ };
122
+
123
+ // node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
124
+ import path2 from "node:path";
125
+
126
+ // node_modules/.pnpm/locate-path@8.0.0/node_modules/locate-path/index.js
127
+ import process2 from "node:process";
128
+ import path from "node:path";
129
+ import fs, { promises as fsPromises } from "node:fs";
130
+ import { fileURLToPath } from "node:url";
131
+ var typeMappings = {
132
+ directory: "isDirectory",
133
+ file: "isFile"
134
+ };
135
+ function checkType(type) {
136
+ if (type === "both" || Object.hasOwn(typeMappings, type)) {
137
+ return;
138
+ }
139
+ throw new Error(`Invalid type specified: ${type}`);
140
+ }
141
+ var matchType = (type, stat) => type === "both" ? stat.isFile() || stat.isDirectory() : stat[typeMappings[type]]();
142
+ var toPath = (urlOrPath) => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
143
+ function locatePathSync(paths, {
144
+ cwd = process2.cwd(),
145
+ type = "file",
146
+ allowSymlinks = true
147
+ } = {}) {
148
+ checkType(type);
149
+ cwd = toPath(cwd);
150
+ const statFunction = allowSymlinks ? fs.statSync : fs.lstatSync;
151
+ for (const path_ of paths) {
152
+ try {
153
+ const stat = statFunction(path.resolve(cwd, path_), {
154
+ throwIfNoEntry: false
155
+ });
156
+ if (!stat) {
157
+ continue;
158
+ }
159
+ if (matchType(type, stat)) {
160
+ return path_;
161
+ }
162
+ } catch {
163
+ }
164
+ }
165
+ }
166
+
167
+ // node_modules/.pnpm/unicorn-magic@0.3.0/node_modules/unicorn-magic/node.js
168
+ import { promisify } from "node:util";
169
+ import { execFile as execFileCallback, execFileSync as execFileSyncOriginal } from "node:child_process";
170
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
171
+ var execFileOriginal = promisify(execFileCallback);
172
+ function toPath2(urlOrPath) {
173
+ return urlOrPath instanceof URL ? fileURLToPath2(urlOrPath) : urlOrPath;
174
+ }
175
+ var TEN_MEGABYTES_IN_BYTES = 10 * 1024 * 1024;
176
+
177
+ // node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
178
+ var findUpStop = /* @__PURE__ */ Symbol("findUpStop");
179
+ function findUpMultipleSync(name, options = {}) {
180
+ let directory = path2.resolve(toPath2(options.cwd) ?? "");
181
+ const { root } = path2.parse(directory);
182
+ const stopAt = path2.resolve(directory, toPath2(options.stopAt) ?? root);
183
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
184
+ const paths = [name].flat();
185
+ const runMatcher = (locateOptions) => {
186
+ if (typeof name !== "function") {
187
+ return locatePathSync(paths, locateOptions);
188
+ }
189
+ const foundPath = name(locateOptions.cwd);
190
+ if (typeof foundPath === "string") {
191
+ return locatePathSync([foundPath], locateOptions);
192
+ }
193
+ return foundPath;
194
+ };
195
+ const matches = [];
196
+ while (true) {
197
+ const foundPath = runMatcher({ ...options, cwd: directory });
198
+ if (foundPath === findUpStop) {
199
+ break;
200
+ }
201
+ if (foundPath) {
202
+ matches.push(path2.resolve(directory, foundPath));
203
+ }
204
+ if (directory === stopAt || matches.length >= limit) {
205
+ break;
206
+ }
207
+ directory = path2.dirname(directory);
208
+ }
209
+ return matches;
210
+ }
211
+ function findUpSync(name, options = {}) {
212
+ const matches = findUpMultipleSync(name, { ...options, limit: 1 });
213
+ return matches[0];
214
+ }
215
+
216
+ // templates/core/governance/common.ts
217
+ var EXIT_PASS = protocol.exit_codes.pass;
218
+ var EXIT_BLOCK = protocol.exit_codes.block;
219
+ var STATE_SEP = protocol.output.field_separator;
220
+ function tryResolveMagicDir() {
221
+ const injected = process.env.MAGIC_DIR;
222
+ if (injected) return injected;
223
+ const startDir = dirname(fileURLToPath3(import.meta.url));
224
+ const hit = findUpSync((dir) => basename(dir).startsWith(".") ? dir : void 0, {
225
+ cwd: startDir,
226
+ type: "directory"
227
+ });
228
+ return hit ? basename(hit) : "";
229
+ }
230
+ function queryPlanStatus() {
231
+ const magicDir = process.env.MAGIC_DIR;
232
+ if (!magicDir) {
233
+ return {
234
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"magicDir \u672A\u6CE8\u5165\u4E14\u65E0\u6CD5\u4ECE\u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC"}',
235
+ exitCode: 3
236
+ };
237
+ }
238
+ const bridge = join(
239
+ process.env.PROJECT_DIR || process.cwd(),
240
+ magicDir,
241
+ "scripts",
242
+ "plan-status-bridge.ts"
243
+ );
244
+ if (!existsSync(bridge)) {
245
+ return {
246
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"plan-status bridge missing"}',
247
+ exitCode: 3
248
+ };
249
+ }
250
+ const r = spawnSync("node", ["--import", "tsx", bridge], {
251
+ encoding: "utf-8",
252
+ timeout: 1e4
253
+ });
254
+ return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
255
+ }
256
+ function detectActiveAdd() {
257
+ const r = queryPlanStatus();
258
+ if (r.exitCode !== 0) {
259
+ let reason = "database status unavailable";
260
+ try {
261
+ const parsed = JSON.parse(r.stdout);
262
+ if (parsed.reason) reason = parsed.reason;
263
+ } catch {
264
+ }
265
+ return `__STATUS_UNAVAILABLE__::${reason}::database::none::none`;
266
+ }
267
+ let snapshot;
268
+ try {
269
+ snapshot = JSON.parse(r.stdout);
270
+ } catch {
271
+ return null;
272
+ }
273
+ if (!(snapshot.availability === "READY" && snapshot.isActive === true)) return null;
274
+ const done = snapshot.progress?.doneTasks ?? 0;
275
+ const total = snapshot.progress?.totalTasks ?? 0;
276
+ const approval = snapshot.approvalStatus ?? "none";
277
+ return `${snapshot.planName}::${done}/${total}::${approval}::none::none`;
278
+ }
279
+ function localIsoSeconds() {
280
+ const d = /* @__PURE__ */ new Date();
281
+ const pad = (n) => String(n).padStart(2, "0");
282
+ const offsetMin = -d.getTimezoneOffset();
283
+ const sign = offsetMin >= 0 ? "+" : "-";
284
+ const offAbs = Math.abs(offsetMin);
285
+ const off = `${sign}${pad(Math.floor(offAbs / 60))}:${pad(offAbs % 60)}`;
286
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${off}`;
287
+ }
288
+ function projectHash() {
289
+ try {
290
+ return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
291
+ `).digest("hex").slice(0, 8);
292
+ } catch {
293
+ return "default";
294
+ }
295
+ }
296
+ var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
297
+ function hasDevAction() {
298
+ return existsSync(DEV_FLAG);
299
+ }
300
+ function clearDevAction() {
301
+ try {
302
+ unlinkSync(DEV_FLAG);
303
+ } catch {
304
+ }
305
+ }
306
+ function checkAddCompleteness(handoff, addRoute) {
307
+ const issues = [];
308
+ if (handoff && existsSync(handoff)) {
309
+ const content = readFileSync(handoff, "utf-8");
310
+ if (!/验收|收敛|闭环|本轮改了什么|devlog/.test(content)) {
311
+ issues.push(" [ ] devlog \u7F3A\u5931\uFF08handoff \u65E0\u9A8C\u6536\u8BB0\u5F55\uFF09");
312
+ }
313
+ const unchecked = (content.match(/\[ \]/g) || []).length;
314
+ if (unchecked > 0) {
315
+ issues.push(` [ ] handoff ${unchecked} \u9879\u672A\u52FE\u9009`);
316
+ }
317
+ }
318
+ if (addRoute && existsSync(addRoute)) {
319
+ const content = readFileSync(addRoute, "utf-8");
320
+ const unchecked = (content.match(/\[ \]/g) || []).length;
321
+ if (unchecked > 0) {
322
+ issues.push(` [ ] add-route ${unchecked} Step \u672A\u95ED\u73AF`);
323
+ }
324
+ }
325
+ return issues;
326
+ }
327
+
328
+ // templates/core/governance/session-end.ts
329
+ import { unlinkSync as unlinkSync2 } from "node:fs";
330
+ function stateField(state, index) {
331
+ return state.split("::")[index] ?? "";
332
+ }
333
+ var SessionEndGuard = class {
334
+ projectDir;
335
+ tplFlag;
336
+ constructor(projectDir) {
337
+ this.projectDir = projectDir ?? process.env.PROJECT_DIR ?? process.cwd();
338
+ this.tplFlag = `/tmp/add_tpl_${projectHash()}`;
339
+ }
340
+ /** ① 清理 tpl-injected 标记 + dev action 标记(对齐 cleanup_tpl_flag/cleanup_dev_flag) */
341
+ cleanupFlags() {
342
+ try {
343
+ unlinkSync2(this.tplFlag);
344
+ } catch {
345
+ }
346
+ clearDevAction();
347
+ }
348
+ /** ② 审计结算(core 默认: stderr 文本;qoder 子类 override: stdout JSON additionalContext) */
349
+ emitSettle() {
350
+ process.stderr.write(`[ADD SessionEnd] \u4F1A\u8BDD\u7ED3\u675F \u2014 ${localIsoSeconds()}
351
+ `);
352
+ }
353
+ /** ② 审计结算(输出到 stderr 供日志记录,对齐 bash date -Iseconds) */
354
+ settle() {
355
+ this.emitSettle();
356
+ }
357
+ /** ③ Stop 未触发兜底(对齐 stop_fallback:dev action 标记还在 → 补 checklist 快照) */
358
+ stopFallback() {
359
+ if (!hasDevAction()) return;
360
+ process.stderr.write("[ADD SessionEnd] \u26A0\uFE0F \u68C0\u6D4B\u5230 dev action \u6807\u8BB0\u672A\u6E05\u9664\u2014\u2014Stop \u53EF\u80FD\u672A\u89E6\u53D1\u9A8C\u6536\u68C0\u67E5\n");
361
+ const state = detectActiveAdd();
362
+ if (state === null) return;
363
+ const handoff = stateField(state, 3);
364
+ const addRoute = stateField(state, 4);
365
+ if (handoff && handoff !== "none") {
366
+ process.stderr.write("[ADD SessionEnd] \u8865\u6267\u884C checklist \u5FEB\u7167\uFF08best-effort\uFF0C\u4E0D\u963B\u65AD\uFF09\n");
367
+ const issues = checkAddCompleteness(handoff, addRoute !== "none" ? addRoute : "");
368
+ for (const issue of issues) {
369
+ process.stderr.write(issue + "\n");
370
+ }
371
+ }
372
+ }
373
+ /** 主入口:①清理 → ②结算 → ③兜底(对齐 bash main) */
374
+ run() {
375
+ this.cleanupFlags();
376
+ this.settle();
377
+ this.stopFallback();
378
+ return 0;
379
+ }
380
+ };
381
+
382
+ // templates/adapters/codex/hooks/session-end.ts
383
+ var CodexSessionEndGuard = class extends SessionEndGuard {
384
+ // 当前无 override(清理/结算/兜底与 core 基线一致);命名子类承载端身份 + 未来演进位
385
+ };
386
+ process.env.PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
387
+ var inferred = tryResolveMagicDir();
388
+ if (inferred && !process.env.MAGIC_DIR) process.env.MAGIC_DIR = inferred;
389
+ process.exit(new CodexSessionEndGuard(process.env.PROJECT_DIR).run());