@plumpslabs/kuma 2.3.31 → 2.3.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1245 -508
- package/package.json +1 -1
- package/packages/ide/studio/dist/index.js +276 -97
- package/packages/ide/studio/public/index.html +141 -4
package/dist/index.js
CHANGED
|
@@ -2509,6 +2509,223 @@ var init_kumaSelfHeal = __esm({
|
|
|
2509
2509
|
}
|
|
2510
2510
|
});
|
|
2511
2511
|
|
|
2512
|
+
// src/engine/languageSupport.ts
|
|
2513
|
+
import path5 from "path";
|
|
2514
|
+
function grepIncludeFlags() {
|
|
2515
|
+
return SOURCE_EXTENSIONS.map((e) => `--include="*${e}"`).join(" ");
|
|
2516
|
+
}
|
|
2517
|
+
function isTestFile(filePath) {
|
|
2518
|
+
const lower = filePath.toLowerCase();
|
|
2519
|
+
const base = path5.basename(filePath);
|
|
2520
|
+
const ext = path5.extname(base);
|
|
2521
|
+
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
2522
|
+
const segments = lower.split(/[\\/]/);
|
|
2523
|
+
for (const lang of LANGUAGES) {
|
|
2524
|
+
if (!lang.extensions.includes(ext.toLowerCase())) continue;
|
|
2525
|
+
for (const p of lang.testPrefixes) {
|
|
2526
|
+
if (p && stem.startsWith(p)) return true;
|
|
2527
|
+
}
|
|
2528
|
+
for (const s of lang.testSuffixes) {
|
|
2529
|
+
if (s && stem.endsWith(s)) return true;
|
|
2530
|
+
}
|
|
2531
|
+
for (const d of lang.testDirs) {
|
|
2532
|
+
const dLower = d.toLowerCase();
|
|
2533
|
+
if (dLower.includes("/")) {
|
|
2534
|
+
if (lower.includes(`${dLower}/`) || lower.includes(`${dLower}\\`)) return true;
|
|
2535
|
+
} else if (segments.includes(dLower)) {
|
|
2536
|
+
return true;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
return /(\.test\.|\.spec\.|_test\.|_spec\.)/.test(lower) || /(\/|^)(__tests__|tests?|specs?)(\/|$)/.test(lower);
|
|
2541
|
+
}
|
|
2542
|
+
function getTestCandidates(sourcePath) {
|
|
2543
|
+
const ext = path5.extname(sourcePath).toLowerCase();
|
|
2544
|
+
const dir = path5.dirname(sourcePath);
|
|
2545
|
+
const base = path5.basename(sourcePath, ext);
|
|
2546
|
+
const lang = LANGUAGES.find((l) => l.extensions.includes(ext));
|
|
2547
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
2548
|
+
const push = (p) => {
|
|
2549
|
+
if (p && !candidates.has(p)) candidates.add(p);
|
|
2550
|
+
};
|
|
2551
|
+
if (lang) {
|
|
2552
|
+
for (const suffix of lang.testSuffixes) {
|
|
2553
|
+
if (!suffix) continue;
|
|
2554
|
+
push(path5.join(dir, `${base}${suffix}${ext}`));
|
|
2555
|
+
}
|
|
2556
|
+
for (const prefix of lang.testPrefixes) {
|
|
2557
|
+
if (prefix) push(path5.join(dir, `${prefix}${base}${ext}`));
|
|
2558
|
+
}
|
|
2559
|
+
for (const td of lang.testDirs) {
|
|
2560
|
+
if (!td.includes("/")) {
|
|
2561
|
+
push(path5.join(dir, td, `${base}${ext}`));
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
push(path5.join(dir, `${base}.test${ext}`));
|
|
2566
|
+
push(path5.join(dir, `${base}.spec${ext}`));
|
|
2567
|
+
push(path5.join(dir, `${base}_test${ext}`));
|
|
2568
|
+
push(path5.join(dir, `test_${base}${ext}`));
|
|
2569
|
+
push(path5.join(dir, "__tests__", `${base}${ext}`));
|
|
2570
|
+
push(path5.join(dir, "tests", `${base}${ext}`));
|
|
2571
|
+
return [...candidates];
|
|
2572
|
+
}
|
|
2573
|
+
function matchImportPath(line) {
|
|
2574
|
+
for (const re of IMPORT_PATTERNS) {
|
|
2575
|
+
re.lastIndex = 0;
|
|
2576
|
+
const m = re.exec(line);
|
|
2577
|
+
if (m) return (m[1] || m[2] || "").trim();
|
|
2578
|
+
}
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
var LANGUAGES, SOURCE_EXTENSIONS, SOURCE_EXT_GLOB, DEFAULT_SOURCE_INCLUDES, IMPORT_PATTERNS;
|
|
2582
|
+
var init_languageSupport = __esm({
|
|
2583
|
+
"src/engine/languageSupport.ts"() {
|
|
2584
|
+
"use strict";
|
|
2585
|
+
LANGUAGES = [
|
|
2586
|
+
{
|
|
2587
|
+
name: "typescript",
|
|
2588
|
+
extensions: [".ts", ".tsx", ".mts", ".cts"],
|
|
2589
|
+
testPrefixes: [],
|
|
2590
|
+
testSuffixes: [".test", ".spec"],
|
|
2591
|
+
testDirs: ["__tests__", "tests", "test", "spec"]
|
|
2592
|
+
},
|
|
2593
|
+
{
|
|
2594
|
+
name: "javascript",
|
|
2595
|
+
extensions: [".js", ".jsx", ".mjs", ".cjs"],
|
|
2596
|
+
testPrefixes: [],
|
|
2597
|
+
testSuffixes: [".test", ".spec"],
|
|
2598
|
+
testDirs: ["__tests__", "tests", "test", "spec"]
|
|
2599
|
+
},
|
|
2600
|
+
{
|
|
2601
|
+
name: "python",
|
|
2602
|
+
extensions: [".py", ".pyw"],
|
|
2603
|
+
testPrefixes: ["test_"],
|
|
2604
|
+
testSuffixes: ["_test"],
|
|
2605
|
+
testDirs: ["tests", "test"]
|
|
2606
|
+
},
|
|
2607
|
+
{
|
|
2608
|
+
name: "go",
|
|
2609
|
+
extensions: [".go"],
|
|
2610
|
+
testPrefixes: [],
|
|
2611
|
+
testSuffixes: ["_test"],
|
|
2612
|
+
testDirs: ["testdata"]
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
name: "rust",
|
|
2616
|
+
extensions: [".rs"],
|
|
2617
|
+
testPrefixes: [],
|
|
2618
|
+
testSuffixes: ["_test"],
|
|
2619
|
+
testDirs: ["tests"]
|
|
2620
|
+
},
|
|
2621
|
+
{
|
|
2622
|
+
name: "java",
|
|
2623
|
+
extensions: [".java"],
|
|
2624
|
+
testPrefixes: ["Test"],
|
|
2625
|
+
testSuffixes: ["Test", "Tests"],
|
|
2626
|
+
testDirs: ["src/test", "test", "tests"]
|
|
2627
|
+
},
|
|
2628
|
+
{
|
|
2629
|
+
name: "kotlin",
|
|
2630
|
+
extensions: [".kt", ".kts"],
|
|
2631
|
+
testPrefixes: ["Test"],
|
|
2632
|
+
testSuffixes: ["Test", "Tests"],
|
|
2633
|
+
testDirs: ["src/test"]
|
|
2634
|
+
},
|
|
2635
|
+
{
|
|
2636
|
+
name: "csharp",
|
|
2637
|
+
extensions: [".cs"],
|
|
2638
|
+
testPrefixes: [],
|
|
2639
|
+
testSuffixes: ["Tests", "Test"],
|
|
2640
|
+
testDirs: ["Tests", "test", "tests"]
|
|
2641
|
+
},
|
|
2642
|
+
{
|
|
2643
|
+
name: "ruby",
|
|
2644
|
+
extensions: [".rb"],
|
|
2645
|
+
testPrefixes: [],
|
|
2646
|
+
testSuffixes: ["_spec", "_test"],
|
|
2647
|
+
testDirs: ["spec", "test", "tests"]
|
|
2648
|
+
},
|
|
2649
|
+
{
|
|
2650
|
+
name: "php",
|
|
2651
|
+
extensions: [".php"],
|
|
2652
|
+
testPrefixes: [],
|
|
2653
|
+
testSuffixes: ["Test"],
|
|
2654
|
+
testDirs: ["tests", "test"]
|
|
2655
|
+
},
|
|
2656
|
+
{
|
|
2657
|
+
name: "cpp",
|
|
2658
|
+
extensions: [".c", ".cc", ".cpp", ".h", ".hpp", ".hxx"],
|
|
2659
|
+
testPrefixes: [],
|
|
2660
|
+
testSuffixes: ["_test"],
|
|
2661
|
+
testDirs: ["tests", "test"]
|
|
2662
|
+
},
|
|
2663
|
+
{
|
|
2664
|
+
name: "swift",
|
|
2665
|
+
extensions: [".swift"],
|
|
2666
|
+
testPrefixes: [],
|
|
2667
|
+
testSuffixes: ["Tests"],
|
|
2668
|
+
testDirs: ["Tests", "test"]
|
|
2669
|
+
},
|
|
2670
|
+
{
|
|
2671
|
+
name: "scala",
|
|
2672
|
+
extensions: [".scala"],
|
|
2673
|
+
testPrefixes: [],
|
|
2674
|
+
testSuffixes: ["Spec", "Test", "Tests"],
|
|
2675
|
+
testDirs: ["src/test"]
|
|
2676
|
+
},
|
|
2677
|
+
{
|
|
2678
|
+
name: "dart",
|
|
2679
|
+
extensions: [".dart"],
|
|
2680
|
+
testPrefixes: [],
|
|
2681
|
+
testSuffixes: ["_test"],
|
|
2682
|
+
testDirs: ["test", "tests"]
|
|
2683
|
+
},
|
|
2684
|
+
{
|
|
2685
|
+
name: "shell",
|
|
2686
|
+
extensions: [".sh", ".bash", ".zsh"],
|
|
2687
|
+
testPrefixes: [],
|
|
2688
|
+
testSuffixes: [],
|
|
2689
|
+
testDirs: ["test", "tests"]
|
|
2690
|
+
}
|
|
2691
|
+
];
|
|
2692
|
+
SOURCE_EXTENSIONS = [
|
|
2693
|
+
...new Set(LANGUAGES.flatMap((l) => l.extensions))
|
|
2694
|
+
];
|
|
2695
|
+
SOURCE_EXT_GLOB = `{${SOURCE_EXTENSIONS.map((e) => e.slice(1)).join(",")}}`;
|
|
2696
|
+
DEFAULT_SOURCE_INCLUDES = [
|
|
2697
|
+
`src/**/*.${SOURCE_EXT_GLOB}`,
|
|
2698
|
+
`app/**/*.${SOURCE_EXT_GLOB}`,
|
|
2699
|
+
`lib/**/*.${SOURCE_EXT_GLOB}`,
|
|
2700
|
+
`packages/**/*.${SOURCE_EXT_GLOB}`,
|
|
2701
|
+
`server/**/*.${SOURCE_EXT_GLOB}`,
|
|
2702
|
+
`api/**/*.${SOURCE_EXT_GLOB}`
|
|
2703
|
+
];
|
|
2704
|
+
IMPORT_PATTERNS = [
|
|
2705
|
+
// JS/TS: import x from 'y' | import {a} from 'y' | import * as x from 'y'
|
|
2706
|
+
/import\s+(?:\{[^}]*\}\s+from\s+)?(?:[^'"\n]*\s+from\s+)?['"]([^'"]+)['"]/,
|
|
2707
|
+
// JS/TS (CJS): const x = require('y')
|
|
2708
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/,
|
|
2709
|
+
// Python: import y | import y.z | from y import x | from .y import x
|
|
2710
|
+
/^\s*(?:from\s+([.\w]+)\s+import\s+\S+|import\s+([.\w]+))/m,
|
|
2711
|
+
// Go: import "y" | import alias "y"
|
|
2712
|
+
/^\s*import\s+(?:\w+\s+)?"([^"]+)"/m,
|
|
2713
|
+
// Rust: use y::z | extern crate y
|
|
2714
|
+
/^\s*use\s+([\w:]+)/m,
|
|
2715
|
+
// C/C++: #include <y> | #include "y"
|
|
2716
|
+
/^\s*#\s*include\s*[<"]([^>"]+)[>"]/m,
|
|
2717
|
+
// Ruby: require 'y' | require_relative 'y'
|
|
2718
|
+
/^\s*require(?:_relative)?\s+['"]([^'"]+)['"]/m,
|
|
2719
|
+
// PHP: require 'y' | include 'y' | use y\z;
|
|
2720
|
+
/^\s*(?:require(?:_once)?|include(?:_once)?)\s*\(?\s*['"]([^'"]+)['"]/m,
|
|
2721
|
+
// Java: import y.z.w;
|
|
2722
|
+
/^\s*import\s+([\w.]+)\s*;/m,
|
|
2723
|
+
// C#: using y.z.w;
|
|
2724
|
+
/^\s*using\s+([\w.]+)\s*;/m
|
|
2725
|
+
];
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
|
|
2512
2729
|
// src/engine/kumaGraph.ts
|
|
2513
2730
|
var kumaGraph_exports = {};
|
|
2514
2731
|
__export(kumaGraph_exports, {
|
|
@@ -3105,7 +3322,7 @@ async function queryGraph(params) {
|
|
|
3105
3322
|
if (results.length === 0) {
|
|
3106
3323
|
return `\u{1F50D} **Graph Query** \u2014 No nodes found for "${query}".
|
|
3107
3324
|
|
|
3108
|
-
Try a different search term or
|
|
3325
|
+
Try a different search term or use kuma_context({ action: 'research', scope: '<term>' }) to build graph context.`;
|
|
3109
3326
|
}
|
|
3110
3327
|
const lines = [
|
|
3111
3328
|
`\u{1F50D} **Graph Nodes** \u2014 ${results.length} result(s) for "${query}"`,
|
|
@@ -3139,7 +3356,7 @@ Try a different search term or check if the graph has been built by using tools
|
|
|
3139
3356
|
if (results.length === 0) {
|
|
3140
3357
|
return `\u{1F50D} **Graph Edges** \u2014 No connections found for node "${query}".
|
|
3141
3358
|
|
|
3142
|
-
Try
|
|
3359
|
+
Try kuma_context({ action: 'research', scope: '<term>' }) to build related graph nodes first.`;
|
|
3143
3360
|
}
|
|
3144
3361
|
const lines = [
|
|
3145
3362
|
`\u{1F517} **Graph Edges** \u2014 ${results.length} connection(s) for "${query}"`,
|
|
@@ -3156,7 +3373,7 @@ Try kuma_graph_query({ type: 'nodes', query: '<search>' }) to find node IDs firs
|
|
|
3156
3373
|
const parts = query.split("\u2192").map((s) => s.trim());
|
|
3157
3374
|
if (parts.length !== 2) {
|
|
3158
3375
|
return `\u26A0\uFE0F For path queries, use format: "sourceNodeID \u2192 targetNodeID"
|
|
3159
|
-
Example:
|
|
3376
|
+
Example: kuma_context({ action: 'impact', target: 'login' }) \u2192 traces connections between nodes.`;
|
|
3160
3377
|
}
|
|
3161
3378
|
const [sourceId, targetId] = parts;
|
|
3162
3379
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -3396,27 +3613,27 @@ async function codebaseSearchFallback(query, limit = 20) {
|
|
|
3396
3613
|
if (results.length >= limit || depth > MAX_WALK_DEPTH) return;
|
|
3397
3614
|
let entries = [];
|
|
3398
3615
|
try {
|
|
3399
|
-
entries =
|
|
3616
|
+
entries = fs30.readdirSync(dir);
|
|
3400
3617
|
} catch {
|
|
3401
3618
|
return;
|
|
3402
3619
|
}
|
|
3403
3620
|
for (const entry of entries) {
|
|
3404
3621
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
|
|
3405
|
-
const full =
|
|
3622
|
+
const full = path31.join(dir, entry);
|
|
3406
3623
|
try {
|
|
3407
|
-
const stat =
|
|
3624
|
+
const stat = fs30.statSync(full);
|
|
3408
3625
|
if (stat.isDirectory()) {
|
|
3409
3626
|
walk2(full, depth + 1);
|
|
3410
3627
|
} else if (entry.toLowerCase().includes(query.toLowerCase())) {
|
|
3411
|
-
results.push(
|
|
3628
|
+
results.push(path31.relative(root, full));
|
|
3412
3629
|
}
|
|
3413
3630
|
} catch {
|
|
3414
3631
|
}
|
|
3415
3632
|
}
|
|
3416
3633
|
};
|
|
3417
3634
|
var walk = walk2;
|
|
3418
|
-
const
|
|
3419
|
-
const
|
|
3635
|
+
const fs30 = await import("fs");
|
|
3636
|
+
const path31 = await import("path");
|
|
3420
3637
|
const root = process.cwd();
|
|
3421
3638
|
const results = [];
|
|
3422
3639
|
const MAX_WALK_DEPTH = 8;
|
|
@@ -3559,7 +3776,7 @@ async function codebaseImpactFallback(target) {
|
|
|
3559
3776
|
const { execSync: execSync8 } = await import("child_process");
|
|
3560
3777
|
const root = process.cwd();
|
|
3561
3778
|
const grepResult = execSync8(
|
|
3562
|
-
`grep -rn
|
|
3779
|
+
`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
3780
|
{ cwd: root, encoding: "utf-8", timeout: 5e3, maxBuffer: 256 * 1024 }
|
|
3564
3781
|
).trim();
|
|
3565
3782
|
const files = grepResult ? grepResult.split("\n").filter(Boolean).filter((f) => !f.startsWith("./")) : [];
|
|
@@ -3567,7 +3784,7 @@ async function codebaseImpactFallback(target) {
|
|
|
3567
3784
|
return { symbol: target, references: 0, files: 0, testFiles: 0, entryPoints: [], risk: "low" };
|
|
3568
3785
|
}
|
|
3569
3786
|
const fileCount = files.length;
|
|
3570
|
-
const testFiles = files.filter((f) =>
|
|
3787
|
+
const testFiles = files.filter((f) => isTestFile(f)).length;
|
|
3571
3788
|
const risk = fileCount > 20 ? "high" : fileCount > 5 ? "medium" : "low";
|
|
3572
3789
|
return {
|
|
3573
3790
|
symbol: target,
|
|
@@ -3666,6 +3883,7 @@ var init_kumaGraph = __esm({
|
|
|
3666
3883
|
"use strict";
|
|
3667
3884
|
init_kumaDb();
|
|
3668
3885
|
init_kumaSelfHeal();
|
|
3886
|
+
init_languageSupport();
|
|
3669
3887
|
GRAPH_LIMITS = {
|
|
3670
3888
|
maxNodes: 5e3,
|
|
3671
3889
|
// Max nodes before pruning
|
|
@@ -3689,10 +3907,10 @@ __export(kumaMemory_exports, {
|
|
|
3689
3907
|
shouldRecordDecision: () => shouldRecordDecision
|
|
3690
3908
|
});
|
|
3691
3909
|
import fs5 from "fs";
|
|
3692
|
-
import
|
|
3910
|
+
import path6 from "path";
|
|
3693
3911
|
function scoreMemoryRelevance(context, limit = 5) {
|
|
3694
3912
|
const results = [];
|
|
3695
|
-
const kumaDir =
|
|
3913
|
+
const kumaDir = path6.join(getProjectRoot(), MEMORY_DIR);
|
|
3696
3914
|
if (!fs5.existsSync(kumaDir)) return [];
|
|
3697
3915
|
const terms = context.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
3698
3916
|
if (terms.length === 0) return [];
|
|
@@ -3700,7 +3918,7 @@ function scoreMemoryRelevance(context, limit = 5) {
|
|
|
3700
3918
|
const files = fs5.readdirSync(kumaDir).filter((f) => f.endsWith(".md"));
|
|
3701
3919
|
for (const file of files) {
|
|
3702
3920
|
try {
|
|
3703
|
-
const content = fs5.readFileSync(
|
|
3921
|
+
const content = fs5.readFileSync(path6.join(kumaDir, file), "utf-8");
|
|
3704
3922
|
const lower = content.toLowerCase();
|
|
3705
3923
|
let matchCount = 0;
|
|
3706
3924
|
for (const term of terms) {
|
|
@@ -4250,6 +4468,136 @@ var init_safetyScore = __esm({
|
|
|
4250
4468
|
}
|
|
4251
4469
|
});
|
|
4252
4470
|
|
|
4471
|
+
// src/engine/kumaPathRules.ts
|
|
4472
|
+
var kumaPathRules_exports = {};
|
|
4473
|
+
__export(kumaPathRules_exports, {
|
|
4474
|
+
ensureRulesDir: () => ensureRulesDir,
|
|
4475
|
+
formatPathRules: () => formatPathRules,
|
|
4476
|
+
getRulesForScope: () => getRulesForScope,
|
|
4477
|
+
hasPathRules: () => hasPathRules,
|
|
4478
|
+
loadPathRules: () => loadPathRules,
|
|
4479
|
+
matchRulePath: () => matchRulePath
|
|
4480
|
+
});
|
|
4481
|
+
import fs6 from "fs";
|
|
4482
|
+
import path7 from "path";
|
|
4483
|
+
function rulesDir() {
|
|
4484
|
+
return path7.join(getProjectRoot(), RULES_DIR);
|
|
4485
|
+
}
|
|
4486
|
+
function ensureRulesDir() {
|
|
4487
|
+
try {
|
|
4488
|
+
const dir = rulesDir();
|
|
4489
|
+
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
4490
|
+
} catch {
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
function parseRuleFile(filePath) {
|
|
4494
|
+
try {
|
|
4495
|
+
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
4496
|
+
const id = path7.basename(filePath, ".md");
|
|
4497
|
+
const fm = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
4498
|
+
if (!fm) {
|
|
4499
|
+
return { id, description: id, paths: ["*"], content: raw.trim(), filePath };
|
|
4500
|
+
}
|
|
4501
|
+
const meta = fm[1];
|
|
4502
|
+
const body = fm[2].trim();
|
|
4503
|
+
const description = meta.match(/description:\s*(.+)/)?.[1]?.trim() || id;
|
|
4504
|
+
const paths = [];
|
|
4505
|
+
const pathSection = meta.match(/paths:\s*\r?\n([\s\S]*?)(?=\r?\n\S|$)/);
|
|
4506
|
+
if (pathSection) {
|
|
4507
|
+
for (const line of pathSection[1].split("\n")) {
|
|
4508
|
+
const m = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/);
|
|
4509
|
+
if (m) paths.push(m[1].trim());
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
return { id, description, paths: paths.length ? paths : ["*"], content: body, filePath };
|
|
4513
|
+
} catch {
|
|
4514
|
+
return null;
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
function loadPathRules() {
|
|
4518
|
+
ensureRulesDir();
|
|
4519
|
+
try {
|
|
4520
|
+
const dir = rulesDir();
|
|
4521
|
+
return fs6.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => parseRuleFile(path7.join(dir, f))).filter((r) => r !== null);
|
|
4522
|
+
} catch {
|
|
4523
|
+
return [];
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
function matchRulePath(pattern, target) {
|
|
4527
|
+
if (pattern === "*" || pattern === "**") return true;
|
|
4528
|
+
const p = pattern.replace(/\\/g, "/").toLowerCase();
|
|
4529
|
+
const t = target.replace(/\\/g, "/").toLowerCase();
|
|
4530
|
+
if (p.endsWith("/**")) {
|
|
4531
|
+
const prefix = p.slice(0, -3);
|
|
4532
|
+
return t === prefix || t.startsWith(prefix + "/");
|
|
4533
|
+
}
|
|
4534
|
+
if (!/[*?]/.test(p) && (t === p || t.startsWith(p + "/"))) return true;
|
|
4535
|
+
let re = "";
|
|
4536
|
+
for (let i = 0; i < p.length; i++) {
|
|
4537
|
+
const c = p[i];
|
|
4538
|
+
if (c === "*") {
|
|
4539
|
+
if (p[i + 1] === "*") {
|
|
4540
|
+
re += i === 0 ? "(?:.*/)?" : ".*";
|
|
4541
|
+
if (p[i + 2] === "/") i += 2;
|
|
4542
|
+
} else {
|
|
4543
|
+
re += "[^/]*";
|
|
4544
|
+
}
|
|
4545
|
+
} else if (c === "?") {
|
|
4546
|
+
re += "[^/]";
|
|
4547
|
+
} else if (/[.()*+?^${}()|[\]\\]/.test(c)) {
|
|
4548
|
+
re += `\\${c}`;
|
|
4549
|
+
} else {
|
|
4550
|
+
re += c;
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
try {
|
|
4554
|
+
const rx = new RegExp(`^${re}$`);
|
|
4555
|
+
return rx.test(t);
|
|
4556
|
+
} catch {
|
|
4557
|
+
return t === p;
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
function getRulesForScope(scope) {
|
|
4561
|
+
const rules = loadPathRules();
|
|
4562
|
+
if (rules.length === 0) return [];
|
|
4563
|
+
const target = (scope || "").toLowerCase();
|
|
4564
|
+
if (!target) return rules.filter((r) => r.paths.includes("*"));
|
|
4565
|
+
return rules.filter((rule) => rule.paths.some((p) => matchRulePath(p, target)));
|
|
4566
|
+
}
|
|
4567
|
+
function formatPathRules(scope) {
|
|
4568
|
+
const matches = getRulesForScope(scope);
|
|
4569
|
+
if (matches.length === 0) return "";
|
|
4570
|
+
const lines = [
|
|
4571
|
+
`\u{1F4CC} **Path Rules** (${matches.length} rule(s) apply to "${scope || "project"}")`,
|
|
4572
|
+
`\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`,
|
|
4573
|
+
""
|
|
4574
|
+
];
|
|
4575
|
+
for (const rule of matches) {
|
|
4576
|
+
lines.push(`**${rule.description}**`);
|
|
4577
|
+
const bodyLines = rule.content.split("\n").filter((l) => l.trim()).slice(0, 6);
|
|
4578
|
+
for (const bl of bodyLines) {
|
|
4579
|
+
lines.push(` ${bl.substring(0, 140)}`);
|
|
4580
|
+
}
|
|
4581
|
+
if (rule.content.split("\n").filter((l) => l.trim()).length > 6) {
|
|
4582
|
+
lines.push(` \u2026 (full rule in ${rule.filePath})`);
|
|
4583
|
+
}
|
|
4584
|
+
lines.push("");
|
|
4585
|
+
}
|
|
4586
|
+
lines.push(`\u{1F4A1} Path rules load on-demand from ${RULES_DIR}/ \u2014 add/remove .md files anytime.`);
|
|
4587
|
+
return lines.join("\n");
|
|
4588
|
+
}
|
|
4589
|
+
function hasPathRules() {
|
|
4590
|
+
return loadPathRules().length > 0;
|
|
4591
|
+
}
|
|
4592
|
+
var RULES_DIR;
|
|
4593
|
+
var init_kumaPathRules = __esm({
|
|
4594
|
+
"src/engine/kumaPathRules.ts"() {
|
|
4595
|
+
"use strict";
|
|
4596
|
+
init_pathValidator();
|
|
4597
|
+
RULES_DIR = ".kuma/rules";
|
|
4598
|
+
}
|
|
4599
|
+
});
|
|
4600
|
+
|
|
4253
4601
|
// src/engine/kumaCodeScanner.ts
|
|
4254
4602
|
var kumaCodeScanner_exports = {};
|
|
4255
4603
|
__export(kumaCodeScanner_exports, {
|
|
@@ -4258,8 +4606,8 @@ __export(kumaCodeScanner_exports, {
|
|
|
4258
4606
|
scanCodebase: () => scanCodebase
|
|
4259
4607
|
});
|
|
4260
4608
|
import fastGlob from "fast-glob";
|
|
4261
|
-
import
|
|
4262
|
-
import
|
|
4609
|
+
import fs7 from "fs";
|
|
4610
|
+
import path8 from "path";
|
|
4263
4611
|
function getRoot() {
|
|
4264
4612
|
if (!_cachedRoot) _cachedRoot = getProjectRoot();
|
|
4265
4613
|
return _cachedRoot;
|
|
@@ -4270,10 +4618,10 @@ function lineAt(content, index) {
|
|
|
4270
4618
|
function getDirectoryDirs(filePaths) {
|
|
4271
4619
|
const dirs = /* @__PURE__ */ new Set();
|
|
4272
4620
|
for (const fp of filePaths) {
|
|
4273
|
-
let dir =
|
|
4621
|
+
let dir = path8.dirname(fp);
|
|
4274
4622
|
while (dir && dir !== "." && dir !== "/") {
|
|
4275
4623
|
dirs.add(dir);
|
|
4276
|
-
dir =
|
|
4624
|
+
dir = path8.dirname(dir);
|
|
4277
4625
|
}
|
|
4278
4626
|
}
|
|
4279
4627
|
return [...dirs].sort();
|
|
@@ -4283,21 +4631,14 @@ async function scanCodebase(options = {}) {
|
|
|
4283
4631
|
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
4284
4632
|
const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
4285
4633
|
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
|
-
];
|
|
4634
|
+
const includePatterns = options.include || [...DEFAULT_SOURCE_INCLUDES];
|
|
4294
4635
|
if (options.scope) {
|
|
4295
4636
|
includePatterns.length = 0;
|
|
4296
4637
|
if (options.scope.includes("/") || options.scope.includes(".")) {
|
|
4297
4638
|
includePatterns.push(options.scope);
|
|
4298
4639
|
} else {
|
|
4299
|
-
includePatterns.push(`**/*${options.scope}
|
|
4300
|
-
includePatterns.push(`**/*${options.scope}
|
|
4640
|
+
includePatterns.push(`**/*${options.scope}*/**/*.${SOURCE_EXT_GLOB}`);
|
|
4641
|
+
includePatterns.push(`**/*${options.scope}*.${SOURCE_EXT_GLOB}`);
|
|
4301
4642
|
}
|
|
4302
4643
|
}
|
|
4303
4644
|
const ignorePatterns = [
|
|
@@ -4330,16 +4671,16 @@ async function scanCodebase(options = {}) {
|
|
|
4330
4671
|
const scannedFilePaths = [];
|
|
4331
4672
|
for (let i = 0; i < scannedCount; i++) {
|
|
4332
4673
|
const filePath = files[i];
|
|
4333
|
-
const fullPath =
|
|
4334
|
-
if (!
|
|
4335
|
-
const stat =
|
|
4674
|
+
const fullPath = path8.join(root, filePath);
|
|
4675
|
+
if (!fs7.existsSync(fullPath)) continue;
|
|
4676
|
+
const stat = fs7.statSync(fullPath);
|
|
4336
4677
|
if (stat.size > maxFileSize) continue;
|
|
4337
4678
|
const mtime = stat.mtimeMs;
|
|
4338
4679
|
const cached = fileCache.get(filePath);
|
|
4339
4680
|
if (cached === mtime && !options.force) continue;
|
|
4340
4681
|
fileCache.set(filePath, mtime);
|
|
4341
4682
|
try {
|
|
4342
|
-
const content =
|
|
4683
|
+
const content = fs7.readFileSync(fullPath, "utf-8");
|
|
4343
4684
|
const parsed = parseFile(filePath, content);
|
|
4344
4685
|
allParsed.push({ filePath, parsed, content });
|
|
4345
4686
|
scannedFilePaths.push(filePath);
|
|
@@ -4382,7 +4723,7 @@ async function scanCodebase(options = {}) {
|
|
|
4382
4723
|
});
|
|
4383
4724
|
result.nodeCount++;
|
|
4384
4725
|
for (const fp of scannedFilePaths) {
|
|
4385
|
-
if (
|
|
4726
|
+
if (path8.dirname(fp) === dir) {
|
|
4386
4727
|
const fileId = nodeId("file", fp);
|
|
4387
4728
|
try {
|
|
4388
4729
|
await addEdge({ sourceId: moduleId, targetId: fileId, type: "owns" });
|
|
@@ -4407,7 +4748,7 @@ function parseFile(filePath, content) {
|
|
|
4407
4748
|
routes: [],
|
|
4408
4749
|
imports: [],
|
|
4409
4750
|
exports: [],
|
|
4410
|
-
isTest:
|
|
4751
|
+
isTest: isTestFile(filePath)
|
|
4411
4752
|
};
|
|
4412
4753
|
const lines = content.split("\n");
|
|
4413
4754
|
const isTsx = /\.(tsx|jsx)$/.test(filePath);
|
|
@@ -4739,17 +5080,20 @@ async function recordParsedFile(filePath, parsed, content, result) {
|
|
|
4739
5080
|
}
|
|
4740
5081
|
function resolveImportPath(fromFile, importPath, root) {
|
|
4741
5082
|
if (!importPath.startsWith(".") && !importPath.startsWith("/")) return null;
|
|
4742
|
-
const fromDir =
|
|
4743
|
-
const exts = [
|
|
5083
|
+
const fromDir = path8.dirname(path8.join(root, fromFile));
|
|
5084
|
+
const exts = [
|
|
5085
|
+
...SOURCE_EXTENSIONS,
|
|
5086
|
+
...SOURCE_EXTENSIONS.map((e) => `/index${e}`)
|
|
5087
|
+
];
|
|
4744
5088
|
for (const ext of exts) {
|
|
4745
|
-
const resolved =
|
|
4746
|
-
if (
|
|
5089
|
+
const resolved = path8.resolve(fromDir, importPath + ext);
|
|
5090
|
+
if (fs7.existsSync(resolved)) return path8.relative(root, resolved);
|
|
4747
5091
|
}
|
|
4748
|
-
const dirPath =
|
|
4749
|
-
if (
|
|
4750
|
-
for (const ext of
|
|
4751
|
-
const indexPath =
|
|
4752
|
-
if (
|
|
5092
|
+
const dirPath = path8.resolve(fromDir, importPath);
|
|
5093
|
+
if (fs7.existsSync(dirPath) && fs7.statSync(dirPath).isDirectory()) {
|
|
5094
|
+
for (const ext of SOURCE_EXTENSIONS) {
|
|
5095
|
+
const indexPath = path8.join(dirPath, `index${ext}`);
|
|
5096
|
+
if (fs7.existsSync(indexPath)) return path8.relative(root, indexPath);
|
|
4753
5097
|
}
|
|
4754
5098
|
}
|
|
4755
5099
|
return null;
|
|
@@ -4784,12 +5128,13 @@ async function scanAndFormat(options = {}) {
|
|
|
4784
5128
|
const result = await scanCodebase(options);
|
|
4785
5129
|
return formatScanResult(result);
|
|
4786
5130
|
}
|
|
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,
|
|
5131
|
+
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
5132
|
var init_kumaCodeScanner = __esm({
|
|
4789
5133
|
"src/engine/kumaCodeScanner.ts"() {
|
|
4790
5134
|
"use strict";
|
|
4791
5135
|
init_pathValidator();
|
|
4792
5136
|
init_kumaGraph();
|
|
5137
|
+
init_languageSupport();
|
|
4793
5138
|
DEFAULT_MAX_FILES = 200;
|
|
4794
5139
|
DEFAULT_MAX_FILE_SIZE = 100 * 1024;
|
|
4795
5140
|
fileCache = /* @__PURE__ */ new Map();
|
|
@@ -4806,7 +5151,6 @@ var init_kumaCodeScanner = __esm({
|
|
|
4806
5151
|
HONO_ROUTE_RE = /c\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/g;
|
|
4807
5152
|
EXPORT_RE = /export\s+(?:default\s+)?(\w+)/g;
|
|
4808
5153
|
CALL_RE = /(\w+)\s*\(/g;
|
|
4809
|
-
TEST_PATTERNS = [".test.", ".spec.", "__tests__", "/test/"];
|
|
4810
5154
|
knownComponents = /* @__PURE__ */ new Set();
|
|
4811
5155
|
knownFunctions = /* @__PURE__ */ new Set();
|
|
4812
5156
|
symbolLocations = /* @__PURE__ */ new Map();
|
|
@@ -5091,18 +5435,18 @@ __export(domainRules_exports, {
|
|
|
5091
5435
|
readLayer: () => readLayer,
|
|
5092
5436
|
writeLayer: () => writeLayer
|
|
5093
5437
|
});
|
|
5094
|
-
import
|
|
5095
|
-
import
|
|
5438
|
+
import fs8 from "fs";
|
|
5439
|
+
import path9 from "path";
|
|
5096
5440
|
function layerFilePath(layer) {
|
|
5097
|
-
return
|
|
5441
|
+
return path9.join(getProjectRoot(), ".kuma", LAYER_FILES[layer].filename);
|
|
5098
5442
|
}
|
|
5099
5443
|
function ensureLayerFile(layer) {
|
|
5100
5444
|
const fp = layerFilePath(layer);
|
|
5101
|
-
if (!
|
|
5445
|
+
if (!fs8.existsSync(fp)) {
|
|
5102
5446
|
const template = layer === "domain_rules" ? DOMAIN_RULES_TEMPLATE : layer === "arch_flow" ? ARCH_FLOW_TEMPLATE : GOTCHAS_TEMPLATE;
|
|
5103
|
-
const dir =
|
|
5104
|
-
if (!
|
|
5105
|
-
|
|
5447
|
+
const dir = path9.dirname(fp);
|
|
5448
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
5449
|
+
fs8.writeFileSync(fp, template, "utf-8");
|
|
5106
5450
|
console.error(`[DomainRules] Auto-created ${LAYER_FILES[layer].filename}`);
|
|
5107
5451
|
}
|
|
5108
5452
|
return fp;
|
|
@@ -5110,7 +5454,7 @@ function ensureLayerFile(layer) {
|
|
|
5110
5454
|
function readLayer(layer) {
|
|
5111
5455
|
try {
|
|
5112
5456
|
const fp = ensureLayerFile(layer);
|
|
5113
|
-
return
|
|
5457
|
+
return fs8.readFileSync(fp, "utf-8");
|
|
5114
5458
|
} catch (err) {
|
|
5115
5459
|
return `Error reading ${layer}: ${err}`;
|
|
5116
5460
|
}
|
|
@@ -5118,7 +5462,7 @@ function readLayer(layer) {
|
|
|
5118
5462
|
function writeLayer(layer, content) {
|
|
5119
5463
|
try {
|
|
5120
5464
|
const fp = ensureLayerFile(layer);
|
|
5121
|
-
|
|
5465
|
+
fs8.writeFileSync(fp, content, "utf-8");
|
|
5122
5466
|
const label = LAYER_FILES[layer].label;
|
|
5123
5467
|
sessionMemory.recordToolCall("kuma_memory", { action: `write_${layer}` });
|
|
5124
5468
|
return `\u2705 **${label}** updated successfully.`;
|
|
@@ -5129,9 +5473,9 @@ function writeLayer(layer, content) {
|
|
|
5129
5473
|
function appendToLayer(layer, entry) {
|
|
5130
5474
|
try {
|
|
5131
5475
|
const fp = ensureLayerFile(layer);
|
|
5132
|
-
const existing =
|
|
5476
|
+
const existing = fs8.readFileSync(fp, "utf-8");
|
|
5133
5477
|
const newContent = existing.trimEnd() + "\n\n" + entry.trim() + "\n";
|
|
5134
|
-
|
|
5478
|
+
fs8.writeFileSync(fp, newContent, "utf-8");
|
|
5135
5479
|
return `\u2705 Entry added to **${LAYER_FILES[layer].label}**.`;
|
|
5136
5480
|
} catch (err) {
|
|
5137
5481
|
return `\u274C Failed to append: ${err}`;
|
|
@@ -5141,11 +5485,11 @@ function getLayersSummary() {
|
|
|
5141
5485
|
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
5486
|
for (const layer of ["domain_rules", "arch_flow", "gotcha"]) {
|
|
5143
5487
|
const fp = layerFilePath(layer);
|
|
5144
|
-
const exists =
|
|
5488
|
+
const exists = fs8.existsSync(fp);
|
|
5145
5489
|
const label = LAYER_FILES[layer].label;
|
|
5146
5490
|
if (exists) {
|
|
5147
5491
|
try {
|
|
5148
|
-
const content =
|
|
5492
|
+
const content = fs8.readFileSync(fp, "utf-8");
|
|
5149
5493
|
const lineCount = content.split("\n").length;
|
|
5150
5494
|
const sizeKB = Math.round(Buffer.byteLength(content) / 1024);
|
|
5151
5495
|
lines.push(` ${layer === "domain_rules" ? "\u{1F4CB}" : layer === "arch_flow" ? "\u{1F3D7}\uFE0F" : "\u26A0\uFE0F"} **${label}** \u2014 ${lineCount} lines, ${sizeKB}KB`);
|
|
@@ -5162,7 +5506,7 @@ function getActiveGotchas() {
|
|
|
5162
5506
|
const results = [];
|
|
5163
5507
|
try {
|
|
5164
5508
|
const fp = ensureLayerFile("gotcha");
|
|
5165
|
-
const content =
|
|
5509
|
+
const content = fs8.readFileSync(fp, "utf-8");
|
|
5166
5510
|
const lines = content.split("\n");
|
|
5167
5511
|
let currentSection = "";
|
|
5168
5512
|
let currentFilePath = "";
|
|
@@ -5210,7 +5554,7 @@ function generateDigest() {
|
|
|
5210
5554
|
];
|
|
5211
5555
|
try {
|
|
5212
5556
|
const fp1 = ensureLayerFile("domain_rules");
|
|
5213
|
-
const content =
|
|
5557
|
+
const content = fs8.readFileSync(fp1, "utf-8");
|
|
5214
5558
|
const rules = [];
|
|
5215
5559
|
const workflows = [];
|
|
5216
5560
|
for (const line of content.split("\n")) {
|
|
@@ -5226,7 +5570,7 @@ function generateDigest() {
|
|
|
5226
5570
|
}
|
|
5227
5571
|
try {
|
|
5228
5572
|
const fp2 = ensureLayerFile("arch_flow");
|
|
5229
|
-
const content =
|
|
5573
|
+
const content = fs8.readFileSync(fp2, "utf-8");
|
|
5230
5574
|
const chains = [];
|
|
5231
5575
|
for (const line of content.split("\n")) {
|
|
5232
5576
|
if (line.includes("->") && line.includes("[")) chains.push(line.trim());
|
|
@@ -5370,8 +5714,8 @@ __export(contextDigest_exports, {
|
|
|
5370
5714
|
generateContextDigest: () => generateContextDigest,
|
|
5371
5715
|
generateDigest: () => generateContextDigest
|
|
5372
5716
|
});
|
|
5373
|
-
import
|
|
5374
|
-
import
|
|
5717
|
+
import fs9 from "fs";
|
|
5718
|
+
import path10 from "path";
|
|
5375
5719
|
function detectTechStack() {
|
|
5376
5720
|
const root = getProjectRoot();
|
|
5377
5721
|
const stack = {
|
|
@@ -5383,12 +5727,12 @@ function detectTechStack() {
|
|
|
5383
5727
|
monorepo: false
|
|
5384
5728
|
};
|
|
5385
5729
|
try {
|
|
5386
|
-
const pkgPath =
|
|
5387
|
-
if (
|
|
5388
|
-
const pkg = JSON.parse(
|
|
5730
|
+
const pkgPath = path10.join(root, "package.json");
|
|
5731
|
+
if (fs9.existsSync(pkgPath)) {
|
|
5732
|
+
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
5389
5733
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5390
|
-
if (
|
|
5391
|
-
else if (
|
|
5734
|
+
if (fs9.existsSync(path10.join(root, "pnpm-lock.yaml"))) stack.packageManager = "pnpm";
|
|
5735
|
+
else if (fs9.existsSync(path10.join(root, "yarn.lock"))) stack.packageManager = "yarn";
|
|
5392
5736
|
else stack.packageManager = "npm";
|
|
5393
5737
|
stack.monorepo = !!pkg.workspaces;
|
|
5394
5738
|
if (deps.next) stack.framework = "Next.js";
|
|
@@ -5411,13 +5755,13 @@ function detectTechStack() {
|
|
|
5411
5755
|
else if (deps.mocha) stack.testRunner = "Mocha";
|
|
5412
5756
|
else if (deps.playwright) stack.testRunner = "Playwright";
|
|
5413
5757
|
else if (deps.cypress) stack.testRunner = "Cypress";
|
|
5414
|
-
if (
|
|
5758
|
+
if (fs9.existsSync(path10.join(root, "tsconfig.json"))) stack.languages.push("TypeScript");
|
|
5415
5759
|
if (deps.typescript) stack.languages.push("TypeScript");
|
|
5416
|
-
if (
|
|
5417
|
-
if (
|
|
5418
|
-
if (
|
|
5419
|
-
if (
|
|
5420
|
-
if (
|
|
5760
|
+
if (fs9.existsSync(path10.join(root, "jsconfig.json"))) stack.languages.push("JavaScript");
|
|
5761
|
+
if (fs9.existsSync(path10.join(root, "go.mod"))) stack.languages.push("Go");
|
|
5762
|
+
if (fs9.existsSync(path10.join(root, "Cargo.toml"))) stack.languages.push("Rust");
|
|
5763
|
+
if (fs9.existsSync(path10.join(root, "pyproject.toml"))) stack.languages.push("Python");
|
|
5764
|
+
if (fs9.existsSync(path10.join(root, "Gemfile"))) stack.languages.push("Ruby");
|
|
5421
5765
|
if (stack.languages.length === 0) stack.languages.push("JavaScript");
|
|
5422
5766
|
}
|
|
5423
5767
|
} catch {
|
|
@@ -5448,13 +5792,13 @@ function detectEntryPoints() {
|
|
|
5448
5792
|
"lib/index.ts"
|
|
5449
5793
|
];
|
|
5450
5794
|
for (const c of candidates) {
|
|
5451
|
-
if (
|
|
5795
|
+
if (fs9.existsSync(path10.join(root, c))) {
|
|
5452
5796
|
entryPoints.push(c);
|
|
5453
5797
|
}
|
|
5454
5798
|
}
|
|
5455
5799
|
try {
|
|
5456
|
-
const pkg = JSON.parse(
|
|
5457
|
-
if (pkg.main &&
|
|
5800
|
+
const pkg = JSON.parse(fs9.readFileSync(path10.join(root, "package.json"), "utf-8"));
|
|
5801
|
+
if (pkg.main && fs9.existsSync(path10.join(root, pkg.main))) {
|
|
5458
5802
|
if (!entryPoints.includes(pkg.main)) entryPoints.push(pkg.main);
|
|
5459
5803
|
}
|
|
5460
5804
|
} catch {
|
|
@@ -5500,9 +5844,9 @@ async function generateContextDigest() {
|
|
|
5500
5844
|
lines.push(`\u{1F6AA} **Entry**: ${entryPoints.slice(0, 3).join(", ")}`);
|
|
5501
5845
|
}
|
|
5502
5846
|
try {
|
|
5503
|
-
const ruleFile =
|
|
5504
|
-
if (
|
|
5505
|
-
const content =
|
|
5847
|
+
const ruleFile = path10.join(getProjectRoot(), ".kuma", "DOMAIN_RULES.md");
|
|
5848
|
+
if (fs9.existsSync(ruleFile)) {
|
|
5849
|
+
const content = fs9.readFileSync(ruleFile, "utf-8");
|
|
5506
5850
|
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
5851
|
if (rules.length > 0) {
|
|
5508
5852
|
lines.push(`\u{1F4CB} **Rules**: ${rules.slice(0, 5).join(" | ")}`);
|
|
@@ -5548,15 +5892,15 @@ __export(kumaDriftDetector_exports, {
|
|
|
5548
5892
|
getDriftSummary: () => getDriftSummary,
|
|
5549
5893
|
hashFile: () => hashFile
|
|
5550
5894
|
});
|
|
5551
|
-
import
|
|
5552
|
-
import
|
|
5895
|
+
import fs10 from "fs";
|
|
5896
|
+
import path11 from "path";
|
|
5553
5897
|
import crypto3 from "crypto";
|
|
5554
5898
|
function hashFile(filePath) {
|
|
5555
5899
|
try {
|
|
5556
5900
|
const root = getProjectRoot();
|
|
5557
|
-
const fullPath =
|
|
5558
|
-
if (!
|
|
5559
|
-
const content =
|
|
5901
|
+
const fullPath = path11.resolve(root, filePath);
|
|
5902
|
+
if (!fs10.existsSync(fullPath)) return null;
|
|
5903
|
+
const content = fs10.readFileSync(fullPath, "utf-8");
|
|
5560
5904
|
return crypto3.createHash("sha256").update(content).digest("hex");
|
|
5561
5905
|
} catch {
|
|
5562
5906
|
return null;
|
|
@@ -5636,8 +5980,8 @@ async function detectDrift() {
|
|
|
5636
5980
|
let hasStaleFile = false;
|
|
5637
5981
|
for (const fp of filePaths) {
|
|
5638
5982
|
if (!fp) continue;
|
|
5639
|
-
const fullPath =
|
|
5640
|
-
if (!
|
|
5983
|
+
const fullPath = path11.resolve(getProjectRoot(), fp);
|
|
5984
|
+
if (!fs10.existsSync(fullPath)) {
|
|
5641
5985
|
hasStaleFile = true;
|
|
5642
5986
|
break;
|
|
5643
5987
|
}
|
|
@@ -5668,12 +6012,12 @@ function computeScopeHash(scope) {
|
|
|
5668
6012
|
hash.update(scope);
|
|
5669
6013
|
let found = false;
|
|
5670
6014
|
try {
|
|
5671
|
-
const files =
|
|
6015
|
+
const files = fs10.readdirSync(root);
|
|
5672
6016
|
for (const file of files) {
|
|
5673
6017
|
if (file.toLowerCase().includes(scope.toLowerCase())) {
|
|
5674
6018
|
try {
|
|
5675
|
-
if (
|
|
5676
|
-
const content =
|
|
6019
|
+
if (fs10.statSync(path11.join(root, file)).isFile()) {
|
|
6020
|
+
const content = fs10.readFileSync(path11.join(root, file), "utf-8");
|
|
5677
6021
|
hash.update(file);
|
|
5678
6022
|
hash.update(content.substring(0, 1e4));
|
|
5679
6023
|
found = true;
|
|
@@ -5684,11 +6028,11 @@ function computeScopeHash(scope) {
|
|
|
5684
6028
|
}
|
|
5685
6029
|
} catch {
|
|
5686
6030
|
}
|
|
5687
|
-
const researchDir =
|
|
5688
|
-
if (
|
|
5689
|
-
const scopeFile =
|
|
5690
|
-
if (
|
|
5691
|
-
const content =
|
|
6031
|
+
const researchDir = path11.join(root, ".kuma", "research");
|
|
6032
|
+
if (fs10.existsSync(researchDir)) {
|
|
6033
|
+
const scopeFile = path11.join(researchDir, `${scope}.json`);
|
|
6034
|
+
if (fs10.existsSync(scopeFile)) {
|
|
6035
|
+
const content = fs10.readFileSync(scopeFile, "utf-8");
|
|
5692
6036
|
hash.update(content);
|
|
5693
6037
|
found = true;
|
|
5694
6038
|
}
|
|
@@ -5786,26 +6130,26 @@ __export(kumaProgressiveContext_exports, {
|
|
|
5786
6130
|
getSkillBoundary: () => getSkillBoundary,
|
|
5787
6131
|
loadSection: () => loadSection
|
|
5788
6132
|
});
|
|
5789
|
-
import
|
|
5790
|
-
import
|
|
6133
|
+
import fs11 from "fs";
|
|
6134
|
+
import path12 from "path";
|
|
5791
6135
|
async function getProjectMeta() {
|
|
5792
6136
|
const root = getProjectRoot();
|
|
5793
6137
|
const name = root.split("/").pop() || "unknown";
|
|
5794
6138
|
const stack = [];
|
|
5795
|
-
if (
|
|
6139
|
+
if (fs11.existsSync(path12.join(root, "package.json"))) {
|
|
5796
6140
|
try {
|
|
5797
|
-
const pkg = JSON.parse(
|
|
6141
|
+
const pkg = JSON.parse(fs11.readFileSync(path12.join(root, "package.json"), "utf-8"));
|
|
5798
6142
|
if (pkg.dependencies?.next) stack.push("Next.js");
|
|
5799
6143
|
else if (pkg.dependencies?.react) stack.push("React");
|
|
5800
6144
|
if (pkg.devDependencies?.typescript) stack.push("TypeScript");
|
|
5801
|
-
if (
|
|
5802
|
-
else if (
|
|
6145
|
+
if (fs11.existsSync(path12.join(root, "pnpm-lock.yaml"))) stack.push("pnpm");
|
|
6146
|
+
else if (fs11.existsSync(path12.join(root, "yarn.lock"))) stack.push("yarn");
|
|
5803
6147
|
} catch {
|
|
5804
6148
|
}
|
|
5805
6149
|
}
|
|
5806
|
-
if (
|
|
5807
|
-
if (
|
|
5808
|
-
if (
|
|
6150
|
+
if (fs11.existsSync(path12.join(root, "go.mod"))) stack.push("Go");
|
|
6151
|
+
if (fs11.existsSync(path12.join(root, "Cargo.toml"))) stack.push("Rust");
|
|
6152
|
+
if (fs11.existsSync(path12.join(root, "pyproject.toml"))) stack.push("Python");
|
|
5809
6153
|
let nodeCount = 0;
|
|
5810
6154
|
let edgeCount = 0;
|
|
5811
6155
|
try {
|
|
@@ -5816,10 +6160,10 @@ async function getProjectMeta() {
|
|
|
5816
6160
|
edgeCount = edgeRes[0]?.values[0]?.[0] ?? 0;
|
|
5817
6161
|
} catch {
|
|
5818
6162
|
}
|
|
5819
|
-
const kumaDir =
|
|
6163
|
+
const kumaDir = path12.join(root, ".kuma");
|
|
5820
6164
|
const layerStatus = {
|
|
5821
|
-
domainRules:
|
|
5822
|
-
archFlow:
|
|
6165
|
+
domainRules: fs11.existsSync(path12.join(kumaDir, "DOMAIN_RULES.md")),
|
|
6166
|
+
archFlow: fs11.existsSync(path12.join(kumaDir, "ARCHITECTURE_FLOW.md")),
|
|
5823
6167
|
gotchas: getActiveGotchas().length
|
|
5824
6168
|
};
|
|
5825
6169
|
let hasDecisions = false;
|
|
@@ -6018,8 +6362,8 @@ __export(kumaSearch_exports, {
|
|
|
6018
6362
|
formatHybridResults: () => formatHybridResults,
|
|
6019
6363
|
hybridSearch: () => hybridSearch
|
|
6020
6364
|
});
|
|
6021
|
-
import
|
|
6022
|
-
import
|
|
6365
|
+
import fs13 from "fs";
|
|
6366
|
+
import path14 from "path";
|
|
6023
6367
|
function expandQueryTerms(query) {
|
|
6024
6368
|
const terms = query.toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
|
|
6025
6369
|
const expanded = /* @__PURE__ */ new Map();
|
|
@@ -6040,6 +6384,11 @@ function expandQueryTerms(query) {
|
|
|
6040
6384
|
}
|
|
6041
6385
|
return expanded;
|
|
6042
6386
|
}
|
|
6387
|
+
function recencyScore(epochSec) {
|
|
6388
|
+
if (!epochSec) return 0.5;
|
|
6389
|
+
const ageDays = Math.max(0, (Date.now() / 1e3 - epochSec) / 86400);
|
|
6390
|
+
return Math.max(0, Math.min(1, 1 - ageDays / 90));
|
|
6391
|
+
}
|
|
6043
6392
|
async function buildSearchVectors() {
|
|
6044
6393
|
if (_vectorCache && Date.now() - _vectorCache.builtAt < VECTOR_CACHE_TTL) {
|
|
6045
6394
|
return _vectorCache.vectors;
|
|
@@ -6047,7 +6396,7 @@ async function buildSearchVectors() {
|
|
|
6047
6396
|
const vectors = [];
|
|
6048
6397
|
try {
|
|
6049
6398
|
const db = await getDb();
|
|
6050
|
-
const stmt = db.prepare("SELECT id, name, metadata, file_path FROM nodes ORDER BY updated_at DESC LIMIT
|
|
6399
|
+
const stmt = db.prepare("SELECT id, name, metadata, file_path, updated_at FROM nodes ORDER BY updated_at DESC LIMIT " + SCALE_LIMITS.maxNodes);
|
|
6051
6400
|
while (stmt.step()) {
|
|
6052
6401
|
const row = stmt.getAsObject();
|
|
6053
6402
|
const name = row.name || "";
|
|
@@ -6068,7 +6417,8 @@ async function buildSearchVectors() {
|
|
|
6068
6417
|
vectors.push({
|
|
6069
6418
|
terms: termCounts,
|
|
6070
6419
|
source: name || row.id,
|
|
6071
|
-
sourceType: "graph"
|
|
6420
|
+
sourceType: "graph",
|
|
6421
|
+
recency: recencyScore(row.updated_at)
|
|
6072
6422
|
});
|
|
6073
6423
|
}
|
|
6074
6424
|
stmt.free();
|
|
@@ -6076,12 +6426,13 @@ async function buildSearchVectors() {
|
|
|
6076
6426
|
}
|
|
6077
6427
|
try {
|
|
6078
6428
|
const root = getProjectRoot();
|
|
6079
|
-
const memDir =
|
|
6080
|
-
if (
|
|
6081
|
-
const files =
|
|
6429
|
+
const memDir = path14.join(root, ".kuma", "memories");
|
|
6430
|
+
if (fs13.existsSync(memDir)) {
|
|
6431
|
+
const files = fs13.readdirSync(memDir).filter((f) => f.endsWith(".md")).slice(0, SCALE_LIMITS.maxMemoryFiles);
|
|
6082
6432
|
for (const file of files) {
|
|
6083
6433
|
try {
|
|
6084
|
-
const
|
|
6434
|
+
const fullPath = path14.join(memDir, file);
|
|
6435
|
+
const content = fs13.readFileSync(fullPath, "utf-8");
|
|
6085
6436
|
const text = content.toLowerCase();
|
|
6086
6437
|
const rawTerms = extractTerms(text);
|
|
6087
6438
|
const termCounts = /* @__PURE__ */ new Map();
|
|
@@ -6091,7 +6442,8 @@ async function buildSearchVectors() {
|
|
|
6091
6442
|
vectors.push({
|
|
6092
6443
|
terms: termCounts,
|
|
6093
6444
|
source: file.replace(/\.md$/, ""),
|
|
6094
|
-
sourceType: "memory"
|
|
6445
|
+
sourceType: "memory",
|
|
6446
|
+
recency: recencyScore(Math.floor(fs13.statSync(fullPath).mtimeMs / 1e3))
|
|
6095
6447
|
});
|
|
6096
6448
|
} catch {
|
|
6097
6449
|
}
|
|
@@ -6101,7 +6453,7 @@ async function buildSearchVectors() {
|
|
|
6101
6453
|
}
|
|
6102
6454
|
try {
|
|
6103
6455
|
const db = await getDb();
|
|
6104
|
-
const stmt = db.prepare("SELECT scope, record FROM research_cache ORDER BY updated_at DESC LIMIT
|
|
6456
|
+
const stmt = db.prepare("SELECT scope, record, updated_at FROM research_cache ORDER BY updated_at DESC LIMIT " + SCALE_LIMITS.maxResearch);
|
|
6105
6457
|
while (stmt.step()) {
|
|
6106
6458
|
const row = stmt.getAsObject();
|
|
6107
6459
|
const scope = row.scope || "";
|
|
@@ -6115,7 +6467,8 @@ async function buildSearchVectors() {
|
|
|
6115
6467
|
vectors.push({
|
|
6116
6468
|
terms: termCounts,
|
|
6117
6469
|
source: scope,
|
|
6118
|
-
sourceType: "research"
|
|
6470
|
+
sourceType: "research",
|
|
6471
|
+
recency: recencyScore(row.updated_at)
|
|
6119
6472
|
});
|
|
6120
6473
|
}
|
|
6121
6474
|
stmt.free();
|
|
@@ -6217,7 +6570,8 @@ async function hybridSearch(query, limit = 10) {
|
|
|
6217
6570
|
const existing = results.find((r) => r.source === vec.source);
|
|
6218
6571
|
if (existing) {
|
|
6219
6572
|
existing.vectorScore = vectorScore;
|
|
6220
|
-
|
|
6573
|
+
const recencyBoost = Math.round((vec.recency ?? 0.5) * 10);
|
|
6574
|
+
existing.score = Math.min(100, Math.round(existing.keywordScore * 0.6 + vectorScore * 0.4 + recencyBoost));
|
|
6221
6575
|
for (const [term] of vec.terms) {
|
|
6222
6576
|
if (queryVector.has(term) && !existing.matchedTerms.includes(term)) {
|
|
6223
6577
|
existing.matchedTerms.push(term);
|
|
@@ -6228,10 +6582,11 @@ async function hybridSearch(query, limit = 10) {
|
|
|
6228
6582
|
for (const [term] of vec.terms) {
|
|
6229
6583
|
if (queryVector.has(term)) matchedTerms.push(term);
|
|
6230
6584
|
}
|
|
6585
|
+
const recencyBoost = Math.round((vec.recency ?? 0.5) * 10);
|
|
6231
6586
|
results.push({
|
|
6232
6587
|
source: vec.source,
|
|
6233
6588
|
sourceType: vec.sourceType,
|
|
6234
|
-
score: Math.round(vectorScore * 0.4),
|
|
6589
|
+
score: Math.min(100, Math.round(vectorScore * 0.4 + recencyBoost)),
|
|
6235
6590
|
keywordScore: 0,
|
|
6236
6591
|
vectorScore,
|
|
6237
6592
|
matchedTerms
|
|
@@ -6286,7 +6641,7 @@ async function buildGraphConnectivity() {
|
|
|
6286
6641
|
let totalEdges = 0;
|
|
6287
6642
|
try {
|
|
6288
6643
|
const db = await getDb();
|
|
6289
|
-
const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT
|
|
6644
|
+
const stmt = db.prepare("SELECT source_id, target_id FROM edges LIMIT " + SCALE_LIMITS.maxEdges);
|
|
6290
6645
|
while (stmt.step()) {
|
|
6291
6646
|
const row = stmt.getAsObject();
|
|
6292
6647
|
const src = row.source_id || "";
|
|
@@ -6336,7 +6691,7 @@ function clearSearchCache() {
|
|
|
6336
6691
|
_vectorCache = null;
|
|
6337
6692
|
clearConnectivityCache();
|
|
6338
6693
|
}
|
|
6339
|
-
var SYNONYM_MAP, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
|
|
6694
|
+
var SYNONYM_MAP, SCALE_LIMITS, _vectorCache, VECTOR_CACHE_TTL, _connectivityCache, CONNECTIVITY_CACHE_TTL;
|
|
6340
6695
|
var init_kumaSearch = __esm({
|
|
6341
6696
|
"src/engine/kumaSearch.ts"() {
|
|
6342
6697
|
"use strict";
|
|
@@ -6381,6 +6736,16 @@ var init_kumaSearch = __esm({
|
|
|
6381
6736
|
update: ["update", "modify", "change", "edit", "patch"],
|
|
6382
6737
|
delete: ["delete", "remove", "drop", "clear", "purge"]
|
|
6383
6738
|
};
|
|
6739
|
+
SCALE_LIMITS = {
|
|
6740
|
+
maxNodes: 5e3,
|
|
6741
|
+
// was 500 — graphs grow with project size
|
|
6742
|
+
maxMemoryFiles: 30,
|
|
6743
|
+
// was 10
|
|
6744
|
+
maxResearch: 300,
|
|
6745
|
+
// was 50
|
|
6746
|
+
maxEdges: 2e4
|
|
6747
|
+
// was 2000 (graph connectivity)
|
|
6748
|
+
};
|
|
6384
6749
|
_vectorCache = null;
|
|
6385
6750
|
VECTOR_CACHE_TTL = 3e5;
|
|
6386
6751
|
_connectivityCache = null;
|
|
@@ -6393,8 +6758,8 @@ var kumaMiner_exports = {};
|
|
|
6393
6758
|
__export(kumaMiner_exports, {
|
|
6394
6759
|
mineHistoricalDecisions: () => mineHistoricalDecisions
|
|
6395
6760
|
});
|
|
6396
|
-
import
|
|
6397
|
-
import
|
|
6761
|
+
import fs14 from "fs";
|
|
6762
|
+
import path15 from "path";
|
|
6398
6763
|
import { execSync as execSync4 } from "child_process";
|
|
6399
6764
|
import fastGlob2 from "fast-glob";
|
|
6400
6765
|
async function mineHistoricalDecisions(options = {}) {
|
|
@@ -6469,7 +6834,7 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
6469
6834
|
}
|
|
6470
6835
|
if (candidates.length < limit) {
|
|
6471
6836
|
try {
|
|
6472
|
-
const scopePattern = options.scope ? `**/*${options.scope}*.*` :
|
|
6837
|
+
const scopePattern = options.scope ? `**/*${options.scope}*.*` : `**/*.{${SOURCE_EXT_GLOB.slice(1, -1)},md}`;
|
|
6473
6838
|
const files = await fastGlob2(scopePattern, {
|
|
6474
6839
|
cwd: root,
|
|
6475
6840
|
ignore: ["node_modules/**", "dist/**", ".kuma/**", ".git/**", "coverage/**"],
|
|
@@ -6484,9 +6849,9 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
6484
6849
|
];
|
|
6485
6850
|
for (const file of files.slice(0, 50)) {
|
|
6486
6851
|
if (candidates.length >= limit) break;
|
|
6487
|
-
const fullPath =
|
|
6488
|
-
if (!
|
|
6489
|
-
const content =
|
|
6852
|
+
const fullPath = path15.join(root, file);
|
|
6853
|
+
if (!fs14.existsSync(fullPath)) continue;
|
|
6854
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
6490
6855
|
const lines = content.split("\n");
|
|
6491
6856
|
for (let i = 0; i < lines.length; i++) {
|
|
6492
6857
|
const l = lines[i];
|
|
@@ -6561,6 +6926,7 @@ var init_kumaMiner = __esm({
|
|
|
6561
6926
|
"use strict";
|
|
6562
6927
|
init_kumaDb();
|
|
6563
6928
|
init_kumaMemory();
|
|
6929
|
+
init_languageSupport();
|
|
6564
6930
|
}
|
|
6565
6931
|
});
|
|
6566
6932
|
|
|
@@ -6793,8 +7159,8 @@ __export(kumaGitHarvester_exports, {
|
|
|
6793
7159
|
installGitHook: () => installGitHook
|
|
6794
7160
|
});
|
|
6795
7161
|
import { execSync as execSync5 } from "child_process";
|
|
6796
|
-
import
|
|
6797
|
-
import
|
|
7162
|
+
import fs15 from "fs";
|
|
7163
|
+
import path16 from "path";
|
|
6798
7164
|
function getRecentCommits(count = 5) {
|
|
6799
7165
|
const root = getProjectRoot();
|
|
6800
7166
|
try {
|
|
@@ -6974,11 +7340,11 @@ async function harvestGitHistory(options) {
|
|
|
6974
7340
|
}
|
|
6975
7341
|
function installGitHook() {
|
|
6976
7342
|
const root = getProjectRoot();
|
|
6977
|
-
const hooksDir =
|
|
6978
|
-
if (!
|
|
7343
|
+
const hooksDir = path16.join(root, ".git", "hooks");
|
|
7344
|
+
if (!fs15.existsSync(hooksDir)) {
|
|
6979
7345
|
return "\u26A0\uFE0F Not a git repository \u2014 no .git/hooks/ directory found.";
|
|
6980
7346
|
}
|
|
6981
|
-
const hookPath =
|
|
7347
|
+
const hookPath = path16.join(hooksDir, "post-commit");
|
|
6982
7348
|
const hookContent = `#!/bin/bash
|
|
6983
7349
|
# Kuma auto-harvester hook (post-commit)
|
|
6984
7350
|
# Automatically extracts decisions and gotchas from commits
|
|
@@ -6986,23 +7352,23 @@ if command -v npx &> /dev/null; then
|
|
|
6986
7352
|
npx -y @plumpslabs/kuma --hook post-commit 2>/dev/null || true
|
|
6987
7353
|
fi
|
|
6988
7354
|
`;
|
|
6989
|
-
if (
|
|
6990
|
-
const existing =
|
|
7355
|
+
if (fs15.existsSync(hookPath)) {
|
|
7356
|
+
const existing = fs15.readFileSync(hookPath, "utf-8");
|
|
6991
7357
|
if (existing.includes("kuma")) {
|
|
6992
7358
|
return "\u2705 Kuma git hook already installed.";
|
|
6993
7359
|
}
|
|
6994
7360
|
const backupPath = hookPath + ".backup." + Date.now();
|
|
6995
|
-
|
|
6996
|
-
|
|
7361
|
+
fs15.copyFileSync(hookPath, backupPath);
|
|
7362
|
+
fs15.writeFileSync(hookPath, hookContent, "utf-8");
|
|
6997
7363
|
try {
|
|
6998
|
-
|
|
7364
|
+
fs15.chmodSync(hookPath, 493);
|
|
6999
7365
|
} catch {
|
|
7000
7366
|
}
|
|
7001
|
-
return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${
|
|
7367
|
+
return `\u2705 Installed kuma post-commit hook (existing hook backed up to ${path16.basename(backupPath)})`;
|
|
7002
7368
|
}
|
|
7003
|
-
|
|
7369
|
+
fs15.writeFileSync(hookPath, hookContent, "utf-8");
|
|
7004
7370
|
try {
|
|
7005
|
-
|
|
7371
|
+
fs15.chmodSync(hookPath, 493);
|
|
7006
7372
|
} catch {
|
|
7007
7373
|
}
|
|
7008
7374
|
return "\u2705 Installed kuma post-commit hook.";
|
|
@@ -7301,19 +7667,19 @@ __export(kumaPolicyEngine_exports, {
|
|
|
7301
7667
|
processOverride: () => processOverride,
|
|
7302
7668
|
savePolicyConfig: () => savePolicyConfig
|
|
7303
7669
|
});
|
|
7304
|
-
import
|
|
7305
|
-
import
|
|
7670
|
+
import fs21 from "fs";
|
|
7671
|
+
import path22 from "path";
|
|
7306
7672
|
function loadPolicyConfig() {
|
|
7307
7673
|
try {
|
|
7308
7674
|
const root = getProjectRoot();
|
|
7309
|
-
const policyPath =
|
|
7310
|
-
if (
|
|
7311
|
-
const content =
|
|
7675
|
+
const policyPath = path22.join(root, ".kuma", "POLICY.json");
|
|
7676
|
+
if (fs21.existsSync(policyPath)) {
|
|
7677
|
+
const content = fs21.readFileSync(policyPath, "utf-8");
|
|
7312
7678
|
const config = JSON.parse(content);
|
|
7313
7679
|
return { ...DEFAULT_POLICY_CONFIG, ...config };
|
|
7314
7680
|
}
|
|
7315
|
-
const ymlPath =
|
|
7316
|
-
if (
|
|
7681
|
+
const ymlPath = path22.join(root, ".kuma", "policy.yml");
|
|
7682
|
+
if (fs21.existsSync(ymlPath)) {
|
|
7317
7683
|
console.error("[PolicyEngine] Found legacy policy.yml \u2014 consider migrating to POLICY.json");
|
|
7318
7684
|
}
|
|
7319
7685
|
} catch (err) {
|
|
@@ -7324,10 +7690,10 @@ function loadPolicyConfig() {
|
|
|
7324
7690
|
function savePolicyConfig(config) {
|
|
7325
7691
|
try {
|
|
7326
7692
|
const root = getProjectRoot();
|
|
7327
|
-
const policyPath =
|
|
7328
|
-
const dir =
|
|
7329
|
-
if (!
|
|
7330
|
-
|
|
7693
|
+
const policyPath = path22.join(root, ".kuma", "POLICY.json");
|
|
7694
|
+
const dir = path22.dirname(policyPath);
|
|
7695
|
+
if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
|
|
7696
|
+
fs21.writeFileSync(policyPath, JSON.stringify(config, null, 2), "utf-8");
|
|
7331
7697
|
return `\u2705 Policy config saved to .kuma/POLICY.json (${config.rules.length} rules)`;
|
|
7332
7698
|
} catch (err) {
|
|
7333
7699
|
return `\u274C Failed to save policy: ${err}`;
|
|
@@ -7612,14 +7978,14 @@ __export(kumaAstValidator_exports, {
|
|
|
7612
7978
|
validateDiff: () => validateDiff,
|
|
7613
7979
|
validateFile: () => validateFile
|
|
7614
7980
|
});
|
|
7615
|
-
import
|
|
7616
|
-
import
|
|
7981
|
+
import fs22 from "fs";
|
|
7982
|
+
import path23 from "path";
|
|
7617
7983
|
function loadImportWhitelist() {
|
|
7618
7984
|
try {
|
|
7619
7985
|
const root = getProjectRoot();
|
|
7620
|
-
const whitelistPath =
|
|
7621
|
-
if (
|
|
7622
|
-
return JSON.parse(
|
|
7986
|
+
const whitelistPath = path23.join(root, ".kuma", "import-whitelist.json");
|
|
7987
|
+
if (fs22.existsSync(whitelistPath)) {
|
|
7988
|
+
return JSON.parse(fs22.readFileSync(whitelistPath, "utf-8"));
|
|
7623
7989
|
}
|
|
7624
7990
|
} catch {
|
|
7625
7991
|
}
|
|
@@ -7645,7 +8011,9 @@ function validateCodeContent(code, filePath) {
|
|
|
7645
8011
|
suggestion: "Restore the assertion or write an equivalent test for the new behavior"
|
|
7646
8012
|
});
|
|
7647
8013
|
}
|
|
7648
|
-
|
|
8014
|
+
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)
|
|
8015
|
+
/except\s*([^:]*)\s*:\s*(pass|\.\.\.)\s*(#.*)?$/.test(line) || /except\s*([^:]*)\s*:\s*$/.test(line) && /^pass(\s*#.*)?$/.test(lines[i + 1]?.trim() ?? "");
|
|
8016
|
+
if (emptyCatch) {
|
|
7649
8017
|
findings.push({
|
|
7650
8018
|
line: lineNum,
|
|
7651
8019
|
severity: "error",
|
|
@@ -7654,8 +8022,8 @@ function validateCodeContent(code, filePath) {
|
|
|
7654
8022
|
suggestion: "Handle the error: log it, throw a meaningful error, or add a comment explaining why it's safe to ignore"
|
|
7655
8023
|
});
|
|
7656
8024
|
}
|
|
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)) {
|
|
8025
|
+
if (isTestFile(filePath || "")) {
|
|
8026
|
+
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
8027
|
findings.push({
|
|
7660
8028
|
line: lineNum,
|
|
7661
8029
|
severity: "warning",
|
|
@@ -7665,7 +8033,7 @@ function validateCodeContent(code, filePath) {
|
|
|
7665
8033
|
});
|
|
7666
8034
|
}
|
|
7667
8035
|
}
|
|
7668
|
-
if (!filePath
|
|
8036
|
+
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
8037
|
findings.push({
|
|
7670
8038
|
line: lineNum,
|
|
7671
8039
|
severity: "warning",
|
|
@@ -7674,18 +8042,15 @@ function validateCodeContent(code, filePath) {
|
|
|
7674
8042
|
suggestion: "Implement proper logic or add a clear comment explaining why this is correct"
|
|
7675
8043
|
});
|
|
7676
8044
|
}
|
|
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
|
-
}
|
|
8045
|
+
const imported = matchImportPath(line);
|
|
8046
|
+
if (imported && whitelist.blocked.some((b) => imported.includes(b))) {
|
|
8047
|
+
findings.push({
|
|
8048
|
+
line: lineNum,
|
|
8049
|
+
severity: "error",
|
|
8050
|
+
category: "import-violation",
|
|
8051
|
+
message: `Blocked import: "${imported}" is not allowed per security whitelist`,
|
|
8052
|
+
suggestion: `Remove or replace the import. Allowed packages: ${whitelist.allowed.slice(0, 5).join(", ") || "(none specified)"}`
|
|
8053
|
+
});
|
|
7689
8054
|
}
|
|
7690
8055
|
}
|
|
7691
8056
|
return findings;
|
|
@@ -7693,9 +8058,9 @@ function validateCodeContent(code, filePath) {
|
|
|
7693
8058
|
function validateFile(filePath) {
|
|
7694
8059
|
try {
|
|
7695
8060
|
const root = getProjectRoot();
|
|
7696
|
-
const fullPath =
|
|
7697
|
-
if (!
|
|
7698
|
-
const content =
|
|
8061
|
+
const fullPath = path23.resolve(root, filePath);
|
|
8062
|
+
if (!fs22.existsSync(fullPath)) return [];
|
|
8063
|
+
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
7699
8064
|
return validateCodeContent(content, filePath);
|
|
7700
8065
|
} catch {
|
|
7701
8066
|
return [];
|
|
@@ -7755,6 +8120,7 @@ var init_kumaAstValidator = __esm({
|
|
|
7755
8120
|
"src/engine/kumaAstValidator.ts"() {
|
|
7756
8121
|
"use strict";
|
|
7757
8122
|
init_pathValidator();
|
|
8123
|
+
init_languageSupport();
|
|
7758
8124
|
}
|
|
7759
8125
|
});
|
|
7760
8126
|
|
|
@@ -7766,16 +8132,16 @@ __export(kumaCheckpoint_exports, {
|
|
|
7766
8132
|
pruneCheckpoints: () => pruneCheckpoints,
|
|
7767
8133
|
rollbackToCheckpoint: () => rollbackToCheckpoint
|
|
7768
8134
|
});
|
|
7769
|
-
import
|
|
7770
|
-
import
|
|
8135
|
+
import fs23 from "fs";
|
|
8136
|
+
import path24 from "path";
|
|
7771
8137
|
async function createCheckpoint(label, description) {
|
|
7772
8138
|
try {
|
|
7773
8139
|
const root = getProjectRoot();
|
|
7774
|
-
const cpDir =
|
|
7775
|
-
if (
|
|
8140
|
+
const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
8141
|
+
if (fs23.existsSync(cpDir)) {
|
|
7776
8142
|
return `\u26A0\uFE0F Checkpoint "${label}" already exists. Use a different label or remove it first.`;
|
|
7777
8143
|
}
|
|
7778
|
-
|
|
8144
|
+
fs23.mkdirSync(cpDir, { recursive: true });
|
|
7779
8145
|
const db = await getDb();
|
|
7780
8146
|
const filesStmt = db.prepare(
|
|
7781
8147
|
"SELECT DISTINCT file_path FROM change_log ORDER BY id DESC LIMIT 100"
|
|
@@ -7784,19 +8150,19 @@ async function createCheckpoint(label, description) {
|
|
|
7784
8150
|
while (filesStmt.step()) {
|
|
7785
8151
|
const row = filesStmt.getAsObject();
|
|
7786
8152
|
const fp = row.file_path;
|
|
7787
|
-
const fullPath =
|
|
7788
|
-
if (
|
|
7789
|
-
const content =
|
|
8153
|
+
const fullPath = path24.resolve(root, fp);
|
|
8154
|
+
if (fs23.existsSync(fullPath)) {
|
|
8155
|
+
const content = fs23.readFileSync(fullPath, "utf-8");
|
|
7790
8156
|
const hash = simpleHash2(content);
|
|
7791
|
-
const fileDir =
|
|
7792
|
-
if (!
|
|
7793
|
-
|
|
8157
|
+
const fileDir = path24.dirname(path24.join(cpDir, "files", fp));
|
|
8158
|
+
if (!fs23.existsSync(fileDir)) fs23.mkdirSync(fileDir, { recursive: true });
|
|
8159
|
+
fs23.writeFileSync(path24.join(cpDir, "files", fp), content, "utf-8");
|
|
7794
8160
|
files.push({ path: fp, hash });
|
|
7795
8161
|
}
|
|
7796
8162
|
}
|
|
7797
8163
|
filesStmt.free();
|
|
7798
8164
|
const dbData = db.export();
|
|
7799
|
-
|
|
8165
|
+
fs23.writeFileSync(path24.join(cpDir, "kuma.db"), Buffer.from(dbData));
|
|
7800
8166
|
const manifest = {
|
|
7801
8167
|
label,
|
|
7802
8168
|
timestamp: Date.now(),
|
|
@@ -7804,8 +8170,8 @@ async function createCheckpoint(label, description) {
|
|
|
7804
8170
|
dbSnapshot: true,
|
|
7805
8171
|
description
|
|
7806
8172
|
};
|
|
7807
|
-
|
|
7808
|
-
|
|
8173
|
+
fs23.writeFileSync(
|
|
8174
|
+
path24.join(cpDir, "manifest.json"),
|
|
7809
8175
|
JSON.stringify(manifest, null, 2),
|
|
7810
8176
|
"utf-8"
|
|
7811
8177
|
);
|
|
@@ -7831,37 +8197,37 @@ async function createCheckpoint(label, description) {
|
|
|
7831
8197
|
async function rollbackToCheckpoint(label) {
|
|
7832
8198
|
try {
|
|
7833
8199
|
const root = getProjectRoot();
|
|
7834
|
-
const cpDir =
|
|
7835
|
-
const manifestPath =
|
|
7836
|
-
if (!
|
|
8200
|
+
const cpDir = path24.join(root, CHECKPOINT_DIR, sanitizeLabel(label));
|
|
8201
|
+
const manifestPath = path24.join(cpDir, "manifest.json");
|
|
8202
|
+
if (!fs23.existsSync(manifestPath)) {
|
|
7837
8203
|
return `\u274C Checkpoint "${label}" not found. Use kuma_safety({ action: 'checkpoint_list' }) to see available checkpoints.`;
|
|
7838
8204
|
}
|
|
7839
8205
|
const manifest = JSON.parse(
|
|
7840
|
-
|
|
8206
|
+
fs23.readFileSync(manifestPath, "utf-8")
|
|
7841
8207
|
);
|
|
7842
8208
|
let restored = 0;
|
|
7843
8209
|
let failed = 0;
|
|
7844
8210
|
for (const f of manifest.files) {
|
|
7845
|
-
const snapshotPath =
|
|
7846
|
-
if (
|
|
8211
|
+
const snapshotPath = path24.join(cpDir, "files", f.path);
|
|
8212
|
+
if (fs23.existsSync(snapshotPath)) {
|
|
7847
8213
|
try {
|
|
7848
|
-
const content =
|
|
7849
|
-
const targetPath =
|
|
7850
|
-
const targetDir =
|
|
7851
|
-
if (!
|
|
7852
|
-
|
|
8214
|
+
const content = fs23.readFileSync(snapshotPath, "utf-8");
|
|
8215
|
+
const targetPath = path24.resolve(root, f.path);
|
|
8216
|
+
const targetDir = path24.dirname(targetPath);
|
|
8217
|
+
if (!fs23.existsSync(targetDir)) fs23.mkdirSync(targetDir, { recursive: true });
|
|
8218
|
+
fs23.writeFileSync(targetPath, content, "utf-8");
|
|
7853
8219
|
restored++;
|
|
7854
8220
|
} catch {
|
|
7855
8221
|
failed++;
|
|
7856
8222
|
}
|
|
7857
8223
|
}
|
|
7858
8224
|
}
|
|
7859
|
-
const dbSnapshotPath =
|
|
7860
|
-
if (
|
|
7861
|
-
const kumaDir =
|
|
7862
|
-
const dbPath =
|
|
7863
|
-
const snapshotData =
|
|
7864
|
-
|
|
8225
|
+
const dbSnapshotPath = path24.join(cpDir, "kuma.db");
|
|
8226
|
+
if (fs23.existsSync(dbSnapshotPath)) {
|
|
8227
|
+
const kumaDir = path24.join(root, ".kuma");
|
|
8228
|
+
const dbPath = path24.join(kumaDir, "kuma.db");
|
|
8229
|
+
const snapshotData = fs23.readFileSync(dbSnapshotPath);
|
|
8230
|
+
fs23.writeFileSync(dbPath, snapshotData);
|
|
7865
8231
|
resetDbInstance();
|
|
7866
8232
|
}
|
|
7867
8233
|
sessionMemory.recordToolCall("kuma_checkpoint_rollback", {
|
|
@@ -7886,18 +8252,18 @@ async function rollbackToCheckpoint(label) {
|
|
|
7886
8252
|
function listCheckpoints() {
|
|
7887
8253
|
try {
|
|
7888
8254
|
const root = getProjectRoot();
|
|
7889
|
-
const cpDir =
|
|
7890
|
-
if (!
|
|
8255
|
+
const cpDir = path24.join(root, CHECKPOINT_DIR);
|
|
8256
|
+
if (!fs23.existsSync(cpDir)) {
|
|
7891
8257
|
return "\u{1F4ED} No checkpoints found. Use kuma_safety({ action: 'checkpoint', label: 'pre-feature-x' }) to create one.";
|
|
7892
8258
|
}
|
|
7893
|
-
const entries =
|
|
8259
|
+
const entries = fs23.readdirSync(cpDir);
|
|
7894
8260
|
const checkpoints = [];
|
|
7895
8261
|
for (const entry of entries) {
|
|
7896
|
-
const manifestPath =
|
|
7897
|
-
if (
|
|
8262
|
+
const manifestPath = path24.join(cpDir, entry, "manifest.json");
|
|
8263
|
+
if (fs23.existsSync(manifestPath)) {
|
|
7898
8264
|
try {
|
|
7899
8265
|
const manifest = JSON.parse(
|
|
7900
|
-
|
|
8266
|
+
fs23.readFileSync(manifestPath, "utf-8")
|
|
7901
8267
|
);
|
|
7902
8268
|
checkpoints.push({ label: entry, manifest });
|
|
7903
8269
|
} catch {
|
|
@@ -7929,12 +8295,12 @@ function listCheckpoints() {
|
|
|
7929
8295
|
function pruneCheckpoints(keep = 5) {
|
|
7930
8296
|
try {
|
|
7931
8297
|
const root = getProjectRoot();
|
|
7932
|
-
const cpDir =
|
|
7933
|
-
if (!
|
|
7934
|
-
const entries =
|
|
8298
|
+
const cpDir = path24.join(root, CHECKPOINT_DIR);
|
|
8299
|
+
if (!fs23.existsSync(cpDir)) return "\u{1F4ED} No checkpoints to prune.";
|
|
8300
|
+
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
8301
|
let removed = 0;
|
|
7936
8302
|
for (let i = keep; i < entries.length; i++) {
|
|
7937
|
-
|
|
8303
|
+
fs23.rmSync(path24.join(cpDir, entries[i].name), { recursive: true, force: true });
|
|
7938
8304
|
removed++;
|
|
7939
8305
|
}
|
|
7940
8306
|
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 +8340,18 @@ __export(kumaContractEngine_exports, {
|
|
|
7974
8340
|
loadContracts: () => loadContracts,
|
|
7975
8341
|
runContractChecks: () => runContractChecks
|
|
7976
8342
|
});
|
|
7977
|
-
import
|
|
7978
|
-
import
|
|
8343
|
+
import fs24 from "fs";
|
|
8344
|
+
import path25 from "path";
|
|
7979
8345
|
function contractsDir() {
|
|
7980
|
-
return
|
|
8346
|
+
return path25.join(getProjectRoot(), ".kuma", "contracts");
|
|
7981
8347
|
}
|
|
7982
8348
|
function ensureContractsDir() {
|
|
7983
8349
|
const dir = contractsDir();
|
|
7984
|
-
if (!
|
|
7985
|
-
|
|
7986
|
-
const defaultPath =
|
|
7987
|
-
if (!
|
|
7988
|
-
|
|
8350
|
+
if (!fs24.existsSync(dir)) {
|
|
8351
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
8352
|
+
const defaultPath = path25.join(dir, "default.json");
|
|
8353
|
+
if (!fs24.existsSync(defaultPath)) {
|
|
8354
|
+
fs24.writeFileSync(
|
|
7989
8355
|
defaultPath,
|
|
7990
8356
|
JSON.stringify(DEFAULT_CONTRACTS, null, 2),
|
|
7991
8357
|
"utf-8"
|
|
@@ -7998,10 +8364,10 @@ function loadContracts() {
|
|
|
7998
8364
|
const dir = contractsDir();
|
|
7999
8365
|
const contracts = [];
|
|
8000
8366
|
try {
|
|
8001
|
-
const files =
|
|
8367
|
+
const files = fs24.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
8002
8368
|
for (const file of files) {
|
|
8003
8369
|
try {
|
|
8004
|
-
const content =
|
|
8370
|
+
const content = fs24.readFileSync(path25.join(dir, file), "utf-8");
|
|
8005
8371
|
const config = JSON.parse(content);
|
|
8006
8372
|
contracts.push(config);
|
|
8007
8373
|
} catch (err) {
|
|
@@ -8074,7 +8440,7 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8074
8440
|
switch (condition.type) {
|
|
8075
8441
|
case "file_exists": {
|
|
8076
8442
|
const targetPath = resolveTarget(condition.target, contextPath, root);
|
|
8077
|
-
const exists = targetPath.some((p) =>
|
|
8443
|
+
const exists = targetPath.some((p) => fs24.existsSync(p));
|
|
8078
8444
|
return {
|
|
8079
8445
|
passed: exists,
|
|
8080
8446
|
message: exists ? `\u2705 File exists: ${condition.target}` : `\u274C Required file not found: ${condition.target} (${condition.description})`
|
|
@@ -8082,7 +8448,7 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8082
8448
|
}
|
|
8083
8449
|
case "file_not_exists": {
|
|
8084
8450
|
const targetPath = resolveTarget(condition.target, contextPath, root);
|
|
8085
|
-
const exists = targetPath.some((p) =>
|
|
8451
|
+
const exists = targetPath.some((p) => fs24.existsSync(p));
|
|
8086
8452
|
return {
|
|
8087
8453
|
passed: !exists,
|
|
8088
8454
|
message: !exists ? `\u2705 File does not exist: ${condition.target}` : `\u274C File should not exist: ${condition.target} (${condition.description})`
|
|
@@ -8090,9 +8456,9 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8090
8456
|
}
|
|
8091
8457
|
case "contains": {
|
|
8092
8458
|
if (!contextPath) return { passed: true, message: "\u26A0\uFE0F No file to check" };
|
|
8093
|
-
const fullPath =
|
|
8094
|
-
if (!
|
|
8095
|
-
const content =
|
|
8459
|
+
const fullPath = path25.resolve(root, contextPath);
|
|
8460
|
+
if (!fs24.existsSync(fullPath)) return { passed: true, message: "\u26A0\uFE0F File not found" };
|
|
8461
|
+
const content = fs24.readFileSync(fullPath, "utf-8");
|
|
8096
8462
|
const hasContent = condition.value ? content.includes(condition.value) : false;
|
|
8097
8463
|
return {
|
|
8098
8464
|
passed: hasContent,
|
|
@@ -8104,12 +8470,12 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8104
8470
|
const root2 = getProjectRoot();
|
|
8105
8471
|
const targets = resolveTarget(condition.target, contextPath, root2);
|
|
8106
8472
|
for (const t of targets) {
|
|
8107
|
-
if (
|
|
8108
|
-
const content =
|
|
8473
|
+
if (fs24.existsSync(t)) {
|
|
8474
|
+
const content = fs24.readFileSync(t, "utf-8");
|
|
8109
8475
|
if (condition.value && content.includes(condition.value)) {
|
|
8110
8476
|
return {
|
|
8111
8477
|
passed: false,
|
|
8112
|
-
message: `\u274C Found forbidden pattern "${condition.value}" in ${
|
|
8478
|
+
message: `\u274C Found forbidden pattern "${condition.value}" in ${path25.relative(root2, t)} (${condition.description})`
|
|
8113
8479
|
};
|
|
8114
8480
|
}
|
|
8115
8481
|
}
|
|
@@ -8118,15 +8484,11 @@ function evaluateCondition(condition, contextPath) {
|
|
|
8118
8484
|
}
|
|
8119
8485
|
case "has_test_file": {
|
|
8120
8486
|
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));
|
|
8487
|
+
const candidates = getTestCandidates(contextPath);
|
|
8488
|
+
const hasTest = candidates.some((t) => {
|
|
8489
|
+
const abs = path25.isAbsolute(t) ? t : path25.resolve(root, t);
|
|
8490
|
+
return fs24.existsSync(abs);
|
|
8491
|
+
});
|
|
8130
8492
|
return {
|
|
8131
8493
|
passed: hasTest,
|
|
8132
8494
|
message: hasTest ? `\u2705 Test file exists` : `\u274C No test file found for ${contextPath} (${condition.description})`
|
|
@@ -8150,18 +8512,24 @@ function resolveTarget(pattern, contextPath, root) {
|
|
|
8150
8512
|
const r = root || getProjectRoot();
|
|
8151
8513
|
if (pattern.startsWith("*")) {
|
|
8152
8514
|
if (contextPath) {
|
|
8153
|
-
const dir =
|
|
8515
|
+
const dir = path25.dirname(path25.resolve(r, contextPath));
|
|
8154
8516
|
const suffix = pattern.replace("*", "");
|
|
8155
8517
|
try {
|
|
8156
|
-
const files =
|
|
8157
|
-
return files.filter((f) =>
|
|
8518
|
+
const files = fs24.readdirSync(dir);
|
|
8519
|
+
return files.filter((f) => {
|
|
8520
|
+
try {
|
|
8521
|
+
return fs24.statSync(path25.join(dir, f)).isFile();
|
|
8522
|
+
} catch {
|
|
8523
|
+
return false;
|
|
8524
|
+
}
|
|
8525
|
+
}).filter((f) => f.endsWith(suffix) || f.includes(suffix)).map((f) => path25.join(dir, f));
|
|
8158
8526
|
} catch {
|
|
8159
|
-
return [
|
|
8527
|
+
return [path25.join(dir, pattern.replace("*", ""))];
|
|
8160
8528
|
}
|
|
8161
8529
|
}
|
|
8162
8530
|
return [];
|
|
8163
8531
|
}
|
|
8164
|
-
return [
|
|
8532
|
+
return [path25.resolve(r, pattern)];
|
|
8165
8533
|
}
|
|
8166
8534
|
async function runContractChecks(toolName, filePath, phase = "pre") {
|
|
8167
8535
|
const verdicts = phase === "pre" ? evaluatePreConditions(toolName, filePath) : evaluatePostConditions(toolName, filePath);
|
|
@@ -8223,6 +8591,7 @@ var init_kumaContractEngine = __esm({
|
|
|
8223
8591
|
"src/engine/kumaContractEngine.ts"() {
|
|
8224
8592
|
"use strict";
|
|
8225
8593
|
init_pathValidator();
|
|
8594
|
+
init_languageSupport();
|
|
8226
8595
|
DEFAULT_CONTRACTS = {
|
|
8227
8596
|
version: 1,
|
|
8228
8597
|
contracts: [
|
|
@@ -8249,15 +8618,21 @@ var init_kumaContractEngine = __esm({
|
|
|
8249
8618
|
postConditions: [
|
|
8250
8619
|
{
|
|
8251
8620
|
type: "not_contains",
|
|
8252
|
-
target: "
|
|
8621
|
+
target: "*",
|
|
8253
8622
|
value: "catch {}",
|
|
8254
8623
|
description: "Empty catch blocks suppress errors silently"
|
|
8255
8624
|
},
|
|
8256
8625
|
{
|
|
8257
8626
|
type: "not_contains",
|
|
8258
|
-
target: "
|
|
8627
|
+
target: "*",
|
|
8259
8628
|
value: "catch (e) {}",
|
|
8260
8629
|
description: "Empty catch (e) {} suppresses errors silently"
|
|
8630
|
+
},
|
|
8631
|
+
{
|
|
8632
|
+
type: "not_contains",
|
|
8633
|
+
target: "*",
|
|
8634
|
+
value: "except:",
|
|
8635
|
+
description: "Bare except blocks suppress errors silently (Python)"
|
|
8261
8636
|
}
|
|
8262
8637
|
],
|
|
8263
8638
|
severity: "error"
|
|
@@ -8275,6 +8650,116 @@ var init_kumaContractEngine = __esm({
|
|
|
8275
8650
|
}
|
|
8276
8651
|
});
|
|
8277
8652
|
|
|
8653
|
+
// src/engine/kumaAutoGotcha.ts
|
|
8654
|
+
var kumaAutoGotcha_exports = {};
|
|
8655
|
+
__export(kumaAutoGotcha_exports, {
|
|
8656
|
+
AUTO_GOTCHA_ESCALATE_AT: () => AUTO_GOTCHA_ESCALATE_AT,
|
|
8657
|
+
AUTO_GOTCHA_THRESHOLD: () => AUTO_GOTCHA_THRESHOLD,
|
|
8658
|
+
loadAutoGotchaState: () => loadAutoGotchaState,
|
|
8659
|
+
resetScopeFailures: () => resetScopeFailures,
|
|
8660
|
+
saveAutoGotchaState: () => saveAutoGotchaState,
|
|
8661
|
+
trackVerificationResult: () => trackVerificationResult
|
|
8662
|
+
});
|
|
8663
|
+
import fs25 from "fs";
|
|
8664
|
+
import path26 from "path";
|
|
8665
|
+
function statePath() {
|
|
8666
|
+
return path26.join(getProjectRoot(), STATE_FILE);
|
|
8667
|
+
}
|
|
8668
|
+
function loadAutoGotchaState() {
|
|
8669
|
+
try {
|
|
8670
|
+
const fp = statePath();
|
|
8671
|
+
if (fs25.existsSync(fp)) {
|
|
8672
|
+
const parsed = JSON.parse(fs25.readFileSync(fp, "utf-8"));
|
|
8673
|
+
return {
|
|
8674
|
+
failures: parsed?.failures ?? {},
|
|
8675
|
+
lastRecorded: parsed?.lastRecorded ?? {}
|
|
8676
|
+
};
|
|
8677
|
+
}
|
|
8678
|
+
} catch {
|
|
8679
|
+
}
|
|
8680
|
+
return { failures: {}, lastRecorded: {} };
|
|
8681
|
+
}
|
|
8682
|
+
function saveAutoGotchaState(state) {
|
|
8683
|
+
try {
|
|
8684
|
+
const fp = statePath();
|
|
8685
|
+
const dir = path26.dirname(fp);
|
|
8686
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
8687
|
+
fs25.writeFileSync(fp, JSON.stringify(state, null, 2), "utf-8");
|
|
8688
|
+
} catch {
|
|
8689
|
+
}
|
|
8690
|
+
}
|
|
8691
|
+
function resetScopeFailures(scope) {
|
|
8692
|
+
const state = loadAutoGotchaState();
|
|
8693
|
+
if (state.failures[scope] !== void 0 || state.lastRecorded?.[scope] !== void 0) {
|
|
8694
|
+
delete state.failures[scope];
|
|
8695
|
+
if (state.lastRecorded) delete state.lastRecorded[scope];
|
|
8696
|
+
saveAutoGotchaState(state);
|
|
8697
|
+
}
|
|
8698
|
+
}
|
|
8699
|
+
async function trackVerificationResult(scope, passed) {
|
|
8700
|
+
if (!scope || scope === "session-impact") return { recorded: false, failureCount: 0 };
|
|
8701
|
+
const state = loadAutoGotchaState();
|
|
8702
|
+
const prev = state.failures[scope] ?? 0;
|
|
8703
|
+
state.lastRecorded = state.lastRecorded || {};
|
|
8704
|
+
if (passed) {
|
|
8705
|
+
if (prev > 0 || state.lastRecorded[scope] !== void 0) {
|
|
8706
|
+
delete state.failures[scope];
|
|
8707
|
+
delete state.lastRecorded[scope];
|
|
8708
|
+
saveAutoGotchaState(state);
|
|
8709
|
+
}
|
|
8710
|
+
return { recorded: false, failureCount: 0 };
|
|
8711
|
+
}
|
|
8712
|
+
const failureCount = prev + 1;
|
|
8713
|
+
state.failures[scope] = failureCount;
|
|
8714
|
+
saveAutoGotchaState(state);
|
|
8715
|
+
const lastRecordedAt = state.lastRecorded[scope] ?? 0;
|
|
8716
|
+
const shouldRecord = failureCount === AUTO_GOTCHA_THRESHOLD || failureCount === AUTO_GOTCHA_ESCALATE_AT;
|
|
8717
|
+
if (!shouldRecord || failureCount <= lastRecordedAt) {
|
|
8718
|
+
return { recorded: false, failureCount };
|
|
8719
|
+
}
|
|
8720
|
+
try {
|
|
8721
|
+
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
8722
|
+
const severity = failureCount >= AUTO_GOTCHA_ESCALATE_AT ? "high" : "medium";
|
|
8723
|
+
const result = await addGotcha2({
|
|
8724
|
+
filePath: scope,
|
|
8725
|
+
description: `Verification keeps failing for scope "${scope}"`,
|
|
8726
|
+
severity,
|
|
8727
|
+
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.`
|
|
8728
|
+
});
|
|
8729
|
+
state.lastRecorded[scope] = failureCount;
|
|
8730
|
+
saveAutoGotchaState(state);
|
|
8731
|
+
return {
|
|
8732
|
+
recorded: true,
|
|
8733
|
+
failureCount,
|
|
8734
|
+
message: [
|
|
8735
|
+
`\u{1F9E0} **Auto-Gotcha recorded** (self-learning loop)`,
|
|
8736
|
+
`\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`,
|
|
8737
|
+
``,
|
|
8738
|
+
`\u26A0\uFE0F Scope \`${scope}\` failed verification ${failureCount} consecutive times.`,
|
|
8739
|
+
`A gotcha has been recorded automatically so future sessions avoid re-discovering this.`,
|
|
8740
|
+
``,
|
|
8741
|
+
result
|
|
8742
|
+
].join("\n")
|
|
8743
|
+
};
|
|
8744
|
+
} catch (err) {
|
|
8745
|
+
return {
|
|
8746
|
+
recorded: false,
|
|
8747
|
+
failureCount,
|
|
8748
|
+
message: `\u26A0\uFE0F Auto-gotcha recording failed: ${err}`
|
|
8749
|
+
};
|
|
8750
|
+
}
|
|
8751
|
+
}
|
|
8752
|
+
var STATE_FILE, AUTO_GOTCHA_THRESHOLD, AUTO_GOTCHA_ESCALATE_AT;
|
|
8753
|
+
var init_kumaAutoGotcha = __esm({
|
|
8754
|
+
"src/engine/kumaAutoGotcha.ts"() {
|
|
8755
|
+
"use strict";
|
|
8756
|
+
init_pathValidator();
|
|
8757
|
+
STATE_FILE = ".kuma/auto-gotcha.json";
|
|
8758
|
+
AUTO_GOTCHA_THRESHOLD = 2;
|
|
8759
|
+
AUTO_GOTCHA_ESCALATE_AT = 4;
|
|
8760
|
+
}
|
|
8761
|
+
});
|
|
8762
|
+
|
|
8278
8763
|
// src/engine/kumaVerifier.ts
|
|
8279
8764
|
var kumaVerifier_exports = {};
|
|
8280
8765
|
__export(kumaVerifier_exports, {
|
|
@@ -8282,37 +8767,37 @@ __export(kumaVerifier_exports, {
|
|
|
8282
8767
|
getRunningVerificationPid: () => getRunningVerificationPid,
|
|
8283
8768
|
runAutoVerification: () => runAutoVerification
|
|
8284
8769
|
});
|
|
8285
|
-
import
|
|
8286
|
-
import
|
|
8770
|
+
import fs26 from "fs";
|
|
8771
|
+
import path27 from "path";
|
|
8287
8772
|
import { exec } from "child_process";
|
|
8288
8773
|
function getLockDir(root = process.cwd()) {
|
|
8289
|
-
return
|
|
8774
|
+
return path27.resolve(root, LOCK_DIR_NAME);
|
|
8290
8775
|
}
|
|
8291
8776
|
function getPidFile(root = process.cwd()) {
|
|
8292
|
-
return
|
|
8777
|
+
return path27.join(getLockDir(root), "pid");
|
|
8293
8778
|
}
|
|
8294
8779
|
function acquireFileLock(root) {
|
|
8295
8780
|
const lockDir = getLockDir(root);
|
|
8296
8781
|
try {
|
|
8297
|
-
|
|
8298
|
-
|
|
8782
|
+
fs26.mkdirSync(lockDir, { recursive: false });
|
|
8783
|
+
fs26.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
8299
8784
|
return true;
|
|
8300
8785
|
} catch {
|
|
8301
8786
|
try {
|
|
8302
|
-
if (
|
|
8303
|
-
const pid = parseInt(
|
|
8787
|
+
if (fs26.existsSync(getPidFile(root))) {
|
|
8788
|
+
const pid = parseInt(fs26.readFileSync(getPidFile(root), "utf-8"), 10);
|
|
8304
8789
|
try {
|
|
8305
8790
|
process.kill(pid, 0);
|
|
8306
8791
|
return false;
|
|
8307
8792
|
} catch {
|
|
8308
|
-
|
|
8793
|
+
fs26.rmSync(lockDir, { recursive: true, force: true });
|
|
8309
8794
|
return acquireFileLock(root);
|
|
8310
8795
|
}
|
|
8311
8796
|
}
|
|
8312
8797
|
} catch {
|
|
8313
8798
|
}
|
|
8314
8799
|
try {
|
|
8315
|
-
|
|
8800
|
+
fs26.rmSync(lockDir, { recursive: true, force: true });
|
|
8316
8801
|
return acquireFileLock(root);
|
|
8317
8802
|
} catch {
|
|
8318
8803
|
return false;
|
|
@@ -8321,7 +8806,7 @@ function acquireFileLock(root) {
|
|
|
8321
8806
|
}
|
|
8322
8807
|
function releaseFileLock(root) {
|
|
8323
8808
|
try {
|
|
8324
|
-
|
|
8809
|
+
fs26.rmSync(getLockDir(root), { recursive: true, force: true });
|
|
8325
8810
|
} catch {
|
|
8326
8811
|
}
|
|
8327
8812
|
}
|
|
@@ -8344,36 +8829,36 @@ function getRunningVerificationPid() {
|
|
|
8344
8829
|
return _currentProcess?.pid ?? null;
|
|
8345
8830
|
}
|
|
8346
8831
|
function detectTestRunner(root = process.cwd()) {
|
|
8347
|
-
const pkgPath =
|
|
8348
|
-
if (
|
|
8832
|
+
const pkgPath = path27.join(root, "package.json");
|
|
8833
|
+
if (fs26.existsSync(pkgPath)) {
|
|
8349
8834
|
try {
|
|
8350
|
-
const pkg = JSON.parse(
|
|
8835
|
+
const pkg = JSON.parse(fs26.readFileSync(pkgPath, "utf-8"));
|
|
8351
8836
|
if (pkg.scripts && pkg.scripts.test) {
|
|
8352
|
-
if (
|
|
8353
|
-
if (
|
|
8837
|
+
if (fs26.existsSync(path27.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
8838
|
+
if (fs26.existsSync(path27.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
|
|
8354
8839
|
return { runner: "npm", baseCommand: "npm test" };
|
|
8355
8840
|
}
|
|
8356
8841
|
} catch {
|
|
8357
8842
|
}
|
|
8358
8843
|
}
|
|
8359
|
-
if (
|
|
8360
|
-
if (
|
|
8361
|
-
if (
|
|
8362
|
-
if (
|
|
8844
|
+
if (fs26.existsSync(path27.join(root, "pytest.ini")) || fs26.existsSync(path27.join(root, "pyproject.toml"))) return { runner: "pytest", baseCommand: "pytest" };
|
|
8845
|
+
if (fs26.existsSync(path27.join(root, "Cargo.toml"))) return { runner: "cargo", baseCommand: "cargo test" };
|
|
8846
|
+
if (fs26.existsSync(path27.join(root, "go.mod"))) return { runner: "go", baseCommand: "go test ./..." };
|
|
8847
|
+
if (fs26.existsSync(path27.join(root, "Makefile"))) {
|
|
8363
8848
|
try {
|
|
8364
|
-
const makefileContent =
|
|
8849
|
+
const makefileContent = fs26.readFileSync(path27.join(root, "Makefile"), "utf-8");
|
|
8365
8850
|
const hasTestTarget = /^test:/m.test(makefileContent) || /^\s+test:/.test(makefileContent);
|
|
8366
8851
|
if (hasTestTarget) return { runner: "make", baseCommand: "make test" };
|
|
8367
8852
|
} catch {
|
|
8368
8853
|
}
|
|
8369
8854
|
}
|
|
8370
|
-
if (
|
|
8855
|
+
if (fs26.existsSync(path27.join(root, "package.json"))) {
|
|
8371
8856
|
try {
|
|
8372
|
-
const pkg = JSON.parse(
|
|
8857
|
+
const pkg = JSON.parse(fs26.readFileSync(path27.join(root, "package.json"), "utf-8"));
|
|
8373
8858
|
if (pkg.dependencies || pkg.devDependencies) {
|
|
8374
|
-
if (
|
|
8375
|
-
const srcDir =
|
|
8376
|
-
if (
|
|
8859
|
+
if (fs26.existsSync(path27.join(root, "tsconfig.json"))) return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
8860
|
+
const srcDir = path27.join(root, "src");
|
|
8861
|
+
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
8862
|
}
|
|
8378
8863
|
} catch {
|
|
8379
8864
|
}
|
|
@@ -8492,7 +8977,7 @@ async function runAutoVerification(options = {}) {
|
|
|
8492
8977
|
const heartbeatTimer = setInterval(() => {
|
|
8493
8978
|
if (getLockDir(root)) {
|
|
8494
8979
|
try {
|
|
8495
|
-
|
|
8980
|
+
fs26.writeFileSync(getPidFile(root), String(process.pid), "utf-8");
|
|
8496
8981
|
} catch {
|
|
8497
8982
|
}
|
|
8498
8983
|
}
|
|
@@ -8512,8 +8997,24 @@ async function runAutoVerification(options = {}) {
|
|
|
8512
8997
|
if (passed && fakePassPatterns.some((p) => p.test(rawOutput))) {
|
|
8513
8998
|
passed = false;
|
|
8514
8999
|
}
|
|
9000
|
+
const isNoTests = fakePassPatterns.some((p) => p.test(rawOutput));
|
|
8515
9001
|
const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
|
|
8516
9002
|
await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
|
|
9003
|
+
let autoGotchaMessage = null;
|
|
9004
|
+
if (scope !== "session-impact" && !isNoTests) {
|
|
9005
|
+
try {
|
|
9006
|
+
const { trackVerificationResult: trackVerificationResult2 } = await Promise.resolve().then(() => (init_kumaAutoGotcha(), kumaAutoGotcha_exports));
|
|
9007
|
+
const autoResult = await trackVerificationResult2(scope, passed);
|
|
9008
|
+
if (autoResult.message) autoGotchaMessage = autoResult.message;
|
|
9009
|
+
} catch {
|
|
9010
|
+
}
|
|
9011
|
+
} else if (passed) {
|
|
9012
|
+
try {
|
|
9013
|
+
const { resetScopeFailures: resetScopeFailures2 } = await Promise.resolve().then(() => (init_kumaAutoGotcha(), kumaAutoGotcha_exports));
|
|
9014
|
+
resetScopeFailures2(scope);
|
|
9015
|
+
} catch {
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
8517
9018
|
_localRunning = false;
|
|
8518
9019
|
_currentProcess = null;
|
|
8519
9020
|
releaseFileLock(root);
|
|
@@ -8530,7 +9031,6 @@ async function runAutoVerification(options = {}) {
|
|
|
8530
9031
|
""
|
|
8531
9032
|
];
|
|
8532
9033
|
if (!passed) {
|
|
8533
|
-
const isNoTests = fakePassPatterns.some((p) => p.test(rawOutput));
|
|
8534
9034
|
if (isNoTests) {
|
|
8535
9035
|
lines[0] = `\u26AA **Verification: NO TESTS**`;
|
|
8536
9036
|
lines.push("\u2139\uFE0F No tests ran. Install Jest, Vitest, pytest, or similar to enable verification.");
|
|
@@ -8545,6 +9045,9 @@ async function runAutoVerification(options = {}) {
|
|
|
8545
9045
|
lines.push("\u{1F389} All scoped tests passed!");
|
|
8546
9046
|
if (rawOutput) lines.push("```text", rawOutput.substring(0, 800), "```");
|
|
8547
9047
|
}
|
|
9048
|
+
if (autoGotchaMessage) {
|
|
9049
|
+
lines.push("", autoGotchaMessage);
|
|
9050
|
+
}
|
|
8548
9051
|
resolve(lines.join("\n"));
|
|
8549
9052
|
});
|
|
8550
9053
|
_currentProcess = child;
|
|
@@ -8586,8 +9089,8 @@ __export(init_exports, {
|
|
|
8586
9089
|
generateInitMdContent: () => generateInitMdContent,
|
|
8587
9090
|
runInit: () => runInit
|
|
8588
9091
|
});
|
|
8589
|
-
import
|
|
8590
|
-
import
|
|
9092
|
+
import fs27 from "fs";
|
|
9093
|
+
import path28 from "path";
|
|
8591
9094
|
function configFilePath(type) {
|
|
8592
9095
|
switch (type) {
|
|
8593
9096
|
case "claude":
|
|
@@ -8997,10 +9500,10 @@ function generateInitMdContent() {
|
|
|
8997
9500
|
].join("\n");
|
|
8998
9501
|
}
|
|
8999
9502
|
function handleOpencodeSecondary(root, results) {
|
|
9000
|
-
const skillPath =
|
|
9503
|
+
const skillPath = path28.resolve(root, ".agents", "skills", "kuma", "SKILL.md");
|
|
9001
9504
|
if (results.some((r) => r.filePath === ".agents/skills/kuma/SKILL.md")) return;
|
|
9002
9505
|
try {
|
|
9003
|
-
const dir =
|
|
9506
|
+
const dir = path28.dirname(skillPath);
|
|
9004
9507
|
const content = [
|
|
9005
9508
|
"---",
|
|
9006
9509
|
"name: kuma-mcp",
|
|
@@ -9024,18 +9527,18 @@ function handleOpencodeSecondary(root, results) {
|
|
|
9024
9527
|
"",
|
|
9025
9528
|
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
9026
9529
|
].join("\n");
|
|
9027
|
-
if (
|
|
9028
|
-
const existing =
|
|
9530
|
+
if (fs27.existsSync(skillPath)) {
|
|
9531
|
+
const existing = fs27.readFileSync(skillPath, "utf-8");
|
|
9029
9532
|
if (existing.includes("kuma-mcp")) {
|
|
9030
9533
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "skipped" });
|
|
9031
9534
|
return;
|
|
9032
9535
|
}
|
|
9033
9536
|
const newContent = existing.trimEnd() + "\n\n---\n\n" + content;
|
|
9034
|
-
|
|
9537
|
+
fs27.writeFileSync(skillPath, newContent, "utf-8");
|
|
9035
9538
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "appended" });
|
|
9036
9539
|
} else {
|
|
9037
|
-
if (!
|
|
9038
|
-
|
|
9540
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9541
|
+
fs27.writeFileSync(skillPath, content, "utf-8");
|
|
9039
9542
|
results.push({ type: "opencode", filePath: ".agents/skills/kuma/SKILL.md", action: "created" });
|
|
9040
9543
|
}
|
|
9041
9544
|
} catch (err) {
|
|
@@ -9048,21 +9551,21 @@ function handleOpencodeSecondary(root, results) {
|
|
|
9048
9551
|
}
|
|
9049
9552
|
}
|
|
9050
9553
|
function handleCodexSecondary(root, results) {
|
|
9051
|
-
const tomlPath =
|
|
9554
|
+
const tomlPath = path28.resolve(root, ".codex/config.toml");
|
|
9052
9555
|
if (results.some((r) => r.filePath === ".codex/config.toml")) return;
|
|
9053
9556
|
try {
|
|
9054
|
-
const dir =
|
|
9055
|
-
if (!
|
|
9056
|
-
if (
|
|
9057
|
-
const existingContent =
|
|
9557
|
+
const dir = path28.dirname(tomlPath);
|
|
9558
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9559
|
+
if (fs27.existsSync(tomlPath)) {
|
|
9560
|
+
const existingContent = fs27.readFileSync(tomlPath, "utf-8");
|
|
9058
9561
|
if (existingContent.includes("kuma")) {
|
|
9059
9562
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
|
|
9060
9563
|
return;
|
|
9061
9564
|
}
|
|
9062
|
-
|
|
9565
|
+
fs27.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
|
|
9063
9566
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
|
|
9064
9567
|
} else {
|
|
9065
|
-
|
|
9568
|
+
fs27.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
|
|
9066
9569
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
|
|
9067
9570
|
}
|
|
9068
9571
|
} catch (err) {
|
|
@@ -9075,11 +9578,11 @@ function handleCodexSecondary(root, results) {
|
|
|
9075
9578
|
}
|
|
9076
9579
|
}
|
|
9077
9580
|
function handleQwenSecondary(root, results) {
|
|
9078
|
-
const settingsPath =
|
|
9581
|
+
const settingsPath = path28.resolve(root, "settings.json");
|
|
9079
9582
|
if (results.some((r) => r.filePath === "settings.json")) return;
|
|
9080
9583
|
try {
|
|
9081
|
-
if (
|
|
9082
|
-
const existingContent =
|
|
9584
|
+
if (fs27.existsSync(settingsPath)) {
|
|
9585
|
+
const existingContent = fs27.readFileSync(settingsPath, "utf-8");
|
|
9083
9586
|
if (existingContent.includes("kuma")) {
|
|
9084
9587
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9085
9588
|
try {
|
|
@@ -9087,7 +9590,7 @@ function handleQwenSecondary(root, results) {
|
|
|
9087
9590
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9088
9591
|
if (!parsed.mcpServers.kuma) {
|
|
9089
9592
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9090
|
-
|
|
9593
|
+
fs27.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9091
9594
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
9092
9595
|
return;
|
|
9093
9596
|
}
|
|
@@ -9102,13 +9605,13 @@ function handleQwenSecondary(root, results) {
|
|
|
9102
9605
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9103
9606
|
if (!parsed.mcpServers.kuma) {
|
|
9104
9607
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9105
|
-
|
|
9608
|
+
fs27.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9106
9609
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
9107
9610
|
}
|
|
9108
9611
|
} catch {
|
|
9109
9612
|
}
|
|
9110
9613
|
} else {
|
|
9111
|
-
|
|
9614
|
+
fs27.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
|
|
9112
9615
|
results.push({ type: "qwen", filePath: "settings.json", action: "created" });
|
|
9113
9616
|
}
|
|
9114
9617
|
} catch (err) {
|
|
@@ -9206,18 +9709,18 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
9206
9709
|
return lines.join("\n");
|
|
9207
9710
|
}
|
|
9208
9711
|
function handleAntigravityMcpConfig(root, results) {
|
|
9209
|
-
const mcpPath =
|
|
9712
|
+
const mcpPath = path28.resolve(root, ".agents/mcp_config.json");
|
|
9210
9713
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
9211
9714
|
try {
|
|
9212
|
-
const mcpDir =
|
|
9213
|
-
if (
|
|
9214
|
-
const existingContent =
|
|
9715
|
+
const mcpDir = path28.dirname(mcpPath);
|
|
9716
|
+
if (fs27.existsSync(mcpPath)) {
|
|
9717
|
+
const existingContent = fs27.readFileSync(mcpPath, "utf-8");
|
|
9215
9718
|
if (existingContent.includes("kuma")) {
|
|
9216
9719
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9217
9720
|
const trimmed = existingContent.trimEnd();
|
|
9218
9721
|
if (trimmed.endsWith("}")) {
|
|
9219
9722
|
const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
|
|
9220
|
-
|
|
9723
|
+
fs27.writeFileSync(mcpPath, updated, "utf-8");
|
|
9221
9724
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
9222
9725
|
}
|
|
9223
9726
|
}
|
|
@@ -9226,11 +9729,11 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
9226
9729
|
const parsed = JSON.parse(existingContent);
|
|
9227
9730
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9228
9731
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9229
|
-
|
|
9732
|
+
fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9230
9733
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
9231
9734
|
} else {
|
|
9232
|
-
if (!
|
|
9233
|
-
|
|
9735
|
+
if (!fs27.existsSync(mcpDir)) fs27.mkdirSync(mcpDir, { recursive: true });
|
|
9736
|
+
fs27.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
9234
9737
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
|
|
9235
9738
|
}
|
|
9236
9739
|
} catch (err) {
|
|
@@ -9243,18 +9746,18 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
9243
9746
|
}
|
|
9244
9747
|
}
|
|
9245
9748
|
function handleAiderSecondary(root, results) {
|
|
9246
|
-
const ymlPath =
|
|
9749
|
+
const ymlPath = path28.resolve(root, ".aider.conf.yml");
|
|
9247
9750
|
if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
|
|
9248
9751
|
try {
|
|
9249
9752
|
const conventionsRef = "read: CONVENTIONS.md";
|
|
9250
|
-
if (
|
|
9251
|
-
const existingContent =
|
|
9753
|
+
if (fs27.existsSync(ymlPath)) {
|
|
9754
|
+
const existingContent = fs27.readFileSync(ymlPath, "utf-8");
|
|
9252
9755
|
if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
|
|
9253
9756
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
|
|
9254
9757
|
return;
|
|
9255
9758
|
}
|
|
9256
9759
|
const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
|
|
9257
|
-
|
|
9760
|
+
fs27.writeFileSync(ymlPath, newContent, "utf-8");
|
|
9258
9761
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
|
|
9259
9762
|
} else {
|
|
9260
9763
|
const content = [
|
|
@@ -9264,7 +9767,7 @@ function handleAiderSecondary(root, results) {
|
|
|
9264
9767
|
conventionsRef,
|
|
9265
9768
|
""
|
|
9266
9769
|
].join("\n");
|
|
9267
|
-
|
|
9770
|
+
fs27.writeFileSync(ymlPath, content, "utf-8");
|
|
9268
9771
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
|
|
9269
9772
|
}
|
|
9270
9773
|
} catch (err) {
|
|
@@ -9277,10 +9780,10 @@ function handleAiderSecondary(root, results) {
|
|
|
9277
9780
|
}
|
|
9278
9781
|
}
|
|
9279
9782
|
function handleCopilotSecondary(root, results) {
|
|
9280
|
-
const skillPath =
|
|
9783
|
+
const skillPath = path28.resolve(root, ".github/skills/kuma/SKILL.md");
|
|
9281
9784
|
if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
|
|
9282
9785
|
try {
|
|
9283
|
-
const dir =
|
|
9786
|
+
const dir = path28.dirname(skillPath);
|
|
9284
9787
|
const content = [
|
|
9285
9788
|
"---",
|
|
9286
9789
|
"name: kuma-mcp",
|
|
@@ -9291,17 +9794,17 @@ function handleCopilotSecondary(root, results) {
|
|
|
9291
9794
|
"",
|
|
9292
9795
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
9293
9796
|
].join("\n");
|
|
9294
|
-
if (
|
|
9295
|
-
const existingContent =
|
|
9797
|
+
if (fs27.existsSync(skillPath)) {
|
|
9798
|
+
const existingContent = fs27.readFileSync(skillPath, "utf-8");
|
|
9296
9799
|
if (existingContent.includes("kuma")) {
|
|
9297
9800
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
|
|
9298
9801
|
return;
|
|
9299
9802
|
}
|
|
9300
|
-
|
|
9803
|
+
fs27.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
|
|
9301
9804
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
|
|
9302
9805
|
} else {
|
|
9303
|
-
if (!
|
|
9304
|
-
|
|
9806
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9807
|
+
fs27.writeFileSync(skillPath, content, "utf-8");
|
|
9305
9808
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
|
|
9306
9809
|
}
|
|
9307
9810
|
} catch (err) {
|
|
@@ -9314,21 +9817,21 @@ function handleCopilotSecondary(root, results) {
|
|
|
9314
9817
|
}
|
|
9315
9818
|
}
|
|
9316
9819
|
function handleOpenclawSecondary(root, results) {
|
|
9317
|
-
const mcpPath =
|
|
9820
|
+
const mcpPath = path28.resolve(root, ".agents/mcp_config.json");
|
|
9318
9821
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
9319
9822
|
try {
|
|
9320
|
-
const dir =
|
|
9321
|
-
if (
|
|
9322
|
-
const existingContent =
|
|
9823
|
+
const dir = path28.dirname(mcpPath);
|
|
9824
|
+
if (fs27.existsSync(mcpPath)) {
|
|
9825
|
+
const existingContent = fs27.readFileSync(mcpPath, "utf-8");
|
|
9323
9826
|
if (existingContent.includes("kuma")) return;
|
|
9324
9827
|
const parsed = JSON.parse(existingContent);
|
|
9325
9828
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9326
9829
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9327
|
-
|
|
9830
|
+
fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9328
9831
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
9329
9832
|
} else {
|
|
9330
|
-
if (!
|
|
9331
|
-
|
|
9833
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9834
|
+
fs27.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
9332
9835
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
|
|
9333
9836
|
}
|
|
9334
9837
|
} catch (err) {
|
|
@@ -9341,7 +9844,7 @@ function handleOpenclawSecondary(root, results) {
|
|
|
9341
9844
|
}
|
|
9342
9845
|
}
|
|
9343
9846
|
function handleQuickrefGeneration(root, results) {
|
|
9344
|
-
const quickrefPath =
|
|
9847
|
+
const quickrefPath = path28.resolve(root, ".kuma/quickref.md");
|
|
9345
9848
|
const content = [
|
|
9346
9849
|
"# Kuma Quick Reference",
|
|
9347
9850
|
"",
|
|
@@ -9366,16 +9869,16 @@ function handleQuickrefGeneration(root, results) {
|
|
|
9366
9869
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9367
9870
|
].join("\n");
|
|
9368
9871
|
try {
|
|
9369
|
-
const kumaDir =
|
|
9370
|
-
if (!
|
|
9371
|
-
if (
|
|
9372
|
-
const existing =
|
|
9872
|
+
const kumaDir = path28.dirname(quickrefPath);
|
|
9873
|
+
if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
|
|
9874
|
+
if (fs27.existsSync(quickrefPath)) {
|
|
9875
|
+
const existing = fs27.readFileSync(quickrefPath, "utf-8");
|
|
9373
9876
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
9374
9877
|
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
|
|
9375
9878
|
return;
|
|
9376
9879
|
}
|
|
9377
9880
|
}
|
|
9378
|
-
|
|
9881
|
+
fs27.writeFileSync(quickrefPath, content, "utf-8");
|
|
9379
9882
|
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
|
|
9380
9883
|
} catch (err) {
|
|
9381
9884
|
results.push({
|
|
@@ -9387,7 +9890,7 @@ function handleQuickrefGeneration(root, results) {
|
|
|
9387
9890
|
}
|
|
9388
9891
|
}
|
|
9389
9892
|
function handleModeMdGeneration(root, results) {
|
|
9390
|
-
const filePath =
|
|
9893
|
+
const filePath = path28.resolve(root, ".kuma/MODE.md");
|
|
9391
9894
|
const content = [
|
|
9392
9895
|
"# Adaptive Mode Selector",
|
|
9393
9896
|
"",
|
|
@@ -9430,10 +9933,10 @@ function handleModeMdGeneration(root, results) {
|
|
|
9430
9933
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9431
9934
|
].join("\n");
|
|
9432
9935
|
try {
|
|
9433
|
-
const dir =
|
|
9434
|
-
if (!
|
|
9435
|
-
if (!
|
|
9436
|
-
|
|
9936
|
+
const dir = path28.dirname(filePath);
|
|
9937
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9938
|
+
if (!fs27.existsSync(filePath)) {
|
|
9939
|
+
fs27.writeFileSync(filePath, content, "utf-8");
|
|
9437
9940
|
results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "created" });
|
|
9438
9941
|
}
|
|
9439
9942
|
} catch (err) {
|
|
@@ -9441,7 +9944,7 @@ function handleModeMdGeneration(root, results) {
|
|
|
9441
9944
|
}
|
|
9442
9945
|
}
|
|
9443
9946
|
function handleSkipRulesMdGeneration(root, results) {
|
|
9444
|
-
const filePath =
|
|
9947
|
+
const filePath = path28.resolve(root, ".kuma/SKIP_RULES.md");
|
|
9445
9948
|
const content = [
|
|
9446
9949
|
"# Skip Rules \u2014 What NOT to Record",
|
|
9447
9950
|
"",
|
|
@@ -9478,10 +9981,10 @@ function handleSkipRulesMdGeneration(root, results) {
|
|
|
9478
9981
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9479
9982
|
].join("\n");
|
|
9480
9983
|
try {
|
|
9481
|
-
const dir =
|
|
9482
|
-
if (!
|
|
9483
|
-
if (!
|
|
9484
|
-
|
|
9984
|
+
const dir = path28.dirname(filePath);
|
|
9985
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9986
|
+
if (!fs27.existsSync(filePath)) {
|
|
9987
|
+
fs27.writeFileSync(filePath, content, "utf-8");
|
|
9485
9988
|
results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "created" });
|
|
9486
9989
|
}
|
|
9487
9990
|
} catch (err) {
|
|
@@ -9489,7 +9992,7 @@ function handleSkipRulesMdGeneration(root, results) {
|
|
|
9489
9992
|
}
|
|
9490
9993
|
}
|
|
9491
9994
|
function handleStalenessMdGeneration(root, results) {
|
|
9492
|
-
const filePath =
|
|
9995
|
+
const filePath = path28.resolve(root, ".kuma/STALENESS.md");
|
|
9493
9996
|
const content = [
|
|
9494
9997
|
"# Auto-Staleness Detection",
|
|
9495
9998
|
"",
|
|
@@ -9520,10 +10023,10 @@ function handleStalenessMdGeneration(root, results) {
|
|
|
9520
10023
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9521
10024
|
].join("\n");
|
|
9522
10025
|
try {
|
|
9523
|
-
const dir =
|
|
9524
|
-
if (!
|
|
9525
|
-
if (!
|
|
9526
|
-
|
|
10026
|
+
const dir = path28.dirname(filePath);
|
|
10027
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
10028
|
+
if (!fs27.existsSync(filePath)) {
|
|
10029
|
+
fs27.writeFileSync(filePath, content, "utf-8");
|
|
9527
10030
|
results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "created" });
|
|
9528
10031
|
}
|
|
9529
10032
|
} catch (err) {
|
|
@@ -9531,20 +10034,20 @@ function handleStalenessMdGeneration(root, results) {
|
|
|
9531
10034
|
}
|
|
9532
10035
|
}
|
|
9533
10036
|
function handleInitMdGeneration(root, results) {
|
|
9534
|
-
const initMdPath =
|
|
10037
|
+
const initMdPath = path28.resolve(root, ".kuma/init.md");
|
|
9535
10038
|
try {
|
|
9536
|
-
const kumaDir =
|
|
9537
|
-
if (!
|
|
9538
|
-
if (
|
|
9539
|
-
const existing =
|
|
10039
|
+
const kumaDir = path28.dirname(initMdPath);
|
|
10040
|
+
if (!fs27.existsSync(kumaDir)) fs27.mkdirSync(kumaDir, { recursive: true });
|
|
10041
|
+
if (fs27.existsSync(initMdPath)) {
|
|
10042
|
+
const existing = fs27.readFileSync(initMdPath, "utf-8");
|
|
9540
10043
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
9541
10044
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
|
|
9542
10045
|
return;
|
|
9543
10046
|
}
|
|
9544
|
-
|
|
10047
|
+
fs27.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
|
|
9545
10048
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
|
|
9546
10049
|
} else {
|
|
9547
|
-
|
|
10050
|
+
fs27.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
9548
10051
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
|
|
9549
10052
|
}
|
|
9550
10053
|
} catch (err) {
|
|
@@ -9561,20 +10064,20 @@ function handleInitMdGeneration(root, results) {
|
|
|
9561
10064
|
handleStalenessMdGeneration(root, results);
|
|
9562
10065
|
}
|
|
9563
10066
|
function handleCodewhaleSecondary(root, results) {
|
|
9564
|
-
const mcpPath =
|
|
10067
|
+
const mcpPath = path28.resolve(root, ".codewhale/mcp.json");
|
|
9565
10068
|
if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
|
|
9566
10069
|
try {
|
|
9567
|
-
const dir =
|
|
9568
|
-
if (
|
|
9569
|
-
const existingContent =
|
|
10070
|
+
const dir = path28.dirname(mcpPath);
|
|
10071
|
+
if (fs27.existsSync(mcpPath)) {
|
|
10072
|
+
const existingContent = fs27.readFileSync(mcpPath, "utf-8");
|
|
9570
10073
|
if (existingContent.includes("kuma")) return;
|
|
9571
10074
|
const parsed = JSON.parse(existingContent);
|
|
9572
10075
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
9573
10076
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
9574
|
-
|
|
10077
|
+
fs27.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
9575
10078
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
|
|
9576
10079
|
} else {
|
|
9577
|
-
if (!
|
|
10080
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
9578
10081
|
const config = {
|
|
9579
10082
|
mcpServers: {
|
|
9580
10083
|
kuma: {
|
|
@@ -9584,7 +10087,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
9584
10087
|
}
|
|
9585
10088
|
}
|
|
9586
10089
|
};
|
|
9587
|
-
|
|
10090
|
+
fs27.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
9588
10091
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
|
|
9589
10092
|
}
|
|
9590
10093
|
} catch (err) {
|
|
@@ -9606,28 +10109,28 @@ function runInit(options) {
|
|
|
9606
10109
|
let agentsMdHandled = false;
|
|
9607
10110
|
for (const type of selected) {
|
|
9608
10111
|
const relativePath = configFilePath(type);
|
|
9609
|
-
const fullPath =
|
|
10112
|
+
const fullPath = path28.resolve(root, relativePath);
|
|
9610
10113
|
const getTemplate = TEMPLATES[type];
|
|
9611
10114
|
try {
|
|
9612
10115
|
if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
|
|
9613
10116
|
agentsMdHandled = true;
|
|
9614
10117
|
const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
|
|
9615
|
-
if (
|
|
10118
|
+
if (fs27.existsSync(fullPath)) {
|
|
9616
10119
|
if (options.skipExisting) {
|
|
9617
10120
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9618
10121
|
} else {
|
|
9619
|
-
const existingContent =
|
|
10122
|
+
const existingContent = fs27.readFileSync(fullPath, "utf-8");
|
|
9620
10123
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9621
10124
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9622
10125
|
} else {
|
|
9623
|
-
|
|
10126
|
+
fs27.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
|
|
9624
10127
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
9625
10128
|
}
|
|
9626
10129
|
}
|
|
9627
10130
|
} else {
|
|
9628
|
-
const dir =
|
|
9629
|
-
if (!
|
|
9630
|
-
|
|
10131
|
+
const dir = path28.dirname(fullPath);
|
|
10132
|
+
if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
|
|
10133
|
+
fs27.writeFileSync(fullPath, combinedContent, "utf-8");
|
|
9631
10134
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
9632
10135
|
}
|
|
9633
10136
|
if (selectedSet.has("opencode")) handleOpencodeSecondary(root, results);
|
|
@@ -9639,12 +10142,12 @@ function runInit(options) {
|
|
|
9639
10142
|
continue;
|
|
9640
10143
|
} else {
|
|
9641
10144
|
const template = getTemplate();
|
|
9642
|
-
if (
|
|
10145
|
+
if (fs27.existsSync(fullPath)) {
|
|
9643
10146
|
if (options.skipExisting) {
|
|
9644
10147
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
9645
10148
|
continue;
|
|
9646
10149
|
}
|
|
9647
|
-
const existingContent =
|
|
10150
|
+
const existingContent = fs27.readFileSync(fullPath, "utf-8");
|
|
9648
10151
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
9649
10152
|
if (type === "antigravity") {
|
|
9650
10153
|
handleAntigravityMcpConfig(root, results);
|
|
@@ -9657,14 +10160,14 @@ function runInit(options) {
|
|
|
9657
10160
|
continue;
|
|
9658
10161
|
}
|
|
9659
10162
|
const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
|
|
9660
|
-
|
|
10163
|
+
fs27.writeFileSync(fullPath, newContent, "utf-8");
|
|
9661
10164
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
9662
10165
|
} else {
|
|
9663
|
-
const dir =
|
|
9664
|
-
if (!
|
|
9665
|
-
|
|
10166
|
+
const dir = path28.dirname(fullPath);
|
|
10167
|
+
if (!fs27.existsSync(dir)) {
|
|
10168
|
+
fs27.mkdirSync(dir, { recursive: true });
|
|
9666
10169
|
}
|
|
9667
|
-
|
|
10170
|
+
fs27.writeFileSync(fullPath, template, "utf-8");
|
|
9668
10171
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
9669
10172
|
}
|
|
9670
10173
|
if (type === "antigravity") {
|
|
@@ -9806,6 +10309,209 @@ var init_init = __esm({
|
|
|
9806
10309
|
}
|
|
9807
10310
|
});
|
|
9808
10311
|
|
|
10312
|
+
// src/engine/kumaLegacyOnboard.ts
|
|
10313
|
+
var kumaLegacyOnboard_exports = {};
|
|
10314
|
+
__export(kumaLegacyOnboard_exports, {
|
|
10315
|
+
runLegacyOnboarding: () => runLegacyOnboarding
|
|
10316
|
+
});
|
|
10317
|
+
import fs28 from "fs";
|
|
10318
|
+
import path29 from "path";
|
|
10319
|
+
async function harvestGit(commitCount) {
|
|
10320
|
+
try {
|
|
10321
|
+
const { harvestGitHistory: harvestGitHistory2 } = await Promise.resolve().then(() => (init_kumaGitHarvester(), kumaGitHarvester_exports));
|
|
10322
|
+
return await harvestGitHistory2({ commitCount });
|
|
10323
|
+
} catch (err) {
|
|
10324
|
+
return `\u26A0\uFE0F Git harvest skipped: ${err}`;
|
|
10325
|
+
}
|
|
10326
|
+
}
|
|
10327
|
+
async function mineInlineMarkers(root, maxFiles) {
|
|
10328
|
+
const markers = [];
|
|
10329
|
+
try {
|
|
10330
|
+
const { default: fg } = await import("fast-glob");
|
|
10331
|
+
const files = await fg(
|
|
10332
|
+
["**/*.{ts,tsx,js,jsx,py,go,rs,java,kt,cs,rb,php,c,cpp,h,hpp}"],
|
|
10333
|
+
{
|
|
10334
|
+
cwd: root,
|
|
10335
|
+
ignore: ["node_modules/**", "dist/**", "build/**", ".git/**", ".kuma/**", "coverage/**", "vendor/**"],
|
|
10336
|
+
onlyFiles: true,
|
|
10337
|
+
deep: 8
|
|
10338
|
+
}
|
|
10339
|
+
);
|
|
10340
|
+
for (const file of files.slice(0, maxFiles)) {
|
|
10341
|
+
try {
|
|
10342
|
+
const fullPath = path29.join(root, file);
|
|
10343
|
+
const content = fs28.readFileSync(fullPath, "utf-8");
|
|
10344
|
+
const lines = content.split("\n");
|
|
10345
|
+
for (let i = 0; i < lines.length && markers.length < 100; i++) {
|
|
10346
|
+
const line = lines[i];
|
|
10347
|
+
for (const { tag, regex } of MARKER_PATTERNS) {
|
|
10348
|
+
const m = regex.exec(line);
|
|
10349
|
+
if (m) {
|
|
10350
|
+
const text = (m[1] || m[2] || m[3] || line.trim()).trim().substring(0, 160);
|
|
10351
|
+
markers.push({ filePath: file, line: i + 1, tag, text });
|
|
10352
|
+
break;
|
|
10353
|
+
}
|
|
10354
|
+
}
|
|
10355
|
+
}
|
|
10356
|
+
} catch {
|
|
10357
|
+
}
|
|
10358
|
+
}
|
|
10359
|
+
} catch {
|
|
10360
|
+
}
|
|
10361
|
+
return markers;
|
|
10362
|
+
}
|
|
10363
|
+
async function buildFeatureNodes(root) {
|
|
10364
|
+
const names = [];
|
|
10365
|
+
try {
|
|
10366
|
+
const { recordFeature: recordFeature2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10367
|
+
const pkgPath = path29.join(root, "package.json");
|
|
10368
|
+
if (fs28.existsSync(pkgPath)) {
|
|
10369
|
+
try {
|
|
10370
|
+
const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
|
|
10371
|
+
const candidates = [
|
|
10372
|
+
pkg.main,
|
|
10373
|
+
pkg.module,
|
|
10374
|
+
pkg.bin ? Object.keys(pkg.bin)[0] : null,
|
|
10375
|
+
...Array.isArray(pkg.exports) ? pkg.exports : []
|
|
10376
|
+
].filter(Boolean);
|
|
10377
|
+
for (const c of candidates.slice(0, 5)) {
|
|
10378
|
+
const name = path29.basename(c, path29.extname(c)).replace(/[^a-zA-Z0-9]/g, " ").trim();
|
|
10379
|
+
if (name && !names.includes(name)) {
|
|
10380
|
+
await recordFeature2({ name, description: `Detected entry point during legacy onboarding (${c})`, files: [c] });
|
|
10381
|
+
names.push(name);
|
|
10382
|
+
}
|
|
10383
|
+
}
|
|
10384
|
+
} catch {
|
|
10385
|
+
}
|
|
10386
|
+
}
|
|
10387
|
+
const srcDir = path29.join(root, "src");
|
|
10388
|
+
if (fs28.existsSync(srcDir)) {
|
|
10389
|
+
try {
|
|
10390
|
+
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));
|
|
10391
|
+
for (const dir of entries.slice(0, 15)) {
|
|
10392
|
+
if (!names.includes(dir)) {
|
|
10393
|
+
const files = [path29.join("src", dir)];
|
|
10394
|
+
await recordFeature2({
|
|
10395
|
+
name: dir,
|
|
10396
|
+
description: `Detected top-level module during legacy onboarding`,
|
|
10397
|
+
files,
|
|
10398
|
+
risk: "medium"
|
|
10399
|
+
});
|
|
10400
|
+
names.push(dir);
|
|
10401
|
+
}
|
|
10402
|
+
}
|
|
10403
|
+
} catch {
|
|
10404
|
+
}
|
|
10405
|
+
}
|
|
10406
|
+
} catch {
|
|
10407
|
+
}
|
|
10408
|
+
return { created: names.length, names };
|
|
10409
|
+
}
|
|
10410
|
+
function buildArchitectureDigest(root, featureNames) {
|
|
10411
|
+
const lines = [];
|
|
10412
|
+
try {
|
|
10413
|
+
const pkgPath = path29.join(root, "package.json");
|
|
10414
|
+
if (fs28.existsSync(pkgPath)) {
|
|
10415
|
+
const pkg = JSON.parse(fs28.readFileSync(pkgPath, "utf-8"));
|
|
10416
|
+
lines.push(`\u{1F3D7}\uFE0F **Stack**: ${[pkg.framework, pkg.orm, pkg.testFramework].filter(Boolean).join(" + ") || "detected"}`);
|
|
10417
|
+
}
|
|
10418
|
+
if (featureNames.length > 0) {
|
|
10419
|
+
lines.push(`\u{1F9E9} **Detected Modules**: ${featureNames.slice(0, 8).join(", ")}`);
|
|
10420
|
+
}
|
|
10421
|
+
lines.push(`\u{1F4C4} **Entry Points**: check package.json main/bin or src/index.*`);
|
|
10422
|
+
} catch {
|
|
10423
|
+
}
|
|
10424
|
+
return lines.join("\n");
|
|
10425
|
+
}
|
|
10426
|
+
async function runLegacyOnboarding(options = {}) {
|
|
10427
|
+
const root = getProjectRoot();
|
|
10428
|
+
const commitCount = options.commitCount ?? 25;
|
|
10429
|
+
const maxFiles = options.maxFiles ?? 200;
|
|
10430
|
+
const lines = [
|
|
10431
|
+
"\u{1F680} **Kuma Legacy Onboarding**",
|
|
10432
|
+
"\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",
|
|
10433
|
+
"",
|
|
10434
|
+
`\u{1F4C1} Project: ${root.split("/").pop() || "unknown"}`,
|
|
10435
|
+
"",
|
|
10436
|
+
"\u23F3 Running bulk analysis \u2014 this may take a few seconds...",
|
|
10437
|
+
""
|
|
10438
|
+
];
|
|
10439
|
+
lines.push("### 1\uFE0F\u20E3 Git History Harvest");
|
|
10440
|
+
try {
|
|
10441
|
+
const harvest = await harvestGit(commitCount);
|
|
10442
|
+
lines.push(harvest);
|
|
10443
|
+
} catch (err) {
|
|
10444
|
+
lines.push(`\u26A0\uFE0F ${err}`);
|
|
10445
|
+
}
|
|
10446
|
+
lines.push("");
|
|
10447
|
+
lines.push("### 2\uFE0F\u20E3 Inline Marker Mining (HACK/FIXME/TODO \u2192 gotchas)");
|
|
10448
|
+
try {
|
|
10449
|
+
const markers = await mineInlineMarkers(root, maxFiles);
|
|
10450
|
+
if (markers.length === 0) {
|
|
10451
|
+
lines.push("\u2705 No HACK/FIXME/TODO markers found \u2014 clean codebase.");
|
|
10452
|
+
} else {
|
|
10453
|
+
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10454
|
+
let recorded = 0;
|
|
10455
|
+
for (const m of markers.slice(0, 40)) {
|
|
10456
|
+
try {
|
|
10457
|
+
const severity = m.tag === "HACK" || m.tag === "WARNING" ? "high" : m.tag === "FIXME" ? "medium" : "low";
|
|
10458
|
+
await addGotcha2({
|
|
10459
|
+
filePath: m.filePath,
|
|
10460
|
+
description: `[${m.tag}] ${m.text} (line ${m.line})`,
|
|
10461
|
+
severity,
|
|
10462
|
+
workaround: `Legacy marker ${m.tag} at ${m.filePath}:${m.line} \u2014 review before modifying this file.`
|
|
10463
|
+
});
|
|
10464
|
+
recorded++;
|
|
10465
|
+
} catch {
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
lines.push(`\u26A0\uFE0F Found ${markers.length} marker(s); recorded ${recorded} as gotchas (capped at 40).`);
|
|
10469
|
+
lines.push(`\u{1F4A1} Remaining ${Math.max(0, markers.length - 40)} \u2014 re-run or use kuma_memory({ action: 'gotcha' }) inline.`);
|
|
10470
|
+
}
|
|
10471
|
+
} catch (err) {
|
|
10472
|
+
lines.push(`\u26A0\uFE0F Marker mining failed: ${err}`);
|
|
10473
|
+
}
|
|
10474
|
+
lines.push("");
|
|
10475
|
+
lines.push("### 3\uFE0F\u20E3 Feature Graph Bootstrap");
|
|
10476
|
+
let featureNames = [];
|
|
10477
|
+
try {
|
|
10478
|
+
const feat = await buildFeatureNodes(root);
|
|
10479
|
+
featureNames = feat.names;
|
|
10480
|
+
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' }).");
|
|
10481
|
+
} catch (err) {
|
|
10482
|
+
lines.push(`\u26A0\uFE0F Feature bootstrap failed: ${err}`);
|
|
10483
|
+
}
|
|
10484
|
+
lines.push("");
|
|
10485
|
+
lines.push("### 4\uFE0F\u20E3 Architecture Digest");
|
|
10486
|
+
try {
|
|
10487
|
+
const digest = buildArchitectureDigest(root, featureNames);
|
|
10488
|
+
lines.push(digest);
|
|
10489
|
+
} catch {
|
|
10490
|
+
}
|
|
10491
|
+
lines.push("");
|
|
10492
|
+
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");
|
|
10493
|
+
lines.push("\u2705 **Onboarding complete.** Suggested next steps:");
|
|
10494
|
+
lines.push(" \u2022 `kuma_context({ action: 'digest' })` \u2014 compact briefing");
|
|
10495
|
+
lines.push(" \u2022 `kuma_context({ action: 'research', scope: '<area>' })` \u2014 deep-dive first area");
|
|
10496
|
+
lines.push(" \u2022 `kuma_memory({ action: 'arch_flow', content: 'domain: X | hops: a \u2192 b \u2192 c' })` \u2014 map core flows");
|
|
10497
|
+
lines.push(" \u2022 `kuma_safety({ action: 'heal' })` \u2014 repair any stale graph entries");
|
|
10498
|
+
return lines.join("\n");
|
|
10499
|
+
}
|
|
10500
|
+
var MARKER_PATTERNS;
|
|
10501
|
+
var init_kumaLegacyOnboard = __esm({
|
|
10502
|
+
"src/engine/kumaLegacyOnboard.ts"() {
|
|
10503
|
+
"use strict";
|
|
10504
|
+
init_pathValidator();
|
|
10505
|
+
MARKER_PATTERNS = [
|
|
10506
|
+
{ tag: "HACK", regex: /\/\/\s*HACK:?\s*(.*)|#\s*HACK:?\s*(.*)|<!--\s*HACK:?\s*(.*)-->/i },
|
|
10507
|
+
{ tag: "FIXME", regex: /\/\/\s*FIXME:?\s*(.*)|#\s*FIXME:?\s*(.*)|<!--\s*FIXME:?\s*(.*)-->/i },
|
|
10508
|
+
{ tag: "TODO", regex: /\/\/\s*TODO:?\s*(.*)|#\s*TODO:?\s*(.*)|<!--\s*TODO:?\s*(.*)-->/i },
|
|
10509
|
+
{ tag: "WARNING", regex: /\/\/\s*WARNING:?\s*(.*)|#\s*WARNING:?\s*(.*)|<!--\s*WARNING:?\s*(.*)-->/i },
|
|
10510
|
+
{ tag: "XXX", regex: /\/\/\s*XXX:?\s*(.*)|#\s*XXX:?\s*(.*)|<!--\s*XXX:?\s*(.*)-->/i }
|
|
10511
|
+
];
|
|
10512
|
+
}
|
|
10513
|
+
});
|
|
10514
|
+
|
|
9809
10515
|
// src/utils/agentDetector.ts
|
|
9810
10516
|
var agentDetector_exports = {};
|
|
9811
10517
|
__export(agentDetector_exports, {
|
|
@@ -9814,19 +10520,19 @@ __export(agentDetector_exports, {
|
|
|
9814
10520
|
getSkillPath: () => getSkillPath,
|
|
9815
10521
|
skillExists: () => skillExists
|
|
9816
10522
|
});
|
|
9817
|
-
import
|
|
9818
|
-
import
|
|
10523
|
+
import fs29 from "fs";
|
|
10524
|
+
import path30 from "path";
|
|
9819
10525
|
function checkFile(root, filePath) {
|
|
9820
10526
|
try {
|
|
9821
|
-
return
|
|
10527
|
+
return fs29.existsSync(path30.join(root, filePath));
|
|
9822
10528
|
} catch {
|
|
9823
10529
|
return false;
|
|
9824
10530
|
}
|
|
9825
10531
|
}
|
|
9826
10532
|
function checkDir(root, dirPath) {
|
|
9827
10533
|
try {
|
|
9828
|
-
const fullPath =
|
|
9829
|
-
return
|
|
10534
|
+
const fullPath = path30.join(root, dirPath);
|
|
10535
|
+
return fs29.existsSync(fullPath) && fs29.statSync(fullPath).isDirectory();
|
|
9830
10536
|
} catch {
|
|
9831
10537
|
return false;
|
|
9832
10538
|
}
|
|
@@ -9894,7 +10600,7 @@ function getAgentLabel(type) {
|
|
|
9894
10600
|
function skillExists(type, projectRoot) {
|
|
9895
10601
|
const root = projectRoot ?? getProjectRoot();
|
|
9896
10602
|
const skillPath = getSkillPath(type);
|
|
9897
|
-
return
|
|
10603
|
+
return fs29.existsSync(path30.join(root, skillPath));
|
|
9898
10604
|
}
|
|
9899
10605
|
var AGENT_DETECTORS;
|
|
9900
10606
|
var init_agentDetector = __esm({
|
|
@@ -10338,8 +11044,8 @@ init_kumaDb();
|
|
|
10338
11044
|
init_kumaGraph();
|
|
10339
11045
|
init_kumaMemory();
|
|
10340
11046
|
init_pathValidator();
|
|
10341
|
-
import
|
|
10342
|
-
import
|
|
11047
|
+
import fs12 from "fs";
|
|
11048
|
+
import path13 from "path";
|
|
10343
11049
|
import crypto4 from "crypto";
|
|
10344
11050
|
var CONTEXT_ALIASES = {
|
|
10345
11051
|
// Research synonyms
|
|
@@ -10513,6 +11219,15 @@ async function handleInit(_params) {
|
|
|
10513
11219
|
lines.push(formatSafetyScore2(score));
|
|
10514
11220
|
} catch {
|
|
10515
11221
|
}
|
|
11222
|
+
try {
|
|
11223
|
+
const { formatPathRules: formatPathRules2 } = await Promise.resolve().then(() => (init_kumaPathRules(), kumaPathRules_exports));
|
|
11224
|
+
const rulesBlock = formatPathRules2(_params.goal);
|
|
11225
|
+
if (rulesBlock) {
|
|
11226
|
+
lines.push("");
|
|
11227
|
+
lines.push(rulesBlock);
|
|
11228
|
+
}
|
|
11229
|
+
} catch {
|
|
11230
|
+
}
|
|
10516
11231
|
lines.push("", "\u{1F4A1} Call kuma_context({ action: 'research', scope: '<area>' }) to research a specific area.");
|
|
10517
11232
|
return lines.join("\n");
|
|
10518
11233
|
}
|
|
@@ -10635,6 +11350,15 @@ async function handleResearch(params) {
|
|
|
10635
11350
|
lines.push(" \u26A0\uFE0F Memory lookup failed");
|
|
10636
11351
|
}
|
|
10637
11352
|
lines.push("");
|
|
11353
|
+
try {
|
|
11354
|
+
const { formatPathRules: formatPathRules2 } = await Promise.resolve().then(() => (init_kumaPathRules(), kumaPathRules_exports));
|
|
11355
|
+
const rulesBlock = formatPathRules2(scope);
|
|
11356
|
+
if (rulesBlock) {
|
|
11357
|
+
lines.push(rulesBlock);
|
|
11358
|
+
lines.push("");
|
|
11359
|
+
}
|
|
11360
|
+
} catch {
|
|
11361
|
+
}
|
|
10638
11362
|
const newRecord = {
|
|
10639
11363
|
scope,
|
|
10640
11364
|
confidence: record?.confidence || 0.7,
|
|
@@ -10817,12 +11541,12 @@ async function handleProgressive(params) {
|
|
|
10817
11541
|
function computeProjectHash(scope) {
|
|
10818
11542
|
try {
|
|
10819
11543
|
const root = getProjectRoot();
|
|
10820
|
-
const files =
|
|
11544
|
+
const files = fs12.readdirSync(root).slice(0, 20);
|
|
10821
11545
|
const hash = crypto4.createHash("md5");
|
|
10822
11546
|
hash.update(scope);
|
|
10823
11547
|
for (const f of files) {
|
|
10824
11548
|
try {
|
|
10825
|
-
const stat =
|
|
11549
|
+
const stat = fs12.statSync(path13.join(root, f));
|
|
10826
11550
|
hash.update(`${f}:${stat.mtimeMs}`);
|
|
10827
11551
|
} catch {
|
|
10828
11552
|
}
|
|
@@ -10891,8 +11615,8 @@ function getNoiseFilterPolicy() {
|
|
|
10891
11615
|
// src/tools/kumaMemoryTool.ts
|
|
10892
11616
|
init_pathValidator();
|
|
10893
11617
|
init_pathValidator();
|
|
10894
|
-
import
|
|
10895
|
-
import
|
|
11618
|
+
import fs16 from "fs";
|
|
11619
|
+
import path17 from "path";
|
|
10896
11620
|
var MEMORY_ALIASES = {
|
|
10897
11621
|
// Session synonyms
|
|
10898
11622
|
"session": "session",
|
|
@@ -11157,9 +11881,9 @@ async function handleResearchSave(params) {
|
|
|
11157
11881
|
} catch {
|
|
11158
11882
|
}
|
|
11159
11883
|
try {
|
|
11160
|
-
const researchDir =
|
|
11161
|
-
if (!
|
|
11162
|
-
|
|
11884
|
+
const researchDir = path17.join(getProjectRoot(), ".kuma", "research");
|
|
11885
|
+
if (!fs16.existsSync(researchDir)) fs16.mkdirSync(researchDir, { recursive: true });
|
|
11886
|
+
fs16.writeFileSync(path17.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
|
|
11163
11887
|
} catch {
|
|
11164
11888
|
}
|
|
11165
11889
|
sessionMemory.recordMemoryAction("research_save");
|
|
@@ -11760,72 +12484,72 @@ init_sessionMemory();
|
|
|
11760
12484
|
|
|
11761
12485
|
// src/engine/kumaLock.ts
|
|
11762
12486
|
init_pathValidator();
|
|
11763
|
-
import
|
|
11764
|
-
import
|
|
12487
|
+
import fs17 from "fs";
|
|
12488
|
+
import path18 from "path";
|
|
11765
12489
|
var LOCKS_DIR = ".kuma/locks";
|
|
11766
12490
|
function locksDir() {
|
|
11767
|
-
return
|
|
12491
|
+
return path18.join(getProjectRoot(), LOCKS_DIR);
|
|
11768
12492
|
}
|
|
11769
12493
|
function ensureLocksDir() {
|
|
11770
12494
|
const dir = locksDir();
|
|
11771
|
-
if (!
|
|
12495
|
+
if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
|
|
11772
12496
|
}
|
|
11773
12497
|
function lockPath(filePath) {
|
|
11774
12498
|
const safeName = filePath.replace(/[^a-zA-Z0-9_./-]/g, "_");
|
|
11775
|
-
return
|
|
12499
|
+
return path18.join(locksDir(), `${safeName}.lock.json`);
|
|
11776
12500
|
}
|
|
11777
12501
|
function acquireLock(filePath, agentId) {
|
|
11778
12502
|
ensureLocksDir();
|
|
11779
12503
|
const lp = lockPath(filePath);
|
|
11780
12504
|
const id = agentId || `agent-${process.pid}`;
|
|
11781
|
-
if (
|
|
12505
|
+
if (fs17.existsSync(lp)) {
|
|
11782
12506
|
try {
|
|
11783
|
-
const existing = JSON.parse(
|
|
12507
|
+
const existing = JSON.parse(fs17.readFileSync(lp, "utf-8"));
|
|
11784
12508
|
if (existing.agentId === id) {
|
|
11785
12509
|
return `\u{1F512} **Already locked** by you (${id}) on ${new Date(existing.acquiredAt).toISOString()}`;
|
|
11786
12510
|
}
|
|
11787
12511
|
const elapsed = Math.floor((Date.now() - existing.acquiredAt) / 1e3);
|
|
11788
12512
|
if (elapsed > 300) {
|
|
11789
|
-
|
|
12513
|
+
fs17.unlinkSync(lp);
|
|
11790
12514
|
} else {
|
|
11791
12515
|
return `\u{1F512} **Locked** by ${existing.agentId} since ${new Date(existing.acquiredAt).toISOString()} (${elapsed}s ago)`;
|
|
11792
12516
|
}
|
|
11793
12517
|
} catch {
|
|
11794
|
-
|
|
12518
|
+
fs17.unlinkSync(lp);
|
|
11795
12519
|
}
|
|
11796
12520
|
}
|
|
11797
12521
|
const entry = { filePath, agentId: id, acquiredAt: Date.now(), status: "locked" };
|
|
11798
|
-
|
|
12522
|
+
fs17.writeFileSync(lp, JSON.stringify(entry, null, 2), "utf-8");
|
|
11799
12523
|
return `\u{1F513} **Lock acquired** on \`${filePath}\` by ${id}`;
|
|
11800
12524
|
}
|
|
11801
12525
|
function releaseLock(filePath, agentId) {
|
|
11802
12526
|
ensureLocksDir();
|
|
11803
12527
|
const lp = lockPath(filePath);
|
|
11804
12528
|
const id = agentId || `agent-${process.pid}`;
|
|
11805
|
-
if (!
|
|
12529
|
+
if (!fs17.existsSync(lp)) {
|
|
11806
12530
|
return `\u26A0\uFE0F No lock found for \`${filePath}\``;
|
|
11807
12531
|
}
|
|
11808
12532
|
try {
|
|
11809
|
-
const existing = JSON.parse(
|
|
12533
|
+
const existing = JSON.parse(fs17.readFileSync(lp, "utf-8"));
|
|
11810
12534
|
if (existing.agentId !== id) {
|
|
11811
12535
|
return `\u26A0\uFE0F Cannot release lock held by ${existing.agentId}. Use force:true to override.`;
|
|
11812
12536
|
}
|
|
11813
|
-
|
|
12537
|
+
fs17.unlinkSync(lp);
|
|
11814
12538
|
return `\u{1F513} **Lock released** on \`${filePath}\``;
|
|
11815
12539
|
} catch {
|
|
11816
|
-
|
|
12540
|
+
fs17.unlinkSync(lp);
|
|
11817
12541
|
return `\u{1F513} **Lock released** (force cleanup) on \`${filePath}\``;
|
|
11818
12542
|
}
|
|
11819
12543
|
}
|
|
11820
12544
|
function listLocks() {
|
|
11821
12545
|
ensureLocksDir();
|
|
11822
12546
|
const dir = locksDir();
|
|
11823
|
-
const files =
|
|
12547
|
+
const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".lock.json"));
|
|
11824
12548
|
if (files.length === 0) return "\u{1F513} No active locks.";
|
|
11825
12549
|
const lines = ["\u{1F512} **Active Locks:**", ""];
|
|
11826
12550
|
for (const f of files) {
|
|
11827
12551
|
try {
|
|
11828
|
-
const entry = JSON.parse(
|
|
12552
|
+
const entry = JSON.parse(fs17.readFileSync(path18.join(dir, f), "utf-8"));
|
|
11829
12553
|
const elapsed = Math.floor((Date.now() - entry.acquiredAt) / 1e3);
|
|
11830
12554
|
lines.push(` \u2022 \`${entry.filePath}\` \u2014 locked by ${entry.agentId} (${elapsed}s ago)`);
|
|
11831
12555
|
} catch {
|
|
@@ -11836,9 +12560,9 @@ function listLocks() {
|
|
|
11836
12560
|
}
|
|
11837
12561
|
function isLocked(filePath) {
|
|
11838
12562
|
const lp = lockPath(filePath);
|
|
11839
|
-
if (!
|
|
12563
|
+
if (!fs17.existsSync(lp)) return { locked: false };
|
|
11840
12564
|
try {
|
|
11841
|
-
const entry = JSON.parse(
|
|
12565
|
+
const entry = JSON.parse(fs17.readFileSync(lp, "utf-8"));
|
|
11842
12566
|
return { locked: true, by: entry.agentId, since: entry.acquiredAt };
|
|
11843
12567
|
} catch {
|
|
11844
12568
|
return { locked: false };
|
|
@@ -11848,16 +12572,16 @@ function cleanStaleLocks() {
|
|
|
11848
12572
|
ensureLocksDir();
|
|
11849
12573
|
const dir = locksDir();
|
|
11850
12574
|
let count = 0;
|
|
11851
|
-
for (const f of
|
|
12575
|
+
for (const f of fs17.readdirSync(dir).filter((f2) => f2.endsWith(".lock.json"))) {
|
|
11852
12576
|
try {
|
|
11853
|
-
const entry = JSON.parse(
|
|
12577
|
+
const entry = JSON.parse(fs17.readFileSync(path18.join(dir, f), "utf-8"));
|
|
11854
12578
|
if (Date.now() - entry.acquiredAt > 3e5) {
|
|
11855
|
-
|
|
12579
|
+
fs17.unlinkSync(path18.join(dir, f));
|
|
11856
12580
|
count++;
|
|
11857
12581
|
}
|
|
11858
12582
|
} catch {
|
|
11859
12583
|
try {
|
|
11860
|
-
|
|
12584
|
+
fs17.unlinkSync(path18.join(dir, f));
|
|
11861
12585
|
count++;
|
|
11862
12586
|
} catch {
|
|
11863
12587
|
}
|
|
@@ -11873,8 +12597,8 @@ init_safetyAudit();
|
|
|
11873
12597
|
|
|
11874
12598
|
// src/tools/kumaPolicy.ts
|
|
11875
12599
|
init_pathValidator();
|
|
11876
|
-
import
|
|
11877
|
-
import
|
|
12600
|
+
import fs18 from "fs";
|
|
12601
|
+
import path19 from "path";
|
|
11878
12602
|
var DEFAULT_POLICY = {
|
|
11879
12603
|
never_touch: [
|
|
11880
12604
|
".env",
|
|
@@ -11900,15 +12624,15 @@ var DEFAULT_POLICY = {
|
|
|
11900
12624
|
};
|
|
11901
12625
|
function loadPolicy() {
|
|
11902
12626
|
const root = getProjectRoot();
|
|
11903
|
-
const ymlPath =
|
|
11904
|
-
const yamlPath =
|
|
12627
|
+
const ymlPath = path19.join(root, ".kuma", "policy.yml");
|
|
12628
|
+
const yamlPath = path19.join(root, ".kuma", "policy.yaml");
|
|
11905
12629
|
let policyContent = null;
|
|
11906
12630
|
let policyPath = null;
|
|
11907
|
-
if (
|
|
11908
|
-
policyContent =
|
|
12631
|
+
if (fs18.existsSync(ymlPath)) {
|
|
12632
|
+
policyContent = fs18.readFileSync(ymlPath, "utf-8");
|
|
11909
12633
|
policyPath = ymlPath;
|
|
11910
|
-
} else if (
|
|
11911
|
-
policyContent =
|
|
12634
|
+
} else if (fs18.existsSync(yamlPath)) {
|
|
12635
|
+
policyContent = fs18.readFileSync(yamlPath, "utf-8");
|
|
11912
12636
|
policyPath = yamlPath;
|
|
11913
12637
|
}
|
|
11914
12638
|
if (!policyContent) {
|
|
@@ -12018,9 +12742,9 @@ function checkFilePathPolicy(filePath, policy) {
|
|
|
12018
12742
|
if (policy.max_file_size && policy.max_file_size > 0) {
|
|
12019
12743
|
try {
|
|
12020
12744
|
const root = getProjectRoot();
|
|
12021
|
-
const fullPath =
|
|
12022
|
-
if (
|
|
12023
|
-
const sizeKB =
|
|
12745
|
+
const fullPath = path19.join(root, filePath);
|
|
12746
|
+
if (fs18.existsSync(fullPath)) {
|
|
12747
|
+
const sizeKB = fs18.statSync(fullPath).size / 1024;
|
|
12024
12748
|
if (sizeKB > policy.max_file_size) {
|
|
12025
12749
|
violations.push({
|
|
12026
12750
|
rule: "max_file_size",
|
|
@@ -12187,12 +12911,12 @@ async function safetyCheck(action, filePath, command) {
|
|
|
12187
12911
|
}
|
|
12188
12912
|
if (filePath) {
|
|
12189
12913
|
try {
|
|
12190
|
-
const
|
|
12191
|
-
const
|
|
12914
|
+
const fs30 = await import("fs");
|
|
12915
|
+
const path31 = await import("path");
|
|
12192
12916
|
const root = process.cwd();
|
|
12193
|
-
const fullPath =
|
|
12194
|
-
if (
|
|
12195
|
-
const stat =
|
|
12917
|
+
const fullPath = path31.resolve(root, filePath);
|
|
12918
|
+
if (fs30.existsSync(fullPath)) {
|
|
12919
|
+
const stat = fs30.statSync(fullPath);
|
|
12196
12920
|
const sizeKB = Math.round(stat.size / 1024);
|
|
12197
12921
|
if (sizeKB > 500) {
|
|
12198
12922
|
checks.push({
|
|
@@ -12202,12 +12926,12 @@ async function safetyCheck(action, filePath, command) {
|
|
|
12202
12926
|
});
|
|
12203
12927
|
}
|
|
12204
12928
|
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
12205
|
-
const baseName =
|
|
12929
|
+
const baseName = path31.basename(filePath, path31.extname(filePath));
|
|
12206
12930
|
const testPatterns = [
|
|
12207
12931
|
filePath.replace(/\.(ts|js)$/, ".test.$1"),
|
|
12208
12932
|
filePath.replace(/\.(ts|js)$/, ".spec.$1"),
|
|
12209
12933
|
filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
|
|
12210
|
-
`**/__tests__/**/${baseName}.test.${
|
|
12934
|
+
`**/__tests__/**/${baseName}.test.${path31.extname(filePath).slice(1)}`
|
|
12211
12935
|
];
|
|
12212
12936
|
let hasTests = false;
|
|
12213
12937
|
const fg = await import("fast-glob");
|
|
@@ -12223,7 +12947,7 @@ async function safetyCheck(action, filePath, command) {
|
|
|
12223
12947
|
name: "Tests Check",
|
|
12224
12948
|
passed: true,
|
|
12225
12949
|
// Not a blocker, just informative
|
|
12226
|
-
detail: `\u{1F9EA} No test file found for "${
|
|
12950
|
+
detail: `\u{1F9EA} No test file found for "${path31.basename(filePath)}" \u2014 consider adding tests`
|
|
12227
12951
|
});
|
|
12228
12952
|
} else {
|
|
12229
12953
|
checks.push({
|
|
@@ -12312,8 +13036,8 @@ init_sessionMemory();
|
|
|
12312
13036
|
// src/guards/antiPatternDetector.ts
|
|
12313
13037
|
init_pathValidator();
|
|
12314
13038
|
init_sessionMemory();
|
|
12315
|
-
import
|
|
12316
|
-
import
|
|
13039
|
+
import fs19 from "fs";
|
|
13040
|
+
import path20 from "path";
|
|
12317
13041
|
import { execSync as execSync6 } from "child_process";
|
|
12318
13042
|
var SCRIPT_PATCH_PATTERNS = [
|
|
12319
13043
|
"writeFileSync",
|
|
@@ -12340,20 +13064,20 @@ function findPatchScripts(projectRoot) {
|
|
|
12340
13064
|
const warnings = [];
|
|
12341
13065
|
const recentFiles = scanRecentFiles(projectRoot);
|
|
12342
13066
|
for (const file of recentFiles) {
|
|
12343
|
-
const ext =
|
|
13067
|
+
const ext = path20.extname(file).toLowerCase();
|
|
12344
13068
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
12345
|
-
const relativePath =
|
|
13069
|
+
const relativePath = path20.relative(projectRoot, file);
|
|
12346
13070
|
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
|
|
12347
13071
|
continue;
|
|
12348
13072
|
}
|
|
12349
13073
|
try {
|
|
12350
|
-
const content =
|
|
13074
|
+
const content = fs19.readFileSync(file, "utf-8").toLowerCase();
|
|
12351
13075
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
12352
13076
|
if (matchedPattern) {
|
|
12353
13077
|
warnings.push({
|
|
12354
13078
|
severity: "high",
|
|
12355
13079
|
pattern: "script-patching",
|
|
12356
|
-
message: `Created script file that modifies other files: ${
|
|
13080
|
+
message: `Created script file that modifies other files: ${path20.basename(file)}`,
|
|
12357
13081
|
suggestion: "Use **precise_diff_editor** instead \u2014 it has fuzzy matching, auto-backup, and rollback support",
|
|
12358
13082
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
12359
13083
|
filePath: relativePath
|
|
@@ -12387,15 +13111,15 @@ function detectBashGrepUsage() {
|
|
|
12387
13111
|
function scanRecentFiles(projectRoot) {
|
|
12388
13112
|
const recent = [];
|
|
12389
13113
|
try {
|
|
12390
|
-
const entries =
|
|
13114
|
+
const entries = fs19.readdirSync(projectRoot, { withFileTypes: true });
|
|
12391
13115
|
const now = Date.now();
|
|
12392
13116
|
const recentThreshold = 30 * 60 * 1e3;
|
|
12393
13117
|
for (const entry of entries) {
|
|
12394
13118
|
if (!entry.isFile()) continue;
|
|
12395
13119
|
try {
|
|
12396
|
-
const stat =
|
|
13120
|
+
const stat = fs19.statSync(path20.join(projectRoot, entry.name));
|
|
12397
13121
|
if (now - stat.mtimeMs < recentThreshold || now - stat.ctimeMs < recentThreshold) {
|
|
12398
|
-
recent.push(
|
|
13122
|
+
recent.push(path20.join(projectRoot, entry.name));
|
|
12399
13123
|
}
|
|
12400
13124
|
} catch {
|
|
12401
13125
|
continue;
|
|
@@ -12419,14 +13143,14 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
12419
13143
|
const prefix = line.substring(0, 2);
|
|
12420
13144
|
if (prefix !== "??" && prefix !== "A ") continue;
|
|
12421
13145
|
const file = line.substring(3).trim();
|
|
12422
|
-
const ext =
|
|
13146
|
+
const ext = path20.extname(file).toLowerCase();
|
|
12423
13147
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
12424
13148
|
const isRootLevel = !file.includes("/");
|
|
12425
13149
|
const isScriptsDir = file.startsWith("scripts/") || file.startsWith("patches/");
|
|
12426
13150
|
if (!isRootLevel && !isScriptsDir) continue;
|
|
12427
|
-
const fullPath =
|
|
12428
|
-
if (
|
|
12429
|
-
const content =
|
|
13151
|
+
const fullPath = path20.join(projectRoot, file);
|
|
13152
|
+
if (fs19.existsSync(fullPath)) {
|
|
13153
|
+
const content = fs19.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
12430
13154
|
const matchedPattern = SCRIPT_PATCH_PATTERNS.find((p) => content.includes(p.toLowerCase()));
|
|
12431
13155
|
if (matchedPattern) {
|
|
12432
13156
|
warnings.push({
|
|
@@ -12449,7 +13173,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
12449
13173
|
if (deletedStdout) {
|
|
12450
13174
|
const deletedFiles = deletedStdout.split("\n").filter(Boolean);
|
|
12451
13175
|
for (const file of deletedFiles) {
|
|
12452
|
-
const ext =
|
|
13176
|
+
const ext = path20.extname(file).toLowerCase();
|
|
12453
13177
|
if (SCRIPT_EXTENSIONS.includes(ext)) {
|
|
12454
13178
|
warnings.push({
|
|
12455
13179
|
severity: "high",
|
|
@@ -12511,21 +13235,21 @@ function detectAllAntiPatterns() {
|
|
|
12511
13235
|
// src/engine/contextSnapshot.ts
|
|
12512
13236
|
init_sessionMemory();
|
|
12513
13237
|
init_pathValidator();
|
|
12514
|
-
import
|
|
12515
|
-
import
|
|
13238
|
+
import fs20 from "fs";
|
|
13239
|
+
import path21 from "path";
|
|
12516
13240
|
import { execSync as execSync7 } from "child_process";
|
|
12517
13241
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
12518
13242
|
function snapshotDir() {
|
|
12519
|
-
return
|
|
13243
|
+
return path21.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
12520
13244
|
}
|
|
12521
13245
|
function ensureSnapshotDir() {
|
|
12522
13246
|
const dir = snapshotDir();
|
|
12523
|
-
if (!
|
|
12524
|
-
|
|
13247
|
+
if (!fs20.existsSync(dir)) {
|
|
13248
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
12525
13249
|
}
|
|
12526
13250
|
}
|
|
12527
13251
|
function snapshotFilePath(timestamp) {
|
|
12528
|
-
return
|
|
13252
|
+
return path21.join(snapshotDir(), `${timestamp}.json`);
|
|
12529
13253
|
}
|
|
12530
13254
|
function saveSnapshot(goal) {
|
|
12531
13255
|
try {
|
|
@@ -12546,7 +13270,7 @@ function saveSnapshot(goal) {
|
|
|
12546
13270
|
hasConventions: !!summary.hasConventions
|
|
12547
13271
|
};
|
|
12548
13272
|
try {
|
|
12549
|
-
|
|
13273
|
+
fs20.writeFileSync(snapshotFilePath(Date.now()), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
12550
13274
|
} catch {
|
|
12551
13275
|
}
|
|
12552
13276
|
sessionMemory.recordToolCall("kuma_context", { action: "save", goal: snapshot.goal });
|
|
@@ -12859,12 +13583,12 @@ async function handleSecurity(params) {
|
|
|
12859
13583
|
sessionMemory.recordToolCall("kuma_safety_security", {});
|
|
12860
13584
|
if (params.filePath) {
|
|
12861
13585
|
try {
|
|
12862
|
-
const
|
|
12863
|
-
const
|
|
13586
|
+
const fs30 = await import("fs");
|
|
13587
|
+
const path31 = await import("path");
|
|
12864
13588
|
const root = process.cwd();
|
|
12865
|
-
const fullPath =
|
|
12866
|
-
if (
|
|
12867
|
-
const content =
|
|
13589
|
+
const fullPath = path31.resolve(root, params.filePath);
|
|
13590
|
+
if (fs30.existsSync(fullPath) && fs30.statSync(fullPath).isFile()) {
|
|
13591
|
+
const content = fs30.readFileSync(fullPath, "utf-8");
|
|
12868
13592
|
const lines = content.split("\n");
|
|
12869
13593
|
const findings = [];
|
|
12870
13594
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -12918,23 +13642,23 @@ async function handleGitignore(_params) {
|
|
|
12918
13642
|
}
|
|
12919
13643
|
async function handleClean(_params) {
|
|
12920
13644
|
sessionMemory.recordToolCall("kuma_safety_clean", {});
|
|
12921
|
-
const
|
|
12922
|
-
const
|
|
13645
|
+
const fs30 = await import("fs");
|
|
13646
|
+
const path31 = await import("path");
|
|
12923
13647
|
const root = process.cwd();
|
|
12924
|
-
const scratchDir =
|
|
13648
|
+
const scratchDir = path31.resolve(root, ".kuma", "scratch");
|
|
12925
13649
|
let removed = 0;
|
|
12926
|
-
if (
|
|
13650
|
+
if (fs30.existsSync(scratchDir)) {
|
|
12927
13651
|
try {
|
|
12928
|
-
const entries =
|
|
13652
|
+
const entries = fs30.readdirSync(scratchDir);
|
|
12929
13653
|
for (const entry of entries) {
|
|
12930
|
-
const fullPath =
|
|
13654
|
+
const fullPath = path31.join(scratchDir, entry);
|
|
12931
13655
|
try {
|
|
12932
|
-
const stat =
|
|
13656
|
+
const stat = fs30.statSync(fullPath);
|
|
12933
13657
|
if (stat.isFile()) {
|
|
12934
|
-
|
|
13658
|
+
fs30.unlinkSync(fullPath);
|
|
12935
13659
|
removed++;
|
|
12936
13660
|
} else if (stat.isDirectory()) {
|
|
12937
|
-
|
|
13661
|
+
fs30.rmSync(fullPath, { recursive: true, force: true });
|
|
12938
13662
|
removed++;
|
|
12939
13663
|
}
|
|
12940
13664
|
} catch {
|
|
@@ -13309,6 +14033,7 @@ Usage:
|
|
|
13309
14033
|
npx @plumpslabs/kuma init --skip-existing Skip generation if file exists
|
|
13310
14034
|
npx @plumpslabs/kuma init --claude --cursor Generate specific files
|
|
13311
14035
|
npx @plumpslabs/kuma studio Start Kuma Studio web dashboard (knowledge graph visualizer)
|
|
14036
|
+
npx @plumpslabs/kuma init --legacy Bulk-onboard an existing (legacy) codebase
|
|
13312
14037
|
npx @plumpslabs/kuma init --help Show this help
|
|
13313
14038
|
|
|
13314
14039
|
Available config files:
|
|
@@ -13353,14 +14078,14 @@ async function main() {
|
|
|
13353
14078
|
} catch {
|
|
13354
14079
|
}
|
|
13355
14080
|
try {
|
|
13356
|
-
const
|
|
13357
|
-
const
|
|
13358
|
-
const lockDir =
|
|
13359
|
-
if (
|
|
13360
|
-
const pidFile =
|
|
13361
|
-
if (
|
|
14081
|
+
const path31 = await import("path");
|
|
14082
|
+
const fs30 = await import("fs");
|
|
14083
|
+
const lockDir = path31.resolve(process.cwd(), ".kuma/verifier.lock");
|
|
14084
|
+
if (fs30.existsSync(lockDir)) {
|
|
14085
|
+
const pidFile = path31.join(lockDir, "pid");
|
|
14086
|
+
if (fs30.existsSync(pidFile)) {
|
|
13362
14087
|
try {
|
|
13363
|
-
const pid = parseInt(
|
|
14088
|
+
const pid = parseInt(fs30.readFileSync(pidFile, "utf-8"), 10);
|
|
13364
14089
|
if (pid && pid !== process.pid) {
|
|
13365
14090
|
try {
|
|
13366
14091
|
process.kill(-pid, "SIGKILL");
|
|
@@ -13376,7 +14101,7 @@ async function main() {
|
|
|
13376
14101
|
} catch {
|
|
13377
14102
|
}
|
|
13378
14103
|
}
|
|
13379
|
-
|
|
14104
|
+
fs30.rmSync(lockDir, { recursive: true, force: true });
|
|
13380
14105
|
}
|
|
13381
14106
|
if (killedCount === 0) {
|
|
13382
14107
|
console.error("\u2705 No running verifications found to kill.");
|
|
@@ -13470,6 +14195,18 @@ async function main() {
|
|
|
13470
14195
|
printHelp();
|
|
13471
14196
|
process.exit(0);
|
|
13472
14197
|
}
|
|
14198
|
+
if (flags.includes("--legacy")) {
|
|
14199
|
+
try {
|
|
14200
|
+
const { runLegacyOnboarding: runLegacyOnboarding2 } = await Promise.resolve().then(() => (init_kumaLegacyOnboard(), kumaLegacyOnboard_exports));
|
|
14201
|
+
console.error("\u{1F43B} Kuma Legacy Onboarding \u2014 making your existing codebase agent-ready...");
|
|
14202
|
+
const report = await runLegacyOnboarding2();
|
|
14203
|
+
console.log(report);
|
|
14204
|
+
} catch (err) {
|
|
14205
|
+
console.error(`\u274C Legacy onboarding failed: ${err}`);
|
|
14206
|
+
process.exit(1);
|
|
14207
|
+
}
|
|
14208
|
+
process.exit(0);
|
|
14209
|
+
}
|
|
13473
14210
|
const requestedFlags = flags.filter((f) => f.startsWith("--"));
|
|
13474
14211
|
let selectedTypes;
|
|
13475
14212
|
if (requestedFlags.length === 0) {
|
|
@@ -13524,23 +14261,23 @@ async function main() {
|
|
|
13524
14261
|
});
|
|
13525
14262
|
const output = formatInitResults(results);
|
|
13526
14263
|
console.log(output);
|
|
13527
|
-
const
|
|
13528
|
-
const
|
|
13529
|
-
const matchaSkills =
|
|
13530
|
-
const matchaAgents =
|
|
14264
|
+
const fs30 = await import("fs");
|
|
14265
|
+
const path31 = await import("path");
|
|
14266
|
+
const matchaSkills = path31.resolve(process.cwd(), "skills/matcha/SKILL.md");
|
|
14267
|
+
const matchaAgents = path31.resolve(
|
|
13531
14268
|
process.cwd(),
|
|
13532
14269
|
".agents/skills/matcha/SKILL.md"
|
|
13533
14270
|
);
|
|
13534
|
-
const matchaRootSkills =
|
|
14271
|
+
const matchaRootSkills = path31.resolve(
|
|
13535
14272
|
process.cwd(),
|
|
13536
14273
|
"skills/matcha/SKILL.md"
|
|
13537
14274
|
);
|
|
13538
|
-
const matchaAgentsMd =
|
|
13539
|
-
const matchaWindsurfRules =
|
|
14275
|
+
const matchaAgentsMd = path31.resolve(process.cwd(), "AGENTS.md");
|
|
14276
|
+
const matchaWindsurfRules = path31.resolve(
|
|
13540
14277
|
process.cwd(),
|
|
13541
14278
|
".windsurf"
|
|
13542
14279
|
);
|
|
13543
|
-
if (
|
|
14280
|
+
if (fs30.existsSync(matchaSkills) || fs30.existsSync(matchaAgents) || fs30.existsSync(matchaRootSkills) || fs30.existsSync(matchaAgentsMd) || fs30.existsSync(matchaWindsurfRules)) {
|
|
13544
14281
|
console.error(
|
|
13545
14282
|
"\n\u{1F375} Hey, I see matcha is installed \u2014 they pair well together!"
|
|
13546
14283
|
);
|
|
@@ -13554,13 +14291,13 @@ async function main() {
|
|
|
13554
14291
|
(async () => {
|
|
13555
14292
|
try {
|
|
13556
14293
|
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
|
-
|
|
14294
|
+
const fs30 = await import("fs");
|
|
14295
|
+
const path31 = await import("path");
|
|
14296
|
+
const initMdPath = path31.resolve(process.cwd(), ".kuma/init.md");
|
|
14297
|
+
if (!fs30.existsSync(initMdPath)) {
|
|
14298
|
+
const kumaDir = path31.dirname(initMdPath);
|
|
14299
|
+
if (!fs30.existsSync(kumaDir)) fs30.mkdirSync(kumaDir, { recursive: true });
|
|
14300
|
+
fs30.writeFileSync(initMdPath, generateInitMdContent2(), "utf-8");
|
|
13564
14301
|
console.error(`[${SERVER_NAME}] Auto-generated .kuma/init.md`);
|
|
13565
14302
|
}
|
|
13566
14303
|
} catch (err) {
|
|
@@ -13571,8 +14308,8 @@ async function main() {
|
|
|
13571
14308
|
try {
|
|
13572
14309
|
const { detectAgent: detectAgent2, getSkillPath: getSkillPath2, getAgentLabel: getAgentLabel2 } = await Promise.resolve().then(() => (init_agentDetector(), agentDetector_exports));
|
|
13573
14310
|
const { generateSkill: generateSkill2, getSecondaryFiles: getSecondaryFiles2 } = await Promise.resolve().then(() => (init_skillGenerator(), skillGenerator_exports));
|
|
13574
|
-
const
|
|
13575
|
-
const
|
|
14311
|
+
const fs30 = await import("fs");
|
|
14312
|
+
const path31 = await import("path");
|
|
13576
14313
|
const detection = detectAgent2();
|
|
13577
14314
|
if (!detection.primary) {
|
|
13578
14315
|
console.error(`[${SERVER_NAME}] No AI agent detected \u2014 skipping auto-skill creation`);
|
|
@@ -13580,22 +14317,22 @@ async function main() {
|
|
|
13580
14317
|
}
|
|
13581
14318
|
const agentType = detection.primary;
|
|
13582
14319
|
const skillPath = getSkillPath2(agentType);
|
|
13583
|
-
const fullPath =
|
|
13584
|
-
if (
|
|
14320
|
+
const fullPath = path31.resolve(process.cwd(), skillPath);
|
|
14321
|
+
if (fs30.existsSync(fullPath)) {
|
|
13585
14322
|
console.error(`[${SERVER_NAME}] Skill exists for ${getAgentLabel2(agentType)} \u2014 skipping`);
|
|
13586
14323
|
return;
|
|
13587
14324
|
}
|
|
13588
|
-
const dir =
|
|
13589
|
-
if (!
|
|
13590
|
-
|
|
14325
|
+
const dir = path31.dirname(fullPath);
|
|
14326
|
+
if (!fs30.existsSync(dir)) fs30.mkdirSync(dir, { recursive: true });
|
|
14327
|
+
fs30.writeFileSync(fullPath, generateSkill2(agentType), "utf-8");
|
|
13591
14328
|
console.error(`[${SERVER_NAME}] Auto-created ${skillPath} for ${getAgentLabel2(agentType)}`);
|
|
13592
14329
|
const secondaryFiles = getSecondaryFiles2(agentType);
|
|
13593
14330
|
for (const sf of secondaryFiles) {
|
|
13594
|
-
const sfPath =
|
|
13595
|
-
const sfDir =
|
|
13596
|
-
if (!
|
|
13597
|
-
if (!
|
|
13598
|
-
|
|
14331
|
+
const sfPath = path31.resolve(process.cwd(), sf.path);
|
|
14332
|
+
const sfDir = path31.dirname(sfPath);
|
|
14333
|
+
if (!fs30.existsSync(sfPath)) {
|
|
14334
|
+
if (!fs30.existsSync(sfDir)) fs30.mkdirSync(sfDir, { recursive: true });
|
|
14335
|
+
fs30.writeFileSync(sfPath, sf.content, "utf-8");
|
|
13599
14336
|
console.error(`[${SERVER_NAME}] Auto-created ${sf.path}`);
|
|
13600
14337
|
}
|
|
13601
14338
|
}
|
|
@@ -13620,11 +14357,11 @@ async function main() {
|
|
|
13620
14357
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
|
|
13621
14358
|
}
|
|
13622
14359
|
try {
|
|
13623
|
-
const
|
|
13624
|
-
const
|
|
13625
|
-
const scratchDir =
|
|
13626
|
-
if (!
|
|
13627
|
-
|
|
14360
|
+
const fs30 = await import("fs");
|
|
14361
|
+
const path31 = await import("path");
|
|
14362
|
+
const scratchDir = path31.resolve(process.cwd(), ".kuma", "scratch");
|
|
14363
|
+
if (!fs30.existsSync(scratchDir)) {
|
|
14364
|
+
fs30.mkdirSync(scratchDir, { recursive: true });
|
|
13628
14365
|
console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
|
|
13629
14366
|
}
|
|
13630
14367
|
} catch (err) {
|