@plumpslabs/kuma 2.3.19 → 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.
Files changed (39) hide show
  1. package/README.md +18 -0
  2. package/dist/index.js +10221 -332
  3. package/package.json +2 -2
  4. package/packages/ide/studio/dist/index.js +37 -5
  5. package/packages/ide/studio/public/index.html +284 -63
  6. package/dist/agentDetector-YOWQVJFR.js +0 -186
  7. package/dist/chunk-3BRBJZ7P.js +0 -1055
  8. package/dist/chunk-3OHYYXYN.js +0 -71
  9. package/dist/chunk-ABKE45T4.js +0 -1264
  10. package/dist/chunk-E2KFPEBT.js +0 -183
  11. package/dist/chunk-FKRSI5U5.js +0 -282
  12. package/dist/chunk-GFLSAXAH.js +0 -155
  13. package/dist/chunk-L7F67KUP.js +0 -172
  14. package/dist/chunk-LVKOGXLC.js +0 -658
  15. package/dist/chunk-PRUTTZBS.js +0 -1113
  16. package/dist/contextDigest-QB5XHPXE.js +0 -177
  17. package/dist/domainRules-QLPAQASB.js +0 -20
  18. package/dist/init-PL4XL662.js +0 -15
  19. package/dist/kumaAstValidator-CNM7FHYA.js +0 -150
  20. package/dist/kumaCheckpoint-J2LDQMEO.js +0 -207
  21. package/dist/kumaCodeScanner-J6B2EHGI.js +0 -563
  22. package/dist/kumaContractEngine-KX27T4N7.js +0 -305
  23. package/dist/kumaDb-4XZ5S2LH.js +0 -65
  24. package/dist/kumaDriftDetector-TOORILSZ.js +0 -237
  25. package/dist/kumaGotchas-XRGFFBTA.js +0 -151
  26. package/dist/kumaGraph-UMXZNGYF.js +0 -44
  27. package/dist/kumaMemory-FBJMV77G.js +0 -16
  28. package/dist/kumaMiner-XJETL7TL.js +0 -176
  29. package/dist/kumaPolicyEngine-2QDJDLM7.js +0 -311
  30. package/dist/kumaProgressiveContext-MWEDRXOH.js +0 -231
  31. package/dist/kumaSearch-PV4QTKE7.js +0 -321
  32. package/dist/kumaTrajectory-7NOAVNG2.js +0 -460
  33. package/dist/kumaVerifier-6YEGC77M.js +0 -265
  34. package/dist/kumaVisualize-264OEBGJ.js +0 -264
  35. package/dist/pathValidator-V4DC6U6Z.js +0 -22
  36. package/dist/safetyAudit-O45SPNTS.js +0 -12
  37. package/dist/safetyScore-TMMRD2MV.js +0 -333
  38. package/dist/sessionMemory-YPKVIOMV.js +0 -6
  39. package/dist/skillGenerator-PWEJKZNX.js +0 -304
@@ -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
- };