@plumpslabs/kuma 2.3.34 → 2.4.1
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 +15 -22
- package/dist/index.js +228 -602
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,7 +89,6 @@ Kuma provides **3 coarse-grained tools** with impactful actions. Removed gimmick
|
|
|
89
89
|
| `verify` | Auto-run scoped tests after edits | 🔴 High |
|
|
90
90
|
| `check` | Pre-execution safety check | 🟡 Linear |
|
|
91
91
|
| `audit` | Query safety audit trail | 🟡 Linear |
|
|
92
|
-
| `health` | Project health score (optional/cosmetic) | 🟢 Skip |
|
|
93
92
|
| `security` | Scan for leaked secrets/tokens | 🟡 Linear |
|
|
94
93
|
| `gc` | Garbage collect stale data | 🟢 Skip |
|
|
95
94
|
| `ast` / `validate` | AST-based code validation | 🟡 Linear |
|
|
@@ -98,32 +97,26 @@ Kuma provides **3 coarse-grained tools** with impactful actions. Removed gimmick
|
|
|
98
97
|
|
|
99
98
|
---
|
|
100
99
|
|
|
101
|
-
##
|
|
100
|
+
## Architecture
|
|
102
101
|
|
|
103
|
-
Kuma
|
|
102
|
+
Kuma exposes exactly **3 coarse-grained tools** — the agent picks an *action*, Kuma runs the internal workflow:
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
| Tool | Core Actions | Purpose |
|
|
105
|
+
|------|--------------|---------|
|
|
106
|
+
| `kuma_context` | `init`, `research`, `history` | Load project context, understand unfamiliar code |
|
|
107
|
+
| `kuma_memory` | `gotcha`, `decision`, `arch_flow`, `research_save` | Persistent knowledge that saves future sessions |
|
|
108
|
+
| `kuma_safety` | `guard`, `verify` | Pre-risk check, post-edit verification |
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
|--------------|-------------|
|
|
109
|
-
| `precise_diff_editor` | Agent has native edit tools |
|
|
110
|
-
| `safe_terminal_exec` | Agent executes commands natively |
|
|
111
|
-
| `smart_grep` | Agent searches natively (ripgrep, semantic) |
|
|
112
|
-
| `batch_file_writer` | Agent creates files natively |
|
|
113
|
-
| `ast_validator` (standalone) | Merged into `kuma_safety` as `ast`/`validate` action |
|
|
114
|
-
| `git_diff_analyzer` | Merged into `kuma_context` as `impact` action |
|
|
110
|
+
Everything else is an internal action for power users. The agent uses its **own native tools** for editing, searching, and execution — Kuma is memory & safety, not a code manager.
|
|
115
111
|
|
|
116
|
-
###
|
|
112
|
+
### What Kuma Provides
|
|
117
113
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
| **Guard System** | Real-time monitoring with blocking warnings for anti-patterns |
|
|
125
|
-
| **Kuma Studio** | Visual dashboard with graph, gotchas, and injection metrics |
|
|
126
|
-
| **Checkpoint/Rollback** | Atomic snapshots before major refactors |
|
|
114
|
+
- **Knowledge Graph** — SQLite + FTS5 full-text search (derived cache)
|
|
115
|
+
- **Session Memory** — track tool calls, recordings, and efficiency per session
|
|
116
|
+
- **Guard System** — real-time monitoring with blocking warnings for anti-patterns
|
|
117
|
+
- **Shadow Injection** — gotchas injected before edits via hooks (zero token waste when clean)
|
|
118
|
+
- **Kuma Studio** — visual dashboard with graph, gotchas, and injection metrics
|
|
119
|
+
- **Checkpoint/Rollback** — atomic snapshots before major refactors
|
|
127
120
|
|
|
128
121
|
---
|
|
129
122
|
|
package/dist/index.js
CHANGED
|
@@ -246,7 +246,6 @@ __export(kumaDb_exports, {
|
|
|
246
246
|
saveBenchmark: () => saveBenchmark,
|
|
247
247
|
saveDb: () => saveDb,
|
|
248
248
|
saveFileSummary: () => saveFileSummary,
|
|
249
|
-
saveHealthSnapshot: () => saveHealthSnapshot,
|
|
250
249
|
saveResearchCache: () => saveResearchCache,
|
|
251
250
|
saveVerification: () => saveVerification,
|
|
252
251
|
updateDecisionStatus: () => updateDecisionStatus
|
|
@@ -442,14 +441,6 @@ function createSchema(db) {
|
|
|
442
441
|
current_content TEXT DEFAULT NULL,
|
|
443
442
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
444
443
|
)`);
|
|
445
|
-
db.run(`CREATE TABLE IF NOT EXISTS health_snapshots (
|
|
446
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
447
|
-
score INTEGER NOT NULL,
|
|
448
|
-
risk_level TEXT NOT NULL DEFAULT 'low',
|
|
449
|
-
checks TEXT DEFAULT '[]',
|
|
450
|
-
summary TEXT,
|
|
451
|
-
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
452
|
-
)`);
|
|
453
444
|
try {
|
|
454
445
|
db.run(`CREATE TABLE IF NOT EXISTS safety_audit (
|
|
455
446
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -698,7 +689,6 @@ function createSchema(db) {
|
|
|
698
689
|
db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_session ON change_log(session_id)`);
|
|
699
690
|
db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_file ON change_log(file_path)`);
|
|
700
691
|
db.run(`CREATE INDEX IF NOT EXISTS idx_change_log_created ON change_log(created_at)`);
|
|
701
|
-
db.run(`CREATE INDEX IF NOT EXISTS idx_health_created ON health_snapshots(created_at)`);
|
|
702
692
|
db.run(`CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status)`);
|
|
703
693
|
db.run(`CREATE INDEX IF NOT EXISTS idx_todos_scope ON todos(scope)`);
|
|
704
694
|
db.run(`CREATE INDEX IF NOT EXISTS idx_security_file ON security_findings(file_path)`);
|
|
@@ -1103,10 +1093,6 @@ async function runGarbageCollection() {
|
|
|
1103
1093
|
if (verifCount > 100) {
|
|
1104
1094
|
db.exec(`DELETE FROM verifications WHERE id IN (SELECT id FROM verifications ORDER BY created_at ASC LIMIT ${verifCount - 100})`);
|
|
1105
1095
|
}
|
|
1106
|
-
const healthCount = getCount("SELECT COUNT(*) FROM health_snapshots");
|
|
1107
|
-
if (healthCount > 50) {
|
|
1108
|
-
db.exec(`DELETE FROM health_snapshots WHERE id IN (SELECT id FROM health_snapshots ORDER BY created_at ASC LIMIT ${healthCount - 50})`);
|
|
1109
|
-
}
|
|
1110
1096
|
const auditCount = getCount("SELECT COUNT(*) FROM safety_audit");
|
|
1111
1097
|
if (auditCount > 200) {
|
|
1112
1098
|
db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY created_at ASC LIMIT ${auditCount - 200})`);
|
|
@@ -1163,7 +1149,7 @@ async function runDoctor() {
|
|
|
1163
1149
|
} catch {
|
|
1164
1150
|
checks.push("**Database Integrity**: \u274C check failed");
|
|
1165
1151
|
}
|
|
1166
|
-
const allTables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "decision_log", "file_summaries", "verifications", "
|
|
1152
|
+
const allTables = ["nodes", "edges", "sessions", "research_cache", "change_log", "safety_audit", "todos", "security_findings", "context_notes", "benchmarks", "decision_log", "file_summaries", "verifications", "tool_calls", "experiences", "experience_patterns", "patterns", "api_endpoints", "otel_config", "cost_tracking", "scratch_entries", "portability_entries", "blackboard_events", "known_gotchas", "trajectories", "distilled_skills"];
|
|
1167
1153
|
let existing = 0;
|
|
1168
1154
|
for (const t of allTables) {
|
|
1169
1155
|
try {
|
|
@@ -1266,18 +1252,6 @@ async function ensureGitignore() {
|
|
|
1266
1252
|
return `\u274C Failed: ${err}`;
|
|
1267
1253
|
}
|
|
1268
1254
|
}
|
|
1269
|
-
async function saveHealthSnapshot(score, riskLevel, checks, summary) {
|
|
1270
|
-
try {
|
|
1271
|
-
const db = await getDb();
|
|
1272
|
-
db.run(
|
|
1273
|
-
`INSERT INTO health_snapshots (score, risk_level, checks, summary) VALUES (?, ?, ?, ?)`,
|
|
1274
|
-
[score, riskLevel, checks, summary]
|
|
1275
|
-
);
|
|
1276
|
-
saveDb();
|
|
1277
|
-
} catch (err) {
|
|
1278
|
-
console.error(`[KumaDB] Failed to save health snapshot: ${err}`);
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
1255
|
async function saveVerification(scope, runner, command, passed, output, durationMs = 0) {
|
|
1282
1256
|
try {
|
|
1283
1257
|
const db = await getDb();
|
|
@@ -2078,7 +2052,7 @@ No unresolved issues.`;
|
|
|
2078
2052
|
};
|
|
2079
2053
|
}
|
|
2080
2054
|
// ============================================================
|
|
2081
|
-
//
|
|
2055
|
+
// Change Tracking (Selective Undo)
|
|
2082
2056
|
// ============================================================
|
|
2083
2057
|
/**
|
|
2084
2058
|
* Track a file change with symbol-level detail.
|
|
@@ -3059,7 +3033,6 @@ async function clearGraph() {
|
|
|
3059
3033
|
db.run("DELETE FROM edges;");
|
|
3060
3034
|
db.run("DELETE FROM known_gotchas;");
|
|
3061
3035
|
db.run("DELETE FROM trajectories;");
|
|
3062
|
-
db.run("DELETE FROM health_snapshots;");
|
|
3063
3036
|
db.run("DELETE FROM research_cache;");
|
|
3064
3037
|
flushDb(db);
|
|
3065
3038
|
return 0;
|
|
@@ -3878,6 +3851,117 @@ var init_kumaGraph = __esm({
|
|
|
3878
3851
|
}
|
|
3879
3852
|
});
|
|
3880
3853
|
|
|
3854
|
+
// src/utils/kumaShared.ts
|
|
3855
|
+
import { execSync as execSync3 } from "child_process";
|
|
3856
|
+
function isEditTool(toolName) {
|
|
3857
|
+
const n = (toolName || "").toLowerCase();
|
|
3858
|
+
return n.includes("edit") || n.includes("write") || n.includes("patch") || n.includes("apply") || n.includes("replace");
|
|
3859
|
+
}
|
|
3860
|
+
function isTestTool(toolName) {
|
|
3861
|
+
const n = (toolName || "").toLowerCase();
|
|
3862
|
+
return n.includes("test") || n.includes("typecheck");
|
|
3863
|
+
}
|
|
3864
|
+
function isReadTool(toolName) {
|
|
3865
|
+
const n = (toolName || "").toLowerCase();
|
|
3866
|
+
return n.includes("read") || n.includes("grep") || n.includes("glob") || n.includes("search") || n.includes("list");
|
|
3867
|
+
}
|
|
3868
|
+
function isBashTool(toolName) {
|
|
3869
|
+
const n = (toolName || "").toLowerCase();
|
|
3870
|
+
return n === "bash" || n.startsWith("bash") || n.includes("command") || n.includes("shell") || n.includes("terminal");
|
|
3871
|
+
}
|
|
3872
|
+
function getSessionStats(inputGoal) {
|
|
3873
|
+
const summary = sessionMemory.getSummary();
|
|
3874
|
+
const goal = inputGoal || summary.currentGoal || "";
|
|
3875
|
+
const modifiedFiles = sessionMemory.getModifiedFiles();
|
|
3876
|
+
const toolCalls = sessionMemory.getToolCallHistory(50);
|
|
3877
|
+
const failedFiles = sessionMemory.getFailedFiles();
|
|
3878
|
+
const loop = sessionMemory.detectLoop();
|
|
3879
|
+
return {
|
|
3880
|
+
goal,
|
|
3881
|
+
modifiedFiles,
|
|
3882
|
+
toolCalls,
|
|
3883
|
+
toolCallCount: toolCalls.length,
|
|
3884
|
+
failedFiles,
|
|
3885
|
+
hasLoop: loop.isLooping,
|
|
3886
|
+
loopMessage: loop.message,
|
|
3887
|
+
hasRunTests: toolCalls.some((c) => isTestTool(c.toolName))
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
function getGitDiffStat(timeout = 3e3) {
|
|
3891
|
+
try {
|
|
3892
|
+
const root = getProjectRoot();
|
|
3893
|
+
return execSync3("git diff --stat", {
|
|
3894
|
+
cwd: root,
|
|
3895
|
+
encoding: "utf-8",
|
|
3896
|
+
timeout
|
|
3897
|
+
}).trim();
|
|
3898
|
+
} catch {
|
|
3899
|
+
return "";
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
function getUnresolvedCount(failedFiles) {
|
|
3903
|
+
let count = 0;
|
|
3904
|
+
for (const f of failedFiles) {
|
|
3905
|
+
for (const ff of f.failures) {
|
|
3906
|
+
if (!ff.resolved) count++;
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
return count;
|
|
3910
|
+
}
|
|
3911
|
+
function buildDriftMessages(modifiedFiles, hasRunTests, unresolvedCount, gitStat, loopMessage) {
|
|
3912
|
+
const drifts = [];
|
|
3913
|
+
if (modifiedFiles > 0 && !hasRunTests) {
|
|
3914
|
+
drifts.push(`${modifiedFiles} file(s) edited but no test run`);
|
|
3915
|
+
}
|
|
3916
|
+
if (loopMessage) {
|
|
3917
|
+
drifts.push(loopMessage);
|
|
3918
|
+
}
|
|
3919
|
+
if (unresolvedCount > 0) {
|
|
3920
|
+
drifts.push(`${unresolvedCount} unresolved failure(s)`);
|
|
3921
|
+
}
|
|
3922
|
+
if (gitStat) {
|
|
3923
|
+
drifts.push(`Git diff: ${gitStat}`);
|
|
3924
|
+
}
|
|
3925
|
+
return drifts;
|
|
3926
|
+
}
|
|
3927
|
+
function getPrioritySuggestion(goal, warnings, hasLoop, unresolvedCount, modifiedFiles, hasRunTests, editCalls) {
|
|
3928
|
+
if (warnings.some((w) => w.severity === "high" && w.pattern === "script-patching")) {
|
|
3929
|
+
return "Remove patch scripts and use your native edit tools for all file modifications";
|
|
3930
|
+
}
|
|
3931
|
+
if (hasLoop) {
|
|
3932
|
+
return "Switch approach \u2014 current tool is not making progress";
|
|
3933
|
+
}
|
|
3934
|
+
if (warnings.some((w) => w.pattern === "no-test-after-edit") || modifiedFiles > 0 && !hasRunTests) {
|
|
3935
|
+
return "Run tests to verify your changes before continuing";
|
|
3936
|
+
}
|
|
3937
|
+
if (unresolvedCount > 0) {
|
|
3938
|
+
return "Fix unresolved failures before continuing";
|
|
3939
|
+
}
|
|
3940
|
+
if (warnings.some((w) => w.pattern === "bash-grep")) {
|
|
3941
|
+
return "Use your native grep tool for code search instead of bash grep";
|
|
3942
|
+
}
|
|
3943
|
+
if (warnings.some((w) => w.pattern === "excessive-edits") || editCalls > 10) {
|
|
3944
|
+
return "Consider if refactoring can be simplified \u2014 fewer files = fewer bugs";
|
|
3945
|
+
}
|
|
3946
|
+
if (!goal) {
|
|
3947
|
+
return "No goal set \u2014 use goal parameter or setGoal to track intent";
|
|
3948
|
+
}
|
|
3949
|
+
if (modifiedFiles === 0) {
|
|
3950
|
+
return "Start by exploring what exists before writing code";
|
|
3951
|
+
}
|
|
3952
|
+
return "On track \u2014 continue with current approach";
|
|
3953
|
+
}
|
|
3954
|
+
function countEditCalls(toolCalls) {
|
|
3955
|
+
return toolCalls.filter((c) => isEditTool(c.toolName)).length;
|
|
3956
|
+
}
|
|
3957
|
+
var init_kumaShared = __esm({
|
|
3958
|
+
"src/utils/kumaShared.ts"() {
|
|
3959
|
+
"use strict";
|
|
3960
|
+
init_sessionMemory();
|
|
3961
|
+
init_pathValidator();
|
|
3962
|
+
}
|
|
3963
|
+
});
|
|
3964
|
+
|
|
3881
3965
|
// src/engine/kumaMemory.ts
|
|
3882
3966
|
var kumaMemory_exports = {};
|
|
3883
3967
|
__export(kumaMemory_exports, {
|
|
@@ -4001,7 +4085,7 @@ async function recordDecisionToGraph(decision) {
|
|
|
4001
4085
|
}
|
|
4002
4086
|
function shouldRecordDecision() {
|
|
4003
4087
|
const history = sessionMemory.getToolCallHistory(30);
|
|
4004
|
-
const edits = history.filter((c) => c.toolName
|
|
4088
|
+
const edits = history.filter((c) => isEditTool(c.toolName));
|
|
4005
4089
|
if (edits.length > decisionCooldown + 10 && edits.length >= 10) {
|
|
4006
4090
|
decisionCooldown = edits.length;
|
|
4007
4091
|
return {
|
|
@@ -4047,6 +4131,7 @@ var init_kumaMemory = __esm({
|
|
|
4047
4131
|
"use strict";
|
|
4048
4132
|
init_sessionMemory();
|
|
4049
4133
|
init_pathValidator();
|
|
4134
|
+
init_kumaShared();
|
|
4050
4135
|
MEMORY_DIR = ".kuma/memories";
|
|
4051
4136
|
decisionCooldown = 0;
|
|
4052
4137
|
}
|
|
@@ -5731,7 +5816,7 @@ function formatScanResult(result) {
|
|
|
5731
5816
|
lines.push(
|
|
5732
5817
|
"",
|
|
5733
5818
|
"\u{1F4A1} The knowledge graph now has richer code structure data.",
|
|
5734
|
-
"\u{1F4A1} Use kuma_context({ action: '
|
|
5819
|
+
"\u{1F4A1} Use kuma_context({ action: 'digest' }) to see the graph overview."
|
|
5735
5820
|
);
|
|
5736
5821
|
return lines.join("\n");
|
|
5737
5822
|
}
|
|
@@ -6075,7 +6160,7 @@ async function generateContextDigest() {
|
|
|
6075
6160
|
if (estimatedTokens > 450) {
|
|
6076
6161
|
lines.push(`\u{1F4CA} *~${estimatedTokens} tokens, fits under 500 limit*`);
|
|
6077
6162
|
}
|
|
6078
|
-
return lines.join("\n") + "\n\n\u{1F4A1} Use kuma_context({ action: '
|
|
6163
|
+
return lines.join("\n") + "\n\n\u{1F4A1} Use kuma_context({ action: 'digest' }) for the full state.";
|
|
6079
6164
|
}
|
|
6080
6165
|
var init_contextDigest = __esm({
|
|
6081
6166
|
"src/engine/contextDigest.ts"() {
|
|
@@ -6497,7 +6582,7 @@ __export(kumaMiner_exports, {
|
|
|
6497
6582
|
});
|
|
6498
6583
|
import fs17 from "fs";
|
|
6499
6584
|
import path18 from "path";
|
|
6500
|
-
import { execSync as
|
|
6585
|
+
import { execSync as execSync4 } from "child_process";
|
|
6501
6586
|
import fastGlob2 from "fast-glob";
|
|
6502
6587
|
async function mineHistoricalDecisions(options = {}) {
|
|
6503
6588
|
const root = process.cwd();
|
|
@@ -6506,7 +6591,7 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
6506
6591
|
try {
|
|
6507
6592
|
const sinceFlag = options.since ? `--since="${options.since}"` : `--since="1 year"`;
|
|
6508
6593
|
const gitCmd = `git log ${sinceFlag} -n 100 --pretty=format:"%h|%an|%ad|%s" --date=short`;
|
|
6509
|
-
const gitOutput =
|
|
6594
|
+
const gitOutput = execSync4(gitCmd, { cwd: root, encoding: "utf-8", timeout: 15e3 });
|
|
6510
6595
|
const keywords = ["fix", "revert", "hack", "workaround", "urgent", "hotfix", "don't touch", "temporary", "deprecated", "workaround"];
|
|
6511
6596
|
const lines = gitOutput.split("\n").filter(Boolean);
|
|
6512
6597
|
const matchingHashes = [];
|
|
@@ -6525,7 +6610,7 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
6525
6610
|
if (matchingHashes.length > 0) {
|
|
6526
6611
|
try {
|
|
6527
6612
|
const hashesStr = matchingHashes.map((h) => h.hash).join(" ");
|
|
6528
|
-
const batchOutput =
|
|
6613
|
+
const batchOutput = execSync4(
|
|
6529
6614
|
`git show --name-only --oneline ${hashesStr} 2>/dev/null | head -200`,
|
|
6530
6615
|
{ cwd: root, encoding: "utf-8", timeout: 1e4, maxBuffer: 256 * 1024 }
|
|
6531
6616
|
);
|
|
@@ -6552,7 +6637,7 @@ async function mineHistoricalDecisions(options = {}) {
|
|
|
6552
6637
|
let changedFiles = changedFilesMap.get(hash) || "";
|
|
6553
6638
|
if (!changedFiles) {
|
|
6554
6639
|
try {
|
|
6555
|
-
changedFiles =
|
|
6640
|
+
changedFiles = execSync4(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8", timeout: 5e3 }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
|
|
6556
6641
|
} catch {
|
|
6557
6642
|
}
|
|
6558
6643
|
candidates.push({
|
|
@@ -6659,403 +6744,6 @@ var init_kumaMiner = __esm({
|
|
|
6659
6744
|
}
|
|
6660
6745
|
});
|
|
6661
6746
|
|
|
6662
|
-
// src/utils/kumaShared.ts
|
|
6663
|
-
import { execSync as execSync6 } from "child_process";
|
|
6664
|
-
function getSessionStats(inputGoal) {
|
|
6665
|
-
const summary = sessionMemory.getSummary();
|
|
6666
|
-
const goal = inputGoal || summary.currentGoal || "";
|
|
6667
|
-
const modifiedFiles = sessionMemory.getModifiedFiles();
|
|
6668
|
-
const toolCalls = sessionMemory.getToolCallHistory(50);
|
|
6669
|
-
const failedFiles = sessionMemory.getFailedFiles();
|
|
6670
|
-
const loop = sessionMemory.detectLoop();
|
|
6671
|
-
return {
|
|
6672
|
-
goal,
|
|
6673
|
-
modifiedFiles,
|
|
6674
|
-
toolCalls,
|
|
6675
|
-
toolCallCount: toolCalls.length,
|
|
6676
|
-
failedFiles,
|
|
6677
|
-
hasLoop: loop.isLooping,
|
|
6678
|
-
loopMessage: loop.message,
|
|
6679
|
-
hasRunTests: toolCalls.some((c) => c.toolName === "execute_safe_test")
|
|
6680
|
-
};
|
|
6681
|
-
}
|
|
6682
|
-
function getGitDiffStat2(timeout = 3e3) {
|
|
6683
|
-
try {
|
|
6684
|
-
const root = getProjectRoot();
|
|
6685
|
-
return execSync6("git diff --stat", {
|
|
6686
|
-
cwd: root,
|
|
6687
|
-
encoding: "utf-8",
|
|
6688
|
-
timeout
|
|
6689
|
-
}).trim();
|
|
6690
|
-
} catch {
|
|
6691
|
-
return "";
|
|
6692
|
-
}
|
|
6693
|
-
}
|
|
6694
|
-
function getUnresolvedCount(failedFiles) {
|
|
6695
|
-
let count = 0;
|
|
6696
|
-
for (const f of failedFiles) {
|
|
6697
|
-
for (const ff of f.failures) {
|
|
6698
|
-
if (!ff.resolved) count++;
|
|
6699
|
-
}
|
|
6700
|
-
}
|
|
6701
|
-
return count;
|
|
6702
|
-
}
|
|
6703
|
-
function buildDriftMessages(modifiedFiles, hasRunTests, unresolvedCount, gitStat, loopMessage) {
|
|
6704
|
-
const drifts = [];
|
|
6705
|
-
if (modifiedFiles > 0 && !hasRunTests) {
|
|
6706
|
-
drifts.push(`${modifiedFiles} file(s) edited but no test run`);
|
|
6707
|
-
}
|
|
6708
|
-
if (loopMessage) {
|
|
6709
|
-
drifts.push(loopMessage);
|
|
6710
|
-
}
|
|
6711
|
-
if (unresolvedCount > 0) {
|
|
6712
|
-
drifts.push(`${unresolvedCount} unresolved failure(s)`);
|
|
6713
|
-
}
|
|
6714
|
-
if (gitStat) {
|
|
6715
|
-
drifts.push(`Git diff: ${gitStat}`);
|
|
6716
|
-
}
|
|
6717
|
-
return drifts;
|
|
6718
|
-
}
|
|
6719
|
-
var init_kumaShared = __esm({
|
|
6720
|
-
"src/utils/kumaShared.ts"() {
|
|
6721
|
-
"use strict";
|
|
6722
|
-
init_sessionMemory();
|
|
6723
|
-
init_pathValidator();
|
|
6724
|
-
}
|
|
6725
|
-
});
|
|
6726
|
-
|
|
6727
|
-
// src/engine/safetyScore.ts
|
|
6728
|
-
var safetyScore_exports = {};
|
|
6729
|
-
__export(safetyScore_exports, {
|
|
6730
|
-
computeSafetyScore: () => computeSafetyScore,
|
|
6731
|
-
formatSafetyScore: () => formatSafetyScore
|
|
6732
|
-
});
|
|
6733
|
-
async function computeSafetyScore(inputGoal) {
|
|
6734
|
-
const stats = getSessionStats(inputGoal);
|
|
6735
|
-
const checks = [];
|
|
6736
|
-
let totalScore = 0;
|
|
6737
|
-
const maxScore = 100;
|
|
6738
|
-
const gitStat = getGitDiffStat2();
|
|
6739
|
-
if (gitStat && gitStat !== "clean") {
|
|
6740
|
-
const lines = gitStat.split("\n").filter(Boolean).length;
|
|
6741
|
-
if (lines === 0) {
|
|
6742
|
-
checks.push({
|
|
6743
|
-
label: "Git Clean",
|
|
6744
|
-
status: "pass",
|
|
6745
|
-
message: "Working tree is clean",
|
|
6746
|
-
weight: 20
|
|
6747
|
-
});
|
|
6748
|
-
totalScore += 20;
|
|
6749
|
-
} else if (lines <= 3) {
|
|
6750
|
-
checks.push({
|
|
6751
|
-
label: "Git Clean",
|
|
6752
|
-
status: "warn",
|
|
6753
|
-
message: `${lines} file(s) modified`,
|
|
6754
|
-
weight: 15
|
|
6755
|
-
});
|
|
6756
|
-
totalScore += 15;
|
|
6757
|
-
} else {
|
|
6758
|
-
checks.push({
|
|
6759
|
-
label: "Git Clean",
|
|
6760
|
-
status: "warn",
|
|
6761
|
-
message: `${lines} file(s) modified \u2014 consider committing or stashing`,
|
|
6762
|
-
weight: 10
|
|
6763
|
-
});
|
|
6764
|
-
totalScore += 10;
|
|
6765
|
-
}
|
|
6766
|
-
} else if (gitStat === "clean") {
|
|
6767
|
-
checks.push({
|
|
6768
|
-
label: "Git Clean",
|
|
6769
|
-
status: "pass",
|
|
6770
|
-
message: "Working tree is clean",
|
|
6771
|
-
weight: 20
|
|
6772
|
-
});
|
|
6773
|
-
totalScore += 20;
|
|
6774
|
-
} else {
|
|
6775
|
-
checks.push({
|
|
6776
|
-
label: "Git Status",
|
|
6777
|
-
status: "pass",
|
|
6778
|
-
message: "Not a git repository or git unavailable",
|
|
6779
|
-
weight: 20
|
|
6780
|
-
});
|
|
6781
|
-
totalScore += 20;
|
|
6782
|
-
}
|
|
6783
|
-
try {
|
|
6784
|
-
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
6785
|
-
const db = await getDb2();
|
|
6786
|
-
const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] ?? 0;
|
|
6787
|
-
const edgeCount = db.exec("SELECT COUNT(*) as c FROM edges")[0]?.values[0][0] ?? 0;
|
|
6788
|
-
if (nodeCount > 0) {
|
|
6789
|
-
checks.push({
|
|
6790
|
-
label: "Graph Health",
|
|
6791
|
-
status: "pass",
|
|
6792
|
-
message: `${nodeCount} nodes, ${edgeCount} edges`,
|
|
6793
|
-
weight: 10
|
|
6794
|
-
});
|
|
6795
|
-
totalScore += 10;
|
|
6796
|
-
} else {
|
|
6797
|
-
checks.push({
|
|
6798
|
-
label: "Graph Health",
|
|
6799
|
-
status: "warn",
|
|
6800
|
-
message: "Empty knowledge graph \u2014 run research first",
|
|
6801
|
-
weight: 5
|
|
6802
|
-
});
|
|
6803
|
-
totalScore += 5;
|
|
6804
|
-
}
|
|
6805
|
-
} catch {
|
|
6806
|
-
checks.push({
|
|
6807
|
-
label: "Graph Health",
|
|
6808
|
-
status: "warn",
|
|
6809
|
-
message: "Could not check graph health",
|
|
6810
|
-
weight: 5
|
|
6811
|
-
});
|
|
6812
|
-
totalScore += 5;
|
|
6813
|
-
}
|
|
6814
|
-
try {
|
|
6815
|
-
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
6816
|
-
const db = await getDb2();
|
|
6817
|
-
const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
|
|
6818
|
-
checks.push({
|
|
6819
|
-
label: "Research Cached",
|
|
6820
|
-
status: researchCount > 0 ? "pass" : "warn",
|
|
6821
|
-
message: researchCount > 0 ? `${researchCount} research scope(s) cached` : "No research cached \u2014 run kuma_context research first",
|
|
6822
|
-
weight: 10
|
|
6823
|
-
});
|
|
6824
|
-
totalScore += researchCount > 0 ? 10 : 5;
|
|
6825
|
-
} catch {
|
|
6826
|
-
checks.push({
|
|
6827
|
-
label: "Research Cached",
|
|
6828
|
-
status: "warn",
|
|
6829
|
-
message: "Could not check research cache",
|
|
6830
|
-
weight: 5
|
|
6831
|
-
});
|
|
6832
|
-
totalScore += 5;
|
|
6833
|
-
}
|
|
6834
|
-
const unresolvedCount = getUnresolvedCount(stats.failedFiles);
|
|
6835
|
-
const allFailures = stats.failedFiles.flatMap((f) => f.failures);
|
|
6836
|
-
const testFailures = allFailures.filter((f) => f.error.toLowerCase().includes("test") || f.error.toLowerCase().includes("fail"));
|
|
6837
|
-
const hasRunTests = stats.hasRunTests;
|
|
6838
|
-
let latestVerif = null;
|
|
6839
|
-
try {
|
|
6840
|
-
const { getLatestVerifications: getLatestVerifications2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
|
|
6841
|
-
const verifs = await getLatestVerifications2(1);
|
|
6842
|
-
if (verifs.length > 0) latestVerif = verifs[0];
|
|
6843
|
-
} catch {
|
|
6844
|
-
}
|
|
6845
|
-
if (!hasRunTests && !latestVerif) {
|
|
6846
|
-
checks.push({
|
|
6847
|
-
label: "Tests Status",
|
|
6848
|
-
status: "warn",
|
|
6849
|
-
message: "No tests run yet this session \u2014 run kuma_safety({ action: 'verify' })",
|
|
6850
|
-
weight: 10
|
|
6851
|
-
});
|
|
6852
|
-
totalScore += 10;
|
|
6853
|
-
} else if (latestVerif) {
|
|
6854
|
-
if (latestVerif.passed) {
|
|
6855
|
-
checks.push({
|
|
6856
|
-
label: "Tests Status",
|
|
6857
|
-
status: "pass",
|
|
6858
|
-
message: `Auto-verified passed (${latestVerif.scope})`,
|
|
6859
|
-
weight: 15
|
|
6860
|
-
});
|
|
6861
|
-
totalScore += 15;
|
|
6862
|
-
} else {
|
|
6863
|
-
checks.push({
|
|
6864
|
-
label: "Tests Status",
|
|
6865
|
-
status: "fail",
|
|
6866
|
-
message: `Auto-verification failed (${latestVerif.scope}) \u2014 fix before shipping`,
|
|
6867
|
-
weight: 0
|
|
6868
|
-
});
|
|
6869
|
-
}
|
|
6870
|
-
} else if (testFailures.length === 0 && unresolvedCount === 0) {
|
|
6871
|
-
checks.push({
|
|
6872
|
-
label: "Tests Status",
|
|
6873
|
-
status: "pass",
|
|
6874
|
-
message: "All tests passing",
|
|
6875
|
-
weight: 15
|
|
6876
|
-
});
|
|
6877
|
-
totalScore += 15;
|
|
6878
|
-
} else if (testFailures.length <= 2) {
|
|
6879
|
-
checks.push({
|
|
6880
|
-
label: "Tests Status",
|
|
6881
|
-
status: "warn",
|
|
6882
|
-
message: `${testFailures.length} test failure(s) \u2014 needs attention`,
|
|
6883
|
-
weight: 8
|
|
6884
|
-
});
|
|
6885
|
-
totalScore += 8;
|
|
6886
|
-
} else {
|
|
6887
|
-
checks.push({
|
|
6888
|
-
label: "Tests Status",
|
|
6889
|
-
status: "fail",
|
|
6890
|
-
message: `${testFailures.length} test failure(s) \u2014 fix before proceeding`,
|
|
6891
|
-
weight: 3
|
|
6892
|
-
});
|
|
6893
|
-
totalScore += 3;
|
|
6894
|
-
}
|
|
6895
|
-
const modifiedCount = stats.modifiedFiles.length;
|
|
6896
|
-
if (modifiedCount === 0) {
|
|
6897
|
-
checks.push({
|
|
6898
|
-
label: "Modified Files",
|
|
6899
|
-
status: "pass",
|
|
6900
|
-
message: "No files modified yet",
|
|
6901
|
-
weight: 15
|
|
6902
|
-
});
|
|
6903
|
-
totalScore += 15;
|
|
6904
|
-
} else if (modifiedCount <= 3) {
|
|
6905
|
-
checks.push({
|
|
6906
|
-
label: "Modified Files",
|
|
6907
|
-
status: "warn",
|
|
6908
|
-
message: `${modifiedCount} file(s) modified`,
|
|
6909
|
-
weight: 12
|
|
6910
|
-
});
|
|
6911
|
-
totalScore += 12;
|
|
6912
|
-
} else if (modifiedCount <= 8) {
|
|
6913
|
-
checks.push({
|
|
6914
|
-
label: "Modified Files",
|
|
6915
|
-
status: "warn",
|
|
6916
|
-
message: `${modifiedCount} file(s) modified \u2014 consider a checkpoint`,
|
|
6917
|
-
weight: 8
|
|
6918
|
-
});
|
|
6919
|
-
totalScore += 8;
|
|
6920
|
-
} else {
|
|
6921
|
-
checks.push({
|
|
6922
|
-
label: "Modified Files",
|
|
6923
|
-
status: "fail",
|
|
6924
|
-
message: `${modifiedCount} file(s) modified \u2014 consider committing`,
|
|
6925
|
-
weight: 4
|
|
6926
|
-
});
|
|
6927
|
-
totalScore += 4;
|
|
6928
|
-
}
|
|
6929
|
-
const loop = sessionMemory.detectLoop();
|
|
6930
|
-
if (loop.isLooping) {
|
|
6931
|
-
checks.push({
|
|
6932
|
-
label: "Loop Detection",
|
|
6933
|
-
status: "fail",
|
|
6934
|
-
message: loop.message || "Potential tool call loop detected",
|
|
6935
|
-
weight: 0
|
|
6936
|
-
});
|
|
6937
|
-
} else {
|
|
6938
|
-
checks.push({
|
|
6939
|
-
label: "Loop Detection",
|
|
6940
|
-
status: "pass",
|
|
6941
|
-
message: "No loops detected",
|
|
6942
|
-
weight: 10
|
|
6943
|
-
});
|
|
6944
|
-
totalScore += 10;
|
|
6945
|
-
}
|
|
6946
|
-
if (unresolvedCount === 0) {
|
|
6947
|
-
checks.push({
|
|
6948
|
-
label: "Unresolved Failures",
|
|
6949
|
-
status: "pass",
|
|
6950
|
-
message: "No unresolved failures",
|
|
6951
|
-
weight: 10
|
|
6952
|
-
});
|
|
6953
|
-
totalScore += 10;
|
|
6954
|
-
} else if (unresolvedCount <= 2) {
|
|
6955
|
-
checks.push({
|
|
6956
|
-
label: "Unresolved Failures",
|
|
6957
|
-
status: "warn",
|
|
6958
|
-
message: `${unresolvedCount} unresolved failure(s)`,
|
|
6959
|
-
weight: 6
|
|
6960
|
-
});
|
|
6961
|
-
totalScore += 6;
|
|
6962
|
-
} else {
|
|
6963
|
-
checks.push({
|
|
6964
|
-
label: "Unresolved Failures",
|
|
6965
|
-
status: "fail",
|
|
6966
|
-
message: `${unresolvedCount} unresolved failure(s) \u2014 fix before continuing`,
|
|
6967
|
-
weight: 2
|
|
6968
|
-
});
|
|
6969
|
-
totalScore += 2;
|
|
6970
|
-
}
|
|
6971
|
-
const hasConventions = !!sessionMemory.getConventions();
|
|
6972
|
-
if (hasConventions) {
|
|
6973
|
-
checks.push({
|
|
6974
|
-
label: "Project Detected",
|
|
6975
|
-
status: "pass",
|
|
6976
|
-
message: "Framework, test runner, and conventions detected",
|
|
6977
|
-
weight: 5
|
|
6978
|
-
});
|
|
6979
|
-
totalScore += 5;
|
|
6980
|
-
} else {
|
|
6981
|
-
checks.push({
|
|
6982
|
-
label: "Project Detected",
|
|
6983
|
-
status: "warn",
|
|
6984
|
-
message: "Run project_conventions() to detect stack",
|
|
6985
|
-
weight: 2
|
|
6986
|
-
});
|
|
6987
|
-
totalScore += 2;
|
|
6988
|
-
}
|
|
6989
|
-
const goal = inputGoal || sessionMemory.getSummary().currentGoal || "";
|
|
6990
|
-
if (goal) {
|
|
6991
|
-
checks.push({
|
|
6992
|
-
label: "Goal Set",
|
|
6993
|
-
status: "pass",
|
|
6994
|
-
message: `Current goal: "${goal.substring(0, 60)}"`,
|
|
6995
|
-
weight: 5
|
|
6996
|
-
});
|
|
6997
|
-
totalScore += 5;
|
|
6998
|
-
} else {
|
|
6999
|
-
checks.push({
|
|
7000
|
-
label: "Goal Set",
|
|
7001
|
-
status: "warn",
|
|
7002
|
-
message: "No goal set \u2014 use goal parameter or setGoal to track intent",
|
|
7003
|
-
weight: 2
|
|
7004
|
-
});
|
|
7005
|
-
totalScore += 2;
|
|
7006
|
-
}
|
|
7007
|
-
let risk;
|
|
7008
|
-
if (totalScore >= 85) risk = "LOW";
|
|
7009
|
-
else if (totalScore >= 65) risk = "MEDIUM";
|
|
7010
|
-
else if (totalScore >= 40) risk = "HIGH";
|
|
7011
|
-
else risk = "CRITICAL";
|
|
7012
|
-
const passCount = checks.filter((c) => c.status === "pass").length;
|
|
7013
|
-
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
7014
|
-
const failCount = checks.filter((c) => c.status === "fail").length;
|
|
7015
|
-
const summaryParts = [];
|
|
7016
|
-
if (passCount > 0) summaryParts.push(`${passCount} check(s) passed`);
|
|
7017
|
-
if (warnCount > 0) summaryParts.push(`${warnCount} warning(s)`);
|
|
7018
|
-
if (failCount > 0) summaryParts.push(`${failCount} failure(s)`);
|
|
7019
|
-
return {
|
|
7020
|
-
score: totalScore,
|
|
7021
|
-
maxScore,
|
|
7022
|
-
risk,
|
|
7023
|
-
checks,
|
|
7024
|
-
summary: summaryParts.join(", "),
|
|
7025
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7026
|
-
};
|
|
7027
|
-
}
|
|
7028
|
-
function formatSafetyScore(report) {
|
|
7029
|
-
const barLength = 20;
|
|
7030
|
-
const filledBars = Math.round(report.score / report.maxScore * barLength);
|
|
7031
|
-
const emptyBars = barLength - filledBars;
|
|
7032
|
-
const bar = "\u2588".repeat(filledBars) + "\u2591".repeat(emptyBars);
|
|
7033
|
-
const riskEmoji = report.risk === "LOW" ? "\u{1F7E2}" : report.risk === "MEDIUM" ? "\u{1F7E1}" : report.risk === "HIGH" ? "\u{1F7E0}" : "\u{1F534}";
|
|
7034
|
-
const lines = [
|
|
7035
|
-
`\u{1F6E1}\uFE0F **Safety Score: ${report.score}/${report.maxScore}** ${riskEmoji}`,
|
|
7036
|
-
` ${bar}`,
|
|
7037
|
-
` Risk: **${report.risk}** \u2014 ${report.summary}`,
|
|
7038
|
-
"",
|
|
7039
|
-
"**Checks:**"
|
|
7040
|
-
];
|
|
7041
|
-
for (const check of report.checks) {
|
|
7042
|
-
const icon = check.status === "pass" ? "\u2705" : check.status === "warn" ? "\u26A0\uFE0F" : "\u274C";
|
|
7043
|
-
lines.push(` ${icon} **${check.label}:** ${check.message}`);
|
|
7044
|
-
}
|
|
7045
|
-
lines.push(
|
|
7046
|
-
"",
|
|
7047
|
-
"\u{1F4A1} Run kuma_safety_score() at any time to re-evaluate project health."
|
|
7048
|
-
);
|
|
7049
|
-
return lines.join("\n");
|
|
7050
|
-
}
|
|
7051
|
-
var init_safetyScore = __esm({
|
|
7052
|
-
"src/engine/safetyScore.ts"() {
|
|
7053
|
-
"use strict";
|
|
7054
|
-
init_sessionMemory();
|
|
7055
|
-
init_kumaShared();
|
|
7056
|
-
}
|
|
7057
|
-
});
|
|
7058
|
-
|
|
7059
6747
|
// src/engine/kumaAstValidator.ts
|
|
7060
6748
|
var kumaAstValidator_exports = {};
|
|
7061
6749
|
__export(kumaAstValidator_exports, {
|
|
@@ -7963,23 +7651,15 @@ function aiderTemplate() {
|
|
|
7963
7651
|
}
|
|
7964
7652
|
function opencodeAgentsMdTemplate() {
|
|
7965
7653
|
const opencodeLines = [
|
|
7966
|
-
"Kuma MCP tools
|
|
7967
|
-
|
|
7968
|
-
"",
|
|
7969
|
-
"\u26A1 **DEFAULT WORKFLOW (Lean \u2014 3 steps):**",
|
|
7970
|
-
' 1. `kuma_kuma_context({ action: "init" })` \u2014 Load context (START HERE)',
|
|
7971
|
-
" 2. *(edit/read using native tools)*",
|
|
7972
|
-
' 3. `kuma_kuma_context({ action: "changes" })` \u2014 Review session',
|
|
7654
|
+
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
7655
|
+
"Record what matters, skip what doesn't.",
|
|
7973
7656
|
"",
|
|
7974
|
-
|
|
7975
|
-
'
|
|
7976
|
-
'
|
|
7977
|
-
'
|
|
7978
|
-
"",
|
|
7979
|
-
|
|
7980
|
-
' \u2022 `kuma_kuma_safety({ action: "guard" })` \u2014 before risky ops',
|
|
7981
|
-
' \u2022 `kuma_kuma_context({ action: "research" })` \u2014 before editing unfamiliar code',
|
|
7982
|
-
' \u2022 `kuma_kuma_safety({ action: "verify" })` \u2014 after edits',
|
|
7657
|
+
'\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
|
|
7658
|
+
'\u{1F52C} **Unfamiliar code:** `kuma_kuma_context({ action: "research", scope: "<area>" })`',
|
|
7659
|
+
'\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7660
|
+
'\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
|
|
7661
|
+
'\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
7662
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
|
|
7983
7663
|
"",
|
|
7984
7664
|
"\u26A0\uFE0F **OpenCode note:** Tool names use `kuma_kuma_*` prefix.",
|
|
7985
7665
|
""
|
|
@@ -8113,26 +7793,14 @@ function generateInitMdContent() {
|
|
|
8113
7793
|
"",
|
|
8114
7794
|
"## SESSION WORKFLOW",
|
|
8115
7795
|
"",
|
|
8116
|
-
"###
|
|
8117
|
-
"",
|
|
8118
|
-
"**For most tasks (edits, reads, simple changes):**",
|
|
7796
|
+
"### 6 Core Actions",
|
|
8119
7797
|
"",
|
|
8120
|
-
'
|
|
8121
|
-
|
|
8122
|
-
'
|
|
8123
|
-
"",
|
|
8124
|
-
|
|
8125
|
-
"",
|
|
8126
|
-
"**Record only when something important happens:**",
|
|
8127
|
-
'- Found a bug/quirk? \u2192 `kuma_memory({ action: "gotcha" })` IMMEDIATELY',
|
|
8128
|
-
'- Traced a complete flow? \u2192 `kuma_memory({ action: "arch_flow" })` (max 5 core files)',
|
|
8129
|
-
'- Chose between options? \u2192 `kuma_memory({ action: "decision" })` to preserve rationale',
|
|
8130
|
-
'- Explored new area? \u2192 `kuma_memory({ action: "research_save", scope: "<file>" })`',
|
|
8131
|
-
"",
|
|
8132
|
-
"**Safety checks (use when appropriate):**",
|
|
8133
|
-
'- Before risky edits: `kuma_safety({ action: "guard", guardGoal: "..." })`',
|
|
8134
|
-
'- After edits: `kuma_safety({ action: "verify", scope: "<area>" })`',
|
|
8135
|
-
'- Before research: `kuma_context({ action: "research", scope: "<area>" })`',
|
|
7798
|
+
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
7799
|
+
'\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
|
|
7800
|
+
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7801
|
+
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
7802
|
+
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
7803
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
|
|
8136
7804
|
"",
|
|
8137
7805
|
"### Recording Rules",
|
|
8138
7806
|
"",
|
|
@@ -8275,18 +7943,15 @@ function handleOpencodeSecondary(root, results) {
|
|
|
8275
7943
|
"description: Kuma MCP \u2014 safety toolkit for AI coding agents. Research, memory, and safety guard.",
|
|
8276
7944
|
"---",
|
|
8277
7945
|
"",
|
|
8278
|
-
"Kuma MCP tools
|
|
8279
|
-
|
|
8280
|
-
"",
|
|
8281
|
-
"\u26A1 **DEFAULT WORKFLOW (Lean \u2014 3 steps):**",
|
|
8282
|
-
' 1. `kuma_kuma_context({ action: "init" })` \u2014 Load context (START HERE)',
|
|
8283
|
-
" 2. *(edit/read using native tools)*",
|
|
8284
|
-
' 3. `kuma_kuma_context({ action: "changes" })` \u2014 Review session',
|
|
8285
|
-
"",
|
|
8286
|
-
"\u{1F4DD} **RECORD when needed:**",
|
|
8287
|
-
' \u2022 Bug? \u2192 `kuma_kuma_memory({ action: "gotcha" })` | Flow? \u2192 `kuma_kuma_memory({ action: "arch_flow" })` | Decision? \u2192 `kuma_kuma_memory({ action: "decision" })`',
|
|
7946
|
+
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
7947
|
+
"Record what matters, skip what doesn't.",
|
|
8288
7948
|
"",
|
|
8289
|
-
|
|
7949
|
+
'\u{1F9E0} **Before coding:** `kuma_kuma_context({ action: "init" })`',
|
|
7950
|
+
'\u{1F52C} **Unfamiliar code:** `kuma_kuma_context({ action: "research", scope: "<area>" })`',
|
|
7951
|
+
'\u{1F41B} **Found a bug/quirk:** `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7952
|
+
'\u{1F9ED} **Chose between options:** `kuma_kuma_memory({ action: "decision" })`',
|
|
7953
|
+
'\u{1F500} **Traced a flow:** `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
7954
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_kuma_safety({ action: "guard" | "verify" })`',
|
|
8290
7955
|
"",
|
|
8291
7956
|
"\u26A0\uFE0F **OpenCode note:** Tool names use `kuma_kuma_*` prefix.",
|
|
8292
7957
|
"",
|
|
@@ -8398,21 +8063,14 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
8398
8063
|
"",
|
|
8399
8064
|
"## SESSION WORKFLOW",
|
|
8400
8065
|
"",
|
|
8401
|
-
"
|
|
8066
|
+
"### 6 Core Actions",
|
|
8402
8067
|
"",
|
|
8403
|
-
'
|
|
8404
|
-
|
|
8405
|
-
'
|
|
8406
|
-
"",
|
|
8407
|
-
|
|
8408
|
-
'
|
|
8409
|
-
'- Traced a complete flow? \u2192 `[kuma_]memory({ action: "arch_flow" })` (max 5 core files)',
|
|
8410
|
-
'- Chose between options? \u2192 `[kuma_]memory({ action: "decision" })`',
|
|
8411
|
-
"",
|
|
8412
|
-
"**Safety checks (use when appropriate):**",
|
|
8413
|
-
'- Before risky edits: `[kuma_]safety({ action: "guard", guardGoal: "..." })`',
|
|
8414
|
-
'- After edits: `[kuma_]safety({ action: "verify", scope: "<area>" })`',
|
|
8415
|
-
'- Before research: `[kuma_]context({ action: "research", scope: "<area>" })`',
|
|
8068
|
+
'\u{1F9E0} **Before coding:** `[kuma_]context({ action: "init" })`',
|
|
8069
|
+
'\u{1F52C} **Unfamiliar code:** `[kuma_]context({ action: "research", scope: "<area>" })`',
|
|
8070
|
+
'\u{1F41B} **Found a bug/quirk:** `[kuma_]memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8071
|
+
'\u{1F9ED} **Chose between options:** `[kuma_]memory({ action: "decision" })`',
|
|
8072
|
+
'\u{1F500} **Traced a flow:** `[kuma_]memory({ action: "arch_flow" })` (max 5 files)',
|
|
8073
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `[kuma_]safety({ action: "guard" | "verify" })`',
|
|
8416
8074
|
"",
|
|
8417
8075
|
"\u{1F5D1}\uFE0F **Delete/Clear:** `delete_node` (delete node/gotcha/todo/decision) | `clear` (wipe entire graph)",
|
|
8418
8076
|
"",
|
|
@@ -8955,29 +8613,19 @@ var init_init = __esm({
|
|
|
8955
8613
|
codewhale: "CodeWhale (skills/ + .codewhale/mcp.json)"
|
|
8956
8614
|
};
|
|
8957
8615
|
BOOTSTRAP_LINES = [
|
|
8958
|
-
"Kuma MCP tools
|
|
8959
|
-
|
|
8960
|
-
"",
|
|
8961
|
-
"\u26A1 **DEFAULT WORKFLOW (Lean \u2014 3 steps):**",
|
|
8962
|
-
' 1. `kuma_context({ action: "init" })` \u2014 Load context (START HERE)',
|
|
8963
|
-
" 2. *(edit/read using native tools)*",
|
|
8964
|
-
' 3. `kuma_context({ action: "changes" })` \u2014 Review session',
|
|
8965
|
-
"",
|
|
8966
|
-
"\u{1F4DD} **RECORD when needed (don't force it):**",
|
|
8967
|
-
' \u2022 Found a bug? \u2192 `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8968
|
-
' \u2022 Traced a flow? \u2192 `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
8969
|
-
' \u2022 Chose between options? \u2192 `kuma_memory({ action: "decision" })`',
|
|
8616
|
+
"Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.",
|
|
8617
|
+
"Record what matters, skip what doesn't.",
|
|
8970
8618
|
"",
|
|
8971
|
-
|
|
8972
|
-
'
|
|
8973
|
-
'
|
|
8974
|
-
'
|
|
8975
|
-
'
|
|
8619
|
+
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
8620
|
+
'\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
|
|
8621
|
+
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
8622
|
+
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
8623
|
+
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
8624
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`',
|
|
8976
8625
|
"",
|
|
8977
8626
|
"\u{1FA84} **Auto-inject (Claude Code):** gotcha/decision/history injected automatically before edits",
|
|
8978
8627
|
" via the PreToolUse hook \u2014 an empty hook output means the file has no gotchas.",
|
|
8979
8628
|
"",
|
|
8980
|
-
"\u{1F5D1}\uFE0F **Delete/Clear:** `delete_node` | `clear`",
|
|
8981
8629
|
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
8982
8630
|
].join("\n");
|
|
8983
8631
|
KUMA_CORE_INSTRUCTIONS = BOOTSTRAP_LINES;
|
|
@@ -9475,39 +9123,32 @@ function generateCodewhaleMcpConfig() {
|
|
|
9475
9123
|
}
|
|
9476
9124
|
}, null, 2) + "\n";
|
|
9477
9125
|
}
|
|
9478
|
-
var BOOTSTRAP, BOOTSTRAP_OPENCODE;
|
|
9126
|
+
var CORE_ACTIONS, BOOTSTRAP, PREFIX_TOOLS, BOOTSTRAP_OPENCODE;
|
|
9479
9127
|
var init_skillGenerator = __esm({
|
|
9480
9128
|
"src/utils/skillGenerator.ts"() {
|
|
9481
9129
|
"use strict";
|
|
9130
|
+
CORE_ACTIONS = [
|
|
9131
|
+
'\u{1F9E0} **Before coding:** `kuma_context({ action: "init" })`',
|
|
9132
|
+
'\u{1F52C} **Unfamiliar code:** `kuma_context({ action: "research", scope: "<area>" })`',
|
|
9133
|
+
'\u{1F41B} **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
9134
|
+
'\u{1F9ED} **Chose between options:** `kuma_memory({ action: "decision" })`',
|
|
9135
|
+
'\u{1F500} **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
9136
|
+
'\u{1F6E1}\uFE0F **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`'
|
|
9137
|
+
].join("\n");
|
|
9482
9138
|
BOOTSTRAP = [
|
|
9483
|
-
"Kuma MCP tools
|
|
9484
|
-
|
|
9485
|
-
"",
|
|
9486
|
-
"\u26A1 **DEFAULT WORKFLOW (Lean \u2014 3 steps):**",
|
|
9487
|
-
" 1. `init` \u2014 load context",
|
|
9488
|
-
" 2. *(edit/read using native tools)*",
|
|
9489
|
-
" 3. `changes` \u2014 review session",
|
|
9139
|
+
"Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.",
|
|
9140
|
+
"Record what matters, skip what doesn't.",
|
|
9490
9141
|
"",
|
|
9491
|
-
|
|
9492
|
-
' \u2022 Found a bug? \u2192 `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
9493
|
-
' \u2022 Traced a flow? \u2192 `kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
9494
|
-
' \u2022 Chose between options? \u2192 `kuma_memory({ action: "decision" })`',
|
|
9142
|
+
CORE_ACTIONS,
|
|
9495
9143
|
"",
|
|
9496
9144
|
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
9497
9145
|
].join("\n");
|
|
9146
|
+
PREFIX_TOOLS = (s, prefix) => s.split(prefix).join("kuma_" + prefix);
|
|
9498
9147
|
BOOTSTRAP_OPENCODE = [
|
|
9499
|
-
"Kuma MCP tools
|
|
9500
|
-
|
|
9501
|
-
"",
|
|
9502
|
-
"\u26A1 **DEFAULT WORKFLOW (Lean \u2014 3 steps):**",
|
|
9503
|
-
" 1. `init` \u2014 load context",
|
|
9504
|
-
" 2. *(edit/read using native tools)*",
|
|
9505
|
-
" 3. `changes` \u2014 review session",
|
|
9148
|
+
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
9149
|
+
"Record what matters, skip what doesn't.",
|
|
9506
9150
|
"",
|
|
9507
|
-
"
|
|
9508
|
-
' \u2022 Found a bug? \u2192 `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
9509
|
-
' \u2022 Traced a flow? \u2192 `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
9510
|
-
' \u2022 Chose between options? \u2192 `kuma_kuma_memory({ action: "decision" })`',
|
|
9151
|
+
["kuma_context", "kuma_memory", "kuma_safety"].reduce((acc, tool) => PREFIX_TOOLS(acc, tool), CORE_ACTIONS),
|
|
9511
9152
|
"",
|
|
9512
9153
|
"\u{1F4D6} Full rules: `.kuma/init.md`"
|
|
9513
9154
|
].join("\n");
|
|
@@ -10942,9 +10583,10 @@ init_sessionMemory();
|
|
|
10942
10583
|
// src/guards/antiPatternDetector.ts
|
|
10943
10584
|
init_pathValidator();
|
|
10944
10585
|
init_sessionMemory();
|
|
10586
|
+
init_kumaShared();
|
|
10945
10587
|
import fs20 from "fs";
|
|
10946
10588
|
import path21 from "path";
|
|
10947
|
-
import { execSync as
|
|
10589
|
+
import { execSync as execSync5 } from "child_process";
|
|
10948
10590
|
var SCRIPT_PATCH_PATTERNS = [
|
|
10949
10591
|
"writeFileSync",
|
|
10950
10592
|
"writeFile",
|
|
@@ -10984,7 +10626,7 @@ function findPatchScripts(projectRoot) {
|
|
|
10984
10626
|
severity: "high",
|
|
10985
10627
|
pattern: "script-patching",
|
|
10986
10628
|
message: `Created script file that modifies other files: ${path21.basename(file)}`,
|
|
10987
|
-
suggestion: "Use
|
|
10629
|
+
suggestion: "Use your native edit tools instead \u2014 targeted edits with auto-backup and rollback",
|
|
10988
10630
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
10989
10631
|
filePath: relativePath
|
|
10990
10632
|
});
|
|
@@ -10998,15 +10640,15 @@ function detectBashGrepUsage() {
|
|
|
10998
10640
|
const warnings = [];
|
|
10999
10641
|
const toolCalls = sessionMemory.getToolCallHistory(100);
|
|
11000
10642
|
for (const call of toolCalls) {
|
|
11001
|
-
if (call.toolName
|
|
10643
|
+
if (!isBashTool(call.toolName)) continue;
|
|
11002
10644
|
const cmd = call.params?.customCommand || call.params?.command || "";
|
|
11003
10645
|
const matchedPattern = BASH_GREP_PATTERNS.find((p) => cmd.toLowerCase().includes(p));
|
|
11004
10646
|
if (matchedPattern) {
|
|
11005
10647
|
warnings.push({
|
|
11006
10648
|
severity: "medium",
|
|
11007
10649
|
pattern: "bash-grep",
|
|
11008
|
-
message: `Used bash grep instead of
|
|
11009
|
-
suggestion: "Use
|
|
10650
|
+
message: `Used bash grep instead of the native grep tool`,
|
|
10651
|
+
suggestion: "Use your native grep tool \u2014 it returns line numbers + context and respects .gitignore",
|
|
11010
10652
|
evidence: `Command: ${cmd.substring(0, 120)}`
|
|
11011
10653
|
});
|
|
11012
10654
|
break;
|
|
@@ -11038,7 +10680,7 @@ function scanRecentFiles(projectRoot) {
|
|
|
11038
10680
|
function detectGitPatchScripts(projectRoot) {
|
|
11039
10681
|
const warnings = [];
|
|
11040
10682
|
try {
|
|
11041
|
-
const statusStdout =
|
|
10683
|
+
const statusStdout = execSync5("git status --porcelain", {
|
|
11042
10684
|
cwd: projectRoot,
|
|
11043
10685
|
encoding: "utf-8",
|
|
11044
10686
|
timeout: 3e3
|
|
@@ -11063,7 +10705,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
11063
10705
|
severity: "high",
|
|
11064
10706
|
pattern: "script-patching",
|
|
11065
10707
|
message: `Patch script detected: ${file}`,
|
|
11066
|
-
suggestion: "Use
|
|
10708
|
+
suggestion: "Use your native edit tools instead \u2014 targeted edits with auto-backup and rollback",
|
|
11067
10709
|
evidence: `File: ${file} (contains '${matchedPattern}')`,
|
|
11068
10710
|
filePath: file
|
|
11069
10711
|
});
|
|
@@ -11071,7 +10713,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
11071
10713
|
}
|
|
11072
10714
|
}
|
|
11073
10715
|
}
|
|
11074
|
-
const deletedStdout =
|
|
10716
|
+
const deletedStdout = execSync5("git diff --name-only --diff-filter=D HEAD", {
|
|
11075
10717
|
cwd: projectRoot,
|
|
11076
10718
|
encoding: "utf-8",
|
|
11077
10719
|
timeout: 3e3
|
|
@@ -11085,7 +10727,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
11085
10727
|
severity: "high",
|
|
11086
10728
|
pattern: "script-patching",
|
|
11087
10729
|
message: `Patch script was tracked then deleted: ${file}`,
|
|
11088
|
-
suggestion: "Use
|
|
10730
|
+
suggestion: "Use your native edit tools instead of creating disposable scripts. They have auto-backup + rollback.",
|
|
11089
10731
|
evidence: `Git shows ${file} was deleted`,
|
|
11090
10732
|
filePath: file
|
|
11091
10733
|
});
|
|
@@ -11113,14 +10755,14 @@ function detectBashSedUsage() {
|
|
|
11113
10755
|
"printf '%s' >"
|
|
11114
10756
|
];
|
|
11115
10757
|
for (const call of toolCalls) {
|
|
11116
|
-
if (call.toolName
|
|
10758
|
+
if (!isBashTool(call.toolName)) continue;
|
|
11117
10759
|
const cmd = call.params?.customCommand || call.params?.command || "";
|
|
11118
10760
|
if (SED_PATTERNS.some((p) => cmd.includes(p))) {
|
|
11119
10761
|
warnings.push({
|
|
11120
10762
|
severity: "high",
|
|
11121
10763
|
pattern: "bash-sed-editing",
|
|
11122
|
-
message: "Used bash sed/awk to edit source files instead of
|
|
11123
|
-
suggestion:
|
|
10764
|
+
message: "Used bash sed/awk to edit source files instead of native edit tools",
|
|
10765
|
+
suggestion: "Use your native edit tools for all file modifications \u2014 targeted edits with auto-backup and rollback.",
|
|
11124
10766
|
evidence: `Command: ${cmd.substring(0, 150)}`
|
|
11125
10767
|
});
|
|
11126
10768
|
break;
|
|
@@ -11143,7 +10785,7 @@ init_sessionMemory();
|
|
|
11143
10785
|
init_pathValidator();
|
|
11144
10786
|
import fs21 from "fs";
|
|
11145
10787
|
import path22 from "path";
|
|
11146
|
-
import { execSync as
|
|
10788
|
+
import { execSync as execSync6 } from "child_process";
|
|
11147
10789
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
11148
10790
|
function snapshotDir() {
|
|
11149
10791
|
return path22.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
@@ -11171,7 +10813,7 @@ function saveSnapshot(goal) {
|
|
|
11171
10813
|
modifiedFiles: summary.modifiedFiles || [],
|
|
11172
10814
|
unresolvedFailures: summary.unresolvedFailures || [],
|
|
11173
10815
|
toolCallCount: summary.toolCallCount || 0,
|
|
11174
|
-
gitDiffStat:
|
|
10816
|
+
gitDiffStat: getGitDiffStat2(),
|
|
11175
10817
|
completedSteps: summary.completedSteps || [],
|
|
11176
10818
|
hasConventions: !!summary.hasConventions
|
|
11177
10819
|
};
|
|
@@ -11212,10 +10854,10 @@ function formatSnapshot(snapshot) {
|
|
|
11212
10854
|
lines.push('\u{1F4A1} Use kuma_guard({ check: "context" }) to create a new snapshot.');
|
|
11213
10855
|
return lines.join("\n");
|
|
11214
10856
|
}
|
|
11215
|
-
function
|
|
10857
|
+
function getGitDiffStat2() {
|
|
11216
10858
|
try {
|
|
11217
10859
|
const root = getProjectRoot();
|
|
11218
|
-
const stdout =
|
|
10860
|
+
const stdout = execSync6("git diff --stat", {
|
|
11219
10861
|
cwd: root,
|
|
11220
10862
|
encoding: "utf-8",
|
|
11221
10863
|
timeout: 3e3
|
|
@@ -11242,16 +10884,14 @@ async function handleKumaGuard(params) {
|
|
|
11242
10884
|
severity: "high",
|
|
11243
10885
|
pattern: "tool-loop",
|
|
11244
10886
|
message: loop.message ?? "Detected potential tool call loop",
|
|
11245
|
-
suggestion: "Switch approach \u2014 try reading the file first with
|
|
10887
|
+
suggestion: "Switch approach \u2014 try reading the file first with your native read/search tools"
|
|
11246
10888
|
});
|
|
11247
10889
|
}
|
|
11248
10890
|
const drifts = [];
|
|
11249
10891
|
if (check === "all" || check === "drift") {
|
|
11250
10892
|
const unresolvedCount = getUnresolvedCount(stats.failedFiles);
|
|
11251
|
-
const gitStat =
|
|
11252
|
-
const editCalls = stats.toolCalls.filter(
|
|
11253
|
-
(c) => c.toolName === "precise_diff_editor" || c.toolName === "batch_file_writer"
|
|
11254
|
-
).length;
|
|
10893
|
+
const gitStat = getGitDiffStat();
|
|
10894
|
+
const editCalls = stats.toolCalls.filter((c) => isEditTool(c.toolName)).length;
|
|
11255
10895
|
drifts.push(...buildDriftMessages(
|
|
11256
10896
|
stats.modifiedFiles.length,
|
|
11257
10897
|
stats.hasRunTests,
|
|
@@ -11263,7 +10903,7 @@ async function handleKumaGuard(params) {
|
|
|
11263
10903
|
severity: "medium",
|
|
11264
10904
|
pattern: "no-test-after-edit",
|
|
11265
10905
|
message: `${stats.modifiedFiles.length} file(s) modified without running tests`,
|
|
11266
|
-
suggestion:
|
|
10906
|
+
suggestion: "Run the project's test/typecheck command to verify changes"
|
|
11267
10907
|
});
|
|
11268
10908
|
}
|
|
11269
10909
|
if (editCalls > 5) {
|
|
@@ -11277,9 +10917,7 @@ async function handleKumaGuard(params) {
|
|
|
11277
10917
|
}
|
|
11278
10918
|
const recordingSummary = sessionMemory.getRecordingSummary();
|
|
11279
10919
|
if (check === "all" || check === "drift") {
|
|
11280
|
-
const readCalls = stats.toolCalls.filter(
|
|
11281
|
-
(c) => c.toolName === "read" || c.toolName === "grep" || c.toolName === "glob" || c.toolName === "smart_file_picker"
|
|
11282
|
-
).length;
|
|
10920
|
+
const readCalls = stats.toolCalls.filter((c) => isReadTool(c.toolName)).length;
|
|
11283
10921
|
if (stats.toolCallCount >= 10 && !recordingSummary.hasAnyRecordings) {
|
|
11284
10922
|
warnings.push({
|
|
11285
10923
|
severity: "high",
|
|
@@ -11305,7 +10943,7 @@ async function handleKumaGuard(params) {
|
|
|
11305
10943
|
if (recordingSummary.total > 0) {
|
|
11306
10944
|
}
|
|
11307
10945
|
const errorCalls = stats.toolCalls.filter(
|
|
11308
|
-
(c) => c.toolName
|
|
10946
|
+
(c) => isBashTool(c.toolName) && JSON.stringify(c.params).includes("error")
|
|
11309
10947
|
).length;
|
|
11310
10948
|
if (errorCalls >= 2 && recordingSummary.gotchas === 0) {
|
|
11311
10949
|
warnings.push({
|
|
@@ -11342,22 +10980,15 @@ async function handleKumaGuard(params) {
|
|
|
11342
10980
|
const hasWarnings = warnings.length > 0;
|
|
11343
10981
|
const hasDrifts = drifts.length > 0;
|
|
11344
10982
|
const onTrack = !hasWarnings && !hasDrifts;
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
} else if (warnings.some((w) => w.pattern === "excessive-edits")) {
|
|
11355
|
-
suggestion = "Pause and review: are all these edits necessary?";
|
|
11356
|
-
} else if (!stats.goal) {
|
|
11357
|
-
suggestion = "No goal set \u2014 use goal parameter or setGoal to track intent";
|
|
11358
|
-
} else {
|
|
11359
|
-
suggestion = "On track \u2014 continue with current approach";
|
|
11360
|
-
}
|
|
10983
|
+
const suggestion = getPrioritySuggestion(
|
|
10984
|
+
stats.goal,
|
|
10985
|
+
warnings,
|
|
10986
|
+
loop.isLooping,
|
|
10987
|
+
getUnresolvedCount(stats.failedFiles),
|
|
10988
|
+
stats.modifiedFiles.length,
|
|
10989
|
+
stats.hasRunTests,
|
|
10990
|
+
countEditCalls(stats.toolCalls)
|
|
10991
|
+
);
|
|
11361
10992
|
const report = {
|
|
11362
10993
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11363
10994
|
onTrack,
|
|
@@ -11406,8 +11037,6 @@ async function handleSafety(params) {
|
|
|
11406
11037
|
return handleCheck(params);
|
|
11407
11038
|
case "audit":
|
|
11408
11039
|
return handleAudit(params);
|
|
11409
|
-
case "health":
|
|
11410
|
-
return handleHealth(params);
|
|
11411
11040
|
case "security":
|
|
11412
11041
|
return handleSecurity(params);
|
|
11413
11042
|
case "gc":
|
|
@@ -11446,16 +11075,6 @@ async function handleAudit(params) {
|
|
|
11446
11075
|
since: params.since
|
|
11447
11076
|
});
|
|
11448
11077
|
}
|
|
11449
|
-
async function handleHealth(_params) {
|
|
11450
|
-
try {
|
|
11451
|
-
const { computeSafetyScore: computeSafetyScore2, formatSafetyScore: formatSafetyScore2 } = await Promise.resolve().then(() => (init_safetyScore(), safetyScore_exports));
|
|
11452
|
-
const score = await computeSafetyScore2();
|
|
11453
|
-
await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
|
|
11454
|
-
return formatSafetyScore2(score);
|
|
11455
|
-
} catch (err) {
|
|
11456
|
-
return `Error computing health: ${err}`;
|
|
11457
|
-
}
|
|
11458
|
-
}
|
|
11459
11078
|
async function handleSecurity(params) {
|
|
11460
11079
|
sessionMemory.recordToolCall("kuma_safety_security", {});
|
|
11461
11080
|
if (params.filePath) {
|
|
@@ -11626,14 +11245,17 @@ async function doInit() {
|
|
|
11626
11245
|
}
|
|
11627
11246
|
|
|
11628
11247
|
// src/manifest.ts
|
|
11248
|
+
var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
|
|
11629
11249
|
function registerAllTools(server) {
|
|
11630
11250
|
server.tool(
|
|
11631
11251
|
"kuma_context",
|
|
11632
|
-
"
|
|
11252
|
+
"Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way). Other actions are internal: flow, impact, navigate, digest, drift, rollback, changes, resume.",
|
|
11633
11253
|
{
|
|
11634
|
-
action: z.enum(["init", "research", "impact", "navigate", "flow", "history", "changes", "rollback", "digest", "drift", "resume"]).describe(
|
|
11254
|
+
action: z.enum(["init", "research", "impact", "navigate", "flow", "history", "changes", "rollback", "digest", "drift", "resume"]).describe(
|
|
11255
|
+
"init=project brief + session restore (CORE), research=research pipeline (CORE), history=file rationale + fresh gotchas (CORE); internal (avoid unless needed): impact, navigate, flow, changes, rollback, digest, drift, resume"
|
|
11256
|
+
),
|
|
11635
11257
|
scope: z.string().optional().describe("Research scope for research action"),
|
|
11636
|
-
target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
|
|
11258
|
+
target: z.string().optional().describe("Target symbol/file for impact/navigate/changes/history"),
|
|
11637
11259
|
goal: z.string().optional().describe("Current goal (for init)"),
|
|
11638
11260
|
since: z.number().optional().describe("Unix timestamp filter for changes")
|
|
11639
11261
|
},
|
|
@@ -11655,11 +11277,13 @@ function registerAllTools(server) {
|
|
|
11655
11277
|
);
|
|
11656
11278
|
server.tool(
|
|
11657
11279
|
"kuma_memory",
|
|
11658
|
-
"
|
|
11280
|
+
"Persistent knowledge. Record what matters, skip what doesn't. This is what saves time in future sessions. Don't record what grep/glob answers faster (functions, imports, types, components)." + CORE_NOTE + "`gotcha` (IMMEDIATELY when you find a bug/quirk), `decision` (when choosing between options), `arch_flow` (after tracing a complete flow, max 5 core files), `research_save` (after exploring an area). Other actions are internal: session, search, changes, mine, delete_node, clear, goal_progress.",
|
|
11659
11281
|
{
|
|
11660
|
-
action: z.enum(["decision", "mine", "research_save", "session", "search", "changes", "arch_flow", "gotcha", "delete_node", "clear", "goal_progress"]).describe(
|
|
11282
|
+
action: z.enum(["decision", "mine", "research_save", "session", "search", "changes", "arch_flow", "gotcha", "delete_node", "clear", "goal_progress"]).describe(
|
|
11283
|
+
"gotcha=record bug/quirk (CORE), decision=ADR (CORE), arch_flow=record architecture flow (CORE), research_save=save findings (CORE); internal (avoid unless needed): mine, session, search, changes, delete_node, clear, goal_progress"
|
|
11284
|
+
),
|
|
11661
11285
|
scope: z.string().optional().describe("Scope for research_save/search/mine \u2014 or file path for gotcha/delete_node"),
|
|
11662
|
-
trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha
|
|
11286
|
+
trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha. Injected by the Bash PreToolUse hook"),
|
|
11663
11287
|
query: z.string().optional().describe("Search query for search action"),
|
|
11664
11288
|
content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
|
|
11665
11289
|
record: z.string().optional().describe("JSON record string for research_save"),
|
|
@@ -11669,8 +11293,8 @@ function registerAllTools(server) {
|
|
|
11669
11293
|
context: z.string().optional().describe("Decision context"),
|
|
11670
11294
|
rationale: z.string().optional().describe("Decision rationale (required for decision)"),
|
|
11671
11295
|
outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
|
|
11672
|
-
status: z.string().optional().describe("Gotcha severity
|
|
11673
|
-
description: z.string().optional().describe("Gotcha workaround
|
|
11296
|
+
status: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
|
|
11297
|
+
description: z.string().optional().describe("Gotcha workaround"),
|
|
11674
11298
|
topic: z.string().optional().describe("Memory topic for session action"),
|
|
11675
11299
|
limit: z.number().min(1).max(100).optional().describe("Result limit for search/mine"),
|
|
11676
11300
|
target: z.string().optional().describe("File path for changes / node ID for delete_node"),
|
|
@@ -11707,9 +11331,11 @@ function registerAllTools(server) {
|
|
|
11707
11331
|
);
|
|
11708
11332
|
server.tool(
|
|
11709
11333
|
"kuma_safety",
|
|
11710
|
-
"Safety
|
|
11334
|
+
"Safety & verification. Use at task boundaries, not on every edit." + CORE_NOTE + "`guard` (before risky work: anti-patterns, drift, loops), `verify` (after edits: scoped tests + validation). Other actions are internal/deprecated: check, audit, security, gc, ast, validate, checkpoint, rollback_label, checkpoint_list, gotcha_staleness.",
|
|
11711
11335
|
{
|
|
11712
|
-
action: z.enum(["guard", "verify", "check", "audit", "
|
|
11336
|
+
action: z.enum(["guard", "verify", "check", "audit", "security", "gc", "ast", "validate", "checkpoint", "rollback_label", "checkpoint_list", "gotcha_staleness"]).describe(
|
|
11337
|
+
"guard=anti-patterns/drift/loops before risky work (CORE), verify=scoped tests after edits (CORE); internal/deprecated (avoid unless needed): check, audit, security, gc, ast, validate, checkpoint, rollback_label, checkpoint_list, gotcha_staleness"
|
|
11338
|
+
),
|
|
11713
11339
|
// Verify params
|
|
11714
11340
|
scope: z.string().optional().describe("Scope for verify/ast/validate (e.g. 'auth', file path)"),
|
|
11715
11341
|
// Guard params
|
|
@@ -11725,7 +11351,7 @@ function registerAllTools(server) {
|
|
|
11725
11351
|
limit: z.number().min(1).max(100).optional().describe("Audit result limit"),
|
|
11726
11352
|
since: z.number().optional().describe("Timestamp filter for audit"),
|
|
11727
11353
|
force: z.boolean().optional().describe("Force re-run even if cache is fresh (verify)"),
|
|
11728
|
-
// Checkpoint params
|
|
11354
|
+
// Checkpoint params
|
|
11729
11355
|
label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label actions"),
|
|
11730
11356
|
description: z.string().optional().describe("Description for checkpoint")
|
|
11731
11357
|
},
|
|
@@ -11754,9 +11380,9 @@ function registerAllTools(server) {
|
|
|
11754
11380
|
}
|
|
11755
11381
|
}
|
|
11756
11382
|
);
|
|
11757
|
-
console.error("[Manifest] Registered 3
|
|
11758
|
-
console.error("[Manifest]
|
|
11759
|
-
console.error("[Manifest]
|
|
11383
|
+
console.error("[Manifest] Registered 3 coarse-grained tools (kuma_context, kuma_memory, kuma_safety).");
|
|
11384
|
+
console.error("[Manifest] Auto-init hooks installed on all tools.");
|
|
11385
|
+
console.error("[Manifest] Namespace aliases active (kuma_kuma_* \u2192 kuma_*, context \u2192 kuma_context).");
|
|
11760
11386
|
}
|
|
11761
11387
|
|
|
11762
11388
|
// src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
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",
|