@plumpslabs/kuma 2.3.1 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{chunk-ZDQVSBFB.js → chunk-FSDW4F3U.js} +2 -2
- package/dist/{chunk-5WSPGLXS.js → chunk-L5WU2HTN.js} +2 -0
- package/dist/{chunk-WG47POMW.js → chunk-MSKQRKME.js} +1 -1
- package/dist/{chunk-2IIDVJPW.js → chunk-MTH4DRP7.js} +68 -1
- package/dist/{chunk-6W7YV4AF.js → chunk-N6QMWZGQ.js} +2 -2
- package/dist/{chunk-4CKTTORL.js → chunk-RWPJ5RWM.js} +2 -2
- package/dist/index.js +213 -35
- package/dist/{init-3MBHSTWD.js → init-AJAESUZZ.js} +1 -1
- package/dist/{kumaDb-PA7XVERC.js → kumaDb-7VSQ4QLW.js} +5 -1
- package/dist/{kumaGraph-VUTLTOFY.js → kumaGraph-NHEAXC2V.js} +2 -2
- package/dist/{kumaMemory-YEIEXGNW.js → kumaMemory-UFRSJTWL.js} +2 -2
- package/dist/kumaMiner-UFDDVBRP.js +142 -0
- package/dist/kumaVerifier-Y4FQ2LOD.js +123 -0
- package/dist/{safetyScore-OQFYLLP7.js → safetyScore-GX2YMCQT.js} +30 -6
- package/dist/{sessionMemory-JQT5GEWO.js → sessionMemory-45V6XJRH.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,6 +122,7 @@ Kuma consolidates everything into **3 coarse-grained tools**. Each action trigge
|
|
|
122
122
|
| Action | Use case |
|
|
123
123
|
|--------|----------|
|
|
124
124
|
| `decision` | ADR-style recording: template, suggest, or record. |
|
|
125
|
+
| `mine` | Mine historical decisions from git log & inline code comments. |
|
|
125
126
|
| `research_save` | Persist research to graph + `.kuma/research/<scope>.json`. |
|
|
126
127
|
| `session` | "What happened this session?" — files, failures, progress. |
|
|
127
128
|
| `heal` | Self-heal knowledge graph — stale detection, git repair. |
|
|
@@ -133,6 +134,7 @@ Kuma consolidates everything into **3 coarse-grained tools**. Each action trigge
|
|
|
133
134
|
| Action | Use case |
|
|
134
135
|
|--------|----------|
|
|
135
136
|
| `guard` | Anti-pattern, drift, tool-loop, and failure checks. |
|
|
137
|
+
| `verify` | Integrated auto-verification — auto-detect runner & execute scoped tests. |
|
|
136
138
|
| `check` | Pre-execution safety: policy, path, lock, risk level. |
|
|
137
139
|
| `audit` | Query audit trail + stats + override log. |
|
|
138
140
|
| `lock` | Multi-agent file locking. |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDb,
|
|
3
3
|
saveDb
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MTH4DRP7.js";
|
|
5
5
|
import {
|
|
6
6
|
getProjectRoot
|
|
7
7
|
} from "./chunk-E2KFPEBT.js";
|
|
@@ -458,7 +458,7 @@ The two nodes may not be connected in the knowledge graph yet. Use more tools to
|
|
|
458
458
|
}
|
|
459
459
|
async function buildFromSessionMemory() {
|
|
460
460
|
try {
|
|
461
|
-
const { sessionMemory } = await import("./sessionMemory-
|
|
461
|
+
const { sessionMemory } = await import("./sessionMemory-45V6XJRH.js");
|
|
462
462
|
const toolCalls = sessionMemory.getToolCallHistory(50);
|
|
463
463
|
let edgeCount = 0;
|
|
464
464
|
for (const call of toolCalls) {
|
|
@@ -270,6 +270,7 @@ function generateInitMdContent() {
|
|
|
270
270
|
"",
|
|
271
271
|
'- `kuma_memory({ action: "research_save", scope: "...", ... })` \u2014 Save research results',
|
|
272
272
|
'- `kuma_memory({ action: "decision", decisionAction: "record", ... })` \u2014 ADR-style decision',
|
|
273
|
+
'- **`kuma_memory({ action: "mine", scope: "..." })` \u2014 Mine historical decisions from git log & comments**',
|
|
273
274
|
'- `kuma_memory({ action: "session" })` \u2014 Session summary',
|
|
274
275
|
'- `kuma_memory({ action: "heal" })` \u2014 Self-heal knowledge graph',
|
|
275
276
|
'- `kuma_memory({ action: "search", query: "..." })` \u2014 Search memories + graph',
|
|
@@ -278,6 +279,7 @@ function generateInitMdContent() {
|
|
|
278
279
|
"### \u{1F6E1}\uFE0F kuma_safety \u2014 Safety & Policy",
|
|
279
280
|
"",
|
|
280
281
|
'- `kuma_safety({ action: "guard", guardGoal: "..." })` \u2014 Anti-pattern, drift, loop detection',
|
|
282
|
+
'- **`kuma_safety({ action: "verify", scope: "..." })` \u2014 Auto-run scoped tests & verify correctness**',
|
|
281
283
|
'- `kuma_safety({ action: "check", ... })` \u2014 Pre-execution safety check',
|
|
282
284
|
'- `kuma_safety({ action: "audit" })` \u2014 Query safety audit trail',
|
|
283
285
|
'- `kuma_safety({ action: "lock", lockAction: "acquire", ... })` \u2014 Multi-agent file lock',
|
|
@@ -239,6 +239,39 @@ function createSchema(db) {
|
|
|
239
239
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
240
240
|
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
241
241
|
)`);
|
|
242
|
+
db.run(`CREATE TABLE IF NOT EXISTS otel_config (
|
|
243
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
244
|
+
endpoint TEXT,
|
|
245
|
+
service_name TEXT DEFAULT 'kuma',
|
|
246
|
+
enabled INTEGER DEFAULT 0,
|
|
247
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
248
|
+
)`);
|
|
249
|
+
db.run(`CREATE TABLE IF NOT EXISTS cost_tracking (
|
|
250
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
251
|
+
session_id INTEGER REFERENCES sessions(id),
|
|
252
|
+
tool_name TEXT NOT NULL,
|
|
253
|
+
token_estimate INTEGER DEFAULT 0,
|
|
254
|
+
cost_estimate REAL DEFAULT 0.0,
|
|
255
|
+
budget_limit REAL DEFAULT 0.0,
|
|
256
|
+
escalated INTEGER DEFAULT 0,
|
|
257
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
258
|
+
)`);
|
|
259
|
+
db.run(`CREATE TABLE IF NOT EXISTS scratch_entries (
|
|
260
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
261
|
+
file_path TEXT NOT NULL,
|
|
262
|
+
reason TEXT,
|
|
263
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
264
|
+
)`);
|
|
265
|
+
db.run(`CREATE TABLE IF NOT EXISTS verifications (
|
|
266
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
267
|
+
scope TEXT NOT NULL,
|
|
268
|
+
runner TEXT NOT NULL,
|
|
269
|
+
test_command TEXT NOT NULL,
|
|
270
|
+
passed INTEGER NOT NULL,
|
|
271
|
+
output TEXT,
|
|
272
|
+
duration_ms INTEGER,
|
|
273
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
274
|
+
)`);
|
|
242
275
|
db.run(`CREATE TABLE IF NOT EXISTS portability_entries (
|
|
243
276
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
244
277
|
entry_type TEXT NOT NULL,
|
|
@@ -804,6 +837,38 @@ async function saveHealthSnapshot(score, riskLevel, checks, summary) {
|
|
|
804
837
|
console.error(`[KumaDB] Failed to save health snapshot: ${err}`);
|
|
805
838
|
}
|
|
806
839
|
}
|
|
840
|
+
async function saveVerification(scope, runner, command, passed, output, durationMs = 0) {
|
|
841
|
+
try {
|
|
842
|
+
const db = await getDb();
|
|
843
|
+
db.run(
|
|
844
|
+
`INSERT INTO verifications (scope, runner, test_command, passed, output, duration_ms) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
845
|
+
[scope, runner, command, passed ? 1 : 0, output, durationMs]
|
|
846
|
+
);
|
|
847
|
+
saveDb();
|
|
848
|
+
} catch (err) {
|
|
849
|
+
console.error(`[KumaDB] Failed to save verification: ${err}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
async function getLatestVerifications(limit = 10) {
|
|
853
|
+
try {
|
|
854
|
+
const db = await getDb();
|
|
855
|
+
const res = db.exec(`SELECT id, scope, runner, test_command, passed, output, duration_ms, created_at FROM verifications ORDER BY created_at DESC LIMIT ${limit}`);
|
|
856
|
+
if (!res[0] || !res[0].values) return [];
|
|
857
|
+
return res[0].values.map((row) => ({
|
|
858
|
+
id: row[0],
|
|
859
|
+
scope: row[1],
|
|
860
|
+
runner: row[2],
|
|
861
|
+
test_command: row[3],
|
|
862
|
+
passed: Boolean(row[4]),
|
|
863
|
+
output: row[5],
|
|
864
|
+
duration_ms: row[6],
|
|
865
|
+
created_at: row[7]
|
|
866
|
+
}));
|
|
867
|
+
} catch (err) {
|
|
868
|
+
console.error(`[KumaDB] Failed to get verifications: ${err}`);
|
|
869
|
+
return [];
|
|
870
|
+
}
|
|
871
|
+
}
|
|
807
872
|
|
|
808
873
|
export {
|
|
809
874
|
getDb,
|
|
@@ -832,5 +897,7 @@ export {
|
|
|
832
897
|
runDoctor,
|
|
833
898
|
checkPortability,
|
|
834
899
|
ensureGitignore,
|
|
835
|
-
saveHealthSnapshot
|
|
900
|
+
saveHealthSnapshot,
|
|
901
|
+
saveVerification,
|
|
902
|
+
getLatestVerifications
|
|
836
903
|
};
|
|
@@ -320,7 +320,7 @@ No unresolved issues.`;
|
|
|
320
320
|
*/
|
|
321
321
|
async autoTrackToDb(toolName, params, timestamp) {
|
|
322
322
|
try {
|
|
323
|
-
const { getDb, saveDb } = await import("./kumaDb-
|
|
323
|
+
const { getDb, saveDb } = await import("./kumaDb-7VSQ4QLW.js");
|
|
324
324
|
const db = await getDb();
|
|
325
325
|
db.run(
|
|
326
326
|
`INSERT INTO tool_calls (tool_name, params, success, duration_ms, created_at) VALUES (?, ?, 1, 0, ?)`,
|
|
@@ -597,7 +597,7 @@ No unresolved issues.`;
|
|
|
597
597
|
this.addModifiedFile(filePath);
|
|
598
598
|
}
|
|
599
599
|
try {
|
|
600
|
-
const { recordChange } = await import("./kumaDb-
|
|
600
|
+
const { recordChange } = await import("./kumaDb-7VSQ4QLW.js");
|
|
601
601
|
const gitHash = this.getGitHead();
|
|
602
602
|
await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
|
|
603
603
|
} catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sessionMemory
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-N6QMWZGQ.js";
|
|
4
4
|
import {
|
|
5
5
|
getProjectRoot
|
|
6
6
|
} from "./chunk-E2KFPEBT.js";
|
|
@@ -82,7 +82,7 @@ function recordDecision(decision) {
|
|
|
82
82
|
}
|
|
83
83
|
async function recordDecisionToGraph(decision) {
|
|
84
84
|
try {
|
|
85
|
-
const { upsertNode, addEdge } = await import("./kumaGraph-
|
|
85
|
+
const { upsertNode, addEdge } = await import("./kumaGraph-NHEAXC2V.js");
|
|
86
86
|
const decisionId = `decision::${decision.title.replace(/[^a-zA-Z0-9_\-\s]/g, "").trim().replace(/\s+/g, "-")}`;
|
|
87
87
|
await upsertNode({
|
|
88
88
|
id: decisionId,
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ALL_CONFIG_TYPES,
|
|
4
|
+
formatInitResults,
|
|
5
|
+
runInit
|
|
6
|
+
} from "./chunk-L5WU2HTN.js";
|
|
2
7
|
import {
|
|
3
8
|
analyzeImpact,
|
|
4
9
|
autoHeal,
|
|
@@ -9,7 +14,13 @@ import {
|
|
|
9
14
|
getGraphStats,
|
|
10
15
|
searchGraph,
|
|
11
16
|
traceFlow
|
|
12
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-FSDW4F3U.js";
|
|
18
|
+
import {
|
|
19
|
+
buildDriftMessages,
|
|
20
|
+
getGitDiffStat,
|
|
21
|
+
getSessionStats,
|
|
22
|
+
getUnresolvedCount
|
|
23
|
+
} from "./chunk-MSKQRKME.js";
|
|
13
24
|
import {
|
|
14
25
|
addContextNote,
|
|
15
26
|
addSecurityFinding,
|
|
@@ -35,27 +46,16 @@ import {
|
|
|
35
46
|
saveResearchCache,
|
|
36
47
|
updateDecisionStatus,
|
|
37
48
|
updateTodoStatus
|
|
38
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-MTH4DRP7.js";
|
|
39
50
|
import {
|
|
40
51
|
formatDecisionTemplate,
|
|
41
52
|
getProactiveMemories,
|
|
42
53
|
recordDecision,
|
|
43
54
|
scoreMemoryRelevance
|
|
44
|
-
} from "./chunk-
|
|
45
|
-
import {
|
|
46
|
-
buildDriftMessages,
|
|
47
|
-
getGitDiffStat,
|
|
48
|
-
getSessionStats,
|
|
49
|
-
getUnresolvedCount
|
|
50
|
-
} from "./chunk-WG47POMW.js";
|
|
55
|
+
} from "./chunk-RWPJ5RWM.js";
|
|
51
56
|
import {
|
|
52
57
|
sessionMemory
|
|
53
|
-
} from "./chunk-
|
|
54
|
-
import {
|
|
55
|
-
ALL_CONFIG_TYPES,
|
|
56
|
-
formatInitResults,
|
|
57
|
-
runInit
|
|
58
|
-
} from "./chunk-5WSPGLXS.js";
|
|
58
|
+
} from "./chunk-N6QMWZGQ.js";
|
|
59
59
|
import {
|
|
60
60
|
getProjectRoot,
|
|
61
61
|
validateFilePath
|
|
@@ -74,8 +74,49 @@ import { z } from "zod";
|
|
|
74
74
|
import fs from "fs";
|
|
75
75
|
import path from "path";
|
|
76
76
|
import crypto from "crypto";
|
|
77
|
+
var CONTEXT_ALIASES = {
|
|
78
|
+
// Research synonyms
|
|
79
|
+
"research": "research",
|
|
80
|
+
"search": "research",
|
|
81
|
+
"explore": "research",
|
|
82
|
+
"inspect": "research",
|
|
83
|
+
// Impact synonyms
|
|
84
|
+
"analyze": "impact",
|
|
85
|
+
"impact": "impact",
|
|
86
|
+
"whatif": "impact",
|
|
87
|
+
"refactor": "impact",
|
|
88
|
+
// Navigate synonyms
|
|
89
|
+
"trace": "navigate",
|
|
90
|
+
"flow": "navigate",
|
|
91
|
+
"navigate": "navigate",
|
|
92
|
+
// Init synonyms
|
|
93
|
+
"init": "init",
|
|
94
|
+
"start": "init",
|
|
95
|
+
"load": "init",
|
|
96
|
+
"brief": "init",
|
|
97
|
+
"project": "init",
|
|
98
|
+
"summary": "init",
|
|
99
|
+
// Changes synonyms
|
|
100
|
+
"changes": "changes",
|
|
101
|
+
"log": "changes",
|
|
102
|
+
"history": "changes",
|
|
103
|
+
// Health synonyms
|
|
104
|
+
"health": "health",
|
|
105
|
+
"score": "health",
|
|
106
|
+
"status": "health",
|
|
107
|
+
// Rollback synonyms
|
|
108
|
+
"rollback": "rollback",
|
|
109
|
+
"undo": "rollback",
|
|
110
|
+
"revert": "rollback",
|
|
111
|
+
// Researches synonyms
|
|
112
|
+
"researches": "researches",
|
|
113
|
+
"research-list": "researches",
|
|
114
|
+
"list-research": "researches"
|
|
115
|
+
};
|
|
77
116
|
async function handleContext(params) {
|
|
78
|
-
const
|
|
117
|
+
const rawAction = params.action || "init";
|
|
118
|
+
const resolvedAction = CONTEXT_ALIASES[rawAction.toLowerCase()] || rawAction;
|
|
119
|
+
const action = resolvedAction;
|
|
79
120
|
switch (action) {
|
|
80
121
|
case "init":
|
|
81
122
|
return handleInit(params);
|
|
@@ -155,8 +196,8 @@ async function handleInit(_params) {
|
|
|
155
196
|
lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
|
|
156
197
|
lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
|
|
157
198
|
try {
|
|
158
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
159
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
199
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-GX2YMCQT.js");
|
|
200
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-7VSQ4QLW.js");
|
|
160
201
|
const score = await computeSafetyScore(_params.goal);
|
|
161
202
|
const checksStr = JSON.stringify(score.checks);
|
|
162
203
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -329,8 +370,8 @@ async function handleResearches(_params) {
|
|
|
329
370
|
async function handleHealth(_params) {
|
|
330
371
|
sessionMemory.recordToolCall("kuma_context_health", {});
|
|
331
372
|
try {
|
|
332
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
333
|
-
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-
|
|
373
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-GX2YMCQT.js");
|
|
374
|
+
const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-7VSQ4QLW.js");
|
|
334
375
|
const score = await computeSafetyScore();
|
|
335
376
|
const checksStr = JSON.stringify(score.checks);
|
|
336
377
|
await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
|
|
@@ -361,12 +402,77 @@ function computeProjectHash(scope) {
|
|
|
361
402
|
// src/tools/kumaMemoryTool.ts
|
|
362
403
|
import fs2 from "fs";
|
|
363
404
|
import path2 from "path";
|
|
405
|
+
var MEMORY_ALIASES = {
|
|
406
|
+
// Session synonyms
|
|
407
|
+
"session": "session",
|
|
408
|
+
"summary": "session",
|
|
409
|
+
"status": "session",
|
|
410
|
+
"get": "session",
|
|
411
|
+
"read": "session",
|
|
412
|
+
"fetch": "session",
|
|
413
|
+
"current": "session",
|
|
414
|
+
// Decision synonyms
|
|
415
|
+
"decision": "decision",
|
|
416
|
+
"adr": "decision",
|
|
417
|
+
"record-decision": "decision",
|
|
418
|
+
// Research save synonyms
|
|
419
|
+
"research_save": "research_save",
|
|
420
|
+
"save": "research_save",
|
|
421
|
+
"store": "research_save",
|
|
422
|
+
"write": "research_save",
|
|
423
|
+
"persist": "research_save",
|
|
424
|
+
"cache": "research_save",
|
|
425
|
+
// Mine synonyms
|
|
426
|
+
"mine": "mine",
|
|
427
|
+
"mine-decisions": "mine",
|
|
428
|
+
"git-mine": "mine",
|
|
429
|
+
"dig": "mine",
|
|
430
|
+
// Heal synonyms
|
|
431
|
+
"heal": "heal",
|
|
432
|
+
"repair": "heal",
|
|
433
|
+
"fix-graph": "heal",
|
|
434
|
+
"clean-graph": "heal",
|
|
435
|
+
// Search synonyms
|
|
436
|
+
"search": "search",
|
|
437
|
+
"find": "search",
|
|
438
|
+
"query": "search",
|
|
439
|
+
"lookup": "search",
|
|
440
|
+
// Todo synonyms
|
|
441
|
+
"todo": "todo",
|
|
442
|
+
"task": "todo",
|
|
443
|
+
"todos": "todo",
|
|
444
|
+
"tasks": "todo",
|
|
445
|
+
// Context notes synonyms
|
|
446
|
+
"context": "context",
|
|
447
|
+
"notes": "context",
|
|
448
|
+
"note": "context",
|
|
449
|
+
"inject": "context",
|
|
450
|
+
"context-note": "context",
|
|
451
|
+
// Benchmark synonyms
|
|
452
|
+
"benchmark": "benchmark",
|
|
453
|
+
"perf": "benchmark",
|
|
454
|
+
"metrics": "benchmark",
|
|
455
|
+
"measure": "benchmark",
|
|
456
|
+
// Decision log synonyms
|
|
457
|
+
"decision_log": "decision_log",
|
|
458
|
+
"decisions": "decision_log",
|
|
459
|
+
"log": "decision_log",
|
|
460
|
+
"decision-log": "decision_log",
|
|
461
|
+
// Changes synonyms
|
|
462
|
+
"changes": "changes",
|
|
463
|
+
"change-log": "changes",
|
|
464
|
+
"history": "changes"
|
|
465
|
+
};
|
|
364
466
|
async function handleMemory(params) {
|
|
365
|
-
const
|
|
366
|
-
|
|
467
|
+
const rawAction = params.action || "session";
|
|
468
|
+
const key = rawAction.toLowerCase().replace(/[\s_-]+/g, "-");
|
|
469
|
+
const action = MEMORY_ALIASES[key] || rawAction;
|
|
470
|
+
sessionMemory.recordToolCall("kuma_memory", { action: rawAction });
|
|
367
471
|
switch (action) {
|
|
368
472
|
case "decision":
|
|
369
473
|
return handleDecision(params);
|
|
474
|
+
case "mine":
|
|
475
|
+
return handleMine(params);
|
|
370
476
|
case "research_save":
|
|
371
477
|
return handleResearchSave(params);
|
|
372
478
|
case "session":
|
|
@@ -395,7 +501,7 @@ async function handleDecision(params) {
|
|
|
395
501
|
case "template":
|
|
396
502
|
return formatDecisionTemplate();
|
|
397
503
|
case "suggest": {
|
|
398
|
-
const { shouldRecordDecision } = await import("./kumaMemory-
|
|
504
|
+
const { shouldRecordDecision } = await import("./kumaMemory-UFRSJTWL.js");
|
|
399
505
|
const check = shouldRecordDecision();
|
|
400
506
|
return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
|
|
401
507
|
Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
|
|
@@ -473,7 +579,7 @@ async function handleSearch(params) {
|
|
|
473
579
|
if (!query) return "\u26A0\uFE0F query or scope parameter required.";
|
|
474
580
|
const limit = params.limit || 20;
|
|
475
581
|
const memResults = sessionMemory.searchMemory(query, limit);
|
|
476
|
-
const { searchGraph: searchGraph2 } = await import("./kumaGraph-
|
|
582
|
+
const { searchGraph: searchGraph2 } = await import("./kumaGraph-NHEAXC2V.js");
|
|
477
583
|
const graphResults = await searchGraph2(query, Math.min(limit, 10));
|
|
478
584
|
const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
|
|
479
585
|
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
@@ -487,7 +593,8 @@ async function handleSearch(params) {
|
|
|
487
593
|
return lines.join("\n");
|
|
488
594
|
}
|
|
489
595
|
async function handleChanges2(params) {
|
|
490
|
-
|
|
596
|
+
const sinceNum = typeof params.since === "number" ? params.since : typeof params.since === "string" ? parseInt(params.since, 10) || void 0 : void 0;
|
|
597
|
+
return await getChanges({ filePath: params.target, since: sinceNum });
|
|
491
598
|
}
|
|
492
599
|
async function handleTodo(params) {
|
|
493
600
|
if (params.todoId && params.status) {
|
|
@@ -545,6 +652,16 @@ async function handleDecisionLog(params) {
|
|
|
545
652
|
}
|
|
546
653
|
return await listDecisionLog(params.status);
|
|
547
654
|
}
|
|
655
|
+
async function handleMine(params) {
|
|
656
|
+
sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
|
|
657
|
+
const { mineHistoricalDecisions } = await import("./kumaMiner-UFDDVBRP.js");
|
|
658
|
+
return await mineHistoricalDecisions({
|
|
659
|
+
scope: params.scope,
|
|
660
|
+
since: typeof params.since === "string" ? params.since : void 0,
|
|
661
|
+
confirm: params.confirm,
|
|
662
|
+
limit: params.limit
|
|
663
|
+
});
|
|
664
|
+
}
|
|
548
665
|
|
|
549
666
|
// src/engine/kumaLock.ts
|
|
550
667
|
import fs3 from "fs";
|
|
@@ -1257,7 +1374,7 @@ function findPatchScripts(projectRoot) {
|
|
|
1257
1374
|
const ext = path5.extname(file).toLowerCase();
|
|
1258
1375
|
if (!SCRIPT_EXTENSIONS.includes(ext)) continue;
|
|
1259
1376
|
const relativePath = path5.relative(projectRoot, file);
|
|
1260
|
-
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".")) {
|
|
1377
|
+
if (relativePath.startsWith("src") || relativePath.startsWith("test") || relativePath.startsWith("node_modules") || relativePath.startsWith(".") || relativePath.startsWith(".kuma/scratch")) {
|
|
1261
1378
|
continue;
|
|
1262
1379
|
}
|
|
1263
1380
|
try {
|
|
@@ -1607,6 +1724,8 @@ async function handleSafety(params) {
|
|
|
1607
1724
|
switch (action) {
|
|
1608
1725
|
case "guard":
|
|
1609
1726
|
return handleGuard(params);
|
|
1727
|
+
case "verify":
|
|
1728
|
+
return handleVerify(params);
|
|
1610
1729
|
case "check":
|
|
1611
1730
|
return handleCheck(params);
|
|
1612
1731
|
case "audit":
|
|
@@ -1627,6 +1746,8 @@ async function handleSafety(params) {
|
|
|
1627
1746
|
return handlePortability(params);
|
|
1628
1747
|
case "gitignore":
|
|
1629
1748
|
return handleGitignore(params);
|
|
1749
|
+
case "clean":
|
|
1750
|
+
return handleClean(params);
|
|
1630
1751
|
default:
|
|
1631
1752
|
return `Unknown action "${action}".`;
|
|
1632
1753
|
}
|
|
@@ -1670,7 +1791,7 @@ async function handleLock(params) {
|
|
|
1670
1791
|
}
|
|
1671
1792
|
async function handleHealth2(_params) {
|
|
1672
1793
|
try {
|
|
1673
|
-
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-
|
|
1794
|
+
const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-GX2YMCQT.js");
|
|
1674
1795
|
const score = await computeSafetyScore();
|
|
1675
1796
|
await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
|
|
1676
1797
|
return formatSafetyScore(score);
|
|
@@ -1742,6 +1863,47 @@ async function handleGitignore(_params) {
|
|
|
1742
1863
|
sessionMemory.recordToolCall("kuma_safety_gitignore", {});
|
|
1743
1864
|
return await ensureGitignore();
|
|
1744
1865
|
}
|
|
1866
|
+
async function handleClean(_params) {
|
|
1867
|
+
sessionMemory.recordToolCall("kuma_safety_clean", {});
|
|
1868
|
+
const fs7 = await import("fs");
|
|
1869
|
+
const path7 = await import("path");
|
|
1870
|
+
const root = process.cwd();
|
|
1871
|
+
const scratchDir = path7.resolve(root, ".kuma", "scratch");
|
|
1872
|
+
let removed = 0;
|
|
1873
|
+
if (fs7.existsSync(scratchDir)) {
|
|
1874
|
+
try {
|
|
1875
|
+
const entries = fs7.readdirSync(scratchDir);
|
|
1876
|
+
for (const entry of entries) {
|
|
1877
|
+
const fullPath = path7.join(scratchDir, entry);
|
|
1878
|
+
try {
|
|
1879
|
+
const stat = fs7.statSync(fullPath);
|
|
1880
|
+
if (stat.isFile()) {
|
|
1881
|
+
fs7.unlinkSync(fullPath);
|
|
1882
|
+
removed++;
|
|
1883
|
+
} else if (stat.isDirectory()) {
|
|
1884
|
+
fs7.rmSync(fullPath, { recursive: true, force: true });
|
|
1885
|
+
removed++;
|
|
1886
|
+
}
|
|
1887
|
+
} catch {
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
} catch {
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
sessionMemory.setGoal(sessionMemory.getSummary().currentGoal || "cleaned");
|
|
1894
|
+
const result = removed > 0 ? `\u{1F9F9} **Scratch Clean** \u2014 Removed ${removed} item(s) from .kuma/scratch/` : `\u{1F9F9} **Scratch Clean** \u2014 No scratch files to clean.`;
|
|
1895
|
+
return `${result}
|
|
1896
|
+
\u{1F4A1} Drift warnings have been reset. Any temporary debug artifacts are now cleared.`;
|
|
1897
|
+
}
|
|
1898
|
+
async function handleVerify(params) {
|
|
1899
|
+
sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
|
|
1900
|
+
const { runAutoVerification } = await import("./kumaVerifier-Y4FQ2LOD.js");
|
|
1901
|
+
return await runAutoVerification({
|
|
1902
|
+
scope: params.scope || params.filePath,
|
|
1903
|
+
force: params.force,
|
|
1904
|
+
timeoutMs: 3e4
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1745
1907
|
|
|
1746
1908
|
// src/manifest.ts
|
|
1747
1909
|
function registerAllTools(server) {
|
|
@@ -1774,14 +1936,15 @@ function registerAllTools(server) {
|
|
|
1774
1936
|
);
|
|
1775
1937
|
server.tool(
|
|
1776
1938
|
"kuma_memory",
|
|
1777
|
-
"Record and retrieve project knowledge. Actions: decision (ADR-style record/template/suggest), research_save (save research), session (session summary), heal (graph repair), search (search all), changes (change log), todo (persistent todo CRUD), context (inject context notes), benchmark (capture/diff metrics), decision_log (living document with status tracking).",
|
|
1939
|
+
"Record and retrieve project knowledge. Actions: decision (ADR-style record/template/suggest), mine (mine historical decisions from git log & code comments), research_save (save research), session (session summary), heal (graph repair), search (search all), changes (change log), todo (persistent todo CRUD), context (inject context notes), benchmark (capture/diff metrics), decision_log (living document with status tracking).",
|
|
1778
1940
|
{
|
|
1779
|
-
action: z.enum(["decision", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log"]).describe("Memory action: decision=ADR, research_save=save, session=summary, heal=repair, search=search, changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions"),
|
|
1780
|
-
scope: z.string().optional().describe("Scope for research_save/search/todo/context"),
|
|
1941
|
+
action: z.enum(["decision", "mine", "research_save", "session", "heal", "search", "changes", "todo", "context", "benchmark", "decision_log"]).describe("Memory action: decision=ADR, mine=mine git log & comments, research_save=save, session=summary, heal=repair, search=search, changes=log, todo=manage todos, context=inject notes, benchmark=capture/diff, decision_log=manage decisions"),
|
|
1942
|
+
scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine"),
|
|
1781
1943
|
query: z.string().optional().describe("Search query for search action"),
|
|
1782
1944
|
content: z.string().optional().describe("Content/notes for research_save / context"),
|
|
1783
1945
|
record: z.string().optional().describe("JSON record string for research_save"),
|
|
1784
1946
|
confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
|
|
1947
|
+
confirm: z.boolean().optional().describe("Confirm and record candidates automatically for mine action"),
|
|
1785
1948
|
// Decision params
|
|
1786
1949
|
decisionAction: z.enum(["template", "suggest", "record"]).optional().describe("Decision sub-action"),
|
|
1787
1950
|
title: z.string().optional().describe("Decision/todo/benchmark title"),
|
|
@@ -1817,6 +1980,7 @@ function registerAllTools(server) {
|
|
|
1817
1980
|
content: params.content,
|
|
1818
1981
|
record: params.record,
|
|
1819
1982
|
confidence: params.confidence,
|
|
1983
|
+
confirm: params.confirm,
|
|
1820
1984
|
decisionAction: params.decisionAction,
|
|
1821
1985
|
title: params.title,
|
|
1822
1986
|
context: params.context,
|
|
@@ -1836,9 +2000,11 @@ function registerAllTools(server) {
|
|
|
1836
2000
|
);
|
|
1837
2001
|
server.tool(
|
|
1838
2002
|
"kuma_safety",
|
|
1839
|
-
"Safety checks, policy enforcement, security scanning, and project hygiene. Actions: guard (anti-pattern detection), check (pre-exec safety), audit (query trail), lock (multi-agent), health (score), security (scan for leaks), gc (garbage collection), doctor (health check), portability (path check), gitignore (auto-config), override (bypass).",
|
|
2003
|
+
"Safety checks, policy enforcement, security scanning, integrated auto-verification, and project hygiene. Actions: guard (anti-pattern detection), verify (auto-run scoped tests), check (pre-exec safety), audit (query trail), lock (multi-agent), health (score), security (scan for leaks), gc (garbage collection), doctor (health check), portability (path check), gitignore (auto-config), override (bypass).",
|
|
1840
2004
|
{
|
|
1841
|
-
action: z.enum(["guard", "check", "audit", "lock", "health", "override", "security", "gc", "doctor", "portability", "gitignore"]).describe("Safety action: guard=anti-patterns, check=pre-exec safety, audit=query trail, lock=multi-agent, health=score, security=scan leaks, gc=garbage collect, doctor=health check, portability=paths, gitignore=config, override=bypass"),
|
|
2005
|
+
action: z.enum(["guard", "verify", "check", "audit", "lock", "health", "override", "security", "gc", "doctor", "portability", "gitignore", "clean"]).describe("Safety action: guard=anti-patterns, verify=auto-run scoped tests, check=pre-exec safety, audit=query trail, lock=multi-agent, health=score, security=scan leaks, gc=garbage collect, doctor=health check, portability=paths, gitignore=config, clean=purge scratch dir & drift, override=bypass"),
|
|
2006
|
+
// Verify params
|
|
2007
|
+
scope: z.string().optional().describe("Scope for verify or context (e.g. 'auth', file path)"),
|
|
1842
2008
|
// Guard params
|
|
1843
2009
|
guardGoal: z.string().optional().describe("Goal for guard check"),
|
|
1844
2010
|
guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
|
|
@@ -1866,6 +2032,7 @@ function registerAllTools(server) {
|
|
|
1866
2032
|
guardGoal: params.guardGoal,
|
|
1867
2033
|
guardCheck: params.guardCheck,
|
|
1868
2034
|
filePath: params.filePath,
|
|
2035
|
+
scope: params.scope,
|
|
1869
2036
|
command: params.command,
|
|
1870
2037
|
toolName: params.toolName,
|
|
1871
2038
|
riskLevel: params.riskLevel,
|
|
@@ -2013,7 +2180,7 @@ async function main() {
|
|
|
2013
2180
|
});
|
|
2014
2181
|
(async () => {
|
|
2015
2182
|
try {
|
|
2016
|
-
const { generateInitMdContent } = await import("./init-
|
|
2183
|
+
const { generateInitMdContent } = await import("./init-AJAESUZZ.js");
|
|
2017
2184
|
const fs7 = await import("fs");
|
|
2018
2185
|
const path7 = await import("path");
|
|
2019
2186
|
const initMdPath = path7.resolve(process.cwd(), ".kuma/init.md");
|
|
@@ -2071,7 +2238,7 @@ async function main() {
|
|
|
2071
2238
|
console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
|
|
2072
2239
|
}
|
|
2073
2240
|
try {
|
|
2074
|
-
const { buildFromSessionMemory } = await import("./kumaGraph-
|
|
2241
|
+
const { buildFromSessionMemory } = await import("./kumaGraph-NHEAXC2V.js");
|
|
2075
2242
|
const edgeCount = await buildFromSessionMemory();
|
|
2076
2243
|
if (edgeCount > 0) {
|
|
2077
2244
|
console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
|
|
@@ -2080,7 +2247,18 @@ async function main() {
|
|
|
2080
2247
|
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
|
|
2081
2248
|
}
|
|
2082
2249
|
try {
|
|
2083
|
-
const
|
|
2250
|
+
const fs7 = await import("fs");
|
|
2251
|
+
const path7 = await import("path");
|
|
2252
|
+
const scratchDir = path7.resolve(process.cwd(), ".kuma", "scratch");
|
|
2253
|
+
if (!fs7.existsSync(scratchDir)) {
|
|
2254
|
+
fs7.mkdirSync(scratchDir, { recursive: true });
|
|
2255
|
+
console.error(`[${SERVER_NAME}] \u2705 Created .kuma/scratch/ for temporary debug artifacts`);
|
|
2256
|
+
}
|
|
2257
|
+
} catch (err) {
|
|
2258
|
+
console.error(`[${SERVER_NAME}] \u26A0\uFE0F Scratch directory setup: ${err}`);
|
|
2259
|
+
}
|
|
2260
|
+
try {
|
|
2261
|
+
const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-7VSQ4QLW.js");
|
|
2084
2262
|
const db = await getDb2();
|
|
2085
2263
|
db.run(
|
|
2086
2264
|
`INSERT INTO sessions (started_at, goal, tool_calls) VALUES (?, ?, ?)`,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getChanges,
|
|
9
9
|
getDb,
|
|
10
10
|
getFileSummary,
|
|
11
|
+
getLatestVerifications,
|
|
11
12
|
getResearchCache,
|
|
12
13
|
getSecurityFindings,
|
|
13
14
|
listContextNotes,
|
|
@@ -24,9 +25,10 @@ import {
|
|
|
24
25
|
saveFileSummary,
|
|
25
26
|
saveHealthSnapshot,
|
|
26
27
|
saveResearchCache,
|
|
28
|
+
saveVerification,
|
|
27
29
|
updateDecisionStatus,
|
|
28
30
|
updateTodoStatus
|
|
29
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-MTH4DRP7.js";
|
|
30
32
|
import "./chunk-E2KFPEBT.js";
|
|
31
33
|
export {
|
|
32
34
|
addContextNote,
|
|
@@ -38,6 +40,7 @@ export {
|
|
|
38
40
|
getChanges,
|
|
39
41
|
getDb,
|
|
40
42
|
getFileSummary,
|
|
43
|
+
getLatestVerifications,
|
|
41
44
|
getResearchCache,
|
|
42
45
|
getSecurityFindings,
|
|
43
46
|
listContextNotes,
|
|
@@ -54,6 +57,7 @@ export {
|
|
|
54
57
|
saveFileSummary,
|
|
55
58
|
saveHealthSnapshot,
|
|
56
59
|
saveResearchCache,
|
|
60
|
+
saveVerification,
|
|
57
61
|
updateDecisionStatus,
|
|
58
62
|
updateTodoStatus
|
|
59
63
|
};
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
recordDecision,
|
|
5
5
|
scoreMemoryRelevance,
|
|
6
6
|
shouldRecordDecision
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-RWPJ5RWM.js";
|
|
8
|
+
import "./chunk-N6QMWZGQ.js";
|
|
9
9
|
import "./chunk-E2KFPEBT.js";
|
|
10
10
|
export {
|
|
11
11
|
formatDecisionTemplate,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
recordDecisionLog
|
|
3
|
+
} from "./chunk-MTH4DRP7.js";
|
|
4
|
+
import {
|
|
5
|
+
recordDecision
|
|
6
|
+
} from "./chunk-RWPJ5RWM.js";
|
|
7
|
+
import "./chunk-N6QMWZGQ.js";
|
|
8
|
+
import "./chunk-E2KFPEBT.js";
|
|
9
|
+
|
|
10
|
+
// src/engine/kumaMiner.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { execSync } from "child_process";
|
|
14
|
+
import fastGlob from "fast-glob";
|
|
15
|
+
async function mineHistoricalDecisions(options = {}) {
|
|
16
|
+
const root = process.cwd();
|
|
17
|
+
const limit = options.limit || 15;
|
|
18
|
+
const candidates = [];
|
|
19
|
+
try {
|
|
20
|
+
const sinceFlag = options.since ? `--since="${options.since}"` : `--since="1 year"`;
|
|
21
|
+
const gitCmd = `git log ${sinceFlag} -n 100 --pretty=format:"%h|%an|%ad|%s" --date=short`;
|
|
22
|
+
const gitOutput = execSync(gitCmd, { cwd: root, encoding: "utf-8" });
|
|
23
|
+
const keywords = ["fix", "revert", "hack", "workaround", "urgent", "hotfix", "don't touch", "temporary", "deprecated", "workaround"];
|
|
24
|
+
const lines = gitOutput.split("\n").filter(Boolean);
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
const parts = line.split("|");
|
|
27
|
+
if (parts.length < 4) continue;
|
|
28
|
+
const [hash, author, date, subject] = parts;
|
|
29
|
+
const lowerSubj = subject.toLowerCase();
|
|
30
|
+
const matchedKeyword = keywords.find((kw) => lowerSubj.includes(kw));
|
|
31
|
+
if (matchedKeyword) {
|
|
32
|
+
let changedFiles = "";
|
|
33
|
+
try {
|
|
34
|
+
changedFiles = execSync(`git show --name-only --oneline ${hash}`, { cwd: root, encoding: "utf-8" }).split("\n").slice(1).filter(Boolean).slice(0, 3).join(", ");
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
candidates.push({
|
|
38
|
+
id: `git-${hash}`,
|
|
39
|
+
type: "git_commit",
|
|
40
|
+
title: `[Git Commit] ${subject}`,
|
|
41
|
+
context: `Commit ${hash} by ${author} on ${date}.${changedFiles ? ` Files touched: ${changedFiles}` : ""}`,
|
|
42
|
+
rationale: `Historical signal pattern "${matchedKeyword}" found in commit message.`,
|
|
43
|
+
commitHash: hash,
|
|
44
|
+
pattern: matchedKeyword
|
|
45
|
+
});
|
|
46
|
+
if (candidates.length >= limit) break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
if (candidates.length < limit) {
|
|
52
|
+
try {
|
|
53
|
+
const scopePattern = options.scope ? `**/*${options.scope}*.*` : "**/*.{ts,js,py,go,rs,java,md}";
|
|
54
|
+
const files = await fastGlob(scopePattern, {
|
|
55
|
+
cwd: root,
|
|
56
|
+
ignore: ["node_modules/**", "dist/**", ".kuma/**", ".git/**", "coverage/**"],
|
|
57
|
+
onlyFiles: true
|
|
58
|
+
});
|
|
59
|
+
const commentPatterns = [
|
|
60
|
+
{ tag: "HACK", regex: /\/\/\s*HACK:?\s*(.*)|#\s*HACK:?\s*(.*)/i },
|
|
61
|
+
{ tag: "FIXME", regex: /\/\/\s*FIXME:?\s*(.*)|#\s*FIXME:?\s*(.*)/i },
|
|
62
|
+
{ tag: "TODO", regex: /\/\/\s*TODO:?\s*(.*)|#\s*TODO:?\s*(.*)/i },
|
|
63
|
+
{ tag: "XXX", regex: /\/\/\s*XXX:?\s*(.*)|#\s*XXX:?\s*(.*)/i },
|
|
64
|
+
{ tag: "WARNING", regex: /\/\/\s*WARNING:?\s*(.*)|#\s*WARNING:?\s*(.*)/i }
|
|
65
|
+
];
|
|
66
|
+
for (const file of files.slice(0, 50)) {
|
|
67
|
+
if (candidates.length >= limit) break;
|
|
68
|
+
const fullPath = path.join(root, file);
|
|
69
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
70
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
71
|
+
const lines = content.split("\n");
|
|
72
|
+
for (let i = 0; i < lines.length; i++) {
|
|
73
|
+
const l = lines[i];
|
|
74
|
+
for (const cp of commentPatterns) {
|
|
75
|
+
const match = cp.regex.exec(l);
|
|
76
|
+
if (match) {
|
|
77
|
+
const text = (match[1] || match[2] || l).trim();
|
|
78
|
+
candidates.push({
|
|
79
|
+
id: `comment-${file}-${i + 1}`,
|
|
80
|
+
type: "inline_comment",
|
|
81
|
+
title: `[${cp.tag}] ${file}:${i + 1}`,
|
|
82
|
+
context: `Inline comment marker "${cp.tag}" found in file "${file}" at line ${i + 1}.`,
|
|
83
|
+
rationale: text || l.trim(),
|
|
84
|
+
filePath: file,
|
|
85
|
+
lineNumber: i + 1,
|
|
86
|
+
pattern: cp.tag
|
|
87
|
+
});
|
|
88
|
+
if (candidates.length >= limit) break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (candidates.length >= limit) break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (options.confirm && candidates.length > 0) {
|
|
98
|
+
let recordedCount = 0;
|
|
99
|
+
for (const c of candidates) {
|
|
100
|
+
await recordDecisionLog({
|
|
101
|
+
title: c.title,
|
|
102
|
+
context: c.context,
|
|
103
|
+
rationale: c.rationale,
|
|
104
|
+
outcome: `Mined from ${c.type} (${c.pattern})`,
|
|
105
|
+
status: "proposed"
|
|
106
|
+
});
|
|
107
|
+
await recordDecision({
|
|
108
|
+
title: c.title,
|
|
109
|
+
context: c.context,
|
|
110
|
+
options: [],
|
|
111
|
+
rationale: c.rationale,
|
|
112
|
+
outcome: `Mined from ${c.type}`,
|
|
113
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
114
|
+
});
|
|
115
|
+
recordedCount++;
|
|
116
|
+
}
|
|
117
|
+
return `\u26CF\uFE0F **Mined & Recorded ${recordedCount} Decisions** into decision log & graph.
|
|
118
|
+
Use \`kuma_memory({ action: 'decision_log' })\` to view all active & proposed decisions.`;
|
|
119
|
+
}
|
|
120
|
+
if (candidates.length === 0) {
|
|
121
|
+
return "\u26CF\uFE0F **Decision Mining**: No significant historical decision markers (HACK/FIXME/git fix) found for the requested scope.";
|
|
122
|
+
}
|
|
123
|
+
const report = [
|
|
124
|
+
`\u26CF\uFE0F **Decision Mining Candidates** (${candidates.length} candidate(s) found)`,
|
|
125
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
126
|
+
"Review these draft historical decisions mined from git history & inline comments:",
|
|
127
|
+
""
|
|
128
|
+
];
|
|
129
|
+
candidates.forEach((c, idx) => {
|
|
130
|
+
report.push(`**${idx + 1}. ${c.title}**`);
|
|
131
|
+
report.push(` \u2022 **Type**: \`${c.type}\` | **Signal**: \`${c.pattern}\``);
|
|
132
|
+
report.push(` \u2022 **Context**: ${c.context}`);
|
|
133
|
+
report.push(` \u2022 **Rationale**: ${c.rationale}`);
|
|
134
|
+
report.push("");
|
|
135
|
+
});
|
|
136
|
+
report.push("\u{1F4A1} *To accept and record these mined decisions into knowledge graph & decision.md:*");
|
|
137
|
+
report.push(`Run: \`kuma_memory({ action: 'mine', confirm: true${options.scope ? `, scope: '${options.scope}'` : ""} })\``);
|
|
138
|
+
return report.join("\n");
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
mineHistoricalDecisions
|
|
142
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
saveVerification
|
|
3
|
+
} from "./chunk-MTH4DRP7.js";
|
|
4
|
+
import {
|
|
5
|
+
sessionMemory
|
|
6
|
+
} from "./chunk-N6QMWZGQ.js";
|
|
7
|
+
import "./chunk-E2KFPEBT.js";
|
|
8
|
+
|
|
9
|
+
// src/engine/kumaVerifier.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { exec } from "child_process";
|
|
13
|
+
function detectTestRunner(root = process.cwd()) {
|
|
14
|
+
const pkgPath = path.join(root, "package.json");
|
|
15
|
+
if (fs.existsSync(pkgPath)) {
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
18
|
+
if (pkg.scripts && pkg.scripts.test) {
|
|
19
|
+
if (fs.existsSync(path.join(root, "pnpm-lock.yaml"))) {
|
|
20
|
+
return { runner: "pnpm", baseCommand: "pnpm test" };
|
|
21
|
+
}
|
|
22
|
+
if (fs.existsSync(path.join(root, "yarn.lock"))) {
|
|
23
|
+
return { runner: "yarn", baseCommand: "yarn test" };
|
|
24
|
+
}
|
|
25
|
+
return { runner: "npm", baseCommand: "npm test" };
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (fs.existsSync(path.join(root, "pytest.ini")) || fs.existsSync(path.join(root, "pyproject.toml"))) {
|
|
31
|
+
return { runner: "pytest", baseCommand: "pytest" };
|
|
32
|
+
}
|
|
33
|
+
if (fs.existsSync(path.join(root, "Cargo.toml"))) {
|
|
34
|
+
return { runner: "cargo", baseCommand: "cargo test" };
|
|
35
|
+
}
|
|
36
|
+
if (fs.existsSync(path.join(root, "go.mod"))) {
|
|
37
|
+
return { runner: "go", baseCommand: "go test ./..." };
|
|
38
|
+
}
|
|
39
|
+
if (fs.existsSync(path.join(root, "Makefile"))) {
|
|
40
|
+
return { runner: "make", baseCommand: "make test" };
|
|
41
|
+
}
|
|
42
|
+
if (fs.existsSync(path.join(root, "package.json"))) {
|
|
43
|
+
try {
|
|
44
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8"));
|
|
45
|
+
if (pkg.dependencies || pkg.devDependencies) {
|
|
46
|
+
if (fs.existsSync(path.join(root, "tsconfig.json"))) {
|
|
47
|
+
return { runner: "tsc", baseCommand: "npx tsc --noEmit" };
|
|
48
|
+
}
|
|
49
|
+
const srcDir = path.join(root, "src");
|
|
50
|
+
if (fs.existsSync(srcDir)) {
|
|
51
|
+
return { runner: "node", baseCommand: `node -c "${srcDir}/**/*.js" 2>/dev/null || node --check $(find src -name '*.js' 2>/dev/null | head -5)` };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { runner: "unknown", baseCommand: "npm test || echo 'No test runner configured'" };
|
|
58
|
+
}
|
|
59
|
+
async function runAutoVerification(options = {}) {
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
const root = process.cwd();
|
|
62
|
+
const scope = options.scope || "session-impact";
|
|
63
|
+
const timeoutMs = options.timeoutMs || 3e4;
|
|
64
|
+
const { runner, baseCommand } = detectTestRunner(root);
|
|
65
|
+
let testFiles = [];
|
|
66
|
+
const modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
|
|
67
|
+
if (options.scope) {
|
|
68
|
+
const term = options.scope.toLowerCase();
|
|
69
|
+
testFiles = modified.filter((f) => f.toLowerCase().includes(term));
|
|
70
|
+
if (testFiles.length === 0) {
|
|
71
|
+
try {
|
|
72
|
+
const { default: glob } = await import("fast-glob");
|
|
73
|
+
const found = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
|
|
74
|
+
testFiles = found;
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
testFiles = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
|
|
80
|
+
}
|
|
81
|
+
let fullCommand = baseCommand;
|
|
82
|
+
if (testFiles.length > 0 && runner === "npm") {
|
|
83
|
+
fullCommand = `${baseCommand} -- ${testFiles.map((f) => `"${f}"`).join(" ")}`;
|
|
84
|
+
}
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
exec(fullCommand, { cwd: root, timeout: timeoutMs }, async (error, stdout, stderr) => {
|
|
87
|
+
const durationMs = Date.now() - startTime;
|
|
88
|
+
const rawOutput = (stdout + "\n" + stderr).trim();
|
|
89
|
+
const passed = !error;
|
|
90
|
+
const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
|
|
91
|
+
await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
|
|
92
|
+
const statusSymbol = passed ? "\u2705" : "\u{1F534}";
|
|
93
|
+
const summaryHeader = `${statusSymbol} **Auto-Verification ${passed ? "PASSED" : "FAILED"}**
|
|
94
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
|
|
95
|
+
const details = [
|
|
96
|
+
summaryHeader,
|
|
97
|
+
`\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
|
|
98
|
+
`\u{1F4BB} **Command**: \`${fullCommand}\``,
|
|
99
|
+
`\u{1F3AF} **Scope**: \`${scope}\`${testFiles.length > 0 ? ` (${testFiles.length} file(s) matched)` : ""}`,
|
|
100
|
+
`\u23F1\uFE0F **Duration**: ${durationMs}ms`,
|
|
101
|
+
""
|
|
102
|
+
];
|
|
103
|
+
if (!passed) {
|
|
104
|
+
details.push("\u26A0\uFE0F **BLOCKER**: Verification failed! Please fix test failures before shipping or continuing edits.");
|
|
105
|
+
details.push("```text");
|
|
106
|
+
details.push(rawOutput.substring(0, 1500));
|
|
107
|
+
details.push("```");
|
|
108
|
+
} else {
|
|
109
|
+
details.push("\u{1F389} All scoped tests passed cleanly!");
|
|
110
|
+
if (rawOutput) {
|
|
111
|
+
details.push("```text");
|
|
112
|
+
details.push(rawOutput.substring(0, 800));
|
|
113
|
+
details.push("```");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
resolve(details.join("\n"));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
detectTestRunner,
|
|
122
|
+
runAutoVerification
|
|
123
|
+
};
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
getGitDiffStat,
|
|
3
3
|
getSessionStats,
|
|
4
4
|
getUnresolvedCount
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MSKQRKME.js";
|
|
6
6
|
import {
|
|
7
7
|
sessionMemory
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-N6QMWZGQ.js";
|
|
9
9
|
import "./chunk-E2KFPEBT.js";
|
|
10
10
|
|
|
11
11
|
// src/engine/safetyScore.ts
|
|
@@ -52,7 +52,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
52
52
|
totalScore += 20;
|
|
53
53
|
}
|
|
54
54
|
try {
|
|
55
|
-
const { getDb } = await import("./kumaDb-
|
|
55
|
+
const { getDb } = await import("./kumaDb-7VSQ4QLW.js");
|
|
56
56
|
const db = await getDb();
|
|
57
57
|
const nodeCount = db.exec("SELECT COUNT(*) as c FROM nodes")[0]?.values[0][0] ?? 0;
|
|
58
58
|
const edgeCount = db.exec("SELECT COUNT(*) as c FROM edges")[0]?.values[0][0] ?? 0;
|
|
@@ -83,7 +83,7 @@ async function computeSafetyScore(inputGoal) {
|
|
|
83
83
|
totalScore += 5;
|
|
84
84
|
}
|
|
85
85
|
try {
|
|
86
|
-
const { getDb } = await import("./kumaDb-
|
|
86
|
+
const { getDb } = await import("./kumaDb-7VSQ4QLW.js");
|
|
87
87
|
const db = await getDb();
|
|
88
88
|
const researchCount = db.exec("SELECT COUNT(*) as c FROM research_cache")[0]?.values[0][0] ?? 0;
|
|
89
89
|
checks.push({
|
|
@@ -106,14 +106,38 @@ async function computeSafetyScore(inputGoal) {
|
|
|
106
106
|
const allFailures = stats.failedFiles.flatMap((f) => f.failures);
|
|
107
107
|
const testFailures = allFailures.filter((f) => f.error.toLowerCase().includes("test") || f.error.toLowerCase().includes("fail"));
|
|
108
108
|
const hasRunTests = stats.hasRunTests;
|
|
109
|
-
|
|
109
|
+
let latestVerif = null;
|
|
110
|
+
try {
|
|
111
|
+
const { getLatestVerifications } = await import("./kumaDb-7VSQ4QLW.js");
|
|
112
|
+
const verifs = await getLatestVerifications(1);
|
|
113
|
+
if (verifs.length > 0) latestVerif = verifs[0];
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
if (!hasRunTests && !latestVerif) {
|
|
110
117
|
checks.push({
|
|
111
118
|
label: "Tests Status",
|
|
112
119
|
status: "warn",
|
|
113
|
-
message: "No tests run yet this session",
|
|
120
|
+
message: "No tests run yet this session \u2014 run kuma_safety({ action: 'verify' })",
|
|
114
121
|
weight: 10
|
|
115
122
|
});
|
|
116
123
|
totalScore += 10;
|
|
124
|
+
} else if (latestVerif) {
|
|
125
|
+
if (latestVerif.passed) {
|
|
126
|
+
checks.push({
|
|
127
|
+
label: "Tests Status",
|
|
128
|
+
status: "pass",
|
|
129
|
+
message: `Auto-verified passed (${latestVerif.scope})`,
|
|
130
|
+
weight: 15
|
|
131
|
+
});
|
|
132
|
+
totalScore += 15;
|
|
133
|
+
} else {
|
|
134
|
+
checks.push({
|
|
135
|
+
label: "Tests Status",
|
|
136
|
+
status: "fail",
|
|
137
|
+
message: `Auto-verification failed (${latestVerif.scope}) \u2014 fix before shipping`,
|
|
138
|
+
weight: 0
|
|
139
|
+
});
|
|
140
|
+
}
|
|
117
141
|
} else if (testFailures.length === 0 && unresolvedCount === 0) {
|
|
118
142
|
checks.push({
|
|
119
143
|
label: "Tests Status",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
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",
|