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,219 @@
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 { spawnSync } from "node:child_process";
5
+ import { createHash } from "node:crypto";
6
+
7
+ // templates/core/governance/rules.ts
8
+ var protocol = {
9
+ "exit_codes": {
10
+ "pass": 0,
11
+ "block": 2,
12
+ "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"
13
+ },
14
+ "output": {
15
+ "stdout_json_only": true,
16
+ "stderr_human_text": true,
17
+ "field_separator": "::",
18
+ "magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
19
+ },
20
+ "adapters": {
21
+ "claude": {
22
+ "stdout_form": "plain-text",
23
+ "project_dir_env": "CLAUDE_PROJECT_DIR",
24
+ "magic_dir": ".claude",
25
+ "handlerTypes": [
26
+ "command",
27
+ "mcp_tool"
28
+ ]
29
+ },
30
+ "qoder": {
31
+ "stdout_form": "json",
32
+ "project_dir_env": "QODER_PROJECT_DIR",
33
+ "magic_dir": ".qoder",
34
+ "handlerTypes": [
35
+ "command",
36
+ "http"
37
+ ]
38
+ },
39
+ "codex": {
40
+ "stdout_form": "systemMessage",
41
+ "project_dir_env": "git-toplevel",
42
+ "magic_dir": ".codex",
43
+ "handlerTypes": [
44
+ "command"
45
+ ]
46
+ },
47
+ "vscode": {
48
+ "stdout_form": "plain-text",
49
+ "project_dir_env": "PWD",
50
+ "magic_dir": ".vscode",
51
+ "handlerTypes": [
52
+ "command"
53
+ ]
54
+ },
55
+ "trae": {
56
+ "stdout_form": "plain-text",
57
+ "project_dir_env": "PWD",
58
+ "magic_dir": ".trae",
59
+ "handlerTypes": [
60
+ "command"
61
+ ]
62
+ }
63
+ },
64
+ "event_outputs": {
65
+ "qoder": {
66
+ "SessionStart": "additionalContext",
67
+ "UserPromptSubmit": "additionalContext",
68
+ "PreToolUse": "permissionDecision",
69
+ "Stop": "additionalContext",
70
+ "PostToolUse": "feedback",
71
+ "SubagentStart": "additionalContext",
72
+ "SubagentStop": "additionalContext",
73
+ "PostToolUseFailure": "text",
74
+ "PermissionRequest": "text",
75
+ "SessionEnd": "text",
76
+ "PreCompact": "text",
77
+ "Notification": "text"
78
+ },
79
+ "claude": {
80
+ "SessionStart": "additionalContext",
81
+ "UserPromptSubmit": "additionalContext",
82
+ "PreToolUse": "permissionDecision",
83
+ "Stop": "text",
84
+ "PostToolUse": "feedback",
85
+ "SubagentStart": "text",
86
+ "SubagentStop": "text",
87
+ "PostToolUseFailure": "text",
88
+ "PermissionRequest": "text",
89
+ "SessionEnd": "text",
90
+ "PreCompact": "text",
91
+ "Notification": "text"
92
+ },
93
+ "codex": {
94
+ "Stop": "systemMessage",
95
+ "PostToolUse": "text"
96
+ },
97
+ "vscode": {
98
+ "PostToolUse": "text"
99
+ },
100
+ "trae": {
101
+ "PostToolUse": "text"
102
+ }
103
+ },
104
+ "core": {
105
+ "stdout_form": "json",
106
+ "magic_dir": ".add",
107
+ "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"
108
+ },
109
+ "adapter_defaults": {
110
+ "magic_dir_fallback": ".qoder",
111
+ "probe_magic_dirs": [
112
+ ".claude",
113
+ ".qoder",
114
+ ".vscode",
115
+ ".add",
116
+ ".trae",
117
+ ".codex"
118
+ ]
119
+ }
120
+ };
121
+
122
+ // templates/core/governance/common.ts
123
+ var EXIT_PASS = protocol.exit_codes.pass;
124
+ var EXIT_BLOCK = protocol.exit_codes.block;
125
+ var STATE_SEP = protocol.output.field_separator;
126
+ function queryPlanStatus() {
127
+ const magicDir = process.env.MAGIC_DIR;
128
+ if (!magicDir) {
129
+ return {
130
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"magicDir \u672A\u6CE8\u5165\u4E14\u65E0\u6CD5\u4ECE\u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC"}',
131
+ exitCode: 3
132
+ };
133
+ }
134
+ const bridge = join(
135
+ process.env.PROJECT_DIR || process.cwd(),
136
+ magicDir,
137
+ "scripts",
138
+ "plan-status-bridge.ts"
139
+ );
140
+ if (!existsSync(bridge)) {
141
+ return {
142
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"plan-status bridge missing"}',
143
+ exitCode: 3
144
+ };
145
+ }
146
+ const r = spawnSync("node", ["--import", "tsx", bridge], {
147
+ encoding: "utf-8",
148
+ timeout: 1e4
149
+ });
150
+ return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
151
+ }
152
+ function detectActiveAdd() {
153
+ const r = queryPlanStatus();
154
+ if (r.exitCode !== 0) {
155
+ let reason = "database status unavailable";
156
+ try {
157
+ const parsed = JSON.parse(r.stdout);
158
+ if (parsed.reason) reason = parsed.reason;
159
+ } catch {
160
+ }
161
+ return `__STATUS_UNAVAILABLE__::${reason}::database::none::none`;
162
+ }
163
+ let snapshot;
164
+ try {
165
+ snapshot = JSON.parse(r.stdout);
166
+ } catch {
167
+ return null;
168
+ }
169
+ if (!(snapshot.availability === "READY" && snapshot.isActive === true)) return null;
170
+ const done = snapshot.progress?.doneTasks ?? 0;
171
+ const total = snapshot.progress?.totalTasks ?? 0;
172
+ const approval = snapshot.approvalStatus ?? "none";
173
+ return `${snapshot.planName}::${done}/${total}::${approval}::none::none`;
174
+ }
175
+ function projectHash() {
176
+ try {
177
+ return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
178
+ `).digest("hex").slice(0, 8);
179
+ } catch {
180
+ return "default";
181
+ }
182
+ }
183
+ var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
184
+
185
+ // templates/core/governance/subagent-guard-router.ts
186
+ var SubagentGuardRouter = class {
187
+ /** 主路由:返回 exit code(0) */
188
+ run() {
189
+ const state = detectActiveAdd();
190
+ if (state === null) return 0;
191
+ this.emit(state);
192
+ return 0;
193
+ }
194
+ /** 上下文注入输出(core 协议: hookSpecificOutput JSON,qoder 同构) */
195
+ emit(state) {
196
+ const [plan, step, rounds, handoff] = state.split("::");
197
+ const ctx = `[ADD SubagentStart] Plan: ${plan}, Step: ${step}, Round: ${rounds}, handoff: ${handoff}\u3002\u9075\u5FAA ADD \u89C4\u8303\uFF0C\u5B8C\u6210\u540E\u68C0\u67E5 checklist\u3002`;
198
+ process.stdout.write(
199
+ JSON.stringify({ hookSpecificOutput: { hookEventName: "SubagentStart", additionalContext: ctx } }) + "\n"
200
+ );
201
+ }
202
+ };
203
+
204
+ // templates/adapters/qoder/hooks/lib/qoder-env.ts
205
+ function resolveQoderProjectDir() {
206
+ return process.env.CLAUDE_PROJECT_DIR || process.env.QODER_PROJECT_DIR || process.env.QODERCN_PROJECT_DIR || process.cwd();
207
+ }
208
+ function injectProjectDir() {
209
+ const dir = resolveQoderProjectDir();
210
+ process.env.PROJECT_DIR = dir;
211
+ return dir;
212
+ }
213
+
214
+ // templates/adapters/qoder/hooks/subagent-guard.ts
215
+ var QoderSubagentGuardRouter = class extends SubagentGuardRouter {
216
+ // 当前无 override(基类 JSON 输出即 qoder 协议)
217
+ };
218
+ injectProjectDir();
219
+ process.exit(new QoderSubagentGuardRouter().run());
@@ -0,0 +1,298 @@
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 { spawnSync } from "node:child_process";
5
+ import { createHash } from "node:crypto";
6
+
7
+ // templates/core/governance/rules.ts
8
+ var protocol = {
9
+ "exit_codes": {
10
+ "pass": 0,
11
+ "block": 2,
12
+ "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"
13
+ },
14
+ "output": {
15
+ "stdout_json_only": true,
16
+ "stderr_human_text": true,
17
+ "field_separator": "::",
18
+ "magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
19
+ },
20
+ "adapters": {
21
+ "claude": {
22
+ "stdout_form": "plain-text",
23
+ "project_dir_env": "CLAUDE_PROJECT_DIR",
24
+ "magic_dir": ".claude",
25
+ "handlerTypes": [
26
+ "command",
27
+ "mcp_tool"
28
+ ]
29
+ },
30
+ "qoder": {
31
+ "stdout_form": "json",
32
+ "project_dir_env": "QODER_PROJECT_DIR",
33
+ "magic_dir": ".qoder",
34
+ "handlerTypes": [
35
+ "command",
36
+ "http"
37
+ ]
38
+ },
39
+ "codex": {
40
+ "stdout_form": "systemMessage",
41
+ "project_dir_env": "git-toplevel",
42
+ "magic_dir": ".codex",
43
+ "handlerTypes": [
44
+ "command"
45
+ ]
46
+ },
47
+ "vscode": {
48
+ "stdout_form": "plain-text",
49
+ "project_dir_env": "PWD",
50
+ "magic_dir": ".vscode",
51
+ "handlerTypes": [
52
+ "command"
53
+ ]
54
+ },
55
+ "trae": {
56
+ "stdout_form": "plain-text",
57
+ "project_dir_env": "PWD",
58
+ "magic_dir": ".trae",
59
+ "handlerTypes": [
60
+ "command"
61
+ ]
62
+ }
63
+ },
64
+ "event_outputs": {
65
+ "qoder": {
66
+ "SessionStart": "additionalContext",
67
+ "UserPromptSubmit": "additionalContext",
68
+ "PreToolUse": "permissionDecision",
69
+ "Stop": "additionalContext",
70
+ "PostToolUse": "feedback",
71
+ "SubagentStart": "additionalContext",
72
+ "SubagentStop": "additionalContext",
73
+ "PostToolUseFailure": "text",
74
+ "PermissionRequest": "text",
75
+ "SessionEnd": "text",
76
+ "PreCompact": "text",
77
+ "Notification": "text"
78
+ },
79
+ "claude": {
80
+ "SessionStart": "additionalContext",
81
+ "UserPromptSubmit": "additionalContext",
82
+ "PreToolUse": "permissionDecision",
83
+ "Stop": "text",
84
+ "PostToolUse": "feedback",
85
+ "SubagentStart": "text",
86
+ "SubagentStop": "text",
87
+ "PostToolUseFailure": "text",
88
+ "PermissionRequest": "text",
89
+ "SessionEnd": "text",
90
+ "PreCompact": "text",
91
+ "Notification": "text"
92
+ },
93
+ "codex": {
94
+ "Stop": "systemMessage",
95
+ "PostToolUse": "text"
96
+ },
97
+ "vscode": {
98
+ "PostToolUse": "text"
99
+ },
100
+ "trae": {
101
+ "PostToolUse": "text"
102
+ }
103
+ },
104
+ "core": {
105
+ "stdout_form": "json",
106
+ "magic_dir": ".add",
107
+ "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"
108
+ },
109
+ "adapter_defaults": {
110
+ "magic_dir_fallback": ".qoder",
111
+ "probe_magic_dirs": [
112
+ ".claude",
113
+ ".qoder",
114
+ ".vscode",
115
+ ".add",
116
+ ".trae",
117
+ ".codex"
118
+ ]
119
+ }
120
+ };
121
+
122
+ // templates/core/governance/common.ts
123
+ var EXIT_PASS = protocol.exit_codes.pass;
124
+ var EXIT_BLOCK = protocol.exit_codes.block;
125
+ var STATE_SEP = protocol.output.field_separator;
126
+ function readHookInput() {
127
+ if (process.stdin.isTTY) return "{}";
128
+ return readFileSync(0, "utf-8");
129
+ }
130
+ function queryPlanStatus() {
131
+ const magicDir = process.env.MAGIC_DIR;
132
+ if (!magicDir) {
133
+ return {
134
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"magicDir \u672A\u6CE8\u5165\u4E14\u65E0\u6CD5\u4ECE\u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC"}',
135
+ exitCode: 3
136
+ };
137
+ }
138
+ const bridge = join(
139
+ process.env.PROJECT_DIR || process.cwd(),
140
+ magicDir,
141
+ "scripts",
142
+ "plan-status-bridge.ts"
143
+ );
144
+ if (!existsSync(bridge)) {
145
+ return {
146
+ stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"plan-status bridge missing"}',
147
+ exitCode: 3
148
+ };
149
+ }
150
+ const r = spawnSync("node", ["--import", "tsx", bridge], {
151
+ encoding: "utf-8",
152
+ timeout: 1e4
153
+ });
154
+ return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
155
+ }
156
+ function detectActiveAdd() {
157
+ const r = queryPlanStatus();
158
+ if (r.exitCode !== 0) {
159
+ let reason = "database status unavailable";
160
+ try {
161
+ const parsed = JSON.parse(r.stdout);
162
+ if (parsed.reason) reason = parsed.reason;
163
+ } catch {
164
+ }
165
+ return `__STATUS_UNAVAILABLE__::${reason}::database::none::none`;
166
+ }
167
+ let snapshot;
168
+ try {
169
+ snapshot = JSON.parse(r.stdout);
170
+ } catch {
171
+ return null;
172
+ }
173
+ if (!(snapshot.availability === "READY" && snapshot.isActive === true)) return null;
174
+ const done = snapshot.progress?.doneTasks ?? 0;
175
+ const total = snapshot.progress?.totalTasks ?? 0;
176
+ const approval = snapshot.approvalStatus ?? "none";
177
+ return `${snapshot.planName}::${done}/${total}::${approval}::none::none`;
178
+ }
179
+ function localIsoSeconds() {
180
+ const d = /* @__PURE__ */ new Date();
181
+ const pad = (n) => String(n).padStart(2, "0");
182
+ const offsetMin = -d.getTimezoneOffset();
183
+ const sign = offsetMin >= 0 ? "+" : "-";
184
+ const offAbs = Math.abs(offsetMin);
185
+ const off = `${sign}${pad(Math.floor(offAbs / 60))}:${pad(offAbs % 60)}`;
186
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${off}`;
187
+ }
188
+ function projectHash() {
189
+ try {
190
+ return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
191
+ `).digest("hex").slice(0, 8);
192
+ } catch {
193
+ return "default";
194
+ }
195
+ }
196
+ var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
197
+
198
+ // templates/core/governance/subagent-stop-router.ts
199
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
200
+ var SubagentStopRouter = class {
201
+ /** 对齐 bash jq -r '.agent_type // .subagent_name // "unknown"':
202
+ * 空输入/非法 JSON → jq 无输出 → 空串(不是 "unknown");字段缺失才回退 "unknown" */
203
+ agentNameFrom(input2) {
204
+ if (input2.trim() === "") return "";
205
+ try {
206
+ const parsed = JSON.parse(input2);
207
+ const v = parsed.agent_type ?? parsed.subagent_name;
208
+ return typeof v === "string" && v !== "" ? v : "unknown";
209
+ } catch {
210
+ return "";
211
+ }
212
+ }
213
+ // ─────────────────────────── 扩展点 ───────────────────────────
214
+ /** ① 无活跃 Plan 告警(core: stderr;qoder 子类 override: stdout JSON) */
215
+ emitNoPlanWarn(agentName) {
216
+ process.stderr.write(`[ADD SubagentStop] \u26A0\uFE0F ${agentName} \u5DF2\u5B8C\u6210\uFF0C\u4F46\u65E0\u6D3B\u8DC3 ADD Plan \u65E0\u6CD5\u6821\u9A8C\u8FB9\u754C
217
+ `);
218
+ }
219
+ /** ② 边界通过审计(core: stderr;qoder 子类 override: stdout JSON) */
220
+ emitBoundaryPass(agentName, planKw, _handoff) {
221
+ process.stderr.write(`[ADD SubagentStop] ${agentName} \u5DF2\u5B8C\u6210 \u2014 \u5173\u8054 Plan: ${planKw}
222
+ `);
223
+ process.stderr.write(`[ADD SubagentStop] ${agentName} \u8FB9\u754C\u6821\u9A8C\u901A\u8FC7 \u2014 ${localIsoSeconds()}
224
+ `);
225
+ }
226
+ /** 交付物越界检查开关(core: true;qoder: false——qoder 轻量提示无阻断) */
227
+ checkDeliverables() {
228
+ return true;
229
+ }
230
+ /** 主路由:返回 exit code(0 放行 / 2 阻断) */
231
+ run(input2) {
232
+ const agentName = this.agentNameFrom(input2);
233
+ const state = detectActiveAdd();
234
+ if (state === null) {
235
+ this.emitNoPlanWarn(agentName);
236
+ return 0;
237
+ }
238
+ const planKw = state.split("::")[0] ?? "";
239
+ const handoff = state.split("::")[3] ?? "";
240
+ if (this.checkDeliverables() && handoff && handoff !== "none" && existsSync2(handoff)) {
241
+ const deliverablesRaw = (() => {
242
+ try {
243
+ const parsed = JSON.parse(input2);
244
+ return typeof parsed.deliverables === "string" ? parsed.deliverables : "";
245
+ } catch {
246
+ return "";
247
+ }
248
+ })();
249
+ if (deliverablesRaw !== "") {
250
+ const handoffContent = readFileSync2(handoff, "utf-8");
251
+ const violations = [];
252
+ for (const f of deliverablesRaw.split(/\s+/)) {
253
+ if (f === "") continue;
254
+ if (!handoffContent.includes(f)) violations.push(f);
255
+ }
256
+ if (violations.length > 0) {
257
+ process.stderr.write(`[ADD SubagentStop] \u274C ${agentName} \u4EA4\u4ED8\u7269\u8D85\u51FA spec \u8FB9\u754C: ${violations.join(" ")}
258
+ `);
259
+ process.stderr.write("[ADD SubagentStop] \u8981\u6C42\u91CD\u505A\u2014\u2014\u8BF7\u68C0\u67E5\u8FD9\u4E9B\u6587\u4EF6\u662F\u5426\u5C5E\u4E8E\u672C\u8F6E spec \u8303\u56F4\n");
260
+ return EXIT_BLOCK;
261
+ }
262
+ }
263
+ }
264
+ this.emitBoundaryPass(agentName, planKw, handoff);
265
+ return 0;
266
+ }
267
+ };
268
+
269
+ // templates/adapters/qoder/hooks/lib/qoder-env.ts
270
+ function resolveQoderProjectDir() {
271
+ return process.env.CLAUDE_PROJECT_DIR || process.env.QODER_PROJECT_DIR || process.env.QODERCN_PROJECT_DIR || process.cwd();
272
+ }
273
+ function injectProjectDir() {
274
+ const dir = resolveQoderProjectDir();
275
+ process.env.PROJECT_DIR = dir;
276
+ return dir;
277
+ }
278
+
279
+ // templates/adapters/qoder/hooks/subagent-stop.ts
280
+ var QoderSubagentStopRouter = class extends SubagentStopRouter {
281
+ /** ① qoder 轻量提示无阻断(bash 原文无交付物越界检查) */
282
+ checkDeliverables() {
283
+ return false;
284
+ }
285
+ /** ② 无活跃 Plan: stdout JSON(qoder 协议) */
286
+ emitNoPlanWarn(agentName) {
287
+ const ctx = `[ADD SubagentStop] \u26A0\uFE0F ${agentName} \u5DF2\u5B8C\u6210\uFF0C\u4F46\u65E0\u6D3B\u8DC3 ADD Plan \u65E0\u6CD5\u6821\u9A8C\u8FB9\u754C\u3002`;
288
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "SubagentStop", additionalContext: ctx } }) + "\n");
289
+ }
290
+ /** ③ 边界通过: stdout JSON(qoder 协议——bash 原文逐字文本) */
291
+ emitBoundaryPass(agentName, planKw, handoff) {
292
+ const ctx = `[ADD SubagentStop] ${agentName} \u5B50\u4EE3\u7406\u7ED3\u675F\u3002Plan: ${planKw}, handoff: ${handoff}\u3002\u8FB9\u754C\u6821\u9A8C\u901A\u8FC7\uFF0C\u5BA1\u8BA1\u5DF2\u805A\u5408\u3002`;
293
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "SubagentStop", additionalContext: ctx } }) + "\n");
294
+ }
295
+ };
296
+ injectProjectDir();
297
+ var input = readHookInput();
298
+ process.exit(new QoderSubagentStopRouter().run(input));