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