@theglitchking/semantic-memory 1.6.0 → 1.7.0
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +90 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +49 -29
- package/THIRD_PARTY_NOTICES.md +17 -0
- package/commands/jev-mode.md +18 -0
- package/commands/skill-loop.md +23 -0
- package/dist/{chunk-7PNF3AP2.js → chunk-34VN47IW.js} +2 -2
- package/dist/chunk-3FPUBAEW.js +113 -0
- package/dist/chunk-3FPUBAEW.js.map +1 -0
- package/dist/{chunk-WZEKIUYG.js → chunk-6TIKWOY4.js} +10 -8
- package/dist/{chunk-WZEKIUYG.js.map → chunk-6TIKWOY4.js.map} +1 -1
- package/dist/{chunk-NBY4TRP7.js → chunk-7M34JDTH.js} +3 -3
- package/dist/{chunk-FTRXH2WC.js → chunk-7UAVDOI6.js} +15 -12
- package/dist/chunk-7UAVDOI6.js.map +1 -0
- package/dist/{chunk-BBP3DG2U.js → chunk-AIXQFTSB.js} +25 -26
- package/dist/{chunk-BBP3DG2U.js.map → chunk-AIXQFTSB.js.map} +1 -1
- package/dist/chunk-BEACL25M.js +224 -0
- package/dist/chunk-BEACL25M.js.map +1 -0
- package/dist/chunk-CATPGINC.js +186 -0
- package/dist/chunk-CATPGINC.js.map +1 -0
- package/dist/{chunk-IGHCVAAL.js → chunk-CJUY55GG.js} +6 -3
- package/dist/{chunk-IGHCVAAL.js.map → chunk-CJUY55GG.js.map} +1 -1
- package/dist/chunk-DBFD3MTT.js +130 -0
- package/dist/chunk-DBFD3MTT.js.map +1 -0
- package/dist/{chunk-XAXK7NM4.js → chunk-EXAUVIGW.js} +9 -7
- package/dist/{chunk-XAXK7NM4.js.map → chunk-EXAUVIGW.js.map} +1 -1
- package/dist/{chunk-LGQPP5FQ.js → chunk-EYVFC745.js} +2 -2
- package/dist/chunk-GYR7U6KA.js +80 -0
- package/dist/chunk-GYR7U6KA.js.map +1 -0
- package/dist/{chunk-OEF5U5GV.js → chunk-H2BES6FU.js} +10 -2
- package/dist/{chunk-OEF5U5GV.js.map → chunk-H2BES6FU.js.map} +1 -1
- package/dist/chunk-IEZVFKUT.js +157 -0
- package/dist/chunk-IEZVFKUT.js.map +1 -0
- package/dist/chunk-J7LYDRML.js +9 -0
- package/dist/chunk-J7LYDRML.js.map +1 -0
- package/dist/chunk-MQS7JEBD.js +137 -0
- package/dist/chunk-MQS7JEBD.js.map +1 -0
- package/dist/chunk-NCGM36NX.js +118 -0
- package/dist/chunk-NCGM36NX.js.map +1 -0
- package/dist/chunk-NIZLBNH4.js +165 -0
- package/dist/chunk-NIZLBNH4.js.map +1 -0
- package/dist/chunk-OT3MV7XF.js +123 -0
- package/dist/chunk-OT3MV7XF.js.map +1 -0
- package/dist/chunk-P3GLY42T.js +291 -0
- package/dist/chunk-P3GLY42T.js.map +1 -0
- package/dist/chunk-PJXYCCI2.js +177 -0
- package/dist/chunk-PJXYCCI2.js.map +1 -0
- package/dist/chunk-R6GNP7DS.js +87 -0
- package/dist/chunk-R6GNP7DS.js.map +1 -0
- package/dist/{chunk-44BQDRXD.js → chunk-SHSOCFJH.js} +4 -1
- package/dist/chunk-SHSOCFJH.js.map +1 -0
- package/dist/chunk-TKHWVISV.js +71 -0
- package/dist/chunk-TKHWVISV.js.map +1 -0
- package/dist/{chunk-CAX46XLY.js → chunk-VBLCUWT4.js} +23 -12
- package/dist/chunk-VBLCUWT4.js.map +1 -0
- package/dist/chunk-VQCNWRTL.js +121 -0
- package/dist/chunk-VQCNWRTL.js.map +1 -0
- package/dist/{chunk-SX427M2R.js → chunk-XKGK643R.js} +2 -2
- package/dist/{chunk-FURCIJBO.js → chunk-Y7H76Q7F.js} +6 -3
- package/dist/chunk-Y7H76Q7F.js.map +1 -0
- package/dist/chunk-ZSWI3Y4Y.js +232 -0
- package/dist/chunk-ZSWI3Y4Y.js.map +1 -0
- package/dist/cli/index.js +389 -60
- package/dist/cli/index.js.map +1 -1
- package/dist/core/features.d.ts +38 -0
- package/dist/core/features.js +28 -0
- package/dist/core/index.d.ts +285 -2
- package/dist/core/index.js +170 -20
- package/dist/core/index.js.map +1 -1
- package/dist/core/jev.d.ts +35 -0
- package/dist/core/jev.js +20 -0
- package/dist/core/redact.js +1 -1
- package/dist/core/skill-curator.d.ts +1 -0
- package/dist/core/skill-curator.js +41 -0
- package/dist/core/skill-evidence.d.ts +25 -0
- package/dist/core/skill-evidence.js +23 -0
- package/dist/core/skill-queue.d.ts +40 -0
- package/dist/core/skill-queue.js +38 -0
- package/dist/core/skill-review.d.ts +3 -0
- package/dist/core/skill-review.js +29 -0
- package/dist/core/skill-worker.d.ts +21 -0
- package/dist/core/skill-worker.js +70 -0
- package/dist/core/skill-worker.js.map +1 -0
- package/dist/{dossier-RWTIMGZN.js → dossier-RXBLQOXC.js} +5 -4
- package/dist/failure-eval-IUJO47UC.js +18 -0
- package/dist/healthcheck-S7D6YNTA.js +21 -0
- package/dist/indexer-RZPH2PVL.js +9 -0
- package/dist/{lexicon-IVNJTMWE.js → lexicon-SEJSLBHO.js} +6 -5
- package/dist/lexicon-SEJSLBHO.js.map +1 -0
- package/dist/lint-3JNLRM5U.js +18 -0
- package/dist/lint-3JNLRM5U.js.map +1 -0
- package/dist/log-RM2NDNPH.js +11 -0
- package/dist/log-RM2NDNPH.js.map +1 -0
- package/dist/matter-SNJ2WDL6.js +8 -0
- package/dist/matter-SNJ2WDL6.js.map +1 -0
- package/dist/mcp/server.js +43 -38
- package/dist/mcp/server.js.map +1 -1
- package/dist/{profile-UE4YVTDC.js → profile-SNULTJ27.js} +6 -5
- package/dist/profile-SNULTJ27.js.map +1 -0
- package/dist/{schema-LY6533NJ.js → schema-RDYUK6CC.js} +3 -2
- package/dist/schema-RDYUK6CC.js.map +1 -0
- package/dist/{search-MZC4DC7H.js → search-325ULY57.js} +4 -3
- package/dist/{search-MZC4DC7H.js.map → search-325ULY57.js.map} +1 -1
- package/dist/skill-curator-D7HHZaUI.d.ts +71 -0
- package/dist/skill-land-XU2APPBJ.js +31 -0
- package/dist/skill-land-XU2APPBJ.js.map +1 -0
- package/dist/skill-ledger-AYXVNFX7.js +18 -0
- package/dist/skill-ledger-AYXVNFX7.js.map +1 -0
- package/dist/skill-merge-GXBQLBZA.js +24 -0
- package/dist/skill-merge-GXBQLBZA.js.map +1 -0
- package/dist/skill-review-BaW5n0Zg.d.ts +179 -0
- package/dist/skill-schema-RVB3EHEF.js +30 -0
- package/dist/skill-schema-RVB3EHEF.js.map +1 -0
- package/dist/{telemetry-B4KP3UVE.js → telemetry-YAG3VA4Y.js} +4 -4
- package/dist/telemetry-YAG3VA4Y.js.map +1 -0
- package/docs/regression-snapshot.md +5 -5
- package/hooks/hooks.json +26 -1
- package/hooks/jev-bridge.py +109 -0
- package/hooks/session-start.js +5 -0
- package/hooks/skill-loop.js +65 -0
- package/hooks/vault-context.js +104 -6
- package/package.json +4 -2
- package/dist/chunk-44BQDRXD.js.map +0 -1
- package/dist/chunk-5F5ZIAW2.js +0 -79
- package/dist/chunk-5F5ZIAW2.js.map +0 -1
- package/dist/chunk-CAX46XLY.js.map +0 -1
- package/dist/chunk-FTRXH2WC.js.map +0 -1
- package/dist/chunk-FURCIJBO.js.map +0 -1
- package/dist/failure-eval-37JRNTIW.js +0 -10
- package/dist/healthcheck-3FSNL2U7.js +0 -20
- package/dist/indexer-RAKJ7JR3.js +0 -8
- package/dist/lint-36WTW762.js +0 -17
- package/dist/log-YIXF6CYM.js +0 -11
- /package/dist/{chunk-7PNF3AP2.js.map → chunk-34VN47IW.js.map} +0 -0
- /package/dist/{chunk-NBY4TRP7.js.map → chunk-7M34JDTH.js.map} +0 -0
- /package/dist/{chunk-LGQPP5FQ.js.map → chunk-EYVFC745.js.map} +0 -0
- /package/dist/{chunk-SX427M2R.js.map → chunk-XKGK643R.js.map} +0 -0
- /package/dist/{dossier-RWTIMGZN.js.map → core/features.js.map} +0 -0
- /package/dist/{failure-eval-37JRNTIW.js.map → core/jev.js.map} +0 -0
- /package/dist/{healthcheck-3FSNL2U7.js.map → core/skill-curator.js.map} +0 -0
- /package/dist/{indexer-RAKJ7JR3.js.map → core/skill-evidence.js.map} +0 -0
- /package/dist/{lexicon-IVNJTMWE.js.map → core/skill-queue.js.map} +0 -0
- /package/dist/{lint-36WTW762.js.map → core/skill-review.js.map} +0 -0
- /package/dist/{log-YIXF6CYM.js.map → dossier-RXBLQOXC.js.map} +0 -0
- /package/dist/{profile-UE4YVTDC.js.map → failure-eval-IUJO47UC.js.map} +0 -0
- /package/dist/{schema-LY6533NJ.js.map → healthcheck-S7D6YNTA.js.map} +0 -0
- /package/dist/{telemetry-B4KP3UVE.js.map → indexer-RZPH2PVL.js.map} +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decideScope,
|
|
3
|
+
indexSkill,
|
|
4
|
+
promotionPeers
|
|
5
|
+
} from "./chunk-3FPUBAEW.js";
|
|
6
|
+
import {
|
|
7
|
+
runClaude
|
|
8
|
+
} from "./chunk-IEZVFKUT.js";
|
|
9
|
+
import {
|
|
10
|
+
factIds,
|
|
11
|
+
findSkills,
|
|
12
|
+
lintSkill,
|
|
13
|
+
renderSkill
|
|
14
|
+
} from "./chunk-ZSWI3Y4Y.js";
|
|
15
|
+
import {
|
|
16
|
+
readEvidence,
|
|
17
|
+
stateDir
|
|
18
|
+
} from "./chunk-R6GNP7DS.js";
|
|
19
|
+
import {
|
|
20
|
+
redact
|
|
21
|
+
} from "./chunk-H2BES6FU.js";
|
|
22
|
+
|
|
23
|
+
// src/core/skill-review.ts
|
|
24
|
+
import { createHash } from "crypto";
|
|
25
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
26
|
+
import { basename, join } from "path";
|
|
27
|
+
|
|
28
|
+
// src/core/skill-review-prompt.ts
|
|
29
|
+
var REVIEW_PROMPT = `You review one finished Claude Code session in this repository and decide whether it taught a
|
|
30
|
+
reusable procedure worth saving as a skill \u2014 a short, step-by-step playbook a future session should follow.
|
|
31
|
+
|
|
32
|
+
Draft only from the evidence in the brief below: a command that failed and the fix that worked, a correction
|
|
33
|
+
the user gave, or a learned skill that was used and then failed or was corrected. Never invent steps, commands,
|
|
34
|
+
paths or facts that the brief or the repository does not show. Most sessions teach nothing reusable: then
|
|
35
|
+
answer with no drafts. A draft is worth it only when the same situation is likely to come up again here.
|
|
36
|
+
|
|
37
|
+
Kinds of draft:
|
|
38
|
+
- "new": a procedure no existing skill covers. Check the existing skills in the brief first.
|
|
39
|
+
- "patch": an existing learned skill (listed with "learned: true") was used in this session and then failed or
|
|
40
|
+
was corrected: give its full revised body. Never patch a skill that is pinned or not learned.
|
|
41
|
+
|
|
42
|
+
Each draft:
|
|
43
|
+
- name: kebab-case, specific ("restart-vite-dev-server", not "dev-helper").
|
|
44
|
+
- description: what it does and when to use it, one or two sentences, starting with the action; include
|
|
45
|
+
"Use when ..." with the literal error, command or request that should load it. Under 300 characters.
|
|
46
|
+
- scope: "project" (about this repository) or "directory" (only about one subdirectory; give it in applies_to.paths).
|
|
47
|
+
- applies_to: { paths: [repo-relative paths that exist], commands: [program names the steps run] }.
|
|
48
|
+
- triggers: the literal error strings, commands or user phrases from the brief that this skill answers.
|
|
49
|
+
- provenance: { sessions: [the session id], failures: [failure ids from the brief it came from], corrections: [correction
|
|
50
|
+
numbers from the brief, as strings] } \u2014 at least one failure or correction.
|
|
51
|
+
- cites: knowledge-base fact ids from the brief's list that the steps rely on (only ids that are listed).
|
|
52
|
+
- body: markdown with exactly these sections, in order:
|
|
53
|
+
## When to use \u2014 the situation, in two or three lines.
|
|
54
|
+
## Steps \u2014 numbered, concrete commands or actions as the session finally did them.
|
|
55
|
+
## Pitfalls \u2014 what went wrong in the session and why; each line names the failure id or fact id it comes from.
|
|
56
|
+
## Verify \u2014 one command whose exit code proves it worked, in a fenced code block; or, if none exists, one
|
|
57
|
+
sentence a reviewer can check.
|
|
58
|
+
|
|
59
|
+
Hard rules:
|
|
60
|
+
- No secrets, tokens, passwords, keys, personal data, customer data, names of people or money amounts, even if
|
|
61
|
+
the brief shows them. Use placeholders like <token>.
|
|
62
|
+
- At most 2 drafts. No duplicates of existing skills.
|
|
63
|
+
- Do not write or change any file. You may read files in this repository to get paths and commands right.
|
|
64
|
+
|
|
65
|
+
Answer with one JSON object and nothing else:
|
|
66
|
+
{"drafts": [{"kind": "new" | "patch", "target": "<existing skill name, patch only>", "name": "...",
|
|
67
|
+
"description": "...", "scope": "project" | "directory", "applies_to": {"paths": [], "commands": []},
|
|
68
|
+
"triggers": [], "provenance": {"sessions": [], "failures": [], "corrections": []}, "cites": [], "body": "..."}],
|
|
69
|
+
"none_reason": "<when drafts is empty: one line on why>"}
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
// src/core/skill-review.ts
|
|
73
|
+
var draftsDir = (repo) => join(stateDir(repo), "skill-drafts");
|
|
74
|
+
var BRIEF_MAX = 4e4;
|
|
75
|
+
var TIMELINE_MAX = 120;
|
|
76
|
+
function transcriptTimeline(path, max = TIMELINE_MAX) {
|
|
77
|
+
if (!path) return [];
|
|
78
|
+
let raw = "";
|
|
79
|
+
try {
|
|
80
|
+
if (statSync(path).size > 200 * 1024 * 1024) return ["(transcript too large to read)"];
|
|
81
|
+
raw = readFileSync(path, "utf8");
|
|
82
|
+
} catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const out = [];
|
|
86
|
+
const calls = /* @__PURE__ */ new Map();
|
|
87
|
+
for (const line of raw.split("\n")) {
|
|
88
|
+
let e;
|
|
89
|
+
try {
|
|
90
|
+
e = JSON.parse(line);
|
|
91
|
+
} catch {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const content = e?.message?.content;
|
|
95
|
+
if (e?.type === "user" && typeof content === "string" && !e.isMeta) {
|
|
96
|
+
if (!content.trimStart().startsWith("<")) out.push(`USER: ${content.slice(0, 600)}`);
|
|
97
|
+
} else if (e?.type === "assistant" && Array.isArray(content)) {
|
|
98
|
+
for (const c of content) {
|
|
99
|
+
if (c?.type !== "tool_use") continue;
|
|
100
|
+
const input = c.input ?? {};
|
|
101
|
+
const what = typeof input.command === "string" ? input.command : typeof input.file_path === "string" ? input.file_path : typeof input.skill === "string" ? input.skill : JSON.stringify(input);
|
|
102
|
+
calls.set(c.id, `${c.name}: ${String(what).slice(0, 300)}`);
|
|
103
|
+
}
|
|
104
|
+
} else if (e?.type === "user" && Array.isArray(content)) {
|
|
105
|
+
for (const c of content) {
|
|
106
|
+
if (c?.type !== "tool_result") continue;
|
|
107
|
+
const call = calls.get(c.tool_use_id) ?? "tool";
|
|
108
|
+
const text = typeof c.content === "string" ? c.content : Array.isArray(c.content) ? c.content.map((x) => x?.text ?? "").join(" ") : "";
|
|
109
|
+
out.push(c.is_error ? `TOOL ${call}
|
|
110
|
+
\u2192 ERROR (${c.tool_use_id}): ${text.slice(0, 500)}` : `TOOL ${call} \u2192 ok`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (out.length <= max) return out;
|
|
115
|
+
const errors = out.filter((l) => l.includes("\u2192 ERROR")).slice(-Math.floor(max / 2));
|
|
116
|
+
const tail = out.slice(-(max - errors.length)).filter((l) => !errors.includes(l));
|
|
117
|
+
return [`(${out.length - errors.length - tail.length} earlier entries omitted)`, ...errors, ...tail];
|
|
118
|
+
}
|
|
119
|
+
function topLevel(repo) {
|
|
120
|
+
try {
|
|
121
|
+
return readdirSync(repo).filter((f) => !f.startsWith(".") || f === ".claude" || f === ".documentation").slice(0, 60);
|
|
122
|
+
} catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function buildBrief(job, skills = findSkills(job.repo)) {
|
|
127
|
+
const repo = job.repo;
|
|
128
|
+
const failures = readEvidence(repo, "failures").filter((f) => f.session_id === job.session_id);
|
|
129
|
+
const corrections = readEvidence(repo, "corrections").filter((c) => c.session_id === job.session_id);
|
|
130
|
+
const used = new Set(job.evidence.skills_used);
|
|
131
|
+
const r = (t) => redact(t, "broad").text;
|
|
132
|
+
const lines = [
|
|
133
|
+
`# Session ${job.session_id}`,
|
|
134
|
+
`Repository: ${basename(repo)} \u2014 top level: ${topLevel(repo).join(", ")}`,
|
|
135
|
+
`Why reviewed: ${job.reason}`,
|
|
136
|
+
"",
|
|
137
|
+
"## Failures (from the evidence log)",
|
|
138
|
+
...failures.length ? failures.map((f) => `- id ${f.id}: \`${r(String(f.command))}\`
|
|
139
|
+
error: ${r(String(f.error).slice(0, 500))}`) : ["(none)"],
|
|
140
|
+
"",
|
|
141
|
+
"## Corrections the user gave",
|
|
142
|
+
...corrections.length ? corrections.map((c, i) => `- correction ${i + 1} (${c.cue}): ${r(String(c.excerpt))}`) : ["(none)"],
|
|
143
|
+
"",
|
|
144
|
+
"## Skills this repository loads",
|
|
145
|
+
...skills.length ? skills.map((s) => `- ${s.skill.name || basename(s.dir)} (${s.scope}; learned: ${s.learned}${s.skill.meta?.pinned ? "; pinned" : ""}${used.has(s.skill.name) ? "; USED this session" : ""}): ${r(s.skill.description.slice(0, 200))}`) : ["(none)"],
|
|
146
|
+
...skills.filter((s) => s.learned && used.has(s.skill.name)).flatMap((s) => ["", `### Current text of the learned skill ${s.skill.name} (used this session)`, r(s.skill.body.slice(0, 4e3))]),
|
|
147
|
+
"",
|
|
148
|
+
"## Knowledge-base fact ids (for cites)",
|
|
149
|
+
[...factIds(repo)].slice(0, 200).join(", ") || "(none)",
|
|
150
|
+
"",
|
|
151
|
+
"## Timeline (prompts, tool calls, errors)",
|
|
152
|
+
...transcriptTimeline(job.transcript_path).map(r)
|
|
153
|
+
];
|
|
154
|
+
let brief = lines.join("\n");
|
|
155
|
+
if (brief.length > BRIEF_MAX) brief = brief.slice(0, BRIEF_MAX) + "\n(brief cut at its size cap)";
|
|
156
|
+
return brief;
|
|
157
|
+
}
|
|
158
|
+
function parseReviewerJson(text) {
|
|
159
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
160
|
+
const candidates = [fenced?.[1], text, text.slice(text.indexOf("{"), text.lastIndexOf("}") + 1)].filter(Boolean);
|
|
161
|
+
for (const c of candidates) {
|
|
162
|
+
try {
|
|
163
|
+
const v = JSON.parse(c.trim());
|
|
164
|
+
if (v && Array.isArray(v.drafts)) return v;
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && !x.includes("[REDACTED]")) : [];
|
|
171
|
+
var sha256 = (s) => createHash("sha256").update(s).digest("hex");
|
|
172
|
+
async function materializeDraft(repo, job, d, skills, resolveScope = (x) => decideScope(repo, x), env = process.env, merge) {
|
|
173
|
+
if (!d || typeof d.name !== "string" || typeof d.body !== "string" || typeof d.description !== "string") return { rejected: "malformed draft" };
|
|
174
|
+
let target;
|
|
175
|
+
if (d.kind === "patch") {
|
|
176
|
+
target = skills.find((s) => s.skill.name === d.target);
|
|
177
|
+
if (!target) return { rejected: `patch target "${d.target}" not found` };
|
|
178
|
+
if (!target.learned) return { rejected: `"${d.target}" was not written by the skill loop: never patched` };
|
|
179
|
+
if (target.skill.meta?.pinned) return { rejected: `"${d.target}" is pinned` };
|
|
180
|
+
}
|
|
181
|
+
const prov = {
|
|
182
|
+
sessions: [.../* @__PURE__ */ new Set([job.session_id, ...strs(d.provenance?.sessions)])],
|
|
183
|
+
failures: strs(d.provenance?.failures),
|
|
184
|
+
corrections: strs(d.provenance?.corrections)
|
|
185
|
+
};
|
|
186
|
+
if (prov.failures.length + prov.corrections.length === 0 && job.evidence.failures.length + job.evidence.corrections === 0) {
|
|
187
|
+
return { rejected: "no failure or correction behind it" };
|
|
188
|
+
}
|
|
189
|
+
const decision = target ? { scope: target.scope === "directory" ? "directory" : "project", globalCandidate: false, why: "patch: the skill's own scope" } : await resolveScope(d);
|
|
190
|
+
const scope = target?.scope ?? decision.scope;
|
|
191
|
+
const paths = strs(d.applies_to?.paths);
|
|
192
|
+
if (decision.directory && !paths.includes(decision.directory)) paths.unshift(decision.directory);
|
|
193
|
+
const name = d.kind === "patch" ? target.skill.name : d.name;
|
|
194
|
+
const meta = {
|
|
195
|
+
schema: 1,
|
|
196
|
+
scope,
|
|
197
|
+
status: "draft",
|
|
198
|
+
pinned: false,
|
|
199
|
+
triggers: strs(d.triggers),
|
|
200
|
+
applies_to: { paths, commands: strs(d.applies_to?.commands) },
|
|
201
|
+
provenance: prov,
|
|
202
|
+
cites: strs(d.cites),
|
|
203
|
+
supersedes: merge ? merge.supersedes.map((p) => basename(join(p, ".."))) : target?.skill.meta?.supersedes ?? []
|
|
204
|
+
};
|
|
205
|
+
const text = redact(renderSkill({ name, description: d.description, meta, body: d.body }), "precise").text;
|
|
206
|
+
const findings = lintSkill(join(draftsDir(repo), name, "SKILL.md"), text, { projectRoot: repo, others: skills.filter((s) => s.path !== target?.path) }, name);
|
|
207
|
+
const errors = findings.filter((f) => f.severity === "error");
|
|
208
|
+
if (errors.length) return { rejected: `lint: ${errors.map((f) => `${f.rule}: ${f.message}`).join("; ")}` };
|
|
209
|
+
const id = `${name}-${sha256(text).slice(0, 8)}`;
|
|
210
|
+
const dir = join(draftsDir(repo), id);
|
|
211
|
+
mkdirSync(dir, { recursive: true });
|
|
212
|
+
writeFileSync(join(dir, "SKILL.md"), text);
|
|
213
|
+
const record = {
|
|
214
|
+
id,
|
|
215
|
+
kind: merge ? "merge" : d.kind === "patch" ? "patch" : "new",
|
|
216
|
+
name,
|
|
217
|
+
scope,
|
|
218
|
+
...target ? { target: target.path, base_sha256: sha256(readFileSync(target.path, "utf8")) } : {},
|
|
219
|
+
session_id: job.session_id,
|
|
220
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
221
|
+
warnings: findings.filter((f) => f.severity === "warn").map((f) => `${f.rule}: ${f.message}`),
|
|
222
|
+
...decision.directory ? { directory: decision.directory } : {},
|
|
223
|
+
scope_why: decision.why,
|
|
224
|
+
...decision.globalCandidate ? { global_candidate: true } : {},
|
|
225
|
+
...merge ? { supersedes: merge.supersedes } : {}
|
|
226
|
+
};
|
|
227
|
+
if (!target && !merge) {
|
|
228
|
+
const peers = promotionPeers(repo, name, env);
|
|
229
|
+
if (peers.length) record.promotion = peers.map((p) => ({ repo: p.repo, path: p.path }));
|
|
230
|
+
indexSkill({ repo, name, path: join(dir, "SKILL.md"), status: "draft", ...decision.globalCandidate ? { global_candidate: true } : {} }, env);
|
|
231
|
+
}
|
|
232
|
+
writeFileSync(join(dir, "draft.json"), JSON.stringify(record, null, 1));
|
|
233
|
+
return { id };
|
|
234
|
+
}
|
|
235
|
+
function listDrafts(repo) {
|
|
236
|
+
let ids = [];
|
|
237
|
+
try {
|
|
238
|
+
ids = readdirSync(draftsDir(repo));
|
|
239
|
+
} catch {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const out = [];
|
|
243
|
+
for (const id of ids) {
|
|
244
|
+
const dir = join(draftsDir(repo), id);
|
|
245
|
+
try {
|
|
246
|
+
if (existsSync(join(dir, "SKILL.md"))) out.push({ dir, record: JSON.parse(readFileSync(join(dir, "draft.json"), "utf8")) });
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return out.sort((a, b) => a.record.created.localeCompare(b.record.created));
|
|
251
|
+
}
|
|
252
|
+
async function reviewSession(job, opts = {}) {
|
|
253
|
+
const skills = findSkills(job.repo);
|
|
254
|
+
const brief = buildBrief(job, skills);
|
|
255
|
+
const run = await runClaude({
|
|
256
|
+
prompt: `${REVIEW_PROMPT}
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
${brief}`,
|
|
261
|
+
cwd: job.repo,
|
|
262
|
+
tools: ["Read", "Grep", "Glob"],
|
|
263
|
+
maxTurns: opts.maxTurns ?? 8,
|
|
264
|
+
output: "json",
|
|
265
|
+
timeoutMs: 10 * 6e4,
|
|
266
|
+
env: opts.env
|
|
267
|
+
});
|
|
268
|
+
if (!run.ok && !run.result) throw new Error(run.error ?? "reviewer run failed");
|
|
269
|
+
const answer = parseReviewerJson(run.result);
|
|
270
|
+
if (!answer) return { drafts: [], rejected: [{ reason: "reviewer answer was not JSON" }], costUsd: run.costUsd };
|
|
271
|
+
const drafts = [];
|
|
272
|
+
const rejected = [];
|
|
273
|
+
for (const d of answer.drafts.slice(0, 2)) {
|
|
274
|
+
const env = opts.env ?? process.env;
|
|
275
|
+
const r = await materializeDraft(job.repo, job, d, skills, opts.resolveScope ?? ((x) => decideScope(job.repo, x, env)), env);
|
|
276
|
+
if (r.id) drafts.push(r.id);
|
|
277
|
+
else rejected.push({ reason: `${d?.name ?? "?"}: ${r.rejected}` });
|
|
278
|
+
}
|
|
279
|
+
return { drafts, rejected, costUsd: run.costUsd, note: answer.drafts.length ? void 0 : answer.none_reason?.slice(0, 200) };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export {
|
|
283
|
+
draftsDir,
|
|
284
|
+
transcriptTimeline,
|
|
285
|
+
buildBrief,
|
|
286
|
+
parseReviewerJson,
|
|
287
|
+
materializeDraft,
|
|
288
|
+
listDrafts,
|
|
289
|
+
reviewSession
|
|
290
|
+
};
|
|
291
|
+
//# sourceMappingURL=chunk-P3GLY42T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/skill-review.ts","../src/core/skill-review-prompt.ts"],"sourcesContent":["/**\n * The skill loop's reviewer (v1.7): one finished session in, zero to two skill drafts out.\n *\n * 1. A brief: the session's prompts, tool calls and errors (from the transcript, read fail-soft: its format\n * is internal to Claude Code), plus the evidence logs, the skills this repo loads and its KB fact ids.\n * Redacted (broad) and capped before the model sees it.\n * 2. A fenced `claude -p` (Read / Grep / Glob on the repo only; see claude-run.ts) answers JSON.\n * 3. Each draft is rendered, redacted, linted (skill-schema.ts) and written to\n * .claude/.semantic-memory/skill-drafts/<id>/ — or rejected with its reasons. Nothing is written anywhere\n * else; landing a draft is a person's call (`skill-loop review`).\n */\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { redact } from \"./redact.js\";\nimport { readEvidence, stateDir } from \"./skill-evidence.js\";\nimport { factIds, findSkills, lintSkill, renderSkill, type SkillFile, type SkillLoopMeta, type SkillScope } from \"./skill-schema.js\";\nimport { runClaude } from \"./claude-run.js\";\nimport { REVIEW_PROMPT } from \"./skill-review-prompt.js\";\nimport type { ReviewJob } from \"./skill-queue.js\";\nimport type { ReviewOutcome } from \"./skill-worker.js\";\nimport { decideScope, indexSkill, promotionPeers, type ScopeDecision } from \"./skill-scope.js\";\n\nexport const draftsDir = (repo: string) => join(stateDir(repo), \"skill-drafts\");\nconst BRIEF_MAX = 40_000;\nconst TIMELINE_MAX = 120;\n\nexport interface DraftRecord {\n id: string;\n kind: \"new\" | \"patch\" | \"merge\";\n name: string;\n scope: SkillScope;\n /** patch: the SKILL.md it replaces, and that file's sha256 when the draft was made */\n target?: string;\n base_sha256?: string;\n session_id: string;\n created: string;\n warnings: string[];\n /** directory scope: where it lands (repo-relative subdirectory) */\n directory?: string;\n scope_why?: string;\n /** Jev thinks it holds in any repo */\n global_candidate?: boolean;\n /** other repos with the same lesson: review offers one global skill replacing the copies */\n promotion?: { repo: string; path: string }[];\n /** merge: the SKILL.md files the merged skill replaces (archived when it lands) */\n supersedes?: string[];\n /** filled by skill evals (Phase 14) when they run at approval time */\n evals?: Record<string, unknown>;\n}\n\n// --- the brief ---\n\n/** The session's timeline from its transcript: prompts, tool calls, and the errors they returned. Fail-soft. */\nexport function transcriptTimeline(path: string | null, max = TIMELINE_MAX): string[] {\n if (!path) return [];\n let raw = \"\";\n try {\n if (statSync(path).size > 200 * 1024 * 1024) return [\"(transcript too large to read)\"];\n raw = readFileSync(path, \"utf8\");\n } catch {\n return [];\n }\n const out: string[] = [];\n const calls = new Map<string, string>();\n for (const line of raw.split(\"\\n\")) {\n let e: any;\n try {\n e = JSON.parse(line);\n } catch {\n continue;\n }\n const content = e?.message?.content;\n if (e?.type === \"user\" && typeof content === \"string\" && !e.isMeta) {\n // wrappers (slash-command echoes, task notifications, local command output) are not the user talking\n if (!content.trimStart().startsWith(\"<\")) out.push(`USER: ${content.slice(0, 600)}`);\n } else if (e?.type === \"assistant\" && Array.isArray(content)) {\n for (const c of content) {\n if (c?.type !== \"tool_use\") continue;\n const input = c.input ?? {};\n const what = typeof input.command === \"string\" ? input.command : typeof input.file_path === \"string\" ? input.file_path : typeof input.skill === \"string\" ? input.skill : JSON.stringify(input);\n calls.set(c.id, `${c.name}: ${String(what).slice(0, 300)}`);\n }\n } else if (e?.type === \"user\" && Array.isArray(content)) {\n for (const c of content) {\n if (c?.type !== \"tool_result\") continue;\n const call = calls.get(c.tool_use_id) ?? \"tool\";\n const text = typeof c.content === \"string\" ? c.content : Array.isArray(c.content) ? c.content.map((x: any) => x?.text ?? \"\").join(\" \") : \"\";\n out.push(c.is_error ? `TOOL ${call}\\n → ERROR (${c.tool_use_id}): ${text.slice(0, 500)}` : `TOOL ${call} → ok`);\n }\n }\n }\n // keep the end of long sessions (where the fix landed) and every error\n if (out.length <= max) return out;\n const errors = out.filter((l) => l.includes(\"→ ERROR\")).slice(-Math.floor(max / 2));\n const tail = out.slice(-(max - errors.length)).filter((l) => !errors.includes(l));\n return [`(${out.length - errors.length - tail.length} earlier entries omitted)`, ...errors, ...tail];\n}\n\nfunction topLevel(repo: string): string[] {\n try {\n return readdirSync(repo).filter((f) => !f.startsWith(\".\") || f === \".claude\" || f === \".documentation\").slice(0, 60);\n } catch {\n return [];\n }\n}\n\nexport function buildBrief(job: ReviewJob, skills: SkillFile[] = findSkills(job.repo)): string {\n const repo = job.repo;\n const failures = readEvidence(repo, \"failures\").filter((f) => f.session_id === job.session_id);\n const corrections = readEvidence(repo, \"corrections\").filter((c) => c.session_id === job.session_id);\n const used = new Set(job.evidence.skills_used);\n // free text is redacted (broad); the ids the reviewer must cite back (session, failure ids) are not, or broad\n // redaction would mask them too. The evidence logs were redacted when written.\n const r = (t: string) => redact(t, \"broad\").text;\n const lines = [\n `# Session ${job.session_id}`,\n `Repository: ${basename(repo)} — top level: ${topLevel(repo).join(\", \")}`,\n `Why reviewed: ${job.reason}`,\n \"\",\n \"## Failures (from the evidence log)\",\n ...(failures.length ? failures.map((f) => `- id ${f.id}: \\`${r(String(f.command))}\\`\\n error: ${r(String(f.error).slice(0, 500))}`) : [\"(none)\"]),\n \"\",\n \"## Corrections the user gave\",\n ...(corrections.length ? corrections.map((c, i) => `- correction ${i + 1} (${c.cue}): ${r(String(c.excerpt))}`) : [\"(none)\"]),\n \"\",\n \"## Skills this repository loads\",\n ...(skills.length\n ? skills.map((s) => `- ${s.skill.name || basename(s.dir)} (${s.scope}; learned: ${s.learned}${s.skill.meta?.pinned ? \"; pinned\" : \"\"}${used.has(s.skill.name) ? \"; USED this session\" : \"\"}): ${r(s.skill.description.slice(0, 200))}`)\n : [\"(none)\"]),\n ...skills\n .filter((s) => s.learned && used.has(s.skill.name))\n .flatMap((s) => [\"\", `### Current text of the learned skill ${s.skill.name} (used this session)`, r(s.skill.body.slice(0, 4000))]),\n \"\",\n \"## Knowledge-base fact ids (for cites)\",\n [...factIds(repo)].slice(0, 200).join(\", \") || \"(none)\",\n \"\",\n \"## Timeline (prompts, tool calls, errors)\",\n ...transcriptTimeline(job.transcript_path).map(r),\n ];\n let brief = lines.join(\"\\n\");\n if (brief.length > BRIEF_MAX) brief = brief.slice(0, BRIEF_MAX) + \"\\n(brief cut at its size cap)\";\n return brief;\n}\n\n// --- the answer ---\n\nexport interface ReviewerDraft {\n kind: \"new\" | \"patch\";\n target?: string;\n name: string;\n description: string;\n scope: string;\n applies_to?: { paths?: string[]; commands?: string[] };\n triggers?: string[];\n provenance?: { sessions?: string[]; failures?: string[]; corrections?: string[] };\n cites?: string[];\n body: string;\n}\n\n/** The JSON object in the reviewer's answer (bare, or inside a ```json fence). */\nexport function parseReviewerJson(text: string): { drafts: ReviewerDraft[]; none_reason?: string } | null {\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n const candidates = [fenced?.[1], text, text.slice(text.indexOf(\"{\"), text.lastIndexOf(\"}\") + 1)].filter(Boolean) as string[];\n for (const c of candidates) {\n try {\n const v = JSON.parse(c.trim());\n if (v && Array.isArray(v.drafts)) return v;\n } catch {}\n }\n return null;\n}\n\n// a placeholder the model copied from redacted text is not evidence\nconst strs = (v: unknown) => (Array.isArray(v) ? v.filter((x): x is string => typeof x === \"string\" && !x.includes(\"[REDACTED]\")) : []);\nconst sha256 = (s: string) => createHash(\"sha256\").update(s).digest(\"hex\");\n\nexport type ScopeResolver = (d: ReviewerDraft) => Promise<ScopeDecision>;\n\n/**\n * Turn one reviewer draft into a draft folder, or say why not. `resolveScope` decides the final scope\n * (skill-scope.ts: deterministic rules, then Jev); the reviewer's own choice is never trusted for it.\n */\nexport async function materializeDraft(\n repo: string,\n job: ReviewJob,\n d: ReviewerDraft,\n skills: SkillFile[],\n resolveScope: ScopeResolver = (x) => decideScope(repo, x),\n env = process.env,\n merge?: { supersedes: string[] },\n): Promise<{ id?: string; rejected?: string }> {\n if (!d || typeof d.name !== \"string\" || typeof d.body !== \"string\" || typeof d.description !== \"string\") return { rejected: \"malformed draft\" };\n let target: SkillFile | undefined;\n if (d.kind === \"patch\") {\n target = skills.find((s) => s.skill.name === d.target);\n if (!target) return { rejected: `patch target \"${d.target}\" not found` };\n if (!target.learned) return { rejected: `\"${d.target}\" was not written by the skill loop: never patched` };\n if (target.skill.meta?.pinned) return { rejected: `\"${d.target}\" is pinned` };\n }\n const prov = {\n sessions: [...new Set([job.session_id, ...strs(d.provenance?.sessions)])],\n failures: strs(d.provenance?.failures),\n corrections: strs(d.provenance?.corrections),\n };\n if (prov.failures.length + prov.corrections.length === 0 && job.evidence.failures.length + job.evidence.corrections === 0) {\n return { rejected: \"no failure or correction behind it\" };\n }\n // a patch keeps its skill's scope; a new skill's comes from the resolver\n const decision: ScopeDecision = target\n ? { scope: target.scope === \"directory\" ? \"directory\" : \"project\", globalCandidate: false, why: \"patch: the skill's own scope\" }\n : await resolveScope(d);\n const scope: SkillScope = target?.scope ?? decision.scope;\n const paths = strs(d.applies_to?.paths);\n if (decision.directory && !paths.includes(decision.directory)) paths.unshift(decision.directory);\n const name = d.kind === \"patch\" ? target!.skill.name : d.name;\n const meta: SkillLoopMeta = {\n schema: 1,\n scope,\n status: \"draft\",\n pinned: false,\n triggers: strs(d.triggers),\n applies_to: { paths, commands: strs(d.applies_to?.commands) },\n provenance: prov,\n cites: strs(d.cites),\n supersedes: merge ? merge.supersedes.map((p) => basename(join(p, \"..\"))) : (target?.skill.meta?.supersedes ?? []),\n };\n // precise: a skill is prose and code, broad would mangle ordinary identifiers\n const text = redact(renderSkill({ name, description: d.description, meta, body: d.body }), \"precise\").text;\n const findings = lintSkill(join(draftsDir(repo), name, \"SKILL.md\"), text, { projectRoot: repo, others: skills.filter((s) => s.path !== target?.path) }, name);\n const errors = findings.filter((f) => f.severity === \"error\");\n if (errors.length) return { rejected: `lint: ${errors.map((f) => `${f.rule}: ${f.message}`).join(\"; \")}` };\n const id = `${name}-${sha256(text).slice(0, 8)}`;\n const dir = join(draftsDir(repo), id);\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, \"SKILL.md\"), text);\n const record: DraftRecord = {\n id,\n kind: merge ? \"merge\" : d.kind === \"patch\" ? \"patch\" : \"new\",\n name,\n scope,\n ...(target ? { target: target.path, base_sha256: sha256(readFileSync(target.path, \"utf8\")) } : {}),\n session_id: job.session_id,\n created: new Date().toISOString(),\n warnings: findings.filter((f) => f.severity === \"warn\").map((f) => `${f.rule}: ${f.message}`),\n ...(decision.directory ? { directory: decision.directory } : {}),\n scope_why: decision.why,\n ...(decision.globalCandidate ? { global_candidate: true } : {}),\n ...(merge ? { supersedes: merge.supersedes } : {}),\n };\n if (!target && !merge) {\n const peers = promotionPeers(repo, name, env);\n if (peers.length) record.promotion = peers.map((p) => ({ repo: p.repo, path: p.path }));\n indexSkill({ repo, name, path: join(dir, \"SKILL.md\"), status: \"draft\", ...(decision.globalCandidate ? { global_candidate: true } : {}) }, env);\n }\n writeFileSync(join(dir, \"draft.json\"), JSON.stringify(record, null, 1));\n return { id };\n}\n\nexport function listDrafts(repo: string): { dir: string; record: DraftRecord }[] {\n let ids: string[] = [];\n try {\n ids = readdirSync(draftsDir(repo));\n } catch {\n return [];\n }\n const out: { dir: string; record: DraftRecord }[] = [];\n for (const id of ids) {\n const dir = join(draftsDir(repo), id);\n try {\n if (existsSync(join(dir, \"SKILL.md\"))) out.push({ dir, record: JSON.parse(readFileSync(join(dir, \"draft.json\"), \"utf8\")) });\n } catch {}\n }\n return out.sort((a, b) => a.record.created.localeCompare(b.record.created));\n}\n\nexport interface ReviewOptions {\n env?: NodeJS.ProcessEnv;\n resolveScope?: ScopeResolver;\n maxTurns?: number;\n}\n\n/** Review one queued session (the worker's review function). */\nexport async function reviewSession(job: ReviewJob, opts: ReviewOptions = {}): Promise<ReviewOutcome> {\n const skills = findSkills(job.repo);\n const brief = buildBrief(job, skills);\n const run = await runClaude({\n prompt: `${REVIEW_PROMPT}\\n\\n---\\n\\n${brief}`,\n cwd: job.repo,\n tools: [\"Read\", \"Grep\", \"Glob\"],\n maxTurns: opts.maxTurns ?? 8,\n output: \"json\",\n timeoutMs: 10 * 60_000,\n env: opts.env,\n });\n if (!run.ok && !run.result) throw new Error(run.error ?? \"reviewer run failed\");\n const answer = parseReviewerJson(run.result);\n if (!answer) return { drafts: [], rejected: [{ reason: \"reviewer answer was not JSON\" }], costUsd: run.costUsd };\n const drafts: string[] = [];\n const rejected: { reason: string }[] = [];\n for (const d of answer.drafts.slice(0, 2)) {\n const env = opts.env ?? process.env;\n const r = await materializeDraft(job.repo, job, d, skills, opts.resolveScope ?? ((x) => decideScope(job.repo, x, env)), env);\n if (r.id) drafts.push(r.id);\n else rejected.push({ reason: `${d?.name ?? \"?\"}: ${r.rejected}` });\n }\n return { drafts, rejected, costUsd: run.costUsd, note: answer.drafts.length ? undefined : answer.none_reason?.slice(0, 200) };\n}\n","/**\n * The skill loop reviewer's fixed instructions (v1.7). The brief (what happened in one session) follows\n * them on stdin. The reviewer can read the repo (Read / Grep / Glob) and nothing else; it answers with\n * JSON, and the worker — not the model — writes drafts, after redaction and lint.\n */\nexport const REVIEW_PROMPT = `You review one finished Claude Code session in this repository and decide whether it taught a\nreusable procedure worth saving as a skill — a short, step-by-step playbook a future session should follow.\n\nDraft only from the evidence in the brief below: a command that failed and the fix that worked, a correction\nthe user gave, or a learned skill that was used and then failed or was corrected. Never invent steps, commands,\npaths or facts that the brief or the repository does not show. Most sessions teach nothing reusable: then\nanswer with no drafts. A draft is worth it only when the same situation is likely to come up again here.\n\nKinds of draft:\n- \"new\": a procedure no existing skill covers. Check the existing skills in the brief first.\n- \"patch\": an existing learned skill (listed with \"learned: true\") was used in this session and then failed or\n was corrected: give its full revised body. Never patch a skill that is pinned or not learned.\n\nEach draft:\n- name: kebab-case, specific (\"restart-vite-dev-server\", not \"dev-helper\").\n- description: what it does and when to use it, one or two sentences, starting with the action; include\n \"Use when ...\" with the literal error, command or request that should load it. Under 300 characters.\n- scope: \"project\" (about this repository) or \"directory\" (only about one subdirectory; give it in applies_to.paths).\n- applies_to: { paths: [repo-relative paths that exist], commands: [program names the steps run] }.\n- triggers: the literal error strings, commands or user phrases from the brief that this skill answers.\n- provenance: { sessions: [the session id], failures: [failure ids from the brief it came from], corrections: [correction\n numbers from the brief, as strings] } — at least one failure or correction.\n- cites: knowledge-base fact ids from the brief's list that the steps rely on (only ids that are listed).\n- body: markdown with exactly these sections, in order:\n ## When to use — the situation, in two or three lines.\n ## Steps — numbered, concrete commands or actions as the session finally did them.\n ## Pitfalls — what went wrong in the session and why; each line names the failure id or fact id it comes from.\n ## Verify — one command whose exit code proves it worked, in a fenced code block; or, if none exists, one\n sentence a reviewer can check.\n\nHard rules:\n- No secrets, tokens, passwords, keys, personal data, customer data, names of people or money amounts, even if\n the brief shows them. Use placeholders like <token>.\n- At most 2 drafts. No duplicates of existing skills.\n- Do not write or change any file. You may read files in this repository to get paths and commands right.\n\nAnswer with one JSON object and nothing else:\n{\"drafts\": [{\"kind\": \"new\" | \"patch\", \"target\": \"<existing skill name, patch only>\", \"name\": \"...\",\n \"description\": \"...\", \"scope\": \"project\" | \"directory\", \"applies_to\": {\"paths\": [], \"commands\": []},\n \"triggers\": [], \"provenance\": {\"sessions\": [], \"failures\": [], \"corrections\": []}, \"cites\": [], \"body\": \"...\"}],\n \"none_reason\": \"<when drafts is empty: one line on why>\"}\n`;\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/core/failure-eval.ts
|
|
2
|
+
var key = (c, p) => `${c}\0${p}`;
|
|
3
|
+
function evaluateFailures(results, judgments, opts = {}) {
|
|
4
|
+
const target = opts.targetPrecision ?? 0.95;
|
|
5
|
+
const minJudged = opts.minJudged ?? 20;
|
|
6
|
+
const thresholds = opts.thresholds ?? Array.from({ length: 17 }, (_, i) => Math.round((0.2 + i * 0.05) * 100) / 100);
|
|
7
|
+
const label = /* @__PURE__ */ new Map();
|
|
8
|
+
const relevantFor = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const j of judgments) {
|
|
10
|
+
label.set(key(j.case, j.path), (label.get(key(j.case, j.path)) ?? false) || j.relevant);
|
|
11
|
+
if (j.relevant) {
|
|
12
|
+
if (!relevantFor.has(j.case)) relevantFor.set(j.case, /* @__PURE__ */ new Set());
|
|
13
|
+
relevantFor.get(j.case).add(j.path);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
let answerable = 0, rank1 = 0, recallAtK = 0;
|
|
17
|
+
const unjudged = [];
|
|
18
|
+
for (const r of results) {
|
|
19
|
+
const relevant = relevantFor.get(r.case);
|
|
20
|
+
if (relevant && relevant.size > 0) {
|
|
21
|
+
answerable++;
|
|
22
|
+
if (r.hits[0] && relevant.has(r.hits[0].path)) rank1++;
|
|
23
|
+
if (r.hits.some((h) => relevant.has(h.path))) recallAtK++;
|
|
24
|
+
}
|
|
25
|
+
const top = r.hits[0];
|
|
26
|
+
if (top && !label.has(key(r.case, top.path))) unjudged.push({ case: r.case, path: top.path, score: top.score });
|
|
27
|
+
}
|
|
28
|
+
const rows = thresholds.map((threshold) => {
|
|
29
|
+
let injected = 0, judged = 0, correct = 0;
|
|
30
|
+
for (const r of results) {
|
|
31
|
+
const top = r.hits[0];
|
|
32
|
+
if (!top || top.score < threshold) continue;
|
|
33
|
+
injected++;
|
|
34
|
+
const l = label.get(key(r.case, top.path));
|
|
35
|
+
if (l === void 0) continue;
|
|
36
|
+
judged++;
|
|
37
|
+
if (l) correct++;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
threshold,
|
|
41
|
+
injected,
|
|
42
|
+
judged,
|
|
43
|
+
correct,
|
|
44
|
+
precision: judged > 0 ? correct / judged : null,
|
|
45
|
+
coverage: results.length > 0 ? injected / results.length : 0
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const meets = (row) => row.precision !== null && row.precision >= target;
|
|
49
|
+
const recommended = rows.find(
|
|
50
|
+
(row, i) => row.judged >= minJudged && meets(row) && rows.slice(i + 1).every((r) => r.judged < minJudged || meets(r))
|
|
51
|
+
)?.threshold ?? null;
|
|
52
|
+
return { cases: results.length, answerable, rank1, recallAtK, rows, recommended, unjudged };
|
|
53
|
+
}
|
|
54
|
+
function formatFailureEval(report, label = "") {
|
|
55
|
+
const pct = (n) => n === null ? " - " : `${(n * 100).toFixed(1)}%`.padStart(6);
|
|
56
|
+
const lines = [
|
|
57
|
+
`Failure-lookup eval${label ? ` (${label})` : ""} \u2014 ${report.cases} cases, ${report.answerable} with a known answer`,
|
|
58
|
+
` answer ranked first: ${report.rank1}/${report.answerable} answer anywhere in hits: ${report.recallAtK}/${report.answerable}`,
|
|
59
|
+
"",
|
|
60
|
+
" threshold injected judged correct precision coverage"
|
|
61
|
+
];
|
|
62
|
+
for (const r of report.rows) {
|
|
63
|
+
lines.push(
|
|
64
|
+
` ${r.threshold.toFixed(2).padStart(9)} ${String(r.injected).padStart(8)} ${String(r.judged).padStart(6)} ${String(r.correct).padStart(7)} ${pct(r.precision).padStart(9)} ${pct(r.coverage).padStart(8)}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
lines.push("");
|
|
68
|
+
lines.push(
|
|
69
|
+
report.recommended === null ? " recommended threshold: none reaches the target precision with enough judged injections" : ` recommended threshold: ${report.recommended.toFixed(2)}`
|
|
70
|
+
);
|
|
71
|
+
lines.push(` unjudged top hits: ${report.unjudged.length}`);
|
|
72
|
+
return lines.join("\n");
|
|
73
|
+
}
|
|
74
|
+
var meetsBar = (picks, wrong) => picks >= 40 && wrong <= 2 && (picks - wrong) / picks >= 0.95;
|
|
75
|
+
function calibrateJudge(labels, jev, bar = 0.95) {
|
|
76
|
+
const label = /* @__PURE__ */ new Map();
|
|
77
|
+
for (const j of labels) label.set(key(j.case, j.path), (label.get(key(j.case, j.path)) ?? false) || j.relevant);
|
|
78
|
+
const relevant = { pairs: 0, yes: 0, no: 0, unsure: 0 };
|
|
79
|
+
const notRelevant = { pairs: 0, yes: 0, no: 0, unsure: 0 };
|
|
80
|
+
const seen = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const r of jev) {
|
|
82
|
+
const k = key(r.case, r.path);
|
|
83
|
+
const l = label.get(k);
|
|
84
|
+
if (l === void 0 || seen.has(k)) continue;
|
|
85
|
+
seen.add(k);
|
|
86
|
+
const side = l ? relevant : notRelevant;
|
|
87
|
+
side.pairs++;
|
|
88
|
+
if (r.p !== null && r.p >= bar) side.yes++;
|
|
89
|
+
else if (r.p !== null && r.p <= 1 - bar + 1e-9) side.no++;
|
|
90
|
+
else side.unsure++;
|
|
91
|
+
}
|
|
92
|
+
const pairs = relevant.pairs + notRelevant.pairs;
|
|
93
|
+
const picks = relevant.yes + relevant.no + notRelevant.yes + notRelevant.no;
|
|
94
|
+
const wrong = relevant.no + notRelevant.yes;
|
|
95
|
+
return {
|
|
96
|
+
pairs,
|
|
97
|
+
relevant,
|
|
98
|
+
notRelevant,
|
|
99
|
+
picks,
|
|
100
|
+
wrong,
|
|
101
|
+
agreement: picks ? (picks - wrong) / picks : null,
|
|
102
|
+
unsureRate: pairs ? (relevant.unsure + notRelevant.unsure) / pairs : 0,
|
|
103
|
+
yesPicks: relevant.yes + notRelevant.yes,
|
|
104
|
+
yesWrong: notRelevant.yes,
|
|
105
|
+
noPicks: relevant.no + notRelevant.no,
|
|
106
|
+
noWrong: relevant.no,
|
|
107
|
+
// both sides separately: a judge that only ever says no would pass a pooled check and still find nothing
|
|
108
|
+
verdict: meetsBar(relevant.yes + notRelevant.yes, notRelevant.yes) && meetsBar(relevant.no + notRelevant.no, relevant.no) ? "jev" : meetsBar(relevant.no + notRelevant.no, relevant.no) ? "hybrid" : "claude"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function evaluateGate(results, judgments, jev, opts = {}) {
|
|
112
|
+
const mode = opts.mode ?? "confirm";
|
|
113
|
+
const thresholds = opts.thresholds ?? [0.3, 0.35, 0.4, 0.5];
|
|
114
|
+
const topK = opts.topK ?? 5;
|
|
115
|
+
const bar = opts.bar ?? 0.95;
|
|
116
|
+
const isCandidate = opts.isCandidate ?? ((p) => /(^|\/)facts\/[^/]+\.md$/.test(p));
|
|
117
|
+
const label = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const j of judgments) label.set(key(j.case, j.path), (label.get(key(j.case, j.path)) ?? false) || j.relevant);
|
|
119
|
+
const score = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const r of jev) score.set(key(r.case, r.path), r.p);
|
|
121
|
+
const missing = /* @__PURE__ */ new Map();
|
|
122
|
+
const rows = thresholds.map((threshold) => {
|
|
123
|
+
let candidates = 0, miss = 0, injected = 0, judged = 0, correct = 0;
|
|
124
|
+
for (const r of results) {
|
|
125
|
+
const hit = r.hits.slice(0, topK).find((h) => isCandidate(h.path) && h.score >= threshold);
|
|
126
|
+
if (!hit) continue;
|
|
127
|
+
candidates++;
|
|
128
|
+
const k = key(r.case, hit.path);
|
|
129
|
+
if (!score.has(k)) {
|
|
130
|
+
miss++;
|
|
131
|
+
missing.set(k, { case: r.case, path: hit.path });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const p = score.get(k);
|
|
135
|
+
const inject = mode === "confirm" ? p !== null && p !== void 0 && p >= bar : !(p !== null && p !== void 0 && p <= 1 - bar + 1e-9);
|
|
136
|
+
if (!inject) continue;
|
|
137
|
+
injected++;
|
|
138
|
+
const l = label.get(k);
|
|
139
|
+
if (l === void 0) continue;
|
|
140
|
+
judged++;
|
|
141
|
+
if (l) correct++;
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
threshold,
|
|
145
|
+
candidates,
|
|
146
|
+
missing: miss,
|
|
147
|
+
injected,
|
|
148
|
+
judged,
|
|
149
|
+
correct,
|
|
150
|
+
precision: judged ? correct / judged : null,
|
|
151
|
+
coverage: results.length ? injected / results.length : 0,
|
|
152
|
+
meetsBar: meetsBar(judged, judged - correct)
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
return { rows, missing: [...missing.values()] };
|
|
156
|
+
}
|
|
157
|
+
function formatGate(rows, mode = "confirm") {
|
|
158
|
+
const pct = (n) => n === null ? " - " : `${(n * 100).toFixed(1)}%`;
|
|
159
|
+
const what = mode === "confirm" ? "only when Jev says yes at >= 0.95" : "unless Jev says no at <= 0.05";
|
|
160
|
+
const lines = [`Jev gate, ${mode} (inject the first fact >= T ${what})`, "", " T candidates missing injected judged correct precision coverage bar"];
|
|
161
|
+
for (const r of rows) {
|
|
162
|
+
lines.push(
|
|
163
|
+
` ${r.threshold.toFixed(2)} ${String(r.candidates).padStart(10)} ${String(r.missing).padStart(7)} ${String(r.injected).padStart(8)} ${String(r.judged).padStart(6)} ${String(r.correct).padStart(7)} ${pct(r.precision).padStart(9)} ${pct(r.coverage).padStart(8)} ${r.meetsBar ? "yes" : "no"}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export {
|
|
170
|
+
evaluateFailures,
|
|
171
|
+
formatFailureEval,
|
|
172
|
+
meetsBar,
|
|
173
|
+
calibrateJudge,
|
|
174
|
+
evaluateGate,
|
|
175
|
+
formatGate
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=chunk-PJXYCCI2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/failure-eval.ts"],"sourcesContent":["/**\n * Failure-lookup eval (v1.6): given failed tool calls, the hits a search returned for each, and human\n * (or judge) relevance labels, measure how well the top hit answers the failure and pick the score\n * threshold above which injecting it is right often enough. Pure — no I/O, no search.\n */\n\nexport interface FailureCase {\n id: string;\n /** notes folder this failure's repo searches (its docs) */\n notes: string;\n error: string;\n command: string;\n}\n\nexport interface Judgment {\n case: string;\n path: string;\n relevant: boolean;\n}\n\nexport interface CaseResult {\n case: string;\n hits: { path: string; score: number }[];\n}\n\nexport interface ThresholdRow {\n threshold: number;\n /** cases whose top hit scored >= threshold (would be injected) */\n injected: number;\n /** injected cases whose top hit has a label */\n judged: number;\n correct: number;\n /** correct / judged; null when nothing judged */\n precision: number | null;\n /** injected / all cases */\n coverage: number;\n}\n\nexport interface FailureEvalReport {\n cases: number;\n /** cases with at least one relevant path among their labels */\n answerable: number;\n /** answerable cases whose top hit is relevant */\n rank1: number;\n /** answerable cases with a relevant path anywhere in the hits */\n recallAtK: number;\n rows: ThresholdRow[];\n /** lowest threshold reaching the target precision on >= minJudged judged injections; null if none */\n recommended: number | null;\n /** top hits nobody has labelled yet: label these, then re-run */\n unjudged: { case: string; path: string; score: number }[];\n}\n\nconst key = (c: string, p: string) => `${c}\\u0000${p}`;\n\nexport function evaluateFailures(\n results: CaseResult[],\n judgments: Judgment[],\n opts: { targetPrecision?: number; minJudged?: number; thresholds?: number[] } = {}\n): FailureEvalReport {\n const target = opts.targetPrecision ?? 0.95;\n const minJudged = opts.minJudged ?? 20;\n const thresholds = opts.thresholds ?? Array.from({ length: 17 }, (_, i) => Math.round((0.2 + i * 0.05) * 100) / 100);\n\n const label = new Map<string, boolean>();\n const relevantFor = new Map<string, Set<string>>();\n for (const j of judgments) {\n // a positive label wins over a negative one for the same pair\n label.set(key(j.case, j.path), (label.get(key(j.case, j.path)) ?? false) || j.relevant);\n if (j.relevant) {\n if (!relevantFor.has(j.case)) relevantFor.set(j.case, new Set());\n relevantFor.get(j.case)!.add(j.path);\n }\n }\n\n let answerable = 0, rank1 = 0, recallAtK = 0;\n const unjudged: FailureEvalReport[\"unjudged\"] = [];\n for (const r of results) {\n const relevant = relevantFor.get(r.case);\n if (relevant && relevant.size > 0) {\n answerable++;\n if (r.hits[0] && relevant.has(r.hits[0].path)) rank1++;\n if (r.hits.some((h) => relevant.has(h.path))) recallAtK++;\n }\n const top = r.hits[0];\n if (top && !label.has(key(r.case, top.path))) unjudged.push({ case: r.case, path: top.path, score: top.score });\n }\n\n const rows: ThresholdRow[] = thresholds.map((threshold) => {\n let injected = 0, judged = 0, correct = 0;\n for (const r of results) {\n const top = r.hits[0];\n if (!top || top.score < threshold) continue;\n injected++;\n const l = label.get(key(r.case, top.path));\n if (l === undefined) continue;\n judged++;\n if (l) correct++;\n }\n return {\n threshold,\n injected,\n judged,\n correct,\n precision: judged > 0 ? correct / judged : null,\n coverage: results.length > 0 ? injected / results.length : 0,\n };\n });\n\n // lowest threshold where this row and every higher row with enough judged injections reach the target\n const meets = (row: ThresholdRow) => row.precision !== null && row.precision >= target;\n const recommended =\n rows.find(\n (row, i) => row.judged >= minJudged && meets(row) && rows.slice(i + 1).every((r) => r.judged < minJudged || meets(r))\n )?.threshold ?? null;\n\n return { cases: results.length, answerable, rank1, recallAtK, rows, recommended, unjudged };\n}\n\nexport function formatFailureEval(report: FailureEvalReport, label = \"\"): string {\n const pct = (n: number | null) => (n === null ? \" - \" : `${(n * 100).toFixed(1)}%`.padStart(6));\n const lines = [\n `Failure-lookup eval${label ? ` (${label})` : \"\"} — ${report.cases} cases, ${report.answerable} with a known answer`,\n ` answer ranked first: ${report.rank1}/${report.answerable} answer anywhere in hits: ${report.recallAtK}/${report.answerable}`,\n \"\",\n \" threshold injected judged correct precision coverage\",\n ];\n for (const r of report.rows) {\n lines.push(\n ` ${r.threshold.toFixed(2).padStart(9)} ${String(r.injected).padStart(8)} ${String(r.judged).padStart(6)} ${String(r.correct).padStart(7)} ${pct(r.precision).padStart(9)} ${pct(r.coverage).padStart(8)}`\n );\n }\n lines.push(\"\");\n lines.push(\n report.recommended === null\n ? \" recommended threshold: none reaches the target precision with enough judged injections\"\n : ` recommended threshold: ${report.recommended.toFixed(2)}`\n );\n lines.push(` unjudged top hits: ${report.unjudged.length}`);\n return lines.join(\"\\n\");\n}\n\n// --- v1.7 jev mode: the judge's agreement with labels, and the failure hook's Jev gate ---\n\n/** jev-loop's adoption bar: right >= 95% of the time over >= 40 picks, with at most 2 wrong. */\nexport const meetsBar = (picks: number, wrong: number) => picks >= 40 && wrong <= 2 && (picks - wrong) / picks >= 0.95;\n\nexport interface JevJudgment {\n case: string;\n path: string;\n p: number | null;\n}\n\nexport interface JudgeCalibration {\n /** labelled pairs Jev answered */\n pairs: number;\n /** label relevant / not relevant: picks Jev made (>= bar yes, <= 1-bar no), how many disagree, unsure */\n relevant: { pairs: number; yes: number; no: number; unsure: number };\n notRelevant: { pairs: number; yes: number; no: number; unsure: number };\n /** every pick (yes or no): agreement with the labels */\n picks: number;\n wrong: number;\n agreement: number | null;\n unsureRate: number;\n /** yes picks alone (the side that would inject) */\n yesPicks: number;\n yesWrong: number;\n /** no picks alone (the side that would filter) */\n noPicks: number;\n noWrong: number;\n /**\n * \"jev\": Jev alone may judge (its yes picks and its no picks each meet the bar);\n * \"hybrid\": its no picks meet the bar — Jev filters, the rest (yes and unsure) go to Claude;\n * \"claude\": neither side does.\n */\n verdict: \"jev\" | \"hybrid\" | \"claude\";\n}\n\nexport function calibrateJudge(labels: Judgment[], jev: JevJudgment[], bar = 0.95): JudgeCalibration {\n const label = new Map<string, boolean>();\n for (const j of labels) label.set(key(j.case, j.path), (label.get(key(j.case, j.path)) ?? false) || j.relevant);\n const relevant = { pairs: 0, yes: 0, no: 0, unsure: 0 };\n const notRelevant = { pairs: 0, yes: 0, no: 0, unsure: 0 };\n const seen = new Set<string>();\n for (const r of jev) {\n const k = key(r.case, r.path);\n const l = label.get(k);\n if (l === undefined || seen.has(k)) continue;\n seen.add(k);\n const side = l ? relevant : notRelevant;\n side.pairs++;\n if (r.p !== null && r.p >= bar) side.yes++;\n else if (r.p !== null && r.p <= 1 - bar + 1e-9) side.no++;\n else side.unsure++;\n }\n const pairs = relevant.pairs + notRelevant.pairs;\n const picks = relevant.yes + relevant.no + notRelevant.yes + notRelevant.no;\n const wrong = relevant.no + notRelevant.yes;\n return {\n pairs,\n relevant,\n notRelevant,\n picks,\n wrong,\n agreement: picks ? (picks - wrong) / picks : null,\n unsureRate: pairs ? (relevant.unsure + notRelevant.unsure) / pairs : 0,\n yesPicks: relevant.yes + notRelevant.yes,\n yesWrong: notRelevant.yes,\n noPicks: relevant.no + notRelevant.no,\n noWrong: relevant.no,\n // both sides separately: a judge that only ever says no would pass a pooled check and still find nothing\n verdict:\n meetsBar(relevant.yes + notRelevant.yes, notRelevant.yes) && meetsBar(relevant.no + notRelevant.no, relevant.no)\n ? \"jev\"\n : meetsBar(relevant.no + notRelevant.no, relevant.no)\n ? \"hybrid\"\n : \"claude\",\n };\n}\n\nexport interface GateRow {\n threshold: number;\n /** cases with a candidate fact (first fact in the top hits scoring >= threshold) */\n candidates: number;\n /** candidates Jev has not answered yet (ask, then re-run) */\n missing: number;\n /** candidates Jev said yes to at >= the bar: would be injected */\n injected: number;\n judged: number;\n correct: number;\n precision: number | null;\n coverage: number;\n meetsBar: boolean;\n}\n\n/**\n * The failure hook's Jev gate, replayed: per threshold T, the candidate is the first fact among the top\n * `topK` hits scoring >= T. `confirm` injects it only when Jev scores it >= the bar; `veto` injects it unless\n * Jev scores it <= 1 - bar (a confident no). Precision is measured on the injected candidates that carry a label.\n */\nexport function evaluateGate(\n results: CaseResult[],\n judgments: Judgment[],\n jev: JevJudgment[],\n opts: { thresholds?: number[]; topK?: number; bar?: number; isCandidate?: (path: string) => boolean; mode?: \"confirm\" | \"veto\" } = {},\n): { rows: GateRow[]; missing: { case: string; path: string }[] } {\n const mode = opts.mode ?? \"confirm\";\n const thresholds = opts.thresholds ?? [0.3, 0.35, 0.4, 0.5];\n const topK = opts.topK ?? 5;\n const bar = opts.bar ?? 0.95;\n const isCandidate = opts.isCandidate ?? ((p: string) => /(^|\\/)facts\\/[^/]+\\.md$/.test(p));\n const label = new Map<string, boolean>();\n for (const j of judgments) label.set(key(j.case, j.path), (label.get(key(j.case, j.path)) ?? false) || j.relevant);\n const score = new Map<string, number | null>();\n for (const r of jev) score.set(key(r.case, r.path), r.p);\n const missing = new Map<string, { case: string; path: string }>();\n const rows = thresholds.map((threshold) => {\n let candidates = 0, miss = 0, injected = 0, judged = 0, correct = 0;\n for (const r of results) {\n const hit = r.hits.slice(0, topK).find((h) => isCandidate(h.path) && h.score >= threshold);\n if (!hit) continue;\n candidates++;\n const k = key(r.case, hit.path);\n if (!score.has(k)) {\n miss++;\n missing.set(k, { case: r.case, path: hit.path });\n continue;\n }\n const p = score.get(k);\n const inject = mode === \"confirm\" ? p !== null && p !== undefined && p >= bar : !(p !== null && p !== undefined && p <= 1 - bar + 1e-9);\n if (!inject) continue;\n injected++;\n const l = label.get(k);\n if (l === undefined) continue;\n judged++;\n if (l) correct++;\n }\n return {\n threshold,\n candidates,\n missing: miss,\n injected,\n judged,\n correct,\n precision: judged ? correct / judged : null,\n coverage: results.length ? injected / results.length : 0,\n meetsBar: meetsBar(judged, judged - correct),\n };\n });\n return { rows, missing: [...missing.values()] };\n}\n\nexport function formatGate(rows: GateRow[], mode: \"confirm\" | \"veto\" = \"confirm\"): string {\n const pct = (n: number | null) => (n === null ? \" - \" : `${(n * 100).toFixed(1)}%`);\n const what = mode === \"confirm\" ? \"only when Jev says yes at >= 0.95\" : \"unless Jev says no at <= 0.05\";\n const lines = [`Jev gate, ${mode} (inject the first fact >= T ${what})`, \"\", \" T candidates missing injected judged correct precision coverage bar\"];\n for (const r of rows) {\n lines.push(\n ` ${r.threshold.toFixed(2)} ${String(r.candidates).padStart(10)} ${String(r.missing).padStart(7)} ${String(r.injected).padStart(8)} ${String(r.judged).padStart(6)} ${String(r.correct).padStart(7)} ${pct(r.precision).padStart(9)} ${pct(r.coverage).padStart(8)} ${r.meetsBar ? \"yes\" : \"no\"}`,\n );\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|