@plumpslabs/kuma 2.4.6 → 2.4.8
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 +654 -184
- package/hooks/kuma-agy-hooks.js +28 -3
- package/package.json +2 -2
- package/plugin.json +1 -1
package/dist/index.js
CHANGED
|
@@ -258,9 +258,23 @@ function generateNodeId(type, name) {
|
|
|
258
258
|
function resetDbInstance() {
|
|
259
259
|
dbInstance = null;
|
|
260
260
|
initPromise = null;
|
|
261
|
+
lastLoadedMtime = 0;
|
|
261
262
|
}
|
|
262
263
|
async function getDb() {
|
|
263
|
-
if (dbInstance)
|
|
264
|
+
if (dbInstance) {
|
|
265
|
+
try {
|
|
266
|
+
const kumaDir = getKumaDir();
|
|
267
|
+
const dbPath = path2.join(kumaDir, DB_FILENAME);
|
|
268
|
+
if (fs2.existsSync(dbPath)) {
|
|
269
|
+
const stat = fs2.statSync(dbPath);
|
|
270
|
+
if (stat.mtimeMs > lastLoadedMtime + 50) {
|
|
271
|
+
resetDbInstance();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
if (dbInstance) return dbInstance;
|
|
277
|
+
}
|
|
264
278
|
if (initPromise) return initPromise;
|
|
265
279
|
initPromise = initDb();
|
|
266
280
|
return initPromise;
|
|
@@ -274,6 +288,10 @@ async function initDb() {
|
|
|
274
288
|
}
|
|
275
289
|
let db;
|
|
276
290
|
if (fs2.existsSync(dbPath)) {
|
|
291
|
+
try {
|
|
292
|
+
lastLoadedMtime = fs2.statSync(dbPath).mtimeMs;
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
277
295
|
const buffer = fs2.readFileSync(dbPath);
|
|
278
296
|
db = new SQL.Database(buffer);
|
|
279
297
|
} else {
|
|
@@ -309,8 +327,16 @@ function flushDb(db) {
|
|
|
309
327
|
fs2.writeFileSync(tmpPath, buffer);
|
|
310
328
|
try {
|
|
311
329
|
fs2.renameSync(tmpPath, dbPath);
|
|
330
|
+
try {
|
|
331
|
+
lastLoadedMtime = fs2.statSync(dbPath).mtimeMs;
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
312
334
|
} catch {
|
|
313
335
|
fs2.writeFileSync(dbPath, buffer);
|
|
336
|
+
try {
|
|
337
|
+
lastLoadedMtime = fs2.statSync(dbPath).mtimeMs;
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
314
340
|
try {
|
|
315
341
|
if (fs2.existsSync(tmpPath)) fs2.unlinkSync(tmpPath);
|
|
316
342
|
} catch {
|
|
@@ -1146,8 +1172,8 @@ async function runDoctor() {
|
|
|
1146
1172
|
} catch {
|
|
1147
1173
|
}
|
|
1148
1174
|
try {
|
|
1149
|
-
const { execSync:
|
|
1150
|
-
const psOutput =
|
|
1175
|
+
const { execSync: execSync7 } = await import("child_process");
|
|
1176
|
+
const psOutput = execSync7(
|
|
1151
1177
|
`ps -eo pid,ppid,command | grep -E "(jest|pnpm test|npm test|yarn test)" | grep -v grep | head -10`,
|
|
1152
1178
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
1153
1179
|
).trim();
|
|
@@ -1265,7 +1291,7 @@ function rebuildFtsIndex() {
|
|
|
1265
1291
|
} catch {
|
|
1266
1292
|
}
|
|
1267
1293
|
}
|
|
1268
|
-
var DB_FILENAME, dbInstance, initPromise, _saveTimeout, _pendingDb, SAVE_DEBOUNCE_MS;
|
|
1294
|
+
var DB_FILENAME, dbInstance, initPromise, lastLoadedMtime, _saveTimeout, _pendingDb, SAVE_DEBOUNCE_MS;
|
|
1269
1295
|
var init_kumaDb = __esm({
|
|
1270
1296
|
"src/engine/kumaDb.ts"() {
|
|
1271
1297
|
"use strict";
|
|
@@ -1273,6 +1299,7 @@ var init_kumaDb = __esm({
|
|
|
1273
1299
|
DB_FILENAME = "kuma.db";
|
|
1274
1300
|
dbInstance = null;
|
|
1275
1301
|
initPromise = null;
|
|
1302
|
+
lastLoadedMtime = 0;
|
|
1276
1303
|
_saveTimeout = null;
|
|
1277
1304
|
_pendingDb = null;
|
|
1278
1305
|
SAVE_DEBOUNCE_MS = 100;
|
|
@@ -1329,25 +1356,31 @@ var init_sessionMemory = __esm({
|
|
|
1329
1356
|
const parsed = JSON.parse(raw);
|
|
1330
1357
|
const storedBranch = parsed.currentBranch;
|
|
1331
1358
|
const branchSwitched = Boolean(storedBranch && currentGitBranch && storedBranch !== currentGitBranch);
|
|
1359
|
+
const lastActive = parsed.lastActiveAt || parsed.startTime || 0;
|
|
1360
|
+
const isIdle = lastActive > 0 && Date.now() - lastActive > 2 * 3600 * 1e3;
|
|
1361
|
+
const startTime = isIdle ? Date.now() : parsed.startTime || config.startTime;
|
|
1362
|
+
const metrics = isIdle ? { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 } : parsed.metrics || { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 };
|
|
1363
|
+
const toolCalls = isIdle ? [] : parsed.toolCalls || [];
|
|
1332
1364
|
this.state = {
|
|
1333
1365
|
projectRoot: parsed.projectRoot || config.projectRoot,
|
|
1334
|
-
startTime
|
|
1366
|
+
startTime,
|
|
1367
|
+
lastActiveAt: Date.now(),
|
|
1335
1368
|
currentGoal: parsed.currentGoal || "",
|
|
1336
1369
|
currentBranch: currentGitBranch || storedBranch || void 0,
|
|
1337
1370
|
previousBranch: branchSwitched ? storedBranch : parsed.previousBranch,
|
|
1338
1371
|
branchSwitched,
|
|
1339
|
-
completedSteps: parsed.completedSteps || [],
|
|
1372
|
+
completedSteps: isIdle ? [] : parsed.completedSteps || [],
|
|
1340
1373
|
modifiedFiles: new Map(parsed.modifiedFiles || []),
|
|
1341
1374
|
failedFiles: new Map(parsed.failedFiles || []),
|
|
1342
1375
|
searchResults: new Map(parsed.searchResults || []),
|
|
1343
1376
|
dependencyGraph: new Map(parsed.dependencyGraph || []),
|
|
1344
|
-
toolCalls
|
|
1377
|
+
toolCalls,
|
|
1345
1378
|
conventions: parsed.conventions,
|
|
1346
1379
|
recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, features: 0, total: 0 },
|
|
1347
|
-
metrics
|
|
1380
|
+
metrics
|
|
1348
1381
|
};
|
|
1349
1382
|
this.initialized = true;
|
|
1350
|
-
console.error(`[SessionMemory] Loaded persistent session memory.json`);
|
|
1383
|
+
console.error(`[SessionMemory] Loaded persistent session memory.json${isIdle ? " (idle reset applied)" : ""}`);
|
|
1351
1384
|
return;
|
|
1352
1385
|
} catch (err) {
|
|
1353
1386
|
console.error(`[SessionMemory] Failed to load persistent session: ${err}. Re-initializing.`);
|
|
@@ -1508,6 +1541,7 @@ No unresolved issues.`;
|
|
|
1508
1541
|
const serialized = {
|
|
1509
1542
|
projectRoot: this.state.projectRoot,
|
|
1510
1543
|
startTime: this.state.startTime,
|
|
1544
|
+
lastActiveAt: this.state.lastActiveAt || Date.now(),
|
|
1511
1545
|
currentGoal: this.state.currentGoal,
|
|
1512
1546
|
currentBranch: this.state.currentBranch,
|
|
1513
1547
|
previousBranch: this.state.previousBranch,
|
|
@@ -1908,6 +1942,32 @@ No unresolved issues.`;
|
|
|
1908
1942
|
}
|
|
1909
1943
|
getModifiedFiles() {
|
|
1910
1944
|
this.ensureInit();
|
|
1945
|
+
try {
|
|
1946
|
+
const root = this.state.projectRoot;
|
|
1947
|
+
const gitOut = execSync("git status --porcelain", {
|
|
1948
|
+
cwd: root,
|
|
1949
|
+
encoding: "utf-8",
|
|
1950
|
+
timeout: 3e3,
|
|
1951
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1952
|
+
}).trim();
|
|
1953
|
+
if (gitOut) {
|
|
1954
|
+
for (const line of gitOut.split("\n")) {
|
|
1955
|
+
const trimmed = line.trim();
|
|
1956
|
+
if (!trimmed) continue;
|
|
1957
|
+
const rawPath = trimmed.slice(2).trim();
|
|
1958
|
+
const cleanPath = rawPath.replace(/^["']|["']$/g, "").split(" -> ").pop();
|
|
1959
|
+
if (cleanPath.startsWith(".kuma/") || cleanPath.startsWith(".git/")) continue;
|
|
1960
|
+
if (!this.state.modifiedFiles.has(cleanPath)) {
|
|
1961
|
+
this.state.modifiedFiles.set(cleanPath, {
|
|
1962
|
+
filePath: cleanPath,
|
|
1963
|
+
modifiedAt: Date.now(),
|
|
1964
|
+
status: trimmed.startsWith("??") || trimmed.startsWith("A") ? "created" : "modified"
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
} catch {
|
|
1970
|
+
}
|
|
1911
1971
|
return Array.from(this.state.modifiedFiles.values());
|
|
1912
1972
|
}
|
|
1913
1973
|
getFailedFiles() {
|
|
@@ -2130,6 +2190,14 @@ No unresolved issues.`;
|
|
|
2130
2190
|
projectRoot: process.cwd(),
|
|
2131
2191
|
startTime: Date.now()
|
|
2132
2192
|
});
|
|
2193
|
+
} else if (this.state) {
|
|
2194
|
+
const lastActive = this.state.lastActiveAt || this.state.startTime || 0;
|
|
2195
|
+
if (lastActive > 0 && Date.now() - lastActive > 2 * 3600 * 1e3) {
|
|
2196
|
+
this.state.startTime = Date.now();
|
|
2197
|
+
this.state.metrics = { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 };
|
|
2198
|
+
this.state.toolCalls = [];
|
|
2199
|
+
}
|
|
2200
|
+
this.state.lastActiveAt = Date.now();
|
|
2133
2201
|
}
|
|
2134
2202
|
}
|
|
2135
2203
|
};
|
|
@@ -2515,6 +2583,8 @@ var init_languageSupport = __esm({
|
|
|
2515
2583
|
// src/engine/kumaDriftDetector.ts
|
|
2516
2584
|
var kumaDriftDetector_exports = {};
|
|
2517
2585
|
__export(kumaDriftDetector_exports, {
|
|
2586
|
+
computeProjectHash: () => computeProjectHash,
|
|
2587
|
+
computeScopeHash: () => computeScopeHash,
|
|
2518
2588
|
detectDrift: () => detectDrift,
|
|
2519
2589
|
flagStaleRecords: () => flagStaleRecords,
|
|
2520
2590
|
formatDriftReport: () => formatDriftReport,
|
|
@@ -2524,6 +2594,7 @@ __export(kumaDriftDetector_exports, {
|
|
|
2524
2594
|
import fs5 from "fs";
|
|
2525
2595
|
import path6 from "path";
|
|
2526
2596
|
import crypto2 from "crypto";
|
|
2597
|
+
import { execSync as execSync3 } from "child_process";
|
|
2527
2598
|
function hashFile(filePath) {
|
|
2528
2599
|
try {
|
|
2529
2600
|
const root = getProjectRoot();
|
|
@@ -2603,34 +2674,70 @@ async function detectDrift() {
|
|
|
2603
2674
|
}
|
|
2604
2675
|
function computeScopeHash(scope) {
|
|
2605
2676
|
try {
|
|
2606
|
-
const hash = crypto2.createHash("sha256");
|
|
2607
2677
|
const root = getProjectRoot();
|
|
2678
|
+
let gitHead = "";
|
|
2679
|
+
let gitStatus = "";
|
|
2680
|
+
try {
|
|
2681
|
+
gitHead = execSync3("git rev-parse HEAD", {
|
|
2682
|
+
cwd: root,
|
|
2683
|
+
encoding: "utf-8",
|
|
2684
|
+
timeout: 1500,
|
|
2685
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2686
|
+
}).trim();
|
|
2687
|
+
gitStatus = execSync3("git status --porcelain", {
|
|
2688
|
+
cwd: root,
|
|
2689
|
+
encoding: "utf-8",
|
|
2690
|
+
timeout: 2e3,
|
|
2691
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2692
|
+
}).trim();
|
|
2693
|
+
} catch {
|
|
2694
|
+
}
|
|
2695
|
+
const hash = crypto2.createHash("sha256");
|
|
2608
2696
|
hash.update(scope);
|
|
2609
2697
|
let found = false;
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2698
|
+
if (gitHead || gitStatus) {
|
|
2699
|
+
hash.update(gitHead);
|
|
2700
|
+
hash.update(gitStatus);
|
|
2701
|
+
found = true;
|
|
2702
|
+
} else {
|
|
2703
|
+
const checkDirs = [
|
|
2704
|
+
root,
|
|
2705
|
+
path6.join(root, "src"),
|
|
2706
|
+
path6.join(root, "lib"),
|
|
2707
|
+
path6.join(root, "backend"),
|
|
2708
|
+
path6.join(root, "packages"),
|
|
2709
|
+
path6.join(root, "app")
|
|
2710
|
+
];
|
|
2711
|
+
for (const dir of checkDirs) {
|
|
2712
|
+
if (fs5.existsSync(dir)) {
|
|
2614
2713
|
try {
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2714
|
+
const files = fs5.readdirSync(dir).slice(0, 30);
|
|
2715
|
+
for (const f of files) {
|
|
2716
|
+
try {
|
|
2717
|
+
const fullPath = path6.join(dir, f);
|
|
2718
|
+
const stat = fs5.statSync(fullPath);
|
|
2719
|
+
if (stat.isFile()) {
|
|
2720
|
+
hash.update(`${f}:${stat.mtimeMs}`);
|
|
2721
|
+
found = true;
|
|
2722
|
+
}
|
|
2723
|
+
} catch {
|
|
2724
|
+
}
|
|
2620
2725
|
}
|
|
2621
2726
|
} catch {
|
|
2622
2727
|
}
|
|
2623
2728
|
}
|
|
2624
2729
|
}
|
|
2625
|
-
} catch {
|
|
2626
2730
|
}
|
|
2627
2731
|
const researchDir = path6.join(root, ".kuma", "research");
|
|
2628
2732
|
if (fs5.existsSync(researchDir)) {
|
|
2629
2733
|
const scopeFile = path6.join(researchDir, `${scope}.json`);
|
|
2630
2734
|
if (fs5.existsSync(scopeFile)) {
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2735
|
+
try {
|
|
2736
|
+
const content = fs5.readFileSync(scopeFile, "utf-8");
|
|
2737
|
+
hash.update(content);
|
|
2738
|
+
found = true;
|
|
2739
|
+
} catch {
|
|
2740
|
+
}
|
|
2634
2741
|
}
|
|
2635
2742
|
}
|
|
2636
2743
|
if (!found) return null;
|
|
@@ -2703,11 +2810,13 @@ async function getDriftSummary() {
|
|
|
2703
2810
|
if (staleCount === 0) return "\u2705 All records fresh";
|
|
2704
2811
|
return `\u26A0\uFE0F ${staleCount} stale record(s) detected. Use kuma_memory({ action: 'heal' }) to repair.`;
|
|
2705
2812
|
}
|
|
2813
|
+
var computeProjectHash;
|
|
2706
2814
|
var init_kumaDriftDetector = __esm({
|
|
2707
2815
|
"src/engine/kumaDriftDetector.ts"() {
|
|
2708
2816
|
"use strict";
|
|
2709
2817
|
init_kumaDb();
|
|
2710
2818
|
init_pathValidator();
|
|
2819
|
+
computeProjectHash = (scope) => computeScopeHash(scope) || Date.now().toString(16);
|
|
2711
2820
|
}
|
|
2712
2821
|
});
|
|
2713
2822
|
|
|
@@ -3481,7 +3590,8 @@ async function recordDomainFlow(params) {
|
|
|
3481
3590
|
type: "feature_domain",
|
|
3482
3591
|
name: params.domain,
|
|
3483
3592
|
metadata: {
|
|
3484
|
-
hops: params.hops
|
|
3593
|
+
hops: params.hops,
|
|
3594
|
+
hopsCount: params.hops.length,
|
|
3485
3595
|
gotchas: params.gotchas?.length || 0,
|
|
3486
3596
|
decisions: params.decisions?.length || 0,
|
|
3487
3597
|
filePaths: params.filePaths || [],
|
|
@@ -4300,9 +4410,9 @@ async function analyzeImpact(target) {
|
|
|
4300
4410
|
}
|
|
4301
4411
|
async function codebaseImpactFallback(target) {
|
|
4302
4412
|
try {
|
|
4303
|
-
const { execSync:
|
|
4413
|
+
const { execSync: execSync7 } = await import("child_process");
|
|
4304
4414
|
const root = process.cwd();
|
|
4305
|
-
const grepResult =
|
|
4415
|
+
const grepResult = execSync7(
|
|
4306
4416
|
`grep -rn ${grepIncludeFlags()} --include="*.json" --include="*.md" -l "${target.replace(/"/g, '\\"')}" . 2>/dev/null | grep -v node_modules | grep -v .git | grep -v dist | grep -v .kuma | head -50`,
|
|
4307
4417
|
{ cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
|
|
4308
4418
|
).trim();
|
|
@@ -4429,7 +4539,7 @@ var init_kumaGraph = __esm({
|
|
|
4429
4539
|
});
|
|
4430
4540
|
|
|
4431
4541
|
// src/utils/kumaShared.ts
|
|
4432
|
-
import { execSync as
|
|
4542
|
+
import { execSync as execSync4 } from "child_process";
|
|
4433
4543
|
function isEditTool(toolName) {
|
|
4434
4544
|
const n = (toolName || "").toLowerCase();
|
|
4435
4545
|
return n.includes("edit") || n.includes("write") || n.includes("patch") || n.includes("apply") || n.includes("replace");
|
|
@@ -4467,7 +4577,7 @@ function getSessionStats(inputGoal) {
|
|
|
4467
4577
|
function getGitDiffStat(timeout = 3e3) {
|
|
4468
4578
|
try {
|
|
4469
4579
|
const root = getProjectRoot();
|
|
4470
|
-
return
|
|
4580
|
+
return execSync4("git diff --stat", {
|
|
4471
4581
|
cwd: root,
|
|
4472
4582
|
encoding: "utf-8",
|
|
4473
4583
|
timeout
|
|
@@ -4554,7 +4664,7 @@ function scoreMemoryRelevance(context, limit = 5) {
|
|
|
4554
4664
|
const results = [];
|
|
4555
4665
|
const kumaDir = path9.join(getProjectRoot(), MEMORY_DIR);
|
|
4556
4666
|
if (!fs8.existsSync(kumaDir)) return [];
|
|
4557
|
-
const terms = context.toLowerCase().split(
|
|
4667
|
+
const terms = Array.from(new Set(context.toLowerCase().split(/[\s/._-]+/).filter((w) => w.length > 2)));
|
|
4558
4668
|
if (terms.length === 0) return [];
|
|
4559
4669
|
try {
|
|
4560
4670
|
const files = fs8.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
@@ -4781,21 +4891,21 @@ function getActiveGotchas() {
|
|
|
4781
4891
|
let currentSeverity = "";
|
|
4782
4892
|
let currentStatus = "active";
|
|
4783
4893
|
for (const line of lines) {
|
|
4784
|
-
const fileMatch = line.match(/^###\s
|
|
4894
|
+
const fileMatch = line.match(/^###\s+\[?(.+?)\]?\s*(?:[—–-]+\s*(.+))?$/);
|
|
4785
4895
|
if (fileMatch) {
|
|
4786
4896
|
if (currentFilePath && currentStatus !== "resolved" && currentStatus !== "deprecated") {
|
|
4787
4897
|
results.push({ filePath: currentFilePath, description: currentDesc, severity: currentSeverity, content: currentSection });
|
|
4788
4898
|
}
|
|
4789
4899
|
currentFilePath = fileMatch[1].trim();
|
|
4790
|
-
currentDesc = fileMatch[2].trim();
|
|
4900
|
+
currentDesc = (fileMatch[2] || fileMatch[1]).trim();
|
|
4791
4901
|
currentSeverity = "medium";
|
|
4792
4902
|
currentStatus = "active";
|
|
4793
4903
|
currentSection = line + "\n";
|
|
4794
4904
|
continue;
|
|
4795
4905
|
}
|
|
4796
|
-
const sevMatch = line.match(
|
|
4906
|
+
const sevMatch = line.match(/[-*]\s*(?:⚡\s*)?\*\*Severity\*\*:\s*(\w+)/i);
|
|
4797
4907
|
if (sevMatch) currentSeverity = sevMatch[1].toLowerCase();
|
|
4798
|
-
const statusMatch = line.match(
|
|
4908
|
+
const statusMatch = line.match(/[-*]\s*(?:🏷️\s*)?\*\*Status\*\*:\s*(\w+)/i);
|
|
4799
4909
|
if (statusMatch) currentStatus = statusMatch[1].toLowerCase();
|
|
4800
4910
|
currentSection += line + "\n";
|
|
4801
4911
|
}
|
|
@@ -4809,8 +4919,10 @@ function getActiveGotchas() {
|
|
|
4809
4919
|
function checkFileGotchas(filePath) {
|
|
4810
4920
|
const gotchas = getActiveGotchas();
|
|
4811
4921
|
const warnings = [];
|
|
4922
|
+
const base = path10.basename(filePath);
|
|
4812
4923
|
for (const g of gotchas) {
|
|
4813
|
-
|
|
4924
|
+
const gBase = path10.basename(g.filePath);
|
|
4925
|
+
if (filePath.includes(g.filePath) || g.filePath.includes(filePath) || base && gBase && base === gBase) {
|
|
4814
4926
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
4815
4927
|
warnings.push(`${icon} **Gotcha**: ${g.description} (${g.severity})`);
|
|
4816
4928
|
}
|
|
@@ -4919,8 +5031,9 @@ function isGotchaFresh(storedHash, currentHash) {
|
|
|
4919
5031
|
if (!currentHash) return true;
|
|
4920
5032
|
return storedHash === currentHash;
|
|
4921
5033
|
}
|
|
4922
|
-
async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
5034
|
+
async function getFreshGotchasForFile(filePath, limit = 5, includeStale = false) {
|
|
4923
5035
|
const root = getProjectRoot();
|
|
5036
|
+
const base = path11.basename(filePath);
|
|
4924
5037
|
try {
|
|
4925
5038
|
const dbFile = path11.join(root, ".kuma", "kuma.db");
|
|
4926
5039
|
if (fs10.existsSync(dbFile)) {
|
|
@@ -4930,44 +5043,58 @@ async function getFreshGotchasForFile(filePath, limit = 5) {
|
|
|
4930
5043
|
SELECT id, file_path, description, severity, workaround, content_hash
|
|
4931
5044
|
FROM known_gotchas
|
|
4932
5045
|
WHERE status IN ('active', 'verified')
|
|
4933
|
-
AND (
|
|
5046
|
+
AND (
|
|
5047
|
+
file_path = ?
|
|
5048
|
+
OR file_path LIKE ?
|
|
5049
|
+
OR ? LIKE ('%' || file_path || '%')
|
|
5050
|
+
OR file_path LIKE ?
|
|
5051
|
+
)
|
|
4934
5052
|
ORDER BY
|
|
4935
|
-
CASE WHEN file_path = ? THEN 0
|
|
5053
|
+
CASE WHEN file_path = ? THEN 0 WHEN file_path LIKE ? THEN 1 ELSE 2 END,
|
|
4936
5054
|
CASE severity WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
|
|
4937
5055
|
updated_at DESC
|
|
4938
5056
|
LIMIT ?
|
|
4939
5057
|
`);
|
|
4940
|
-
stmt.bind([filePath, filePath, filePath, limit]);
|
|
5058
|
+
stmt.bind([filePath, `%${filePath}%`, filePath, `%${base}%`, filePath, `%${filePath}%`, limit * 2]);
|
|
4941
5059
|
const rows = [];
|
|
4942
5060
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
4943
5061
|
stmt.free();
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
5062
|
+
if (rows.length > 0) {
|
|
5063
|
+
const currentHash = hashFileContent(filePath);
|
|
5064
|
+
const mapped = rows.map((r) => ({
|
|
5065
|
+
id: r.id,
|
|
5066
|
+
filePath: r.file_path,
|
|
5067
|
+
description: r.description,
|
|
5068
|
+
severity: r.severity,
|
|
5069
|
+
workaround: r.workaround ?? null,
|
|
5070
|
+
stale: !isGotchaFresh(r.content_hash ?? null, currentHash)
|
|
5071
|
+
}));
|
|
5072
|
+
const filtered = includeStale ? mapped : mapped.filter((g) => !g.stale);
|
|
5073
|
+
return filtered.slice(0, limit);
|
|
5074
|
+
}
|
|
4953
5075
|
}
|
|
4954
5076
|
} catch {
|
|
4955
5077
|
}
|
|
4956
5078
|
try {
|
|
4957
5079
|
if (!fs10.existsSync(path11.join(root, ".kuma"))) return [];
|
|
4958
|
-
const {
|
|
4959
|
-
const
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
5080
|
+
const { getActiveGotchas: getActiveGotchas2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
|
|
5081
|
+
const allGotchas = getActiveGotchas2();
|
|
5082
|
+
const matched = allGotchas.filter(
|
|
5083
|
+
(g) => g.filePath.includes(filePath) || filePath.includes(g.filePath) || base && path11.basename(g.filePath) === base
|
|
5084
|
+
);
|
|
5085
|
+
if (matched.length > 0) {
|
|
5086
|
+
return matched.slice(0, limit).map((g) => ({
|
|
5087
|
+
filePath: g.filePath,
|
|
5088
|
+
description: g.description,
|
|
5089
|
+
severity: g.severity,
|
|
5090
|
+
workaround: null,
|
|
5091
|
+
stale: false
|
|
5092
|
+
}));
|
|
5093
|
+
}
|
|
4968
5094
|
} catch {
|
|
4969
5095
|
return [];
|
|
4970
5096
|
}
|
|
5097
|
+
return [];
|
|
4971
5098
|
}
|
|
4972
5099
|
async function getFileTrace(filePath, limit = 8) {
|
|
4973
5100
|
try {
|
|
@@ -5015,14 +5142,81 @@ function formatFileTrace(entries, filePath) {
|
|
|
5015
5142
|
return lines.join("\n");
|
|
5016
5143
|
}
|
|
5017
5144
|
async function getDecisionsForFile(filePath, limit = 3) {
|
|
5145
|
+
const decisions = [];
|
|
5146
|
+
const root = getProjectRoot();
|
|
5147
|
+
const base = path11.basename(filePath);
|
|
5148
|
+
const baseWithoutExt = base.replace(/\.[^.]+$/, "");
|
|
5149
|
+
try {
|
|
5150
|
+
const decisionsMdPath = path11.join(root, ".kuma", "memories", "decisions.md");
|
|
5151
|
+
if (fs10.existsSync(decisionsMdPath)) {
|
|
5152
|
+
const content = fs10.readFileSync(decisionsMdPath, "utf-8");
|
|
5153
|
+
const sections = content.split(/^##\s+/m);
|
|
5154
|
+
for (const section of sections) {
|
|
5155
|
+
if (!section.trim() || section.startsWith("# ")) continue;
|
|
5156
|
+
const sectionLines = section.split("\n");
|
|
5157
|
+
const title = sectionLines[0].trim();
|
|
5158
|
+
const sectionText = section.toLowerCase();
|
|
5159
|
+
const matches = sectionText.includes(filePath.toLowerCase()) || sectionText.includes(base.toLowerCase()) || baseWithoutExt.length > 3 && sectionText.includes(baseWithoutExt.toLowerCase());
|
|
5160
|
+
if (matches) {
|
|
5161
|
+
let rationale = "";
|
|
5162
|
+
for (const line of sectionLines) {
|
|
5163
|
+
const rMatch = line.match(/^[-*]\s*\*\*Rationale:\*\*\s*(.+)$/i);
|
|
5164
|
+
if (rMatch) {
|
|
5165
|
+
rationale = rMatch[1].trim();
|
|
5166
|
+
break;
|
|
5167
|
+
}
|
|
5168
|
+
const oMatch = line.match(/^[-*]\s*\*\*Outcome:\*\*\s*(.+)$/i);
|
|
5169
|
+
if (!rationale && oMatch) {
|
|
5170
|
+
rationale = oMatch[1].trim();
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
decisions.push(`\u{1F4CC} ${title}${rationale ? ` \u2014 *${rationale}*` : ""}`);
|
|
5174
|
+
if (decisions.length >= limit) return decisions;
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
} catch {
|
|
5179
|
+
}
|
|
5180
|
+
try {
|
|
5181
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
5182
|
+
const db = await getDb2();
|
|
5183
|
+
const stmt = db.prepare(`
|
|
5184
|
+
SELECT name, metadata FROM nodes
|
|
5185
|
+
WHERE type = 'decision'
|
|
5186
|
+
AND (name LIKE ? OR metadata LIKE ? OR name LIKE ? OR metadata LIKE ?)
|
|
5187
|
+
LIMIT ?
|
|
5188
|
+
`);
|
|
5189
|
+
stmt.bind([`%${base}%`, `%${base}%`, `%${baseWithoutExt}%`, `%${baseWithoutExt}%`, limit]);
|
|
5190
|
+
while (stmt.step()) {
|
|
5191
|
+
const row = stmt.getAsObject();
|
|
5192
|
+
try {
|
|
5193
|
+
const meta = JSON.parse(row.metadata || "{}");
|
|
5194
|
+
const title = row.name.replace(/^ADR:\s*/, "");
|
|
5195
|
+
const reason = meta.rationale || meta.context || meta.outcome || "";
|
|
5196
|
+
const formatted = `\u{1F4CC} ${title}${reason ? ` \u2014 *${reason.substring(0, 100)}*` : ""}`;
|
|
5197
|
+
if (!decisions.includes(formatted)) {
|
|
5198
|
+
decisions.push(formatted);
|
|
5199
|
+
}
|
|
5200
|
+
} catch {
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5203
|
+
stmt.free();
|
|
5204
|
+
if (decisions.length >= limit) return decisions.slice(0, limit);
|
|
5205
|
+
} catch {
|
|
5206
|
+
}
|
|
5018
5207
|
try {
|
|
5019
5208
|
const { scoreMemoryRelevance: scoreMemoryRelevance2 } = await Promise.resolve().then(() => (init_kumaMemory(), kumaMemory_exports));
|
|
5020
|
-
const
|
|
5021
|
-
const scored = scoreMemoryRelevance2(
|
|
5022
|
-
|
|
5209
|
+
const searchTerms = `${filePath} ${base} ${baseWithoutExt}`.replace(/[\/._-]/g, " ");
|
|
5210
|
+
const scored = scoreMemoryRelevance2(searchTerms, limit);
|
|
5211
|
+
for (const m of scored) {
|
|
5212
|
+
const formatted = `\u{1F4CC} ${m.topic} \u2014 ${m.content ? m.content.substring(0, 80) : `${m.score}% match`}`;
|
|
5213
|
+
if (!decisions.includes(formatted)) {
|
|
5214
|
+
decisions.push(formatted);
|
|
5215
|
+
}
|
|
5216
|
+
}
|
|
5023
5217
|
} catch {
|
|
5024
|
-
return [];
|
|
5025
5218
|
}
|
|
5219
|
+
return decisions.slice(0, limit);
|
|
5026
5220
|
}
|
|
5027
5221
|
async function getRelevantContext(filePath, goal) {
|
|
5028
5222
|
const parts = [];
|
|
@@ -5253,6 +5447,7 @@ __export(kumaGotchas_exports, {
|
|
|
5253
5447
|
resolveGotcha: () => resolveGotcha,
|
|
5254
5448
|
resolveGotchasForScope: () => resolveGotchasForScope,
|
|
5255
5449
|
setGotchaStatus: () => setGotchaStatus,
|
|
5450
|
+
syncGotchaStatusToMarkdown: () => syncGotchaStatusToMarkdown,
|
|
5256
5451
|
syncGotchasGraph: () => syncGotchasGraph,
|
|
5257
5452
|
syncGotchasToDb: () => syncGotchasToDb
|
|
5258
5453
|
});
|
|
@@ -5529,34 +5724,8 @@ async function syncGotchasGraph() {
|
|
|
5529
5724
|
return { created: 0 };
|
|
5530
5725
|
}
|
|
5531
5726
|
}
|
|
5532
|
-
async function resolveGotchasForScope(
|
|
5533
|
-
|
|
5534
|
-
await ensureGotchasSchema();
|
|
5535
|
-
const db = await getDb();
|
|
5536
|
-
const stmt = db.prepare(
|
|
5537
|
-
`SELECT id, file_path, content_hash FROM known_gotchas
|
|
5538
|
-
WHERE status = 'active' AND file_path LIKE ? AND content_hash IS NOT NULL`
|
|
5539
|
-
);
|
|
5540
|
-
stmt.bind([`%${scope}%`]);
|
|
5541
|
-
const rows = [];
|
|
5542
|
-
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
5543
|
-
stmt.free();
|
|
5544
|
-
let resolved = 0;
|
|
5545
|
-
for (const r of rows) {
|
|
5546
|
-
const current = hashFileContent(r.file_path);
|
|
5547
|
-
if (current && current !== r.content_hash) {
|
|
5548
|
-
db.run(
|
|
5549
|
-
`UPDATE known_gotchas SET status = 'resolved', last_verified_at = strftime('%s','now') WHERE id = ?`,
|
|
5550
|
-
[r.id]
|
|
5551
|
-
);
|
|
5552
|
-
resolved++;
|
|
5553
|
-
}
|
|
5554
|
-
}
|
|
5555
|
-
if (resolved > 0) saveDb();
|
|
5556
|
-
return { resolved };
|
|
5557
|
-
} catch {
|
|
5558
|
-
return { resolved: 0 };
|
|
5559
|
-
}
|
|
5727
|
+
async function resolveGotchasForScope(_scope) {
|
|
5728
|
+
return { resolved: 0 };
|
|
5560
5729
|
}
|
|
5561
5730
|
async function getActiveGotchasForCommand(command) {
|
|
5562
5731
|
try {
|
|
@@ -5658,14 +5827,73 @@ async function getGotchasByStatus(status, limit = 20) {
|
|
|
5658
5827
|
return [];
|
|
5659
5828
|
}
|
|
5660
5829
|
}
|
|
5830
|
+
function syncGotchaStatusToMarkdown(targetPaths, newStatus, reason) {
|
|
5831
|
+
try {
|
|
5832
|
+
const mdPath = path12.join(getProjectRoot(), ".kuma", "KNOWN_GOTCHAS.md");
|
|
5833
|
+
if (!fs11.existsSync(mdPath)) return false;
|
|
5834
|
+
const content = fs11.readFileSync(mdPath, "utf-8");
|
|
5835
|
+
const lines = content.split("\n");
|
|
5836
|
+
const normalizedTargets = targetPaths.map((p) => p.trim().toLowerCase()).filter(Boolean);
|
|
5837
|
+
if (normalizedTargets.length === 0) return false;
|
|
5838
|
+
let modified = false;
|
|
5839
|
+
const outputLines = [];
|
|
5840
|
+
let inMatchingSection = false;
|
|
5841
|
+
let sectionHasStatus = false;
|
|
5842
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5843
|
+
const line = lines[i];
|
|
5844
|
+
const headingMatch = line.match(/^###\s+\[?(.+?)\]?\s*(?:[—–-]+\s*(.+))?$/);
|
|
5845
|
+
if (headingMatch) {
|
|
5846
|
+
if (inMatchingSection && !sectionHasStatus) {
|
|
5847
|
+
outputLines.push(`- \u{1F3F7}\uFE0F **Status**: ${newStatus}${reason ? ` (${reason})` : ""}`);
|
|
5848
|
+
modified = true;
|
|
5849
|
+
}
|
|
5850
|
+
const headingFile = headingMatch[1].trim().toLowerCase();
|
|
5851
|
+
const headingBase = path12.basename(headingFile);
|
|
5852
|
+
inMatchingSection = normalizedTargets.some(
|
|
5853
|
+
(t) => headingFile.includes(t) || t.includes(headingFile) || headingBase === t || headingBase === path12.basename(t)
|
|
5854
|
+
);
|
|
5855
|
+
sectionHasStatus = false;
|
|
5856
|
+
outputLines.push(line);
|
|
5857
|
+
continue;
|
|
5858
|
+
}
|
|
5859
|
+
if (inMatchingSection) {
|
|
5860
|
+
const statusMatch = line.match(/^(\s*[-*]\s*(?:🏷️\s*)?\*\*Status\*\*:\s*)(.*)$/i);
|
|
5861
|
+
if (statusMatch) {
|
|
5862
|
+
outputLines.push(`- \u{1F3F7}\uFE0F **Status**: ${newStatus}${reason ? ` (${reason})` : ""}`);
|
|
5863
|
+
sectionHasStatus = true;
|
|
5864
|
+
modified = true;
|
|
5865
|
+
continue;
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
outputLines.push(line);
|
|
5869
|
+
}
|
|
5870
|
+
if (inMatchingSection && !sectionHasStatus) {
|
|
5871
|
+
outputLines.push(`- \u{1F3F7}\uFE0F **Status**: ${newStatus}${reason ? ` (${reason})` : ""}`);
|
|
5872
|
+
modified = true;
|
|
5873
|
+
}
|
|
5874
|
+
if (modified) {
|
|
5875
|
+
fs11.writeFileSync(mdPath, outputLines.join("\n"), "utf-8");
|
|
5876
|
+
}
|
|
5877
|
+
return modified;
|
|
5878
|
+
} catch {
|
|
5879
|
+
return false;
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5661
5882
|
async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent") {
|
|
5662
5883
|
try {
|
|
5663
5884
|
await ensureGotchasSchema();
|
|
5664
5885
|
const db = await getDb();
|
|
5665
5886
|
let resolved = 0;
|
|
5666
5887
|
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5888
|
+
const targetPaths = [];
|
|
5667
5889
|
if (isId) {
|
|
5668
5890
|
const id = Number(idOrScope);
|
|
5891
|
+
const findStmt = db.prepare(`SELECT file_path FROM known_gotchas WHERE id = ?`);
|
|
5892
|
+
findStmt.bind([id]);
|
|
5893
|
+
if (findStmt.step()) {
|
|
5894
|
+
targetPaths.push(findStmt.getAsObject().file_path);
|
|
5895
|
+
}
|
|
5896
|
+
findStmt.free();
|
|
5669
5897
|
db.run(
|
|
5670
5898
|
`UPDATE known_gotchas SET status = 'resolved', verified_by = ?, last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ? AND status != 'resolved'`,
|
|
5671
5899
|
[verifiedBy, id]
|
|
@@ -5673,12 +5901,22 @@ async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent")
|
|
|
5673
5901
|
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5674
5902
|
} else {
|
|
5675
5903
|
const scope = String(idOrScope).trim();
|
|
5904
|
+
targetPaths.push(scope);
|
|
5905
|
+
const findStmt = db.prepare(`SELECT DISTINCT file_path FROM known_gotchas WHERE (file_path = ? OR file_path LIKE ?)`);
|
|
5906
|
+
findStmt.bind([scope, `%${scope}%`]);
|
|
5907
|
+
while (findStmt.step()) {
|
|
5908
|
+
targetPaths.push(findStmt.getAsObject().file_path);
|
|
5909
|
+
}
|
|
5910
|
+
findStmt.free();
|
|
5676
5911
|
db.run(
|
|
5677
5912
|
`UPDATE known_gotchas SET status = 'resolved', verified_by = ?, last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE (file_path = ? OR file_path LIKE ?) AND status != 'resolved'`,
|
|
5678
5913
|
[verifiedBy, scope, `%${scope}%`]
|
|
5679
5914
|
);
|
|
5680
5915
|
resolved = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5681
5916
|
}
|
|
5917
|
+
if (targetPaths.length > 0) {
|
|
5918
|
+
syncGotchaStatusToMarkdown(targetPaths, "resolved", resolutionReason);
|
|
5919
|
+
}
|
|
5682
5920
|
if (resolved > 0) {
|
|
5683
5921
|
saveDb();
|
|
5684
5922
|
rebuildFtsIndex();
|
|
@@ -5687,6 +5925,13 @@ async function resolveGotcha(idOrScope, resolutionReason, verifiedBy = "agent")
|
|
|
5687
5925
|
message: `\u2705 Resolved ${resolved} gotcha(s) for "${idOrScope}"${resolutionReason ? `: ${resolutionReason}` : ""}.`
|
|
5688
5926
|
};
|
|
5689
5927
|
}
|
|
5928
|
+
const mdUpdated = syncGotchaStatusToMarkdown(targetPaths.length > 0 ? targetPaths : [String(idOrScope)], "resolved", resolutionReason);
|
|
5929
|
+
if (mdUpdated) {
|
|
5930
|
+
return {
|
|
5931
|
+
resolved: 1,
|
|
5932
|
+
message: `\u2705 Resolved gotcha in KNOWN_GOTCHAS.md for "${idOrScope}"${resolutionReason ? `: ${resolutionReason}` : ""}.`
|
|
5933
|
+
};
|
|
5934
|
+
}
|
|
5690
5935
|
return { resolved: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5691
5936
|
} catch (err) {
|
|
5692
5937
|
return { resolved: 0, message: `\u274C Failed to resolve gotcha: ${err}` };
|
|
@@ -5698,8 +5943,15 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5698
5943
|
const db = await getDb();
|
|
5699
5944
|
let deprecated = 0;
|
|
5700
5945
|
const isId = typeof idOrScope === "number" || /^\d+$/.test(String(idOrScope).trim());
|
|
5946
|
+
const targetPaths = [];
|
|
5701
5947
|
if (isId) {
|
|
5702
5948
|
const id = Number(idOrScope);
|
|
5949
|
+
const findStmt = db.prepare(`SELECT file_path FROM known_gotchas WHERE id = ?`);
|
|
5950
|
+
findStmt.bind([id]);
|
|
5951
|
+
if (findStmt.step()) {
|
|
5952
|
+
targetPaths.push(findStmt.getAsObject().file_path);
|
|
5953
|
+
}
|
|
5954
|
+
findStmt.free();
|
|
5703
5955
|
db.run(
|
|
5704
5956
|
`UPDATE known_gotchas SET status = 'deprecated', verified_by = 'deprecation', last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE id = ? AND status != 'deprecated'`,
|
|
5705
5957
|
[id]
|
|
@@ -5707,12 +5959,22 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5707
5959
|
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5708
5960
|
} else {
|
|
5709
5961
|
const scope = String(idOrScope).trim();
|
|
5962
|
+
targetPaths.push(scope);
|
|
5963
|
+
const findStmt = db.prepare(`SELECT DISTINCT file_path FROM known_gotchas WHERE (file_path = ? OR file_path LIKE ?)`);
|
|
5964
|
+
findStmt.bind([scope, `%${scope}%`]);
|
|
5965
|
+
while (findStmt.step()) {
|
|
5966
|
+
targetPaths.push(findStmt.getAsObject().file_path);
|
|
5967
|
+
}
|
|
5968
|
+
findStmt.free();
|
|
5710
5969
|
db.run(
|
|
5711
5970
|
`UPDATE known_gotchas SET status = 'deprecated', verified_by = 'deprecation', last_verified_at = strftime('%s','now'), updated_at = strftime('%s','now') WHERE (file_path = ? OR file_path LIKE ?) AND status != 'deprecated'`,
|
|
5712
5971
|
[scope, `%${scope}%`]
|
|
5713
5972
|
);
|
|
5714
5973
|
deprecated = Number(db.exec("SELECT changes()")[0]?.values?.[0]?.[0] || 0);
|
|
5715
5974
|
}
|
|
5975
|
+
if (targetPaths.length > 0) {
|
|
5976
|
+
syncGotchaStatusToMarkdown(targetPaths, "deprecated", reason);
|
|
5977
|
+
}
|
|
5716
5978
|
if (deprecated > 0) {
|
|
5717
5979
|
saveDb();
|
|
5718
5980
|
rebuildFtsIndex();
|
|
@@ -5721,6 +5983,13 @@ async function deprecateGotcha(idOrScope, reason) {
|
|
|
5721
5983
|
message: `\u{1F9F9} Deprecated ${deprecated} gotcha(s) for "${idOrScope}"${reason ? `: ${reason}` : ""}.`
|
|
5722
5984
|
};
|
|
5723
5985
|
}
|
|
5986
|
+
const mdUpdated = syncGotchaStatusToMarkdown(targetPaths.length > 0 ? targetPaths : [String(idOrScope)], "deprecated", reason);
|
|
5987
|
+
if (mdUpdated) {
|
|
5988
|
+
return {
|
|
5989
|
+
deprecated: 1,
|
|
5990
|
+
message: `\u{1F9F9} Deprecated gotcha in KNOWN_GOTCHAS.md for "${idOrScope}"${reason ? `: ${reason}` : ""}.`
|
|
5991
|
+
};
|
|
5992
|
+
}
|
|
5724
5993
|
return { deprecated: 0, message: `\u26A0\uFE0F No active gotchas found matching "${idOrScope}".` };
|
|
5725
5994
|
} catch (err) {
|
|
5726
5995
|
return { deprecated: 0, message: `\u274C Failed to deprecate gotcha: ${err}` };
|
|
@@ -6479,7 +6748,8 @@ var kumaFlowCache_exports = {};
|
|
|
6479
6748
|
__export(kumaFlowCache_exports, {
|
|
6480
6749
|
deriveHopsFromImports: () => deriveHopsFromImports,
|
|
6481
6750
|
getFlowFreshness: () => getFlowFreshness,
|
|
6482
|
-
getFreshDomainFlow: () => getFreshDomainFlow
|
|
6751
|
+
getFreshDomainFlow: () => getFreshDomainFlow,
|
|
6752
|
+
getHopsForDomain: () => getHopsForDomain
|
|
6483
6753
|
});
|
|
6484
6754
|
import fs15 from "fs";
|
|
6485
6755
|
import path16 from "path";
|
|
@@ -6570,9 +6840,86 @@ function deriveHopsFromImports(entryFile, maxHops = 6) {
|
|
|
6570
6840
|
}
|
|
6571
6841
|
return hops;
|
|
6572
6842
|
}
|
|
6843
|
+
function escapeRegex(str) {
|
|
6844
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6845
|
+
}
|
|
6846
|
+
async function getHopsForDomain(domain) {
|
|
6847
|
+
const hops = [];
|
|
6848
|
+
try {
|
|
6849
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
6850
|
+
const db = await getDb2();
|
|
6851
|
+
const stmt = db.prepare(`SELECT metadata FROM nodes WHERE type = 'feature_domain' AND name = ? LIMIT 1`);
|
|
6852
|
+
stmt.bind([domain]);
|
|
6853
|
+
if (stmt.step()) {
|
|
6854
|
+
const meta = JSON.parse(stmt.getAsObject().metadata || "{}");
|
|
6855
|
+
if (Array.isArray(meta.hops) && meta.hops.length > 0 && typeof meta.hops[0] === "object") {
|
|
6856
|
+
stmt.free();
|
|
6857
|
+
return meta.hops;
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
stmt.free();
|
|
6861
|
+
const nodeStmt = db.prepare(`
|
|
6862
|
+
SELECT name, metadata FROM nodes
|
|
6863
|
+
WHERE type = 'cross_service_link' AND (metadata LIKE ? OR id LIKE ?)
|
|
6864
|
+
LIMIT 25
|
|
6865
|
+
`);
|
|
6866
|
+
nodeStmt.bind([`%"domain":"${domain}"%`, `%${domain}::%`]);
|
|
6867
|
+
const rawNodes = [];
|
|
6868
|
+
while (nodeStmt.step()) {
|
|
6869
|
+
const row = nodeStmt.getAsObject();
|
|
6870
|
+
try {
|
|
6871
|
+
const meta = JSON.parse(row.metadata || "{}");
|
|
6872
|
+
rawNodes.push({
|
|
6873
|
+
name: row.name,
|
|
6874
|
+
hopIndex: typeof meta.hopIndex === "number" ? meta.hopIndex : 999,
|
|
6875
|
+
relation: meta.relation,
|
|
6876
|
+
description: meta.description
|
|
6877
|
+
});
|
|
6878
|
+
} catch {
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
nodeStmt.free();
|
|
6882
|
+
if (rawNodes.length >= 2) {
|
|
6883
|
+
rawNodes.sort((a, b) => a.hopIndex - b.hopIndex);
|
|
6884
|
+
const uniqueSteps = [];
|
|
6885
|
+
for (const n of rawNodes) {
|
|
6886
|
+
if (uniqueSteps.length === 0 || uniqueSteps[uniqueSteps.length - 1] !== n.name) {
|
|
6887
|
+
uniqueSteps.push(n.name);
|
|
6888
|
+
}
|
|
6889
|
+
}
|
|
6890
|
+
for (let i = 0; i < uniqueSteps.length - 1; i++) {
|
|
6891
|
+
hops.push({ from: uniqueSteps[i], to: uniqueSteps[i + 1] });
|
|
6892
|
+
}
|
|
6893
|
+
if (hops.length > 0) return hops;
|
|
6894
|
+
}
|
|
6895
|
+
} catch {
|
|
6896
|
+
}
|
|
6897
|
+
try {
|
|
6898
|
+
const root = getProjectRoot();
|
|
6899
|
+
const archPath = path16.join(root, ".kuma", "ARCHITECTURE_FLOW.md");
|
|
6900
|
+
if (fs15.existsSync(archPath)) {
|
|
6901
|
+
const content = fs15.readFileSync(archPath, "utf-8");
|
|
6902
|
+
const lines = content.split("\n");
|
|
6903
|
+
for (const line of lines) {
|
|
6904
|
+
const domMatch = line.match(new RegExp(`domain:\\s*${escapeRegex(domain)}\\s*\\|\\s*hops:\\s*(.+)`, "i"));
|
|
6905
|
+
if (domMatch) {
|
|
6906
|
+
const hopsPart = domMatch[1].trim();
|
|
6907
|
+
const arrowSteps = hopsPart.split(/→|->/).map((s) => s.trim()).filter(Boolean);
|
|
6908
|
+
for (let i = 0; i < arrowSteps.length - 1; i++) {
|
|
6909
|
+
hops.push({ from: arrowSteps[i], to: arrowSteps[i + 1] });
|
|
6910
|
+
}
|
|
6911
|
+
if (hops.length > 0) return hops;
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
} catch {
|
|
6916
|
+
}
|
|
6917
|
+
return hops;
|
|
6918
|
+
}
|
|
6573
6919
|
async function getFreshDomainFlow(domain) {
|
|
6574
6920
|
const freshness = await getFlowFreshness(domain);
|
|
6575
|
-
|
|
6921
|
+
const hops = await getHopsForDomain(domain);
|
|
6922
|
+
if (!freshness && hops.length === 0) {
|
|
6576
6923
|
return `\u2139\uFE0F No arch_flow recorded for domain "${domain}".
|
|
6577
6924
|
Record one with kuma_memory({ action: 'arch_flow', content: 'domain: ${domain} | hops: a.ts \u2192 b.ts \u2192 c.ts' }).`;
|
|
6578
6925
|
}
|
|
@@ -6581,33 +6928,55 @@ Record one with kuma_memory({ action: 'arch_flow', content: 'domain: ${domain} |
|
|
|
6581
6928
|
`\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`,
|
|
6582
6929
|
""
|
|
6583
6930
|
];
|
|
6584
|
-
if (freshness
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6931
|
+
if (freshness) {
|
|
6932
|
+
if (freshness.fresh) {
|
|
6933
|
+
lines.push(`\u2705 Cache FRESH \u2014 served as-is (${freshness.filePaths.length} file(s) tracked).`);
|
|
6934
|
+
} else {
|
|
6935
|
+
lines.push(`\u{1F501} Cache STALE (${freshness.staleFiles.length} file(s) changed) \u2014 re-deriving from imports...`);
|
|
6936
|
+
try {
|
|
6937
|
+
const entry = freshness.filePaths[0];
|
|
6938
|
+
const derivedHops = deriveHopsFromImports(entry);
|
|
6939
|
+
if (derivedHops.length > 0) {
|
|
6940
|
+
const { recordDomainFlow: recordDomainFlow2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
6941
|
+
await recordDomainFlow2({
|
|
6942
|
+
domain,
|
|
6943
|
+
hops: derivedHops,
|
|
6944
|
+
filePaths: freshness.filePaths
|
|
6945
|
+
});
|
|
6946
|
+
lines.push(`\u2705 Re-derived ${derivedHops.length} hop(s) from ${path16.basename(entry)} \u2014 cache refreshed.`);
|
|
6947
|
+
hops.splice(0, hops.length, ...derivedHops);
|
|
6948
|
+
} else {
|
|
6949
|
+
lines.push("\u26A0\uFE0F No imports to follow \u2014 cache left as-is (record hops manually if needed).");
|
|
6950
|
+
}
|
|
6951
|
+
} catch (err) {
|
|
6952
|
+
lines.push(`\u26A0\uFE0F Re-derivation failed: ${err}`);
|
|
6601
6953
|
}
|
|
6602
|
-
} catch (err) {
|
|
6603
|
-
lines.push(`\u26A0\uFE0F Re-derivation failed: ${err}`);
|
|
6604
6954
|
}
|
|
6955
|
+
} else {
|
|
6956
|
+
lines.push(`\u2139\uFE0F Serving flow from markdown definition.`);
|
|
6605
6957
|
}
|
|
6606
6958
|
lines.push("");
|
|
6607
|
-
|
|
6608
|
-
lines.push(
|
|
6959
|
+
if (hops.length > 0) {
|
|
6960
|
+
lines.push("\u{1F504} **Flow Sequence (Hops):**");
|
|
6961
|
+
for (let i = 0; i < hops.length; i++) {
|
|
6962
|
+
const h = hops[i];
|
|
6963
|
+
const detail = h.relation && h.relation !== "flows" ? ` (${h.relation})` : "";
|
|
6964
|
+
if (i === 0) {
|
|
6965
|
+
lines.push(` 1. ${h.from} \u2192`);
|
|
6966
|
+
}
|
|
6967
|
+
const isLast = i === hops.length - 1;
|
|
6968
|
+
lines.push(` ${i + 2}. ${h.to}${detail}${isLast ? "" : " \u2192"}`);
|
|
6969
|
+
}
|
|
6970
|
+
lines.push("");
|
|
6609
6971
|
}
|
|
6610
|
-
|
|
6972
|
+
if (freshness && freshness.filePaths.length > 0) {
|
|
6973
|
+
lines.push("\u{1F4C1} **Tracked Files:**");
|
|
6974
|
+
for (const fp of freshness.filePaths) {
|
|
6975
|
+
lines.push(` \u{1F4C4} ${fp}${freshness.staleFiles.includes(fp) ? " \u26A0\uFE0F changed" : ""}`);
|
|
6976
|
+
}
|
|
6977
|
+
lines.push("");
|
|
6978
|
+
}
|
|
6979
|
+
return lines.join("\n").trimEnd();
|
|
6611
6980
|
}
|
|
6612
6981
|
var init_kumaFlowCache = __esm({
|
|
6613
6982
|
"src/engine/kumaFlowCache.ts"() {
|
|
@@ -7170,30 +7539,93 @@ async function createCheckpoint(label, description) {
|
|
|
7170
7539
|
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
7171
7540
|
}
|
|
7172
7541
|
fs22.mkdirSync(cpDir, { recursive: true });
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7542
|
+
let gitHead;
|
|
7543
|
+
let isGitClean = false;
|
|
7544
|
+
try {
|
|
7545
|
+
const { execSync: execSync7 } = await import("child_process");
|
|
7546
|
+
gitHead = execSync7("git rev-parse HEAD", {
|
|
7547
|
+
cwd: root,
|
|
7548
|
+
encoding: "utf-8",
|
|
7549
|
+
timeout: 3e3,
|
|
7550
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7551
|
+
}).trim();
|
|
7552
|
+
const statusOut = execSync7("git status --porcelain", {
|
|
7553
|
+
cwd: root,
|
|
7554
|
+
encoding: "utf-8",
|
|
7555
|
+
timeout: 3e3,
|
|
7556
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7557
|
+
}).trim();
|
|
7558
|
+
isGitClean = statusOut.length === 0;
|
|
7559
|
+
} catch {
|
|
7560
|
+
}
|
|
7561
|
+
const candidatePaths = /* @__PURE__ */ new Set();
|
|
7562
|
+
try {
|
|
7563
|
+
const { execSync: execSync7 } = await import("child_process");
|
|
7564
|
+
const gitOut = execSync7("git status --porcelain", {
|
|
7565
|
+
cwd: root,
|
|
7566
|
+
encoding: "utf-8",
|
|
7567
|
+
timeout: 4e3,
|
|
7568
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7569
|
+
}).trim();
|
|
7570
|
+
if (gitOut) {
|
|
7571
|
+
for (const line of gitOut.split("\n")) {
|
|
7572
|
+
const trimmed = line.trim();
|
|
7573
|
+
if (!trimmed) continue;
|
|
7574
|
+
const rawPath = trimmed.slice(2).trim();
|
|
7575
|
+
const cleanPath = rawPath.replace(/^["']|["']$/g, "").split(" -> ").pop();
|
|
7576
|
+
if (cleanPath.startsWith(".kuma/") || cleanPath.startsWith(".git/")) continue;
|
|
7577
|
+
candidatePaths.add(cleanPath);
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
} catch {
|
|
7581
|
+
}
|
|
7582
|
+
try {
|
|
7583
|
+
for (const m of sessionMemory.getModifiedFiles()) {
|
|
7584
|
+
if (!m.filePath.startsWith(".kuma/") && !m.filePath.startsWith(".git/")) {
|
|
7585
|
+
candidatePaths.add(m.filePath);
|
|
7586
|
+
}
|
|
7587
|
+
}
|
|
7588
|
+
} catch {
|
|
7589
|
+
}
|
|
7590
|
+
try {
|
|
7591
|
+
const db2 = await getDb();
|
|
7592
|
+
const filesStmt = db2.prepare(
|
|
7593
|
+
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
7594
|
+
);
|
|
7595
|
+
while (filesStmt.step()) {
|
|
7596
|
+
const row = filesStmt.getAsObject();
|
|
7597
|
+
const fp = row.file_path;
|
|
7598
|
+
if (fp && !fp.startsWith(".kuma/") && !fp.startsWith(".git/")) {
|
|
7599
|
+
candidatePaths.add(fp);
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
7602
|
+
filesStmt.free();
|
|
7603
|
+
} catch {
|
|
7604
|
+
}
|
|
7177
7605
|
const files = [];
|
|
7178
|
-
|
|
7179
|
-
const row = filesStmt.getAsObject();
|
|
7180
|
-
const fp = row.file_path;
|
|
7606
|
+
for (const fp of candidatePaths) {
|
|
7181
7607
|
const fullPath = path23.resolve(root, fp);
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7608
|
+
try {
|
|
7609
|
+
if (fs22.existsSync(fullPath) && fs22.statSync(fullPath).isFile()) {
|
|
7610
|
+
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
7611
|
+
const hash = simpleHash2(content);
|
|
7612
|
+
const snapFile = path23.join(cpDir, "files", `${fp}.kuma-snap`);
|
|
7613
|
+
const fileDir = path23.dirname(snapFile);
|
|
7614
|
+
if (!fs22.existsSync(fileDir)) fs22.mkdirSync(fileDir, { recursive: true });
|
|
7615
|
+
fs22.writeFileSync(snapFile, content, "utf-8");
|
|
7616
|
+
files.push({ path: fp, hash });
|
|
7617
|
+
}
|
|
7618
|
+
} catch {
|
|
7189
7619
|
}
|
|
7190
7620
|
}
|
|
7191
|
-
|
|
7621
|
+
const db = await getDb();
|
|
7192
7622
|
const dbData = db.export();
|
|
7193
7623
|
fs22.writeFileSync(path23.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
7194
7624
|
const manifest = {
|
|
7195
7625
|
label,
|
|
7196
7626
|
timestamp: Date.now(),
|
|
7627
|
+
gitHead,
|
|
7628
|
+
isGitClean,
|
|
7197
7629
|
files,
|
|
7198
7630
|
dbSnapshot: true,
|
|
7199
7631
|
description
|
|
@@ -7205,13 +7637,15 @@ async function createCheckpoint(label, description) {
|
|
|
7205
7637
|
);
|
|
7206
7638
|
sessionMemory.recordToolCall("kuma_checkpoint_create", {
|
|
7207
7639
|
label,
|
|
7208
|
-
filesCount: files.length
|
|
7640
|
+
filesCount: files.length,
|
|
7641
|
+
gitHead
|
|
7209
7642
|
});
|
|
7643
|
+
const fileNotice = files.length > 0 ? `\u{1F4E6} **${files.length} file(s)** snapshotted` : gitHead ? `\u{1F4E6} **Git commit snapshotted** (\`${gitHead.substring(0, 8)}\` \u2014 clean tree)` : `\u{1F4E6} **0 file(s)** snapshotted`;
|
|
7210
7644
|
return [
|
|
7211
7645
|
`\u2705 **Checkpoint Created**: "${label}"`,
|
|
7212
7646
|
`\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`,
|
|
7213
7647
|
``,
|
|
7214
|
-
|
|
7648
|
+
fileNotice,
|
|
7215
7649
|
`\u{1F5C4}\uFE0F **Database** snapshotted`,
|
|
7216
7650
|
`\u{1F4CD} ${cpDir}`,
|
|
7217
7651
|
``,
|
|
@@ -7238,10 +7672,12 @@ ${listCheckpoints()}`;
|
|
|
7238
7672
|
let restored = 0;
|
|
7239
7673
|
let failed = 0;
|
|
7240
7674
|
for (const f of manifest.files) {
|
|
7241
|
-
const
|
|
7242
|
-
|
|
7675
|
+
const snapPath = path23.join(cpDir, "files", `${f.path}.kuma-snap`);
|
|
7676
|
+
const legacyPath = path23.join(cpDir, "files", f.path);
|
|
7677
|
+
const sourcePath = fs22.existsSync(snapPath) ? snapPath : legacyPath;
|
|
7678
|
+
if (fs22.existsSync(sourcePath)) {
|
|
7243
7679
|
try {
|
|
7244
|
-
const content = fs22.readFileSync(
|
|
7680
|
+
const content = fs22.readFileSync(sourcePath, "utf-8");
|
|
7245
7681
|
const targetPath = path23.resolve(root, f.path);
|
|
7246
7682
|
const targetDir = path23.dirname(targetPath);
|
|
7247
7683
|
if (!fs22.existsSync(targetDir)) fs22.mkdirSync(targetDir, { recursive: true });
|
|
@@ -7252,6 +7688,20 @@ ${listCheckpoints()}`;
|
|
|
7252
7688
|
}
|
|
7253
7689
|
}
|
|
7254
7690
|
}
|
|
7691
|
+
if (manifest.files.length === 0 && manifest.gitHead) {
|
|
7692
|
+
try {
|
|
7693
|
+
const { execSync: execSync7 } = await import("child_process");
|
|
7694
|
+
execSync7(`git checkout ${manifest.gitHead} -- .`, {
|
|
7695
|
+
cwd: root,
|
|
7696
|
+
encoding: "utf-8",
|
|
7697
|
+
timeout: 8e3,
|
|
7698
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7699
|
+
});
|
|
7700
|
+
restored++;
|
|
7701
|
+
} catch {
|
|
7702
|
+
failed++;
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7255
7705
|
const dbSnapshotPath = path23.join(cpDir, "kuma.db");
|
|
7256
7706
|
if (fs22.existsSync(dbSnapshotPath)) {
|
|
7257
7707
|
const kumaDir = path23.join(root, ".kuma");
|
|
@@ -7620,10 +8070,12 @@ async function runAutoVerification(options = {}) {
|
|
|
7620
8070
|
const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
7621
8071
|
const denial = checkAllowed(root);
|
|
7622
8072
|
if (denial) return denial;
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
8073
|
+
if (!options.force) {
|
|
8074
|
+
const runawayBlock = checkRunaway();
|
|
8075
|
+
if (runawayBlock) {
|
|
8076
|
+
releaseFileLock(root);
|
|
8077
|
+
return runawayBlock;
|
|
8078
|
+
}
|
|
7627
8079
|
}
|
|
7628
8080
|
try {
|
|
7629
8081
|
if (!options.force) {
|
|
@@ -7659,8 +8111,8 @@ async function runAutoVerification(options = {}) {
|
|
|
7659
8111
|
let modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
|
|
7660
8112
|
if (modified.length === 0) {
|
|
7661
8113
|
try {
|
|
7662
|
-
const { execSync:
|
|
7663
|
-
const gitDiff =
|
|
8114
|
+
const { execSync: execSync7 } = await import("child_process");
|
|
8115
|
+
const gitDiff = execSync7("git diff --name-only HEAD", { encoding: "utf-8", timeout: 3e3, cwd: root }).trim();
|
|
7664
8116
|
if (gitDiff) {
|
|
7665
8117
|
modified = gitDiff.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
7666
8118
|
}
|
|
@@ -7800,14 +8252,6 @@ async function runAutoVerification(options = {}) {
|
|
|
7800
8252
|
} catch {
|
|
7801
8253
|
}
|
|
7802
8254
|
}
|
|
7803
|
-
if (passed && !isNoTests && scope !== "session-impact") {
|
|
7804
|
-
try {
|
|
7805
|
-
const { resolveGotchasForScope: resolveGotchasForScope2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
7806
|
-
const { resolved } = await resolveGotchasForScope2(scope);
|
|
7807
|
-
if (resolved > 0) autoGotchaMessage = `\u2705 ${resolved} gotcha(s) resolved automatically (verification passed after file changes).`;
|
|
7808
|
-
} catch {
|
|
7809
|
-
}
|
|
7810
|
-
}
|
|
7811
8255
|
_localRunning = false;
|
|
7812
8256
|
_currentProcess = null;
|
|
7813
8257
|
releaseFileLock(root);
|
|
@@ -7872,7 +8316,7 @@ var init_kumaVerifier = __esm({
|
|
|
7872
8316
|
STALE_RESULT_MS = 3e5;
|
|
7873
8317
|
DEFAULT_TIMEOUT_MS = 6e4;
|
|
7874
8318
|
RUNAWAY_WINDOW_MS = 3e5;
|
|
7875
|
-
RUNAWAY_MAX_CALLS =
|
|
8319
|
+
RUNAWAY_MAX_CALLS = 10;
|
|
7876
8320
|
_verifyCallTimestamps = [];
|
|
7877
8321
|
}
|
|
7878
8322
|
});
|
|
@@ -10146,10 +10590,10 @@ init_sessionMemory();
|
|
|
10146
10590
|
init_kumaDb();
|
|
10147
10591
|
init_kumaGraph();
|
|
10148
10592
|
init_kumaMemory();
|
|
10593
|
+
init_kumaDriftDetector();
|
|
10149
10594
|
init_pathValidator();
|
|
10150
10595
|
import fs16 from "fs";
|
|
10151
10596
|
import path17 from "path";
|
|
10152
|
-
import crypto3 from "crypto";
|
|
10153
10597
|
var CONTEXT_ALIASES = {
|
|
10154
10598
|
"research": "research",
|
|
10155
10599
|
"search": "research",
|
|
@@ -10480,11 +10924,32 @@ async function handleHistory(params) {
|
|
|
10480
10924
|
try {
|
|
10481
10925
|
const { getFileTrace: getFileTrace2, formatFileTrace: formatFileTrace2, getFreshGotchasForFile: getFreshGotchasForFile2, getDecisionsForFile: getDecisionsForFile2 } = await Promise.resolve().then(() => (init_kumaInject(), kumaInject_exports));
|
|
10482
10926
|
const trace = await getFileTrace2(target, 8);
|
|
10483
|
-
|
|
10927
|
+
const traceFormatted = formatFileTrace2(trace, target);
|
|
10928
|
+
if (traceFormatted) {
|
|
10929
|
+
lines.push(traceFormatted);
|
|
10930
|
+
} else {
|
|
10931
|
+
try {
|
|
10932
|
+
const { execSync: execSync7 } = await import("child_process");
|
|
10933
|
+
const { getProjectRoot: getProjectRoot2 } = await Promise.resolve().then(() => (init_pathValidator(), pathValidator_exports));
|
|
10934
|
+
const root = getProjectRoot2();
|
|
10935
|
+
const gitHistory = execSync7(
|
|
10936
|
+
`git log -n 5 --pretty=format:" \u2022 %h (%ad) %s" --date=short -- "${target}"`,
|
|
10937
|
+
{ cwd: root, encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
10938
|
+
).trim();
|
|
10939
|
+
if (gitHistory) {
|
|
10940
|
+
lines.push(`\u{1F570}\uFE0F **Git Commit History (latest):**
|
|
10941
|
+
${gitHistory}`);
|
|
10942
|
+
} else {
|
|
10943
|
+
lines.push(" (no changes recorded for this file yet)");
|
|
10944
|
+
}
|
|
10945
|
+
} catch {
|
|
10946
|
+
lines.push(" (no changes recorded for this file yet)");
|
|
10947
|
+
}
|
|
10948
|
+
}
|
|
10484
10949
|
lines.push("");
|
|
10485
|
-
const gotchas = await getFreshGotchasForFile2(target, 5);
|
|
10950
|
+
const gotchas = await getFreshGotchasForFile2(target, 5, true);
|
|
10486
10951
|
if (gotchas.length > 0) {
|
|
10487
|
-
lines.push("\u26A0\uFE0F **Active gotchas
|
|
10952
|
+
lines.push("\u26A0\uFE0F **Active gotchas:**");
|
|
10488
10953
|
for (const g of gotchas) {
|
|
10489
10954
|
const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
10490
10955
|
lines.push(` ${icon} [${g.severity}] ${g.description}`);
|
|
@@ -10499,11 +10964,34 @@ async function handleHistory(params) {
|
|
|
10499
10964
|
try {
|
|
10500
10965
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
10501
10966
|
const db = await getDb2();
|
|
10502
|
-
const
|
|
10503
|
-
stmt.
|
|
10967
|
+
const base = path17.basename(target);
|
|
10968
|
+
const stmt = db.prepare(`
|
|
10969
|
+
SELECT description, last_verified_at FROM known_gotchas
|
|
10970
|
+
WHERE status = 'resolved' AND (file_path LIKE ? OR file_path LIKE ?)
|
|
10971
|
+
ORDER BY last_verified_at DESC LIMIT 5
|
|
10972
|
+
`);
|
|
10973
|
+
stmt.bind([`%${base}%`, `%${target}%`]);
|
|
10504
10974
|
const resolved = [];
|
|
10505
10975
|
while (stmt.step()) resolved.push(stmt.getAsObject());
|
|
10506
10976
|
stmt.free();
|
|
10977
|
+
if (resolved.length === 0) {
|
|
10978
|
+
try {
|
|
10979
|
+
const { getProjectRoot: getProjectRoot2 } = await Promise.resolve().then(() => (init_pathValidator(), pathValidator_exports));
|
|
10980
|
+
const mdPath = path17.join(getProjectRoot2(), ".kuma", "KNOWN_GOTCHAS.md");
|
|
10981
|
+
if (fs16.existsSync(mdPath)) {
|
|
10982
|
+
const mdContent = fs16.readFileSync(mdPath, "utf-8");
|
|
10983
|
+
const sections = mdContent.split(/^###\s+/m);
|
|
10984
|
+
for (const sec of sections) {
|
|
10985
|
+
const secLower = sec.toLowerCase();
|
|
10986
|
+
if ((secLower.includes(base.toLowerCase()) || secLower.includes(target.toLowerCase())) && secLower.includes("status**: resolved")) {
|
|
10987
|
+
const descLine = sec.split("\n")[0].trim();
|
|
10988
|
+
resolved.push({ description: descLine, last_verified_at: null });
|
|
10989
|
+
}
|
|
10990
|
+
}
|
|
10991
|
+
}
|
|
10992
|
+
} catch {
|
|
10993
|
+
}
|
|
10994
|
+
}
|
|
10507
10995
|
if (resolved.length > 0) {
|
|
10508
10996
|
lines.push("\u2705 **Resolved gotchas** (fixed):");
|
|
10509
10997
|
for (const r of resolved) {
|
|
@@ -10516,7 +11004,7 @@ async function handleHistory(params) {
|
|
|
10516
11004
|
}
|
|
10517
11005
|
const decisions = await getDecisionsForFile2(target, 3);
|
|
10518
11006
|
if (decisions.length > 0) {
|
|
10519
|
-
lines.push("\u{1F4CC} **Relevant decisions
|
|
11007
|
+
lines.push("\u{1F4CC} **Relevant decisions:**");
|
|
10520
11008
|
for (const d of decisions) lines.push(` ${d}`);
|
|
10521
11009
|
lines.push("");
|
|
10522
11010
|
}
|
|
@@ -10545,24 +11033,6 @@ async function handleImpact(params) {
|
|
|
10545
11033
|
const impact = await analyzeImpact2(target);
|
|
10546
11034
|
return formatImpact2(impact);
|
|
10547
11035
|
}
|
|
10548
|
-
function computeProjectHash(scope) {
|
|
10549
|
-
try {
|
|
10550
|
-
const root = getProjectRoot();
|
|
10551
|
-
const files = fs16.readdirSync(root).slice(0, 20);
|
|
10552
|
-
const hash = crypto3.createHash("md5");
|
|
10553
|
-
hash.update(scope);
|
|
10554
|
-
for (const f of files) {
|
|
10555
|
-
try {
|
|
10556
|
-
const stat = fs16.statSync(path17.join(root, f));
|
|
10557
|
-
hash.update(`${f}:${stat.mtimeMs}`);
|
|
10558
|
-
} catch {
|
|
10559
|
-
}
|
|
10560
|
-
}
|
|
10561
|
-
return hash.digest("hex").substring(0, 12);
|
|
10562
|
-
} catch {
|
|
10563
|
-
return Date.now().toString(16);
|
|
10564
|
-
}
|
|
10565
|
-
}
|
|
10566
11036
|
|
|
10567
11037
|
// src/tools/kumaMemoryTool.ts
|
|
10568
11038
|
init_sessionMemory();
|
|
@@ -10864,7 +11334,7 @@ init_sessionMemory();
|
|
|
10864
11334
|
init_kumaShared();
|
|
10865
11335
|
import fs19 from "fs";
|
|
10866
11336
|
import path20 from "path";
|
|
10867
|
-
import { execSync as
|
|
11337
|
+
import { execSync as execSync5 } from "child_process";
|
|
10868
11338
|
var SCRIPT_PATCH_PATTERNS = [
|
|
10869
11339
|
"writeFileSync",
|
|
10870
11340
|
"writeFile",
|
|
@@ -10958,7 +11428,7 @@ function scanRecentFiles(projectRoot) {
|
|
|
10958
11428
|
function detectGitPatchScripts(projectRoot) {
|
|
10959
11429
|
const warnings = [];
|
|
10960
11430
|
try {
|
|
10961
|
-
const statusStdout =
|
|
11431
|
+
const statusStdout = execSync5("git status --porcelain", {
|
|
10962
11432
|
cwd: projectRoot,
|
|
10963
11433
|
encoding: "utf-8",
|
|
10964
11434
|
timeout: 3e3
|
|
@@ -10991,7 +11461,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
10991
11461
|
}
|
|
10992
11462
|
}
|
|
10993
11463
|
}
|
|
10994
|
-
const deletedStdout =
|
|
11464
|
+
const deletedStdout = execSync5("git diff --name-only --diff-filter=D HEAD", {
|
|
10995
11465
|
cwd: projectRoot,
|
|
10996
11466
|
encoding: "utf-8",
|
|
10997
11467
|
timeout: 3e3
|
|
@@ -11063,7 +11533,7 @@ init_sessionMemory();
|
|
|
11063
11533
|
init_pathValidator();
|
|
11064
11534
|
import fs20 from "fs";
|
|
11065
11535
|
import path21 from "path";
|
|
11066
|
-
import { execSync as
|
|
11536
|
+
import { execSync as execSync6 } from "child_process";
|
|
11067
11537
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
11068
11538
|
function snapshotDir() {
|
|
11069
11539
|
return path21.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
@@ -11135,7 +11605,7 @@ function formatSnapshot(snapshot) {
|
|
|
11135
11605
|
function getGitDiffStat2() {
|
|
11136
11606
|
try {
|
|
11137
11607
|
const root = getProjectRoot();
|
|
11138
|
-
const stdout =
|
|
11608
|
+
const stdout = execSync6("git diff --stat", {
|
|
11139
11609
|
cwd: root,
|
|
11140
11610
|
encoding: "utf-8",
|
|
11141
11611
|
timeout: 3e3
|
|
@@ -11735,25 +12205,25 @@ async function main() {
|
|
|
11735
12205
|
} catch {
|
|
11736
12206
|
}
|
|
11737
12207
|
try {
|
|
11738
|
-
const { execSync:
|
|
12208
|
+
const { execSync: execSync7 } = await import("child_process");
|
|
11739
12209
|
try {
|
|
11740
|
-
|
|
12210
|
+
execSync7("pkill -f 'jest' 2>/dev/null || true");
|
|
11741
12211
|
} catch {
|
|
11742
12212
|
}
|
|
11743
12213
|
try {
|
|
11744
|
-
|
|
12214
|
+
execSync7("pkill -f 'jest-worker' 2>/dev/null || true");
|
|
11745
12215
|
} catch {
|
|
11746
12216
|
}
|
|
11747
12217
|
try {
|
|
11748
|
-
|
|
12218
|
+
execSync7("pkill -f 'pnpm test' 2>/dev/null || true");
|
|
11749
12219
|
} catch {
|
|
11750
12220
|
}
|
|
11751
12221
|
try {
|
|
11752
|
-
|
|
12222
|
+
execSync7("pkill -f 'npm test' 2>/dev/null || true");
|
|
11753
12223
|
} catch {
|
|
11754
12224
|
}
|
|
11755
12225
|
try {
|
|
11756
|
-
|
|
12226
|
+
execSync7("pkill -f 'yarn test' 2>/dev/null || true");
|
|
11757
12227
|
} catch {
|
|
11758
12228
|
}
|
|
11759
12229
|
console.error("\u2705 Killed orphaned test processes");
|