@plumpslabs/kuma 2.3.34 → 2.4.2
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 +240 -610
- 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)`);
|
|
@@ -1091,7 +1081,7 @@ async function runGarbageCollection() {
|
|
|
1091
1081
|
};
|
|
1092
1082
|
const orphanResult = db.exec(`DELETE FROM nodes WHERE id NOT IN (SELECT source_id FROM edges UNION SELECT target_id FROM edges) AND file_path IS NULL AND updated_at < strftime('%s','now','-30 days')`);
|
|
1093
1083
|
removed += orphanResult[0]?.values?.length || 0;
|
|
1094
|
-
const staleEdges = db.exec(`DELETE FROM edges WHERE weight < 0.1 AND
|
|
1084
|
+
const staleEdges = db.exec(`DELETE FROM edges WHERE weight < 0.1 AND created_at < strftime('%s','now','-30 days')`);
|
|
1095
1085
|
removed += staleEdges[0]?.values?.length || 0;
|
|
1096
1086
|
db.exec(`DELETE FROM tool_calls WHERE created_at < strftime('%s','now','-90 days')`);
|
|
1097
1087
|
db.exec(`DELETE FROM experiences WHERE created_at < strftime('%s','now','-90 days')`);
|
|
@@ -1103,13 +1093,9 @@ 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
|
-
db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY
|
|
1098
|
+
db.exec(`DELETE FROM safety_audit WHERE id IN (SELECT id FROM safety_audit ORDER BY timestamp ASC LIMIT ${auditCount - 200})`);
|
|
1113
1099
|
}
|
|
1114
1100
|
db.exec(`DELETE FROM research_cache WHERE updated_at < strftime('%s','now','-60 days')`);
|
|
1115
1101
|
const secCount = getCount("SELECT COUNT(*) FROM security_findings");
|
|
@@ -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',
|
|
7973
|
-
"",
|
|
7974
|
-
"\u{1F4DD} **RECORD when needed (don't force it):**",
|
|
7975
|
-
' \u2022 Found a bug? \u2192 `kuma_kuma_memory({ action: "gotcha" })` (IMMEDIATELY)',
|
|
7976
|
-
' \u2022 Traced a flow? \u2192 `kuma_kuma_memory({ action: "arch_flow" })` (max 5 files)',
|
|
7977
|
-
' \u2022 Chose between options? \u2192 `kuma_kuma_memory({ action: "decision" })`',
|
|
7654
|
+
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
7655
|
+
"Record what matters, skip what doesn't.",
|
|
7978
7656
|
"",
|
|
7979
|
-
|
|
7980
|
-
'
|
|
7981
|
-
'
|
|
7982
|
-
'
|
|
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):**",
|
|
8119
|
-
"",
|
|
8120
|
-
'1. `kuma_context({ action: "init" })` \u2014 Load context (START HERE)',
|
|
8121
|
-
"2. *(edit/read using native tools)*",
|
|
8122
|
-
'3. `kuma_context({ action: "changes" })` \u2014 Review session',
|
|
7796
|
+
"### 6 Core Actions",
|
|
8123
7797
|
"",
|
|
8124
|
-
|
|
8125
|
-
"",
|
|
8126
|
-
|
|
8127
|
-
'
|
|
8128
|
-
'
|
|
8129
|
-
'
|
|
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',
|
|
7946
|
+
"Kuma MCP tools: kuma_kuma_context, kuma_kuma_memory, kuma_kuma_safety.",
|
|
7947
|
+
"Record what matters, skip what doesn't.",
|
|
8285
7948
|
"",
|
|
8286
|
-
|
|
8287
|
-
'
|
|
8288
|
-
"",
|
|
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
|
-
"
|
|
8402
|
-
"",
|
|
8403
|
-
' 1. `[kuma_]context({ action: "init" })` \u2014 Load context (START HERE)',
|
|
8404
|
-
" 2. *(edit/read using native tools)*",
|
|
8405
|
-
' 3. `[kuma_]context({ action: "changes" })` \u2014 Review session',
|
|
8066
|
+
"### 6 Core Actions",
|
|
8406
8067
|
"",
|
|
8407
|
-
|
|
8408
|
-
'
|
|
8409
|
-
'
|
|
8410
|
-
'
|
|
8411
|
-
"",
|
|
8412
|
-
|
|
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',
|
|
8616
|
+
"Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.",
|
|
8617
|
+
"Record what matters, skip what doesn't.",
|
|
8965
8618
|
"",
|
|
8966
|
-
|
|
8967
|
-
'
|
|
8968
|
-
'
|
|
8969
|
-
'
|
|
8970
|
-
"",
|
|
8971
|
-
|
|
8972
|
-
' \u2022 `kuma_safety({ action: "guard" })` \u2014 before risky ops (deletions, refactors)',
|
|
8973
|
-
' \u2022 `kuma_context({ action: "research" })` \u2014 before editing unfamiliar code',
|
|
8974
|
-
' \u2022 `kuma_context({ action: "history", target: "<file>" })` \u2014 WHY this file is written this way',
|
|
8975
|
-
' \u2022 `kuma_safety({ action: "verify" })` \u2014 after edits to confirm nothing broken',
|
|
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
|
-
|
|
9139
|
+
"Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.",
|
|
9140
|
+
"Record what matters, skip what doesn't.",
|
|
9485
9141
|
"",
|
|
9486
|
-
|
|
9487
|
-
" 1. `init` \u2014 load context",
|
|
9488
|
-
" 2. *(edit/read using native tools)*",
|
|
9489
|
-
" 3. `changes` \u2014 review session",
|
|
9490
|
-
"",
|
|
9491
|
-
"\u{1F4DD} **RECORD when needed (don't force it):**",
|
|
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");
|
|
@@ -10283,21 +9924,25 @@ async function handleArchFlow(params) {
|
|
|
10283
9924
|
return readLayer2("arch_flow");
|
|
10284
9925
|
}
|
|
10285
9926
|
async function handleGotchaAction(params) {
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
const normalizedScope = normalizeScope(
|
|
9927
|
+
const content = (params.content ?? params.description ?? "").trim();
|
|
9928
|
+
const scope = (params.scope ?? "").trim();
|
|
9929
|
+
if (content && scope) {
|
|
9930
|
+
const normalizedScope = normalizeScope(scope) || scope;
|
|
10290
9931
|
const { addGotcha: addGotcha2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10291
9932
|
const result = await addGotcha2({
|
|
10292
9933
|
filePath: normalizedScope,
|
|
10293
|
-
description:
|
|
9934
|
+
description: content,
|
|
10294
9935
|
severity: params.status || "medium",
|
|
10295
|
-
workaround
|
|
9936
|
+
// `description` doubles as workaround only when the canonical `content` field was used.
|
|
9937
|
+
workaround: params.content ? params.description : void 0,
|
|
10296
9938
|
triggerCommand: params.trigger_command
|
|
10297
9939
|
});
|
|
10298
9940
|
sessionMemory.recordMemoryAction("gotcha");
|
|
10299
9941
|
return result;
|
|
10300
9942
|
}
|
|
9943
|
+
if (scope || params.title || params.status || params.trigger_command || params.description) {
|
|
9944
|
+
return '\u274C **Gotcha NOT saved** \u2014 missing `content` (bug description).\n\u2705 Use: kuma_memory({ action: "gotcha", scope: "<file_path>", content: "<what went wrong>", status: "high" })\n- `scope` \u2014 file path where the bug was found\n- `content` \u2014 bug description (required to save)\n- `status` \u2014 low | medium | high | critical';
|
|
9945
|
+
}
|
|
10301
9946
|
const { listGotchas: listGotchas2, syncGotchasToDb: syncGotchasToDb2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
|
|
10302
9947
|
await syncGotchasToDb2();
|
|
10303
9948
|
return await listGotchas2({ filePath: params.scope, severity: params.status });
|
|
@@ -10942,9 +10587,10 @@ init_sessionMemory();
|
|
|
10942
10587
|
// src/guards/antiPatternDetector.ts
|
|
10943
10588
|
init_pathValidator();
|
|
10944
10589
|
init_sessionMemory();
|
|
10590
|
+
init_kumaShared();
|
|
10945
10591
|
import fs20 from "fs";
|
|
10946
10592
|
import path21 from "path";
|
|
10947
|
-
import { execSync as
|
|
10593
|
+
import { execSync as execSync5 } from "child_process";
|
|
10948
10594
|
var SCRIPT_PATCH_PATTERNS = [
|
|
10949
10595
|
"writeFileSync",
|
|
10950
10596
|
"writeFile",
|
|
@@ -10984,7 +10630,7 @@ function findPatchScripts(projectRoot) {
|
|
|
10984
10630
|
severity: "high",
|
|
10985
10631
|
pattern: "script-patching",
|
|
10986
10632
|
message: `Created script file that modifies other files: ${path21.basename(file)}`,
|
|
10987
|
-
suggestion: "Use
|
|
10633
|
+
suggestion: "Use your native edit tools instead \u2014 targeted edits with auto-backup and rollback",
|
|
10988
10634
|
evidence: `File: ${relativePath} contains '${matchedPattern}'`,
|
|
10989
10635
|
filePath: relativePath
|
|
10990
10636
|
});
|
|
@@ -10998,15 +10644,15 @@ function detectBashGrepUsage() {
|
|
|
10998
10644
|
const warnings = [];
|
|
10999
10645
|
const toolCalls = sessionMemory.getToolCallHistory(100);
|
|
11000
10646
|
for (const call of toolCalls) {
|
|
11001
|
-
if (call.toolName
|
|
10647
|
+
if (!isBashTool(call.toolName)) continue;
|
|
11002
10648
|
const cmd = call.params?.customCommand || call.params?.command || "";
|
|
11003
10649
|
const matchedPattern = BASH_GREP_PATTERNS.find((p) => cmd.toLowerCase().includes(p));
|
|
11004
10650
|
if (matchedPattern) {
|
|
11005
10651
|
warnings.push({
|
|
11006
10652
|
severity: "medium",
|
|
11007
10653
|
pattern: "bash-grep",
|
|
11008
|
-
message: `Used bash grep instead of
|
|
11009
|
-
suggestion: "Use
|
|
10654
|
+
message: `Used bash grep instead of the native grep tool`,
|
|
10655
|
+
suggestion: "Use your native grep tool \u2014 it returns line numbers + context and respects .gitignore",
|
|
11010
10656
|
evidence: `Command: ${cmd.substring(0, 120)}`
|
|
11011
10657
|
});
|
|
11012
10658
|
break;
|
|
@@ -11038,7 +10684,7 @@ function scanRecentFiles(projectRoot) {
|
|
|
11038
10684
|
function detectGitPatchScripts(projectRoot) {
|
|
11039
10685
|
const warnings = [];
|
|
11040
10686
|
try {
|
|
11041
|
-
const statusStdout =
|
|
10687
|
+
const statusStdout = execSync5("git status --porcelain", {
|
|
11042
10688
|
cwd: projectRoot,
|
|
11043
10689
|
encoding: "utf-8",
|
|
11044
10690
|
timeout: 3e3
|
|
@@ -11063,7 +10709,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
11063
10709
|
severity: "high",
|
|
11064
10710
|
pattern: "script-patching",
|
|
11065
10711
|
message: `Patch script detected: ${file}`,
|
|
11066
|
-
suggestion: "Use
|
|
10712
|
+
suggestion: "Use your native edit tools instead \u2014 targeted edits with auto-backup and rollback",
|
|
11067
10713
|
evidence: `File: ${file} (contains '${matchedPattern}')`,
|
|
11068
10714
|
filePath: file
|
|
11069
10715
|
});
|
|
@@ -11071,7 +10717,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
11071
10717
|
}
|
|
11072
10718
|
}
|
|
11073
10719
|
}
|
|
11074
|
-
const deletedStdout =
|
|
10720
|
+
const deletedStdout = execSync5("git diff --name-only --diff-filter=D HEAD", {
|
|
11075
10721
|
cwd: projectRoot,
|
|
11076
10722
|
encoding: "utf-8",
|
|
11077
10723
|
timeout: 3e3
|
|
@@ -11085,7 +10731,7 @@ function detectGitPatchScripts(projectRoot) {
|
|
|
11085
10731
|
severity: "high",
|
|
11086
10732
|
pattern: "script-patching",
|
|
11087
10733
|
message: `Patch script was tracked then deleted: ${file}`,
|
|
11088
|
-
suggestion: "Use
|
|
10734
|
+
suggestion: "Use your native edit tools instead of creating disposable scripts. They have auto-backup + rollback.",
|
|
11089
10735
|
evidence: `Git shows ${file} was deleted`,
|
|
11090
10736
|
filePath: file
|
|
11091
10737
|
});
|
|
@@ -11113,14 +10759,14 @@ function detectBashSedUsage() {
|
|
|
11113
10759
|
"printf '%s' >"
|
|
11114
10760
|
];
|
|
11115
10761
|
for (const call of toolCalls) {
|
|
11116
|
-
if (call.toolName
|
|
10762
|
+
if (!isBashTool(call.toolName)) continue;
|
|
11117
10763
|
const cmd = call.params?.customCommand || call.params?.command || "";
|
|
11118
10764
|
if (SED_PATTERNS.some((p) => cmd.includes(p))) {
|
|
11119
10765
|
warnings.push({
|
|
11120
10766
|
severity: "high",
|
|
11121
10767
|
pattern: "bash-sed-editing",
|
|
11122
|
-
message: "Used bash sed/awk to edit source files instead of
|
|
11123
|
-
suggestion:
|
|
10768
|
+
message: "Used bash sed/awk to edit source files instead of native edit tools",
|
|
10769
|
+
suggestion: "Use your native edit tools for all file modifications \u2014 targeted edits with auto-backup and rollback.",
|
|
11124
10770
|
evidence: `Command: ${cmd.substring(0, 150)}`
|
|
11125
10771
|
});
|
|
11126
10772
|
break;
|
|
@@ -11143,7 +10789,7 @@ init_sessionMemory();
|
|
|
11143
10789
|
init_pathValidator();
|
|
11144
10790
|
import fs21 from "fs";
|
|
11145
10791
|
import path22 from "path";
|
|
11146
|
-
import { execSync as
|
|
10792
|
+
import { execSync as execSync6 } from "child_process";
|
|
11147
10793
|
var SNAPSHOT_DIR = "context-snapshots";
|
|
11148
10794
|
function snapshotDir() {
|
|
11149
10795
|
return path22.join(getProjectRoot(), ".kuma", SNAPSHOT_DIR);
|
|
@@ -11171,7 +10817,7 @@ function saveSnapshot(goal) {
|
|
|
11171
10817
|
modifiedFiles: summary.modifiedFiles || [],
|
|
11172
10818
|
unresolvedFailures: summary.unresolvedFailures || [],
|
|
11173
10819
|
toolCallCount: summary.toolCallCount || 0,
|
|
11174
|
-
gitDiffStat:
|
|
10820
|
+
gitDiffStat: getGitDiffStat2(),
|
|
11175
10821
|
completedSteps: summary.completedSteps || [],
|
|
11176
10822
|
hasConventions: !!summary.hasConventions
|
|
11177
10823
|
};
|
|
@@ -11212,10 +10858,10 @@ function formatSnapshot(snapshot) {
|
|
|
11212
10858
|
lines.push('\u{1F4A1} Use kuma_guard({ check: "context" }) to create a new snapshot.');
|
|
11213
10859
|
return lines.join("\n");
|
|
11214
10860
|
}
|
|
11215
|
-
function
|
|
10861
|
+
function getGitDiffStat2() {
|
|
11216
10862
|
try {
|
|
11217
10863
|
const root = getProjectRoot();
|
|
11218
|
-
const stdout =
|
|
10864
|
+
const stdout = execSync6("git diff --stat", {
|
|
11219
10865
|
cwd: root,
|
|
11220
10866
|
encoding: "utf-8",
|
|
11221
10867
|
timeout: 3e3
|
|
@@ -11242,16 +10888,14 @@ async function handleKumaGuard(params) {
|
|
|
11242
10888
|
severity: "high",
|
|
11243
10889
|
pattern: "tool-loop",
|
|
11244
10890
|
message: loop.message ?? "Detected potential tool call loop",
|
|
11245
|
-
suggestion: "Switch approach \u2014 try reading the file first with
|
|
10891
|
+
suggestion: "Switch approach \u2014 try reading the file first with your native read/search tools"
|
|
11246
10892
|
});
|
|
11247
10893
|
}
|
|
11248
10894
|
const drifts = [];
|
|
11249
10895
|
if (check === "all" || check === "drift") {
|
|
11250
10896
|
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;
|
|
10897
|
+
const gitStat = getGitDiffStat();
|
|
10898
|
+
const editCalls = stats.toolCalls.filter((c) => isEditTool(c.toolName)).length;
|
|
11255
10899
|
drifts.push(...buildDriftMessages(
|
|
11256
10900
|
stats.modifiedFiles.length,
|
|
11257
10901
|
stats.hasRunTests,
|
|
@@ -11263,7 +10907,7 @@ async function handleKumaGuard(params) {
|
|
|
11263
10907
|
severity: "medium",
|
|
11264
10908
|
pattern: "no-test-after-edit",
|
|
11265
10909
|
message: `${stats.modifiedFiles.length} file(s) modified without running tests`,
|
|
11266
|
-
suggestion:
|
|
10910
|
+
suggestion: "Run the project's test/typecheck command to verify changes"
|
|
11267
10911
|
});
|
|
11268
10912
|
}
|
|
11269
10913
|
if (editCalls > 5) {
|
|
@@ -11277,9 +10921,7 @@ async function handleKumaGuard(params) {
|
|
|
11277
10921
|
}
|
|
11278
10922
|
const recordingSummary = sessionMemory.getRecordingSummary();
|
|
11279
10923
|
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;
|
|
10924
|
+
const readCalls = stats.toolCalls.filter((c) => isReadTool(c.toolName)).length;
|
|
11283
10925
|
if (stats.toolCallCount >= 10 && !recordingSummary.hasAnyRecordings) {
|
|
11284
10926
|
warnings.push({
|
|
11285
10927
|
severity: "high",
|
|
@@ -11305,7 +10947,7 @@ async function handleKumaGuard(params) {
|
|
|
11305
10947
|
if (recordingSummary.total > 0) {
|
|
11306
10948
|
}
|
|
11307
10949
|
const errorCalls = stats.toolCalls.filter(
|
|
11308
|
-
(c) => c.toolName
|
|
10950
|
+
(c) => isBashTool(c.toolName) && JSON.stringify(c.params).includes("error")
|
|
11309
10951
|
).length;
|
|
11310
10952
|
if (errorCalls >= 2 && recordingSummary.gotchas === 0) {
|
|
11311
10953
|
warnings.push({
|
|
@@ -11342,22 +10984,15 @@ async function handleKumaGuard(params) {
|
|
|
11342
10984
|
const hasWarnings = warnings.length > 0;
|
|
11343
10985
|
const hasDrifts = drifts.length > 0;
|
|
11344
10986
|
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
|
-
}
|
|
10987
|
+
const suggestion = getPrioritySuggestion(
|
|
10988
|
+
stats.goal,
|
|
10989
|
+
warnings,
|
|
10990
|
+
loop.isLooping,
|
|
10991
|
+
getUnresolvedCount(stats.failedFiles),
|
|
10992
|
+
stats.modifiedFiles.length,
|
|
10993
|
+
stats.hasRunTests,
|
|
10994
|
+
countEditCalls(stats.toolCalls)
|
|
10995
|
+
);
|
|
11361
10996
|
const report = {
|
|
11362
10997
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11363
10998
|
onTrack,
|
|
@@ -11406,8 +11041,6 @@ async function handleSafety(params) {
|
|
|
11406
11041
|
return handleCheck(params);
|
|
11407
11042
|
case "audit":
|
|
11408
11043
|
return handleAudit(params);
|
|
11409
|
-
case "health":
|
|
11410
|
-
return handleHealth(params);
|
|
11411
11044
|
case "security":
|
|
11412
11045
|
return handleSecurity(params);
|
|
11413
11046
|
case "gc":
|
|
@@ -11446,16 +11079,6 @@ async function handleAudit(params) {
|
|
|
11446
11079
|
since: params.since
|
|
11447
11080
|
});
|
|
11448
11081
|
}
|
|
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
11082
|
async function handleSecurity(params) {
|
|
11460
11083
|
sessionMemory.recordToolCall("kuma_safety_security", {});
|
|
11461
11084
|
if (params.filePath) {
|
|
@@ -11626,14 +11249,17 @@ async function doInit() {
|
|
|
11626
11249
|
}
|
|
11627
11250
|
|
|
11628
11251
|
// src/manifest.ts
|
|
11252
|
+
var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
|
|
11629
11253
|
function registerAllTools(server) {
|
|
11630
11254
|
server.tool(
|
|
11631
11255
|
"kuma_context",
|
|
11632
|
-
"
|
|
11256
|
+
"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
11257
|
{
|
|
11634
|
-
action: z.enum(["init", "research", "impact", "navigate", "flow", "history", "changes", "rollback", "digest", "drift", "resume"]).describe(
|
|
11258
|
+
action: z.enum(["init", "research", "impact", "navigate", "flow", "history", "changes", "rollback", "digest", "drift", "resume"]).describe(
|
|
11259
|
+
"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"
|
|
11260
|
+
),
|
|
11635
11261
|
scope: z.string().optional().describe("Research scope for research action"),
|
|
11636
|
-
target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
|
|
11262
|
+
target: z.string().optional().describe("Target symbol/file for impact/navigate/changes/history"),
|
|
11637
11263
|
goal: z.string().optional().describe("Current goal (for init)"),
|
|
11638
11264
|
since: z.number().optional().describe("Unix timestamp filter for changes")
|
|
11639
11265
|
},
|
|
@@ -11655,11 +11281,13 @@ function registerAllTools(server) {
|
|
|
11655
11281
|
);
|
|
11656
11282
|
server.tool(
|
|
11657
11283
|
"kuma_memory",
|
|
11658
|
-
"
|
|
11284
|
+
"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
11285
|
{
|
|
11660
|
-
action: z.enum(["decision", "mine", "research_save", "session", "search", "changes", "arch_flow", "gotcha", "delete_node", "clear", "goal_progress"]).describe(
|
|
11286
|
+
action: z.enum(["decision", "mine", "research_save", "session", "search", "changes", "arch_flow", "gotcha", "delete_node", "clear", "goal_progress"]).describe(
|
|
11287
|
+
"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"
|
|
11288
|
+
),
|
|
11661
11289
|
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
|
|
11290
|
+
trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha. Injected by the Bash PreToolUse hook"),
|
|
11663
11291
|
query: z.string().optional().describe("Search query for search action"),
|
|
11664
11292
|
content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
|
|
11665
11293
|
record: z.string().optional().describe("JSON record string for research_save"),
|
|
@@ -11669,8 +11297,8 @@ function registerAllTools(server) {
|
|
|
11669
11297
|
context: z.string().optional().describe("Decision context"),
|
|
11670
11298
|
rationale: z.string().optional().describe("Decision rationale (required for decision)"),
|
|
11671
11299
|
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
|
|
11300
|
+
status: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
|
|
11301
|
+
description: z.string().optional().describe("Gotcha workaround"),
|
|
11674
11302
|
topic: z.string().optional().describe("Memory topic for session action"),
|
|
11675
11303
|
limit: z.number().min(1).max(100).optional().describe("Result limit for search/mine"),
|
|
11676
11304
|
target: z.string().optional().describe("File path for changes / node ID for delete_node"),
|
|
@@ -11707,9 +11335,11 @@ function registerAllTools(server) {
|
|
|
11707
11335
|
);
|
|
11708
11336
|
server.tool(
|
|
11709
11337
|
"kuma_safety",
|
|
11710
|
-
"Safety
|
|
11338
|
+
"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
11339
|
{
|
|
11712
|
-
action: z.enum(["guard", "verify", "check", "audit", "
|
|
11340
|
+
action: z.enum(["guard", "verify", "check", "audit", "security", "gc", "ast", "validate", "checkpoint", "rollback_label", "checkpoint_list", "gotcha_staleness"]).describe(
|
|
11341
|
+
"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"
|
|
11342
|
+
),
|
|
11713
11343
|
// Verify params
|
|
11714
11344
|
scope: z.string().optional().describe("Scope for verify/ast/validate (e.g. 'auth', file path)"),
|
|
11715
11345
|
// Guard params
|
|
@@ -11725,7 +11355,7 @@ function registerAllTools(server) {
|
|
|
11725
11355
|
limit: z.number().min(1).max(100).optional().describe("Audit result limit"),
|
|
11726
11356
|
since: z.number().optional().describe("Timestamp filter for audit"),
|
|
11727
11357
|
force: z.boolean().optional().describe("Force re-run even if cache is fresh (verify)"),
|
|
11728
|
-
// Checkpoint params
|
|
11358
|
+
// Checkpoint params
|
|
11729
11359
|
label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label actions"),
|
|
11730
11360
|
description: z.string().optional().describe("Description for checkpoint")
|
|
11731
11361
|
},
|
|
@@ -11754,9 +11384,9 @@ function registerAllTools(server) {
|
|
|
11754
11384
|
}
|
|
11755
11385
|
}
|
|
11756
11386
|
);
|
|
11757
|
-
console.error("[Manifest] Registered 3
|
|
11758
|
-
console.error("[Manifest]
|
|
11759
|
-
console.error("[Manifest]
|
|
11387
|
+
console.error("[Manifest] Registered 3 coarse-grained tools (kuma_context, kuma_memory, kuma_safety).");
|
|
11388
|
+
console.error("[Manifest] Auto-init hooks installed on all tools.");
|
|
11389
|
+
console.error("[Manifest] Namespace aliases active (kuma_kuma_* \u2192 kuma_*, context \u2192 kuma_context).");
|
|
11760
11390
|
}
|
|
11761
11391
|
|
|
11762
11392
|
// 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.2",
|
|
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",
|