@plumpslabs/kuma 2.3.20 → 2.3.23

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 +10316 -380
  3. package/package.json +2 -2
  4. package/packages/ide/studio/dist/index.js +90 -9
  5. package/packages/ide/studio/public/index.html +528 -233
  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,177 +0,0 @@
1
- import {
2
- getActiveGotchas
3
- } from "./chunk-FKRSI5U5.js";
4
- import {
5
- sessionMemory
6
- } from "./chunk-LVKOGXLC.js";
7
- import {
8
- getDb
9
- } from "./chunk-3BRBJZ7P.js";
10
- import {
11
- getProjectRoot
12
- } from "./chunk-E2KFPEBT.js";
13
-
14
- // src/engine/contextDigest.ts
15
- import fs from "fs";
16
- import path from "path";
17
- function detectTechStack() {
18
- const root = getProjectRoot();
19
- const stack = {
20
- languages: [],
21
- framework: null,
22
- database: null,
23
- testRunner: null,
24
- packageManager: null,
25
- monorepo: false
26
- };
27
- try {
28
- const pkgPath = path.join(root, "package.json");
29
- if (fs.existsSync(pkgPath)) {
30
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
31
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
32
- if (fs.existsSync(path.join(root, "pnpm-lock.yaml"))) stack.packageManager = "pnpm";
33
- else if (fs.existsSync(path.join(root, "yarn.lock"))) stack.packageManager = "yarn";
34
- else stack.packageManager = "npm";
35
- stack.monorepo = !!pkg.workspaces;
36
- if (deps.next) stack.framework = "Next.js";
37
- else if (deps.react) stack.framework = "React";
38
- else if (deps.vue) stack.framework = "Vue";
39
- else if (deps.express) stack.framework = "Express";
40
- else if (deps.fastify) stack.framework = "Fastify";
41
- else if (deps.nest) stack.framework = "NestJS";
42
- else if (deps["@remix-run/react"]) stack.framework = "Remix";
43
- else if (deps.svelte) stack.framework = "Svelte";
44
- else if (deps.angular) stack.framework = "Angular";
45
- if (deps.prisma) stack.database = "Prisma";
46
- else if (deps.typeorm) stack.database = "TypeORM";
47
- else if (deps.mongoose) stack.database = "MongoDB";
48
- else if (deps.pg || deps["@neondatabase/serverless"]) stack.database = "PostgreSQL";
49
- else if (deps.redis) stack.database = "Redis";
50
- else if (deps.better || deps.drizzle) stack.database = "Drizzle";
51
- if (deps.jest || deps["@jest/core"]) stack.testRunner = "Jest";
52
- else if (deps.vitest) stack.testRunner = "Vitest";
53
- else if (deps.mocha) stack.testRunner = "Mocha";
54
- else if (deps.playwright) stack.testRunner = "Playwright";
55
- else if (deps.cypress) stack.testRunner = "Cypress";
56
- if (fs.existsSync(path.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
57
- if (deps.typescript) stack.languages.push("TypeScript");
58
- if (fs.existsSync(path.join(root, "jsconfig.json"))) stack.languages.push("JavaScript");
59
- if (fs.existsSync(path.join(root, "go.mod"))) stack.languages.push("Go");
60
- if (fs.existsSync(path.join(root, "Cargo.toml"))) stack.languages.push("Rust");
61
- if (fs.existsSync(path.join(root, "pyproject.toml"))) stack.languages.push("Python");
62
- if (fs.existsSync(path.join(root, "Gemfile"))) stack.languages.push("Ruby");
63
- if (stack.languages.length === 0) stack.languages.push("JavaScript");
64
- }
65
- } catch {
66
- }
67
- return stack;
68
- }
69
- function detectEntryPoints() {
70
- const root = getProjectRoot();
71
- const entryPoints = [];
72
- const candidates = [
73
- "src/index.ts",
74
- "src/index.js",
75
- "src/app.ts",
76
- "src/app.js",
77
- "index.ts",
78
- "index.js",
79
- "src/main.ts",
80
- "src/main.js",
81
- "src/server.ts",
82
- "src/server.js",
83
- "app.ts",
84
- "app.js",
85
- "pages/index.tsx",
86
- "pages/index.jsx",
87
- "src/App.tsx",
88
- "src/App.js",
89
- "lib/main.ts",
90
- "lib/index.ts"
91
- ];
92
- for (const c of candidates) {
93
- if (fs.existsSync(path.join(root, c))) {
94
- entryPoints.push(c);
95
- }
96
- }
97
- try {
98
- const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8"));
99
- if (pkg.main && fs.existsSync(path.join(root, pkg.main))) {
100
- if (!entryPoints.includes(pkg.main)) entryPoints.push(pkg.main);
101
- }
102
- } catch {
103
- }
104
- return entryPoints;
105
- }
106
- async function getActiveADRs() {
107
- try {
108
- const db = await getDb();
109
- const stmt = db.prepare(
110
- "SELECT title FROM decision_log WHERE status = 'active' ORDER BY created_at DESC LIMIT 5"
111
- );
112
- const results = [];
113
- while (stmt.step()) {
114
- const row = stmt.getAsObject();
115
- results.push(row.title);
116
- }
117
- stmt.free();
118
- return results;
119
- } catch {
120
- return [];
121
- }
122
- }
123
- async function generateContextDigest() {
124
- const stack = detectTechStack();
125
- const entryPoints = detectEntryPoints();
126
- const adrs = await getActiveADRs();
127
- const gotchas = getActiveGotchas();
128
- const summary = sessionMemory.getSummary();
129
- const lines = [
130
- "\u{1F4CB} **Kuma Digest** \u2014 <500 token briefing",
131
- "\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
132
- ""
133
- ];
134
- const langStr = stack.languages.join("/");
135
- const frameworkStr = stack.framework || "\u2014";
136
- const dbStr = stack.database || "\u2014";
137
- const testStr = stack.testRunner || "\u2014";
138
- const pmStr = stack.packageManager || "\u2014";
139
- lines.push(`\u{1F527} **Stack**: ${langStr} | ${frameworkStr} | ${dbStr} | ${testStr} | ${pmStr}${stack.monorepo ? " (monorepo)" : ""}`);
140
- lines.push(`\u{1F4C1} **Root**: ${getProjectRoot().split("/").pop() || "?"}`);
141
- if (entryPoints.length > 0) {
142
- lines.push(`\u{1F6AA} **Entry**: ${entryPoints.slice(0, 3).join(", ")}`);
143
- }
144
- try {
145
- const ruleFile = path.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
146
- if (fs.existsSync(ruleFile)) {
147
- const content = fs.readFileSync(ruleFile, "utf-8");
148
- const rules = content.split("\n").filter((l) => /^- Rule \d+:?\s*/i.test(l)).map((l) => l.replace(/^- Rule \d+:?\s*/i, "").trim()).filter(Boolean);
149
- if (rules.length > 0) {
150
- lines.push(`\u{1F4CB} **Rules**: ${rules.slice(0, 5).join(" | ")}`);
151
- }
152
- }
153
- } catch {
154
- }
155
- if (gotchas.length > 0) {
156
- const critical = gotchas.filter((g) => g.severity === "critical" || g.severity === "high");
157
- if (critical.length > 0) {
158
- lines.push(`\u26A0\uFE0F **Gotchas**: ${critical.length} high/critical \u2014 ${critical.slice(0, 3).map((g) => g.filePath).join(", ")}`);
159
- } else {
160
- lines.push(`\u26A0\uFE0F **Gotchas**: ${gotchas.length} recorded`);
161
- }
162
- }
163
- if (adrs.length > 0) {
164
- lines.push(`\u{1F4DD} **ADRs**: ${adrs.slice(0, 3).join(" | ")}`);
165
- }
166
- lines.push(`\u{1F3AF} **Goal**: ${summary.currentGoal?.substring(0, 80) || "not set"}`);
167
- const text = lines.join("\n");
168
- const estimatedTokens = Math.ceil(text.length / 4);
169
- if (estimatedTokens > 450) {
170
- lines.push(`\u{1F4CA} *~${estimatedTokens} tokens, fits under 500 limit*`);
171
- }
172
- return lines.join("\n") + "\n\n\u{1F4A1} Use kuma_context({ action: 'sync' }) for full state.";
173
- }
174
- export {
175
- generateContextDigest,
176
- generateContextDigest as generateDigest
177
- };
@@ -1,20 +0,0 @@
1
- import {
2
- appendToLayer,
3
- checkFileGotchas,
4
- generateDigest,
5
- getActiveGotchas,
6
- getLayersSummary,
7
- readLayer,
8
- writeLayer
9
- } from "./chunk-FKRSI5U5.js";
10
- import "./chunk-LVKOGXLC.js";
11
- import "./chunk-E2KFPEBT.js";
12
- export {
13
- appendToLayer,
14
- checkFileGotchas,
15
- generateDigest,
16
- getActiveGotchas,
17
- getLayersSummary,
18
- readLayer,
19
- writeLayer
20
- };
@@ -1,15 +0,0 @@
1
- import {
2
- ALL_CONFIG_TYPES,
3
- CONFIG_LABELS,
4
- formatInitResults,
5
- generateInitMdContent,
6
- runInit
7
- } from "./chunk-ABKE45T4.js";
8
- import "./chunk-E2KFPEBT.js";
9
- export {
10
- ALL_CONFIG_TYPES,
11
- CONFIG_LABELS,
12
- formatInitResults,
13
- generateInitMdContent,
14
- runInit
15
- };
@@ -1,150 +0,0 @@
1
- import {
2
- getProjectRoot
3
- } from "./chunk-E2KFPEBT.js";
4
-
5
- // src/engine/kumaAstValidator.ts
6
- import fs from "fs";
7
- import path from "path";
8
- function loadImportWhitelist() {
9
- try {
10
- const root = getProjectRoot();
11
- const whitelistPath = path.join(root, ".kuma", "import-whitelist.json");
12
- if (fs.existsSync(whitelistPath)) {
13
- return JSON.parse(fs.readFileSync(whitelistPath, "utf-8"));
14
- }
15
- } catch {
16
- }
17
- return {
18
- allowed: [],
19
- blocked: ["sql.js", "better-sqlite3", "mysql", "mssql"]
20
- // dangerous native deps
21
- };
22
- }
23
- function validateCodeContent(code, filePath) {
24
- const findings = [];
25
- const lines = code.split("\n");
26
- const whitelist = loadImportWhitelist();
27
- for (let i = 0; i < lines.length; i++) {
28
- const line = lines[i];
29
- const lineNum = i + 1;
30
- if (/\/\/\s*(expect|assert|should|test|it\.)\s*\(/.test(line) || /(\/\*[\s\S]*?\*\/)/.test(line) && /(expect|assert|should|test)/.test(line)) {
31
- findings.push({
32
- line: lineNum,
33
- severity: "error",
34
- category: "assertion-deletion",
35
- message: `Test assertion appears to be commented out on line ${lineNum}`,
36
- suggestion: "Restore the assertion or write an equivalent test for the new behavior"
37
- });
38
- }
39
- if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line) || /catch\s*\([^)]*\)\s*\{/.test(line) && lines[i + 1]?.trim() === "}") {
40
- findings.push({
41
- line: lineNum,
42
- severity: "error",
43
- category: "exception-swallowing",
44
- message: `Empty catch block on line ${lineNum} \u2014 exceptions are being swallowed`,
45
- suggestion: "Handle the error: log it, throw a meaningful error, or add a comment explaining why it's safe to ignore"
46
- });
47
- }
48
- if (filePath?.includes(".test.") || filePath?.includes(".spec.")) {
49
- if (/\breturn\s+(true|false|null|undefined|0|""|'')\s*;/.test(line) || /\breturn\s+\d+\s*;/.test(line) || /\bPromise\.resolve\s*\(\s*(true|false|null|undefined|0|""|'')\s*\)/.test(line)) {
50
- findings.push({
51
- line: lineNum,
52
- severity: "warning",
53
- category: "reward-hacking",
54
- message: `Possible hardcoded return in test file on line ${lineNum}: ${line.trim().substring(0, 60)}`,
55
- suggestion: "Implement the actual logic instead of returning a hardcoded value to pass tests"
56
- });
57
- }
58
- }
59
- if (!filePath?.includes(".test.") && !filePath?.includes(".spec.") && /\breturn\s+(true|false|null)\s*;/.test(line) && ((lines[i - 1] || "").includes("TODO") || (lines[i - 1] || "").includes("FIXME") || (lines[i - 1] || "").includes("HACK"))) {
60
- findings.push({
61
- line: lineNum,
62
- severity: "warning",
63
- category: "hardcoded-return",
64
- message: `Hardcoded return value on line ${lineNum} (possibly temporary/hack)`,
65
- suggestion: "Implement proper logic or add a clear comment explaining why this is correct"
66
- });
67
- }
68
- const importMatch = line.match(/import\s+(?:\{[^}]*\}\s+from\s+)?['"]([^'"]+)['"]/);
69
- if (importMatch) {
70
- const imported = importMatch[1];
71
- if (whitelist.blocked.some((b) => imported.includes(b))) {
72
- findings.push({
73
- line: lineNum,
74
- severity: "error",
75
- category: "import-violation",
76
- message: `Blocked import: "${imported}" is not allowed per security whitelist`,
77
- suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
78
- });
79
- }
80
- }
81
- }
82
- return findings;
83
- }
84
- function validateFile(filePath) {
85
- try {
86
- const root = getProjectRoot();
87
- const fullPath = path.resolve(root, filePath);
88
- if (!fs.existsSync(fullPath)) return [];
89
- const content = fs.readFileSync(fullPath, "utf-8");
90
- return validateCodeContent(content, filePath);
91
- } catch {
92
- return [];
93
- }
94
- }
95
- function formatValidationFindings(findings, filePath) {
96
- if (findings.length === 0) {
97
- return "\u2705 **AST Validation** \u2014 No structural issues found.";
98
- }
99
- const errors = findings.filter((f) => f.severity === "error");
100
- const warnings = findings.filter((f) => f.severity === "warning");
101
- const lines = [
102
- "\u{1F52C} **AST Code Validation Report**",
103
- "\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
104
- "",
105
- `${findings.length} finding(s): ${errors.length} errors, ${warnings.length} warnings`,
106
- filePath ? `\u{1F4C4} ${filePath}` : "",
107
- ""
108
- ];
109
- for (const f of findings) {
110
- const icon = f.severity === "error" ? "\u{1F534}" : f.severity === "warning" ? "\u{1F7E1}" : "\u{1F7E2}";
111
- lines.push(`${icon} L${f.line} [${f.category}]`);
112
- lines.push(` ${f.message}`);
113
- lines.push(` \u{1F4A1} ${f.suggestion}`);
114
- lines.push("");
115
- }
116
- return lines.join("\n");
117
- }
118
- function validateDiff(oldContent, newContent, filePath) {
119
- const findings = [];
120
- const oldFindings = validateCodeContent(oldContent, filePath);
121
- const newFindings = validateCodeContent(newContent, filePath);
122
- const oldAssertions = oldFindings.filter((f) => f.category === "assertion-deletion");
123
- const newAssertions = newFindings.filter((f) => f.category === "assertion-deletion");
124
- for (const oldAssertion of oldAssertions) {
125
- if (!newAssertions.some((f) => f.line === oldAssertion.line && f.message === oldAssertion.message)) {
126
- continue;
127
- }
128
- }
129
- const oldLines = oldContent.split("\n");
130
- const newLines = newContent.split("\n");
131
- for (let i = 0; i < newLines.length; i++) {
132
- if (i >= oldLines.length || newLines[i] !== oldLines[i]) {
133
- const changedLineFindings = validateCodeContent(newLines[i], filePath);
134
- for (const f of changedLineFindings) {
135
- findings.push({
136
- ...f,
137
- line: i + 1,
138
- message: `[NEW] ${f.message}`
139
- });
140
- }
141
- }
142
- }
143
- return findings;
144
- }
145
- export {
146
- formatValidationFindings,
147
- validateCodeContent,
148
- validateDiff,
149
- validateFile
150
- };
@@ -1,207 +0,0 @@
1
- import {
2
- sessionMemory
3
- } from "./chunk-LVKOGXLC.js";
4
- import {
5
- getDb,
6
- resetDbInstance
7
- } from "./chunk-3BRBJZ7P.js";
8
- import {
9
- getProjectRoot
10
- } from "./chunk-E2KFPEBT.js";
11
-
12
- // src/engine/kumaCheckpoint.ts
13
- import fs from "fs";
14
- import path from "path";
15
- var CHECKPOINT_DIR = ".kuma/checkpoints";
16
- async function createCheckpoint(label, description) {
17
- try {
18
- const root = getProjectRoot();
19
- const cpDir = path.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
20
- if (fs.existsSync(cpDir)) {
21
- return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
22
- }
23
- fs.mkdirSync(cpDir, { recursive: true });
24
- const db = await getDb();
25
- const filesStmt = db.prepare(
26
- "SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
27
- );
28
- const files = [];
29
- while (filesStmt.step()) {
30
- const row = filesStmt.getAsObject();
31
- const fp = row.file_path;
32
- const fullPath = path.resolve(root, fp);
33
- if (fs.existsSync(fullPath)) {
34
- const content = fs.readFileSync(fullPath, "utf-8");
35
- const hash = simpleHash(content);
36
- const fileDir = path.dirname(path.join(cpDir, "files", fp));
37
- if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir, { recursive: true });
38
- fs.writeFileSync(path.join(cpDir, "files", fp), content, "utf-8");
39
- files.push({ path: fp, hash });
40
- }
41
- }
42
- filesStmt.free();
43
- const dbData = db.export();
44
- fs.writeFileSync(path.join(cpDir, "kuma.db"), Buffer.from(dbData));
45
- const manifest = {
46
- label,
47
- timestamp: Date.now(),
48
- files,
49
- dbSnapshot: true,
50
- description
51
- };
52
- fs.writeFileSync(
53
- path.join(cpDir, "manifest.json"),
54
- JSON.stringify(manifest, null, 2),
55
- "utf-8"
56
- );
57
- sessionMemory.recordToolCall("kuma_checkpoint_create", {
58
- label,
59
- filesCount: files.length
60
- });
61
- return [
62
- `\u2705 **Checkpoint Created**: "${label}"`,
63
- `\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
64
- ``,
65
- `\u{1F4E6} **${files.length} file(s)** snapshotted`,
66
- `\u{1F5C4}\uFE0F **Database** snapshotted`,
67
- `\u{1F4CD} ${cpDir}`,
68
- ``,
69
- `\u{1F4A1} To restore: kuma_safety({ action: 'rollback', label: '${label}' })`,
70
- description ? `\u{1F4DD} ${description}` : ""
71
- ].filter(Boolean).join("\n");
72
- } catch (err) {
73
- return `\u274C Checkpoint failed: ${err}`;
74
- }
75
- }
76
- async function rollbackToCheckpoint(label) {
77
- try {
78
- const root = getProjectRoot();
79
- const cpDir = path.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
80
- const manifestPath = path.join(cpDir, "manifest.json");
81
- if (!fs.existsSync(manifestPath)) {
82
- return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
83
- }
84
- const manifest = JSON.parse(
85
- fs.readFileSync(manifestPath, "utf-8")
86
- );
87
- let restored = 0;
88
- let failed = 0;
89
- for (const f of manifest.files) {
90
- const snapshotPath = path.join(cpDir, "files", f.path);
91
- if (fs.existsSync(snapshotPath)) {
92
- try {
93
- const content = fs.readFileSync(snapshotPath, "utf-8");
94
- const targetPath = path.resolve(root, f.path);
95
- const targetDir = path.dirname(targetPath);
96
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
97
- fs.writeFileSync(targetPath, content, "utf-8");
98
- restored++;
99
- } catch {
100
- failed++;
101
- }
102
- }
103
- }
104
- const dbSnapshotPath = path.join(cpDir, "kuma.db");
105
- if (fs.existsSync(dbSnapshotPath)) {
106
- const kumaDir = path.join(root, ".kuma");
107
- const dbPath = path.join(kumaDir, "kuma.db");
108
- const snapshotData = fs.readFileSync(dbSnapshotPath);
109
- fs.writeFileSync(dbPath, snapshotData);
110
- resetDbInstance();
111
- }
112
- sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
113
- label,
114
- restored,
115
- failed
116
- });
117
- return [
118
- `\u{1F504} **Rollback Complete**: "${label}"`,
119
- `\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\u2501\u2501\u2501\u2501\u2501\u2501`,
120
- ``,
121
- `\u2705 Restored **${restored} file(s)**`,
122
- failed > 0 ? `\u26A0\uFE0F ${failed} file(s) failed to restore` : "",
123
- `\u{1F5C4}\uFE0F Database restored to checkpoint state`,
124
- ``,
125
- `\u{1F4A1} You may need to restart your language server for full effect.`
126
- ].filter(Boolean).join("\n");
127
- } catch (err) {
128
- return `\u274C Rollback failed: ${err}`;
129
- }
130
- }
131
- function listCheckpoints() {
132
- try {
133
- const root = getProjectRoot();
134
- const cpDir = path.join(root, CHECKPOINT_DIR);
135
- if (!fs.existsSync(cpDir)) {
136
- return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
137
- }
138
- const entries = fs.readdirSync(cpDir);
139
- const checkpoints = [];
140
- for (const entry of entries) {
141
- const manifestPath = path.join(cpDir, entry, "manifest.json");
142
- if (fs.existsSync(manifestPath)) {
143
- try {
144
- const manifest = JSON.parse(
145
- fs.readFileSync(manifestPath, "utf-8")
146
- );
147
- checkpoints.push({ label: entry, manifest });
148
- } catch {
149
- }
150
- }
151
- }
152
- if (checkpoints.length === 0) {
153
- return "\u{1F4ED} No valid checkpoints found.";
154
- }
155
- const lines = [
156
- "\u{1F4E6} **Checkpoints**",
157
- "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
158
- ""
159
- ];
160
- for (const cp of checkpoints) {
161
- const time = new Date(cp.manifest.timestamp).toLocaleString();
162
- lines.push(` \u{1F4CC} **${cp.manifest.label}**`);
163
- lines.push(` \u{1F550} ${time} | \u{1F4C1} ${cp.manifest.files.length} files | \u{1F4BE} ${cp.manifest.dbSnapshot ? "DB included" : "no DB"}`);
164
- if (cp.manifest.description) {
165
- lines.push(` \u{1F4DD} ${cp.manifest.description}`);
166
- }
167
- lines.push("");
168
- }
169
- return lines.join("\n");
170
- } catch (err) {
171
- return `Error: ${err}`;
172
- }
173
- }
174
- function pruneCheckpoints(keep = 5) {
175
- try {
176
- const root = getProjectRoot();
177
- const cpDir = path.join(root, CHECKPOINT_DIR);
178
- if (!fs.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
179
- const entries = fs.readdirSync(cpDir).map((e) => ({ name: e, time: fs.statSync(path.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
180
- let removed = 0;
181
- for (let i = keep; i < entries.length; i++) {
182
- fs.rmSync(path.join(cpDir, entries[i].name), { recursive: true, force: true });
183
- removed++;
184
- }
185
- return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
186
- } catch (err) {
187
- return `Error: ${err}`;
188
- }
189
- }
190
- function sanitizeLabel(label) {
191
- return label.replace(/[^a-zA-Z0-9_-]/g, "_").substring(0, 64);
192
- }
193
- function simpleHash(str) {
194
- let hash = 0;
195
- for (let i = 0; i < str.length; i++) {
196
- const char = str.charCodeAt(i);
197
- hash = (hash << 5) - hash + char;
198
- hash = hash & hash;
199
- }
200
- return Math.abs(hash).toString(36);
201
- }
202
- export {
203
- createCheckpoint,
204
- listCheckpoints,
205
- pruneCheckpoints,
206
- rollbackToCheckpoint
207
- };