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,789 @@
|
|
|
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 event = {
|
|
10
|
+
"file": {
|
|
11
|
+
"path": "{magicDir}/reports/hook-events.jsonl",
|
|
12
|
+
"rotate_bytes": 262144,
|
|
13
|
+
"total_bytes": 524288,
|
|
14
|
+
"note": "MCP Server \u5B95\u673A\u4E0D\u4E22\u4E8B\u4EF6\uFF0C\u91CD\u542F\u540E\u4ECE\u6587\u4EF6\u6062\u590D\u6D88\u8D39"
|
|
15
|
+
},
|
|
16
|
+
"schema": {
|
|
17
|
+
"fields": [
|
|
18
|
+
"ts",
|
|
19
|
+
"hook",
|
|
20
|
+
"decision",
|
|
21
|
+
"cmd",
|
|
22
|
+
"reason",
|
|
23
|
+
"planKeyword",
|
|
24
|
+
"planStatus"
|
|
25
|
+
],
|
|
26
|
+
"ts_format": "date -u +%Y-%m-%dT%H:%M:%SZ",
|
|
27
|
+
"extra_fields": [
|
|
28
|
+
"anchor_hit",
|
|
29
|
+
"struct_score",
|
|
30
|
+
"override"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"daily": {
|
|
34
|
+
"warn_threshold": 10
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var protocol = {
|
|
38
|
+
"exit_codes": {
|
|
39
|
+
"pass": 0,
|
|
40
|
+
"block": 2,
|
|
41
|
+
"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"
|
|
42
|
+
},
|
|
43
|
+
"output": {
|
|
44
|
+
"stdout_json_only": true,
|
|
45
|
+
"stderr_human_text": true,
|
|
46
|
+
"field_separator": "::",
|
|
47
|
+
"magic_dir_resolution": "\u6CE8\u5165\u4F18\u5148 \u2192 \u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC \u2192 failClosed\uFF08\u7981\u6B62\u731C\u6D4B adapter \u540D\uFF09"
|
|
48
|
+
},
|
|
49
|
+
"adapters": {
|
|
50
|
+
"claude": {
|
|
51
|
+
"stdout_form": "plain-text",
|
|
52
|
+
"project_dir_env": "CLAUDE_PROJECT_DIR",
|
|
53
|
+
"magic_dir": ".claude",
|
|
54
|
+
"handlerTypes": [
|
|
55
|
+
"command",
|
|
56
|
+
"mcp_tool"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"qoder": {
|
|
60
|
+
"stdout_form": "json",
|
|
61
|
+
"project_dir_env": "QODER_PROJECT_DIR",
|
|
62
|
+
"magic_dir": ".qoder",
|
|
63
|
+
"handlerTypes": [
|
|
64
|
+
"command",
|
|
65
|
+
"http"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"codex": {
|
|
69
|
+
"stdout_form": "systemMessage",
|
|
70
|
+
"project_dir_env": "git-toplevel",
|
|
71
|
+
"magic_dir": ".codex",
|
|
72
|
+
"handlerTypes": [
|
|
73
|
+
"command"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"vscode": {
|
|
77
|
+
"stdout_form": "plain-text",
|
|
78
|
+
"project_dir_env": "PWD",
|
|
79
|
+
"magic_dir": ".vscode",
|
|
80
|
+
"handlerTypes": [
|
|
81
|
+
"command"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
"trae": {
|
|
85
|
+
"stdout_form": "plain-text",
|
|
86
|
+
"project_dir_env": "PWD",
|
|
87
|
+
"magic_dir": ".trae",
|
|
88
|
+
"handlerTypes": [
|
|
89
|
+
"command"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"event_outputs": {
|
|
94
|
+
"qoder": {
|
|
95
|
+
"SessionStart": "additionalContext",
|
|
96
|
+
"UserPromptSubmit": "additionalContext",
|
|
97
|
+
"PreToolUse": "permissionDecision",
|
|
98
|
+
"Stop": "additionalContext",
|
|
99
|
+
"PostToolUse": "feedback",
|
|
100
|
+
"SubagentStart": "additionalContext",
|
|
101
|
+
"SubagentStop": "additionalContext",
|
|
102
|
+
"PostToolUseFailure": "text",
|
|
103
|
+
"PermissionRequest": "text",
|
|
104
|
+
"SessionEnd": "text",
|
|
105
|
+
"PreCompact": "text",
|
|
106
|
+
"Notification": "text"
|
|
107
|
+
},
|
|
108
|
+
"claude": {
|
|
109
|
+
"SessionStart": "additionalContext",
|
|
110
|
+
"UserPromptSubmit": "additionalContext",
|
|
111
|
+
"PreToolUse": "permissionDecision",
|
|
112
|
+
"Stop": "text",
|
|
113
|
+
"PostToolUse": "feedback",
|
|
114
|
+
"SubagentStart": "text",
|
|
115
|
+
"SubagentStop": "text",
|
|
116
|
+
"PostToolUseFailure": "text",
|
|
117
|
+
"PermissionRequest": "text",
|
|
118
|
+
"SessionEnd": "text",
|
|
119
|
+
"PreCompact": "text",
|
|
120
|
+
"Notification": "text"
|
|
121
|
+
},
|
|
122
|
+
"codex": {
|
|
123
|
+
"Stop": "systemMessage",
|
|
124
|
+
"PostToolUse": "text"
|
|
125
|
+
},
|
|
126
|
+
"vscode": {
|
|
127
|
+
"PostToolUse": "text"
|
|
128
|
+
},
|
|
129
|
+
"trae": {
|
|
130
|
+
"PostToolUse": "text"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"core": {
|
|
134
|
+
"stdout_form": "json",
|
|
135
|
+
"magic_dir": ".add",
|
|
136
|
+
"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"
|
|
137
|
+
},
|
|
138
|
+
"adapter_defaults": {
|
|
139
|
+
"magic_dir_fallback": ".qoder",
|
|
140
|
+
"probe_magic_dirs": [
|
|
141
|
+
".claude",
|
|
142
|
+
".qoder",
|
|
143
|
+
".vscode",
|
|
144
|
+
".add",
|
|
145
|
+
".trae",
|
|
146
|
+
".codex"
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
|
|
152
|
+
import path2 from "node:path";
|
|
153
|
+
|
|
154
|
+
// node_modules/.pnpm/locate-path@8.0.0/node_modules/locate-path/index.js
|
|
155
|
+
import process2 from "node:process";
|
|
156
|
+
import path from "node:path";
|
|
157
|
+
import fs, { promises as fsPromises } from "node:fs";
|
|
158
|
+
import { fileURLToPath } from "node:url";
|
|
159
|
+
var typeMappings = {
|
|
160
|
+
directory: "isDirectory",
|
|
161
|
+
file: "isFile"
|
|
162
|
+
};
|
|
163
|
+
function checkType(type) {
|
|
164
|
+
if (type === "both" || Object.hasOwn(typeMappings, type)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
throw new Error(`Invalid type specified: ${type}`);
|
|
168
|
+
}
|
|
169
|
+
var matchType = (type, stat) => type === "both" ? stat.isFile() || stat.isDirectory() : stat[typeMappings[type]]();
|
|
170
|
+
var toPath = (urlOrPath) => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
|
|
171
|
+
function locatePathSync(paths, {
|
|
172
|
+
cwd = process2.cwd(),
|
|
173
|
+
type = "file",
|
|
174
|
+
allowSymlinks = true
|
|
175
|
+
} = {}) {
|
|
176
|
+
checkType(type);
|
|
177
|
+
cwd = toPath(cwd);
|
|
178
|
+
const statFunction = allowSymlinks ? fs.statSync : fs.lstatSync;
|
|
179
|
+
for (const path_ of paths) {
|
|
180
|
+
try {
|
|
181
|
+
const stat = statFunction(path.resolve(cwd, path_), {
|
|
182
|
+
throwIfNoEntry: false
|
|
183
|
+
});
|
|
184
|
+
if (!stat) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (matchType(type, stat)) {
|
|
188
|
+
return path_;
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// node_modules/.pnpm/unicorn-magic@0.3.0/node_modules/unicorn-magic/node.js
|
|
196
|
+
import { promisify } from "node:util";
|
|
197
|
+
import { execFile as execFileCallback, execFileSync as execFileSyncOriginal } from "node:child_process";
|
|
198
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
199
|
+
var execFileOriginal = promisify(execFileCallback);
|
|
200
|
+
function toPath2(urlOrPath) {
|
|
201
|
+
return urlOrPath instanceof URL ? fileURLToPath2(urlOrPath) : urlOrPath;
|
|
202
|
+
}
|
|
203
|
+
var TEN_MEGABYTES_IN_BYTES = 10 * 1024 * 1024;
|
|
204
|
+
|
|
205
|
+
// node_modules/.pnpm/find-up@8.0.0/node_modules/find-up/index.js
|
|
206
|
+
var findUpStop = /* @__PURE__ */ Symbol("findUpStop");
|
|
207
|
+
function findUpMultipleSync(name, options = {}) {
|
|
208
|
+
let directory = path2.resolve(toPath2(options.cwd) ?? "");
|
|
209
|
+
const { root } = path2.parse(directory);
|
|
210
|
+
const stopAt = path2.resolve(directory, toPath2(options.stopAt) ?? root);
|
|
211
|
+
const limit = options.limit ?? Number.POSITIVE_INFINITY;
|
|
212
|
+
const paths = [name].flat();
|
|
213
|
+
const runMatcher = (locateOptions) => {
|
|
214
|
+
if (typeof name !== "function") {
|
|
215
|
+
return locatePathSync(paths, locateOptions);
|
|
216
|
+
}
|
|
217
|
+
const foundPath = name(locateOptions.cwd);
|
|
218
|
+
if (typeof foundPath === "string") {
|
|
219
|
+
return locatePathSync([foundPath], locateOptions);
|
|
220
|
+
}
|
|
221
|
+
return foundPath;
|
|
222
|
+
};
|
|
223
|
+
const matches = [];
|
|
224
|
+
while (true) {
|
|
225
|
+
const foundPath = runMatcher({ ...options, cwd: directory });
|
|
226
|
+
if (foundPath === findUpStop) {
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
if (foundPath) {
|
|
230
|
+
matches.push(path2.resolve(directory, foundPath));
|
|
231
|
+
}
|
|
232
|
+
if (directory === stopAt || matches.length >= limit) {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
directory = path2.dirname(directory);
|
|
236
|
+
}
|
|
237
|
+
return matches;
|
|
238
|
+
}
|
|
239
|
+
function findUpSync(name, options = {}) {
|
|
240
|
+
const matches = findUpMultipleSync(name, { ...options, limit: 1 });
|
|
241
|
+
return matches[0];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// templates/core/governance/common.ts
|
|
245
|
+
var EXIT_PASS = protocol.exit_codes.pass;
|
|
246
|
+
var EXIT_BLOCK = protocol.exit_codes.block;
|
|
247
|
+
var STATE_SEP = protocol.output.field_separator;
|
|
248
|
+
function readHookInput() {
|
|
249
|
+
if (process.stdin.isTTY) return "{}";
|
|
250
|
+
return readFileSync(0, "utf-8");
|
|
251
|
+
}
|
|
252
|
+
function jsonGet(json, field) {
|
|
253
|
+
const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`);
|
|
254
|
+
const m = re.exec(json);
|
|
255
|
+
return m?.[1] ?? "";
|
|
256
|
+
}
|
|
257
|
+
function tryResolveMagicDir() {
|
|
258
|
+
const injected = process.env.MAGIC_DIR;
|
|
259
|
+
if (injected) return injected;
|
|
260
|
+
const startDir = dirname(fileURLToPath3(import.meta.url));
|
|
261
|
+
const hit = findUpSync((dir) => basename(dir).startsWith(".") ? dir : void 0, {
|
|
262
|
+
cwd: startDir,
|
|
263
|
+
type: "directory"
|
|
264
|
+
});
|
|
265
|
+
return hit ? basename(hit) : "";
|
|
266
|
+
}
|
|
267
|
+
function queryPlanStatus() {
|
|
268
|
+
const magicDir = process.env.MAGIC_DIR;
|
|
269
|
+
if (!magicDir) {
|
|
270
|
+
return {
|
|
271
|
+
stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"magicDir \u672A\u6CE8\u5165\u4E14\u65E0\u6CD5\u4ECE\u7269\u7406\u4F4D\u7F6E\u63A8\u5BFC"}',
|
|
272
|
+
exitCode: 3
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const bridge = join(
|
|
276
|
+
process.env.PROJECT_DIR || process.cwd(),
|
|
277
|
+
magicDir,
|
|
278
|
+
"scripts",
|
|
279
|
+
"plan-status-bridge.ts"
|
|
280
|
+
);
|
|
281
|
+
if (!existsSync(bridge)) {
|
|
282
|
+
return {
|
|
283
|
+
stdout: '{"availability":"STATUS_UNAVAILABLE","source":"database","reason":"plan-status bridge missing"}',
|
|
284
|
+
exitCode: 3
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const r = spawnSync("node", ["--import", "tsx", bridge], {
|
|
288
|
+
encoding: "utf-8",
|
|
289
|
+
timeout: 1e4
|
|
290
|
+
});
|
|
291
|
+
return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
|
|
292
|
+
}
|
|
293
|
+
function detectActiveAdd() {
|
|
294
|
+
const r = queryPlanStatus();
|
|
295
|
+
if (r.exitCode !== 0) {
|
|
296
|
+
let reason = "database status unavailable";
|
|
297
|
+
try {
|
|
298
|
+
const parsed = JSON.parse(r.stdout);
|
|
299
|
+
if (parsed.reason) reason = parsed.reason;
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
return `__STATUS_UNAVAILABLE__::${reason}::database::none::none`;
|
|
303
|
+
}
|
|
304
|
+
let snapshot;
|
|
305
|
+
try {
|
|
306
|
+
snapshot = JSON.parse(r.stdout);
|
|
307
|
+
} catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
if (!(snapshot.availability === "READY" && snapshot.isActive === true)) return null;
|
|
311
|
+
const done = snapshot.progress?.doneTasks ?? 0;
|
|
312
|
+
const total = snapshot.progress?.totalTasks ?? 0;
|
|
313
|
+
const approval = snapshot.approvalStatus ?? "none";
|
|
314
|
+
return `${snapshot.planName}::${done}/${total}::${approval}::none::none`;
|
|
315
|
+
}
|
|
316
|
+
function localIsoSeconds() {
|
|
317
|
+
const d = /* @__PURE__ */ new Date();
|
|
318
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
319
|
+
const offsetMin = -d.getTimezoneOffset();
|
|
320
|
+
const sign = offsetMin >= 0 ? "+" : "-";
|
|
321
|
+
const offAbs = Math.abs(offsetMin);
|
|
322
|
+
const off = `${sign}${pad(Math.floor(offAbs / 60))}:${pad(offAbs % 60)}`;
|
|
323
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${off}`;
|
|
324
|
+
}
|
|
325
|
+
function projectHash() {
|
|
326
|
+
try {
|
|
327
|
+
return createHash("md5").update(`${process.env.PROJECT_DIR || process.cwd()}
|
|
328
|
+
`).digest("hex").slice(0, 8);
|
|
329
|
+
} catch {
|
|
330
|
+
return "default";
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
|
|
334
|
+
function isAlreadyAccepted(addRoute, handoff) {
|
|
335
|
+
if (addRoute && existsSync(addRoute)) {
|
|
336
|
+
const content = readFileSync(addRoute, "utf-8");
|
|
337
|
+
const step8 = content.match(/Step 8[\s\S]{0,2000}/)?.[0] ?? "";
|
|
338
|
+
if (/\[x\].*验证并更新项目状态/.test(step8)) {
|
|
339
|
+
if (handoff && existsSync(handoff)) {
|
|
340
|
+
const h = readFileSync(handoff, "utf-8");
|
|
341
|
+
if (/✅.*验收|收敛|全部闭环|全部.*完成/.test(h)) {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// templates/core/governance/prompt-router.ts
|
|
351
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
352
|
+
import { join as join4 } from "node:path";
|
|
353
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
354
|
+
|
|
355
|
+
// templates/core/governance/vocabulary.ts
|
|
356
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
357
|
+
import { join as join2 } from "node:path";
|
|
358
|
+
function serializeTrigger(e) {
|
|
359
|
+
return `${e.priority}::${e.regex}::${e.action}`;
|
|
360
|
+
}
|
|
361
|
+
function vocabularyFile() {
|
|
362
|
+
const magicDir = tryResolveMagicDir();
|
|
363
|
+
if (!magicDir) return "";
|
|
364
|
+
return join2(
|
|
365
|
+
process.env.PROJECT_DIR || process.cwd(),
|
|
366
|
+
magicDir,
|
|
367
|
+
"vocabulary",
|
|
368
|
+
"add-governance-vocabulary.md"
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
function mapTableRow(line, map) {
|
|
372
|
+
const cells = line.split("|");
|
|
373
|
+
const prio = cells[1]?.trim() ?? "";
|
|
374
|
+
const rawTrigger = cells[2] ?? "";
|
|
375
|
+
const action = cells[3]?.trim() ?? "";
|
|
376
|
+
const trigger = rawTrigger.replace(/`/g, "").replace(/ *\/ */g, "|").trim();
|
|
377
|
+
if (!trigger) return null;
|
|
378
|
+
return map(prio, trigger, action);
|
|
379
|
+
}
|
|
380
|
+
function loadTriggers() {
|
|
381
|
+
const file = vocabularyFile();
|
|
382
|
+
if (!file || !existsSync2(file)) return [];
|
|
383
|
+
const lines = readFileSync2(file, "utf-8").split("\n");
|
|
384
|
+
const out = [];
|
|
385
|
+
let inRange = false;
|
|
386
|
+
for (const line of lines) {
|
|
387
|
+
if (/^## 类别 A: 文档类型/.test(line)) inRange = true;
|
|
388
|
+
if (/^## 类别 [G-Z]/.test(line)) inRange = false;
|
|
389
|
+
if (!inRange) continue;
|
|
390
|
+
if (!/^\| (P0|P1|P2) /.test(line)) continue;
|
|
391
|
+
const entry = mapTableRow(line, (priority, regex, action) => ({ priority, regex, action }));
|
|
392
|
+
if (entry) out.push(entry);
|
|
393
|
+
}
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
396
|
+
function isDevKeywordLine(regex) {
|
|
397
|
+
return /修\.?bug|fix\.?bug/i.test(regex);
|
|
398
|
+
}
|
|
399
|
+
function matchTrigger(prompt) {
|
|
400
|
+
return loadTriggers().flatMap(({ regex, action }) => {
|
|
401
|
+
if (isDevKeywordLine(regex)) return [];
|
|
402
|
+
try {
|
|
403
|
+
return new RegExp(regex, "i").test(prompt) ? [`[ADD \u89E6\u53D1] ${regex} \u2192 ${action}`] : [];
|
|
404
|
+
} catch {
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function loadDevKeywords() {
|
|
410
|
+
return loadTriggers().filter((e) => serializeTrigger(e).includes("\u5F00\u53D1|\u6539\u529F\u80FD")).map((e) => e.regex);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// templates/core/governance/notify.ts
|
|
414
|
+
import { existsSync as existsSync3, mkdirSync, renameSync, statSync, appendFileSync } from "node:fs";
|
|
415
|
+
import { join as join3 } from "node:path";
|
|
416
|
+
function writeHookEvent(hook, decision, cmd, reason, plan = "unknown", status = "none", extra = "", magicDirOverride) {
|
|
417
|
+
const defaults = protocol.adapter_defaults;
|
|
418
|
+
const fallback = defaults?.magic_dir_fallback ?? ".qoder";
|
|
419
|
+
const dir = join3(magicDirOverride ?? (process.env.MAGIC_DIR || fallback), "reports");
|
|
420
|
+
try {
|
|
421
|
+
mkdirSync(dir, { recursive: true });
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
const file = join3(dir, "hook-events.jsonl");
|
|
425
|
+
if (existsSync3(file)) {
|
|
426
|
+
let sz = 0;
|
|
427
|
+
try {
|
|
428
|
+
sz = statSync(file).size;
|
|
429
|
+
} catch {
|
|
430
|
+
sz = 0;
|
|
431
|
+
}
|
|
432
|
+
if (sz > event.file.rotate_bytes) {
|
|
433
|
+
try {
|
|
434
|
+
renameSync(file, `${file}.old`);
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
440
|
+
const extraPart = extra ? `,${extra}` : "";
|
|
441
|
+
const line = `{"ts":"${ts}","hook":"${hook}","decision":"${decision}","cmd":"${cmd}","reason":"${reason}","planKeyword":"${plan}","planStatus":"${status}"${extraPart}}
|
|
442
|
+
`;
|
|
443
|
+
appendFileSync(file, line);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// templates/core/governance/prompt-router.ts
|
|
447
|
+
var PromptRouter = class {
|
|
448
|
+
magicDir;
|
|
449
|
+
constructor(magicDir) {
|
|
450
|
+
this.magicDir = magicDir;
|
|
451
|
+
}
|
|
452
|
+
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
453
|
+
/** prompt 提取(core: jsonGet) */
|
|
454
|
+
extractPrompt(input2) {
|
|
455
|
+
return jsonGet(input2, "prompt");
|
|
456
|
+
}
|
|
457
|
+
/** 验收幂等文本(core 协议: 6 行含 ★ 同步检查) */
|
|
458
|
+
acceptedText() {
|
|
459
|
+
return `[ADD \u9A8C\u6536] \u26A0\uFE0F \u5DF2\u9A8C\u6536\u3002\u8FDB\u5165 Review \u6A21\u5F0F:
|
|
460
|
+
\u2460 \u91CD\u65B0\u68C0\u67E5 checklist [T]/[R] \u9879
|
|
461
|
+
\u2461 \u5BA1\u67E5 audit \u8BB0\u5F55\u5B8C\u6574\u6027
|
|
462
|
+
\u2462 \u5982\u6709\u5DEE\u5F02 \u2192 Review \u56DE\u6D41\u81F3 handoff\uFF08\u589E\u91CF\u66F4\u65B0\uFF0C\u4E0D\u8986\u76D6\u5DF2\u6709\u7ED3\u8BBA\uFF09
|
|
463
|
+
\u2463 \u65E0\u5DEE\u5F02 \u2192 \u8BB0\u5F55 'Review \u5DF2\u786E\u8BA4\uFF0C\u65E0\u65B0\u53D1\u73B0'
|
|
464
|
+
\u2605 \u540C\u6B65\u68C0\u67E5: \u5982 checklist \u6709\u65B0 cuid \u4F46 handoff \u5BA1\u8BA1\u8868\u7F3A\u5931 \u2192 \u66F4\u65B0 handoff ADD-7 \u8868 + query_audit_logs \u547D\u4EE4
|
|
465
|
+
`;
|
|
466
|
+
}
|
|
467
|
+
/** 验收后置处理(core: spawn review-checklist 子进程,输出丢弃) */
|
|
468
|
+
onAccepted(handoff, addRoute) {
|
|
469
|
+
const reviewScript = join4(this.magicDir, "hooks", "review-checklist.mjs");
|
|
470
|
+
if (existsSync4(reviewScript)) {
|
|
471
|
+
spawnSync2(process.execPath, [reviewScript, handoff, addRoute], { stdio: "ignore" });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/** 开发关键词命中提示(core: 无) */
|
|
475
|
+
onDevKwMatched() {
|
|
476
|
+
}
|
|
477
|
+
/** 开发关键词匹配(core: join("|") 单正则;qoder: 逐个正则 some) */
|
|
478
|
+
devKwMatched(prompt, devKw) {
|
|
479
|
+
try {
|
|
480
|
+
return new RegExp(devKw.join("|"), "i").test(prompt);
|
|
481
|
+
} catch {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/** Layer 2 输出通道(core: stderr) */
|
|
486
|
+
layer2ToStderr() {
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
/** Layer 3 状态文本(core: 单行) */
|
|
490
|
+
layer3Text(plan, rounds, step, handoff) {
|
|
491
|
+
return `[ADD \u72B6\u6001] Plan: ${plan}, \u8F6E\u6B21: ${rounds}, Step: ${step}, handoff: ${handoff}
|
|
492
|
+
`;
|
|
493
|
+
}
|
|
494
|
+
/** 日报告警文本(core: 含"或检查 hooks 误报") */
|
|
495
|
+
dailyWarnText(noPlan, threshold) {
|
|
496
|
+
return `[Hook \u26A0\uFE0F] \u65E0 Plan \u63D0\u793A\u5DF2\u8FBE ${noPlan} \u6B21\uFF08\u2265${threshold}\uFF09\uFF0C\u5EFA\u8BAE\u521B\u5EFA Plan \u6216\u68C0\u67E5 hooks \u8BEF\u62A5
|
|
497
|
+
`;
|
|
498
|
+
}
|
|
499
|
+
/** Layer 3 附加注入(core: 无) */
|
|
500
|
+
afterLayer3() {
|
|
501
|
+
}
|
|
502
|
+
/** 日报跳过条件(core: 不跳过;claude: MAGIC_DIR 未设置时跳过) */
|
|
503
|
+
shouldSkipDaily() {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
/** 前置注入(core: 无;qoder: 无条件 "ADD workflow active" JSON) */
|
|
507
|
+
preamble(_input) {
|
|
508
|
+
}
|
|
509
|
+
/** Layer 3 输出形态(core: 纯文本逐行;qoder: hookSpecificOutput JSON 包) */
|
|
510
|
+
layer3Json() {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
/** 日报并入 Layer3 上下文(core: 独立行输出;qoder: 并入 additionalContext) */
|
|
514
|
+
dailyInContext() {
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
// ─────────────────────────── 流程固化 ───────────────────────────
|
|
518
|
+
/** 主路由(模板方法):返回 exit code(0 放行) */
|
|
519
|
+
run(input2) {
|
|
520
|
+
const prompt = this.extractPrompt(input2);
|
|
521
|
+
if (prompt === "") return 0;
|
|
522
|
+
this.preamble(input2);
|
|
523
|
+
const matched = matchTrigger(prompt);
|
|
524
|
+
if (matched.length > 0) {
|
|
525
|
+
if (/验收|收敛/i.test(prompt)) {
|
|
526
|
+
const addState = detectActiveAdd();
|
|
527
|
+
if (addState !== null) {
|
|
528
|
+
const handoff2 = addState.split("::")[3] ?? "";
|
|
529
|
+
const addRoute = addState.split("::")[4] ?? "";
|
|
530
|
+
if (isAlreadyAccepted(addRoute, handoff2)) {
|
|
531
|
+
process.stdout.write(this.acceptedText());
|
|
532
|
+
this.onAccepted(handoff2, addRoute);
|
|
533
|
+
return 0;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
for (const m of matched) process.stdout.write(m + "\n");
|
|
538
|
+
return 0;
|
|
539
|
+
}
|
|
540
|
+
const devKw = loadDevKeywords();
|
|
541
|
+
if (devKw.length === 0) return 0;
|
|
542
|
+
if (!this.devKwMatched(prompt, devKw)) return 0;
|
|
543
|
+
this.onDevKwMatched();
|
|
544
|
+
const state = detectActiveAdd();
|
|
545
|
+
if (state === null) {
|
|
546
|
+
const text = `[ADD \u63D0\u793A] \u68C0\u6D4B\u5230\u5F00\u53D1\u4EFB\u52A1\uFF0C\u4F46\u65E0\u6D3B\u8DC3 ADD Plan\u3002\u5EFA\u8BAE\u5148\u6267\u884C add-paradigm SKILL:
|
|
547
|
+
Step 0: \u6587\u6863\u5148\u884C (Plan \u2192 Review \u2192 Specs)
|
|
548
|
+
Step 3: \u4EE3\u7801\u5B9E\u73B0 + \u5BA1\u8BA1\u690D\u5165
|
|
549
|
+
Step 8: \u6536\u655B\u5224\u65AD
|
|
550
|
+
`;
|
|
551
|
+
if (this.layer2ToStderr()) {
|
|
552
|
+
process.stderr.write(text);
|
|
553
|
+
} else {
|
|
554
|
+
process.stdout.write(text);
|
|
555
|
+
}
|
|
556
|
+
writeHookEvent("prompt-submit", "info", prompt, "\u65E0\u6D3B\u8DC3 ADD Plan \u4E0B\u68C0\u6D4B\u5230\u5F00\u53D1\u4EFB\u52A1", "no-active-plan", "none");
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
const plan = state.split("::")[0] ?? "";
|
|
560
|
+
const step = state.split("::")[1] ?? "";
|
|
561
|
+
const rounds = state.split("::")[2] ?? "";
|
|
562
|
+
const handoff = state.split("::")[3] ?? "";
|
|
563
|
+
let block = this.layer3Text(plan, rounds, step, handoff);
|
|
564
|
+
if (!this.shouldSkipDaily()) {
|
|
565
|
+
const HOOK_JSONL = join4(this.magicDir, "reports", "hook-events.jsonl");
|
|
566
|
+
if (existsSync4(HOOK_JSONL)) {
|
|
567
|
+
const today = localIsoSeconds().slice(0, 10);
|
|
568
|
+
const content = readFileSync3(HOOK_JSONL, "utf-8");
|
|
569
|
+
const todayLines = content.split("\n").filter((l) => l.includes(`"ts":"${today}`));
|
|
570
|
+
const total = todayLines.length;
|
|
571
|
+
const noPlan = todayLines.filter((l) => l.includes('"planKeyword":"no-active-plan"')).length;
|
|
572
|
+
if (total > 0) {
|
|
573
|
+
const dailyLine = `[Hook \u6CBB\u7406] \u4ECA\u65E5\u63D0\u793A: ${total} \u6B21 | \u65E0 Plan \u63D0\u793A: ${noPlan} \u6B21`;
|
|
574
|
+
const warnThreshold = event.daily.warn_threshold;
|
|
575
|
+
const warnLine = noPlan >= warnThreshold ? this.dailyWarnText(noPlan, warnThreshold) : "";
|
|
576
|
+
if (this.dailyInContext()) {
|
|
577
|
+
block += `
|
|
578
|
+
${dailyLine}`;
|
|
579
|
+
if (warnLine) block += `
|
|
580
|
+
${warnLine.trimEnd()}`;
|
|
581
|
+
} else {
|
|
582
|
+
process.stdout.write(dailyLine + "\n");
|
|
583
|
+
if (warnLine) process.stdout.write(warnLine);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (this.layer3Json()) {
|
|
589
|
+
process.stdout.write(
|
|
590
|
+
JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: block.trimEnd() } }) + "\n"
|
|
591
|
+
);
|
|
592
|
+
} else {
|
|
593
|
+
process.stdout.write(block);
|
|
594
|
+
}
|
|
595
|
+
this.afterLayer3();
|
|
596
|
+
return 0;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// templates/core/governance/review-checklist-guard.ts
|
|
601
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync } from "node:fs";
|
|
602
|
+
import { join as join5, basename as basename2 } from "node:path";
|
|
603
|
+
function checkedLines(content) {
|
|
604
|
+
return content.split("\n").filter((l) => l.includes("[x]"));
|
|
605
|
+
}
|
|
606
|
+
function locateSpecFile(specRoot, planKw, name) {
|
|
607
|
+
const specDir = join5(specRoot, planKw);
|
|
608
|
+
const direct = join5(specDir, name);
|
|
609
|
+
if (existsSync5(direct)) return direct;
|
|
610
|
+
if (existsSync5(specRoot)) {
|
|
611
|
+
for (const dir of readdirSync(specRoot)) {
|
|
612
|
+
if (!dir.includes(planKw)) continue;
|
|
613
|
+
const cand = join5(specRoot, dir, name);
|
|
614
|
+
if (existsSync5(cand)) return cand;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return direct;
|
|
618
|
+
}
|
|
619
|
+
function defaultSpecRoot() {
|
|
620
|
+
const projectDir = process.env.PROJECT_DIR || process.cwd();
|
|
621
|
+
const magicDir = tryResolveMagicDir();
|
|
622
|
+
return magicDir ? join5(projectDir, magicDir, "specs") : "";
|
|
623
|
+
}
|
|
624
|
+
function checkReviewQuality(handoff, addRoute, specRoot) {
|
|
625
|
+
if (handoff === "") return "";
|
|
626
|
+
const projectDir = process.env.PROJECT_DIR || process.cwd();
|
|
627
|
+
const planKw = basename2(handoff).replace(/-handoff.*$/, "");
|
|
628
|
+
const root = specRoot ?? defaultSpecRoot();
|
|
629
|
+
const checklist = locateSpecFile(root, planKw, "checklist.md");
|
|
630
|
+
const tasksFile = locateSpecFile(root, planKw, "tasks.md");
|
|
631
|
+
let issues = "";
|
|
632
|
+
let missingDocs = "";
|
|
633
|
+
if (!existsSync5(handoff)) {
|
|
634
|
+
missingDocs += " Handoff";
|
|
635
|
+
} else {
|
|
636
|
+
const h = readFileSync4(handoff, "utf-8");
|
|
637
|
+
let hSections = 0;
|
|
638
|
+
if (h.includes("spec \u6587\u4EF6")) hSections++;
|
|
639
|
+
if (h.includes("\u4F60\u8981\u6539\u7684\u6587\u4EF6")) hSections++;
|
|
640
|
+
if (h.includes("\u9A8C\u8BC1\u6807\u51C6")) hSections++;
|
|
641
|
+
if (h.includes("\u5B8C\u6210\u540E\u8BB0\u5F55 ADD-7 \u5BA1\u8BA1")) hSections++;
|
|
642
|
+
if (hSections < 4) missingDocs += ` Handoff(\u7F3A\u7AE0\u8282:${hSections}/4)`;
|
|
643
|
+
}
|
|
644
|
+
if (!existsSync5(addRoute)) {
|
|
645
|
+
missingDocs += " add-route";
|
|
646
|
+
} else {
|
|
647
|
+
const ar = readFileSync4(addRoute, "utf-8");
|
|
648
|
+
if (!ar.includes("Task \u6620\u5C04\u8868") && !ar.includes("\u6587\u4EF6\u6E05\u5355")) {
|
|
649
|
+
missingDocs += " add-route(\u7F3ATask\u6620\u5C04/\u6587\u4EF6\u6E05\u5355)";
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (!existsSync5(checklist)) {
|
|
653
|
+
missingDocs += " checklist";
|
|
654
|
+
} else {
|
|
655
|
+
const cl = readFileSync4(checklist, "utf-8");
|
|
656
|
+
if (!cl.includes("[T]")) missingDocs += " checklist(\u65E0[T]\u9879)";
|
|
657
|
+
}
|
|
658
|
+
if (!existsSync5(tasksFile)) {
|
|
659
|
+
missingDocs += " tasks";
|
|
660
|
+
} else {
|
|
661
|
+
const tk = readFileSync4(tasksFile, "utf-8");
|
|
662
|
+
if (!/Task|\[ \]/.test(tk)) missingDocs += " tasks(\u65E0Task\u9879)";
|
|
663
|
+
}
|
|
664
|
+
if (missingDocs !== "") {
|
|
665
|
+
return ` \u274C Step 0 \u672A\u5B8C\u6210:${missingDocs.trim().replace(/ /g, ", ")} \u6587\u4EF6\u7F3A\u5931\u3002\u56DE\u9000 Step 0.5/Step 1 \u8865\u5EFA\u540E\u518D\u8FDB\u5165\u4EE3\u7801\u5B9E\u73B0\u3002`;
|
|
666
|
+
}
|
|
667
|
+
if (existsSync5(checklist) && existsSync5(tasksFile)) {
|
|
668
|
+
const cl = readFileSync4(checklist, "utf-8");
|
|
669
|
+
const clChecked = checkedLines(cl).length;
|
|
670
|
+
const clOpen = (cl.match(/\[ \]/g) || []).length;
|
|
671
|
+
const tkChecked = (readFileSync4(tasksFile, "utf-8").match(/\[x\]/g) || []).length;
|
|
672
|
+
if (clOpen > 0) {
|
|
673
|
+
issues += ` \u26A0\uFE0F checklist \u6709 ${clOpen} \u9879\u672A\u52FE\u9009
|
|
674
|
+
`;
|
|
675
|
+
}
|
|
676
|
+
const tItems = (cl.match(/\[T\]/g) || []).length;
|
|
677
|
+
const tChecked = checkedLines(cl).filter((l) => l.includes("[T]")).length;
|
|
678
|
+
if (tItems > tChecked) {
|
|
679
|
+
issues += ` \u26A0\uFE0F [T] \u7F16\u8BD1\u671F\u9A8C\u8BC1: ${tChecked}/${tItems} \u901A\u8FC7
|
|
680
|
+
`;
|
|
681
|
+
}
|
|
682
|
+
let noEvidence = 0;
|
|
683
|
+
for (const line of checkedLines(cl)) {
|
|
684
|
+
if (/—\s*证据:\s*\S/.test(line)) continue;
|
|
685
|
+
if (/npx|tsc|vitest|grep|✅|cmq[0-9a-z]{10}|18\/18|exit.*0/.test(line)) continue;
|
|
686
|
+
noEvidence++;
|
|
687
|
+
}
|
|
688
|
+
if (noEvidence > 0) {
|
|
689
|
+
issues += ` \u274C ${noEvidence} \u9879 [x] \u7F3A\u5C11\u9A8C\u6536\u8BC1\u636E\uFF08\u9700\u9644 \u2014 \u8BC1\u636E: tsc/vitest/grep/\u5BA1\u8BA1ID \u7B49\uFF09
|
|
690
|
+
`;
|
|
691
|
+
}
|
|
692
|
+
const withAudit = checkedLines(cl).filter((l) => /cmq[a-z0-9]{10,}/.test(l)).length;
|
|
693
|
+
const fakeAudit = checkedLines(cl).filter((l) => /cmq\.\.\.|cmqxxx|审计.*cmq\.\./.test(l)).length;
|
|
694
|
+
if (fakeAudit > 0) {
|
|
695
|
+
issues += ` \u274C ${fakeAudit} \u9879 [x] \u4F7F\u7528\u4E86\u5360\u4F4D\u7B26\u5BA1\u8BA1ID\uFF08cmq.../cmqxxx\uFF09\uFF0C\u5FC5\u987B\u8C03 record_dev_operation \u83B7\u53D6\u771F\u5B9E cuid \u540E\u66FF\u6362
|
|
696
|
+
`;
|
|
697
|
+
}
|
|
698
|
+
const withEvidence = checkedLines(cl).filter(
|
|
699
|
+
(l) => /tsc|vitest|npx|grep|✅|验证|确认|compgen|审计.*cmq[a-z0-9]{10}/.test(l)
|
|
700
|
+
).length;
|
|
701
|
+
if (withEvidence > 0 && withAudit === 0) {
|
|
702
|
+
issues += ` \u{1F4CE} \u521D\u9A8C: ${withEvidence}/${clChecked} \u9879\u6709\u8BC1\u636E\u4F46\u672A\u5199\u5BA1\u8BA1 ID\uFF08\u9700\u8C03 record_dev_operation \u843D\u5E93\uFF09
|
|
703
|
+
`;
|
|
704
|
+
} else if (withAudit > 0 && withAudit < clChecked) {
|
|
705
|
+
issues += ` \u{1F4CE} \u590D\u9A8C: ${withAudit}/${clChecked} \u9879\u5F15\u7528\u5BA1\u8BA1 ID\u3002${withEvidence}/${clChecked} \u9879\u6709\u8BC1\u636E\u3002\u8BC1\u636E\u4E00\u81F4\u5219\u4E0D\u9700\u8FFD\u5199 devlog\u65E5\u5FD7(\u8D70mcp)
|
|
706
|
+
`;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (existsSync5(addRoute)) {
|
|
710
|
+
const ar = readFileSync4(addRoute, "utf-8");
|
|
711
|
+
const arOpen = (ar.match(/\[ \]/g) || []).length;
|
|
712
|
+
if (arOpen > 0) {
|
|
713
|
+
issues += ` \u26A0\uFE0F add-route ${arOpen} Step \u672A\u95ED\u73AF
|
|
714
|
+
`;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (existsSync5(handoff) && existsSync5(checklist)) {
|
|
718
|
+
const h = readFileSync4(handoff, "utf-8");
|
|
719
|
+
const cl = readFileSync4(checklist, "utf-8");
|
|
720
|
+
const cuids = [...new Set(cl.match(/cmq[a-z0-9]{10,}/g) || [])];
|
|
721
|
+
const newCuids = cuids.filter((c) => !h.includes(c));
|
|
722
|
+
if (newCuids.length > 0) {
|
|
723
|
+
issues += ` \u274C handoff \u5BA1\u8BA1\u8868\u672A\u540C\u6B65: ${newCuids.length} \u4E2A cuid \u5728 checklist \u4E2D\u5B58\u5728\u4F46 handoff \u4E2D\u7F3A\u5931\uFF08\u9700\u66F4\u65B0 handoff ADD-7 \u8868 + query_audit_logs \u547D\u4EE4\uFF09
|
|
724
|
+
`;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (issues === "") {
|
|
728
|
+
return " \u2705 Review: checklist \u8D28\u91CF\u68C0\u67E5\u901A\u8FC7";
|
|
729
|
+
}
|
|
730
|
+
return ` \u{1F4CB} Review \u53D1\u73B0\u95EE\u9898:
|
|
731
|
+
${issues}`;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// templates/adapters/qoder/hooks/lib/qoder-env.ts
|
|
735
|
+
function resolveQoderProjectDir() {
|
|
736
|
+
return process.env.CLAUDE_PROJECT_DIR || process.env.QODER_PROJECT_DIR || process.env.QODERCN_PROJECT_DIR || process.cwd();
|
|
737
|
+
}
|
|
738
|
+
function injectProjectDir() {
|
|
739
|
+
const dir = resolveQoderProjectDir();
|
|
740
|
+
process.env.PROJECT_DIR = dir;
|
|
741
|
+
return dir;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// templates/adapters/qoder/hooks/prompt-submit.ts
|
|
745
|
+
injectProjectDir();
|
|
746
|
+
var inferred = tryResolveMagicDir();
|
|
747
|
+
if (inferred && !process.env.MAGIC_DIR) process.env.MAGIC_DIR = inferred;
|
|
748
|
+
var MAGIC_DIR = process.env.MAGIC_DIR ?? ".qoder";
|
|
749
|
+
var QoderPromptRouter = class extends PromptRouter {
|
|
750
|
+
/** ① 前置注入: 无条件 JSON(Qoder CN IDE stdout JSON 注入模型上下文) */
|
|
751
|
+
preamble(_input) {
|
|
752
|
+
process.stdout.write(
|
|
753
|
+
JSON.stringify({
|
|
754
|
+
hookSpecificOutput: {
|
|
755
|
+
hookEventName: "UserPromptSubmit",
|
|
756
|
+
additionalContext: "ADD workflow active. Templates preloaded. Use add-paradigm SKILL."
|
|
757
|
+
}
|
|
758
|
+
}) + "\n"
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
/** ② 验收后置: checklist 质量检查内存调用(bash 外部调用 → TS 内存调用,stdout 直出行为等价) */
|
|
762
|
+
onAccepted(handoff, addRoute) {
|
|
763
|
+
process.stdout.write(checkReviewQuality(handoff, addRoute) + "\n");
|
|
764
|
+
}
|
|
765
|
+
/** ③ 开发关键词匹配: 逐个正则 some */
|
|
766
|
+
devKwMatched(prompt, devKw) {
|
|
767
|
+
return devKw.some((kw) => {
|
|
768
|
+
try {
|
|
769
|
+
return new RegExp(kw, "i").test(prompt);
|
|
770
|
+
} catch {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
/** ④ Layer3 JSON 输出 */
|
|
776
|
+
layer3Json() {
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
/** ⑤ 日报并入 additionalContext */
|
|
780
|
+
dailyInContext() {
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
/** ⑥ 日报告警文本(无"或检查 hooks 误报") */
|
|
784
|
+
dailyWarnText(noPlan, threshold) {
|
|
785
|
+
return `[Hook \u26A0\uFE0F] \u65E0 Plan \u63D0\u793A\u5DF2\u8FBE ${noPlan} \u6B21\uFF08\u2265${threshold}\uFF09\uFF0C\u5EFA\u8BAE\u521B\u5EFA Plan`;
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
var input = readHookInput();
|
|
789
|
+
process.exit(new QoderPromptRouter(MAGIC_DIR).run(input));
|