@plumpslabs/kuma 2.3.22 → 2.3.24
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/dist/index.js +1293 -1134
- package/package.json +1 -1
- package/packages/ide/studio/dist/index.js +55 -6
- package/packages/ide/studio/public/index.html +434 -311
package/dist/index.js
CHANGED
|
@@ -311,7 +311,7 @@ function createSchema(db) {
|
|
|
311
311
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
312
312
|
source_id TEXT NOT NULL REFERENCES nodes(id),
|
|
313
313
|
target_id TEXT NOT NULL REFERENCES nodes(id),
|
|
314
|
-
type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with')),
|
|
314
|
+
type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with','affects')),
|
|
315
315
|
weight REAL DEFAULT 1.0,
|
|
316
316
|
metadata TEXT DEFAULT '{}',
|
|
317
317
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
@@ -326,7 +326,7 @@ function createSchema(db) {
|
|
|
326
326
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
327
327
|
source_id TEXT NOT NULL REFERENCES nodes(id),
|
|
328
328
|
target_id TEXT NOT NULL REFERENCES nodes(id),
|
|
329
|
-
type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with')),
|
|
329
|
+
type TEXT NOT NULL CHECK(type IN ('calls','imports','defines','tests','routes','implements','extends','depends_on','owns','modified_by','contains','composes','flows_through','triggers','syncs_with','affects')),
|
|
330
330
|
weight REAL DEFAULT 1.0,
|
|
331
331
|
metadata TEXT DEFAULT '{}',
|
|
332
332
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
@@ -717,22 +717,20 @@ async function listResearchCache() {
|
|
|
717
717
|
async function recordChange(entry) {
|
|
718
718
|
try {
|
|
719
719
|
const db = await getDb();
|
|
720
|
-
const root = process.cwd();
|
|
721
|
-
const fullPath = path2.resolve(root, entry.filePath);
|
|
722
|
-
let previousContent = null;
|
|
723
|
-
if (entry.changeType === "modified" && fs2.existsSync(fullPath)) {
|
|
724
|
-
try {
|
|
725
|
-
previousContent = fs2.readFileSync(fullPath, "utf-8");
|
|
726
|
-
} catch {
|
|
727
|
-
}
|
|
728
|
-
} else if (entry.changeType === "created") {
|
|
729
|
-
previousContent = "";
|
|
730
|
-
}
|
|
731
720
|
db.run(
|
|
732
721
|
`INSERT INTO change_log (session_id, file_path, change_type, symbol, diff_summary, git_commit_hash, previous_content) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
733
|
-
[entry.sessionId ?? null, entry.filePath, entry.changeType, entry.symbol ?? null, entry.diffSummary ?? null, entry.gitCommitHash ?? null,
|
|
722
|
+
[entry.sessionId ?? null, entry.filePath, entry.changeType, entry.symbol ?? null, entry.diffSummary ?? null, entry.gitCommitHash ?? null, null]
|
|
734
723
|
);
|
|
735
724
|
saveDb();
|
|
725
|
+
try {
|
|
726
|
+
const countResult = db.exec("SELECT COUNT(*) FROM change_log");
|
|
727
|
+
const count = countResult[0]?.values?.[0] || 0;
|
|
728
|
+
if (count > 600) {
|
|
729
|
+
db.exec("DELETE FROM change_log WHERE id IN (SELECT id FROM change_log ORDER BY created_at ASC LIMIT 200)");
|
|
730
|
+
saveDb();
|
|
731
|
+
}
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
736
734
|
} catch (err) {
|
|
737
735
|
console.error(`[KumaDB] Failed to record change: ${err}`);
|
|
738
736
|
}
|
|
@@ -1077,16 +1075,69 @@ async function runGarbageCollection() {
|
|
|
1077
1075
|
try {
|
|
1078
1076
|
const db = await getDb();
|
|
1079
1077
|
let removed = 0;
|
|
1078
|
+
const getCount = (sql) => {
|
|
1079
|
+
try {
|
|
1080
|
+
const result = db.exec(sql);
|
|
1081
|
+
return result[0]?.values?.[0] || 0;
|
|
1082
|
+
} catch {
|
|
1083
|
+
return 0;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1080
1086
|
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')`);
|
|
1081
1087
|
removed += orphanResult[0]?.values?.length || 0;
|
|
1088
|
+
const staleEdges = db.exec(`DELETE FROM edges WHERE weight < 0.1 AND updated_at < strftime('%s','now','-30 days')`);
|
|
1089
|
+
removed += staleEdges[0]?.values?.length || 0;
|
|
1082
1090
|
db.exec(`DELETE FROM tool_calls WHERE created_at < strftime('%s','now','-90 days')`);
|
|
1083
1091
|
db.exec(`DELETE FROM experiences WHERE created_at < strftime('%s','now','-90 days')`);
|
|
1092
|
+
const changeCount = getCount("SELECT COUNT(*) FROM change_log");
|
|
1093
|
+
if (changeCount > 500) {
|
|
1094
|
+
db.exec(`DELETE FROM change_log WHERE id IN (SELECT id FROM change_log ORDER BY created_at ASC LIMIT ${changeCount - 500})`);
|
|
1095
|
+
}
|
|
1096
|
+
const verifCount = getCount("SELECT COUNT(*) FROM verifications");
|
|
1097
|
+
if (verifCount > 100) {
|
|
1098
|
+
db.exec(`DELETE FROM verifications WHERE id IN (SELECT id FROM verifications ORDER BY created_at ASC LIMIT ${verifCount - 100})`);
|
|
1099
|
+
}
|
|
1100
|
+
const healthCount = getCount("SELECT COUNT(*) FROM health_snapshots");
|
|
1101
|
+
if (healthCount > 50) {
|
|
1102
|
+
db.exec(`DELETE FROM health_snapshots WHERE id IN (SELECT id FROM health_snapshots ORDER BY created_at ASC LIMIT ${healthCount - 50})`);
|
|
1103
|
+
}
|
|
1104
|
+
const auditCount = getCount("SELECT COUNT(*) FROM safety_audit");
|
|
1105
|
+
if (auditCount > 200) {
|
|
1106
|
+
db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY created_at ASC LIMIT ${auditCount - 200})`);
|
|
1107
|
+
}
|
|
1108
|
+
db.exec(`DELETE FROM research_cache WHERE updated_at < strftime('%s','now','-60 days')`);
|
|
1109
|
+
const secCount = getCount("SELECT COUNT(*) FROM security_findings");
|
|
1110
|
+
if (secCount > 100) {
|
|
1111
|
+
db.exec(`DELETE FROM security_findings WHERE id IN (SELECT id FROM security_findings ORDER BY created_at ASC LIMIT ${secCount - 100})`);
|
|
1112
|
+
}
|
|
1113
|
+
const ctxCount = getCount("SELECT COUNT(*) FROM context_notes");
|
|
1114
|
+
if (ctxCount > 200) {
|
|
1115
|
+
db.exec(`DELETE FROM context_notes WHERE id IN (SELECT id FROM context_notes ORDER BY created_at ASC LIMIT ${ctxCount - 200})`);
|
|
1116
|
+
}
|
|
1117
|
+
const benchCount = getCount("SELECT COUNT(*) FROM benchmarks");
|
|
1118
|
+
if (benchCount > 100) {
|
|
1119
|
+
db.exec(`DELETE FROM benchmarks WHERE id IN (SELECT id FROM benchmarks ORDER BY created_at ASC LIMIT ${benchCount - 100})`);
|
|
1120
|
+
}
|
|
1121
|
+
const decCount = getCount("SELECT COUNT(*) FROM decision_log");
|
|
1122
|
+
if (decCount > 200) {
|
|
1123
|
+
db.exec(`DELETE FROM decision_log WHERE id IN (SELECT id FROM decision_log ORDER BY created_at ASC LIMIT ${decCount - 200})`);
|
|
1124
|
+
}
|
|
1125
|
+
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 '%::%'`);
|
|
1126
|
+
db.exec(`DELETE FROM scratch_entries WHERE created_at < strftime('%s','now','-7 days')`);
|
|
1127
|
+
const costCount = getCount("SELECT COUNT(*) FROM cost_tracking");
|
|
1128
|
+
if (costCount > 500) {
|
|
1129
|
+
db.exec(`DELETE FROM cost_tracking WHERE id IN (SELECT id FROM cost_tracking ORDER BY created_at ASC LIMIT ${costCount - 500})`);
|
|
1130
|
+
}
|
|
1131
|
+
const sessCount = getCount("SELECT COUNT(*) FROM sessions");
|
|
1132
|
+
if (sessCount > 50) {
|
|
1133
|
+
db.exec(`DELETE FROM sessions WHERE id IN (SELECT id FROM sessions ORDER BY started_at ASC LIMIT ${sessCount - 50})`);
|
|
1134
|
+
}
|
|
1084
1135
|
try {
|
|
1085
1136
|
db.exec("VACUUM");
|
|
1086
1137
|
} catch {
|
|
1087
1138
|
}
|
|
1088
1139
|
saveDb();
|
|
1089
|
-
return `\u{1F9F9} GC complete
|
|
1140
|
+
return `\u{1F9F9} GC complete \u2014 cleaned 17 tables. Database vacuumed.`;
|
|
1090
1141
|
} catch (err) {
|
|
1091
1142
|
return `\u274C GC failed: ${err}`;
|
|
1092
1143
|
}
|
|
@@ -1324,7 +1375,9 @@ var init_sessionMemory = __esm({
|
|
|
1324
1375
|
searchResults: new Map(parsed.searchResults || []),
|
|
1325
1376
|
dependencyGraph: new Map(parsed.dependencyGraph || []),
|
|
1326
1377
|
toolCalls: parsed.toolCalls || [],
|
|
1327
|
-
conventions: parsed.conventions
|
|
1378
|
+
conventions: parsed.conventions,
|
|
1379
|
+
recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
|
|
1380
|
+
metrics: parsed.metrics || { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
|
|
1328
1381
|
};
|
|
1329
1382
|
this.initialized = true;
|
|
1330
1383
|
console.error(`[SessionMemory] Loaded persistent session memory.json`);
|
|
@@ -1342,7 +1395,9 @@ var init_sessionMemory = __esm({
|
|
|
1342
1395
|
failedFiles: /* @__PURE__ */ new Map(),
|
|
1343
1396
|
searchResults: /* @__PURE__ */ new Map(),
|
|
1344
1397
|
dependencyGraph: /* @__PURE__ */ new Map(),
|
|
1345
|
-
toolCalls: []
|
|
1398
|
+
toolCalls: [],
|
|
1399
|
+
recordings: { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
|
|
1400
|
+
metrics: { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
|
|
1346
1401
|
};
|
|
1347
1402
|
this.initialized = true;
|
|
1348
1403
|
this.save();
|
|
@@ -1490,7 +1545,9 @@ No unresolved issues.`;
|
|
|
1490
1545
|
searchResults: Array.from(this.state.searchResults.entries()),
|
|
1491
1546
|
dependencyGraph: Array.from(this.state.dependencyGraph.entries()),
|
|
1492
1547
|
toolCalls: this.state.toolCalls,
|
|
1493
|
-
conventions: this.state.conventions
|
|
1548
|
+
conventions: this.state.conventions,
|
|
1549
|
+
recordings: this.state.recordings,
|
|
1550
|
+
metrics: this.state.metrics
|
|
1494
1551
|
};
|
|
1495
1552
|
fs3.writeFileSync(path3.join(kumaDir, "memory.json"), JSON.stringify(serialized, null, 2), "utf-8");
|
|
1496
1553
|
} catch (err) {
|
|
@@ -1516,6 +1573,10 @@ No unresolved issues.`;
|
|
|
1516
1573
|
existing.modifiedAt = Date.now();
|
|
1517
1574
|
existing.status = "modified";
|
|
1518
1575
|
} else {
|
|
1576
|
+
if (this.state.modifiedFiles.size >= 200) {
|
|
1577
|
+
const oldest = this.state.modifiedFiles.keys().next().value;
|
|
1578
|
+
if (oldest) this.state.modifiedFiles.delete(oldest);
|
|
1579
|
+
}
|
|
1519
1580
|
this.state.modifiedFiles.set(filePath, {
|
|
1520
1581
|
filePath,
|
|
1521
1582
|
modifiedAt: Date.now(),
|
|
@@ -1547,7 +1608,14 @@ No unresolved issues.`;
|
|
|
1547
1608
|
timestamp: Date.now(),
|
|
1548
1609
|
resolved: false
|
|
1549
1610
|
});
|
|
1611
|
+
if (failures.length > 10) {
|
|
1612
|
+
failures.splice(0, failures.length - 10);
|
|
1613
|
+
}
|
|
1550
1614
|
this.state.failedFiles.set(task, failures);
|
|
1615
|
+
if (this.state.failedFiles.size > 100) {
|
|
1616
|
+
const oldest = this.state.failedFiles.keys().next().value;
|
|
1617
|
+
if (oldest) this.state.failedFiles.delete(oldest);
|
|
1618
|
+
}
|
|
1551
1619
|
this.save();
|
|
1552
1620
|
this.ensureMemoriesDir();
|
|
1553
1621
|
this.writeMemoryFile("known-issues", this.generateKnownIssuesMd());
|
|
@@ -1572,6 +1640,10 @@ No unresolved issues.`;
|
|
|
1572
1640
|
}
|
|
1573
1641
|
addSearchResult(query, files) {
|
|
1574
1642
|
this.ensureInit();
|
|
1643
|
+
if (this.state.searchResults.size >= 50) {
|
|
1644
|
+
const oldest = this.state.searchResults.keys().next().value;
|
|
1645
|
+
if (oldest) this.state.searchResults.delete(oldest);
|
|
1646
|
+
}
|
|
1575
1647
|
this.state.searchResults.set(query, files);
|
|
1576
1648
|
this.save();
|
|
1577
1649
|
}
|
|
@@ -1580,6 +1652,9 @@ No unresolved issues.`;
|
|
|
1580
1652
|
const deps = this.state.dependencyGraph.get(file) ?? [];
|
|
1581
1653
|
if (!deps.includes(dependsOn)) {
|
|
1582
1654
|
deps.push(dependsOn);
|
|
1655
|
+
if (deps.length > 20) {
|
|
1656
|
+
deps.splice(0, deps.length - 20);
|
|
1657
|
+
}
|
|
1583
1658
|
this.state.dependencyGraph.set(file, deps);
|
|
1584
1659
|
this.save();
|
|
1585
1660
|
}
|
|
@@ -1879,6 +1954,95 @@ No unresolved issues.`;
|
|
|
1879
1954
|
return { isLooping: false };
|
|
1880
1955
|
}
|
|
1881
1956
|
// ============================================================
|
|
1957
|
+
// RECORDING TRACKING — Enforcement & Feedback
|
|
1958
|
+
// ============================================================
|
|
1959
|
+
/**
|
|
1960
|
+
* Increment recording counter for a specific type.
|
|
1961
|
+
* Called by kumaMemoryTool after successful record operations.
|
|
1962
|
+
*/
|
|
1963
|
+
recordMemoryAction(type) {
|
|
1964
|
+
this.ensureInit();
|
|
1965
|
+
switch (type) {
|
|
1966
|
+
case "arch_flow":
|
|
1967
|
+
this.state.recordings.archFlows++;
|
|
1968
|
+
break;
|
|
1969
|
+
case "gotcha":
|
|
1970
|
+
this.state.recordings.gotchas++;
|
|
1971
|
+
break;
|
|
1972
|
+
case "decision":
|
|
1973
|
+
this.state.recordings.decisions++;
|
|
1974
|
+
break;
|
|
1975
|
+
case "research_save":
|
|
1976
|
+
this.state.recordings.researchSaves++;
|
|
1977
|
+
break;
|
|
1978
|
+
}
|
|
1979
|
+
this.state.recordings.total++;
|
|
1980
|
+
this.save();
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Get recording summary for this session.
|
|
1984
|
+
* Used by guard to detect if agent hasn't recorded anything.
|
|
1985
|
+
*/
|
|
1986
|
+
getRecordingSummary() {
|
|
1987
|
+
this.ensureInit();
|
|
1988
|
+
const r = this.state.recordings;
|
|
1989
|
+
const missing = [];
|
|
1990
|
+
if (r.archFlows === 0) missing.push("arch_flow");
|
|
1991
|
+
if (r.gotchas === 0) missing.push("gotcha");
|
|
1992
|
+
if (r.decisions === 0) missing.push("decision");
|
|
1993
|
+
if (r.researchSaves === 0) missing.push("research_save");
|
|
1994
|
+
return {
|
|
1995
|
+
...r,
|
|
1996
|
+
hasAnyRecordings: r.total > 0,
|
|
1997
|
+
missingRecordings: missing
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
// ============================================================
|
|
2001
|
+
// SESSION METRICS — Time saved tracking
|
|
2002
|
+
// ============================================================
|
|
2003
|
+
/**
|
|
2004
|
+
* Track a file read operation.
|
|
2005
|
+
* Estimates time saved if research_cache exists for this file.
|
|
2006
|
+
*/
|
|
2007
|
+
trackFileRead(filePath) {
|
|
2008
|
+
this.ensureInit();
|
|
2009
|
+
this.state.metrics.filesRead++;
|
|
2010
|
+
try {
|
|
2011
|
+
const researchDir = path3.join(this.state.projectRoot, ".kuma", "research");
|
|
2012
|
+
const cacheFile = path3.join(researchDir, `${filePath}.json`);
|
|
2013
|
+
if (fs3.existsSync(cacheFile)) {
|
|
2014
|
+
this.state.metrics.researchTimeSaved += 4e3;
|
|
2015
|
+
}
|
|
2016
|
+
} catch {
|
|
2017
|
+
}
|
|
2018
|
+
this.save();
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Track a file edit operation.
|
|
2022
|
+
*/
|
|
2023
|
+
trackFileEdit(_filePath) {
|
|
2024
|
+
this.ensureInit();
|
|
2025
|
+
this.state.metrics.filesEdited++;
|
|
2026
|
+
this.save();
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Get metrics summary for session.
|
|
2030
|
+
*/
|
|
2031
|
+
getMetricsSummary() {
|
|
2032
|
+
this.ensureInit();
|
|
2033
|
+
const m = this.state.metrics;
|
|
2034
|
+
const durationMs = Date.now() - this.state.startTime;
|
|
2035
|
+
const durationMin = Math.round(durationMs / 6e4);
|
|
2036
|
+
const savedSec = Math.round(m.researchTimeSaved / 1e3);
|
|
2037
|
+
return {
|
|
2038
|
+
filesRead: m.filesRead,
|
|
2039
|
+
filesEdited: m.filesEdited,
|
|
2040
|
+
researchTimeSaved: m.researchTimeSaved,
|
|
2041
|
+
researchTimeSavedFormatted: savedSec > 60 ? `${Math.round(savedSec / 60)}m ${savedSec % 60}s` : `${savedSec}s`,
|
|
2042
|
+
sessionDuration: durationMin > 60 ? `${Math.round(durationMin / 60)}h ${durationMin % 60}m` : `${durationMin}m`
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
// ============================================================
|
|
1882
2046
|
// V3: Change Tracking (Selective Undo)
|
|
1883
2047
|
// ============================================================
|
|
1884
2048
|
/**
|
|
@@ -1997,7 +2161,7 @@ async function detectStaleNodes() {
|
|
|
1997
2161
|
SELECT id, type, name, file_path FROM nodes
|
|
1998
2162
|
WHERE file_path IS NOT NULL
|
|
1999
2163
|
AND length(file_path) > 0
|
|
2000
|
-
ORDER BY updated_at DESC LIMIT
|
|
2164
|
+
ORDER BY updated_at DESC LIMIT 2000
|
|
2001
2165
|
`);
|
|
2002
2166
|
while (stmt.step()) {
|
|
2003
2167
|
const row = stmt.getAsObject();
|
|
@@ -2182,16 +2346,18 @@ __export(kumaGraph_exports, {
|
|
|
2182
2346
|
formatFlow: () => formatFlow,
|
|
2183
2347
|
formatImpact: () => formatImpact,
|
|
2184
2348
|
getGraphStats: () => getGraphStats,
|
|
2185
|
-
listFederatedReferences: () => listFederatedReferences,
|
|
2186
2349
|
nodeId: () => nodeId,
|
|
2350
|
+
propagateImpact: () => propagateImpact,
|
|
2187
2351
|
queryGraph: () => queryGraph,
|
|
2352
|
+
recordAffects: () => recordAffects,
|
|
2188
2353
|
recordApiRoute: () => recordApiRoute,
|
|
2189
2354
|
recordDomainFlow: () => recordDomainFlow,
|
|
2355
|
+
recordFeature: () => recordFeature,
|
|
2190
2356
|
recordFileDefinition: () => recordFileDefinition,
|
|
2191
2357
|
recordFunctionCall: () => recordFunctionCall,
|
|
2192
2358
|
recordImport: () => recordImport,
|
|
2193
2359
|
recordTestRelation: () => recordTestRelation,
|
|
2194
|
-
|
|
2360
|
+
retrieveForTask: () => retrieveForTask,
|
|
2195
2361
|
searchGraph: () => searchGraph,
|
|
2196
2362
|
traceFlow: () => traceFlow,
|
|
2197
2363
|
upsertNode: () => upsertNode
|
|
@@ -2199,6 +2365,60 @@ __export(kumaGraph_exports, {
|
|
|
2199
2365
|
function nodeId(type, name) {
|
|
2200
2366
|
return `${type}::${name}`;
|
|
2201
2367
|
}
|
|
2368
|
+
async function pruneGraphIfNeeded() {
|
|
2369
|
+
try {
|
|
2370
|
+
const db = await getDb();
|
|
2371
|
+
const getCount = (sql) => {
|
|
2372
|
+
try {
|
|
2373
|
+
const result = db.exec(sql);
|
|
2374
|
+
return result[0]?.values?.[0] || 0;
|
|
2375
|
+
} catch {
|
|
2376
|
+
return 0;
|
|
2377
|
+
}
|
|
2378
|
+
};
|
|
2379
|
+
const nodeCount = getCount("SELECT COUNT(*) FROM nodes");
|
|
2380
|
+
const edgeCount = getCount("SELECT COUNT(*) FROM edges");
|
|
2381
|
+
if (nodeCount <= GRAPH_LIMITS.maxNodes && edgeCount <= GRAPH_LIMITS.maxEdges) {
|
|
2382
|
+
return { pruned: false, removedNodes: 0, removedEdges: 0 };
|
|
2383
|
+
}
|
|
2384
|
+
let removedNodes = 0;
|
|
2385
|
+
let removedEdges = 0;
|
|
2386
|
+
if (edgeCount > GRAPH_LIMITS.maxEdges) {
|
|
2387
|
+
const result = db.exec(`DELETE FROM edges WHERE weight < ${GRAPH_LIMITS.minWeight} AND metadata NOT LIKE '%stale:0%'`);
|
|
2388
|
+
removedEdges = result[0]?.values?.length || 0;
|
|
2389
|
+
}
|
|
2390
|
+
const currentNodes = getCount("SELECT COUNT(*) FROM nodes");
|
|
2391
|
+
if (currentNodes > GRAPH_LIMITS.maxNodes) {
|
|
2392
|
+
const result = db.exec(`
|
|
2393
|
+
DELETE FROM nodes WHERE id IN (
|
|
2394
|
+
SELECT id FROM nodes
|
|
2395
|
+
WHERE id NOT IN (SELECT source_id FROM edges UNION SELECT target_id FROM edges)
|
|
2396
|
+
ORDER BY updated_at ASC
|
|
2397
|
+
LIMIT ${GRAPH_LIMITS.pruneBatch}
|
|
2398
|
+
)
|
|
2399
|
+
`);
|
|
2400
|
+
removedNodes = result[0]?.values?.length || 0;
|
|
2401
|
+
}
|
|
2402
|
+
const finalNodes = getCount("SELECT COUNT(*) FROM nodes");
|
|
2403
|
+
if (finalNodes > GRAPH_LIMITS.maxNodes) {
|
|
2404
|
+
const excess = finalNodes - GRAPH_LIMITS.maxNodes + 100;
|
|
2405
|
+
const result = db.exec(`
|
|
2406
|
+
DELETE FROM nodes WHERE id IN (
|
|
2407
|
+
SELECT id FROM nodes ORDER BY updated_at ASC LIMIT ${excess}
|
|
2408
|
+
)
|
|
2409
|
+
`);
|
|
2410
|
+
removedNodes += result[0]?.values?.length || 0;
|
|
2411
|
+
}
|
|
2412
|
+
if (removedNodes > 0 || removedEdges > 0) {
|
|
2413
|
+
saveDb(db);
|
|
2414
|
+
return { pruned: true, removedNodes, removedEdges };
|
|
2415
|
+
}
|
|
2416
|
+
return { pruned: false, removedNodes: 0, removedEdges: 0 };
|
|
2417
|
+
} catch (err) {
|
|
2418
|
+
console.error(`[KumaGraph] Prune failed: ${err}`);
|
|
2419
|
+
return { pruned: false, removedNodes: 0, removedEdges: 0 };
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2202
2422
|
async function upsertNode(node) {
|
|
2203
2423
|
try {
|
|
2204
2424
|
const db = await getDb();
|
|
@@ -2219,6 +2439,14 @@ async function upsertNode(node) {
|
|
|
2219
2439
|
} catch {
|
|
2220
2440
|
}
|
|
2221
2441
|
saveDb(db);
|
|
2442
|
+
try {
|
|
2443
|
+
const countResult = db.exec("SELECT COUNT(*) FROM nodes");
|
|
2444
|
+
const nodeCount = countResult[0]?.values?.[0] || 0;
|
|
2445
|
+
if (nodeCount % 50 === 0 && nodeCount > GRAPH_LIMITS.maxNodes * 0.8) {
|
|
2446
|
+
await pruneGraphIfNeeded();
|
|
2447
|
+
}
|
|
2448
|
+
} catch {
|
|
2449
|
+
}
|
|
2222
2450
|
} catch (err) {
|
|
2223
2451
|
console.error(`[KumaGraph] Failed to upsert node: ${err}`);
|
|
2224
2452
|
}
|
|
@@ -2327,6 +2555,14 @@ async function recordDomainFlow(params) {
|
|
|
2327
2555
|
metadata: { relation: hop.relation, description: hop.description }
|
|
2328
2556
|
});
|
|
2329
2557
|
edgeCount++;
|
|
2558
|
+
await addEdge({
|
|
2559
|
+
sourceId: fromId,
|
|
2560
|
+
targetId: toId,
|
|
2561
|
+
type: "affects",
|
|
2562
|
+
weight: 0.8,
|
|
2563
|
+
metadata: { reason: "arch_flow", domain: params.domain }
|
|
2564
|
+
});
|
|
2565
|
+
edgeCount++;
|
|
2330
2566
|
await addEdge({
|
|
2331
2567
|
sourceId: domainId,
|
|
2332
2568
|
targetId: fromId,
|
|
@@ -2403,6 +2639,14 @@ async function recordDomainFlow(params) {
|
|
|
2403
2639
|
metadata: { domain: params.domain }
|
|
2404
2640
|
});
|
|
2405
2641
|
edgeCount++;
|
|
2642
|
+
await addEdge({
|
|
2643
|
+
sourceId: domainId,
|
|
2644
|
+
targetId: fileId,
|
|
2645
|
+
type: "affects",
|
|
2646
|
+
weight: 0.7,
|
|
2647
|
+
metadata: { reason: "arch_flow" }
|
|
2648
|
+
});
|
|
2649
|
+
edgeCount++;
|
|
2406
2650
|
} catch {
|
|
2407
2651
|
}
|
|
2408
2652
|
}
|
|
@@ -2430,6 +2674,214 @@ async function clearGraph() {
|
|
|
2430
2674
|
return -1;
|
|
2431
2675
|
}
|
|
2432
2676
|
}
|
|
2677
|
+
async function recordFeature(params) {
|
|
2678
|
+
let nodeCount = 0;
|
|
2679
|
+
let edgeCount = 0;
|
|
2680
|
+
try {
|
|
2681
|
+
const featureId = nodeId("feature", params.name);
|
|
2682
|
+
await upsertNode({
|
|
2683
|
+
id: featureId,
|
|
2684
|
+
type: "feature",
|
|
2685
|
+
name: params.name,
|
|
2686
|
+
metadata: {
|
|
2687
|
+
description: params.description || "",
|
|
2688
|
+
tags: params.tags || [],
|
|
2689
|
+
risk: params.risk || "medium"
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
nodeCount++;
|
|
2693
|
+
if (params.files) {
|
|
2694
|
+
for (const fp of params.files) {
|
|
2695
|
+
const fileId = nodeId("file", fp);
|
|
2696
|
+
await upsertNode({ id: fileId, type: "file", name: fp });
|
|
2697
|
+
await addEdge({
|
|
2698
|
+
sourceId: featureId,
|
|
2699
|
+
targetId: fileId,
|
|
2700
|
+
type: "owns",
|
|
2701
|
+
weight: 1,
|
|
2702
|
+
metadata: { feature: params.name }
|
|
2703
|
+
});
|
|
2704
|
+
nodeCount++;
|
|
2705
|
+
edgeCount++;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
flushDb();
|
|
2709
|
+
return { nodeCount, edgeCount };
|
|
2710
|
+
} catch (err) {
|
|
2711
|
+
console.error(`[KumaGraph] Failed to record feature: ${err}`);
|
|
2712
|
+
return { nodeCount, edgeCount };
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
async function recordAffects(sourceId, targetId, weight = 0.8, reason) {
|
|
2716
|
+
await addEdge({
|
|
2717
|
+
sourceId,
|
|
2718
|
+
targetId,
|
|
2719
|
+
type: "affects",
|
|
2720
|
+
weight,
|
|
2721
|
+
metadata: { reason: reason || "manual" }
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
async function propagateImpact(startNodeId, maxDepth = 5, minWeight = 0.1) {
|
|
2725
|
+
try {
|
|
2726
|
+
const db = await getDb();
|
|
2727
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2728
|
+
const results = [];
|
|
2729
|
+
const queue = [[startNodeId, 0, 1]];
|
|
2730
|
+
while (queue.length > 0) {
|
|
2731
|
+
const [currentId, depth, accWeight] = queue.shift();
|
|
2732
|
+
if (visited.has(currentId) || depth > maxDepth) continue;
|
|
2733
|
+
visited.add(currentId);
|
|
2734
|
+
const stmt = db.prepare(`
|
|
2735
|
+
SELECT e.target_id, e.weight, n.type, n.name, n.file_path
|
|
2736
|
+
FROM edges e
|
|
2737
|
+
LEFT JOIN nodes n ON n.id = e.target_id
|
|
2738
|
+
WHERE e.source_id = ? AND e.type = 'affects' AND e.weight >= ?
|
|
2739
|
+
`);
|
|
2740
|
+
stmt.bind([currentId, minWeight]);
|
|
2741
|
+
while (stmt.step()) {
|
|
2742
|
+
const row = stmt.getAsObject();
|
|
2743
|
+
const targetId = row.target_id;
|
|
2744
|
+
const edgeWeight = row.weight || 0.8;
|
|
2745
|
+
const propagatedWeight = accWeight * edgeWeight;
|
|
2746
|
+
if (!visited.has(targetId) && propagatedWeight >= minWeight) {
|
|
2747
|
+
results.push({
|
|
2748
|
+
id: targetId,
|
|
2749
|
+
type: row.type,
|
|
2750
|
+
name: row.name,
|
|
2751
|
+
depth: depth + 1,
|
|
2752
|
+
weight: Math.round(propagatedWeight * 100) / 100,
|
|
2753
|
+
filePath: row.file_path || void 0
|
|
2754
|
+
});
|
|
2755
|
+
queue.push([targetId, depth + 1, propagatedWeight]);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
stmt.free();
|
|
2759
|
+
}
|
|
2760
|
+
return results.sort((a, b) => b.weight - a.weight);
|
|
2761
|
+
} catch (err) {
|
|
2762
|
+
console.error(`[KumaGraph] Failed to propagate impact: ${err}`);
|
|
2763
|
+
return [];
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
async function retrieveForTask(taskDescription, maxNodes = 10) {
|
|
2767
|
+
try {
|
|
2768
|
+
const db = await getDb();
|
|
2769
|
+
const keywords = taskDescription.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2);
|
|
2770
|
+
if (keywords.length === 0) {
|
|
2771
|
+
return `\u{1F50D} **Task Retrieval** \u2014 No meaningful keywords in "${taskDescription}".`;
|
|
2772
|
+
}
|
|
2773
|
+
const allNodes = [];
|
|
2774
|
+
try {
|
|
2775
|
+
const ftsQuery = keywords.join(" OR ");
|
|
2776
|
+
const stmt = db.prepare(`
|
|
2777
|
+
SELECT n.id, n.type, n.name, n.file_path
|
|
2778
|
+
FROM nodes_fts f
|
|
2779
|
+
JOIN nodes n ON n.rowid = f.rowid
|
|
2780
|
+
WHERE nodes_fts MATCH ?
|
|
2781
|
+
LIMIT ?
|
|
2782
|
+
`);
|
|
2783
|
+
stmt.bind([ftsQuery, maxNodes * 2]);
|
|
2784
|
+
while (stmt.step()) {
|
|
2785
|
+
const row = stmt.getAsObject();
|
|
2786
|
+
allNodes.push({
|
|
2787
|
+
id: row.id,
|
|
2788
|
+
type: row.type,
|
|
2789
|
+
name: row.name,
|
|
2790
|
+
filePath: row.file_path || void 0,
|
|
2791
|
+
relevance: 0.8
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
stmt.free();
|
|
2795
|
+
} catch {
|
|
2796
|
+
}
|
|
2797
|
+
if (allNodes.length === 0) {
|
|
2798
|
+
for (const kw of keywords) {
|
|
2799
|
+
const stmt = db.prepare(`
|
|
2800
|
+
SELECT id, type, name, file_path FROM nodes
|
|
2801
|
+
WHERE name LIKE ? OR file_path LIKE ?
|
|
2802
|
+
LIMIT ?
|
|
2803
|
+
`);
|
|
2804
|
+
stmt.bind([`%${kw}%`, `%${kw}%`, maxNodes]);
|
|
2805
|
+
while (stmt.step()) {
|
|
2806
|
+
const row = stmt.getAsObject();
|
|
2807
|
+
allNodes.push({
|
|
2808
|
+
id: row.id,
|
|
2809
|
+
type: row.type,
|
|
2810
|
+
name: row.name,
|
|
2811
|
+
filePath: row.file_path || void 0,
|
|
2812
|
+
relevance: 0.6
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
stmt.free();
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2819
|
+
const prioritized = [];
|
|
2820
|
+
const typePriority = {
|
|
2821
|
+
feature: 10,
|
|
2822
|
+
feature_domain: 9,
|
|
2823
|
+
class: 8,
|
|
2824
|
+
api_route: 7,
|
|
2825
|
+
db_table: 6,
|
|
2826
|
+
file: 5,
|
|
2827
|
+
function: 4,
|
|
2828
|
+
component: 3,
|
|
2829
|
+
gotcha: 2,
|
|
2830
|
+
decision: 1
|
|
2831
|
+
};
|
|
2832
|
+
for (const node of allNodes) {
|
|
2833
|
+
if (!seen.has(node.id)) {
|
|
2834
|
+
seen.add(node.id);
|
|
2835
|
+
node.relevance += typePriority[node.type] || 0;
|
|
2836
|
+
prioritized.push(node);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
prioritized.sort((a, b) => b.relevance - a.relevance);
|
|
2840
|
+
const topNodes = prioritized.slice(0, maxNodes);
|
|
2841
|
+
if (topNodes.length === 0) {
|
|
2842
|
+
return `\u{1F50D} **Task Retrieval** \u2014 No relevant nodes found for "${taskDescription}".
|
|
2843
|
+
|
|
2844
|
+
\u{1F4A1} Try recording features, arch_flows, or gotchas first.`;
|
|
2845
|
+
}
|
|
2846
|
+
const lines = [
|
|
2847
|
+
`\u{1F50D} **Task Context** \u2014 "${taskDescription}"`,
|
|
2848
|
+
`\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`,
|
|
2849
|
+
`\u{1F4CA} ${topNodes.length} relevant node(s) found
|
|
2850
|
+
`
|
|
2851
|
+
];
|
|
2852
|
+
for (const node of topNodes) {
|
|
2853
|
+
const emoji = node.type === "feature" ? "\u2B50" : node.type === "feature_domain" ? "\u{1F3DB}\uFE0F" : node.type === "class" ? "\u{1F3D7}\uFE0F" : node.type === "api_route" ? "\u{1F310}" : node.type === "db_table" ? "\u{1F5C4}\uFE0F" : node.type === "file" ? "\u{1F4C4}" : node.type === "function" ? "\u{1F527}" : node.type === "component" ? "\u25C7" : node.type === "gotcha" ? "\u26A0\uFE0F" : "\u{1F4CC}";
|
|
2854
|
+
lines.push(`${emoji} **${node.name}** (${node.type})`);
|
|
2855
|
+
if (node.filePath) lines.push(` \u{1F4CD} ${node.filePath}`);
|
|
2856
|
+
try {
|
|
2857
|
+
const stmt = db.prepare(`
|
|
2858
|
+
SELECT e.type AS edgeType, n.type AS nodeType, n.name AS nodeName
|
|
2859
|
+
FROM edges e
|
|
2860
|
+
LEFT JOIN nodes n ON n.id = (CASE WHEN e.source_id = ? THEN e.target_id ELSE e.source_id END)
|
|
2861
|
+
WHERE (e.source_id = ? OR e.target_id = ?) AND n.id IS NOT NULL
|
|
2862
|
+
ORDER BY e.weight DESC
|
|
2863
|
+
LIMIT 3
|
|
2864
|
+
`);
|
|
2865
|
+
stmt.bind([node.id, node.id, node.id]);
|
|
2866
|
+
const connections = [];
|
|
2867
|
+
while (stmt.step()) {
|
|
2868
|
+
const row = stmt.getAsObject();
|
|
2869
|
+
connections.push(`${row.edgeType}\u2192${row.nodeName}`);
|
|
2870
|
+
}
|
|
2871
|
+
stmt.free();
|
|
2872
|
+
if (connections.length > 0) {
|
|
2873
|
+
lines.push(` \u{1F517} ${connections.join(", ")}`);
|
|
2874
|
+
}
|
|
2875
|
+
} catch {
|
|
2876
|
+
}
|
|
2877
|
+
lines.push("");
|
|
2878
|
+
}
|
|
2879
|
+
lines.push("\u{1F4A1} Use this context instead of scanning the full codebase.");
|
|
2880
|
+
return lines.join("\n");
|
|
2881
|
+
} catch (err) {
|
|
2882
|
+
return `Error retrieving task context: ${err}`;
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2433
2885
|
async function queryGraph(params) {
|
|
2434
2886
|
try {
|
|
2435
2887
|
const db = await getDb();
|
|
@@ -2701,27 +3153,27 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
2701
3153
|
if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
|
|
2702
3154
|
let entries = [];
|
|
2703
3155
|
try {
|
|
2704
|
-
entries =
|
|
3156
|
+
entries = fs26.readdirSync(dir);
|
|
2705
3157
|
} catch {
|
|
2706
3158
|
return;
|
|
2707
3159
|
}
|
|
2708
3160
|
for (const entry of entries) {
|
|
2709
3161
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
|
|
2710
|
-
const full =
|
|
3162
|
+
const full = path26.join(dir, entry);
|
|
2711
3163
|
try {
|
|
2712
|
-
const stat =
|
|
3164
|
+
const stat = fs26.statSync(full);
|
|
2713
3165
|
if (stat.isDirectory()) {
|
|
2714
3166
|
walk2(full, depth + 1);
|
|
2715
3167
|
} else if (entry.toLowerCase().includes(query.toLowerCase())) {
|
|
2716
|
-
results.push(
|
|
3168
|
+
results.push(path26.relative(root, full));
|
|
2717
3169
|
}
|
|
2718
3170
|
} catch {
|
|
2719
3171
|
}
|
|
2720
3172
|
}
|
|
2721
3173
|
};
|
|
2722
3174
|
var walk = walk2;
|
|
2723
|
-
const
|
|
2724
|
-
const
|
|
3175
|
+
const fs26 = await import("fs");
|
|
3176
|
+
const path26 = await import("path");
|
|
2725
3177
|
const root = process.cwd();
|
|
2726
3178
|
const results = [];
|
|
2727
3179
|
const MAX_WALK_DEPTH = 8;
|
|
@@ -2741,114 +3193,19 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
2741
3193
|
}
|
|
2742
3194
|
}
|
|
2743
3195
|
}
|
|
2744
|
-
async function resolveFederatedNode(uri) {
|
|
2745
|
-
try {
|
|
2746
|
-
if (!uri.startsWith("kuma://")) {
|
|
2747
|
-
return `\u26A0\uFE0F Invalid URI scheme: "${uri.substring(0, 20)}...". Use kuma://project/node-id`;
|
|
2748
|
-
}
|
|
2749
|
-
const pathPart = uri.replace("kuma://", "");
|
|
2750
|
-
const firstSlash = pathPart.indexOf("/");
|
|
2751
|
-
if (firstSlash === -1) {
|
|
2752
|
-
return `\u26A0\uFE0F Invalid federated URI: "${uri}". Format: kuma://<project>/<node-id>`;
|
|
2753
|
-
}
|
|
2754
|
-
const projectName = pathPart.substring(0, firstSlash);
|
|
2755
|
-
const nodeRef = pathPart.substring(firstSlash + 1);
|
|
2756
|
-
const db = await getDb();
|
|
2757
|
-
const stmt = db.prepare(`
|
|
2758
|
-
SELECT id, type, name, file_path, metadata FROM nodes
|
|
2759
|
-
WHERE id = ? OR name LIKE ?
|
|
2760
|
-
LIMIT 5
|
|
2761
|
-
`);
|
|
2762
|
-
stmt.bind([nodeRef, `%${nodeRef}%`]);
|
|
2763
|
-
const results = [];
|
|
2764
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
2765
|
-
stmt.free();
|
|
2766
|
-
if (results.length > 0) {
|
|
2767
|
-
const lines = [
|
|
2768
|
-
`\u{1F517} **Federated Node Resolved** (local)`,
|
|
2769
|
-
`\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`,
|
|
2770
|
-
`\u{1F4C1} Project: ${projectName}`,
|
|
2771
|
-
`\u{1F517} URI: ${uri}`,
|
|
2772
|
-
""
|
|
2773
|
-
];
|
|
2774
|
-
for (const r of results) {
|
|
2775
|
-
lines.push(` \u2022 **${r.name}** (${r.type}) \u2014 ${r.file_path || "no path"}`);
|
|
2776
|
-
}
|
|
2777
|
-
lines.push("", "\u{1F4A1} Federated cross-repo resolution is active. Remote resolution via HTTP coming soon.");
|
|
2778
|
-
return lines.join("\n");
|
|
2779
|
-
}
|
|
2780
|
-
const federatedNodes = await registerFederatedReference(uri, projectName, nodeRef);
|
|
2781
|
-
return [
|
|
2782
|
-
`\u{1F517} **Federated Reference**`,
|
|
2783
|
-
`\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`,
|
|
2784
|
-
`\u{1F4C1} Remote project: ${projectName}`,
|
|
2785
|
-
`\u{1F517} URI: ${uri}`,
|
|
2786
|
-
`\u{1F4CC} Node ref: ${nodeRef}`,
|
|
2787
|
-
"",
|
|
2788
|
-
`\u{1F4DD} Registered as federated reference for future resolution.`,
|
|
2789
|
-
`\u{1F4A1} Remote graph resolution will be available when the target project is reachable.`,
|
|
2790
|
-
federatedNodes > 0 ? `\u2705 Created ${federatedNodes} federated node(s) in local graph.` : ""
|
|
2791
|
-
].filter(Boolean).join("\n");
|
|
2792
|
-
} catch (err) {
|
|
2793
|
-
return `\u274C Federated resolution failed: ${err}`;
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
async function registerFederatedReference(uri, projectName, nodeRef) {
|
|
2797
|
-
try {
|
|
2798
|
-
await upsertNode({
|
|
2799
|
-
id: `federated::${uri}`,
|
|
2800
|
-
type: "module",
|
|
2801
|
-
name: `[${projectName}] ${nodeRef}`,
|
|
2802
|
-
metadata: {
|
|
2803
|
-
federated: true,
|
|
2804
|
-
uri,
|
|
2805
|
-
project: projectName,
|
|
2806
|
-
ref: nodeRef,
|
|
2807
|
-
resolved: false
|
|
2808
|
-
}
|
|
2809
|
-
});
|
|
2810
|
-
return 1;
|
|
2811
|
-
} catch {
|
|
2812
|
-
return 0;
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
async function listFederatedReferences() {
|
|
2816
|
-
try {
|
|
2817
|
-
const db = await getDb();
|
|
2818
|
-
const stmt = db.prepare(`
|
|
2819
|
-
SELECT id, name, metadata FROM nodes
|
|
2820
|
-
WHERE metadata LIKE '%"federated":true%'
|
|
2821
|
-
ORDER BY updated_at DESC
|
|
2822
|
-
LIMIT 20
|
|
2823
|
-
`);
|
|
2824
|
-
const results = [];
|
|
2825
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
2826
|
-
stmt.free();
|
|
2827
|
-
if (results.length === 0) {
|
|
2828
|
-
return "\u{1F517} **No federated references.** Use kuma_memory({ action: 'federated', uri: 'kuma://project/node-id' }) to add one.";
|
|
2829
|
-
}
|
|
2830
|
-
const lines = [
|
|
2831
|
-
"\u{1F517} **Federated References**",
|
|
2832
|
-
"\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",
|
|
2833
|
-
""
|
|
2834
|
-
];
|
|
2835
|
-
for (const r of results) {
|
|
2836
|
-
const meta = JSON.parse(r.metadata || "{}");
|
|
2837
|
-
lines.push(` \u{1F517} **${r.name}**`);
|
|
2838
|
-
lines.push(` URI: ${meta.uri || "unknown"}`);
|
|
2839
|
-
lines.push(` Resolved: ${meta.resolved ? "\u2705" : "\u23F3"}`);
|
|
2840
|
-
lines.push("");
|
|
2841
|
-
}
|
|
2842
|
-
return lines.join("\n");
|
|
2843
|
-
} catch (err) {
|
|
2844
|
-
return `Error: ${err}`;
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
3196
|
async function getGraphStats() {
|
|
2848
3197
|
try {
|
|
2849
3198
|
const db = await getDb();
|
|
2850
|
-
const
|
|
2851
|
-
|
|
3199
|
+
const getCount = (sql) => {
|
|
3200
|
+
try {
|
|
3201
|
+
const result = db.exec(sql);
|
|
3202
|
+
return result[0]?.values?.[0] || 0;
|
|
3203
|
+
} catch {
|
|
3204
|
+
return 0;
|
|
3205
|
+
}
|
|
3206
|
+
};
|
|
3207
|
+
const nodeCount = getCount("SELECT COUNT(*) as c FROM nodes");
|
|
3208
|
+
const edgeCount = getCount("SELECT COUNT(*) as c FROM edges");
|
|
2852
3209
|
const typeCounts = [];
|
|
2853
3210
|
try {
|
|
2854
3211
|
const stmt = db.prepare("SELECT type, COUNT(*) as cnt FROM nodes GROUP BY type ORDER BY cnt DESC");
|
|
@@ -2863,6 +3220,7 @@ async function getGraphStats() {
|
|
|
2863
3220
|
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
2864
3221
|
"",
|
|
2865
3222
|
`\u{1F4CA} **${nodeCount} nodes** | **${edgeCount} edges**`,
|
|
3223
|
+
`\u{1F4CF} **Limits:** ${nodeCount}/${GRAPH_LIMITS.maxNodes} nodes (${Math.round(nodeCount / GRAPH_LIMITS.maxNodes * 100)}%) | ${edgeCount}/${GRAPH_LIMITS.maxEdges} edges (${Math.round(edgeCount / GRAPH_LIMITS.maxEdges * 100)}%)`,
|
|
2866
3224
|
""
|
|
2867
3225
|
];
|
|
2868
3226
|
if (typeCounts.length > 0) {
|
|
@@ -3059,11 +3417,22 @@ function formatFlow(entryPoint, steps) {
|
|
|
3059
3417
|
lines.push("", `\u{1F4A1} ${steps.length} step(s) traversed.`);
|
|
3060
3418
|
return lines.join("\n");
|
|
3061
3419
|
}
|
|
3420
|
+
var GRAPH_LIMITS;
|
|
3062
3421
|
var init_kumaGraph = __esm({
|
|
3063
3422
|
"src/engine/kumaGraph.ts"() {
|
|
3064
3423
|
"use strict";
|
|
3065
3424
|
init_kumaDb();
|
|
3066
3425
|
init_kumaSelfHeal();
|
|
3426
|
+
GRAPH_LIMITS = {
|
|
3427
|
+
maxNodes: 5e3,
|
|
3428
|
+
// Max nodes before pruning
|
|
3429
|
+
maxEdges: 1e4,
|
|
3430
|
+
// Max edges before pruning
|
|
3431
|
+
pruneBatch: 500,
|
|
3432
|
+
// Remove this many old nodes when limit hit
|
|
3433
|
+
minWeight: 0.1
|
|
3434
|
+
// Minimum edge weight before deletion
|
|
3435
|
+
};
|
|
3067
3436
|
}
|
|
3068
3437
|
});
|
|
3069
3438
|
|
|
@@ -5965,525 +6334,23 @@ async function listGotchas(params) {
|
|
|
5965
6334
|
}
|
|
5966
6335
|
sql += " ORDER BY severity DESC, created_at DESC LIMIT 50";
|
|
5967
6336
|
const stmt = db.prepare(sql);
|
|
5968
|
-
stmt.bind(bind);
|
|
5969
|
-
const results = [];
|
|
5970
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
5971
|
-
stmt.free();
|
|
5972
|
-
if (results.length === 0) {
|
|
5973
|
-
return "\u2705 **No gotchas recorded** \u2014 legacy codebase looks clean.";
|
|
5974
|
-
}
|
|
5975
|
-
const lines = [
|
|
5976
|
-
`\u26A0\uFE0F **Known Gotchas** \u2014 ${results.length} recorded`,
|
|
5977
|
-
"\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",
|
|
5978
|
-
""
|
|
5979
|
-
];
|
|
5980
|
-
for (const g of results) {
|
|
5981
|
-
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
5982
|
-
lines.push(`${icon} [${g.severity}] ${g.file_path}`);
|
|
5983
|
-
lines.push(` \u{1F4DD} ${g.description?.toString().substring(0, 100)}`);
|
|
5984
|
-
if (g.workaround) lines.push(` \u{1F4A1} ${g.workaround.substring(0, 100)}`);
|
|
5985
|
-
lines.push("");
|
|
5986
|
-
}
|
|
5987
|
-
return lines.join("\n");
|
|
5988
|
-
} catch (err) {
|
|
5989
|
-
return `Error: ${err}`;
|
|
5990
|
-
}
|
|
5991
|
-
}
|
|
5992
|
-
function checkGotchasForFile(filePath) {
|
|
5993
|
-
const warnings = [];
|
|
5994
|
-
const markdownWarnings = checkFileGotchas(filePath);
|
|
5995
|
-
try {
|
|
5996
|
-
const gotchas = getActiveGotchas();
|
|
5997
|
-
for (const g of gotchas) {
|
|
5998
|
-
if (filePath.includes(g.filePath) || g.filePath.includes(filePath)) {
|
|
5999
|
-
if (!markdownWarnings.some((w) => w.includes(g.description))) {
|
|
6000
|
-
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
6001
|
-
warnings.push(`${icon} **Known Gotcha**: ${g.description} (${g.severity})`);
|
|
6002
|
-
}
|
|
6003
|
-
}
|
|
6004
|
-
}
|
|
6005
|
-
} catch {
|
|
6006
|
-
}
|
|
6007
|
-
return [.../* @__PURE__ */ new Set([...markdownWarnings, ...warnings])];
|
|
6008
|
-
}
|
|
6009
|
-
async function syncGotchasToDb() {
|
|
6010
|
-
try {
|
|
6011
|
-
await ensureGotchasSchema();
|
|
6012
|
-
const db = await getDb();
|
|
6013
|
-
const gotchas = getActiveGotchas();
|
|
6014
|
-
let synced = 0;
|
|
6015
|
-
for (const g of gotchas) {
|
|
6016
|
-
const checkStmt = db.prepare(
|
|
6017
|
-
`SELECT id FROM known_gotchas WHERE file_path = ? AND description = ?`
|
|
6018
|
-
);
|
|
6019
|
-
checkStmt.bind([g.filePath, g.description]);
|
|
6020
|
-
const exists = checkStmt.step();
|
|
6021
|
-
checkStmt.free();
|
|
6022
|
-
if (exists) continue;
|
|
6023
|
-
db.run(
|
|
6024
|
-
`INSERT INTO known_gotchas (file_path, description, severity) VALUES (?, ?, ?)`,
|
|
6025
|
-
[g.filePath, g.description, g.severity]
|
|
6026
|
-
);
|
|
6027
|
-
synced++;
|
|
6028
|
-
}
|
|
6029
|
-
saveDb();
|
|
6030
|
-
return { synced };
|
|
6031
|
-
} catch {
|
|
6032
|
-
return { synced: 0 };
|
|
6033
|
-
}
|
|
6034
|
-
}
|
|
6035
|
-
var init_kumaGotchas = __esm({
|
|
6036
|
-
"src/engine/kumaGotchas.ts"() {
|
|
6037
|
-
"use strict";
|
|
6038
|
-
init_sessionMemory();
|
|
6039
|
-
init_kumaDb();
|
|
6040
|
-
init_domainRules();
|
|
6041
|
-
}
|
|
6042
|
-
});
|
|
6043
|
-
|
|
6044
|
-
// src/engine/kumaTrajectory.ts
|
|
6045
|
-
var kumaTrajectory_exports = {};
|
|
6046
|
-
__export(kumaTrajectory_exports, {
|
|
6047
|
-
findSimilarTrajectories: () => findSimilarTrajectories,
|
|
6048
|
-
generateTestFromTrajectory: () => generateTestFromTrajectory,
|
|
6049
|
-
generateTestFromTrajectoryId: () => generateTestFromTrajectoryId,
|
|
6050
|
-
listDistilledSkills: () => listDistilledSkills,
|
|
6051
|
-
listGeneratedTests: () => listGeneratedTests,
|
|
6052
|
-
listTrajectories: () => listTrajectories,
|
|
6053
|
-
recordTrajectory: () => recordTrajectory
|
|
6054
|
-
});
|
|
6055
|
-
import fs14 from "fs";
|
|
6056
|
-
import path14 from "path";
|
|
6057
|
-
async function ensureTrajectorySchema() {
|
|
6058
|
-
const db = await getDb();
|
|
6059
|
-
db.exec(`
|
|
6060
|
-
CREATE TABLE IF NOT EXISTS trajectories (
|
|
6061
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6062
|
-
goal TEXT NOT NULL,
|
|
6063
|
-
steps TEXT NOT NULL DEFAULT '[]',
|
|
6064
|
-
total_duration_ms INTEGER DEFAULT 0,
|
|
6065
|
-
success_rate REAL DEFAULT 0.0,
|
|
6066
|
-
complexity INTEGER DEFAULT 0,
|
|
6067
|
-
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
6068
|
-
);
|
|
6069
|
-
CREATE INDEX IF NOT EXISTS idx_traj_goal ON trajectories(goal);
|
|
6070
|
-
CREATE INDEX IF NOT EXISTS idx_traj_complexity ON trajectories(complexity);
|
|
6071
|
-
CREATE INDEX IF NOT EXISTS idx_traj_created ON trajectories(created_at);
|
|
6072
|
-
`);
|
|
6073
|
-
db.exec(`
|
|
6074
|
-
CREATE TABLE IF NOT EXISTS distilled_skills (
|
|
6075
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6076
|
-
name TEXT NOT NULL UNIQUE,
|
|
6077
|
-
description TEXT NOT NULL,
|
|
6078
|
-
pattern TEXT NOT NULL, -- JSON: tool sequence pattern
|
|
6079
|
-
parameters TEXT DEFAULT '[]', -- JSON: parameterized inputs
|
|
6080
|
-
success_count INTEGER DEFAULT 1,
|
|
6081
|
-
avg_duration_ms INTEGER DEFAULT 0,
|
|
6082
|
-
source_trajectory_id INTEGER,
|
|
6083
|
-
last_used_at INTEGER,
|
|
6084
|
-
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
6085
|
-
);
|
|
6086
|
-
CREATE INDEX IF NOT EXISTS idx_skills_name ON distilled_skills(name);
|
|
6087
|
-
CREATE INDEX IF NOT EXISTS idx_skills_used ON distilled_skills(last_used_at);
|
|
6088
|
-
`);
|
|
6089
|
-
db.exec(`
|
|
6090
|
-
CREATE TABLE IF NOT EXISTS generated_tests (
|
|
6091
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6092
|
-
trajectory_id INTEGER,
|
|
6093
|
-
file_path TEXT NOT NULL,
|
|
6094
|
-
test_framework TEXT NOT NULL,
|
|
6095
|
-
description TEXT,
|
|
6096
|
-
test_code TEXT NOT NULL,
|
|
6097
|
-
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
6098
|
-
);
|
|
6099
|
-
CREATE INDEX IF NOT EXISTS idx_gen_tests_traj ON generated_tests(trajectory_id);
|
|
6100
|
-
`);
|
|
6101
|
-
saveDb();
|
|
6102
|
-
}
|
|
6103
|
-
async function recordTrajectory(goal) {
|
|
6104
|
-
try {
|
|
6105
|
-
await ensureTrajectorySchema();
|
|
6106
|
-
const calls = sessionMemory.getToolCallHistory(100);
|
|
6107
|
-
const steps = calls.map((c) => ({
|
|
6108
|
-
toolName: c.toolName,
|
|
6109
|
-
params: c.params,
|
|
6110
|
-
success: true,
|
|
6111
|
-
durationMs: 0,
|
|
6112
|
-
timestamp: c.timestamp
|
|
6113
|
-
}));
|
|
6114
|
-
const totalDurationMs = steps.length > 0 ? steps[steps.length - 1].timestamp - steps[0].timestamp : 0;
|
|
6115
|
-
const errorCalls = steps.filter((s) => {
|
|
6116
|
-
const action = s.params.action;
|
|
6117
|
-
return action === "verify" && s.params.success === false;
|
|
6118
|
-
}).length;
|
|
6119
|
-
const successRate = steps.length > 0 ? (steps.length - errorCalls) / steps.length : 1;
|
|
6120
|
-
const complexity = Math.min(
|
|
6121
|
-
100,
|
|
6122
|
-
Math.round(
|
|
6123
|
-
steps.length * 5 + // 5 pts per step
|
|
6124
|
-
errorCalls * 15 + // 15 pts per error
|
|
6125
|
-
Math.min(totalDurationMs / 6e4 * 10, 30)
|
|
6126
|
-
// 10 pts per minute (max 30)
|
|
6127
|
-
)
|
|
6128
|
-
);
|
|
6129
|
-
const db2 = await getDb();
|
|
6130
|
-
db2.run(
|
|
6131
|
-
`INSERT INTO trajectories (goal, steps, total_duration_ms, success_rate, complexity)
|
|
6132
|
-
VALUES (?, ?, ?, ?, ?)`,
|
|
6133
|
-
[
|
|
6134
|
-
goal.substring(0, 200),
|
|
6135
|
-
JSON.stringify(steps),
|
|
6136
|
-
totalDurationMs,
|
|
6137
|
-
successRate,
|
|
6138
|
-
complexity
|
|
6139
|
-
]
|
|
6140
|
-
);
|
|
6141
|
-
saveDb();
|
|
6142
|
-
const result = db2.exec("SELECT last_insert_rowid() as id");
|
|
6143
|
-
const id = result[0]?.values[0]?.[0];
|
|
6144
|
-
if (id !== null && complexity > 40 && successRate > 0.8) {
|
|
6145
|
-
await distillSkill(id, goal, steps).catch(() => {
|
|
6146
|
-
});
|
|
6147
|
-
if (goal.toLowerCase().includes("fix") || goal.toLowerCase().includes("bug") || goal.toLowerCase().includes("error")) {
|
|
6148
|
-
await generateTestFromTrajectory(id, goal, steps).catch(() => {
|
|
6149
|
-
});
|
|
6150
|
-
}
|
|
6151
|
-
}
|
|
6152
|
-
return id ?? null;
|
|
6153
|
-
} catch (err) {
|
|
6154
|
-
console.error(`[Trajectory] Record error: ${err}`);
|
|
6155
|
-
return null;
|
|
6156
|
-
}
|
|
6157
|
-
}
|
|
6158
|
-
async function distillSkill(trajectoryId, goal, steps) {
|
|
6159
|
-
try {
|
|
6160
|
-
const db = await getDb();
|
|
6161
|
-
const toolSequence = steps.map((s) => s.toolName);
|
|
6162
|
-
const patternKey = toolSequence.join(" \u2192 ");
|
|
6163
|
-
const uniqueTools = [...new Set(toolSequence)];
|
|
6164
|
-
const skillName = `trajectory:${uniqueTools.slice(0, 3).join("-")}`;
|
|
6165
|
-
const checkStmt = db.prepare(
|
|
6166
|
-
"SELECT id, success_count, avg_duration_ms FROM distilled_skills WHERE name = ?"
|
|
6167
|
-
);
|
|
6168
|
-
checkStmt.bind([skillName]);
|
|
6169
|
-
const exists = checkStmt.step();
|
|
6170
|
-
let existingRow = null;
|
|
6171
|
-
if (exists) {
|
|
6172
|
-
existingRow = checkStmt.getAsObject();
|
|
6173
|
-
}
|
|
6174
|
-
checkStmt.free();
|
|
6175
|
-
const totalDuration = steps.reduce((sum, s) => sum + s.durationMs, 0);
|
|
6176
|
-
if (existingRow) {
|
|
6177
|
-
const existingId = existingRow.id;
|
|
6178
|
-
const existingCount = existingRow.success_count;
|
|
6179
|
-
const existingAvg = existingRow.avg_duration_ms;
|
|
6180
|
-
db.run(
|
|
6181
|
-
`UPDATE distilled_skills
|
|
6182
|
-
SET success_count = ?, avg_duration_ms = ?, last_used_at = strftime('%s','now')
|
|
6183
|
-
WHERE id = ?`,
|
|
6184
|
-
[
|
|
6185
|
-
existingCount + 1,
|
|
6186
|
-
Math.round((existingAvg * existingCount + totalDuration) / (existingCount + 1)),
|
|
6187
|
-
existingId
|
|
6188
|
-
]
|
|
6189
|
-
);
|
|
6190
|
-
} else {
|
|
6191
|
-
const description = [
|
|
6192
|
-
`Distilled trajectory pattern for "${goal.substring(0, 60)}"`,
|
|
6193
|
-
`Tool sequence: ${patternKey}`,
|
|
6194
|
-
`${steps.length} steps, ${uniqueTools.length} unique tools`
|
|
6195
|
-
].join(". ");
|
|
6196
|
-
db.run(
|
|
6197
|
-
`INSERT INTO distilled_skills (name, description, pattern, parameters, success_count, avg_duration_ms, source_trajectory_id, last_used_at)
|
|
6198
|
-
VALUES (?, ?, ?, ?, 1, ?, ?, strftime('%s','now'))`,
|
|
6199
|
-
[
|
|
6200
|
-
skillName,
|
|
6201
|
-
description,
|
|
6202
|
-
JSON.stringify(toolSequence),
|
|
6203
|
-
JSON.stringify(extractParameters(steps)),
|
|
6204
|
-
totalDuration,
|
|
6205
|
-
trajectoryId
|
|
6206
|
-
]
|
|
6207
|
-
);
|
|
6208
|
-
}
|
|
6209
|
-
saveDb();
|
|
6210
|
-
console.error(`[Trajectory] Distilled skill "${skillName}" from trajectory #${trajectoryId}`);
|
|
6211
|
-
} catch (err) {
|
|
6212
|
-
console.error(`[Trajectory] Distill error: ${err}`);
|
|
6213
|
-
}
|
|
6214
|
-
}
|
|
6215
|
-
function extractParameters(steps) {
|
|
6216
|
-
const params = /* @__PURE__ */ new Set();
|
|
6217
|
-
for (const step of steps) {
|
|
6218
|
-
for (const [key] of Object.entries(step.params)) {
|
|
6219
|
-
if (["scope", "target", "query", "filePath", "action"].includes(key)) {
|
|
6220
|
-
params.add(key);
|
|
6221
|
-
}
|
|
6222
|
-
}
|
|
6223
|
-
}
|
|
6224
|
-
return Array.from(params);
|
|
6225
|
-
}
|
|
6226
|
-
async function generateTestFromTrajectory(trajectoryId, goal, steps) {
|
|
6227
|
-
try {
|
|
6228
|
-
await ensureTrajectorySchema();
|
|
6229
|
-
const modifiedFiles = steps.filter((s) => s.params.filePath || s.params.target).map((s) => s.params.filePath || s.params.target).filter(Boolean);
|
|
6230
|
-
if (modifiedFiles.length === 0) return null;
|
|
6231
|
-
const framework = detectTestFramework();
|
|
6232
|
-
const primaryFile = modifiedFiles[0];
|
|
6233
|
-
const testCode = synthesizeTestCode(goal, primaryFile, modifiedFiles, framework);
|
|
6234
|
-
const testFilePath = determineTestPath(primaryFile, framework);
|
|
6235
|
-
const genTest = {
|
|
6236
|
-
filePath: testFilePath,
|
|
6237
|
-
framework,
|
|
6238
|
-
code: testCode,
|
|
6239
|
-
description: `Auto-generated from trajectory #${trajectoryId}: ${goal.substring(0, 80)}`
|
|
6240
|
-
};
|
|
6241
|
-
const db = await getDb();
|
|
6242
|
-
db.run(
|
|
6243
|
-
`INSERT INTO generated_tests (trajectory_id, file_path, test_framework, description, test_code)
|
|
6244
|
-
VALUES (?, ?, ?, ?, ?)`,
|
|
6245
|
-
[trajectoryId, testFilePath, framework, genTest.description, testCode]
|
|
6246
|
-
);
|
|
6247
|
-
saveDb();
|
|
6248
|
-
const root = getProjectRoot();
|
|
6249
|
-
const fullPath = path14.resolve(root, testFilePath);
|
|
6250
|
-
const dir = path14.dirname(fullPath);
|
|
6251
|
-
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
6252
|
-
fs14.writeFileSync(fullPath, testCode, "utf-8");
|
|
6253
|
-
console.error(`[Trajectory] Generated test: ${testFilePath} from trajectory #${trajectoryId}`);
|
|
6254
|
-
return genTest;
|
|
6255
|
-
} catch (err) {
|
|
6256
|
-
console.error(`[Trajectory] Test generation error: ${err}`);
|
|
6257
|
-
return null;
|
|
6258
|
-
}
|
|
6259
|
-
}
|
|
6260
|
-
async function generateTestFromTrajectoryId(trajectoryId) {
|
|
6261
|
-
try {
|
|
6262
|
-
await ensureTrajectorySchema();
|
|
6263
|
-
const db = await getDb();
|
|
6264
|
-
const stmt = db.prepare("SELECT id, goal, steps FROM trajectories WHERE id = ?");
|
|
6265
|
-
stmt.bind([trajectoryId]);
|
|
6266
|
-
if (!stmt.step()) {
|
|
6267
|
-
stmt.free();
|
|
6268
|
-
return `\u274C Trajectory #${trajectoryId} not found.`;
|
|
6269
|
-
}
|
|
6270
|
-
const row = stmt.getAsObject();
|
|
6271
|
-
stmt.free();
|
|
6272
|
-
const goal = row.goal;
|
|
6273
|
-
const steps = JSON.parse(row.steps);
|
|
6274
|
-
const result = await generateTestFromTrajectory(trajectoryId, goal, steps);
|
|
6275
|
-
if (!result) {
|
|
6276
|
-
return "\u26A0\uFE0F Could not generate test \u2014 no modified files found in trajectory.";
|
|
6277
|
-
}
|
|
6278
|
-
return [
|
|
6279
|
-
`\u{1F9EA} **Test Generated** from trajectory #${trajectoryId}`,
|
|
6280
|
-
`\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`,
|
|
6281
|
-
``,
|
|
6282
|
-
`\u{1F4C1} **File**: ${result.filePath}`,
|
|
6283
|
-
`\u26A1 **Framework**: ${result.framework}`,
|
|
6284
|
-
`\u{1F4DD} **Description**: ${result.description}`,
|
|
6285
|
-
``,
|
|
6286
|
-
`\`\`\`${result.framework === "jest" ? "typescript" : "javascript"}`,
|
|
6287
|
-
result.code.substring(0, 800),
|
|
6288
|
-
result.code.length > 800 ? "..." : "",
|
|
6289
|
-
`\`\`\``,
|
|
6290
|
-
``,
|
|
6291
|
-
`\u{1F4A1} Test file has been written to disk. Run your test suite to verify.`
|
|
6292
|
-
].filter(Boolean).join("\n");
|
|
6293
|
-
} catch (err) {
|
|
6294
|
-
return `\u274C Test generation failed: ${err}`;
|
|
6295
|
-
}
|
|
6296
|
-
}
|
|
6297
|
-
async function listGeneratedTests() {
|
|
6298
|
-
try {
|
|
6299
|
-
await ensureTrajectorySchema();
|
|
6300
|
-
const db = await getDb();
|
|
6301
|
-
const stmt = db.prepare(`
|
|
6302
|
-
SELECT gt.*, t.goal FROM generated_tests gt
|
|
6303
|
-
LEFT JOIN trajectories t ON t.id = gt.trajectory_id
|
|
6304
|
-
ORDER BY gt.created_at DESC LIMIT 20
|
|
6305
|
-
`);
|
|
6306
|
-
const results = [];
|
|
6307
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
6308
|
-
stmt.free();
|
|
6309
|
-
if (results.length === 0) {
|
|
6310
|
-
return "\u{1F9EA} **No generated tests yet.** Tests are auto-generated from successful fix trajectories.";
|
|
6311
|
-
}
|
|
6312
|
-
const lines = [
|
|
6313
|
-
"\u{1F9EA} **Generated Tests**",
|
|
6314
|
-
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
6315
|
-
""
|
|
6316
|
-
];
|
|
6317
|
-
for (const r of results) {
|
|
6318
|
-
const time = new Date(r.created_at * 1e3).toLocaleString();
|
|
6319
|
-
lines.push(` \u{1F4C4} **${r.file_path}**`);
|
|
6320
|
-
lines.push(` \u26A1 ${r.test_framework} | \u{1F550} ${time}`);
|
|
6321
|
-
if (r.goal) lines.push(` \u{1F3AF} ${r.goal.substring(0, 60)}`);
|
|
6322
|
-
lines.push("");
|
|
6323
|
-
}
|
|
6324
|
-
return lines.join("\n");
|
|
6325
|
-
} catch (err) {
|
|
6326
|
-
return `Error: ${err}`;
|
|
6327
|
-
}
|
|
6328
|
-
}
|
|
6329
|
-
function detectTestFramework() {
|
|
6330
|
-
const root = getProjectRoot();
|
|
6331
|
-
try {
|
|
6332
|
-
const pkg = JSON.parse(fs14.readFileSync(path14.join(root, "package.json"), "utf-8"));
|
|
6333
|
-
if (pkg.devDependencies?.vitest) return "vitest";
|
|
6334
|
-
if (pkg.devDependencies?.jest || pkg.devDependencies?.["@jest/globals"]) return "jest";
|
|
6335
|
-
} catch {
|
|
6336
|
-
}
|
|
6337
|
-
if (fs14.existsSync(path14.join(root, "vitest.config.ts")) || fs14.existsSync(path14.join(root, "vitest.config.js"))) return "vitest";
|
|
6338
|
-
if (fs14.existsSync(path14.join(root, "jest.config.ts")) || fs14.existsSync(path14.join(root, "jest.config.js"))) return "jest";
|
|
6339
|
-
return "node";
|
|
6340
|
-
}
|
|
6341
|
-
function determineTestPath(filePath, _framework) {
|
|
6342
|
-
const dir = path14.dirname(filePath);
|
|
6343
|
-
const basename = path14.basename(filePath, path14.extname(filePath));
|
|
6344
|
-
return path14.join(dir, `__tests__`, `${basename}.fix.test.ts`);
|
|
6345
|
-
}
|
|
6346
|
-
function synthesizeTestCode(goal, primaryFile, modifiedFiles, framework) {
|
|
6347
|
-
const importPath = primaryFile.replace(/\.ts$/, "").replace(/\.js$/, "");
|
|
6348
|
-
const describeBlock = `describe('Fix: ${goal.substring(0, 60)}', () => {`;
|
|
6349
|
-
const testBody = `
|
|
6350
|
-
it('should not regress the fix applied in ${path14.basename(primaryFile)}', async () => {
|
|
6351
|
-
// Auto-generated from Kuma trajectory \u2014 regression test
|
|
6352
|
-
// Source file: ${primaryFile}
|
|
6353
|
-
// Goal: ${goal}
|
|
6354
|
-
// Modified files: ${modifiedFiles.slice(0, 3).join(", ")}
|
|
6355
|
-
|
|
6356
|
-
// TODO: Replace with actual test logic
|
|
6357
|
-
// const { yourFunction } = await import('${importPath}');
|
|
6358
|
-
// const result = yourFunction();
|
|
6359
|
-
// expect(result).toBeDefined();
|
|
6360
|
-
|
|
6361
|
-
expect(true).toBe(true);
|
|
6362
|
-
});
|
|
6363
|
-
`;
|
|
6364
|
-
const closeBlock = `});`;
|
|
6365
|
-
switch (framework) {
|
|
6366
|
-
case "vitest":
|
|
6367
|
-
case "jest":
|
|
6368
|
-
return [
|
|
6369
|
-
`// ============================================================`,
|
|
6370
|
-
`// AUTO-GENERATED REGRESSION TEST`,
|
|
6371
|
-
`// Generated by Kuma Trajectory-to-Test Generator (Issue #28)`,
|
|
6372
|
-
`// ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
6373
|
-
`// Goal: ${goal}`,
|
|
6374
|
-
`// Files: ${modifiedFiles.join(", ")}`,
|
|
6375
|
-
`// ============================================================`,
|
|
6376
|
-
``,
|
|
6377
|
-
`import { describe, it, expect } from '@jest/globals';`,
|
|
6378
|
-
``,
|
|
6379
|
-
describeBlock,
|
|
6380
|
-
testBody,
|
|
6381
|
-
closeBlock,
|
|
6382
|
-
``
|
|
6383
|
-
].join("\n");
|
|
6384
|
-
default:
|
|
6385
|
-
return [
|
|
6386
|
-
`// AUTO-GENERATED REGRESSION TEST`,
|
|
6387
|
-
`// Goal: ${goal}`,
|
|
6388
|
-
`// Files: ${modifiedFiles.join(", ")}`,
|
|
6389
|
-
``,
|
|
6390
|
-
`const assert = require('assert');`,
|
|
6391
|
-
``,
|
|
6392
|
-
describeBlock,
|
|
6393
|
-
testBody.replace("import", "// ").replace("expect", "// expect"),
|
|
6394
|
-
closeBlock,
|
|
6395
|
-
``
|
|
6396
|
-
].join("\n");
|
|
6397
|
-
}
|
|
6398
|
-
}
|
|
6399
|
-
async function listDistilledSkills() {
|
|
6400
|
-
try {
|
|
6401
|
-
await ensureTrajectorySchema();
|
|
6402
|
-
const db = await getDb();
|
|
6403
|
-
const stmt = db.prepare(`
|
|
6404
|
-
SELECT * FROM distilled_skills ORDER BY success_count DESC, last_used_at DESC LIMIT 20
|
|
6405
|
-
`);
|
|
6406
|
-
const results = [];
|
|
6407
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
6408
|
-
stmt.free();
|
|
6409
|
-
if (results.length === 0) {
|
|
6410
|
-
return "\u{1F9E0} **No distilled skills yet** \u2014 skills are auto-generated from successful trajectories.";
|
|
6411
|
-
}
|
|
6412
|
-
const lines = [
|
|
6413
|
-
"\u{1F9E0} **Distilled Skills** \u2014 auto-generated from trajectories",
|
|
6414
|
-
"\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",
|
|
6415
|
-
""
|
|
6416
|
-
];
|
|
6417
|
-
for (const s of results) {
|
|
6418
|
-
const pattern = JSON.parse(s.pattern || "[]");
|
|
6419
|
-
const patternStr = pattern.slice(0, 5).join(" \u2192 ");
|
|
6420
|
-
lines.push(` \u26A1 **${s.name}**`);
|
|
6421
|
-
lines.push(` Pattern: ${patternStr}${pattern.length > 5 ? ` (+${pattern.length - 5} more)` : ""}`);
|
|
6422
|
-
lines.push(` Used ${s.success_count}x | Avg ${s.avg_duration_ms}ms`);
|
|
6423
|
-
lines.push("");
|
|
6424
|
-
}
|
|
6425
|
-
return lines.join("\n");
|
|
6426
|
-
} catch (err) {
|
|
6427
|
-
return `Error: ${err}`;
|
|
6428
|
-
}
|
|
6429
|
-
}
|
|
6430
|
-
async function findSimilarTrajectories(errorPattern, limit = 5) {
|
|
6431
|
-
try {
|
|
6432
|
-
await ensureTrajectorySchema();
|
|
6433
|
-
const db = await getDb();
|
|
6434
|
-
const stmt = db.prepare(`
|
|
6435
|
-
SELECT id, goal, steps, total_duration_ms, success_rate, complexity, created_at
|
|
6436
|
-
FROM trajectories
|
|
6437
|
-
WHERE steps LIKE ?
|
|
6438
|
-
ORDER BY complexity DESC, created_at DESC
|
|
6439
|
-
LIMIT ?
|
|
6440
|
-
`);
|
|
6441
|
-
stmt.bind([`%${errorPattern}%`, limit]);
|
|
6442
|
-
const results = [];
|
|
6443
|
-
while (stmt.step()) results.push(stmt.getAsObject());
|
|
6444
|
-
stmt.free();
|
|
6445
|
-
if (results.length === 0) return "";
|
|
6446
|
-
const lines = [
|
|
6447
|
-
"\u{1F504} **Similar Past Trajectories Found**",
|
|
6448
|
-
"\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",
|
|
6449
|
-
""
|
|
6450
|
-
];
|
|
6451
|
-
for (const r of results) {
|
|
6452
|
-
const successIcon = r.success_rate > 0.8 ? "\u2705" : "\u26A0\uFE0F";
|
|
6453
|
-
lines.push(`${successIcon} #${r.id} \u2014 ${r.goal.substring(0, 60)}`);
|
|
6454
|
-
lines.push(` Complexity: ${r.complexity} | Duration: ${Math.round(r.total_duration_ms / 1e3)}s | Rate: ${Math.round(r.success_rate * 100)}%`);
|
|
6455
|
-
lines.push("");
|
|
6456
|
-
}
|
|
6457
|
-
return lines.join("\n");
|
|
6458
|
-
} catch {
|
|
6459
|
-
return "";
|
|
6460
|
-
}
|
|
6461
|
-
}
|
|
6462
|
-
async function listTrajectories(limit = 10) {
|
|
6463
|
-
try {
|
|
6464
|
-
await ensureTrajectorySchema();
|
|
6465
|
-
const db = await getDb();
|
|
6466
|
-
const stmt = db.prepare(`
|
|
6467
|
-
SELECT id, goal, total_duration_ms, success_rate, complexity, created_at
|
|
6468
|
-
FROM trajectories ORDER BY created_at DESC LIMIT ?
|
|
6469
|
-
`);
|
|
6470
|
-
stmt.bind([limit]);
|
|
6337
|
+
stmt.bind(bind);
|
|
6471
6338
|
const results = [];
|
|
6472
6339
|
while (stmt.step()) results.push(stmt.getAsObject());
|
|
6473
6340
|
stmt.free();
|
|
6474
6341
|
if (results.length === 0) {
|
|
6475
|
-
return "\
|
|
6342
|
+
return "\u2705 **No gotchas recorded** \u2014 legacy codebase looks clean.";
|
|
6476
6343
|
}
|
|
6477
6344
|
const lines = [
|
|
6478
|
-
|
|
6479
|
-
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
6345
|
+
`\u26A0\uFE0F **Known Gotchas** \u2014 ${results.length} recorded`,
|
|
6346
|
+
"\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",
|
|
6480
6347
|
""
|
|
6481
6348
|
];
|
|
6482
|
-
for (const
|
|
6483
|
-
const
|
|
6484
|
-
|
|
6485
|
-
lines.push(`
|
|
6486
|
-
lines.push(`
|
|
6349
|
+
for (const g of results) {
|
|
6350
|
+
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
6351
|
+
lines.push(`${icon} [${g.severity}] ${g.file_path}`);
|
|
6352
|
+
lines.push(` \u{1F4DD} ${g.description?.toString().substring(0, 100)}`);
|
|
6353
|
+
if (g.workaround) lines.push(` \u{1F4A1} ${g.workaround.substring(0, 100)}`);
|
|
6487
6354
|
lines.push("");
|
|
6488
6355
|
}
|
|
6489
6356
|
return lines.join("\n");
|
|
@@ -6491,12 +6358,55 @@ async function listTrajectories(limit = 10) {
|
|
|
6491
6358
|
return `Error: ${err}`;
|
|
6492
6359
|
}
|
|
6493
6360
|
}
|
|
6494
|
-
|
|
6495
|
-
|
|
6361
|
+
function checkGotchasForFile(filePath) {
|
|
6362
|
+
const warnings = [];
|
|
6363
|
+
const markdownWarnings = checkFileGotchas(filePath);
|
|
6364
|
+
try {
|
|
6365
|
+
const gotchas = getActiveGotchas();
|
|
6366
|
+
for (const g of gotchas) {
|
|
6367
|
+
if (filePath.includes(g.filePath) || g.filePath.includes(filePath)) {
|
|
6368
|
+
if (!markdownWarnings.some((w) => w.includes(g.description))) {
|
|
6369
|
+
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
6370
|
+
warnings.push(`${icon} **Known Gotcha**: ${g.description} (${g.severity})`);
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
} catch {
|
|
6375
|
+
}
|
|
6376
|
+
return [.../* @__PURE__ */ new Set([...markdownWarnings, ...warnings])];
|
|
6377
|
+
}
|
|
6378
|
+
async function syncGotchasToDb() {
|
|
6379
|
+
try {
|
|
6380
|
+
await ensureGotchasSchema();
|
|
6381
|
+
const db = await getDb();
|
|
6382
|
+
const gotchas = getActiveGotchas();
|
|
6383
|
+
let synced = 0;
|
|
6384
|
+
for (const g of gotchas) {
|
|
6385
|
+
const checkStmt = db.prepare(
|
|
6386
|
+
`SELECT id FROM known_gotchas WHERE file_path = ? AND description = ?`
|
|
6387
|
+
);
|
|
6388
|
+
checkStmt.bind([g.filePath, g.description]);
|
|
6389
|
+
const exists = checkStmt.step();
|
|
6390
|
+
checkStmt.free();
|
|
6391
|
+
if (exists) continue;
|
|
6392
|
+
db.run(
|
|
6393
|
+
`INSERT INTO known_gotchas (file_path, description, severity) VALUES (?, ?, ?)`,
|
|
6394
|
+
[g.filePath, g.description, g.severity]
|
|
6395
|
+
);
|
|
6396
|
+
synced++;
|
|
6397
|
+
}
|
|
6398
|
+
saveDb();
|
|
6399
|
+
return { synced };
|
|
6400
|
+
} catch {
|
|
6401
|
+
return { synced: 0 };
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
var init_kumaGotchas = __esm({
|
|
6405
|
+
"src/engine/kumaGotchas.ts"() {
|
|
6496
6406
|
"use strict";
|
|
6497
|
-
init_kumaDb();
|
|
6498
6407
|
init_sessionMemory();
|
|
6499
|
-
|
|
6408
|
+
init_kumaDb();
|
|
6409
|
+
init_domainRules();
|
|
6500
6410
|
}
|
|
6501
6411
|
});
|
|
6502
6412
|
|
|
@@ -6669,19 +6579,19 @@ __export(kumaPolicyEngine_exports, {
|
|
|
6669
6579
|
processOverride: () => processOverride,
|
|
6670
6580
|
savePolicyConfig: () => savePolicyConfig
|
|
6671
6581
|
});
|
|
6672
|
-
import
|
|
6673
|
-
import
|
|
6582
|
+
import fs19 from "fs";
|
|
6583
|
+
import path19 from "path";
|
|
6674
6584
|
function loadPolicyConfig() {
|
|
6675
6585
|
try {
|
|
6676
6586
|
const root = getProjectRoot();
|
|
6677
|
-
const policyPath =
|
|
6678
|
-
if (
|
|
6679
|
-
const content =
|
|
6587
|
+
const policyPath = path19.join(root, ".kuma", "POLICY.json");
|
|
6588
|
+
if (fs19.existsSync(policyPath)) {
|
|
6589
|
+
const content = fs19.readFileSync(policyPath, "utf-8");
|
|
6680
6590
|
const config = JSON.parse(content);
|
|
6681
6591
|
return { ...DEFAULT_POLICY_CONFIG, ...config };
|
|
6682
6592
|
}
|
|
6683
|
-
const ymlPath =
|
|
6684
|
-
if (
|
|
6593
|
+
const ymlPath = path19.join(root, ".kuma", "policy.yml");
|
|
6594
|
+
if (fs19.existsSync(ymlPath)) {
|
|
6685
6595
|
console.error("[PolicyEngine] Found legacy policy.yml \u2014 consider migrating to POLICY.json");
|
|
6686
6596
|
}
|
|
6687
6597
|
} catch (err) {
|
|
@@ -6692,10 +6602,10 @@ function loadPolicyConfig() {
|
|
|
6692
6602
|
function savePolicyConfig(config) {
|
|
6693
6603
|
try {
|
|
6694
6604
|
const root = getProjectRoot();
|
|
6695
|
-
const policyPath =
|
|
6696
|
-
const dir =
|
|
6697
|
-
if (!
|
|
6698
|
-
|
|
6605
|
+
const policyPath = path19.join(root, ".kuma", "POLICY.json");
|
|
6606
|
+
const dir = path19.dirname(policyPath);
|
|
6607
|
+
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
6608
|
+
fs19.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6699
6609
|
return `\u2705 Policy config saved to .kuma/POLICY.json (${config.rules.length} rules)`;
|
|
6700
6610
|
} catch (err) {
|
|
6701
6611
|
return `\u274C Failed to save policy: ${err}`;
|
|
@@ -6980,14 +6890,14 @@ __export(kumaAstValidator_exports, {
|
|
|
6980
6890
|
validateDiff: () => validateDiff,
|
|
6981
6891
|
validateFile: () => validateFile
|
|
6982
6892
|
});
|
|
6983
|
-
import
|
|
6984
|
-
import
|
|
6893
|
+
import fs20 from "fs";
|
|
6894
|
+
import path20 from "path";
|
|
6985
6895
|
function loadImportWhitelist() {
|
|
6986
6896
|
try {
|
|
6987
6897
|
const root = getProjectRoot();
|
|
6988
|
-
const whitelistPath =
|
|
6989
|
-
if (
|
|
6990
|
-
return JSON.parse(
|
|
6898
|
+
const whitelistPath = path20.join(root, ".kuma", "import-whitelist.json");
|
|
6899
|
+
if (fs20.existsSync(whitelistPath)) {
|
|
6900
|
+
return JSON.parse(fs20.readFileSync(whitelistPath, "utf-8"));
|
|
6991
6901
|
}
|
|
6992
6902
|
} catch {
|
|
6993
6903
|
}
|
|
@@ -7061,9 +6971,9 @@ function validateCodeContent(code, filePath) {
|
|
|
7061
6971
|
function validateFile(filePath) {
|
|
7062
6972
|
try {
|
|
7063
6973
|
const root = getProjectRoot();
|
|
7064
|
-
const fullPath =
|
|
7065
|
-
if (!
|
|
7066
|
-
const content =
|
|
6974
|
+
const fullPath = path20.resolve(root, filePath);
|
|
6975
|
+
if (!fs20.existsSync(fullPath)) return [];
|
|
6976
|
+
const content = fs20.readFileSync(fullPath, "utf-8");
|
|
7067
6977
|
return validateCodeContent(content, filePath);
|
|
7068
6978
|
} catch {
|
|
7069
6979
|
return [];
|
|
@@ -7134,16 +7044,16 @@ __export(kumaCheckpoint_exports, {
|
|
|
7134
7044
|
pruneCheckpoints: () => pruneCheckpoints,
|
|
7135
7045
|
rollbackToCheckpoint: () => rollbackToCheckpoint
|
|
7136
7046
|
});
|
|
7137
|
-
import
|
|
7138
|
-
import
|
|
7047
|
+
import fs21 from "fs";
|
|
7048
|
+
import path21 from "path";
|
|
7139
7049
|
async function createCheckpoint(label, description) {
|
|
7140
7050
|
try {
|
|
7141
7051
|
const root = getProjectRoot();
|
|
7142
|
-
const cpDir =
|
|
7143
|
-
if (
|
|
7052
|
+
const cpDir = path21.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
7053
|
+
if (fs21.existsSync(cpDir)) {
|
|
7144
7054
|
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
7145
7055
|
}
|
|
7146
|
-
|
|
7056
|
+
fs21.mkdirSync(cpDir, { recursive: true });
|
|
7147
7057
|
const db = await getDb();
|
|
7148
7058
|
const filesStmt = db.prepare(
|
|
7149
7059
|
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
@@ -7152,19 +7062,19 @@ async function createCheckpoint(label, description) {
|
|
|
7152
7062
|
while (filesStmt.step()) {
|
|
7153
7063
|
const row = filesStmt.getAsObject();
|
|
7154
7064
|
const fp = row.file_path;
|
|
7155
|
-
const fullPath =
|
|
7156
|
-
if (
|
|
7157
|
-
const content =
|
|
7065
|
+
const fullPath = path21.resolve(root, fp);
|
|
7066
|
+
if (fs21.existsSync(fullPath)) {
|
|
7067
|
+
const content = fs21.readFileSync(fullPath, "utf-8");
|
|
7158
7068
|
const hash = simpleHash2(content);
|
|
7159
|
-
const fileDir =
|
|
7160
|
-
if (!
|
|
7161
|
-
|
|
7069
|
+
const fileDir = path21.dirname(path21.join(cpDir, "files", fp));
|
|
7070
|
+
if (!fs21.existsSync(fileDir)) fs21.mkdirSync(fileDir, { recursive: true });
|
|
7071
|
+
fs21.writeFileSync(path21.join(cpDir, "files", fp), content, "utf-8");
|
|
7162
7072
|
files.push({ path: fp, hash });
|
|
7163
7073
|
}
|
|
7164
7074
|
}
|
|
7165
7075
|
filesStmt.free();
|
|
7166
7076
|
const dbData = db.export();
|
|
7167
|
-
|
|
7077
|
+
fs21.writeFileSync(path21.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
7168
7078
|
const manifest = {
|
|
7169
7079
|
label,
|
|
7170
7080
|
timestamp: Date.now(),
|
|
@@ -7172,8 +7082,8 @@ async function createCheckpoint(label, description) {
|
|
|
7172
7082
|
dbSnapshot: true,
|
|
7173
7083
|
description
|
|
7174
7084
|
};
|
|
7175
|
-
|
|
7176
|
-
|
|
7085
|
+
fs21.writeFileSync(
|
|
7086
|
+
path21.join(cpDir, "manifest.json"),
|
|
7177
7087
|
JSON.stringify(manifest, null, 2),
|
|
7178
7088
|
"utf-8"
|
|
7179
7089
|
);
|
|
@@ -7199,37 +7109,37 @@ async function createCheckpoint(label, description) {
|
|
|
7199
7109
|
async function rollbackToCheckpoint(label) {
|
|
7200
7110
|
try {
|
|
7201
7111
|
const root = getProjectRoot();
|
|
7202
|
-
const cpDir =
|
|
7203
|
-
const manifestPath =
|
|
7204
|
-
if (!
|
|
7112
|
+
const cpDir = path21.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
7113
|
+
const manifestPath = path21.join(cpDir, "manifest.json");
|
|
7114
|
+
if (!fs21.existsSync(manifestPath)) {
|
|
7205
7115
|
return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
|
|
7206
7116
|
}
|
|
7207
7117
|
const manifest = JSON.parse(
|
|
7208
|
-
|
|
7118
|
+
fs21.readFileSync(manifestPath, "utf-8")
|
|
7209
7119
|
);
|
|
7210
7120
|
let restored = 0;
|
|
7211
7121
|
let failed = 0;
|
|
7212
7122
|
for (const f of manifest.files) {
|
|
7213
|
-
const snapshotPath =
|
|
7214
|
-
if (
|
|
7123
|
+
const snapshotPath = path21.join(cpDir, "files", f.path);
|
|
7124
|
+
if (fs21.existsSync(snapshotPath)) {
|
|
7215
7125
|
try {
|
|
7216
|
-
const content =
|
|
7217
|
-
const targetPath =
|
|
7218
|
-
const targetDir =
|
|
7219
|
-
if (!
|
|
7220
|
-
|
|
7126
|
+
const content = fs21.readFileSync(snapshotPath, "utf-8");
|
|
7127
|
+
const targetPath = path21.resolve(root, f.path);
|
|
7128
|
+
const targetDir = path21.dirname(targetPath);
|
|
7129
|
+
if (!fs21.existsSync(targetDir)) fs21.mkdirSync(targetDir, { recursive: true });
|
|
7130
|
+
fs21.writeFileSync(targetPath, content, "utf-8");
|
|
7221
7131
|
restored++;
|
|
7222
7132
|
} catch {
|
|
7223
7133
|
failed++;
|
|
7224
7134
|
}
|
|
7225
7135
|
}
|
|
7226
7136
|
}
|
|
7227
|
-
const dbSnapshotPath =
|
|
7228
|
-
if (
|
|
7229
|
-
const kumaDir =
|
|
7230
|
-
const dbPath =
|
|
7231
|
-
const snapshotData =
|
|
7232
|
-
|
|
7137
|
+
const dbSnapshotPath = path21.join(cpDir, "kuma.db");
|
|
7138
|
+
if (fs21.existsSync(dbSnapshotPath)) {
|
|
7139
|
+
const kumaDir = path21.join(root, ".kuma");
|
|
7140
|
+
const dbPath = path21.join(kumaDir, "kuma.db");
|
|
7141
|
+
const snapshotData = fs21.readFileSync(dbSnapshotPath);
|
|
7142
|
+
fs21.writeFileSync(dbPath, snapshotData);
|
|
7233
7143
|
resetDbInstance();
|
|
7234
7144
|
}
|
|
7235
7145
|
sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
|
|
@@ -7254,18 +7164,18 @@ async function rollbackToCheckpoint(label) {
|
|
|
7254
7164
|
function listCheckpoints() {
|
|
7255
7165
|
try {
|
|
7256
7166
|
const root = getProjectRoot();
|
|
7257
|
-
const cpDir =
|
|
7258
|
-
if (!
|
|
7167
|
+
const cpDir = path21.join(root, CHECKPOINT_DIR);
|
|
7168
|
+
if (!fs21.existsSync(cpDir)) {
|
|
7259
7169
|
return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
|
|
7260
7170
|
}
|
|
7261
|
-
const entries =
|
|
7171
|
+
const entries = fs21.readdirSync(cpDir);
|
|
7262
7172
|
const checkpoints = [];
|
|
7263
7173
|
for (const entry of entries) {
|
|
7264
|
-
const manifestPath =
|
|
7265
|
-
if (
|
|
7174
|
+
const manifestPath = path21.join(cpDir, entry, "manifest.json");
|
|
7175
|
+
if (fs21.existsSync(manifestPath)) {
|
|
7266
7176
|
try {
|
|
7267
7177
|
const manifest = JSON.parse(
|
|
7268
|
-
|
|
7178
|
+
fs21.readFileSync(manifestPath, "utf-8")
|
|
7269
7179
|
);
|
|
7270
7180
|
checkpoints.push({ label: entry, manifest });
|
|
7271
7181
|
} catch {
|
|
@@ -7297,12 +7207,12 @@ function listCheckpoints() {
|
|
|
7297
7207
|
function pruneCheckpoints(keep = 5) {
|
|
7298
7208
|
try {
|
|
7299
7209
|
const root = getProjectRoot();
|
|
7300
|
-
const cpDir =
|
|
7301
|
-
if (!
|
|
7302
|
-
const entries =
|
|
7210
|
+
const cpDir = path21.join(root, CHECKPOINT_DIR);
|
|
7211
|
+
if (!fs21.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
|
|
7212
|
+
const entries = fs21.readdirSync(cpDir).map((e) => ({ name: e, time: fs21.statSync(path21.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
7303
7213
|
let removed = 0;
|
|
7304
7214
|
for (let i = keep; i < entries.length; i++) {
|
|
7305
|
-
|
|
7215
|
+
fs21.rmSync(path21.join(cpDir, entries[i].name), { recursive: true, force: true });
|
|
7306
7216
|
removed++;
|
|
7307
7217
|
}
|
|
7308
7218
|
return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
|
|
@@ -7342,18 +7252,18 @@ __export(kumaContractEngine_exports, {
|
|
|
7342
7252
|
loadContracts: () => loadContracts,
|
|
7343
7253
|
runContractChecks: () => runContractChecks
|
|
7344
7254
|
});
|
|
7345
|
-
import
|
|
7346
|
-
import
|
|
7255
|
+
import fs22 from "fs";
|
|
7256
|
+
import path22 from "path";
|
|
7347
7257
|
function contractsDir() {
|
|
7348
|
-
return
|
|
7258
|
+
return path22.join(getProjectRoot(), ".kuma", "contracts");
|
|
7349
7259
|
}
|
|
7350
7260
|
function ensureContractsDir() {
|
|
7351
7261
|
const dir = contractsDir();
|
|
7352
|
-
if (!
|
|
7353
|
-
|
|
7354
|
-
const defaultPath =
|
|
7355
|
-
if (!
|
|
7356
|
-
|
|
7262
|
+
if (!fs22.existsSync(dir)) {
|
|
7263
|
+
fs22.mkdirSync(dir, { recursive: true });
|
|
7264
|
+
const defaultPath = path22.join(dir, "default.json");
|
|
7265
|
+
if (!fs22.existsSync(defaultPath)) {
|
|
7266
|
+
fs22.writeFileSync(
|
|
7357
7267
|
defaultPath,
|
|
7358
7268
|
JSON.stringify(DEFAULT_CONTRACTS, null, 2),
|
|
7359
7269
|
"utf-8"
|
|
@@ -7366,10 +7276,10 @@ function loadContracts() {
|
|
|
7366
7276
|
const dir = contractsDir();
|
|
7367
7277
|
const contracts = [];
|
|
7368
7278
|
try {
|
|
7369
|
-
const files =
|
|
7279
|
+
const files = fs22.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
7370
7280
|
for (const file of files) {
|
|
7371
7281
|
try {
|
|
7372
|
-
const content =
|
|
7282
|
+
const content = fs22.readFileSync(path22.join(dir, file), "utf-8");
|
|
7373
7283
|
const config = JSON.parse(content);
|
|
7374
7284
|
contracts.push(config);
|
|
7375
7285
|
} catch (err) {
|
|
@@ -7442,7 +7352,7 @@ function evaluateCondition(condition, contextPath) {
|
|
|
7442
7352
|
switch (condition.type) {
|
|
7443
7353
|
case "file_exists": {
|
|
7444
7354
|
const targetPath = resolveTarget(condition.target, contextPath, root);
|
|
7445
|
-
const exists = targetPath.some((p) =>
|
|
7355
|
+
const exists = targetPath.some((p) => fs22.existsSync(p));
|
|
7446
7356
|
return {
|
|
7447
7357
|
passed: exists,
|
|
7448
7358
|
message: exists ? `\u2705 File exists: ${condition.target}` : `\u274C Required file not found: ${condition.target} (${condition.description})`
|
|
@@ -7450,7 +7360,7 @@ function evaluateCondition(condition, contextPath) {
|
|
|
7450
7360
|
}
|
|
7451
7361
|
case "file_not_exists": {
|
|
7452
7362
|
const targetPath = resolveTarget(condition.target, contextPath, root);
|
|
7453
|
-
const exists = targetPath.some((p) =>
|
|
7363
|
+
const exists = targetPath.some((p) => fs22.existsSync(p));
|
|
7454
7364
|
return {
|
|
7455
7365
|
passed: !exists,
|
|
7456
7366
|
message: !exists ? `\u2705 File does not exist: ${condition.target}` : `\u274C File should not exist: ${condition.target} (${condition.description})`
|
|
@@ -7458,9 +7368,9 @@ function evaluateCondition(condition, contextPath) {
|
|
|
7458
7368
|
}
|
|
7459
7369
|
case "contains": {
|
|
7460
7370
|
if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
|
|
7461
|
-
const fullPath =
|
|
7462
|
-
if (!
|
|
7463
|
-
const content =
|
|
7371
|
+
const fullPath = path22.resolve(root, contextPath);
|
|
7372
|
+
if (!fs22.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
|
|
7373
|
+
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
7464
7374
|
const hasContent = condition.value ? content.includes(condition.value) : false;
|
|
7465
7375
|
return {
|
|
7466
7376
|
passed: hasContent,
|
|
@@ -7472,12 +7382,12 @@ function evaluateCondition(condition, contextPath) {
|
|
|
7472
7382
|
const root2 = getProjectRoot();
|
|
7473
7383
|
const targets = resolveTarget(condition.target, contextPath, root2);
|
|
7474
7384
|
for (const t of targets) {
|
|
7475
|
-
if (
|
|
7476
|
-
const content =
|
|
7385
|
+
if (fs22.existsSync(t)) {
|
|
7386
|
+
const content = fs22.readFileSync(t, "utf-8");
|
|
7477
7387
|
if (condition.value && content.includes(condition.value)) {
|
|
7478
7388
|
return {
|
|
7479
7389
|
passed: false,
|
|
7480
|
-
message: `\u274C Found forbidden pattern "${condition.value}" in ${
|
|
7390
|
+
message: `\u274C Found forbidden pattern "${condition.value}" in ${path22.relative(root2, t)} (${condition.description})`
|
|
7481
7391
|
};
|
|
7482
7392
|
}
|
|
7483
7393
|
}
|
|
@@ -7486,15 +7396,15 @@ function evaluateCondition(condition, contextPath) {
|
|
|
7486
7396
|
}
|
|
7487
7397
|
case "has_test_file": {
|
|
7488
7398
|
if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
|
|
7489
|
-
const basename =
|
|
7490
|
-
const dir =
|
|
7399
|
+
const basename = path22.basename(contextPath, path22.extname(contextPath));
|
|
7400
|
+
const dir = path22.dirname(contextPath);
|
|
7491
7401
|
const possibleTests = [
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7402
|
+
path22.join(dir, `${basename}.test.ts`),
|
|
7403
|
+
path22.join(dir, `${basename}.spec.ts`),
|
|
7404
|
+
path22.join(dir, `__tests__/${basename}.test.ts`),
|
|
7405
|
+
path22.join(dir, `__tests__/${basename}.spec.ts`)
|
|
7496
7406
|
];
|
|
7497
|
-
const hasTest = possibleTests.some((t) =>
|
|
7407
|
+
const hasTest = possibleTests.some((t) => fs22.existsSync(t));
|
|
7498
7408
|
return {
|
|
7499
7409
|
passed: hasTest,
|
|
7500
7410
|
message: hasTest ? `\u2705 Test file exists` : `\u274C No test file found for ${contextPath} (${condition.description})`
|
|
@@ -7518,18 +7428,18 @@ function resolveTarget(pattern, contextPath, root) {
|
|
|
7518
7428
|
const r = root || getProjectRoot();
|
|
7519
7429
|
if (pattern.startsWith("*")) {
|
|
7520
7430
|
if (contextPath) {
|
|
7521
|
-
const dir =
|
|
7431
|
+
const dir = path22.dirname(path22.resolve(r, contextPath));
|
|
7522
7432
|
const suffix = pattern.replace("*", "");
|
|
7523
7433
|
try {
|
|
7524
|
-
const files =
|
|
7525
|
-
return files.filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) =>
|
|
7434
|
+
const files = fs22.readdirSync(dir);
|
|
7435
|
+
return files.filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path22.join(dir, f));
|
|
7526
7436
|
} catch {
|
|
7527
|
-
return [
|
|
7437
|
+
return [path22.join(dir, pattern.replace("*", ""))];
|
|
7528
7438
|
}
|
|
7529
7439
|
}
|
|
7530
7440
|
return [];
|
|
7531
7441
|
}
|
|
7532
|
-
return [
|
|
7442
|
+
return [path22.resolve(r, pattern)];
|
|
7533
7443
|
}
|
|
7534
7444
|
async function runContractChecks(toolName, filePath, phase = "pre") {
|
|
7535
7445
|
const verdicts = phase === "pre" ? evaluatePreConditions(toolName, filePath) : evaluatePostConditions(toolName, filePath);
|
|
@@ -7650,37 +7560,37 @@ __export(kumaVerifier_exports, {
|
|
|
7650
7560
|
getRunningVerificationPid: () => getRunningVerificationPid,
|
|
7651
7561
|
runAutoVerification: () => runAutoVerification
|
|
7652
7562
|
});
|
|
7653
|
-
import
|
|
7654
|
-
import
|
|
7563
|
+
import fs23 from "fs";
|
|
7564
|
+
import path23 from "path";
|
|
7655
7565
|
import { exec } from "child_process";
|
|
7656
7566
|
function getLockDir(root = process.cwd()) {
|
|
7657
|
-
return
|
|
7567
|
+
return path23.resolve(root, LOCK_DIR_NAME);
|
|
7658
7568
|
}
|
|
7659
7569
|
function getPidFile(root = process.cwd()) {
|
|
7660
|
-
return
|
|
7570
|
+
return path23.join(getLockDir(root), "pid");
|
|
7661
7571
|
}
|
|
7662
7572
|
function acquireFileLock(root) {
|
|
7663
7573
|
const lockDir = getLockDir(root);
|
|
7664
7574
|
try {
|
|
7665
|
-
|
|
7666
|
-
|
|
7575
|
+
fs23.mkdirSync(lockDir, { recursive: false });
|
|
7576
|
+
fs23.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
7667
7577
|
return true;
|
|
7668
7578
|
} catch {
|
|
7669
7579
|
try {
|
|
7670
|
-
if (
|
|
7671
|
-
const pid = parseInt(
|
|
7580
|
+
if (fs23.existsSync(getPidFile(root))) {
|
|
7581
|
+
const pid = parseInt(fs23.readFileSync(getPidFile(root), "utf-8"), 10);
|
|
7672
7582
|
try {
|
|
7673
7583
|
process.kill(pid, 0);
|
|
7674
7584
|
return false;
|
|
7675
7585
|
} catch {
|
|
7676
|
-
|
|
7586
|
+
fs23.rmSync(lockDir, { recursive: true, force: true });
|
|
7677
7587
|
return acquireFileLock(root);
|
|
7678
7588
|
}
|
|
7679
7589
|
}
|
|
7680
7590
|
} catch {
|
|
7681
7591
|
}
|
|
7682
7592
|
try {
|
|
7683
|
-
|
|
7593
|
+
fs23.rmSync(lockDir, { recursive: true, force: true });
|
|
7684
7594
|
return acquireFileLock(root);
|
|
7685
7595
|
} catch {
|
|
7686
7596
|
return false;
|
|
@@ -7689,7 +7599,7 @@ function acquireFileLock(root) {
|
|
|
7689
7599
|
}
|
|
7690
7600
|
function releaseFileLock(root) {
|
|
7691
7601
|
try {
|
|
7692
|
-
|
|
7602
|
+
fs23.rmSync(getLockDir(root), { recursive: true, force: true });
|
|
7693
7603
|
} catch {
|
|
7694
7604
|
}
|
|
7695
7605
|
}
|
|
@@ -7712,36 +7622,36 @@ function getRunningVerificationPid() {
|
|
|
7712
7622
|
return _currentProcess?.pid ?? null;
|
|
7713
7623
|
}
|
|
7714
7624
|
function detectTestRunner(root = process.cwd()) {
|
|
7715
|
-
const pkgPath =
|
|
7716
|
-
if (
|
|
7625
|
+
const pkgPath = path23.join(root, "package.json");
|
|
7626
|
+
if (fs23.existsSync(pkgPath)) {
|
|
7717
7627
|
try {
|
|
7718
|
-
const pkg = JSON.parse(
|
|
7628
|
+
const pkg = JSON.parse(fs23.readFileSync(pkgPath, "utf-8"));
|
|
7719
7629
|
if (pkg.scripts && pkg.scripts.test) {
|
|
7720
|
-
if (
|
|
7721
|
-
if (
|
|
7630
|
+
if (fs23.existsSync(path23.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
7631
|
+
if (fs23.existsSync(path23.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
|
|
7722
7632
|
return { runner: "npm", baseCommand: "npm test" };
|
|
7723
7633
|
}
|
|
7724
7634
|
} catch {
|
|
7725
7635
|
}
|
|
7726
7636
|
}
|
|
7727
|
-
if (
|
|
7728
|
-
if (
|
|
7729
|
-
if (
|
|
7730
|
-
if (
|
|
7637
|
+
if (fs23.existsSync(path23.join(root, "pytest.ini")) || fs23.existsSync(path23.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
|
|
7638
|
+
if (fs23.existsSync(path23.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
|
|
7639
|
+
if (fs23.existsSync(path23.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
|
|
7640
|
+
if (fs23.existsSync(path23.join(root, "Makefile"))) {
|
|
7731
7641
|
try {
|
|
7732
|
-
const makefileContent =
|
|
7642
|
+
const makefileContent = fs23.readFileSync(path23.join(root, "Makefile"), "utf-8");
|
|
7733
7643
|
const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
|
|
7734
7644
|
if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
|
|
7735
7645
|
} catch {
|
|
7736
7646
|
}
|
|
7737
7647
|
}
|
|
7738
|
-
if (
|
|
7648
|
+
if (fs23.existsSync(path23.join(root, "package.json"))) {
|
|
7739
7649
|
try {
|
|
7740
|
-
const pkg = JSON.parse(
|
|
7650
|
+
const pkg = JSON.parse(fs23.readFileSync(path23.join(root, "package.json"), "utf-8"));
|
|
7741
7651
|
if (pkg.dependencies || pkg.devDependencies) {
|
|
7742
|
-
if (
|
|
7743
|
-
const srcDir =
|
|
7744
|
-
if (
|
|
7652
|
+
if (fs23.existsSync(path23.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
7653
|
+
const srcDir = path23.join(root, "src");
|
|
7654
|
+
if (fs23.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
|
|
7745
7655
|
}
|
|
7746
7656
|
} catch {
|
|
7747
7657
|
}
|
|
@@ -7841,7 +7751,7 @@ async function runAutoVerification(options = {}) {
|
|
|
7841
7751
|
const heartbeatTimer = setInterval(() => {
|
|
7842
7752
|
if (getLockDir(root)) {
|
|
7843
7753
|
try {
|
|
7844
|
-
|
|
7754
|
+
fs23.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
7845
7755
|
} catch {
|
|
7846
7756
|
}
|
|
7847
7757
|
}
|
|
@@ -7918,8 +7828,8 @@ __export(init_exports, {
|
|
|
7918
7828
|
generateInitMdContent: () => generateInitMdContent,
|
|
7919
7829
|
runInit: () => runInit
|
|
7920
7830
|
});
|
|
7921
|
-
import
|
|
7922
|
-
import
|
|
7831
|
+
import fs24 from "fs";
|
|
7832
|
+
import path24 from "path";
|
|
7923
7833
|
function configFilePath(type) {
|
|
7924
7834
|
switch (type) {
|
|
7925
7835
|
case "claude":
|
|
@@ -8366,14 +8276,11 @@ function generateInitMdContent() {
|
|
|
8366
8276
|
"| `todo` | Persistent todo CRUD | When managing tasks |",
|
|
8367
8277
|
"| `context` | Inject context notes | Additional notes when needed |",
|
|
8368
8278
|
"| `domain_rules` | Layer 1: business rules | Record business rules when discovered |",
|
|
8369
|
-
"| `
|
|
8370
|
-
"| `gen_test` | Generate test from trajectory | After trajectory analysis |",
|
|
8371
|
-
"| `trajectory` | List trajectories | Review past agent trajectories |",
|
|
8372
|
-
"| `skills` | List distilled skills | See auto-learned patterns |",
|
|
8279
|
+
"| `feature` | Record high-level feature with owns edges | Define features like 'Authentication', 'Billing' |",
|
|
8373
8280
|
"| `add_node` | Create structural nodes (low impact \u2014 grep faster) | Rarely; only for visualize debugging |",
|
|
8374
8281
|
"",
|
|
8375
8282
|
"| `delete_node` | Delete specific nodes, gotchas, todos, or decisions by ID | When you saved wrong data or need cleanup |",
|
|
8376
|
-
"| `clear` | Wipe ALL graph nodes, edges, gotchas
|
|
8283
|
+
"| `clear` | Wipe ALL graph nodes, edges, gotchas | Emergency reset \u2014 irreversible |",
|
|
8377
8284
|
"| `layers` | Show all 3 layers summary | Overview memory layers |",
|
|
8378
8285
|
"",
|
|
8379
8286
|
"### \u{1F6E1}\uFE0F kuma_safety \u2014 Safety & Policy",
|
|
@@ -8445,6 +8352,7 @@ function generateInitMdContent() {
|
|
|
8445
8352
|
"| \u{1F539} `small_square` | variable | Variables/constants | AUTO by scanner |",
|
|
8446
8353
|
"| \u{1F504} `circle` | workflow | Workflow definitions | AUTO by scanner |",
|
|
8447
8354
|
"| \u{1F4C4} `note` | context | Manual context notes | MANUAL \u2014 kuma_memory context |",
|
|
8355
|
+
"| \u2B50 `star` | feature | High-level feature (Authentication, Billing) | MANUAL \u2014 kuma_memory feature |",
|
|
8448
8356
|
"| \u{1F3DB}\uFE0F `pillar` | feature_domain | High-level feature domain anchor | MANUAL \u2014 kuma_memory arch_flow (domain:) |",
|
|
8449
8357
|
"| \u{1F517} `link` | cross_service_link | Cross-service/inter-file flow step | MANUAL \u2014 kuma_memory arch_flow (hops:) |",
|
|
8450
8358
|
"| \u{1F3F7}\uFE0F `tag` | domain_rule | Business rules (Layer 1) | MANUAL \u2014 kuma_memory domain_rules |",
|
|
@@ -8461,7 +8369,8 @@ function generateInitMdContent() {
|
|
|
8461
8369
|
"- `extends` \u2014 class extends class (AUTO)",
|
|
8462
8370
|
"- `contains` \u2014 file contains function (AUTO)",
|
|
8463
8371
|
"- `composes` \u2014 component contains sub-component (AUTO)",
|
|
8464
|
-
"- `owns` \u2014 domain owns file (MANUAL \u2014 arch_flow)",
|
|
8372
|
+
"- `owns` \u2014 domain/file owns file (MANUAL \u2014 arch_flow, feature)",
|
|
8373
|
+
"- `affects` \u2014 impact propagation chain (MANUAL \u2014 arch_flow, feature)",
|
|
8465
8374
|
"- `flows_through` \u2014 flow step to next step (MANUAL \u2014 arch_flow)",
|
|
8466
8375
|
"- `triggers` \u2014 flow triggers action (MANUAL)",
|
|
8467
8376
|
"- `syncs_with` \u2014 cross-service sync (MANUAL)",
|
|
@@ -8490,11 +8399,17 @@ function generateInitMdContent() {
|
|
|
8490
8399
|
"\u{1F9E0} Graph is persistent \u2014 nodes/edges accumulate across sessions.",
|
|
8491
8400
|
"",
|
|
8492
8401
|
"ON DISCOVERY (When needed):",
|
|
8402
|
+
" kuma_memory \u2192 feature \u2014 Record high-level feature (Authentication, Billing)",
|
|
8493
8403
|
" kuma_memory \u2192 domain_rules \u2014 Business rules (Layer 1)",
|
|
8494
8404
|
" kuma_memory \u2192 context \u2014 Additional notes",
|
|
8495
8405
|
" kuma_memory \u2192 todo \u2014 Task management",
|
|
8496
8406
|
" kuma_memory \u2192 mine \u2014 Historical context",
|
|
8497
8407
|
" kuma_memory \u2192 add_node \u2014 Manual function/class/component node (for visualize)",
|
|
8408
|
+
"",
|
|
8409
|
+
"GRAPH-AS-RETRIEVAL (search now includes):",
|
|
8410
|
+
" \u2022 Task-focused retrieval: top N relevant nodes for a task description",
|
|
8411
|
+
" \u2022 Impact analysis: which nodes are affected by a change",
|
|
8412
|
+
" \u2022 arch_flow now auto-creates affects edges for impact propagation",
|
|
8498
8413
|
"```",
|
|
8499
8414
|
"",
|
|
8500
8415
|
"## \u{1F9E0} How the Scanner Works",
|
|
@@ -8589,10 +8504,10 @@ function generateInitMdContent() {
|
|
|
8589
8504
|
].join("\n");
|
|
8590
8505
|
}
|
|
8591
8506
|
function handleOpencodeSecondary(root, results) {
|
|
8592
|
-
const skillPath =
|
|
8507
|
+
const skillPath = path24.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
|
|
8593
8508
|
if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
|
|
8594
8509
|
try {
|
|
8595
|
-
const dir =
|
|
8510
|
+
const dir = path24.dirname(skillPath);
|
|
8596
8511
|
const content = [
|
|
8597
8512
|
"---",
|
|
8598
8513
|
"name: kuma-mcp",
|
|
@@ -8625,18 +8540,18 @@ function handleOpencodeSecondary(root, results) {
|
|
|
8625
8540
|
"",
|
|
8626
8541
|
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
8627
8542
|
].join("\n");
|
|
8628
|
-
if (
|
|
8629
|
-
const existing =
|
|
8543
|
+
if (fs24.existsSync(skillPath)) {
|
|
8544
|
+
const existing = fs24.readFileSync(skillPath, "utf-8");
|
|
8630
8545
|
if (existing.includes("kuma-mcp")) {
|
|
8631
8546
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
|
|
8632
8547
|
return;
|
|
8633
8548
|
}
|
|
8634
8549
|
const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
|
|
8635
|
-
|
|
8550
|
+
fs24.writeFileSync(skillPath, newContent, "utf-8");
|
|
8636
8551
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
|
|
8637
8552
|
} else {
|
|
8638
|
-
if (!
|
|
8639
|
-
|
|
8553
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
8554
|
+
fs24.writeFileSync(skillPath, content, "utf-8");
|
|
8640
8555
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
|
|
8641
8556
|
}
|
|
8642
8557
|
} catch (err) {
|
|
@@ -8649,21 +8564,21 @@ function handleOpencodeSecondary(root, results) {
|
|
|
8649
8564
|
}
|
|
8650
8565
|
}
|
|
8651
8566
|
function handleCodexSecondary(root, results) {
|
|
8652
|
-
const tomlPath =
|
|
8567
|
+
const tomlPath = path24.resolve(root, ".codex/config.toml");
|
|
8653
8568
|
if (results.some((r) => r.filePath === ".codex/config.toml")) return;
|
|
8654
8569
|
try {
|
|
8655
|
-
const dir =
|
|
8656
|
-
if (!
|
|
8657
|
-
if (
|
|
8658
|
-
const existingContent =
|
|
8570
|
+
const dir = path24.dirname(tomlPath);
|
|
8571
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
8572
|
+
if (fs24.existsSync(tomlPath)) {
|
|
8573
|
+
const existingContent = fs24.readFileSync(tomlPath, "utf-8");
|
|
8659
8574
|
if (existingContent.includes("kuma")) {
|
|
8660
8575
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
|
|
8661
8576
|
return;
|
|
8662
8577
|
}
|
|
8663
|
-
|
|
8578
|
+
fs24.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
|
|
8664
8579
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
|
|
8665
8580
|
} else {
|
|
8666
|
-
|
|
8581
|
+
fs24.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
|
|
8667
8582
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
|
|
8668
8583
|
}
|
|
8669
8584
|
} catch (err) {
|
|
@@ -8676,11 +8591,11 @@ function handleCodexSecondary(root, results) {
|
|
|
8676
8591
|
}
|
|
8677
8592
|
}
|
|
8678
8593
|
function handleQwenSecondary(root, results) {
|
|
8679
|
-
const settingsPath =
|
|
8594
|
+
const settingsPath = path24.resolve(root, "settings.json");
|
|
8680
8595
|
if (results.some((r) => r.filePath === "settings.json")) return;
|
|
8681
8596
|
try {
|
|
8682
|
-
if (
|
|
8683
|
-
const existingContent =
|
|
8597
|
+
if (fs24.existsSync(settingsPath)) {
|
|
8598
|
+
const existingContent = fs24.readFileSync(settingsPath, "utf-8");
|
|
8684
8599
|
if (existingContent.includes("kuma")) {
|
|
8685
8600
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
8686
8601
|
try {
|
|
@@ -8688,7 +8603,7 @@ function handleQwenSecondary(root, results) {
|
|
|
8688
8603
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
8689
8604
|
if (!parsed.mcpServers.kuma) {
|
|
8690
8605
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
8691
|
-
|
|
8606
|
+
fs24.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
8692
8607
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
8693
8608
|
return;
|
|
8694
8609
|
}
|
|
@@ -8703,13 +8618,13 @@ function handleQwenSecondary(root, results) {
|
|
|
8703
8618
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
8704
8619
|
if (!parsed.mcpServers.kuma) {
|
|
8705
8620
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
8706
|
-
|
|
8621
|
+
fs24.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
8707
8622
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
8708
8623
|
}
|
|
8709
8624
|
} catch {
|
|
8710
8625
|
}
|
|
8711
8626
|
} else {
|
|
8712
|
-
|
|
8627
|
+
fs24.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
|
|
8713
8628
|
results.push({ type: "qwen", filePath: "settings.json", action: "created" });
|
|
8714
8629
|
}
|
|
8715
8630
|
} catch (err) {
|
|
@@ -8812,18 +8727,18 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
8812
8727
|
return lines.join("\n");
|
|
8813
8728
|
}
|
|
8814
8729
|
function handleAntigravityMcpConfig(root, results) {
|
|
8815
|
-
const mcpPath =
|
|
8730
|
+
const mcpPath = path24.resolve(root, ".agents/mcp_config.json");
|
|
8816
8731
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
8817
8732
|
try {
|
|
8818
|
-
const mcpDir =
|
|
8819
|
-
if (
|
|
8820
|
-
const existingContent =
|
|
8733
|
+
const mcpDir = path24.dirname(mcpPath);
|
|
8734
|
+
if (fs24.existsSync(mcpPath)) {
|
|
8735
|
+
const existingContent = fs24.readFileSync(mcpPath, "utf-8");
|
|
8821
8736
|
if (existingContent.includes("kuma")) {
|
|
8822
8737
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
8823
8738
|
const trimmed = existingContent.trimEnd();
|
|
8824
8739
|
if (trimmed.endsWith("}")) {
|
|
8825
8740
|
const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
|
|
8826
|
-
|
|
8741
|
+
fs24.writeFileSync(mcpPath, updated, "utf-8");
|
|
8827
8742
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
8828
8743
|
}
|
|
8829
8744
|
}
|
|
@@ -8832,11 +8747,11 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
8832
8747
|
const parsed = JSON.parse(existingContent);
|
|
8833
8748
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
8834
8749
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
8835
|
-
|
|
8750
|
+
fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
8836
8751
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
8837
8752
|
} else {
|
|
8838
|
-
if (!
|
|
8839
|
-
|
|
8753
|
+
if (!fs24.existsSync(mcpDir)) fs24.mkdirSync(mcpDir, { recursive: true });
|
|
8754
|
+
fs24.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
8840
8755
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
|
|
8841
8756
|
}
|
|
8842
8757
|
} catch (err) {
|
|
@@ -8849,18 +8764,18 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
8849
8764
|
}
|
|
8850
8765
|
}
|
|
8851
8766
|
function handleAiderSecondary(root, results) {
|
|
8852
|
-
const ymlPath =
|
|
8767
|
+
const ymlPath = path24.resolve(root, ".aider.conf.yml");
|
|
8853
8768
|
if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
|
|
8854
8769
|
try {
|
|
8855
8770
|
const conventionsRef = "read: CONVENTIONS.md";
|
|
8856
|
-
if (
|
|
8857
|
-
const existingContent =
|
|
8771
|
+
if (fs24.existsSync(ymlPath)) {
|
|
8772
|
+
const existingContent = fs24.readFileSync(ymlPath, "utf-8");
|
|
8858
8773
|
if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
|
|
8859
8774
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
|
|
8860
8775
|
return;
|
|
8861
8776
|
}
|
|
8862
8777
|
const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
|
|
8863
|
-
|
|
8778
|
+
fs24.writeFileSync(ymlPath, newContent, "utf-8");
|
|
8864
8779
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
|
|
8865
8780
|
} else {
|
|
8866
8781
|
const content = [
|
|
@@ -8870,7 +8785,7 @@ function handleAiderSecondary(root, results) {
|
|
|
8870
8785
|
conventionsRef,
|
|
8871
8786
|
""
|
|
8872
8787
|
].join("\n");
|
|
8873
|
-
|
|
8788
|
+
fs24.writeFileSync(ymlPath, content, "utf-8");
|
|
8874
8789
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
|
|
8875
8790
|
}
|
|
8876
8791
|
} catch (err) {
|
|
@@ -8883,10 +8798,10 @@ function handleAiderSecondary(root, results) {
|
|
|
8883
8798
|
}
|
|
8884
8799
|
}
|
|
8885
8800
|
function handleCopilotSecondary(root, results) {
|
|
8886
|
-
const skillPath =
|
|
8801
|
+
const skillPath = path24.resolve(root, ".github/skills/kuma/SKILL.md");
|
|
8887
8802
|
if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
|
|
8888
8803
|
try {
|
|
8889
|
-
const dir =
|
|
8804
|
+
const dir = path24.dirname(skillPath);
|
|
8890
8805
|
const content = [
|
|
8891
8806
|
"---",
|
|
8892
8807
|
"name: kuma-mcp",
|
|
@@ -8897,17 +8812,17 @@ function handleCopilotSecondary(root, results) {
|
|
|
8897
8812
|
"",
|
|
8898
8813
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
8899
8814
|
].join("\n");
|
|
8900
|
-
if (
|
|
8901
|
-
const existingContent =
|
|
8815
|
+
if (fs24.existsSync(skillPath)) {
|
|
8816
|
+
const existingContent = fs24.readFileSync(skillPath, "utf-8");
|
|
8902
8817
|
if (existingContent.includes("kuma")) {
|
|
8903
8818
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
|
|
8904
8819
|
return;
|
|
8905
8820
|
}
|
|
8906
|
-
|
|
8821
|
+
fs24.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
|
|
8907
8822
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
|
|
8908
8823
|
} else {
|
|
8909
|
-
if (!
|
|
8910
|
-
|
|
8824
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
8825
|
+
fs24.writeFileSync(skillPath, content, "utf-8");
|
|
8911
8826
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
|
|
8912
8827
|
}
|
|
8913
8828
|
} catch (err) {
|
|
@@ -8920,21 +8835,21 @@ function handleCopilotSecondary(root, results) {
|
|
|
8920
8835
|
}
|
|
8921
8836
|
}
|
|
8922
8837
|
function handleOpenclawSecondary(root, results) {
|
|
8923
|
-
const mcpPath =
|
|
8838
|
+
const mcpPath = path24.resolve(root, ".agents/mcp_config.json");
|
|
8924
8839
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
8925
8840
|
try {
|
|
8926
|
-
const dir =
|
|
8927
|
-
if (
|
|
8928
|
-
const existingContent =
|
|
8841
|
+
const dir = path24.dirname(mcpPath);
|
|
8842
|
+
if (fs24.existsSync(mcpPath)) {
|
|
8843
|
+
const existingContent = fs24.readFileSync(mcpPath, "utf-8");
|
|
8929
8844
|
if (existingContent.includes("kuma")) return;
|
|
8930
8845
|
const parsed = JSON.parse(existingContent);
|
|
8931
8846
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
8932
8847
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
8933
|
-
|
|
8848
|
+
fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
8934
8849
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
8935
8850
|
} else {
|
|
8936
|
-
if (!
|
|
8937
|
-
|
|
8851
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
8852
|
+
fs24.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
8938
8853
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
|
|
8939
8854
|
}
|
|
8940
8855
|
} catch (err) {
|
|
@@ -8947,53 +8862,42 @@ function handleOpenclawSecondary(root, results) {
|
|
|
8947
8862
|
}
|
|
8948
8863
|
}
|
|
8949
8864
|
function handleQuickrefGeneration(root, results) {
|
|
8950
|
-
const quickrefPath =
|
|
8865
|
+
const quickrefPath = path24.resolve(root, ".kuma/quickref.md");
|
|
8951
8866
|
const content = [
|
|
8952
8867
|
"# Kuma Quick Reference",
|
|
8953
8868
|
"",
|
|
8954
|
-
"
|
|
8869
|
+
"## Lean Mode (default \u2014 for speed)",
|
|
8870
|
+
"1. `init` \u2192 edit \u2192 record (if needed)",
|
|
8955
8871
|
"",
|
|
8956
|
-
"##
|
|
8872
|
+
"## Standard Mode (for safety)",
|
|
8873
|
+
"1. `init` \u2192 `guard` \u2192 `research` \u2192 edit \u2192 `record` \u2192 `verify`",
|
|
8957
8874
|
"",
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
' 3. `kuma_context({ action: "research", scope: "<area>" })` \u2014 Research',
|
|
8961
|
-
" 4. *(edit/read using native tools)*",
|
|
8962
|
-
' 5. `kuma_memory({ action: "research_save", ... })` \u2014 After exploring area (creates search cache)',
|
|
8963
|
-
' 6. `kuma_memory({ action: "gotcha" })` \u2014 \u{1F525} IMMEDIATELY when bug found (exponential)',
|
|
8964
|
-
' 7. `kuma_memory({ action: "arch_flow" })` \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential)',
|
|
8965
|
-
' 8. `kuma_memory({ action: "decision" })` \u2014 When choosing (preserves rationale)',
|
|
8966
|
-
' 9. `kuma_safety({ action: "verify", ... })` \u2014 Run tests / confirm nothing broken',
|
|
8967
|
-
' 10. `kuma_context({ action: "changes" })` \u2014 Review session activity',
|
|
8968
|
-
"",
|
|
8969
|
-
"\u{1F525} `arch_flow` + `gotcha` = EXPONENTIAL value. Each record saves 5-10 files next session.",
|
|
8970
|
-
"\u{1F7E2} SKIP recording function/class/component nodes \u2014 grep/glob is faster.",
|
|
8971
|
-
"\u{1F9E0} Graph is persistent \u2014 nodes/edges accumulate across sessions.",
|
|
8875
|
+
"## Full Mode (for complex changes)",
|
|
8876
|
+
"1. `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
|
|
8972
8877
|
"",
|
|
8973
|
-
"
|
|
8878
|
+
"## Record Rules",
|
|
8879
|
+
"- **MUST:** decision, gotcha",
|
|
8880
|
+
"- **SKIP:** function, class, import, route",
|
|
8974
8881
|
"",
|
|
8975
8882
|
"## Platform Tool Names",
|
|
8883
|
+
"- OpenCode / Antigravity: `kuma_kuma_*`",
|
|
8884
|
+
"- Others: `kuma_*`",
|
|
8976
8885
|
"",
|
|
8977
|
-
"
|
|
8978
|
-
"- **Other platforms:** Use `kuma_*` directly (e.g. `kuma_context`)",
|
|
8979
|
-
"",
|
|
8980
|
-
"\u{1F4D6} Full rules: `.kuma/init.md`",
|
|
8886
|
+
"\u{1F4D6} Full: `.kuma/init.md` | \u{1F4CA} Modes: `.kuma/MODE.md` | \u{1F7E2} Skip: `.kuma/SKIP_RULES.md`",
|
|
8981
8887
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
8982
8888
|
].join("\n");
|
|
8983
8889
|
try {
|
|
8984
|
-
const kumaDir =
|
|
8985
|
-
if (!
|
|
8986
|
-
if (
|
|
8987
|
-
const existing =
|
|
8890
|
+
const kumaDir = path24.dirname(quickrefPath);
|
|
8891
|
+
if (!fs24.existsSync(kumaDir)) fs24.mkdirSync(kumaDir, { recursive: true });
|
|
8892
|
+
if (fs24.existsSync(quickrefPath)) {
|
|
8893
|
+
const existing = fs24.readFileSync(quickrefPath, "utf-8");
|
|
8988
8894
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
8989
8895
|
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
|
|
8990
8896
|
return;
|
|
8991
8897
|
}
|
|
8992
|
-
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
|
|
8993
|
-
} else {
|
|
8994
|
-
fs25.writeFileSync(quickrefPath, content, "utf-8");
|
|
8995
|
-
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
|
|
8996
8898
|
}
|
|
8899
|
+
fs24.writeFileSync(quickrefPath, content, "utf-8");
|
|
8900
|
+
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
|
|
8997
8901
|
} catch (err) {
|
|
8998
8902
|
results.push({
|
|
8999
8903
|
type: "claude",
|
|
@@ -9003,21 +8907,165 @@ function handleQuickrefGeneration(root, results) {
|
|
|
9003
8907
|
});
|
|
9004
8908
|
}
|
|
9005
8909
|
}
|
|
8910
|
+
function handleModeMdGeneration(root, results) {
|
|
8911
|
+
const filePath = path24.resolve(root, ".kuma/MODE.md");
|
|
8912
|
+
const content = [
|
|
8913
|
+
"# Adaptive Mode Selector",
|
|
8914
|
+
"",
|
|
8915
|
+
"## Mode Definitions",
|
|
8916
|
+
"",
|
|
8917
|
+
"| Mode | When to Use | Token Cost | Steps |",
|
|
8918
|
+
"|------|-------------|------------|-------|",
|
|
8919
|
+
"| **Lean** (default) | Small fixes, familiar code, hotfixes, < 3 files | ~100 | 3 |",
|
|
8920
|
+
"| **Standard** | New features, unfamiliar modules, 3-10 files | ~300 | 5 |",
|
|
8921
|
+
"| **Full** | Cross-module refactors, architecture changes, > 10 files | ~500 | 7 |",
|
|
8922
|
+
"",
|
|
8923
|
+
"## Auto-Detection Rules",
|
|
8924
|
+
"",
|
|
8925
|
+
"```",
|
|
8926
|
+
"IF file_count < 3 AND area_familiar == true:",
|
|
8927
|
+
' mode = "lean"',
|
|
8928
|
+
"ELSE IF file_count <= 10 OR area_familiar == false:",
|
|
8929
|
+
' mode = "standard"',
|
|
8930
|
+
"ELSE IF file_count > 10 OR cross_module == true:",
|
|
8931
|
+
' mode = "full"',
|
|
8932
|
+
"```",
|
|
8933
|
+
"",
|
|
8934
|
+
"## Mode Behaviors",
|
|
8935
|
+
"",
|
|
8936
|
+
"### Lean Mode (Default)",
|
|
8937
|
+
"- Skip: guard, research, verify, changes",
|
|
8938
|
+
"- Record: only if decision/gotcha explicit",
|
|
8939
|
+
"- Focus: speed, minimal overhead",
|
|
8940
|
+
"",
|
|
8941
|
+
"### Standard Mode",
|
|
8942
|
+
"- Include: guard (before unfamiliar), research (before edit)",
|
|
8943
|
+
"- Record: decision, gotcha, arch_flow (if complex)",
|
|
8944
|
+
"- Focus: safety + knowledge capture",
|
|
8945
|
+
"",
|
|
8946
|
+
"### Full Mode",
|
|
8947
|
+
"- Include: all steps including impact, changes",
|
|
8948
|
+
"- Record: everything valuable",
|
|
8949
|
+
"- Focus: completeness, audit trail",
|
|
8950
|
+
"",
|
|
8951
|
+
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
8952
|
+
].join("\n");
|
|
8953
|
+
try {
|
|
8954
|
+
const dir = path24.dirname(filePath);
|
|
8955
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
8956
|
+
if (!fs24.existsSync(filePath)) {
|
|
8957
|
+
fs24.writeFileSync(filePath, content, "utf-8");
|
|
8958
|
+
results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "created" });
|
|
8959
|
+
}
|
|
8960
|
+
} catch (err) {
|
|
8961
|
+
results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "error", error: err instanceof Error ? err.message : String(err) });
|
|
8962
|
+
}
|
|
8963
|
+
}
|
|
8964
|
+
function handleSkipRulesMdGeneration(root, results) {
|
|
8965
|
+
const filePath = path24.resolve(root, ".kuma/SKIP_RULES.md");
|
|
8966
|
+
const content = [
|
|
8967
|
+
"# Skip Rules \u2014 What NOT to Record",
|
|
8968
|
+
"",
|
|
8969
|
+
"## \u{1F7E2} NEVER Record (grep/glob is faster)",
|
|
8970
|
+
"",
|
|
8971
|
+
"| What | Why Skip | Better Tool |",
|
|
8972
|
+
"|------|----------|-------------|",
|
|
8973
|
+
"| Function definitions | `grep funcName(` | Grep |",
|
|
8974
|
+
"| Class definitions | `grep class ClassName` | Grep |",
|
|
8975
|
+
"| Import statements | Read import block | Read |",
|
|
8976
|
+
"| Component definitions | `glob **/*Component*` | Glob |",
|
|
8977
|
+
"| Route definitions | Check router file | Read |",
|
|
8978
|
+
"| Type/interface definitions | `grep interface TypeName` | Grep |",
|
|
8979
|
+
"| Variable/const declarations | `grep const varName` | Grep |",
|
|
8980
|
+
"| Test file locations | `glob **/*.test.*` | Glob |",
|
|
8981
|
+
"",
|
|
8982
|
+
"## \u{1F534} ALWAYS Record (high value)",
|
|
8983
|
+
"",
|
|
8984
|
+
"| What | Why Record | Tool |",
|
|
8985
|
+
"|------|------------|------|",
|
|
8986
|
+
"| Architecture decisions with rationale | Preserves context | `decision` |",
|
|
8987
|
+
"| Bugs/quirks found and fixed | Prevents re-discovery | `gotcha` |",
|
|
8988
|
+
"| Cross-module flow paths | Saves 5-10 files next session | `arch_flow` |",
|
|
8989
|
+
"| Business rules discovered | Domain knowledge | `domain_rules` |",
|
|
8990
|
+
"",
|
|
8991
|
+
"## \u{1F7E1} Record ONLY If Complex",
|
|
8992
|
+
"",
|
|
8993
|
+
"| What | Condition | Tool |",
|
|
8994
|
+
"|------|-----------|------|",
|
|
8995
|
+
"| Research findings | Multi-file exploration (> 3 files) | `research_save` |",
|
|
8996
|
+
"| Architecture flow | Cross-service or > 3 hops | `arch_flow` |",
|
|
8997
|
+
"| Feature definition | High-level feature with owns edges | `feature` |",
|
|
8998
|
+
"",
|
|
8999
|
+
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9000
|
+
].join("\n");
|
|
9001
|
+
try {
|
|
9002
|
+
const dir = path24.dirname(filePath);
|
|
9003
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
9004
|
+
if (!fs24.existsSync(filePath)) {
|
|
9005
|
+
fs24.writeFileSync(filePath, content, "utf-8");
|
|
9006
|
+
results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "created" });
|
|
9007
|
+
}
|
|
9008
|
+
} catch (err) {
|
|
9009
|
+
results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "error", error: err instanceof Error ? err.message : String(err) });
|
|
9010
|
+
}
|
|
9011
|
+
}
|
|
9012
|
+
function handleStalenessMdGeneration(root, results) {
|
|
9013
|
+
const filePath = path24.resolve(root, ".kuma/STALENESS.md");
|
|
9014
|
+
const content = [
|
|
9015
|
+
"# Auto-Staleness Detection",
|
|
9016
|
+
"",
|
|
9017
|
+
"## Staleness Signals",
|
|
9018
|
+
"",
|
|
9019
|
+
"| Signal | Detection | Action |",
|
|
9020
|
+
"|--------|-----------|--------|",
|
|
9021
|
+
"| File modified after save | Compare file mtime vs research_save timestamp | Mark STALE, skip cache |",
|
|
9022
|
+
"| Git commit touches file | `git log --since` on researched files | Mark STALE, warn |",
|
|
9023
|
+
'| Age > 7 days | Current time - research_save timestamp | Warn: "Cache may be stale" |',
|
|
9024
|
+
"| Age > 30 days | Current time - research_save timestamp | Auto-invalidate, re-read |",
|
|
9025
|
+
"",
|
|
9026
|
+
"## Recovery Protocol",
|
|
9027
|
+
"",
|
|
9028
|
+
"```",
|
|
9029
|
+
"ON stale_detected:",
|
|
9030
|
+
" 1. Skip cached data",
|
|
9031
|
+
" 2. Re-read file from disk",
|
|
9032
|
+
" 3. Update research_save timestamp",
|
|
9033
|
+
" 4. Continue with fresh data",
|
|
9034
|
+
"",
|
|
9035
|
+
"ON search_conflict:",
|
|
9036
|
+
" 1. Prefer recent research (newer timestamp)",
|
|
9037
|
+
" 2. If same age, prefer live file over cache",
|
|
9038
|
+
" 3. Log conflict for review",
|
|
9039
|
+
"```",
|
|
9040
|
+
"",
|
|
9041
|
+
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9042
|
+
].join("\n");
|
|
9043
|
+
try {
|
|
9044
|
+
const dir = path24.dirname(filePath);
|
|
9045
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
9046
|
+
if (!fs24.existsSync(filePath)) {
|
|
9047
|
+
fs24.writeFileSync(filePath, content, "utf-8");
|
|
9048
|
+
results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "created" });
|
|
9049
|
+
}
|
|
9050
|
+
} catch (err) {
|
|
9051
|
+
results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "error", error: err instanceof Error ? err.message : String(err) });
|
|
9052
|
+
}
|
|
9053
|
+
}
|
|
9006
9054
|
function handleInitMdGeneration(root, results) {
|
|
9007
|
-
const initMdPath =
|
|
9055
|
+
const initMdPath = path24.resolve(root, ".kuma/init.md");
|
|
9008
9056
|
try {
|
|
9009
|
-
const kumaDir =
|
|
9010
|
-
if (!
|
|
9011
|
-
if (
|
|
9012
|
-
const existing =
|
|
9057
|
+
const kumaDir = path24.dirname(initMdPath);
|
|
9058
|
+
if (!fs24.existsSync(kumaDir)) fs24.mkdirSync(kumaDir, { recursive: true });
|
|
9059
|
+
if (fs24.existsSync(initMdPath)) {
|
|
9060
|
+
const existing = fs24.readFileSync(initMdPath, "utf-8");
|
|
9013
9061
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
9014
9062
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
|
|
9015
9063
|
return;
|
|
9016
9064
|
}
|
|
9017
|
-
|
|
9065
|
+
fs24.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
|
|
9018
9066
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
|
|
9019
9067
|
} else {
|
|
9020
|
-
|
|
9068
|
+
fs24.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
9021
9069
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
|
|
9022
9070
|
}
|
|
9023
9071
|
} catch (err) {
|
|
@@ -9029,22 +9077,25 @@ function handleInitMdGeneration(root, results) {
|
|
|
9029
9077
|
});
|
|
9030
9078
|
}
|
|
9031
9079
|
handleQuickrefGeneration(root, results);
|
|
9080
|
+
handleModeMdGeneration(root, results);
|
|
9081
|
+
handleSkipRulesMdGeneration(root, results);
|
|
9082
|
+
handleStalenessMdGeneration(root, results);
|
|
9032
9083
|
}
|
|
9033
9084
|
function handleCodewhaleSecondary(root, results) {
|
|
9034
|
-
const mcpPath =
|
|
9085
|
+
const mcpPath = path24.resolve(root, ".codewhale/mcp.json");
|
|
9035
9086
|
if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
|
|
9036
9087
|
try {
|
|
9037
|
-
const dir =
|
|
9038
|
-
if (
|
|
9039
|
-
const existingContent =
|
|
9088
|
+
const dir = path24.dirname(mcpPath);
|
|
9089
|
+
if (fs24.existsSync(mcpPath)) {
|
|
9090
|
+
const existingContent = fs24.readFileSync(mcpPath, "utf-8");
|
|
9040
9091
|
if (existingContent.includes("kuma")) return;
|
|
9041
9092
|
const parsed = JSON.parse(existingContent);
|
|
9042
9093
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9043
9094
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9044
|
-
|
|
9095
|
+
fs24.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9045
9096
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
|
|
9046
9097
|
} else {
|
|
9047
|
-
if (!
|
|
9098
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
9048
9099
|
const config = {
|
|
9049
9100
|
mcpServers: {
|
|
9050
9101
|
kuma: {
|
|
@@ -9054,7 +9105,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
9054
9105
|
}
|
|
9055
9106
|
}
|
|
9056
9107
|
};
|
|
9057
|
-
|
|
9108
|
+
fs24.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
9058
9109
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
|
|
9059
9110
|
}
|
|
9060
9111
|
} catch (err) {
|
|
@@ -9076,28 +9127,28 @@ function runInit(options) {
|
|
|
9076
9127
|
let agentsMdHandled = false;
|
|
9077
9128
|
for (const type of selected) {
|
|
9078
9129
|
const relativePath = configFilePath(type);
|
|
9079
|
-
const fullPath =
|
|
9130
|
+
const fullPath = path24.resolve(root, relativePath);
|
|
9080
9131
|
const getTemplate = TEMPLATES[type];
|
|
9081
9132
|
try {
|
|
9082
9133
|
if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
|
|
9083
9134
|
agentsMdHandled = true;
|
|
9084
9135
|
const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
|
|
9085
|
-
if (
|
|
9136
|
+
if (fs24.existsSync(fullPath)) {
|
|
9086
9137
|
if (options.skipExisting) {
|
|
9087
9138
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9088
9139
|
} else {
|
|
9089
|
-
const existingContent =
|
|
9140
|
+
const existingContent = fs24.readFileSync(fullPath, "utf-8");
|
|
9090
9141
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9091
9142
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9092
9143
|
} else {
|
|
9093
|
-
|
|
9144
|
+
fs24.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
|
|
9094
9145
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
9095
9146
|
}
|
|
9096
9147
|
}
|
|
9097
9148
|
} else {
|
|
9098
|
-
const dir =
|
|
9099
|
-
if (!
|
|
9100
|
-
|
|
9149
|
+
const dir = path24.dirname(fullPath);
|
|
9150
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
9151
|
+
fs24.writeFileSync(fullPath, combinedContent, "utf-8");
|
|
9101
9152
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
9102
9153
|
}
|
|
9103
9154
|
if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
|
|
@@ -9109,12 +9160,12 @@ function runInit(options) {
|
|
|
9109
9160
|
continue;
|
|
9110
9161
|
} else {
|
|
9111
9162
|
const template = getTemplate();
|
|
9112
|
-
if (
|
|
9163
|
+
if (fs24.existsSync(fullPath)) {
|
|
9113
9164
|
if (options.skipExisting) {
|
|
9114
9165
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9115
9166
|
continue;
|
|
9116
9167
|
}
|
|
9117
|
-
const existingContent =
|
|
9168
|
+
const existingContent = fs24.readFileSync(fullPath, "utf-8");
|
|
9118
9169
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9119
9170
|
if (type === "antigravity") {
|
|
9120
9171
|
handleAntigravityMcpConfig(root, results);
|
|
@@ -9127,14 +9178,14 @@ function runInit(options) {
|
|
|
9127
9178
|
continue;
|
|
9128
9179
|
}
|
|
9129
9180
|
const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
|
|
9130
|
-
|
|
9181
|
+
fs24.writeFileSync(fullPath, newContent, "utf-8");
|
|
9131
9182
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
9132
9183
|
} else {
|
|
9133
|
-
const dir =
|
|
9134
|
-
if (!
|
|
9135
|
-
|
|
9184
|
+
const dir = path24.dirname(fullPath);
|
|
9185
|
+
if (!fs24.existsSync(dir)) {
|
|
9186
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
9136
9187
|
}
|
|
9137
|
-
|
|
9188
|
+
fs24.writeFileSync(fullPath, template, "utf-8");
|
|
9138
9189
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
9139
9190
|
}
|
|
9140
9191
|
if (type === "antigravity") {
|
|
@@ -9290,19 +9341,19 @@ __export(agentDetector_exports, {
|
|
|
9290
9341
|
getSkillPath: () => getSkillPath,
|
|
9291
9342
|
skillExists: () => skillExists
|
|
9292
9343
|
});
|
|
9293
|
-
import
|
|
9294
|
-
import
|
|
9344
|
+
import fs25 from "fs";
|
|
9345
|
+
import path25 from "path";
|
|
9295
9346
|
function checkFile(root, filePath) {
|
|
9296
9347
|
try {
|
|
9297
|
-
return
|
|
9348
|
+
return fs25.existsSync(path25.join(root, filePath));
|
|
9298
9349
|
} catch {
|
|
9299
9350
|
return false;
|
|
9300
9351
|
}
|
|
9301
9352
|
}
|
|
9302
9353
|
function checkDir(root, dirPath) {
|
|
9303
9354
|
try {
|
|
9304
|
-
const fullPath =
|
|
9305
|
-
return
|
|
9355
|
+
const fullPath = path25.join(root, dirPath);
|
|
9356
|
+
return fs25.existsSync(fullPath) && fs25.statSync(fullPath).isDirectory();
|
|
9306
9357
|
} catch {
|
|
9307
9358
|
return false;
|
|
9308
9359
|
}
|
|
@@ -9370,7 +9421,7 @@ function getAgentLabel(type) {
|
|
|
9370
9421
|
function skillExists(type, projectRoot) {
|
|
9371
9422
|
const root = projectRoot ?? getProjectRoot();
|
|
9372
9423
|
const skillPath = getSkillPath(type);
|
|
9373
|
-
return
|
|
9424
|
+
return fs25.existsSync(path25.join(root, skillPath));
|
|
9374
9425
|
}
|
|
9375
9426
|
var AGENT_DETECTORS;
|
|
9376
9427
|
var init_agentDetector = __esm({
|
|
@@ -9766,30 +9817,18 @@ var init_skillGenerator = __esm({
|
|
|
9766
9817
|
"use strict";
|
|
9767
9818
|
BOOTSTRAP = [
|
|
9768
9819
|
"Kuma MCP tools are available (kuma_context, kuma_memory, kuma_safety).",
|
|
9769
|
-
'**Before coding, call `kuma_context({ action: "init" })`** to load project context
|
|
9820
|
+
'**Before coding, call `kuma_context({ action: "init" })`** to load project context.',
|
|
9770
9821
|
"Project knowledge persists in `.kuma/memories/*.md` across sessions.",
|
|
9771
9822
|
"",
|
|
9772
|
-
"\
|
|
9773
|
-
"",
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
' 3. `kuma_context({ action: "research", scope: "<area>" })` \u2014 Research before editing',
|
|
9777
|
-
" 4. *(edit/read using native tools)*",
|
|
9778
|
-
' 5. `kuma_memory({ action: "research_save", ... })` \u2014 After exploring area (creates search cache)',
|
|
9779
|
-
' 6. `kuma_memory({ action: "gotcha" })` \u2014 \u{1F525} IMMEDIATELY when bug found (exponential value)',
|
|
9780
|
-
' 7. `kuma_memory({ action: "arch_flow" })` \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential value)',
|
|
9781
|
-
' 8. `kuma_memory({ action: "decision" })` \u2014 When choosing between options (preserves rationale)',
|
|
9782
|
-
' 9. `kuma_safety({ action: "verify", ... })` \u2014 Run tests / confirm nothing broken',
|
|
9783
|
-
' 10. `kuma_context({ action: "changes" })` \u2014 Review session activity',
|
|
9823
|
+
"\u26A1 **ADAPTIVE WORKFLOW (Lean Mode by Default):**",
|
|
9824
|
+
" \u2022 **Lean Mode (Default, < 3 files):** `init` \u2192 edit/read \u2192 record (`decision`/`gotcha` if needed)",
|
|
9825
|
+
" \u2022 **Standard Mode (3-10 files/unfamiliar):** `init` \u2192 `guard` \u2192 `research` \u2192 edit \u2192 `record` \u2192 `verify`",
|
|
9826
|
+
" \u2022 **Full Mode (> 10 files/refactor):** `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
|
|
9784
9827
|
"",
|
|
9785
|
-
"\u{
|
|
9786
|
-
"
|
|
9787
|
-
"\u{1F7E2} **SKIP** recording function/class/component nodes \u2014 grep/glob is faster.",
|
|
9788
|
-
"\u{1F9E0} Knowledge graph is persistent \u2014 nodes/edges accumulate across sessions, getting richer over time.",
|
|
9828
|
+
"\u{1F534} **MUST RECORD:** `decision` (rationale), `gotcha` (bug/quirk), `arch_flow` (complex flow)",
|
|
9829
|
+
"\u{1F7E2} **SKIP RECORDING:** function, class, import, route (use native grep/glob instead)",
|
|
9789
9830
|
"",
|
|
9790
|
-
"\
|
|
9791
|
-
"\u{1F5D1}\uFE0F **Delete/Clear:** `delete_node` (delete node/gotcha/todo/decision) | `clear` (wipe entire graph)",
|
|
9792
|
-
"\u{1F4D6} Full rules: `.kuma/init.md`",
|
|
9831
|
+
"\u{1F4D6} Full rules & modes: `.kuma/init.md` | `.kuma/MODE.md` | `.kuma/SKIP_RULES.md`",
|
|
9793
9832
|
"\u{1F9E0} Tools: `kuma_context` | `kuma_memory` | `kuma_safety`"
|
|
9794
9833
|
].join("\n");
|
|
9795
9834
|
BOOTSTRAP_OPENCODE = [
|
|
@@ -9797,27 +9836,15 @@ var init_skillGenerator = __esm({
|
|
|
9797
9836
|
'**Before coding, call `kuma_kuma_context({ action: "init" })`** to load project context.',
|
|
9798
9837
|
"Project knowledge persists in `.kuma/memories/*.md` across sessions.",
|
|
9799
9838
|
"",
|
|
9800
|
-
"\
|
|
9801
|
-
"",
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
' 3. `kuma_kuma_context({ action: "research", scope: "<area>" })` \u2014 Research before editing',
|
|
9805
|
-
" 4. *(edit/read using native tools)*",
|
|
9806
|
-
' 5. `kuma_kuma_memory({ action: "research_save", ... })` \u2014 After exploring area (creates search cache)',
|
|
9807
|
-
' 6. `kuma_kuma_memory({ action: "gotcha" })` \u2014 \u{1F525} IMMEDIATELY when bug found (exponential value)',
|
|
9808
|
-
' 7. `kuma_kuma_memory({ action: "arch_flow" })` \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential value)',
|
|
9809
|
-
' 8. `kuma_kuma_memory({ action: "decision" })` \u2014 When choosing between options (preserves rationale)',
|
|
9810
|
-
' 9. `kuma_kuma_safety({ action: "verify", ... })` \u2014 Run tests / confirm nothing broken',
|
|
9811
|
-
' 10. `kuma_kuma_context({ action: "changes" })` \u2014 Review session activity',
|
|
9839
|
+
"\u26A1 **ADAPTIVE WORKFLOW (Lean Mode by Default):**",
|
|
9840
|
+
' \u2022 **Lean Mode (Default, < 3 files):** `kuma_kuma_context({ action: "init" })` \u2192 edit/read \u2192 record (`decision`/`gotcha`)',
|
|
9841
|
+
" \u2022 **Standard Mode (3-10 files/unfamiliar):** `init` \u2192 `guard` \u2192 `research` \u2192 edit \u2192 `record` \u2192 `verify`",
|
|
9842
|
+
" \u2022 **Full Mode (> 10 files/refactor):** `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
|
|
9812
9843
|
"",
|
|
9813
|
-
"\u{
|
|
9814
|
-
"
|
|
9815
|
-
"\u{1F7E2} **SKIP** recording function/class/component nodes \u2014 grep/glob is faster.",
|
|
9816
|
-
"\u{1F9E0} Knowledge graph is persistent \u2014 nodes/edges accumulate across sessions, getting richer over time.",
|
|
9844
|
+
"\u{1F534} **MUST RECORD:** `decision` (rationale), `gotcha` (bug/quirk), `arch_flow` (complex flow)",
|
|
9845
|
+
"\u{1F7E2} **SKIP RECORDING:** function, class, import, route (use native grep/glob instead)",
|
|
9817
9846
|
"",
|
|
9818
|
-
"\
|
|
9819
|
-
"\u{1F5D1}\uFE0F **Delete/Clear:** `delete_node` (delete node/gotcha/todo/decision) | `clear` (wipe entire graph)",
|
|
9820
|
-
"\u{1F4D6} Full rules: `.kuma/init.md`",
|
|
9847
|
+
"\u{1F4D6} Full rules & modes: `.kuma/init.md` | `.kuma/MODE.md` | `.kuma/SKIP_RULES.md`",
|
|
9821
9848
|
"\u{1F9E0} Tools: `kuma_kuma_context` | `kuma_kuma_memory` | `kuma_kuma_safety`"
|
|
9822
9849
|
].join("\n");
|
|
9823
9850
|
}
|
|
@@ -10339,8 +10366,8 @@ init_kumaDb();
|
|
|
10339
10366
|
init_kumaSelfHeal();
|
|
10340
10367
|
init_kumaMemory();
|
|
10341
10368
|
init_pathValidator();
|
|
10342
|
-
import
|
|
10343
|
-
import
|
|
10369
|
+
import fs14 from "fs";
|
|
10370
|
+
import path14 from "path";
|
|
10344
10371
|
var MEMORY_ALIASES = {
|
|
10345
10372
|
// Session synonyms
|
|
10346
10373
|
"session": "session",
|
|
@@ -10421,29 +10448,14 @@ var MEMORY_ALIASES = {
|
|
|
10421
10448
|
"layers": "layers",
|
|
10422
10449
|
"3-layer": "layers",
|
|
10423
10450
|
"memory-layers": "layers",
|
|
10424
|
-
// Federated synonyms (Issue #27)
|
|
10425
|
-
"federated": "federated",
|
|
10426
|
-
"federated-graph": "federated",
|
|
10427
|
-
"kuma-uri": "federated",
|
|
10428
|
-
"remote-graph": "federated",
|
|
10429
|
-
// Test generation synonyms (Issue #28)
|
|
10430
|
-
"gen_test": "gen_test",
|
|
10431
|
-
"gen-test": "gen_test",
|
|
10432
|
-
"generate-test": "gen_test",
|
|
10433
|
-
"gentest": "gen_test",
|
|
10434
|
-
// Trajectory synonyms
|
|
10435
|
-
"trajectory": "trajectory",
|
|
10436
|
-
"trajectories": "trajectory",
|
|
10437
|
-
"traj": "trajectory",
|
|
10438
|
-
// Skills synonyms
|
|
10439
|
-
"skills": "skills",
|
|
10440
|
-
"distilled-skills": "skills",
|
|
10441
|
-
"skill-list": "skills",
|
|
10442
10451
|
// Add node synonyms
|
|
10443
|
-
"add_node": "add_node",
|
|
10444
10452
|
"add-node": "add_node",
|
|
10445
|
-
"node": "add_node",
|
|
10446
10453
|
"create-node": "add_node",
|
|
10454
|
+
// Feature synonyms
|
|
10455
|
+
"feature": "feature",
|
|
10456
|
+
"features": "feature",
|
|
10457
|
+
"record-feature": "feature",
|
|
10458
|
+
"define-feature": "feature",
|
|
10447
10459
|
"record-node": "add_node",
|
|
10448
10460
|
// Delete node synonyms
|
|
10449
10461
|
"delete_node": "delete_node",
|
|
@@ -10495,22 +10507,16 @@ async function handleMemory(params) {
|
|
|
10495
10507
|
return handleGotchaAction(params);
|
|
10496
10508
|
case "layers":
|
|
10497
10509
|
return handleLayersSummary(params);
|
|
10498
|
-
case "federated":
|
|
10499
|
-
return handleFederated(params);
|
|
10500
|
-
case "gen_test":
|
|
10501
|
-
return handleGenTest(params);
|
|
10502
|
-
case "trajectory":
|
|
10503
|
-
return handleTrajectoryList(params);
|
|
10504
|
-
case "skills":
|
|
10505
|
-
return handleSkillsList(params);
|
|
10506
10510
|
case "add_node":
|
|
10507
10511
|
return handleAddNode(params);
|
|
10512
|
+
case "feature":
|
|
10513
|
+
return handleFeature(params);
|
|
10508
10514
|
case "delete_node":
|
|
10509
10515
|
return handleDeleteNode(params);
|
|
10510
10516
|
case "clear": {
|
|
10511
10517
|
const { clearGraph: clearGraph2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10512
10518
|
await clearGraph2();
|
|
10513
|
-
return "\u{1F5D1}\uFE0F **Knowledge Graph Cleared** \u2014 All nodes, edges,
|
|
10519
|
+
return "\u{1F5D1}\uFE0F **Knowledge Graph Cleared** \u2014 All nodes, edges, and gotchas have been wiped from disk and memory.";
|
|
10514
10520
|
}
|
|
10515
10521
|
default:
|
|
10516
10522
|
return `Unknown action "${action}".`;
|
|
@@ -10528,8 +10534,13 @@ async function handleDecision(params) {
|
|
|
10528
10534
|
Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
|
|
10529
10535
|
}
|
|
10530
10536
|
case "record":
|
|
10531
|
-
if (!params.title
|
|
10532
|
-
|
|
10537
|
+
if (!params.title) return `\u274C **decision format error:** title is required.
|
|
10538
|
+
|
|
10539
|
+
\u2705 Correct: kuma_memory({ action: "decision", decisionAction: "record", title: "Use bcrypt vs argon2", rationale: "bcrypt is battle-tested, argon2 is newer but less adoption", context: "Login system security", outcome: "Chose bcrypt" })`;
|
|
10540
|
+
if (!params.rationale) return `\u274C **decision format error:** rationale is required.
|
|
10541
|
+
|
|
10542
|
+
\u2705 Correct: kuma_memory({ action: "decision", decisionAction: "record", title: "${params.title}", rationale: "WHY you chose this option", context: "optional context", outcome: "optional outcome" })`;
|
|
10543
|
+
const decisionResult = await recordDecision({
|
|
10533
10544
|
title: params.title,
|
|
10534
10545
|
context: params.context || "",
|
|
10535
10546
|
options: [],
|
|
@@ -10537,6 +10548,8 @@ Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '
|
|
|
10537
10548
|
outcome: params.outcome || "implemented",
|
|
10538
10549
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10539
10550
|
});
|
|
10551
|
+
sessionMemory.recordMemoryAction("decision");
|
|
10552
|
+
return decisionResult;
|
|
10540
10553
|
default:
|
|
10541
10554
|
return formatDecisionTemplate();
|
|
10542
10555
|
}
|
|
@@ -10562,11 +10575,12 @@ async function handleResearchSave(params) {
|
|
|
10562
10575
|
} catch {
|
|
10563
10576
|
}
|
|
10564
10577
|
try {
|
|
10565
|
-
const researchDir =
|
|
10566
|
-
if (!
|
|
10567
|
-
|
|
10578
|
+
const researchDir = path14.join(getProjectRoot(), ".kuma", "research");
|
|
10579
|
+
if (!fs14.existsSync(researchDir)) fs14.mkdirSync(researchDir, { recursive: true });
|
|
10580
|
+
fs14.writeFileSync(path14.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
|
|
10568
10581
|
} catch {
|
|
10569
10582
|
}
|
|
10583
|
+
sessionMemory.recordMemoryAction("research_save");
|
|
10570
10584
|
return `\u2705 Research "${scope}" saved.`;
|
|
10571
10585
|
}
|
|
10572
10586
|
async function handleSession(params) {
|
|
@@ -10575,13 +10589,23 @@ async function handleSession(params) {
|
|
|
10575
10589
|
if (topic) return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
|
|
10576
10590
|
const modifiedFiles = summary.modifiedFiles || [];
|
|
10577
10591
|
const failures = summary.unresolvedFailures || [];
|
|
10592
|
+
const recordingSummary = sessionMemory.getRecordingSummary();
|
|
10593
|
+
const metricsSummary = sessionMemory.getMetricsSummary();
|
|
10578
10594
|
const lines = [
|
|
10579
10595
|
"\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",
|
|
10580
10596
|
`\u{1F3AF} Goal: ${summary.currentGoal || "not set"}`,
|
|
10581
10597
|
`\u{1F550} Duration: ${summary.sessionDuration}`,
|
|
10582
10598
|
`\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}
|
|
10583
|
-
|
|
10599
|
+
`,
|
|
10600
|
+
`\u{1F9E0} **Recordings:** ${recordingSummary.total} total \u2014 ${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save`,
|
|
10601
|
+
`\u{1F4CA} **Metrics:** ${metricsSummary.filesRead} files read, ${metricsSummary.filesEdited} files edited`,
|
|
10602
|
+
`\u23F1\uFE0F **Time saved:** ~${metricsSummary.researchTimeSavedFormatted} (from research cache)`
|
|
10584
10603
|
];
|
|
10604
|
+
if (recordingSummary.missingRecordings.length > 0 && recordingSummary.total === 0) {
|
|
10605
|
+
lines.push(`
|
|
10606
|
+
\u26A0\uFE0F **No recordings yet!** Missing: ${recordingSummary.missingRecordings.join(", ")}`);
|
|
10607
|
+
lines.push(`\u{1F4A1} Tip: Record findings after reading files. arch_flow + gotcha are exponential value.`);
|
|
10608
|
+
}
|
|
10585
10609
|
if (modifiedFiles.length > 0) {
|
|
10586
10610
|
lines.push(`**Modified Files** (${modifiedFiles.length}):`);
|
|
10587
10611
|
for (const f of modifiedFiles.slice(0, 10)) {
|
|
@@ -10613,6 +10637,33 @@ async function handleSearch(params) {
|
|
|
10613
10637
|
const memResults = sessionMemory.searchMemory(query, limit);
|
|
10614
10638
|
const { searchGraph: searchGraph2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10615
10639
|
const graphResults = await searchGraph2(query, Math.min(limit, 10));
|
|
10640
|
+
let taskContext = "";
|
|
10641
|
+
try {
|
|
10642
|
+
const { retrieveForTask: retrieveForTask2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10643
|
+
taskContext = await retrieveForTask2(query, 10);
|
|
10644
|
+
} catch {
|
|
10645
|
+
}
|
|
10646
|
+
let impactAnalysis = "";
|
|
10647
|
+
try {
|
|
10648
|
+
const { propagateImpact: propagateImpact2, searchGraph: sg } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10649
|
+
const searchRes = await sg(query, 1);
|
|
10650
|
+
const nodeIdMatch = searchRes.match(/\*\*([^*]+)\*\* \(([^)]+)\)/);
|
|
10651
|
+
if (nodeIdMatch) {
|
|
10652
|
+
const nodeType = nodeIdMatch[2];
|
|
10653
|
+
const nodeName = nodeIdMatch[1];
|
|
10654
|
+
const nid = `${nodeType}::${nodeName}`;
|
|
10655
|
+
const impacts = await propagateImpact2(nid, 3, 0.2);
|
|
10656
|
+
if (impacts.length > 0) {
|
|
10657
|
+
const impactLines = impacts.slice(0, 5).map(
|
|
10658
|
+
(i) => ` \u2022 ${i.name} (${i.type}) \u2014 depth ${i.depth}, weight ${i.weight}`
|
|
10659
|
+
);
|
|
10660
|
+
impactAnalysis = `
|
|
10661
|
+
\u{1F4A5} **Impact Analysis** \u2014 ${impacts.length} node(s) affected by "${nodeName}":
|
|
10662
|
+
${impactLines.join("\n")}`;
|
|
10663
|
+
}
|
|
10664
|
+
}
|
|
10665
|
+
} catch {
|
|
10666
|
+
}
|
|
10616
10667
|
let hybridResults = "";
|
|
10617
10668
|
try {
|
|
10618
10669
|
const { hybridSearch: hybridSearch2, formatHybridResults: formatHybridResults2 } = await Promise.resolve().then(() => (init_kumaSearch(), kumaSearch_exports));
|
|
@@ -10630,7 +10681,14 @@ async function handleSearch(params) {
|
|
|
10630
10681
|
for (const r of memResults.slice(0, 5)) lines.push(` \u2022 ${r.content.substring(0, 120)}`);
|
|
10631
10682
|
lines.push("");
|
|
10632
10683
|
}
|
|
10684
|
+
if (taskContext) {
|
|
10685
|
+
lines.push(taskContext);
|
|
10686
|
+
lines.push("");
|
|
10687
|
+
}
|
|
10633
10688
|
lines.push("**Knowledge Graph:\n" + graphResults);
|
|
10689
|
+
if (impactAnalysis) {
|
|
10690
|
+
lines.push(impactAnalysis);
|
|
10691
|
+
}
|
|
10634
10692
|
if (hybridResults) {
|
|
10635
10693
|
lines.push("");
|
|
10636
10694
|
lines.push(hybridResults);
|
|
@@ -10722,12 +10780,24 @@ async function handleLayerAction(layer, params) {
|
|
|
10722
10780
|
const gotchasStr = gotchasMatch ? gotchasMatch[1].trim() : "";
|
|
10723
10781
|
const decisionsStr = decisionsMatch ? decisionsMatch[1].trim() : "";
|
|
10724
10782
|
const filesStr = filesMatch ? filesMatch[1].trim() : "";
|
|
10783
|
+
if (hopsStr && !content.includes("\u2192")) {
|
|
10784
|
+
return `\u274C **arch_flow format error:** hops must use \u2192 separator.
|
|
10785
|
+
|
|
10786
|
+
\u274C Wrong: hops: file1.js, file2.js
|
|
10787
|
+
\u274C Wrong: hops: file1.js\\nfile2.js
|
|
10788
|
+
\u2705 Correct: domain: <name> | hops: file1.js \u2192 file2.js \u2192 file3.js`;
|
|
10789
|
+
}
|
|
10725
10790
|
const hops = hopsStr ? hopsStr.split("\u2192").map((h) => h.trim()).filter(Boolean).map((h, i, arr) => ({
|
|
10726
10791
|
from: i === 0 ? domain : arr[i - 1],
|
|
10727
10792
|
to: h,
|
|
10728
10793
|
relation: "flows",
|
|
10729
10794
|
description: h
|
|
10730
10795
|
})) : [];
|
|
10796
|
+
if (hops.length === 0) {
|
|
10797
|
+
return `\u274C **arch_flow format error:** no hops found after \u2192 separator.
|
|
10798
|
+
|
|
10799
|
+
\u2705 Correct: domain: <name> | hops: file1.js \u2192 file2.js`;
|
|
10800
|
+
}
|
|
10731
10801
|
const gotchas = gotchasStr ? gotchasStr.split(",").map((g) => g.trim()).filter(Boolean) : [];
|
|
10732
10802
|
const decisions = decisionsStr ? decisionsStr.split(",").map((d) => d.trim()).filter(Boolean) : [];
|
|
10733
10803
|
const filePaths = filesStr ? filesStr.split(",").map((f) => f.trim()).filter(Boolean) : [];
|
|
@@ -10735,6 +10805,7 @@ async function handleLayerAction(layer, params) {
|
|
|
10735
10805
|
const { recordDomainFlow: recordDomainFlow2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10736
10806
|
const flow = await recordDomainFlow2({ domain, hops, gotchas, decisions, filePaths });
|
|
10737
10807
|
await writeLayer2("arch_flow", content);
|
|
10808
|
+
sessionMemory.recordMemoryAction("arch_flow");
|
|
10738
10809
|
return `\u2705 Domain flow "${domain}" recorded \u2014 ${flow.nodeCount} nodes, ${flow.edgeCount} edges created.
|
|
10739
10810
|
\u{1F3DB}\uFE0F **Domain Anchor:** ${domain}
|
|
10740
10811
|
\u{1F504} **Hops:** ${hops.length}
|
|
@@ -10745,7 +10816,15 @@ async function handleLayerAction(layer, params) {
|
|
|
10745
10816
|
return `\u2705 Architecture flow saved (text only). Graph recording failed: ${err}`;
|
|
10746
10817
|
}
|
|
10747
10818
|
}
|
|
10748
|
-
return
|
|
10819
|
+
return `\u274C **arch_flow format not recognized.** Use this format:
|
|
10820
|
+
|
|
10821
|
+
domain: <DomainName> | hops: <file1.tsx> \u2192 <file2.js> \u2192 <file3.ts>
|
|
10822
|
+
|
|
10823
|
+
Fields:
|
|
10824
|
+
- domain: short name (e.g. InviteUserFlow)
|
|
10825
|
+
- hops: file names separated by \u2192 (NOT commas, NOT newlines)
|
|
10826
|
+
- gotchas: optional, comma-separated
|
|
10827
|
+
- decisions: optional, comma-separated`;
|
|
10749
10828
|
}
|
|
10750
10829
|
if (params.content) {
|
|
10751
10830
|
return writeLayer2(layer, params.content);
|
|
@@ -10754,13 +10833,25 @@ async function handleLayerAction(layer, params) {
|
|
|
10754
10833
|
}
|
|
10755
10834
|
async function handleGotchaAction(params) {
|
|
10756
10835
|
if (params.content && params.scope) {
|
|
10836
|
+
if (!params.scope.trim()) {
|
|
10837
|
+
return `\u274C **gotcha format error:** scope (file path) is required.
|
|
10838
|
+
|
|
10839
|
+
\u2705 Correct: kuma_memory({ action: "gotcha", scope: "path/to/file.ts", content: "bug description", status: "high" })`;
|
|
10840
|
+
}
|
|
10841
|
+
if (!params.content.trim()) {
|
|
10842
|
+
return `\u274C **gotcha format error:** content (description) is required.
|
|
10843
|
+
|
|
10844
|
+
\u2705 Correct: kuma_memory({ action: "gotcha", scope: "path/to/file.ts", content: "bug description", status: "high" })`;
|
|
10845
|
+
}
|
|
10757
10846
|
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10758
|
-
|
|
10847
|
+
const result = await addGotcha2({
|
|
10759
10848
|
filePath: params.scope,
|
|
10760
10849
|
description: params.content,
|
|
10761
10850
|
severity: params.status || "medium",
|
|
10762
10851
|
workaround: params.description
|
|
10763
10852
|
});
|
|
10853
|
+
sessionMemory.recordMemoryAction("gotcha");
|
|
10854
|
+
return result;
|
|
10764
10855
|
}
|
|
10765
10856
|
const { listGotchas: listGotchas2, syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10766
10857
|
await syncGotchasToDb2();
|
|
@@ -10796,19 +10887,13 @@ async function handleDeleteNode(params) {
|
|
|
10796
10887
|
saveDb2(db);
|
|
10797
10888
|
return `\u{1F5D1}\uFE0F **Decision #${id} deleted.**`;
|
|
10798
10889
|
}
|
|
10799
|
-
case "trajectory":
|
|
10800
|
-
case "trajectories": {
|
|
10801
|
-
db.run("DELETE FROM trajectories WHERE id = ?", [id]);
|
|
10802
|
-
saveDb2(db);
|
|
10803
|
-
return `\u{1F5D1}\uFE0F **Trajectory #${id} deleted.**`;
|
|
10804
|
-
}
|
|
10805
10890
|
case "checkpoint": {
|
|
10806
10891
|
db.run("DELETE FROM health_snapshots WHERE id = ?", [id]);
|
|
10807
10892
|
saveDb2(db);
|
|
10808
10893
|
return `\u{1F5D1}\uFE0F **Checkpoint #${id} deleted.**`;
|
|
10809
10894
|
}
|
|
10810
10895
|
default:
|
|
10811
|
-
return `\u26A0\uFE0F Unknown scope "${params.scope}". Supported: gotcha, todo, decision,
|
|
10896
|
+
return `\u26A0\uFE0F Unknown scope "${params.scope}". Supported: gotcha, todo, decision, checkpoint`;
|
|
10812
10897
|
}
|
|
10813
10898
|
}
|
|
10814
10899
|
if (params.target) {
|
|
@@ -10841,38 +10926,7 @@ async function handleDeleteNode(params) {
|
|
|
10841
10926
|
return `\u{1F5D1}\uFE0F **Node #${id} deleted.**`;
|
|
10842
10927
|
}
|
|
10843
10928
|
}
|
|
10844
|
-
return "\u26A0\uFE0F Provide `target` (node ID) to delete, or `scope` + `target` (numeric ID) for gotcha/todo/decision/
|
|
10845
|
-
}
|
|
10846
|
-
async function handleFederated(params) {
|
|
10847
|
-
sessionMemory.recordToolCall("kuma_memory_federated", { scope: params.scope, uri: params.uri });
|
|
10848
|
-
if (params.uri) {
|
|
10849
|
-
const { resolveFederatedNode: resolveFederatedNode2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10850
|
-
return await resolveFederatedNode2(params.uri);
|
|
10851
|
-
}
|
|
10852
|
-
const { listFederatedReferences: listFederatedReferences2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10853
|
-
return await listFederatedReferences2();
|
|
10854
|
-
}
|
|
10855
|
-
async function handleGenTest(params) {
|
|
10856
|
-
sessionMemory.recordToolCall("kuma_memory_gen_test", { target: params.target });
|
|
10857
|
-
if (params.target) {
|
|
10858
|
-
const id = parseInt(params.target, 10);
|
|
10859
|
-
if (!isNaN(id)) {
|
|
10860
|
-
const { generateTestFromTrajectoryId: generateTestFromTrajectoryId2 } = await Promise.resolve().then(() => (init_kumaTrajectory(), kumaTrajectory_exports));
|
|
10861
|
-
return await generateTestFromTrajectoryId2(id);
|
|
10862
|
-
}
|
|
10863
|
-
}
|
|
10864
|
-
const { listGeneratedTests: listGeneratedTests2 } = await Promise.resolve().then(() => (init_kumaTrajectory(), kumaTrajectory_exports));
|
|
10865
|
-
return await listGeneratedTests2();
|
|
10866
|
-
}
|
|
10867
|
-
async function handleTrajectoryList(params) {
|
|
10868
|
-
sessionMemory.recordToolCall("kuma_memory_trajectory", {});
|
|
10869
|
-
const { listTrajectories: listTrajectories2 } = await Promise.resolve().then(() => (init_kumaTrajectory(), kumaTrajectory_exports));
|
|
10870
|
-
return await listTrajectories2(params.limit || 10);
|
|
10871
|
-
}
|
|
10872
|
-
async function handleSkillsList(_params) {
|
|
10873
|
-
sessionMemory.recordToolCall("kuma_memory_skills", {});
|
|
10874
|
-
const { listDistilledSkills: listDistilledSkills2 } = await Promise.resolve().then(() => (init_kumaTrajectory(), kumaTrajectory_exports));
|
|
10875
|
-
return await listDistilledSkills2();
|
|
10929
|
+
return "\u26A0\uFE0F Provide `target` (node ID) to delete, or `scope` + `target` (numeric ID) for gotcha/todo/decision/checkpoint.\n\nExamples:\n- `delete_node`, target: 'function::sendMessage'\n- `delete_node`, scope: 'gotcha', target: '42'\n- `delete_node`, scope: 'todo', target: '7'\n- `delete_node`, scope: 'decision', target: '3'\n- `delete_node`, scope: 'checkpoint', target: '1'";
|
|
10876
10930
|
}
|
|
10877
10931
|
async function handleAddNode(params) {
|
|
10878
10932
|
const type = params.title;
|
|
@@ -10907,6 +10961,29 @@ async function handleAddNode(params) {
|
|
|
10907
10961
|
return `\u274C Failed to create node: ${err}`;
|
|
10908
10962
|
}
|
|
10909
10963
|
}
|
|
10964
|
+
async function handleFeature(params) {
|
|
10965
|
+
sessionMemory.recordToolCall("kuma_memory_feature", { title: params.title, scope: params.scope });
|
|
10966
|
+
if (!params.title) {
|
|
10967
|
+
return "\u26A0\uFE0F `title` (feature name) required.\n\n\u2705 Correct: kuma_memory({ action: 'feature', title: 'Authentication', content: 'description', scope: 'src/auth/login.ts,src/auth/session.ts' })\n\n\u2022 title = feature name (e.g. 'Authentication', 'Billing')\n\u2022 content = description (optional)\n\u2022 scope = comma-separated file paths (optional)\n\u2022 tags = comma-separated tags (optional)\n\u2022 status = risk level: low/medium/high/critical (optional)";
|
|
10968
|
+
}
|
|
10969
|
+
const { recordFeature: recordFeature2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10970
|
+
const files = params.scope ? params.scope.split(",").map((f) => f.trim()).filter(Boolean) : [];
|
|
10971
|
+
const tags = params.tags ? params.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
10972
|
+
const risk = params.status || "medium";
|
|
10973
|
+
const result = await recordFeature2({
|
|
10974
|
+
name: params.title,
|
|
10975
|
+
description: params.content,
|
|
10976
|
+
files,
|
|
10977
|
+
tags,
|
|
10978
|
+
risk
|
|
10979
|
+
});
|
|
10980
|
+
sessionMemory.recordMemoryAction("research_save");
|
|
10981
|
+
return `\u2705 **Feature "${params.title}" recorded** \u2014 ${result.nodeCount} node(s), ${result.edgeCount} edge(s)
|
|
10982
|
+
\u2B50 Level: feature
|
|
10983
|
+
\u{1F4C1} Files: ${files.length}
|
|
10984
|
+
\u{1F3F7}\uFE0F Tags: ${tags.length}
|
|
10985
|
+
\u26A0\uFE0F Risk: ${risk}`;
|
|
10986
|
+
}
|
|
10910
10987
|
async function handleLayersSummary(_params) {
|
|
10911
10988
|
const { getLayersSummary: getLayersSummary2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
|
|
10912
10989
|
return getLayersSummary2();
|
|
@@ -10917,72 +10994,72 @@ init_sessionMemory();
|
|
|
10917
10994
|
|
|
10918
10995
|
// src/engine/kumaLock.ts
|
|
10919
10996
|
init_pathValidator();
|
|
10920
|
-
import
|
|
10921
|
-
import
|
|
10997
|
+
import fs15 from "fs";
|
|
10998
|
+
import path15 from "path";
|
|
10922
10999
|
var LOCKS_DIR = ".kuma/locks";
|
|
10923
11000
|
function locksDir() {
|
|
10924
|
-
return
|
|
11001
|
+
return path15.join(getProjectRoot(), LOCKS_DIR);
|
|
10925
11002
|
}
|
|
10926
11003
|
function ensureLocksDir() {
|
|
10927
11004
|
const dir = locksDir();
|
|
10928
|
-
if (!
|
|
11005
|
+
if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
|
|
10929
11006
|
}
|
|
10930
11007
|
function lockPath(filePath) {
|
|
10931
11008
|
const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
|
|
10932
|
-
return
|
|
11009
|
+
return path15.join(locksDir(), `${safeName}.lock.json`);
|
|
10933
11010
|
}
|
|
10934
11011
|
function acquireLock(filePath, agentId) {
|
|
10935
11012
|
ensureLocksDir();
|
|
10936
11013
|
const lp = lockPath(filePath);
|
|
10937
11014
|
const id = agentId || `agent-${process.pid}`;
|
|
10938
|
-
if (
|
|
11015
|
+
if (fs15.existsSync(lp)) {
|
|
10939
11016
|
try {
|
|
10940
|
-
const existing = JSON.parse(
|
|
11017
|
+
const existing = JSON.parse(fs15.readFileSync(lp, "utf-8"));
|
|
10941
11018
|
if (existing.agentId === id) {
|
|
10942
11019
|
return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
|
|
10943
11020
|
}
|
|
10944
11021
|
const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
|
|
10945
11022
|
if (elapsed > 300) {
|
|
10946
|
-
|
|
11023
|
+
fs15.unlinkSync(lp);
|
|
10947
11024
|
} else {
|
|
10948
11025
|
return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
|
|
10949
11026
|
}
|
|
10950
11027
|
} catch {
|
|
10951
|
-
|
|
11028
|
+
fs15.unlinkSync(lp);
|
|
10952
11029
|
}
|
|
10953
11030
|
}
|
|
10954
11031
|
const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
|
|
10955
|
-
|
|
11032
|
+
fs15.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
|
|
10956
11033
|
return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
|
|
10957
11034
|
}
|
|
10958
11035
|
function releaseLock(filePath, agentId) {
|
|
10959
11036
|
ensureLocksDir();
|
|
10960
11037
|
const lp = lockPath(filePath);
|
|
10961
11038
|
const id = agentId || `agent-${process.pid}`;
|
|
10962
|
-
if (!
|
|
11039
|
+
if (!fs15.existsSync(lp)) {
|
|
10963
11040
|
return `\u26A0\uFE0F No lock found for \`${filePath}\``;
|
|
10964
11041
|
}
|
|
10965
11042
|
try {
|
|
10966
|
-
const existing = JSON.parse(
|
|
11043
|
+
const existing = JSON.parse(fs15.readFileSync(lp, "utf-8"));
|
|
10967
11044
|
if (existing.agentId !== id) {
|
|
10968
11045
|
return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
|
|
10969
11046
|
}
|
|
10970
|
-
|
|
11047
|
+
fs15.unlinkSync(lp);
|
|
10971
11048
|
return `\u{1F513} **Lock released** on \`${filePath}\``;
|
|
10972
11049
|
} catch {
|
|
10973
|
-
|
|
11050
|
+
fs15.unlinkSync(lp);
|
|
10974
11051
|
return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
|
|
10975
11052
|
}
|
|
10976
11053
|
}
|
|
10977
11054
|
function listLocks() {
|
|
10978
11055
|
ensureLocksDir();
|
|
10979
11056
|
const dir = locksDir();
|
|
10980
|
-
const files =
|
|
11057
|
+
const files = fs15.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
|
|
10981
11058
|
if (files.length === 0) return "\u{1F513} No active locks.";
|
|
10982
11059
|
const lines = ["\u{1F512} **Active Locks:**", ""];
|
|
10983
11060
|
for (const f of files) {
|
|
10984
11061
|
try {
|
|
10985
|
-
const entry = JSON.parse(
|
|
11062
|
+
const entry = JSON.parse(fs15.readFileSync(path15.join(dir, f), "utf-8"));
|
|
10986
11063
|
const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
|
|
10987
11064
|
lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
|
|
10988
11065
|
} catch {
|
|
@@ -10993,9 +11070,9 @@ function listLocks() {
|
|
|
10993
11070
|
}
|
|
10994
11071
|
function isLocked(filePath) {
|
|
10995
11072
|
const lp = lockPath(filePath);
|
|
10996
|
-
if (!
|
|
11073
|
+
if (!fs15.existsSync(lp)) return { locked: false };
|
|
10997
11074
|
try {
|
|
10998
|
-
const entry = JSON.parse(
|
|
11075
|
+
const entry = JSON.parse(fs15.readFileSync(lp, "utf-8"));
|
|
10999
11076
|
return { locked: true, by: entry.agentId, since: entry.acquiredAt };
|
|
11000
11077
|
} catch {
|
|
11001
11078
|
return { locked: false };
|
|
@@ -11005,16 +11082,16 @@ function cleanStaleLocks() {
|
|
|
11005
11082
|
ensureLocksDir();
|
|
11006
11083
|
const dir = locksDir();
|
|
11007
11084
|
let count = 0;
|
|
11008
|
-
for (const f of
|
|
11085
|
+
for (const f of fs15.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
|
|
11009
11086
|
try {
|
|
11010
|
-
const entry = JSON.parse(
|
|
11087
|
+
const entry = JSON.parse(fs15.readFileSync(path15.join(dir, f), "utf-8"));
|
|
11011
11088
|
if (Date.now() - entry.acquiredAt > 3e5) {
|
|
11012
|
-
|
|
11089
|
+
fs15.unlinkSync(path15.join(dir, f));
|
|
11013
11090
|
count++;
|
|
11014
11091
|
}
|
|
11015
11092
|
} catch {
|
|
11016
11093
|
try {
|
|
11017
|
-
|
|
11094
|
+
fs15.unlinkSync(path15.join(dir, f));
|
|
11018
11095
|
count++;
|
|
11019
11096
|
} catch {
|
|
11020
11097
|
}
|
|
@@ -11030,8 +11107,8 @@ init_safetyAudit();
|
|
|
11030
11107
|
|
|
11031
11108
|
// src/tools/kumaPolicy.ts
|
|
11032
11109
|
init_pathValidator();
|
|
11033
|
-
import
|
|
11034
|
-
import
|
|
11110
|
+
import fs16 from "fs";
|
|
11111
|
+
import path16 from "path";
|
|
11035
11112
|
var DEFAULT_POLICY = {
|
|
11036
11113
|
never_touch: [
|
|
11037
11114
|
".env",
|
|
@@ -11057,15 +11134,15 @@ var DEFAULT_POLICY = {
|
|
|
11057
11134
|
};
|
|
11058
11135
|
function loadPolicy() {
|
|
11059
11136
|
const root = getProjectRoot();
|
|
11060
|
-
const ymlPath =
|
|
11061
|
-
const yamlPath =
|
|
11137
|
+
const ymlPath = path16.join(root, ".kuma", "policy.yml");
|
|
11138
|
+
const yamlPath = path16.join(root, ".kuma", "policy.yaml");
|
|
11062
11139
|
let policyContent = null;
|
|
11063
11140
|
let policyPath = null;
|
|
11064
|
-
if (
|
|
11065
|
-
policyContent =
|
|
11141
|
+
if (fs16.existsSync(ymlPath)) {
|
|
11142
|
+
policyContent = fs16.readFileSync(ymlPath, "utf-8");
|
|
11066
11143
|
policyPath = ymlPath;
|
|
11067
|
-
} else if (
|
|
11068
|
-
policyContent =
|
|
11144
|
+
} else if (fs16.existsSync(yamlPath)) {
|
|
11145
|
+
policyContent = fs16.readFileSync(yamlPath, "utf-8");
|
|
11069
11146
|
policyPath = yamlPath;
|
|
11070
11147
|
}
|
|
11071
11148
|
if (!policyContent) {
|
|
@@ -11175,9 +11252,9 @@ function checkFilePathPolicy(filePath, policy) {
|
|
|
11175
11252
|
if (policy.max_file_size && policy.max_file_size > 0) {
|
|
11176
11253
|
try {
|
|
11177
11254
|
const root = getProjectRoot();
|
|
11178
|
-
const fullPath =
|
|
11179
|
-
if (
|
|
11180
|
-
const sizeKB =
|
|
11255
|
+
const fullPath = path16.join(root, filePath);
|
|
11256
|
+
if (fs16.existsSync(fullPath)) {
|
|
11257
|
+
const sizeKB = fs16.statSync(fullPath).size / 1024;
|
|
11181
11258
|
if (sizeKB > policy.max_file_size) {
|
|
11182
11259
|
violations.push({
|
|
11183
11260
|
rule: "max_file_size",
|
|
@@ -11344,12 +11421,12 @@ async function safetyCheck(action, filePath, command) {
|
|
|
11344
11421
|
}
|
|
11345
11422
|
if (filePath) {
|
|
11346
11423
|
try {
|
|
11347
|
-
const
|
|
11348
|
-
const
|
|
11424
|
+
const fs26 = await import("fs");
|
|
11425
|
+
const path26 = await import("path");
|
|
11349
11426
|
const root = process.cwd();
|
|
11350
|
-
const fullPath =
|
|
11351
|
-
if (
|
|
11352
|
-
const stat =
|
|
11427
|
+
const fullPath = path26.resolve(root, filePath);
|
|
11428
|
+
if (fs26.existsSync(fullPath)) {
|
|
11429
|
+
const stat = fs26.statSync(fullPath);
|
|
11353
11430
|
const sizeKB = Math.round(stat.size / 1024);
|
|
11354
11431
|
if (sizeKB > 500) {
|
|
11355
11432
|
checks.push({
|
|
@@ -11359,12 +11436,12 @@ async function safetyCheck(action, filePath, command) {
|
|
|
11359
11436
|
});
|
|
11360
11437
|
}
|
|
11361
11438
|
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
11362
|
-
const baseName =
|
|
11439
|
+
const baseName = path26.basename(filePath, path26.extname(filePath));
|
|
11363
11440
|
const testPatterns = [
|
|
11364
11441
|
filePath.replace(/\.(ts|js)$/, ".test.$1"),
|
|
11365
11442
|
filePath.replace(/\.(ts|js)$/, ".spec.$1"),
|
|
11366
11443
|
filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
|
|
11367
|
-
`**/__tests__/**/${baseName}.test.${
|
|
11444
|
+
`**/__tests__/**/${baseName}.test.${path26.extname(filePath).slice(1)}`
|
|
11368
11445
|
];
|
|
11369
11446
|
let hasTests = false;
|
|
11370
11447
|
const fg = await import("fast-glob");
|
|
@@ -11380,7 +11457,7 @@ async function safetyCheck(action, filePath, command) {
|
|
|
11380
11457
|
name: "Tests Check",
|
|
11381
11458
|
passed: true,
|
|
11382
11459
|
// Not a blocker, just informative
|
|
11383
|
-
detail: `\u{1F9EA} No test file found for "${
|
|
11460
|
+
detail: `\u{1F9EA} No test file found for "${path26.basename(filePath)}" \u2014 consider adding tests`
|
|
11384
11461
|
});
|
|
11385
11462
|
} else {
|
|
11386
11463
|
checks.push({
|
|
@@ -11469,8 +11546,8 @@ init_sessionMemory();
|
|
|
11469
11546
|
// src/guards/antiPatternDetector.ts
|
|
11470
11547
|
init_pathValidator();
|
|
11471
11548
|
init_sessionMemory();
|
|
11472
|
-
import
|
|
11473
|
-
import
|
|
11549
|
+
import fs17 from "fs";
|
|
11550
|
+
import path17 from "path";
|
|
11474
11551
|
import { execSync as execSync5 } from "child_process";
|
|
11475
11552
|
var SCRIPT_PATCH_PATTERNS = [
|
|
11476
11553
|
"writeFileSync",
|
|
@@ -11497,20 +11574,20 @@ function findPatchScripts(projectRoot) {
|
|
|
11497
11574
|
const warnings = [];
|
|
11498
11575
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
11499
11576
|
for (const file of recentFiles) {
|
|
11500
|
-
const ext =
|
|
11577
|
+
const ext = path17.extname(file).toLowerCase();
|
|
11501
11578
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
11502
|
-
const relativePath =
|
|
11579
|
+
const relativePath = path17.relative(projectRoot, file);
|
|
11503
11580
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
|
|
11504
11581
|
continue;
|
|
11505
11582
|
}
|
|
11506
11583
|
try {
|
|
11507
|
-
const content =
|
|
11584
|
+
const content = fs17.readFileSync(file, "utf-8").toLowerCase();
|
|
11508
11585
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
11509
11586
|
if (matchedPattern) {
|
|
11510
11587
|
warnings.push({
|
|
11511
11588
|
severity: "high",
|
|
11512
11589
|
pattern: "script-patching",
|
|
11513
|
-
message: `Created script file that modifies other files: ${
|
|
11590
|
+
message: `Created script file that modifies other files: ${path17.basename(file)}`,
|
|
11514
11591
|
suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
|
|
11515
11592
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
11516
11593
|
filePath: relativePath
|
|
@@ -11544,15 +11621,15 @@ function detectBashGrepUsage() {
|
|
|
11544
11621
|
function scanRecentFiles(projectRoot) {
|
|
11545
11622
|
const recent = [];
|
|
11546
11623
|
try {
|
|
11547
|
-
const entries =
|
|
11624
|
+
const entries = fs17.readdirSync(projectRoot, { withFileTypes: true });
|
|
11548
11625
|
const now = Date.now();
|
|
11549
11626
|
const recentThreshold = 30 * 60 * 1e3;
|
|
11550
11627
|
for (const entry of entries) {
|
|
11551
11628
|
if (!entry.isFile()) continue;
|
|
11552
11629
|
try {
|
|
11553
|
-
const stat =
|
|
11630
|
+
const stat = fs17.statSync(path17.join(projectRoot, entry.name));
|
|
11554
11631
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
11555
|
-
recent.push(
|
|
11632
|
+
recent.push(path17.join(projectRoot, entry.name));
|
|
11556
11633
|
}
|
|
11557
11634
|
} catch {
|
|
11558
11635
|
continue;
|
|
@@ -11576,14 +11653,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
11576
11653
|
const prefix = line.substring(0, 2);
|
|
11577
11654
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
11578
11655
|
const file = line.substring(3).trim();
|
|
11579
|
-
const ext =
|
|
11656
|
+
const ext = path17.extname(file).toLowerCase();
|
|
11580
11657
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
11581
11658
|
const isRootLevel = !file.includes("/");
|
|
11582
11659
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
11583
11660
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
11584
|
-
const fullPath =
|
|
11585
|
-
if (
|
|
11586
|
-
const content =
|
|
11661
|
+
const fullPath = path17.join(projectRoot, file);
|
|
11662
|
+
if (fs17.existsSync(fullPath)) {
|
|
11663
|
+
const content = fs17.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
11587
11664
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
11588
11665
|
if (matchedPattern) {
|
|
11589
11666
|
warnings.push({
|
|
@@ -11606,7 +11683,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
11606
11683
|
if (deletedStdout) {
|
|
11607
11684
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
11608
11685
|
for (const file of deletedFiles) {
|
|
11609
|
-
const ext =
|
|
11686
|
+
const ext = path17.extname(file).toLowerCase();
|
|
11610
11687
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
11611
11688
|
warnings.push({
|
|
11612
11689
|
severity: "high",
|
|
@@ -11668,21 +11745,21 @@ function detectAllAntiPatterns() {
|
|
|
11668
11745
|
// src/engine/contextSnapshot.ts
|
|
11669
11746
|
init_sessionMemory();
|
|
11670
11747
|
init_pathValidator();
|
|
11671
|
-
import
|
|
11672
|
-
import
|
|
11748
|
+
import fs18 from "fs";
|
|
11749
|
+
import path18 from "path";
|
|
11673
11750
|
import { execSync as execSync6 } from "child_process";
|
|
11674
11751
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
11675
11752
|
function snapshotDir() {
|
|
11676
|
-
return
|
|
11753
|
+
return path18.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
11677
11754
|
}
|
|
11678
11755
|
function ensureSnapshotDir() {
|
|
11679
11756
|
const dir = snapshotDir();
|
|
11680
|
-
if (!
|
|
11681
|
-
|
|
11757
|
+
if (!fs18.existsSync(dir)) {
|
|
11758
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
11682
11759
|
}
|
|
11683
11760
|
}
|
|
11684
11761
|
function snapshotFilePath(timestamp) {
|
|
11685
|
-
return
|
|
11762
|
+
return path18.join(snapshotDir(), `${timestamp}.json`);
|
|
11686
11763
|
}
|
|
11687
11764
|
function saveSnapshot(goal) {
|
|
11688
11765
|
try {
|
|
@@ -11703,7 +11780,7 @@ function saveSnapshot(goal) {
|
|
|
11703
11780
|
hasConventions: !!summary.hasConventions
|
|
11704
11781
|
};
|
|
11705
11782
|
try {
|
|
11706
|
-
|
|
11783
|
+
fs18.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
11707
11784
|
} catch {
|
|
11708
11785
|
}
|
|
11709
11786
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -11802,6 +11879,55 @@ async function handleKumaGuard(params) {
|
|
|
11802
11879
|
});
|
|
11803
11880
|
}
|
|
11804
11881
|
}
|
|
11882
|
+
const recordingSummary = sessionMemory.getRecordingSummary();
|
|
11883
|
+
if (check === "all" || check === "drift") {
|
|
11884
|
+
const readCalls = stats.toolCalls.filter(
|
|
11885
|
+
(c) => c.toolName === "read" || c.toolName === "grep" || c.toolName === "glob" || c.toolName === "smart_file_picker"
|
|
11886
|
+
).length;
|
|
11887
|
+
if (stats.toolCallCount >= 10 && !recordingSummary.hasAnyRecordings) {
|
|
11888
|
+
warnings.push({
|
|
11889
|
+
severity: "high",
|
|
11890
|
+
pattern: "no-recordings-critical",
|
|
11891
|
+
message: `\u{1F6AB} BLOCKING: ${stats.toolCallCount} tool calls with 0 knowledge recordings. You are wasting future sessions by not recording.`,
|
|
11892
|
+
suggestion: "STOP. Record now:\n- kuma_memory({ action: 'research_save', scope: '<file>' }) after reading files\n- kuma_memory({ action: 'gotcha', scope: '<file>', content: '<bug>' }) when finding bugs\n- kuma_memory({ action: 'arch_flow', content: 'domain: <X> | hops: <file1> \u2192 <file2>' }) when tracing flows"
|
|
11893
|
+
});
|
|
11894
|
+
} else if (stats.toolCallCount >= 5 && !recordingSummary.hasAnyRecordings) {
|
|
11895
|
+
warnings.push({
|
|
11896
|
+
severity: "medium",
|
|
11897
|
+
pattern: "no-recordings",
|
|
11898
|
+
message: `${stats.toolCallCount} tool calls made but 0 knowledge recordings. Agent is not building persistent knowledge.`,
|
|
11899
|
+
suggestion: "Record findings after reading files. arch_flow + gotcha are exponential value."
|
|
11900
|
+
});
|
|
11901
|
+
} else if (readCalls >= 3 && recordingSummary.researchSaves === 0) {
|
|
11902
|
+
warnings.push({
|
|
11903
|
+
severity: "low",
|
|
11904
|
+
pattern: "read-without-record",
|
|
11905
|
+
message: `${readCalls} file reads but 0 research_save calls. Reading without recording = wasted context.`,
|
|
11906
|
+
suggestion: "After reading unfamiliar files: kuma_memory({ action: 'research_save', scope: '<file>' })"
|
|
11907
|
+
});
|
|
11908
|
+
}
|
|
11909
|
+
if (recordingSummary.total > 0) {
|
|
11910
|
+
}
|
|
11911
|
+
const errorCalls = stats.toolCalls.filter(
|
|
11912
|
+
(c) => c.toolName === "execute_safe_command" && JSON.stringify(c.params).includes("error")
|
|
11913
|
+
).length;
|
|
11914
|
+
if (errorCalls >= 2 && recordingSummary.gotchas === 0) {
|
|
11915
|
+
warnings.push({
|
|
11916
|
+
severity: "medium",
|
|
11917
|
+
pattern: "error-without-gotcha",
|
|
11918
|
+
message: `${errorCalls} error encounters but 0 gotchas recorded. Errors = future gotchas.`,
|
|
11919
|
+
suggestion: "Record gotchas for bugs you encounter:\nkuma_memory({ action: 'gotcha', scope: '<file>', content: '<what went wrong>', status: 'high' })"
|
|
11920
|
+
});
|
|
11921
|
+
}
|
|
11922
|
+
if (readCalls >= 4 && recordingSummary.archFlows === 0) {
|
|
11923
|
+
warnings.push({
|
|
11924
|
+
severity: "low",
|
|
11925
|
+
pattern: "trace-without-arch-flow",
|
|
11926
|
+
message: `${readCalls} file reads (possible flow tracing) but 0 arch_flows recorded. Flow knowledge dissipates fast.`,
|
|
11927
|
+
suggestion: "After tracing a flow, record it:\nkuma_memory({ action: 'arch_flow', content: 'domain: <Name> | hops: <file1> \u2192 <file2> \u2192 <file3>' })"
|
|
11928
|
+
});
|
|
11929
|
+
}
|
|
11930
|
+
}
|
|
11805
11931
|
if (check === "context") {
|
|
11806
11932
|
const snapshot = saveSnapshot(stats.goal);
|
|
11807
11933
|
if (!snapshot) {
|
|
@@ -11843,7 +11969,24 @@ async function handleKumaGuard(params) {
|
|
|
11843
11969
|
hasRunTests: stats.hasRunTests
|
|
11844
11970
|
}
|
|
11845
11971
|
};
|
|
11846
|
-
|
|
11972
|
+
const metricsSummary = sessionMemory.getMetricsSummary();
|
|
11973
|
+
const reportWithRecordings = {
|
|
11974
|
+
...report,
|
|
11975
|
+
recordings: {
|
|
11976
|
+
archFlows: recordingSummary.archFlows,
|
|
11977
|
+
gotchas: recordingSummary.gotchas,
|
|
11978
|
+
decisions: recordingSummary.decisions,
|
|
11979
|
+
researchSaves: recordingSummary.researchSaves,
|
|
11980
|
+
total: recordingSummary.total
|
|
11981
|
+
},
|
|
11982
|
+
metrics: {
|
|
11983
|
+
filesRead: metricsSummary.filesRead,
|
|
11984
|
+
filesEdited: metricsSummary.filesEdited,
|
|
11985
|
+
researchTimeSaved: metricsSummary.researchTimeSavedFormatted,
|
|
11986
|
+
sessionDuration: metricsSummary.sessionDuration
|
|
11987
|
+
}
|
|
11988
|
+
};
|
|
11989
|
+
return JSON.stringify(reportWithRecordings, null, 2);
|
|
11847
11990
|
}
|
|
11848
11991
|
|
|
11849
11992
|
// src/tools/kumaSafetyTool.ts
|
|
@@ -11948,12 +12091,12 @@ async function handleSecurity(params) {
|
|
|
11948
12091
|
sessionMemory.recordToolCall("kuma_safety_security", {});
|
|
11949
12092
|
if (params.filePath) {
|
|
11950
12093
|
try {
|
|
11951
|
-
const
|
|
11952
|
-
const
|
|
12094
|
+
const fs26 = await import("fs");
|
|
12095
|
+
const path26 = await import("path");
|
|
11953
12096
|
const root = process.cwd();
|
|
11954
|
-
const fullPath =
|
|
11955
|
-
if (
|
|
11956
|
-
const content =
|
|
12097
|
+
const fullPath = path26.resolve(root, params.filePath);
|
|
12098
|
+
if (fs26.existsSync(fullPath) && fs26.statSync(fullPath).isFile()) {
|
|
12099
|
+
const content = fs26.readFileSync(fullPath, "utf-8");
|
|
11957
12100
|
const lines = content.split("\n");
|
|
11958
12101
|
const findings = [];
|
|
11959
12102
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -12007,23 +12150,23 @@ async function handleGitignore(_params) {
|
|
|
12007
12150
|
}
|
|
12008
12151
|
async function handleClean(_params) {
|
|
12009
12152
|
sessionMemory.recordToolCall("kuma_safety_clean", {});
|
|
12010
|
-
const
|
|
12011
|
-
const
|
|
12153
|
+
const fs26 = await import("fs");
|
|
12154
|
+
const path26 = await import("path");
|
|
12012
12155
|
const root = process.cwd();
|
|
12013
|
-
const scratchDir =
|
|
12156
|
+
const scratchDir = path26.resolve(root, ".kuma", "scratch");
|
|
12014
12157
|
let removed = 0;
|
|
12015
|
-
if (
|
|
12158
|
+
if (fs26.existsSync(scratchDir)) {
|
|
12016
12159
|
try {
|
|
12017
|
-
const entries =
|
|
12160
|
+
const entries = fs26.readdirSync(scratchDir);
|
|
12018
12161
|
for (const entry of entries) {
|
|
12019
|
-
const fullPath =
|
|
12162
|
+
const fullPath = path26.join(scratchDir, entry);
|
|
12020
12163
|
try {
|
|
12021
|
-
const stat =
|
|
12164
|
+
const stat = fs26.statSync(fullPath);
|
|
12022
12165
|
if (stat.isFile()) {
|
|
12023
|
-
|
|
12166
|
+
fs26.unlinkSync(fullPath);
|
|
12024
12167
|
removed++;
|
|
12025
12168
|
} else if (stat.isDirectory()) {
|
|
12026
|
-
|
|
12169
|
+
fs26.rmSync(fullPath, { recursive: true, force: true });
|
|
12027
12170
|
removed++;
|
|
12028
12171
|
}
|
|
12029
12172
|
} catch {
|
|
@@ -12125,13 +12268,30 @@ async function handleVerify(params) {
|
|
|
12125
12268
|
return `\u23F3 **Handler rate limit** \u2014 verify was just called ${Math.floor((now - _lastVerifyCall) / 1e3)}s ago. Please wait ${remaining}s before calling verify again.`;
|
|
12126
12269
|
}
|
|
12127
12270
|
_lastVerifyCall = now;
|
|
12271
|
+
const recordingSummary = sessionMemory.getRecordingSummary();
|
|
12272
|
+
const stats = sessionMemory.getSummary();
|
|
12273
|
+
const toolCallCount = stats.toolCallCount || 0;
|
|
12274
|
+
let recordingWarning = "";
|
|
12275
|
+
if (toolCallCount >= 5 && !recordingSummary.hasAnyRecordings) {
|
|
12276
|
+
recordingWarning = `
|
|
12277
|
+
|
|
12278
|
+
\u26A0\uFE0F **RECORDING MISSING:** You made ${toolCallCount} tool calls with 0 recordings. Before switching tasks, record what you learned:
|
|
12279
|
+
- kuma_memory({ action: 'research_save', scope: '<file>' })
|
|
12280
|
+
- kuma_memory({ action: 'gotcha', ... }) if you found bugs
|
|
12281
|
+
- kuma_memory({ action: 'arch_flow', ... }) if you traced a flow`;
|
|
12282
|
+
} else if (recordingSummary.total > 0) {
|
|
12283
|
+
recordingWarning = `
|
|
12284
|
+
|
|
12285
|
+
\u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
|
|
12286
|
+
}
|
|
12128
12287
|
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
|
|
12129
12288
|
const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
|
|
12130
|
-
|
|
12289
|
+
const verifyResult = await runAutoVerification2({
|
|
12131
12290
|
scope: params.scope || params.filePath,
|
|
12132
12291
|
force: params.force,
|
|
12133
12292
|
timeoutMs: 3e4
|
|
12134
12293
|
});
|
|
12294
|
+
return verifyResult + recordingWarning;
|
|
12135
12295
|
}
|
|
12136
12296
|
|
|
12137
12297
|
// src/manifest.ts
|
|
@@ -12169,8 +12329,8 @@ function registerAllTools(server) {
|
|
|
12169
12329
|
"kuma_memory",
|
|
12170
12330
|
"**Call after research/editing** (including research-only). Record what matters, skip what doesn't.\n\n\u{1F534} MUST RECORD \u2014 High Impact (saves agent time next session):\n\u2022 STEP 5: `research_save` \u2014 after exploring area (creates search cache).\n\u2022 STEP 6: `gotcha` \u2014 IMMEDIATELY when you discover bugs/quirks. No re-research.\n\u2022 STEP 7: `arch_flow` \u2014 IMMEDIATELY after EACH flow hop. Saves reading 5-10 files.\n\u2022 STEP 8: `decision` \u2014 IMMEDIATELY when choosing between options. Preserves rationale.\n\n\u{1F7E2} SKIP using MCP (agent native tools are faster):\n\u2022 Function/class nodes \u2192 grep\n\u2022 Component/route nodes \u2192 glob or check directly\n\u2022 Import edges \u2192 read imports directly\n\u2022 Visual graph \u2192 for humans, not AI agents\n\nOther actions: session, heal, search, changes, benchmark, layers.",
|
|
12171
12331
|
{
|
|
12172
|
-
action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log", "domain_rules", "arch_flow", "gotcha", "layers", "
|
|
12173
|
-
scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine
|
|
12332
|
+
action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log", "domain_rules", "arch_flow", "gotcha", "layers", "add_node", "delete_node", "clear", "feature"]).describe("Memory action: decision=ADR, mine=mine git log & comments, research_save=save (creates file + graph node), session=summary, heal=repair, search=search (now with task retrieval + impact analysis), changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions, domain_rules=Layer 1 business rules, arch_flow=Layer 2 architecture flow (now auto-creates affects edges), gotcha=Layer 3 known gotchas, layers=all 3 layers summary, add_node=manually create function/class/component structural nodes, delete_node=delete a specific node/gotcha/todo/decision by ID or scope+target, clear=wipe all nodes/edges/gotchas, feature=record high-level feature with owns edges to files"),
|
|
12333
|
+
scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine"),
|
|
12174
12334
|
query: z.string().optional().describe("Search query for search action"),
|
|
12175
12335
|
content: z.string().optional().describe("Content/notes for research_save / context"),
|
|
12176
12336
|
record: z.string().optional().describe("JSON record string for research_save"),
|
|
@@ -12186,7 +12346,7 @@ function registerAllTools(server) {
|
|
|
12186
12346
|
healAction: z.enum(["check", "heal"]).optional().describe("Heal sub-action"),
|
|
12187
12347
|
topic: z.string().optional().describe("Memory topic for session"),
|
|
12188
12348
|
limit: z.number().min(1).max(100).optional().describe("Result limit"),
|
|
12189
|
-
target: z.string().optional().describe("File path for changes / decision_log ID
|
|
12349
|
+
target: z.string().optional().describe("File path for changes / decision_log ID"),
|
|
12190
12350
|
since: z.number().optional().describe("Timestamp filter for changes"),
|
|
12191
12351
|
compact: z.boolean().optional().default(false).describe("Compact output mode"),
|
|
12192
12352
|
// Todo params
|
|
@@ -12223,8 +12383,7 @@ function registerAllTools(server) {
|
|
|
12223
12383
|
topic: params.topic,
|
|
12224
12384
|
limit: params.limit,
|
|
12225
12385
|
target: params.target,
|
|
12226
|
-
since: params.since
|
|
12227
|
-
uri: params.uri
|
|
12386
|
+
since: params.since
|
|
12228
12387
|
});
|
|
12229
12388
|
return { content: [{ type: "text", text }] };
|
|
12230
12389
|
} catch (err) {
|
|
@@ -12358,14 +12517,14 @@ async function main() {
|
|
|
12358
12517
|
} catch {
|
|
12359
12518
|
}
|
|
12360
12519
|
try {
|
|
12361
|
-
const
|
|
12362
|
-
const
|
|
12363
|
-
const lockDir =
|
|
12364
|
-
if (
|
|
12365
|
-
const pidFile =
|
|
12366
|
-
if (
|
|
12520
|
+
const path26 = await import("path");
|
|
12521
|
+
const fs26 = await import("fs");
|
|
12522
|
+
const lockDir = path26.resolve(process.cwd(), ".kuma/verifier.lock");
|
|
12523
|
+
if (fs26.existsSync(lockDir)) {
|
|
12524
|
+
const pidFile = path26.join(lockDir, "pid");
|
|
12525
|
+
if (fs26.existsSync(pidFile)) {
|
|
12367
12526
|
try {
|
|
12368
|
-
const pid = parseInt(
|
|
12527
|
+
const pid = parseInt(fs26.readFileSync(pidFile, "utf-8"), 10);
|
|
12369
12528
|
if (pid && pid !== process.pid) {
|
|
12370
12529
|
try {
|
|
12371
12530
|
process.kill(-pid, "SIGKILL");
|
|
@@ -12381,7 +12540,7 @@ async function main() {
|
|
|
12381
12540
|
} catch {
|
|
12382
12541
|
}
|
|
12383
12542
|
}
|
|
12384
|
-
|
|
12543
|
+
fs26.rmSync(lockDir, { recursive: true, force: true });
|
|
12385
12544
|
}
|
|
12386
12545
|
if (killedCount === 0) {
|
|
12387
12546
|
console.error("\u2705 No running verifications found to kill.");
|
|
@@ -12521,23 +12680,23 @@ async function main() {
|
|
|
12521
12680
|
});
|
|
12522
12681
|
const output = formatInitResults(results);
|
|
12523
12682
|
console.log(output);
|
|
12524
|
-
const
|
|
12525
|
-
const
|
|
12526
|
-
const matchaSkills =
|
|
12527
|
-
const matchaAgents =
|
|
12683
|
+
const fs26 = await import("fs");
|
|
12684
|
+
const path26 = await import("path");
|
|
12685
|
+
const matchaSkills = path26.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
12686
|
+
const matchaAgents = path26.resolve(
|
|
12528
12687
|
process.cwd(),
|
|
12529
12688
|
".agents/skills/matcha/SKILL.md"
|
|
12530
12689
|
);
|
|
12531
|
-
const matchaRootSkills =
|
|
12690
|
+
const matchaRootSkills = path26.resolve(
|
|
12532
12691
|
process.cwd(),
|
|
12533
12692
|
"skills/matcha/SKILL.md"
|
|
12534
12693
|
);
|
|
12535
|
-
const matchaAgentsMd =
|
|
12536
|
-
const matchaWindsurfRules =
|
|
12694
|
+
const matchaAgentsMd = path26.resolve(process.cwd(), "AGENTS.md");
|
|
12695
|
+
const matchaWindsurfRules = path26.resolve(
|
|
12537
12696
|
process.cwd(),
|
|
12538
12697
|
".windsurf"
|
|
12539
12698
|
);
|
|
12540
|
-
if (
|
|
12699
|
+
if (fs26.existsSync(matchaSkills) || fs26.existsSync(matchaAgents) || fs26.existsSync(matchaRootSkills) || fs26.existsSync(matchaAgentsMd) || fs26.existsSync(matchaWindsurfRules)) {
|
|
12541
12700
|
console.error(
|
|
12542
12701
|
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
12543
12702
|
);
|
|
@@ -12551,13 +12710,13 @@ async function main() {
|
|
|
12551
12710
|
(async () => {
|
|
12552
12711
|
try {
|
|
12553
12712
|
const { generateInitMdContent: generateInitMdContent2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
12554
|
-
const
|
|
12555
|
-
const
|
|
12556
|
-
const initMdPath =
|
|
12557
|
-
if (!
|
|
12558
|
-
const kumaDir =
|
|
12559
|
-
if (!
|
|
12560
|
-
|
|
12713
|
+
const fs26 = await import("fs");
|
|
12714
|
+
const path26 = await import("path");
|
|
12715
|
+
const initMdPath = path26.resolve(process.cwd(), ".kuma/init.md");
|
|
12716
|
+
if (!fs26.existsSync(initMdPath)) {
|
|
12717
|
+
const kumaDir = path26.dirname(initMdPath);
|
|
12718
|
+
if (!fs26.existsSync(kumaDir)) fs26.mkdirSync(kumaDir, { recursive: true });
|
|
12719
|
+
fs26.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
|
|
12561
12720
|
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
12562
12721
|
}
|
|
12563
12722
|
} catch (err) {
|
|
@@ -12568,8 +12727,8 @@ async function main() {
|
|
|
12568
12727
|
try {
|
|
12569
12728
|
const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
|
|
12570
12729
|
const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
|
|
12571
|
-
const
|
|
12572
|
-
const
|
|
12730
|
+
const fs26 = await import("fs");
|
|
12731
|
+
const path26 = await import("path");
|
|
12573
12732
|
const detection = detectAgent2();
|
|
12574
12733
|
if (!detection.primary) {
|
|
12575
12734
|
console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
|
|
@@ -12577,22 +12736,22 @@ async function main() {
|
|
|
12577
12736
|
}
|
|
12578
12737
|
const agentType = detection.primary;
|
|
12579
12738
|
const skillPath = getSkillPath2(agentType);
|
|
12580
|
-
const fullPath =
|
|
12581
|
-
if (
|
|
12739
|
+
const fullPath = path26.resolve(process.cwd(), skillPath);
|
|
12740
|
+
if (fs26.existsSync(fullPath)) {
|
|
12582
12741
|
console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
|
|
12583
12742
|
return;
|
|
12584
12743
|
}
|
|
12585
|
-
const dir =
|
|
12586
|
-
if (!
|
|
12587
|
-
|
|
12744
|
+
const dir = path26.dirname(fullPath);
|
|
12745
|
+
if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
|
|
12746
|
+
fs26.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
|
|
12588
12747
|
console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
|
|
12589
12748
|
const secondaryFiles = getSecondaryFiles2(agentType);
|
|
12590
12749
|
for (const sf of secondaryFiles) {
|
|
12591
|
-
const sfPath =
|
|
12592
|
-
const sfDir =
|
|
12593
|
-
if (!
|
|
12594
|
-
if (!
|
|
12595
|
-
|
|
12750
|
+
const sfPath = path26.resolve(process.cwd(), sf.path);
|
|
12751
|
+
const sfDir = path26.dirname(sfPath);
|
|
12752
|
+
if (!fs26.existsSync(sfPath)) {
|
|
12753
|
+
if (!fs26.existsSync(sfDir)) fs26.mkdirSync(sfDir, { recursive: true });
|
|
12754
|
+
fs26.writeFileSync(sfPath, sf.content, "utf-8");
|
|
12596
12755
|
console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
|
|
12597
12756
|
}
|
|
12598
12757
|
}
|
|
@@ -12617,11 +12776,11 @@ async function main() {
|
|
|
12617
12776
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
|
|
12618
12777
|
}
|
|
12619
12778
|
try {
|
|
12620
|
-
const
|
|
12621
|
-
const
|
|
12622
|
-
const scratchDir =
|
|
12623
|
-
if (!
|
|
12624
|
-
|
|
12779
|
+
const fs26 = await import("fs");
|
|
12780
|
+
const path26 = await import("path");
|
|
12781
|
+
const scratchDir = path26.resolve(process.cwd(), ".kuma", "scratch");
|
|
12782
|
+
if (!fs26.existsSync(scratchDir)) {
|
|
12783
|
+
fs26.mkdirSync(scratchDir, { recursive: true });
|
|
12625
12784
|
console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
|
|
12626
12785
|
}
|
|
12627
12786
|
} catch (err) {
|
|
@@ -12639,11 +12798,11 @@ async function main() {
|
|
|
12639
12798
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Session DB record: ${err}`);
|
|
12640
12799
|
}
|
|
12641
12800
|
try {
|
|
12642
|
-
const
|
|
12643
|
-
const
|
|
12644
|
-
const hooksDir =
|
|
12645
|
-
if (!
|
|
12646
|
-
|
|
12801
|
+
const fs26 = await import("fs");
|
|
12802
|
+
const path26 = await import("path");
|
|
12803
|
+
const hooksDir = path26.resolve(process.cwd(), ".kuma", "hooks");
|
|
12804
|
+
if (!fs26.existsSync(hooksDir)) {
|
|
12805
|
+
fs26.mkdirSync(hooksDir, { recursive: true });
|
|
12647
12806
|
const hookContent = `#!/bin/bash
|
|
12648
12807
|
# Kuma auto-capture hook (post-commit)
|
|
12649
12808
|
# Auto-updates knowledge graph after git commits
|
|
@@ -12651,13 +12810,13 @@ if command -v npx &> /dev/null; then
|
|
|
12651
12810
|
npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
|
|
12652
12811
|
fi
|
|
12653
12812
|
`;
|
|
12654
|
-
const gitHooksDir =
|
|
12655
|
-
if (
|
|
12656
|
-
const postCommitPath =
|
|
12657
|
-
if (!
|
|
12658
|
-
|
|
12813
|
+
const gitHooksDir = path26.resolve(process.cwd(), ".git", "hooks");
|
|
12814
|
+
if (fs26.existsSync(gitHooksDir)) {
|
|
12815
|
+
const postCommitPath = path26.join(gitHooksDir, "post-commit");
|
|
12816
|
+
if (!fs26.existsSync(postCommitPath)) {
|
|
12817
|
+
fs26.writeFileSync(postCommitPath, hookContent, "utf-8");
|
|
12659
12818
|
try {
|
|
12660
|
-
|
|
12819
|
+
fs26.chmodSync(postCommitPath, 493);
|
|
12661
12820
|
} catch {
|
|
12662
12821
|
}
|
|
12663
12822
|
console.error(`[${SERVER_NAME}] \u2705 Created .git/hooks/post-commit for auto-capture`);
|