agentcache 0.2.4 → 0.3.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 +38 -9
- package/dist/chunk-IER56P6A.js +589 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/chunk-RXBTPJVW.js +74 -0
- package/dist/{chunk-OSFK44XC.js → chunk-TE6DBLJ5.js} +137 -3
- package/dist/cli.js +10 -6
- package/dist/compile-all-RWCAU7MO.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/README.md
CHANGED
|
@@ -137,21 +137,15 @@ AgentCache prevents knowledge from growing unbounded:
|
|
|
137
137
|
| IDE | MCP | Auto-Approve | Transcript Recovery | Hooks |
|
|
138
138
|
|-----|-----|-------------|--------------------|----|
|
|
139
139
|
| Claude Code | Yes | Yes (automatic) | Full (JSONL) | Stop, SessionStart, PreToolUse |
|
|
140
|
-
| Cursor | Yes |
|
|
140
|
+
| Cursor | Yes | Yes (automatic) | Incremental only | — |
|
|
141
141
|
| Roo Code | Yes | Yes (automatic) | Incremental only | — |
|
|
142
142
|
| Windsurf | Yes | Yes (automatic) | Incremental only | — |
|
|
143
143
|
| Continue | Yes | Yes (automatic) | Full (JSON) | — |
|
|
144
144
|
| Codex | Yes | Yes (automatic) | Incremental only | — |
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
### Cursor: Enable Auto-Approve
|
|
146
|
+
All IDEs are fully auto-approved at install time — no manual steps required.
|
|
149
147
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
1. Open Cursor Settings → MCP
|
|
153
|
-
2. Find **agentcache** in the server list
|
|
154
|
-
3. Set tool approval to **"Always allow"** (or enable "Yolo mode" in Cursor settings for all tools)
|
|
148
|
+
"Incremental only" means if the agent submits observations during the session, they're saved. If the session terminates before any submission, those observations are lost (no transcript access).
|
|
155
149
|
|
|
156
150
|
## Data Storage
|
|
157
151
|
|
|
@@ -191,6 +185,41 @@ Projects are identified by a hash of their full filesystem path, not just the fo
|
|
|
191
185
|
- Renaming a folder creates a new project identity
|
|
192
186
|
- Knowledge doesn't leak between same-named projects
|
|
193
187
|
|
|
188
|
+
## Roadmap
|
|
189
|
+
|
|
190
|
+
### More IDEs & Coding Agents
|
|
191
|
+
|
|
192
|
+
| Platform | Status |
|
|
193
|
+
|----------|--------|
|
|
194
|
+
| Claude Code | Supported |
|
|
195
|
+
| Cursor | Supported |
|
|
196
|
+
| Roo Code | Supported |
|
|
197
|
+
| Windsurf | Supported |
|
|
198
|
+
| Continue | Supported |
|
|
199
|
+
| Codex | Supported |
|
|
200
|
+
| Goose (Block) | Coming soon |
|
|
201
|
+
| Aider | Coming soon |
|
|
202
|
+
| GitHub Copilot | Coming soon |
|
|
203
|
+
| Zed AI | Coming soon |
|
|
204
|
+
|
|
205
|
+
Any tool that supports MCP can use AgentCache today via `agentcache serve`. Native integrations for the above are planned to ensure zero-config setup.
|
|
206
|
+
|
|
207
|
+
### Native Plugins
|
|
208
|
+
|
|
209
|
+
Marketplace listings and deeper UI integrations for all supported IDEs — surfacing knowledge inline, showing compilation status, and providing one-click management of rules and decisions.
|
|
210
|
+
|
|
211
|
+
### Team Knowledge Sharing
|
|
212
|
+
|
|
213
|
+
Share compiled knowledge across your team. Rules and lessons that work for one developer benefit everyone.
|
|
214
|
+
|
|
215
|
+
### Cloud Sync
|
|
216
|
+
|
|
217
|
+
Sync your knowledge database across machines. Same developer, different computers, same knowledge.
|
|
218
|
+
|
|
219
|
+
### Analytics Dashboard
|
|
220
|
+
|
|
221
|
+
Visibility into what AgentCache is learning — compilation stats, knowledge growth, most-referenced rules, and session coverage.
|
|
222
|
+
|
|
194
223
|
## Contributing
|
|
195
224
|
|
|
196
225
|
```bash
|
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDataDir,
|
|
3
|
+
getGitContext
|
|
4
|
+
} from "./chunk-WHP4Z32Z.js";
|
|
5
|
+
|
|
6
|
+
// src/knowledge/passes/3-canonicalizer.ts
|
|
7
|
+
import { createHash } from "crypto";
|
|
8
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
9
|
+
"a",
|
|
10
|
+
"an",
|
|
11
|
+
"the",
|
|
12
|
+
"is",
|
|
13
|
+
"are",
|
|
14
|
+
"was",
|
|
15
|
+
"were",
|
|
16
|
+
"be",
|
|
17
|
+
"been",
|
|
18
|
+
"being",
|
|
19
|
+
"have",
|
|
20
|
+
"has",
|
|
21
|
+
"had",
|
|
22
|
+
"do",
|
|
23
|
+
"does",
|
|
24
|
+
"did",
|
|
25
|
+
"will",
|
|
26
|
+
"would",
|
|
27
|
+
"could",
|
|
28
|
+
"should",
|
|
29
|
+
"may",
|
|
30
|
+
"might",
|
|
31
|
+
"shall",
|
|
32
|
+
"can",
|
|
33
|
+
"need",
|
|
34
|
+
"must",
|
|
35
|
+
"to",
|
|
36
|
+
"of",
|
|
37
|
+
"in",
|
|
38
|
+
"for",
|
|
39
|
+
"on",
|
|
40
|
+
"with",
|
|
41
|
+
"at",
|
|
42
|
+
"by",
|
|
43
|
+
"from",
|
|
44
|
+
"as",
|
|
45
|
+
"into",
|
|
46
|
+
"through",
|
|
47
|
+
"during",
|
|
48
|
+
"before",
|
|
49
|
+
"after",
|
|
50
|
+
"above",
|
|
51
|
+
"below",
|
|
52
|
+
"this",
|
|
53
|
+
"that",
|
|
54
|
+
"these",
|
|
55
|
+
"those",
|
|
56
|
+
"it",
|
|
57
|
+
"its",
|
|
58
|
+
"and",
|
|
59
|
+
"but",
|
|
60
|
+
"or",
|
|
61
|
+
"nor",
|
|
62
|
+
"not",
|
|
63
|
+
"so",
|
|
64
|
+
"yet",
|
|
65
|
+
"all",
|
|
66
|
+
"each",
|
|
67
|
+
"every",
|
|
68
|
+
"both",
|
|
69
|
+
"few",
|
|
70
|
+
"more",
|
|
71
|
+
"most",
|
|
72
|
+
"i",
|
|
73
|
+
"we",
|
|
74
|
+
"you",
|
|
75
|
+
"they",
|
|
76
|
+
"he",
|
|
77
|
+
"she"
|
|
78
|
+
]);
|
|
79
|
+
var ANTONYM_MAP = [
|
|
80
|
+
[/\bnever\b/g, "forbidden"],
|
|
81
|
+
[/\bdon'?t\b/g, "forbidden"],
|
|
82
|
+
[/\bavoid\b/g, "forbidden"],
|
|
83
|
+
[/\bprohibit(ed)?\b/g, "forbidden"],
|
|
84
|
+
[/\balways\b/g, "required"],
|
|
85
|
+
[/\bmust\b/g, "required"],
|
|
86
|
+
[/\brequire(d)?\b/g, "required"],
|
|
87
|
+
[/\buse\b/g, "use"],
|
|
88
|
+
[/\bprefer\b/g, "use"]
|
|
89
|
+
];
|
|
90
|
+
function canonicalize(observations, existingCanonicalKeys) {
|
|
91
|
+
const canonicalized = observations.map((obs) => ({
|
|
92
|
+
...obs,
|
|
93
|
+
canonicalKey: computeCanonicalKey(obs.content)
|
|
94
|
+
}));
|
|
95
|
+
const existingSet = new Set(existingCanonicalKeys || []);
|
|
96
|
+
const autoReinforced = [];
|
|
97
|
+
const needsClustering = [];
|
|
98
|
+
for (const obs of canonicalized) {
|
|
99
|
+
if (existingSet.has(obs.canonicalKey)) {
|
|
100
|
+
autoReinforced.push(obs);
|
|
101
|
+
} else {
|
|
102
|
+
needsClustering.push(obs);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { observations: canonicalized, autoReinforced, needsClustering };
|
|
106
|
+
}
|
|
107
|
+
function computeCanonicalKey(content) {
|
|
108
|
+
let text = content.toLowerCase().trim();
|
|
109
|
+
for (const [pattern, replacement] of ANTONYM_MAP) {
|
|
110
|
+
text = text.replace(pattern, replacement);
|
|
111
|
+
}
|
|
112
|
+
text = text.replace(/[^\w\s]/g, " ");
|
|
113
|
+
const tokens = text.split(/\s+/).filter((t) => !STOP_WORDS.has(t) && t.length > 1).sort();
|
|
114
|
+
return tokens.join(" ");
|
|
115
|
+
}
|
|
116
|
+
function computeCanonicalHash(content) {
|
|
117
|
+
const key = computeCanonicalKey(content);
|
|
118
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/knowledge/compiler.ts
|
|
122
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
123
|
+
|
|
124
|
+
// src/knowledge/passes/1-extractor.ts
|
|
125
|
+
import { randomUUID } from "crypto";
|
|
126
|
+
var EXTRACT_PROMPT_VERSION = "extract-v1";
|
|
127
|
+
function buildExtractionPrompt(events) {
|
|
128
|
+
const transcript = events.filter((e) => e.content || e.tool_name).map((e) => {
|
|
129
|
+
if (e.role) return `[${e.role}]: ${e.content}`;
|
|
130
|
+
if (e.tool_name) return `[tool:${e.tool_name}]: ${JSON.stringify(e.tool_input).slice(0, 500)}`;
|
|
131
|
+
return "";
|
|
132
|
+
}).filter(Boolean).join("\n");
|
|
133
|
+
return `You are a knowledge extraction engine. Analyze this coding session transcript and extract distinct learnings.
|
|
134
|
+
|
|
135
|
+
Extract into four types:
|
|
136
|
+
- rule: a standing instruction or constraint the developer expressed
|
|
137
|
+
- lesson: a mistake made and what fixed it
|
|
138
|
+
- decision: an architectural or design choice with rationale
|
|
139
|
+
- context: current task state, open threads, what was left in progress
|
|
140
|
+
|
|
141
|
+
Return ONLY valid JSON: { "observations": [{ "type": "rule"|"lesson"|"decision"|"context", "content": "...", "sourceQuote": "...", "confidence": "high"|"medium" }] }
|
|
142
|
+
|
|
143
|
+
Only return high and medium confidence items. Ignore conversational noise, tool outputs, and implementation details that aren't generalizable.
|
|
144
|
+
|
|
145
|
+
<transcript>
|
|
146
|
+
${transcript}
|
|
147
|
+
</transcript>`;
|
|
148
|
+
}
|
|
149
|
+
function parseExtractionResponse(text, sessionId, project) {
|
|
150
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
151
|
+
if (!jsonMatch) return [];
|
|
152
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
153
|
+
if (!parsed.observations || !Array.isArray(parsed.observations)) return [];
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
return parsed.observations.filter((o) => o.type && o.content && o.confidence).filter((o) => ["high", "medium"].includes(o.confidence)).map((o) => ({
|
|
156
|
+
id: `obs_${randomUUID().slice(0, 8)}`,
|
|
157
|
+
sessionId,
|
|
158
|
+
timestamp: now,
|
|
159
|
+
type: o.type,
|
|
160
|
+
content: o.content,
|
|
161
|
+
sourceQuote: o.sourceQuote || "",
|
|
162
|
+
confidence: o.confidence,
|
|
163
|
+
project,
|
|
164
|
+
scope: o.scope || (o.type === "rule" || o.type === "lesson" ? "global" : "project")
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/knowledge/passes/2-normalizer.ts
|
|
169
|
+
var FILLER_PATTERNS = [
|
|
170
|
+
/^i (noticed|realized|learned|found|discovered|think) that /i,
|
|
171
|
+
/^it (seems|appears|looks) (like|that) /i,
|
|
172
|
+
/^we should /i,
|
|
173
|
+
/^you should /i,
|
|
174
|
+
/^basically,? /i,
|
|
175
|
+
/^essentially,? /i,
|
|
176
|
+
/^actually,? /i
|
|
177
|
+
];
|
|
178
|
+
var IMPERATIVE_RULES = [
|
|
179
|
+
[/^you should never /i, "Never "],
|
|
180
|
+
[/^we should never /i, "Never "],
|
|
181
|
+
[/^don't ever /i, "Never "],
|
|
182
|
+
[/^never /i, "Never "],
|
|
183
|
+
[/^you should always /i, "Always "],
|
|
184
|
+
[/^we should always /i, "Always "],
|
|
185
|
+
[/^always /i, "Always "]
|
|
186
|
+
];
|
|
187
|
+
function normalize(observations) {
|
|
188
|
+
const normalized = observations.map((obs) => ({
|
|
189
|
+
...obs,
|
|
190
|
+
content: normalizeContent(obs.content, obs.type)
|
|
191
|
+
}));
|
|
192
|
+
const seen = /* @__PURE__ */ new Set();
|
|
193
|
+
return normalized.filter((obs) => {
|
|
194
|
+
const key = obs.content.toLowerCase().trim();
|
|
195
|
+
if (seen.has(key)) return false;
|
|
196
|
+
seen.add(key);
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function normalizeContent(content, type) {
|
|
201
|
+
let text = content.trim();
|
|
202
|
+
for (const pattern of FILLER_PATTERNS) {
|
|
203
|
+
text = text.replace(pattern, "");
|
|
204
|
+
}
|
|
205
|
+
if (type === "rule") {
|
|
206
|
+
for (const [pattern, replacement] of IMPERATIVE_RULES) {
|
|
207
|
+
if (pattern.test(text)) {
|
|
208
|
+
text = text.replace(pattern, replacement);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
text = text.charAt(0).toUpperCase() + text.slice(1);
|
|
214
|
+
const firstSentenceEnd = text.search(/\. [A-Z]/);
|
|
215
|
+
if (firstSentenceEnd > 0) {
|
|
216
|
+
text = text.slice(0, firstSentenceEnd + 1);
|
|
217
|
+
}
|
|
218
|
+
return text;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/knowledge/passes/4-clusterer.ts
|
|
222
|
+
var CLUSTER_PROMPT_VERSION = "cluster-v1";
|
|
223
|
+
function buildClusteringPrompt(observations, existingItems) {
|
|
224
|
+
const obsJson = observations.map((o) => ({
|
|
225
|
+
id: o.id,
|
|
226
|
+
type: o.type,
|
|
227
|
+
content: o.content,
|
|
228
|
+
canonicalKey: o.canonicalKey
|
|
229
|
+
}));
|
|
230
|
+
const itemsJson = existingItems.filter((i) => i.status === "active").map((i) => ({
|
|
231
|
+
id: i.id,
|
|
232
|
+
type: i.type,
|
|
233
|
+
content: i.content,
|
|
234
|
+
canonicalHash: i.canonicalHash
|
|
235
|
+
}));
|
|
236
|
+
return `You are a knowledge clustering engine. Determine whether new observations create new knowledge or relate to existing items. Be conservative.
|
|
237
|
+
|
|
238
|
+
For each observation, assign an action:
|
|
239
|
+
CREATE \u2014 genuinely new knowledge, no existing item covers it
|
|
240
|
+
REINFORCE \u2014 confirms an existing item (provide targetKnowledgeItemId)
|
|
241
|
+
SUPERSEDE \u2014 replaces/corrects an existing item (provide targetKnowledgeItemId)
|
|
242
|
+
DEPRECATE \u2014 makes an existing item irrelevant (provide targetKnowledgeItemId)
|
|
243
|
+
IGNORE \u2014 duplicate, trivial, or too vague to keep
|
|
244
|
+
|
|
245
|
+
New observations:
|
|
246
|
+
${JSON.stringify(obsJson, null, 2)}
|
|
247
|
+
|
|
248
|
+
Existing knowledge items:
|
|
249
|
+
${JSON.stringify(itemsJson, null, 2)}
|
|
250
|
+
|
|
251
|
+
Return ONLY valid JSON: { "clusters": [{ "observationId": "...", "action": "CREATE"|"REINFORCE"|"SUPERSEDE"|"DEPRECATE"|"IGNORE", "targetKnowledgeItemId": "..." (only if action targets an existing item), "reasoning": "..." }] }`;
|
|
252
|
+
}
|
|
253
|
+
function parseClusteringResponse(text, observations) {
|
|
254
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
255
|
+
if (!jsonMatch) {
|
|
256
|
+
return observations.map((o) => ({ observationId: o.id, action: "CREATE", reasoning: "Parse failure \u2014 defaulting to CREATE" }));
|
|
257
|
+
}
|
|
258
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
259
|
+
if (!parsed.clusters || !Array.isArray(parsed.clusters)) {
|
|
260
|
+
return observations.map((o) => ({ observationId: o.id, action: "CREATE", reasoning: "Parse failure \u2014 defaulting to CREATE" }));
|
|
261
|
+
}
|
|
262
|
+
return parsed.clusters.map((c) => ({
|
|
263
|
+
observationId: c.observationId,
|
|
264
|
+
action: c.action || "CREATE",
|
|
265
|
+
targetKnowledgeItemId: c.targetKnowledgeItemId || void 0,
|
|
266
|
+
reasoning: c.reasoning || ""
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/knowledge/passes/5-contradiction.ts
|
|
271
|
+
var CONTRADICTION_PROMPT_VERSION = "contradiction-v1";
|
|
272
|
+
|
|
273
|
+
// src/knowledge/passes/6-compile.ts
|
|
274
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
275
|
+
function calculateConfidence(count) {
|
|
276
|
+
if (count >= 7) return "high";
|
|
277
|
+
if (count >= 3) return "medium";
|
|
278
|
+
return "low";
|
|
279
|
+
}
|
|
280
|
+
function compileKnowledge(clusters, existingItems, observations, project, now) {
|
|
281
|
+
const itemMap = new Map(existingItems.map((i) => [i.id, { ...i }]));
|
|
282
|
+
const obsMap = new Map(observations.map((o) => [o.id, o]));
|
|
283
|
+
const result = {
|
|
284
|
+
created: [],
|
|
285
|
+
reinforced: [],
|
|
286
|
+
superseded: [],
|
|
287
|
+
deprecated: [],
|
|
288
|
+
ignored: 0
|
|
289
|
+
};
|
|
290
|
+
for (const cluster of clusters) {
|
|
291
|
+
const obs = obsMap.get(cluster.observationId);
|
|
292
|
+
if (!obs) continue;
|
|
293
|
+
switch (cluster.action) {
|
|
294
|
+
case "CREATE": {
|
|
295
|
+
const newItem = {
|
|
296
|
+
id: `ki_${randomUUID2().slice(0, 8)}`,
|
|
297
|
+
canonicalHash: computeCanonicalHash(obs.content),
|
|
298
|
+
type: obs.type,
|
|
299
|
+
title: obs.content.slice(0, 80),
|
|
300
|
+
content: obs.content,
|
|
301
|
+
confidence: "low",
|
|
302
|
+
observationCount: 1,
|
|
303
|
+
authority: "AUTO",
|
|
304
|
+
status: "active",
|
|
305
|
+
supersededById: void 0,
|
|
306
|
+
enforce: false,
|
|
307
|
+
project,
|
|
308
|
+
scope: obs.scope || (obs.type === "rule" || obs.type === "lesson" ? "global" : "project"),
|
|
309
|
+
createdAt: now,
|
|
310
|
+
updatedAt: now,
|
|
311
|
+
lastSeenAt: now,
|
|
312
|
+
metadata: {}
|
|
313
|
+
};
|
|
314
|
+
result.created.push(newItem);
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case "REINFORCE": {
|
|
318
|
+
const target = itemMap.get(cluster.targetKnowledgeItemId);
|
|
319
|
+
if (!target) break;
|
|
320
|
+
target.observationCount += 1;
|
|
321
|
+
target.lastSeenAt = now;
|
|
322
|
+
target.updatedAt = now;
|
|
323
|
+
target.confidence = calculateConfidence(target.observationCount);
|
|
324
|
+
result.reinforced.push(target);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case "SUPERSEDE": {
|
|
328
|
+
const target = itemMap.get(cluster.targetKnowledgeItemId);
|
|
329
|
+
if (target) {
|
|
330
|
+
const newItem = {
|
|
331
|
+
id: `ki_${randomUUID2().slice(0, 8)}`,
|
|
332
|
+
canonicalHash: computeCanonicalHash(obs.content),
|
|
333
|
+
type: obs.type,
|
|
334
|
+
title: obs.content.slice(0, 80),
|
|
335
|
+
content: obs.content,
|
|
336
|
+
confidence: "low",
|
|
337
|
+
observationCount: 1,
|
|
338
|
+
authority: "AUTO",
|
|
339
|
+
status: "active",
|
|
340
|
+
supersededById: void 0,
|
|
341
|
+
enforce: false,
|
|
342
|
+
project,
|
|
343
|
+
scope: obs.scope || (obs.type === "rule" || obs.type === "lesson" ? "global" : "project"),
|
|
344
|
+
createdAt: now,
|
|
345
|
+
updatedAt: now,
|
|
346
|
+
lastSeenAt: now,
|
|
347
|
+
metadata: {}
|
|
348
|
+
};
|
|
349
|
+
target.status = "superseded";
|
|
350
|
+
target.updatedAt = now;
|
|
351
|
+
target.supersededById = newItem.id;
|
|
352
|
+
result.superseded.push(target);
|
|
353
|
+
result.created.push(newItem);
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
case "DEPRECATE": {
|
|
358
|
+
const target = itemMap.get(cluster.targetKnowledgeItemId);
|
|
359
|
+
if (target) {
|
|
360
|
+
target.status = "deprecated";
|
|
361
|
+
target.updatedAt = now;
|
|
362
|
+
result.deprecated.push(target);
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
case "IGNORE":
|
|
367
|
+
result.ignored += 1;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/knowledge/passes/7-projector.ts
|
|
375
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
376
|
+
function projectToMarkdown(items, generatedDir, compilerVersion) {
|
|
377
|
+
mkdirSync(generatedDir, { recursive: true });
|
|
378
|
+
const active = items.filter((i) => i.status === "active");
|
|
379
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
380
|
+
const header = (title) => `<!-- AUTO-GENERATED BY AGENTCACHE v${compilerVersion} \u2014 DO NOT EDIT -->
|
|
381
|
+
<!-- Source of truth: .agentcache/agentcache.db -->
|
|
382
|
+
<!-- Last compiled: ${timestamp} | ${active.length} active items -->
|
|
383
|
+
|
|
384
|
+
# ${title}
|
|
385
|
+
|
|
386
|
+
`;
|
|
387
|
+
const rules = active.filter((i) => i.type === "rule").sort((a, b) => confidenceOrder(b) - confidenceOrder(a));
|
|
388
|
+
const lessons = active.filter((i) => i.type === "lesson").sort((a, b) => confidenceOrder(b) - confidenceOrder(a));
|
|
389
|
+
const decisions = active.filter((i) => i.type === "decision").sort((a, b) => confidenceOrder(b) - confidenceOrder(a));
|
|
390
|
+
const context = active.filter((i) => i.type === "context").sort((a, b) => b.lastSeenAt - a.lastSeenAt);
|
|
391
|
+
writeFileSync(
|
|
392
|
+
`${generatedDir}/RULES.md`,
|
|
393
|
+
header("Rules") + formatItems(rules),
|
|
394
|
+
"utf-8"
|
|
395
|
+
);
|
|
396
|
+
writeFileSync(
|
|
397
|
+
`${generatedDir}/LESSONS.md`,
|
|
398
|
+
header("Lessons") + formatItems(lessons),
|
|
399
|
+
"utf-8"
|
|
400
|
+
);
|
|
401
|
+
writeFileSync(
|
|
402
|
+
`${generatedDir}/DECISIONS.md`,
|
|
403
|
+
header("Decisions") + formatItems(decisions),
|
|
404
|
+
"utf-8"
|
|
405
|
+
);
|
|
406
|
+
writeFileSync(
|
|
407
|
+
`${generatedDir}/CONTEXT.md`,
|
|
408
|
+
header("Context") + formatItems(context),
|
|
409
|
+
"utf-8"
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
function confidenceOrder(item) {
|
|
413
|
+
switch (item.confidence) {
|
|
414
|
+
case "high":
|
|
415
|
+
return 3;
|
|
416
|
+
case "medium":
|
|
417
|
+
return 2;
|
|
418
|
+
case "low":
|
|
419
|
+
return 1;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function formatItems(items) {
|
|
423
|
+
if (items.length === 0) return "_No items yet._\n";
|
|
424
|
+
return items.map((i) => {
|
|
425
|
+
const badge = i.enforce ? " \u{1F6E1}\uFE0F" : "";
|
|
426
|
+
const conf = `(${i.confidence}, ${i.observationCount}\xD7)`;
|
|
427
|
+
return `- ${i.content}${badge} ${conf}`;
|
|
428
|
+
}).join("\n") + "\n";
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/knowledge/compiler.ts
|
|
432
|
+
var COMPILER_VERSION = "0.1.0";
|
|
433
|
+
function startCompile(events, sessionId, project, projectRoot, repo, transcriptPath) {
|
|
434
|
+
const git = getGitContext(projectRoot);
|
|
435
|
+
const session = {
|
|
436
|
+
id: sessionId,
|
|
437
|
+
project,
|
|
438
|
+
startedAt: Date.now() - 6e4,
|
|
439
|
+
endedAt: Date.now(),
|
|
440
|
+
gitBranch: git.branch,
|
|
441
|
+
gitCommit: git.commit,
|
|
442
|
+
provider: "agent",
|
|
443
|
+
model: "host-agent",
|
|
444
|
+
transcriptPath: transcriptPath || "",
|
|
445
|
+
observationCount: 0
|
|
446
|
+
};
|
|
447
|
+
repo.saveSession(session);
|
|
448
|
+
const prompt = buildExtractionPrompt(events);
|
|
449
|
+
return { sessionId, project, projectRoot, prompt };
|
|
450
|
+
}
|
|
451
|
+
function processExtraction(repo, responseText, sessionId, project, projectRoot) {
|
|
452
|
+
const rawObservations = parseExtractionResponse(responseText, sessionId, project);
|
|
453
|
+
const normalized = normalize(rawObservations);
|
|
454
|
+
const existingItems = repo.getKnowledgeItems(project, { status: "active" });
|
|
455
|
+
const existingKeys = existingItems.map((i) => computeCanonicalKey(i.content));
|
|
456
|
+
const canonicalized = canonicalize(normalized, existingKeys);
|
|
457
|
+
for (const obs of canonicalized.autoReinforced) {
|
|
458
|
+
const matchingItem = existingItems.find(
|
|
459
|
+
(item) => computeCanonicalKey(item.content) === obs.canonicalKey
|
|
460
|
+
);
|
|
461
|
+
if (matchingItem) {
|
|
462
|
+
const newCount = matchingItem.observationCount + 1;
|
|
463
|
+
const confidence = newCount >= 7 ? "high" : newCount >= 3 ? "medium" : "low";
|
|
464
|
+
repo.updateKnowledgeItem(matchingItem.id, {
|
|
465
|
+
observationCount: newCount,
|
|
466
|
+
lastSeenAt: Date.now(),
|
|
467
|
+
updatedAt: Date.now(),
|
|
468
|
+
confidence
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
repo.saveObservations(normalized);
|
|
473
|
+
if (canonicalized.needsClustering.length === 0) {
|
|
474
|
+
saveCompileRun(repo, sessionId, project, normalized.length, canonicalized.autoReinforced.length, 0, 0, 0, 0, 0, Date.now());
|
|
475
|
+
projectToMarkdown(repo.getKnowledgeItems(project, { status: "active" }), getDataDir(), COMPILER_VERSION);
|
|
476
|
+
return {
|
|
477
|
+
status: "complete",
|
|
478
|
+
diagnostics: formatDiagnostics(normalized.length, canonicalized.autoReinforced.length, 0, 0, 0, 0, 0, project, sessionId)
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const clusteringPrompt = buildClusteringPrompt(canonicalized.needsClustering, existingItems);
|
|
482
|
+
return {
|
|
483
|
+
status: "needs_clustering",
|
|
484
|
+
clusteringPrompt,
|
|
485
|
+
sessionId
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function processClustering(repo, responseText, sessionId, project, projectRoot) {
|
|
489
|
+
const startedAt = Date.now();
|
|
490
|
+
const existingItems = repo.getKnowledgeItems(project, { status: "active" });
|
|
491
|
+
const observations = repo.getObservations(project);
|
|
492
|
+
const sessionObs = observations.filter((o) => o.sessionId === sessionId);
|
|
493
|
+
const canonicalized = canonicalize(sessionObs);
|
|
494
|
+
const needsClustering = canonicalized.needsClustering;
|
|
495
|
+
const clusters = parseClusteringResponse(responseText, needsClustering);
|
|
496
|
+
const contradictions = [];
|
|
497
|
+
const supersedeActions = clusters.filter((c) => c.action === "SUPERSEDE");
|
|
498
|
+
for (const s of supersedeActions) {
|
|
499
|
+
if (s.targetKnowledgeItemId) {
|
|
500
|
+
const target = existingItems.find((i) => i.id === s.targetKnowledgeItemId);
|
|
501
|
+
if (target) {
|
|
502
|
+
contradictions.push({
|
|
503
|
+
id: `con_${randomUUID3().slice(0, 8)}`,
|
|
504
|
+
project,
|
|
505
|
+
itemAId: target.id,
|
|
506
|
+
itemBId: s.observationId,
|
|
507
|
+
topic: target.title.slice(0, 50),
|
|
508
|
+
description: `"${target.content}" superseded by new observation`,
|
|
509
|
+
recommendation: "keep_newer",
|
|
510
|
+
resolved: false,
|
|
511
|
+
createdAt: Date.now()
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
for (const c of contradictions) {
|
|
517
|
+
repo.saveContradiction(c);
|
|
518
|
+
}
|
|
519
|
+
const now = Date.now();
|
|
520
|
+
const compiled = compileKnowledge(clusters, existingItems, needsClustering, project, now);
|
|
521
|
+
for (const item of compiled.created) repo.saveKnowledgeItem(item);
|
|
522
|
+
for (const item of compiled.reinforced) {
|
|
523
|
+
repo.updateKnowledgeItem(item.id, {
|
|
524
|
+
observationCount: item.observationCount,
|
|
525
|
+
lastSeenAt: item.lastSeenAt,
|
|
526
|
+
updatedAt: item.updatedAt,
|
|
527
|
+
confidence: item.confidence
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
for (const item of compiled.superseded) {
|
|
531
|
+
repo.updateKnowledgeItem(item.id, {
|
|
532
|
+
status: item.status,
|
|
533
|
+
updatedAt: item.updatedAt,
|
|
534
|
+
supersededById: item.supersededById
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
for (const item of compiled.deprecated) {
|
|
538
|
+
repo.updateKnowledgeItem(item.id, { status: item.status, updatedAt: item.updatedAt });
|
|
539
|
+
}
|
|
540
|
+
const totalObs = sessionObs.length;
|
|
541
|
+
saveCompileRun(repo, sessionId, project, totalObs, 0, compiled.created.length, compiled.reinforced.length, compiled.superseded.length, compiled.deprecated.length, compiled.ignored, startedAt);
|
|
542
|
+
projectToMarkdown(repo.getKnowledgeItems(project, { status: "active" }), getDataDir(), COMPILER_VERSION);
|
|
543
|
+
return {
|
|
544
|
+
status: "complete",
|
|
545
|
+
diagnostics: formatDiagnostics(totalObs, 0, compiled.created.length, compiled.reinforced.length, compiled.superseded.length, compiled.deprecated.length, compiled.ignored, project, sessionId)
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function saveCompileRun(repo, sessionId, project, observationsProcessed, autoReinforced, created, reinforced, superseded, deprecated, ignored, startedAt) {
|
|
549
|
+
const endedAt = Date.now();
|
|
550
|
+
const run = {
|
|
551
|
+
id: `cr_${randomUUID3().slice(0, 8)}`,
|
|
552
|
+
project,
|
|
553
|
+
sessionId,
|
|
554
|
+
compilerVersion: COMPILER_VERSION,
|
|
555
|
+
promptVersions: { extract: EXTRACT_PROMPT_VERSION, cluster: CLUSTER_PROMPT_VERSION, contradiction: CONTRADICTION_PROMPT_VERSION },
|
|
556
|
+
startedAt,
|
|
557
|
+
endedAt,
|
|
558
|
+
durationMs: endedAt - startedAt,
|
|
559
|
+
observationsProcessed,
|
|
560
|
+
knowledgeCreated: created,
|
|
561
|
+
knowledgeReinforced: reinforced + autoReinforced,
|
|
562
|
+
knowledgeDeprecated: deprecated,
|
|
563
|
+
knowledgeSuperseded: superseded,
|
|
564
|
+
knowledgeIgnored: ignored,
|
|
565
|
+
contradictionsDetected: 0,
|
|
566
|
+
diagnostics: ""
|
|
567
|
+
};
|
|
568
|
+
repo.saveCompileRun(run);
|
|
569
|
+
}
|
|
570
|
+
function formatDiagnostics(extracted, autoReinforced, created, reinforced, superseded, deprecated, ignored, project, sessionId) {
|
|
571
|
+
return [
|
|
572
|
+
`AgentCache Compiler v${COMPILER_VERSION}`,
|
|
573
|
+
`Project: ${project} | Session: ${sessionId}`,
|
|
574
|
+
` ${extracted} observations processed`,
|
|
575
|
+
autoReinforced > 0 ? ` ${autoReinforced} auto-reinforced (no LLM needed)` : "",
|
|
576
|
+
` ${created} new knowledge items`,
|
|
577
|
+
` ${reinforced} reinforced`,
|
|
578
|
+
superseded > 0 ? ` ${superseded} superseded` : "",
|
|
579
|
+
deprecated > 0 ? ` ${deprecated} deprecated` : "",
|
|
580
|
+
ignored > 0 ? ` ${ignored} ignored` : ""
|
|
581
|
+
].filter(Boolean).join("\n");
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export {
|
|
585
|
+
computeCanonicalHash,
|
|
586
|
+
startCompile,
|
|
587
|
+
processExtraction,
|
|
588
|
+
processClustering
|
|
589
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
__require,
|
|
30
|
+
__esm,
|
|
31
|
+
__export,
|
|
32
|
+
__toCommonJS
|
|
33
|
+
};
|