@plumpslabs/kuma 2.3.31 → 2.3.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +321 -184
- package/dist/index.js +1469 -521
- package/package.json +1 -1
- package/packages/ide/studio/dist/index.js +298 -101
- package/packages/ide/studio/public/index.html +486 -74
package/dist/index.js
CHANGED
|
@@ -1427,7 +1427,7 @@ var init_sessionMemory = __esm({
|
|
|
1427
1427
|
dependencyGraph: new Map(parsed.dependencyGraph || []),
|
|
1428
1428
|
toolCalls: parsed.toolCalls || [],
|
|
1429
1429
|
conventions: parsed.conventions,
|
|
1430
|
-
recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
|
|
1430
|
+
recordings: parsed.recordings || { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, features: 0, total: 0 },
|
|
1431
1431
|
metrics: parsed.metrics || { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
|
|
1432
1432
|
};
|
|
1433
1433
|
this.initialized = true;
|
|
@@ -1447,7 +1447,7 @@ var init_sessionMemory = __esm({
|
|
|
1447
1447
|
searchResults: /* @__PURE__ */ new Map(),
|
|
1448
1448
|
dependencyGraph: /* @__PURE__ */ new Map(),
|
|
1449
1449
|
toolCalls: [],
|
|
1450
|
-
recordings: { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, total: 0 },
|
|
1450
|
+
recordings: { archFlows: 0, gotchas: 0, decisions: 0, researchSaves: 0, features: 0, total: 0 },
|
|
1451
1451
|
metrics: { filesRead: 0, filesEdited: 0, researchTimeSaved: 0 }
|
|
1452
1452
|
};
|
|
1453
1453
|
this.initialized = true;
|
|
@@ -1610,6 +1610,18 @@ No unresolved issues.`;
|
|
|
1610
1610
|
this.state.currentGoal = goal;
|
|
1611
1611
|
this.save();
|
|
1612
1612
|
}
|
|
1613
|
+
setGoalProgress(percentage, milestone) {
|
|
1614
|
+
this.ensureInit();
|
|
1615
|
+
this.state.goalProgress = {
|
|
1616
|
+
percentage: Math.min(100, Math.max(0, percentage)),
|
|
1617
|
+
milestone,
|
|
1618
|
+
updatedAt: Date.now()
|
|
1619
|
+
};
|
|
1620
|
+
this.save();
|
|
1621
|
+
}
|
|
1622
|
+
getGoalProgress() {
|
|
1623
|
+
return this.state.goalProgress || null;
|
|
1624
|
+
}
|
|
1613
1625
|
addCompletedStep(step) {
|
|
1614
1626
|
this.ensureInit();
|
|
1615
1627
|
if (!this.state.completedSteps.includes(step)) {
|
|
@@ -2031,6 +2043,9 @@ No unresolved issues.`;
|
|
|
2031
2043
|
case "research_save":
|
|
2032
2044
|
this.state.recordings.researchSaves++;
|
|
2033
2045
|
break;
|
|
2046
|
+
case "feature":
|
|
2047
|
+
this.state.recordings.features++;
|
|
2048
|
+
break;
|
|
2034
2049
|
}
|
|
2035
2050
|
this.state.recordings.total++;
|
|
2036
2051
|
this.save();
|
|
@@ -2047,6 +2062,7 @@ No unresolved issues.`;
|
|
|
2047
2062
|
if (r.gotchas === 0) missing.push("gotcha");
|
|
2048
2063
|
if (r.decisions === 0) missing.push("decision");
|
|
2049
2064
|
if (r.researchSaves === 0) missing.push("research_save");
|
|
2065
|
+
if (r.features === 0) missing.push("feature");
|
|
2050
2066
|
return {
|
|
2051
2067
|
...r,
|
|
2052
2068
|
hasAnyRecordings: r.total > 0,
|
|
@@ -2509,6 +2525,223 @@ var init_kumaSelfHeal = __esm({
|
|
|
2509
2525
|
}
|
|
2510
2526
|
});
|
|
2511
2527
|
|
|
2528
|
+
// src/engine/languageSupport.ts
|
|
2529
|
+
import path5 from "path";
|
|
2530
|
+
function grepIncludeFlags() {
|
|
2531
|
+
return SOURCE_EXTENSIONS.map((e) => `--include="*${e}"`).join(" ");
|
|
2532
|
+
}
|
|
2533
|
+
function isTestFile(filePath) {
|
|
2534
|
+
const lower = filePath.toLowerCase();
|
|
2535
|
+
const base = path5.basename(filePath);
|
|
2536
|
+
const ext = path5.extname(base);
|
|
2537
|
+
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
2538
|
+
const segments = lower.split(/[\\/]/);
|
|
2539
|
+
for (const lang of LANGUAGES) {
|
|
2540
|
+
if (!lang.extensions.includes(ext.toLowerCase())) continue;
|
|
2541
|
+
for (const p of lang.testPrefixes) {
|
|
2542
|
+
if (p && stem.startsWith(p)) return true;
|
|
2543
|
+
}
|
|
2544
|
+
for (const s of lang.testSuffixes) {
|
|
2545
|
+
if (s && stem.endsWith(s)) return true;
|
|
2546
|
+
}
|
|
2547
|
+
for (const d of lang.testDirs) {
|
|
2548
|
+
const dLower = d.toLowerCase();
|
|
2549
|
+
if (dLower.includes("/")) {
|
|
2550
|
+
if (lower.includes(`${dLower}/`) || lower.includes(`${dLower}\\`)) return true;
|
|
2551
|
+
} else if (segments.includes(dLower)) {
|
|
2552
|
+
return true;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
return /(\.test\.|\.spec\.|_test\.|_spec\.)/.test(lower) || /(\/|^)(__tests__|tests?|specs?)(\/|$)/.test(lower);
|
|
2557
|
+
}
|
|
2558
|
+
function getTestCandidates(sourcePath) {
|
|
2559
|
+
const ext = path5.extname(sourcePath).toLowerCase();
|
|
2560
|
+
const dir = path5.dirname(sourcePath);
|
|
2561
|
+
const base = path5.basename(sourcePath, ext);
|
|
2562
|
+
const lang = LANGUAGES.find((l) => l.extensions.includes(ext));
|
|
2563
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
2564
|
+
const push = (p) => {
|
|
2565
|
+
if (p && !candidates.has(p)) candidates.add(p);
|
|
2566
|
+
};
|
|
2567
|
+
if (lang) {
|
|
2568
|
+
for (const suffix of lang.testSuffixes) {
|
|
2569
|
+
if (!suffix) continue;
|
|
2570
|
+
push(path5.join(dir, `${base}${suffix}${ext}`));
|
|
2571
|
+
}
|
|
2572
|
+
for (const prefix of lang.testPrefixes) {
|
|
2573
|
+
if (prefix) push(path5.join(dir, `${prefix}${base}${ext}`));
|
|
2574
|
+
}
|
|
2575
|
+
for (const td of lang.testDirs) {
|
|
2576
|
+
if (!td.includes("/")) {
|
|
2577
|
+
push(path5.join(dir, td, `${base}${ext}`));
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
push(path5.join(dir, `${base}.test${ext}`));
|
|
2582
|
+
push(path5.join(dir, `${base}.spec${ext}`));
|
|
2583
|
+
push(path5.join(dir, `${base}_test${ext}`));
|
|
2584
|
+
push(path5.join(dir, `test_${base}${ext}`));
|
|
2585
|
+
push(path5.join(dir, "__tests__", `${base}${ext}`));
|
|
2586
|
+
push(path5.join(dir, "tests", `${base}${ext}`));
|
|
2587
|
+
return [...candidates];
|
|
2588
|
+
}
|
|
2589
|
+
function matchImportPath(line) {
|
|
2590
|
+
for (const re of IMPORT_PATTERNS) {
|
|
2591
|
+
re.lastIndex = 0;
|
|
2592
|
+
const m = re.exec(line);
|
|
2593
|
+
if (m) return (m[1] || m[2] || "").trim();
|
|
2594
|
+
}
|
|
2595
|
+
return null;
|
|
2596
|
+
}
|
|
2597
|
+
var LANGUAGES, SOURCE_EXTENSIONS, SOURCE_EXT_GLOB, DEFAULT_SOURCE_INCLUDES, IMPORT_PATTERNS;
|
|
2598
|
+
var init_languageSupport = __esm({
|
|
2599
|
+
"src/engine/languageSupport.ts"() {
|
|
2600
|
+
"use strict";
|
|
2601
|
+
LANGUAGES = [
|
|
2602
|
+
{
|
|
2603
|
+
name: "typescript",
|
|
2604
|
+
extensions: [".ts", ".tsx", ".mts", ".cts"],
|
|
2605
|
+
testPrefixes: [],
|
|
2606
|
+
testSuffixes: [".test", ".spec"],
|
|
2607
|
+
testDirs: ["__tests__", "tests", "test", "spec"]
|
|
2608
|
+
},
|
|
2609
|
+
{
|
|
2610
|
+
name: "javascript",
|
|
2611
|
+
extensions: [".js", ".jsx", ".mjs", ".cjs"],
|
|
2612
|
+
testPrefixes: [],
|
|
2613
|
+
testSuffixes: [".test", ".spec"],
|
|
2614
|
+
testDirs: ["__tests__", "tests", "test", "spec"]
|
|
2615
|
+
},
|
|
2616
|
+
{
|
|
2617
|
+
name: "python",
|
|
2618
|
+
extensions: [".py", ".pyw"],
|
|
2619
|
+
testPrefixes: ["test_"],
|
|
2620
|
+
testSuffixes: ["_test"],
|
|
2621
|
+
testDirs: ["tests", "test"]
|
|
2622
|
+
},
|
|
2623
|
+
{
|
|
2624
|
+
name: "go",
|
|
2625
|
+
extensions: [".go"],
|
|
2626
|
+
testPrefixes: [],
|
|
2627
|
+
testSuffixes: ["_test"],
|
|
2628
|
+
testDirs: ["testdata"]
|
|
2629
|
+
},
|
|
2630
|
+
{
|
|
2631
|
+
name: "rust",
|
|
2632
|
+
extensions: [".rs"],
|
|
2633
|
+
testPrefixes: [],
|
|
2634
|
+
testSuffixes: ["_test"],
|
|
2635
|
+
testDirs: ["tests"]
|
|
2636
|
+
},
|
|
2637
|
+
{
|
|
2638
|
+
name: "java",
|
|
2639
|
+
extensions: [".java"],
|
|
2640
|
+
testPrefixes: ["Test"],
|
|
2641
|
+
testSuffixes: ["Test", "Tests"],
|
|
2642
|
+
testDirs: ["src/test", "test", "tests"]
|
|
2643
|
+
},
|
|
2644
|
+
{
|
|
2645
|
+
name: "kotlin",
|
|
2646
|
+
extensions: [".kt", ".kts"],
|
|
2647
|
+
testPrefixes: ["Test"],
|
|
2648
|
+
testSuffixes: ["Test", "Tests"],
|
|
2649
|
+
testDirs: ["src/test"]
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
name: "csharp",
|
|
2653
|
+
extensions: [".cs"],
|
|
2654
|
+
testPrefixes: [],
|
|
2655
|
+
testSuffixes: ["Tests", "Test"],
|
|
2656
|
+
testDirs: ["Tests", "test", "tests"]
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
name: "ruby",
|
|
2660
|
+
extensions: [".rb"],
|
|
2661
|
+
testPrefixes: [],
|
|
2662
|
+
testSuffixes: ["_spec", "_test"],
|
|
2663
|
+
testDirs: ["spec", "test", "tests"]
|
|
2664
|
+
},
|
|
2665
|
+
{
|
|
2666
|
+
name: "php",
|
|
2667
|
+
extensions: [".php"],
|
|
2668
|
+
testPrefixes: [],
|
|
2669
|
+
testSuffixes: ["Test"],
|
|
2670
|
+
testDirs: ["tests", "test"]
|
|
2671
|
+
},
|
|
2672
|
+
{
|
|
2673
|
+
name: "cpp",
|
|
2674
|
+
extensions: [".c", ".cc", ".cpp", ".h", ".hpp", ".hxx"],
|
|
2675
|
+
testPrefixes: [],
|
|
2676
|
+
testSuffixes: ["_test"],
|
|
2677
|
+
testDirs: ["tests", "test"]
|
|
2678
|
+
},
|
|
2679
|
+
{
|
|
2680
|
+
name: "swift",
|
|
2681
|
+
extensions: [".swift"],
|
|
2682
|
+
testPrefixes: [],
|
|
2683
|
+
testSuffixes: ["Tests"],
|
|
2684
|
+
testDirs: ["Tests", "test"]
|
|
2685
|
+
},
|
|
2686
|
+
{
|
|
2687
|
+
name: "scala",
|
|
2688
|
+
extensions: [".scala"],
|
|
2689
|
+
testPrefixes: [],
|
|
2690
|
+
testSuffixes: ["Spec", "Test", "Tests"],
|
|
2691
|
+
testDirs: ["src/test"]
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
name: "dart",
|
|
2695
|
+
extensions: [".dart"],
|
|
2696
|
+
testPrefixes: [],
|
|
2697
|
+
testSuffixes: ["_test"],
|
|
2698
|
+
testDirs: ["test", "tests"]
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
name: "shell",
|
|
2702
|
+
extensions: [".sh", ".bash", ".zsh"],
|
|
2703
|
+
testPrefixes: [],
|
|
2704
|
+
testSuffixes: [],
|
|
2705
|
+
testDirs: ["test", "tests"]
|
|
2706
|
+
}
|
|
2707
|
+
];
|
|
2708
|
+
SOURCE_EXTENSIONS = [
|
|
2709
|
+
...new Set(LANGUAGES.flatMap((l) => l.extensions))
|
|
2710
|
+
];
|
|
2711
|
+
SOURCE_EXT_GLOB = `{${SOURCE_EXTENSIONS.map((e) => e.slice(1)).join(",")}}`;
|
|
2712
|
+
DEFAULT_SOURCE_INCLUDES = [
|
|
2713
|
+
`src/**/*.${SOURCE_EXT_GLOB}`,
|
|
2714
|
+
`app/**/*.${SOURCE_EXT_GLOB}`,
|
|
2715
|
+
`lib/**/*.${SOURCE_EXT_GLOB}`,
|
|
2716
|
+
`packages/**/*.${SOURCE_EXT_GLOB}`,
|
|
2717
|
+
`server/**/*.${SOURCE_EXT_GLOB}`,
|
|
2718
|
+
`api/**/*.${SOURCE_EXT_GLOB}`
|
|
2719
|
+
];
|
|
2720
|
+
IMPORT_PATTERNS = [
|
|
2721
|
+
// JS/TS: import x from 'y' | import {a} from 'y' | import * as x from 'y'
|
|
2722
|
+
/import\s+(?:\{[^}]*\}\s+from\s+)?(?:[^'"\n]*\s+from\s+)?['"]([^'"]+)['"]/,
|
|
2723
|
+
// JS/TS (CJS): const x = require('y')
|
|
2724
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/,
|
|
2725
|
+
// Python: import y | import y.z | from y import x | from .y import x
|
|
2726
|
+
/^\s*(?:from\s+([.\w]+)\s+import\s+\S+|import\s+([.\w]+))/m,
|
|
2727
|
+
// Go: import "y" | import alias "y"
|
|
2728
|
+
/^\s*import\s+(?:\w+\s+)?"([^"]+)"/m,
|
|
2729
|
+
// Rust: use y::z | extern crate y
|
|
2730
|
+
/^\s*use\s+([\w:]+)/m,
|
|
2731
|
+
// C/C++: #include <y> | #include "y"
|
|
2732
|
+
/^\s*#\s*include\s*[<"]([^>"]+)[>"]/m,
|
|
2733
|
+
// Ruby: require 'y' | require_relative 'y'
|
|
2734
|
+
/^\s*require(?:_relative)?\s+['"]([^'"]+)['"]/m,
|
|
2735
|
+
// PHP: require 'y' | include 'y' | use y\z;
|
|
2736
|
+
/^\s*(?:require(?:_once)?|include(?:_once)?)\s*\(?\s*['"]([^'"]+)['"]/m,
|
|
2737
|
+
// Java: import y.z.w;
|
|
2738
|
+
/^\s*import\s+([\w.]+)\s*;/m,
|
|
2739
|
+
// C#: using y.z.w;
|
|
2740
|
+
/^\s*using\s+([\w.]+)\s*;/m
|
|
2741
|
+
];
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
|
|
2512
2745
|
// src/engine/kumaGraph.ts
|
|
2513
2746
|
var kumaGraph_exports = {};
|
|
2514
2747
|
__export(kumaGraph_exports, {
|
|
@@ -3105,7 +3338,7 @@ async function queryGraph(params) {
|
|
|
3105
3338
|
if (results.length === 0) {
|
|
3106
3339
|
return `\u{1F50D} **Graph Query** \u2014 No nodes found for "${query}".
|
|
3107
3340
|
|
|
3108
|
-
Try a different search term or
|
|
3341
|
+
Try a different search term or use kuma_context({ action: 'research', scope: '<term>' }) to build graph context.`;
|
|
3109
3342
|
}
|
|
3110
3343
|
const lines = [
|
|
3111
3344
|
`\u{1F50D} **Graph Nodes** \u2014 ${results.length} result(s) for "${query}"`,
|
|
@@ -3139,7 +3372,7 @@ Try a different search term or check if the graph has been built by using tools
|
|
|
3139
3372
|
if (results.length === 0) {
|
|
3140
3373
|
return `\u{1F50D} **Graph Edges** \u2014 No connections found for node "${query}".
|
|
3141
3374
|
|
|
3142
|
-
Try
|
|
3375
|
+
Try kuma_context({ action: 'research', scope: '<term>' }) to build related graph nodes first.`;
|
|
3143
3376
|
}
|
|
3144
3377
|
const lines = [
|
|
3145
3378
|
`\u{1F517} **Graph Edges** \u2014 ${results.length} connection(s) for "${query}"`,
|
|
@@ -3156,7 +3389,7 @@ Try kuma_graph_query({ type: 'nodes', query: '<search>' }) to find node IDs firs
|
|
|
3156
3389
|
const parts = query.split("\u2192").map((s) => s.trim());
|
|
3157
3390
|
if (parts.length !== 2) {
|
|
3158
3391
|
return `\u26A0\uFE0F For path queries, use format: "sourceNodeID \u2192 targetNodeID"
|
|
3159
|
-
Example:
|
|
3392
|
+
Example: kuma_context({ action: 'impact', target: 'login' }) \u2192 traces connections between nodes.`;
|
|
3160
3393
|
}
|
|
3161
3394
|
const [sourceId, targetId] = parts;
|
|
3162
3395
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -3396,27 +3629,27 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
3396
3629
|
if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
|
|
3397
3630
|
let entries = [];
|
|
3398
3631
|
try {
|
|
3399
|
-
entries =
|
|
3632
|
+
entries = fs30.readdirSync(dir);
|
|
3400
3633
|
} catch {
|
|
3401
3634
|
return;
|
|
3402
3635
|
}
|
|
3403
3636
|
for (const entry of entries) {
|
|
3404
3637
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
|
|
3405
|
-
const full =
|
|
3638
|
+
const full = path31.join(dir, entry);
|
|
3406
3639
|
try {
|
|
3407
|
-
const stat =
|
|
3640
|
+
const stat = fs30.statSync(full);
|
|
3408
3641
|
if (stat.isDirectory()) {
|
|
3409
3642
|
walk2(full, depth + 1);
|
|
3410
3643
|
} else if (entry.toLowerCase().includes(query.toLowerCase())) {
|
|
3411
|
-
results.push(
|
|
3644
|
+
results.push(path31.relative(root, full));
|
|
3412
3645
|
}
|
|
3413
3646
|
} catch {
|
|
3414
3647
|
}
|
|
3415
3648
|
}
|
|
3416
3649
|
};
|
|
3417
3650
|
var walk = walk2;
|
|
3418
|
-
const
|
|
3419
|
-
const
|
|
3651
|
+
const fs30 = await import("fs");
|
|
3652
|
+
const path31 = await import("path");
|
|
3420
3653
|
const root = process.cwd();
|
|
3421
3654
|
const results = [];
|
|
3422
3655
|
const MAX_WALK_DEPTH = 8;
|
|
@@ -3559,7 +3792,7 @@ async function codebaseImpactFallback(target) {
|
|
|
3559
3792
|
const { execSync: execSync8 } = await import("child_process");
|
|
3560
3793
|
const root = process.cwd();
|
|
3561
3794
|
const grepResult = execSync8(
|
|
3562
|
-
`grep -rn
|
|
3795
|
+
`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`,
|
|
3563
3796
|
{ cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
|
|
3564
3797
|
).trim();
|
|
3565
3798
|
const files = grepResult ? grepResult.split("\n").filter(Boolean).filter((f) => !f.startsWith("./")) : [];
|
|
@@ -3567,7 +3800,7 @@ async function codebaseImpactFallback(target) {
|
|
|
3567
3800
|
return { symbol: target, references: 0, files: 0, testFiles: 0, entryPoints: [], risk: "low" };
|
|
3568
3801
|
}
|
|
3569
3802
|
const fileCount = files.length;
|
|
3570
|
-
const testFiles = files.filter((f) =>
|
|
3803
|
+
const testFiles = files.filter((f) => isTestFile(f)).length;
|
|
3571
3804
|
const risk = fileCount > 20 ? "high" : fileCount > 5 ? "medium" : "low";
|
|
3572
3805
|
return {
|
|
3573
3806
|
symbol: target,
|
|
@@ -3666,6 +3899,7 @@ var init_kumaGraph = __esm({
|
|
|
3666
3899
|
"use strict";
|
|
3667
3900
|
init_kumaDb();
|
|
3668
3901
|
init_kumaSelfHeal();
|
|
3902
|
+
init_languageSupport();
|
|
3669
3903
|
GRAPH_LIMITS = {
|
|
3670
3904
|
maxNodes: 5e3,
|
|
3671
3905
|
// Max nodes before pruning
|
|
@@ -3689,10 +3923,10 @@ __export(kumaMemory_exports, {
|
|
|
3689
3923
|
shouldRecordDecision: () => shouldRecordDecision
|
|
3690
3924
|
});
|
|
3691
3925
|
import fs5 from "fs";
|
|
3692
|
-
import
|
|
3926
|
+
import path6 from "path";
|
|
3693
3927
|
function scoreMemoryRelevance(context, limit = 5) {
|
|
3694
3928
|
const results = [];
|
|
3695
|
-
const kumaDir =
|
|
3929
|
+
const kumaDir = path6.join(getProjectRoot(), MEMORY_DIR);
|
|
3696
3930
|
if (!fs5.existsSync(kumaDir)) return [];
|
|
3697
3931
|
const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
3698
3932
|
if (terms.length === 0) return [];
|
|
@@ -3700,7 +3934,7 @@ function scoreMemoryRelevance(context, limit = 5) {
|
|
|
3700
3934
|
const files = fs5.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
3701
3935
|
for (const file of files) {
|
|
3702
3936
|
try {
|
|
3703
|
-
const content = fs5.readFileSync(
|
|
3937
|
+
const content = fs5.readFileSync(path6.join(kumaDir, file), "utf-8");
|
|
3704
3938
|
const lower = content.toLowerCase();
|
|
3705
3939
|
let matchCount = 0;
|
|
3706
3940
|
for (const term of terms) {
|
|
@@ -4250,6 +4484,136 @@ var init_safetyScore = __esm({
|
|
|
4250
4484
|
}
|
|
4251
4485
|
});
|
|
4252
4486
|
|
|
4487
|
+
// src/engine/kumaPathRules.ts
|
|
4488
|
+
var kumaPathRules_exports = {};
|
|
4489
|
+
__export(kumaPathRules_exports, {
|
|
4490
|
+
ensureRulesDir: () => ensureRulesDir,
|
|
4491
|
+
formatPathRules: () => formatPathRules,
|
|
4492
|
+
getRulesForScope: () => getRulesForScope,
|
|
4493
|
+
hasPathRules: () => hasPathRules,
|
|
4494
|
+
loadPathRules: () => loadPathRules,
|
|
4495
|
+
matchRulePath: () => matchRulePath
|
|
4496
|
+
});
|
|
4497
|
+
import fs6 from "fs";
|
|
4498
|
+
import path7 from "path";
|
|
4499
|
+
function rulesDir() {
|
|
4500
|
+
return path7.join(getProjectRoot(), RULES_DIR);
|
|
4501
|
+
}
|
|
4502
|
+
function ensureRulesDir() {
|
|
4503
|
+
try {
|
|
4504
|
+
const dir = rulesDir();
|
|
4505
|
+
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
4506
|
+
} catch {
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
function parseRuleFile(filePath) {
|
|
4510
|
+
try {
|
|
4511
|
+
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
4512
|
+
const id = path7.basename(filePath, ".md");
|
|
4513
|
+
const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
4514
|
+
if (!fm) {
|
|
4515
|
+
return { id, description: id, paths: ["*"], content: raw.trim(), filePath };
|
|
4516
|
+
}
|
|
4517
|
+
const meta = fm[1];
|
|
4518
|
+
const body = fm[2].trim();
|
|
4519
|
+
const description = meta.match(/description:\s*(.+)/)?.[1]?.trim() || id;
|
|
4520
|
+
const paths = [];
|
|
4521
|
+
const pathSection = meta.match(/paths:\s*\r?\n([\s\S]*?)(?=\r?\n\S|$)/);
|
|
4522
|
+
if (pathSection) {
|
|
4523
|
+
for (const line of pathSection[1].split("\n")) {
|
|
4524
|
+
const m = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/);
|
|
4525
|
+
if (m) paths.push(m[1].trim());
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
return { id, description, paths: paths.length ? paths : ["*"], content: body, filePath };
|
|
4529
|
+
} catch {
|
|
4530
|
+
return null;
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
function loadPathRules() {
|
|
4534
|
+
ensureRulesDir();
|
|
4535
|
+
try {
|
|
4536
|
+
const dir = rulesDir();
|
|
4537
|
+
return fs6.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => parseRuleFile(path7.join(dir, f))).filter((r) => r !== null);
|
|
4538
|
+
} catch {
|
|
4539
|
+
return [];
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
function matchRulePath(pattern, target) {
|
|
4543
|
+
if (pattern === "*" || pattern === "**") return true;
|
|
4544
|
+
const p = pattern.replace(/\\/g, "/").toLowerCase();
|
|
4545
|
+
const t = target.replace(/\\/g, "/").toLowerCase();
|
|
4546
|
+
if (p.endsWith("/**")) {
|
|
4547
|
+
const prefix = p.slice(0, -3);
|
|
4548
|
+
return t === prefix || t.startsWith(prefix + "/");
|
|
4549
|
+
}
|
|
4550
|
+
if (!/[*?]/.test(p) && (t === p || t.startsWith(p + "/"))) return true;
|
|
4551
|
+
let re = "";
|
|
4552
|
+
for (let i = 0; i < p.length; i++) {
|
|
4553
|
+
const c = p[i];
|
|
4554
|
+
if (c === "*") {
|
|
4555
|
+
if (p[i + 1] === "*") {
|
|
4556
|
+
re += i === 0 ? "(?:.*/)?" : ".*";
|
|
4557
|
+
if (p[i + 2] === "/") i += 2;
|
|
4558
|
+
} else {
|
|
4559
|
+
re += "[^/]*";
|
|
4560
|
+
}
|
|
4561
|
+
} else if (c === "?") {
|
|
4562
|
+
re += "[^/]";
|
|
4563
|
+
} else if (/[.()*+?^${}()|[\]\\]/.test(c)) {
|
|
4564
|
+
re += `\\${c}`;
|
|
4565
|
+
} else {
|
|
4566
|
+
re += c;
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
try {
|
|
4570
|
+
const rx = new RegExp(`^${re}$`);
|
|
4571
|
+
return rx.test(t);
|
|
4572
|
+
} catch {
|
|
4573
|
+
return t === p;
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
function getRulesForScope(scope) {
|
|
4577
|
+
const rules = loadPathRules();
|
|
4578
|
+
if (rules.length === 0) return [];
|
|
4579
|
+
const target = (scope || "").toLowerCase();
|
|
4580
|
+
if (!target) return rules.filter((r) => r.paths.includes("*"));
|
|
4581
|
+
return rules.filter((rule) => rule.paths.some((p) => matchRulePath(p, target)));
|
|
4582
|
+
}
|
|
4583
|
+
function formatPathRules(scope) {
|
|
4584
|
+
const matches = getRulesForScope(scope);
|
|
4585
|
+
if (matches.length === 0) return "";
|
|
4586
|
+
const lines = [
|
|
4587
|
+
`\u{1F4CC} **Path Rules** (${matches.length} rule(s) apply to "${scope || "project"}")`,
|
|
4588
|
+
`\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`,
|
|
4589
|
+
""
|
|
4590
|
+
];
|
|
4591
|
+
for (const rule of matches) {
|
|
4592
|
+
lines.push(`**${rule.description}**`);
|
|
4593
|
+
const bodyLines = rule.content.split("\n").filter((l) => l.trim()).slice(0, 6);
|
|
4594
|
+
for (const bl of bodyLines) {
|
|
4595
|
+
lines.push(` ${bl.substring(0, 140)}`);
|
|
4596
|
+
}
|
|
4597
|
+
if (rule.content.split("\n").filter((l) => l.trim()).length > 6) {
|
|
4598
|
+
lines.push(` \u2026 (full rule in ${rule.filePath})`);
|
|
4599
|
+
}
|
|
4600
|
+
lines.push("");
|
|
4601
|
+
}
|
|
4602
|
+
lines.push(`\u{1F4A1} Path rules load on-demand from ${RULES_DIR}/ \u2014 add/remove .md files anytime.`);
|
|
4603
|
+
return lines.join("\n");
|
|
4604
|
+
}
|
|
4605
|
+
function hasPathRules() {
|
|
4606
|
+
return loadPathRules().length > 0;
|
|
4607
|
+
}
|
|
4608
|
+
var RULES_DIR;
|
|
4609
|
+
var init_kumaPathRules = __esm({
|
|
4610
|
+
"src/engine/kumaPathRules.ts"() {
|
|
4611
|
+
"use strict";
|
|
4612
|
+
init_pathValidator();
|
|
4613
|
+
RULES_DIR = ".kuma/rules";
|
|
4614
|
+
}
|
|
4615
|
+
});
|
|
4616
|
+
|
|
4253
4617
|
// src/engine/kumaCodeScanner.ts
|
|
4254
4618
|
var kumaCodeScanner_exports = {};
|
|
4255
4619
|
__export(kumaCodeScanner_exports, {
|
|
@@ -4258,8 +4622,8 @@ __export(kumaCodeScanner_exports, {
|
|
|
4258
4622
|
scanCodebase: () => scanCodebase
|
|
4259
4623
|
});
|
|
4260
4624
|
import fastGlob from "fast-glob";
|
|
4261
|
-
import
|
|
4262
|
-
import
|
|
4625
|
+
import fs7 from "fs";
|
|
4626
|
+
import path8 from "path";
|
|
4263
4627
|
function getRoot() {
|
|
4264
4628
|
if (!_cachedRoot) _cachedRoot = getProjectRoot();
|
|
4265
4629
|
return _cachedRoot;
|
|
@@ -4270,10 +4634,10 @@ function lineAt(content, index) {
|
|
|
4270
4634
|
function getDirectoryDirs(filePaths) {
|
|
4271
4635
|
const dirs = /* @__PURE__ */ new Set();
|
|
4272
4636
|
for (const fp of filePaths) {
|
|
4273
|
-
let dir =
|
|
4637
|
+
let dir = path8.dirname(fp);
|
|
4274
4638
|
while (dir && dir !== "." && dir !== "/") {
|
|
4275
4639
|
dirs.add(dir);
|
|
4276
|
-
dir =
|
|
4640
|
+
dir = path8.dirname(dir);
|
|
4277
4641
|
}
|
|
4278
4642
|
}
|
|
4279
4643
|
return [...dirs].sort();
|
|
@@ -4283,21 +4647,14 @@ async function scanCodebase(options = {}) {
|
|
|
4283
4647
|
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
4284
4648
|
const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
4285
4649
|
const result = { nodeCount: 0, edgeCount: 0, filesScanned: 0, errors: [] };
|
|
4286
|
-
const includePatterns = options.include || [
|
|
4287
|
-
"src/**/*.{ts,tsx,js,jsx}",
|
|
4288
|
-
"app/**/*.{ts,tsx,js,jsx}",
|
|
4289
|
-
"lib/**/*.{ts,tsx,js,jsx}",
|
|
4290
|
-
"packages/**/*.{ts,tsx,js,jsx}",
|
|
4291
|
-
"server/**/*.{ts,tsx,js,jsx}",
|
|
4292
|
-
"api/**/*.{ts,tsx,js,jsx}"
|
|
4293
|
-
];
|
|
4650
|
+
const includePatterns = options.include || [...DEFAULT_SOURCE_INCLUDES];
|
|
4294
4651
|
if (options.scope) {
|
|
4295
4652
|
includePatterns.length = 0;
|
|
4296
4653
|
if (options.scope.includes("/") || options.scope.includes(".")) {
|
|
4297
4654
|
includePatterns.push(options.scope);
|
|
4298
4655
|
} else {
|
|
4299
|
-
includePatterns.push(`**/*${options.scope}
|
|
4300
|
-
includePatterns.push(`**/*${options.scope}
|
|
4656
|
+
includePatterns.push(`**/*${options.scope}*/**/*.${SOURCE_EXT_GLOB}`);
|
|
4657
|
+
includePatterns.push(`**/*${options.scope}*.${SOURCE_EXT_GLOB}`);
|
|
4301
4658
|
}
|
|
4302
4659
|
}
|
|
4303
4660
|
const ignorePatterns = [
|
|
@@ -4330,16 +4687,16 @@ async function scanCodebase(options = {}) {
|
|
|
4330
4687
|
const scannedFilePaths = [];
|
|
4331
4688
|
for (let i = 0; i < scannedCount; i++) {
|
|
4332
4689
|
const filePath = files[i];
|
|
4333
|
-
const fullPath =
|
|
4334
|
-
if (!
|
|
4335
|
-
const stat =
|
|
4690
|
+
const fullPath = path8.join(root, filePath);
|
|
4691
|
+
if (!fs7.existsSync(fullPath)) continue;
|
|
4692
|
+
const stat = fs7.statSync(fullPath);
|
|
4336
4693
|
if (stat.size > maxFileSize) continue;
|
|
4337
4694
|
const mtime = stat.mtimeMs;
|
|
4338
4695
|
const cached = fileCache.get(filePath);
|
|
4339
4696
|
if (cached === mtime && !options.force) continue;
|
|
4340
4697
|
fileCache.set(filePath, mtime);
|
|
4341
4698
|
try {
|
|
4342
|
-
const content =
|
|
4699
|
+
const content = fs7.readFileSync(fullPath, "utf-8");
|
|
4343
4700
|
const parsed = parseFile(filePath, content);
|
|
4344
4701
|
allParsed.push({ filePath, parsed, content });
|
|
4345
4702
|
scannedFilePaths.push(filePath);
|
|
@@ -4382,7 +4739,7 @@ async function scanCodebase(options = {}) {
|
|
|
4382
4739
|
});
|
|
4383
4740
|
result.nodeCount++;
|
|
4384
4741
|
for (const fp of scannedFilePaths) {
|
|
4385
|
-
if (
|
|
4742
|
+
if (path8.dirname(fp) === dir) {
|
|
4386
4743
|
const fileId = nodeId("file", fp);
|
|
4387
4744
|
try {
|
|
4388
4745
|
await addEdge({ sourceId: moduleId, targetId: fileId, type: "owns" });
|
|
@@ -4407,7 +4764,7 @@ function parseFile(filePath, content) {
|
|
|
4407
4764
|
routes: [],
|
|
4408
4765
|
imports: [],
|
|
4409
4766
|
exports: [],
|
|
4410
|
-
isTest:
|
|
4767
|
+
isTest: isTestFile(filePath)
|
|
4411
4768
|
};
|
|
4412
4769
|
const lines = content.split("\n");
|
|
4413
4770
|
const isTsx = /\.(tsx|jsx)$/.test(filePath);
|
|
@@ -4739,17 +5096,20 @@ async function recordParsedFile(filePath, parsed, content, result) {
|
|
|
4739
5096
|
}
|
|
4740
5097
|
function resolveImportPath(fromFile, importPath, root) {
|
|
4741
5098
|
if (!importPath.startsWith(".") && !importPath.startsWith("/")) return null;
|
|
4742
|
-
const fromDir =
|
|
4743
|
-
const exts = [
|
|
5099
|
+
const fromDir = path8.dirname(path8.join(root, fromFile));
|
|
5100
|
+
const exts = [
|
|
5101
|
+
...SOURCE_EXTENSIONS,
|
|
5102
|
+
...SOURCE_EXTENSIONS.map((e) => `/index${e}`)
|
|
5103
|
+
];
|
|
4744
5104
|
for (const ext of exts) {
|
|
4745
|
-
const resolved =
|
|
4746
|
-
if (
|
|
5105
|
+
const resolved = path8.resolve(fromDir, importPath + ext);
|
|
5106
|
+
if (fs7.existsSync(resolved)) return path8.relative(root, resolved);
|
|
4747
5107
|
}
|
|
4748
|
-
const dirPath =
|
|
4749
|
-
if (
|
|
4750
|
-
for (const ext of
|
|
4751
|
-
const indexPath =
|
|
4752
|
-
if (
|
|
5108
|
+
const dirPath = path8.resolve(fromDir, importPath);
|
|
5109
|
+
if (fs7.existsSync(dirPath) && fs7.statSync(dirPath).isDirectory()) {
|
|
5110
|
+
for (const ext of SOURCE_EXTENSIONS) {
|
|
5111
|
+
const indexPath = path8.join(dirPath, `index${ext}`);
|
|
5112
|
+
if (fs7.existsSync(indexPath)) return path8.relative(root, indexPath);
|
|
4753
5113
|
}
|
|
4754
5114
|
}
|
|
4755
5115
|
return null;
|
|
@@ -4784,12 +5144,13 @@ async function scanAndFormat(options = {}) {
|
|
|
4784
5144
|
const result = await scanCodebase(options);
|
|
4785
5145
|
return formatScanResult(result);
|
|
4786
5146
|
}
|
|
4787
|
-
var DEFAULT_MAX_FILES, DEFAULT_MAX_FILE_SIZE, fileCache, _cachedRoot, FUNCTION_DECL_RE, ARROW_FN_RE, TYPED_ARROW_RE, CLASS_RE, IMPORT_RE, JSX_RETURN_RE, JSX_ELEMENT_RE, JSX_IMPLICIT_RE, EXPRESS_ROUTE_RE, HONO_ROUTE_RE, EXPORT_RE, CALL_RE,
|
|
5147
|
+
var DEFAULT_MAX_FILES, DEFAULT_MAX_FILE_SIZE, fileCache, _cachedRoot, FUNCTION_DECL_RE, ARROW_FN_RE, TYPED_ARROW_RE, CLASS_RE, IMPORT_RE, JSX_RETURN_RE, JSX_ELEMENT_RE, JSX_IMPLICIT_RE, EXPRESS_ROUTE_RE, HONO_ROUTE_RE, EXPORT_RE, CALL_RE, knownComponents, knownFunctions, symbolLocations;
|
|
4788
5148
|
var init_kumaCodeScanner = __esm({
|
|
4789
5149
|
"src/engine/kumaCodeScanner.ts"() {
|
|
4790
5150
|
"use strict";
|
|
4791
5151
|
init_pathValidator();
|
|
4792
5152
|
init_kumaGraph();
|
|
5153
|
+
init_languageSupport();
|
|
4793
5154
|
DEFAULT_MAX_FILES = 200;
|
|
4794
5155
|
DEFAULT_MAX_FILE_SIZE = 100 * 1024;
|
|
4795
5156
|
fileCache = /* @__PURE__ */ new Map();
|
|
@@ -4806,7 +5167,6 @@ var init_kumaCodeScanner = __esm({
|
|
|
4806
5167
|
HONO_ROUTE_RE = /c\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/g;
|
|
4807
5168
|
EXPORT_RE = /export\s+(?:default\s+)?(\w+)/g;
|
|
4808
5169
|
CALL_RE = /(\w+)\s*\(/g;
|
|
4809
|
-
TEST_PATTERNS = [".test.", ".spec.", "__tests__", "/test/"];
|
|
4810
5170
|
knownComponents = /* @__PURE__ */ new Set();
|
|
4811
5171
|
knownFunctions = /* @__PURE__ */ new Set();
|
|
4812
5172
|
symbolLocations = /* @__PURE__ */ new Map();
|
|
@@ -5091,18 +5451,18 @@ __export(domainRules_exports, {
|
|
|
5091
5451
|
readLayer: () => readLayer,
|
|
5092
5452
|
writeLayer: () => writeLayer
|
|
5093
5453
|
});
|
|
5094
|
-
import
|
|
5095
|
-
import
|
|
5454
|
+
import fs8 from "fs";
|
|
5455
|
+
import path9 from "path";
|
|
5096
5456
|
function layerFilePath(layer) {
|
|
5097
|
-
return
|
|
5457
|
+
return path9.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
|
|
5098
5458
|
}
|
|
5099
5459
|
function ensureLayerFile(layer) {
|
|
5100
5460
|
const fp = layerFilePath(layer);
|
|
5101
|
-
if (!
|
|
5461
|
+
if (!fs8.existsSync(fp)) {
|
|
5102
5462
|
const template = layer === "domain_rules" ? DOMAIN_RULES_TEMPLATE : layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
|
|
5103
|
-
const dir =
|
|
5104
|
-
if (!
|
|
5105
|
-
|
|
5463
|
+
const dir = path9.dirname(fp);
|
|
5464
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
5465
|
+
fs8.writeFileSync(fp, template, "utf-8");
|
|
5106
5466
|
console.error(`[DomainRules] Auto-created ${LAYER_FILES[layer].filename}`);
|
|
5107
5467
|
}
|
|
5108
5468
|
return fp;
|
|
@@ -5110,7 +5470,7 @@ function ensureLayerFile(layer) {
|
|
|
5110
5470
|
function readLayer(layer) {
|
|
5111
5471
|
try {
|
|
5112
5472
|
const fp = ensureLayerFile(layer);
|
|
5113
|
-
return
|
|
5473
|
+
return fs8.readFileSync(fp, "utf-8");
|
|
5114
5474
|
} catch (err) {
|
|
5115
5475
|
return `Error reading ${layer}: ${err}`;
|
|
5116
5476
|
}
|
|
@@ -5118,7 +5478,7 @@ function readLayer(layer) {
|
|
|
5118
5478
|
function writeLayer(layer, content) {
|
|
5119
5479
|
try {
|
|
5120
5480
|
const fp = ensureLayerFile(layer);
|
|
5121
|
-
|
|
5481
|
+
fs8.writeFileSync(fp, content, "utf-8");
|
|
5122
5482
|
const label = LAYER_FILES[layer].label;
|
|
5123
5483
|
sessionMemory.recordToolCall("kuma_memory", { action: `write_${layer}` });
|
|
5124
5484
|
return `\u2705 **${label}** updated successfully.`;
|
|
@@ -5129,9 +5489,9 @@ function writeLayer(layer, content) {
|
|
|
5129
5489
|
function appendToLayer(layer, entry) {
|
|
5130
5490
|
try {
|
|
5131
5491
|
const fp = ensureLayerFile(layer);
|
|
5132
|
-
const existing =
|
|
5492
|
+
const existing = fs8.readFileSync(fp, "utf-8");
|
|
5133
5493
|
const newContent = existing.trimEnd() + "\n\n" + entry.trim() + "\n";
|
|
5134
|
-
|
|
5494
|
+
fs8.writeFileSync(fp, newContent, "utf-8");
|
|
5135
5495
|
return `\u2705 Entry added to **${LAYER_FILES[layer].label}**.`;
|
|
5136
5496
|
} catch (err) {
|
|
5137
5497
|
return `\u274C Failed to append: ${err}`;
|
|
@@ -5141,11 +5501,11 @@ function getLayersSummary() {
|
|
|
5141
5501
|
const lines = ["\u{1F4DA} **3-Layer Memory Engine**", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"];
|
|
5142
5502
|
for (const layer of ["domain_rules", "arch_flow", "gotcha"]) {
|
|
5143
5503
|
const fp = layerFilePath(layer);
|
|
5144
|
-
const exists =
|
|
5504
|
+
const exists = fs8.existsSync(fp);
|
|
5145
5505
|
const label = LAYER_FILES[layer].label;
|
|
5146
5506
|
if (exists) {
|
|
5147
5507
|
try {
|
|
5148
|
-
const content =
|
|
5508
|
+
const content = fs8.readFileSync(fp, "utf-8");
|
|
5149
5509
|
const lineCount = content.split("\n").length;
|
|
5150
5510
|
const sizeKB = Math.round(Buffer.byteLength(content) / 1024);
|
|
5151
5511
|
lines.push(` ${layer === "domain_rules" ? "\u{1F4CB}" : layer === "arch_flow" ? "\u{1F3D7}\uFE0F" : "\u26A0\uFE0F"} **${label}** \u2014 ${lineCount} lines, ${sizeKB}KB`);
|
|
@@ -5162,7 +5522,7 @@ function getActiveGotchas() {
|
|
|
5162
5522
|
const results = [];
|
|
5163
5523
|
try {
|
|
5164
5524
|
const fp = ensureLayerFile("gotcha");
|
|
5165
|
-
const content =
|
|
5525
|
+
const content = fs8.readFileSync(fp, "utf-8");
|
|
5166
5526
|
const lines = content.split("\n");
|
|
5167
5527
|
let currentSection = "";
|
|
5168
5528
|
let currentFilePath = "";
|
|
@@ -5210,7 +5570,7 @@ function generateDigest() {
|
|
|
5210
5570
|
];
|
|
5211
5571
|
try {
|
|
5212
5572
|
const fp1 = ensureLayerFile("domain_rules");
|
|
5213
|
-
const content =
|
|
5573
|
+
const content = fs8.readFileSync(fp1, "utf-8");
|
|
5214
5574
|
const rules = [];
|
|
5215
5575
|
const workflows = [];
|
|
5216
5576
|
for (const line of content.split("\n")) {
|
|
@@ -5226,7 +5586,7 @@ function generateDigest() {
|
|
|
5226
5586
|
}
|
|
5227
5587
|
try {
|
|
5228
5588
|
const fp2 = ensureLayerFile("arch_flow");
|
|
5229
|
-
const content =
|
|
5589
|
+
const content = fs8.readFileSync(fp2, "utf-8");
|
|
5230
5590
|
const chains = [];
|
|
5231
5591
|
for (const line of content.split("\n")) {
|
|
5232
5592
|
if (line.includes("->") && line.includes("[")) chains.push(line.trim());
|
|
@@ -5370,8 +5730,8 @@ __export(contextDigest_exports, {
|
|
|
5370
5730
|
generateContextDigest: () => generateContextDigest,
|
|
5371
5731
|
generateDigest: () => generateContextDigest
|
|
5372
5732
|
});
|
|
5373
|
-
import
|
|
5374
|
-
import
|
|
5733
|
+
import fs9 from "fs";
|
|
5734
|
+
import path10 from "path";
|
|
5375
5735
|
function detectTechStack() {
|
|
5376
5736
|
const root = getProjectRoot();
|
|
5377
5737
|
const stack = {
|
|
@@ -5383,12 +5743,12 @@ function detectTechStack() {
|
|
|
5383
5743
|
monorepo: false
|
|
5384
5744
|
};
|
|
5385
5745
|
try {
|
|
5386
|
-
const pkgPath =
|
|
5387
|
-
if (
|
|
5388
|
-
const pkg = JSON.parse(
|
|
5746
|
+
const pkgPath = path10.join(root, "package.json");
|
|
5747
|
+
if (fs9.existsSync(pkgPath)) {
|
|
5748
|
+
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
5389
5749
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5390
|
-
if (
|
|
5391
|
-
else if (
|
|
5750
|
+
if (fs9.existsSync(path10.join(root, "pnpm-lock.yaml"))) stack.packageManager = "pnpm";
|
|
5751
|
+
else if (fs9.existsSync(path10.join(root, "yarn.lock"))) stack.packageManager = "yarn";
|
|
5392
5752
|
else stack.packageManager = "npm";
|
|
5393
5753
|
stack.monorepo = !!pkg.workspaces;
|
|
5394
5754
|
if (deps.next) stack.framework = "Next.js";
|
|
@@ -5411,13 +5771,13 @@ function detectTechStack() {
|
|
|
5411
5771
|
else if (deps.mocha) stack.testRunner = "Mocha";
|
|
5412
5772
|
else if (deps.playwright) stack.testRunner = "Playwright";
|
|
5413
5773
|
else if (deps.cypress) stack.testRunner = "Cypress";
|
|
5414
|
-
if (
|
|
5774
|
+
if (fs9.existsSync(path10.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
|
|
5415
5775
|
if (deps.typescript) stack.languages.push("TypeScript");
|
|
5416
|
-
if (
|
|
5417
|
-
if (
|
|
5418
|
-
if (
|
|
5419
|
-
if (
|
|
5420
|
-
if (
|
|
5776
|
+
if (fs9.existsSync(path10.join(root, "jsconfig.json"))) stack.languages.push("JavaScript");
|
|
5777
|
+
if (fs9.existsSync(path10.join(root, "go.mod"))) stack.languages.push("Go");
|
|
5778
|
+
if (fs9.existsSync(path10.join(root, "Cargo.toml"))) stack.languages.push("Rust");
|
|
5779
|
+
if (fs9.existsSync(path10.join(root, "pyproject.toml"))) stack.languages.push("Python");
|
|
5780
|
+
if (fs9.existsSync(path10.join(root, "Gemfile"))) stack.languages.push("Ruby");
|
|
5421
5781
|
if (stack.languages.length === 0) stack.languages.push("JavaScript");
|
|
5422
5782
|
}
|
|
5423
5783
|
} catch {
|
|
@@ -5448,13 +5808,13 @@ function detectEntryPoints() {
|
|
|
5448
5808
|
"lib/index.ts"
|
|
5449
5809
|
];
|
|
5450
5810
|
for (const c of candidates) {
|
|
5451
|
-
if (
|
|
5811
|
+
if (fs9.existsSync(path10.join(root, c))) {
|
|
5452
5812
|
entryPoints.push(c);
|
|
5453
5813
|
}
|
|
5454
5814
|
}
|
|
5455
5815
|
try {
|
|
5456
|
-
const pkg = JSON.parse(
|
|
5457
|
-
if (pkg.main &&
|
|
5816
|
+
const pkg = JSON.parse(fs9.readFileSync(path10.join(root, "package.json"), "utf-8"));
|
|
5817
|
+
if (pkg.main && fs9.existsSync(path10.join(root, pkg.main))) {
|
|
5458
5818
|
if (!entryPoints.includes(pkg.main)) entryPoints.push(pkg.main);
|
|
5459
5819
|
}
|
|
5460
5820
|
} catch {
|
|
@@ -5500,9 +5860,9 @@ async function generateContextDigest() {
|
|
|
5500
5860
|
lines.push(`\u{1F6AA} **Entry**: ${entryPoints.slice(0, 3).join(", ")}`);
|
|
5501
5861
|
}
|
|
5502
5862
|
try {
|
|
5503
|
-
const ruleFile =
|
|
5504
|
-
if (
|
|
5505
|
-
const content =
|
|
5863
|
+
const ruleFile = path10.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
|
|
5864
|
+
if (fs9.existsSync(ruleFile)) {
|
|
5865
|
+
const content = fs9.readFileSync(ruleFile, "utf-8");
|
|
5506
5866
|
const rules = content.split("\n").filter((l) => /^- Rule \d+:?\s*/i.test(l)).map((l) => l.replace(/^- Rule \d+:?\s*/i, "").trim()).filter(Boolean);
|
|
5507
5867
|
if (rules.length > 0) {
|
|
5508
5868
|
lines.push(`\u{1F4CB} **Rules**: ${rules.slice(0, 5).join(" | ")}`);
|
|
@@ -5548,15 +5908,15 @@ __export(kumaDriftDetector_exports, {
|
|
|
5548
5908
|
getDriftSummary: () => getDriftSummary,
|
|
5549
5909
|
hashFile: () => hashFile
|
|
5550
5910
|
});
|
|
5551
|
-
import
|
|
5552
|
-
import
|
|
5911
|
+
import fs10 from "fs";
|
|
5912
|
+
import path11 from "path";
|
|
5553
5913
|
import crypto3 from "crypto";
|
|
5554
5914
|
function hashFile(filePath) {
|
|
5555
5915
|
try {
|
|
5556
5916
|
const root = getProjectRoot();
|
|
5557
|
-
const fullPath =
|
|
5558
|
-
if (!
|
|
5559
|
-
const content =
|
|
5917
|
+
const fullPath = path11.resolve(root, filePath);
|
|
5918
|
+
if (!fs10.existsSync(fullPath)) return null;
|
|
5919
|
+
const content = fs10.readFileSync(fullPath, "utf-8");
|
|
5560
5920
|
return crypto3.createHash("sha256").update(content).digest("hex");
|
|
5561
5921
|
} catch {
|
|
5562
5922
|
return null;
|
|
@@ -5636,8 +5996,8 @@ async function detectDrift() {
|
|
|
5636
5996
|
let hasStaleFile = false;
|
|
5637
5997
|
for (const fp of filePaths) {
|
|
5638
5998
|
if (!fp) continue;
|
|
5639
|
-
const fullPath =
|
|
5640
|
-
if (!
|
|
5999
|
+
const fullPath = path11.resolve(getProjectRoot(), fp);
|
|
6000
|
+
if (!fs10.existsSync(fullPath)) {
|
|
5641
6001
|
hasStaleFile = true;
|
|
5642
6002
|
break;
|
|
5643
6003
|
}
|
|
@@ -5668,12 +6028,12 @@ function computeScopeHash(scope) {
|
|
|
5668
6028
|
hash.update(scope);
|
|
5669
6029
|
let found = false;
|
|
5670
6030
|
try {
|
|
5671
|
-
const files =
|
|
6031
|
+
const files = fs10.readdirSync(root);
|
|
5672
6032
|
for (const file of files) {
|
|
5673
6033
|
if (file.toLowerCase().includes(scope.toLowerCase())) {
|
|
5674
6034
|
try {
|
|
5675
|
-
if (
|
|
5676
|
-
const content =
|
|
6035
|
+
if (fs10.statSync(path11.join(root, file)).isFile()) {
|
|
6036
|
+
const content = fs10.readFileSync(path11.join(root, file), "utf-8");
|
|
5677
6037
|
hash.update(file);
|
|
5678
6038
|
hash.update(content.substring(0, 1e4));
|
|
5679
6039
|
found = true;
|
|
@@ -5684,11 +6044,11 @@ function computeScopeHash(scope) {
|
|
|
5684
6044
|
}
|
|
5685
6045
|
} catch {
|
|
5686
6046
|
}
|
|
5687
|
-
const researchDir =
|
|
5688
|
-
if (
|
|
5689
|
-
const scopeFile =
|
|
5690
|
-
if (
|
|
5691
|
-
const content =
|
|
6047
|
+
const researchDir = path11.join(root, ".kuma", "research");
|
|
6048
|
+
if (fs10.existsSync(researchDir)) {
|
|
6049
|
+
const scopeFile = path11.join(researchDir, `${scope}.json`);
|
|
6050
|
+
if (fs10.existsSync(scopeFile)) {
|
|
6051
|
+
const content = fs10.readFileSync(scopeFile, "utf-8");
|
|
5692
6052
|
hash.update(content);
|
|
5693
6053
|
found = true;
|
|
5694
6054
|
}
|
|
@@ -5786,26 +6146,26 @@ __export(kumaProgressiveContext_exports, {
|
|
|
5786
6146
|
getSkillBoundary: () => getSkillBoundary,
|
|
5787
6147
|
loadSection: () => loadSection
|
|
5788
6148
|
});
|
|
5789
|
-
import
|
|
5790
|
-
import
|
|
6149
|
+
import fs11 from "fs";
|
|
6150
|
+
import path12 from "path";
|
|
5791
6151
|
async function getProjectMeta() {
|
|
5792
6152
|
const root = getProjectRoot();
|
|
5793
6153
|
const name = root.split("/").pop() || "unknown";
|
|
5794
6154
|
const stack = [];
|
|
5795
|
-
if (
|
|
6155
|
+
if (fs11.existsSync(path12.join(root, "package.json"))) {
|
|
5796
6156
|
try {
|
|
5797
|
-
const pkg = JSON.parse(
|
|
6157
|
+
const pkg = JSON.parse(fs11.readFileSync(path12.join(root, "package.json"), "utf-8"));
|
|
5798
6158
|
if (pkg.dependencies?.next) stack.push("Next.js");
|
|
5799
6159
|
else if (pkg.dependencies?.react) stack.push("React");
|
|
5800
6160
|
if (pkg.devDependencies?.typescript) stack.push("TypeScript");
|
|
5801
|
-
if (
|
|
5802
|
-
else if (
|
|
6161
|
+
if (fs11.existsSync(path12.join(root, "pnpm-lock.yaml"))) stack.push("pnpm");
|
|
6162
|
+
else if (fs11.existsSync(path12.join(root, "yarn.lock"))) stack.push("yarn");
|
|
5803
6163
|
} catch {
|
|
5804
6164
|
}
|
|
5805
6165
|
}
|
|
5806
|
-
if (
|
|
5807
|
-
if (
|
|
5808
|
-
if (
|
|
6166
|
+
if (fs11.existsSync(path12.join(root, "go.mod"))) stack.push("Go");
|
|
6167
|
+
if (fs11.existsSync(path12.join(root, "Cargo.toml"))) stack.push("Rust");
|
|
6168
|
+
if (fs11.existsSync(path12.join(root, "pyproject.toml"))) stack.push("Python");
|
|
5809
6169
|
let nodeCount = 0;
|
|
5810
6170
|
let edgeCount = 0;
|
|
5811
6171
|
try {
|
|
@@ -5816,10 +6176,10 @@ async function getProjectMeta() {
|
|
|
5816
6176
|
edgeCount = edgeRes[0]?.values[0]?.[0] ?? 0;
|
|
5817
6177
|
} catch {
|
|
5818
6178
|
}
|
|
5819
|
-
const kumaDir =
|
|
6179
|
+
const kumaDir = path12.join(root, ".kuma");
|
|
5820
6180
|
const layerStatus = {
|
|
5821
|
-
domainRules:
|
|
5822
|
-
archFlow:
|
|
6181
|
+
domainRules: fs11.existsSync(path12.join(kumaDir, "DOMAIN_RULES.md")),
|
|
6182
|
+
archFlow: fs11.existsSync(path12.join(kumaDir, "ARCHITECTURE_FLOW.md")),
|
|
5823
6183
|
gotchas: getActiveGotchas().length
|
|
5824
6184
|
};
|
|
5825
6185
|
let hasDecisions = false;
|
|
@@ -6018,8 +6378,8 @@ __export(kumaSearch_exports, {
|
|
|
6018
6378
|
formatHybridResults: () => formatHybridResults,
|
|
6019
6379
|
hybridSearch: () => hybridSearch
|
|
6020
6380
|
});
|
|
6021
|
-
import
|
|
6022
|
-
import
|
|
6381
|
+
import fs13 from "fs";
|
|
6382
|
+
import path14 from "path";
|
|
6023
6383
|
function expandQueryTerms(query) {
|
|
6024
6384
|
const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
|
|
6025
6385
|
const expanded = /* @__PURE__ */ new Map();
|
|
@@ -6040,6 +6400,11 @@ function expandQueryTerms(query) {
|
|
|
6040
6400
|
}
|
|
6041
6401
|
return expanded;
|
|
6042
6402
|
}
|
|
6403
|
+
function recencyScore(epochSec) {
|
|
6404
|
+
if (!epochSec) return 0.5;
|
|
6405
|
+
const ageDays = Math.max(0, (Date.now() / 1e3 - epochSec) / 86400);
|
|
6406
|
+
return Math.max(0, Math.min(1, 1 - ageDays / 90));
|
|
6407
|
+
}
|
|
6043
6408
|
async function buildSearchVectors() {
|
|
6044
6409
|
if (_vectorCache && Date.now() - _vectorCache.builtAt < VECTOR_CACHE_TTL) {
|
|
6045
6410
|
return _vectorCache.vectors;
|
|
@@ -6047,7 +6412,7 @@ async function buildSearchVectors() {
|
|
|
6047
6412
|
const vectors = [];
|
|
6048
6413
|
try {
|
|
6049
6414
|
const db = await getDb();
|
|
6050
|
-
const stmt = db.prepare("SELECT id, name, metadata, file_path FROM nodes ORDER BY updated_at DESC LIMIT
|
|
6415
|
+
const stmt = db.prepare("SELECT id, name, metadata, file_path, updated_at FROM nodes ORDER BY updated_at DESC LIMIT " + SCALE_LIMITS.maxNodes);
|
|
6051
6416
|
while (stmt.step()) {
|
|
6052
6417
|
const row = stmt.getAsObject();
|
|
6053
6418
|
const name = row.name || "";
|
|
@@ -6068,7 +6433,8 @@ async function buildSearchVectors() {
|
|
|
6068
6433
|
vectors.push({
|
|
6069
6434
|
terms: termCounts,
|
|
6070
6435
|
source: name || row.id,
|
|
6071
|
-
sourceType: "graph"
|
|
6436
|
+
sourceType: "graph",
|
|
6437
|
+
recency: recencyScore(row.updated_at)
|
|
6072
6438
|
});
|
|
6073
6439
|
}
|
|
6074
6440
|
stmt.free();
|
|
@@ -6076,12 +6442,13 @@ async function buildSearchVectors() {
|
|
|
6076
6442
|
}
|
|
6077
6443
|
try {
|
|
6078
6444
|
const root = getProjectRoot();
|
|
6079
|
-
const memDir =
|
|
6080
|
-
if (
|
|
6081
|
-
const files =
|
|
6445
|
+
const memDir = path14.join(root, ".kuma", "memories");
|
|
6446
|
+
if (fs13.existsSync(memDir)) {
|
|
6447
|
+
const files = fs13.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
|
|
6082
6448
|
for (const file of files) {
|
|
6083
6449
|
try {
|
|
6084
|
-
const
|
|
6450
|
+
const fullPath = path14.join(memDir, file);
|
|
6451
|
+
const content = fs13.readFileSync(fullPath, "utf-8");
|
|
6085
6452
|
const text = content.toLowerCase();
|
|
6086
6453
|
const rawTerms = extractTerms(text);
|
|
6087
6454
|
const termCounts = /* @__PURE__ */ new Map();
|
|
@@ -6091,7 +6458,8 @@ async function buildSearchVectors() {
|
|
|
6091
6458
|
vectors.push({
|
|
6092
6459
|
terms: termCounts,
|
|
6093
6460
|
source: file.replace(/\.md$/, ""),
|
|
6094
|
-
sourceType: "memory"
|
|
6461
|
+
sourceType: "memory",
|
|
6462
|
+
recency: recencyScore(Math.floor(fs13.statSync(fullPath).mtimeMs / 1e3))
|
|
6095
6463
|
});
|
|
6096
6464
|
} catch {
|
|
6097
6465
|
}
|
|
@@ -6101,7 +6469,7 @@ async function buildSearchVectors() {
|
|
|
6101
6469
|
}
|
|
6102
6470
|
try {
|
|
6103
6471
|
const db = await getDb();
|
|
6104
|
-
const stmt = db.prepare("SELECT scope, record FROM research_cache ORDER BY updated_at DESC LIMIT
|
|
6472
|
+
const stmt = db.prepare("SELECT scope, record, updated_at FROM research_cache ORDER BY updated_at DESC LIMIT " + SCALE_LIMITS.maxResearch);
|
|
6105
6473
|
while (stmt.step()) {
|
|
6106
6474
|
const row = stmt.getAsObject();
|
|
6107
6475
|
const scope = row.scope || "";
|
|
@@ -6115,7 +6483,8 @@ async function buildSearchVectors() {
|
|
|
6115
6483
|
vectors.push({
|
|
6116
6484
|
terms: termCounts,
|
|
6117
6485
|
source: scope,
|
|
6118
|
-
sourceType: "research"
|
|
6486
|
+
sourceType: "research",
|
|
6487
|
+
recency: recencyScore(row.updated_at)
|
|
6119
6488
|
});
|
|
6120
6489
|
}
|
|
6121
6490
|
stmt.free();
|
|
@@ -6217,7 +6586,8 @@ async function hybridSearch(query, limit = 10) {
|
|
|
6217
6586
|
const existing = results.find((r) => r.source === vec.source);
|
|
6218
6587
|
if (existing) {
|
|
6219
6588
|
existing.vectorScore = vectorScore;
|
|
6220
|
-
|
|
6589
|
+
const recencyBoost = Math.round((vec.recency ?? 0.5) * 10);
|
|
6590
|
+
existing.score = Math.min(100, Math.round(existing.keywordScore * 0.6 + vectorScore * 0.4 + recencyBoost));
|
|
6221
6591
|
for (const [term] of vec.terms) {
|
|
6222
6592
|
if (queryVector.has(term) && !existing.matchedTerms.includes(term)) {
|
|
6223
6593
|
existing.matchedTerms.push(term);
|
|
@@ -6228,10 +6598,11 @@ async function hybridSearch(query, limit = 10) {
|
|
|
6228
6598
|
for (const [term] of vec.terms) {
|
|
6229
6599
|
if (queryVector.has(term)) matchedTerms.push(term);
|
|
6230
6600
|
}
|
|
6601
|
+
const recencyBoost = Math.round((vec.recency ?? 0.5) * 10);
|
|
6231
6602
|
results.push({
|
|
6232
6603
|
source: vec.source,
|
|
6233
6604
|
sourceType: vec.sourceType,
|
|
6234
|
-
score: Math.round(vectorScore * 0.4),
|
|
6605
|
+
score: Math.min(100, Math.round(vectorScore * 0.4 + recencyBoost)),
|
|
6235
6606
|
keywordScore: 0,
|
|
6236
6607
|
vectorScore,
|
|
6237
6608
|
matchedTerms
|
|
@@ -6286,7 +6657,7 @@ async function buildGraphConnectivity() {
|
|
|
6286
6657
|
let totalEdges = 0;
|
|
6287
6658
|
try {
|
|
6288
6659
|
const db = await getDb();
|
|
6289
|
-
const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT
|
|
6660
|
+
const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT " + SCALE_LIMITS.maxEdges);
|
|
6290
6661
|
while (stmt.step()) {
|
|
6291
6662
|
const row = stmt.getAsObject();
|
|
6292
6663
|
const src = row.source_id || "";
|
|
@@ -6336,7 +6707,7 @@ function clearSearchCache() {
|
|
|
6336
6707
|
_vectorCache = null;
|
|
6337
6708
|
clearConnectivityCache();
|
|
6338
6709
|
}
|
|
6339
|
-
var SYNONYM_MAP, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
|
|
6710
|
+
var SYNONYM_MAP, SCALE_LIMITS, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
|
|
6340
6711
|
var init_kumaSearch = __esm({
|
|
6341
6712
|
"src/engine/kumaSearch.ts"() {
|
|
6342
6713
|
"use strict";
|
|
@@ -6381,6 +6752,16 @@ var init_kumaSearch = __esm({
|
|
|
6381
6752
|
update: ["update", "modify", "change", "edit", "patch"],
|
|
6382
6753
|
delete: ["delete", "remove", "drop", "clear", "purge"]
|
|
6383
6754
|
};
|
|
6755
|
+
SCALE_LIMITS = {
|
|
6756
|
+
maxNodes: 5e3,
|
|
6757
|
+
// was 500 — graphs grow with project size
|
|
6758
|
+
maxMemoryFiles: 30,
|
|
6759
|
+
// was 10
|
|
6760
|
+
maxResearch: 300,
|
|
6761
|
+
// was 50
|
|
6762
|
+
maxEdges: 2e4
|
|
6763
|
+
// was 2000 (graph connectivity)
|
|
6764
|
+
};
|
|
6384
6765
|
_vectorCache = null;
|
|
6385
6766
|
VECTOR_CACHE_TTL = 3e5;
|
|
6386
6767
|
_connectivityCache = null;
|
|
@@ -6393,8 +6774,8 @@ var kumaMiner_exports = {};
|
|
|
6393
6774
|
__export(kumaMiner_exports, {
|
|
6394
6775
|
mineHistoricalDecisions: () => mineHistoricalDecisions
|
|
6395
6776
|
});
|
|
6396
|
-
import
|
|
6397
|
-
import
|
|
6777
|
+
import fs14 from "fs";
|
|
6778
|
+
import path15 from "path";
|
|
6398
6779
|
import { execSync as execSync4 } from "child_process";
|
|
6399
6780
|
import fastGlob2 from "fast-glob";
|
|
6400
6781
|
async function mineHistoricalDecisions(options = {}) {
|
|
@@ -6469,7 +6850,7 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
6469
6850
|
}
|
|
6470
6851
|
if (candidates.length < limit) {
|
|
6471
6852
|
try {
|
|
6472
|
-
const scopePattern = options.scope ? `**/*${options.scope}*.*` :
|
|
6853
|
+
const scopePattern = options.scope ? `**/*${options.scope}*.*` : `**/*.{${SOURCE_EXT_GLOB.slice(1, -1)},md}`;
|
|
6473
6854
|
const files = await fastGlob2(scopePattern, {
|
|
6474
6855
|
cwd: root,
|
|
6475
6856
|
ignore: ["node_modules/**", "dist/**", ".kuma/**", ".git/**", "coverage/**"],
|
|
@@ -6484,9 +6865,9 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
6484
6865
|
];
|
|
6485
6866
|
for (const file of files.slice(0, 50)) {
|
|
6486
6867
|
if (candidates.length >= limit) break;
|
|
6487
|
-
const fullPath =
|
|
6488
|
-
if (!
|
|
6489
|
-
const content =
|
|
6868
|
+
const fullPath = path15.join(root, file);
|
|
6869
|
+
if (!fs14.existsSync(fullPath)) continue;
|
|
6870
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
6490
6871
|
const lines = content.split("\n");
|
|
6491
6872
|
for (let i = 0; i < lines.length; i++) {
|
|
6492
6873
|
const l = lines[i];
|
|
@@ -6561,6 +6942,7 @@ var init_kumaMiner = __esm({
|
|
|
6561
6942
|
"use strict";
|
|
6562
6943
|
init_kumaDb();
|
|
6563
6944
|
init_kumaMemory();
|
|
6945
|
+
init_languageSupport();
|
|
6564
6946
|
}
|
|
6565
6947
|
});
|
|
6566
6948
|
|
|
@@ -6793,8 +7175,8 @@ __export(kumaGitHarvester_exports, {
|
|
|
6793
7175
|
installGitHook: () => installGitHook
|
|
6794
7176
|
});
|
|
6795
7177
|
import { execSync as execSync5 } from "child_process";
|
|
6796
|
-
import
|
|
6797
|
-
import
|
|
7178
|
+
import fs15 from "fs";
|
|
7179
|
+
import path16 from "path";
|
|
6798
7180
|
function getRecentCommits(count = 5) {
|
|
6799
7181
|
const root = getProjectRoot();
|
|
6800
7182
|
try {
|
|
@@ -6974,11 +7356,11 @@ async function harvestGitHistory(options) {
|
|
|
6974
7356
|
}
|
|
6975
7357
|
function installGitHook() {
|
|
6976
7358
|
const root = getProjectRoot();
|
|
6977
|
-
const hooksDir =
|
|
6978
|
-
if (!
|
|
7359
|
+
const hooksDir = path16.join(root, ".git", "hooks");
|
|
7360
|
+
if (!fs15.existsSync(hooksDir)) {
|
|
6979
7361
|
return "\u26A0\uFE0F Not a git repository \u2014 no .git/hooks/ directory found.";
|
|
6980
7362
|
}
|
|
6981
|
-
const hookPath =
|
|
7363
|
+
const hookPath = path16.join(hooksDir, "post-commit");
|
|
6982
7364
|
const hookContent = `#!/bin/bash
|
|
6983
7365
|
# Kuma auto-harvester hook (post-commit)
|
|
6984
7366
|
# Automatically extracts decisions and gotchas from commits
|
|
@@ -6986,23 +7368,23 @@ if command -v npx &> /dev/null; then
|
|
|
6986
7368
|
npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
|
|
6987
7369
|
fi
|
|
6988
7370
|
`;
|
|
6989
|
-
if (
|
|
6990
|
-
const existing =
|
|
7371
|
+
if (fs15.existsSync(hookPath)) {
|
|
7372
|
+
const existing = fs15.readFileSync(hookPath, "utf-8");
|
|
6991
7373
|
if (existing.includes("kuma")) {
|
|
6992
7374
|
return "\u2705 Kuma git hook already installed.";
|
|
6993
7375
|
}
|
|
6994
7376
|
const backupPath = hookPath + ".backup." + Date.now();
|
|
6995
|
-
|
|
6996
|
-
|
|
7377
|
+
fs15.copyFileSync(hookPath, backupPath);
|
|
7378
|
+
fs15.writeFileSync(hookPath, hookContent, "utf-8");
|
|
6997
7379
|
try {
|
|
6998
|
-
|
|
7380
|
+
fs15.chmodSync(hookPath, 493);
|
|
6999
7381
|
} catch {
|
|
7000
7382
|
}
|
|
7001
|
-
return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${
|
|
7383
|
+
return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${path16.basename(backupPath)})`;
|
|
7002
7384
|
}
|
|
7003
|
-
|
|
7385
|
+
fs15.writeFileSync(hookPath, hookContent, "utf-8");
|
|
7004
7386
|
try {
|
|
7005
|
-
|
|
7387
|
+
fs15.chmodSync(hookPath, 493);
|
|
7006
7388
|
} catch {
|
|
7007
7389
|
}
|
|
7008
7390
|
return "\u2705 Installed kuma post-commit hook.";
|
|
@@ -7099,6 +7481,102 @@ function mineSessionInsights() {
|
|
|
7099
7481
|
}
|
|
7100
7482
|
}
|
|
7101
7483
|
}
|
|
7484
|
+
const dirReads = {};
|
|
7485
|
+
for (const call of toolCalls) {
|
|
7486
|
+
if (call.toolName === "read" || call.toolName === "browser_read") {
|
|
7487
|
+
const file = call.params.filePath || "";
|
|
7488
|
+
if (file) {
|
|
7489
|
+
const dir = file.split("/").slice(0, -1).join("/") || "root";
|
|
7490
|
+
if (!dirReads[dir]) dirReads[dir] = [];
|
|
7491
|
+
dirReads[dir].push(file.split("/").pop() || file);
|
|
7492
|
+
}
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
for (const [dir, files] of Object.entries(dirReads)) {
|
|
7496
|
+
if (files.length >= 3) {
|
|
7497
|
+
const dirName = dir.split("/").pop() || dir;
|
|
7498
|
+
insights.push({
|
|
7499
|
+
type: "feature",
|
|
7500
|
+
scope: dir,
|
|
7501
|
+
content: `${dirName} \u2014 ${files.length} files explored: ${files.slice(0, 5).join(", ")}${files.length > 5 ? "..." : ""}`,
|
|
7502
|
+
confidence: 0.6,
|
|
7503
|
+
source: `directory_exploration:${files.length}_files`
|
|
7504
|
+
});
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
for (let i = 0; i < toolCalls.length - 1; i++) {
|
|
7508
|
+
const a = toolCalls[i];
|
|
7509
|
+
const b = toolCalls[i + 1];
|
|
7510
|
+
if (a.toolName === "test" && b.toolName === "edit") {
|
|
7511
|
+
const file = b.params.filePath || "";
|
|
7512
|
+
if (file) {
|
|
7513
|
+
insights.push({
|
|
7514
|
+
type: "gotcha",
|
|
7515
|
+
scope: file,
|
|
7516
|
+
content: `Test failed then ${file.split("/").pop()} was edited \u2014 likely a bug fix needed`,
|
|
7517
|
+
confidence: 0.7,
|
|
7518
|
+
source: `test_fail_fix`
|
|
7519
|
+
});
|
|
7520
|
+
}
|
|
7521
|
+
}
|
|
7522
|
+
}
|
|
7523
|
+
const importChain = [];
|
|
7524
|
+
for (const call of toolCalls) {
|
|
7525
|
+
if (call.toolName === "read" || call.toolName === "browser_read") {
|
|
7526
|
+
const file = call.params.filePath || "";
|
|
7527
|
+
if (file && file.endsWith(".ts")) {
|
|
7528
|
+
importChain.push(file);
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
}
|
|
7532
|
+
if (importChain.length >= 4) {
|
|
7533
|
+
const uniqueFiles = [...new Set(importChain)];
|
|
7534
|
+
if (uniqueFiles.length >= 3) {
|
|
7535
|
+
insights.push({
|
|
7536
|
+
type: "arch_flow",
|
|
7537
|
+
scope: uniqueFiles[0],
|
|
7538
|
+
content: `Import chain traced: ${uniqueFiles.slice(0, 5).map((f) => f.split("/").pop() || f).join(" \u2192 ")}`,
|
|
7539
|
+
confidence: 0.6,
|
|
7540
|
+
source: `import_chain:${uniqueFiles.length}_files`
|
|
7541
|
+
});
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
const routeFiles = [];
|
|
7545
|
+
for (const call of toolCalls) {
|
|
7546
|
+
if (call.toolName === "read" || call.toolName === "browser_read") {
|
|
7547
|
+
const file = call.params.filePath || "";
|
|
7548
|
+
if (file && (file.includes("route") || file.includes("api") || file.includes("controller"))) {
|
|
7549
|
+
routeFiles.push(file.split("/").pop() || file);
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
if (routeFiles.length >= 2) {
|
|
7554
|
+
insights.push({
|
|
7555
|
+
type: "feature",
|
|
7556
|
+
scope: "api",
|
|
7557
|
+
content: `API routes explored: ${routeFiles.slice(0, 5).join(", ")}`,
|
|
7558
|
+
confidence: 0.5,
|
|
7559
|
+
source: `api_route_discovery`
|
|
7560
|
+
});
|
|
7561
|
+
}
|
|
7562
|
+
const componentFiles = [];
|
|
7563
|
+
for (const call of toolCalls) {
|
|
7564
|
+
if (call.toolName === "read" || call.toolName === "browser_read") {
|
|
7565
|
+
const file = call.params.filePath || "";
|
|
7566
|
+
if (file && (file.includes("component") || file.includes(".tsx") || file.includes(".jsx"))) {
|
|
7567
|
+
componentFiles.push(file.split("/").pop() || file);
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7571
|
+
if (componentFiles.length >= 3) {
|
|
7572
|
+
insights.push({
|
|
7573
|
+
type: "arch_flow",
|
|
7574
|
+
scope: "ui",
|
|
7575
|
+
content: `UI components explored: ${componentFiles.slice(0, 5).join(", ")}`,
|
|
7576
|
+
confidence: 0.6,
|
|
7577
|
+
source: `component_discovery:${componentFiles.length}_files`
|
|
7578
|
+
});
|
|
7579
|
+
}
|
|
7102
7580
|
return insights;
|
|
7103
7581
|
}
|
|
7104
7582
|
function formatMinedInsights(insights) {
|
|
@@ -7114,12 +7592,12 @@ function formatMinedInsights(insights) {
|
|
|
7114
7592
|
];
|
|
7115
7593
|
for (let i = 0; i < insights.length; i++) {
|
|
7116
7594
|
const ins = insights[i];
|
|
7117
|
-
const icon = ins.type === "gotcha" ? "\u26A0\uFE0F" : ins.type === "decision" ? "\u{1F4CC}" : "\u{1F500}";
|
|
7595
|
+
const icon = ins.type === "gotcha" ? "\u26A0\uFE0F" : ins.type === "decision" ? "\u{1F4CC}" : ins.type === "feature" ? "\u2B50" : "\u{1F500}";
|
|
7118
7596
|
const conf = Math.round(ins.confidence * 100);
|
|
7119
7597
|
lines.push(`${i + 1}. ${icon} **${ins.type}** (${conf}% confidence)`);
|
|
7120
7598
|
lines.push(` Scope: ${ins.scope}`);
|
|
7121
7599
|
lines.push(` ${ins.content}`);
|
|
7122
|
-
lines.push(` \u2192 \`kuma_memory({ action: "${ins.type}", ${ins.type === "arch_flow" ? "content" : 'scope: \\"${ins.scope}\\", content'}: "${ins.content.substring(0, 60)}..." })\``);
|
|
7600
|
+
lines.push(` \u2192 \`kuma_memory({ action: "${ins.type}", ${ins.type === "arch_flow" ? "content" : ins.type === "feature" ? `title: "\${ins.scope.split('/').pop() || ins.scope}", content` : 'scope: \\"${ins.scope}\\", content'}: "${ins.content.substring(0, 60)}..." })\``);
|
|
7123
7601
|
lines.push("");
|
|
7124
7602
|
}
|
|
7125
7603
|
lines.push("\u{1F4A1} Approve by recording the ones you agree with. Skip the rest.");
|
|
@@ -7301,19 +7779,19 @@ __export(kumaPolicyEngine_exports, {
|
|
|
7301
7779
|
processOverride: () => processOverride,
|
|
7302
7780
|
savePolicyConfig: () => savePolicyConfig
|
|
7303
7781
|
});
|
|
7304
|
-
import
|
|
7305
|
-
import
|
|
7782
|
+
import fs21 from "fs";
|
|
7783
|
+
import path22 from "path";
|
|
7306
7784
|
function loadPolicyConfig() {
|
|
7307
7785
|
try {
|
|
7308
7786
|
const root = getProjectRoot();
|
|
7309
|
-
const policyPath =
|
|
7310
|
-
if (
|
|
7311
|
-
const content =
|
|
7787
|
+
const policyPath = path22.join(root, ".kuma", "POLICY.json");
|
|
7788
|
+
if (fs21.existsSync(policyPath)) {
|
|
7789
|
+
const content = fs21.readFileSync(policyPath, "utf-8");
|
|
7312
7790
|
const config = JSON.parse(content);
|
|
7313
7791
|
return { ...DEFAULT_POLICY_CONFIG, ...config };
|
|
7314
7792
|
}
|
|
7315
|
-
const ymlPath =
|
|
7316
|
-
if (
|
|
7793
|
+
const ymlPath = path22.join(root, ".kuma", "policy.yml");
|
|
7794
|
+
if (fs21.existsSync(ymlPath)) {
|
|
7317
7795
|
console.error("[PolicyEngine] Found legacy policy.yml \u2014 consider migrating to POLICY.json");
|
|
7318
7796
|
}
|
|
7319
7797
|
} catch (err) {
|
|
@@ -7324,10 +7802,10 @@ function loadPolicyConfig() {
|
|
|
7324
7802
|
function savePolicyConfig(config) {
|
|
7325
7803
|
try {
|
|
7326
7804
|
const root = getProjectRoot();
|
|
7327
|
-
const policyPath =
|
|
7328
|
-
const dir =
|
|
7329
|
-
if (!
|
|
7330
|
-
|
|
7805
|
+
const policyPath = path22.join(root, ".kuma", "POLICY.json");
|
|
7806
|
+
const dir = path22.dirname(policyPath);
|
|
7807
|
+
if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
|
|
7808
|
+
fs21.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
|
|
7331
7809
|
return `\u2705 Policy config saved to .kuma/POLICY.json (${config.rules.length} rules)`;
|
|
7332
7810
|
} catch (err) {
|
|
7333
7811
|
return `\u274C Failed to save policy: ${err}`;
|
|
@@ -7612,14 +8090,14 @@ __export(kumaAstValidator_exports, {
|
|
|
7612
8090
|
validateDiff: () => validateDiff,
|
|
7613
8091
|
validateFile: () => validateFile
|
|
7614
8092
|
});
|
|
7615
|
-
import
|
|
7616
|
-
import
|
|
8093
|
+
import fs22 from "fs";
|
|
8094
|
+
import path23 from "path";
|
|
7617
8095
|
function loadImportWhitelist() {
|
|
7618
8096
|
try {
|
|
7619
8097
|
const root = getProjectRoot();
|
|
7620
|
-
const whitelistPath =
|
|
7621
|
-
if (
|
|
7622
|
-
return JSON.parse(
|
|
8098
|
+
const whitelistPath = path23.join(root, ".kuma", "import-whitelist.json");
|
|
8099
|
+
if (fs22.existsSync(whitelistPath)) {
|
|
8100
|
+
return JSON.parse(fs22.readFileSync(whitelistPath, "utf-8"));
|
|
7623
8101
|
}
|
|
7624
8102
|
} catch {
|
|
7625
8103
|
}
|
|
@@ -7645,7 +8123,9 @@ function validateCodeContent(code, filePath) {
|
|
|
7645
8123
|
suggestion: "Restore the assertion or write an equivalent test for the new behavior"
|
|
7646
8124
|
});
|
|
7647
8125
|
}
|
|
7648
|
-
|
|
8126
|
+
const emptyCatch = /catch\s*\([^)]*\)\s*\{\s*\}/.test(line) || /catch\s*\([^)]*\)\s*\{/.test(line) && lines[i + 1]?.trim() === "}" || // Python: bare/empty except blocks (except:, except Exception: pass)
|
|
8127
|
+
/except\s*([^:]*)\s*:\s*(pass|\.\.\.)\s*(#.*)?$/.test(line) || /except\s*([^:]*)\s*:\s*$/.test(line) && /^pass(\s*#.*)?$/.test(lines[i + 1]?.trim() ?? "");
|
|
8128
|
+
if (emptyCatch) {
|
|
7649
8129
|
findings.push({
|
|
7650
8130
|
line: lineNum,
|
|
7651
8131
|
severity: "error",
|
|
@@ -7654,8 +8134,8 @@ function validateCodeContent(code, filePath) {
|
|
|
7654
8134
|
suggestion: "Handle the error: log it, throw a meaningful error, or add a comment explaining why it's safe to ignore"
|
|
7655
8135
|
});
|
|
7656
8136
|
}
|
|
7657
|
-
if (filePath
|
|
7658
|
-
if (/\breturn\s+(true|false|null|undefined|0|""|'')\s*;/.test(line) || /\breturn\s+\d+\s*;/.test(line) || /\bPromise\.resolve\s*\(\s*(true|false|null|undefined|0|""|'')\s*\)/.test(line)) {
|
|
8137
|
+
if (isTestFile(filePath || "")) {
|
|
8138
|
+
if (/\breturn\s+(true|false|null|undefined|0|""|'')\s*;/.test(line) || /\breturn\s+\d+\s*;/.test(line) || /\breturn\s+(True|False|None)\b/.test(line) || /\breturn\s+0\b/.test(line) || /\bPromise\.resolve\s*\(\s*(true|false|null|undefined|0|""|'')\s*\)/.test(line)) {
|
|
7659
8139
|
findings.push({
|
|
7660
8140
|
line: lineNum,
|
|
7661
8141
|
severity: "warning",
|
|
@@ -7665,7 +8145,7 @@ function validateCodeContent(code, filePath) {
|
|
|
7665
8145
|
});
|
|
7666
8146
|
}
|
|
7667
8147
|
}
|
|
7668
|
-
if (!filePath
|
|
8148
|
+
if (!isTestFile(filePath || "") && /\breturn\s+(true|false|null)\s*;/.test(line) && ((lines[i - 1] || "").includes("TODO") || (lines[i - 1] || "").includes("FIXME") || (lines[i - 1] || "").includes("HACK"))) {
|
|
7669
8149
|
findings.push({
|
|
7670
8150
|
line: lineNum,
|
|
7671
8151
|
severity: "warning",
|
|
@@ -7674,18 +8154,15 @@ function validateCodeContent(code, filePath) {
|
|
|
7674
8154
|
suggestion: "Implement proper logic or add a clear comment explaining why this is correct"
|
|
7675
8155
|
});
|
|
7676
8156
|
}
|
|
7677
|
-
const
|
|
7678
|
-
if (
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
|
|
7687
|
-
});
|
|
7688
|
-
}
|
|
8157
|
+
const imported = matchImportPath(line);
|
|
8158
|
+
if (imported && whitelist.blocked.some((b) => imported.includes(b))) {
|
|
8159
|
+
findings.push({
|
|
8160
|
+
line: lineNum,
|
|
8161
|
+
severity: "error",
|
|
8162
|
+
category: "import-violation",
|
|
8163
|
+
message: `Blocked import: "${imported}" is not allowed per security whitelist`,
|
|
8164
|
+
suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
|
|
8165
|
+
});
|
|
7689
8166
|
}
|
|
7690
8167
|
}
|
|
7691
8168
|
return findings;
|
|
@@ -7693,9 +8170,9 @@ function validateCodeContent(code, filePath) {
|
|
|
7693
8170
|
function validateFile(filePath) {
|
|
7694
8171
|
try {
|
|
7695
8172
|
const root = getProjectRoot();
|
|
7696
|
-
const fullPath =
|
|
7697
|
-
if (!
|
|
7698
|
-
const content =
|
|
8173
|
+
const fullPath = path23.resolve(root, filePath);
|
|
8174
|
+
if (!fs22.existsSync(fullPath)) return [];
|
|
8175
|
+
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
7699
8176
|
return validateCodeContent(content, filePath);
|
|
7700
8177
|
} catch {
|
|
7701
8178
|
return [];
|
|
@@ -7755,6 +8232,7 @@ var init_kumaAstValidator = __esm({
|
|
|
7755
8232
|
"src/engine/kumaAstValidator.ts"() {
|
|
7756
8233
|
"use strict";
|
|
7757
8234
|
init_pathValidator();
|
|
8235
|
+
init_languageSupport();
|
|
7758
8236
|
}
|
|
7759
8237
|
});
|
|
7760
8238
|
|
|
@@ -7766,16 +8244,16 @@ __export(kumaCheckpoint_exports, {
|
|
|
7766
8244
|
pruneCheckpoints: () => pruneCheckpoints,
|
|
7767
8245
|
rollbackToCheckpoint: () => rollbackToCheckpoint
|
|
7768
8246
|
});
|
|
7769
|
-
import
|
|
7770
|
-
import
|
|
8247
|
+
import fs23 from "fs";
|
|
8248
|
+
import path24 from "path";
|
|
7771
8249
|
async function createCheckpoint(label, description) {
|
|
7772
8250
|
try {
|
|
7773
8251
|
const root = getProjectRoot();
|
|
7774
|
-
const cpDir =
|
|
7775
|
-
if (
|
|
8252
|
+
const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
8253
|
+
if (fs23.existsSync(cpDir)) {
|
|
7776
8254
|
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
7777
8255
|
}
|
|
7778
|
-
|
|
8256
|
+
fs23.mkdirSync(cpDir, { recursive: true });
|
|
7779
8257
|
const db = await getDb();
|
|
7780
8258
|
const filesStmt = db.prepare(
|
|
7781
8259
|
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
@@ -7784,19 +8262,19 @@ async function createCheckpoint(label, description) {
|
|
|
7784
8262
|
while (filesStmt.step()) {
|
|
7785
8263
|
const row = filesStmt.getAsObject();
|
|
7786
8264
|
const fp = row.file_path;
|
|
7787
|
-
const fullPath =
|
|
7788
|
-
if (
|
|
7789
|
-
const content =
|
|
8265
|
+
const fullPath = path24.resolve(root, fp);
|
|
8266
|
+
if (fs23.existsSync(fullPath)) {
|
|
8267
|
+
const content = fs23.readFileSync(fullPath, "utf-8");
|
|
7790
8268
|
const hash = simpleHash2(content);
|
|
7791
|
-
const fileDir =
|
|
7792
|
-
if (!
|
|
7793
|
-
|
|
8269
|
+
const fileDir = path24.dirname(path24.join(cpDir, "files", fp));
|
|
8270
|
+
if (!fs23.existsSync(fileDir)) fs23.mkdirSync(fileDir, { recursive: true });
|
|
8271
|
+
fs23.writeFileSync(path24.join(cpDir, "files", fp), content, "utf-8");
|
|
7794
8272
|
files.push({ path: fp, hash });
|
|
7795
8273
|
}
|
|
7796
8274
|
}
|
|
7797
8275
|
filesStmt.free();
|
|
7798
8276
|
const dbData = db.export();
|
|
7799
|
-
|
|
8277
|
+
fs23.writeFileSync(path24.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
7800
8278
|
const manifest = {
|
|
7801
8279
|
label,
|
|
7802
8280
|
timestamp: Date.now(),
|
|
@@ -7804,8 +8282,8 @@ async function createCheckpoint(label, description) {
|
|
|
7804
8282
|
dbSnapshot: true,
|
|
7805
8283
|
description
|
|
7806
8284
|
};
|
|
7807
|
-
|
|
7808
|
-
|
|
8285
|
+
fs23.writeFileSync(
|
|
8286
|
+
path24.join(cpDir, "manifest.json"),
|
|
7809
8287
|
JSON.stringify(manifest, null, 2),
|
|
7810
8288
|
"utf-8"
|
|
7811
8289
|
);
|
|
@@ -7831,37 +8309,37 @@ async function createCheckpoint(label, description) {
|
|
|
7831
8309
|
async function rollbackToCheckpoint(label) {
|
|
7832
8310
|
try {
|
|
7833
8311
|
const root = getProjectRoot();
|
|
7834
|
-
const cpDir =
|
|
7835
|
-
const manifestPath =
|
|
7836
|
-
if (!
|
|
8312
|
+
const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
8313
|
+
const manifestPath = path24.join(cpDir, "manifest.json");
|
|
8314
|
+
if (!fs23.existsSync(manifestPath)) {
|
|
7837
8315
|
return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
|
|
7838
8316
|
}
|
|
7839
8317
|
const manifest = JSON.parse(
|
|
7840
|
-
|
|
8318
|
+
fs23.readFileSync(manifestPath, "utf-8")
|
|
7841
8319
|
);
|
|
7842
8320
|
let restored = 0;
|
|
7843
8321
|
let failed = 0;
|
|
7844
8322
|
for (const f of manifest.files) {
|
|
7845
|
-
const snapshotPath =
|
|
7846
|
-
if (
|
|
8323
|
+
const snapshotPath = path24.join(cpDir, "files", f.path);
|
|
8324
|
+
if (fs23.existsSync(snapshotPath)) {
|
|
7847
8325
|
try {
|
|
7848
|
-
const content =
|
|
7849
|
-
const targetPath =
|
|
7850
|
-
const targetDir =
|
|
7851
|
-
if (!
|
|
7852
|
-
|
|
8326
|
+
const content = fs23.readFileSync(snapshotPath, "utf-8");
|
|
8327
|
+
const targetPath = path24.resolve(root, f.path);
|
|
8328
|
+
const targetDir = path24.dirname(targetPath);
|
|
8329
|
+
if (!fs23.existsSync(targetDir)) fs23.mkdirSync(targetDir, { recursive: true });
|
|
8330
|
+
fs23.writeFileSync(targetPath, content, "utf-8");
|
|
7853
8331
|
restored++;
|
|
7854
8332
|
} catch {
|
|
7855
8333
|
failed++;
|
|
7856
8334
|
}
|
|
7857
8335
|
}
|
|
7858
8336
|
}
|
|
7859
|
-
const dbSnapshotPath =
|
|
7860
|
-
if (
|
|
7861
|
-
const kumaDir =
|
|
7862
|
-
const dbPath =
|
|
7863
|
-
const snapshotData =
|
|
7864
|
-
|
|
8337
|
+
const dbSnapshotPath = path24.join(cpDir, "kuma.db");
|
|
8338
|
+
if (fs23.existsSync(dbSnapshotPath)) {
|
|
8339
|
+
const kumaDir = path24.join(root, ".kuma");
|
|
8340
|
+
const dbPath = path24.join(kumaDir, "kuma.db");
|
|
8341
|
+
const snapshotData = fs23.readFileSync(dbSnapshotPath);
|
|
8342
|
+
fs23.writeFileSync(dbPath, snapshotData);
|
|
7865
8343
|
resetDbInstance();
|
|
7866
8344
|
}
|
|
7867
8345
|
sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
|
|
@@ -7886,18 +8364,18 @@ async function rollbackToCheckpoint(label) {
|
|
|
7886
8364
|
function listCheckpoints() {
|
|
7887
8365
|
try {
|
|
7888
8366
|
const root = getProjectRoot();
|
|
7889
|
-
const cpDir =
|
|
7890
|
-
if (!
|
|
8367
|
+
const cpDir = path24.join(root, CHECKPOINT_DIR);
|
|
8368
|
+
if (!fs23.existsSync(cpDir)) {
|
|
7891
8369
|
return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
|
|
7892
8370
|
}
|
|
7893
|
-
const entries =
|
|
8371
|
+
const entries = fs23.readdirSync(cpDir);
|
|
7894
8372
|
const checkpoints = [];
|
|
7895
8373
|
for (const entry of entries) {
|
|
7896
|
-
const manifestPath =
|
|
7897
|
-
if (
|
|
8374
|
+
const manifestPath = path24.join(cpDir, entry, "manifest.json");
|
|
8375
|
+
if (fs23.existsSync(manifestPath)) {
|
|
7898
8376
|
try {
|
|
7899
8377
|
const manifest = JSON.parse(
|
|
7900
|
-
|
|
8378
|
+
fs23.readFileSync(manifestPath, "utf-8")
|
|
7901
8379
|
);
|
|
7902
8380
|
checkpoints.push({ label: entry, manifest });
|
|
7903
8381
|
} catch {
|
|
@@ -7929,12 +8407,12 @@ function listCheckpoints() {
|
|
|
7929
8407
|
function pruneCheckpoints(keep = 5) {
|
|
7930
8408
|
try {
|
|
7931
8409
|
const root = getProjectRoot();
|
|
7932
|
-
const cpDir =
|
|
7933
|
-
if (!
|
|
7934
|
-
const entries =
|
|
8410
|
+
const cpDir = path24.join(root, CHECKPOINT_DIR);
|
|
8411
|
+
if (!fs23.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
|
|
8412
|
+
const entries = fs23.readdirSync(cpDir).map((e) => ({ name: e, time: fs23.statSync(path24.join(cpDir, e)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
7935
8413
|
let removed = 0;
|
|
7936
8414
|
for (let i = keep; i < entries.length; i++) {
|
|
7937
|
-
|
|
8415
|
+
fs23.rmSync(path24.join(cpDir, entries[i].name), { recursive: true, force: true });
|
|
7938
8416
|
removed++;
|
|
7939
8417
|
}
|
|
7940
8418
|
return removed > 0 ? `\u{1F9F9} Pruned **${removed} old checkpoint(s)**. Kept ${Math.min(keep, entries.length)} most recent.` : "\u2705 No checkpoints needed pruning.";
|
|
@@ -7974,18 +8452,18 @@ __export(kumaContractEngine_exports, {
|
|
|
7974
8452
|
loadContracts: () => loadContracts,
|
|
7975
8453
|
runContractChecks: () => runContractChecks
|
|
7976
8454
|
});
|
|
7977
|
-
import
|
|
7978
|
-
import
|
|
8455
|
+
import fs24 from "fs";
|
|
8456
|
+
import path25 from "path";
|
|
7979
8457
|
function contractsDir() {
|
|
7980
|
-
return
|
|
8458
|
+
return path25.join(getProjectRoot(), ".kuma", "contracts");
|
|
7981
8459
|
}
|
|
7982
8460
|
function ensureContractsDir() {
|
|
7983
8461
|
const dir = contractsDir();
|
|
7984
|
-
if (!
|
|
7985
|
-
|
|
7986
|
-
const defaultPath =
|
|
7987
|
-
if (!
|
|
7988
|
-
|
|
8462
|
+
if (!fs24.existsSync(dir)) {
|
|
8463
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
8464
|
+
const defaultPath = path25.join(dir, "default.json");
|
|
8465
|
+
if (!fs24.existsSync(defaultPath)) {
|
|
8466
|
+
fs24.writeFileSync(
|
|
7989
8467
|
defaultPath,
|
|
7990
8468
|
JSON.stringify(DEFAULT_CONTRACTS, null, 2),
|
|
7991
8469
|
"utf-8"
|
|
@@ -7998,10 +8476,10 @@ function loadContracts() {
|
|
|
7998
8476
|
const dir = contractsDir();
|
|
7999
8477
|
const contracts = [];
|
|
8000
8478
|
try {
|
|
8001
|
-
const files =
|
|
8479
|
+
const files = fs24.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
8002
8480
|
for (const file of files) {
|
|
8003
8481
|
try {
|
|
8004
|
-
const content =
|
|
8482
|
+
const content = fs24.readFileSync(path25.join(dir, file), "utf-8");
|
|
8005
8483
|
const config = JSON.parse(content);
|
|
8006
8484
|
contracts.push(config);
|
|
8007
8485
|
} catch (err) {
|
|
@@ -8074,7 +8552,7 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8074
8552
|
switch (condition.type) {
|
|
8075
8553
|
case "file_exists": {
|
|
8076
8554
|
const targetPath = resolveTarget(condition.target, contextPath, root);
|
|
8077
|
-
const exists = targetPath.some((p) =>
|
|
8555
|
+
const exists = targetPath.some((p) => fs24.existsSync(p));
|
|
8078
8556
|
return {
|
|
8079
8557
|
passed: exists,
|
|
8080
8558
|
message: exists ? `\u2705 File exists: ${condition.target}` : `\u274C Required file not found: ${condition.target} (${condition.description})`
|
|
@@ -8082,7 +8560,7 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8082
8560
|
}
|
|
8083
8561
|
case "file_not_exists": {
|
|
8084
8562
|
const targetPath = resolveTarget(condition.target, contextPath, root);
|
|
8085
|
-
const exists = targetPath.some((p) =>
|
|
8563
|
+
const exists = targetPath.some((p) => fs24.existsSync(p));
|
|
8086
8564
|
return {
|
|
8087
8565
|
passed: !exists,
|
|
8088
8566
|
message: !exists ? `\u2705 File does not exist: ${condition.target}` : `\u274C File should not exist: ${condition.target} (${condition.description})`
|
|
@@ -8090,9 +8568,9 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8090
8568
|
}
|
|
8091
8569
|
case "contains": {
|
|
8092
8570
|
if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
|
|
8093
|
-
const fullPath =
|
|
8094
|
-
if (!
|
|
8095
|
-
const content =
|
|
8571
|
+
const fullPath = path25.resolve(root, contextPath);
|
|
8572
|
+
if (!fs24.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
|
|
8573
|
+
const content = fs24.readFileSync(fullPath, "utf-8");
|
|
8096
8574
|
const hasContent = condition.value ? content.includes(condition.value) : false;
|
|
8097
8575
|
return {
|
|
8098
8576
|
passed: hasContent,
|
|
@@ -8104,12 +8582,12 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8104
8582
|
const root2 = getProjectRoot();
|
|
8105
8583
|
const targets = resolveTarget(condition.target, contextPath, root2);
|
|
8106
8584
|
for (const t of targets) {
|
|
8107
|
-
if (
|
|
8108
|
-
const content =
|
|
8585
|
+
if (fs24.existsSync(t)) {
|
|
8586
|
+
const content = fs24.readFileSync(t, "utf-8");
|
|
8109
8587
|
if (condition.value && content.includes(condition.value)) {
|
|
8110
8588
|
return {
|
|
8111
8589
|
passed: false,
|
|
8112
|
-
message: `\u274C Found forbidden pattern "${condition.value}" in ${
|
|
8590
|
+
message: `\u274C Found forbidden pattern "${condition.value}" in ${path25.relative(root2, t)} (${condition.description})`
|
|
8113
8591
|
};
|
|
8114
8592
|
}
|
|
8115
8593
|
}
|
|
@@ -8118,15 +8596,11 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8118
8596
|
}
|
|
8119
8597
|
case "has_test_file": {
|
|
8120
8598
|
if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
|
|
8121
|
-
const
|
|
8122
|
-
const
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
path23.join(dir, `__tests__/${basename}.test.ts`),
|
|
8127
|
-
path23.join(dir, `__tests__/${basename}.spec.ts`)
|
|
8128
|
-
];
|
|
8129
|
-
const hasTest = possibleTests.some((t) => fs23.existsSync(t));
|
|
8599
|
+
const candidates = getTestCandidates(contextPath);
|
|
8600
|
+
const hasTest = candidates.some((t) => {
|
|
8601
|
+
const abs = path25.isAbsolute(t) ? t : path25.resolve(root, t);
|
|
8602
|
+
return fs24.existsSync(abs);
|
|
8603
|
+
});
|
|
8130
8604
|
return {
|
|
8131
8605
|
passed: hasTest,
|
|
8132
8606
|
message: hasTest ? `\u2705 Test file exists` : `\u274C No test file found for ${contextPath} (${condition.description})`
|
|
@@ -8150,18 +8624,24 @@ function resolveTarget(pattern, contextPath, root) {
|
|
|
8150
8624
|
const r = root || getProjectRoot();
|
|
8151
8625
|
if (pattern.startsWith("*")) {
|
|
8152
8626
|
if (contextPath) {
|
|
8153
|
-
const dir =
|
|
8627
|
+
const dir = path25.dirname(path25.resolve(r, contextPath));
|
|
8154
8628
|
const suffix = pattern.replace("*", "");
|
|
8155
8629
|
try {
|
|
8156
|
-
const files =
|
|
8157
|
-
return files.filter((f) =>
|
|
8630
|
+
const files = fs24.readdirSync(dir);
|
|
8631
|
+
return files.filter((f) => {
|
|
8632
|
+
try {
|
|
8633
|
+
return fs24.statSync(path25.join(dir, f)).isFile();
|
|
8634
|
+
} catch {
|
|
8635
|
+
return false;
|
|
8636
|
+
}
|
|
8637
|
+
}).filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path25.join(dir, f));
|
|
8158
8638
|
} catch {
|
|
8159
|
-
return [
|
|
8639
|
+
return [path25.join(dir, pattern.replace("*", ""))];
|
|
8160
8640
|
}
|
|
8161
8641
|
}
|
|
8162
8642
|
return [];
|
|
8163
8643
|
}
|
|
8164
|
-
return [
|
|
8644
|
+
return [path25.resolve(r, pattern)];
|
|
8165
8645
|
}
|
|
8166
8646
|
async function runContractChecks(toolName, filePath, phase = "pre") {
|
|
8167
8647
|
const verdicts = phase === "pre" ? evaluatePreConditions(toolName, filePath) : evaluatePostConditions(toolName, filePath);
|
|
@@ -8223,6 +8703,7 @@ var init_kumaContractEngine = __esm({
|
|
|
8223
8703
|
"src/engine/kumaContractEngine.ts"() {
|
|
8224
8704
|
"use strict";
|
|
8225
8705
|
init_pathValidator();
|
|
8706
|
+
init_languageSupport();
|
|
8226
8707
|
DEFAULT_CONTRACTS = {
|
|
8227
8708
|
version: 1,
|
|
8228
8709
|
contracts: [
|
|
@@ -8249,15 +8730,21 @@ var init_kumaContractEngine = __esm({
|
|
|
8249
8730
|
postConditions: [
|
|
8250
8731
|
{
|
|
8251
8732
|
type: "not_contains",
|
|
8252
|
-
target: "
|
|
8733
|
+
target: "*",
|
|
8253
8734
|
value: "catch {}",
|
|
8254
8735
|
description: "Empty catch blocks suppress errors silently"
|
|
8255
8736
|
},
|
|
8256
8737
|
{
|
|
8257
8738
|
type: "not_contains",
|
|
8258
|
-
target: "
|
|
8739
|
+
target: "*",
|
|
8259
8740
|
value: "catch (e) {}",
|
|
8260
8741
|
description: "Empty catch (e) {} suppresses errors silently"
|
|
8742
|
+
},
|
|
8743
|
+
{
|
|
8744
|
+
type: "not_contains",
|
|
8745
|
+
target: "*",
|
|
8746
|
+
value: "except:",
|
|
8747
|
+
description: "Bare except blocks suppress errors silently (Python)"
|
|
8261
8748
|
}
|
|
8262
8749
|
],
|
|
8263
8750
|
severity: "error"
|
|
@@ -8275,6 +8762,116 @@ var init_kumaContractEngine = __esm({
|
|
|
8275
8762
|
}
|
|
8276
8763
|
});
|
|
8277
8764
|
|
|
8765
|
+
// src/engine/kumaAutoGotcha.ts
|
|
8766
|
+
var kumaAutoGotcha_exports = {};
|
|
8767
|
+
__export(kumaAutoGotcha_exports, {
|
|
8768
|
+
AUTO_GOTCHA_ESCALATE_AT: () => AUTO_GOTCHA_ESCALATE_AT,
|
|
8769
|
+
AUTO_GOTCHA_THRESHOLD: () => AUTO_GOTCHA_THRESHOLD,
|
|
8770
|
+
loadAutoGotchaState: () => loadAutoGotchaState,
|
|
8771
|
+
resetScopeFailures: () => resetScopeFailures,
|
|
8772
|
+
saveAutoGotchaState: () => saveAutoGotchaState,
|
|
8773
|
+
trackVerificationResult: () => trackVerificationResult
|
|
8774
|
+
});
|
|
8775
|
+
import fs25 from "fs";
|
|
8776
|
+
import path26 from "path";
|
|
8777
|
+
function statePath() {
|
|
8778
|
+
return path26.join(getProjectRoot(), STATE_FILE);
|
|
8779
|
+
}
|
|
8780
|
+
function loadAutoGotchaState() {
|
|
8781
|
+
try {
|
|
8782
|
+
const fp = statePath();
|
|
8783
|
+
if (fs25.existsSync(fp)) {
|
|
8784
|
+
const parsed = JSON.parse(fs25.readFileSync(fp, "utf-8"));
|
|
8785
|
+
return {
|
|
8786
|
+
failures: parsed?.failures ?? {},
|
|
8787
|
+
lastRecorded: parsed?.lastRecorded ?? {}
|
|
8788
|
+
};
|
|
8789
|
+
}
|
|
8790
|
+
} catch {
|
|
8791
|
+
}
|
|
8792
|
+
return { failures: {}, lastRecorded: {} };
|
|
8793
|
+
}
|
|
8794
|
+
function saveAutoGotchaState(state) {
|
|
8795
|
+
try {
|
|
8796
|
+
const fp = statePath();
|
|
8797
|
+
const dir = path26.dirname(fp);
|
|
8798
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8799
|
+
fs25.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
|
|
8800
|
+
} catch {
|
|
8801
|
+
}
|
|
8802
|
+
}
|
|
8803
|
+
function resetScopeFailures(scope) {
|
|
8804
|
+
const state = loadAutoGotchaState();
|
|
8805
|
+
if (state.failures[scope] !== void 0 || state.lastRecorded?.[scope] !== void 0) {
|
|
8806
|
+
delete state.failures[scope];
|
|
8807
|
+
if (state.lastRecorded) delete state.lastRecorded[scope];
|
|
8808
|
+
saveAutoGotchaState(state);
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8811
|
+
async function trackVerificationResult(scope, passed) {
|
|
8812
|
+
if (!scope || scope === "session-impact") return { recorded: false, failureCount: 0 };
|
|
8813
|
+
const state = loadAutoGotchaState();
|
|
8814
|
+
const prev = state.failures[scope] ?? 0;
|
|
8815
|
+
state.lastRecorded = state.lastRecorded || {};
|
|
8816
|
+
if (passed) {
|
|
8817
|
+
if (prev > 0 || state.lastRecorded[scope] !== void 0) {
|
|
8818
|
+
delete state.failures[scope];
|
|
8819
|
+
delete state.lastRecorded[scope];
|
|
8820
|
+
saveAutoGotchaState(state);
|
|
8821
|
+
}
|
|
8822
|
+
return { recorded: false, failureCount: 0 };
|
|
8823
|
+
}
|
|
8824
|
+
const failureCount = prev + 1;
|
|
8825
|
+
state.failures[scope] = failureCount;
|
|
8826
|
+
saveAutoGotchaState(state);
|
|
8827
|
+
const lastRecordedAt = state.lastRecorded[scope] ?? 0;
|
|
8828
|
+
const shouldRecord = failureCount === AUTO_GOTCHA_THRESHOLD || failureCount === AUTO_GOTCHA_ESCALATE_AT;
|
|
8829
|
+
if (!shouldRecord || failureCount <= lastRecordedAt) {
|
|
8830
|
+
return { recorded: false, failureCount };
|
|
8831
|
+
}
|
|
8832
|
+
try {
|
|
8833
|
+
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
8834
|
+
const severity = failureCount >= AUTO_GOTCHA_ESCALATE_AT ? "high" : "medium";
|
|
8835
|
+
const result = await addGotcha2({
|
|
8836
|
+
filePath: scope,
|
|
8837
|
+
description: `Verification keeps failing for scope "${scope}"`,
|
|
8838
|
+
severity,
|
|
8839
|
+
workaround: `Run tests locally before touching this area. Failing scope: "${scope}" \u2014 ${failureCount} consecutive failures. Verify with kuma_safety({ action: "verify", scope: "${scope}" }) after fixes.`
|
|
8840
|
+
});
|
|
8841
|
+
state.lastRecorded[scope] = failureCount;
|
|
8842
|
+
saveAutoGotchaState(state);
|
|
8843
|
+
return {
|
|
8844
|
+
recorded: true,
|
|
8845
|
+
failureCount,
|
|
8846
|
+
message: [
|
|
8847
|
+
`\u{1F9E0} **Auto-Gotcha recorded** (self-learning loop)`,
|
|
8848
|
+
`\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`,
|
|
8849
|
+
``,
|
|
8850
|
+
`\u26A0\uFE0F Scope \`${scope}\` failed verification ${failureCount} consecutive times.`,
|
|
8851
|
+
`A gotcha has been recorded automatically so future sessions avoid re-discovering this.`,
|
|
8852
|
+
``,
|
|
8853
|
+
result
|
|
8854
|
+
].join("\n")
|
|
8855
|
+
};
|
|
8856
|
+
} catch (err) {
|
|
8857
|
+
return {
|
|
8858
|
+
recorded: false,
|
|
8859
|
+
failureCount,
|
|
8860
|
+
message: `\u26A0\uFE0F Auto-gotcha recording failed: ${err}`
|
|
8861
|
+
};
|
|
8862
|
+
}
|
|
8863
|
+
}
|
|
8864
|
+
var STATE_FILE, AUTO_GOTCHA_THRESHOLD, AUTO_GOTCHA_ESCALATE_AT;
|
|
8865
|
+
var init_kumaAutoGotcha = __esm({
|
|
8866
|
+
"src/engine/kumaAutoGotcha.ts"() {
|
|
8867
|
+
"use strict";
|
|
8868
|
+
init_pathValidator();
|
|
8869
|
+
STATE_FILE = ".kuma/auto-gotcha.json";
|
|
8870
|
+
AUTO_GOTCHA_THRESHOLD = 2;
|
|
8871
|
+
AUTO_GOTCHA_ESCALATE_AT = 4;
|
|
8872
|
+
}
|
|
8873
|
+
});
|
|
8874
|
+
|
|
8278
8875
|
// src/engine/kumaVerifier.ts
|
|
8279
8876
|
var kumaVerifier_exports = {};
|
|
8280
8877
|
__export(kumaVerifier_exports, {
|
|
@@ -8282,37 +8879,37 @@ __export(kumaVerifier_exports, {
|
|
|
8282
8879
|
getRunningVerificationPid: () => getRunningVerificationPid,
|
|
8283
8880
|
runAutoVerification: () => runAutoVerification
|
|
8284
8881
|
});
|
|
8285
|
-
import
|
|
8286
|
-
import
|
|
8882
|
+
import fs26 from "fs";
|
|
8883
|
+
import path27 from "path";
|
|
8287
8884
|
import { exec } from "child_process";
|
|
8288
8885
|
function getLockDir(root = process.cwd()) {
|
|
8289
|
-
return
|
|
8886
|
+
return path27.resolve(root, LOCK_DIR_NAME);
|
|
8290
8887
|
}
|
|
8291
8888
|
function getPidFile(root = process.cwd()) {
|
|
8292
|
-
return
|
|
8889
|
+
return path27.join(getLockDir(root), "pid");
|
|
8293
8890
|
}
|
|
8294
8891
|
function acquireFileLock(root) {
|
|
8295
8892
|
const lockDir = getLockDir(root);
|
|
8296
8893
|
try {
|
|
8297
|
-
|
|
8298
|
-
|
|
8894
|
+
fs26.mkdirSync(lockDir, { recursive: false });
|
|
8895
|
+
fs26.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
8299
8896
|
return true;
|
|
8300
8897
|
} catch {
|
|
8301
8898
|
try {
|
|
8302
|
-
if (
|
|
8303
|
-
const pid = parseInt(
|
|
8899
|
+
if (fs26.existsSync(getPidFile(root))) {
|
|
8900
|
+
const pid = parseInt(fs26.readFileSync(getPidFile(root), "utf-8"), 10);
|
|
8304
8901
|
try {
|
|
8305
8902
|
process.kill(pid, 0);
|
|
8306
8903
|
return false;
|
|
8307
8904
|
} catch {
|
|
8308
|
-
|
|
8905
|
+
fs26.rmSync(lockDir, { recursive: true, force: true });
|
|
8309
8906
|
return acquireFileLock(root);
|
|
8310
8907
|
}
|
|
8311
8908
|
}
|
|
8312
8909
|
} catch {
|
|
8313
8910
|
}
|
|
8314
8911
|
try {
|
|
8315
|
-
|
|
8912
|
+
fs26.rmSync(lockDir, { recursive: true, force: true });
|
|
8316
8913
|
return acquireFileLock(root);
|
|
8317
8914
|
} catch {
|
|
8318
8915
|
return false;
|
|
@@ -8321,7 +8918,7 @@ function acquireFileLock(root) {
|
|
|
8321
8918
|
}
|
|
8322
8919
|
function releaseFileLock(root) {
|
|
8323
8920
|
try {
|
|
8324
|
-
|
|
8921
|
+
fs26.rmSync(getLockDir(root), { recursive: true, force: true });
|
|
8325
8922
|
} catch {
|
|
8326
8923
|
}
|
|
8327
8924
|
}
|
|
@@ -8344,36 +8941,36 @@ function getRunningVerificationPid() {
|
|
|
8344
8941
|
return _currentProcess?.pid ?? null;
|
|
8345
8942
|
}
|
|
8346
8943
|
function detectTestRunner(root = process.cwd()) {
|
|
8347
|
-
const pkgPath =
|
|
8348
|
-
if (
|
|
8944
|
+
const pkgPath = path27.join(root, "package.json");
|
|
8945
|
+
if (fs26.existsSync(pkgPath)) {
|
|
8349
8946
|
try {
|
|
8350
|
-
const pkg = JSON.parse(
|
|
8947
|
+
const pkg = JSON.parse(fs26.readFileSync(pkgPath, "utf-8"));
|
|
8351
8948
|
if (pkg.scripts && pkg.scripts.test) {
|
|
8352
|
-
if (
|
|
8353
|
-
if (
|
|
8949
|
+
if (fs26.existsSync(path27.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
8950
|
+
if (fs26.existsSync(path27.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
|
|
8354
8951
|
return { runner: "npm", baseCommand: "npm test" };
|
|
8355
8952
|
}
|
|
8356
8953
|
} catch {
|
|
8357
8954
|
}
|
|
8358
8955
|
}
|
|
8359
|
-
if (
|
|
8360
|
-
if (
|
|
8361
|
-
if (
|
|
8362
|
-
if (
|
|
8956
|
+
if (fs26.existsSync(path27.join(root, "pytest.ini")) || fs26.existsSync(path27.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
|
|
8957
|
+
if (fs26.existsSync(path27.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
|
|
8958
|
+
if (fs26.existsSync(path27.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
|
|
8959
|
+
if (fs26.existsSync(path27.join(root, "Makefile"))) {
|
|
8363
8960
|
try {
|
|
8364
|
-
const makefileContent =
|
|
8961
|
+
const makefileContent = fs26.readFileSync(path27.join(root, "Makefile"), "utf-8");
|
|
8365
8962
|
const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
|
|
8366
8963
|
if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
|
|
8367
8964
|
} catch {
|
|
8368
8965
|
}
|
|
8369
8966
|
}
|
|
8370
|
-
if (
|
|
8967
|
+
if (fs26.existsSync(path27.join(root, "package.json"))) {
|
|
8371
8968
|
try {
|
|
8372
|
-
const pkg = JSON.parse(
|
|
8969
|
+
const pkg = JSON.parse(fs26.readFileSync(path27.join(root, "package.json"), "utf-8"));
|
|
8373
8970
|
if (pkg.dependencies || pkg.devDependencies) {
|
|
8374
|
-
if (
|
|
8375
|
-
const srcDir =
|
|
8376
|
-
if (
|
|
8971
|
+
if (fs26.existsSync(path27.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
8972
|
+
const srcDir = path27.join(root, "src");
|
|
8973
|
+
if (fs26.existsSync(srcDir)) return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
|
|
8377
8974
|
}
|
|
8378
8975
|
} catch {
|
|
8379
8976
|
}
|
|
@@ -8492,7 +9089,7 @@ async function runAutoVerification(options = {}) {
|
|
|
8492
9089
|
const heartbeatTimer = setInterval(() => {
|
|
8493
9090
|
if (getLockDir(root)) {
|
|
8494
9091
|
try {
|
|
8495
|
-
|
|
9092
|
+
fs26.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
8496
9093
|
} catch {
|
|
8497
9094
|
}
|
|
8498
9095
|
}
|
|
@@ -8512,8 +9109,24 @@ async function runAutoVerification(options = {}) {
|
|
|
8512
9109
|
if (passed && fakePassPatterns.some((p) => p.test(rawOutput))) {
|
|
8513
9110
|
passed = false;
|
|
8514
9111
|
}
|
|
9112
|
+
const isNoTests = fakePassPatterns.some((p) => p.test(rawOutput));
|
|
8515
9113
|
const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
|
|
8516
9114
|
await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
|
|
9115
|
+
let autoGotchaMessage = null;
|
|
9116
|
+
if (scope !== "session-impact" && !isNoTests) {
|
|
9117
|
+
try {
|
|
9118
|
+
const { trackVerificationResult: trackVerificationResult2 } = await Promise.resolve().then(() => (init_kumaAutoGotcha(), kumaAutoGotcha_exports));
|
|
9119
|
+
const autoResult = await trackVerificationResult2(scope, passed);
|
|
9120
|
+
if (autoResult.message) autoGotchaMessage = autoResult.message;
|
|
9121
|
+
} catch {
|
|
9122
|
+
}
|
|
9123
|
+
} else if (passed) {
|
|
9124
|
+
try {
|
|
9125
|
+
const { resetScopeFailures: resetScopeFailures2 } = await Promise.resolve().then(() => (init_kumaAutoGotcha(), kumaAutoGotcha_exports));
|
|
9126
|
+
resetScopeFailures2(scope);
|
|
9127
|
+
} catch {
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
8517
9130
|
_localRunning = false;
|
|
8518
9131
|
_currentProcess = null;
|
|
8519
9132
|
releaseFileLock(root);
|
|
@@ -8530,7 +9143,6 @@ async function runAutoVerification(options = {}) {
|
|
|
8530
9143
|
""
|
|
8531
9144
|
];
|
|
8532
9145
|
if (!passed) {
|
|
8533
|
-
const isNoTests = fakePassPatterns.some((p) => p.test(rawOutput));
|
|
8534
9146
|
if (isNoTests) {
|
|
8535
9147
|
lines[0] = `\u26AA **Verification: NO TESTS**`;
|
|
8536
9148
|
lines.push("\u2139\uFE0F No tests ran. Install Jest, Vitest, pytest, or similar to enable verification.");
|
|
@@ -8545,6 +9157,9 @@ async function runAutoVerification(options = {}) {
|
|
|
8545
9157
|
lines.push("\u{1F389} All scoped tests passed!");
|
|
8546
9158
|
if (rawOutput) lines.push("```text", rawOutput.substring(0, 800), "```");
|
|
8547
9159
|
}
|
|
9160
|
+
if (autoGotchaMessage) {
|
|
9161
|
+
lines.push("", autoGotchaMessage);
|
|
9162
|
+
}
|
|
8548
9163
|
resolve(lines.join("\n"));
|
|
8549
9164
|
});
|
|
8550
9165
|
_currentProcess = child;
|
|
@@ -8586,8 +9201,8 @@ __export(init_exports, {
|
|
|
8586
9201
|
generateInitMdContent: () => generateInitMdContent,
|
|
8587
9202
|
runInit: () => runInit
|
|
8588
9203
|
});
|
|
8589
|
-
import
|
|
8590
|
-
import
|
|
9204
|
+
import fs27 from "fs";
|
|
9205
|
+
import path28 from "path";
|
|
8591
9206
|
function configFilePath(type) {
|
|
8592
9207
|
switch (type) {
|
|
8593
9208
|
case "claude":
|
|
@@ -8997,10 +9612,10 @@ function generateInitMdContent() {
|
|
|
8997
9612
|
].join("\n");
|
|
8998
9613
|
}
|
|
8999
9614
|
function handleOpencodeSecondary(root, results) {
|
|
9000
|
-
const skillPath =
|
|
9615
|
+
const skillPath = path28.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
|
|
9001
9616
|
if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
|
|
9002
9617
|
try {
|
|
9003
|
-
const dir =
|
|
9618
|
+
const dir = path28.dirname(skillPath);
|
|
9004
9619
|
const content = [
|
|
9005
9620
|
"---",
|
|
9006
9621
|
"name: kuma-mcp",
|
|
@@ -9024,18 +9639,18 @@ function handleOpencodeSecondary(root, results) {
|
|
|
9024
9639
|
"",
|
|
9025
9640
|
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
9026
9641
|
].join("\n");
|
|
9027
|
-
if (
|
|
9028
|
-
const existing =
|
|
9642
|
+
if (fs27.existsSync(skillPath)) {
|
|
9643
|
+
const existing = fs27.readFileSync(skillPath, "utf-8");
|
|
9029
9644
|
if (existing.includes("kuma-mcp")) {
|
|
9030
9645
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
|
|
9031
9646
|
return;
|
|
9032
9647
|
}
|
|
9033
9648
|
const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
|
|
9034
|
-
|
|
9649
|
+
fs27.writeFileSync(skillPath, newContent, "utf-8");
|
|
9035
9650
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
|
|
9036
9651
|
} else {
|
|
9037
|
-
if (!
|
|
9038
|
-
|
|
9652
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9653
|
+
fs27.writeFileSync(skillPath, content, "utf-8");
|
|
9039
9654
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
|
|
9040
9655
|
}
|
|
9041
9656
|
} catch (err) {
|
|
@@ -9048,21 +9663,21 @@ function handleOpencodeSecondary(root, results) {
|
|
|
9048
9663
|
}
|
|
9049
9664
|
}
|
|
9050
9665
|
function handleCodexSecondary(root, results) {
|
|
9051
|
-
const tomlPath =
|
|
9666
|
+
const tomlPath = path28.resolve(root, ".codex/config.toml");
|
|
9052
9667
|
if (results.some((r) => r.filePath === ".codex/config.toml")) return;
|
|
9053
9668
|
try {
|
|
9054
|
-
const dir =
|
|
9055
|
-
if (!
|
|
9056
|
-
if (
|
|
9057
|
-
const existingContent =
|
|
9669
|
+
const dir = path28.dirname(tomlPath);
|
|
9670
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9671
|
+
if (fs27.existsSync(tomlPath)) {
|
|
9672
|
+
const existingContent = fs27.readFileSync(tomlPath, "utf-8");
|
|
9058
9673
|
if (existingContent.includes("kuma")) {
|
|
9059
9674
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
|
|
9060
9675
|
return;
|
|
9061
9676
|
}
|
|
9062
|
-
|
|
9677
|
+
fs27.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
|
|
9063
9678
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
|
|
9064
9679
|
} else {
|
|
9065
|
-
|
|
9680
|
+
fs27.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
|
|
9066
9681
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
|
|
9067
9682
|
}
|
|
9068
9683
|
} catch (err) {
|
|
@@ -9075,11 +9690,11 @@ function handleCodexSecondary(root, results) {
|
|
|
9075
9690
|
}
|
|
9076
9691
|
}
|
|
9077
9692
|
function handleQwenSecondary(root, results) {
|
|
9078
|
-
const settingsPath =
|
|
9693
|
+
const settingsPath = path28.resolve(root, "settings.json");
|
|
9079
9694
|
if (results.some((r) => r.filePath === "settings.json")) return;
|
|
9080
9695
|
try {
|
|
9081
|
-
if (
|
|
9082
|
-
const existingContent =
|
|
9696
|
+
if (fs27.existsSync(settingsPath)) {
|
|
9697
|
+
const existingContent = fs27.readFileSync(settingsPath, "utf-8");
|
|
9083
9698
|
if (existingContent.includes("kuma")) {
|
|
9084
9699
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9085
9700
|
try {
|
|
@@ -9087,7 +9702,7 @@ function handleQwenSecondary(root, results) {
|
|
|
9087
9702
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9088
9703
|
if (!parsed.mcpServers.kuma) {
|
|
9089
9704
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9090
|
-
|
|
9705
|
+
fs27.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9091
9706
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
9092
9707
|
return;
|
|
9093
9708
|
}
|
|
@@ -9102,13 +9717,13 @@ function handleQwenSecondary(root, results) {
|
|
|
9102
9717
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9103
9718
|
if (!parsed.mcpServers.kuma) {
|
|
9104
9719
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9105
|
-
|
|
9720
|
+
fs27.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9106
9721
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
9107
9722
|
}
|
|
9108
9723
|
} catch {
|
|
9109
9724
|
}
|
|
9110
9725
|
} else {
|
|
9111
|
-
|
|
9726
|
+
fs27.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
|
|
9112
9727
|
results.push({ type: "qwen", filePath: "settings.json", action: "created" });
|
|
9113
9728
|
}
|
|
9114
9729
|
} catch (err) {
|
|
@@ -9206,18 +9821,18 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
9206
9821
|
return lines.join("\n");
|
|
9207
9822
|
}
|
|
9208
9823
|
function handleAntigravityMcpConfig(root, results) {
|
|
9209
|
-
const mcpPath =
|
|
9824
|
+
const mcpPath = path28.resolve(root, ".agents/mcp_config.json");
|
|
9210
9825
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
9211
9826
|
try {
|
|
9212
|
-
const mcpDir =
|
|
9213
|
-
if (
|
|
9214
|
-
const existingContent =
|
|
9827
|
+
const mcpDir = path28.dirname(mcpPath);
|
|
9828
|
+
if (fs27.existsSync(mcpPath)) {
|
|
9829
|
+
const existingContent = fs27.readFileSync(mcpPath, "utf-8");
|
|
9215
9830
|
if (existingContent.includes("kuma")) {
|
|
9216
9831
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9217
9832
|
const trimmed = existingContent.trimEnd();
|
|
9218
9833
|
if (trimmed.endsWith("}")) {
|
|
9219
9834
|
const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
|
|
9220
|
-
|
|
9835
|
+
fs27.writeFileSync(mcpPath, updated, "utf-8");
|
|
9221
9836
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
9222
9837
|
}
|
|
9223
9838
|
}
|
|
@@ -9226,11 +9841,11 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
9226
9841
|
const parsed = JSON.parse(existingContent);
|
|
9227
9842
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9228
9843
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9229
|
-
|
|
9844
|
+
fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9230
9845
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
9231
9846
|
} else {
|
|
9232
|
-
if (!
|
|
9233
|
-
|
|
9847
|
+
if (!fs27.existsSync(mcpDir)) fs27.mkdirSync(mcpDir, { recursive: true });
|
|
9848
|
+
fs27.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
9234
9849
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
|
|
9235
9850
|
}
|
|
9236
9851
|
} catch (err) {
|
|
@@ -9243,18 +9858,18 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
9243
9858
|
}
|
|
9244
9859
|
}
|
|
9245
9860
|
function handleAiderSecondary(root, results) {
|
|
9246
|
-
const ymlPath =
|
|
9861
|
+
const ymlPath = path28.resolve(root, ".aider.conf.yml");
|
|
9247
9862
|
if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
|
|
9248
9863
|
try {
|
|
9249
9864
|
const conventionsRef = "read: CONVENTIONS.md";
|
|
9250
|
-
if (
|
|
9251
|
-
const existingContent =
|
|
9865
|
+
if (fs27.existsSync(ymlPath)) {
|
|
9866
|
+
const existingContent = fs27.readFileSync(ymlPath, "utf-8");
|
|
9252
9867
|
if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
|
|
9253
9868
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
|
|
9254
9869
|
return;
|
|
9255
9870
|
}
|
|
9256
9871
|
const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
|
|
9257
|
-
|
|
9872
|
+
fs27.writeFileSync(ymlPath, newContent, "utf-8");
|
|
9258
9873
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
|
|
9259
9874
|
} else {
|
|
9260
9875
|
const content = [
|
|
@@ -9264,7 +9879,7 @@ function handleAiderSecondary(root, results) {
|
|
|
9264
9879
|
conventionsRef,
|
|
9265
9880
|
""
|
|
9266
9881
|
].join("\n");
|
|
9267
|
-
|
|
9882
|
+
fs27.writeFileSync(ymlPath, content, "utf-8");
|
|
9268
9883
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
|
|
9269
9884
|
}
|
|
9270
9885
|
} catch (err) {
|
|
@@ -9277,10 +9892,10 @@ function handleAiderSecondary(root, results) {
|
|
|
9277
9892
|
}
|
|
9278
9893
|
}
|
|
9279
9894
|
function handleCopilotSecondary(root, results) {
|
|
9280
|
-
const skillPath =
|
|
9895
|
+
const skillPath = path28.resolve(root, ".github/skills/kuma/SKILL.md");
|
|
9281
9896
|
if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
|
|
9282
9897
|
try {
|
|
9283
|
-
const dir =
|
|
9898
|
+
const dir = path28.dirname(skillPath);
|
|
9284
9899
|
const content = [
|
|
9285
9900
|
"---",
|
|
9286
9901
|
"name: kuma-mcp",
|
|
@@ -9291,17 +9906,17 @@ function handleCopilotSecondary(root, results) {
|
|
|
9291
9906
|
"",
|
|
9292
9907
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9293
9908
|
].join("\n");
|
|
9294
|
-
if (
|
|
9295
|
-
const existingContent =
|
|
9909
|
+
if (fs27.existsSync(skillPath)) {
|
|
9910
|
+
const existingContent = fs27.readFileSync(skillPath, "utf-8");
|
|
9296
9911
|
if (existingContent.includes("kuma")) {
|
|
9297
9912
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
|
|
9298
9913
|
return;
|
|
9299
9914
|
}
|
|
9300
|
-
|
|
9915
|
+
fs27.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
|
|
9301
9916
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
|
|
9302
9917
|
} else {
|
|
9303
|
-
if (!
|
|
9304
|
-
|
|
9918
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9919
|
+
fs27.writeFileSync(skillPath, content, "utf-8");
|
|
9305
9920
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
|
|
9306
9921
|
}
|
|
9307
9922
|
} catch (err) {
|
|
@@ -9314,21 +9929,21 @@ function handleCopilotSecondary(root, results) {
|
|
|
9314
9929
|
}
|
|
9315
9930
|
}
|
|
9316
9931
|
function handleOpenclawSecondary(root, results) {
|
|
9317
|
-
const mcpPath =
|
|
9932
|
+
const mcpPath = path28.resolve(root, ".agents/mcp_config.json");
|
|
9318
9933
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
9319
9934
|
try {
|
|
9320
|
-
const dir =
|
|
9321
|
-
if (
|
|
9322
|
-
const existingContent =
|
|
9935
|
+
const dir = path28.dirname(mcpPath);
|
|
9936
|
+
if (fs27.existsSync(mcpPath)) {
|
|
9937
|
+
const existingContent = fs27.readFileSync(mcpPath, "utf-8");
|
|
9323
9938
|
if (existingContent.includes("kuma")) return;
|
|
9324
9939
|
const parsed = JSON.parse(existingContent);
|
|
9325
9940
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9326
9941
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9327
|
-
|
|
9942
|
+
fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9328
9943
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
9329
9944
|
} else {
|
|
9330
|
-
if (!
|
|
9331
|
-
|
|
9945
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9946
|
+
fs27.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
9332
9947
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
|
|
9333
9948
|
}
|
|
9334
9949
|
} catch (err) {
|
|
@@ -9341,7 +9956,7 @@ function handleOpenclawSecondary(root, results) {
|
|
|
9341
9956
|
}
|
|
9342
9957
|
}
|
|
9343
9958
|
function handleQuickrefGeneration(root, results) {
|
|
9344
|
-
const quickrefPath =
|
|
9959
|
+
const quickrefPath = path28.resolve(root, ".kuma/quickref.md");
|
|
9345
9960
|
const content = [
|
|
9346
9961
|
"# Kuma Quick Reference",
|
|
9347
9962
|
"",
|
|
@@ -9355,8 +9970,9 @@ function handleQuickrefGeneration(root, results) {
|
|
|
9355
9970
|
"1. `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
|
|
9356
9971
|
"",
|
|
9357
9972
|
"## Record Rules",
|
|
9358
|
-
"- **MUST:** decision, gotcha",
|
|
9973
|
+
"- **MUST:** decision, gotcha, arch_flow, research_save, feature",
|
|
9359
9974
|
"- **SKIP:** function, class, import, route",
|
|
9975
|
+
"- **RELATIONSHIPS:** Every node MUST have edges. No orphan nodes.",
|
|
9360
9976
|
"",
|
|
9361
9977
|
"## Platform Tool Names",
|
|
9362
9978
|
"- OpenCode / Antigravity: `kuma_kuma_*`",
|
|
@@ -9366,16 +9982,16 @@ function handleQuickrefGeneration(root, results) {
|
|
|
9366
9982
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9367
9983
|
].join("\n");
|
|
9368
9984
|
try {
|
|
9369
|
-
const kumaDir =
|
|
9370
|
-
if (!
|
|
9371
|
-
if (
|
|
9372
|
-
const existing =
|
|
9985
|
+
const kumaDir = path28.dirname(quickrefPath);
|
|
9986
|
+
if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
|
|
9987
|
+
if (fs27.existsSync(quickrefPath)) {
|
|
9988
|
+
const existing = fs27.readFileSync(quickrefPath, "utf-8");
|
|
9373
9989
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
9374
9990
|
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
|
|
9375
9991
|
return;
|
|
9376
9992
|
}
|
|
9377
9993
|
}
|
|
9378
|
-
|
|
9994
|
+
fs27.writeFileSync(quickrefPath, content, "utf-8");
|
|
9379
9995
|
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
|
|
9380
9996
|
} catch (err) {
|
|
9381
9997
|
results.push({
|
|
@@ -9387,7 +10003,7 @@ function handleQuickrefGeneration(root, results) {
|
|
|
9387
10003
|
}
|
|
9388
10004
|
}
|
|
9389
10005
|
function handleModeMdGeneration(root, results) {
|
|
9390
|
-
const filePath =
|
|
10006
|
+
const filePath = path28.resolve(root, ".kuma/MODE.md");
|
|
9391
10007
|
const content = [
|
|
9392
10008
|
"# Adaptive Mode Selector",
|
|
9393
10009
|
"",
|
|
@@ -9430,10 +10046,10 @@ function handleModeMdGeneration(root, results) {
|
|
|
9430
10046
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9431
10047
|
].join("\n");
|
|
9432
10048
|
try {
|
|
9433
|
-
const dir =
|
|
9434
|
-
if (!
|
|
9435
|
-
if (!
|
|
9436
|
-
|
|
10049
|
+
const dir = path28.dirname(filePath);
|
|
10050
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
10051
|
+
if (!fs27.existsSync(filePath)) {
|
|
10052
|
+
fs27.writeFileSync(filePath, content, "utf-8");
|
|
9437
10053
|
results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "created" });
|
|
9438
10054
|
}
|
|
9439
10055
|
} catch (err) {
|
|
@@ -9441,7 +10057,7 @@ function handleModeMdGeneration(root, results) {
|
|
|
9441
10057
|
}
|
|
9442
10058
|
}
|
|
9443
10059
|
function handleSkipRulesMdGeneration(root, results) {
|
|
9444
|
-
const filePath =
|
|
10060
|
+
const filePath = path28.resolve(root, ".kuma/SKIP_RULES.md");
|
|
9445
10061
|
const content = [
|
|
9446
10062
|
"# Skip Rules \u2014 What NOT to Record",
|
|
9447
10063
|
"",
|
|
@@ -9478,10 +10094,10 @@ function handleSkipRulesMdGeneration(root, results) {
|
|
|
9478
10094
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9479
10095
|
].join("\n");
|
|
9480
10096
|
try {
|
|
9481
|
-
const dir =
|
|
9482
|
-
if (!
|
|
9483
|
-
if (!
|
|
9484
|
-
|
|
10097
|
+
const dir = path28.dirname(filePath);
|
|
10098
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
10099
|
+
if (!fs27.existsSync(filePath)) {
|
|
10100
|
+
fs27.writeFileSync(filePath, content, "utf-8");
|
|
9485
10101
|
results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "created" });
|
|
9486
10102
|
}
|
|
9487
10103
|
} catch (err) {
|
|
@@ -9489,7 +10105,7 @@ function handleSkipRulesMdGeneration(root, results) {
|
|
|
9489
10105
|
}
|
|
9490
10106
|
}
|
|
9491
10107
|
function handleStalenessMdGeneration(root, results) {
|
|
9492
|
-
const filePath =
|
|
10108
|
+
const filePath = path28.resolve(root, ".kuma/STALENESS.md");
|
|
9493
10109
|
const content = [
|
|
9494
10110
|
"# Auto-Staleness Detection",
|
|
9495
10111
|
"",
|
|
@@ -9520,10 +10136,10 @@ function handleStalenessMdGeneration(root, results) {
|
|
|
9520
10136
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9521
10137
|
].join("\n");
|
|
9522
10138
|
try {
|
|
9523
|
-
const dir =
|
|
9524
|
-
if (!
|
|
9525
|
-
if (!
|
|
9526
|
-
|
|
10139
|
+
const dir = path28.dirname(filePath);
|
|
10140
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
10141
|
+
if (!fs27.existsSync(filePath)) {
|
|
10142
|
+
fs27.writeFileSync(filePath, content, "utf-8");
|
|
9527
10143
|
results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "created" });
|
|
9528
10144
|
}
|
|
9529
10145
|
} catch (err) {
|
|
@@ -9531,20 +10147,20 @@ function handleStalenessMdGeneration(root, results) {
|
|
|
9531
10147
|
}
|
|
9532
10148
|
}
|
|
9533
10149
|
function handleInitMdGeneration(root, results) {
|
|
9534
|
-
const initMdPath =
|
|
10150
|
+
const initMdPath = path28.resolve(root, ".kuma/init.md");
|
|
9535
10151
|
try {
|
|
9536
|
-
const kumaDir =
|
|
9537
|
-
if (!
|
|
9538
|
-
if (
|
|
9539
|
-
const existing =
|
|
10152
|
+
const kumaDir = path28.dirname(initMdPath);
|
|
10153
|
+
if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
|
|
10154
|
+
if (fs27.existsSync(initMdPath)) {
|
|
10155
|
+
const existing = fs27.readFileSync(initMdPath, "utf-8");
|
|
9540
10156
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
9541
10157
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
|
|
9542
10158
|
return;
|
|
9543
10159
|
}
|
|
9544
|
-
|
|
10160
|
+
fs27.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
|
|
9545
10161
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
|
|
9546
10162
|
} else {
|
|
9547
|
-
|
|
10163
|
+
fs27.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
9548
10164
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
|
|
9549
10165
|
}
|
|
9550
10166
|
} catch (err) {
|
|
@@ -9561,20 +10177,20 @@ function handleInitMdGeneration(root, results) {
|
|
|
9561
10177
|
handleStalenessMdGeneration(root, results);
|
|
9562
10178
|
}
|
|
9563
10179
|
function handleCodewhaleSecondary(root, results) {
|
|
9564
|
-
const mcpPath =
|
|
10180
|
+
const mcpPath = path28.resolve(root, ".codewhale/mcp.json");
|
|
9565
10181
|
if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
|
|
9566
10182
|
try {
|
|
9567
|
-
const dir =
|
|
9568
|
-
if (
|
|
9569
|
-
const existingContent =
|
|
10183
|
+
const dir = path28.dirname(mcpPath);
|
|
10184
|
+
if (fs27.existsSync(mcpPath)) {
|
|
10185
|
+
const existingContent = fs27.readFileSync(mcpPath, "utf-8");
|
|
9570
10186
|
if (existingContent.includes("kuma")) return;
|
|
9571
10187
|
const parsed = JSON.parse(existingContent);
|
|
9572
10188
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9573
10189
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9574
|
-
|
|
10190
|
+
fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9575
10191
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
|
|
9576
10192
|
} else {
|
|
9577
|
-
if (!
|
|
10193
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9578
10194
|
const config = {
|
|
9579
10195
|
mcpServers: {
|
|
9580
10196
|
kuma: {
|
|
@@ -9584,7 +10200,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
9584
10200
|
}
|
|
9585
10201
|
}
|
|
9586
10202
|
};
|
|
9587
|
-
|
|
10203
|
+
fs27.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
9588
10204
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
|
|
9589
10205
|
}
|
|
9590
10206
|
} catch (err) {
|
|
@@ -9606,28 +10222,28 @@ function runInit(options) {
|
|
|
9606
10222
|
let agentsMdHandled = false;
|
|
9607
10223
|
for (const type of selected) {
|
|
9608
10224
|
const relativePath = configFilePath(type);
|
|
9609
|
-
const fullPath =
|
|
10225
|
+
const fullPath = path28.resolve(root, relativePath);
|
|
9610
10226
|
const getTemplate = TEMPLATES[type];
|
|
9611
10227
|
try {
|
|
9612
10228
|
if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
|
|
9613
10229
|
agentsMdHandled = true;
|
|
9614
10230
|
const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
|
|
9615
|
-
if (
|
|
10231
|
+
if (fs27.existsSync(fullPath)) {
|
|
9616
10232
|
if (options.skipExisting) {
|
|
9617
10233
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9618
10234
|
} else {
|
|
9619
|
-
const existingContent =
|
|
10235
|
+
const existingContent = fs27.readFileSync(fullPath, "utf-8");
|
|
9620
10236
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9621
10237
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9622
10238
|
} else {
|
|
9623
|
-
|
|
10239
|
+
fs27.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
|
|
9624
10240
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
9625
10241
|
}
|
|
9626
10242
|
}
|
|
9627
10243
|
} else {
|
|
9628
|
-
const dir =
|
|
9629
|
-
if (!
|
|
9630
|
-
|
|
10244
|
+
const dir = path28.dirname(fullPath);
|
|
10245
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
10246
|
+
fs27.writeFileSync(fullPath, combinedContent, "utf-8");
|
|
9631
10247
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
9632
10248
|
}
|
|
9633
10249
|
if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
|
|
@@ -9639,12 +10255,12 @@ function runInit(options) {
|
|
|
9639
10255
|
continue;
|
|
9640
10256
|
} else {
|
|
9641
10257
|
const template = getTemplate();
|
|
9642
|
-
if (
|
|
10258
|
+
if (fs27.existsSync(fullPath)) {
|
|
9643
10259
|
if (options.skipExisting) {
|
|
9644
10260
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9645
10261
|
continue;
|
|
9646
10262
|
}
|
|
9647
|
-
const existingContent =
|
|
10263
|
+
const existingContent = fs27.readFileSync(fullPath, "utf-8");
|
|
9648
10264
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9649
10265
|
if (type === "antigravity") {
|
|
9650
10266
|
handleAntigravityMcpConfig(root, results);
|
|
@@ -9657,14 +10273,14 @@ function runInit(options) {
|
|
|
9657
10273
|
continue;
|
|
9658
10274
|
}
|
|
9659
10275
|
const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
|
|
9660
|
-
|
|
10276
|
+
fs27.writeFileSync(fullPath, newContent, "utf-8");
|
|
9661
10277
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
9662
10278
|
} else {
|
|
9663
|
-
const dir =
|
|
9664
|
-
if (!
|
|
9665
|
-
|
|
10279
|
+
const dir = path28.dirname(fullPath);
|
|
10280
|
+
if (!fs27.existsSync(dir)) {
|
|
10281
|
+
fs27.mkdirSync(dir, { recursive: true });
|
|
9666
10282
|
}
|
|
9667
|
-
|
|
10283
|
+
fs27.writeFileSync(fullPath, template, "utf-8");
|
|
9668
10284
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
9669
10285
|
}
|
|
9670
10286
|
if (type === "antigravity") {
|
|
@@ -9806,6 +10422,209 @@ var init_init = __esm({
|
|
|
9806
10422
|
}
|
|
9807
10423
|
});
|
|
9808
10424
|
|
|
10425
|
+
// src/engine/kumaLegacyOnboard.ts
|
|
10426
|
+
var kumaLegacyOnboard_exports = {};
|
|
10427
|
+
__export(kumaLegacyOnboard_exports, {
|
|
10428
|
+
runLegacyOnboarding: () => runLegacyOnboarding
|
|
10429
|
+
});
|
|
10430
|
+
import fs28 from "fs";
|
|
10431
|
+
import path29 from "path";
|
|
10432
|
+
async function harvestGit(commitCount) {
|
|
10433
|
+
try {
|
|
10434
|
+
const { harvestGitHistory: harvestGitHistory2 } = await Promise.resolve().then(() => (init_kumaGitHarvester(), kumaGitHarvester_exports));
|
|
10435
|
+
return await harvestGitHistory2({ commitCount });
|
|
10436
|
+
} catch (err) {
|
|
10437
|
+
return `\u26A0\uFE0F Git harvest skipped: ${err}`;
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
async function mineInlineMarkers(root, maxFiles) {
|
|
10441
|
+
const markers = [];
|
|
10442
|
+
try {
|
|
10443
|
+
const { default: fg } = await import("fast-glob");
|
|
10444
|
+
const files = await fg(
|
|
10445
|
+
["**/*.{ts,tsx,js,jsx,py,go,rs,java,kt,cs,rb,php,c,cpp,h,hpp}"],
|
|
10446
|
+
{
|
|
10447
|
+
cwd: root,
|
|
10448
|
+
ignore: ["node_modules/**", "dist/**", "build/**", ".git/**", ".kuma/**", "coverage/**", "vendor/**"],
|
|
10449
|
+
onlyFiles: true,
|
|
10450
|
+
deep: 8
|
|
10451
|
+
}
|
|
10452
|
+
);
|
|
10453
|
+
for (const file of files.slice(0, maxFiles)) {
|
|
10454
|
+
try {
|
|
10455
|
+
const fullPath = path29.join(root, file);
|
|
10456
|
+
const content = fs28.readFileSync(fullPath, "utf-8");
|
|
10457
|
+
const lines = content.split("\n");
|
|
10458
|
+
for (let i = 0; i < lines.length && markers.length < 100; i++) {
|
|
10459
|
+
const line = lines[i];
|
|
10460
|
+
for (const { tag, regex } of MARKER_PATTERNS) {
|
|
10461
|
+
const m = regex.exec(line);
|
|
10462
|
+
if (m) {
|
|
10463
|
+
const text = (m[1] || m[2] || m[3] || line.trim()).trim().substring(0, 160);
|
|
10464
|
+
markers.push({ filePath: file, line: i + 1, tag, text });
|
|
10465
|
+
break;
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
}
|
|
10469
|
+
} catch {
|
|
10470
|
+
}
|
|
10471
|
+
}
|
|
10472
|
+
} catch {
|
|
10473
|
+
}
|
|
10474
|
+
return markers;
|
|
10475
|
+
}
|
|
10476
|
+
async function buildFeatureNodes(root) {
|
|
10477
|
+
const names = [];
|
|
10478
|
+
try {
|
|
10479
|
+
const { recordFeature: recordFeature2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10480
|
+
const pkgPath = path29.join(root, "package.json");
|
|
10481
|
+
if (fs28.existsSync(pkgPath)) {
|
|
10482
|
+
try {
|
|
10483
|
+
const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
|
|
10484
|
+
const candidates = [
|
|
10485
|
+
pkg.main,
|
|
10486
|
+
pkg.module,
|
|
10487
|
+
pkg.bin ? Object.keys(pkg.bin)[0] : null,
|
|
10488
|
+
...Array.isArray(pkg.exports) ? pkg.exports : []
|
|
10489
|
+
].filter(Boolean);
|
|
10490
|
+
for (const c of candidates.slice(0, 5)) {
|
|
10491
|
+
const name = path29.basename(c, path29.extname(c)).replace(/[^a-zA-Z0-9]/g, " ").trim();
|
|
10492
|
+
if (name && !names.includes(name)) {
|
|
10493
|
+
await recordFeature2({ name, description: `Detected entry point during legacy onboarding (${c})`, files: [c] });
|
|
10494
|
+
names.push(name);
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
} catch {
|
|
10498
|
+
}
|
|
10499
|
+
}
|
|
10500
|
+
const srcDir = path29.join(root, "src");
|
|
10501
|
+
if (fs28.existsSync(srcDir)) {
|
|
10502
|
+
try {
|
|
10503
|
+
const entries = fs28.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((n) => !n.startsWith(".") && !["node_modules", "dist", "build", "__tests__", "test", "tests", "assets", "styles", "types", "utils"].includes(n));
|
|
10504
|
+
for (const dir of entries.slice(0, 15)) {
|
|
10505
|
+
if (!names.includes(dir)) {
|
|
10506
|
+
const files = [path29.join("src", dir)];
|
|
10507
|
+
await recordFeature2({
|
|
10508
|
+
name: dir,
|
|
10509
|
+
description: `Detected top-level module during legacy onboarding`,
|
|
10510
|
+
files,
|
|
10511
|
+
risk: "medium"
|
|
10512
|
+
});
|
|
10513
|
+
names.push(dir);
|
|
10514
|
+
}
|
|
10515
|
+
}
|
|
10516
|
+
} catch {
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
10519
|
+
} catch {
|
|
10520
|
+
}
|
|
10521
|
+
return { created: names.length, names };
|
|
10522
|
+
}
|
|
10523
|
+
function buildArchitectureDigest(root, featureNames) {
|
|
10524
|
+
const lines = [];
|
|
10525
|
+
try {
|
|
10526
|
+
const pkgPath = path29.join(root, "package.json");
|
|
10527
|
+
if (fs28.existsSync(pkgPath)) {
|
|
10528
|
+
const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
|
|
10529
|
+
lines.push(`\u{1F3D7}\uFE0F **Stack**: ${[pkg.framework, pkg.orm, pkg.testFramework].filter(Boolean).join(" + ") || "detected"}`);
|
|
10530
|
+
}
|
|
10531
|
+
if (featureNames.length > 0) {
|
|
10532
|
+
lines.push(`\u{1F9E9} **Detected Modules**: ${featureNames.slice(0, 8).join(", ")}`);
|
|
10533
|
+
}
|
|
10534
|
+
lines.push(`\u{1F4C4} **Entry Points**: check package.json main/bin or src/index.*`);
|
|
10535
|
+
} catch {
|
|
10536
|
+
}
|
|
10537
|
+
return lines.join("\n");
|
|
10538
|
+
}
|
|
10539
|
+
async function runLegacyOnboarding(options = {}) {
|
|
10540
|
+
const root = getProjectRoot();
|
|
10541
|
+
const commitCount = options.commitCount ?? 25;
|
|
10542
|
+
const maxFiles = options.maxFiles ?? 200;
|
|
10543
|
+
const lines = [
|
|
10544
|
+
"\u{1F680} **Kuma Legacy Onboarding**",
|
|
10545
|
+
"\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",
|
|
10546
|
+
"",
|
|
10547
|
+
`\u{1F4C1} Project: ${root.split("/").pop() || "unknown"}`,
|
|
10548
|
+
"",
|
|
10549
|
+
"\u23F3 Running bulk analysis \u2014 this may take a few seconds...",
|
|
10550
|
+
""
|
|
10551
|
+
];
|
|
10552
|
+
lines.push("### 1\uFE0F\u20E3 Git History Harvest");
|
|
10553
|
+
try {
|
|
10554
|
+
const harvest = await harvestGit(commitCount);
|
|
10555
|
+
lines.push(harvest);
|
|
10556
|
+
} catch (err) {
|
|
10557
|
+
lines.push(`\u26A0\uFE0F ${err}`);
|
|
10558
|
+
}
|
|
10559
|
+
lines.push("");
|
|
10560
|
+
lines.push("### 2\uFE0F\u20E3 Inline Marker Mining (HACK/FIXME/TODO \u2192 gotchas)");
|
|
10561
|
+
try {
|
|
10562
|
+
const markers = await mineInlineMarkers(root, maxFiles);
|
|
10563
|
+
if (markers.length === 0) {
|
|
10564
|
+
lines.push("\u2705 No HACK/FIXME/TODO markers found \u2014 clean codebase.");
|
|
10565
|
+
} else {
|
|
10566
|
+
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10567
|
+
let recorded = 0;
|
|
10568
|
+
for (const m of markers.slice(0, 40)) {
|
|
10569
|
+
try {
|
|
10570
|
+
const severity = m.tag === "HACK" || m.tag === "WARNING" ? "high" : m.tag === "FIXME" ? "medium" : "low";
|
|
10571
|
+
await addGotcha2({
|
|
10572
|
+
filePath: m.filePath,
|
|
10573
|
+
description: `[${m.tag}] ${m.text} (line ${m.line})`,
|
|
10574
|
+
severity,
|
|
10575
|
+
workaround: `Legacy marker ${m.tag} at ${m.filePath}:${m.line} \u2014 review before modifying this file.`
|
|
10576
|
+
});
|
|
10577
|
+
recorded++;
|
|
10578
|
+
} catch {
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
lines.push(`\u26A0\uFE0F Found ${markers.length} marker(s); recorded ${recorded} as gotchas (capped at 40).`);
|
|
10582
|
+
lines.push(`\u{1F4A1} Remaining ${Math.max(0, markers.length - 40)} \u2014 re-run or use kuma_memory({ action: 'gotcha' }) inline.`);
|
|
10583
|
+
}
|
|
10584
|
+
} catch (err) {
|
|
10585
|
+
lines.push(`\u26A0\uFE0F Marker mining failed: ${err}`);
|
|
10586
|
+
}
|
|
10587
|
+
lines.push("");
|
|
10588
|
+
lines.push("### 3\uFE0F\u20E3 Feature Graph Bootstrap");
|
|
10589
|
+
let featureNames = [];
|
|
10590
|
+
try {
|
|
10591
|
+
const feat = await buildFeatureNodes(root);
|
|
10592
|
+
featureNames = feat.names;
|
|
10593
|
+
lines.push(feat.created > 0 ? `\u{1F9E9} Built ${feat.created} feature node(s): ${feat.names.slice(0, 8).join(", ")}` : "\u2139\uFE0F No obvious top-level features detected \u2014 record manually via kuma_memory({ action: 'feature' }).");
|
|
10594
|
+
} catch (err) {
|
|
10595
|
+
lines.push(`\u26A0\uFE0F Feature bootstrap failed: ${err}`);
|
|
10596
|
+
}
|
|
10597
|
+
lines.push("");
|
|
10598
|
+
lines.push("### 4\uFE0F\u20E3 Architecture Digest");
|
|
10599
|
+
try {
|
|
10600
|
+
const digest = buildArchitectureDigest(root, featureNames);
|
|
10601
|
+
lines.push(digest);
|
|
10602
|
+
} catch {
|
|
10603
|
+
}
|
|
10604
|
+
lines.push("");
|
|
10605
|
+
lines.push("\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");
|
|
10606
|
+
lines.push("\u2705 **Onboarding complete.** Suggested next steps:");
|
|
10607
|
+
lines.push(" \u2022 `kuma_context({ action: 'digest' })` \u2014 compact briefing");
|
|
10608
|
+
lines.push(" \u2022 `kuma_context({ action: 'research', scope: '<area>' })` \u2014 deep-dive first area");
|
|
10609
|
+
lines.push(" \u2022 `kuma_memory({ action: 'arch_flow', content: 'domain: X | hops: a \u2192 b \u2192 c' })` \u2014 map core flows");
|
|
10610
|
+
lines.push(" \u2022 `kuma_safety({ action: 'heal' })` \u2014 repair any stale graph entries");
|
|
10611
|
+
return lines.join("\n");
|
|
10612
|
+
}
|
|
10613
|
+
var MARKER_PATTERNS;
|
|
10614
|
+
var init_kumaLegacyOnboard = __esm({
|
|
10615
|
+
"src/engine/kumaLegacyOnboard.ts"() {
|
|
10616
|
+
"use strict";
|
|
10617
|
+
init_pathValidator();
|
|
10618
|
+
MARKER_PATTERNS = [
|
|
10619
|
+
{ tag: "HACK", regex: /\/\/\s*HACK:?\s*(.*)|#\s*HACK:?\s*(.*)|<!--\s*HACK:?\s*(.*)-->/i },
|
|
10620
|
+
{ tag: "FIXME", regex: /\/\/\s*FIXME:?\s*(.*)|#\s*FIXME:?\s*(.*)|<!--\s*FIXME:?\s*(.*)-->/i },
|
|
10621
|
+
{ tag: "TODO", regex: /\/\/\s*TODO:?\s*(.*)|#\s*TODO:?\s*(.*)|<!--\s*TODO:?\s*(.*)-->/i },
|
|
10622
|
+
{ tag: "WARNING", regex: /\/\/\s*WARNING:?\s*(.*)|#\s*WARNING:?\s*(.*)|<!--\s*WARNING:?\s*(.*)-->/i },
|
|
10623
|
+
{ tag: "XXX", regex: /\/\/\s*XXX:?\s*(.*)|#\s*XXX:?\s*(.*)|<!--\s*XXX:?\s*(.*)-->/i }
|
|
10624
|
+
];
|
|
10625
|
+
}
|
|
10626
|
+
});
|
|
10627
|
+
|
|
9809
10628
|
// src/utils/agentDetector.ts
|
|
9810
10629
|
var agentDetector_exports = {};
|
|
9811
10630
|
__export(agentDetector_exports, {
|
|
@@ -9814,19 +10633,19 @@ __export(agentDetector_exports, {
|
|
|
9814
10633
|
getSkillPath: () => getSkillPath,
|
|
9815
10634
|
skillExists: () => skillExists
|
|
9816
10635
|
});
|
|
9817
|
-
import
|
|
9818
|
-
import
|
|
10636
|
+
import fs29 from "fs";
|
|
10637
|
+
import path30 from "path";
|
|
9819
10638
|
function checkFile(root, filePath) {
|
|
9820
10639
|
try {
|
|
9821
|
-
return
|
|
10640
|
+
return fs29.existsSync(path30.join(root, filePath));
|
|
9822
10641
|
} catch {
|
|
9823
10642
|
return false;
|
|
9824
10643
|
}
|
|
9825
10644
|
}
|
|
9826
10645
|
function checkDir(root, dirPath) {
|
|
9827
10646
|
try {
|
|
9828
|
-
const fullPath =
|
|
9829
|
-
return
|
|
10647
|
+
const fullPath = path30.join(root, dirPath);
|
|
10648
|
+
return fs29.existsSync(fullPath) && fs29.statSync(fullPath).isDirectory();
|
|
9830
10649
|
} catch {
|
|
9831
10650
|
return false;
|
|
9832
10651
|
}
|
|
@@ -9894,7 +10713,7 @@ function getAgentLabel(type) {
|
|
|
9894
10713
|
function skillExists(type, projectRoot) {
|
|
9895
10714
|
const root = projectRoot ?? getProjectRoot();
|
|
9896
10715
|
const skillPath = getSkillPath(type);
|
|
9897
|
-
return
|
|
10716
|
+
return fs29.existsSync(path30.join(root, skillPath));
|
|
9898
10717
|
}
|
|
9899
10718
|
var AGENT_DETECTORS;
|
|
9900
10719
|
var init_agentDetector = __esm({
|
|
@@ -10338,8 +11157,8 @@ init_kumaDb();
|
|
|
10338
11157
|
init_kumaGraph();
|
|
10339
11158
|
init_kumaMemory();
|
|
10340
11159
|
init_pathValidator();
|
|
10341
|
-
import
|
|
10342
|
-
import
|
|
11160
|
+
import fs12 from "fs";
|
|
11161
|
+
import path13 from "path";
|
|
10343
11162
|
import crypto4 from "crypto";
|
|
10344
11163
|
var CONTEXT_ALIASES = {
|
|
10345
11164
|
// Research synonyms
|
|
@@ -10360,6 +11179,11 @@ var CONTEXT_ALIASES = {
|
|
|
10360
11179
|
"init": "init",
|
|
10361
11180
|
"start": "init",
|
|
10362
11181
|
"load": "init",
|
|
11182
|
+
// Resume synonyms
|
|
11183
|
+
"resume": "resume",
|
|
11184
|
+
"continue": "resume",
|
|
11185
|
+
"restore": "resume",
|
|
11186
|
+
"reload": "resume",
|
|
10363
11187
|
"brief": "init",
|
|
10364
11188
|
"project": "init",
|
|
10365
11189
|
"summary": "init",
|
|
@@ -10441,9 +11265,77 @@ async function handleContext(params) {
|
|
|
10441
11265
|
return handleDrift(params);
|
|
10442
11266
|
case "progressive":
|
|
10443
11267
|
return handleProgressive(params);
|
|
11268
|
+
case "resume":
|
|
11269
|
+
return handleResume(params);
|
|
10444
11270
|
default:
|
|
10445
|
-
return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync, visualize, digest, drift, progressive`;
|
|
11271
|
+
return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health, sync, visualize, digest, drift, progressive, resume`;
|
|
11272
|
+
}
|
|
11273
|
+
}
|
|
11274
|
+
async function handleResume(_params) {
|
|
11275
|
+
sessionMemory.recordToolCall("kuma_context_resume", {});
|
|
11276
|
+
const lines = [
|
|
11277
|
+
"\u{1F504} **Kuma \u2014 Session Resume**",
|
|
11278
|
+
`\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`,
|
|
11279
|
+
""
|
|
11280
|
+
];
|
|
11281
|
+
try {
|
|
11282
|
+
const db = await getDb();
|
|
11283
|
+
const res = db.exec(
|
|
11284
|
+
"SELECT goal, tool_calls, edits, rollbacks, failures, safety_score, started_at FROM sessions ORDER BY started_at DESC LIMIT 1"
|
|
11285
|
+
);
|
|
11286
|
+
if (res[0]?.values.length) {
|
|
11287
|
+
const row = res[0].values[0];
|
|
11288
|
+
const goal = row[0] || "no goal";
|
|
11289
|
+
const calls = row[1] || 0;
|
|
11290
|
+
const edits = row[2] || 0;
|
|
11291
|
+
const rollbacks = row[3] || 0;
|
|
11292
|
+
const failures2 = row[4] || 0;
|
|
11293
|
+
const safety = row[5] == null ? null : row[5];
|
|
11294
|
+
const time = new Date(row[6] * 1e3).toLocaleString();
|
|
11295
|
+
lines.push("**Last Session**");
|
|
11296
|
+
lines.push(` \u{1F3AF} Goal: ${goal.substring(0, 80)}`);
|
|
11297
|
+
lines.push(` \u{1F550} ${time}`);
|
|
11298
|
+
lines.push(` \u{1F527} ${calls} calls \xB7 \u{1F4DD} ${edits} edits \xB7 \u21A9 ${rollbacks} rollbacks \xB7 \u274C ${failures2} failures${safety != null ? ` \xB7 \u{1F6E1} safety ${safety}` : ""}`);
|
|
11299
|
+
lines.push("");
|
|
11300
|
+
}
|
|
11301
|
+
} catch {
|
|
11302
|
+
}
|
|
11303
|
+
const summary = sessionMemory.getSummary();
|
|
11304
|
+
lines.push("**Current State**");
|
|
11305
|
+
lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
|
|
11306
|
+
const progress = sessionMemory.getGoalProgress();
|
|
11307
|
+
if (progress) {
|
|
11308
|
+
lines.push(` \u{1F4CA} Progress: ${progress.percentage}%${progress.milestone ? ` \u2014 ${progress.milestone}` : ""}`);
|
|
11309
|
+
}
|
|
11310
|
+
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
11311
|
+
lines.push(` \u2705 Completed steps: ${summary.completedSteps?.length || 0}`);
|
|
11312
|
+
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
11313
|
+
lines.push(` \u23F1\uFE0F Session duration: ${summary.sessionDuration}`);
|
|
11314
|
+
const failures = summary.unresolvedFailures;
|
|
11315
|
+
if (failures?.length) {
|
|
11316
|
+
lines.push(` \u26A0\uFE0F Unresolved failures: ${failures.length}`);
|
|
11317
|
+
}
|
|
11318
|
+
lines.push("");
|
|
11319
|
+
try {
|
|
11320
|
+
const changes = await getChanges({ limit: 5 });
|
|
11321
|
+
if (changes && !changes.startsWith("No changes")) {
|
|
11322
|
+
lines.push("**Recent Changes**");
|
|
11323
|
+
lines.push(changes);
|
|
11324
|
+
lines.push("");
|
|
11325
|
+
}
|
|
11326
|
+
} catch {
|
|
10446
11327
|
}
|
|
11328
|
+
const history = sessionMemory.getToolCallHistory(5);
|
|
11329
|
+
if (history.length) {
|
|
11330
|
+
lines.push("**Recent Tool Calls**");
|
|
11331
|
+
for (const h of history) {
|
|
11332
|
+
const action = h.params?.action || "";
|
|
11333
|
+
lines.push(` \u{1F6E0} ${h.toolName}${action ? ` (${action})` : ""}`);
|
|
11334
|
+
}
|
|
11335
|
+
lines.push("");
|
|
11336
|
+
}
|
|
11337
|
+
lines.push("\u{1F4A1} Continue: kuma_context({ action: 'research', scope: '<area>' }) to pick up where you left off.");
|
|
11338
|
+
return lines.join("\n");
|
|
10447
11339
|
}
|
|
10448
11340
|
async function handleInit(_params) {
|
|
10449
11341
|
sessionMemory.setGoal(_params.goal || "Exploring project");
|
|
@@ -10513,6 +11405,15 @@ async function handleInit(_params) {
|
|
|
10513
11405
|
lines.push(formatSafetyScore2(score));
|
|
10514
11406
|
} catch {
|
|
10515
11407
|
}
|
|
11408
|
+
try {
|
|
11409
|
+
const { formatPathRules: formatPathRules2 } = await Promise.resolve().then(() => (init_kumaPathRules(), kumaPathRules_exports));
|
|
11410
|
+
const rulesBlock = formatPathRules2(_params.goal);
|
|
11411
|
+
if (rulesBlock) {
|
|
11412
|
+
lines.push("");
|
|
11413
|
+
lines.push(rulesBlock);
|
|
11414
|
+
}
|
|
11415
|
+
} catch {
|
|
11416
|
+
}
|
|
10516
11417
|
lines.push("", "\u{1F4A1} Call kuma_context({ action: 'research', scope: '<area>' }) to research a specific area.");
|
|
10517
11418
|
return lines.join("\n");
|
|
10518
11419
|
}
|
|
@@ -10635,6 +11536,15 @@ async function handleResearch(params) {
|
|
|
10635
11536
|
lines.push(" \u26A0\uFE0F Memory lookup failed");
|
|
10636
11537
|
}
|
|
10637
11538
|
lines.push("");
|
|
11539
|
+
try {
|
|
11540
|
+
const { formatPathRules: formatPathRules2 } = await Promise.resolve().then(() => (init_kumaPathRules(), kumaPathRules_exports));
|
|
11541
|
+
const rulesBlock = formatPathRules2(scope);
|
|
11542
|
+
if (rulesBlock) {
|
|
11543
|
+
lines.push(rulesBlock);
|
|
11544
|
+
lines.push("");
|
|
11545
|
+
}
|
|
11546
|
+
} catch {
|
|
11547
|
+
}
|
|
10638
11548
|
const newRecord = {
|
|
10639
11549
|
scope,
|
|
10640
11550
|
confidence: record?.confidence || 0.7,
|
|
@@ -10817,12 +11727,12 @@ async function handleProgressive(params) {
|
|
|
10817
11727
|
function computeProjectHash(scope) {
|
|
10818
11728
|
try {
|
|
10819
11729
|
const root = getProjectRoot();
|
|
10820
|
-
const files =
|
|
11730
|
+
const files = fs12.readdirSync(root).slice(0, 20);
|
|
10821
11731
|
const hash = crypto4.createHash("md5");
|
|
10822
11732
|
hash.update(scope);
|
|
10823
11733
|
for (const f of files) {
|
|
10824
11734
|
try {
|
|
10825
|
-
const stat =
|
|
11735
|
+
const stat = fs12.statSync(path13.join(root, f));
|
|
10826
11736
|
hash.update(`${f}:${stat.mtimeMs}`);
|
|
10827
11737
|
} catch {
|
|
10828
11738
|
}
|
|
@@ -10891,8 +11801,8 @@ function getNoiseFilterPolicy() {
|
|
|
10891
11801
|
// src/tools/kumaMemoryTool.ts
|
|
10892
11802
|
init_pathValidator();
|
|
10893
11803
|
init_pathValidator();
|
|
10894
|
-
import
|
|
10895
|
-
import
|
|
11804
|
+
import fs16 from "fs";
|
|
11805
|
+
import path17 from "path";
|
|
10896
11806
|
var MEMORY_ALIASES = {
|
|
10897
11807
|
// Session synonyms
|
|
10898
11808
|
"session": "session",
|
|
@@ -11057,6 +11967,8 @@ async function handleMemory(params) {
|
|
|
11057
11967
|
return handleAddNode(params);
|
|
11058
11968
|
case "feature":
|
|
11059
11969
|
return handleFeature(params);
|
|
11970
|
+
case "goal_progress":
|
|
11971
|
+
return handleGoalProgress(params);
|
|
11060
11972
|
case "delete_node":
|
|
11061
11973
|
return handleDeleteNode(params);
|
|
11062
11974
|
case "graph_health":
|
|
@@ -11068,8 +11980,6 @@ async function handleMemory(params) {
|
|
|
11068
11980
|
}
|
|
11069
11981
|
case "harvest":
|
|
11070
11982
|
return handleHarvest(params);
|
|
11071
|
-
case "mine":
|
|
11072
|
-
return handleMine(params);
|
|
11073
11983
|
case "session_mine":
|
|
11074
11984
|
return handleSessionMine(params);
|
|
11075
11985
|
case "noise_policy":
|
|
@@ -11157,9 +12067,9 @@ async function handleResearchSave(params) {
|
|
|
11157
12067
|
} catch {
|
|
11158
12068
|
}
|
|
11159
12069
|
try {
|
|
11160
|
-
const researchDir =
|
|
11161
|
-
if (!
|
|
11162
|
-
|
|
12070
|
+
const researchDir = path17.join(getProjectRoot(), ".kuma", "research");
|
|
12071
|
+
if (!fs16.existsSync(researchDir)) fs16.mkdirSync(researchDir, { recursive: true });
|
|
12072
|
+
fs16.writeFileSync(path17.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
|
|
11163
12073
|
} catch {
|
|
11164
12074
|
}
|
|
11165
12075
|
sessionMemory.recordMemoryAction("research_save");
|
|
@@ -11623,13 +12533,22 @@ async function handleFeature(params) {
|
|
|
11623
12533
|
tags,
|
|
11624
12534
|
risk
|
|
11625
12535
|
});
|
|
11626
|
-
sessionMemory.recordMemoryAction("
|
|
12536
|
+
sessionMemory.recordMemoryAction("feature");
|
|
11627
12537
|
return `\u2705 **Feature "${params.title}" recorded** \u2014 ${result.nodeCount} node(s), ${result.edgeCount} edge(s)
|
|
11628
12538
|
\u2B50 Level: feature
|
|
11629
12539
|
\u{1F4C1} Files: ${files.length}
|
|
11630
12540
|
\u{1F3F7}\uFE0F Tags: ${tags.length}
|
|
11631
12541
|
\u26A0\uFE0F Risk: ${risk}`;
|
|
11632
12542
|
}
|
|
12543
|
+
async function handleGoalProgress(params) {
|
|
12544
|
+
const pct = params.confidence ?? 0;
|
|
12545
|
+
const ms = params.content || params.title;
|
|
12546
|
+
sessionMemory.setGoalProgress(pct, ms);
|
|
12547
|
+
const p = sessionMemory.getGoalProgress();
|
|
12548
|
+
if (!p) return "Error";
|
|
12549
|
+
const bar = "\u2588".repeat(Math.floor(p.percentage / 10)) + "\u2591".repeat(10 - Math.floor(p.percentage / 10));
|
|
12550
|
+
return `\u{1F4CA} ${bar} ${p.percentage}%${p.milestone ? " \u2014 " + p.milestone : ""}`;
|
|
12551
|
+
}
|
|
11633
12552
|
async function handleGraphHealth(_params) {
|
|
11634
12553
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
11635
12554
|
const db = await getDb2();
|
|
@@ -11760,72 +12679,72 @@ init_sessionMemory();
|
|
|
11760
12679
|
|
|
11761
12680
|
// src/engine/kumaLock.ts
|
|
11762
12681
|
init_pathValidator();
|
|
11763
|
-
import
|
|
11764
|
-
import
|
|
12682
|
+
import fs17 from "fs";
|
|
12683
|
+
import path18 from "path";
|
|
11765
12684
|
var LOCKS_DIR = ".kuma/locks";
|
|
11766
12685
|
function locksDir() {
|
|
11767
|
-
return
|
|
12686
|
+
return path18.join(getProjectRoot(), LOCKS_DIR);
|
|
11768
12687
|
}
|
|
11769
12688
|
function ensureLocksDir() {
|
|
11770
12689
|
const dir = locksDir();
|
|
11771
|
-
if (!
|
|
12690
|
+
if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
|
|
11772
12691
|
}
|
|
11773
12692
|
function lockPath(filePath) {
|
|
11774
12693
|
const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
|
|
11775
|
-
return
|
|
12694
|
+
return path18.join(locksDir(), `${safeName}.lock.json`);
|
|
11776
12695
|
}
|
|
11777
12696
|
function acquireLock(filePath, agentId) {
|
|
11778
12697
|
ensureLocksDir();
|
|
11779
12698
|
const lp = lockPath(filePath);
|
|
11780
12699
|
const id = agentId || `agent-${process.pid}`;
|
|
11781
|
-
if (
|
|
12700
|
+
if (fs17.existsSync(lp)) {
|
|
11782
12701
|
try {
|
|
11783
|
-
const existing = JSON.parse(
|
|
12702
|
+
const existing = JSON.parse(fs17.readFileSync(lp, "utf-8"));
|
|
11784
12703
|
if (existing.agentId === id) {
|
|
11785
12704
|
return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
|
|
11786
12705
|
}
|
|
11787
12706
|
const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
|
|
11788
12707
|
if (elapsed > 300) {
|
|
11789
|
-
|
|
12708
|
+
fs17.unlinkSync(lp);
|
|
11790
12709
|
} else {
|
|
11791
12710
|
return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
|
|
11792
12711
|
}
|
|
11793
12712
|
} catch {
|
|
11794
|
-
|
|
12713
|
+
fs17.unlinkSync(lp);
|
|
11795
12714
|
}
|
|
11796
12715
|
}
|
|
11797
12716
|
const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
|
|
11798
|
-
|
|
12717
|
+
fs17.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
|
|
11799
12718
|
return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
|
|
11800
12719
|
}
|
|
11801
12720
|
function releaseLock(filePath, agentId) {
|
|
11802
12721
|
ensureLocksDir();
|
|
11803
12722
|
const lp = lockPath(filePath);
|
|
11804
12723
|
const id = agentId || `agent-${process.pid}`;
|
|
11805
|
-
if (!
|
|
12724
|
+
if (!fs17.existsSync(lp)) {
|
|
11806
12725
|
return `\u26A0\uFE0F No lock found for \`${filePath}\``;
|
|
11807
12726
|
}
|
|
11808
12727
|
try {
|
|
11809
|
-
const existing = JSON.parse(
|
|
12728
|
+
const existing = JSON.parse(fs17.readFileSync(lp, "utf-8"));
|
|
11810
12729
|
if (existing.agentId !== id) {
|
|
11811
12730
|
return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
|
|
11812
12731
|
}
|
|
11813
|
-
|
|
12732
|
+
fs17.unlinkSync(lp);
|
|
11814
12733
|
return `\u{1F513} **Lock released** on \`${filePath}\``;
|
|
11815
12734
|
} catch {
|
|
11816
|
-
|
|
12735
|
+
fs17.unlinkSync(lp);
|
|
11817
12736
|
return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
|
|
11818
12737
|
}
|
|
11819
12738
|
}
|
|
11820
12739
|
function listLocks() {
|
|
11821
12740
|
ensureLocksDir();
|
|
11822
12741
|
const dir = locksDir();
|
|
11823
|
-
const files =
|
|
12742
|
+
const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
|
|
11824
12743
|
if (files.length === 0) return "\u{1F513} No active locks.";
|
|
11825
12744
|
const lines = ["\u{1F512} **Active Locks:**", ""];
|
|
11826
12745
|
for (const f of files) {
|
|
11827
12746
|
try {
|
|
11828
|
-
const entry = JSON.parse(
|
|
12747
|
+
const entry = JSON.parse(fs17.readFileSync(path18.join(dir, f), "utf-8"));
|
|
11829
12748
|
const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
|
|
11830
12749
|
lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
|
|
11831
12750
|
} catch {
|
|
@@ -11836,9 +12755,9 @@ function listLocks() {
|
|
|
11836
12755
|
}
|
|
11837
12756
|
function isLocked(filePath) {
|
|
11838
12757
|
const lp = lockPath(filePath);
|
|
11839
|
-
if (!
|
|
12758
|
+
if (!fs17.existsSync(lp)) return { locked: false };
|
|
11840
12759
|
try {
|
|
11841
|
-
const entry = JSON.parse(
|
|
12760
|
+
const entry = JSON.parse(fs17.readFileSync(lp, "utf-8"));
|
|
11842
12761
|
return { locked: true, by: entry.agentId, since: entry.acquiredAt };
|
|
11843
12762
|
} catch {
|
|
11844
12763
|
return { locked: false };
|
|
@@ -11848,16 +12767,16 @@ function cleanStaleLocks() {
|
|
|
11848
12767
|
ensureLocksDir();
|
|
11849
12768
|
const dir = locksDir();
|
|
11850
12769
|
let count = 0;
|
|
11851
|
-
for (const f of
|
|
12770
|
+
for (const f of fs17.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
|
|
11852
12771
|
try {
|
|
11853
|
-
const entry = JSON.parse(
|
|
12772
|
+
const entry = JSON.parse(fs17.readFileSync(path18.join(dir, f), "utf-8"));
|
|
11854
12773
|
if (Date.now() - entry.acquiredAt > 3e5) {
|
|
11855
|
-
|
|
12774
|
+
fs17.unlinkSync(path18.join(dir, f));
|
|
11856
12775
|
count++;
|
|
11857
12776
|
}
|
|
11858
12777
|
} catch {
|
|
11859
12778
|
try {
|
|
11860
|
-
|
|
12779
|
+
fs17.unlinkSync(path18.join(dir, f));
|
|
11861
12780
|
count++;
|
|
11862
12781
|
} catch {
|
|
11863
12782
|
}
|
|
@@ -11873,8 +12792,8 @@ init_safetyAudit();
|
|
|
11873
12792
|
|
|
11874
12793
|
// src/tools/kumaPolicy.ts
|
|
11875
12794
|
init_pathValidator();
|
|
11876
|
-
import
|
|
11877
|
-
import
|
|
12795
|
+
import fs18 from "fs";
|
|
12796
|
+
import path19 from "path";
|
|
11878
12797
|
var DEFAULT_POLICY = {
|
|
11879
12798
|
never_touch: [
|
|
11880
12799
|
".env",
|
|
@@ -11900,15 +12819,15 @@ var DEFAULT_POLICY = {
|
|
|
11900
12819
|
};
|
|
11901
12820
|
function loadPolicy() {
|
|
11902
12821
|
const root = getProjectRoot();
|
|
11903
|
-
const ymlPath =
|
|
11904
|
-
const yamlPath =
|
|
12822
|
+
const ymlPath = path19.join(root, ".kuma", "policy.yml");
|
|
12823
|
+
const yamlPath = path19.join(root, ".kuma", "policy.yaml");
|
|
11905
12824
|
let policyContent = null;
|
|
11906
12825
|
let policyPath = null;
|
|
11907
|
-
if (
|
|
11908
|
-
policyContent =
|
|
12826
|
+
if (fs18.existsSync(ymlPath)) {
|
|
12827
|
+
policyContent = fs18.readFileSync(ymlPath, "utf-8");
|
|
11909
12828
|
policyPath = ymlPath;
|
|
11910
|
-
} else if (
|
|
11911
|
-
policyContent =
|
|
12829
|
+
} else if (fs18.existsSync(yamlPath)) {
|
|
12830
|
+
policyContent = fs18.readFileSync(yamlPath, "utf-8");
|
|
11912
12831
|
policyPath = yamlPath;
|
|
11913
12832
|
}
|
|
11914
12833
|
if (!policyContent) {
|
|
@@ -12018,9 +12937,9 @@ function checkFilePathPolicy(filePath, policy) {
|
|
|
12018
12937
|
if (policy.max_file_size && policy.max_file_size > 0) {
|
|
12019
12938
|
try {
|
|
12020
12939
|
const root = getProjectRoot();
|
|
12021
|
-
const fullPath =
|
|
12022
|
-
if (
|
|
12023
|
-
const sizeKB =
|
|
12940
|
+
const fullPath = path19.join(root, filePath);
|
|
12941
|
+
if (fs18.existsSync(fullPath)) {
|
|
12942
|
+
const sizeKB = fs18.statSync(fullPath).size / 1024;
|
|
12024
12943
|
if (sizeKB > policy.max_file_size) {
|
|
12025
12944
|
violations.push({
|
|
12026
12945
|
rule: "max_file_size",
|
|
@@ -12187,12 +13106,12 @@ async function safetyCheck(action, filePath, command) {
|
|
|
12187
13106
|
}
|
|
12188
13107
|
if (filePath) {
|
|
12189
13108
|
try {
|
|
12190
|
-
const
|
|
12191
|
-
const
|
|
13109
|
+
const fs30 = await import("fs");
|
|
13110
|
+
const path31 = await import("path");
|
|
12192
13111
|
const root = process.cwd();
|
|
12193
|
-
const fullPath =
|
|
12194
|
-
if (
|
|
12195
|
-
const stat =
|
|
13112
|
+
const fullPath = path31.resolve(root, filePath);
|
|
13113
|
+
if (fs30.existsSync(fullPath)) {
|
|
13114
|
+
const stat = fs30.statSync(fullPath);
|
|
12196
13115
|
const sizeKB = Math.round(stat.size / 1024);
|
|
12197
13116
|
if (sizeKB > 500) {
|
|
12198
13117
|
checks.push({
|
|
@@ -12202,12 +13121,12 @@ async function safetyCheck(action, filePath, command) {
|
|
|
12202
13121
|
});
|
|
12203
13122
|
}
|
|
12204
13123
|
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
12205
|
-
const baseName =
|
|
13124
|
+
const baseName = path31.basename(filePath, path31.extname(filePath));
|
|
12206
13125
|
const testPatterns = [
|
|
12207
13126
|
filePath.replace(/\.(ts|js)$/, ".test.$1"),
|
|
12208
13127
|
filePath.replace(/\.(ts|js)$/, ".spec.$1"),
|
|
12209
13128
|
filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
|
|
12210
|
-
`**/__tests__/**/${baseName}.test.${
|
|
13129
|
+
`**/__tests__/**/${baseName}.test.${path31.extname(filePath).slice(1)}`
|
|
12211
13130
|
];
|
|
12212
13131
|
let hasTests = false;
|
|
12213
13132
|
const fg = await import("fast-glob");
|
|
@@ -12223,7 +13142,7 @@ async function safetyCheck(action, filePath, command) {
|
|
|
12223
13142
|
name: "Tests Check",
|
|
12224
13143
|
passed: true,
|
|
12225
13144
|
// Not a blocker, just informative
|
|
12226
|
-
detail: `\u{1F9EA} No test file found for "${
|
|
13145
|
+
detail: `\u{1F9EA} No test file found for "${path31.basename(filePath)}" \u2014 consider adding tests`
|
|
12227
13146
|
});
|
|
12228
13147
|
} else {
|
|
12229
13148
|
checks.push({
|
|
@@ -12312,8 +13231,8 @@ init_sessionMemory();
|
|
|
12312
13231
|
// src/guards/antiPatternDetector.ts
|
|
12313
13232
|
init_pathValidator();
|
|
12314
13233
|
init_sessionMemory();
|
|
12315
|
-
import
|
|
12316
|
-
import
|
|
13234
|
+
import fs19 from "fs";
|
|
13235
|
+
import path20 from "path";
|
|
12317
13236
|
import { execSync as execSync6 } from "child_process";
|
|
12318
13237
|
var SCRIPT_PATCH_PATTERNS = [
|
|
12319
13238
|
"writeFileSync",
|
|
@@ -12340,20 +13259,20 @@ function findPatchScripts(projectRoot) {
|
|
|
12340
13259
|
const warnings = [];
|
|
12341
13260
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
12342
13261
|
for (const file of recentFiles) {
|
|
12343
|
-
const ext =
|
|
13262
|
+
const ext = path20.extname(file).toLowerCase();
|
|
12344
13263
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
12345
|
-
const relativePath =
|
|
13264
|
+
const relativePath = path20.relative(projectRoot, file);
|
|
12346
13265
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
|
|
12347
13266
|
continue;
|
|
12348
13267
|
}
|
|
12349
13268
|
try {
|
|
12350
|
-
const content =
|
|
13269
|
+
const content = fs19.readFileSync(file, "utf-8").toLowerCase();
|
|
12351
13270
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
12352
13271
|
if (matchedPattern) {
|
|
12353
13272
|
warnings.push({
|
|
12354
13273
|
severity: "high",
|
|
12355
13274
|
pattern: "script-patching",
|
|
12356
|
-
message: `Created script file that modifies other files: ${
|
|
13275
|
+
message: `Created script file that modifies other files: ${path20.basename(file)}`,
|
|
12357
13276
|
suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
|
|
12358
13277
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
12359
13278
|
filePath: relativePath
|
|
@@ -12387,15 +13306,15 @@ function detectBashGrepUsage() {
|
|
|
12387
13306
|
function scanRecentFiles(projectRoot) {
|
|
12388
13307
|
const recent = [];
|
|
12389
13308
|
try {
|
|
12390
|
-
const entries =
|
|
13309
|
+
const entries = fs19.readdirSync(projectRoot, { withFileTypes: true });
|
|
12391
13310
|
const now = Date.now();
|
|
12392
13311
|
const recentThreshold = 30 * 60 * 1e3;
|
|
12393
13312
|
for (const entry of entries) {
|
|
12394
13313
|
if (!entry.isFile()) continue;
|
|
12395
13314
|
try {
|
|
12396
|
-
const stat =
|
|
13315
|
+
const stat = fs19.statSync(path20.join(projectRoot, entry.name));
|
|
12397
13316
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
12398
|
-
recent.push(
|
|
13317
|
+
recent.push(path20.join(projectRoot, entry.name));
|
|
12399
13318
|
}
|
|
12400
13319
|
} catch {
|
|
12401
13320
|
continue;
|
|
@@ -12419,14 +13338,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
12419
13338
|
const prefix = line.substring(0, 2);
|
|
12420
13339
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
12421
13340
|
const file = line.substring(3).trim();
|
|
12422
|
-
const ext =
|
|
13341
|
+
const ext = path20.extname(file).toLowerCase();
|
|
12423
13342
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
12424
13343
|
const isRootLevel = !file.includes("/");
|
|
12425
13344
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
12426
13345
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
12427
|
-
const fullPath =
|
|
12428
|
-
if (
|
|
12429
|
-
const content =
|
|
13346
|
+
const fullPath = path20.join(projectRoot, file);
|
|
13347
|
+
if (fs19.existsSync(fullPath)) {
|
|
13348
|
+
const content = fs19.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
12430
13349
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
12431
13350
|
if (matchedPattern) {
|
|
12432
13351
|
warnings.push({
|
|
@@ -12449,7 +13368,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
12449
13368
|
if (deletedStdout) {
|
|
12450
13369
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
12451
13370
|
for (const file of deletedFiles) {
|
|
12452
|
-
const ext =
|
|
13371
|
+
const ext = path20.extname(file).toLowerCase();
|
|
12453
13372
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
12454
13373
|
warnings.push({
|
|
12455
13374
|
severity: "high",
|
|
@@ -12511,21 +13430,21 @@ function detectAllAntiPatterns() {
|
|
|
12511
13430
|
// src/engine/contextSnapshot.ts
|
|
12512
13431
|
init_sessionMemory();
|
|
12513
13432
|
init_pathValidator();
|
|
12514
|
-
import
|
|
12515
|
-
import
|
|
13433
|
+
import fs20 from "fs";
|
|
13434
|
+
import path21 from "path";
|
|
12516
13435
|
import { execSync as execSync7 } from "child_process";
|
|
12517
13436
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
12518
13437
|
function snapshotDir() {
|
|
12519
|
-
return
|
|
13438
|
+
return path21.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
12520
13439
|
}
|
|
12521
13440
|
function ensureSnapshotDir() {
|
|
12522
13441
|
const dir = snapshotDir();
|
|
12523
|
-
if (!
|
|
12524
|
-
|
|
13442
|
+
if (!fs20.existsSync(dir)) {
|
|
13443
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
12525
13444
|
}
|
|
12526
13445
|
}
|
|
12527
13446
|
function snapshotFilePath(timestamp) {
|
|
12528
|
-
return
|
|
13447
|
+
return path21.join(snapshotDir(), `${timestamp}.json`);
|
|
12529
13448
|
}
|
|
12530
13449
|
function saveSnapshot(goal) {
|
|
12531
13450
|
try {
|
|
@@ -12546,7 +13465,7 @@ function saveSnapshot(goal) {
|
|
|
12546
13465
|
hasConventions: !!summary.hasConventions
|
|
12547
13466
|
};
|
|
12548
13467
|
try {
|
|
12549
|
-
|
|
13468
|
+
fs20.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
12550
13469
|
} catch {
|
|
12551
13470
|
}
|
|
12552
13471
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -12693,6 +13612,14 @@ async function handleKumaGuard(params) {
|
|
|
12693
13612
|
suggestion: "After tracing a flow, record it:\nkuma_memory({ action: 'arch_flow', content: 'domain: <Name> | hops: <file1> \u2192 <file2> \u2192 <file3>' })"
|
|
12694
13613
|
});
|
|
12695
13614
|
}
|
|
13615
|
+
if (readCalls >= 5 && recordingSummary.features === 0) {
|
|
13616
|
+
warnings.push({
|
|
13617
|
+
severity: "low",
|
|
13618
|
+
pattern: "explore-without-feature",
|
|
13619
|
+
message: `${readCalls} file reads but 0 features recorded. High-level module understanding helps future sessions.`,
|
|
13620
|
+
suggestion: "After exploring a module/feature, record it:\nkuma_memory({ action: 'feature', title: '<FeatureName>', content: 'description', scope: 'file1.ts,file2.ts' })"
|
|
13621
|
+
});
|
|
13622
|
+
}
|
|
12696
13623
|
}
|
|
12697
13624
|
if (check === "context") {
|
|
12698
13625
|
const snapshot = saveSnapshot(stats.goal);
|
|
@@ -12859,12 +13786,12 @@ async function handleSecurity(params) {
|
|
|
12859
13786
|
sessionMemory.recordToolCall("kuma_safety_security", {});
|
|
12860
13787
|
if (params.filePath) {
|
|
12861
13788
|
try {
|
|
12862
|
-
const
|
|
12863
|
-
const
|
|
13789
|
+
const fs30 = await import("fs");
|
|
13790
|
+
const path31 = await import("path");
|
|
12864
13791
|
const root = process.cwd();
|
|
12865
|
-
const fullPath =
|
|
12866
|
-
if (
|
|
12867
|
-
const content =
|
|
13792
|
+
const fullPath = path31.resolve(root, params.filePath);
|
|
13793
|
+
if (fs30.existsSync(fullPath) && fs30.statSync(fullPath).isFile()) {
|
|
13794
|
+
const content = fs30.readFileSync(fullPath, "utf-8");
|
|
12868
13795
|
const lines = content.split("\n");
|
|
12869
13796
|
const findings = [];
|
|
12870
13797
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -12918,23 +13845,23 @@ async function handleGitignore(_params) {
|
|
|
12918
13845
|
}
|
|
12919
13846
|
async function handleClean(_params) {
|
|
12920
13847
|
sessionMemory.recordToolCall("kuma_safety_clean", {});
|
|
12921
|
-
const
|
|
12922
|
-
const
|
|
13848
|
+
const fs30 = await import("fs");
|
|
13849
|
+
const path31 = await import("path");
|
|
12923
13850
|
const root = process.cwd();
|
|
12924
|
-
const scratchDir =
|
|
13851
|
+
const scratchDir = path31.resolve(root, ".kuma", "scratch");
|
|
12925
13852
|
let removed = 0;
|
|
12926
|
-
if (
|
|
13853
|
+
if (fs30.existsSync(scratchDir)) {
|
|
12927
13854
|
try {
|
|
12928
|
-
const entries =
|
|
13855
|
+
const entries = fs30.readdirSync(scratchDir);
|
|
12929
13856
|
for (const entry of entries) {
|
|
12930
|
-
const fullPath =
|
|
13857
|
+
const fullPath = path31.join(scratchDir, entry);
|
|
12931
13858
|
try {
|
|
12932
|
-
const stat =
|
|
13859
|
+
const stat = fs30.statSync(fullPath);
|
|
12933
13860
|
if (stat.isFile()) {
|
|
12934
|
-
|
|
13861
|
+
fs30.unlinkSync(fullPath);
|
|
12935
13862
|
removed++;
|
|
12936
13863
|
} else if (stat.isDirectory()) {
|
|
12937
|
-
|
|
13864
|
+
fs30.rmSync(fullPath, { recursive: true, force: true });
|
|
12938
13865
|
removed++;
|
|
12939
13866
|
}
|
|
12940
13867
|
} catch {
|
|
@@ -13050,7 +13977,7 @@ async function handleVerify(params) {
|
|
|
13050
13977
|
} else if (recordingSummary.total > 0) {
|
|
13051
13978
|
recordingWarning = `
|
|
13052
13979
|
|
|
13053
|
-
\u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
|
|
13980
|
+
\u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save, ${recordingSummary.features} feature)`;
|
|
13054
13981
|
}
|
|
13055
13982
|
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
|
|
13056
13983
|
const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
|
|
@@ -13131,7 +14058,7 @@ function registerAllTools(server) {
|
|
|
13131
14058
|
"kuma_context",
|
|
13132
14059
|
"**Call FIRST every session.** Understand your project before making changes.\n\nWORKFLOW:\n\u2022 STEP 1 (ALWAYS): `init` \u2014 load project brief, restore session\n\u2022 STEP 3 (BEFORE edits): `research` \u2014 5-step pipeline (cache\u2192staleness\u2192graph\u2192impact\u2192decision)\n\u2022 STEP 8 (END of session): `changes` \u2014 review what you modified\n\nOther actions: impact (analyze changes), navigate (trace flow), health (score 0-100), digest (compact briefing), drift (detect staleness).",
|
|
13133
14060
|
{
|
|
13134
|
-
action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches", "sync", "visualize", "digest", "drift", "progressive"]).describe("Action: init=project brief, research=5-step research pipeline (REQUIRED before edits), impact=analyze change effects, navigate=trace code flow, changes=view change log, rollback=undo a change by ID, researches=list all cached research, sync=unified batch state, visualize=Mermaid knowledge graph diagram, digest=ultra-compact <500 token project briefing (Issue #18), drift=detect memory staleness & code drift (Issue #20), progressive=progressive context loading (Issue #25), health=project health score"),
|
|
14061
|
+
action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches", "sync", "visualize", "digest", "drift", "progressive", "resume"]).describe("Action: init=project brief, research=5-step research pipeline (REQUIRED before edits), impact=analyze change effects, navigate=trace code flow, changes=view change log, rollback=undo a change by ID, researches=list all cached research, sync=unified batch state, visualize=Mermaid knowledge graph diagram, digest=ultra-compact <500 token project briefing (Issue #18), drift=detect memory staleness & code drift (Issue #20), progressive=progressive context loading (Issue #25), health=project health score, resume=load previous session context (goal, progress, changes)"),
|
|
13135
14062
|
scope: z.string().optional().describe("Research scope for research/progressive action"),
|
|
13136
14063
|
target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
|
|
13137
14064
|
goal: z.string().optional().describe("Current goal (for init/health)"),
|
|
@@ -13159,9 +14086,9 @@ function registerAllTools(server) {
|
|
|
13159
14086
|
);
|
|
13160
14087
|
server.tool(
|
|
13161
14088
|
"kuma_memory",
|
|
13162
|
-
"**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 AFTER tracing COMPLETE flow (max 5 core 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.",
|
|
14089
|
+
"**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 AFTER tracing COMPLETE flow (max 5 core files).\n\u2022 STEP 8: `decision` \u2014 IMMEDIATELY when choosing between options. Preserves rationale.\n\u2022 STEP 9: `feature` \u2014 WHEN identifying a high-level feature/module (e.g. Auth, Billing). Creates owns edges to files.\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.",
|
|
13163
14090
|
{
|
|
13164
|
-
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"),
|
|
14091
|
+
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", "goal_progress"]).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, goal_progress=update goal completion percentage"),
|
|
13165
14092
|
scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine"),
|
|
13166
14093
|
query: z.string().optional().describe("Search query for search action"),
|
|
13167
14094
|
content: z.string().optional().describe("Content/notes for research_save / context"),
|
|
@@ -13309,6 +14236,7 @@ Usage:
|
|
|
13309
14236
|
npx @plumpslabs/kuma init --skip-existing Skip generation if file exists
|
|
13310
14237
|
npx @plumpslabs/kuma init --claude --cursor Generate specific files
|
|
13311
14238
|
npx @plumpslabs/kuma studio Start Kuma Studio web dashboard (knowledge graph visualizer)
|
|
14239
|
+
npx @plumpslabs/kuma init --legacy Bulk-onboard an existing (legacy) codebase
|
|
13312
14240
|
npx @plumpslabs/kuma init --help Show this help
|
|
13313
14241
|
|
|
13314
14242
|
Available config files:
|
|
@@ -13331,6 +14259,14 @@ If no flags specified, you'll be prompted to select files interactively.
|
|
|
13331
14259
|
}
|
|
13332
14260
|
async function main() {
|
|
13333
14261
|
const args = process.argv.slice(2);
|
|
14262
|
+
if (args[0] === "--version" || args[0] === "-v" || args[0] === "version") {
|
|
14263
|
+
console.log(`\u{1F43B} Kuma v${SERVER_VERSION}`);
|
|
14264
|
+
process.exit(0);
|
|
14265
|
+
}
|
|
14266
|
+
if (args[0] === "--help" || args[0] === "-h" || args[0] === "help") {
|
|
14267
|
+
printHelp();
|
|
14268
|
+
process.exit(0);
|
|
14269
|
+
}
|
|
13334
14270
|
if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
|
|
13335
14271
|
console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
|
|
13336
14272
|
console.error("");
|
|
@@ -13353,14 +14289,14 @@ async function main() {
|
|
|
13353
14289
|
} catch {
|
|
13354
14290
|
}
|
|
13355
14291
|
try {
|
|
13356
|
-
const
|
|
13357
|
-
const
|
|
13358
|
-
const lockDir =
|
|
13359
|
-
if (
|
|
13360
|
-
const pidFile =
|
|
13361
|
-
if (
|
|
14292
|
+
const path31 = await import("path");
|
|
14293
|
+
const fs30 = await import("fs");
|
|
14294
|
+
const lockDir = path31.resolve(process.cwd(), ".kuma/verifier.lock");
|
|
14295
|
+
if (fs30.existsSync(lockDir)) {
|
|
14296
|
+
const pidFile = path31.join(lockDir, "pid");
|
|
14297
|
+
if (fs30.existsSync(pidFile)) {
|
|
13362
14298
|
try {
|
|
13363
|
-
const pid = parseInt(
|
|
14299
|
+
const pid = parseInt(fs30.readFileSync(pidFile, "utf-8"), 10);
|
|
13364
14300
|
if (pid && pid !== process.pid) {
|
|
13365
14301
|
try {
|
|
13366
14302
|
process.kill(-pid, "SIGKILL");
|
|
@@ -13376,7 +14312,7 @@ async function main() {
|
|
|
13376
14312
|
} catch {
|
|
13377
14313
|
}
|
|
13378
14314
|
}
|
|
13379
|
-
|
|
14315
|
+
fs30.rmSync(lockDir, { recursive: true, force: true });
|
|
13380
14316
|
}
|
|
13381
14317
|
if (killedCount === 0) {
|
|
13382
14318
|
console.error("\u2705 No running verifications found to kill.");
|
|
@@ -13470,6 +14406,18 @@ async function main() {
|
|
|
13470
14406
|
printHelp();
|
|
13471
14407
|
process.exit(0);
|
|
13472
14408
|
}
|
|
14409
|
+
if (flags.includes("--legacy")) {
|
|
14410
|
+
try {
|
|
14411
|
+
const { runLegacyOnboarding: runLegacyOnboarding2 } = await Promise.resolve().then(() => (init_kumaLegacyOnboard(), kumaLegacyOnboard_exports));
|
|
14412
|
+
console.error("\u{1F43B} Kuma Legacy Onboarding \u2014 making your existing codebase agent-ready...");
|
|
14413
|
+
const report = await runLegacyOnboarding2();
|
|
14414
|
+
console.log(report);
|
|
14415
|
+
} catch (err) {
|
|
14416
|
+
console.error(`\u274C Legacy onboarding failed: ${err}`);
|
|
14417
|
+
process.exit(1);
|
|
14418
|
+
}
|
|
14419
|
+
process.exit(0);
|
|
14420
|
+
}
|
|
13473
14421
|
const requestedFlags = flags.filter((f) => f.startsWith("--"));
|
|
13474
14422
|
let selectedTypes;
|
|
13475
14423
|
if (requestedFlags.length === 0) {
|
|
@@ -13524,23 +14472,23 @@ async function main() {
|
|
|
13524
14472
|
});
|
|
13525
14473
|
const output = formatInitResults(results);
|
|
13526
14474
|
console.log(output);
|
|
13527
|
-
const
|
|
13528
|
-
const
|
|
13529
|
-
const matchaSkills =
|
|
13530
|
-
const matchaAgents =
|
|
14475
|
+
const fs30 = await import("fs");
|
|
14476
|
+
const path31 = await import("path");
|
|
14477
|
+
const matchaSkills = path31.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
14478
|
+
const matchaAgents = path31.resolve(
|
|
13531
14479
|
process.cwd(),
|
|
13532
14480
|
".agents/skills/matcha/SKILL.md"
|
|
13533
14481
|
);
|
|
13534
|
-
const matchaRootSkills =
|
|
14482
|
+
const matchaRootSkills = path31.resolve(
|
|
13535
14483
|
process.cwd(),
|
|
13536
14484
|
"skills/matcha/SKILL.md"
|
|
13537
14485
|
);
|
|
13538
|
-
const matchaAgentsMd =
|
|
13539
|
-
const matchaWindsurfRules =
|
|
14486
|
+
const matchaAgentsMd = path31.resolve(process.cwd(), "AGENTS.md");
|
|
14487
|
+
const matchaWindsurfRules = path31.resolve(
|
|
13540
14488
|
process.cwd(),
|
|
13541
14489
|
".windsurf"
|
|
13542
14490
|
);
|
|
13543
|
-
if (
|
|
14491
|
+
if (fs30.existsSync(matchaSkills) || fs30.existsSync(matchaAgents) || fs30.existsSync(matchaRootSkills) || fs30.existsSync(matchaAgentsMd) || fs30.existsSync(matchaWindsurfRules)) {
|
|
13544
14492
|
console.error(
|
|
13545
14493
|
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
13546
14494
|
);
|
|
@@ -13554,13 +14502,13 @@ async function main() {
|
|
|
13554
14502
|
(async () => {
|
|
13555
14503
|
try {
|
|
13556
14504
|
const { generateInitMdContent: generateInitMdContent2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
13557
|
-
const
|
|
13558
|
-
const
|
|
13559
|
-
const initMdPath =
|
|
13560
|
-
if (!
|
|
13561
|
-
const kumaDir =
|
|
13562
|
-
if (!
|
|
13563
|
-
|
|
14505
|
+
const fs30 = await import("fs");
|
|
14506
|
+
const path31 = await import("path");
|
|
14507
|
+
const initMdPath = path31.resolve(process.cwd(), ".kuma/init.md");
|
|
14508
|
+
if (!fs30.existsSync(initMdPath)) {
|
|
14509
|
+
const kumaDir = path31.dirname(initMdPath);
|
|
14510
|
+
if (!fs30.existsSync(kumaDir)) fs30.mkdirSync(kumaDir, { recursive: true });
|
|
14511
|
+
fs30.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
|
|
13564
14512
|
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
13565
14513
|
}
|
|
13566
14514
|
} catch (err) {
|
|
@@ -13571,8 +14519,8 @@ async function main() {
|
|
|
13571
14519
|
try {
|
|
13572
14520
|
const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
|
|
13573
14521
|
const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
|
|
13574
|
-
const
|
|
13575
|
-
const
|
|
14522
|
+
const fs30 = await import("fs");
|
|
14523
|
+
const path31 = await import("path");
|
|
13576
14524
|
const detection = detectAgent2();
|
|
13577
14525
|
if (!detection.primary) {
|
|
13578
14526
|
console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
|
|
@@ -13580,22 +14528,22 @@ async function main() {
|
|
|
13580
14528
|
}
|
|
13581
14529
|
const agentType = detection.primary;
|
|
13582
14530
|
const skillPath = getSkillPath2(agentType);
|
|
13583
|
-
const fullPath =
|
|
13584
|
-
if (
|
|
14531
|
+
const fullPath = path31.resolve(process.cwd(), skillPath);
|
|
14532
|
+
if (fs30.existsSync(fullPath)) {
|
|
13585
14533
|
console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
|
|
13586
14534
|
return;
|
|
13587
14535
|
}
|
|
13588
|
-
const dir =
|
|
13589
|
-
if (!
|
|
13590
|
-
|
|
14536
|
+
const dir = path31.dirname(fullPath);
|
|
14537
|
+
if (!fs30.existsSync(dir)) fs30.mkdirSync(dir, { recursive: true });
|
|
14538
|
+
fs30.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
|
|
13591
14539
|
console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
|
|
13592
14540
|
const secondaryFiles = getSecondaryFiles2(agentType);
|
|
13593
14541
|
for (const sf of secondaryFiles) {
|
|
13594
|
-
const sfPath =
|
|
13595
|
-
const sfDir =
|
|
13596
|
-
if (!
|
|
13597
|
-
if (!
|
|
13598
|
-
|
|
14542
|
+
const sfPath = path31.resolve(process.cwd(), sf.path);
|
|
14543
|
+
const sfDir = path31.dirname(sfPath);
|
|
14544
|
+
if (!fs30.existsSync(sfPath)) {
|
|
14545
|
+
if (!fs30.existsSync(sfDir)) fs30.mkdirSync(sfDir, { recursive: true });
|
|
14546
|
+
fs30.writeFileSync(sfPath, sf.content, "utf-8");
|
|
13599
14547
|
console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
|
|
13600
14548
|
}
|
|
13601
14549
|
}
|
|
@@ -13620,11 +14568,11 @@ async function main() {
|
|
|
13620
14568
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
|
|
13621
14569
|
}
|
|
13622
14570
|
try {
|
|
13623
|
-
const
|
|
13624
|
-
const
|
|
13625
|
-
const scratchDir =
|
|
13626
|
-
if (!
|
|
13627
|
-
|
|
14571
|
+
const fs30 = await import("fs");
|
|
14572
|
+
const path31 = await import("path");
|
|
14573
|
+
const scratchDir = path31.resolve(process.cwd(), ".kuma", "scratch");
|
|
14574
|
+
if (!fs30.existsSync(scratchDir)) {
|
|
14575
|
+
fs30.mkdirSync(scratchDir, { recursive: true });
|
|
13628
14576
|
console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
|
|
13629
14577
|
}
|
|
13630
14578
|
} catch (err) {
|