@plumpslabs/kuma 2.3.23 → 2.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.js +300 -91
- package/package.json +1 -1
- package/packages/ide/studio/public/index.html +52 -12
package/README.md
CHANGED
|
@@ -142,6 +142,8 @@ Kuma consolidates everything into **3 coarse-grained tools**. Each action trigge
|
|
|
142
142
|
| `arch_flow` | Layer 2 — Architecture flow mapping (Issue #17). |
|
|
143
143
|
| `gotcha` | Layer 3 — Known gotchas & anti-regression shield (Issue #17/#21). |
|
|
144
144
|
| `layers` | Show all 3 memory layers summary. |
|
|
145
|
+
| `delete_node` | Delete specific node or record from disk & RAM immediately. |
|
|
146
|
+
| `graph_health` | Monitor knowledge graph health, orphan nodes, and duplicate groups. |
|
|
145
147
|
|
|
146
148
|
### 🛡️ `kuma_safety` — Safety & Policy
|
|
147
149
|
|
package/dist/index.js
CHANGED
|
@@ -2558,7 +2558,7 @@ async function recordDomainFlow(params) {
|
|
|
2558
2558
|
await addEdge({
|
|
2559
2559
|
sourceId: fromId,
|
|
2560
2560
|
targetId: toId,
|
|
2561
|
-
type: "
|
|
2561
|
+
type: "depends_on",
|
|
2562
2562
|
weight: 0.8,
|
|
2563
2563
|
metadata: { reason: "arch_flow", domain: params.domain }
|
|
2564
2564
|
});
|
|
@@ -8196,19 +8196,25 @@ function generateInitMdContent() {
|
|
|
8196
8196
|
"- `status` \u2014 severity: `low` | `medium` | `high` | `critical` (default: `medium`)",
|
|
8197
8197
|
"- `description` \u2014 optional workaround/fix description",
|
|
8198
8198
|
"",
|
|
8199
|
-
"### STEP 7: Record Arch Flow (RECORD
|
|
8199
|
+
"### STEP 7: Record Arch Flow (RECORD COMPLETE FLOW \u2014 Core Logic Only)",
|
|
8200
8200
|
"```",
|
|
8201
|
-
'kuma_memory({ action: "arch_flow", content: "domain: <name> | hops: <
|
|
8201
|
+
'kuma_memory({ action: "arch_flow", content: "domain: <name> | hops: <core_file1> \u2192 <core_file2> \u2192 <core_file3>" })',
|
|
8202
8202
|
"```",
|
|
8203
|
-
"**When:**
|
|
8204
|
-
"**Why:**
|
|
8203
|
+
"**When:** AFTER tracing a COMPLETE architecture flow (not per hop).",
|
|
8204
|
+
"**Why:** Record flow architecture for future sessions without cluttering the graph.",
|
|
8205
|
+
"",
|
|
8206
|
+
"**Rules:**",
|
|
8207
|
+
"1. MAX 5 files per flow \u2014 record core business logic files only.",
|
|
8208
|
+
"2. SKIP UI components (*.tsx, *.jsx), Controllers (*Controller.js), Schemas (*Schema.js), and simple CRUD services.",
|
|
8209
|
+
"3. INCLUDE files with complex business logic, decision points, state mutations, or error handling.",
|
|
8210
|
+
"4. Format hops separated by ` \u2192 ` (e.g. `domain: LeadDistributionFlow | hops: LeadRotationService.js \u2192 ProspectAssignmentService.js \u2192 ActivityLogService.js`).",
|
|
8205
8211
|
"",
|
|
8206
8212
|
"**\u26A0\uFE0F CRITICAL FORMAT \u2014 Content MUST use this exact syntax:**",
|
|
8207
8213
|
"```",
|
|
8208
|
-
"domain: <DomainName> | hops: <
|
|
8214
|
+
"domain: <DomainName> | hops: <core_file1.js> \u2192 <core_file2.js> \u2192 <core_file3.js> | gotchas: <optional> | decisions: <optional>",
|
|
8209
8215
|
"```",
|
|
8210
8216
|
"- `domain:` \u2014 short domain name (e.g. `InviteUserFlow`, `LeadManagementFlow`)",
|
|
8211
|
-
"- `hops:` \u2014 file names separated by ` \u2192 `
|
|
8217
|
+
"- `hops:` \u2014 max 5 core file names separated by ` \u2192 `",
|
|
8212
8218
|
"- `gotchas:` \u2014 optional comma-separated gotchas",
|
|
8213
8219
|
"- `decisions:` \u2014 optional comma-separated decisions",
|
|
8214
8220
|
"",
|
|
@@ -8866,33 +8872,24 @@ function handleQuickrefGeneration(root, results) {
|
|
|
8866
8872
|
const content = [
|
|
8867
8873
|
"# Kuma Quick Reference",
|
|
8868
8874
|
"",
|
|
8869
|
-
"
|
|
8870
|
-
"",
|
|
8871
|
-
"## Every Session (Mandatory \u2014 including research-only)",
|
|
8875
|
+
"## Lean Mode (default \u2014 for speed)",
|
|
8876
|
+
"1. `init` \u2192 edit \u2192 record (if needed)",
|
|
8872
8877
|
"",
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
' 3. `kuma_context({ action: "research", scope: "<area>" })` \u2014 Research',
|
|
8876
|
-
" 4. *(edit/read using native tools)*",
|
|
8877
|
-
' 5. `kuma_memory({ action: "research_save", ... })` \u2014 After exploring area (creates search cache)',
|
|
8878
|
-
' 6. `kuma_memory({ action: "gotcha" })` \u2014 \u{1F525} IMMEDIATELY when bug found (exponential)',
|
|
8879
|
-
' 7. `kuma_memory({ action: "arch_flow" })` \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential)',
|
|
8880
|
-
' 8. `kuma_memory({ action: "decision" })` \u2014 When choosing (preserves rationale)',
|
|
8881
|
-
' 9. `kuma_safety({ action: "verify", ... })` \u2014 Run tests / confirm nothing broken',
|
|
8882
|
-
' 10. `kuma_context({ action: "changes" })` \u2014 Review session activity',
|
|
8878
|
+
"## Standard Mode (for safety)",
|
|
8879
|
+
"1. `init` \u2192 `guard` \u2192 `research` \u2192 edit \u2192 `record` \u2192 `verify`",
|
|
8883
8880
|
"",
|
|
8884
|
-
"
|
|
8885
|
-
"\
|
|
8886
|
-
"\u{1F9E0} Graph is persistent \u2014 nodes/edges accumulate across sessions.",
|
|
8881
|
+
"## Full Mode (for complex changes)",
|
|
8882
|
+
"1. `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
|
|
8887
8883
|
"",
|
|
8888
|
-
"
|
|
8884
|
+
"## Record Rules",
|
|
8885
|
+
"- **MUST:** decision, gotcha",
|
|
8886
|
+
"- **SKIP:** function, class, import, route",
|
|
8889
8887
|
"",
|
|
8890
8888
|
"## Platform Tool Names",
|
|
8889
|
+
"- OpenCode / Antigravity: `kuma_kuma_*`",
|
|
8890
|
+
"- Others: `kuma_*`",
|
|
8891
8891
|
"",
|
|
8892
|
-
"
|
|
8893
|
-
"- **Other platforms:** Use `kuma_*` directly (e.g. `kuma_context`)",
|
|
8894
|
-
"",
|
|
8895
|
-
"\u{1F4D6} Full rules: `.kuma/init.md`",
|
|
8892
|
+
"\u{1F4D6} Full: `.kuma/init.md` | \u{1F4CA} Modes: `.kuma/MODE.md` | \u{1F7E2} Skip: `.kuma/SKIP_RULES.md`",
|
|
8896
8893
|
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
8897
8894
|
].join("\n");
|
|
8898
8895
|
try {
|
|
@@ -8904,11 +8901,9 @@ function handleQuickrefGeneration(root, results) {
|
|
|
8904
8901
|
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
|
|
8905
8902
|
return;
|
|
8906
8903
|
}
|
|
8907
|
-
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "skipped" });
|
|
8908
|
-
} else {
|
|
8909
|
-
fs24.writeFileSync(quickrefPath, content, "utf-8");
|
|
8910
|
-
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
|
|
8911
8904
|
}
|
|
8905
|
+
fs24.writeFileSync(quickrefPath, content, "utf-8");
|
|
8906
|
+
results.push({ type: "claude", filePath: ".kuma/quickref.md", action: "created" });
|
|
8912
8907
|
} catch (err) {
|
|
8913
8908
|
results.push({
|
|
8914
8909
|
type: "claude",
|
|
@@ -8918,6 +8913,150 @@ function handleQuickrefGeneration(root, results) {
|
|
|
8918
8913
|
});
|
|
8919
8914
|
}
|
|
8920
8915
|
}
|
|
8916
|
+
function handleModeMdGeneration(root, results) {
|
|
8917
|
+
const filePath = path24.resolve(root, ".kuma/MODE.md");
|
|
8918
|
+
const content = [
|
|
8919
|
+
"# Adaptive Mode Selector",
|
|
8920
|
+
"",
|
|
8921
|
+
"## Mode Definitions",
|
|
8922
|
+
"",
|
|
8923
|
+
"| Mode | When to Use | Token Cost | Steps |",
|
|
8924
|
+
"|------|-------------|------------|-------|",
|
|
8925
|
+
"| **Lean** (default) | Small fixes, familiar code, hotfixes, < 3 files | ~100 | 3 |",
|
|
8926
|
+
"| **Standard** | New features, unfamiliar modules, 3-10 files | ~300 | 5 |",
|
|
8927
|
+
"| **Full** | Cross-module refactors, architecture changes, > 10 files | ~500 | 7 |",
|
|
8928
|
+
"",
|
|
8929
|
+
"## Auto-Detection Rules",
|
|
8930
|
+
"",
|
|
8931
|
+
"```",
|
|
8932
|
+
"IF file_count < 3 AND area_familiar == true:",
|
|
8933
|
+
' mode = "lean"',
|
|
8934
|
+
"ELSE IF file_count <= 10 OR area_familiar == false:",
|
|
8935
|
+
' mode = "standard"',
|
|
8936
|
+
"ELSE IF file_count > 10 OR cross_module == true:",
|
|
8937
|
+
' mode = "full"',
|
|
8938
|
+
"```",
|
|
8939
|
+
"",
|
|
8940
|
+
"## Mode Behaviors",
|
|
8941
|
+
"",
|
|
8942
|
+
"### Lean Mode (Default)",
|
|
8943
|
+
"- Skip: guard, research, verify, changes",
|
|
8944
|
+
"- Record: only if decision/gotcha explicit",
|
|
8945
|
+
"- Focus: speed, minimal overhead",
|
|
8946
|
+
"",
|
|
8947
|
+
"### Standard Mode",
|
|
8948
|
+
"- Include: guard (before unfamiliar), research (before edit)",
|
|
8949
|
+
"- Record: decision, gotcha, arch_flow (if complex)",
|
|
8950
|
+
"- Focus: safety + knowledge capture",
|
|
8951
|
+
"",
|
|
8952
|
+
"### Full Mode",
|
|
8953
|
+
"- Include: all steps including impact, changes",
|
|
8954
|
+
"- Record: everything valuable",
|
|
8955
|
+
"- Focus: completeness, audit trail",
|
|
8956
|
+
"",
|
|
8957
|
+
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
8958
|
+
].join("\n");
|
|
8959
|
+
try {
|
|
8960
|
+
const dir = path24.dirname(filePath);
|
|
8961
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
8962
|
+
if (!fs24.existsSync(filePath)) {
|
|
8963
|
+
fs24.writeFileSync(filePath, content, "utf-8");
|
|
8964
|
+
results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "created" });
|
|
8965
|
+
}
|
|
8966
|
+
} catch (err) {
|
|
8967
|
+
results.push({ type: "claude", filePath: ".kuma/MODE.md", action: "error", error: err instanceof Error ? err.message : String(err) });
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
function handleSkipRulesMdGeneration(root, results) {
|
|
8971
|
+
const filePath = path24.resolve(root, ".kuma/SKIP_RULES.md");
|
|
8972
|
+
const content = [
|
|
8973
|
+
"# Skip Rules \u2014 What NOT to Record",
|
|
8974
|
+
"",
|
|
8975
|
+
"## \u{1F7E2} NEVER Record (grep/glob is faster)",
|
|
8976
|
+
"",
|
|
8977
|
+
"| What | Why Skip | Better Tool |",
|
|
8978
|
+
"|------|----------|-------------|",
|
|
8979
|
+
"| Function definitions | `grep funcName(` | Grep |",
|
|
8980
|
+
"| Class definitions | `grep class ClassName` | Grep |",
|
|
8981
|
+
"| Import statements | Read import block | Read |",
|
|
8982
|
+
"| Component definitions | `glob **/*Component*` | Glob |",
|
|
8983
|
+
"| Route definitions | Check router file | Read |",
|
|
8984
|
+
"| Type/interface definitions | `grep interface TypeName` | Grep |",
|
|
8985
|
+
"| Variable/const declarations | `grep const varName` | Grep |",
|
|
8986
|
+
"| Test file locations | `glob **/*.test.*` | Glob |",
|
|
8987
|
+
"",
|
|
8988
|
+
"## \u{1F534} ALWAYS Record (high value)",
|
|
8989
|
+
"",
|
|
8990
|
+
"| What | Why Record | Tool |",
|
|
8991
|
+
"|------|------------|------|",
|
|
8992
|
+
"| Architecture decisions with rationale | Preserves context | `decision` |",
|
|
8993
|
+
"| Bugs/quirks found and fixed | Prevents re-discovery | `gotcha` |",
|
|
8994
|
+
"| Cross-module flow paths | Saves 5-10 files next session | `arch_flow` |",
|
|
8995
|
+
"| Business rules discovered | Domain knowledge | `domain_rules` |",
|
|
8996
|
+
"",
|
|
8997
|
+
"## \u{1F7E1} Record ONLY If Complex",
|
|
8998
|
+
"",
|
|
8999
|
+
"| What | Condition | Tool |",
|
|
9000
|
+
"|------|-----------|------|",
|
|
9001
|
+
"| Research findings | Multi-file exploration (> 3 files) | `research_save` |",
|
|
9002
|
+
"| Architecture flow | Cross-service or > 3 hops | `arch_flow` |",
|
|
9003
|
+
"| Feature definition | High-level feature with owns edges | `feature` |",
|
|
9004
|
+
"",
|
|
9005
|
+
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9006
|
+
].join("\n");
|
|
9007
|
+
try {
|
|
9008
|
+
const dir = path24.dirname(filePath);
|
|
9009
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
9010
|
+
if (!fs24.existsSync(filePath)) {
|
|
9011
|
+
fs24.writeFileSync(filePath, content, "utf-8");
|
|
9012
|
+
results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "created" });
|
|
9013
|
+
}
|
|
9014
|
+
} catch (err) {
|
|
9015
|
+
results.push({ type: "claude", filePath: ".kuma/SKIP_RULES.md", action: "error", error: err instanceof Error ? err.message : String(err) });
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
9018
|
+
function handleStalenessMdGeneration(root, results) {
|
|
9019
|
+
const filePath = path24.resolve(root, ".kuma/STALENESS.md");
|
|
9020
|
+
const content = [
|
|
9021
|
+
"# Auto-Staleness Detection",
|
|
9022
|
+
"",
|
|
9023
|
+
"## Staleness Signals",
|
|
9024
|
+
"",
|
|
9025
|
+
"| Signal | Detection | Action |",
|
|
9026
|
+
"|--------|-----------|--------|",
|
|
9027
|
+
"| File modified after save | Compare file mtime vs research_save timestamp | Mark STALE, skip cache |",
|
|
9028
|
+
"| Git commit touches file | `git log --since` on researched files | Mark STALE, warn |",
|
|
9029
|
+
'| Age > 7 days | Current time - research_save timestamp | Warn: "Cache may be stale" |',
|
|
9030
|
+
"| Age > 30 days | Current time - research_save timestamp | Auto-invalidate, re-read |",
|
|
9031
|
+
"",
|
|
9032
|
+
"## Recovery Protocol",
|
|
9033
|
+
"",
|
|
9034
|
+
"```",
|
|
9035
|
+
"ON stale_detected:",
|
|
9036
|
+
" 1. Skip cached data",
|
|
9037
|
+
" 2. Re-read file from disk",
|
|
9038
|
+
" 3. Update research_save timestamp",
|
|
9039
|
+
" 4. Continue with fresh data",
|
|
9040
|
+
"",
|
|
9041
|
+
"ON search_conflict:",
|
|
9042
|
+
" 1. Prefer recent research (newer timestamp)",
|
|
9043
|
+
" 2. If same age, prefer live file over cache",
|
|
9044
|
+
" 3. Log conflict for review",
|
|
9045
|
+
"```",
|
|
9046
|
+
"",
|
|
9047
|
+
"_Generated by Kuma MCP - https://github.com/plumpslabs/kuma_"
|
|
9048
|
+
].join("\n");
|
|
9049
|
+
try {
|
|
9050
|
+
const dir = path24.dirname(filePath);
|
|
9051
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
9052
|
+
if (!fs24.existsSync(filePath)) {
|
|
9053
|
+
fs24.writeFileSync(filePath, content, "utf-8");
|
|
9054
|
+
results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "created" });
|
|
9055
|
+
}
|
|
9056
|
+
} catch (err) {
|
|
9057
|
+
results.push({ type: "claude", filePath: ".kuma/STALENESS.md", action: "error", error: err instanceof Error ? err.message : String(err) });
|
|
9058
|
+
}
|
|
9059
|
+
}
|
|
8921
9060
|
function handleInitMdGeneration(root, results) {
|
|
8922
9061
|
const initMdPath = path24.resolve(root, ".kuma/init.md");
|
|
8923
9062
|
try {
|
|
@@ -8944,6 +9083,9 @@ function handleInitMdGeneration(root, results) {
|
|
|
8944
9083
|
});
|
|
8945
9084
|
}
|
|
8946
9085
|
handleQuickrefGeneration(root, results);
|
|
9086
|
+
handleModeMdGeneration(root, results);
|
|
9087
|
+
handleSkipRulesMdGeneration(root, results);
|
|
9088
|
+
handleStalenessMdGeneration(root, results);
|
|
8947
9089
|
}
|
|
8948
9090
|
function handleCodewhaleSecondary(root, results) {
|
|
8949
9091
|
const mcpPath = path24.resolve(root, ".codewhale/mcp.json");
|
|
@@ -9681,30 +9823,18 @@ var init_skillGenerator = __esm({
|
|
|
9681
9823
|
"use strict";
|
|
9682
9824
|
BOOTSTRAP = [
|
|
9683
9825
|
"Kuma MCP tools are available (kuma_context, kuma_memory, kuma_safety).",
|
|
9684
|
-
'**Before coding, call `kuma_context({ action: "init" })`** to load project context
|
|
9826
|
+
'**Before coding, call `kuma_context({ action: "init" })`** to load project context.',
|
|
9685
9827
|
"Project knowledge persists in `.kuma/memories/*.md` across sessions.",
|
|
9686
9828
|
"",
|
|
9687
|
-
"\
|
|
9829
|
+
"\u26A1 **ADAPTIVE WORKFLOW (Lean Mode by Default):**",
|
|
9830
|
+
" \u2022 **Lean Mode (Default, < 3 files):** `init` \u2192 edit/read \u2192 record (`decision`/`gotcha` if needed)",
|
|
9831
|
+
" \u2022 **Standard Mode (3-10 files/unfamiliar):** `init` \u2192 `guard` \u2192 `research` \u2192 edit \u2192 `record` \u2192 `verify`",
|
|
9832
|
+
" \u2022 **Full Mode (> 10 files/refactor):** `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
|
|
9688
9833
|
"",
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
' 3. `kuma_context({ action: "research", scope: "<area>" })` \u2014 Research before editing',
|
|
9692
|
-
" 4. *(edit/read using native tools)*",
|
|
9693
|
-
' 5. `kuma_memory({ action: "research_save", ... })` \u2014 After exploring area (creates search cache)',
|
|
9694
|
-
' 6. `kuma_memory({ action: "gotcha" })` \u2014 \u{1F525} IMMEDIATELY when bug found (exponential value)',
|
|
9695
|
-
' 7. `kuma_memory({ action: "arch_flow" })` \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential value)',
|
|
9696
|
-
' 8. `kuma_memory({ action: "decision" })` \u2014 When choosing between options (preserves rationale)',
|
|
9697
|
-
' 9. `kuma_safety({ action: "verify", ... })` \u2014 Run tests / confirm nothing broken',
|
|
9698
|
-
' 10. `kuma_context({ action: "changes" })` \u2014 Review session activity',
|
|
9834
|
+
"\u{1F534} **MUST RECORD:** `decision` (rationale), `gotcha` (bug/quirk), `arch_flow` (complex flow)",
|
|
9835
|
+
"\u{1F7E2} **SKIP RECORDING:** function, class, import, route (use native grep/glob instead)",
|
|
9699
9836
|
"",
|
|
9700
|
-
"\u{
|
|
9701
|
-
" The more you record, the more overpowered the agent becomes. These are the highest-value actions.",
|
|
9702
|
-
"\u{1F7E2} **SKIP** recording function/class/component nodes \u2014 grep/glob is faster.",
|
|
9703
|
-
"\u{1F9E0} Knowledge graph is persistent \u2014 nodes/edges accumulate across sessions, getting richer over time.",
|
|
9704
|
-
"",
|
|
9705
|
-
"\u26D4 Do NOT skip steps 5-10 after research \u2014 even if no code was changed.",
|
|
9706
|
-
"\u{1F5D1}\uFE0F **Delete/Clear:** `delete_node` (delete node/gotcha/todo/decision) | `clear` (wipe entire graph)",
|
|
9707
|
-
"\u{1F4D6} Full rules: `.kuma/init.md`",
|
|
9837
|
+
"\u{1F4D6} Full rules & modes: `.kuma/init.md` | `.kuma/MODE.md` | `.kuma/SKIP_RULES.md`",
|
|
9708
9838
|
"\u{1F9E0} Tools: `kuma_context` | `kuma_memory` | `kuma_safety`"
|
|
9709
9839
|
].join("\n");
|
|
9710
9840
|
BOOTSTRAP_OPENCODE = [
|
|
@@ -9712,27 +9842,15 @@ var init_skillGenerator = __esm({
|
|
|
9712
9842
|
'**Before coding, call `kuma_kuma_context({ action: "init" })`** to load project context.',
|
|
9713
9843
|
"Project knowledge persists in `.kuma/memories/*.md` across sessions.",
|
|
9714
9844
|
"",
|
|
9715
|
-
"\
|
|
9716
|
-
"",
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
' 3. `kuma_kuma_context({ action: "research", scope: "<area>" })` \u2014 Research before editing',
|
|
9720
|
-
" 4. *(edit/read using native tools)*",
|
|
9721
|
-
' 5. `kuma_kuma_memory({ action: "research_save", ... })` \u2014 After exploring area (creates search cache)',
|
|
9722
|
-
' 6. `kuma_kuma_memory({ action: "gotcha" })` \u2014 \u{1F525} IMMEDIATELY when bug found (exponential value)',
|
|
9723
|
-
' 7. `kuma_kuma_memory({ action: "arch_flow" })` \u2014 \u{1F525} IMMEDIATELY after each flow hop (exponential value)',
|
|
9724
|
-
' 8. `kuma_kuma_memory({ action: "decision" })` \u2014 When choosing between options (preserves rationale)',
|
|
9725
|
-
' 9. `kuma_kuma_safety({ action: "verify", ... })` \u2014 Run tests / confirm nothing broken',
|
|
9726
|
-
' 10. `kuma_kuma_context({ action: "changes" })` \u2014 Review session activity',
|
|
9845
|
+
"\u26A1 **ADAPTIVE WORKFLOW (Lean Mode by Default):**",
|
|
9846
|
+
' \u2022 **Lean Mode (Default, < 3 files):** `kuma_kuma_context({ action: "init" })` \u2192 edit/read \u2192 record (`decision`/`gotcha`)',
|
|
9847
|
+
" \u2022 **Standard Mode (3-10 files/unfamiliar):** `init` \u2192 `guard` \u2192 `research` \u2192 edit \u2192 `record` \u2192 `verify`",
|
|
9848
|
+
" \u2022 **Full Mode (> 10 files/refactor):** `init` \u2192 `guard` \u2192 `research` \u2192 `impact` \u2192 edit \u2192 `record` \u2192 `verify` \u2192 `changes`",
|
|
9727
9849
|
"",
|
|
9728
|
-
"\u{
|
|
9729
|
-
"
|
|
9730
|
-
"\u{1F7E2} **SKIP** recording function/class/component nodes \u2014 grep/glob is faster.",
|
|
9731
|
-
"\u{1F9E0} Knowledge graph is persistent \u2014 nodes/edges accumulate across sessions, getting richer over time.",
|
|
9850
|
+
"\u{1F534} **MUST RECORD:** `decision` (rationale), `gotcha` (bug/quirk), `arch_flow` (complex flow)",
|
|
9851
|
+
"\u{1F7E2} **SKIP RECORDING:** function, class, import, route (use native grep/glob instead)",
|
|
9732
9852
|
"",
|
|
9733
|
-
"\
|
|
9734
|
-
"\u{1F5D1}\uFE0F **Delete/Clear:** `delete_node` (delete node/gotcha/todo/decision) | `clear` (wipe entire graph)",
|
|
9735
|
-
"\u{1F4D6} Full rules: `.kuma/init.md`",
|
|
9853
|
+
"\u{1F4D6} Full rules & modes: `.kuma/init.md` | `.kuma/MODE.md` | `.kuma/SKIP_RULES.md`",
|
|
9736
9854
|
"\u{1F9E0} Tools: `kuma_kuma_context` | `kuma_kuma_memory` | `kuma_kuma_safety`"
|
|
9737
9855
|
].join("\n");
|
|
9738
9856
|
}
|
|
@@ -10265,6 +10383,11 @@ var MEMORY_ALIASES = {
|
|
|
10265
10383
|
"read": "session",
|
|
10266
10384
|
"fetch": "session",
|
|
10267
10385
|
"current": "session",
|
|
10386
|
+
// Graph health synonyms
|
|
10387
|
+
"graph_health": "graph_health",
|
|
10388
|
+
"graph-health": "graph_health",
|
|
10389
|
+
"health-check": "graph_health",
|
|
10390
|
+
"graph-status": "graph_health",
|
|
10268
10391
|
// Decision synonyms
|
|
10269
10392
|
"decision": "decision",
|
|
10270
10393
|
"adr": "decision",
|
|
@@ -10296,7 +10419,6 @@ var MEMORY_ALIASES = {
|
|
|
10296
10419
|
"task": "todo",
|
|
10297
10420
|
"todos": "todo",
|
|
10298
10421
|
"tasks": "todo",
|
|
10299
|
-
// Context notes synonyms
|
|
10300
10422
|
"context": "context",
|
|
10301
10423
|
"notes": "context",
|
|
10302
10424
|
"note": "context",
|
|
@@ -10401,6 +10523,8 @@ async function handleMemory(params) {
|
|
|
10401
10523
|
return handleFeature(params);
|
|
10402
10524
|
case "delete_node":
|
|
10403
10525
|
return handleDeleteNode(params);
|
|
10526
|
+
case "graph_health":
|
|
10527
|
+
return handleGraphHealth(params);
|
|
10404
10528
|
case "clear": {
|
|
10405
10529
|
const { clearGraph: clearGraph2 } = await Promise.resolve().then(() => (init_kumaGraph(), kumaGraph_exports));
|
|
10406
10530
|
await clearGraph2();
|
|
@@ -10452,14 +10576,26 @@ async function handleResearchSave(params) {
|
|
|
10452
10576
|
});
|
|
10453
10577
|
await saveResearchCache(scope, record, void 0, params.confidence);
|
|
10454
10578
|
try {
|
|
10455
|
-
const {
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10579
|
+
const { getDb: getDb2, flushDb: flushDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
10580
|
+
const db = await getDb2();
|
|
10581
|
+
const fileId = `file::${scope}`;
|
|
10582
|
+
const researchId = `research::${scope}`;
|
|
10583
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
10584
|
+
db.run(`
|
|
10585
|
+
INSERT INTO nodes (id, type, name, file_path, metadata, updated_at)
|
|
10586
|
+
VALUES (?, 'file', ?, ?, ?, ?)
|
|
10587
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
10588
|
+
metadata = json_patch(COALESCE(nodes.metadata, '{}'), excluded.metadata),
|
|
10589
|
+
updated_at = excluded.updated_at
|
|
10590
|
+
`, [fileId, scope, scope, JSON.stringify({ findings: [params.content || ""], confidence: params.confidence || 0.8 }), now]);
|
|
10591
|
+
db.run(`
|
|
10592
|
+
INSERT INTO nodes (id, type, name, file_path, metadata, updated_at)
|
|
10593
|
+
VALUES (?, 'research', ?, null, ?, ?)
|
|
10594
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
10595
|
+
metadata = json_patch(COALESCE(nodes.metadata, '{}'), excluded.metadata),
|
|
10596
|
+
updated_at = excluded.updated_at
|
|
10597
|
+
`, [researchId, `research:${scope}`, JSON.stringify({ confidence: params.confidence || 0.8, last_findings: params.content || "" }), now]);
|
|
10598
|
+
flushDb2(db);
|
|
10463
10599
|
} catch {
|
|
10464
10600
|
}
|
|
10465
10601
|
try {
|
|
@@ -10675,12 +10811,20 @@ async function handleLayerAction(layer, params) {
|
|
|
10675
10811
|
\u274C Wrong: hops: file1.js\\nfile2.js
|
|
10676
10812
|
\u2705 Correct: domain: <name> | hops: file1.js \u2192 file2.js \u2192 file3.js`;
|
|
10677
10813
|
}
|
|
10678
|
-
const
|
|
10814
|
+
const rawHops = hopsStr ? hopsStr.split("\u2192").map((h) => h.trim()).filter(Boolean) : [];
|
|
10815
|
+
const coreHops = rawHops.filter((file) => {
|
|
10816
|
+
if (file.endsWith(".tsx") || file.endsWith(".jsx")) return false;
|
|
10817
|
+
if (file.includes("Controller")) return false;
|
|
10818
|
+
if (file.includes("Schema") || file.includes("RequestSchema")) return false;
|
|
10819
|
+
return true;
|
|
10820
|
+
});
|
|
10821
|
+
const limitedHopsList = coreHops.slice(0, 5);
|
|
10822
|
+
const hops = limitedHopsList.map((h, i, arr) => ({
|
|
10679
10823
|
from: i === 0 ? domain : arr[i - 1],
|
|
10680
10824
|
to: h,
|
|
10681
10825
|
relation: "flows",
|
|
10682
10826
|
description: h
|
|
10683
|
-
}))
|
|
10827
|
+
}));
|
|
10684
10828
|
if (hops.length === 0) {
|
|
10685
10829
|
return `\u274C **arch_flow format error:** no hops found after \u2192 separator.
|
|
10686
10830
|
|
|
@@ -10784,11 +10928,12 @@ async function handleDeleteNode(params) {
|
|
|
10784
10928
|
return `\u26A0\uFE0F Unknown scope "${params.scope}". Supported: gotcha, todo, decision, checkpoint`;
|
|
10785
10929
|
}
|
|
10786
10930
|
}
|
|
10787
|
-
if (params.target) {
|
|
10931
|
+
if (params.target || params.scope) {
|
|
10788
10932
|
const { flushDb: flushDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
10789
|
-
|
|
10933
|
+
const targetStr = params.target || "";
|
|
10934
|
+
const targetId = params.scope && !targetStr.includes("::") ? `${targetStr}::${params.scope}` : targetStr;
|
|
10935
|
+
if (targetId && (targetId.includes("::") || targetId.includes(":") || targetId.includes("-") || isNaN(parseInt(targetId, 10)))) {
|
|
10790
10936
|
try {
|
|
10791
|
-
const targetId = params.target;
|
|
10792
10937
|
const domainName = targetId.startsWith("feature_domain::") ? targetId.replace("feature_domain::", "") : targetId;
|
|
10793
10938
|
if (targetId.startsWith("feature_domain::")) {
|
|
10794
10939
|
db.run("DELETE FROM nodes WHERE id LIKE ? OR id LIKE ? OR id LIKE ?", [
|
|
@@ -10802,16 +10947,18 @@ async function handleDeleteNode(params) {
|
|
|
10802
10947
|
db.run("DELETE FROM nodes WHERE id = ?", [targetId]);
|
|
10803
10948
|
}
|
|
10804
10949
|
flushDb2(db);
|
|
10805
|
-
return `\u{1F5D1}\uFE0F **Node & relations deleted instantly from RAM and Disk:** ${
|
|
10950
|
+
return `\u{1F5D1}\uFE0F **Node & relations deleted instantly from RAM and Disk:** ${targetId}`;
|
|
10806
10951
|
} catch (err) {
|
|
10807
10952
|
return `\u274C Failed to delete node: ${err}`;
|
|
10808
10953
|
}
|
|
10809
10954
|
}
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10955
|
+
if (targetId) {
|
|
10956
|
+
const id = parseInt(targetId, 10);
|
|
10957
|
+
if (!isNaN(id)) {
|
|
10958
|
+
db.run("DELETE FROM nodes WHERE rowid = ?", [id]);
|
|
10959
|
+
flushDb2(db);
|
|
10960
|
+
return `\u{1F5D1}\uFE0F **Node #${id} deleted.**`;
|
|
10961
|
+
}
|
|
10815
10962
|
}
|
|
10816
10963
|
}
|
|
10817
10964
|
return "\u26A0\uFE0F Provide `target` (node ID) to delete, or `scope` + `target` (numeric ID) for gotcha/todo/decision/checkpoint.\n\nExamples:\n- `delete_node`, target: 'function::sendMessage'\n- `delete_node`, scope: 'gotcha', target: '42'\n- `delete_node`, scope: 'todo', target: '7'\n- `delete_node`, scope: 'decision', target: '3'\n- `delete_node`, scope: 'checkpoint', target: '1'";
|
|
@@ -10872,6 +11019,68 @@ async function handleFeature(params) {
|
|
|
10872
11019
|
\u{1F3F7}\uFE0F Tags: ${tags.length}
|
|
10873
11020
|
\u26A0\uFE0F Risk: ${risk}`;
|
|
10874
11021
|
}
|
|
11022
|
+
async function handleGraphHealth(_params) {
|
|
11023
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
11024
|
+
const db = await getDb2();
|
|
11025
|
+
const getRows = (sql) => {
|
|
11026
|
+
try {
|
|
11027
|
+
const res = db.exec(sql);
|
|
11028
|
+
if (!res.length) return [];
|
|
11029
|
+
const cols = res[0].columns;
|
|
11030
|
+
return res[0].values.map((val) => {
|
|
11031
|
+
const obj = {};
|
|
11032
|
+
cols.forEach((c, idx) => obj[c] = val[idx]);
|
|
11033
|
+
return obj;
|
|
11034
|
+
});
|
|
11035
|
+
} catch {
|
|
11036
|
+
return [];
|
|
11037
|
+
}
|
|
11038
|
+
};
|
|
11039
|
+
const totalNodesRow = getRows("SELECT COUNT(*) as count FROM nodes")[0];
|
|
11040
|
+
const totalNodes = totalNodesRow ? totalNodesRow.count : 0;
|
|
11041
|
+
const totalEdgesRow = getRows("SELECT COUNT(*) as count FROM edges")[0];
|
|
11042
|
+
const totalEdges = totalEdgesRow ? totalEdgesRow.count : 0;
|
|
11043
|
+
const orphanRows = getRows(`
|
|
11044
|
+
SELECT COUNT(*) as count FROM nodes
|
|
11045
|
+
WHERE id NOT IN (SELECT DISTINCT source_id FROM edges UNION SELECT DISTINCT target_id FROM edges)
|
|
11046
|
+
`);
|
|
11047
|
+
const totalOrphans = orphanRows[0] ? orphanRows[0].count : 0;
|
|
11048
|
+
const duplicateGroups = getRows(`
|
|
11049
|
+
SELECT name, COUNT(*) as count FROM nodes GROUP BY name HAVING count > 1
|
|
11050
|
+
`);
|
|
11051
|
+
const typeStats = getRows(`
|
|
11052
|
+
SELECT type, COUNT(*) as count FROM nodes GROUP BY type ORDER BY count DESC
|
|
11053
|
+
`);
|
|
11054
|
+
const lines = [
|
|
11055
|
+
"\u{1F4CA} **Kuma Knowledge Graph Health Report**",
|
|
11056
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
11057
|
+
`\u2022 **Total Nodes:** ${totalNodes}`,
|
|
11058
|
+
`\u2022 **Total Edges:** ${totalEdges}`,
|
|
11059
|
+
`\u2022 **Orphan Nodes:** ${totalOrphans}`,
|
|
11060
|
+
`\u2022 **Duplicate Groups:** ${duplicateGroups.length}`,
|
|
11061
|
+
"",
|
|
11062
|
+
"**Nodes by Type:**",
|
|
11063
|
+
...typeStats.map((s) => ` - \`${s.type}\`: ${s.count}`),
|
|
11064
|
+
"",
|
|
11065
|
+
"**Recommendations:**"
|
|
11066
|
+
];
|
|
11067
|
+
const recs = [];
|
|
11068
|
+
const crossLinks = typeStats.find((s) => s.type === "cross_service_link");
|
|
11069
|
+
if (crossLinks && crossLinks.count > 50) {
|
|
11070
|
+
recs.push("\u26A0\uFE0F Too many `cross_service_link` nodes. Run cleanup or delete obsolete feature domains.");
|
|
11071
|
+
}
|
|
11072
|
+
if (duplicateGroups.length > 0) {
|
|
11073
|
+
recs.push(`\u26A0\uFE0F ${duplicateGroups.length} duplicate node groups detected. Delete duplicates using \`delete_node\`.`);
|
|
11074
|
+
}
|
|
11075
|
+
if (totalOrphans > 10) {
|
|
11076
|
+
recs.push(`\u26A0\uFE0F ${totalOrphans} orphan nodes found (0 edges). Consider running \`heal\` or \`clear\`.`);
|
|
11077
|
+
}
|
|
11078
|
+
if (recs.length === 0) {
|
|
11079
|
+
recs.push("\u2705 Graph health is optimal! Low noise, good signal-to-noise ratio.");
|
|
11080
|
+
}
|
|
11081
|
+
lines.push(...recs.map((r) => ` ${r}`));
|
|
11082
|
+
return lines.join("\n");
|
|
11083
|
+
}
|
|
10875
11084
|
async function handleLayersSummary(_params) {
|
|
10876
11085
|
const { getLayersSummary: getLayersSummary2 } = await Promise.resolve().then(() => (init_domainRules(), domainRules_exports));
|
|
10877
11086
|
return getLayersSummary2();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.25",
|
|
4
4
|
"description": "Safety-first context & orchestration engine for AI coding agents. MCP server with mandatory research pipeline, knowledge graph, impact analysis, decision memory, and safety guard — works with any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -234,8 +234,7 @@ header .status .dot.off { background: var(--red); }
|
|
|
234
234
|
<div id="graph-container"></div>
|
|
235
235
|
<div id="search-box"><input id="node-search" type="text" placeholder="Search nodes..." /></div>
|
|
236
236
|
<div id="graph-controls">
|
|
237
|
-
<button onclick="
|
|
238
|
-
<button onclick="if(network)network.fit()" title="Fit to viewport"><i data-lucide="maximize-2" style="width:12px;height:12px"></i></button>
|
|
237
|
+
<button onclick="if(network)network.fit()" title="Fit to viewport"><i data-lucide="maximize-2" style="width:12px;height:12px"></i> Fit</button>
|
|
239
238
|
<div class="ctrl-sep"></div>
|
|
240
239
|
<button class="depth-btn" data-depth="1" onclick="setDepth(1)">1</button>
|
|
241
240
|
<button class="depth-btn active" data-depth="2" onclick="setDepth(2)">2</button>
|
|
@@ -482,7 +481,7 @@ function renderGraph(data) {
|
|
|
482
481
|
stabilization: { iterations: 120, updateInterval: 50, onlyDynamicEdges: false, fit: false },
|
|
483
482
|
},
|
|
484
483
|
interaction: { hover: true, tooltipDelay: 300, zoomView: true, dragView: true, dragNodes: true, keyboard: true, multiselect: true, selectable: true, selectConnectedEdges: false },
|
|
485
|
-
nodes: { borderWidth: 0, font: { color: '#ffffff', size: 11, face: 'sans-serif'
|
|
484
|
+
nodes: { borderWidth: 0, font: { color: '#ffffff', size: 11, face: 'sans-serif' }, margin: { top: 6, bottom: 6, left: 10, right: 10 }, shapeProperties: { borderRadius: 6 } },
|
|
486
485
|
edges: { smooth: { type: 'continuous', roundness: 0.2 }, width: 1, color: { color: '#4a4a6a', highlight: '#7c3aed' } },
|
|
487
486
|
configure: { enabled: false },
|
|
488
487
|
});
|
|
@@ -780,7 +779,7 @@ function renderFeatures(data) {
|
|
|
780
779
|
var items = data.features || [];
|
|
781
780
|
var el = document.getElementById("panel-features");
|
|
782
781
|
if(!el) return;
|
|
783
|
-
if(!items.length){el.innerHTML='<div class="empty">No features recorded yet.<br><br>Use:<br><code style="color:var(--accent)">kuma_memory
|
|
782
|
+
if(!items.length){el.innerHTML='<div class="empty">No features recorded yet.<br><br>Use:<br><code style="color:var(--accent)">kuma_memory</code></div>';return;}
|
|
784
783
|
el.innerHTML = items.map(function(f){
|
|
785
784
|
var feat = typeof f === 'string' ? JSON.parse(f) : f;
|
|
786
785
|
var meta = {};
|
|
@@ -829,15 +828,56 @@ function renderHealth(data){
|
|
|
829
828
|
}
|
|
830
829
|
|
|
831
830
|
function setupSearch() {
|
|
831
|
+
var searchTimeout = null;
|
|
832
832
|
document.getElementById("node-search").oninput = function() {
|
|
833
|
-
var
|
|
834
|
-
if (
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
833
|
+
var self = this;
|
|
834
|
+
if (searchTimeout) clearTimeout(searchTimeout);
|
|
835
|
+
searchTimeout = setTimeout(function() {
|
|
836
|
+
var query = self.value.toLowerCase().trim();
|
|
837
|
+
if (!network || !allNodes) return;
|
|
838
|
+
|
|
839
|
+
if (!query) {
|
|
840
|
+
resetHighlight();
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
var matched = new Set();
|
|
845
|
+
var firstMatchedId = null;
|
|
846
|
+
allNodes.forEach(function(n) {
|
|
847
|
+
if (n.name.toLowerCase().includes(query) || (n.file_path && n.file_path.toLowerCase().includes(query))) {
|
|
848
|
+
matched.add(n.id);
|
|
849
|
+
if (!firstMatchedId) firstMatchedId = n.id;
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// Update Nodes
|
|
854
|
+
var nodeBatchUpdates = [];
|
|
855
|
+
network.body.data.nodes.forEach(function(n) {
|
|
856
|
+
var hit = matched.has(n.id);
|
|
857
|
+
nodeBatchUpdates.push({ id: n.id, opacity: hit ? 1.0 : 0.08, font: { color: hit ? '#ffffff' : 'rgba(255,255,255,0.04)', bold: hit } });
|
|
858
|
+
});
|
|
859
|
+
network.body.data.nodes.update(nodeBatchUpdates);
|
|
860
|
+
|
|
861
|
+
// Update Edges (Dim edges whose endpoints are both not in search results)
|
|
862
|
+
var edgeBatchUpdates = [];
|
|
863
|
+
network.body.data.edges.forEach(function(e) {
|
|
864
|
+
var hit = matched.has(e.from) || matched.has(e.to);
|
|
865
|
+
var def = edgeDefaults[e.id] || {};
|
|
866
|
+
var strokeColor = hit ? (def.color || '#64748b') : 'rgba(100,116,139,0.05)';
|
|
867
|
+
var fontColor = hit ? '#94a3b8' : 'rgba(148,163,184,0.03)';
|
|
868
|
+
edgeBatchUpdates.push({
|
|
869
|
+
id: e.id,
|
|
870
|
+
color: { color: strokeColor, highlight: '#c084fc', hover: '#38bdf8', opacity: hit ? 0.95 : 0.05 },
|
|
871
|
+
font: { color: fontColor, strokeWidth: 0, background: 'transparent' },
|
|
872
|
+
width: hit ? (def.width || 1.5) : 0.2
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
network.body.data.edges.update(edgeBatchUpdates);
|
|
876
|
+
|
|
877
|
+
if (firstMatchedId && matched.size < 10) {
|
|
878
|
+
network.focus(firstMatchedId, { scale: 1.2, animation: { duration: 300 } });
|
|
879
|
+
}
|
|
880
|
+
}, 150);
|
|
841
881
|
};
|
|
842
882
|
}
|
|
843
883
|
</script>
|