@plumpslabs/kuma 2.3.20 → 2.3.22
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 +18 -0
- package/dist/index.js +10221 -332
- package/package.json +2 -2
- package/packages/ide/studio/dist/index.js +37 -5
- package/packages/ide/studio/public/index.html +239 -68
- package/dist/agentDetector-YOWQVJFR.js +0 -186
- package/dist/chunk-3BRBJZ7P.js +0 -1055
- package/dist/chunk-3OHYYXYN.js +0 -71
- package/dist/chunk-ABKE45T4.js +0 -1264
- package/dist/chunk-E2KFPEBT.js +0 -183
- package/dist/chunk-FKRSI5U5.js +0 -282
- package/dist/chunk-GFLSAXAH.js +0 -155
- package/dist/chunk-L7F67KUP.js +0 -172
- package/dist/chunk-LVKOGXLC.js +0 -658
- package/dist/chunk-PRUTTZBS.js +0 -1113
- package/dist/contextDigest-QB5XHPXE.js +0 -177
- package/dist/domainRules-QLPAQASB.js +0 -20
- package/dist/init-PL4XL662.js +0 -15
- package/dist/kumaAstValidator-CNM7FHYA.js +0 -150
- package/dist/kumaCheckpoint-J2LDQMEO.js +0 -207
- package/dist/kumaCodeScanner-J6B2EHGI.js +0 -563
- package/dist/kumaContractEngine-KX27T4N7.js +0 -305
- package/dist/kumaDb-4XZ5S2LH.js +0 -65
- package/dist/kumaDriftDetector-TOORILSZ.js +0 -237
- package/dist/kumaGotchas-XRGFFBTA.js +0 -151
- package/dist/kumaGraph-UMXZNGYF.js +0 -44
- package/dist/kumaMemory-FBJMV77G.js +0 -16
- package/dist/kumaMiner-XJETL7TL.js +0 -176
- package/dist/kumaPolicyEngine-2QDJDLM7.js +0 -311
- package/dist/kumaProgressiveContext-MWEDRXOH.js +0 -231
- package/dist/kumaSearch-PV4QTKE7.js +0 -321
- package/dist/kumaTrajectory-7NOAVNG2.js +0 -460
- package/dist/kumaVerifier-6YEGC77M.js +0 -265
- package/dist/kumaVisualize-264OEBGJ.js +0 -264
- package/dist/pathValidator-V4DC6U6Z.js +0 -22
- package/dist/safetyAudit-O45SPNTS.js +0 -12
- package/dist/safetyScore-TMMRD2MV.js +0 -333
- package/dist/sessionMemory-YPKVIOMV.js +0 -6
- package/dist/skillGenerator-PWEJKZNX.js +0 -304
package/dist/chunk-LVKOGXLC.js
DELETED
|
@@ -1,658 +0,0 @@
|
|
|
1
|
-
// src/engine/sessionMemory.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { execSync } from "child_process";
|
|
5
|
-
var SessionMemory = class {
|
|
6
|
-
state;
|
|
7
|
-
initialized = false;
|
|
8
|
-
init(config) {
|
|
9
|
-
const kumaDir = path.join(config.projectRoot, ".kuma");
|
|
10
|
-
const sessionFile = path.join(kumaDir, "memory.json");
|
|
11
|
-
const oldFile = path.join(kumaDir, ".kuma-memory.json");
|
|
12
|
-
if (fs.existsSync(oldFile) && !fs.existsSync(sessionFile)) {
|
|
13
|
-
try {
|
|
14
|
-
fs.renameSync(oldFile, sessionFile);
|
|
15
|
-
console.error("[SessionMemory] Migrated .kuma-memory.json \u2192 memory.json");
|
|
16
|
-
} catch {
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
const oldSessionFile = path.join(kumaDir, "session.json");
|
|
20
|
-
if (fs.existsSync(oldSessionFile) && !fs.existsSync(sessionFile)) {
|
|
21
|
-
try {
|
|
22
|
-
fs.renameSync(oldSessionFile, sessionFile);
|
|
23
|
-
console.error("[SessionMemory] Migrated session.json \u2192 memory.json");
|
|
24
|
-
} catch {
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
if (fs.existsSync(sessionFile)) {
|
|
28
|
-
try {
|
|
29
|
-
const raw = fs.readFileSync(sessionFile, "utf-8");
|
|
30
|
-
const parsed = JSON.parse(raw);
|
|
31
|
-
this.state = {
|
|
32
|
-
projectRoot: parsed.projectRoot || config.projectRoot,
|
|
33
|
-
startTime: parsed.startTime || config.startTime,
|
|
34
|
-
currentGoal: parsed.currentGoal || "",
|
|
35
|
-
completedSteps: parsed.completedSteps || [],
|
|
36
|
-
modifiedFiles: new Map(parsed.modifiedFiles || []),
|
|
37
|
-
failedFiles: new Map(parsed.failedFiles || []),
|
|
38
|
-
searchResults: new Map(parsed.searchResults || []),
|
|
39
|
-
dependencyGraph: new Map(parsed.dependencyGraph || []),
|
|
40
|
-
toolCalls: parsed.toolCalls || [],
|
|
41
|
-
conventions: parsed.conventions
|
|
42
|
-
};
|
|
43
|
-
this.initialized = true;
|
|
44
|
-
console.error(`[SessionMemory] Loaded persistent session memory.json`);
|
|
45
|
-
return;
|
|
46
|
-
} catch (err) {
|
|
47
|
-
console.error(`[SessionMemory] Failed to load persistent session: ${err}. Re-initializing.`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
this.state = {
|
|
51
|
-
projectRoot: config.projectRoot,
|
|
52
|
-
startTime: config.startTime,
|
|
53
|
-
currentGoal: "",
|
|
54
|
-
completedSteps: [],
|
|
55
|
-
modifiedFiles: /* @__PURE__ */ new Map(),
|
|
56
|
-
failedFiles: /* @__PURE__ */ new Map(),
|
|
57
|
-
searchResults: /* @__PURE__ */ new Map(),
|
|
58
|
-
dependencyGraph: /* @__PURE__ */ new Map(),
|
|
59
|
-
toolCalls: []
|
|
60
|
-
};
|
|
61
|
-
this.initialized = true;
|
|
62
|
-
this.save();
|
|
63
|
-
this.ensureMemoriesDir();
|
|
64
|
-
console.error(`[SessionMemory] Initialized at ${new Date(config.startTime).toISOString()}`);
|
|
65
|
-
}
|
|
66
|
-
memoriesDir() {
|
|
67
|
-
return path.join(this.state.projectRoot, ".kuma", "memories");
|
|
68
|
-
}
|
|
69
|
-
ensureMemoriesDir() {
|
|
70
|
-
const dir = this.memoriesDir();
|
|
71
|
-
if (!fs.existsSync(dir)) {
|
|
72
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
memoryFilePath(topic) {
|
|
76
|
-
return path.join(this.memoriesDir(), `${topic}.md`);
|
|
77
|
-
}
|
|
78
|
-
getMemoryContent(topic) {
|
|
79
|
-
this.ensureInit();
|
|
80
|
-
this.ensureMemoriesDir();
|
|
81
|
-
const filePath = this.memoryFilePath(topic);
|
|
82
|
-
if (fs.existsSync(filePath)) {
|
|
83
|
-
return fs.readFileSync(filePath, "utf-8");
|
|
84
|
-
}
|
|
85
|
-
return this.generateMemory(topic);
|
|
86
|
-
}
|
|
87
|
-
writeMemory(topic, content) {
|
|
88
|
-
this.ensureInit();
|
|
89
|
-
this.ensureMemoriesDir();
|
|
90
|
-
this.writeMemoryFile(topic, content);
|
|
91
|
-
}
|
|
92
|
-
writeMemoryFile(topic, content) {
|
|
93
|
-
const filePath = this.memoryFilePath(topic);
|
|
94
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
95
|
-
}
|
|
96
|
-
generateMemory(topic) {
|
|
97
|
-
switch (topic) {
|
|
98
|
-
case "architecture":
|
|
99
|
-
return this.generateArchitectureMd();
|
|
100
|
-
case "conventions":
|
|
101
|
-
return this.generateConventionsMd();
|
|
102
|
-
case "known-issues":
|
|
103
|
-
return this.generateKnownIssuesMd();
|
|
104
|
-
default:
|
|
105
|
-
return `# ${topic}
|
|
106
|
-
|
|
107
|
-
(empty)`;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
generateArchitectureMd() {
|
|
111
|
-
const conv = this.state.conventions;
|
|
112
|
-
if (!conv) return `# Architecture
|
|
113
|
-
|
|
114
|
-
Run project_conventions first to auto-generate.`;
|
|
115
|
-
const stackKeys = ["framework", "projectType", "monorepo", "buildTool", "packageManager"];
|
|
116
|
-
const lines = [
|
|
117
|
-
"# Architecture",
|
|
118
|
-
"",
|
|
119
|
-
`Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
120
|
-
"",
|
|
121
|
-
"## Stack",
|
|
122
|
-
""
|
|
123
|
-
];
|
|
124
|
-
for (const key of stackKeys) {
|
|
125
|
-
if (conv[key] !== void 0) {
|
|
126
|
-
lines.push(`- **${key}**: ${JSON.stringify(conv[key])}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
lines.push("", "## Project Layout", "");
|
|
130
|
-
if (conv.workspaces) {
|
|
131
|
-
lines.push(`- Monorepo workspaces: ${JSON.stringify(conv.workspaces)}`);
|
|
132
|
-
}
|
|
133
|
-
lines.push(`- Source root: ${conv.srcDir || "src/"}`);
|
|
134
|
-
return lines.join("\n");
|
|
135
|
-
}
|
|
136
|
-
generateConventionsMd() {
|
|
137
|
-
const conv = this.state.conventions;
|
|
138
|
-
if (!conv) return `# Conventions
|
|
139
|
-
|
|
140
|
-
Run project_conventions first to auto-generate.`;
|
|
141
|
-
const styleKeys = ["testRunner", "styling", "importAlias", "lintConfig", "codeStyle"];
|
|
142
|
-
const lines = [
|
|
143
|
-
"# Conventions",
|
|
144
|
-
"",
|
|
145
|
-
`Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
146
|
-
"",
|
|
147
|
-
"## Code Style",
|
|
148
|
-
""
|
|
149
|
-
];
|
|
150
|
-
for (const key of styleKeys) {
|
|
151
|
-
if (conv[key] !== void 0) {
|
|
152
|
-
lines.push(`- **${key}**: ${JSON.stringify(conv[key])}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (conv.testRunner) {
|
|
156
|
-
lines.push("", `## Testing
|
|
157
|
-
|
|
158
|
-
- Test runner: ${conv.testRunner}`);
|
|
159
|
-
}
|
|
160
|
-
if (conv.importAlias) {
|
|
161
|
-
lines.push("", `## Imports
|
|
162
|
-
|
|
163
|
-
- Alias: \`${conv.importAlias}\``);
|
|
164
|
-
}
|
|
165
|
-
return lines.join("\n");
|
|
166
|
-
}
|
|
167
|
-
generateKnownIssuesMd() {
|
|
168
|
-
const allFailures = this.getFailedFiles();
|
|
169
|
-
const unresolved = allFailures.filter((f) => f.failures.some((ff) => !ff.resolved));
|
|
170
|
-
if (unresolved.length === 0) return `# Known Issues
|
|
171
|
-
|
|
172
|
-
No unresolved issues.`;
|
|
173
|
-
const lines = [
|
|
174
|
-
"# Known Issues",
|
|
175
|
-
"",
|
|
176
|
-
`Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
177
|
-
""
|
|
178
|
-
];
|
|
179
|
-
for (const f of unresolved) {
|
|
180
|
-
lines.push(`## ${f.task}`);
|
|
181
|
-
for (const ff of f.failures) {
|
|
182
|
-
if (!ff.resolved) {
|
|
183
|
-
lines.push(`- ${new Date(ff.timestamp).toISOString()}: ${ff.error}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
lines.push("");
|
|
187
|
-
}
|
|
188
|
-
return lines.join("\n");
|
|
189
|
-
}
|
|
190
|
-
save() {
|
|
191
|
-
if (!this.initialized || !this.state) return;
|
|
192
|
-
try {
|
|
193
|
-
const kumaDir = path.join(this.state.projectRoot, ".kuma");
|
|
194
|
-
if (!fs.existsSync(kumaDir)) {
|
|
195
|
-
fs.mkdirSync(kumaDir, { recursive: true });
|
|
196
|
-
}
|
|
197
|
-
const serialized = {
|
|
198
|
-
projectRoot: this.state.projectRoot,
|
|
199
|
-
startTime: this.state.startTime,
|
|
200
|
-
currentGoal: this.state.currentGoal,
|
|
201
|
-
completedSteps: this.state.completedSteps,
|
|
202
|
-
modifiedFiles: Array.from(this.state.modifiedFiles.entries()),
|
|
203
|
-
failedFiles: Array.from(this.state.failedFiles.entries()),
|
|
204
|
-
searchResults: Array.from(this.state.searchResults.entries()),
|
|
205
|
-
dependencyGraph: Array.from(this.state.dependencyGraph.entries()),
|
|
206
|
-
toolCalls: this.state.toolCalls,
|
|
207
|
-
conventions: this.state.conventions
|
|
208
|
-
};
|
|
209
|
-
fs.writeFileSync(path.join(kumaDir, "memory.json"), JSON.stringify(serialized, null, 2), "utf-8");
|
|
210
|
-
} catch (err) {
|
|
211
|
-
console.error(`[SessionMemory] Failed to save session: ${err}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
setGoal(goal) {
|
|
215
|
-
this.ensureInit();
|
|
216
|
-
this.state.currentGoal = goal;
|
|
217
|
-
this.save();
|
|
218
|
-
}
|
|
219
|
-
addCompletedStep(step) {
|
|
220
|
-
this.ensureInit();
|
|
221
|
-
if (!this.state.completedSteps.includes(step)) {
|
|
222
|
-
this.state.completedSteps.push(step);
|
|
223
|
-
this.save();
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
addModifiedFile(filePath) {
|
|
227
|
-
this.ensureInit();
|
|
228
|
-
const existing = this.state.modifiedFiles.get(filePath);
|
|
229
|
-
if (existing) {
|
|
230
|
-
existing.modifiedAt = Date.now();
|
|
231
|
-
existing.status = "modified";
|
|
232
|
-
} else {
|
|
233
|
-
this.state.modifiedFiles.set(filePath, {
|
|
234
|
-
filePath,
|
|
235
|
-
modifiedAt: Date.now(),
|
|
236
|
-
status: "modified"
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
this.save();
|
|
240
|
-
}
|
|
241
|
-
addCreatedFile(filePath) {
|
|
242
|
-
this.ensureInit();
|
|
243
|
-
this.state.modifiedFiles.set(filePath, {
|
|
244
|
-
filePath,
|
|
245
|
-
modifiedAt: Date.now(),
|
|
246
|
-
status: "created"
|
|
247
|
-
});
|
|
248
|
-
this.save();
|
|
249
|
-
}
|
|
250
|
-
addFailedFile(task, error) {
|
|
251
|
-
this.ensureInit();
|
|
252
|
-
const trimmedError = error?.trim() ?? "";
|
|
253
|
-
if (!trimmedError) return;
|
|
254
|
-
const truncatedError = trimmedError.substring(0, 500);
|
|
255
|
-
const failures = this.state.failedFiles.get(task) ?? [];
|
|
256
|
-
const lastUnresolved = [...failures].reverse().find((f) => !f.resolved);
|
|
257
|
-
if (lastUnresolved && lastUnresolved.error === truncatedError) return;
|
|
258
|
-
failures.push({
|
|
259
|
-
task,
|
|
260
|
-
error: truncatedError,
|
|
261
|
-
timestamp: Date.now(),
|
|
262
|
-
resolved: false
|
|
263
|
-
});
|
|
264
|
-
this.state.failedFiles.set(task, failures);
|
|
265
|
-
this.save();
|
|
266
|
-
this.ensureMemoriesDir();
|
|
267
|
-
this.writeMemoryFile("known-issues", this.generateKnownIssuesMd());
|
|
268
|
-
}
|
|
269
|
-
markFailureResolved(task) {
|
|
270
|
-
this.ensureInit();
|
|
271
|
-
const failures = this.state.failedFiles.get(task);
|
|
272
|
-
if (failures) {
|
|
273
|
-
let changed = false;
|
|
274
|
-
for (const f of failures) {
|
|
275
|
-
if (!f.resolved) {
|
|
276
|
-
f.resolved = true;
|
|
277
|
-
changed = true;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (changed) {
|
|
281
|
-
this.save();
|
|
282
|
-
this.ensureMemoriesDir();
|
|
283
|
-
this.writeMemoryFile("known-issues", this.generateKnownIssuesMd());
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
addSearchResult(query, files) {
|
|
288
|
-
this.ensureInit();
|
|
289
|
-
this.state.searchResults.set(query, files);
|
|
290
|
-
this.save();
|
|
291
|
-
}
|
|
292
|
-
addDependency(file, dependsOn) {
|
|
293
|
-
this.ensureInit();
|
|
294
|
-
const deps = this.state.dependencyGraph.get(file) ?? [];
|
|
295
|
-
if (!deps.includes(dependsOn)) {
|
|
296
|
-
deps.push(dependsOn);
|
|
297
|
-
this.state.dependencyGraph.set(file, deps);
|
|
298
|
-
this.save();
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
recordToolCall(toolName, params) {
|
|
302
|
-
this.ensureInit();
|
|
303
|
-
const timestamp = Date.now();
|
|
304
|
-
this.state.toolCalls.push({
|
|
305
|
-
toolName,
|
|
306
|
-
params,
|
|
307
|
-
timestamp
|
|
308
|
-
});
|
|
309
|
-
if (this.state.toolCalls.length > 100) {
|
|
310
|
-
this.state.toolCalls = this.state.toolCalls.slice(-100);
|
|
311
|
-
}
|
|
312
|
-
this.save();
|
|
313
|
-
this.autoTrackToDb(toolName, params, timestamp).catch(() => {
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Auto-track tool calls to the database sessions + tool_calls + experiences tables.
|
|
318
|
-
* This is the passive observability pipeline — every tool call gets recorded
|
|
319
|
-
* automatically without requiring the tool to explicitly write to the DB.
|
|
320
|
-
*/
|
|
321
|
-
async autoTrackToDb(toolName, params, timestamp) {
|
|
322
|
-
try {
|
|
323
|
-
const { getDb, saveDb } = await import("./kumaDb-4XZ5S2LH.js");
|
|
324
|
-
const db = await getDb();
|
|
325
|
-
db.run(
|
|
326
|
-
`INSERT INTO tool_calls (tool_name, params, success, duration_ms, created_at) VALUES (?, ?, 1, 0, ?)`,
|
|
327
|
-
[toolName, JSON.stringify(params), Math.floor(timestamp / 1e3)]
|
|
328
|
-
);
|
|
329
|
-
const paramsHash = simpleHash(JSON.stringify(params));
|
|
330
|
-
const filePath = params.filePath || params.file_path || params.target || null;
|
|
331
|
-
db.run(
|
|
332
|
-
`INSERT INTO experiences (tool_name, params_hash, success, context_file, context_action, created_at) VALUES (?, ?, 1, ?, ?, ?)`,
|
|
333
|
-
[toolName, paramsHash, filePath, params.action || null, Math.floor(timestamp / 1e3)]
|
|
334
|
-
);
|
|
335
|
-
saveDb(db);
|
|
336
|
-
} catch {
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
setConventions(conventions) {
|
|
340
|
-
this.ensureInit();
|
|
341
|
-
this.state.conventions = conventions;
|
|
342
|
-
this.save();
|
|
343
|
-
this.ensureMemoriesDir();
|
|
344
|
-
this.writeMemoryFile("architecture", this.generateArchitectureMd());
|
|
345
|
-
this.writeMemoryFile("conventions", this.generateConventionsMd());
|
|
346
|
-
}
|
|
347
|
-
// ============================================================
|
|
348
|
-
// KEYWORD SEARCH — Search session memory content
|
|
349
|
-
// ============================================================
|
|
350
|
-
/**
|
|
351
|
-
* Load previous session data into current session.
|
|
352
|
-
* Called by kuma_init() to hydrate conventions, failures, and modified files
|
|
353
|
-
* from a prior session stored in memory.json.
|
|
354
|
-
*/
|
|
355
|
-
loadSession() {
|
|
356
|
-
this.ensureInit();
|
|
357
|
-
const kumaDir = path.join(this.state.projectRoot, ".kuma");
|
|
358
|
-
const sessionFile = path.join(kumaDir, "memory.json");
|
|
359
|
-
if (!fs.existsSync(sessionFile)) {
|
|
360
|
-
return { hasPrevSession: false, toolCallCount: 0, hasConventions: !!this.state.conventions };
|
|
361
|
-
}
|
|
362
|
-
try {
|
|
363
|
-
const raw = fs.readFileSync(sessionFile, "utf-8");
|
|
364
|
-
const parsed = JSON.parse(raw);
|
|
365
|
-
if (!this.state.conventions && parsed.conventions) {
|
|
366
|
-
this.state.conventions = parsed.conventions;
|
|
367
|
-
}
|
|
368
|
-
const prevFailed = parsed.failedFiles;
|
|
369
|
-
if (prevFailed) {
|
|
370
|
-
for (const [task, failures] of prevFailed) {
|
|
371
|
-
for (const f of failures) {
|
|
372
|
-
if (!f.resolved) {
|
|
373
|
-
const existing = this.state.failedFiles.get(task);
|
|
374
|
-
if (!existing?.some((ef) => ef.error === f.error)) {
|
|
375
|
-
this.state.failedFiles.set(task, [...existing || [], f]);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
const prevModified = parsed.modifiedFiles;
|
|
382
|
-
if (prevModified) {
|
|
383
|
-
for (const [filePath, mod] of prevModified) {
|
|
384
|
-
if (!this.state.modifiedFiles.has(filePath)) {
|
|
385
|
-
this.state.modifiedFiles.set(filePath, {
|
|
386
|
-
filePath,
|
|
387
|
-
modifiedAt: mod.modifiedAt,
|
|
388
|
-
status: mod.status
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
const prevSearch = parsed.searchResults;
|
|
394
|
-
if (prevSearch) {
|
|
395
|
-
for (const [query, files] of prevSearch) {
|
|
396
|
-
if (!this.state.searchResults.has(query)) {
|
|
397
|
-
this.state.searchResults.set(query, files);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
const toolCallCount = parsed.toolCalls?.length ?? 0;
|
|
402
|
-
this.save();
|
|
403
|
-
return {
|
|
404
|
-
hasPrevSession: true,
|
|
405
|
-
toolCallCount,
|
|
406
|
-
hasConventions: !!this.state.conventions
|
|
407
|
-
};
|
|
408
|
-
} catch {
|
|
409
|
-
return { hasPrevSession: false, toolCallCount: 0, hasConventions: !!this.state.conventions };
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Search through tool call history, memory files, search results,
|
|
414
|
-
* errors, and file modifications for a keyword.
|
|
415
|
-
*/
|
|
416
|
-
searchMemory(query, limit = 20) {
|
|
417
|
-
this.ensureInit();
|
|
418
|
-
const results = [];
|
|
419
|
-
const q = query.toLowerCase();
|
|
420
|
-
for (const call of this.state.toolCalls) {
|
|
421
|
-
const paramStr = JSON.stringify(call.params);
|
|
422
|
-
if (call.toolName.toLowerCase().includes(q) || paramStr.toLowerCase().includes(q)) {
|
|
423
|
-
results.push({
|
|
424
|
-
type: `tool:${call.toolName}`,
|
|
425
|
-
content: `${call.toolName}(${JSON.stringify(call.params).substring(0, 200)})`,
|
|
426
|
-
timestamp: call.timestamp
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
for (const [queryStr, files] of this.state.searchResults) {
|
|
431
|
-
if (queryStr.toLowerCase().includes(q) || files.some((f) => f.toLowerCase().includes(q))) {
|
|
432
|
-
results.push({
|
|
433
|
-
type: "search",
|
|
434
|
-
content: `Query: "${queryStr}" \u2192 ${files.slice(0, 5).join(", ")}${files.length > 5 ? ` (+${files.length - 5} more)` : ""}`
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
for (const [, mod] of this.state.modifiedFiles) {
|
|
439
|
-
if (mod.filePath.toLowerCase().includes(q)) {
|
|
440
|
-
results.push({
|
|
441
|
-
type: `file:${mod.status}`,
|
|
442
|
-
content: `${mod.status.toUpperCase()}: ${mod.filePath}`,
|
|
443
|
-
timestamp: mod.modifiedAt
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
for (const [task, failures] of this.state.failedFiles) {
|
|
448
|
-
for (const f of failures) {
|
|
449
|
-
if (task.toLowerCase().includes(q) || f.error.toLowerCase().includes(q)) {
|
|
450
|
-
results.push({
|
|
451
|
-
type: `failure:${task}`,
|
|
452
|
-
content: `[${f.resolved ? "RESOLVED" : "UNRESOLVED"}] ${task}: ${f.error.substring(0, 200)}`,
|
|
453
|
-
timestamp: f.timestamp
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
for (const [file, deps] of this.state.dependencyGraph) {
|
|
459
|
-
if (file.toLowerCase().includes(q)) {
|
|
460
|
-
results.push({
|
|
461
|
-
type: "dependency",
|
|
462
|
-
content: `${file} depends on: ${deps.join(", ")}`
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
const memoriesDir = this.memoriesDir();
|
|
467
|
-
if (fs.existsSync(memoriesDir)) {
|
|
468
|
-
try {
|
|
469
|
-
const files = fs.readdirSync(memoriesDir).filter((f) => f.endsWith(".md")).slice(0, 3);
|
|
470
|
-
for (const file of files) {
|
|
471
|
-
const filePath = path.join(memoriesDir, file);
|
|
472
|
-
try {
|
|
473
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
474
|
-
const lines = content.split("\n");
|
|
475
|
-
const maxLinesPerFile = 500;
|
|
476
|
-
for (let i = 0; i < Math.min(lines.length, maxLinesPerFile); i++) {
|
|
477
|
-
if (lines[i].toLowerCase().includes(q)) {
|
|
478
|
-
const topicName = file.replace(/\.md$/, "");
|
|
479
|
-
results.push({
|
|
480
|
-
type: `memory:${topicName}`,
|
|
481
|
-
content: `[${topicName}] L${i + 1}: ${lines[i].trim().substring(0, 150)}`
|
|
482
|
-
});
|
|
483
|
-
if (results.filter((r) => r.type.startsWith("memory")).length >= 5) break;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
} catch {
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
} catch {
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
results.sort((a, b) => {
|
|
493
|
-
if (a.timestamp && b.timestamp) return b.timestamp - a.timestamp;
|
|
494
|
-
if (a.timestamp) return -1;
|
|
495
|
-
if (b.timestamp) return 1;
|
|
496
|
-
return 0;
|
|
497
|
-
});
|
|
498
|
-
return results.slice(0, limit);
|
|
499
|
-
}
|
|
500
|
-
getConventions() {
|
|
501
|
-
this.ensureInit();
|
|
502
|
-
return this.state.conventions;
|
|
503
|
-
}
|
|
504
|
-
pruneMemory() {
|
|
505
|
-
this.ensureInit();
|
|
506
|
-
this.state.toolCalls = this.state.toolCalls.slice(-3);
|
|
507
|
-
this.state.searchResults.clear();
|
|
508
|
-
this.state.completedSteps = [];
|
|
509
|
-
this.save();
|
|
510
|
-
}
|
|
511
|
-
getSummary(topic) {
|
|
512
|
-
this.ensureInit();
|
|
513
|
-
if (topic) {
|
|
514
|
-
const content = this.getMemoryContent(topic);
|
|
515
|
-
return { topic, content };
|
|
516
|
-
}
|
|
517
|
-
const unresolvedFailures = [];
|
|
518
|
-
const seen = /* @__PURE__ */ new Set();
|
|
519
|
-
for (const [, failures] of this.state.failedFiles) {
|
|
520
|
-
for (const f of failures) {
|
|
521
|
-
if (f.resolved) continue;
|
|
522
|
-
const error = f.error.substring(0, 200);
|
|
523
|
-
if (!error.trim()) continue;
|
|
524
|
-
const key = `${f.task}::${error}`;
|
|
525
|
-
if (seen.has(key)) continue;
|
|
526
|
-
seen.add(key);
|
|
527
|
-
unresolvedFailures.push({ task: f.task, error });
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
const hasMemories = fs.existsSync(this.memoriesDir());
|
|
531
|
-
let availableMemories = [];
|
|
532
|
-
if (hasMemories) {
|
|
533
|
-
try {
|
|
534
|
-
availableMemories = fs.readdirSync(this.memoriesDir()).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
|
|
535
|
-
} catch {
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
return {
|
|
539
|
-
projectRoot: this.state.projectRoot,
|
|
540
|
-
sessionDuration: Math.round((Date.now() - this.state.startTime) / 1e3) + "s",
|
|
541
|
-
currentGoal: this.state.currentGoal,
|
|
542
|
-
completedSteps: this.state.completedSteps,
|
|
543
|
-
modifiedFiles: Array.from(this.state.modifiedFiles.values()),
|
|
544
|
-
unresolvedFailures,
|
|
545
|
-
toolCallCount: this.state.toolCalls.length,
|
|
546
|
-
recentSearches: Array.from(this.state.searchResults.keys()).slice(-5),
|
|
547
|
-
hasConventions: !!this.state.conventions,
|
|
548
|
-
...availableMemories.length > 0 ? { availableMemories } : {},
|
|
549
|
-
...availableMemories.length > 0 ? { hint: `Use get_session_memory({ topic: "<name>" }) to load a specific memory. Topics: ${availableMemories.join(", ")}` } : {}
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
getModifiedFiles() {
|
|
553
|
-
this.ensureInit();
|
|
554
|
-
return Array.from(this.state.modifiedFiles.values());
|
|
555
|
-
}
|
|
556
|
-
getFailedFiles() {
|
|
557
|
-
this.ensureInit();
|
|
558
|
-
const result = [];
|
|
559
|
-
for (const [task, failures] of this.state.failedFiles) {
|
|
560
|
-
result.push({ task, failures });
|
|
561
|
-
}
|
|
562
|
-
return result;
|
|
563
|
-
}
|
|
564
|
-
getToolCallHistory(limit = 10) {
|
|
565
|
-
this.ensureInit();
|
|
566
|
-
return this.state.toolCalls.slice(-limit);
|
|
567
|
-
}
|
|
568
|
-
detectLoop() {
|
|
569
|
-
this.ensureInit();
|
|
570
|
-
const recentCalls = this.state.toolCalls.slice(-10);
|
|
571
|
-
if (recentCalls.length < 6) return { isLooping: false };
|
|
572
|
-
const toolCounts = /* @__PURE__ */ new Map();
|
|
573
|
-
for (const call of recentCalls) {
|
|
574
|
-
const existing = toolCounts.get(call.toolName);
|
|
575
|
-
const action = call.params.action || "";
|
|
576
|
-
if (existing) {
|
|
577
|
-
existing.count++;
|
|
578
|
-
existing.actions.add(action);
|
|
579
|
-
} else {
|
|
580
|
-
toolCounts.set(call.toolName, { count: 1, actions: /* @__PURE__ */ new Set([action]) });
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
for (const [toolName, { count, actions }] of toolCounts) {
|
|
584
|
-
if (count >= 4) {
|
|
585
|
-
if (actions.size >= 3) continue;
|
|
586
|
-
return {
|
|
587
|
-
isLooping: true,
|
|
588
|
-
toolName,
|
|
589
|
-
message: `Detected potential loop: "${toolName}" called ${count} times in last ${recentCalls.length} tool calls. Consider a different approach.`
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return { isLooping: false };
|
|
594
|
-
}
|
|
595
|
-
// ============================================================
|
|
596
|
-
// V3: Change Tracking (Selective Undo)
|
|
597
|
-
// ============================================================
|
|
598
|
-
/**
|
|
599
|
-
* Track a file change with symbol-level detail.
|
|
600
|
-
* Stores in memory.json AND writes to change_log in DB.
|
|
601
|
-
*/
|
|
602
|
-
async trackChange(filePath, changeType, symbol) {
|
|
603
|
-
this.ensureInit();
|
|
604
|
-
if (changeType === "modified" || changeType === "created") {
|
|
605
|
-
this.addModifiedFile(filePath);
|
|
606
|
-
}
|
|
607
|
-
try {
|
|
608
|
-
const { recordChange } = await import("./kumaDb-4XZ5S2LH.js");
|
|
609
|
-
const gitHash = this.getGitHead();
|
|
610
|
-
await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
|
|
611
|
-
} catch {
|
|
612
|
-
}
|
|
613
|
-
this.save();
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Get the current git HEAD hash.
|
|
617
|
-
*/
|
|
618
|
-
getGitHead() {
|
|
619
|
-
try {
|
|
620
|
-
return execSync("git rev-parse HEAD", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
621
|
-
} catch {
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* Get changes for selective undo, grouped by session.
|
|
627
|
-
*/
|
|
628
|
-
getChangesGrouped() {
|
|
629
|
-
this.ensureInit();
|
|
630
|
-
return [{
|
|
631
|
-
sessionId: String(this.state.startTime),
|
|
632
|
-
files: Array.from(this.state.modifiedFiles.keys()),
|
|
633
|
-
goal: this.state.currentGoal
|
|
634
|
-
}];
|
|
635
|
-
}
|
|
636
|
-
ensureInit() {
|
|
637
|
-
if (!this.initialized) {
|
|
638
|
-
this.init({
|
|
639
|
-
projectRoot: process.cwd(),
|
|
640
|
-
startTime: Date.now()
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
function simpleHash(str) {
|
|
646
|
-
let hash = 0;
|
|
647
|
-
for (let i = 0; i < str.length; i++) {
|
|
648
|
-
const char = str.charCodeAt(i);
|
|
649
|
-
hash = (hash << 5) - hash + char;
|
|
650
|
-
hash = hash & hash;
|
|
651
|
-
}
|
|
652
|
-
return Math.abs(hash).toString(36);
|
|
653
|
-
}
|
|
654
|
-
var sessionMemory = new SessionMemory();
|
|
655
|
-
|
|
656
|
-
export {
|
|
657
|
-
sessionMemory
|
|
658
|
-
};
|