@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 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-2IIDVJPW.js";
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-JQT5GEWO.js");
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',
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-6W7YV4AF.js";
3
+ } from "./chunk-N6QMWZGQ.js";
4
4
  import {
5
5
  getProjectRoot
6
6
  } from "./chunk-E2KFPEBT.js";
@@ -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-PA7XVERC.js");
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-PA7XVERC.js");
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-6W7YV4AF.js";
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-VUTLTOFY.js");
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-ZDQVSBFB.js";
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-2IIDVJPW.js";
49
+ } from "./chunk-MTH4DRP7.js";
39
50
  import {
40
51
  formatDecisionTemplate,
41
52
  getProactiveMemories,
42
53
  recordDecision,
43
54
  scoreMemoryRelevance
44
- } from "./chunk-4CKTTORL.js";
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-6W7YV4AF.js";
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 { action } = params;
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-OQFYLLP7.js");
159
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-PA7XVERC.js");
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-OQFYLLP7.js");
333
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-PA7XVERC.js");
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 { action } = params;
366
- sessionMemory.recordToolCall("kuma_memory", { action });
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-YEIEXGNW.js");
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-VUTLTOFY.js");
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
- return await getChanges({ filePath: params.target, since: params.since });
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-OQFYLLP7.js");
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-3MBHSTWD.js");
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-VUTLTOFY.js");
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 { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-PA7XVERC.js");
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 (?, ?, ?)`,
@@ -4,7 +4,7 @@ import {
4
4
  formatInitResults,
5
5
  generateInitMdContent,
6
6
  runInit
7
- } from "./chunk-5WSPGLXS.js";
7
+ } from "./chunk-L5WU2HTN.js";
8
8
  import "./chunk-E2KFPEBT.js";
9
9
  export {
10
10
  ALL_CONFIG_TYPES,
@@ -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-2IIDVJPW.js";
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
  };
@@ -14,8 +14,8 @@ import {
14
14
  searchGraph,
15
15
  traceFlow,
16
16
  upsertNode
17
- } from "./chunk-ZDQVSBFB.js";
18
- import "./chunk-2IIDVJPW.js";
17
+ } from "./chunk-FSDW4F3U.js";
18
+ import "./chunk-MTH4DRP7.js";
19
19
  import "./chunk-E2KFPEBT.js";
20
20
  export {
21
21
  addEdge,
@@ -4,8 +4,8 @@ import {
4
4
  recordDecision,
5
5
  scoreMemoryRelevance,
6
6
  shouldRecordDecision
7
- } from "./chunk-4CKTTORL.js";
8
- import "./chunk-6W7YV4AF.js";
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-WG47POMW.js";
5
+ } from "./chunk-MSKQRKME.js";
6
6
  import {
7
7
  sessionMemory
8
- } from "./chunk-6W7YV4AF.js";
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-PA7XVERC.js");
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-PA7XVERC.js");
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
- if (!hasRunTests) {
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",
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-6W7YV4AF.js";
3
+ } from "./chunk-N6QMWZGQ.js";
4
4
  export {
5
5
  sessionMemory
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.3.1",
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",