agentcache 0.2.4 → 0.3.1
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 +105 -36
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/chunk-R4SJESNM.js +684 -0
- package/dist/chunk-RXBTPJVW.js +74 -0
- package/dist/{chunk-OSFK44XC.js → chunk-TE6DBLJ5.js} +137 -3
- package/dist/cli.js +11 -7
- package/dist/compile-all-DOQ5XU5K.js +461 -0
- package/dist/mcp.js +27 -532
- package/dist/{paths-LEZQCRKI.js → paths-LOLEIMI5.js} +1 -1
- package/dist/postinstall.js +8 -1
- package/dist/{pre-tool-use-TPCPTJXS.js → pre-tool-use-OWWS54XG.js} +1 -1
- package/dist/{session-start-BIY7CBXU.js → session-start-YSRUGD5N.js} +8 -4
- package/dist/{setup-TVSEXURZ.js → setup-5APZETPV.js} +1 -1
- package/dist/{sqlite-5V565IV3.js → sqlite-AUND6HGQ.js} +1 -1
- package/dist/{stop-6MKD743B.js → stop-6SROS34Q.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-MLKGABMK.js +0 -9
package/dist/mcp.js
CHANGED
|
@@ -1,548 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
2
|
evaluatePolicy
|
|
3
3
|
} from "./chunk-T7BJPANN.js";
|
|
4
|
+
import {
|
|
5
|
+
computeCanonicalHash,
|
|
6
|
+
processClustering,
|
|
7
|
+
processExtraction,
|
|
8
|
+
startCompile
|
|
9
|
+
} from "./chunk-R4SJESNM.js";
|
|
10
|
+
import {
|
|
11
|
+
spawnCompileAll
|
|
12
|
+
} from "./chunk-RXBTPJVW.js";
|
|
4
13
|
import {
|
|
5
14
|
parseTranscript
|
|
6
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-TE6DBLJ5.js";
|
|
7
16
|
import {
|
|
8
17
|
findProjectRoot,
|
|
9
18
|
getDbPath,
|
|
10
|
-
getGitContext,
|
|
11
19
|
getProjectId,
|
|
12
20
|
isInitialized
|
|
13
21
|
} from "./chunk-WHP4Z32Z.js";
|
|
14
22
|
import {
|
|
15
23
|
SqliteKnowledgeRepository
|
|
16
24
|
} from "./chunk-MMSMDJ4O.js";
|
|
17
|
-
import "./chunk-
|
|
25
|
+
import "./chunk-KFQGP6VL.js";
|
|
18
26
|
|
|
19
27
|
// src/mcp.ts
|
|
20
28
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
29
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
30
|
import { CallToolRequestSchema, ListToolsRequestSchema, RootsListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
-
|
|
24
|
-
// src/knowledge/compiler.ts
|
|
25
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
26
|
-
|
|
27
|
-
// src/knowledge/passes/1-extractor.ts
|
|
28
|
-
import { randomUUID } from "crypto";
|
|
29
|
-
var EXTRACT_PROMPT_VERSION = "extract-v1";
|
|
30
|
-
function buildExtractionPrompt(events) {
|
|
31
|
-
const transcript = events.filter((e) => e.content || e.tool_name).map((e) => {
|
|
32
|
-
if (e.role) return `[${e.role}]: ${e.content}`;
|
|
33
|
-
if (e.tool_name) return `[tool:${e.tool_name}]: ${JSON.stringify(e.tool_input).slice(0, 500)}`;
|
|
34
|
-
return "";
|
|
35
|
-
}).filter(Boolean).join("\n");
|
|
36
|
-
return `You are a knowledge extraction engine. Analyze this coding session transcript and extract distinct learnings.
|
|
37
|
-
|
|
38
|
-
Extract into four types:
|
|
39
|
-
- rule: a standing instruction or constraint the developer expressed
|
|
40
|
-
- lesson: a mistake made and what fixed it
|
|
41
|
-
- decision: an architectural or design choice with rationale
|
|
42
|
-
- context: current task state, open threads, what was left in progress
|
|
43
|
-
|
|
44
|
-
Return ONLY valid JSON: { "observations": [{ "type": "rule"|"lesson"|"decision"|"context", "content": "...", "sourceQuote": "...", "confidence": "high"|"medium" }] }
|
|
45
|
-
|
|
46
|
-
Only return high and medium confidence items. Ignore conversational noise, tool outputs, and implementation details that aren't generalizable.
|
|
47
|
-
|
|
48
|
-
<transcript>
|
|
49
|
-
${transcript}
|
|
50
|
-
</transcript>`;
|
|
51
|
-
}
|
|
52
|
-
function parseExtractionResponse(text, sessionId, project) {
|
|
53
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
54
|
-
if (!jsonMatch) return [];
|
|
55
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
56
|
-
if (!parsed.observations || !Array.isArray(parsed.observations)) return [];
|
|
57
|
-
const now = Date.now();
|
|
58
|
-
return parsed.observations.filter((o) => o.type && o.content && o.confidence).filter((o) => ["high", "medium"].includes(o.confidence)).map((o) => ({
|
|
59
|
-
id: `obs_${randomUUID().slice(0, 8)}`,
|
|
60
|
-
sessionId,
|
|
61
|
-
timestamp: now,
|
|
62
|
-
type: o.type,
|
|
63
|
-
content: o.content,
|
|
64
|
-
sourceQuote: o.sourceQuote || "",
|
|
65
|
-
confidence: o.confidence,
|
|
66
|
-
project,
|
|
67
|
-
scope: o.scope || (o.type === "rule" || o.type === "lesson" ? "global" : "project")
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// src/knowledge/passes/2-normalizer.ts
|
|
72
|
-
var FILLER_PATTERNS = [
|
|
73
|
-
/^i (noticed|realized|learned|found|discovered|think) that /i,
|
|
74
|
-
/^it (seems|appears|looks) (like|that) /i,
|
|
75
|
-
/^we should /i,
|
|
76
|
-
/^you should /i,
|
|
77
|
-
/^basically,? /i,
|
|
78
|
-
/^essentially,? /i,
|
|
79
|
-
/^actually,? /i
|
|
80
|
-
];
|
|
81
|
-
var IMPERATIVE_RULES = [
|
|
82
|
-
[/^you should never /i, "Never "],
|
|
83
|
-
[/^we should never /i, "Never "],
|
|
84
|
-
[/^don't ever /i, "Never "],
|
|
85
|
-
[/^never /i, "Never "],
|
|
86
|
-
[/^you should always /i, "Always "],
|
|
87
|
-
[/^we should always /i, "Always "],
|
|
88
|
-
[/^always /i, "Always "]
|
|
89
|
-
];
|
|
90
|
-
function normalize(observations) {
|
|
91
|
-
const normalized = observations.map((obs) => ({
|
|
92
|
-
...obs,
|
|
93
|
-
content: normalizeContent(obs.content, obs.type)
|
|
94
|
-
}));
|
|
95
|
-
const seen = /* @__PURE__ */ new Set();
|
|
96
|
-
return normalized.filter((obs) => {
|
|
97
|
-
const key = obs.content.toLowerCase().trim();
|
|
98
|
-
if (seen.has(key)) return false;
|
|
99
|
-
seen.add(key);
|
|
100
|
-
return true;
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
function normalizeContent(content, type) {
|
|
104
|
-
let text = content.trim();
|
|
105
|
-
for (const pattern of FILLER_PATTERNS) {
|
|
106
|
-
text = text.replace(pattern, "");
|
|
107
|
-
}
|
|
108
|
-
if (type === "rule") {
|
|
109
|
-
for (const [pattern, replacement] of IMPERATIVE_RULES) {
|
|
110
|
-
if (pattern.test(text)) {
|
|
111
|
-
text = text.replace(pattern, replacement);
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
text = text.charAt(0).toUpperCase() + text.slice(1);
|
|
117
|
-
const firstSentenceEnd = text.search(/\. [A-Z]/);
|
|
118
|
-
if (firstSentenceEnd > 0) {
|
|
119
|
-
text = text.slice(0, firstSentenceEnd + 1);
|
|
120
|
-
}
|
|
121
|
-
return text;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// src/knowledge/passes/3-canonicalizer.ts
|
|
125
|
-
import { createHash } from "crypto";
|
|
126
|
-
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
127
|
-
"a",
|
|
128
|
-
"an",
|
|
129
|
-
"the",
|
|
130
|
-
"is",
|
|
131
|
-
"are",
|
|
132
|
-
"was",
|
|
133
|
-
"were",
|
|
134
|
-
"be",
|
|
135
|
-
"been",
|
|
136
|
-
"being",
|
|
137
|
-
"have",
|
|
138
|
-
"has",
|
|
139
|
-
"had",
|
|
140
|
-
"do",
|
|
141
|
-
"does",
|
|
142
|
-
"did",
|
|
143
|
-
"will",
|
|
144
|
-
"would",
|
|
145
|
-
"could",
|
|
146
|
-
"should",
|
|
147
|
-
"may",
|
|
148
|
-
"might",
|
|
149
|
-
"shall",
|
|
150
|
-
"can",
|
|
151
|
-
"need",
|
|
152
|
-
"must",
|
|
153
|
-
"to",
|
|
154
|
-
"of",
|
|
155
|
-
"in",
|
|
156
|
-
"for",
|
|
157
|
-
"on",
|
|
158
|
-
"with",
|
|
159
|
-
"at",
|
|
160
|
-
"by",
|
|
161
|
-
"from",
|
|
162
|
-
"as",
|
|
163
|
-
"into",
|
|
164
|
-
"through",
|
|
165
|
-
"during",
|
|
166
|
-
"before",
|
|
167
|
-
"after",
|
|
168
|
-
"above",
|
|
169
|
-
"below",
|
|
170
|
-
"this",
|
|
171
|
-
"that",
|
|
172
|
-
"these",
|
|
173
|
-
"those",
|
|
174
|
-
"it",
|
|
175
|
-
"its",
|
|
176
|
-
"and",
|
|
177
|
-
"but",
|
|
178
|
-
"or",
|
|
179
|
-
"nor",
|
|
180
|
-
"not",
|
|
181
|
-
"so",
|
|
182
|
-
"yet",
|
|
183
|
-
"all",
|
|
184
|
-
"each",
|
|
185
|
-
"every",
|
|
186
|
-
"both",
|
|
187
|
-
"few",
|
|
188
|
-
"more",
|
|
189
|
-
"most",
|
|
190
|
-
"i",
|
|
191
|
-
"we",
|
|
192
|
-
"you",
|
|
193
|
-
"they",
|
|
194
|
-
"he",
|
|
195
|
-
"she"
|
|
196
|
-
]);
|
|
197
|
-
var ANTONYM_MAP = [
|
|
198
|
-
[/\bnever\b/g, "forbidden"],
|
|
199
|
-
[/\bdon'?t\b/g, "forbidden"],
|
|
200
|
-
[/\bavoid\b/g, "forbidden"],
|
|
201
|
-
[/\bprohibit(ed)?\b/g, "forbidden"],
|
|
202
|
-
[/\balways\b/g, "required"],
|
|
203
|
-
[/\bmust\b/g, "required"],
|
|
204
|
-
[/\brequire(d)?\b/g, "required"],
|
|
205
|
-
[/\buse\b/g, "use"],
|
|
206
|
-
[/\bprefer\b/g, "use"]
|
|
207
|
-
];
|
|
208
|
-
function canonicalize(observations, existingCanonicalKeys) {
|
|
209
|
-
const canonicalized = observations.map((obs) => ({
|
|
210
|
-
...obs,
|
|
211
|
-
canonicalKey: computeCanonicalKey(obs.content)
|
|
212
|
-
}));
|
|
213
|
-
const existingSet = new Set(existingCanonicalKeys || []);
|
|
214
|
-
const autoReinforced = [];
|
|
215
|
-
const needsClustering = [];
|
|
216
|
-
for (const obs of canonicalized) {
|
|
217
|
-
if (existingSet.has(obs.canonicalKey)) {
|
|
218
|
-
autoReinforced.push(obs);
|
|
219
|
-
} else {
|
|
220
|
-
needsClustering.push(obs);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return { observations: canonicalized, autoReinforced, needsClustering };
|
|
224
|
-
}
|
|
225
|
-
function computeCanonicalKey(content) {
|
|
226
|
-
let text = content.toLowerCase().trim();
|
|
227
|
-
for (const [pattern, replacement] of ANTONYM_MAP) {
|
|
228
|
-
text = text.replace(pattern, replacement);
|
|
229
|
-
}
|
|
230
|
-
text = text.replace(/[^\w\s]/g, " ");
|
|
231
|
-
const tokens = text.split(/\s+/).filter((t) => !STOP_WORDS.has(t) && t.length > 1).sort();
|
|
232
|
-
return tokens.join(" ");
|
|
233
|
-
}
|
|
234
|
-
function computeCanonicalHash(content) {
|
|
235
|
-
const key = computeCanonicalKey(content);
|
|
236
|
-
return createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// src/knowledge/passes/4-clusterer.ts
|
|
240
|
-
var CLUSTER_PROMPT_VERSION = "cluster-v1";
|
|
241
|
-
function buildClusteringPrompt(observations, existingItems) {
|
|
242
|
-
const obsJson = observations.map((o) => ({
|
|
243
|
-
id: o.id,
|
|
244
|
-
type: o.type,
|
|
245
|
-
content: o.content,
|
|
246
|
-
canonicalKey: o.canonicalKey
|
|
247
|
-
}));
|
|
248
|
-
const itemsJson = existingItems.filter((i) => i.status === "active").map((i) => ({
|
|
249
|
-
id: i.id,
|
|
250
|
-
type: i.type,
|
|
251
|
-
content: i.content,
|
|
252
|
-
canonicalHash: i.canonicalHash
|
|
253
|
-
}));
|
|
254
|
-
return `You are a knowledge clustering engine. Determine whether new observations create new knowledge or relate to existing items. Be conservative.
|
|
255
|
-
|
|
256
|
-
For each observation, assign an action:
|
|
257
|
-
CREATE \u2014 genuinely new knowledge, no existing item covers it
|
|
258
|
-
REINFORCE \u2014 confirms an existing item (provide targetKnowledgeItemId)
|
|
259
|
-
SUPERSEDE \u2014 replaces/corrects an existing item (provide targetKnowledgeItemId)
|
|
260
|
-
DEPRECATE \u2014 makes an existing item irrelevant (provide targetKnowledgeItemId)
|
|
261
|
-
IGNORE \u2014 duplicate, trivial, or too vague to keep
|
|
262
|
-
|
|
263
|
-
New observations:
|
|
264
|
-
${JSON.stringify(obsJson, null, 2)}
|
|
265
|
-
|
|
266
|
-
Existing knowledge items:
|
|
267
|
-
${JSON.stringify(itemsJson, null, 2)}
|
|
268
|
-
|
|
269
|
-
Return ONLY valid JSON: { "clusters": [{ "observationId": "...", "action": "CREATE"|"REINFORCE"|"SUPERSEDE"|"DEPRECATE"|"IGNORE", "targetKnowledgeItemId": "..." (only if action targets an existing item), "reasoning": "..." }] }`;
|
|
270
|
-
}
|
|
271
|
-
function parseClusteringResponse(text, observations) {
|
|
272
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
273
|
-
if (!jsonMatch) {
|
|
274
|
-
return observations.map((o) => ({ observationId: o.id, action: "CREATE", reasoning: "Parse failure \u2014 defaulting to CREATE" }));
|
|
275
|
-
}
|
|
276
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
277
|
-
if (!parsed.clusters || !Array.isArray(parsed.clusters)) {
|
|
278
|
-
return observations.map((o) => ({ observationId: o.id, action: "CREATE", reasoning: "Parse failure \u2014 defaulting to CREATE" }));
|
|
279
|
-
}
|
|
280
|
-
return parsed.clusters.map((c) => ({
|
|
281
|
-
observationId: c.observationId,
|
|
282
|
-
action: c.action || "CREATE",
|
|
283
|
-
targetKnowledgeItemId: c.targetKnowledgeItemId || void 0,
|
|
284
|
-
reasoning: c.reasoning || ""
|
|
285
|
-
}));
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// src/knowledge/passes/5-contradiction.ts
|
|
289
|
-
var CONTRADICTION_PROMPT_VERSION = "contradiction-v1";
|
|
290
|
-
|
|
291
|
-
// src/knowledge/passes/6-compile.ts
|
|
292
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
293
|
-
function calculateConfidence(count) {
|
|
294
|
-
if (count >= 7) return "high";
|
|
295
|
-
if (count >= 3) return "medium";
|
|
296
|
-
return "low";
|
|
297
|
-
}
|
|
298
|
-
function compileKnowledge(clusters, existingItems, observations, project, now) {
|
|
299
|
-
const itemMap = new Map(existingItems.map((i) => [i.id, { ...i }]));
|
|
300
|
-
const obsMap = new Map(observations.map((o) => [o.id, o]));
|
|
301
|
-
const result = {
|
|
302
|
-
created: [],
|
|
303
|
-
reinforced: [],
|
|
304
|
-
superseded: [],
|
|
305
|
-
deprecated: [],
|
|
306
|
-
ignored: 0
|
|
307
|
-
};
|
|
308
|
-
for (const cluster of clusters) {
|
|
309
|
-
const obs = obsMap.get(cluster.observationId);
|
|
310
|
-
if (!obs) continue;
|
|
311
|
-
switch (cluster.action) {
|
|
312
|
-
case "CREATE": {
|
|
313
|
-
const newItem = {
|
|
314
|
-
id: `ki_${randomUUID2().slice(0, 8)}`,
|
|
315
|
-
canonicalHash: computeCanonicalHash(obs.content),
|
|
316
|
-
type: obs.type,
|
|
317
|
-
title: obs.content.slice(0, 80),
|
|
318
|
-
content: obs.content,
|
|
319
|
-
confidence: "low",
|
|
320
|
-
observationCount: 1,
|
|
321
|
-
authority: "AUTO",
|
|
322
|
-
status: "active",
|
|
323
|
-
supersededById: void 0,
|
|
324
|
-
enforce: false,
|
|
325
|
-
project,
|
|
326
|
-
scope: obs.scope || (obs.type === "rule" || obs.type === "lesson" ? "global" : "project"),
|
|
327
|
-
createdAt: now,
|
|
328
|
-
updatedAt: now,
|
|
329
|
-
lastSeenAt: now,
|
|
330
|
-
metadata: {}
|
|
331
|
-
};
|
|
332
|
-
result.created.push(newItem);
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
case "REINFORCE": {
|
|
336
|
-
const target = itemMap.get(cluster.targetKnowledgeItemId);
|
|
337
|
-
if (!target) break;
|
|
338
|
-
target.observationCount += 1;
|
|
339
|
-
target.lastSeenAt = now;
|
|
340
|
-
target.updatedAt = now;
|
|
341
|
-
target.confidence = calculateConfidence(target.observationCount);
|
|
342
|
-
result.reinforced.push(target);
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
case "SUPERSEDE": {
|
|
346
|
-
const target = itemMap.get(cluster.targetKnowledgeItemId);
|
|
347
|
-
if (target) {
|
|
348
|
-
const newItem = {
|
|
349
|
-
id: `ki_${randomUUID2().slice(0, 8)}`,
|
|
350
|
-
canonicalHash: computeCanonicalHash(obs.content),
|
|
351
|
-
type: obs.type,
|
|
352
|
-
title: obs.content.slice(0, 80),
|
|
353
|
-
content: obs.content,
|
|
354
|
-
confidence: "low",
|
|
355
|
-
observationCount: 1,
|
|
356
|
-
authority: "AUTO",
|
|
357
|
-
status: "active",
|
|
358
|
-
supersededById: void 0,
|
|
359
|
-
enforce: false,
|
|
360
|
-
project,
|
|
361
|
-
scope: obs.scope || (obs.type === "rule" || obs.type === "lesson" ? "global" : "project"),
|
|
362
|
-
createdAt: now,
|
|
363
|
-
updatedAt: now,
|
|
364
|
-
lastSeenAt: now,
|
|
365
|
-
metadata: {}
|
|
366
|
-
};
|
|
367
|
-
target.status = "superseded";
|
|
368
|
-
target.updatedAt = now;
|
|
369
|
-
target.supersededById = newItem.id;
|
|
370
|
-
result.superseded.push(target);
|
|
371
|
-
result.created.push(newItem);
|
|
372
|
-
}
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
case "DEPRECATE": {
|
|
376
|
-
const target = itemMap.get(cluster.targetKnowledgeItemId);
|
|
377
|
-
if (target) {
|
|
378
|
-
target.status = "deprecated";
|
|
379
|
-
target.updatedAt = now;
|
|
380
|
-
result.deprecated.push(target);
|
|
381
|
-
}
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
case "IGNORE":
|
|
385
|
-
result.ignored += 1;
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return result;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// src/knowledge/compiler.ts
|
|
393
|
-
var COMPILER_VERSION = "0.1.0";
|
|
394
|
-
function startCompile(events, sessionId, project, projectRoot, repo, transcriptPath) {
|
|
395
|
-
const git = getGitContext(projectRoot);
|
|
396
|
-
const session = {
|
|
397
|
-
id: sessionId,
|
|
398
|
-
project,
|
|
399
|
-
startedAt: Date.now() - 6e4,
|
|
400
|
-
endedAt: Date.now(),
|
|
401
|
-
gitBranch: git.branch,
|
|
402
|
-
gitCommit: git.commit,
|
|
403
|
-
provider: "agent",
|
|
404
|
-
model: "host-agent",
|
|
405
|
-
transcriptPath: transcriptPath || "",
|
|
406
|
-
observationCount: 0
|
|
407
|
-
};
|
|
408
|
-
repo.saveSession(session);
|
|
409
|
-
const prompt = buildExtractionPrompt(events);
|
|
410
|
-
return { sessionId, project, projectRoot, prompt };
|
|
411
|
-
}
|
|
412
|
-
function processExtraction(repo, responseText, sessionId, project, projectRoot) {
|
|
413
|
-
const rawObservations = parseExtractionResponse(responseText, sessionId, project);
|
|
414
|
-
const normalized = normalize(rawObservations);
|
|
415
|
-
const existingItems = repo.getKnowledgeItems(project, { status: "active" });
|
|
416
|
-
const existingKeys = existingItems.map((i) => computeCanonicalKey(i.content));
|
|
417
|
-
const canonicalized = canonicalize(normalized, existingKeys);
|
|
418
|
-
for (const obs of canonicalized.autoReinforced) {
|
|
419
|
-
const matchingItem = existingItems.find(
|
|
420
|
-
(item) => computeCanonicalKey(item.content) === obs.canonicalKey
|
|
421
|
-
);
|
|
422
|
-
if (matchingItem) {
|
|
423
|
-
const newCount = matchingItem.observationCount + 1;
|
|
424
|
-
const confidence = newCount >= 7 ? "high" : newCount >= 3 ? "medium" : "low";
|
|
425
|
-
repo.updateKnowledgeItem(matchingItem.id, {
|
|
426
|
-
observationCount: newCount,
|
|
427
|
-
lastSeenAt: Date.now(),
|
|
428
|
-
updatedAt: Date.now(),
|
|
429
|
-
confidence
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
repo.saveObservations(normalized);
|
|
434
|
-
if (canonicalized.needsClustering.length === 0) {
|
|
435
|
-
saveCompileRun(repo, sessionId, project, normalized.length, canonicalized.autoReinforced.length, 0, 0, 0, 0, 0, Date.now());
|
|
436
|
-
return {
|
|
437
|
-
status: "complete",
|
|
438
|
-
diagnostics: formatDiagnostics(normalized.length, canonicalized.autoReinforced.length, 0, 0, 0, 0, 0, project, sessionId)
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
const clusteringPrompt = buildClusteringPrompt(canonicalized.needsClustering, existingItems);
|
|
442
|
-
return {
|
|
443
|
-
status: "needs_clustering",
|
|
444
|
-
clusteringPrompt,
|
|
445
|
-
sessionId
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
function processClustering(repo, responseText, sessionId, project, projectRoot) {
|
|
449
|
-
const startedAt = Date.now();
|
|
450
|
-
const existingItems = repo.getKnowledgeItems(project, { status: "active" });
|
|
451
|
-
const observations = repo.getObservations(project);
|
|
452
|
-
const sessionObs = observations.filter((o) => o.sessionId === sessionId);
|
|
453
|
-
const canonicalized = canonicalize(sessionObs);
|
|
454
|
-
const needsClustering = canonicalized.needsClustering;
|
|
455
|
-
const clusters = parseClusteringResponse(responseText, needsClustering);
|
|
456
|
-
const contradictions = [];
|
|
457
|
-
const supersedeActions = clusters.filter((c) => c.action === "SUPERSEDE");
|
|
458
|
-
for (const s of supersedeActions) {
|
|
459
|
-
if (s.targetKnowledgeItemId) {
|
|
460
|
-
const target = existingItems.find((i) => i.id === s.targetKnowledgeItemId);
|
|
461
|
-
if (target) {
|
|
462
|
-
contradictions.push({
|
|
463
|
-
id: `con_${randomUUID3().slice(0, 8)}`,
|
|
464
|
-
project,
|
|
465
|
-
itemAId: target.id,
|
|
466
|
-
itemBId: s.observationId,
|
|
467
|
-
topic: target.title.slice(0, 50),
|
|
468
|
-
description: `"${target.content}" superseded by new observation`,
|
|
469
|
-
recommendation: "keep_newer",
|
|
470
|
-
resolved: false,
|
|
471
|
-
createdAt: Date.now()
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
for (const c of contradictions) {
|
|
477
|
-
repo.saveContradiction(c);
|
|
478
|
-
}
|
|
479
|
-
const now = Date.now();
|
|
480
|
-
const compiled = compileKnowledge(clusters, existingItems, needsClustering, project, now);
|
|
481
|
-
for (const item of compiled.created) repo.saveKnowledgeItem(item);
|
|
482
|
-
for (const item of compiled.reinforced) {
|
|
483
|
-
repo.updateKnowledgeItem(item.id, {
|
|
484
|
-
observationCount: item.observationCount,
|
|
485
|
-
lastSeenAt: item.lastSeenAt,
|
|
486
|
-
updatedAt: item.updatedAt,
|
|
487
|
-
confidence: item.confidence
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
for (const item of compiled.superseded) {
|
|
491
|
-
repo.updateKnowledgeItem(item.id, {
|
|
492
|
-
status: item.status,
|
|
493
|
-
updatedAt: item.updatedAt,
|
|
494
|
-
supersededById: item.supersededById
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
for (const item of compiled.deprecated) {
|
|
498
|
-
repo.updateKnowledgeItem(item.id, { status: item.status, updatedAt: item.updatedAt });
|
|
499
|
-
}
|
|
500
|
-
const totalObs = sessionObs.length;
|
|
501
|
-
saveCompileRun(repo, sessionId, project, totalObs, 0, compiled.created.length, compiled.reinforced.length, compiled.superseded.length, compiled.deprecated.length, compiled.ignored, startedAt);
|
|
502
|
-
return {
|
|
503
|
-
status: "complete",
|
|
504
|
-
diagnostics: formatDiagnostics(totalObs, 0, compiled.created.length, compiled.reinforced.length, compiled.superseded.length, compiled.deprecated.length, compiled.ignored, project, sessionId)
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
function saveCompileRun(repo, sessionId, project, observationsProcessed, autoReinforced, created, reinforced, superseded, deprecated, ignored, startedAt) {
|
|
508
|
-
const endedAt = Date.now();
|
|
509
|
-
const run = {
|
|
510
|
-
id: `cr_${randomUUID3().slice(0, 8)}`,
|
|
511
|
-
project,
|
|
512
|
-
sessionId,
|
|
513
|
-
compilerVersion: COMPILER_VERSION,
|
|
514
|
-
promptVersions: { extract: EXTRACT_PROMPT_VERSION, cluster: CLUSTER_PROMPT_VERSION, contradiction: CONTRADICTION_PROMPT_VERSION },
|
|
515
|
-
startedAt,
|
|
516
|
-
endedAt,
|
|
517
|
-
durationMs: endedAt - startedAt,
|
|
518
|
-
observationsProcessed,
|
|
519
|
-
knowledgeCreated: created,
|
|
520
|
-
knowledgeReinforced: reinforced + autoReinforced,
|
|
521
|
-
knowledgeDeprecated: deprecated,
|
|
522
|
-
knowledgeSuperseded: superseded,
|
|
523
|
-
knowledgeIgnored: ignored,
|
|
524
|
-
contradictionsDetected: 0,
|
|
525
|
-
diagnostics: ""
|
|
526
|
-
};
|
|
527
|
-
repo.saveCompileRun(run);
|
|
528
|
-
}
|
|
529
|
-
function formatDiagnostics(extracted, autoReinforced, created, reinforced, superseded, deprecated, ignored, project, sessionId) {
|
|
530
|
-
return [
|
|
531
|
-
`AgentCache Compiler v${COMPILER_VERSION}`,
|
|
532
|
-
`Project: ${project} | Session: ${sessionId}`,
|
|
533
|
-
` ${extracted} observations processed`,
|
|
534
|
-
autoReinforced > 0 ? ` ${autoReinforced} auto-reinforced (no LLM needed)` : "",
|
|
535
|
-
` ${created} new knowledge items`,
|
|
536
|
-
` ${reinforced} reinforced`,
|
|
537
|
-
superseded > 0 ? ` ${superseded} superseded` : "",
|
|
538
|
-
deprecated > 0 ? ` ${deprecated} deprecated` : "",
|
|
539
|
-
ignored > 0 ? ` ${ignored} ignored` : ""
|
|
540
|
-
].filter(Boolean).join("\n");
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// src/mcp.ts
|
|
544
31
|
import { existsSync } from "fs";
|
|
545
|
-
import { randomUUID
|
|
32
|
+
import { randomUUID } from "crypto";
|
|
546
33
|
function defaultScope(type) {
|
|
547
34
|
return type === "rule" || type === "lesson" ? "global" : "project";
|
|
548
35
|
}
|
|
@@ -743,10 +230,18 @@ async function startMcpServer() {
|
|
|
743
230
|
if (!output) output = "No compiled knowledge yet. This will populate as you use AgentCache across sessions.\n";
|
|
744
231
|
const pendingCount = repo.getPendingCount();
|
|
745
232
|
if (pendingCount > 20) {
|
|
746
|
-
|
|
233
|
+
const spawned = spawnCompileAll();
|
|
234
|
+
if (spawned) {
|
|
235
|
+
output += `
|
|
236
|
+
---
|
|
237
|
+
Background compilation started for ${pendingCount} pending sessions. Knowledge will be available in future sessions.
|
|
238
|
+
`;
|
|
239
|
+
} else {
|
|
240
|
+
output += `
|
|
747
241
|
---
|
|
748
|
-
|
|
242
|
+
${pendingCount} sessions pending compilation (background compiler already running).
|
|
749
243
|
`;
|
|
244
|
+
}
|
|
750
245
|
} else if (pendingCount > 0) {
|
|
751
246
|
output += `
|
|
752
247
|
<!-- ${pendingCount} session(s) pending compilation (below threshold, will process when backlog grows). -->
|
|
@@ -758,7 +253,7 @@ KNOWLEDGE COMPILATION AVAILABLE: You have ${pendingCount} past sessions that hav
|
|
|
758
253
|
case "compile_submit": {
|
|
759
254
|
const args = request.params.arguments;
|
|
760
255
|
const project = args.project || detectedProject;
|
|
761
|
-
const sessionId = `sess_${
|
|
256
|
+
const sessionId = `sess_${randomUUID().slice(0, 8)}`;
|
|
762
257
|
const observationsWithScope = args.observations.map((o) => ({
|
|
763
258
|
...o,
|
|
764
259
|
scope: o.scope || defaultScope(o.type)
|
|
@@ -793,7 +288,7 @@ KNOWLEDGE COMPILATION AVAILABLE: You have ${pendingCount} past sessions that hav
|
|
|
793
288
|
if (events.length === 0) {
|
|
794
289
|
return { content: [{ type: "text", text: JSON.stringify({ message: "Empty transcript, skipped." }) }] };
|
|
795
290
|
}
|
|
796
|
-
const sessionId = `sess_${
|
|
291
|
+
const sessionId = `sess_${randomUUID().slice(0, 8)}`;
|
|
797
292
|
const project = entry.project || (args.project || detectedProject);
|
|
798
293
|
const state = startCompile(events, sessionId, project, entry.projectRoot || projectRoot, repo, entry.transcriptPath);
|
|
799
294
|
return { content: [{ type: "text", text: JSON.stringify({ sessionId: state.sessionId, prompt: state.prompt }) }] };
|
|
@@ -809,7 +304,7 @@ KNOWLEDGE COMPILATION AVAILABLE: You have ${pendingCount} past sessions that hav
|
|
|
809
304
|
const args = request.params.arguments;
|
|
810
305
|
const project = args.project || detectedProject;
|
|
811
306
|
const scope = args.scope || defaultScope(args.type);
|
|
812
|
-
const sessionId = `manual_${
|
|
307
|
+
const sessionId = `manual_${randomUUID().slice(0, 8)}`;
|
|
813
308
|
repo.saveSession({
|
|
814
309
|
id: sessionId,
|
|
815
310
|
project,
|
|
@@ -823,7 +318,7 @@ KNOWLEDGE COMPILATION AVAILABLE: You have ${pendingCount} past sessions that hav
|
|
|
823
318
|
observationCount: 1
|
|
824
319
|
});
|
|
825
320
|
const obs = {
|
|
826
|
-
id: `obs_${
|
|
321
|
+
id: `obs_${randomUUID().slice(0, 8)}`,
|
|
827
322
|
sessionId,
|
|
828
323
|
timestamp: Date.now(),
|
|
829
324
|
type: args.type,
|
|
@@ -835,7 +330,7 @@ KNOWLEDGE COMPILATION AVAILABLE: You have ${pendingCount} past sessions that hav
|
|
|
835
330
|
};
|
|
836
331
|
repo.saveObservation(obs);
|
|
837
332
|
repo.saveKnowledgeItem({
|
|
838
|
-
id: `ki_${
|
|
333
|
+
id: `ki_${randomUUID().slice(0, 8)}`,
|
|
839
334
|
canonicalHash: computeCanonicalHash(args.content),
|
|
840
335
|
type: args.type,
|
|
841
336
|
title: args.content.slice(0, 80),
|
package/dist/postinstall.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
spawnCompileAll
|
|
3
|
+
} from "./chunk-RXBTPJVW.js";
|
|
1
4
|
import {
|
|
2
5
|
detectInstalledIdes,
|
|
3
6
|
registerClaudeHooks,
|
|
@@ -11,7 +14,7 @@ import {
|
|
|
11
14
|
import {
|
|
12
15
|
SqliteKnowledgeRepository
|
|
13
16
|
} from "./chunk-MMSMDJ4O.js";
|
|
14
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-KFQGP6VL.js";
|
|
15
18
|
|
|
16
19
|
// src/postinstall.ts
|
|
17
20
|
import { mkdirSync } from "fs";
|
|
@@ -35,6 +38,10 @@ try {
|
|
|
35
38
|
console.error(`agentcache: registered with ${registered.join(", ")}`);
|
|
36
39
|
}
|
|
37
40
|
console.error("agentcache: ready. Knowledge compiles automatically across all sessions.");
|
|
41
|
+
const spawned = spawnCompileAll();
|
|
42
|
+
if (spawned) {
|
|
43
|
+
console.error("agentcache: background compilation started for existing transcripts.");
|
|
44
|
+
}
|
|
38
45
|
} catch (err) {
|
|
39
46
|
console.error(`agentcache postinstall: ${err.message}. Run 'agentcache setup' manually.`);
|
|
40
47
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findAllClaudeTranscripts,
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
findAllCodexTranscripts,
|
|
4
|
+
findAllContinueTranscripts,
|
|
5
|
+
findAllRooCodeTranscripts
|
|
6
|
+
} from "./chunk-TE6DBLJ5.js";
|
|
5
7
|
import {
|
|
6
8
|
getDbPath,
|
|
7
9
|
getProjectId,
|
|
@@ -10,7 +12,7 @@ import {
|
|
|
10
12
|
import {
|
|
11
13
|
SqliteKnowledgeRepository
|
|
12
14
|
} from "./chunk-MMSMDJ4O.js";
|
|
13
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-KFQGP6VL.js";
|
|
14
16
|
|
|
15
17
|
// src/hooks/session-start.ts
|
|
16
18
|
import { statSync } from "fs";
|
|
@@ -30,7 +32,9 @@ async function handleSessionStart() {
|
|
|
30
32
|
const compiledPaths = new Set(repo.getAllCompiledTranscriptPaths());
|
|
31
33
|
const allTranscripts = [
|
|
32
34
|
...findAllClaudeTranscripts(),
|
|
33
|
-
...findAllContinueTranscripts()
|
|
35
|
+
...findAllContinueTranscripts(),
|
|
36
|
+
...findAllCodexTranscripts(),
|
|
37
|
+
...findAllRooCodeTranscripts()
|
|
34
38
|
];
|
|
35
39
|
const oneMinuteAgo = Date.now() - 6e4;
|
|
36
40
|
const uncompiled = allTranscripts.filter((path) => {
|