@plumpslabs/kuma 2.2.8 → 2.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 +133 -446
- package/dist/{agentDetector-HROEABDO.js → agentDetector-G4RBMZ6X.js} +1 -1
- package/dist/chunk-2IIDVJPW.js +836 -0
- package/dist/chunk-4CKTTORL.js +172 -0
- package/dist/{chunk-IR6HFFDU.js → chunk-5WSPGLXS.js} +47 -29
- package/dist/{chunk-QPVPFUCQ.js → chunk-6W7YV4AF.js} +79 -41
- package/dist/{chunk-T55NCW63.js → chunk-E2KFPEBT.js} +18 -0
- package/dist/chunk-WG47POMW.js +71 -0
- package/dist/chunk-ZDQVSBFB.js +867 -0
- package/dist/index.js +1406 -12868
- package/dist/{init-INY6XOQT.js → init-3MBHSTWD.js} +2 -2
- package/dist/kumaDb-PA7XVERC.js +59 -0
- package/dist/kumaGraph-VUTLTOFY.js +36 -0
- package/dist/kumaMemory-YEIEXGNW.js +16 -0
- package/dist/pathValidator-V4DC6U6Z.js +22 -0
- package/dist/safetyScore-OQFYLLP7.js +301 -0
- package/dist/sessionMemory-JQT5GEWO.js +6 -0
- package/package.json +2 -2
- package/dist/sessionMemory-LGQFVHHE.js +0 -14
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionMemory
|
|
3
|
+
} from "./chunk-6W7YV4AF.js";
|
|
4
|
+
import {
|
|
5
|
+
getProjectRoot
|
|
6
|
+
} from "./chunk-E2KFPEBT.js";
|
|
7
|
+
|
|
8
|
+
// src/engine/kumaMemory.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
var MEMORY_DIR = ".kuma/memories";
|
|
12
|
+
function scoreMemoryRelevance(context, limit = 5) {
|
|
13
|
+
const results = [];
|
|
14
|
+
const kumaDir = path.join(getProjectRoot(), MEMORY_DIR);
|
|
15
|
+
if (!fs.existsSync(kumaDir)) return [];
|
|
16
|
+
const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
17
|
+
if (terms.length === 0) return [];
|
|
18
|
+
try {
|
|
19
|
+
const files = fs.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
try {
|
|
22
|
+
const content = fs.readFileSync(path.join(kumaDir, file), "utf-8");
|
|
23
|
+
const lower = content.toLowerCase();
|
|
24
|
+
let matchCount = 0;
|
|
25
|
+
for (const term of terms) {
|
|
26
|
+
if (lower.includes(term)) matchCount++;
|
|
27
|
+
}
|
|
28
|
+
const score = Math.round(matchCount / terms.length * 100);
|
|
29
|
+
if (score > 0) {
|
|
30
|
+
const topic = file.replace(/\.md$/, "");
|
|
31
|
+
const firstLine = content.split("\n").slice(0, 3).join(" ").substring(0, 150);
|
|
32
|
+
results.push({
|
|
33
|
+
topic,
|
|
34
|
+
content: firstLine,
|
|
35
|
+
score,
|
|
36
|
+
reason: `${matchCount}/${terms.length} terms matched`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
45
|
+
}
|
|
46
|
+
function formatScoredMemories(memories, context) {
|
|
47
|
+
if (memories.length === 0) return "";
|
|
48
|
+
const lines = [
|
|
49
|
+
`\u{1F9E0} **Relevant Memories** (for "${context.substring(0, 40)}")`,
|
|
50
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"
|
|
51
|
+
];
|
|
52
|
+
for (const m of memories) {
|
|
53
|
+
const bar = "\u2588".repeat(Math.round(m.score / 10)) + "\u2591".repeat(Math.round(10 - m.score / 10));
|
|
54
|
+
lines.push(` **${m.topic}** \u2014 ${bar} ${m.score}%`);
|
|
55
|
+
lines.push(` ${m.content.substring(0, 100)}`);
|
|
56
|
+
lines.push(` \u{1F4A1} ${m.reason}`);
|
|
57
|
+
lines.push("");
|
|
58
|
+
}
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
function recordDecision(decision) {
|
|
62
|
+
try {
|
|
63
|
+
const entry = [
|
|
64
|
+
"",
|
|
65
|
+
`## ${decision.title}`,
|
|
66
|
+
`- **Date:** ${decision.timestamp || (/* @__PURE__ */ new Date()).toISOString()}`,
|
|
67
|
+
`- **Context:** ${decision.context}`,
|
|
68
|
+
`- **Options:** ${decision.options.join(", ")}`,
|
|
69
|
+
`- **Rationale:** ${decision.rationale}`,
|
|
70
|
+
`- **Outcome:** ${decision.outcome}`,
|
|
71
|
+
""
|
|
72
|
+
].join("\n");
|
|
73
|
+
const existing = sessionMemory.getMemoryContent("decisions");
|
|
74
|
+
sessionMemory.writeMemory("decisions", existing + entry);
|
|
75
|
+
sessionMemory.recordToolCall("kuma_decision", { title: decision.title });
|
|
76
|
+
recordDecisionToGraph(decision).catch(() => {
|
|
77
|
+
});
|
|
78
|
+
return `\u2705 Decision "${decision.title}" recorded.`;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return `Error recording decision: ${err}`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function recordDecisionToGraph(decision) {
|
|
84
|
+
try {
|
|
85
|
+
const { upsertNode, addEdge } = await import("./kumaGraph-VUTLTOFY.js");
|
|
86
|
+
const decisionId = `decision::${decision.title.replace(/[^a-zA-Z0-9_\-\s]/g, "").trim().replace(/\s+/g, "-")}`;
|
|
87
|
+
await upsertNode({
|
|
88
|
+
id: decisionId,
|
|
89
|
+
type: "variable",
|
|
90
|
+
name: `ADR: ${decision.title.substring(0, 80)}`,
|
|
91
|
+
metadata: {
|
|
92
|
+
type: "architectural-decision",
|
|
93
|
+
context: decision.context.substring(0, 200),
|
|
94
|
+
rationale: decision.rationale.substring(0, 200),
|
|
95
|
+
outcome: decision.outcome,
|
|
96
|
+
timestamp: decision.timestamp
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
const filePathMatches = decision.context.matchAll(/["']?([\w./-]+\.\w+)["']?/g);
|
|
100
|
+
for (const match of filePathMatches) {
|
|
101
|
+
const possiblePath = match[1];
|
|
102
|
+
if (possiblePath.includes("/") && (possiblePath.endsWith(".ts") || possiblePath.endsWith(".js") || possiblePath.endsWith(".json") || possiblePath.endsWith(".md"))) {
|
|
103
|
+
try {
|
|
104
|
+
const fileId = `file::${possiblePath}`;
|
|
105
|
+
await upsertNode({ id: fileId, type: "file", name: possiblePath });
|
|
106
|
+
await addEdge({ sourceId: decisionId, targetId: fileId, type: "depends_on", metadata: { reason: "adr-context" } });
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (decision.outcome && decision.outcome !== "implemented") {
|
|
112
|
+
try {
|
|
113
|
+
const outcomeId = `decision-outcome::${decision.outcome.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase()}`;
|
|
114
|
+
await upsertNode({ id: outcomeId, type: "variable", name: `Outcome: ${decision.outcome.substring(0, 60)}` });
|
|
115
|
+
await addEdge({ sourceId: decisionId, targetId: outcomeId, type: "depends_on" });
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
var decisionCooldown = 0;
|
|
123
|
+
function shouldRecordDecision() {
|
|
124
|
+
const history = sessionMemory.getToolCallHistory(30);
|
|
125
|
+
const edits = history.filter((c) => c.toolName === "precise_diff_editor");
|
|
126
|
+
if (edits.length > decisionCooldown + 10 && edits.length >= 10) {
|
|
127
|
+
decisionCooldown = edits.length;
|
|
128
|
+
return {
|
|
129
|
+
worth: true,
|
|
130
|
+
title: `Significant edits (${edits.length} changes)`
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return { worth: false };
|
|
134
|
+
}
|
|
135
|
+
function getProactiveMemories() {
|
|
136
|
+
const summary = sessionMemory.getSummary();
|
|
137
|
+
const goal = summary.currentGoal || "";
|
|
138
|
+
const modifiedFiles = summary.modifiedFiles || [];
|
|
139
|
+
const context = [goal, ...modifiedFiles.map((f) => f.filePath || "")].join(" ");
|
|
140
|
+
if (!context.trim()) return "";
|
|
141
|
+
const memories = scoreMemoryRelevance(context, 3);
|
|
142
|
+
return formatScoredMemories(memories, goal || "current context");
|
|
143
|
+
}
|
|
144
|
+
function formatDecisionTemplate() {
|
|
145
|
+
return [
|
|
146
|
+
"\u{1F4DD} **Decision Recording Template**",
|
|
147
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
148
|
+
"",
|
|
149
|
+
"Use kuma_decision({ action: 'record', ... }) to log important decisions:",
|
|
150
|
+
"",
|
|
151
|
+
"```",
|
|
152
|
+
"kuma_decision({",
|
|
153
|
+
" action: 'record',",
|
|
154
|
+
" title: 'Why Redis instead of in-memory cache',",
|
|
155
|
+
" context: 'Need stateless auth for mobile clients',",
|
|
156
|
+
" options: ['Redis', 'In-memory', 'Database'],",
|
|
157
|
+
" rationale: 'Latency <10ms with persistence required',",
|
|
158
|
+
" outcome: 'Redis chosen'",
|
|
159
|
+
"})",
|
|
160
|
+
"```",
|
|
161
|
+
"",
|
|
162
|
+
"\u{1F4A1} Call kuma_decision({ action: 'suggest' }) to check if now is a good time to record."
|
|
163
|
+
].join("\n");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
scoreMemoryRelevance,
|
|
168
|
+
recordDecision,
|
|
169
|
+
shouldRecordDecision,
|
|
170
|
+
getProactiveMemories,
|
|
171
|
+
formatDecisionTemplate
|
|
172
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getProjectRoot
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-E2KFPEBT.js";
|
|
4
4
|
|
|
5
5
|
// src/cli/init.ts
|
|
6
6
|
import fs from "fs";
|
|
@@ -252,50 +252,68 @@ function generateInitMdContent() {
|
|
|
252
252
|
"",
|
|
253
253
|
"_(Auto-generated by `kuma init` \u2014 edit this file directly to customize rules)_",
|
|
254
254
|
"",
|
|
255
|
-
"##
|
|
255
|
+
"## Kuma V3 \u2014 3 Coarse-Grained Tools",
|
|
256
256
|
"",
|
|
257
|
-
"
|
|
258
|
-
"- smart_grep returns line numbers + context, caches results, respects .gitignore",
|
|
257
|
+
"Kuma provides 3 pipeline-driven tools. Each action triggers a multi-step deterministic workflow internally.",
|
|
259
258
|
"",
|
|
260
|
-
"
|
|
259
|
+
"### \u{1F9E0} kuma_context \u2014 Context & Research (call FIRST every session)",
|
|
261
260
|
"",
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
'- `kuma_context({ action: "init" })` \u2014 Load project brief, detect stack, show structure',
|
|
262
|
+
'- **`kuma_context({ action: "research", scope: "..." })` \u2014 WAJIB before editing unfamiliar code**',
|
|
263
|
+
" - 5-step pipeline: load cache \u2192 check staleness \u2192 query graph \u2192 impact analysis \u2192 decision lookup",
|
|
264
|
+
'- `kuma_context({ action: "impact", target: "symbol" })` \u2014 Analyze change effects',
|
|
265
|
+
'- `kuma_context({ action: "navigate", target: "flow" })` \u2014 Trace code flow',
|
|
266
|
+
'- `kuma_context({ action: "changes" })` \u2014 View session change log',
|
|
267
|
+
'- `kuma_context({ action: "health" })` \u2014 Project health score 0-100',
|
|
264
268
|
"",
|
|
265
|
-
"
|
|
269
|
+
"### \u{1F4DD} kuma_memory \u2014 Decision & Knowledge",
|
|
266
270
|
"",
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
271
|
+
'- `kuma_memory({ action: "research_save", scope: "...", ... })` \u2014 Save research results',
|
|
272
|
+
'- `kuma_memory({ action: "decision", decisionAction: "record", ... })` \u2014 ADR-style decision',
|
|
273
|
+
'- `kuma_memory({ action: "session" })` \u2014 Session summary',
|
|
274
|
+
'- `kuma_memory({ action: "heal" })` \u2014 Self-heal knowledge graph',
|
|
275
|
+
'- `kuma_memory({ action: "search", query: "..." })` \u2014 Search memories + graph',
|
|
276
|
+
'- `kuma_memory({ action: "changes" })` \u2014 View change log',
|
|
270
277
|
"",
|
|
271
|
-
"
|
|
278
|
+
"### \u{1F6E1}\uFE0F kuma_safety \u2014 Safety & Policy",
|
|
272
279
|
"",
|
|
273
|
-
|
|
274
|
-
|
|
280
|
+
'- `kuma_safety({ action: "guard", guardGoal: "..." })` \u2014 Anti-pattern, drift, loop detection',
|
|
281
|
+
'- `kuma_safety({ action: "check", ... })` \u2014 Pre-execution safety check',
|
|
282
|
+
'- `kuma_safety({ action: "audit" })` \u2014 Query safety audit trail',
|
|
283
|
+
'- `kuma_safety({ action: "lock", lockAction: "acquire", ... })` \u2014 Multi-agent file lock',
|
|
284
|
+
'- `kuma_safety({ action: "health" })` \u2014 Safety score 0-100',
|
|
285
|
+
'- `kuma_safety({ action: "override", ... })` \u2014 Logged safety bypass',
|
|
275
286
|
"",
|
|
276
|
-
"##
|
|
287
|
+
"## Mandatory Research Pipeline",
|
|
277
288
|
"",
|
|
278
|
-
"
|
|
279
|
-
"- Always run typecheck after editing TypeScript files",
|
|
289
|
+
"**Before editing any unfamiliar code, you MUST call:**",
|
|
280
290
|
"",
|
|
281
|
-
"
|
|
291
|
+
' kuma_context({ action: "research", scope: "<area>" })',
|
|
282
292
|
"",
|
|
283
|
-
"
|
|
284
|
-
"
|
|
293
|
+
"This runs: cache load \u2192 staleness check \u2192 graph query \u2192 impact analysis \u2192 decision lookup.",
|
|
294
|
+
"Only after reviewing the structured result should you make changes.",
|
|
285
295
|
"",
|
|
286
|
-
"
|
|
296
|
+
"After editing, save what you learned:",
|
|
287
297
|
"",
|
|
288
|
-
"
|
|
289
|
-
"- Use **get_session_memory()** to recall what happened before",
|
|
290
|
-
"- Use **kuma_reflect** / **kuma_guard** to stay on track",
|
|
291
|
-
"- Use **write_memory()** to persist decisions and glossary terms",
|
|
298
|
+
' kuma_memory({ action: "research_save", scope: "<area>", confidence: 0-1 })',
|
|
292
299
|
"",
|
|
293
300
|
"## General Rules",
|
|
294
301
|
"",
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
302
|
+
'- **Call `kuma_context({ action: "init" })` at session start** \u2014 always',
|
|
303
|
+
'- **Research before edit** \u2014 always call `kuma_context({ action: "research" })` first',
|
|
304
|
+
'- **Save after research** \u2014 use `kuma_memory({ action: "research_save" })` to persist',
|
|
305
|
+
'- **Record decisions** \u2014 use `kuma_memory({ action: "decision" })` for significant changes',
|
|
306
|
+
'- **Verify with guard** \u2014 use `kuma_safety({ action: "guard" })` after editing',
|
|
307
|
+
'- **Check changes** \u2014 use `kuma_context({ action: "changes" })` to track what you modified',
|
|
308
|
+
"",
|
|
309
|
+
"## What Kuma Does NOT Do (Use Agent Native Tools)",
|
|
310
|
+
"",
|
|
311
|
+
"- Editing files \u2014 use your AI agent's native edit tools",
|
|
312
|
+
"- Searching code \u2014 use your agent's native grep/search",
|
|
313
|
+
"- Running commands \u2014 use your agent's native terminal",
|
|
314
|
+
"- Creating files \u2014 use your agent's native file creation",
|
|
315
|
+
"- Git operations \u2014 use your agent's native git tools",
|
|
316
|
+
"- Linting/testing \u2014 use your agent's native run commands",
|
|
299
317
|
"",
|
|
300
318
|
"---",
|
|
301
319
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/engine/sessionMemory.ts
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
4
5
|
var SessionMemory = class {
|
|
5
6
|
state;
|
|
6
7
|
initialized = false;
|
|
@@ -299,15 +300,41 @@ No unresolved issues.`;
|
|
|
299
300
|
}
|
|
300
301
|
recordToolCall(toolName, params) {
|
|
301
302
|
this.ensureInit();
|
|
303
|
+
const timestamp = Date.now();
|
|
302
304
|
this.state.toolCalls.push({
|
|
303
305
|
toolName,
|
|
304
306
|
params,
|
|
305
|
-
timestamp
|
|
307
|
+
timestamp
|
|
306
308
|
});
|
|
307
309
|
if (this.state.toolCalls.length > 100) {
|
|
308
310
|
this.state.toolCalls = this.state.toolCalls.slice(-100);
|
|
309
311
|
}
|
|
310
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-PA7XVERC.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
|
+
}
|
|
311
338
|
}
|
|
312
339
|
setConventions(conventions) {
|
|
313
340
|
this.ensureInit();
|
|
@@ -557,6 +584,47 @@ No unresolved issues.`;
|
|
|
557
584
|
}
|
|
558
585
|
return { isLooping: false };
|
|
559
586
|
}
|
|
587
|
+
// ============================================================
|
|
588
|
+
// V3: Change Tracking (Selective Undo)
|
|
589
|
+
// ============================================================
|
|
590
|
+
/**
|
|
591
|
+
* Track a file change with symbol-level detail.
|
|
592
|
+
* Stores in memory.json AND writes to change_log in DB.
|
|
593
|
+
*/
|
|
594
|
+
async trackChange(filePath, changeType, symbol) {
|
|
595
|
+
this.ensureInit();
|
|
596
|
+
if (changeType === "modified" || changeType === "created") {
|
|
597
|
+
this.addModifiedFile(filePath);
|
|
598
|
+
}
|
|
599
|
+
try {
|
|
600
|
+
const { recordChange } = await import("./kumaDb-PA7XVERC.js");
|
|
601
|
+
const gitHash = this.getGitHead();
|
|
602
|
+
await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
|
|
603
|
+
} catch {
|
|
604
|
+
}
|
|
605
|
+
this.save();
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Get the current git HEAD hash.
|
|
609
|
+
*/
|
|
610
|
+
getGitHead() {
|
|
611
|
+
try {
|
|
612
|
+
return execSync("git rev-parse HEAD", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
613
|
+
} catch {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Get changes for selective undo, grouped by session.
|
|
619
|
+
*/
|
|
620
|
+
getChangesGrouped() {
|
|
621
|
+
this.ensureInit();
|
|
622
|
+
return [{
|
|
623
|
+
sessionId: String(this.state.startTime),
|
|
624
|
+
files: Array.from(this.state.modifiedFiles.keys()),
|
|
625
|
+
goal: this.state.currentGoal
|
|
626
|
+
}];
|
|
627
|
+
}
|
|
560
628
|
ensureInit() {
|
|
561
629
|
if (!this.initialized) {
|
|
562
630
|
this.init({
|
|
@@ -566,47 +634,17 @@ No unresolved issues.`;
|
|
|
566
634
|
}
|
|
567
635
|
}
|
|
568
636
|
};
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (results.length === 0) {
|
|
578
|
-
return `\u{1F50D} **Search Memory** \u2014 No results for "${query}".`;
|
|
579
|
-
}
|
|
580
|
-
const lines = [
|
|
581
|
-
`\u{1F50D} **Search Memory** \u2014 ${results.length} results for "${query}"`,
|
|
582
|
-
""
|
|
583
|
-
];
|
|
584
|
-
for (const r of results) {
|
|
585
|
-
const icon = r.type.startsWith("tool:") ? "\u{1F6E0}\uFE0F" : r.type.startsWith("file:") ? r.type.includes("created") ? "\u2728" : "\u{1F4DD}" : r.type.startsWith("failure") ? "\u274C" : r.type.startsWith("memory") ? "\u{1F9E0}" : r.type === "search" ? "\u{1F50E}" : r.type === "dependency" ? "\u{1F517}" : "\u{1F4C4}";
|
|
586
|
-
const timeStr = r.timestamp ? new Date(r.timestamp).toLocaleTimeString() : "";
|
|
587
|
-
lines.push(`${icon} ${timeStr ? `[${timeStr}] ` : ""}${r.content}`);
|
|
588
|
-
}
|
|
589
|
-
lines.push("", `\u{1F4A1} Use get_session_memory({topic: "..."}) to load a specific memory topic.`);
|
|
590
|
-
return lines.join("\n");
|
|
591
|
-
}
|
|
592
|
-
function handleWriteMemory(params) {
|
|
593
|
-
const { topic, content, mode = "append" } = params;
|
|
594
|
-
const existing = sessionMemory.getMemoryContent(topic);
|
|
595
|
-
let finalContent = content;
|
|
596
|
-
if (mode === "prepend") {
|
|
597
|
-
finalContent = content + "\n\n" + existing;
|
|
598
|
-
} else if (mode === "append") {
|
|
599
|
-
finalContent = existing + "\n\n" + content;
|
|
600
|
-
}
|
|
601
|
-
sessionMemory.writeMemory(topic, finalContent);
|
|
602
|
-
sessionMemory.recordToolCall("write_memory", { topic, mode });
|
|
603
|
-
return `\u2705 Memory "${topic}" updated (mode: ${mode}).`;
|
|
637
|
+
function simpleHash(str) {
|
|
638
|
+
let hash = 0;
|
|
639
|
+
for (let i = 0; i < str.length; i++) {
|
|
640
|
+
const char = str.charCodeAt(i);
|
|
641
|
+
hash = (hash << 5) - hash + char;
|
|
642
|
+
hash = hash & hash;
|
|
643
|
+
}
|
|
644
|
+
return Math.abs(hash).toString(36);
|
|
604
645
|
}
|
|
646
|
+
var sessionMemory = new SessionMemory();
|
|
605
647
|
|
|
606
648
|
export {
|
|
607
|
-
|
|
608
|
-
sessionMemory,
|
|
609
|
-
getSessionMemory,
|
|
610
|
-
searchSessionMemory,
|
|
611
|
-
handleWriteMemory
|
|
649
|
+
sessionMemory
|
|
612
650
|
};
|
|
@@ -132,6 +132,22 @@ function validateFileExtension(filePath) {
|
|
|
132
132
|
function getProjectRoot() {
|
|
133
133
|
return process.env.AGENT_PROJECT_ROOT ?? process.cwd();
|
|
134
134
|
}
|
|
135
|
+
function isReadable(filePath) {
|
|
136
|
+
try {
|
|
137
|
+
fs.accessSync(filePath, fs.constants.R_OK);
|
|
138
|
+
return true;
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function isWritable(dirPath) {
|
|
144
|
+
try {
|
|
145
|
+
fs.accessSync(dirPath, fs.constants.W_OK);
|
|
146
|
+
return true;
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
135
151
|
function getBackupPath(filePath, timestamp) {
|
|
136
152
|
const ts = timestamp ?? Date.now();
|
|
137
153
|
const root = getProjectRoot();
|
|
@@ -158,6 +174,8 @@ export {
|
|
|
158
174
|
validateFilePath,
|
|
159
175
|
validateFileExtension,
|
|
160
176
|
getProjectRoot,
|
|
177
|
+
isReadable,
|
|
178
|
+
isWritable,
|
|
161
179
|
getBackupPath,
|
|
162
180
|
ensureBackupDir,
|
|
163
181
|
getKumaDir,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionMemory
|
|
3
|
+
} from "./chunk-6W7YV4AF.js";
|
|
4
|
+
import {
|
|
5
|
+
getProjectRoot
|
|
6
|
+
} from "./chunk-E2KFPEBT.js";
|
|
7
|
+
|
|
8
|
+
// src/utils/kumaShared.ts
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
function getSessionStats(inputGoal) {
|
|
11
|
+
const summary = sessionMemory.getSummary();
|
|
12
|
+
const goal = inputGoal || summary.currentGoal || "";
|
|
13
|
+
const modifiedFiles = sessionMemory.getModifiedFiles();
|
|
14
|
+
const toolCalls = sessionMemory.getToolCallHistory(50);
|
|
15
|
+
const failedFiles = sessionMemory.getFailedFiles();
|
|
16
|
+
const loop = sessionMemory.detectLoop();
|
|
17
|
+
return {
|
|
18
|
+
goal,
|
|
19
|
+
modifiedFiles,
|
|
20
|
+
toolCalls,
|
|
21
|
+
toolCallCount: toolCalls.length,
|
|
22
|
+
failedFiles,
|
|
23
|
+
hasLoop: loop.isLooping,
|
|
24
|
+
loopMessage: loop.message,
|
|
25
|
+
hasRunTests: toolCalls.some((c) => c.toolName === "execute_safe_test")
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function getGitDiffStat(timeout = 3e3) {
|
|
29
|
+
try {
|
|
30
|
+
const root = getProjectRoot();
|
|
31
|
+
return execSync("git diff --stat", {
|
|
32
|
+
cwd: root,
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
timeout
|
|
35
|
+
}).trim();
|
|
36
|
+
} catch {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function getUnresolvedCount(failedFiles) {
|
|
41
|
+
let count = 0;
|
|
42
|
+
for (const f of failedFiles) {
|
|
43
|
+
for (const ff of f.failures) {
|
|
44
|
+
if (!ff.resolved) count++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return count;
|
|
48
|
+
}
|
|
49
|
+
function buildDriftMessages(modifiedFiles, hasRunTests, unresolvedCount, gitStat, loopMessage) {
|
|
50
|
+
const drifts = [];
|
|
51
|
+
if (modifiedFiles > 0 && !hasRunTests) {
|
|
52
|
+
drifts.push(`${modifiedFiles} file(s) edited but no test run`);
|
|
53
|
+
}
|
|
54
|
+
if (loopMessage) {
|
|
55
|
+
drifts.push(loopMessage);
|
|
56
|
+
}
|
|
57
|
+
if (unresolvedCount > 0) {
|
|
58
|
+
drifts.push(`${unresolvedCount} unresolved failure(s)`);
|
|
59
|
+
}
|
|
60
|
+
if (gitStat) {
|
|
61
|
+
drifts.push(`Git diff: ${gitStat}`);
|
|
62
|
+
}
|
|
63
|
+
return drifts;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
getSessionStats,
|
|
68
|
+
getGitDiffStat,
|
|
69
|
+
getUnresolvedCount,
|
|
70
|
+
buildDriftMessages
|
|
71
|
+
};
|