@djolex999/vir-cli 0.24.2 → 0.25.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/README.md +69 -20
- package/dist/claude/updater.js +83 -7
- package/dist/claude/updater.js.map +1 -1
- package/dist/cli/connect.js +100 -0
- package/dist/cli/connect.js.map +1 -0
- package/dist/cli/init.js +13 -13
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/initConfig.js +2 -0
- package/dist/cli/initConfig.js.map +1 -1
- package/dist/cli/insightsStatus.js +13 -0
- package/dist/cli/insightsStatus.js.map +1 -0
- package/dist/cli/notesDir.js +9 -0
- package/dist/cli/notesDir.js.map +1 -0
- package/dist/cli/projects.js +8 -7
- package/dist/cli/projects.js.map +1 -1
- package/dist/cli/reconcile.js +10 -8
- package/dist/cli/reconcile.js.map +1 -1
- package/dist/cli/reviewInsights.js +145 -0
- package/dist/cli/reviewInsights.js.map +1 -0
- package/dist/cli/syncClaude.js +101 -0
- package/dist/cli/syncClaude.js.map +1 -0
- package/dist/cli.js +50 -73
- package/dist/cli.js.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/connect/calibrate.js +44 -0
- package/dist/connect/calibrate.js.map +1 -0
- package/dist/connect/cluster.js +63 -0
- package/dist/connect/cluster.js.map +1 -0
- package/dist/connect/embed.js +47 -0
- package/dist/connect/embed.js.map +1 -0
- package/dist/connect/extract.js +129 -0
- package/dist/connect/extract.js.map +1 -0
- package/dist/connect/identity.js +65 -0
- package/dist/connect/identity.js.map +1 -0
- package/dist/connect/insightFile.js +61 -0
- package/dist/connect/insightFile.js.map +1 -0
- package/dist/connect/qualify.js +39 -0
- package/dist/connect/qualify.js.map +1 -0
- package/dist/connect/run.js +199 -0
- package/dist/connect/run.js.map +1 -0
- package/dist/connect/text.js +20 -0
- package/dist/connect/text.js.map +1 -0
- package/dist/connect/types.js +2 -0
- package/dist/connect/types.js.map +1 -0
- package/dist/connect/verify.js +77 -0
- package/dist/connect/verify.js.map +1 -0
- package/dist/mcp/server.js +12 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/output/json.js +4 -2
- package/dist/output/json.js.map +1 -1
- package/dist/pipeline/distiller.js +1 -1
- package/dist/pipeline/distiller.js.map +1 -1
- package/dist/pipeline/parser.js +2 -2
- package/dist/pipeline/parser.js.map +1 -1
- package/dist/pipeline/projects.js +1 -1
- package/dist/pipeline/projects.js.map +1 -1
- package/dist/pipeline/run.js +15 -10
- package/dist/pipeline/run.js.map +1 -1
- package/dist/pipeline/toolCallFilter.js +25 -1
- package/dist/pipeline/toolCallFilter.js.map +1 -1
- package/dist/prune/classify.js +8 -2
- package/dist/prune/classify.js.map +1 -1
- package/dist/prune/prune.js +9 -6
- package/dist/prune/prune.js.map +1 -1
- package/dist/search/retriever.js +19 -0
- package/dist/search/retriever.js.map +1 -1
- package/dist/search/thresholds.js +22 -2
- package/dist/search/thresholds.js.map +1 -1
- package/dist/sources/claudeCode.js +35 -0
- package/dist/sources/claudeCode.js.map +1 -0
- package/dist/sources/registry.js +49 -0
- package/dist/sources/registry.js.map +1 -0
- package/dist/sources/types.js +2 -0
- package/dist/sources/types.js.map +1 -0
- package/dist/state/db.js +240 -3
- package/dist/state/db.js.map +1 -1
- package/dist/version.js +2 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { parseFrontmatter } from "../cli/review.js";
|
|
5
|
+
import { kebab } from "../pipeline/slug.js";
|
|
6
|
+
import { CATEGORY_DIR } from "../pipeline/writer.js";
|
|
7
|
+
const ITEM_RE = /^(?:[-*]|\d+\.)?\s*(?:\*\*|__)/;
|
|
8
|
+
const HEADING_RE = /^#{1,2}\s/;
|
|
9
|
+
function section(lines, title) {
|
|
10
|
+
const start = lines.findIndex((l) => title.test(l));
|
|
11
|
+
if (start === -1)
|
|
12
|
+
return null;
|
|
13
|
+
const out = [];
|
|
14
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
15
|
+
const line = lines[i] ?? "";
|
|
16
|
+
if (HEADING_RE.test(line))
|
|
17
|
+
break;
|
|
18
|
+
out.push(line);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
// A lesson starts on a base-indent line beginning with an optional list marker
|
|
23
|
+
// and bold; its body runs to the next base-indent item or heading. Nested
|
|
24
|
+
// (indented) bold lines belong to their parent. Mid-line bold is not an item.
|
|
25
|
+
export function extractLessonTexts(markdown) {
|
|
26
|
+
const lines = markdown.split("\n");
|
|
27
|
+
const learned = section(lines, /^##\s+What Was Learned\s*$/);
|
|
28
|
+
if (learned === null) {
|
|
29
|
+
const summary = section(lines, /^##\s+Summary\s*$/);
|
|
30
|
+
const text = (summary ?? []).join("\n").trim();
|
|
31
|
+
return text.length > 0 ? [text] : [];
|
|
32
|
+
}
|
|
33
|
+
const items = [];
|
|
34
|
+
let cur = null;
|
|
35
|
+
for (const line of learned) {
|
|
36
|
+
// ### subheadings group lessons; they end the current item and are not text.
|
|
37
|
+
if (/^#{3,6}\s/.test(line)) {
|
|
38
|
+
cur = null;
|
|
39
|
+
}
|
|
40
|
+
else if (ITEM_RE.test(line) && !/^\s/.test(line)) {
|
|
41
|
+
cur = [line];
|
|
42
|
+
items.push(cur);
|
|
43
|
+
}
|
|
44
|
+
else if (cur) {
|
|
45
|
+
cur.push(line);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return items.map((l) => l.join("\n").trim()).filter((t) => t.length > 0);
|
|
49
|
+
}
|
|
50
|
+
export function normalizeLesson(raw) {
|
|
51
|
+
return raw
|
|
52
|
+
.split("\n")
|
|
53
|
+
.map((l) => l.replace(/^\s*(?:[-*]|\d+\.)\s+/, ""))
|
|
54
|
+
.join(" ")
|
|
55
|
+
.replace(/\*\*|__/g, "")
|
|
56
|
+
.replace(/\s+/g, " ")
|
|
57
|
+
.trim();
|
|
58
|
+
}
|
|
59
|
+
export function lessonHash(normalized) {
|
|
60
|
+
return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
61
|
+
}
|
|
62
|
+
export function archivedDuplicateSlugs(markdown) {
|
|
63
|
+
const lines = section(markdown.split("\n"), /^##\s+Archived Duplicates\s*$/) ?? [];
|
|
64
|
+
return lines
|
|
65
|
+
.map((l) => l.match(/^\s*-\s*\[\[([^\]|]+)(?:\|[^\]]*)?\]\]/)?.[1]?.trim())
|
|
66
|
+
.filter((s) => typeof s === "string" && s.length > 0);
|
|
67
|
+
}
|
|
68
|
+
function lessonsFromNote(path, citeSlug, archivedVia) {
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = readFileSync(path, "utf8");
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
const fm = parseFrontmatter(raw);
|
|
77
|
+
const sessionId = fm.session_id ?? "";
|
|
78
|
+
if (sessionId.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const noteSlug = basename(path, ".md");
|
|
81
|
+
return extractLessonTexts(raw).map((t, itemIndex) => {
|
|
82
|
+
const text = normalizeLesson(t);
|
|
83
|
+
return {
|
|
84
|
+
id: `${noteSlug}#${itemIndex}`,
|
|
85
|
+
noteSlug,
|
|
86
|
+
citeSlug,
|
|
87
|
+
sessionId,
|
|
88
|
+
noteDate: fm.date ?? "",
|
|
89
|
+
project: kebab(fm.project ?? ""),
|
|
90
|
+
category: (fm.category ?? "pattern"),
|
|
91
|
+
itemIndex,
|
|
92
|
+
text,
|
|
93
|
+
contentHash: lessonHash(text),
|
|
94
|
+
archivedVia,
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
export function collectLessons(vaultRoot) {
|
|
99
|
+
const out = [];
|
|
100
|
+
for (const dir of Object.values(CATEGORY_DIR)) {
|
|
101
|
+
const full = join(vaultRoot, dir);
|
|
102
|
+
let names;
|
|
103
|
+
try {
|
|
104
|
+
names = readdirSync(full).filter((n) => n.endsWith(".md")).sort();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
for (const name of names) {
|
|
110
|
+
const path = join(full, name);
|
|
111
|
+
const slug = basename(name, ".md");
|
|
112
|
+
out.push(...lessonsFromNote(path, slug, null));
|
|
113
|
+
let raw = "";
|
|
114
|
+
try {
|
|
115
|
+
raw = readFileSync(path, "utf8");
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
for (const loser of archivedDuplicateSlugs(raw)) {
|
|
121
|
+
const lp = join(vaultRoot, "archived", `${loser}.md`);
|
|
122
|
+
if (existsSync(lp))
|
|
123
|
+
out.push(...lessonsFromNote(lp, slug, slug));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/connect/extract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export function jaccard(a, b) {
|
|
2
|
+
const sa = new Set(a);
|
|
3
|
+
const sb = new Set(b);
|
|
4
|
+
let inter = 0;
|
|
5
|
+
for (const x of sa)
|
|
6
|
+
if (sb.has(x))
|
|
7
|
+
inter += 1;
|
|
8
|
+
const union = sa.size + sb.size - inter;
|
|
9
|
+
return union === 0 ? 0 : inter / union;
|
|
10
|
+
}
|
|
11
|
+
export const MATCH_JACCARD = 0.5;
|
|
12
|
+
function cosine(a, b) {
|
|
13
|
+
let dot = 0;
|
|
14
|
+
let na = 0;
|
|
15
|
+
let nb = 0;
|
|
16
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
17
|
+
const x = a[i] ?? 0;
|
|
18
|
+
const y = b[i] ?? 0;
|
|
19
|
+
dot += x * y;
|
|
20
|
+
na += x * x;
|
|
21
|
+
nb += y * y;
|
|
22
|
+
}
|
|
23
|
+
return na === 0 || nb === 0 ? 0 : dot / Math.sqrt(na * nb);
|
|
24
|
+
}
|
|
25
|
+
// Identity is the member SESSION set: session ids survive note rewrites
|
|
26
|
+
// (reconcile, run --full, dedupe winners, review edits); content hashes don't.
|
|
27
|
+
// Hash overlap only breaks ties. A rejected rule is also matched by its rule
|
|
28
|
+
// embedding, so a reworded re-discovery from new sessions stays rejected.
|
|
29
|
+
export function matchCandidate(candidate, insights, rejectedVectors, coreSim) {
|
|
30
|
+
let best = null;
|
|
31
|
+
let bestJ = -1;
|
|
32
|
+
let bestH = -1;
|
|
33
|
+
for (const ins of insights) {
|
|
34
|
+
const j = jaccard(candidate.sessionIds, ins.memberSessionIds);
|
|
35
|
+
if (j < MATCH_JACCARD)
|
|
36
|
+
continue;
|
|
37
|
+
const h = jaccard(candidate.hashes, ins.memberHashes);
|
|
38
|
+
if (j > bestJ || (j === bestJ && h > bestH)) {
|
|
39
|
+
best = ins;
|
|
40
|
+
bestJ = j;
|
|
41
|
+
bestH = h;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (best === null && candidate.ruleVector !== null) {
|
|
45
|
+
for (const ins of insights) {
|
|
46
|
+
if (ins.status !== "rejected")
|
|
47
|
+
continue;
|
|
48
|
+
const v = rejectedVectors.get(ins.id);
|
|
49
|
+
if (v && cosine(candidate.ruleVector, v) >= coreSim)
|
|
50
|
+
return { kind: "skip-rejected", insight: ins };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (best === null)
|
|
54
|
+
return { kind: "new" };
|
|
55
|
+
if (best.status === "rejected")
|
|
56
|
+
return { kind: "skip-rejected", insight: best };
|
|
57
|
+
if (best.status === "proposed")
|
|
58
|
+
return { kind: "update-proposed", insight: best };
|
|
59
|
+
const known = new Set(best.memberSessionIds);
|
|
60
|
+
const added = [...new Set(candidate.sessionIds)].filter((s) => !known.has(s));
|
|
61
|
+
return added.length === 0
|
|
62
|
+
? { kind: "accepted-unchanged", insight: best }
|
|
63
|
+
: { kind: "accepted-additions", insight: best, addedSessions: added };
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/connect/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { kebab } from "../pipeline/slug.js";
|
|
4
|
+
import { ruleText } from "./text.js";
|
|
5
|
+
export const INSIGHTS_RULES_DIR = "insights/rules";
|
|
6
|
+
export function insightSlug(rule, id) {
|
|
7
|
+
const base = kebab(rule).slice(0, 60).replace(/-+$/, "");
|
|
8
|
+
const suffix = id.replace(/[^a-z0-9]/gi, "").slice(0, 8).toLowerCase();
|
|
9
|
+
return base.length > 0 ? `${base}-${suffix}` : `rule-${suffix}`;
|
|
10
|
+
}
|
|
11
|
+
const q = (s) => JSON.stringify(s);
|
|
12
|
+
export function renderInsight(row) {
|
|
13
|
+
const fm = [
|
|
14
|
+
"---",
|
|
15
|
+
"type: insight",
|
|
16
|
+
`insight_type: ${row.insightType}`,
|
|
17
|
+
`status: ${row.status}`,
|
|
18
|
+
`promotion: ${row.promotion}`,
|
|
19
|
+
`verified: ${row.status === "accepted"}`,
|
|
20
|
+
`scope: ${row.scope}`,
|
|
21
|
+
`sessions: ${row.sessions}`,
|
|
22
|
+
`projects: [${row.projects.map(q).join(", ")}]`,
|
|
23
|
+
`first_seen: ${row.firstSeen.slice(0, 10)}`,
|
|
24
|
+
`last_seen: ${row.lastSeen.slice(0, 10)}`,
|
|
25
|
+
"sources:",
|
|
26
|
+
...row.sources.map((s) => ` - ${q(`[[${s}]]`)}`),
|
|
27
|
+
`generated: ${row.createdAt}`,
|
|
28
|
+
`model: ${row.model}`,
|
|
29
|
+
"---",
|
|
30
|
+
];
|
|
31
|
+
const evidence = row.evidence.map((e) => `- [[${e.citeSlug}]] (${e.project || "-"}, ${e.date.slice(0, 10)}): ${q(e.quote)}`);
|
|
32
|
+
return [
|
|
33
|
+
...fm,
|
|
34
|
+
`**Rule:** ${row.rule}`,
|
|
35
|
+
"",
|
|
36
|
+
`**Why:** ${row.why}`,
|
|
37
|
+
"",
|
|
38
|
+
"## Evidence",
|
|
39
|
+
"",
|
|
40
|
+
...evidence,
|
|
41
|
+
"",
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
// The owner edits only the **Rule:** and **Why:** lines; everything else is
|
|
45
|
+
// re-rendered from the DB on the next state write.
|
|
46
|
+
export function parseRuleEdit(raw) {
|
|
47
|
+
const rule = ruleText(raw.match(/^\*\*Rule:\*\*[ \t]*(.+)$/m)?.[1] ?? "");
|
|
48
|
+
if (rule.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
const why = ruleText(raw.match(/^\*\*Why:\*\*[ \t]*(.+)$/m)?.[1] ?? "");
|
|
51
|
+
return { rule, why };
|
|
52
|
+
}
|
|
53
|
+
export function writeInsightFile(vaultRoot, row) {
|
|
54
|
+
const path = join(vaultRoot, INSIGHTS_RULES_DIR, `${row.slug}.md`);
|
|
55
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
56
|
+
const tmp = `${path}.vir-${process.pid}.tmp`;
|
|
57
|
+
writeFileSync(tmp, renderInsight(row));
|
|
58
|
+
renameSync(tmp, path);
|
|
59
|
+
return path;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=insightFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insightFile.js","sourceRoot":"","sources":["../../src/connect/insightFile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const MIN_SESSIONS = 3;
|
|
2
|
+
export const MIN_SPAN_DAYS = 7;
|
|
3
|
+
const DAY_MS = 86_400_000;
|
|
4
|
+
// Each session counts once, dated by its earliest parseable note date. A
|
|
5
|
+
// session with no parseable date still counts toward sessions but not the span.
|
|
6
|
+
export function recurrence(members) {
|
|
7
|
+
const earliest = new Map();
|
|
8
|
+
for (const m of members) {
|
|
9
|
+
const t = Date.parse(m.noteDate);
|
|
10
|
+
const prev = earliest.get(m.sessionId);
|
|
11
|
+
if (prev === undefined)
|
|
12
|
+
earliest.set(m.sessionId, Number.isFinite(t) ? t : null);
|
|
13
|
+
else if (Number.isFinite(t) && (prev === null || t < prev))
|
|
14
|
+
earliest.set(m.sessionId, t);
|
|
15
|
+
}
|
|
16
|
+
const times = [...earliest.values()].filter((t) => t !== null);
|
|
17
|
+
const first = times.length > 0 ? Math.min(...times) : null;
|
|
18
|
+
const last = times.length > 0 ? Math.max(...times) : null;
|
|
19
|
+
const projects = [...new Set(members.map((m) => m.project).filter((p) => p.length > 0))].sort();
|
|
20
|
+
return {
|
|
21
|
+
sessions: [...earliest.keys()],
|
|
22
|
+
firstSeen: first === null ? "" : new Date(first).toISOString(),
|
|
23
|
+
lastSeen: last === null ? "" : new Date(last).toISOString(),
|
|
24
|
+
projects,
|
|
25
|
+
spanDays: first === null || last === null ? 0 : (last - first) / DAY_MS,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function qualifies(r) {
|
|
29
|
+
return r.sessions.length >= MIN_SESSIONS && r.spanDays >= MIN_SPAN_DAYS;
|
|
30
|
+
}
|
|
31
|
+
export function rankCandidates(clusters) {
|
|
32
|
+
return clusters
|
|
33
|
+
.map((members) => ({ members, rec: recurrence(members) }))
|
|
34
|
+
.filter((c) => qualifies(c.rec))
|
|
35
|
+
.sort((a, b) => b.rec.sessions.length - a.rec.sessions.length ||
|
|
36
|
+
b.rec.spanDays - a.rec.spanDays ||
|
|
37
|
+
b.rec.projects.length - a.rec.projects.length);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=qualify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qualify.js","sourceRoot":"","sources":["../../src/connect/qualify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { acquireLock, releaseLock } from "../pipeline/lock.js";
|
|
3
|
+
import { vaultRoot } from "../search/retriever.js";
|
|
4
|
+
import { embedNoteWithProvider } from "../search/provider.js";
|
|
5
|
+
import { thresholdsFor } from "../search/thresholds.js";
|
|
6
|
+
import { clusterLessons } from "./cluster.js";
|
|
7
|
+
import { backfillInsightEmbeddings, embedLessons } from "./embed.js";
|
|
8
|
+
import { collectLessons } from "./extract.js";
|
|
9
|
+
import { matchCandidate } from "./identity.js";
|
|
10
|
+
import { insightSlug, writeInsightFile } from "./insightFile.js";
|
|
11
|
+
import { rankCandidates, recurrence } from "./qualify.js";
|
|
12
|
+
import { buildVerifyPrompt, parseVerdict, validateVerdict } from "./verify.js";
|
|
13
|
+
const VERIFY_OUTPUT_TOKENS = 600;
|
|
14
|
+
function emptySummary() {
|
|
15
|
+
return {
|
|
16
|
+
lessons: 0, clusters: 0, candidates: 0, deferred: 0, proposed: 0, updated: 0, unchanged: 0,
|
|
17
|
+
skippedRejected: 0, unverified: 0, llmFailures: 0, additionsFlagged: 0, llmCalls: 0,
|
|
18
|
+
estCostUsd: null, keptRatios: [],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function evidenceOf(v) {
|
|
22
|
+
return v.evidence.map((e) => ({
|
|
23
|
+
sessionId: e.lesson.sessionId,
|
|
24
|
+
citeSlug: e.lesson.citeSlug,
|
|
25
|
+
project: e.lesson.project,
|
|
26
|
+
date: e.lesson.noteDate,
|
|
27
|
+
quote: e.quote,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
function reconsider(cfg, db, slug, now) {
|
|
31
|
+
const row = db.getInsightBySlug(slug);
|
|
32
|
+
if (row === null)
|
|
33
|
+
throw new Error(`no rule with slug ${slug}`);
|
|
34
|
+
// Spec §9: the way back for a rejected or declined rule — never a silent
|
|
35
|
+
// demotion of one the owner accepted and promoted.
|
|
36
|
+
if (row.status !== "rejected" && row.promotion !== "declined") {
|
|
37
|
+
throw new Error(`only a rejected or declined rule can be reconsidered — ${slug} is ${row.status}`);
|
|
38
|
+
}
|
|
39
|
+
const next = {
|
|
40
|
+
...row,
|
|
41
|
+
status: "proposed",
|
|
42
|
+
promotion: "none",
|
|
43
|
+
evidenceChanged: false,
|
|
44
|
+
pending: null,
|
|
45
|
+
updatedAt: now.toISOString(),
|
|
46
|
+
};
|
|
47
|
+
db.upsertInsight(next);
|
|
48
|
+
writeInsightFile(vaultRoot(cfg), next);
|
|
49
|
+
return emptySummary();
|
|
50
|
+
}
|
|
51
|
+
// Find lessons the user keeps re-learning and propose them as cited rules.
|
|
52
|
+
// Paid: at most connectMaxCandidates LLM calls. Never run implicitly.
|
|
53
|
+
export async function runConnect(cfg, db, opts, deps) {
|
|
54
|
+
acquireLock(deps.lockPath);
|
|
55
|
+
try {
|
|
56
|
+
if (opts.reconsider !== undefined)
|
|
57
|
+
return reconsider(cfg, db, opts.reconsider, deps.now());
|
|
58
|
+
const provider = deps.provider;
|
|
59
|
+
if (provider === null)
|
|
60
|
+
throw new Error("clustering needs an embedding provider — run vir embed setup");
|
|
61
|
+
const summary = emptySummary();
|
|
62
|
+
const root = vaultRoot(cfg);
|
|
63
|
+
if (!opts.dryRun)
|
|
64
|
+
await backfillInsightEmbeddings(db, provider);
|
|
65
|
+
const lessons = collectLessons(root);
|
|
66
|
+
summary.lessons = lessons.length;
|
|
67
|
+
const vectors = await embedLessons(lessons, db, provider);
|
|
68
|
+
const { connectMinSim, connectCoreSim } = thresholdsFor(provider.modelName);
|
|
69
|
+
const clusters = clusterLessons(lessons, vectors, connectMinSim, connectCoreSim);
|
|
70
|
+
summary.clusters = clusters.length;
|
|
71
|
+
// Free pre-LLM triage: rejected stays rejected, and a known rule with no new
|
|
72
|
+
// session is unchanged — neither earns a paid call.
|
|
73
|
+
const insights = db.listInsights();
|
|
74
|
+
const todo = [];
|
|
75
|
+
for (const c of rankCandidates(clusters)) {
|
|
76
|
+
const hashes = c.members.map((m) => m.contentHash);
|
|
77
|
+
const pre = matchCandidate({ sessionIds: c.rec.sessions, hashes, ruleVector: null }, insights, new Map(), connectCoreSim);
|
|
78
|
+
if (pre.kind === "skip-rejected") {
|
|
79
|
+
summary.skippedRejected += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (pre.kind === "accepted-unchanged") {
|
|
83
|
+
summary.unchanged += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (pre.kind === "update-proposed" || pre.kind === "accepted-additions") {
|
|
87
|
+
// Sessions already flagged as pending additions are known too: the
|
|
88
|
+
// owner hasn't decided on them yet, so asking the model again is waste.
|
|
89
|
+
const known = new Set([
|
|
90
|
+
...pre.insight.memberSessionIds,
|
|
91
|
+
...(pre.insight.pending ?? []).map((e) => e.sessionId),
|
|
92
|
+
]);
|
|
93
|
+
if (c.rec.sessions.every((s) => known.has(s))) {
|
|
94
|
+
summary.unchanged += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
todo.push(c);
|
|
99
|
+
}
|
|
100
|
+
summary.candidates = todo.length;
|
|
101
|
+
const batch = todo.slice(0, cfg.connectMaxCandidates);
|
|
102
|
+
summary.deferred = todo.length - batch.length;
|
|
103
|
+
if (opts.dryRun) {
|
|
104
|
+
let total = 0;
|
|
105
|
+
for (const c of batch) {
|
|
106
|
+
const cost = deps.estimateCostUsd(Math.ceil(buildVerifyPrompt(c.members).length / 4), VERIFY_OUTPUT_TOKENS);
|
|
107
|
+
total = cost === null || total === null ? null : total + cost;
|
|
108
|
+
}
|
|
109
|
+
summary.estCostUsd = total;
|
|
110
|
+
return summary;
|
|
111
|
+
}
|
|
112
|
+
const rejectedVectors = db.getInsightVectors("rejected");
|
|
113
|
+
for (const c of batch) {
|
|
114
|
+
summary.llmCalls += 1;
|
|
115
|
+
let text;
|
|
116
|
+
try {
|
|
117
|
+
text = await deps.llm(buildVerifyPrompt(c.members));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
summary.llmFailures += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const verdict = parseVerdict(text);
|
|
124
|
+
const v = verdict === null ? null : validateVerdict(c.members, verdict);
|
|
125
|
+
if (v === null) {
|
|
126
|
+
summary.unverified += 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
summary.keptRatios.push(v.keptOfTotal);
|
|
130
|
+
const rec = recurrence(v.members);
|
|
131
|
+
const ruleVector = await embedNoteWithProvider(provider, `${v.rule}\n${v.why}`);
|
|
132
|
+
const outcome = matchCandidate({ sessionIds: rec.sessions, hashes: v.members.map((m) => m.contentHash), ruleVector }, db.listInsights(), rejectedVectors, connectCoreSim);
|
|
133
|
+
const now = deps.now().toISOString();
|
|
134
|
+
const evidence = evidenceOf(v);
|
|
135
|
+
const fields = {
|
|
136
|
+
memberSessionIds: rec.sessions,
|
|
137
|
+
memberHashes: v.members.map((m) => m.contentHash),
|
|
138
|
+
sources: [...new Set(evidence.map((e) => e.citeSlug))],
|
|
139
|
+
evidence,
|
|
140
|
+
sessions: rec.sessions.length,
|
|
141
|
+
projects: rec.projects,
|
|
142
|
+
firstSeen: rec.firstSeen,
|
|
143
|
+
lastSeen: rec.lastSeen,
|
|
144
|
+
};
|
|
145
|
+
if (outcome.kind === "skip-rejected") {
|
|
146
|
+
summary.skippedRejected += 1;
|
|
147
|
+
}
|
|
148
|
+
else if (outcome.kind === "accepted-unchanged") {
|
|
149
|
+
summary.unchanged += 1;
|
|
150
|
+
}
|
|
151
|
+
else if (outcome.kind === "accepted-additions") {
|
|
152
|
+
const added = new Set(outcome.addedSessions);
|
|
153
|
+
db.upsertInsight({
|
|
154
|
+
...outcome.insight,
|
|
155
|
+
evidenceChanged: true,
|
|
156
|
+
pending: evidence.filter((e) => added.has(e.sessionId)),
|
|
157
|
+
updatedAt: now,
|
|
158
|
+
});
|
|
159
|
+
summary.additionsFlagged += 1;
|
|
160
|
+
}
|
|
161
|
+
else if (outcome.kind === "update-proposed") {
|
|
162
|
+
const next = { ...outcome.insight, ...fields, scope: v.scope, updatedAt: now };
|
|
163
|
+
db.upsertInsight(next);
|
|
164
|
+
writeInsightFile(root, next);
|
|
165
|
+
summary.updated += 1;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const id = randomUUID();
|
|
169
|
+
const row = {
|
|
170
|
+
id,
|
|
171
|
+
slug: insightSlug(v.rule, id),
|
|
172
|
+
insightType: "recurring-rule",
|
|
173
|
+
status: "proposed",
|
|
174
|
+
promotion: "none",
|
|
175
|
+
scope: v.scope,
|
|
176
|
+
rule: v.rule,
|
|
177
|
+
why: v.why,
|
|
178
|
+
...fields,
|
|
179
|
+
evidenceChanged: false,
|
|
180
|
+
pending: null,
|
|
181
|
+
model: deps.model,
|
|
182
|
+
createdAt: now,
|
|
183
|
+
updatedAt: now,
|
|
184
|
+
};
|
|
185
|
+
db.upsertInsight(row);
|
|
186
|
+
if (ruleVector !== null) {
|
|
187
|
+
db.setInsightEmbedding(id, ruleVector, { model: provider.modelName, dim: ruleVector.length });
|
|
188
|
+
}
|
|
189
|
+
writeInsightFile(root, row);
|
|
190
|
+
summary.proposed += 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return summary;
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
releaseLock(deps.lockPath);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/connect/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAA0B,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAsB,MAAM,aAAa,CAAC;AAEnG,MAAM,oBAAoB,GAAG,GAAG,CAAC;AA6BjC,SAAS,YAAY;IACnB,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;QAC1F,eAAe,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;QACnF,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE;KACjC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,CAAgB;IAClC,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS;QAC7B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ;QAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO;QACzB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ;QACvB,KAAK,EAAE,CAAC,CAAC,KAAK;KACf,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,UAAU,CAAC,GAAW,EAAE,EAAW,EAAE,IAAY,EAAE,GAAS;IACnE,MAAM,GAAG,GAAG,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,GAAG,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC;IAC/D,yEAAyE;IACzE,mDAAmD;IACnD,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,IAAI,GAAG,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CAAC,0DAA0D,IAAI,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACrG,CAAC;IACD,MAAM,IAAI,GAAe;QACvB,GAAG,GAAG;QACN,MAAM,EAAE,UAAU;QAClB,SAAS,EAAE,MAAM;QACjB,eAAe,EAAE,KAAK;QACtB,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE;KAC7B,CAAC;IACF,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACvB,gBAAgB,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IACvC,OAAO,YAAY,EAAE,CAAC;AACxB,CAAC;AAED,2EAA2E;AAC3E,sEAAsE;AACtE,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,GAAW,EACX,EAAW,EACX,IAA8C,EAC9C,IAAiB;IAEjB,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3B,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,OAAO,UAAU,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3F,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,QAAQ,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAEvG,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,yBAAyB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACrC,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;QACjC,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC1D,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC;QACjF,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC;QAEnC,6EAA6E;QAC7E,oDAAoD;QACpD,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC;QACnC,MAAM,IAAI,GAAiC,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;YACnD,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC;YAC1H,IAAI,GAAG,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACjC,OAAO,CAAC,eAAe,IAAI,CAAC,CAAC;gBAC7B,SAAS;YACX,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;gBACtC,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC;gBACvB,SAAS;YACX,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,KAAK,iBAAiB,IAAI,GAAG,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;gBACxE,mEAAmE;gBACnE,wEAAwE;gBACxE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC;oBACpB,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB;oBAC/B,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;iBACvD,CAAC,CAAC;gBACH,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC9C,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC;oBACvB,SAAS;gBACX,CAAC;YACH,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACf,CAAC;QACD,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC;QACtD,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAE9C,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,KAAK,GAAkB,CAAC,CAAC;YAC7B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;gBAC5G,KAAK,GAAG,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;YAChE,CAAC;YACD,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC;YAC3B,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,MAAM,eAAe,GAAG,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;QACzD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC;YACtB,IAAI,IAAY,CAAC;YACjB,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC;gBACzB,SAAS;YACX,CAAC;YACD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YACnC,MAAM,CAAC,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACxE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gBACf,OAAO,CAAC,UAAU,IAAI,CAAC,CAAC;gBACxB,SAAS;YACX,CAAC;YACD,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;YACvC,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAClC,MAAM,UAAU,GAAG,MAAM,qBAAqB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;YAChF,MAAM,OAAO,GAAG,cAAc,CAC5B,EAAE,UAAU,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,EACrF,EAAE,CAAC,YAAY,EAAE,EACjB,eAAe,EACf,cAAc,CACf,CAAC;YACF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;YACrC,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,MAAM,GAAG;gBACb,gBAAgB,EAAE,GAAG,CAAC,QAAQ;gBAC9B,YAAY,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;gBACjD,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACtD,QAAQ;gBACR,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM;gBAC7B,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;aACvB,CAAC;YACF,IAAI,OAAO,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACrC,OAAO,CAAC,eAAe,IAAI,CAAC,CAAC;YAC/B,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;gBACjD,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC;YACzB,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;gBACjD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;gBAC7C,EAAE,CAAC,aAAa,CAAC;oBACf,GAAG,OAAO,CAAC,OAAO;oBAClB,eAAe,EAAE,IAAI;oBACrB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;oBACvD,SAAS,EAAE,GAAG;iBACf,CAAC,CAAC;gBACH,OAAO,CAAC,gBAAgB,IAAI,CAAC,CAAC;YAChC,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBAC9C,MAAM,IAAI,GAAe,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;gBAC3F,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBACvB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC7B,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;gBACxB,MAAM,GAAG,GAAe;oBACtB,EAAE;oBACF,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC7B,WAAW,EAAE,gBAAgB;oBAC7B,MAAM,EAAE,UAAU;oBAClB,SAAS,EAAE,MAAM;oBACjB,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,GAAG,EAAE,CAAC,CAAC,GAAG;oBACV,GAAG,MAAM;oBACT,eAAe,EAAE,KAAK;oBACtB,OAAO,EAAE,IAAI;oBACb,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,SAAS,EAAE,GAAG;oBACd,SAAS,EAAE,GAAG;iBACf,CAAC;gBACF,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;gBACtB,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;oBACxB,EAAE,CAAC,mBAAmB,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;gBAChG,CAAC;gBACD,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC5B,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;YAAS,CAAC;QACT,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Rule and why text is model-written and owner-editable, then shown in the
|
|
2
|
+
// terminal for approval and written into CLAUDE.md, which agents read as
|
|
3
|
+
// instructions. One normalization, applied at every boundary, so what the
|
|
4
|
+
// owner judges is byte-for-byte what gets stored and written:
|
|
5
|
+
// - no C0/C1 control characters (ANSI escapes could hide or recolor text)
|
|
6
|
+
// - no bidi overrides/isolates (could reorder what the owner reads)
|
|
7
|
+
// - no HTML-comment syntax (no fake VIR markers or rule ids)
|
|
8
|
+
// - one line (no injected headings)
|
|
9
|
+
const CONTROL = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
10
|
+
const BIDI = /[\u202a-\u202e\u2066-\u2069\u200e\u200f\u061c]/g;
|
|
11
|
+
export function ruleText(text) {
|
|
12
|
+
return text
|
|
13
|
+
.replace(/\r\n|\r|\n|\t/g, " ")
|
|
14
|
+
.replace(CONTROL, "")
|
|
15
|
+
.replace(BIDI, "")
|
|
16
|
+
.replace(/<!--|-->/g, " ")
|
|
17
|
+
.replace(/\s+/g, " ")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/connect/text.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,0EAA0E;AAC1E,8DAA8D;AAC9D,2EAA2E;AAC3E,qEAAqE;AACrE,8DAA8D;AAC9D,qCAAqC;AACrC,MAAM,OAAO,GAAG,+BAA+B,CAAC;AAChD,MAAM,IAAI,GAAG,iDAAiD,CAAC;AAE/D,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,IAAI;SACR,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC;SAC9B,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;SACjB,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC;SACzB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/connect/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { normalizeLesson } from "./extract.js";
|
|
2
|
+
import { qualifies, recurrence } from "./qualify.js";
|
|
3
|
+
import { ruleText } from "./text.js";
|
|
4
|
+
// Projects that say nothing about where a rule belongs.
|
|
5
|
+
export const NO_PROJECT = new Set(["", "codex-scratch", "unknown"]);
|
|
6
|
+
export function buildVerifyPrompt(members) {
|
|
7
|
+
const lines = members.map((m, i) => `[L${i + 1}] (${m.project || "-"}, ${m.noteDate.slice(0, 10)}) ${m.text}`);
|
|
8
|
+
return `You are reviewing lessons a developer recorded across separate coding sessions.
|
|
9
|
+
Decide which of them are the SAME lesson, then state it as one rule.
|
|
10
|
+
|
|
11
|
+
Lessons:
|
|
12
|
+
${lines.join("\n")}
|
|
13
|
+
|
|
14
|
+
Return JSON only:
|
|
15
|
+
{"same_lesson": ["L…"], "rule": "<imperative one-liner>", "why": "<1-2 sentences: the failure it prevents>",
|
|
16
|
+
"evidence": [{"id": "L…", "quote": "<exact substring of that lesson>"}]}
|
|
17
|
+
|
|
18
|
+
Rules: include a lesson in same_lesson only if it teaches the same thing. Every quote must be copied
|
|
19
|
+
verbatim from its lesson. If fewer than three lessons are the same, return {"same_lesson": []}.`;
|
|
20
|
+
}
|
|
21
|
+
export function parseVerdict(text) {
|
|
22
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
23
|
+
if (!m)
|
|
24
|
+
return null;
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
raw = JSON.parse(m[0]);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (!raw || typeof raw !== "object")
|
|
33
|
+
return null;
|
|
34
|
+
const o = raw;
|
|
35
|
+
if (!Array.isArray(o.same_lesson) || typeof o.rule !== "string" || typeof o.why !== "string" || !Array.isArray(o.evidence)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
same_lesson: o.same_lesson.filter((x) => typeof x === "string"),
|
|
40
|
+
rule: o.rule,
|
|
41
|
+
why: o.why,
|
|
42
|
+
evidence: o.evidence
|
|
43
|
+
.filter((e) => !!e && typeof e === "object")
|
|
44
|
+
.filter((e) => typeof e.id === "string" && typeof e.quote === "string"),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function computeScope(members) {
|
|
48
|
+
const slugs = new Set(members.map((m) => m.project).filter((p) => !NO_PROJECT.has(p)));
|
|
49
|
+
const only = [...slugs][0];
|
|
50
|
+
return slugs.size === 1 && only !== undefined ? `project:${only}` : "global";
|
|
51
|
+
}
|
|
52
|
+
const norm = (s) => normalizeLesson(s).toLowerCase();
|
|
53
|
+
// Everything the model claims is checked against the cluster: ids must exist,
|
|
54
|
+
// quotes must be real substrings, and the surviving members must still recur.
|
|
55
|
+
export function validateVerdict(members, v) {
|
|
56
|
+
const rule = ruleText(v.rule);
|
|
57
|
+
const why = ruleText(v.why);
|
|
58
|
+
if (rule.length === 0)
|
|
59
|
+
return null;
|
|
60
|
+
const byId = new Map(members.map((m, i) => [`L${i + 1}`, m]));
|
|
61
|
+
const claimed = new Set(v.same_lesson.filter((id) => byId.has(id)));
|
|
62
|
+
const evidence = [];
|
|
63
|
+
for (const e of v.evidence) {
|
|
64
|
+
const lesson = byId.get(e.id);
|
|
65
|
+
if (!lesson || !claimed.has(e.id))
|
|
66
|
+
continue;
|
|
67
|
+
const q = norm(e.quote);
|
|
68
|
+
if (q.length === 0 || !norm(lesson.text).includes(q))
|
|
69
|
+
continue;
|
|
70
|
+
evidence.push({ lesson, quote: normalizeLesson(e.quote) });
|
|
71
|
+
}
|
|
72
|
+
const kept = members.filter((m) => evidence.some((e) => e.lesson === m));
|
|
73
|
+
if (!qualifies(recurrence(kept)))
|
|
74
|
+
return null;
|
|
75
|
+
return { rule, why, members: kept, evidence, scope: computeScope(kept), keptOfTotal: [kept.length, members.length] };
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=verify.js.map
|