immune-brain 3.6.7 → 3.6.9
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/README.md +99 -54
- package/README.zh-CN.md +139 -48
- package/package.json +1 -1
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/package.json +3 -2
- package/plugins/immune-brain/.pi-extension/pi-canary-interaction.ts +149 -4
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +3 -2
- package/plugins/immune-brain/dist/imm-loop.md +3 -0
- package/plugins/immune-brain/dist/imm-planner.md +19 -5
- package/plugins/immune-brain/dist/imm-review-retro.md +123 -0
- package/plugins/immune-brain/dist/registry.yaml +9 -0
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +253 -28
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/skills/imm-review-retro/SKILL.md +23 -0
- package/plugins/immune-brain/skills/imm-review-retro/scripts/review_retro.ts +355 -0
- package/plugins/immune-brain/skills/registry.yaml +9 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Retro: how many code reviews did each model's own code trigger, across recent pi sessions.
|
|
4
|
+
*
|
|
5
|
+
* Usage: bun review_retro.ts <days> [--root <sessions-dir>] [--project <substr>] [--top N]
|
|
6
|
+
*
|
|
7
|
+
* Counting rules (the 口径 that keeps the numbers honest):
|
|
8
|
+
* review executed = Agent(subagent_type="Review") tool call
|
|
9
|
+
* avgSc / pass% = average score (0-10) and PASS rate from [SCORE: ...] tags in Review toolResult
|
|
10
|
+
* kernel:submit_review = the *registration* of that same review, reported separately (never added)
|
|
11
|
+
* attribution = the model behind the most recent edit/write before the review (the code's author)
|
|
12
|
+
* findings = imm_kernel_canary record_finding, deduped per session, harness bookkeeping split out
|
|
13
|
+
*/
|
|
14
|
+
import { createReadStream, existsSync, readdirSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { createInterface } from "node:readline";
|
|
18
|
+
import { pathToFileURL } from "node:url";
|
|
19
|
+
|
|
20
|
+
const BOOKKEEPING = /recorded cleanly|receipt recorded|round recorded|no findings?\b/i;
|
|
21
|
+
const EDIT_TOOLS = new Set(["edit", "write", "multiedit"]);
|
|
22
|
+
const SCORE_RE = /\[SCORE:\s*([\d.]+)\s*(?:\/\s*10)?\]/i;
|
|
23
|
+
const VERDICT_RE = /\[VERDICT:\s*(\w+)\]/i;
|
|
24
|
+
const RISK_RE = /\[RISK:\s*(\w+)\]/i;
|
|
25
|
+
const BLOCK_RE = /\[BLOCKING:\s*(\d+)\]/i;
|
|
26
|
+
const ADVIS_RE = /\[ADVISORY:\s*(\d+)\]/i;
|
|
27
|
+
|
|
28
|
+
type ReviewTag = {
|
|
29
|
+
score: number;
|
|
30
|
+
verdict: string;
|
|
31
|
+
risk: string;
|
|
32
|
+
blocking: number;
|
|
33
|
+
advisory: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function parseReviewTag(text: string): ReviewTag | null {
|
|
37
|
+
if (!text.includes("[SCORE:")) return null;
|
|
38
|
+
const sc = SCORE_RE.exec(text);
|
|
39
|
+
if (!sc) return null;
|
|
40
|
+
const score = Number(sc[1]);
|
|
41
|
+
if (!Number.isFinite(score)) return null;
|
|
42
|
+
return {
|
|
43
|
+
score,
|
|
44
|
+
verdict: (VERDICT_RE.exec(text)?.[1] ?? "UNKNOWN").toUpperCase(),
|
|
45
|
+
risk: (RISK_RE.exec(text)?.[1] ?? "UNKNOWN").toUpperCase(),
|
|
46
|
+
blocking: Number(BLOCK_RE.exec(text)?.[1] ?? 0),
|
|
47
|
+
advisory: Number(ADVIS_RE.exec(text)?.[1] ?? 0),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractText(content: unknown): string {
|
|
52
|
+
if (typeof content === "string") return content;
|
|
53
|
+
if (Array.isArray(content)) {
|
|
54
|
+
return content
|
|
55
|
+
.map((item) => {
|
|
56
|
+
if (typeof item === "string") return item;
|
|
57
|
+
if (item && typeof item === "object") {
|
|
58
|
+
const rec = item as Record<string, unknown>;
|
|
59
|
+
if ("text" in rec) return String(rec.text);
|
|
60
|
+
if ("result" in rec) return String(rec.result);
|
|
61
|
+
}
|
|
62
|
+
return "";
|
|
63
|
+
})
|
|
64
|
+
.join("\n");
|
|
65
|
+
}
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function norm(a: unknown): Record<string, unknown> {
|
|
70
|
+
if (typeof a === "string") {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(a) as Record<string, unknown>;
|
|
73
|
+
} catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return a && typeof a === "object" ? (a as Record<string, unknown>) : {};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function bump(map: Map<string, number>, key: string, n = 1): void {
|
|
81
|
+
map.set(key, (map.get(key) ?? 0) + n);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type Args = { days: number; root: string; project: string; top: number };
|
|
85
|
+
|
|
86
|
+
function parseArgs(argv: string[]): Args {
|
|
87
|
+
const out: Args = {
|
|
88
|
+
days: NaN,
|
|
89
|
+
root: join(homedir(), ".pi/agent/sessions"),
|
|
90
|
+
project: "",
|
|
91
|
+
top: 15,
|
|
92
|
+
};
|
|
93
|
+
const rest: string[] = [];
|
|
94
|
+
for (let i = 0; i < argv.length; i++) {
|
|
95
|
+
const a = argv[i]!;
|
|
96
|
+
if (a === "--root") out.root = argv[++i] ?? "";
|
|
97
|
+
else if (a === "--project") out.project = argv[++i] ?? "";
|
|
98
|
+
else if (a === "--top") out.top = Number(argv[++i]);
|
|
99
|
+
else if (a.startsWith("-")) throw new Error(`unknown flag: ${a}`);
|
|
100
|
+
else rest.push(a);
|
|
101
|
+
}
|
|
102
|
+
out.days = Number(rest[0]);
|
|
103
|
+
if (!(out.days > 0) || rest[1] !== undefined) {
|
|
104
|
+
throw new Error("usage: review_retro.ts <days> [--root <dir>] [--project <substr>] [--top N]");
|
|
105
|
+
}
|
|
106
|
+
if (!Number.isFinite(out.top) || out.top <= 0) throw new Error("--top must be > 0");
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function listJsonl(root: string): string[] {
|
|
111
|
+
if (!existsSync(root)) return [];
|
|
112
|
+
const paths: string[] = [];
|
|
113
|
+
for (const dir of readdirSync(root, { withFileTypes: true })) {
|
|
114
|
+
if (!dir.isDirectory()) continue;
|
|
115
|
+
const folder = join(root, dir.name);
|
|
116
|
+
for (const name of readdirSync(folder)) {
|
|
117
|
+
if (name.endsWith(".jsonl")) paths.push(join(folder, name));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return paths;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function run(argv: string[]): Promise<string> {
|
|
124
|
+
const args = parseArgs(argv);
|
|
125
|
+
const cut = new Date(Date.now() - args.days * 86400000).toISOString().slice(0, 19);
|
|
126
|
+
const home = homedir();
|
|
127
|
+
const dev = new Map<string, number>();
|
|
128
|
+
const turns = new Map<string, number>();
|
|
129
|
+
const rev = new Map<string, number>();
|
|
130
|
+
const sub = new Map<string, number>();
|
|
131
|
+
const tools = new Map<string, number>();
|
|
132
|
+
const proj = new Map<string, number>();
|
|
133
|
+
const episode = new Map<string, Set<string>>();
|
|
134
|
+
const tasks = new Map<string, Set<string>>();
|
|
135
|
+
const findUniq = new Map<string, Set<string>>();
|
|
136
|
+
const findingsRaw = new Map<string, number>();
|
|
137
|
+
const scores = new Map<string, number[]>();
|
|
138
|
+
const verdicts = new Map<string, number>();
|
|
139
|
+
const risks = new Map<string, number>();
|
|
140
|
+
let files = 0;
|
|
141
|
+
|
|
142
|
+
for (const path of listJsonl(args.root)) {
|
|
143
|
+
let editor: string | null = null;
|
|
144
|
+
let cwd = "";
|
|
145
|
+
let used = false;
|
|
146
|
+
const pending = new Map<string, string>();
|
|
147
|
+
const rl = createInterface({ input: createReadStream(path, { encoding: "utf8" }) });
|
|
148
|
+
for await (const raw of rl) {
|
|
149
|
+
const line = raw.trim();
|
|
150
|
+
if (!line) continue;
|
|
151
|
+
let o: Record<string, unknown>;
|
|
152
|
+
try {
|
|
153
|
+
o = JSON.parse(line) as Record<string, unknown>;
|
|
154
|
+
} catch {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (o.type === "session") {
|
|
158
|
+
cwd = String(o.cwd ?? "");
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const m = (o.message && typeof o.message === "object" ? o.message : {}) as Record<string, unknown>;
|
|
162
|
+
const role = m.role;
|
|
163
|
+
if (String(o.timestamp ?? "") < cut) continue;
|
|
164
|
+
if (args.project && !cwd.includes(args.project)) continue;
|
|
165
|
+
|
|
166
|
+
if (role === "assistant") {
|
|
167
|
+
used = true;
|
|
168
|
+
const mo = `${m.provider ?? "?"}/${m.model ?? "?"}`;
|
|
169
|
+
bump(turns, mo);
|
|
170
|
+
const content = Array.isArray(m.content) ? m.content : [];
|
|
171
|
+
for (const c of content) {
|
|
172
|
+
if (!c || typeof c !== "object") continue;
|
|
173
|
+
const call = c as Record<string, unknown>;
|
|
174
|
+
if (call.type !== "toolCall") continue;
|
|
175
|
+
const name = String(call.name ?? "");
|
|
176
|
+
const a = norm(call.arguments);
|
|
177
|
+
const cid = typeof call.id === "string" ? call.id : "";
|
|
178
|
+
bump(tools, name || "?");
|
|
179
|
+
if (EDIT_TOOLS.has(name)) {
|
|
180
|
+
bump(dev, mo);
|
|
181
|
+
editor = mo;
|
|
182
|
+
} else if (name === "Agent" && a.subagent_type === "Review") {
|
|
183
|
+
const owner = editor ?? "no-edit (review-only)";
|
|
184
|
+
bump(rev, owner);
|
|
185
|
+
const ep = episode.get(owner) ?? new Set();
|
|
186
|
+
ep.add(`${path}\0${String(a.description ?? "")}${String(a.prompt ?? "").slice(0, 240)}`);
|
|
187
|
+
episode.set(owner, ep);
|
|
188
|
+
const projKey = `${cwd.replace(home, "~")}\0${owner}`;
|
|
189
|
+
bump(proj, projKey);
|
|
190
|
+
if (cid) pending.set(cid, owner);
|
|
191
|
+
} else if (name === "imm_kernel_canary") {
|
|
192
|
+
const act = norm(a.action);
|
|
193
|
+
const op = act.op;
|
|
194
|
+
const owner = editor ?? "no-edit (review-only)";
|
|
195
|
+
if (op === "submit_review") {
|
|
196
|
+
bump(sub, owner);
|
|
197
|
+
const t = tasks.get(owner) ?? new Set();
|
|
198
|
+
t.add(`${cwd}\0${String(a.task_id ?? "")}`);
|
|
199
|
+
tasks.set(owner, t);
|
|
200
|
+
} else if (op === "record_finding") {
|
|
201
|
+
const f = norm(act.finding);
|
|
202
|
+
const summ = String(f.summary ?? "");
|
|
203
|
+
const kind = BOOKKEEPING.test(summ) ? "bookkeeping" : String(f.kind ?? "");
|
|
204
|
+
const rawKey = `${owner}\0${kind}`;
|
|
205
|
+
bump(findingsRaw, rawKey);
|
|
206
|
+
const uniq = findUniq.get(rawKey) ?? new Set();
|
|
207
|
+
uniq.add(`${path}\0${summ.slice(0, 160)}`);
|
|
208
|
+
findUniq.set(rawKey, uniq);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else if (role === "toolResult") {
|
|
213
|
+
const tcid = String(m.toolCallId ?? "");
|
|
214
|
+
const owner = pending.get(tcid);
|
|
215
|
+
if (!owner) continue;
|
|
216
|
+
pending.delete(tcid);
|
|
217
|
+
const tag = parseReviewTag(extractText(m.content));
|
|
218
|
+
if (!tag) continue;
|
|
219
|
+
const sl = scores.get(owner) ?? [];
|
|
220
|
+
sl.push(tag.score);
|
|
221
|
+
scores.set(owner, sl);
|
|
222
|
+
bump(verdicts, `${owner}\0${tag.verdict}`);
|
|
223
|
+
bump(risks, `${owner}\0${tag.risk}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (used) files += 1;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const lines: string[] = [];
|
|
230
|
+
const emit = (s = "") => lines.push(s);
|
|
231
|
+
emit(`window: last ${args.days}d (UTC >= ${cut}Z) | sessions with activity: ${files} | root: ${args.root}`);
|
|
232
|
+
emit("review = Agent(Review) executed; attributed to the model that last edited the code under review");
|
|
233
|
+
emit("avgSc/pass% = parsed from Review [SCORE: .../10] [VERDICT: ...] tags (shows '-' if untagged)");
|
|
234
|
+
emit("");
|
|
235
|
+
const hdr =
|
|
236
|
+
`${"model".padEnd(42)}${"devEdits".padStart(9)}${"turns".padStart(6)}${"reviews".padStart(8)}${"uniq".padStart(5)}${"rev/100ed".padStart(10)}${"avgSc".padStart(6)}${"pass%".padStart(6)}${"registr".padStart(8)}${"block".padStart(6)}${"advis".padStart(6)}${"noisy".padStart(6)}`;
|
|
237
|
+
emit(hdr);
|
|
238
|
+
emit("-".repeat(hdr.length));
|
|
239
|
+
|
|
240
|
+
const models = [...new Set([...rev.keys(), ...dev.keys()])].sort(
|
|
241
|
+
(a, b) => (rev.get(b) ?? 0) - (rev.get(a) ?? 0),
|
|
242
|
+
);
|
|
243
|
+
const shown: string[] = [];
|
|
244
|
+
for (const mo of models) {
|
|
245
|
+
if (!rev.get(mo) && (dev.get(mo) ?? 0) < 30) continue;
|
|
246
|
+
shown.push(mo);
|
|
247
|
+
const uq = episode.get(mo)?.size ?? 0;
|
|
248
|
+
const d = dev.get(mo) ?? 0;
|
|
249
|
+
const r = rev.get(mo) ?? 0;
|
|
250
|
+
const rate = d ? ((100 * r) / d).toFixed(1) : "-";
|
|
251
|
+
const sl = scores.get(mo) ?? [];
|
|
252
|
+
const avgSc = sl.length ? (sl.reduce((x, y) => x + y, 0) / sl.length).toFixed(1) : "-";
|
|
253
|
+
const passCnt = verdicts.get(`${mo}\0PASS`) ?? 0;
|
|
254
|
+
const passPct = sl.length ? `${Math.round((100 * passCnt) / sl.length)}%` : "-";
|
|
255
|
+
const block = findUniq.get(`${mo}\0blocking`)?.size ?? 0;
|
|
256
|
+
const advis = findUniq.get(`${mo}\0advisory`)?.size ?? 0;
|
|
257
|
+
const noisy = findUniq.get(`${mo}\0bookkeeping`)?.size ?? 0;
|
|
258
|
+
emit(
|
|
259
|
+
`${mo.padEnd(42)}${String(d).padStart(9)}${String(turns.get(mo) ?? 0).padStart(6)}${String(r).padStart(8)}${String(uq).padStart(5)}${rate.padStart(10)}${avgSc.padStart(6)}${passPct.padStart(6)}${String(sub.get(mo) ?? 0).padStart(8)}${String(block).padStart(6)}${String(advis).padStart(6)}${String(noisy).padStart(6)}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
emit("-".repeat(hdr.length));
|
|
263
|
+
const totalScores = [...scores.values()].flat();
|
|
264
|
+
const totAvg = totalScores.length
|
|
265
|
+
? (totalScores.reduce((x, y) => x + y, 0) / totalScores.length).toFixed(1)
|
|
266
|
+
: "-";
|
|
267
|
+
const totPass = shown.reduce((n, mo) => n + (verdicts.get(`${mo}\0PASS`) ?? 0), 0);
|
|
268
|
+
const totPassPct = totalScores.length ? `${Math.round((100 * totPass) / totalScores.length)}%` : "-";
|
|
269
|
+
const totBlock = [...findUniq.entries()].reduce((n, [k, v]) => n + (k.endsWith("\0blocking") ? v.size : 0), 0);
|
|
270
|
+
const totAdvis = [...findUniq.entries()].reduce((n, [k, v]) => n + (k.endsWith("\0advisory") ? v.size : 0), 0);
|
|
271
|
+
const totNoisy = [...findUniq.entries()].reduce((n, [k, v]) => n + (k.endsWith("\0bookkeeping") ? v.size : 0), 0);
|
|
272
|
+
const totDev = [...dev.values()].reduce((a, b) => a + b, 0);
|
|
273
|
+
const totTurns = [...turns.values()].reduce((a, b) => a + b, 0);
|
|
274
|
+
const totRev = [...rev.values()].reduce((a, b) => a + b, 0);
|
|
275
|
+
const totUniq = [...episode.values()].reduce((n, s) => n + s.size, 0);
|
|
276
|
+
const totSub = [...sub.values()].reduce((a, b) => a + b, 0);
|
|
277
|
+
emit(
|
|
278
|
+
`${"TOTAL".padEnd(42)}${String(totDev).padStart(9)}${String(totTurns).padStart(6)}${String(totRev).padStart(8)}${String(totUniq).padStart(5)}${"".padStart(10)}${totAvg.padStart(6)}${totPassPct.padStart(6)}${String(totSub).padStart(8)}${String(totBlock).padStart(6)}${String(totAdvis).padStart(6)}${String(totNoisy).padStart(6)}`,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
emit("");
|
|
282
|
+
emit("=== usage (sessions / turns / edits / tools) ===");
|
|
283
|
+
emit(`sessions: ${files} | turns: ${totTurns} | edits: ${totDev}`);
|
|
284
|
+
const toolRows = [...tools.entries()].sort((a, b) => b[1] - a[1]);
|
|
285
|
+
if (toolRows.length === 0) emit("(no tool calls in this window)");
|
|
286
|
+
else for (const [name, n] of toolRows) emit(`${String(n).padStart(5)} ${name}`);
|
|
287
|
+
|
|
288
|
+
emit("");
|
|
289
|
+
emit("=== review quality & scores (new rubric) ===");
|
|
290
|
+
const scoredModels = [...scores.keys()]
|
|
291
|
+
.filter((m) => (scores.get(m) ?? []).length > 0)
|
|
292
|
+
.sort((a, b) => (scores.get(b)?.length ?? 0) - (scores.get(a)?.length ?? 0));
|
|
293
|
+
if (scoredModels.length) {
|
|
294
|
+
const qHdr = `${"model".padEnd(42)}${"scored".padStart(7)}${"avgScore".padStart(9)}${"PASS".padStart(6)}${"REVISE".padStart(7)}${"REJECT".padStart(7)}${"highRisk".padStart(9)}`;
|
|
295
|
+
emit(qHdr);
|
|
296
|
+
emit("-".repeat(qHdr.length));
|
|
297
|
+
for (const mo of scoredModels) {
|
|
298
|
+
const sl = scores.get(mo) ?? [];
|
|
299
|
+
const avgS = (sl.reduce((x, y) => x + y, 0) / sl.length).toFixed(2);
|
|
300
|
+
const pC = verdicts.get(`${mo}\0PASS`) ?? 0;
|
|
301
|
+
const revC = verdicts.get(`${mo}\0REVISE`) ?? 0;
|
|
302
|
+
const rejC = verdicts.get(`${mo}\0REJECT`) ?? 0;
|
|
303
|
+
const highR = (risks.get(`${mo}\0HIGH`) ?? 0) + (risks.get(`${mo}\0CRITICAL`) ?? 0);
|
|
304
|
+
emit(
|
|
305
|
+
`${mo.padEnd(42)}${String(sl.length).padStart(7)}${avgS.padStart(9)}${String(pC).padStart(6)}${String(revC).padStart(7)}${String(rejC).padStart(7)}${String(highR).padStart(9)}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
emit("(no scored reviews found in this window yet; reviews with [SCORE: .../10] will appear here)");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
emit("");
|
|
313
|
+
emit("=== where the reviews landed (project x author) ===");
|
|
314
|
+
const projRows = [...proj.entries()].sort((a, b) => b[1] - a[1]).slice(0, args.top);
|
|
315
|
+
for (const [key, v] of projRows) {
|
|
316
|
+
const [p, mo] = key.split("\0");
|
|
317
|
+
emit(`${String(v).padStart(5)} ${String(p).padEnd(58)} ${mo}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
emit("");
|
|
321
|
+
emit("=== review rounds per kernel task (rework signal) ===");
|
|
322
|
+
const taskModels = [...tasks.keys()].sort((a, b) => (sub.get(b) ?? 0) - (sub.get(a) ?? 0));
|
|
323
|
+
for (const mo of taskModels) {
|
|
324
|
+
const nt = tasks.get(mo)?.size ?? 0;
|
|
325
|
+
if (!nt) continue;
|
|
326
|
+
const s = sub.get(mo) ?? 0;
|
|
327
|
+
emit(
|
|
328
|
+
`${(s / nt).toFixed(1).padStart(6)} rounds/task ${String(s).padStart(4)} registrations / ${String(nt).padStart(3)} tasks ${mo}`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const rawSum = [...findingsRaw.values()].reduce((a, b) => a + b, 0);
|
|
333
|
+
const uniqSum = [...findUniq.values()].reduce((n, s) => n + s.size, 0);
|
|
334
|
+
if (rawSum && rawSum !== uniqSum) {
|
|
335
|
+
emit("");
|
|
336
|
+
emit(
|
|
337
|
+
`(findings: ${rawSum} raw calls deduped to ${uniqSum} distinct — re-submits of the same finding are counted once)`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
emit("caveats: 'bookkeep' = kernel self-entries mislabelled as findings, excluded from blocking/advisory;");
|
|
341
|
+
emit(" high rounds/task can be canary/QA harness re-registration, not human-visible rework.");
|
|
342
|
+
return `${lines.join("\n")}\n`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const isMain = Boolean(process.argv[1]) && pathToFileURL(process.argv[1]!).href === import.meta.url;
|
|
346
|
+
if (isMain) {
|
|
347
|
+
run(process.argv.slice(2))
|
|
348
|
+
.then((text) => {
|
|
349
|
+
process.stdout.write(text);
|
|
350
|
+
})
|
|
351
|
+
.catch((err: unknown) => {
|
|
352
|
+
console.error(err);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
@@ -56,3 +56,12 @@ skills:
|
|
|
56
56
|
output_artifacts: [maintain_report]
|
|
57
57
|
next_actions: []
|
|
58
58
|
boundary: Minimize tracked agent-instruction context after explicit manifest approval; no Managed authority mutation, contract installation, or reference-document creation.
|
|
59
|
+
- name: imm-review-retro
|
|
60
|
+
path: skills/imm-review-retro/SKILL.md
|
|
61
|
+
role: execute
|
|
62
|
+
title: Review Retro
|
|
63
|
+
role_class: discovery
|
|
64
|
+
canonical: true
|
|
65
|
+
output_artifacts: [retro_report]
|
|
66
|
+
next_actions: []
|
|
67
|
+
boundary: Rank models by review load and report project usage from pi session logs. Read-only. No Managed Path mutation.
|