@plumpslabs/kuma 2.2.7 → 2.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 +133 -446
- package/dist/{agentDetector-HROEABDO.js → agentDetector-FDRVUNJY.js} +1 -1
- package/dist/{chunk-IR6HFFDU.js → chunk-5A6AKUJZ.js} +47 -29
- package/dist/chunk-6NEMSUKP.js +304 -0
- package/dist/chunk-IMO4QAFJ.js +752 -0
- package/dist/{chunk-T55NCW63.js → chunk-IXMWW5WA.js} +1 -62
- package/dist/chunk-OUPFJQKW.js +131 -0
- package/dist/{chunk-QPVPFUCQ.js → chunk-TT37TE4P.js} +43 -40
- package/dist/chunk-WP3WRPDC.js +71 -0
- package/dist/index.js +1215 -12848
- package/dist/{init-INY6XOQT.js → init-O4MBXFZL.js} +2 -2
- package/dist/kumaDb-KN7Z4B5V.js +19 -0
- package/dist/kumaGraph-GW5SWOHQ.js +36 -0
- package/dist/kumaMemory-IO7TVE7W.js +16 -0
- package/dist/safetyScore-7FU7XSY5.js +301 -0
- package/dist/sessionMemory-52ECPXZ6.js +6 -0
- package/package.json +2 -2
- package/dist/sessionMemory-LGQFVHHE.js +0 -14
|
@@ -89,77 +89,16 @@ function validateFilePath(filePath, projectRoot) {
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
function validateFileExtension(filePath) {
|
|
93
|
-
const allowedExtensions = [
|
|
94
|
-
".ts",
|
|
95
|
-
".tsx",
|
|
96
|
-
".js",
|
|
97
|
-
".jsx",
|
|
98
|
-
".mjs",
|
|
99
|
-
".cjs",
|
|
100
|
-
".json",
|
|
101
|
-
".md",
|
|
102
|
-
".css",
|
|
103
|
-
".html",
|
|
104
|
-
".htm",
|
|
105
|
-
".env",
|
|
106
|
-
".env.example",
|
|
107
|
-
".env.local",
|
|
108
|
-
".yml",
|
|
109
|
-
".yaml",
|
|
110
|
-
".toml",
|
|
111
|
-
".svg",
|
|
112
|
-
".png",
|
|
113
|
-
".jpg",
|
|
114
|
-
".gif",
|
|
115
|
-
".sh",
|
|
116
|
-
".bat",
|
|
117
|
-
".ps1",
|
|
118
|
-
".sql",
|
|
119
|
-
".graphql",
|
|
120
|
-
".gql",
|
|
121
|
-
".vue",
|
|
122
|
-
".svelte",
|
|
123
|
-
".astro",
|
|
124
|
-
".prisma",
|
|
125
|
-
".proto",
|
|
126
|
-
".txt",
|
|
127
|
-
".log"
|
|
128
|
-
];
|
|
129
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
130
|
-
return allowedExtensions.includes(ext);
|
|
131
|
-
}
|
|
132
92
|
function getProjectRoot() {
|
|
133
93
|
return process.env.AGENT_PROJECT_ROOT ?? process.cwd();
|
|
134
94
|
}
|
|
135
|
-
function getBackupPath(filePath, timestamp) {
|
|
136
|
-
const ts = timestamp ?? Date.now();
|
|
137
|
-
const root = getProjectRoot();
|
|
138
|
-
const relativePath = path.relative(root, filePath);
|
|
139
|
-
return path.join(root, ".kuma", "backups", String(ts), relativePath);
|
|
140
|
-
}
|
|
141
|
-
function ensureBackupDir() {
|
|
142
|
-
const root = getProjectRoot();
|
|
143
|
-
const backupDir = path.join(root, ".kuma", "backups");
|
|
144
|
-
if (!fs.existsSync(backupDir)) {
|
|
145
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
146
|
-
}
|
|
147
|
-
return backupDir;
|
|
148
|
-
}
|
|
149
95
|
function getKumaDir() {
|
|
150
96
|
const root = getProjectRoot();
|
|
151
97
|
return path.join(root, ".kuma");
|
|
152
98
|
}
|
|
153
|
-
function getKumaBackupsDir() {
|
|
154
|
-
return path.join(getKumaDir(), "backups");
|
|
155
|
-
}
|
|
156
99
|
|
|
157
100
|
export {
|
|
158
101
|
validateFilePath,
|
|
159
|
-
validateFileExtension,
|
|
160
102
|
getProjectRoot,
|
|
161
|
-
|
|
162
|
-
ensureBackupDir,
|
|
163
|
-
getKumaDir,
|
|
164
|
-
getKumaBackupsDir
|
|
103
|
+
getKumaDir
|
|
165
104
|
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionMemory
|
|
3
|
+
} from "./chunk-TT37TE4P.js";
|
|
4
|
+
import {
|
|
5
|
+
getProjectRoot
|
|
6
|
+
} from "./chunk-IXMWW5WA.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
|
+
return `\u2705 Decision "${decision.title}" recorded.`;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
return `Error recording decision: ${err}`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
var decisionCooldown = 0;
|
|
82
|
+
function shouldRecordDecision() {
|
|
83
|
+
const history = sessionMemory.getToolCallHistory(30);
|
|
84
|
+
const edits = history.filter((c) => c.toolName === "precise_diff_editor");
|
|
85
|
+
if (edits.length > decisionCooldown + 10 && edits.length >= 10) {
|
|
86
|
+
decisionCooldown = edits.length;
|
|
87
|
+
return {
|
|
88
|
+
worth: true,
|
|
89
|
+
title: `Significant edits (${edits.length} changes)`
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return { worth: false };
|
|
93
|
+
}
|
|
94
|
+
function getProactiveMemories() {
|
|
95
|
+
const summary = sessionMemory.getSummary();
|
|
96
|
+
const goal = summary.currentGoal || "";
|
|
97
|
+
const modifiedFiles = summary.modifiedFiles || [];
|
|
98
|
+
const context = [goal, ...modifiedFiles.map((f) => f.filePath || "")].join(" ");
|
|
99
|
+
if (!context.trim()) return "";
|
|
100
|
+
const memories = scoreMemoryRelevance(context, 3);
|
|
101
|
+
return formatScoredMemories(memories, goal || "current context");
|
|
102
|
+
}
|
|
103
|
+
function formatDecisionTemplate() {
|
|
104
|
+
return [
|
|
105
|
+
"\u{1F4DD} **Decision Recording Template**",
|
|
106
|
+
"\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",
|
|
107
|
+
"",
|
|
108
|
+
"Use kuma_decision({ action: 'record', ... }) to log important decisions:",
|
|
109
|
+
"",
|
|
110
|
+
"```",
|
|
111
|
+
"kuma_decision({",
|
|
112
|
+
" action: 'record',",
|
|
113
|
+
" title: 'Why Redis instead of in-memory cache',",
|
|
114
|
+
" context: 'Need stateless auth for mobile clients',",
|
|
115
|
+
" options: ['Redis', 'In-memory', 'Database'],",
|
|
116
|
+
" rationale: 'Latency <10ms with persistence required',",
|
|
117
|
+
" outcome: 'Redis chosen'",
|
|
118
|
+
"})",
|
|
119
|
+
"```",
|
|
120
|
+
"",
|
|
121
|
+
"\u{1F4A1} Call kuma_decision({ action: 'suggest' }) to check if now is a good time to record."
|
|
122
|
+
].join("\n");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
scoreMemoryRelevance,
|
|
127
|
+
recordDecision,
|
|
128
|
+
shouldRecordDecision,
|
|
129
|
+
getProactiveMemories,
|
|
130
|
+
formatDecisionTemplate
|
|
131
|
+
};
|
|
@@ -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;
|
|
@@ -557,6 +558,47 @@ No unresolved issues.`;
|
|
|
557
558
|
}
|
|
558
559
|
return { isLooping: false };
|
|
559
560
|
}
|
|
561
|
+
// ============================================================
|
|
562
|
+
// V3: Change Tracking (Selective Undo)
|
|
563
|
+
// ============================================================
|
|
564
|
+
/**
|
|
565
|
+
* Track a file change with symbol-level detail.
|
|
566
|
+
* Stores in memory.json AND writes to change_log in DB.
|
|
567
|
+
*/
|
|
568
|
+
async trackChange(filePath, changeType, symbol) {
|
|
569
|
+
this.ensureInit();
|
|
570
|
+
if (changeType === "modified" || changeType === "created") {
|
|
571
|
+
this.addModifiedFile(filePath);
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
const { recordChange } = await import("./kumaDb-KN7Z4B5V.js");
|
|
575
|
+
const gitHash = this.getGitHead();
|
|
576
|
+
await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
|
|
577
|
+
} catch {
|
|
578
|
+
}
|
|
579
|
+
this.save();
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Get the current git HEAD hash.
|
|
583
|
+
*/
|
|
584
|
+
getGitHead() {
|
|
585
|
+
try {
|
|
586
|
+
return execSync("git rev-parse HEAD", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
587
|
+
} catch {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Get changes for selective undo, grouped by session.
|
|
593
|
+
*/
|
|
594
|
+
getChangesGrouped() {
|
|
595
|
+
this.ensureInit();
|
|
596
|
+
return [{
|
|
597
|
+
sessionId: String(this.state.startTime),
|
|
598
|
+
files: Array.from(this.state.modifiedFiles.keys()),
|
|
599
|
+
goal: this.state.currentGoal
|
|
600
|
+
}];
|
|
601
|
+
}
|
|
560
602
|
ensureInit() {
|
|
561
603
|
if (!this.initialized) {
|
|
562
604
|
this.init({
|
|
@@ -567,46 +609,7 @@ No unresolved issues.`;
|
|
|
567
609
|
}
|
|
568
610
|
};
|
|
569
611
|
var sessionMemory = new SessionMemory();
|
|
570
|
-
function getSessionMemory(topic) {
|
|
571
|
-
return sessionMemory.getSummary(topic);
|
|
572
|
-
}
|
|
573
|
-
function searchSessionMemory(params) {
|
|
574
|
-
const { query, limit = 20 } = params;
|
|
575
|
-
sessionMemory.recordToolCall("search_session_memory", { query, limit });
|
|
576
|
-
const results = sessionMemory.searchMemory(query, limit);
|
|
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}).`;
|
|
604
|
-
}
|
|
605
612
|
|
|
606
613
|
export {
|
|
607
|
-
|
|
608
|
-
sessionMemory,
|
|
609
|
-
getSessionMemory,
|
|
610
|
-
searchSessionMemory,
|
|
611
|
-
handleWriteMemory
|
|
614
|
+
sessionMemory
|
|
612
615
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionMemory
|
|
3
|
+
} from "./chunk-TT37TE4P.js";
|
|
4
|
+
import {
|
|
5
|
+
getProjectRoot
|
|
6
|
+
} from "./chunk-IXMWW5WA.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
|
+
};
|