@plumpslabs/kuma 2.3.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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. |
@@ -239,6 +239,16 @@ 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 verifications (
243
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
244
+ scope TEXT NOT NULL,
245
+ runner TEXT NOT NULL,
246
+ test_command TEXT NOT NULL,
247
+ passed INTEGER NOT NULL,
248
+ output TEXT,
249
+ duration_ms INTEGER,
250
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
251
+ )`);
242
252
  db.run(`CREATE TABLE IF NOT EXISTS portability_entries (
243
253
  id INTEGER PRIMARY KEY AUTOINCREMENT,
244
254
  entry_type TEXT NOT NULL,
@@ -804,6 +814,38 @@ async function saveHealthSnapshot(score, riskLevel, checks, summary) {
804
814
  console.error(`[KumaDB] Failed to save health snapshot: ${err}`);
805
815
  }
806
816
  }
817
+ async function saveVerification(scope, runner, command, passed, output, durationMs = 0) {
818
+ try {
819
+ const db = await getDb();
820
+ db.run(
821
+ `INSERT INTO verifications (scope, runner, test_command, passed, output, duration_ms) VALUES (?, ?, ?, ?, ?, ?)`,
822
+ [scope, runner, command, passed ? 1 : 0, output, durationMs]
823
+ );
824
+ saveDb();
825
+ } catch (err) {
826
+ console.error(`[KumaDB] Failed to save verification: ${err}`);
827
+ }
828
+ }
829
+ async function getLatestVerifications(limit = 10) {
830
+ try {
831
+ const db = await getDb();
832
+ 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}`);
833
+ if (!res[0] || !res[0].values) return [];
834
+ return res[0].values.map((row) => ({
835
+ id: row[0],
836
+ scope: row[1],
837
+ runner: row[2],
838
+ test_command: row[3],
839
+ passed: Boolean(row[4]),
840
+ output: row[5],
841
+ duration_ms: row[6],
842
+ created_at: row[7]
843
+ }));
844
+ } catch (err) {
845
+ console.error(`[KumaDB] Failed to get verifications: ${err}`);
846
+ return [];
847
+ }
848
+ }
807
849
 
808
850
  export {
809
851
  getDb,
@@ -832,5 +874,7 @@ export {
832
874
  runDoctor,
833
875
  checkPortability,
834
876
  ensureGitignore,
835
- saveHealthSnapshot
877
+ saveHealthSnapshot,
878
+ saveVerification,
879
+ getLatestVerifications
836
880
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-6W7YV4AF.js";
3
+ } from "./chunk-SU7BTOND.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-5IZ4FDZA.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,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getDb,
3
3
  saveDb
4
- } from "./chunk-2IIDVJPW.js";
4
+ } from "./chunk-5N6KXACT.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-LI4FIAMF.js");
462
462
  const toolCalls = sessionMemory.getToolCallHistory(50);
463
463
  let edgeCount = 0;
464
464
  for (const call of toolCalls) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionMemory
3
- } from "./chunk-6W7YV4AF.js";
3
+ } from "./chunk-SU7BTOND.js";
4
4
  import {
5
5
  getProjectRoot
6
6
  } from "./chunk-E2KFPEBT.js";
@@ -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',
@@ -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-RC2EO3OB.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-RC2EO3OB.js");
601
601
  const gitHash = this.getGitHead();
602
602
  await recordChange({ filePath, changeType, symbol, gitCommitHash: gitHash || void 0 });
603
603
  } catch {
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-EQ2CE4UC.js";
18
+ import {
19
+ buildDriftMessages,
20
+ getGitDiffStat,
21
+ getSessionStats,
22
+ getUnresolvedCount
23
+ } from "./chunk-JIL533AM.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-5N6KXACT.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-EIK7T746.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-SU7BTOND.js";
59
59
  import {
60
60
  getProjectRoot,
61
61
  validateFilePath
@@ -155,8 +155,8 @@ async function handleInit(_params) {
155
155
  lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
156
156
  lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
157
157
  try {
158
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-OQFYLLP7.js");
159
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-PA7XVERC.js");
158
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-QX2LBJ7H.js");
159
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-RC2EO3OB.js");
160
160
  const score = await computeSafetyScore(_params.goal);
161
161
  const checksStr = JSON.stringify(score.checks);
162
162
  await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
@@ -329,8 +329,8 @@ async function handleResearches(_params) {
329
329
  async function handleHealth(_params) {
330
330
  sessionMemory.recordToolCall("kuma_context_health", {});
331
331
  try {
332
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-OQFYLLP7.js");
333
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-PA7XVERC.js");
332
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-QX2LBJ7H.js");
333
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-RC2EO3OB.js");
334
334
  const score = await computeSafetyScore();
335
335
  const checksStr = JSON.stringify(score.checks);
336
336
  await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
@@ -367,6 +367,8 @@ async function handleMemory(params) {
367
367
  switch (action) {
368
368
  case "decision":
369
369
  return handleDecision(params);
370
+ case "mine":
371
+ return handleMine(params);
370
372
  case "research_save":
371
373
  return handleResearchSave(params);
372
374
  case "session":
@@ -395,7 +397,7 @@ async function handleDecision(params) {
395
397
  case "template":
396
398
  return formatDecisionTemplate();
397
399
  case "suggest": {
398
- const { shouldRecordDecision } = await import("./kumaMemory-YEIEXGNW.js");
400
+ const { shouldRecordDecision } = await import("./kumaMemory-ME4XWD5Z.js");
399
401
  const check = shouldRecordDecision();
400
402
  return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
401
403
  Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
@@ -473,7 +475,7 @@ async function handleSearch(params) {
473
475
  if (!query) return "\u26A0\uFE0F query or scope parameter required.";
474
476
  const limit = params.limit || 20;
475
477
  const memResults = sessionMemory.searchMemory(query, limit);
476
- const { searchGraph: searchGraph2 } = await import("./kumaGraph-VUTLTOFY.js");
478
+ const { searchGraph: searchGraph2 } = await import("./kumaGraph-5IZ4FDZA.js");
477
479
  const graphResults = await searchGraph2(query, Math.min(limit, 10));
478
480
  const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
479
481
  \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 +489,8 @@ async function handleSearch(params) {
487
489
  return lines.join("\n");
488
490
  }
489
491
  async function handleChanges2(params) {
490
- return await getChanges({ filePath: params.target, since: params.since });
492
+ const sinceNum = typeof params.since === "number" ? params.since : typeof params.since === "string" ? parseInt(params.since, 10) || void 0 : void 0;
493
+ return await getChanges({ filePath: params.target, since: sinceNum });
491
494
  }
492
495
  async function handleTodo(params) {
493
496
  if (params.todoId && params.status) {
@@ -545,6 +548,16 @@ async function handleDecisionLog(params) {
545
548
  }
546
549
  return await listDecisionLog(params.status);
547
550
  }
551
+ async function handleMine(params) {
552
+ sessionMemory.recordToolCall("kuma_memory_mine", { scope: params.scope });
553
+ const { mineHistoricalDecisions } = await import("./kumaMiner-GFEGUWGH.js");
554
+ return await mineHistoricalDecisions({
555
+ scope: params.scope,
556
+ since: typeof params.since === "string" ? params.since : void 0,
557
+ confirm: params.confirm,
558
+ limit: params.limit
559
+ });
560
+ }
548
561
 
549
562
  // src/engine/kumaLock.ts
550
563
  import fs3 from "fs";
@@ -1607,6 +1620,8 @@ async function handleSafety(params) {
1607
1620
  switch (action) {
1608
1621
  case "guard":
1609
1622
  return handleGuard(params);
1623
+ case "verify":
1624
+ return handleVerify(params);
1610
1625
  case "check":
1611
1626
  return handleCheck(params);
1612
1627
  case "audit":
@@ -1670,7 +1685,7 @@ async function handleLock(params) {
1670
1685
  }
1671
1686
  async function handleHealth2(_params) {
1672
1687
  try {
1673
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-OQFYLLP7.js");
1688
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-QX2LBJ7H.js");
1674
1689
  const score = await computeSafetyScore();
1675
1690
  await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
1676
1691
  return formatSafetyScore(score);
@@ -1742,6 +1757,15 @@ async function handleGitignore(_params) {
1742
1757
  sessionMemory.recordToolCall("kuma_safety_gitignore", {});
1743
1758
  return await ensureGitignore();
1744
1759
  }
1760
+ async function handleVerify(params) {
1761
+ sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope || params.filePath });
1762
+ const { runAutoVerification } = await import("./kumaVerifier-VYW5WH6V.js");
1763
+ return await runAutoVerification({
1764
+ scope: params.scope || params.filePath,
1765
+ force: params.force,
1766
+ timeoutMs: 3e4
1767
+ });
1768
+ }
1745
1769
 
1746
1770
  // src/manifest.ts
1747
1771
  function registerAllTools(server) {
@@ -1774,14 +1798,15 @@ function registerAllTools(server) {
1774
1798
  );
1775
1799
  server.tool(
1776
1800
  "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).",
1801
+ "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
1802
  {
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"),
1803
+ 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"),
1804
+ scope: z.string().optional().describe("Scope for research_save/search/todo/context/mine"),
1781
1805
  query: z.string().optional().describe("Search query for search action"),
1782
1806
  content: z.string().optional().describe("Content/notes for research_save / context"),
1783
1807
  record: z.string().optional().describe("JSON record string for research_save"),
1784
1808
  confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
1809
+ confirm: z.boolean().optional().describe("Confirm and record candidates automatically for mine action"),
1785
1810
  // Decision params
1786
1811
  decisionAction: z.enum(["template", "suggest", "record"]).optional().describe("Decision sub-action"),
1787
1812
  title: z.string().optional().describe("Decision/todo/benchmark title"),
@@ -1817,6 +1842,7 @@ function registerAllTools(server) {
1817
1842
  content: params.content,
1818
1843
  record: params.record,
1819
1844
  confidence: params.confidence,
1845
+ confirm: params.confirm,
1820
1846
  decisionAction: params.decisionAction,
1821
1847
  title: params.title,
1822
1848
  context: params.context,
@@ -1836,9 +1862,11 @@ function registerAllTools(server) {
1836
1862
  );
1837
1863
  server.tool(
1838
1864
  "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).",
1865
+ "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
1866
  {
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"),
1867
+ action: z.enum(["guard", "verify", "check", "audit", "lock", "health", "override", "security", "gc", "doctor", "portability", "gitignore"]).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, override=bypass"),
1868
+ // Verify params
1869
+ scope: z.string().optional().describe("Scope for verify or context (e.g. 'auth', file path)"),
1842
1870
  // Guard params
1843
1871
  guardGoal: z.string().optional().describe("Goal for guard check"),
1844
1872
  guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
@@ -1866,6 +1894,7 @@ function registerAllTools(server) {
1866
1894
  guardGoal: params.guardGoal,
1867
1895
  guardCheck: params.guardCheck,
1868
1896
  filePath: params.filePath,
1897
+ scope: params.scope,
1869
1898
  command: params.command,
1870
1899
  toolName: params.toolName,
1871
1900
  riskLevel: params.riskLevel,
@@ -2013,7 +2042,7 @@ async function main() {
2013
2042
  });
2014
2043
  (async () => {
2015
2044
  try {
2016
- const { generateInitMdContent } = await import("./init-3MBHSTWD.js");
2045
+ const { generateInitMdContent } = await import("./init-AJAESUZZ.js");
2017
2046
  const fs7 = await import("fs");
2018
2047
  const path7 = await import("path");
2019
2048
  const initMdPath = path7.resolve(process.cwd(), ".kuma/init.md");
@@ -2071,7 +2100,7 @@ async function main() {
2071
2100
  console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
2072
2101
  }
2073
2102
  try {
2074
- const { buildFromSessionMemory } = await import("./kumaGraph-VUTLTOFY.js");
2103
+ const { buildFromSessionMemory } = await import("./kumaGraph-5IZ4FDZA.js");
2075
2104
  const edgeCount = await buildFromSessionMemory();
2076
2105
  if (edgeCount > 0) {
2077
2106
  console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
@@ -2080,7 +2109,7 @@ async function main() {
2080
2109
  console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
2081
2110
  }
2082
2111
  try {
2083
- const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-PA7XVERC.js");
2112
+ const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-RC2EO3OB.js");
2084
2113
  const db = await getDb2();
2085
2114
  db.run(
2086
2115
  `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-5N6KXACT.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-EQ2CE4UC.js";
18
+ import "./chunk-5N6KXACT.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-EIK7T746.js";
8
+ import "./chunk-SU7BTOND.js";
9
9
  import "./chunk-E2KFPEBT.js";
10
10
  export {
11
11
  formatDecisionTemplate,
@@ -0,0 +1,142 @@
1
+ import {
2
+ recordDecisionLog
3
+ } from "./chunk-5N6KXACT.js";
4
+ import {
5
+ recordDecision
6
+ } from "./chunk-EIK7T746.js";
7
+ import "./chunk-SU7BTOND.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,108 @@
1
+ import {
2
+ saveVerification
3
+ } from "./chunk-5N6KXACT.js";
4
+ import {
5
+ sessionMemory
6
+ } from "./chunk-SU7BTOND.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
+ return { runner: "unknown", baseCommand: "npm test" };
43
+ }
44
+ async function runAutoVerification(options = {}) {
45
+ const startTime = Date.now();
46
+ const root = process.cwd();
47
+ const scope = options.scope || "session-impact";
48
+ const timeoutMs = options.timeoutMs || 3e4;
49
+ const { runner, baseCommand } = detectTestRunner(root);
50
+ let testFiles = [];
51
+ const modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
52
+ if (options.scope) {
53
+ const term = options.scope.toLowerCase();
54
+ testFiles = modified.filter((f) => f.toLowerCase().includes(term));
55
+ if (testFiles.length === 0) {
56
+ try {
57
+ const { default: glob } = await import("fast-glob");
58
+ const found = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
59
+ testFiles = found;
60
+ } catch {
61
+ }
62
+ }
63
+ } else {
64
+ testFiles = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
65
+ }
66
+ let fullCommand = baseCommand;
67
+ if (testFiles.length > 0 && runner === "npm") {
68
+ fullCommand = `${baseCommand} -- ${testFiles.map((f) => `"${f}"`).join(" ")}`;
69
+ }
70
+ return new Promise((resolve) => {
71
+ exec(fullCommand, { cwd: root, timeout: timeoutMs }, async (error, stdout, stderr) => {
72
+ const durationMs = Date.now() - startTime;
73
+ const rawOutput = (stdout + "\n" + stderr).trim();
74
+ const passed = !error;
75
+ const truncatedOutput = rawOutput.length > 2e3 ? rawOutput.substring(rawOutput.length - 2e3) : rawOutput;
76
+ await saveVerification(scope, runner, fullCommand, passed, truncatedOutput, durationMs);
77
+ const statusSymbol = passed ? "\u2705" : "\u{1F534}";
78
+ const summaryHeader = `${statusSymbol} **Auto-Verification ${passed ? "PASSED" : "FAILED"}**
79
+ \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`;
80
+ const details = [
81
+ summaryHeader,
82
+ `\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
83
+ `\u{1F4BB} **Command**: \`${fullCommand}\``,
84
+ `\u{1F3AF} **Scope**: \`${scope}\`${testFiles.length > 0 ? ` (${testFiles.length} file(s) matched)` : ""}`,
85
+ `\u23F1\uFE0F **Duration**: ${durationMs}ms`,
86
+ ""
87
+ ];
88
+ if (!passed) {
89
+ details.push("\u26A0\uFE0F **BLOCKER**: Verification failed! Please fix test failures before shipping or continuing edits.");
90
+ details.push("```text");
91
+ details.push(rawOutput.substring(0, 1500));
92
+ details.push("```");
93
+ } else {
94
+ details.push("\u{1F389} All scoped tests passed cleanly!");
95
+ if (rawOutput) {
96
+ details.push("```text");
97
+ details.push(rawOutput.substring(0, 800));
98
+ details.push("```");
99
+ }
100
+ }
101
+ resolve(details.join("\n"));
102
+ });
103
+ });
104
+ }
105
+ export {
106
+ detectTestRunner,
107
+ runAutoVerification
108
+ };
@@ -2,10 +2,10 @@ import {
2
2
  getGitDiffStat,
3
3
  getSessionStats,
4
4
  getUnresolvedCount
5
- } from "./chunk-WG47POMW.js";
5
+ } from "./chunk-JIL533AM.js";
6
6
  import {
7
7
  sessionMemory
8
- } from "./chunk-6W7YV4AF.js";
8
+ } from "./chunk-SU7BTOND.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-RC2EO3OB.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-RC2EO3OB.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-RC2EO3OB.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-SU7BTOND.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.2",
4
4
  "description": "Safety-first context & orchestration engine for AI coding agents. MCP server with mandatory research pipeline, knowledge graph, impact analysis, decision memory, and safety guard — works with any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",