@plumpslabs/kuma 2.4.1 → 2.4.3
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 +14 -31
- package/dist/index.js +479 -2210
- package/package.json +1 -1
- package/packages/ide/studio/dist/index.js +2 -2
- package/packages/ide/studio/public/index.html +3 -1
package/dist/index.js
CHANGED
|
@@ -234,11 +234,9 @@ __export(kumaDb_exports, {
|
|
|
234
234
|
getResearchCache: () => getResearchCache,
|
|
235
235
|
getSecurityFindings: () => getSecurityFindings,
|
|
236
236
|
listContextNotes: () => listContextNotes,
|
|
237
|
-
listDecisionLog: () => listDecisionLog,
|
|
238
237
|
listResearchCache: () => listResearchCache,
|
|
239
238
|
rebuildFtsIndex: () => rebuildFtsIndex,
|
|
240
239
|
recordChange: () => recordChange,
|
|
241
|
-
recordDecisionLog: () => recordDecisionLog,
|
|
242
240
|
resetDbInstance: () => resetDbInstance,
|
|
243
241
|
rollbackChange: () => rollbackChange,
|
|
244
242
|
runDoctor: () => runDoctor,
|
|
@@ -247,8 +245,7 @@ __export(kumaDb_exports, {
|
|
|
247
245
|
saveDb: () => saveDb,
|
|
248
246
|
saveFileSummary: () => saveFileSummary,
|
|
249
247
|
saveResearchCache: () => saveResearchCache,
|
|
250
|
-
saveVerification: () => saveVerification
|
|
251
|
-
updateDecisionStatus: () => updateDecisionStatus
|
|
248
|
+
saveVerification: () => saveVerification
|
|
252
249
|
});
|
|
253
250
|
import initSqlJs from "sql.js";
|
|
254
251
|
import fs2 from "fs";
|
|
@@ -516,17 +513,10 @@ function createSchema(db) {
|
|
|
516
513
|
comparison_with TEXT,
|
|
517
514
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
518
515
|
)`);
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
rationale TEXT,
|
|
524
|
-
outcome TEXT,
|
|
525
|
-
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','superseded','deprecated','proposed')),
|
|
526
|
-
superseded_by INTEGER,
|
|
527
|
-
file_paths TEXT DEFAULT '[]',
|
|
528
|
-
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
529
|
-
)`);
|
|
516
|
+
try {
|
|
517
|
+
db.exec(`DROP TABLE IF EXISTS decision_log`);
|
|
518
|
+
} catch {
|
|
519
|
+
}
|
|
530
520
|
db.run(`CREATE TABLE IF NOT EXISTS file_summaries (
|
|
531
521
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
532
522
|
file_path TEXT NOT NULL UNIQUE,
|
|
@@ -555,12 +545,6 @@ function createSchema(db) {
|
|
|
555
545
|
escalated INTEGER DEFAULT 0,
|
|
556
546
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
557
547
|
)`);
|
|
558
|
-
db.run(`CREATE TABLE IF NOT EXISTS scratch_entries (
|
|
559
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
560
|
-
file_path TEXT NOT NULL,
|
|
561
|
-
reason TEXT,
|
|
562
|
-
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
563
|
-
)`);
|
|
564
548
|
db.run(`CREATE TABLE IF NOT EXISTS verifications (
|
|
565
549
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
566
550
|
scope TEXT NOT NULL,
|
|
@@ -696,7 +680,6 @@ function createSchema(db) {
|
|
|
696
680
|
db.run(`CREATE INDEX IF NOT EXISTS idx_api_path ON api_endpoints(path)`);
|
|
697
681
|
db.run(`CREATE INDEX IF NOT EXISTS idx_context_notes_scope ON context_notes(scope)`);
|
|
698
682
|
db.run(`CREATE INDEX IF NOT EXISTS idx_benchmarks_label ON benchmarks(label)`);
|
|
699
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_decision_status ON decision_log(status)`);
|
|
700
683
|
db.run(`CREATE INDEX IF NOT EXISTS idx_file_summaries_path ON file_summaries(file_path)`);
|
|
701
684
|
db.run(`CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON safety_audit(timestamp)`);
|
|
702
685
|
db.run(`CREATE INDEX IF NOT EXISTS idx_audit_tool ON safety_audit(tool_name)`);
|
|
@@ -989,57 +972,6 @@ async function getBenchmarkDiff(labelA, labelB) {
|
|
|
989
972
|
return `Error: ${err}`;
|
|
990
973
|
}
|
|
991
974
|
}
|
|
992
|
-
async function recordDecisionLog(entry) {
|
|
993
|
-
try {
|
|
994
|
-
const db = await getDb();
|
|
995
|
-
db.run(
|
|
996
|
-
`INSERT INTO decision_log (title, context, rationale, outcome, status) VALUES (?, ?, ?, ?, ?)`,
|
|
997
|
-
[entry.title, entry.context || "", entry.rationale || "", entry.outcome || "", entry.status || "active"]
|
|
998
|
-
);
|
|
999
|
-
saveDb();
|
|
1000
|
-
return `\u2705 Decision "${entry.title}" logged as ${entry.status || "active"}.`;
|
|
1001
|
-
} catch (err) {
|
|
1002
|
-
return `\u274C Failed: ${err}`;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
async function listDecisionLog(status) {
|
|
1006
|
-
try {
|
|
1007
|
-
const db = await getDb();
|
|
1008
|
-
let sql = `SELECT * FROM decision_log WHERE 1=1`;
|
|
1009
|
-
const bind = [];
|
|
1010
|
-
if (status) {
|
|
1011
|
-
sql += ` AND status = ?`;
|
|
1012
|
-
bind.push(status);
|
|
1013
|
-
}
|
|
1014
|
-
sql += ` ORDER BY created_at DESC LIMIT 50`;
|
|
1015
|
-
const stmt = db.prepare(sql);
|
|
1016
|
-
stmt.bind(bind);
|
|
1017
|
-
const results = [];
|
|
1018
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
1019
|
-
stmt.free();
|
|
1020
|
-
if (results.length === 0) return "\u{1F4CB} No decisions logged.";
|
|
1021
|
-
const lines = [`\u{1F4CB} Decision Log \u2014 ${results.length}
|
|
1022
|
-
`];
|
|
1023
|
-
for (const d of results) {
|
|
1024
|
-
const icon = d.status === "active" ? "\u2705" : d.status === "superseded" ? "\u{1F504}" : d.status === "deprecated" ? "\u274C" : "\u{1F4DD}";
|
|
1025
|
-
lines.push(` ${icon} ${d.title} (${d.status})`);
|
|
1026
|
-
if (d.context) lines.push(` Context: ${d.context.substring(0, 150)}`);
|
|
1027
|
-
}
|
|
1028
|
-
return lines.join("\n");
|
|
1029
|
-
} catch (err) {
|
|
1030
|
-
return `Error: ${err}`;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
async function updateDecisionStatus(id, status) {
|
|
1034
|
-
try {
|
|
1035
|
-
const db = await getDb();
|
|
1036
|
-
db.run(`UPDATE decision_log SET status = ? WHERE id = ?`, [status, id]);
|
|
1037
|
-
saveDb();
|
|
1038
|
-
return `\u2705 Decision #${id} updated to "${status}"`;
|
|
1039
|
-
} catch (err) {
|
|
1040
|
-
return `\u274C Failed: ${err}`;
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
975
|
async function getFileSummary(filePath) {
|
|
1044
976
|
try {
|
|
1045
977
|
const db = await getDb();
|
|
@@ -1081,7 +1013,7 @@ async function runGarbageCollection() {
|
|
|
1081
1013
|
};
|
|
1082
1014
|
const orphanResult = db.exec(`DELETE FROM nodes WHERE id NOT IN (SELECT source_id FROM edges UNION SELECT target_id FROM edges) AND file_path IS NULL AND updated_at < strftime('%s','now','-30 days')`);
|
|
1083
1015
|
removed += orphanResult[0]?.values?.length || 0;
|
|
1084
|
-
const staleEdges = db.exec(`DELETE FROM edges WHERE weight < 0.1 AND
|
|
1016
|
+
const staleEdges = db.exec(`DELETE FROM edges WHERE weight < 0.1 AND created_at < strftime('%s','now','-30 days')`);
|
|
1085
1017
|
removed += staleEdges[0]?.values?.length || 0;
|
|
1086
1018
|
db.exec(`DELETE FROM tool_calls WHERE created_at < strftime('%s','now','-90 days')`);
|
|
1087
1019
|
db.exec(`DELETE FROM experiences WHERE created_at < strftime('%s','now','-90 days')`);
|
|
@@ -1095,7 +1027,7 @@ async function runGarbageCollection() {
|
|
|
1095
1027
|
}
|
|
1096
1028
|
const auditCount = getCount("SELECT COUNT(*) FROM safety_audit");
|
|
1097
1029
|
if (auditCount > 200) {
|
|
1098
|
-
db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY
|
|
1030
|
+
db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY timestamp ASC LIMIT ${auditCount - 200})`);
|
|
1099
1031
|
}
|
|
1100
1032
|
db.exec(`DELETE FROM research_cache WHERE updated_at < strftime('%s','now','-60 days')`);
|
|
1101
1033
|
const secCount = getCount("SELECT COUNT(*) FROM security_findings");
|
|
@@ -1110,12 +1042,7 @@ async function runGarbageCollection() {
|
|
|
1110
1042
|
if (benchCount > 100) {
|
|
1111
1043
|
db.exec(`DELETE FROM benchmarks WHERE id IN (SELECT id FROM benchmarks ORDER BY created_at ASC LIMIT ${benchCount - 100})`);
|
|
1112
1044
|
}
|
|
1113
|
-
const decCount = getCount("SELECT COUNT(*) FROM decision_log");
|
|
1114
|
-
if (decCount > 200) {
|
|
1115
|
-
db.exec(`DELETE FROM decision_log WHERE id IN (SELECT id FROM decision_log ORDER BY created_at ASC LIMIT ${decCount - 200})`);
|
|
1116
|
-
}
|
|
1117
1045
|
db.exec(`DELETE FROM known_gotchas WHERE file_path NOT IN (SELECT DISTINCT file_path FROM nodes WHERE file_path IS NOT NULL) AND file_path NOT LIKE '%::%'`);
|
|
1118
|
-
db.exec(`DELETE FROM scratch_entries WHERE created_at < strftime('%s','now','-7 days')`);
|
|
1119
1046
|
const costCount = getCount("SELECT COUNT(*) FROM cost_tracking");
|
|
1120
1047
|
if (costCount > 500) {
|
|
1121
1048
|
db.exec(`DELETE FROM cost_tracking WHERE id IN (SELECT id FROM cost_tracking ORDER BY created_at ASC LIMIT ${costCount - 500})`);
|
|
@@ -1149,7 +1076,7 @@ async function runDoctor() {
|
|
|
1149
1076
|
} catch {
|
|
1150
1077
|
checks.push("**Database Integrity**: \u274C check failed");
|
|
1151
1078
|
}
|
|
1152
|
-
const allTables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "
|
|
1079
|
+
const allTables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "file_summaries", "verifications", "tool_calls", "experiences", "experience_patterns", "patterns", "api_endpoints", "otel_config", "cost_tracking", "portability_entries", "blackboard_events", "known_gotchas", "trajectories", "distilled_skills"];
|
|
1153
1080
|
let existing = 0;
|
|
1154
1081
|
for (const t of allTables) {
|
|
1155
1082
|
try {
|
|
@@ -1173,8 +1100,8 @@ async function runDoctor() {
|
|
|
1173
1100
|
} catch {
|
|
1174
1101
|
}
|
|
1175
1102
|
try {
|
|
1176
|
-
const { execSync:
|
|
1177
|
-
const psOutput =
|
|
1103
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
1104
|
+
const psOutput = execSync6(
|
|
1178
1105
|
`ps -eo pid,ppid,command | grep -E "(jest|pnpm test|npm test|yarn test)" | grep -v grep | head -10`,
|
|
1179
1106
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
1180
1107
|
).trim();
|
|
@@ -1185,7 +1112,7 @@ async function runDoctor() {
|
|
|
1185
1112
|
for (const p of processes) {
|
|
1186
1113
|
checks.push(` \u26A1 ${p.substring(0, 120)}`);
|
|
1187
1114
|
}
|
|
1188
|
-
checks.push(" \u{1F4A1}
|
|
1115
|
+
checks.push(" \u{1F4A1} Stale test processes may slow down test runs.");
|
|
1189
1116
|
} else {
|
|
1190
1117
|
checks.push("");
|
|
1191
1118
|
checks.push("**Running Test Processes**: \u2705 None detected");
|
|
@@ -2106,12 +2033,6 @@ No unresolved issues.`;
|
|
|
2106
2033
|
});
|
|
2107
2034
|
|
|
2108
2035
|
// src/engine/kumaSelfHeal.ts
|
|
2109
|
-
var kumaSelfHeal_exports = {};
|
|
2110
|
-
__export(kumaSelfHeal_exports, {
|
|
2111
|
-
formatGotchaStalenessReport: () => formatGotchaStalenessReport,
|
|
2112
|
-
healOnQuery: () => healOnQuery,
|
|
2113
|
-
verifyGotchaStaleness: () => verifyGotchaStaleness
|
|
2114
|
-
});
|
|
2115
2036
|
import { execSync as execSync2 } from "child_process";
|
|
2116
2037
|
import fs4 from "fs";
|
|
2117
2038
|
import path4 from "path";
|
|
@@ -2172,73 +2093,6 @@ async function healOnQuery(filePaths) {
|
|
|
2172
2093
|
return { healed: 0 };
|
|
2173
2094
|
}
|
|
2174
2095
|
}
|
|
2175
|
-
async function verifyGotchaStaleness() {
|
|
2176
|
-
const db = await getDb();
|
|
2177
|
-
const stale = [];
|
|
2178
|
-
try {
|
|
2179
|
-
const rows = db.exec(`
|
|
2180
|
-
SELECT id, metadata FROM nodes WHERE type = 'gotcha'
|
|
2181
|
-
`);
|
|
2182
|
-
if (rows.length === 0) return stale;
|
|
2183
|
-
const columns = rows[0].columns;
|
|
2184
|
-
const filePathIdx = columns.indexOf("metadata");
|
|
2185
|
-
for (const row of rows[0].values) {
|
|
2186
|
-
const nodeId2 = row[0];
|
|
2187
|
-
let metadata = {};
|
|
2188
|
-
try {
|
|
2189
|
-
metadata = JSON.parse(row[filePathIdx]);
|
|
2190
|
-
} catch {
|
|
2191
|
-
}
|
|
2192
|
-
const filePath = metadata.file_path;
|
|
2193
|
-
if (!filePath || filePath.startsWith("search::") || filePath.startsWith("api_route::")) continue;
|
|
2194
|
-
const fullPath = path4.join(getProjectRoot(), filePath);
|
|
2195
|
-
if (!fs4.existsSync(fullPath)) {
|
|
2196
|
-
stale.push({ gotchaId: nodeId2, file_path: filePath, issue: "file_missing" });
|
|
2197
|
-
} else {
|
|
2198
|
-
const desc = metadata.description || "";
|
|
2199
|
-
const symbolMatch = desc.match(/\b(function|class|method|const|let|var|export)\s+(\w+)/i);
|
|
2200
|
-
if (symbolMatch) {
|
|
2201
|
-
const symbolName = symbolMatch[2];
|
|
2202
|
-
try {
|
|
2203
|
-
const content = fs4.readFileSync(fullPath, "utf-8");
|
|
2204
|
-
if (!content.includes(symbolName)) {
|
|
2205
|
-
stale.push({ gotchaId: nodeId2, file_path: filePath, issue: "symbol_missing" });
|
|
2206
|
-
}
|
|
2207
|
-
} catch {
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
} catch {
|
|
2213
|
-
}
|
|
2214
|
-
return stale;
|
|
2215
|
-
}
|
|
2216
|
-
function formatGotchaStalenessReport(stale) {
|
|
2217
|
-
if (stale.length === 0) {
|
|
2218
|
-
return "\u2705 **Gotcha Staleness Check** \u2014 All gotcha references are valid.";
|
|
2219
|
-
}
|
|
2220
|
-
const fileMissing = stale.filter((s) => s.issue === "file_missing");
|
|
2221
|
-
const symbolMissing = stale.filter((s) => s.issue === "symbol_missing");
|
|
2222
|
-
const lines = [
|
|
2223
|
-
`\u26A0\uFE0F **Gotcha Staleness Report**`,
|
|
2224
|
-
`\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`,
|
|
2225
|
-
"",
|
|
2226
|
-
`\u{1F4CA} ${stale.length} stale gotcha${stale.length > 1 ? "s" : ""} found`,
|
|
2227
|
-
fileMissing.length > 0 ? ` \u{1F5D1}\uFE0F ${fileMissing.length} reference${fileMissing.length > 1 ? "s" : ""} deleted file` : "",
|
|
2228
|
-
symbolMissing.length > 0 ? ` \u{1F50D} ${symbolMissing.length} reference${symbolMissing.length > 1 ? "s" : ""} missing symbol` : "",
|
|
2229
|
-
""
|
|
2230
|
-
];
|
|
2231
|
-
for (const s of fileMissing.slice(0, 5)) {
|
|
2232
|
-
lines.push(` \u{1F5D1}\uFE0F \`${s.gotchaId}\` \u2192 file missing: \`${s.file_path}\``);
|
|
2233
|
-
}
|
|
2234
|
-
for (const s of symbolMissing.slice(0, 5)) {
|
|
2235
|
-
lines.push(` \u{1F50D} \`${s.gotchaId}\` \u2192 symbol missing in \`${s.file_path}\``);
|
|
2236
|
-
}
|
|
2237
|
-
if (stale.length > 5) lines.push(` ... and ${stale.length - 5} more`);
|
|
2238
|
-
lines.push("");
|
|
2239
|
-
lines.push("\u{1F4A1} Use `kuma_memory({ action: 'delete_node', target: '<gotchaId>' })` to remove obsolete gotchas.");
|
|
2240
|
-
return lines.join("\n");
|
|
2241
|
-
}
|
|
2242
2096
|
var init_kumaSelfHeal = __esm({
|
|
2243
2097
|
"src/engine/kumaSelfHeal.ts"() {
|
|
2244
2098
|
"use strict";
|
|
@@ -2517,39 +2371,6 @@ async function detectDrift() {
|
|
|
2517
2371
|
}
|
|
2518
2372
|
}
|
|
2519
2373
|
fsStmt.free();
|
|
2520
|
-
const dlStmt = db.prepare(
|
|
2521
|
-
"SELECT id, title, file_paths, created_at FROM decision_log WHERE file_paths IS NOT NULL ORDER BY created_at DESC LIMIT 50"
|
|
2522
|
-
);
|
|
2523
|
-
while (dlStmt.step()) {
|
|
2524
|
-
const row = dlStmt.getAsObject();
|
|
2525
|
-
const filePaths = [];
|
|
2526
|
-
try {
|
|
2527
|
-
filePaths.push(...JSON.parse(row.file_paths));
|
|
2528
|
-
} catch {
|
|
2529
|
-
}
|
|
2530
|
-
let hasStaleFile = false;
|
|
2531
|
-
for (const fp of filePaths) {
|
|
2532
|
-
if (!fp) continue;
|
|
2533
|
-
const fullPath = path6.resolve(getProjectRoot(), fp);
|
|
2534
|
-
if (!fs5.existsSync(fullPath)) {
|
|
2535
|
-
hasStaleFile = true;
|
|
2536
|
-
break;
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
if (hasStaleFile) {
|
|
2540
|
-
records.push({
|
|
2541
|
-
id: row.id,
|
|
2542
|
-
source: "decision_log",
|
|
2543
|
-
description: row.title,
|
|
2544
|
-
filePath: filePaths[0] || null,
|
|
2545
|
-
oldHash: "",
|
|
2546
|
-
currentHash: "",
|
|
2547
|
-
age: "\u2014",
|
|
2548
|
-
severity: "stale"
|
|
2549
|
-
});
|
|
2550
|
-
}
|
|
2551
|
-
}
|
|
2552
|
-
dlStmt.free();
|
|
2553
2374
|
} catch (err) {
|
|
2554
2375
|
console.error(`[DriftDetector] Error: ${err}`);
|
|
2555
2376
|
}
|
|
@@ -2612,12 +2433,6 @@ async function flagStaleRecords() {
|
|
|
2612
2433
|
[record.id]
|
|
2613
2434
|
);
|
|
2614
2435
|
flagged++;
|
|
2615
|
-
} else if (record.source === "decision_log") {
|
|
2616
|
-
db.run(
|
|
2617
|
-
`UPDATE decision_log SET status = 'deprecated' WHERE id = ?`,
|
|
2618
|
-
[record.id]
|
|
2619
|
-
);
|
|
2620
|
-
flagged++;
|
|
2621
2436
|
}
|
|
2622
2437
|
}
|
|
2623
2438
|
saveDb();
|
|
@@ -3327,7 +3142,7 @@ Try kuma_context({ action: 'research', scope: '<term>' }) to build related graph
|
|
|
3327
3142
|
const parts = query.split("\u2192").map((s) => s.trim());
|
|
3328
3143
|
if (parts.length !== 2) {
|
|
3329
3144
|
return `\u26A0\uFE0F For path queries, use format: "sourceNodeID \u2192 targetNodeID"
|
|
3330
|
-
Example: kuma_context({ action: '
|
|
3145
|
+
Example: kuma_context({ action: 'research', scope: 'login' }) \u2192 traces connections between nodes.`;
|
|
3331
3146
|
}
|
|
3332
3147
|
const [sourceId, targetId] = parts;
|
|
3333
3148
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -3567,27 +3382,27 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
3567
3382
|
if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
|
|
3568
3383
|
let entries = [];
|
|
3569
3384
|
try {
|
|
3570
|
-
entries =
|
|
3385
|
+
entries = fs24.readdirSync(dir);
|
|
3571
3386
|
} catch {
|
|
3572
3387
|
return;
|
|
3573
3388
|
}
|
|
3574
3389
|
for (const entry of entries) {
|
|
3575
3390
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
|
|
3576
|
-
const full =
|
|
3391
|
+
const full = path25.join(dir, entry);
|
|
3577
3392
|
try {
|
|
3578
|
-
const stat =
|
|
3393
|
+
const stat = fs24.statSync(full);
|
|
3579
3394
|
if (stat.isDirectory()) {
|
|
3580
3395
|
walk2(full, depth + 1);
|
|
3581
3396
|
} else if (entry.toLowerCase().includes(query.toLowerCase())) {
|
|
3582
|
-
results.push(
|
|
3397
|
+
results.push(path25.relative(root, full));
|
|
3583
3398
|
}
|
|
3584
3399
|
} catch {
|
|
3585
3400
|
}
|
|
3586
3401
|
}
|
|
3587
3402
|
};
|
|
3588
3403
|
var walk = walk2;
|
|
3589
|
-
const
|
|
3590
|
-
const
|
|
3404
|
+
const fs24 = await import("fs");
|
|
3405
|
+
const path25 = await import("path");
|
|
3591
3406
|
const root = process.cwd();
|
|
3592
3407
|
const results = [];
|
|
3593
3408
|
const MAX_WALK_DEPTH = 8;
|
|
@@ -3727,9 +3542,9 @@ async function analyzeImpact(target) {
|
|
|
3727
3542
|
}
|
|
3728
3543
|
async function codebaseImpactFallback(target) {
|
|
3729
3544
|
try {
|
|
3730
|
-
const { execSync:
|
|
3545
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
3731
3546
|
const root = process.cwd();
|
|
3732
|
-
const grepResult =
|
|
3547
|
+
const grepResult = execSync6(
|
|
3733
3548
|
`grep -rn ${grepIncludeFlags()} --include="*.json" --include="*.md" -l "${target.replace(/"/g, '\\"')}" . 2>/dev/null | grep -v node_modules | grep -v .git | grep -v dist | grep -v .kuma | head -50`,
|
|
3734
3549
|
{ cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
|
|
3735
3550
|
).trim();
|
|
@@ -4142,9 +3957,7 @@ var domainRules_exports = {};
|
|
|
4142
3957
|
__export(domainRules_exports, {
|
|
4143
3958
|
appendToLayer: () => appendToLayer,
|
|
4144
3959
|
checkFileGotchas: () => checkFileGotchas,
|
|
4145
|
-
generateDigest: () => generateDigest,
|
|
4146
3960
|
getActiveGotchas: () => getActiveGotchas,
|
|
4147
|
-
getLayersSummary: () => getLayersSummary,
|
|
4148
3961
|
readLayer: () => readLayer,
|
|
4149
3962
|
writeLayer: () => writeLayer
|
|
4150
3963
|
});
|
|
@@ -4156,7 +3969,7 @@ function layerFilePath(layer) {
|
|
|
4156
3969
|
function ensureLayerFile(layer) {
|
|
4157
3970
|
const fp = layerFilePath(layer);
|
|
4158
3971
|
if (!fs7.existsSync(fp)) {
|
|
4159
|
-
const template = layer === "
|
|
3972
|
+
const template = layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
|
|
4160
3973
|
const dir = path8.dirname(fp);
|
|
4161
3974
|
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
4162
3975
|
fs7.writeFileSync(fp, template, "utf-8");
|
|
@@ -4194,27 +4007,6 @@ function appendToLayer(layer, entry) {
|
|
|
4194
4007
|
return `\u274C Failed to append: ${err}`;
|
|
4195
4008
|
}
|
|
4196
4009
|
}
|
|
4197
|
-
function getLayersSummary() {
|
|
4198
|
-
const lines = ["\u{1F4DA} **3-Layer Memory Engine**", "\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"];
|
|
4199
|
-
for (const layer of ["domain_rules", "arch_flow", "gotcha"]) {
|
|
4200
|
-
const fp = layerFilePath(layer);
|
|
4201
|
-
const exists = fs7.existsSync(fp);
|
|
4202
|
-
const label = LAYER_FILES[layer].label;
|
|
4203
|
-
if (exists) {
|
|
4204
|
-
try {
|
|
4205
|
-
const content = fs7.readFileSync(fp, "utf-8");
|
|
4206
|
-
const lineCount = content.split("\n").length;
|
|
4207
|
-
const sizeKB = Math.round(Buffer.byteLength(content) / 1024);
|
|
4208
|
-
lines.push(` ${layer === "domain_rules" ? "\u{1F4CB}" : layer === "arch_flow" ? "\u{1F3D7}\uFE0F" : "\u26A0\uFE0F"} **${label}** \u2014 ${lineCount} lines, ${sizeKB}KB`);
|
|
4209
|
-
} catch {
|
|
4210
|
-
lines.push(` \u2753 **${label}** \u2014 could not read`);
|
|
4211
|
-
}
|
|
4212
|
-
} else {
|
|
4213
|
-
lines.push(` \u{1F4C4} **${label}** \u2014 not yet created`);
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
return lines.join("\n");
|
|
4217
|
-
}
|
|
4218
4010
|
function getActiveGotchas() {
|
|
4219
4011
|
const results = [];
|
|
4220
4012
|
try {
|
|
@@ -4259,106 +4051,19 @@ function checkFileGotchas(filePath) {
|
|
|
4259
4051
|
}
|
|
4260
4052
|
return warnings;
|
|
4261
4053
|
}
|
|
4262
|
-
|
|
4263
|
-
const lines = [
|
|
4264
|
-
"\u{1F4CB} **Kuma Project Digest**",
|
|
4265
|
-
"\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",
|
|
4266
|
-
""
|
|
4267
|
-
];
|
|
4268
|
-
try {
|
|
4269
|
-
const fp1 = ensureLayerFile("domain_rules");
|
|
4270
|
-
const content = fs7.readFileSync(fp1, "utf-8");
|
|
4271
|
-
const rules = [];
|
|
4272
|
-
const workflows = [];
|
|
4273
|
-
for (const line of content.split("\n")) {
|
|
4274
|
-
const r = line.match(/^- Rule \d+:?\s*(.+)/);
|
|
4275
|
-
if (r) rules.push(r[1].substring(0, 60));
|
|
4276
|
-
const w = line.match(/^\d+\.\s*(.+)/);
|
|
4277
|
-
if (w) workflows.push(w[1].substring(0, 60));
|
|
4278
|
-
}
|
|
4279
|
-
if (rules.length > 0) lines.push(`\u{1F4CB} **Domain Rules** (${rules.length}): ${rules.slice(0, 3).join("; ")}`);
|
|
4280
|
-
if (workflows.length > 0) lines.push(`\u{1F504} **Workflows**: ${workflows.slice(0, 3).join(" \u2192 ")}`);
|
|
4281
|
-
} catch {
|
|
4282
|
-
lines.push("\u{1F4CB} **Domain Rules**: not set");
|
|
4283
|
-
}
|
|
4284
|
-
try {
|
|
4285
|
-
const fp2 = ensureLayerFile("arch_flow");
|
|
4286
|
-
const content = fs7.readFileSync(fp2, "utf-8");
|
|
4287
|
-
const chains = [];
|
|
4288
|
-
for (const line of content.split("\n")) {
|
|
4289
|
-
if (line.includes("->") && line.includes("[")) chains.push(line.trim());
|
|
4290
|
-
}
|
|
4291
|
-
if (chains.length > 0) lines.push(`\u{1F3D7}\uFE0F **Architecture**: ${chains[0].substring(0, 80)}`);
|
|
4292
|
-
else lines.push("\u{1F3D7}\uFE0F **Architecture**: see .kuma/ARCHITECTURE_FLOW.md");
|
|
4293
|
-
} catch {
|
|
4294
|
-
lines.push("\u{1F3D7}\uFE0F **Architecture**: not documented");
|
|
4295
|
-
}
|
|
4296
|
-
const gotchas = getActiveGotchas();
|
|
4297
|
-
if (gotchas.length > 0) {
|
|
4298
|
-
const highCount = gotchas.filter((g) => g.severity === "high" || g.severity === "critical").length;
|
|
4299
|
-
lines.push(`\u26A0\uFE0F **Gotchas**: ${gotchas.length} active (${highCount} high/critical)`);
|
|
4300
|
-
for (const g of gotchas.slice(0, 3)) {
|
|
4301
|
-
lines.push(` \u2022 ${g.filePath}: ${g.description.substring(0, 60)}`);
|
|
4302
|
-
}
|
|
4303
|
-
} else {
|
|
4304
|
-
lines.push("\u26A0\uFE0F **Gotchas**: none recorded");
|
|
4305
|
-
}
|
|
4306
|
-
const summary = sessionMemory.getSummary();
|
|
4307
|
-
lines.push(`\u{1F3AF} **Goal**: ${summary.currentGoal?.substring(0, 60) || "not set"}`);
|
|
4308
|
-
lines.push(`\u{1F4DD} **Modified**: ${summary.modifiedFiles?.length || 0} files | \u{1F6E0}\uFE0F ${summary.toolCallCount} calls`);
|
|
4309
|
-
return lines.join("\n");
|
|
4310
|
-
}
|
|
4311
|
-
var LAYER_FILES, DOMAIN_RULES_TEMPLATE, ARCH_FLOW_TEMPLATE, GOTCHAS_TEMPLATE;
|
|
4054
|
+
var LAYER_FILES, ARCH_FLOW_TEMPLATE, GOTCHAS_TEMPLATE;
|
|
4312
4055
|
var init_domainRules = __esm({
|
|
4313
4056
|
"src/engine/domainRules.ts"() {
|
|
4314
4057
|
"use strict";
|
|
4315
4058
|
init_pathValidator();
|
|
4316
4059
|
init_sessionMemory();
|
|
4317
4060
|
LAYER_FILES = {
|
|
4318
|
-
domain_rules: { filename: "DOMAIN_RULES.md", label: "Domain & Business Rules" },
|
|
4319
4061
|
arch_flow: { filename: "ARCHITECTURE_FLOW.md", label: "Architecture & Sequence Flow" },
|
|
4320
4062
|
gotcha: { filename: "KNOWN_GOTCHAS.md", label: "Known Code Gotchas" }
|
|
4321
4063
|
};
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
Auto-generated by Kuma 3-Layer Memory Engine.
|
|
4325
|
-
Update this file with core business logic, workflows, and boundary rules.
|
|
4326
|
-
|
|
4327
|
-
## Who
|
|
4328
|
-
<!-- Who are the users/actors? -->
|
|
4329
|
-
|
|
4330
|
-
## What
|
|
4331
|
-
<!-- What are the core features and business capabilities? -->
|
|
4332
|
-
|
|
4333
|
-
## When
|
|
4334
|
-
<!-- What are the time-based rules, schedules, deadlines? -->
|
|
4335
|
-
|
|
4336
|
-
## Where
|
|
4337
|
-
<!-- Where does the business operate (environments, regions)? -->
|
|
4338
|
-
|
|
4339
|
-
## Why
|
|
4340
|
-
<!-- Why were key architectural decisions made? -->
|
|
4341
|
-
|
|
4342
|
-
## How
|
|
4343
|
-
<!-- How do core workflows execute? -->
|
|
4344
|
-
|
|
4345
|
-
## Business Rules
|
|
4346
|
-
<!-- Critical business logic constraints -->
|
|
4347
|
-
- Rule 1:
|
|
4348
|
-
- Rule 2:
|
|
4064
|
+
ARCH_FLOW_TEMPLATE = `# Architecture & Sequence Flow Map
|
|
4349
4065
|
|
|
4350
|
-
|
|
4351
|
-
<!-- Ordered sequence of operations -->
|
|
4352
|
-
1.
|
|
4353
|
-
2.
|
|
4354
|
-
|
|
4355
|
-
## Boundary Conditions
|
|
4356
|
-
<!-- Edge cases, error states, validation rules -->
|
|
4357
|
-
-
|
|
4358
|
-
`;
|
|
4359
|
-
ARCH_FLOW_TEMPLATE = `# Architecture & Sequence Flow Map (Layer 2)
|
|
4360
|
-
|
|
4361
|
-
Auto-generated by Kuma 3-Layer Memory Engine.
|
|
4066
|
+
Auto-generated by Kuma Memory Engine.
|
|
4362
4067
|
Document execution call chains and architectural dependencies.
|
|
4363
4068
|
|
|
4364
4069
|
## System Overview
|
|
@@ -4391,9 +4096,9 @@ Request Flow:
|
|
|
4391
4096
|
<!-- External services, APIs, databases -->
|
|
4392
4097
|
-
|
|
4393
4098
|
`;
|
|
4394
|
-
GOTCHAS_TEMPLATE = `# Known Code Gotchas
|
|
4099
|
+
GOTCHAS_TEMPLATE = `# Known Code Gotchas
|
|
4395
4100
|
|
|
4396
|
-
Auto-generated by Kuma
|
|
4101
|
+
Auto-generated by Kuma Memory Engine.
|
|
4397
4102
|
Catalog of fragile legacy contracts, edge cases, and unexpected framework quirks.
|
|
4398
4103
|
|
|
4399
4104
|
## How to Add a Gotcha
|
|
@@ -5816,7 +5521,7 @@ function formatScanResult(result) {
|
|
|
5816
5521
|
lines.push(
|
|
5817
5522
|
"",
|
|
5818
5523
|
"\u{1F4A1} The knowledge graph now has richer code structure data.",
|
|
5819
|
-
"\u{1F4A1} Use kuma_context({ action: '
|
|
5524
|
+
"\u{1F4A1} Use kuma_context({ action: 'research', scope: '<area>' }) to see the graph overview."
|
|
5820
5525
|
);
|
|
5821
5526
|
return lines.join("\n");
|
|
5822
5527
|
}
|
|
@@ -5997,181 +5702,6 @@ var init_kumaFlowCache = __esm({
|
|
|
5997
5702
|
}
|
|
5998
5703
|
});
|
|
5999
5704
|
|
|
6000
|
-
// src/engine/contextDigest.ts
|
|
6001
|
-
var contextDigest_exports = {};
|
|
6002
|
-
__export(contextDigest_exports, {
|
|
6003
|
-
generateContextDigest: () => generateContextDigest,
|
|
6004
|
-
generateDigest: () => generateContextDigest
|
|
6005
|
-
});
|
|
6006
|
-
import fs14 from "fs";
|
|
6007
|
-
import path15 from "path";
|
|
6008
|
-
function detectTechStack() {
|
|
6009
|
-
const root = getProjectRoot();
|
|
6010
|
-
const stack = {
|
|
6011
|
-
languages: [],
|
|
6012
|
-
framework: null,
|
|
6013
|
-
database: null,
|
|
6014
|
-
testRunner: null,
|
|
6015
|
-
packageManager: null,
|
|
6016
|
-
monorepo: false
|
|
6017
|
-
};
|
|
6018
|
-
try {
|
|
6019
|
-
const pkgPath = path15.join(root, "package.json");
|
|
6020
|
-
if (fs14.existsSync(pkgPath)) {
|
|
6021
|
-
const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
|
|
6022
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6023
|
-
if (fs14.existsSync(path15.join(root, "pnpm-lock.yaml"))) stack.packageManager = "pnpm";
|
|
6024
|
-
else if (fs14.existsSync(path15.join(root, "yarn.lock"))) stack.packageManager = "yarn";
|
|
6025
|
-
else stack.packageManager = "npm";
|
|
6026
|
-
stack.monorepo = !!pkg.workspaces;
|
|
6027
|
-
if (deps.next) stack.framework = "Next.js";
|
|
6028
|
-
else if (deps.react) stack.framework = "React";
|
|
6029
|
-
else if (deps.vue) stack.framework = "Vue";
|
|
6030
|
-
else if (deps.express) stack.framework = "Express";
|
|
6031
|
-
else if (deps.fastify) stack.framework = "Fastify";
|
|
6032
|
-
else if (deps.nest) stack.framework = "NestJS";
|
|
6033
|
-
else if (deps["@remix-run/react"]) stack.framework = "Remix";
|
|
6034
|
-
else if (deps.svelte) stack.framework = "Svelte";
|
|
6035
|
-
else if (deps.angular) stack.framework = "Angular";
|
|
6036
|
-
if (deps.prisma) stack.database = "Prisma";
|
|
6037
|
-
else if (deps.typeorm) stack.database = "TypeORM";
|
|
6038
|
-
else if (deps.mongoose) stack.database = "MongoDB";
|
|
6039
|
-
else if (deps.pg || deps["@neondatabase/serverless"]) stack.database = "PostgreSQL";
|
|
6040
|
-
else if (deps.redis) stack.database = "Redis";
|
|
6041
|
-
else if (deps.better || deps.drizzle) stack.database = "Drizzle";
|
|
6042
|
-
if (deps.jest || deps["@jest/core"]) stack.testRunner = "Jest";
|
|
6043
|
-
else if (deps.vitest) stack.testRunner = "Vitest";
|
|
6044
|
-
else if (deps.mocha) stack.testRunner = "Mocha";
|
|
6045
|
-
else if (deps.playwright) stack.testRunner = "Playwright";
|
|
6046
|
-
else if (deps.cypress) stack.testRunner = "Cypress";
|
|
6047
|
-
if (fs14.existsSync(path15.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
|
|
6048
|
-
if (deps.typescript) stack.languages.push("TypeScript");
|
|
6049
|
-
if (fs14.existsSync(path15.join(root, "jsconfig.json"))) stack.languages.push("JavaScript");
|
|
6050
|
-
if (fs14.existsSync(path15.join(root, "go.mod"))) stack.languages.push("Go");
|
|
6051
|
-
if (fs14.existsSync(path15.join(root, "Cargo.toml"))) stack.languages.push("Rust");
|
|
6052
|
-
if (fs14.existsSync(path15.join(root, "pyproject.toml"))) stack.languages.push("Python");
|
|
6053
|
-
if (fs14.existsSync(path15.join(root, "Gemfile"))) stack.languages.push("Ruby");
|
|
6054
|
-
if (stack.languages.length === 0) stack.languages.push("JavaScript");
|
|
6055
|
-
}
|
|
6056
|
-
} catch {
|
|
6057
|
-
}
|
|
6058
|
-
return stack;
|
|
6059
|
-
}
|
|
6060
|
-
function detectEntryPoints() {
|
|
6061
|
-
const root = getProjectRoot();
|
|
6062
|
-
const entryPoints = [];
|
|
6063
|
-
const candidates = [
|
|
6064
|
-
"src/index.ts",
|
|
6065
|
-
"src/index.js",
|
|
6066
|
-
"src/app.ts",
|
|
6067
|
-
"src/app.js",
|
|
6068
|
-
"index.ts",
|
|
6069
|
-
"index.js",
|
|
6070
|
-
"src/main.ts",
|
|
6071
|
-
"src/main.js",
|
|
6072
|
-
"src/server.ts",
|
|
6073
|
-
"src/server.js",
|
|
6074
|
-
"app.ts",
|
|
6075
|
-
"app.js",
|
|
6076
|
-
"pages/index.tsx",
|
|
6077
|
-
"pages/index.jsx",
|
|
6078
|
-
"src/App.tsx",
|
|
6079
|
-
"src/App.js",
|
|
6080
|
-
"lib/main.ts",
|
|
6081
|
-
"lib/index.ts"
|
|
6082
|
-
];
|
|
6083
|
-
for (const c of candidates) {
|
|
6084
|
-
if (fs14.existsSync(path15.join(root, c))) {
|
|
6085
|
-
entryPoints.push(c);
|
|
6086
|
-
}
|
|
6087
|
-
}
|
|
6088
|
-
try {
|
|
6089
|
-
const pkg = JSON.parse(fs14.readFileSync(path15.join(root, "package.json"), "utf-8"));
|
|
6090
|
-
if (pkg.main && fs14.existsSync(path15.join(root, pkg.main))) {
|
|
6091
|
-
if (!entryPoints.includes(pkg.main)) entryPoints.push(pkg.main);
|
|
6092
|
-
}
|
|
6093
|
-
} catch {
|
|
6094
|
-
}
|
|
6095
|
-
return entryPoints;
|
|
6096
|
-
}
|
|
6097
|
-
async function getActiveADRs() {
|
|
6098
|
-
try {
|
|
6099
|
-
const db = await getDb();
|
|
6100
|
-
const stmt = db.prepare(
|
|
6101
|
-
"SELECT title FROM decision_log WHERE status = 'active' ORDER BY created_at DESC LIMIT 5"
|
|
6102
|
-
);
|
|
6103
|
-
const results = [];
|
|
6104
|
-
while (stmt.step()) {
|
|
6105
|
-
const row = stmt.getAsObject();
|
|
6106
|
-
results.push(row.title);
|
|
6107
|
-
}
|
|
6108
|
-
stmt.free();
|
|
6109
|
-
return results;
|
|
6110
|
-
} catch {
|
|
6111
|
-
return [];
|
|
6112
|
-
}
|
|
6113
|
-
}
|
|
6114
|
-
async function generateContextDigest() {
|
|
6115
|
-
const stack = detectTechStack();
|
|
6116
|
-
const entryPoints = detectEntryPoints();
|
|
6117
|
-
const adrs = await getActiveADRs();
|
|
6118
|
-
const gotchas = getActiveGotchas();
|
|
6119
|
-
const summary = sessionMemory.getSummary();
|
|
6120
|
-
const lines = [
|
|
6121
|
-
"\u{1F4CB} **Kuma Digest** \u2014 <500 token briefing",
|
|
6122
|
-
"\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",
|
|
6123
|
-
""
|
|
6124
|
-
];
|
|
6125
|
-
const langStr = stack.languages.join("/");
|
|
6126
|
-
const frameworkStr = stack.framework || "\u2014";
|
|
6127
|
-
const dbStr = stack.database || "\u2014";
|
|
6128
|
-
const testStr = stack.testRunner || "\u2014";
|
|
6129
|
-
const pmStr = stack.packageManager || "\u2014";
|
|
6130
|
-
lines.push(`\u{1F527} **Stack**: ${langStr} | ${frameworkStr} | ${dbStr} | ${testStr} | ${pmStr}${stack.monorepo ? " (monorepo)" : ""}`);
|
|
6131
|
-
lines.push(`\u{1F4C1} **Root**: ${getProjectRoot().split("/").pop() || "?"}`);
|
|
6132
|
-
if (entryPoints.length > 0) {
|
|
6133
|
-
lines.push(`\u{1F6AA} **Entry**: ${entryPoints.slice(0, 3).join(", ")}`);
|
|
6134
|
-
}
|
|
6135
|
-
try {
|
|
6136
|
-
const ruleFile = path15.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
|
|
6137
|
-
if (fs14.existsSync(ruleFile)) {
|
|
6138
|
-
const content = fs14.readFileSync(ruleFile, "utf-8");
|
|
6139
|
-
const rules = content.split("\n").filter((l) => /^- Rule \d+:?\s*/i.test(l)).map((l) => l.replace(/^- Rule \d+:?\s*/i, "").trim()).filter(Boolean);
|
|
6140
|
-
if (rules.length > 0) {
|
|
6141
|
-
lines.push(`\u{1F4CB} **Rules**: ${rules.slice(0, 5).join(" | ")}`);
|
|
6142
|
-
}
|
|
6143
|
-
}
|
|
6144
|
-
} catch {
|
|
6145
|
-
}
|
|
6146
|
-
if (gotchas.length > 0) {
|
|
6147
|
-
const critical = gotchas.filter((g) => g.severity === "critical" || g.severity === "high");
|
|
6148
|
-
if (critical.length > 0) {
|
|
6149
|
-
lines.push(`\u26A0\uFE0F **Gotchas**: ${critical.length} high/critical \u2014 ${critical.slice(0, 3).map((g) => g.filePath).join(", ")}`);
|
|
6150
|
-
} else {
|
|
6151
|
-
lines.push(`\u26A0\uFE0F **Gotchas**: ${gotchas.length} recorded`);
|
|
6152
|
-
}
|
|
6153
|
-
}
|
|
6154
|
-
if (adrs.length > 0) {
|
|
6155
|
-
lines.push(`\u{1F4DD} **ADRs**: ${adrs.slice(0, 3).join(" | ")}`);
|
|
6156
|
-
}
|
|
6157
|
-
lines.push(`\u{1F3AF} **Goal**: ${summary.currentGoal?.substring(0, 80) || "not set"}`);
|
|
6158
|
-
const text = lines.join("\n");
|
|
6159
|
-
const estimatedTokens = Math.ceil(text.length / 4);
|
|
6160
|
-
if (estimatedTokens > 450) {
|
|
6161
|
-
lines.push(`\u{1F4CA} *~${estimatedTokens} tokens, fits under 500 limit*`);
|
|
6162
|
-
}
|
|
6163
|
-
return lines.join("\n") + "\n\n\u{1F4A1} Use kuma_context({ action: 'digest' }) for the full state.";
|
|
6164
|
-
}
|
|
6165
|
-
var init_contextDigest = __esm({
|
|
6166
|
-
"src/engine/contextDigest.ts"() {
|
|
6167
|
-
"use strict";
|
|
6168
|
-
init_pathValidator();
|
|
6169
|
-
init_sessionMemory();
|
|
6170
|
-
init_kumaDb();
|
|
6171
|
-
init_domainRules();
|
|
6172
|
-
}
|
|
6173
|
-
});
|
|
6174
|
-
|
|
6175
5705
|
// src/engine/kumaSearch.ts
|
|
6176
5706
|
var kumaSearch_exports = {};
|
|
6177
5707
|
__export(kumaSearch_exports, {
|
|
@@ -6184,8 +5714,8 @@ __export(kumaSearch_exports, {
|
|
|
6184
5714
|
formatHybridResults: () => formatHybridResults,
|
|
6185
5715
|
hybridSearch: () => hybridSearch
|
|
6186
5716
|
});
|
|
6187
|
-
import
|
|
6188
|
-
import
|
|
5717
|
+
import fs15 from "fs";
|
|
5718
|
+
import path16 from "path";
|
|
6189
5719
|
function expandQueryTerms(query) {
|
|
6190
5720
|
const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
|
|
6191
5721
|
const expanded = /* @__PURE__ */ new Map();
|
|
@@ -6248,13 +5778,13 @@ async function buildSearchVectors() {
|
|
|
6248
5778
|
}
|
|
6249
5779
|
try {
|
|
6250
5780
|
const root = getProjectRoot();
|
|
6251
|
-
const memDir =
|
|
6252
|
-
if (
|
|
6253
|
-
const files =
|
|
5781
|
+
const memDir = path16.join(root, ".kuma", "memories");
|
|
5782
|
+
if (fs15.existsSync(memDir)) {
|
|
5783
|
+
const files = fs15.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
|
|
6254
5784
|
for (const file of files) {
|
|
6255
5785
|
try {
|
|
6256
|
-
const fullPath =
|
|
6257
|
-
const content =
|
|
5786
|
+
const fullPath = path16.join(memDir, file);
|
|
5787
|
+
const content = fs15.readFileSync(fullPath, "utf-8");
|
|
6258
5788
|
const text = content.toLowerCase();
|
|
6259
5789
|
const rawTerms = extractTerms(text);
|
|
6260
5790
|
const termCounts = /* @__PURE__ */ new Map();
|
|
@@ -6265,7 +5795,7 @@ async function buildSearchVectors() {
|
|
|
6265
5795
|
terms: termCounts,
|
|
6266
5796
|
source: file.replace(/\.md$/, ""),
|
|
6267
5797
|
sourceType: "memory",
|
|
6268
|
-
recency: recencyScore(Math.floor(
|
|
5798
|
+
recency: recencyScore(Math.floor(fs15.statSync(fullPath).mtimeMs / 1e3))
|
|
6269
5799
|
});
|
|
6270
5800
|
} catch {
|
|
6271
5801
|
}
|
|
@@ -6575,368 +6105,45 @@ var init_kumaSearch = __esm({
|
|
|
6575
6105
|
}
|
|
6576
6106
|
});
|
|
6577
6107
|
|
|
6578
|
-
// src/engine/
|
|
6579
|
-
var
|
|
6580
|
-
__export(
|
|
6581
|
-
|
|
6108
|
+
// src/engine/kumaCheckpoint.ts
|
|
6109
|
+
var kumaCheckpoint_exports = {};
|
|
6110
|
+
__export(kumaCheckpoint_exports, {
|
|
6111
|
+
createCheckpoint: () => createCheckpoint,
|
|
6112
|
+
listCheckpoints: () => listCheckpoints,
|
|
6113
|
+
pruneCheckpoints: () => pruneCheckpoints,
|
|
6114
|
+
rollbackToCheckpoint: () => rollbackToCheckpoint
|
|
6582
6115
|
});
|
|
6583
|
-
import
|
|
6584
|
-
import
|
|
6585
|
-
|
|
6586
|
-
import fastGlob2 from "fast-glob";
|
|
6587
|
-
async function mineHistoricalDecisions(options = {}) {
|
|
6588
|
-
const root = process.cwd();
|
|
6589
|
-
const limit = options.limit || 15;
|
|
6590
|
-
const candidates = [];
|
|
6116
|
+
import fs19 from "fs";
|
|
6117
|
+
import path20 from "path";
|
|
6118
|
+
async function createCheckpoint(label, description) {
|
|
6591
6119
|
try {
|
|
6592
|
-
const
|
|
6593
|
-
const
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
const lines = gitOutput.split("\n").filter(Boolean);
|
|
6597
|
-
const matchingHashes = [];
|
|
6598
|
-
for (const line of lines) {
|
|
6599
|
-
const parts = line.split("|");
|
|
6600
|
-
if (parts.length < 4) continue;
|
|
6601
|
-
const [hash, author, date, subject] = parts;
|
|
6602
|
-
const lowerSubj = subject.toLowerCase();
|
|
6603
|
-
const matchedKeyword = keywords.find((kw) => lowerSubj.includes(kw));
|
|
6604
|
-
if (matchedKeyword) {
|
|
6605
|
-
matchingHashes.push({ hash, author, date, subject, keyword: matchedKeyword });
|
|
6606
|
-
if (matchingHashes.length >= limit) break;
|
|
6607
|
-
}
|
|
6608
|
-
}
|
|
6609
|
-
let changedFilesMap = /* @__PURE__ */ new Map();
|
|
6610
|
-
if (matchingHashes.length > 0) {
|
|
6611
|
-
try {
|
|
6612
|
-
const hashesStr = matchingHashes.map((h) => h.hash).join(" ");
|
|
6613
|
-
const batchOutput = execSync4(
|
|
6614
|
-
`git show --name-only --oneline ${hashesStr} 2>/dev/null | head -200`,
|
|
6615
|
-
{ cwd: root, encoding: "utf-8", timeout: 1e4, maxBuffer: 256 * 1024 }
|
|
6616
|
-
);
|
|
6617
|
-
let currentHash = "";
|
|
6618
|
-
const files = [];
|
|
6619
|
-
for (const line of batchOutput.split("\n")) {
|
|
6620
|
-
if (matchingHashes.find((h) => line.startsWith(h.hash))) {
|
|
6621
|
-
if (currentHash && files.length > 0) {
|
|
6622
|
-
changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
|
|
6623
|
-
}
|
|
6624
|
-
currentHash = line.split(" ")[0];
|
|
6625
|
-
files.length = 0;
|
|
6626
|
-
} else if (line.trim() && !line.startsWith("diff")) {
|
|
6627
|
-
files.push(line.trim());
|
|
6628
|
-
}
|
|
6629
|
-
}
|
|
6630
|
-
if (currentHash && files.length > 0) {
|
|
6631
|
-
changedFilesMap.set(currentHash, files.slice(0, 3).join(", "));
|
|
6632
|
-
}
|
|
6633
|
-
} catch {
|
|
6634
|
-
}
|
|
6635
|
-
}
|
|
6636
|
-
for (const { hash, author, date, subject, keyword: matchedKeyword } of matchingHashes) {
|
|
6637
|
-
let changedFiles = changedFilesMap.get(hash) || "";
|
|
6638
|
-
if (!changedFiles) {
|
|
6639
|
-
try {
|
|
6640
|
-
changedFiles = execSync4(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8", timeout: 5e3 }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
|
|
6641
|
-
} catch {
|
|
6642
|
-
}
|
|
6643
|
-
candidates.push({
|
|
6644
|
-
id: `git-${hash}`,
|
|
6645
|
-
type: "git_commit",
|
|
6646
|
-
title: `[Git Commit] ${subject}`,
|
|
6647
|
-
context: `Commit ${hash} by ${author} on ${date}.${changedFiles ? ` Files touched: ${changedFiles}` : ""}`,
|
|
6648
|
-
rationale: `Historical signal pattern "${matchedKeyword}" found in commit message.`,
|
|
6649
|
-
commitHash: hash,
|
|
6650
|
-
pattern: matchedKeyword
|
|
6651
|
-
});
|
|
6652
|
-
if (candidates.length >= limit) break;
|
|
6653
|
-
}
|
|
6120
|
+
const root = getProjectRoot();
|
|
6121
|
+
const cpDir = path20.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
6122
|
+
if (fs19.existsSync(cpDir)) {
|
|
6123
|
+
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
6654
6124
|
}
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
for (const file of files.slice(0, 50)) {
|
|
6673
|
-
if (candidates.length >= limit) break;
|
|
6674
|
-
const fullPath = path18.join(root, file);
|
|
6675
|
-
if (!fs17.existsSync(fullPath)) continue;
|
|
6676
|
-
const content = fs17.readFileSync(fullPath, "utf-8");
|
|
6677
|
-
const lines = content.split("\n");
|
|
6678
|
-
for (let i = 0; i < lines.length; i++) {
|
|
6679
|
-
const l = lines[i];
|
|
6680
|
-
for (const cp of commentPatterns) {
|
|
6681
|
-
const match = cp.regex.exec(l);
|
|
6682
|
-
if (match) {
|
|
6683
|
-
const text = (match[1] || match[2] || l).trim();
|
|
6684
|
-
candidates.push({
|
|
6685
|
-
id: `comment-${file}-${i + 1}`,
|
|
6686
|
-
type: "inline_comment",
|
|
6687
|
-
title: `[${cp.tag}] ${file}:${i + 1}`,
|
|
6688
|
-
context: `Inline comment marker "${cp.tag}" found in file "${file}" at line ${i + 1}.`,
|
|
6689
|
-
rationale: text || l.trim(),
|
|
6690
|
-
filePath: file,
|
|
6691
|
-
lineNumber: i + 1,
|
|
6692
|
-
pattern: cp.tag
|
|
6693
|
-
});
|
|
6694
|
-
if (candidates.length >= limit) break;
|
|
6695
|
-
}
|
|
6696
|
-
}
|
|
6697
|
-
if (candidates.length >= limit) break;
|
|
6698
|
-
}
|
|
6699
|
-
}
|
|
6700
|
-
} catch {
|
|
6701
|
-
}
|
|
6702
|
-
}
|
|
6703
|
-
if (options.confirm && candidates.length > 0) {
|
|
6704
|
-
let recordedCount = 0;
|
|
6705
|
-
for (const c of candidates) {
|
|
6706
|
-
await recordDecision({
|
|
6707
|
-
title: c.title,
|
|
6708
|
-
context: c.context,
|
|
6709
|
-
options: [],
|
|
6710
|
-
rationale: c.rationale,
|
|
6711
|
-
outcome: `Mined from ${c.type}`,
|
|
6712
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6713
|
-
});
|
|
6714
|
-
recordedCount++;
|
|
6715
|
-
}
|
|
6716
|
-
return `\u26CF\uFE0F **Mined & Recorded ${recordedCount} Decisions** into decision log & graph.
|
|
6717
|
-
Use \`kuma_memory({ action: 'decision_log' })\` to view all active & proposed decisions.`;
|
|
6718
|
-
}
|
|
6719
|
-
if (candidates.length === 0) {
|
|
6720
|
-
return "\u26CF\uFE0F **Decision Mining**: No significant historical decision markers (HACK/FIXME/git fix) found for the requested scope.";
|
|
6721
|
-
}
|
|
6722
|
-
const report = [
|
|
6723
|
-
`\u26CF\uFE0F **Decision Mining Candidates** (${candidates.length} candidate(s) found)`,
|
|
6724
|
-
"\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\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",
|
|
6725
|
-
"Review these draft historical decisions mined from git history & inline comments:",
|
|
6726
|
-
""
|
|
6727
|
-
];
|
|
6728
|
-
candidates.forEach((c, idx) => {
|
|
6729
|
-
report.push(`**${idx + 1}. ${c.title}**`);
|
|
6730
|
-
report.push(` \u2022 **Type**: \`${c.type}\` | **Signal**: \`${c.pattern}\``);
|
|
6731
|
-
report.push(` \u2022 **Context**: ${c.context}`);
|
|
6732
|
-
report.push(` \u2022 **Rationale**: ${c.rationale}`);
|
|
6733
|
-
report.push("");
|
|
6734
|
-
});
|
|
6735
|
-
report.push("\u{1F4A1} *To accept and record these mined decisions into knowledge graph & decision.md:*");
|
|
6736
|
-
report.push(`Run: \`kuma_memory({ action: 'mine', confirm: true${options.scope ? `, scope: '${options.scope}'` : ""} })\``);
|
|
6737
|
-
return report.join("\n");
|
|
6738
|
-
}
|
|
6739
|
-
var init_kumaMiner = __esm({
|
|
6740
|
-
"src/engine/kumaMiner.ts"() {
|
|
6741
|
-
"use strict";
|
|
6742
|
-
init_kumaMemory();
|
|
6743
|
-
init_languageSupport();
|
|
6744
|
-
}
|
|
6745
|
-
});
|
|
6746
|
-
|
|
6747
|
-
// src/engine/kumaAstValidator.ts
|
|
6748
|
-
var kumaAstValidator_exports = {};
|
|
6749
|
-
__export(kumaAstValidator_exports, {
|
|
6750
|
-
formatValidationFindings: () => formatValidationFindings,
|
|
6751
|
-
validateCodeContent: () => validateCodeContent,
|
|
6752
|
-
validateDiff: () => validateDiff,
|
|
6753
|
-
validateFile: () => validateFile
|
|
6754
|
-
});
|
|
6755
|
-
import fs22 from "fs";
|
|
6756
|
-
import path23 from "path";
|
|
6757
|
-
function loadImportWhitelist() {
|
|
6758
|
-
try {
|
|
6759
|
-
const root = getProjectRoot();
|
|
6760
|
-
const whitelistPath = path23.join(root, ".kuma", "import-whitelist.json");
|
|
6761
|
-
if (fs22.existsSync(whitelistPath)) {
|
|
6762
|
-
return JSON.parse(fs22.readFileSync(whitelistPath, "utf-8"));
|
|
6763
|
-
}
|
|
6764
|
-
} catch {
|
|
6765
|
-
}
|
|
6766
|
-
return {
|
|
6767
|
-
allowed: [],
|
|
6768
|
-
blocked: ["sql.js", "better-sqlite3", "mysql", "mssql"]
|
|
6769
|
-
// dangerous native deps
|
|
6770
|
-
};
|
|
6771
|
-
}
|
|
6772
|
-
function validateCodeContent(code, filePath) {
|
|
6773
|
-
const findings = [];
|
|
6774
|
-
const lines = code.split("\n");
|
|
6775
|
-
const whitelist = loadImportWhitelist();
|
|
6776
|
-
for (let i = 0; i < lines.length; i++) {
|
|
6777
|
-
const line = lines[i];
|
|
6778
|
-
const lineNum = i + 1;
|
|
6779
|
-
if (/\/\/\s*(expect|assert|should|test|it\.)\s*\(/.test(line) || /(\/\*[\s\S]*?\*\/)/.test(line) && /(expect|assert|should|test)/.test(line)) {
|
|
6780
|
-
findings.push({
|
|
6781
|
-
line: lineNum,
|
|
6782
|
-
severity: "error",
|
|
6783
|
-
category: "assertion-deletion",
|
|
6784
|
-
message: `Test assertion appears to be commented out on line ${lineNum}`,
|
|
6785
|
-
suggestion: "Restore the assertion or write an equivalent test for the new behavior"
|
|
6786
|
-
});
|
|
6787
|
-
}
|
|
6788
|
-
const emptyCatch = /catch\s*\([^)]*\)\s*\{\s*\}/.test(line) || /catch\s*\([^)]*\)\s*\{/.test(line) && lines[i + 1]?.trim() === "}" || // Python: bare/empty except blocks (except:, except Exception: pass)
|
|
6789
|
-
/except\s*([^:]*)\s*:\s*(pass|\.\.\.)\s*(#.*)?$/.test(line) || /except\s*([^:]*)\s*:\s*$/.test(line) && /^pass(\s*#.*)?$/.test(lines[i + 1]?.trim() ?? "");
|
|
6790
|
-
if (emptyCatch) {
|
|
6791
|
-
findings.push({
|
|
6792
|
-
line: lineNum,
|
|
6793
|
-
severity: "error",
|
|
6794
|
-
category: "exception-swallowing",
|
|
6795
|
-
message: `Empty catch block on line ${lineNum} \u2014 exceptions are being swallowed`,
|
|
6796
|
-
suggestion: "Handle the error: log it, throw a meaningful error, or add a comment explaining why it's safe to ignore"
|
|
6797
|
-
});
|
|
6798
|
-
}
|
|
6799
|
-
if (isTestFile(filePath || "")) {
|
|
6800
|
-
if (/\breturn\s+(true|false|null|undefined|0|""|'')\s*;/.test(line) || /\breturn\s+\d+\s*;/.test(line) || /\breturn\s+(True|False|None)\b/.test(line) || /\breturn\s+0\b/.test(line) || /\bPromise\.resolve\s*\(\s*(true|false|null|undefined|0|""|'')\s*\)/.test(line)) {
|
|
6801
|
-
findings.push({
|
|
6802
|
-
line: lineNum,
|
|
6803
|
-
severity: "warning",
|
|
6804
|
-
category: "reward-hacking",
|
|
6805
|
-
message: `Possible hardcoded return in test file on line ${lineNum}: ${line.trim().substring(0, 60)}`,
|
|
6806
|
-
suggestion: "Implement the actual logic instead of returning a hardcoded value to pass tests"
|
|
6807
|
-
});
|
|
6808
|
-
}
|
|
6809
|
-
}
|
|
6810
|
-
if (!isTestFile(filePath || "") && /\breturn\s+(true|false|null)\s*;/.test(line) && ((lines[i - 1] || "").includes("TODO") || (lines[i - 1] || "").includes("FIXME") || (lines[i - 1] || "").includes("HACK"))) {
|
|
6811
|
-
findings.push({
|
|
6812
|
-
line: lineNum,
|
|
6813
|
-
severity: "warning",
|
|
6814
|
-
category: "hardcoded-return",
|
|
6815
|
-
message: `Hardcoded return value on line ${lineNum} (possibly temporary/hack)`,
|
|
6816
|
-
suggestion: "Implement proper logic or add a clear comment explaining why this is correct"
|
|
6817
|
-
});
|
|
6818
|
-
}
|
|
6819
|
-
const imported = matchImportPath(line);
|
|
6820
|
-
if (imported && whitelist.blocked.some((b) => imported.includes(b))) {
|
|
6821
|
-
findings.push({
|
|
6822
|
-
line: lineNum,
|
|
6823
|
-
severity: "error",
|
|
6824
|
-
category: "import-violation",
|
|
6825
|
-
message: `Blocked import: "${imported}" is not allowed per security whitelist`,
|
|
6826
|
-
suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
|
|
6827
|
-
});
|
|
6828
|
-
}
|
|
6829
|
-
}
|
|
6830
|
-
return findings;
|
|
6831
|
-
}
|
|
6832
|
-
function validateFile(filePath) {
|
|
6833
|
-
try {
|
|
6834
|
-
const root = getProjectRoot();
|
|
6835
|
-
const fullPath = path23.resolve(root, filePath);
|
|
6836
|
-
if (!fs22.existsSync(fullPath)) return [];
|
|
6837
|
-
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
6838
|
-
return validateCodeContent(content, filePath);
|
|
6839
|
-
} catch {
|
|
6840
|
-
return [];
|
|
6841
|
-
}
|
|
6842
|
-
}
|
|
6843
|
-
function formatValidationFindings(findings, filePath) {
|
|
6844
|
-
if (findings.length === 0) {
|
|
6845
|
-
return "\u2705 **AST Validation** \u2014 No structural issues found.";
|
|
6846
|
-
}
|
|
6847
|
-
const errors = findings.filter((f) => f.severity === "error");
|
|
6848
|
-
const warnings = findings.filter((f) => f.severity === "warning");
|
|
6849
|
-
const lines = [
|
|
6850
|
-
"\u{1F52C} **AST Code Validation Report**",
|
|
6851
|
-
"\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",
|
|
6852
|
-
"",
|
|
6853
|
-
`${findings.length} finding(s): ${errors.length} errors, ${warnings.length} warnings`,
|
|
6854
|
-
filePath ? `\u{1F4C4} ${filePath}` : "",
|
|
6855
|
-
""
|
|
6856
|
-
];
|
|
6857
|
-
for (const f of findings) {
|
|
6858
|
-
const icon = f.severity === "error" ? "\u{1F534}" : f.severity === "warning" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
6859
|
-
lines.push(`${icon} L${f.line} [${f.category}]`);
|
|
6860
|
-
lines.push(` ${f.message}`);
|
|
6861
|
-
lines.push(` \u{1F4A1} ${f.suggestion}`);
|
|
6862
|
-
lines.push("");
|
|
6863
|
-
}
|
|
6864
|
-
return lines.join("\n");
|
|
6865
|
-
}
|
|
6866
|
-
function validateDiff(oldContent, newContent, filePath) {
|
|
6867
|
-
const findings = [];
|
|
6868
|
-
const oldFindings = validateCodeContent(oldContent, filePath);
|
|
6869
|
-
const newFindings = validateCodeContent(newContent, filePath);
|
|
6870
|
-
const oldAssertions = oldFindings.filter((f) => f.category === "assertion-deletion");
|
|
6871
|
-
const newAssertions = newFindings.filter((f) => f.category === "assertion-deletion");
|
|
6872
|
-
for (const oldAssertion of oldAssertions) {
|
|
6873
|
-
if (!newAssertions.some((f) => f.line === oldAssertion.line && f.message === oldAssertion.message)) {
|
|
6874
|
-
continue;
|
|
6875
|
-
}
|
|
6876
|
-
}
|
|
6877
|
-
const oldLines = oldContent.split("\n");
|
|
6878
|
-
const newLines = newContent.split("\n");
|
|
6879
|
-
for (let i = 0; i < newLines.length; i++) {
|
|
6880
|
-
if (i >= oldLines.length || newLines[i] !== oldLines[i]) {
|
|
6881
|
-
const changedLineFindings = validateCodeContent(newLines[i], filePath);
|
|
6882
|
-
for (const f of changedLineFindings) {
|
|
6883
|
-
findings.push({
|
|
6884
|
-
...f,
|
|
6885
|
-
line: i + 1,
|
|
6886
|
-
message: `[NEW] ${f.message}`
|
|
6887
|
-
});
|
|
6888
|
-
}
|
|
6889
|
-
}
|
|
6890
|
-
}
|
|
6891
|
-
return findings;
|
|
6892
|
-
}
|
|
6893
|
-
var init_kumaAstValidator = __esm({
|
|
6894
|
-
"src/engine/kumaAstValidator.ts"() {
|
|
6895
|
-
"use strict";
|
|
6896
|
-
init_pathValidator();
|
|
6897
|
-
init_languageSupport();
|
|
6898
|
-
}
|
|
6899
|
-
});
|
|
6900
|
-
|
|
6901
|
-
// src/engine/kumaCheckpoint.ts
|
|
6902
|
-
var kumaCheckpoint_exports = {};
|
|
6903
|
-
__export(kumaCheckpoint_exports, {
|
|
6904
|
-
createCheckpoint: () => createCheckpoint,
|
|
6905
|
-
listCheckpoints: () => listCheckpoints,
|
|
6906
|
-
pruneCheckpoints: () => pruneCheckpoints,
|
|
6907
|
-
rollbackToCheckpoint: () => rollbackToCheckpoint
|
|
6908
|
-
});
|
|
6909
|
-
import fs23 from "fs";
|
|
6910
|
-
import path24 from "path";
|
|
6911
|
-
async function createCheckpoint(label, description) {
|
|
6912
|
-
try {
|
|
6913
|
-
const root = getProjectRoot();
|
|
6914
|
-
const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
6915
|
-
if (fs23.existsSync(cpDir)) {
|
|
6916
|
-
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
6917
|
-
}
|
|
6918
|
-
fs23.mkdirSync(cpDir, { recursive: true });
|
|
6919
|
-
const db = await getDb();
|
|
6920
|
-
const filesStmt = db.prepare(
|
|
6921
|
-
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
6922
|
-
);
|
|
6923
|
-
const files = [];
|
|
6924
|
-
while (filesStmt.step()) {
|
|
6925
|
-
const row = filesStmt.getAsObject();
|
|
6926
|
-
const fp = row.file_path;
|
|
6927
|
-
const fullPath = path24.resolve(root, fp);
|
|
6928
|
-
if (fs23.existsSync(fullPath)) {
|
|
6929
|
-
const content = fs23.readFileSync(fullPath, "utf-8");
|
|
6930
|
-
const hash = simpleHash2(content);
|
|
6931
|
-
const fileDir = path24.dirname(path24.join(cpDir, "files", fp));
|
|
6932
|
-
if (!fs23.existsSync(fileDir)) fs23.mkdirSync(fileDir, { recursive: true });
|
|
6933
|
-
fs23.writeFileSync(path24.join(cpDir, "files", fp), content, "utf-8");
|
|
6934
|
-
files.push({ path: fp, hash });
|
|
6125
|
+
fs19.mkdirSync(cpDir, { recursive: true });
|
|
6126
|
+
const db = await getDb();
|
|
6127
|
+
const filesStmt = db.prepare(
|
|
6128
|
+
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
6129
|
+
);
|
|
6130
|
+
const files = [];
|
|
6131
|
+
while (filesStmt.step()) {
|
|
6132
|
+
const row = filesStmt.getAsObject();
|
|
6133
|
+
const fp = row.file_path;
|
|
6134
|
+
const fullPath = path20.resolve(root, fp);
|
|
6135
|
+
if (fs19.existsSync(fullPath)) {
|
|
6136
|
+
const content = fs19.readFileSync(fullPath, "utf-8");
|
|
6137
|
+
const hash = simpleHash2(content);
|
|
6138
|
+
const fileDir = path20.dirname(path20.join(cpDir, "files", fp));
|
|
6139
|
+
if (!fs19.existsSync(fileDir)) fs19.mkdirSync(fileDir, { recursive: true });
|
|
6140
|
+
fs19.writeFileSync(path20.join(cpDir, "files", fp), content, "utf-8");
|
|
6141
|
+
files.push({ path: fp, hash });
|
|
6935
6142
|
}
|
|
6936
6143
|
}
|
|
6937
6144
|
filesStmt.free();
|
|
6938
6145
|
const dbData = db.export();
|
|
6939
|
-
|
|
6146
|
+
fs19.writeFileSync(path20.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
6940
6147
|
const manifest = {
|
|
6941
6148
|
label,
|
|
6942
6149
|
timestamp: Date.now(),
|
|
@@ -6944,8 +6151,8 @@ async function createCheckpoint(label, description) {
|
|
|
6944
6151
|
dbSnapshot: true,
|
|
6945
6152
|
description
|
|
6946
6153
|
};
|
|
6947
|
-
|
|
6948
|
-
|
|
6154
|
+
fs19.writeFileSync(
|
|
6155
|
+
path20.join(cpDir, "manifest.json"),
|
|
6949
6156
|
JSON.stringify(manifest, null, 2),
|
|
6950
6157
|
"utf-8"
|
|
6951
6158
|
);
|
|
@@ -6961,7 +6168,7 @@ async function createCheckpoint(label, description) {
|
|
|
6961
6168
|
`\u{1F5C4}\uFE0F **Database** snapshotted`,
|
|
6962
6169
|
`\u{1F4CD} ${cpDir}`,
|
|
6963
6170
|
``,
|
|
6964
|
-
`\u{1F4A1} To restore: kuma_safety({ action: '
|
|
6171
|
+
`\u{1F4A1} To restore: kuma_safety({ action: 'rollback_label', label: '${label}' })`,
|
|
6965
6172
|
description ? `\u{1F4DD} ${description}` : ""
|
|
6966
6173
|
].filter(Boolean).join("\n");
|
|
6967
6174
|
} catch (err) {
|
|
@@ -6971,37 +6178,39 @@ async function createCheckpoint(label, description) {
|
|
|
6971
6178
|
async function rollbackToCheckpoint(label) {
|
|
6972
6179
|
try {
|
|
6973
6180
|
const root = getProjectRoot();
|
|
6974
|
-
const cpDir =
|
|
6975
|
-
const manifestPath =
|
|
6976
|
-
if (!
|
|
6977
|
-
return `\u274C Checkpoint "${label}" not found.
|
|
6181
|
+
const cpDir = path20.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
6182
|
+
const manifestPath = path20.join(cpDir, "manifest.json");
|
|
6183
|
+
if (!fs19.existsSync(manifestPath)) {
|
|
6184
|
+
return `\u274C Checkpoint "${label}" not found. Available checkpoints:
|
|
6185
|
+
|
|
6186
|
+
${listCheckpoints()}`;
|
|
6978
6187
|
}
|
|
6979
6188
|
const manifest = JSON.parse(
|
|
6980
|
-
|
|
6189
|
+
fs19.readFileSync(manifestPath, "utf-8")
|
|
6981
6190
|
);
|
|
6982
6191
|
let restored = 0;
|
|
6983
6192
|
let failed = 0;
|
|
6984
6193
|
for (const f of manifest.files) {
|
|
6985
|
-
const snapshotPath =
|
|
6986
|
-
if (
|
|
6194
|
+
const snapshotPath = path20.join(cpDir, "files", f.path);
|
|
6195
|
+
if (fs19.existsSync(snapshotPath)) {
|
|
6987
6196
|
try {
|
|
6988
|
-
const content =
|
|
6989
|
-
const targetPath =
|
|
6990
|
-
const targetDir =
|
|
6991
|
-
if (!
|
|
6992
|
-
|
|
6197
|
+
const content = fs19.readFileSync(snapshotPath, "utf-8");
|
|
6198
|
+
const targetPath = path20.resolve(root, f.path);
|
|
6199
|
+
const targetDir = path20.dirname(targetPath);
|
|
6200
|
+
if (!fs19.existsSync(targetDir)) fs19.mkdirSync(targetDir, { recursive: true });
|
|
6201
|
+
fs19.writeFileSync(targetPath, content, "utf-8");
|
|
6993
6202
|
restored++;
|
|
6994
6203
|
} catch {
|
|
6995
6204
|
failed++;
|
|
6996
6205
|
}
|
|
6997
6206
|
}
|
|
6998
6207
|
}
|
|
6999
|
-
const dbSnapshotPath =
|
|
7000
|
-
if (
|
|
7001
|
-
const kumaDir =
|
|
7002
|
-
const dbPath =
|
|
7003
|
-
const snapshotData =
|
|
7004
|
-
|
|
6208
|
+
const dbSnapshotPath = path20.join(cpDir, "kuma.db");
|
|
6209
|
+
if (fs19.existsSync(dbSnapshotPath)) {
|
|
6210
|
+
const kumaDir = path20.join(root, ".kuma");
|
|
6211
|
+
const dbPath = path20.join(kumaDir, "kuma.db");
|
|
6212
|
+
const snapshotData = fs19.readFileSync(dbSnapshotPath);
|
|
6213
|
+
fs19.writeFileSync(dbPath, snapshotData);
|
|
7005
6214
|
resetDbInstance();
|
|
7006
6215
|
}
|
|
7007
6216
|
sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
|
|
@@ -7026,18 +6235,18 @@ async function rollbackToCheckpoint(label) {
|
|
|
7026
6235
|
function listCheckpoints() {
|
|
7027
6236
|
try {
|
|
7028
6237
|
const root = getProjectRoot();
|
|
7029
|
-
const cpDir =
|
|
7030
|
-
if (!
|
|
6238
|
+
const cpDir = path20.join(root, CHECKPOINT_DIR);
|
|
6239
|
+
if (!fs19.existsSync(cpDir)) {
|
|
7031
6240
|
return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
|
|
7032
6241
|
}
|
|
7033
|
-
const entries =
|
|
6242
|
+
const entries = fs19.readdirSync(cpDir);
|
|
7034
6243
|
const checkpoints = [];
|
|
7035
6244
|
for (const entry of entries) {
|
|
7036
|
-
const manifestPath =
|
|
7037
|
-
if (
|
|
6245
|
+
const manifestPath = path20.join(cpDir, entry, "manifest.json");
|
|
6246
|
+
if (fs19.existsSync(manifestPath)) {
|
|
7038
6247
|
try {
|
|
7039
6248
|
const manifest = JSON.parse(
|
|
7040
|
-
|
|
6249
|
+
fs19.readFileSync(manifestPath, "utf-8")
|
|
7041
6250
|
);
|
|
7042
6251
|
checkpoints.push({ label: entry, manifest });
|
|
7043
6252
|
} catch {
|
|
@@ -7069,12 +6278,12 @@ function listCheckpoints() {
|
|
|
7069
6278
|
function pruneCheckpoints(keep = 5) {
|
|
7070
6279
|
try {
|
|
7071
6280
|
const root = getProjectRoot();
|
|
7072
|
-
const cpDir =
|
|
7073
|
-
if (!
|
|
7074
|
-
const entries =
|
|
6281
|
+
const cpDir = path20.join(root, CHECKPOINT_DIR);
|
|
6282
|
+
if (!fs19.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
|
|
6283
|
+
const entries = fs19.readdirSync(cpDir).map((e) => ({ name: e, time: fs19.statSync(path20.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
7075
6284
|
let removed = 0;
|
|
7076
6285
|
for (let i = keep; i < entries.length; i++) {
|
|
7077
|
-
|
|
6286
|
+
fs19.rmSync(path20.join(cpDir, entries[i].name), { recursive: true, force: true });
|
|
7078
6287
|
removed++;
|
|
7079
6288
|
}
|
|
7080
6289
|
return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
|
|
@@ -7115,16 +6324,16 @@ __export(kumaAutoGotcha_exports, {
|
|
|
7115
6324
|
saveAutoGotchaState: () => saveAutoGotchaState,
|
|
7116
6325
|
trackVerificationResult: () => trackVerificationResult
|
|
7117
6326
|
});
|
|
7118
|
-
import
|
|
7119
|
-
import
|
|
6327
|
+
import fs20 from "fs";
|
|
6328
|
+
import path21 from "path";
|
|
7120
6329
|
function statePath() {
|
|
7121
|
-
return
|
|
6330
|
+
return path21.join(getProjectRoot(), STATE_FILE);
|
|
7122
6331
|
}
|
|
7123
6332
|
function loadAutoGotchaState() {
|
|
7124
6333
|
try {
|
|
7125
6334
|
const fp = statePath();
|
|
7126
|
-
if (
|
|
7127
|
-
const parsed = JSON.parse(
|
|
6335
|
+
if (fs20.existsSync(fp)) {
|
|
6336
|
+
const parsed = JSON.parse(fs20.readFileSync(fp, "utf-8"));
|
|
7128
6337
|
return {
|
|
7129
6338
|
failures: parsed?.failures ?? {},
|
|
7130
6339
|
lastRecorded: parsed?.lastRecorded ?? {}
|
|
@@ -7137,9 +6346,9 @@ function loadAutoGotchaState() {
|
|
|
7137
6346
|
function saveAutoGotchaState(state) {
|
|
7138
6347
|
try {
|
|
7139
6348
|
const fp = statePath();
|
|
7140
|
-
const dir =
|
|
7141
|
-
if (!
|
|
7142
|
-
|
|
6349
|
+
const dir = path21.dirname(fp);
|
|
6350
|
+
if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
|
|
6351
|
+
fs20.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
|
|
7143
6352
|
} catch {
|
|
7144
6353
|
}
|
|
7145
6354
|
}
|
|
@@ -7222,37 +6431,37 @@ __export(kumaVerifier_exports, {
|
|
|
7222
6431
|
getRunningVerificationPid: () => getRunningVerificationPid,
|
|
7223
6432
|
runAutoVerification: () => runAutoVerification
|
|
7224
6433
|
});
|
|
7225
|
-
import
|
|
7226
|
-
import
|
|
6434
|
+
import fs21 from "fs";
|
|
6435
|
+
import path22 from "path";
|
|
7227
6436
|
import { exec } from "child_process";
|
|
7228
6437
|
function getLockDir(root = process.cwd()) {
|
|
7229
|
-
return
|
|
6438
|
+
return path22.resolve(root, LOCK_DIR_NAME);
|
|
7230
6439
|
}
|
|
7231
6440
|
function getPidFile(root = process.cwd()) {
|
|
7232
|
-
return
|
|
6441
|
+
return path22.join(getLockDir(root), "pid");
|
|
7233
6442
|
}
|
|
7234
6443
|
function acquireFileLock(root) {
|
|
7235
6444
|
const lockDir = getLockDir(root);
|
|
7236
6445
|
try {
|
|
7237
|
-
|
|
7238
|
-
|
|
6446
|
+
fs21.mkdirSync(lockDir, { recursive: false });
|
|
6447
|
+
fs21.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
7239
6448
|
return true;
|
|
7240
6449
|
} catch {
|
|
7241
6450
|
try {
|
|
7242
|
-
if (
|
|
7243
|
-
const pid = parseInt(
|
|
6451
|
+
if (fs21.existsSync(getPidFile(root))) {
|
|
6452
|
+
const pid = parseInt(fs21.readFileSync(getPidFile(root), "utf-8"), 10);
|
|
7244
6453
|
try {
|
|
7245
6454
|
process.kill(pid, 0);
|
|
7246
6455
|
return false;
|
|
7247
6456
|
} catch {
|
|
7248
|
-
|
|
6457
|
+
fs21.rmSync(lockDir, { recursive: true, force: true });
|
|
7249
6458
|
return acquireFileLock(root);
|
|
7250
6459
|
}
|
|
7251
6460
|
}
|
|
7252
6461
|
} catch {
|
|
7253
6462
|
}
|
|
7254
6463
|
try {
|
|
7255
|
-
|
|
6464
|
+
fs21.rmSync(lockDir, { recursive: true, force: true });
|
|
7256
6465
|
return acquireFileLock(root);
|
|
7257
6466
|
} catch {
|
|
7258
6467
|
return false;
|
|
@@ -7261,7 +6470,7 @@ function acquireFileLock(root) {
|
|
|
7261
6470
|
}
|
|
7262
6471
|
function releaseFileLock(root) {
|
|
7263
6472
|
try {
|
|
7264
|
-
|
|
6473
|
+
fs21.rmSync(getLockDir(root), { recursive: true, force: true });
|
|
7265
6474
|
} catch {
|
|
7266
6475
|
}
|
|
7267
6476
|
}
|
|
@@ -7284,36 +6493,36 @@ function getRunningVerificationPid() {
|
|
|
7284
6493
|
return _currentProcess?.pid ?? null;
|
|
7285
6494
|
}
|
|
7286
6495
|
function detectTestRunner(root = process.cwd()) {
|
|
7287
|
-
const pkgPath =
|
|
7288
|
-
if (
|
|
6496
|
+
const pkgPath = path22.join(root, "package.json");
|
|
6497
|
+
if (fs21.existsSync(pkgPath)) {
|
|
7289
6498
|
try {
|
|
7290
|
-
const pkg = JSON.parse(
|
|
6499
|
+
const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
|
|
7291
6500
|
if (pkg.scripts && pkg.scripts.test) {
|
|
7292
|
-
if (
|
|
7293
|
-
if (
|
|
6501
|
+
if (fs21.existsSync(path22.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
6502
|
+
if (fs21.existsSync(path22.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
|
|
7294
6503
|
return { runner: "npm", baseCommand: "npm test" };
|
|
7295
6504
|
}
|
|
7296
6505
|
} catch {
|
|
7297
6506
|
}
|
|
7298
6507
|
}
|
|
7299
|
-
if (
|
|
7300
|
-
if (
|
|
7301
|
-
if (
|
|
7302
|
-
if (
|
|
6508
|
+
if (fs21.existsSync(path22.join(root, "pytest.ini")) || fs21.existsSync(path22.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
|
|
6509
|
+
if (fs21.existsSync(path22.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
|
|
6510
|
+
if (fs21.existsSync(path22.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
|
|
6511
|
+
if (fs21.existsSync(path22.join(root, "Makefile"))) {
|
|
7303
6512
|
try {
|
|
7304
|
-
const makefileContent =
|
|
6513
|
+
const makefileContent = fs21.readFileSync(path22.join(root, "Makefile"), "utf-8");
|
|
7305
6514
|
const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
|
|
7306
6515
|
if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
|
|
7307
6516
|
} catch {
|
|
7308
6517
|
}
|
|
7309
6518
|
}
|
|
7310
|
-
if (
|
|
6519
|
+
if (fs21.existsSync(path22.join(root, "package.json"))) {
|
|
7311
6520
|
try {
|
|
7312
|
-
const pkg = JSON.parse(
|
|
6521
|
+
const pkg = JSON.parse(fs21.readFileSync(path22.join(root, "package.json"), "utf-8"));
|
|
7313
6522
|
if (pkg.dependencies || pkg.devDependencies) {
|
|
7314
|
-
if (
|
|
7315
|
-
const srcDir =
|
|
7316
|
-
if (
|
|
6523
|
+
if (fs21.existsSync(path22.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
6524
|
+
const srcDir = path22.join(root, "src");
|
|
6525
|
+
if (fs21.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
|
|
7317
6526
|
}
|
|
7318
6527
|
} catch {
|
|
7319
6528
|
}
|
|
@@ -7432,7 +6641,7 @@ async function runAutoVerification(options = {}) {
|
|
|
7432
6641
|
const heartbeatTimer = setInterval(() => {
|
|
7433
6642
|
if (getLockDir(root)) {
|
|
7434
6643
|
try {
|
|
7435
|
-
|
|
6644
|
+
fs21.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
7436
6645
|
} catch {
|
|
7437
6646
|
}
|
|
7438
6647
|
}
|
|
@@ -7552,8 +6761,8 @@ __export(init_exports, {
|
|
|
7552
6761
|
generateInitMdContent: () => generateInitMdContent,
|
|
7553
6762
|
runInit: () => runInit
|
|
7554
6763
|
});
|
|
7555
|
-
import
|
|
7556
|
-
import
|
|
6764
|
+
import fs22 from "fs";
|
|
6765
|
+
import path23 from "path";
|
|
7557
6766
|
function configFilePath(type) {
|
|
7558
6767
|
switch (type) {
|
|
7559
6768
|
case "claude":
|
|
@@ -7595,7 +6804,7 @@ function claudeTemplate() {
|
|
|
7595
6804
|
" \u26A0\uFE0F Requires `kuma` on PATH (global install). A silent hook means no gotchas for that file.",
|
|
7596
6805
|
"",
|
|
7597
6806
|
"\u{1F4D6} Rules: `.kuma/init.md`",
|
|
7598
|
-
"\u{
|
|
6807
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
|
|
7599
6808
|
].join("\n");
|
|
7600
6809
|
}
|
|
7601
6810
|
function cursorRulesTemplate() {
|
|
@@ -7617,7 +6826,7 @@ function windsurfRulesTemplate() {
|
|
|
7617
6826
|
"Windsurf Cascade agent: Kuma MCP tools are installed.",
|
|
7618
6827
|
"All behavioral rules are in `.kuma/init.md`.",
|
|
7619
6828
|
'**Before coding, call `kuma_context({ action: "init" })`** to load project context and session memory.',
|
|
7620
|
-
"Project knowledge persists
|
|
6829
|
+
"Project knowledge persists across sessions (`.kuma/` \u2014 kuma.db + markdown layers).",
|
|
7621
6830
|
"",
|
|
7622
6831
|
"Also auto-detected as SKILL.md in `.windsurf/skills/` and `.agents/skills/`."
|
|
7623
6832
|
].join("\n");
|
|
@@ -7654,12 +6863,22 @@ function opencodeAgentsMdTemplate() {
|
|
|
7654
6863
|
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
7655
6864
|
"Record what matters, skip what doesn't.",
|
|
7656
6865
|
"",
|
|
6866
|
+
"<kuma_obedience>",
|
|
6867
|
+
"## \u{1F512} Obedience (non-negotiable)",
|
|
6868
|
+
'- MUST call `kuma_kuma_context({ action: "init" })` at session start.',
|
|
6869
|
+
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
6870
|
+
'- MUST call `kuma_kuma_safety({ action: "verify" })` after edits.',
|
|
6871
|
+
"- MUST NOT call actions outside the 13 core actions.",
|
|
6872
|
+
"</kuma_obedience>",
|
|
6873
|
+
"",
|
|
6874
|
+
"<kuma_workflow>",
|
|
7657
6875
|
'\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
|
|
7658
6876
|
'\u{1F52C} **Unfamiliar code:** `kuma_kuma_context({ action: "research", scope: "<area>" })`',
|
|
7659
6877
|
'\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7660
6878
|
'\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
|
|
7661
6879
|
'\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
7662
6880
|
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
|
|
6881
|
+
"</kuma_workflow>",
|
|
7663
6882
|
"",
|
|
7664
6883
|
"\u26A0\uFE0F **OpenCode note:** Tool names use `kuma_kuma_*` prefix.",
|
|
7665
6884
|
""
|
|
@@ -7736,7 +6955,7 @@ function openclawSkillTemplate() {
|
|
|
7736
6955
|
KUMA_CORE_INSTRUCTIONS,
|
|
7737
6956
|
"",
|
|
7738
6957
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules.",
|
|
7739
|
-
"\u{
|
|
6958
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
|
|
7740
6959
|
].join("\n");
|
|
7741
6960
|
}
|
|
7742
6961
|
function codewhaleTemplate() {
|
|
@@ -7749,7 +6968,7 @@ function codewhaleTemplate() {
|
|
|
7749
6968
|
KUMA_CORE_INSTRUCTIONS,
|
|
7750
6969
|
"",
|
|
7751
6970
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules.",
|
|
7752
|
-
"\u{
|
|
6971
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
|
|
7753
6972
|
].join("\n");
|
|
7754
6973
|
}
|
|
7755
6974
|
function antigravitySkillTemplate() {
|
|
@@ -7762,7 +6981,7 @@ function antigravitySkillTemplate() {
|
|
|
7762
6981
|
KUMA_CORE_INSTRUCTIONS,
|
|
7763
6982
|
"",
|
|
7764
6983
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules.",
|
|
7765
|
-
"\u{
|
|
6984
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)"
|
|
7766
6985
|
].join("\n");
|
|
7767
6986
|
}
|
|
7768
6987
|
function antigravityMcpConfigTemplate() {
|
|
@@ -7791,16 +7010,36 @@ function generateInitMdContent() {
|
|
|
7791
7010
|
"",
|
|
7792
7011
|
"---",
|
|
7793
7012
|
"",
|
|
7013
|
+
"<kuma_obedience>",
|
|
7014
|
+
"## \u{1F512} OBEDIENCE (non-negotiable)",
|
|
7015
|
+
"",
|
|
7016
|
+
'1. **MUST call `kuma_context({ action: "init" })`** at session start \u2014 before any code edit.',
|
|
7017
|
+
'2. **MUST record gotchas IMMEDIATELY** when a bug/quirk is found (`kuma_memory({ action: "gotcha" })`).',
|
|
7018
|
+
'3. **MUST call `kuma_safety({ action: "verify" })`** after code edits.',
|
|
7019
|
+
"4. **MUST NOT call actions outside the 13 core actions** in this file \u2014 anything else is rejected by the MCP schema.",
|
|
7020
|
+
"5. If a Kuma call fails or returns an error, **re-read this file and retry** \u2014 do not silently continue.",
|
|
7021
|
+
"",
|
|
7022
|
+
"> These rules are enforced: the MCP schema rejects unknown actions, and Claude Code hooks inject gotchas before every edit.",
|
|
7023
|
+
"</kuma_obedience>",
|
|
7024
|
+
"",
|
|
7025
|
+
"---",
|
|
7026
|
+
"",
|
|
7027
|
+
"<kuma_workflow>",
|
|
7794
7028
|
"## SESSION WORKFLOW",
|
|
7795
7029
|
"",
|
|
7796
|
-
"###
|
|
7030
|
+
"### Core Actions (13)",
|
|
7797
7031
|
"",
|
|
7798
7032
|
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
7799
7033
|
'\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
|
|
7034
|
+
'\u{1F570}\uFE0F **Why this file?** `kuma_context({ action: "history", target: "<file>" })`',
|
|
7035
|
+
'\u{1F5C2}\uFE0F **Read a flow:** `kuma_context({ action: "flow", target: "<domain>" })`',
|
|
7800
7036
|
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7801
7037
|
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
7802
7038
|
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
7039
|
+
'\u{1F4BE} **Saved findings:** `kuma_memory({ action: "research_save" })` \xB7 \u{1F50D} **Quick lookup:** `kuma_memory({ action: "search" })`',
|
|
7803
7040
|
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
|
|
7041
|
+
'\u{1F4F8} **Rollback (one mechanism):** `kuma_safety({ action: "checkpoint", label: "pre-x" })` \u2192 `rollback_label` to restore',
|
|
7042
|
+
"</kuma_workflow>",
|
|
7804
7043
|
"",
|
|
7805
7044
|
"### Recording Rules",
|
|
7806
7045
|
"",
|
|
@@ -7838,7 +7077,7 @@ function generateInitMdContent() {
|
|
|
7838
7077
|
"",
|
|
7839
7078
|
"## \u26A0\uFE0F HONEST LIMITATIONS",
|
|
7840
7079
|
"",
|
|
7841
|
-
"- **Node IDs are text-based** \u2014 same file recorded with slightly different paths can create duplicates.
|
|
7080
|
+
"- **Node IDs are text-based** \u2014 same file recorded with slightly different paths can create duplicates.",
|
|
7842
7081
|
"- **Metadata is JSON** \u2014 format varies per node type. Studio displays all fields dynamically.",
|
|
7843
7082
|
"- **Gotchas have 2 stores** \u2014 `known_gotchas` table + `nodes` graph. `syncGotchasGraph()` keeps them in sync.",
|
|
7844
7083
|
"- **Edge weights are static** \u2014 `contains`=2.0, `flows_through`=1.0. Dynamic ranking not yet implemented.",
|
|
@@ -7852,15 +7091,9 @@ function generateInitMdContent() {
|
|
|
7852
7091
|
"| Action | Description |",
|
|
7853
7092
|
"|--------|-------------|",
|
|
7854
7093
|
"| `init` | Lean project brief, restore session |",
|
|
7094
|
+
"| `research` | 5-step pipeline before editing unfamiliar code |",
|
|
7855
7095
|
"| `history` | Why is this file written this way (cross-session trace) |",
|
|
7856
|
-
"| `flow` |
|
|
7857
|
-
"| `impact` | Analyze change effects |",
|
|
7858
|
-
"| `navigate` | Trace code flow |",
|
|
7859
|
-
"| `changes` | View session change log |",
|
|
7860
|
-
"| `digest` | Ultra-compact project briefing |",
|
|
7861
|
-
"| `drift` | Detect memory staleness |",
|
|
7862
|
-
"| `rollback` | Undo a change by change ID |",
|
|
7863
|
-
"| `resume` | Load previous session context |",
|
|
7096
|
+
"| `flow` | Read a recorded architecture flow |",
|
|
7864
7097
|
"",
|
|
7865
7098
|
"### kuma_memory \u2014 Knowledge Recording",
|
|
7866
7099
|
"",
|
|
@@ -7870,11 +7103,7 @@ function generateInitMdContent() {
|
|
|
7870
7103
|
"| `arch_flow` | Record architecture flow (max 5 core files) |",
|
|
7871
7104
|
"| `decision` | Record ADR / decision rationale |",
|
|
7872
7105
|
"| `research_save` | Cache research findings |",
|
|
7873
|
-
"| `search` |
|
|
7874
|
-
"| `session` | Session summary |",
|
|
7875
|
-
"| `mine` | Mine decisions from git history |",
|
|
7876
|
-
"| `delete_node` | Remove node + graph + table entries |",
|
|
7877
|
-
"| `goal_progress` | Track goal completion |",
|
|
7106
|
+
"| `search` | Quick lookup of memory + knowledge graph |",
|
|
7878
7107
|
"",
|
|
7879
7108
|
"### kuma_safety \u2014 Safety & Verification",
|
|
7880
7109
|
"",
|
|
@@ -7882,12 +7111,31 @@ function generateInitMdContent() {
|
|
|
7882
7111
|
"|--------|-------------|",
|
|
7883
7112
|
"| `guard` | Anti-pattern detection before edits |",
|
|
7884
7113
|
"| `verify` | Auto-run scoped tests after edits |",
|
|
7885
|
-
"| `
|
|
7886
|
-
"| `
|
|
7887
|
-
"
|
|
7888
|
-
"
|
|
7889
|
-
"
|
|
7890
|
-
"
|
|
7114
|
+
"| `checkpoint` | Labeled snapshot before risky work |",
|
|
7115
|
+
"| `rollback_label` | Restore a labeled snapshot |",
|
|
7116
|
+
"",
|
|
7117
|
+
"---",
|
|
7118
|
+
"",
|
|
7119
|
+
"<kuma_storage>",
|
|
7120
|
+
"## \u{1F4C2} What Kuma Stores (.kuma/)",
|
|
7121
|
+
"",
|
|
7122
|
+
"Kuma persists project knowledge in `.kuma/`. You can READ these files directly ",
|
|
7123
|
+
"(no MCP call needed) \u2014 but never edit them by hand; record via the actions instead.",
|
|
7124
|
+
"",
|
|
7125
|
+
"| File | Contents | Read via |",
|
|
7126
|
+
"|------|----------|----------|",
|
|
7127
|
+
"| `init.md` | This file \u2014 behavioral rules | read directly |",
|
|
7128
|
+
'| `KNOWN_GOTCHAS.md` | Gotchas (human-readable) | `kuma_context({ action: "history" })` or read directly |',
|
|
7129
|
+
'| `ARCHITECTURE_FLOW.md` | Recorded flows | `kuma_context({ action: "flow" })` or read directly |',
|
|
7130
|
+
'| `memories/decisions.md` | Decision log (ADR-style) | `kuma_context({ action: "history" })` or read directly |',
|
|
7131
|
+
'| `kuma.db` | SQLite graph (nodes, edges, research cache) | `kuma_memory({ action: "search" })` |',
|
|
7132
|
+
'| `memory.json` | Session memory (internal) | `kuma_context({ action: "init" })` auto-restores |',
|
|
7133
|
+
"| `auto-gotcha.json` | Loop auto-capture state (internal) | \u2014 |",
|
|
7134
|
+
"",
|
|
7135
|
+
"> Rules of thumb: `gotcha`/`arch_flow`/`decision` are the WRITE paths; `init`/`history`/",
|
|
7136
|
+
"> `flow`/`search` are the READ paths. Keep recordings small and high-signal \u2014 each one",
|
|
7137
|
+
"> is re-injected on future sessions.",
|
|
7138
|
+
"</kuma_storage>",
|
|
7891
7139
|
"",
|
|
7892
7140
|
"---",
|
|
7893
7141
|
"",
|
|
@@ -7916,27 +7164,16 @@ function generateInitMdContent() {
|
|
|
7916
7164
|
"",
|
|
7917
7165
|
"---",
|
|
7918
7166
|
"",
|
|
7919
|
-
"## \u{1F5D1}\uFE0F Graph Management",
|
|
7920
|
-
"",
|
|
7921
|
-
'**Delete:** `kuma_memory({ action: "delete_node", target: "<node_id_or_type>" })`',
|
|
7922
|
-
"- Deletes from graph nodes, edges, AND known_gotchas table (unified delete)",
|
|
7923
|
-
"",
|
|
7924
|
-
'**Clear entire graph:** `kuma_memory({ action: "clear" })`',
|
|
7925
|
-
"",
|
|
7926
|
-
'**Self-heal:** `kuma_safety({ action: "gotcha_staleness" })`',
|
|
7927
|
-
"- Verifies gotcha file/symbol references still exist",
|
|
7928
|
-
"- Removes obsolete gotchas (deleted files)",
|
|
7929
|
-
"",
|
|
7930
7167
|
"---",
|
|
7931
7168
|
"",
|
|
7932
7169
|
"_Generated by Kuma MCP_"
|
|
7933
7170
|
].join("\n");
|
|
7934
7171
|
}
|
|
7935
7172
|
function handleOpencodeSecondary(root, results) {
|
|
7936
|
-
const skillPath =
|
|
7173
|
+
const skillPath = path23.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
|
|
7937
7174
|
if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
|
|
7938
7175
|
try {
|
|
7939
|
-
const dir =
|
|
7176
|
+
const dir = path23.dirname(skillPath);
|
|
7940
7177
|
const content = [
|
|
7941
7178
|
"---",
|
|
7942
7179
|
"name: kuma-mcp",
|
|
@@ -7946,29 +7183,39 @@ function handleOpencodeSecondary(root, results) {
|
|
|
7946
7183
|
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
7947
7184
|
"Record what matters, skip what doesn't.",
|
|
7948
7185
|
"",
|
|
7186
|
+
"<kuma_obedience>",
|
|
7187
|
+
"## \u{1F512} Obedience (non-negotiable)",
|
|
7188
|
+
'- MUST call `kuma_kuma_context({ action: "init" })` at session start.',
|
|
7189
|
+
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
7190
|
+
'- MUST call `kuma_kuma_safety({ action: "verify" })` after edits.',
|
|
7191
|
+
"- MUST NOT call actions outside the 13 core actions.",
|
|
7192
|
+
"</kuma_obedience>",
|
|
7193
|
+
"",
|
|
7194
|
+
"<kuma_workflow>",
|
|
7949
7195
|
'\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
|
|
7950
7196
|
'\u{1F52C} **Unfamiliar code:** `kuma_kuma_context({ action: "research", scope: "<area>" })`',
|
|
7951
7197
|
'\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7952
7198
|
'\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
|
|
7953
7199
|
'\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
7954
7200
|
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
|
|
7201
|
+
"</kuma_workflow>",
|
|
7955
7202
|
"",
|
|
7956
7203
|
"\u26A0\uFE0F **OpenCode note:** Tool names use `kuma_kuma_*` prefix.",
|
|
7957
7204
|
"",
|
|
7958
7205
|
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
7959
7206
|
].join("\n");
|
|
7960
|
-
if (
|
|
7961
|
-
const existing =
|
|
7207
|
+
if (fs22.existsSync(skillPath)) {
|
|
7208
|
+
const existing = fs22.readFileSync(skillPath, "utf-8");
|
|
7962
7209
|
if (existing.includes("kuma-mcp")) {
|
|
7963
7210
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
|
|
7964
7211
|
return;
|
|
7965
7212
|
}
|
|
7966
7213
|
const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
|
|
7967
|
-
|
|
7214
|
+
fs22.writeFileSync(skillPath, newContent, "utf-8");
|
|
7968
7215
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
|
|
7969
7216
|
} else {
|
|
7970
|
-
if (!
|
|
7971
|
-
|
|
7217
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
7218
|
+
fs22.writeFileSync(skillPath, content, "utf-8");
|
|
7972
7219
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
|
|
7973
7220
|
}
|
|
7974
7221
|
} catch (err) {
|
|
@@ -7981,21 +7228,21 @@ function handleOpencodeSecondary(root, results) {
|
|
|
7981
7228
|
}
|
|
7982
7229
|
}
|
|
7983
7230
|
function handleCodexSecondary(root, results) {
|
|
7984
|
-
const tomlPath =
|
|
7231
|
+
const tomlPath = path23.resolve(root, ".codex/config.toml");
|
|
7985
7232
|
if (results.some((r) => r.filePath === ".codex/config.toml")) return;
|
|
7986
7233
|
try {
|
|
7987
|
-
const dir =
|
|
7988
|
-
if (!
|
|
7989
|
-
if (
|
|
7990
|
-
const existingContent =
|
|
7234
|
+
const dir = path23.dirname(tomlPath);
|
|
7235
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
7236
|
+
if (fs22.existsSync(tomlPath)) {
|
|
7237
|
+
const existingContent = fs22.readFileSync(tomlPath, "utf-8");
|
|
7991
7238
|
if (existingContent.includes("kuma")) {
|
|
7992
7239
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
|
|
7993
7240
|
return;
|
|
7994
7241
|
}
|
|
7995
|
-
|
|
7242
|
+
fs22.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
|
|
7996
7243
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
|
|
7997
7244
|
} else {
|
|
7998
|
-
|
|
7245
|
+
fs22.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
|
|
7999
7246
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
|
|
8000
7247
|
}
|
|
8001
7248
|
} catch (err) {
|
|
@@ -8008,11 +7255,11 @@ function handleCodexSecondary(root, results) {
|
|
|
8008
7255
|
}
|
|
8009
7256
|
}
|
|
8010
7257
|
function handleQwenSecondary(root, results) {
|
|
8011
|
-
const settingsPath =
|
|
7258
|
+
const settingsPath = path23.resolve(root, "settings.json");
|
|
8012
7259
|
if (results.some((r) => r.filePath === "settings.json")) return;
|
|
8013
7260
|
try {
|
|
8014
|
-
if (
|
|
8015
|
-
const existingContent =
|
|
7261
|
+
if (fs22.existsSync(settingsPath)) {
|
|
7262
|
+
const existingContent = fs22.readFileSync(settingsPath, "utf-8");
|
|
8016
7263
|
if (existingContent.includes("kuma")) {
|
|
8017
7264
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
8018
7265
|
try {
|
|
@@ -8020,7 +7267,7 @@ function handleQwenSecondary(root, results) {
|
|
|
8020
7267
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
8021
7268
|
if (!parsed.mcpServers.kuma) {
|
|
8022
7269
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
8023
|
-
|
|
7270
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
8024
7271
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
8025
7272
|
return;
|
|
8026
7273
|
}
|
|
@@ -8035,13 +7282,13 @@ function handleQwenSecondary(root, results) {
|
|
|
8035
7282
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
8036
7283
|
if (!parsed.mcpServers.kuma) {
|
|
8037
7284
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
8038
|
-
|
|
7285
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
8039
7286
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
8040
7287
|
}
|
|
8041
7288
|
} catch {
|
|
8042
7289
|
}
|
|
8043
7290
|
} else {
|
|
8044
|
-
|
|
7291
|
+
fs22.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
|
|
8045
7292
|
results.push({ type: "qwen", filePath: "settings.json", action: "created" });
|
|
8046
7293
|
}
|
|
8047
7294
|
} catch (err) {
|
|
@@ -8063,16 +7310,28 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
8063
7310
|
"",
|
|
8064
7311
|
"## SESSION WORKFLOW",
|
|
8065
7312
|
"",
|
|
8066
|
-
"
|
|
7313
|
+
"<kuma_obedience>",
|
|
7314
|
+
"## \u{1F512} Obedience (non-negotiable)",
|
|
7315
|
+
'- MUST call `[kuma_]context({ action: "init" })` at session start.',
|
|
7316
|
+
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
7317
|
+
'- MUST call `[kuma_]safety({ action: "verify" })` after edits.',
|
|
7318
|
+
"- MUST NOT call actions outside the 13 core actions.",
|
|
7319
|
+
"</kuma_obedience>",
|
|
7320
|
+
"",
|
|
7321
|
+
"<kuma_workflow>",
|
|
7322
|
+
"### Core Actions (13)",
|
|
8067
7323
|
"",
|
|
8068
7324
|
'\u{1F9E0} **Before coding:** `[kuma_]context({ action: "init" })`',
|
|
8069
7325
|
'\u{1F52C} **Unfamiliar code:** `[kuma_]context({ action: "research", scope: "<area>" })`',
|
|
7326
|
+
'\u{1F570}\uFE0F **Why this file?** `[kuma_]context({ action: "history", target: "<file>" })`',
|
|
7327
|
+
'\u{1F5C2}\uFE0F **Read a flow:** `[kuma_]context({ action: "flow", target: "<domain>" })`',
|
|
8070
7328
|
'\u{1F41B} **Found a bug/quirk:** `[kuma_]memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8071
7329
|
'\u{1F9ED} **Chose between options:** `[kuma_]memory({ action: "decision" })`',
|
|
8072
7330
|
'\u{1F500} **Traced a flow:** `[kuma_]memory({ action: "arch_flow" })` (max 5 files)',
|
|
7331
|
+
'\u{1F4BE} **Saved findings:** `[kuma_]memory({ action: "research_save" })` \xB7 \u{1F50D} **Quick lookup:** `[kuma_]memory({ action: "search" })`',
|
|
8073
7332
|
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `[kuma_]safety({ action: "guard" | "verify" })`',
|
|
8074
|
-
"",
|
|
8075
|
-
"
|
|
7333
|
+
'\u{1F4F8} **Rollback:** `[kuma_]safety({ action: "checkpoint", label: "pre-x" })` \u2192 `rollback_label` to restore',
|
|
7334
|
+
"</kuma_workflow>",
|
|
8076
7335
|
"",
|
|
8077
7336
|
"Replace `[kuma_]` with the correct prefix for your platform (see below).",
|
|
8078
7337
|
"",
|
|
@@ -8093,7 +7352,7 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
8093
7352
|
'| Research | `kuma_kuma_context({ action: "research", scope: "..." })` |',
|
|
8094
7353
|
'| Save | `kuma_kuma_memory({ action: "research_save", ... })` |',
|
|
8095
7354
|
'| Verify | `kuma_kuma_safety({ action: "verify", ... })` |',
|
|
8096
|
-
'|
|
|
7355
|
+
'| Checkpoint | `kuma_kuma_safety({ action: "checkpoint", label: "pre-x" })` |',
|
|
8097
7356
|
"",
|
|
8098
7357
|
"\u{1F4D6} Skill: `.agents/skills/kuma/SKILL.md`",
|
|
8099
7358
|
"",
|
|
@@ -8117,7 +7376,7 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
8117
7376
|
'| Research | `kuma_context({ action: "research", scope: "..." })` |',
|
|
8118
7377
|
'| Save | `kuma_memory({ action: "research_save", ... })` |',
|
|
8119
7378
|
'| Verify | `kuma_safety({ action: "verify", ... })` |',
|
|
8120
|
-
'|
|
|
7379
|
+
'| Checkpoint | `kuma_safety({ action: "checkpoint", label: "pre-x" })` |',
|
|
8121
7380
|
"",
|
|
8122
7381
|
"---",
|
|
8123
7382
|
""
|
|
@@ -8125,38 +7384,35 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
8125
7384
|
}
|
|
8126
7385
|
lines.push(
|
|
8127
7386
|
"\u{1F4D6} Rules: `.kuma/init.md`",
|
|
8128
|
-
"\u{
|
|
7387
|
+
"\u{1F4C2} Readable state: `.kuma/KNOWN_GOTCHAS.md` (gotchas) \xB7 `.kuma/memories/decisions.md` (decisions)",
|
|
8129
7388
|
"",
|
|
8130
7389
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
8131
7390
|
);
|
|
8132
7391
|
return lines.join("\n");
|
|
8133
7392
|
}
|
|
8134
7393
|
function handleAntigravityMcpConfig(root, results) {
|
|
8135
|
-
const mcpPath =
|
|
7394
|
+
const mcpPath = path23.resolve(root, ".agents/mcp_config.json");
|
|
8136
7395
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
8137
7396
|
try {
|
|
8138
|
-
const mcpDir =
|
|
8139
|
-
if (
|
|
8140
|
-
const existingContent =
|
|
7397
|
+
const mcpDir = path23.dirname(mcpPath);
|
|
7398
|
+
if (fs22.existsSync(mcpPath)) {
|
|
7399
|
+
const existingContent = fs22.readFileSync(mcpPath, "utf-8");
|
|
8141
7400
|
if (existingContent.includes("kuma")) {
|
|
8142
|
-
|
|
8143
|
-
const trimmed = existingContent.trimEnd();
|
|
8144
|
-
if (trimmed.endsWith("}")) {
|
|
8145
|
-
const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
|
|
8146
|
-
fs26.writeFileSync(mcpPath, updated, "utf-8");
|
|
8147
|
-
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
8148
|
-
}
|
|
8149
|
-
}
|
|
7401
|
+
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "skipped" });
|
|
8150
7402
|
return;
|
|
8151
7403
|
}
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
7404
|
+
try {
|
|
7405
|
+
const parsed = JSON.parse(existingContent);
|
|
7406
|
+
parsed.mcpServers = parsed.mcpServers || {};
|
|
7407
|
+
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
7408
|
+
fs22.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
7409
|
+
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
7410
|
+
} catch {
|
|
7411
|
+
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "error", error: "Invalid JSON in existing mcp_config.json" });
|
|
7412
|
+
}
|
|
8157
7413
|
} else {
|
|
8158
|
-
if (!
|
|
8159
|
-
|
|
7414
|
+
if (!fs22.existsSync(mcpDir)) fs22.mkdirSync(mcpDir, { recursive: true });
|
|
7415
|
+
fs22.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
8160
7416
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
|
|
8161
7417
|
}
|
|
8162
7418
|
} catch (err) {
|
|
@@ -8169,18 +7425,18 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
8169
7425
|
}
|
|
8170
7426
|
}
|
|
8171
7427
|
function handleAiderSecondary(root, results) {
|
|
8172
|
-
const ymlPath =
|
|
7428
|
+
const ymlPath = path23.resolve(root, ".aider.conf.yml");
|
|
8173
7429
|
if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
|
|
8174
7430
|
try {
|
|
8175
7431
|
const conventionsRef = "read: CONVENTIONS.md";
|
|
8176
|
-
if (
|
|
8177
|
-
const existingContent =
|
|
7432
|
+
if (fs22.existsSync(ymlPath)) {
|
|
7433
|
+
const existingContent = fs22.readFileSync(ymlPath, "utf-8");
|
|
8178
7434
|
if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
|
|
8179
7435
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
|
|
8180
7436
|
return;
|
|
8181
7437
|
}
|
|
8182
7438
|
const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
|
|
8183
|
-
|
|
7439
|
+
fs22.writeFileSync(ymlPath, newContent, "utf-8");
|
|
8184
7440
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
|
|
8185
7441
|
} else {
|
|
8186
7442
|
const content = [
|
|
@@ -8190,7 +7446,7 @@ function handleAiderSecondary(root, results) {
|
|
|
8190
7446
|
conventionsRef,
|
|
8191
7447
|
""
|
|
8192
7448
|
].join("\n");
|
|
8193
|
-
|
|
7449
|
+
fs22.writeFileSync(ymlPath, content, "utf-8");
|
|
8194
7450
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
|
|
8195
7451
|
}
|
|
8196
7452
|
} catch (err) {
|
|
@@ -8203,10 +7459,10 @@ function handleAiderSecondary(root, results) {
|
|
|
8203
7459
|
}
|
|
8204
7460
|
}
|
|
8205
7461
|
function handleCopilotSecondary(root, results) {
|
|
8206
|
-
const skillPath =
|
|
7462
|
+
const skillPath = path23.resolve(root, ".github/skills/kuma/SKILL.md");
|
|
8207
7463
|
if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
|
|
8208
7464
|
try {
|
|
8209
|
-
const dir =
|
|
7465
|
+
const dir = path23.dirname(skillPath);
|
|
8210
7466
|
const content = [
|
|
8211
7467
|
"---",
|
|
8212
7468
|
"name: kuma-mcp",
|
|
@@ -8217,17 +7473,17 @@ function handleCopilotSecondary(root, results) {
|
|
|
8217
7473
|
"",
|
|
8218
7474
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8219
7475
|
].join("\n");
|
|
8220
|
-
if (
|
|
8221
|
-
const existingContent =
|
|
7476
|
+
if (fs22.existsSync(skillPath)) {
|
|
7477
|
+
const existingContent = fs22.readFileSync(skillPath, "utf-8");
|
|
8222
7478
|
if (existingContent.includes("kuma")) {
|
|
8223
7479
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
|
|
8224
7480
|
return;
|
|
8225
7481
|
}
|
|
8226
|
-
|
|
7482
|
+
fs22.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
|
|
8227
7483
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
|
|
8228
7484
|
} else {
|
|
8229
|
-
if (!
|
|
8230
|
-
|
|
7485
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
7486
|
+
fs22.writeFileSync(skillPath, content, "utf-8");
|
|
8231
7487
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
|
|
8232
7488
|
}
|
|
8233
7489
|
} catch (err) {
|
|
@@ -8240,21 +7496,21 @@ function handleCopilotSecondary(root, results) {
|
|
|
8240
7496
|
}
|
|
8241
7497
|
}
|
|
8242
7498
|
function handleOpenclawSecondary(root, results) {
|
|
8243
|
-
const mcpPath =
|
|
7499
|
+
const mcpPath = path23.resolve(root, ".agents/mcp_config.json");
|
|
8244
7500
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
8245
7501
|
try {
|
|
8246
|
-
const dir =
|
|
8247
|
-
if (
|
|
8248
|
-
const existingContent =
|
|
7502
|
+
const dir = path23.dirname(mcpPath);
|
|
7503
|
+
if (fs22.existsSync(mcpPath)) {
|
|
7504
|
+
const existingContent = fs22.readFileSync(mcpPath, "utf-8");
|
|
8249
7505
|
if (existingContent.includes("kuma")) return;
|
|
8250
7506
|
const parsed = JSON.parse(existingContent);
|
|
8251
7507
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
8252
7508
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
8253
|
-
|
|
7509
|
+
fs22.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
8254
7510
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
8255
7511
|
} else {
|
|
8256
|
-
if (!
|
|
8257
|
-
|
|
7512
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
7513
|
+
fs22.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
8258
7514
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
|
|
8259
7515
|
}
|
|
8260
7516
|
} catch (err) {
|
|
@@ -8267,20 +7523,20 @@ function handleOpenclawSecondary(root, results) {
|
|
|
8267
7523
|
}
|
|
8268
7524
|
}
|
|
8269
7525
|
function handleInitMdGeneration(root, results) {
|
|
8270
|
-
const initMdPath =
|
|
7526
|
+
const initMdPath = path23.resolve(root, ".kuma/init.md");
|
|
8271
7527
|
try {
|
|
8272
|
-
const kumaDir =
|
|
8273
|
-
if (!
|
|
8274
|
-
if (
|
|
8275
|
-
const existing =
|
|
7528
|
+
const kumaDir = path23.dirname(initMdPath);
|
|
7529
|
+
if (!fs22.existsSync(kumaDir)) fs22.mkdirSync(kumaDir, { recursive: true });
|
|
7530
|
+
if (fs22.existsSync(initMdPath)) {
|
|
7531
|
+
const existing = fs22.readFileSync(initMdPath, "utf-8");
|
|
8276
7532
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
8277
7533
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
|
|
8278
7534
|
return;
|
|
8279
7535
|
}
|
|
8280
|
-
|
|
7536
|
+
fs22.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
|
|
8281
7537
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
|
|
8282
7538
|
} else {
|
|
8283
|
-
|
|
7539
|
+
fs22.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
8284
7540
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
|
|
8285
7541
|
}
|
|
8286
7542
|
} catch (err) {
|
|
@@ -8293,20 +7549,20 @@ function handleInitMdGeneration(root, results) {
|
|
|
8293
7549
|
}
|
|
8294
7550
|
}
|
|
8295
7551
|
function handleCodewhaleSecondary(root, results) {
|
|
8296
|
-
const mcpPath =
|
|
7552
|
+
const mcpPath = path23.resolve(root, ".codewhale/mcp.json");
|
|
8297
7553
|
if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
|
|
8298
7554
|
try {
|
|
8299
|
-
const dir =
|
|
8300
|
-
if (
|
|
8301
|
-
const existingContent =
|
|
7555
|
+
const dir = path23.dirname(mcpPath);
|
|
7556
|
+
if (fs22.existsSync(mcpPath)) {
|
|
7557
|
+
const existingContent = fs22.readFileSync(mcpPath, "utf-8");
|
|
8302
7558
|
if (existingContent.includes("kuma")) return;
|
|
8303
7559
|
const parsed = JSON.parse(existingContent);
|
|
8304
7560
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
8305
7561
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
8306
|
-
|
|
7562
|
+
fs22.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
8307
7563
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
|
|
8308
7564
|
} else {
|
|
8309
|
-
if (!
|
|
7565
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
8310
7566
|
const config = {
|
|
8311
7567
|
mcpServers: {
|
|
8312
7568
|
kuma: {
|
|
@@ -8316,7 +7572,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
8316
7572
|
}
|
|
8317
7573
|
}
|
|
8318
7574
|
};
|
|
8319
|
-
|
|
7575
|
+
fs22.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
8320
7576
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
|
|
8321
7577
|
}
|
|
8322
7578
|
} catch (err) {
|
|
@@ -8329,7 +7585,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
8329
7585
|
}
|
|
8330
7586
|
}
|
|
8331
7587
|
function handleClaudeSecondary(root, results) {
|
|
8332
|
-
const settingsPath =
|
|
7588
|
+
const settingsPath = path23.resolve(root, ".claude", "settings.json");
|
|
8333
7589
|
const hookBlock = {
|
|
8334
7590
|
hooks: {
|
|
8335
7591
|
PreToolUse: [
|
|
@@ -8345,8 +7601,8 @@ function handleClaudeSecondary(root, results) {
|
|
|
8345
7601
|
}
|
|
8346
7602
|
};
|
|
8347
7603
|
try {
|
|
8348
|
-
if (
|
|
8349
|
-
const existing = JSON.parse(
|
|
7604
|
+
if (fs22.existsSync(settingsPath)) {
|
|
7605
|
+
const existing = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
8350
7606
|
const merged = {
|
|
8351
7607
|
...existing,
|
|
8352
7608
|
hooks: {
|
|
@@ -8361,12 +7617,12 @@ function handleClaudeSecondary(root, results) {
|
|
|
8361
7617
|
matchers.add(entry.matcher);
|
|
8362
7618
|
}
|
|
8363
7619
|
}
|
|
8364
|
-
|
|
7620
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
8365
7621
|
results.push({ type: "claude", filePath: ".claude/settings.json", action: "appended" });
|
|
8366
7622
|
} else {
|
|
8367
|
-
const dir =
|
|
8368
|
-
if (!
|
|
8369
|
-
|
|
7623
|
+
const dir = path23.dirname(settingsPath);
|
|
7624
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
7625
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(hookBlock, null, 2) + "\n", "utf-8");
|
|
8370
7626
|
results.push({ type: "claude", filePath: ".claude/settings.json", action: "created" });
|
|
8371
7627
|
}
|
|
8372
7628
|
} catch (err) {
|
|
@@ -8379,18 +7635,18 @@ function handleClaudeSecondary(root, results) {
|
|
|
8379
7635
|
}
|
|
8380
7636
|
}
|
|
8381
7637
|
function handleCursorGotchaRules(root, results) {
|
|
8382
|
-
const rulesDir2 =
|
|
7638
|
+
const rulesDir2 = path23.resolve(root, ".cursor", "rules", "kuma-gotchas");
|
|
8383
7639
|
try {
|
|
8384
7640
|
let gotchas = [];
|
|
8385
7641
|
try {
|
|
8386
7642
|
gotchas = getActiveGotchas().filter((g) => g.severity === "high" || g.severity === "critical");
|
|
8387
7643
|
} catch {
|
|
8388
7644
|
}
|
|
8389
|
-
if (
|
|
8390
|
-
for (const f of
|
|
7645
|
+
if (fs22.existsSync(rulesDir2)) {
|
|
7646
|
+
for (const f of fs22.readdirSync(rulesDir2)) {
|
|
8391
7647
|
if (f.endsWith(".mdc")) {
|
|
8392
7648
|
try {
|
|
8393
|
-
|
|
7649
|
+
fs22.rmSync(path23.join(rulesDir2, f), { force: true });
|
|
8394
7650
|
} catch {
|
|
8395
7651
|
}
|
|
8396
7652
|
}
|
|
@@ -8400,7 +7656,7 @@ function handleCursorGotchaRules(root, results) {
|
|
|
8400
7656
|
results.push({ type: "cursor", filePath: ".cursor/rules/kuma-gotchas/", action: "skipped" });
|
|
8401
7657
|
return;
|
|
8402
7658
|
}
|
|
8403
|
-
|
|
7659
|
+
fs22.mkdirSync(rulesDir2, { recursive: true });
|
|
8404
7660
|
const seen = /* @__PURE__ */ new Set();
|
|
8405
7661
|
let created = 0;
|
|
8406
7662
|
for (const g of gotchas) {
|
|
@@ -8421,7 +7677,7 @@ function handleCursorGotchaRules(root, results) {
|
|
|
8421
7677
|
"`kuma_context({ action: 'history', target: '<file>' })`",
|
|
8422
7678
|
""
|
|
8423
7679
|
].join("\n");
|
|
8424
|
-
|
|
7680
|
+
fs22.writeFileSync(path23.join(rulesDir2, `${slug}.mdc`), md, "utf-8");
|
|
8425
7681
|
created++;
|
|
8426
7682
|
}
|
|
8427
7683
|
results.push({
|
|
@@ -8448,28 +7704,28 @@ function runInit(options) {
|
|
|
8448
7704
|
let agentsMdHandled = false;
|
|
8449
7705
|
for (const type of selected) {
|
|
8450
7706
|
const relativePath = configFilePath(type);
|
|
8451
|
-
const fullPath =
|
|
7707
|
+
const fullPath = path23.resolve(root, relativePath);
|
|
8452
7708
|
const getTemplate = TEMPLATES[type];
|
|
8453
7709
|
try {
|
|
8454
7710
|
if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
|
|
8455
7711
|
agentsMdHandled = true;
|
|
8456
7712
|
const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
|
|
8457
|
-
if (
|
|
7713
|
+
if (fs22.existsSync(fullPath)) {
|
|
8458
7714
|
if (options.skipExisting) {
|
|
8459
7715
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
8460
7716
|
} else {
|
|
8461
|
-
const existingContent =
|
|
7717
|
+
const existingContent = fs22.readFileSync(fullPath, "utf-8");
|
|
8462
7718
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
8463
7719
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
8464
7720
|
} else {
|
|
8465
|
-
|
|
7721
|
+
fs22.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
|
|
8466
7722
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
8467
7723
|
}
|
|
8468
7724
|
}
|
|
8469
7725
|
} else {
|
|
8470
|
-
const dir =
|
|
8471
|
-
if (!
|
|
8472
|
-
|
|
7726
|
+
const dir = path23.dirname(fullPath);
|
|
7727
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
7728
|
+
fs22.writeFileSync(fullPath, combinedContent, "utf-8");
|
|
8473
7729
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
8474
7730
|
}
|
|
8475
7731
|
if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
|
|
@@ -8481,12 +7737,12 @@ function runInit(options) {
|
|
|
8481
7737
|
continue;
|
|
8482
7738
|
} else {
|
|
8483
7739
|
const template = getTemplate();
|
|
8484
|
-
if (
|
|
7740
|
+
if (fs22.existsSync(fullPath)) {
|
|
8485
7741
|
if (options.skipExisting) {
|
|
8486
7742
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
8487
7743
|
continue;
|
|
8488
7744
|
}
|
|
8489
|
-
const existingContent =
|
|
7745
|
+
const existingContent = fs22.readFileSync(fullPath, "utf-8");
|
|
8490
7746
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
8491
7747
|
if (type === "antigravity") {
|
|
8492
7748
|
handleAntigravityMcpConfig(root, results);
|
|
@@ -8499,14 +7755,14 @@ function runInit(options) {
|
|
|
8499
7755
|
continue;
|
|
8500
7756
|
}
|
|
8501
7757
|
const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
|
|
8502
|
-
|
|
7758
|
+
fs22.writeFileSync(fullPath, newContent, "utf-8");
|
|
8503
7759
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
8504
7760
|
} else {
|
|
8505
|
-
const dir =
|
|
8506
|
-
if (!
|
|
8507
|
-
|
|
7761
|
+
const dir = path23.dirname(fullPath);
|
|
7762
|
+
if (!fs22.existsSync(dir)) {
|
|
7763
|
+
fs22.mkdirSync(dir, { recursive: true });
|
|
8508
7764
|
}
|
|
8509
|
-
|
|
7765
|
+
fs22.writeFileSync(fullPath, template, "utf-8");
|
|
8510
7766
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
8511
7767
|
}
|
|
8512
7768
|
if (type === "antigravity") {
|
|
@@ -8616,12 +7872,22 @@ var init_init = __esm({
|
|
|
8616
7872
|
"Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.",
|
|
8617
7873
|
"Record what matters, skip what doesn't.",
|
|
8618
7874
|
"",
|
|
7875
|
+
"<kuma_obedience>",
|
|
7876
|
+
"## \u{1F512} Obedience (non-negotiable)",
|
|
7877
|
+
'- MUST call `kuma_context({ action: "init" })` at session start.',
|
|
7878
|
+
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
7879
|
+
'- MUST call `kuma_safety({ action: "verify" })` after edits.',
|
|
7880
|
+
"- MUST NOT call actions outside the 13 core actions.",
|
|
7881
|
+
"</kuma_obedience>",
|
|
7882
|
+
"",
|
|
7883
|
+
"<kuma_workflow>",
|
|
8619
7884
|
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
8620
7885
|
'\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
|
|
8621
7886
|
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8622
7887
|
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
8623
7888
|
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
8624
7889
|
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
|
|
7890
|
+
"</kuma_workflow>",
|
|
8625
7891
|
"",
|
|
8626
7892
|
"\u{1FA84} **Auto-inject (Claude Code):** gotcha/decision/history injected automatically before edits",
|
|
8627
7893
|
" via the PreToolUse hook \u2014 an empty hook output means the file has no gotchas.",
|
|
@@ -8657,19 +7923,19 @@ __export(agentDetector_exports, {
|
|
|
8657
7923
|
getSkillPath: () => getSkillPath,
|
|
8658
7924
|
skillExists: () => skillExists
|
|
8659
7925
|
});
|
|
8660
|
-
import
|
|
8661
|
-
import
|
|
7926
|
+
import fs23 from "fs";
|
|
7927
|
+
import path24 from "path";
|
|
8662
7928
|
function checkFile(root, filePath) {
|
|
8663
7929
|
try {
|
|
8664
|
-
return
|
|
7930
|
+
return fs23.existsSync(path24.join(root, filePath));
|
|
8665
7931
|
} catch {
|
|
8666
7932
|
return false;
|
|
8667
7933
|
}
|
|
8668
7934
|
}
|
|
8669
7935
|
function checkDir(root, dirPath) {
|
|
8670
7936
|
try {
|
|
8671
|
-
const fullPath =
|
|
8672
|
-
return
|
|
7937
|
+
const fullPath = path24.join(root, dirPath);
|
|
7938
|
+
return fs23.existsSync(fullPath) && fs23.statSync(fullPath).isDirectory();
|
|
8673
7939
|
} catch {
|
|
8674
7940
|
return false;
|
|
8675
7941
|
}
|
|
@@ -8737,7 +8003,7 @@ function getAgentLabel(type) {
|
|
|
8737
8003
|
function skillExists(type, projectRoot) {
|
|
8738
8004
|
const root = projectRoot ?? getProjectRoot();
|
|
8739
8005
|
const skillPath = getSkillPath(type);
|
|
8740
|
-
return
|
|
8006
|
+
return fs23.existsSync(path24.join(root, skillPath));
|
|
8741
8007
|
}
|
|
8742
8008
|
var AGENT_DETECTORS;
|
|
8743
8009
|
var init_agentDetector = __esm({
|
|
@@ -8906,7 +8172,7 @@ function generateClaudeSkill() {
|
|
|
8906
8172
|
' \u2022 `kuma_safety({ action: "guard" })` \u2014 safety check before risky ops',
|
|
8907
8173
|
' \u2022 `kuma_safety({ action: "verify", scope: "<area>" })` \u2014 verify after edits',
|
|
8908
8174
|
" \u2022 `.kuma/init.md` \u2014 full behavioral rules",
|
|
8909
|
-
" \u2022 `.kuma/memories
|
|
8175
|
+
" \u2022 `.kuma/KNOWN_GOTCHAS.md` \u2014 gotchas \xB7 `.kuma/memories/decisions.md` \u2014 decisions (readable)"
|
|
8910
8176
|
].join("\n");
|
|
8911
8177
|
}
|
|
8912
8178
|
function generateCursorSkill() {
|
|
@@ -8923,7 +8189,7 @@ function generateCursorSkill() {
|
|
|
8923
8189
|
' \u2022 `kuma_safety({ action: "guard" })` \u2014 safety check before risky ops',
|
|
8924
8190
|
' \u2022 `kuma_safety({ action: "verify", scope: "<area>" })` \u2014 verify after edits',
|
|
8925
8191
|
" \u2022 `.kuma/init.md` \u2014 full behavioral rules",
|
|
8926
|
-
" \u2022 `.kuma/memories
|
|
8192
|
+
" \u2022 `.kuma/KNOWN_GOTCHAS.md` \u2014 gotchas \xB7 `.kuma/memories/decisions.md` \u2014 decisions (readable)"
|
|
8927
8193
|
].join("\n");
|
|
8928
8194
|
}
|
|
8929
8195
|
function generateClineSkill() {
|
|
@@ -9128,12 +8394,22 @@ var init_skillGenerator = __esm({
|
|
|
9128
8394
|
"src/utils/skillGenerator.ts"() {
|
|
9129
8395
|
"use strict";
|
|
9130
8396
|
CORE_ACTIONS = [
|
|
8397
|
+
"<kuma_obedience>",
|
|
8398
|
+
"## \u{1F512} Obedience (non-negotiable)",
|
|
8399
|
+
'- MUST call `kuma_context({ action: "init" })` at session start.',
|
|
8400
|
+
"- MUST record gotchas IMMEDIATELY when a bug/quirk is found.",
|
|
8401
|
+
'- MUST call `kuma_safety({ action: "verify" })` after edits.',
|
|
8402
|
+
"- MUST NOT call actions outside the 13 core actions.",
|
|
8403
|
+
"</kuma_obedience>",
|
|
8404
|
+
"",
|
|
8405
|
+
"<kuma_workflow>",
|
|
9131
8406
|
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
9132
8407
|
'\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
|
|
9133
8408
|
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
9134
8409
|
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
9135
8410
|
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
9136
|
-
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`'
|
|
8411
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
|
|
8412
|
+
"</kuma_workflow>"
|
|
9137
8413
|
].join("\n");
|
|
9138
8414
|
BOOTSTRAP = [
|
|
9139
8415
|
"Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.",
|
|
@@ -9170,21 +8446,14 @@ init_kumaDb();
|
|
|
9170
8446
|
init_kumaGraph();
|
|
9171
8447
|
init_kumaMemory();
|
|
9172
8448
|
init_pathValidator();
|
|
9173
|
-
import
|
|
9174
|
-
import
|
|
8449
|
+
import fs14 from "fs";
|
|
8450
|
+
import path15 from "path";
|
|
9175
8451
|
import crypto3 from "crypto";
|
|
9176
8452
|
var CONTEXT_ALIASES = {
|
|
9177
8453
|
"research": "research",
|
|
9178
8454
|
"search": "research",
|
|
9179
8455
|
"explore": "research",
|
|
9180
8456
|
"inspect": "research",
|
|
9181
|
-
"impact": "impact",
|
|
9182
|
-
"analyze": "impact",
|
|
9183
|
-
"whatif": "impact",
|
|
9184
|
-
"refactor": "impact",
|
|
9185
|
-
"navigate": "navigate",
|
|
9186
|
-
"trace": "navigate",
|
|
9187
|
-
"flow-trace": "navigate",
|
|
9188
8457
|
"flow": "flow",
|
|
9189
8458
|
"domain-flow": "flow",
|
|
9190
8459
|
"domain_flow": "flow",
|
|
@@ -9194,28 +8463,10 @@ var CONTEXT_ALIASES = {
|
|
|
9194
8463
|
"load": "init",
|
|
9195
8464
|
"brief": "init",
|
|
9196
8465
|
"project": "init",
|
|
9197
|
-
"resume": "resume",
|
|
9198
|
-
"continue": "resume",
|
|
9199
|
-
"restore": "resume",
|
|
9200
|
-
"reload": "resume",
|
|
9201
|
-
"changes": "changes",
|
|
9202
|
-
"log": "changes",
|
|
9203
|
-
"changelog": "changes",
|
|
9204
8466
|
"history": "history",
|
|
9205
8467
|
"why": "history",
|
|
9206
8468
|
"touched": "history",
|
|
9207
|
-
"provenance": "history"
|
|
9208
|
-
"rollback": "rollback",
|
|
9209
|
-
"undo": "rollback",
|
|
9210
|
-
"revert": "rollback",
|
|
9211
|
-
"digest": "digest",
|
|
9212
|
-
"bootstrap": "digest",
|
|
9213
|
-
"briefing": "digest",
|
|
9214
|
-
"overview": "digest",
|
|
9215
|
-
"drift": "drift",
|
|
9216
|
-
"staleness": "drift",
|
|
9217
|
-
"code-drift": "drift",
|
|
9218
|
-
"freshness": "drift"
|
|
8469
|
+
"provenance": "history"
|
|
9219
8470
|
};
|
|
9220
8471
|
async function handleContext(params) {
|
|
9221
8472
|
const rawAction = params.action || "init";
|
|
@@ -9226,87 +8477,13 @@ async function handleContext(params) {
|
|
|
9226
8477
|
return handleInit(params);
|
|
9227
8478
|
case "research":
|
|
9228
8479
|
return handleResearch(params);
|
|
9229
|
-
case "impact":
|
|
9230
|
-
return handleImpact(params);
|
|
9231
|
-
case "navigate":
|
|
9232
|
-
return handleNavigate(params);
|
|
9233
|
-
case "flow":
|
|
9234
|
-
return handleFlow(params);
|
|
9235
8480
|
case "history":
|
|
9236
8481
|
return handleHistory(params);
|
|
9237
|
-
case "
|
|
9238
|
-
return
|
|
9239
|
-
case "rollback":
|
|
9240
|
-
return handleRollback(params);
|
|
9241
|
-
case "digest":
|
|
9242
|
-
return handleDigest(params);
|
|
9243
|
-
case "drift":
|
|
9244
|
-
return handleDrift(params);
|
|
9245
|
-
case "resume":
|
|
9246
|
-
return handleResume(params);
|
|
8482
|
+
case "flow":
|
|
8483
|
+
return handleFlow(params);
|
|
9247
8484
|
default:
|
|
9248
|
-
return `Unknown action "${action}". Use: init, research,
|
|
9249
|
-
}
|
|
9250
|
-
}
|
|
9251
|
-
async function handleResume(_params) {
|
|
9252
|
-
sessionMemory.recordToolCall("kuma_context_resume", {});
|
|
9253
|
-
const lines = [
|
|
9254
|
-
"\u{1F504} **Kuma \u2014 Session Resume**",
|
|
9255
|
-
`\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
9256
|
-
""
|
|
9257
|
-
];
|
|
9258
|
-
try {
|
|
9259
|
-
const db = await getDb();
|
|
9260
|
-
const res = db.exec("SELECT goal, tool_calls, edits, rollbacks, failures, safety_score, started_at FROM sessions ORDER BY started_at DESC LIMIT 1");
|
|
9261
|
-
if (res[0]?.values.length) {
|
|
9262
|
-
const row = res[0].values[0];
|
|
9263
|
-
const goal = row[0] || "no goal";
|
|
9264
|
-
const calls = row[1] || 0;
|
|
9265
|
-
const edits = row[2] || 0;
|
|
9266
|
-
const rollbacks = row[3] || 0;
|
|
9267
|
-
const failures2 = row[4] || 0;
|
|
9268
|
-
const safety = row[5] == null ? null : row[5];
|
|
9269
|
-
const time = new Date(row[6] * 1e3).toLocaleString();
|
|
9270
|
-
lines.push("**Last Session**");
|
|
9271
|
-
lines.push(` \u{1F3AF} Goal: ${goal.substring(0, 80)}`);
|
|
9272
|
-
lines.push(` \u{1F550} ${time}`);
|
|
9273
|
-
lines.push(` \u{1F527} ${calls} calls \xB7 \u{1F4DD} ${edits} edits \xB7 \u21A9 ${rollbacks} rollbacks \xB7 \u274C ${failures2} failures${safety != null ? ` \xB7 \u{1F6E1} safety ${safety}` : ""}`);
|
|
9274
|
-
lines.push("");
|
|
9275
|
-
}
|
|
9276
|
-
} catch {
|
|
8485
|
+
return `Unknown action "${action}". Use: init, research, history, flow`;
|
|
9277
8486
|
}
|
|
9278
|
-
const summary = sessionMemory.getSummary();
|
|
9279
|
-
lines.push("**Current State**");
|
|
9280
|
-
lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
|
|
9281
|
-
const progress = sessionMemory.getGoalProgress();
|
|
9282
|
-
if (progress) lines.push(` \u{1F4CA} Progress: ${progress.percentage}%${progress.milestone ? ` \u2014 ${progress.milestone}` : ""}`);
|
|
9283
|
-
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
9284
|
-
lines.push(` \u2705 Completed steps: ${summary.completedSteps?.length || 0}`);
|
|
9285
|
-
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
9286
|
-
lines.push(` \u23F1\uFE0F Session duration: ${summary.sessionDuration}`);
|
|
9287
|
-
const failures = summary.unresolvedFailures;
|
|
9288
|
-
if (failures?.length) lines.push(` \u26A0\uFE0F Unresolved failures: ${failures.length}`);
|
|
9289
|
-
lines.push("");
|
|
9290
|
-
try {
|
|
9291
|
-
const changes = await getChanges({ limit: 5 });
|
|
9292
|
-
if (changes && !changes.startsWith("No changes")) {
|
|
9293
|
-
lines.push("**Recent Changes**");
|
|
9294
|
-
lines.push(changes);
|
|
9295
|
-
lines.push("");
|
|
9296
|
-
}
|
|
9297
|
-
} catch {
|
|
9298
|
-
}
|
|
9299
|
-
const history = sessionMemory.getToolCallHistory(5);
|
|
9300
|
-
if (history.length) {
|
|
9301
|
-
lines.push("**Recent Tool Calls**");
|
|
9302
|
-
for (const h of history) {
|
|
9303
|
-
const action = h.params?.action || "";
|
|
9304
|
-
lines.push(` \u{1F6E0} ${h.toolName}${action ? ` (${action})` : ""}`);
|
|
9305
|
-
}
|
|
9306
|
-
lines.push("");
|
|
9307
|
-
}
|
|
9308
|
-
lines.push("\u{1F4A1} Continue: kuma_context({ action: 'research', scope: '<area>' }) to pick up where you left off.");
|
|
9309
|
-
return lines.join("\n");
|
|
9310
8487
|
}
|
|
9311
8488
|
async function handleInit(_params) {
|
|
9312
8489
|
sessionMemory.setGoal(_params.goal || "Exploring project");
|
|
@@ -9375,7 +8552,7 @@ async function handleInit(_params) {
|
|
|
9375
8552
|
}
|
|
9376
8553
|
} catch {
|
|
9377
8554
|
}
|
|
9378
|
-
lines.push("\u{1F4A1}
|
|
8555
|
+
lines.push("\u{1F4A1} Deeper: kuma_context({ action: 'history', target: '<file>' }) \xB7 Flow: kuma_context({ action: 'flow', target: '<domain>' }) \xB7 Research: kuma_context({ action: 'research', scope: '<area>' })");
|
|
9379
8556
|
return lines.join("\n");
|
|
9380
8557
|
}
|
|
9381
8558
|
function detectSessionFocus(goal) {
|
|
@@ -9504,32 +8681,6 @@ async function handleResearch(params) {
|
|
|
9504
8681
|
lines.push("\u{1F4DD} Research saved to cache. Call kuma_memory({ action: 'research_save' }) to save custom notes.");
|
|
9505
8682
|
return lines.join("\n");
|
|
9506
8683
|
}
|
|
9507
|
-
async function handleImpact(params) {
|
|
9508
|
-
const target = params.target || params.scope;
|
|
9509
|
-
if (!target) return "\u26A0\uFE0F target or scope parameter required.";
|
|
9510
|
-
sessionMemory.recordToolCall("kuma_context_impact", { target });
|
|
9511
|
-
const impact = await analyzeImpact(target);
|
|
9512
|
-
return formatImpact(impact);
|
|
9513
|
-
}
|
|
9514
|
-
async function handleNavigate(params) {
|
|
9515
|
-
const entryPoint = params.target || params.scope;
|
|
9516
|
-
if (!entryPoint) return "\u26A0\uFE0F target or scope parameter required.";
|
|
9517
|
-
sessionMemory.recordToolCall("kuma_context_navigate", { entryPoint });
|
|
9518
|
-
const steps = await traceFlow(entryPoint);
|
|
9519
|
-
return formatFlow(entryPoint, steps);
|
|
9520
|
-
}
|
|
9521
|
-
async function handleChanges(params) {
|
|
9522
|
-
const filePath = params.target;
|
|
9523
|
-
const since = params.since;
|
|
9524
|
-
return await getChanges({ filePath, since });
|
|
9525
|
-
}
|
|
9526
|
-
async function handleFlow(params) {
|
|
9527
|
-
const domain = params.target || params.scope;
|
|
9528
|
-
sessionMemory.recordToolCall("kuma_context_flow", { domain });
|
|
9529
|
-
if (!domain) return "\u2139\uFE0F **kuma_context({ action: 'flow' })** \u2014 requires a `target` (domain name).\n\n kuma_context({ action: 'flow', target: 'WhatsApp Omnichannel' })\n\nServes the domain flow from cache, re-deriving it from imports when stale (F13).";
|
|
9530
|
-
const { getFreshDomainFlow: getFreshDomainFlow2 } = await Promise.resolve().then(() => (init_kumaFlowCache(), kumaFlowCache_exports));
|
|
9531
|
-
return await getFreshDomainFlow2(domain);
|
|
9532
|
-
}
|
|
9533
8684
|
async function handleHistory(params) {
|
|
9534
8685
|
const target = params.target || params.scope;
|
|
9535
8686
|
sessionMemory.recordToolCall("kuma_context_history", { target });
|
|
@@ -9555,7 +8706,7 @@ async function handleHistory(params) {
|
|
|
9555
8706
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
9556
8707
|
const db = await getDb2();
|
|
9557
8708
|
const stmt = db.prepare(`SELECT description, last_verified_at FROM known_gotchas WHERE status = 'resolved' AND file_path LIKE ? ORDER BY last_verified_at DESC LIMIT 3`);
|
|
9558
|
-
stmt.bind([`%${
|
|
8709
|
+
stmt.bind([`%${path15.basename(target)}%`]);
|
|
9559
8710
|
const resolved = [];
|
|
9560
8711
|
while (stmt.step()) resolved.push(stmt.getAsObject());
|
|
9561
8712
|
stmt.free();
|
|
@@ -9577,48 +8728,22 @@ async function handleHistory(params) {
|
|
|
9577
8728
|
lines.push("\u{1F4A1} This content is injected automatically before edits via the Claude Code PreToolUse hook (`kuma hook pre-edit`).");
|
|
9578
8729
|
return lines.join("\n");
|
|
9579
8730
|
}
|
|
9580
|
-
async function
|
|
9581
|
-
const
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
return await rollbackChange(changeId);
|
|
9587
|
-
}
|
|
9588
|
-
async function handleDigest(_params) {
|
|
9589
|
-
sessionMemory.recordToolCall("kuma_context_digest", {});
|
|
9590
|
-
try {
|
|
9591
|
-
const { generateContextDigest: generateContextDigest2 } = await Promise.resolve().then(() => (init_contextDigest(), contextDigest_exports));
|
|
9592
|
-
return await generateContextDigest2();
|
|
9593
|
-
} catch (err) {
|
|
9594
|
-
try {
|
|
9595
|
-
const { generateDigest: generateDigest2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
|
|
9596
|
-
return generateDigest2();
|
|
9597
|
-
} catch {
|
|
9598
|
-
return `Error generating digest: ${err}`;
|
|
9599
|
-
}
|
|
9600
|
-
}
|
|
9601
|
-
}
|
|
9602
|
-
async function handleDrift(_params) {
|
|
9603
|
-
sessionMemory.recordToolCall("kuma_context_drift", {});
|
|
9604
|
-
try {
|
|
9605
|
-
const { detectDrift: detectDrift2, formatDriftReport: formatDriftReport2, flagStaleRecords: flagStaleRecords2 } = await Promise.resolve().then(() => (init_kumaDriftDetector(), kumaDriftDetector_exports));
|
|
9606
|
-
await flagStaleRecords2();
|
|
9607
|
-
const records = await detectDrift2();
|
|
9608
|
-
return formatDriftReport2(records);
|
|
9609
|
-
} catch (err) {
|
|
9610
|
-
return `Error detecting drift: ${err}`;
|
|
9611
|
-
}
|
|
8731
|
+
async function handleFlow(params) {
|
|
8732
|
+
const domain = params.target || params.scope;
|
|
8733
|
+
sessionMemory.recordToolCall("kuma_context_flow", { domain });
|
|
8734
|
+
if (!domain) return "\u2139\uFE0F **kuma_context({ action: 'flow' })** \u2014 requires a `target` (domain name).\n\n kuma_context({ action: 'flow', target: 'WhatsApp Omnichannel' })\n\nServes the domain flow from cache, re-deriving it from imports when stale (F13).";
|
|
8735
|
+
const { getFreshDomainFlow: getFreshDomainFlow2 } = await Promise.resolve().then(() => (init_kumaFlowCache(), kumaFlowCache_exports));
|
|
8736
|
+
return await getFreshDomainFlow2(domain);
|
|
9612
8737
|
}
|
|
9613
8738
|
function computeProjectHash(scope) {
|
|
9614
8739
|
try {
|
|
9615
8740
|
const root = getProjectRoot();
|
|
9616
|
-
const files =
|
|
8741
|
+
const files = fs14.readdirSync(root).slice(0, 20);
|
|
9617
8742
|
const hash = crypto3.createHash("md5");
|
|
9618
8743
|
hash.update(scope);
|
|
9619
8744
|
for (const f of files) {
|
|
9620
8745
|
try {
|
|
9621
|
-
const stat =
|
|
8746
|
+
const stat = fs14.statSync(path15.join(root, f));
|
|
9622
8747
|
hash.update(`${f}:${stat.mtimeMs}`);
|
|
9623
8748
|
} catch {
|
|
9624
8749
|
}
|
|
@@ -9635,76 +8760,43 @@ init_kumaDb();
|
|
|
9635
8760
|
init_kumaMemory();
|
|
9636
8761
|
init_pathValidator();
|
|
9637
8762
|
init_pathValidator();
|
|
9638
|
-
import
|
|
9639
|
-
import
|
|
8763
|
+
import fs16 from "fs";
|
|
8764
|
+
import path17 from "path";
|
|
9640
8765
|
var MEMORY_ALIASES = {
|
|
9641
|
-
"session": "session",
|
|
9642
|
-
"summary": "session",
|
|
9643
|
-
"status": "session",
|
|
9644
|
-
"get": "session",
|
|
9645
8766
|
"decision": "decision",
|
|
9646
8767
|
"adr": "decision",
|
|
9647
8768
|
"research_save": "research_save",
|
|
9648
8769
|
"save": "research_save",
|
|
9649
8770
|
"store": "research_save",
|
|
9650
|
-
"mine": "mine",
|
|
9651
|
-
"dig": "mine",
|
|
9652
8771
|
"search": "search",
|
|
9653
8772
|
"find": "search",
|
|
9654
8773
|
"query": "search",
|
|
9655
8774
|
"lookup": "search",
|
|
9656
|
-
"changes": "changes",
|
|
9657
|
-
"change-log": "changes",
|
|
9658
|
-
"history": "changes",
|
|
9659
8775
|
"arch_flow": "arch_flow",
|
|
9660
8776
|
"arch-flow": "arch_flow",
|
|
9661
8777
|
"architecture": "arch_flow",
|
|
9662
8778
|
"gotcha": "gotcha",
|
|
9663
8779
|
"gotchas": "gotcha",
|
|
9664
|
-
"quirk": "gotcha"
|
|
9665
|
-
"delete_node": "delete_node",
|
|
9666
|
-
"delete-node": "delete_node",
|
|
9667
|
-
"remove-node": "delete_node",
|
|
9668
|
-
"clear": "clear",
|
|
9669
|
-
"clear-graph": "clear",
|
|
9670
|
-
"wipe": "clear",
|
|
9671
|
-
"reset": "clear",
|
|
9672
|
-
"goal_progress": "goal_progress",
|
|
9673
|
-
"progress": "goal_progress"
|
|
8780
|
+
"quirk": "gotcha"
|
|
9674
8781
|
};
|
|
9675
8782
|
async function handleMemory(params) {
|
|
9676
|
-
const rawAction = params.action || "
|
|
8783
|
+
const rawAction = params.action || "search";
|
|
9677
8784
|
const key = rawAction.toLowerCase().replace(/[\s_-]+/g, "-");
|
|
9678
8785
|
const action = MEMORY_ALIASES[key] || rawAction;
|
|
9679
8786
|
sessionMemory.recordToolCall("kuma_memory", { action: rawAction });
|
|
9680
8787
|
switch (action) {
|
|
9681
8788
|
case "decision":
|
|
9682
8789
|
return handleDecision(params);
|
|
9683
|
-
case "mine":
|
|
9684
|
-
return handleMine(params);
|
|
9685
8790
|
case "research_save":
|
|
9686
8791
|
return handleResearchSave(params);
|
|
9687
|
-
case "session":
|
|
9688
|
-
return handleSession(params);
|
|
9689
8792
|
case "search":
|
|
9690
8793
|
return handleSearch(params);
|
|
9691
|
-
case "changes":
|
|
9692
|
-
return handleChanges2(params);
|
|
9693
8794
|
case "arch_flow":
|
|
9694
8795
|
return handleArchFlow(params);
|
|
9695
8796
|
case "gotcha":
|
|
9696
8797
|
return handleGotchaAction(params);
|
|
9697
|
-
case "goal_progress":
|
|
9698
|
-
return handleGoalProgress(params);
|
|
9699
|
-
case "delete_node":
|
|
9700
|
-
return handleDeleteNode(params);
|
|
9701
|
-
case "clear": {
|
|
9702
|
-
const { clearGraph: clearGraph2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
9703
|
-
await clearGraph2();
|
|
9704
|
-
return "\u{1F5D1}\uFE0F **Knowledge Graph Cleared** \u2014 All nodes, edges, and gotchas have been wiped from disk and memory.";
|
|
9705
|
-
}
|
|
9706
8798
|
default:
|
|
9707
|
-
return `Unknown action "${action}"
|
|
8799
|
+
return `Unknown action "${action}". Use: gotcha, decision, arch_flow, research_save, search`;
|
|
9708
8800
|
}
|
|
9709
8801
|
}
|
|
9710
8802
|
async function handleDecision(params) {
|
|
@@ -9752,50 +8844,14 @@ async function handleResearchSave(params) {
|
|
|
9752
8844
|
} catch {
|
|
9753
8845
|
}
|
|
9754
8846
|
try {
|
|
9755
|
-
const researchDir =
|
|
9756
|
-
if (!
|
|
9757
|
-
|
|
8847
|
+
const researchDir = path17.join(getProjectRoot(), ".kuma", "research");
|
|
8848
|
+
if (!fs16.existsSync(researchDir)) fs16.mkdirSync(researchDir, { recursive: true });
|
|
8849
|
+
fs16.writeFileSync(path17.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
|
|
9758
8850
|
} catch {
|
|
9759
8851
|
}
|
|
9760
8852
|
sessionMemory.recordMemoryAction("research_save");
|
|
9761
8853
|
return `\u2705 Research "${scope}" saved.`;
|
|
9762
8854
|
}
|
|
9763
|
-
async function handleSession(params) {
|
|
9764
|
-
const topic = params.topic;
|
|
9765
|
-
const summary = sessionMemory.getSummary(topic);
|
|
9766
|
-
if (topic) return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
|
|
9767
|
-
const modifiedFiles = summary.modifiedFiles || [];
|
|
9768
|
-
const failures = summary.unresolvedFailures || [];
|
|
9769
|
-
const recordingSummary = sessionMemory.getRecordingSummary();
|
|
9770
|
-
const metricsSummary = sessionMemory.getMetricsSummary();
|
|
9771
|
-
const lines = [
|
|
9772
|
-
"\u{1F4CB} **Session Summary**\n\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\n",
|
|
9773
|
-
`\u{1F3AF} Goal: ${summary.currentGoal || "not set"}`,
|
|
9774
|
-
`\u{1F550} Duration: ${summary.sessionDuration}`,
|
|
9775
|
-
`\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}
|
|
9776
|
-
`,
|
|
9777
|
-
`\u{1F9E0} **Recordings:** ${recordingSummary.total} total \u2014 ${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save`,
|
|
9778
|
-
`\u{1F4CA} **Metrics:** ${metricsSummary.filesRead} files read, ${metricsSummary.filesEdited} files edited`,
|
|
9779
|
-
`\u23F1\uFE0F **Time saved:** ~${metricsSummary.researchTimeSavedFormatted} (from research cache)`
|
|
9780
|
-
];
|
|
9781
|
-
if (recordingSummary.missingRecordings.length > 0 && recordingSummary.total === 0) {
|
|
9782
|
-
lines.push(`
|
|
9783
|
-
\u26A0\uFE0F **No recordings yet!** Missing: ${recordingSummary.missingRecordings.join(", ")}`);
|
|
9784
|
-
lines.push(`\u{1F4A1} Tip: Record findings after reading files. arch_flow + gotcha are exponential value.`);
|
|
9785
|
-
}
|
|
9786
|
-
if (modifiedFiles.length > 0) {
|
|
9787
|
-
lines.push(`**Modified Files** (${modifiedFiles.length}):`);
|
|
9788
|
-
for (const f of modifiedFiles.slice(0, 10)) {
|
|
9789
|
-
lines.push(` ${f.status === "created" ? "\u2728" : f.status === "modified" ? "\u{1F4DD}" : "\u274C"} ${f.filePath}`);
|
|
9790
|
-
}
|
|
9791
|
-
if (modifiedFiles.length > 10) lines.push(` ... and ${modifiedFiles.length - 10} more`);
|
|
9792
|
-
}
|
|
9793
|
-
if (failures.length > 0) {
|
|
9794
|
-
lines.push(`**Unresolved Issues** (${failures.length}):`);
|
|
9795
|
-
for (const f of failures.slice(0, 5)) lines.push(` \u274C ${f.task}: ${f.error.substring(0, 80)}`);
|
|
9796
|
-
}
|
|
9797
|
-
return lines.join("\n");
|
|
9798
|
-
}
|
|
9799
8855
|
async function handleSearch(params) {
|
|
9800
8856
|
const query = params.query || params.scope;
|
|
9801
8857
|
if (!query) return "\u26A0\uFE0F query or scope parameter required.";
|
|
@@ -9855,20 +8911,6 @@ ${impactLines.join("\n")}`;
|
|
|
9855
8911
|
}
|
|
9856
8912
|
return lines.join("\n");
|
|
9857
8913
|
}
|
|
9858
|
-
async function handleChanges2(params) {
|
|
9859
|
-
const sinceNum = typeof params.since === "number" ? params.since : typeof params.since === "string" ? parseInt(params.since, 10) || void 0 : void 0;
|
|
9860
|
-
return await getChanges({ filePath: params.target, since: sinceNum });
|
|
9861
|
-
}
|
|
9862
|
-
async function handleMine(params) {
|
|
9863
|
-
sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
|
|
9864
|
-
const { mineHistoricalDecisions: mineHistoricalDecisions2 } = await Promise.resolve().then(() => (init_kumaMiner(), kumaMiner_exports));
|
|
9865
|
-
return await mineHistoricalDecisions2({
|
|
9866
|
-
scope: params.scope,
|
|
9867
|
-
since: typeof params.since === "string" ? params.since : void 0,
|
|
9868
|
-
confirm: params.confirm,
|
|
9869
|
-
limit: params.limit
|
|
9870
|
-
});
|
|
9871
|
-
}
|
|
9872
8914
|
async function handleArchFlow(params) {
|
|
9873
8915
|
const { readLayer: readLayer2, writeLayer: writeLayer2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
|
|
9874
8916
|
if (params.content) {
|
|
@@ -9924,659 +8966,33 @@ async function handleArchFlow(params) {
|
|
|
9924
8966
|
return readLayer2("arch_flow");
|
|
9925
8967
|
}
|
|
9926
8968
|
async function handleGotchaAction(params) {
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
const normalizedScope = normalizeScope(
|
|
8969
|
+
const content = (params.content ?? params.description ?? "").trim();
|
|
8970
|
+
const scope = (params.scope ?? "").trim();
|
|
8971
|
+
if (content && scope) {
|
|
8972
|
+
const normalizedScope = normalizeScope(scope) || scope;
|
|
9931
8973
|
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
9932
8974
|
const result = await addGotcha2({
|
|
9933
8975
|
filePath: normalizedScope,
|
|
9934
|
-
description:
|
|
8976
|
+
description: content,
|
|
9935
8977
|
severity: params.status || "medium",
|
|
9936
|
-
workaround
|
|
8978
|
+
// `description` doubles as workaround only when the canonical `content` field was used.
|
|
8979
|
+
workaround: params.content ? params.description : void 0,
|
|
9937
8980
|
triggerCommand: params.trigger_command
|
|
9938
8981
|
});
|
|
9939
8982
|
sessionMemory.recordMemoryAction("gotcha");
|
|
9940
8983
|
return result;
|
|
9941
8984
|
}
|
|
8985
|
+
if (scope || params.title || params.status || params.trigger_command || params.description) {
|
|
8986
|
+
return '\u274C **Gotcha NOT saved** \u2014 missing `content` (bug description).\n\u2705 Use: kuma_memory({ action: "gotcha", scope: "<file_path>", content: "<what went wrong>", status: "high" })\n- `scope` \u2014 file path where the bug was found\n- `content` \u2014 bug description (required to save)\n- `status` \u2014 low | medium | high | critical';
|
|
8987
|
+
}
|
|
9942
8988
|
const { listGotchas: listGotchas2, syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
9943
8989
|
await syncGotchasToDb2();
|
|
9944
8990
|
return await listGotchas2({ filePath: params.scope, severity: params.status });
|
|
9945
8991
|
}
|
|
9946
|
-
async function handleDeleteNode(params) {
|
|
9947
|
-
sessionMemory.recordToolCall("kuma_memory_delete", { scope: params.scope, target: params.target });
|
|
9948
|
-
const { getDb: getDb2, saveDb: saveDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
9949
|
-
const db = await getDb2();
|
|
9950
|
-
if (params.scope && params.target) {
|
|
9951
|
-
const id = parseInt(params.target, 10);
|
|
9952
|
-
if (isNaN(id)) return `\u26A0\uFE0F target "${params.target}" is not a valid numeric ID.`;
|
|
9953
|
-
switch (params.scope) {
|
|
9954
|
-
case "gotcha":
|
|
9955
|
-
case "gotchas": {
|
|
9956
|
-
const gotchaRow = db.prepare("SELECT file_path, description FROM known_gotchas WHERE id = ?");
|
|
9957
|
-
gotchaRow.bind([id]);
|
|
9958
|
-
let gotchaFilePath = "", gotchaDesc = "";
|
|
9959
|
-
if (gotchaRow.step()) {
|
|
9960
|
-
const row = gotchaRow.getAsObject();
|
|
9961
|
-
gotchaFilePath = row.file_path;
|
|
9962
|
-
gotchaDesc = row.description;
|
|
9963
|
-
}
|
|
9964
|
-
gotchaRow.free();
|
|
9965
|
-
db.run("DELETE FROM known_gotchas WHERE id = ?", [id]);
|
|
9966
|
-
if (gotchaFilePath && gotchaDesc) {
|
|
9967
|
-
const gotchaNodeId = `gotcha::${gotchaFilePath}::${gotchaDesc.substring(0, 30)}`;
|
|
9968
|
-
db.run("DELETE FROM edges WHERE source_id = ? OR target_id = ?", [gotchaNodeId, gotchaNodeId]);
|
|
9969
|
-
db.run("DELETE FROM nodes WHERE id = ?", [gotchaNodeId]);
|
|
9970
|
-
}
|
|
9971
|
-
saveDb2(db);
|
|
9972
|
-
return `\u{1F5D1}\uFE0F **Gotcha #${id} deleted** (table + graph).`;
|
|
9973
|
-
}
|
|
9974
|
-
default:
|
|
9975
|
-
return `\u26A0\uFE0F Unknown scope "${params.scope}". Supported: gotcha`;
|
|
9976
|
-
}
|
|
9977
|
-
}
|
|
9978
|
-
if (params.target || params.scope) {
|
|
9979
|
-
const { flushDb: flushDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
9980
|
-
const targetStr = params.target || "";
|
|
9981
|
-
const targetId = params.scope && !targetStr.includes("::") ? `${targetStr}::${params.scope}` : targetStr;
|
|
9982
|
-
if (targetId && (targetId.includes("::") || targetId.includes(":") || targetId.includes("-") || isNaN(parseInt(targetId, 10)))) {
|
|
9983
|
-
try {
|
|
9984
|
-
if (targetId.startsWith("feature_domain::")) {
|
|
9985
|
-
const domainName = targetId.replace("feature_domain::", "");
|
|
9986
|
-
db.run("DELETE FROM nodes WHERE id LIKE ? OR id LIKE ? OR id LIKE ?", [`feature_domain::${domainName}`, `cross_service_link::${domainName}::%`, `gotcha::${domainName}::%`]);
|
|
9987
|
-
db.run("DELETE FROM edges WHERE source_id LIKE ? OR target_id LIKE ?", [`%${domainName}%`, `%${domainName}%`]);
|
|
9988
|
-
} else if (targetId.startsWith("gotcha::")) {
|
|
9989
|
-
db.run("DELETE FROM edges WHERE source_id = ? OR target_id = ?", [targetId, targetId]);
|
|
9990
|
-
db.run("DELETE FROM nodes WHERE id = ?", [targetId]);
|
|
9991
|
-
const parts = targetId.split("::");
|
|
9992
|
-
if (parts.length >= 3) {
|
|
9993
|
-
db.run("DELETE FROM known_gotchas WHERE file_path = ? AND description LIKE ?", [parts[1], `${parts.slice(2).join("::")}%`]);
|
|
9994
|
-
}
|
|
9995
|
-
} else {
|
|
9996
|
-
db.run("DELETE FROM edges WHERE source_id = ? OR target_id = ?", [targetId, targetId]);
|
|
9997
|
-
db.run("DELETE FROM nodes WHERE id = ?", [targetId]);
|
|
9998
|
-
}
|
|
9999
|
-
flushDb2(db);
|
|
10000
|
-
return `\u{1F5D1}\uFE0F **Node & relations deleted:** ${targetId}`;
|
|
10001
|
-
} catch (err) {
|
|
10002
|
-
return `\u274C Failed to delete node: ${err}`;
|
|
10003
|
-
}
|
|
10004
|
-
}
|
|
10005
|
-
const id = parseInt(targetId, 10);
|
|
10006
|
-
if (!isNaN(id)) {
|
|
10007
|
-
db.run("DELETE FROM nodes WHERE rowid = ?", [id]);
|
|
10008
|
-
flushDb2(db);
|
|
10009
|
-
return `\u{1F5D1}\uFE0F **Node #${id} deleted.**`;
|
|
10010
|
-
}
|
|
10011
|
-
}
|
|
10012
|
-
return "\u26A0\uFE0F Provide `target` (node ID) to delete.\n\nExamples:\n- `delete_node`, target: 'function::sendMessage'\n- `delete_node`, scope: 'gotcha', target: '42'";
|
|
10013
|
-
}
|
|
10014
|
-
async function handleGoalProgress(params) {
|
|
10015
|
-
const pct = params.confidence ?? 0;
|
|
10016
|
-
const ms = params.content || params.title;
|
|
10017
|
-
sessionMemory.setGoalProgress(pct, ms);
|
|
10018
|
-
const p = sessionMemory.getGoalProgress();
|
|
10019
|
-
if (!p) return "Error";
|
|
10020
|
-
const bar = "\u2588".repeat(Math.floor(p.percentage / 10)) + "\u2591".repeat(10 - Math.floor(p.percentage / 10));
|
|
10021
|
-
return `\u{1F4CA} ${bar} ${p.percentage}%${p.milestone ? " \u2014 " + p.milestone : ""}`;
|
|
10022
|
-
}
|
|
10023
8992
|
|
|
10024
8993
|
// src/tools/kumaSafetyTool.ts
|
|
10025
8994
|
init_sessionMemory();
|
|
10026
8995
|
|
|
10027
|
-
// src/engine/kumaSafetyLayer.ts
|
|
10028
|
-
init_kumaDb();
|
|
10029
|
-
init_sessionMemory();
|
|
10030
|
-
|
|
10031
|
-
// src/engine/safetyAudit.ts
|
|
10032
|
-
init_kumaDb();
|
|
10033
|
-
var SCHEMA_SQL = `
|
|
10034
|
-
CREATE TABLE IF NOT EXISTS safety_audit (
|
|
10035
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10036
|
-
timestamp INTEGER NOT NULL,
|
|
10037
|
-
tool_name TEXT NOT NULL,
|
|
10038
|
-
action TEXT NOT NULL,
|
|
10039
|
-
file_path TEXT,
|
|
10040
|
-
risk_level TEXT NOT NULL DEFAULT 'low',
|
|
10041
|
-
policy_violations INTEGER DEFAULT 0,
|
|
10042
|
-
allowed INTEGER NOT NULL DEFAULT 1,
|
|
10043
|
-
duration_ms INTEGER DEFAULT 0,
|
|
10044
|
-
metadata TEXT DEFAULT '{}'
|
|
10045
|
-
);
|
|
10046
|
-
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON safety_audit(timestamp);
|
|
10047
|
-
CREATE INDEX IF NOT EXISTS idx_audit_tool ON safety_audit(tool_name);
|
|
10048
|
-
CREATE INDEX IF NOT EXISTS idx_audit_risk ON safety_audit(risk_level);
|
|
10049
|
-
CREATE INDEX IF NOT EXISTS idx_audit_allowed ON safety_audit(allowed);
|
|
10050
|
-
`;
|
|
10051
|
-
var schemaEnsured = false;
|
|
10052
|
-
async function ensureSchema() {
|
|
10053
|
-
if (schemaEnsured) return;
|
|
10054
|
-
try {
|
|
10055
|
-
const db = await getDb();
|
|
10056
|
-
db.exec(SCHEMA_SQL);
|
|
10057
|
-
saveDb();
|
|
10058
|
-
schemaEnsured = true;
|
|
10059
|
-
} catch (err) {
|
|
10060
|
-
console.error(`[SafetyAudit] Failed to ensure schema: ${err}`);
|
|
10061
|
-
}
|
|
10062
|
-
}
|
|
10063
|
-
async function recordAudit(entry) {
|
|
10064
|
-
try {
|
|
10065
|
-
await ensureSchema();
|
|
10066
|
-
const db = await getDb();
|
|
10067
|
-
db.run(`
|
|
10068
|
-
INSERT INTO safety_audit (timestamp, tool_name, action, file_path, risk_level, policy_violations, allowed, duration_ms, metadata)
|
|
10069
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
10070
|
-
`, [
|
|
10071
|
-
entry.timestamp,
|
|
10072
|
-
entry.toolName,
|
|
10073
|
-
entry.action,
|
|
10074
|
-
entry.filePath || null,
|
|
10075
|
-
entry.riskLevel,
|
|
10076
|
-
entry.policyViolations,
|
|
10077
|
-
entry.allowed ? 1 : 0,
|
|
10078
|
-
entry.durationMs,
|
|
10079
|
-
JSON.stringify(entry.metadata || {})
|
|
10080
|
-
]);
|
|
10081
|
-
saveDb();
|
|
10082
|
-
} catch (err) {
|
|
10083
|
-
console.error(`[SafetyAudit] Failed to record audit: ${err}`);
|
|
10084
|
-
}
|
|
10085
|
-
}
|
|
10086
|
-
async function queryAudit(params) {
|
|
10087
|
-
try {
|
|
10088
|
-
await ensureSchema();
|
|
10089
|
-
const db = await getDb();
|
|
10090
|
-
const { toolName, riskLevel, allowed, limit = 20, since } = params;
|
|
10091
|
-
let sql = "SELECT * FROM safety_audit WHERE 1=1";
|
|
10092
|
-
const bindParams = [];
|
|
10093
|
-
if (toolName) {
|
|
10094
|
-
sql += " AND tool_name = ?";
|
|
10095
|
-
bindParams.push(toolName);
|
|
10096
|
-
}
|
|
10097
|
-
if (riskLevel) {
|
|
10098
|
-
sql += " AND risk_level = ?";
|
|
10099
|
-
bindParams.push(riskLevel);
|
|
10100
|
-
}
|
|
10101
|
-
if (allowed !== void 0) {
|
|
10102
|
-
sql += " AND allowed = ?";
|
|
10103
|
-
bindParams.push(allowed ? 1 : 0);
|
|
10104
|
-
}
|
|
10105
|
-
if (since) {
|
|
10106
|
-
sql += " AND timestamp >= ?";
|
|
10107
|
-
bindParams.push(since);
|
|
10108
|
-
}
|
|
10109
|
-
sql += " ORDER BY timestamp DESC LIMIT ?";
|
|
10110
|
-
bindParams.push(limit);
|
|
10111
|
-
const stmt = db.prepare(sql);
|
|
10112
|
-
stmt.bind(bindParams);
|
|
10113
|
-
const results = [];
|
|
10114
|
-
while (stmt.step()) {
|
|
10115
|
-
results.push(stmt.getAsObject());
|
|
10116
|
-
}
|
|
10117
|
-
stmt.free();
|
|
10118
|
-
if (results.length === 0) {
|
|
10119
|
-
return "\u{1F4CB} **Safety Audit** \u2014 No records found for the given filters.";
|
|
10120
|
-
}
|
|
10121
|
-
const lines = [
|
|
10122
|
-
`\u{1F4CB} **Safety Audit** \u2014 ${results.length} record(s)`,
|
|
10123
|
-
`\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`,
|
|
10124
|
-
""
|
|
10125
|
-
];
|
|
10126
|
-
for (const r of results) {
|
|
10127
|
-
const riskIcon = r.risk_level === "critical" ? "\u{1F534}" : r.risk_level === "high" ? "\u{1F7E0}" : r.risk_level === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
10128
|
-
const allowIcon = r.allowed ? "\u2705" : "\u26D4";
|
|
10129
|
-
const time = new Date(r.timestamp * 1e3).toLocaleTimeString();
|
|
10130
|
-
lines.push(`${allowIcon} ${riskIcon} **${r.tool_name}** \u2014 ${r.action}`);
|
|
10131
|
-
if (r.file_path) lines.push(` \u{1F4CD} ${r.file_path}`);
|
|
10132
|
-
lines.push(` \u{1F550} ${time} | ${r.duration_ms}ms | ${r.policy_violations} policy violation(s)`);
|
|
10133
|
-
lines.push("");
|
|
10134
|
-
}
|
|
10135
|
-
return lines.join("\n");
|
|
10136
|
-
} catch (err) {
|
|
10137
|
-
return `Error querying audit: ${err}`;
|
|
10138
|
-
}
|
|
10139
|
-
}
|
|
10140
|
-
async function auditStats() {
|
|
10141
|
-
try {
|
|
10142
|
-
await ensureSchema();
|
|
10143
|
-
const db = await getDb();
|
|
10144
|
-
const total = db.exec("SELECT COUNT(*) as c FROM safety_audit")[0]?.values[0][0] ?? 0;
|
|
10145
|
-
const blocked = db.exec("SELECT COUNT(*) as c FROM safety_audit WHERE allowed = 0")[0]?.values[0][0] ?? 0;
|
|
10146
|
-
const critical = db.exec("SELECT COUNT(*) as c FROM safety_audit WHERE risk_level IN ('high','critical')")[0]?.values[0][0] ?? 0;
|
|
10147
|
-
let topBlocked = [];
|
|
10148
|
-
try {
|
|
10149
|
-
const stmt = db.prepare("SELECT tool_name, COUNT(*) as cnt FROM safety_audit WHERE allowed = 0 GROUP BY tool_name ORDER BY cnt DESC LIMIT 5");
|
|
10150
|
-
while (stmt.step()) {
|
|
10151
|
-
topBlocked.push(stmt.getAsObject());
|
|
10152
|
-
}
|
|
10153
|
-
stmt.free();
|
|
10154
|
-
} catch {
|
|
10155
|
-
}
|
|
10156
|
-
const lines = [
|
|
10157
|
-
`\u{1F4CA} **Safety Audit Stats**`,
|
|
10158
|
-
`\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`,
|
|
10159
|
-
"",
|
|
10160
|
-
`\u{1F4DD} Total operations: ${total}`,
|
|
10161
|
-
`\u26D4 Blocked: ${blocked} (${total > 0 ? (blocked / total * 100).toFixed(1) : 0}%)`,
|
|
10162
|
-
`\u{1F534} High/Critical risk: ${critical}`,
|
|
10163
|
-
""
|
|
10164
|
-
];
|
|
10165
|
-
if (topBlocked.length > 0) {
|
|
10166
|
-
lines.push("**Most blocked tools:**");
|
|
10167
|
-
for (const t of topBlocked) {
|
|
10168
|
-
lines.push(` \u2022 ${t.tool_name}: ${t.cnt}x blocked`);
|
|
10169
|
-
}
|
|
10170
|
-
}
|
|
10171
|
-
return lines.join("\n");
|
|
10172
|
-
} catch (err) {
|
|
10173
|
-
return `Error getting audit stats: ${err}`;
|
|
10174
|
-
}
|
|
10175
|
-
}
|
|
10176
|
-
|
|
10177
|
-
// src/tools/kumaPolicy.ts
|
|
10178
|
-
init_pathValidator();
|
|
10179
|
-
import fs19 from "fs";
|
|
10180
|
-
import path20 from "path";
|
|
10181
|
-
var DEFAULT_POLICY = {
|
|
10182
|
-
never_touch: [
|
|
10183
|
-
".env",
|
|
10184
|
-
".env.local",
|
|
10185
|
-
".env.production",
|
|
10186
|
-
".env.development",
|
|
10187
|
-
"package-lock.json",
|
|
10188
|
-
"yarn.lock",
|
|
10189
|
-
"pnpm-lock.yaml",
|
|
10190
|
-
"node_modules/**"
|
|
10191
|
-
],
|
|
10192
|
-
block_commands: [
|
|
10193
|
-
"rm -rf",
|
|
10194
|
-
"rm -fr",
|
|
10195
|
-
"git push --force",
|
|
10196
|
-
"git push -f",
|
|
10197
|
-
"npm publish",
|
|
10198
|
-
"yarn publish",
|
|
10199
|
-
"pnpm publish",
|
|
10200
|
-
"curl | bash",
|
|
10201
|
-
"curl | sh"
|
|
10202
|
-
]
|
|
10203
|
-
};
|
|
10204
|
-
function loadPolicy() {
|
|
10205
|
-
const root = getProjectRoot();
|
|
10206
|
-
const ymlPath = path20.join(root, ".kuma", "policy.yml");
|
|
10207
|
-
const yamlPath = path20.join(root, ".kuma", "policy.yaml");
|
|
10208
|
-
let policyContent = null;
|
|
10209
|
-
let policyPath = null;
|
|
10210
|
-
if (fs19.existsSync(ymlPath)) {
|
|
10211
|
-
policyContent = fs19.readFileSync(ymlPath, "utf-8");
|
|
10212
|
-
policyPath = ymlPath;
|
|
10213
|
-
} else if (fs19.existsSync(yamlPath)) {
|
|
10214
|
-
policyContent = fs19.readFileSync(yamlPath, "utf-8");
|
|
10215
|
-
policyPath = yamlPath;
|
|
10216
|
-
}
|
|
10217
|
-
if (!policyContent) {
|
|
10218
|
-
return { ...DEFAULT_POLICY };
|
|
10219
|
-
}
|
|
10220
|
-
try {
|
|
10221
|
-
return parseSimpleYaml(policyContent);
|
|
10222
|
-
} catch (err) {
|
|
10223
|
-
console.error(`[Policy] Failed to parse ${policyPath}: ${err}. Using defaults.`);
|
|
10224
|
-
return { ...DEFAULT_POLICY };
|
|
10225
|
-
}
|
|
10226
|
-
}
|
|
10227
|
-
function parseSimpleYaml(content) {
|
|
10228
|
-
const policy = {};
|
|
10229
|
-
const lines = content.split("\n");
|
|
10230
|
-
let currentKey = null;
|
|
10231
|
-
let currentArray = [];
|
|
10232
|
-
for (const rawLine of lines) {
|
|
10233
|
-
const line = rawLine.trim();
|
|
10234
|
-
if (!line || line.startsWith("#")) continue;
|
|
10235
|
-
const keyMatch = line.match(/^(\w[\w_-]*):\s*(.*)/);
|
|
10236
|
-
if (keyMatch) {
|
|
10237
|
-
if (currentKey && currentArray.length > 0) {
|
|
10238
|
-
setPolicyValue(policy, currentKey, currentArray);
|
|
10239
|
-
currentArray = [];
|
|
10240
|
-
}
|
|
10241
|
-
currentKey = keyMatch[1];
|
|
10242
|
-
const value = keyMatch[2].trim();
|
|
10243
|
-
if (value === "" || value === "|") {
|
|
10244
|
-
currentArray = [];
|
|
10245
|
-
} else {
|
|
10246
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
10247
|
-
const items = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
|
|
10248
|
-
setPolicyValue(policy, currentKey, items);
|
|
10249
|
-
} else {
|
|
10250
|
-
setPolicyValue(policy, currentKey, value.replace(/^['"]|['"]$/g, ""));
|
|
10251
|
-
}
|
|
10252
|
-
currentKey = null;
|
|
10253
|
-
}
|
|
10254
|
-
} else if (currentKey && line.startsWith("- ")) {
|
|
10255
|
-
currentArray.push(line.slice(2).trim().replace(/^['"]|['"]$/g, ""));
|
|
10256
|
-
}
|
|
10257
|
-
}
|
|
10258
|
-
if (currentKey && currentArray.length > 0) {
|
|
10259
|
-
setPolicyValue(policy, currentKey, currentArray);
|
|
10260
|
-
}
|
|
10261
|
-
return policy;
|
|
10262
|
-
}
|
|
10263
|
-
function setPolicyValue(policy, key, value) {
|
|
10264
|
-
switch (key) {
|
|
10265
|
-
case "never_touch":
|
|
10266
|
-
policy.never_touch = value;
|
|
10267
|
-
break;
|
|
10268
|
-
case "require_review":
|
|
10269
|
-
policy.require_review = value;
|
|
10270
|
-
break;
|
|
10271
|
-
case "require_tests":
|
|
10272
|
-
policy.require_tests = value;
|
|
10273
|
-
break;
|
|
10274
|
-
case "max_file_size":
|
|
10275
|
-
policy.max_file_size = Number(value);
|
|
10276
|
-
break;
|
|
10277
|
-
case "block_commands":
|
|
10278
|
-
policy.block_commands = value;
|
|
10279
|
-
break;
|
|
10280
|
-
}
|
|
10281
|
-
}
|
|
10282
|
-
function matchesGlob(pattern, filePath) {
|
|
10283
|
-
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
10284
|
-
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
10285
|
-
let regexStr = "^";
|
|
10286
|
-
for (let i = 0; i < normalizedPattern.length; i++) {
|
|
10287
|
-
const ch = normalizedPattern[i];
|
|
10288
|
-
if (ch === "*" && normalizedPattern[i + 1] === "*" && normalizedPattern[i + 2] === "/") {
|
|
10289
|
-
regexStr += "(.+/)?";
|
|
10290
|
-
i += 2;
|
|
10291
|
-
} else if (ch === "*") {
|
|
10292
|
-
regexStr += "[^/]*";
|
|
10293
|
-
} else if (ch === "?") {
|
|
10294
|
-
regexStr += "[^/]";
|
|
10295
|
-
} else {
|
|
10296
|
-
regexStr += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
10297
|
-
}
|
|
10298
|
-
}
|
|
10299
|
-
regexStr += "$";
|
|
10300
|
-
try {
|
|
10301
|
-
return new RegExp(regexStr).test(normalizedPath);
|
|
10302
|
-
} catch {
|
|
10303
|
-
return false;
|
|
10304
|
-
}
|
|
10305
|
-
}
|
|
10306
|
-
function checkFilePathPolicy(filePath, policy) {
|
|
10307
|
-
const violations = [];
|
|
10308
|
-
const warnings = [];
|
|
10309
|
-
if (policy.never_touch) {
|
|
10310
|
-
for (const pattern of policy.never_touch) {
|
|
10311
|
-
if (matchesGlob(pattern, filePath)) {
|
|
10312
|
-
violations.push({
|
|
10313
|
-
rule: "never_touch",
|
|
10314
|
-
pattern,
|
|
10315
|
-
message: `File "${filePath}" matches never_touch pattern "${pattern}"`,
|
|
10316
|
-
severity: "error"
|
|
10317
|
-
});
|
|
10318
|
-
}
|
|
10319
|
-
}
|
|
10320
|
-
}
|
|
10321
|
-
if (policy.max_file_size && policy.max_file_size > 0) {
|
|
10322
|
-
try {
|
|
10323
|
-
const root = getProjectRoot();
|
|
10324
|
-
const fullPath = path20.join(root, filePath);
|
|
10325
|
-
if (fs19.existsSync(fullPath)) {
|
|
10326
|
-
const sizeKB = fs19.statSync(fullPath).size / 1024;
|
|
10327
|
-
if (sizeKB > policy.max_file_size) {
|
|
10328
|
-
violations.push({
|
|
10329
|
-
rule: "max_file_size",
|
|
10330
|
-
pattern: `${policy.max_file_size}KB`,
|
|
10331
|
-
message: `File "${filePath}" is ${Math.round(sizeKB)}KB, exceeds max_file_size of ${policy.max_file_size}KB`,
|
|
10332
|
-
severity: "error"
|
|
10333
|
-
});
|
|
10334
|
-
}
|
|
10335
|
-
}
|
|
10336
|
-
} catch {
|
|
10337
|
-
}
|
|
10338
|
-
}
|
|
10339
|
-
if (policy.require_review) {
|
|
10340
|
-
for (const pattern of policy.require_review) {
|
|
10341
|
-
if (matchesGlob(pattern, filePath)) {
|
|
10342
|
-
warnings.push({
|
|
10343
|
-
rule: "require_review",
|
|
10344
|
-
message: `File "${filePath}" requires review (matches "${pattern}")`
|
|
10345
|
-
});
|
|
10346
|
-
}
|
|
10347
|
-
}
|
|
10348
|
-
}
|
|
10349
|
-
return { violations, warnings };
|
|
10350
|
-
}
|
|
10351
|
-
|
|
10352
|
-
// src/engine/kumaSafetyProxy.ts
|
|
10353
|
-
init_pathValidator();
|
|
10354
|
-
init_sessionMemory();
|
|
10355
|
-
init_kumaDb();
|
|
10356
|
-
async function preCheck(_toolName, params, opts = {}) {
|
|
10357
|
-
const messages = [];
|
|
10358
|
-
let policyViolations = 0;
|
|
10359
|
-
let highestRisk = "low";
|
|
10360
|
-
const blockThreshold = opts.blockRiskThreshold || "high";
|
|
10361
|
-
const filePath = opts.extractFilePath?.(params);
|
|
10362
|
-
if (filePath) {
|
|
10363
|
-
const validation = validateFilePath(filePath);
|
|
10364
|
-
if (!validation.valid) {
|
|
10365
|
-
messages.push(`\u{1F6AB} Path blocked: ${validation.error.message}`);
|
|
10366
|
-
policyViolations++;
|
|
10367
|
-
highestRisk = "critical";
|
|
10368
|
-
}
|
|
10369
|
-
}
|
|
10370
|
-
const policy = loadPolicy();
|
|
10371
|
-
if (filePath) {
|
|
10372
|
-
const { violations, warnings } = checkFilePathPolicy(filePath, policy);
|
|
10373
|
-
policyViolations += violations.length;
|
|
10374
|
-
for (const v of violations) {
|
|
10375
|
-
messages.push(`\u{1F4DC} Policy violation (${v.rule}): ${v.message}`);
|
|
10376
|
-
highestRisk = "critical";
|
|
10377
|
-
}
|
|
10378
|
-
for (const w of warnings) {
|
|
10379
|
-
messages.push(`\u26A0\uFE0F Policy warning (${w.rule}): ${w.message}`);
|
|
10380
|
-
if (highestRisk === "low") highestRisk = "medium";
|
|
10381
|
-
}
|
|
10382
|
-
}
|
|
10383
|
-
const command = opts.extractCommand?.(params);
|
|
10384
|
-
if (command) {
|
|
10385
|
-
const dangerousPatterns = [
|
|
10386
|
-
"rm -rf",
|
|
10387
|
-
"rm -fr",
|
|
10388
|
-
"git push",
|
|
10389
|
-
"git commit",
|
|
10390
|
-
"npm publish",
|
|
10391
|
-
"yarn publish",
|
|
10392
|
-
"pnpm publish",
|
|
10393
|
-
"| bash",
|
|
10394
|
-
"| sh",
|
|
10395
|
-
"eval ",
|
|
10396
|
-
"exec ",
|
|
10397
|
-
"mkfs",
|
|
10398
|
-
"dd if=",
|
|
10399
|
-
"shred"
|
|
10400
|
-
];
|
|
10401
|
-
const matched = dangerousPatterns.find((p) => command.toLowerCase().includes(p));
|
|
10402
|
-
if (matched) {
|
|
10403
|
-
messages.push(`\u{1F6AB} Command blocked: contains dangerous pattern "${matched}"`);
|
|
10404
|
-
policyViolations++;
|
|
10405
|
-
highestRisk = "critical";
|
|
10406
|
-
}
|
|
10407
|
-
}
|
|
10408
|
-
try {
|
|
10409
|
-
const db = await getDb();
|
|
10410
|
-
const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
|
|
10411
|
-
const action = params.action || "";
|
|
10412
|
-
if (researchCount === 0 && (action === "edit" || action === "impact" || action === "navigate")) {
|
|
10413
|
-
messages.push("\u{1F4A1} No research cached \u2014 run kuma_context({ action: 'research', scope: '...' }) first");
|
|
10414
|
-
if (highestRisk === "low") highestRisk = "medium";
|
|
10415
|
-
}
|
|
10416
|
-
} catch {
|
|
10417
|
-
}
|
|
10418
|
-
try {
|
|
10419
|
-
const summary = sessionMemory.getSummary();
|
|
10420
|
-
const goal = summary.currentGoal;
|
|
10421
|
-
if (!goal) {
|
|
10422
|
-
messages.push("\u{1F4A1} No goal set \u2014 kuma_context({ action: 'init' }) sets one automatically");
|
|
10423
|
-
}
|
|
10424
|
-
} catch {
|
|
10425
|
-
}
|
|
10426
|
-
const riskLevel = highestRisk;
|
|
10427
|
-
const thresholdLevels = ["low", "medium", "high", "critical"];
|
|
10428
|
-
const thresholdIndex = thresholdLevels.indexOf(blockThreshold);
|
|
10429
|
-
const riskIndex = thresholdLevels.indexOf(riskLevel);
|
|
10430
|
-
const allowed = opts.blockOnViolation !== false ? riskIndex < thresholdIndex : policyViolations === 0;
|
|
10431
|
-
return { allowed, riskLevel, policyViolations, messages };
|
|
10432
|
-
}
|
|
10433
|
-
|
|
10434
|
-
// src/engine/kumaSafetyLayer.ts
|
|
10435
|
-
async function safetyCheck(action, filePath, command) {
|
|
10436
|
-
try {
|
|
10437
|
-
const checks = [];
|
|
10438
|
-
const db = await getDb();
|
|
10439
|
-
let changes = 0;
|
|
10440
|
-
try {
|
|
10441
|
-
const summary = sessionMemory.getSummary();
|
|
10442
|
-
const modified = summary.modifiedFiles?.length || 0;
|
|
10443
|
-
changes = modified;
|
|
10444
|
-
checks.push({
|
|
10445
|
-
name: "Uncommitted Changes",
|
|
10446
|
-
passed: changes < 10,
|
|
10447
|
-
detail: changes > 0 ? `\u{1F4DD} ${changes} uncommitted change(s)` : "\u2705 Clean working tree"
|
|
10448
|
-
});
|
|
10449
|
-
} catch {
|
|
10450
|
-
checks.push({ name: "Uncommitted Changes", passed: true, detail: "\u2705 Could not check" });
|
|
10451
|
-
}
|
|
10452
|
-
if (action && filePath) {
|
|
10453
|
-
const verdict = await preCheck(action, { filePath, action }, {
|
|
10454
|
-
extractFilePath: (p) => p.filePath
|
|
10455
|
-
});
|
|
10456
|
-
checks.push({
|
|
10457
|
-
name: "Policy & Path Safety",
|
|
10458
|
-
passed: verdict.allowed,
|
|
10459
|
-
detail: verdict.allowed ? `\u2705 ${verdict.policyViolations} violations` : `\u274C ${verdict.policyViolations} violation(s) \u2014 ${verdict.messages[0] || "blocked"}`
|
|
10460
|
-
});
|
|
10461
|
-
}
|
|
10462
|
-
let recentChanges = 0;
|
|
10463
|
-
try {
|
|
10464
|
-
const changeResult = db.exec("SELECT COUNT(*) as c FROM change_log WHERE created_at > strftime('%s','now','-1 hour')");
|
|
10465
|
-
recentChanges = changeResult[0]?.values[0][0] || 0;
|
|
10466
|
-
} catch {
|
|
10467
|
-
}
|
|
10468
|
-
checks.push({
|
|
10469
|
-
name: "Recent Changes",
|
|
10470
|
-
passed: recentChanges < 20,
|
|
10471
|
-
detail: recentChanges > 0 ? `\u{1F4DD} ${recentChanges} change(s) in last hour` : "\u2705 No recent changes"
|
|
10472
|
-
});
|
|
10473
|
-
try {
|
|
10474
|
-
const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] || 0;
|
|
10475
|
-
checks.push({
|
|
10476
|
-
name: "Knowledge Graph Health",
|
|
10477
|
-
passed: nodeCount > 0,
|
|
10478
|
-
detail: nodeCount > 0 ? `\u2705 ${nodeCount} nodes in graph` : "\u26A0\uFE0F Empty knowledge graph"
|
|
10479
|
-
});
|
|
10480
|
-
} catch {
|
|
10481
|
-
checks.push({ name: "Knowledge Graph Health", passed: true, detail: "\u26A0\uFE0F Could not check" });
|
|
10482
|
-
}
|
|
10483
|
-
if (filePath) {
|
|
10484
|
-
try {
|
|
10485
|
-
const fs28 = await import("fs");
|
|
10486
|
-
const path29 = await import("path");
|
|
10487
|
-
const root = process.cwd();
|
|
10488
|
-
const fullPath = path29.resolve(root, filePath);
|
|
10489
|
-
if (fs28.existsSync(fullPath)) {
|
|
10490
|
-
const stat = fs28.statSync(fullPath);
|
|
10491
|
-
const sizeKB = Math.round(stat.size / 1024);
|
|
10492
|
-
if (sizeKB > 500) {
|
|
10493
|
-
checks.push({
|
|
10494
|
-
name: "File Size Warning",
|
|
10495
|
-
passed: false,
|
|
10496
|
-
detail: `\u{1F4CF} File is ${sizeKB}KB \u2014 large file edits are risky`
|
|
10497
|
-
});
|
|
10498
|
-
}
|
|
10499
|
-
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
10500
|
-
const baseName = path29.basename(filePath, path29.extname(filePath));
|
|
10501
|
-
const testPatterns = [
|
|
10502
|
-
filePath.replace(/\.(ts|js)$/, ".test.$1"),
|
|
10503
|
-
filePath.replace(/\.(ts|js)$/, ".spec.$1"),
|
|
10504
|
-
filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
|
|
10505
|
-
`**/__tests__/**/${baseName}.test.${path29.extname(filePath).slice(1)}`
|
|
10506
|
-
];
|
|
10507
|
-
let hasTests = false;
|
|
10508
|
-
const fg = await import("fast-glob");
|
|
10509
|
-
for (const pattern of testPatterns) {
|
|
10510
|
-
const matches = await fg.default(pattern, { cwd: root, ignore: ["**/node_modules/**"] });
|
|
10511
|
-
if (matches.length > 0) {
|
|
10512
|
-
hasTests = true;
|
|
10513
|
-
break;
|
|
10514
|
-
}
|
|
10515
|
-
}
|
|
10516
|
-
if (!hasTests) {
|
|
10517
|
-
checks.push({
|
|
10518
|
-
name: "Tests Check",
|
|
10519
|
-
passed: true,
|
|
10520
|
-
// Not a blocker, just informative
|
|
10521
|
-
detail: `\u{1F9EA} No test file found for "${path29.basename(filePath)}" \u2014 consider adding tests`
|
|
10522
|
-
});
|
|
10523
|
-
} else {
|
|
10524
|
-
checks.push({
|
|
10525
|
-
name: "Tests Check",
|
|
10526
|
-
passed: true,
|
|
10527
|
-
detail: "\u2705 Test file found"
|
|
10528
|
-
});
|
|
10529
|
-
}
|
|
10530
|
-
}
|
|
10531
|
-
}
|
|
10532
|
-
} catch {
|
|
10533
|
-
}
|
|
10534
|
-
}
|
|
10535
|
-
const failed = checks.filter((c) => !c.passed);
|
|
10536
|
-
const riskLevel = failed.length >= 3 ? "critical" : failed.length >= 2 ? "high" : failed.length >= 1 ? "medium" : "low";
|
|
10537
|
-
const allowed = failed.length === 0;
|
|
10538
|
-
const recommendation = allowed ? "\u{1F7E2} All checks passed \u2014 proceed safely." : riskLevel === "critical" ? "\u{1F534} Critical \u2014 Do not proceed without resolving all issues." : riskLevel === "high" ? "\u{1F7E0} High risk \u2014 Resolve issues before proceeding." : "\u{1F7E1} Medium risk \u2014 Proceed with caution.";
|
|
10539
|
-
const result = { action, filePath, allowed, checks, riskLevel, recommendation };
|
|
10540
|
-
await recordAudit({
|
|
10541
|
-
timestamp: Math.floor(Date.now() / 1e3),
|
|
10542
|
-
toolName: "kuma_safety_check",
|
|
10543
|
-
action,
|
|
10544
|
-
filePath,
|
|
10545
|
-
riskLevel,
|
|
10546
|
-
policyViolations: failed.length,
|
|
10547
|
-
allowed,
|
|
10548
|
-
durationMs: 0,
|
|
10549
|
-
metadata: { checkType: "full" }
|
|
10550
|
-
});
|
|
10551
|
-
return formatSafetyCheck(result, command);
|
|
10552
|
-
} catch (err) {
|
|
10553
|
-
return `Error in safety check: ${err}`;
|
|
10554
|
-
}
|
|
10555
|
-
}
|
|
10556
|
-
function formatSafetyCheck(r, command) {
|
|
10557
|
-
const emoji = r.riskLevel === "critical" ? "\u{1F534}" : r.riskLevel === "high" ? "\u{1F7E0}" : r.riskLevel === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
10558
|
-
const lines = [
|
|
10559
|
-
`${emoji} **Safety AI Layer** \u2014 ${r.allowed ? "\u2705 Allowed" : "\u26D4 Blocked"}`,
|
|
10560
|
-
`\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`,
|
|
10561
|
-
"",
|
|
10562
|
-
`\u{1F3AF} Action: \`${r.action}\``,
|
|
10563
|
-
r.filePath ? `\u{1F4C4} File: \`${r.filePath}\`` : "",
|
|
10564
|
-
command ? `\u{1F4BB} Command: \`${command}\`` : "",
|
|
10565
|
-
`\u26A0\uFE0F Risk Level: **${r.riskLevel.toUpperCase()}**`,
|
|
10566
|
-
"",
|
|
10567
|
-
r.recommendation,
|
|
10568
|
-
"",
|
|
10569
|
-
"**Checks:**"
|
|
10570
|
-
].filter(Boolean);
|
|
10571
|
-
for (const c of r.checks) {
|
|
10572
|
-
lines.push(` ${c.passed ? "\u2705" : "\u274C"} **${c.name}**: ${c.detail}`);
|
|
10573
|
-
}
|
|
10574
|
-
return lines.join("\n");
|
|
10575
|
-
}
|
|
10576
|
-
|
|
10577
|
-
// src/tools/kumaSafetyTool.ts
|
|
10578
|
-
init_kumaDb();
|
|
10579
|
-
|
|
10580
8996
|
// src/tools/kumaGuard.ts
|
|
10581
8997
|
init_sessionMemory();
|
|
10582
8998
|
|
|
@@ -10584,9 +9000,9 @@ init_sessionMemory();
|
|
|
10584
9000
|
init_pathValidator();
|
|
10585
9001
|
init_sessionMemory();
|
|
10586
9002
|
init_kumaShared();
|
|
10587
|
-
import
|
|
10588
|
-
import
|
|
10589
|
-
import { execSync as
|
|
9003
|
+
import fs17 from "fs";
|
|
9004
|
+
import path18 from "path";
|
|
9005
|
+
import { execSync as execSync4 } from "child_process";
|
|
10590
9006
|
var SCRIPT_PATCH_PATTERNS = [
|
|
10591
9007
|
"writeFileSync",
|
|
10592
9008
|
"writeFile",
|
|
@@ -10612,20 +9028,20 @@ function findPatchScripts(projectRoot) {
|
|
|
10612
9028
|
const warnings = [];
|
|
10613
9029
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
10614
9030
|
for (const file of recentFiles) {
|
|
10615
|
-
const ext =
|
|
9031
|
+
const ext = path18.extname(file).toLowerCase();
|
|
10616
9032
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
10617
|
-
const relativePath =
|
|
9033
|
+
const relativePath = path18.relative(projectRoot, file);
|
|
10618
9034
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
|
|
10619
9035
|
continue;
|
|
10620
9036
|
}
|
|
10621
9037
|
try {
|
|
10622
|
-
const content =
|
|
9038
|
+
const content = fs17.readFileSync(file, "utf-8").toLowerCase();
|
|
10623
9039
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
10624
9040
|
if (matchedPattern) {
|
|
10625
9041
|
warnings.push({
|
|
10626
9042
|
severity: "high",
|
|
10627
9043
|
pattern: "script-patching",
|
|
10628
|
-
message: `Created script file that modifies other files: ${
|
|
9044
|
+
message: `Created script file that modifies other files: ${path18.basename(file)}`,
|
|
10629
9045
|
suggestion: "Use your native edit tools instead \u2014 targeted edits with auto-backup and rollback",
|
|
10630
9046
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
10631
9047
|
filePath: relativePath
|
|
@@ -10659,15 +9075,15 @@ function detectBashGrepUsage() {
|
|
|
10659
9075
|
function scanRecentFiles(projectRoot) {
|
|
10660
9076
|
const recent = [];
|
|
10661
9077
|
try {
|
|
10662
|
-
const entries =
|
|
9078
|
+
const entries = fs17.readdirSync(projectRoot, { withFileTypes: true });
|
|
10663
9079
|
const now = Date.now();
|
|
10664
9080
|
const recentThreshold = 30 * 60 * 1e3;
|
|
10665
9081
|
for (const entry of entries) {
|
|
10666
9082
|
if (!entry.isFile()) continue;
|
|
10667
9083
|
try {
|
|
10668
|
-
const stat =
|
|
9084
|
+
const stat = fs17.statSync(path18.join(projectRoot, entry.name));
|
|
10669
9085
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
10670
|
-
recent.push(
|
|
9086
|
+
recent.push(path18.join(projectRoot, entry.name));
|
|
10671
9087
|
}
|
|
10672
9088
|
} catch {
|
|
10673
9089
|
continue;
|
|
@@ -10680,7 +9096,7 @@ function scanRecentFiles(projectRoot) {
|
|
|
10680
9096
|
function detectGitPatchScripts(projectRoot) {
|
|
10681
9097
|
const warnings = [];
|
|
10682
9098
|
try {
|
|
10683
|
-
const statusStdout =
|
|
9099
|
+
const statusStdout = execSync4("git status --porcelain", {
|
|
10684
9100
|
cwd: projectRoot,
|
|
10685
9101
|
encoding: "utf-8",
|
|
10686
9102
|
timeout: 3e3
|
|
@@ -10691,14 +9107,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
10691
9107
|
const prefix = line.substring(0, 2);
|
|
10692
9108
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
10693
9109
|
const file = line.substring(3).trim();
|
|
10694
|
-
const ext =
|
|
9110
|
+
const ext = path18.extname(file).toLowerCase();
|
|
10695
9111
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
10696
9112
|
const isRootLevel = !file.includes("/");
|
|
10697
9113
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
10698
9114
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
10699
|
-
const fullPath =
|
|
10700
|
-
if (
|
|
10701
|
-
const content =
|
|
9115
|
+
const fullPath = path18.join(projectRoot, file);
|
|
9116
|
+
if (fs17.existsSync(fullPath)) {
|
|
9117
|
+
const content = fs17.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
10702
9118
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
10703
9119
|
if (matchedPattern) {
|
|
10704
9120
|
warnings.push({
|
|
@@ -10713,7 +9129,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
10713
9129
|
}
|
|
10714
9130
|
}
|
|
10715
9131
|
}
|
|
10716
|
-
const deletedStdout =
|
|
9132
|
+
const deletedStdout = execSync4("git diff --name-only --diff-filter=D HEAD", {
|
|
10717
9133
|
cwd: projectRoot,
|
|
10718
9134
|
encoding: "utf-8",
|
|
10719
9135
|
timeout: 3e3
|
|
@@ -10721,7 +9137,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
10721
9137
|
if (deletedStdout) {
|
|
10722
9138
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
10723
9139
|
for (const file of deletedFiles) {
|
|
10724
|
-
const ext =
|
|
9140
|
+
const ext = path18.extname(file).toLowerCase();
|
|
10725
9141
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
10726
9142
|
warnings.push({
|
|
10727
9143
|
severity: "high",
|
|
@@ -10783,21 +9199,21 @@ function detectAllAntiPatterns() {
|
|
|
10783
9199
|
// src/engine/contextSnapshot.ts
|
|
10784
9200
|
init_sessionMemory();
|
|
10785
9201
|
init_pathValidator();
|
|
10786
|
-
import
|
|
10787
|
-
import
|
|
10788
|
-
import { execSync as
|
|
9202
|
+
import fs18 from "fs";
|
|
9203
|
+
import path19 from "path";
|
|
9204
|
+
import { execSync as execSync5 } from "child_process";
|
|
10789
9205
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
10790
9206
|
function snapshotDir() {
|
|
10791
|
-
return
|
|
9207
|
+
return path19.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
10792
9208
|
}
|
|
10793
9209
|
function ensureSnapshotDir() {
|
|
10794
9210
|
const dir = snapshotDir();
|
|
10795
|
-
if (!
|
|
10796
|
-
|
|
9211
|
+
if (!fs18.existsSync(dir)) {
|
|
9212
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
10797
9213
|
}
|
|
10798
9214
|
}
|
|
10799
9215
|
function snapshotFilePath(timestamp) {
|
|
10800
|
-
return
|
|
9216
|
+
return path19.join(snapshotDir(), `${timestamp}.json`);
|
|
10801
9217
|
}
|
|
10802
9218
|
function saveSnapshot(goal) {
|
|
10803
9219
|
try {
|
|
@@ -10818,7 +9234,7 @@ function saveSnapshot(goal) {
|
|
|
10818
9234
|
hasConventions: !!summary.hasConventions
|
|
10819
9235
|
};
|
|
10820
9236
|
try {
|
|
10821
|
-
|
|
9237
|
+
fs18.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
10822
9238
|
} catch {
|
|
10823
9239
|
}
|
|
10824
9240
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -10857,7 +9273,7 @@ function formatSnapshot(snapshot) {
|
|
|
10857
9273
|
function getGitDiffStat2() {
|
|
10858
9274
|
try {
|
|
10859
9275
|
const root = getProjectRoot();
|
|
10860
|
-
const stdout =
|
|
9276
|
+
const stdout = execSync5("git diff --stat", {
|
|
10861
9277
|
cwd: root,
|
|
10862
9278
|
encoding: "utf-8",
|
|
10863
9279
|
timeout: 3e3
|
|
@@ -11033,27 +9449,12 @@ async function handleSafety(params) {
|
|
|
11033
9449
|
return handleGuard(params);
|
|
11034
9450
|
case "verify":
|
|
11035
9451
|
return handleVerify(params);
|
|
11036
|
-
case "check":
|
|
11037
|
-
return handleCheck(params);
|
|
11038
|
-
case "audit":
|
|
11039
|
-
return handleAudit(params);
|
|
11040
|
-
case "security":
|
|
11041
|
-
return handleSecurity(params);
|
|
11042
|
-
case "gc":
|
|
11043
|
-
return handleGc(params);
|
|
11044
|
-
case "ast":
|
|
11045
|
-
case "validate":
|
|
11046
|
-
return handleAstValidation(params);
|
|
11047
9452
|
case "checkpoint":
|
|
11048
9453
|
return handleCheckpoint(params);
|
|
11049
9454
|
case "rollback_label":
|
|
11050
9455
|
return handleRollbackLabel(params);
|
|
11051
|
-
case "checkpoint_list":
|
|
11052
|
-
return handleCheckpointList(params);
|
|
11053
|
-
case "gotcha_staleness":
|
|
11054
|
-
return handleGotchaStaleness(params);
|
|
11055
9456
|
default:
|
|
11056
|
-
return `Unknown action "${action}"
|
|
9457
|
+
return `Unknown action "${action}". Use: guard, verify, checkpoint, rollback_label`;
|
|
11057
9458
|
}
|
|
11058
9459
|
}
|
|
11059
9460
|
async function handleGuard(params) {
|
|
@@ -11062,73 +9463,6 @@ async function handleGuard(params) {
|
|
|
11062
9463
|
check: params.guardCheck || "all"
|
|
11063
9464
|
});
|
|
11064
9465
|
}
|
|
11065
|
-
async function handleCheck(params) {
|
|
11066
|
-
return await safetyCheck("check", params.filePath, params.command);
|
|
11067
|
-
}
|
|
11068
|
-
async function handleAudit(params) {
|
|
11069
|
-
if (params.limit === 0) return await auditStats();
|
|
11070
|
-
return await queryAudit({
|
|
11071
|
-
toolName: params.toolName,
|
|
11072
|
-
riskLevel: params.riskLevel,
|
|
11073
|
-
allowed: params.allowed,
|
|
11074
|
-
limit: params.limit || 20,
|
|
11075
|
-
since: params.since
|
|
11076
|
-
});
|
|
11077
|
-
}
|
|
11078
|
-
async function handleSecurity(params) {
|
|
11079
|
-
sessionMemory.recordToolCall("kuma_safety_security", {});
|
|
11080
|
-
if (params.filePath) {
|
|
11081
|
-
try {
|
|
11082
|
-
const fs28 = await import("fs");
|
|
11083
|
-
const path29 = await import("path");
|
|
11084
|
-
const root = process.cwd();
|
|
11085
|
-
const fullPath = path29.resolve(root, params.filePath);
|
|
11086
|
-
if (fs28.existsSync(fullPath) && fs28.statSync(fullPath).isFile()) {
|
|
11087
|
-
const content = fs28.readFileSync(fullPath, "utf-8");
|
|
11088
|
-
const lines = content.split("\n");
|
|
11089
|
-
const findings = [];
|
|
11090
|
-
for (let i = 0; i < lines.length; i++) {
|
|
11091
|
-
const line = lines[i];
|
|
11092
|
-
const lc = line.toLowerCase();
|
|
11093
|
-
if (lc.includes("credentials: true")) {
|
|
11094
|
-
findings.push({ line: i + 1, pattern: "credentials-leak", severity: "high", message: "`credentials: true` in Prisma select may leak sensitive fields" });
|
|
11095
|
-
}
|
|
11096
|
-
if (lc.includes("console.log") && (lc.includes("token") || lc.includes("secret") || lc.includes("key") || lc.includes("password"))) {
|
|
11097
|
-
findings.push({ line: i + 1, pattern: "console-log-secret", severity: "high", message: "Potential secret logged to console" });
|
|
11098
|
-
}
|
|
11099
|
-
if (lc.includes("process.env") && (lc.includes("api_key") || lc.includes("secret") || lc.includes("token"))) {
|
|
11100
|
-
findings.push({ line: i + 1, pattern: "env-in-code", severity: "medium", message: "process.env.KEY referenced in code \u2014 ensure not in client bundle" });
|
|
11101
|
-
}
|
|
11102
|
-
}
|
|
11103
|
-
for (const f of findings) {
|
|
11104
|
-
await addSecurityFinding({ filePath: params.filePath, lineNumber: f.line, pattern: f.pattern, severity: f.severity, message: f.message });
|
|
11105
|
-
}
|
|
11106
|
-
if (findings.length === 0) return `\u{1F512} No security issues found in "${params.filePath}".`;
|
|
11107
|
-
return `\u{1F512} **Security Scan** \u2014 ${findings.length} finding(s) in "${params.filePath}":
|
|
11108
|
-
${findings.map((f) => ` ${f.severity === "high" ? "\u{1F534}" : "\u{1F7E1}"} L${f.line}: ${f.message}`).join("\n")}`;
|
|
11109
|
-
}
|
|
11110
|
-
} catch {
|
|
11111
|
-
}
|
|
11112
|
-
}
|
|
11113
|
-
return await getSecurityFindings();
|
|
11114
|
-
}
|
|
11115
|
-
async function handleGc(_params) {
|
|
11116
|
-
sessionMemory.recordToolCall("kuma_safety_gc", {});
|
|
11117
|
-
return await runGarbageCollection();
|
|
11118
|
-
}
|
|
11119
|
-
async function handleAstValidation(params) {
|
|
11120
|
-
sessionMemory.recordToolCall("kuma_safety_ast", { scope: params.scope });
|
|
11121
|
-
const { validateCodeContent: validateCodeContent2, validateFile: validateFile2, formatValidationFindings: formatValidationFindings2 } = await Promise.resolve().then(() => (init_kumaAstValidator(), kumaAstValidator_exports));
|
|
11122
|
-
if (params.command) {
|
|
11123
|
-
const findings = validateCodeContent2(params.command, params.scope);
|
|
11124
|
-
return formatValidationFindings2(findings, params.scope);
|
|
11125
|
-
}
|
|
11126
|
-
if (params.scope) {
|
|
11127
|
-
const findings = validateFile2(params.scope);
|
|
11128
|
-
return formatValidationFindings2(findings, params.scope);
|
|
11129
|
-
}
|
|
11130
|
-
return "\u26A0\uFE0F Provide a scope (file path) or command (code content) to validate.";
|
|
11131
|
-
}
|
|
11132
9466
|
async function handleCheckpoint(params) {
|
|
11133
9467
|
sessionMemory.recordToolCall("kuma_safety_checkpoint", { label: params.label });
|
|
11134
9468
|
if (!params.label) return "\u26A0\uFE0F label parameter required. Example: kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' })";
|
|
@@ -11141,11 +9475,6 @@ async function handleRollbackLabel(params) {
|
|
|
11141
9475
|
const { rollbackToCheckpoint: rollbackToCheckpoint2 } = await Promise.resolve().then(() => (init_kumaCheckpoint(), kumaCheckpoint_exports));
|
|
11142
9476
|
return await rollbackToCheckpoint2(params.label);
|
|
11143
9477
|
}
|
|
11144
|
-
async function handleCheckpointList(_params) {
|
|
11145
|
-
sessionMemory.recordToolCall("kuma_safety_checkpoint_list", {});
|
|
11146
|
-
const { listCheckpoints: listCheckpoints2 } = await Promise.resolve().then(() => (init_kumaCheckpoint(), kumaCheckpoint_exports));
|
|
11147
|
-
return listCheckpoints2();
|
|
11148
|
-
}
|
|
11149
9478
|
var _lastVerifyCall = 0;
|
|
11150
9479
|
var VERIFY_COOLDOWN_MS = 3e4;
|
|
11151
9480
|
async function handleVerify(params) {
|
|
@@ -11171,20 +9500,15 @@ async function handleVerify(params) {
|
|
|
11171
9500
|
|
|
11172
9501
|
\u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
|
|
11173
9502
|
}
|
|
11174
|
-
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope
|
|
9503
|
+
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope });
|
|
11175
9504
|
const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
|
|
11176
9505
|
const verifyResult = await runAutoVerification2({
|
|
11177
|
-
scope: params.scope
|
|
9506
|
+
scope: params.scope,
|
|
11178
9507
|
force: params.force,
|
|
11179
9508
|
timeoutMs: 3e4
|
|
11180
9509
|
});
|
|
11181
9510
|
return verifyResult + recordingWarning;
|
|
11182
9511
|
}
|
|
11183
|
-
async function handleGotchaStaleness(_params) {
|
|
11184
|
-
const { verifyGotchaStaleness: verifyGotchaStaleness2, formatGotchaStalenessReport: formatGotchaStalenessReport2 } = await Promise.resolve().then(() => (init_kumaSelfHeal(), kumaSelfHeal_exports));
|
|
11185
|
-
const stale = await verifyGotchaStaleness2();
|
|
11186
|
-
return formatGotchaStalenessReport2(stale);
|
|
11187
|
-
}
|
|
11188
9512
|
|
|
11189
9513
|
// src/engine/kumaAutoInit.ts
|
|
11190
9514
|
init_sessionMemory();
|
|
@@ -11249,15 +9573,14 @@ var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
|
|
|
11249
9573
|
function registerAllTools(server) {
|
|
11250
9574
|
server.tool(
|
|
11251
9575
|
"kuma_context",
|
|
11252
|
-
"Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way)
|
|
9576
|
+
"Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow).",
|
|
11253
9577
|
{
|
|
11254
|
-
action: z.enum(["init", "research", "
|
|
11255
|
-
"init=project brief + session restore
|
|
9578
|
+
action: z.enum(["init", "research", "history", "flow"]).describe(
|
|
9579
|
+
"init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow"
|
|
11256
9580
|
),
|
|
11257
9581
|
scope: z.string().optional().describe("Research scope for research action"),
|
|
11258
|
-
target: z.string().optional().describe("Target symbol/file for
|
|
11259
|
-
goal: z.string().optional().describe("Current goal (for init)")
|
|
11260
|
-
since: z.number().optional().describe("Unix timestamp filter for changes")
|
|
9582
|
+
target: z.string().optional().describe("Target symbol/file for history/flow"),
|
|
9583
|
+
goal: z.string().optional().describe("Current goal (for init)")
|
|
11261
9584
|
},
|
|
11262
9585
|
async (params) => {
|
|
11263
9586
|
try {
|
|
@@ -11266,8 +9589,7 @@ function registerAllTools(server) {
|
|
|
11266
9589
|
action: params.action,
|
|
11267
9590
|
scope: params.scope,
|
|
11268
9591
|
target: params.target,
|
|
11269
|
-
goal: params.goal
|
|
11270
|
-
since: params.since
|
|
9592
|
+
goal: params.goal
|
|
11271
9593
|
});
|
|
11272
9594
|
return { content: [{ type: "text", text }] };
|
|
11273
9595
|
} catch (err) {
|
|
@@ -11277,28 +9599,24 @@ function registerAllTools(server) {
|
|
|
11277
9599
|
);
|
|
11278
9600
|
server.tool(
|
|
11279
9601
|
"kuma_memory",
|
|
11280
|
-
"Persistent knowledge. Record what matters, skip what doesn't. This is what saves time in future sessions. Don't record what grep/glob answers faster (functions, imports, types, components)." + CORE_NOTE + "`gotcha` (IMMEDIATELY when you find a bug/quirk), `decision` (when choosing between options), `arch_flow` (after tracing a complete flow, max 5 core files), `research_save` (after exploring an area)
|
|
9602
|
+
"Persistent knowledge. Record what matters, skip what doesn't. This is what saves time in future sessions. Don't record what grep/glob answers faster (functions, imports, types, components)." + CORE_NOTE + "`gotcha` (IMMEDIATELY when you find a bug/quirk), `decision` (when choosing between options), `arch_flow` (after tracing a complete flow, max 5 core files), `research_save` (after exploring an area), `search` (quick lookup of memory + knowledge graph).",
|
|
11281
9603
|
{
|
|
11282
|
-
action: z.enum(["
|
|
11283
|
-
"gotcha=record bug/quirk
|
|
9604
|
+
action: z.enum(["gotcha", "decision", "arch_flow", "research_save", "search"]).describe(
|
|
9605
|
+
"gotcha=record bug/quirk, decision=ADR, arch_flow=record architecture flow, research_save=save findings, search=quick memory+graph lookup"
|
|
11284
9606
|
),
|
|
11285
|
-
scope: z.string().optional().describe("
|
|
11286
|
-
trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha
|
|
9607
|
+
scope: z.string().optional().describe("File path for gotcha \u2014 or scope for research_save/search"),
|
|
9608
|
+
trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha"),
|
|
11287
9609
|
query: z.string().optional().describe("Search query for search action"),
|
|
11288
9610
|
content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
|
|
11289
9611
|
record: z.string().optional().describe("JSON record string for research_save"),
|
|
11290
9612
|
confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
|
|
11291
|
-
confirm: z.boolean().optional().describe("Confirm and record candidates automatically for mine action"),
|
|
11292
9613
|
title: z.string().optional().describe("Decision title (required for decision)"),
|
|
11293
9614
|
context: z.string().optional().describe("Decision context"),
|
|
11294
9615
|
rationale: z.string().optional().describe("Decision rationale (required for decision)"),
|
|
11295
9616
|
outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
|
|
11296
9617
|
status: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
|
|
11297
9618
|
description: z.string().optional().describe("Gotcha workaround"),
|
|
11298
|
-
|
|
11299
|
-
limit: z.number().min(1).max(100).optional().describe("Result limit for search/mine"),
|
|
11300
|
-
target: z.string().optional().describe("File path for changes / node ID for delete_node"),
|
|
11301
|
-
since: z.number().optional().describe("Timestamp filter for changes/mine")
|
|
9619
|
+
limit: z.number().min(1).max(100).optional().describe("Result limit for search")
|
|
11302
9620
|
},
|
|
11303
9621
|
async (params) => {
|
|
11304
9622
|
try {
|
|
@@ -11310,15 +9628,11 @@ function registerAllTools(server) {
|
|
|
11310
9628
|
content: params.content,
|
|
11311
9629
|
record: params.record,
|
|
11312
9630
|
confidence: params.confidence,
|
|
11313
|
-
confirm: params.confirm,
|
|
11314
9631
|
title: params.title,
|
|
11315
9632
|
context: params.context,
|
|
11316
9633
|
rationale: params.rationale,
|
|
11317
9634
|
outcome: params.outcome,
|
|
11318
|
-
topic: params.topic,
|
|
11319
9635
|
limit: params.limit,
|
|
11320
|
-
target: params.target,
|
|
11321
|
-
since: params.since,
|
|
11322
9636
|
status: params.status,
|
|
11323
9637
|
description: params.description,
|
|
11324
9638
|
trigger_command: params.trigger_command
|
|
@@ -11331,28 +9645,16 @@ function registerAllTools(server) {
|
|
|
11331
9645
|
);
|
|
11332
9646
|
server.tool(
|
|
11333
9647
|
"kuma_safety",
|
|
11334
|
-
"Safety & verification. Use at task boundaries, not on every edit." + CORE_NOTE + "`guard` (before risky work: anti-patterns, drift, loops), `verify` (after edits: scoped tests + validation)
|
|
9648
|
+
"Safety & verification. Use at task boundaries, not on every edit." + CORE_NOTE + "`guard` (before risky work: anti-patterns, drift, loops), `verify` (after edits: scoped tests + validation), `checkpoint` + `rollback_label` (the one rollback mechanism: snapshot before risky work, restore after).",
|
|
11335
9649
|
{
|
|
11336
|
-
action: z.enum(["guard", "verify", "
|
|
11337
|
-
"guard=anti-patterns/drift/loops before risky work
|
|
9650
|
+
action: z.enum(["guard", "verify", "checkpoint", "rollback_label"]).describe(
|
|
9651
|
+
"guard=anti-patterns/drift/loops before risky work, verify=scoped tests after edits, checkpoint=labeled snapshot before risky work, rollback_label=restore a labeled snapshot"
|
|
11338
9652
|
),
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
// Guard params
|
|
9653
|
+
scope: z.string().optional().describe("Scope for verify (e.g. 'auth', file path)"),
|
|
9654
|
+
force: z.boolean().optional().describe("Force re-run even if cache is fresh (verify)"),
|
|
11342
9655
|
guardGoal: z.string().optional().describe("Goal for guard check"),
|
|
11343
9656
|
guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
|
|
11344
|
-
|
|
11345
|
-
filePath: z.string().optional().describe("File path for check/security scan"),
|
|
11346
|
-
command: z.string().optional().describe("Command for check"),
|
|
11347
|
-
// Audit params
|
|
11348
|
-
toolName: z.string().optional().describe("Filter audit by tool name"),
|
|
11349
|
-
riskLevel: z.string().optional().describe("Filter audit by risk level"),
|
|
11350
|
-
allowed: z.boolean().optional().describe("Filter audit by allowed/blocked"),
|
|
11351
|
-
limit: z.number().min(1).max(100).optional().describe("Audit result limit"),
|
|
11352
|
-
since: z.number().optional().describe("Timestamp filter for audit"),
|
|
11353
|
-
force: z.boolean().optional().describe("Force re-run even if cache is fresh (verify)"),
|
|
11354
|
-
// Checkpoint params
|
|
11355
|
-
label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label actions"),
|
|
9657
|
+
label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label"),
|
|
11356
9658
|
description: z.string().optional().describe("Description for checkpoint")
|
|
11357
9659
|
},
|
|
11358
9660
|
async (params) => {
|
|
@@ -11362,14 +9664,7 @@ function registerAllTools(server) {
|
|
|
11362
9664
|
action: params.action,
|
|
11363
9665
|
guardGoal: params.guardGoal,
|
|
11364
9666
|
guardCheck: params.guardCheck,
|
|
11365
|
-
filePath: params.filePath,
|
|
11366
9667
|
scope: params.scope,
|
|
11367
|
-
command: params.command,
|
|
11368
|
-
toolName: params.toolName,
|
|
11369
|
-
riskLevel: params.riskLevel,
|
|
11370
|
-
allowed: params.allowed,
|
|
11371
|
-
limit: params.limit,
|
|
11372
|
-
since: params.since,
|
|
11373
9668
|
force: params.force,
|
|
11374
9669
|
label: params.label,
|
|
11375
9670
|
description: params.description
|
|
@@ -11380,7 +9675,7 @@ function registerAllTools(server) {
|
|
|
11380
9675
|
}
|
|
11381
9676
|
}
|
|
11382
9677
|
);
|
|
11383
|
-
console.error("[Manifest] Registered 3 coarse-grained tools (
|
|
9678
|
+
console.error("[Manifest] Registered 3 coarse-grained tools with 13 core actions (init/research/history/flow \xB7 gotcha/decision/arch_flow/research_save/search \xB7 guard/verify/checkpoint/rollback_label).");
|
|
11384
9679
|
console.error("[Manifest] Auto-init hooks installed on all tools.");
|
|
11385
9680
|
console.error("[Manifest] Namespace aliases active (kuma_kuma_* \u2192 kuma_*, context \u2192 kuma_context).");
|
|
11386
9681
|
}
|
|
@@ -11459,14 +9754,14 @@ async function main() {
|
|
|
11459
9754
|
} catch {
|
|
11460
9755
|
}
|
|
11461
9756
|
try {
|
|
11462
|
-
const
|
|
11463
|
-
const
|
|
11464
|
-
const lockDir =
|
|
11465
|
-
if (
|
|
11466
|
-
const pidFile =
|
|
11467
|
-
if (
|
|
9757
|
+
const path25 = await import("path");
|
|
9758
|
+
const fs24 = await import("fs");
|
|
9759
|
+
const lockDir = path25.resolve(process.cwd(), ".kuma/verifier.lock");
|
|
9760
|
+
if (fs24.existsSync(lockDir)) {
|
|
9761
|
+
const pidFile = path25.join(lockDir, "pid");
|
|
9762
|
+
if (fs24.existsSync(pidFile)) {
|
|
11468
9763
|
try {
|
|
11469
|
-
const pid = parseInt(
|
|
9764
|
+
const pid = parseInt(fs24.readFileSync(pidFile, "utf-8"), 10);
|
|
11470
9765
|
if (pid && pid !== process.pid) {
|
|
11471
9766
|
try {
|
|
11472
9767
|
process.kill(-pid, "SIGKILL");
|
|
@@ -11482,7 +9777,7 @@ async function main() {
|
|
|
11482
9777
|
} catch {
|
|
11483
9778
|
}
|
|
11484
9779
|
}
|
|
11485
|
-
|
|
9780
|
+
fs24.rmSync(lockDir, { recursive: true, force: true });
|
|
11486
9781
|
}
|
|
11487
9782
|
if (killedCount === 0) {
|
|
11488
9783
|
console.error("\u2705 No running verifications found to kill.");
|
|
@@ -11490,25 +9785,25 @@ async function main() {
|
|
|
11490
9785
|
} catch {
|
|
11491
9786
|
}
|
|
11492
9787
|
try {
|
|
11493
|
-
const { execSync:
|
|
9788
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
11494
9789
|
try {
|
|
11495
|
-
|
|
9790
|
+
execSync6("pkill -f 'jest' 2>/dev/null || true");
|
|
11496
9791
|
} catch {
|
|
11497
9792
|
}
|
|
11498
9793
|
try {
|
|
11499
|
-
|
|
9794
|
+
execSync6("pkill -f 'jest-worker' 2>/dev/null || true");
|
|
11500
9795
|
} catch {
|
|
11501
9796
|
}
|
|
11502
9797
|
try {
|
|
11503
|
-
|
|
9798
|
+
execSync6("pkill -f 'pnpm test' 2>/dev/null || true");
|
|
11504
9799
|
} catch {
|
|
11505
9800
|
}
|
|
11506
9801
|
try {
|
|
11507
|
-
|
|
9802
|
+
execSync6("pkill -f 'npm test' 2>/dev/null || true");
|
|
11508
9803
|
} catch {
|
|
11509
9804
|
}
|
|
11510
9805
|
try {
|
|
11511
|
-
|
|
9806
|
+
execSync6("pkill -f 'yarn test' 2>/dev/null || true");
|
|
11512
9807
|
} catch {
|
|
11513
9808
|
}
|
|
11514
9809
|
console.error("\u2705 Killed orphaned test processes");
|
|
@@ -11681,23 +9976,23 @@ async function main() {
|
|
|
11681
9976
|
});
|
|
11682
9977
|
const output = formatInitResults(results);
|
|
11683
9978
|
console.log(output);
|
|
11684
|
-
const
|
|
11685
|
-
const
|
|
11686
|
-
const matchaSkills =
|
|
11687
|
-
const matchaAgents =
|
|
9979
|
+
const fs24 = await import("fs");
|
|
9980
|
+
const path25 = await import("path");
|
|
9981
|
+
const matchaSkills = path25.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
9982
|
+
const matchaAgents = path25.resolve(
|
|
11688
9983
|
process.cwd(),
|
|
11689
9984
|
".agents/skills/matcha/SKILL.md"
|
|
11690
9985
|
);
|
|
11691
|
-
const matchaRootSkills =
|
|
9986
|
+
const matchaRootSkills = path25.resolve(
|
|
11692
9987
|
process.cwd(),
|
|
11693
9988
|
"skills/matcha/SKILL.md"
|
|
11694
9989
|
);
|
|
11695
|
-
const matchaAgentsMd =
|
|
11696
|
-
const matchaWindsurfRules =
|
|
9990
|
+
const matchaAgentsMd = path25.resolve(process.cwd(), "AGENTS.md");
|
|
9991
|
+
const matchaWindsurfRules = path25.resolve(
|
|
11697
9992
|
process.cwd(),
|
|
11698
9993
|
".windsurf"
|
|
11699
9994
|
);
|
|
11700
|
-
if (
|
|
9995
|
+
if (fs24.existsSync(matchaSkills) || fs24.existsSync(matchaAgents) || fs24.existsSync(matchaRootSkills) || fs24.existsSync(matchaAgentsMd) || fs24.existsSync(matchaWindsurfRules)) {
|
|
11701
9996
|
console.error(
|
|
11702
9997
|
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
11703
9998
|
);
|
|
@@ -11711,13 +10006,13 @@ async function main() {
|
|
|
11711
10006
|
(async () => {
|
|
11712
10007
|
try {
|
|
11713
10008
|
const { generateInitMdContent: generateInitMdContent2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
11714
|
-
const
|
|
11715
|
-
const
|
|
11716
|
-
const initMdPath =
|
|
11717
|
-
if (!
|
|
11718
|
-
const kumaDir =
|
|
11719
|
-
if (!
|
|
11720
|
-
|
|
10009
|
+
const fs24 = await import("fs");
|
|
10010
|
+
const path25 = await import("path");
|
|
10011
|
+
const initMdPath = path25.resolve(process.cwd(), ".kuma/init.md");
|
|
10012
|
+
if (!fs24.existsSync(initMdPath)) {
|
|
10013
|
+
const kumaDir = path25.dirname(initMdPath);
|
|
10014
|
+
if (!fs24.existsSync(kumaDir)) fs24.mkdirSync(kumaDir, { recursive: true });
|
|
10015
|
+
fs24.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
|
|
11721
10016
|
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
11722
10017
|
}
|
|
11723
10018
|
} catch (err) {
|
|
@@ -11728,8 +10023,8 @@ async function main() {
|
|
|
11728
10023
|
try {
|
|
11729
10024
|
const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
|
|
11730
10025
|
const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
|
|
11731
|
-
const
|
|
11732
|
-
const
|
|
10026
|
+
const fs24 = await import("fs");
|
|
10027
|
+
const path25 = await import("path");
|
|
11733
10028
|
const detection = detectAgent2();
|
|
11734
10029
|
if (!detection.primary) {
|
|
11735
10030
|
console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
|
|
@@ -11737,22 +10032,22 @@ async function main() {
|
|
|
11737
10032
|
}
|
|
11738
10033
|
const agentType = detection.primary;
|
|
11739
10034
|
const skillPath = getSkillPath2(agentType);
|
|
11740
|
-
const fullPath =
|
|
11741
|
-
if (
|
|
10035
|
+
const fullPath = path25.resolve(process.cwd(), skillPath);
|
|
10036
|
+
if (fs24.existsSync(fullPath)) {
|
|
11742
10037
|
console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
|
|
11743
10038
|
return;
|
|
11744
10039
|
}
|
|
11745
|
-
const dir =
|
|
11746
|
-
if (!
|
|
11747
|
-
|
|
10040
|
+
const dir = path25.dirname(fullPath);
|
|
10041
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
10042
|
+
fs24.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
|
|
11748
10043
|
console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
|
|
11749
10044
|
const secondaryFiles = getSecondaryFiles2(agentType);
|
|
11750
10045
|
for (const sf of secondaryFiles) {
|
|
11751
|
-
const sfPath =
|
|
11752
|
-
const sfDir =
|
|
11753
|
-
if (!
|
|
11754
|
-
if (!
|
|
11755
|
-
|
|
10046
|
+
const sfPath = path25.resolve(process.cwd(), sf.path);
|
|
10047
|
+
const sfDir = path25.dirname(sfPath);
|
|
10048
|
+
if (!fs24.existsSync(sfPath)) {
|
|
10049
|
+
if (!fs24.existsSync(sfDir)) fs24.mkdirSync(sfDir, { recursive: true });
|
|
10050
|
+
fs24.writeFileSync(sfPath, sf.content, "utf-8");
|
|
11756
10051
|
console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
|
|
11757
10052
|
}
|
|
11758
10053
|
}
|
|
@@ -11776,17 +10071,6 @@ async function main() {
|
|
|
11776
10071
|
} catch (err) {
|
|
11777
10072
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
|
|
11778
10073
|
}
|
|
11779
|
-
try {
|
|
11780
|
-
const fs28 = await import("fs");
|
|
11781
|
-
const path29 = await import("path");
|
|
11782
|
-
const scratchDir = path29.resolve(process.cwd(), ".kuma", "scratch");
|
|
11783
|
-
if (!fs28.existsSync(scratchDir)) {
|
|
11784
|
-
fs28.mkdirSync(scratchDir, { recursive: true });
|
|
11785
|
-
console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
|
|
11786
|
-
}
|
|
11787
|
-
} catch (err) {
|
|
11788
|
-
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Scratch directory setup: ${err}`);
|
|
11789
|
-
}
|
|
11790
10074
|
try {
|
|
11791
10075
|
const { getDb: getDb2, saveDb: saveDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
11792
10076
|
const db = await getDb2();
|
|
@@ -11839,22 +10123,7 @@ async function main() {
|
|
|
11839
10123
|
`[${SERVER_NAME}] Session started: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
11840
10124
|
);
|
|
11841
10125
|
console.error(
|
|
11842
|
-
`[${SERVER_NAME}]
|
|
11843
|
-
);
|
|
11844
|
-
console.error(
|
|
11845
|
-
`[${SERVER_NAME}] \u26A1 Auto-init: no manual init needed (Pilar 1)`
|
|
11846
|
-
);
|
|
11847
|
-
console.error(
|
|
11848
|
-
`[${SERVER_NAME}] \u{1F4DB} Namespace normalized: kuma_kuma_* \u2192 kuma_* (Pilar 2)`
|
|
11849
|
-
);
|
|
11850
|
-
console.error(
|
|
11851
|
-
`[${SERVER_NAME}] \u{1F9F9} Noise filter: AST node types rejected (Pilar 4)`
|
|
11852
|
-
);
|
|
11853
|
-
console.error(
|
|
11854
|
-
`[${SERVER_NAME}] \u{1F419} Git auto-harvester installed (Pilar 5)`
|
|
11855
|
-
);
|
|
11856
|
-
console.error(
|
|
11857
|
-
`[${SERVER_NAME}] \u{1F6E1}\uFE0F 3 coarse-grained tools: kuma_context, kuma_memory, kuma_safety`
|
|
10126
|
+
`[${SERVER_NAME}] \u{1F6E1}\uFE0F 3 coarse-grained tools: kuma_context, kuma_memory, kuma_safety \u2014 13 core actions`
|
|
11858
10127
|
);
|
|
11859
10128
|
await server.connect(transport);
|
|
11860
10129
|
console.error(
|