@plumpslabs/kuma 2.3.0 → 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/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,39 +14,52 @@ import {
9
14
  getGraphStats,
10
15
  searchGraph,
11
16
  traceFlow
12
- } from "./chunk-IMO4QAFJ.js";
17
+ } from "./chunk-EQ2CE4UC.js";
13
18
  import {
19
+ buildDriftMessages,
20
+ getGitDiffStat,
21
+ getSessionStats,
22
+ getUnresolvedCount
23
+ } from "./chunk-JIL533AM.js";
24
+ import {
25
+ addContextNote,
26
+ addSecurityFinding,
27
+ addTodo,
28
+ checkPortability,
29
+ ensureGitignore,
30
+ getBenchmarkDiff,
14
31
  getChanges,
15
32
  getDb,
16
33
  getResearchCache,
34
+ getSecurityFindings,
35
+ listContextNotes,
36
+ listDecisionLog,
37
+ listResearchCache,
38
+ listTodos,
39
+ recordDecisionLog,
40
+ rollbackChange,
41
+ runDoctor,
42
+ runGarbageCollection,
43
+ saveBenchmark,
17
44
  saveDb,
18
45
  saveHealthSnapshot,
19
- saveResearchCache
20
- } from "./chunk-6NEMSUKP.js";
46
+ saveResearchCache,
47
+ updateDecisionStatus,
48
+ updateTodoStatus
49
+ } from "./chunk-5N6KXACT.js";
21
50
  import {
22
51
  formatDecisionTemplate,
23
52
  getProactiveMemories,
24
53
  recordDecision,
25
54
  scoreMemoryRelevance
26
- } from "./chunk-OUPFJQKW.js";
27
- import {
28
- buildDriftMessages,
29
- getGitDiffStat,
30
- getSessionStats,
31
- getUnresolvedCount
32
- } from "./chunk-WP3WRPDC.js";
55
+ } from "./chunk-EIK7T746.js";
33
56
  import {
34
57
  sessionMemory
35
- } from "./chunk-TT37TE4P.js";
36
- import {
37
- ALL_CONFIG_TYPES,
38
- formatInitResults,
39
- runInit
40
- } from "./chunk-5A6AKUJZ.js";
58
+ } from "./chunk-SU7BTOND.js";
41
59
  import {
42
60
  getProjectRoot,
43
61
  validateFilePath
44
- } from "./chunk-IXMWW5WA.js";
62
+ } from "./chunk-E2KFPEBT.js";
45
63
 
46
64
  // src/index.ts
47
65
  import { readFileSync } from "fs";
@@ -69,10 +87,14 @@ async function handleContext(params) {
69
87
  return handleNavigate(params);
70
88
  case "changes":
71
89
  return handleChanges(params);
90
+ case "rollback":
91
+ return handleRollback(params);
92
+ case "researches":
93
+ return handleResearches(params);
72
94
  case "health":
73
95
  return handleHealth(params);
74
96
  default:
75
- return `Unknown action "${action}". Use: init, research, impact, navigate, changes, health`;
97
+ return `Unknown action "${action}". Use: init, research, impact, navigate, changes, rollback, researches, health`;
76
98
  }
77
99
  }
78
100
  async function handleInit(_params) {
@@ -132,6 +154,17 @@ async function handleInit(_params) {
132
154
  lines.push(` \u{1F3AF} Goal: ${summary.currentGoal || "not set"}`);
133
155
  lines.push(` \u{1F4DD} Modified: ${summary.modifiedFiles?.length || 0} file(s)`);
134
156
  lines.push(` \u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`);
157
+ try {
158
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-QX2LBJ7H.js");
159
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-RC2EO3OB.js");
160
+ const score = await computeSafetyScore(_params.goal);
161
+ const checksStr = JSON.stringify(score.checks);
162
+ await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
163
+ lines.push("");
164
+ lines.push("**Health Score**");
165
+ lines.push(formatSafetyScore(score));
166
+ } catch {
167
+ }
135
168
  lines.push("", "\u{1F4A1} Call kuma_context({ action: 'research', scope: '<area>' }) to research a specific area.");
136
169
  return lines.join("\n");
137
170
  }
@@ -154,8 +187,15 @@ async function handleResearch(params) {
154
187
  }
155
188
  }
156
189
  if (record) {
157
- const age = Math.floor((Date.now() - (record.validatedAt ? new Date(record.validatedAt).getTime() : 0)) / 1e3);
158
- lines.push(` \u2705 Found cached research (${age > 86400 ? `${Math.floor(age / 86400)}d` : `${Math.floor(age / 3600)}h`} old)`);
190
+ const ageSeconds = Math.floor((Date.now() - (record.validatedAt ? new Date(record.validatedAt).getTime() : 0)) / 1e3);
191
+ const ageStr = ageSeconds > 86400 ? `${Math.floor(ageSeconds / 86400)}d` : ageSeconds > 3600 ? `${Math.floor(ageSeconds / 3600)}h` : `${Math.floor(ageSeconds / 60)}m`;
192
+ lines.push(` \u2705 Found cached research (${ageStr} old)`);
193
+ if (ageSeconds > 86400) {
194
+ lines.push(` ${ageSeconds > 604800 ? "\u{1F534}" : "\u{1F7E1}"} **Staleness:** Cache is ${ageStr} old \u2014 may be stale`);
195
+ if (ageSeconds > 604800) {
196
+ lines.push(" \u{1F4A1} Consider re-researching: cache is over a week old");
197
+ }
198
+ }
159
199
  } else {
160
200
  lines.push(" \u23F3 No cached research \u2014 starting fresh");
161
201
  }
@@ -182,13 +222,30 @@ async function handleResearch(params) {
182
222
  try {
183
223
  const graphResult = await searchGraph(scope, 15);
184
224
  const graphLines = graphResult.split("\n");
185
- if (graphLines.length > 1) {
225
+ if (graphLines.length > 1 && !graphResult.includes("No results")) {
186
226
  lines.push(` \u{1F4CA} ${graphLines.filter((l) => l.includes("\u2022")).length} relevant node(s) found`);
187
227
  for (const l of graphLines.slice(0, 8)) {
188
228
  if (l.includes("\u2022")) lines.push(` ${l}`);
189
229
  }
190
230
  } else {
191
- lines.push(" \u23F3 No graph data yet \u2014 build by using more tools");
231
+ lines.push(" \u23F3 No graph data \u2014 searching codebase...");
232
+ try {
233
+ const fg = (await import("fast-glob")).default;
234
+ const root = getProjectRoot();
235
+ const ignorePatterns = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**", "**/.kuma/**"];
236
+ const files = await fg([`**/*${scope}*`], { cwd: root, ignore: ignorePatterns, onlyFiles: true, deep: 6 });
237
+ if (files.length > 0) {
238
+ lines.push(` \u{1F4C1} Found ${files.length} file(s) in codebase matching "${scope}":`);
239
+ for (const f of files.slice(0, 8)) {
240
+ lines.push(` \u{1F4C4} ${f}`);
241
+ }
242
+ if (files.length > 8) lines.push(` ... and ${files.length - 8} more`);
243
+ } else {
244
+ lines.push(" \u23F3 No codebase matches \u2014 build graph by using more tools");
245
+ }
246
+ } catch {
247
+ lines.push(" \u23F3 No graph data yet \u2014 build by using more tools");
248
+ }
192
249
  }
193
250
  } catch {
194
251
  lines.push(" \u26A0\uFE0F Graph query failed");
@@ -257,11 +314,23 @@ async function handleChanges(params) {
257
314
  const since = params.since;
258
315
  return await getChanges({ filePath, since });
259
316
  }
317
+ async function handleRollback(params) {
318
+ const target = params.target;
319
+ if (!target) return "\u26A0\uFE0F target parameter required (change ID, e.g. '5').";
320
+ const changeId = parseInt(target, 10);
321
+ if (isNaN(changeId)) return `\u26A0\uFE0F Invalid change ID: "${target}". Use a numeric ID from kuma_context({ action: 'changes' }).`;
322
+ sessionMemory.recordToolCall("kuma_context_rollback", { changeId });
323
+ return await rollbackChange(changeId);
324
+ }
325
+ async function handleResearches(_params) {
326
+ sessionMemory.recordToolCall("kuma_context_researches", {});
327
+ return await listResearchCache();
328
+ }
260
329
  async function handleHealth(_params) {
261
330
  sessionMemory.recordToolCall("kuma_context_health", {});
262
331
  try {
263
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-7FU7XSY5.js");
264
- const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-KN7Z4B5V.js");
332
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-QX2LBJ7H.js");
333
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-RC2EO3OB.js");
265
334
  const score = await computeSafetyScore();
266
335
  const checksStr = JSON.stringify(score.checks);
267
336
  await saveHealthSnapshot2(score.score, score.risk, checksStr, score.summary);
@@ -298,6 +367,8 @@ async function handleMemory(params) {
298
367
  switch (action) {
299
368
  case "decision":
300
369
  return handleDecision(params);
370
+ case "mine":
371
+ return handleMine(params);
301
372
  case "research_save":
302
373
  return handleResearchSave(params);
303
374
  case "session":
@@ -308,8 +379,16 @@ async function handleMemory(params) {
308
379
  return handleSearch(params);
309
380
  case "changes":
310
381
  return handleChanges2(params);
382
+ case "todo":
383
+ return handleTodo(params);
384
+ case "context":
385
+ return handleContext2(params);
386
+ case "benchmark":
387
+ return handleBenchmark(params);
388
+ case "decision_log":
389
+ return handleDecisionLog(params);
311
390
  default:
312
- return `Unknown action "${action}". Use: decision, research_save, session, heal, search, changes`;
391
+ return `Unknown action "${action}".`;
313
392
  }
314
393
  }
315
394
  async function handleDecision(params) {
@@ -318,15 +397,13 @@ async function handleDecision(params) {
318
397
  case "template":
319
398
  return formatDecisionTemplate();
320
399
  case "suggest": {
321
- const { shouldRecordDecision } = await import("./kumaMemory-IO7TVE7W.js");
400
+ const { shouldRecordDecision } = await import("./kumaMemory-ME4XWD5Z.js");
322
401
  const check = shouldRecordDecision();
323
402
  return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
324
403
  Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
325
404
  }
326
405
  case "record":
327
- if (!params.title || !params.rationale) {
328
- return "\u26A0\uFE0F title and rationale are required. Optionally: context, outcome.";
329
- }
406
+ if (!params.title || !params.rationale) return "\u26A0\uFE0F title and rationale are required.";
330
407
  return recordDecision({
331
408
  title: params.title,
332
409
  context: params.context || "",
@@ -350,68 +427,44 @@ async function handleResearchSave(params) {
350
427
  await saveResearchCache(scope, record, void 0, params.confidence);
351
428
  try {
352
429
  const researchDir = path2.join(getProjectRoot(), ".kuma", "research");
353
- if (!fs2.existsSync(researchDir)) {
354
- fs2.mkdirSync(researchDir, { recursive: true });
355
- }
356
- fs2.writeFileSync(
357
- path2.join(researchDir, `${scope}.json`),
358
- JSON.stringify(JSON.parse(record), null, 2),
359
- "utf-8"
360
- );
430
+ if (!fs2.existsSync(researchDir)) fs2.mkdirSync(researchDir, { recursive: true });
431
+ fs2.writeFileSync(path2.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
361
432
  } catch {
362
433
  }
363
- return `\u2705 Research "${scope}" saved to cache and .kuma/research/${scope}.json`;
434
+ return `\u2705 Research "${scope}" saved.`;
364
435
  }
365
436
  async function handleSession(params) {
366
437
  const topic = params.topic;
367
438
  const summary = sessionMemory.getSummary(topic);
368
- if (topic) {
369
- return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
370
- }
439
+ if (topic) return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
371
440
  const modifiedFiles = summary.modifiedFiles || [];
372
441
  const failures = summary.unresolvedFailures || [];
373
442
  const lines = [
374
- "\u{1F4CB} **Session Summary**",
375
- `\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`,
376
- "",
443
+ "\u{1F4CB} **Session Summary**\n\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\n",
377
444
  `\u{1F3AF} Goal: ${summary.currentGoal || "not set"}`,
378
445
  `\u{1F550} Duration: ${summary.sessionDuration}`,
379
- `\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`,
380
- ""
446
+ `\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}
447
+ `
381
448
  ];
382
449
  if (modifiedFiles.length > 0) {
383
450
  lines.push(`**Modified Files** (${modifiedFiles.length}):`);
384
451
  for (const f of modifiedFiles.slice(0, 10)) {
385
- const icon = f.status === "created" ? "\u2728" : f.status === "modified" ? "\u{1F4DD}" : "\u274C";
386
- lines.push(` ${icon} ${f.filePath}`);
387
- }
388
- if (modifiedFiles.length > 10) {
389
- lines.push(` ... and ${modifiedFiles.length - 10} more`);
452
+ lines.push(` ${f.status === "created" ? "\u2728" : f.status === "modified" ? "\u{1F4DD}" : "\u274C"} ${f.filePath}`);
390
453
  }
454
+ if (modifiedFiles.length > 10) lines.push(` ... and ${modifiedFiles.length - 10} more`);
391
455
  lines.push("");
392
456
  }
393
457
  if (failures.length > 0) {
394
458
  lines.push(`**Unresolved Issues** (${failures.length}):`);
395
- for (const f of failures.slice(0, 5)) {
396
- lines.push(` \u274C ${f.task}: ${f.error.substring(0, 80)}`);
397
- }
398
- lines.push("");
399
- }
400
- const completed = summary.completedSteps || [];
401
- if (completed.length > 0) {
402
- lines.push(`**Completed Steps**:`);
403
- for (const s of completed) {
404
- lines.push(` \u2705 ${s}`);
405
- }
459
+ for (const f of failures.slice(0, 5)) lines.push(` \u274C ${f.task}: ${f.error.substring(0, 80)}`);
406
460
  lines.push("");
407
461
  }
408
- lines.push("\u{1F4A1} Use kuma_memory({ action: 'decision', decisionAction: 'record', ... }) to document decisions.");
409
462
  return lines.join("\n");
410
463
  }
411
464
  async function handleHeal(params) {
412
465
  if (params.healAction === "check") {
413
466
  const stale = await detectStaleNodes();
414
- if (stale.length === 0) return "\u2705 No stale entries found in knowledge graph.";
467
+ if (stale.length === 0) return "\u2705 No stale entries found.";
415
468
  return `\u{1F50D} ${stale.length} stale entr${stale.length > 1 ? "ies" : "y"} found. Use kuma_memory({ action: 'heal' }) to repair.`;
416
469
  }
417
470
  const result = await autoHeal();
@@ -422,31 +475,88 @@ async function handleSearch(params) {
422
475
  if (!query) return "\u26A0\uFE0F query or scope parameter required.";
423
476
  const limit = params.limit || 20;
424
477
  const memResults = sessionMemory.searchMemory(query, limit);
425
- const { searchGraph: searchGraph2 } = await import("./kumaGraph-GW5SWOHQ.js");
478
+ const { searchGraph: searchGraph2 } = await import("./kumaGraph-5IZ4FDZA.js");
426
479
  const graphResults = await searchGraph2(query, Math.min(limit, 10));
427
- const lines = [
428
- `\u{1F50D} **Search Results** \u2014 "${query}"`,
429
- `\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`,
430
- ""
431
- ];
480
+ const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
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
482
+ `];
432
483
  if (memResults.length > 0) {
433
484
  lines.push(`**Session Memory** (${memResults.length}):`);
434
- for (const r of memResults.slice(0, 5)) {
435
- lines.push(` \u2022 ${r.content.substring(0, 120)}`);
436
- }
485
+ for (const r of memResults.slice(0, 5)) lines.push(` \u2022 ${r.content.substring(0, 120)}`);
437
486
  lines.push("");
438
487
  }
439
- lines.push("**Knowledge Graph:**");
440
- lines.push(graphResults);
441
- if (memResults.length === 0 && graphResults.includes("No results")) {
442
- lines.push("", "\u{1F4A1} No results found. Try a different query or research the topic first.");
443
- }
488
+ lines.push("**Knowledge Graph:**\n" + graphResults);
444
489
  return lines.join("\n");
445
490
  }
446
491
  async function handleChanges2(params) {
447
- const filePath = params.target;
448
- const since = params.since;
449
- return await getChanges({ filePath, 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 });
494
+ }
495
+ async function handleTodo(params) {
496
+ if (params.todoId && params.status) {
497
+ return await updateTodoStatus(params.todoId, params.status);
498
+ }
499
+ if (params.title) {
500
+ return await addTodo({
501
+ title: params.title,
502
+ description: params.description,
503
+ scope: params.scope,
504
+ deps: params.deps,
505
+ successCriteria: params.success_criteria
506
+ });
507
+ }
508
+ return await listTodos(params.scope, params.status);
509
+ }
510
+ async function handleContext2(params) {
511
+ if (params.content && params.source) {
512
+ return await addContextNote({
513
+ source: params.source,
514
+ content: params.content,
515
+ scope: params.scope,
516
+ filePaths: params.target ? JSON.stringify([params.target]) : void 0
517
+ });
518
+ }
519
+ return await listContextNotes(params.scope);
520
+ }
521
+ async function handleBenchmark(params) {
522
+ if (params.label) {
523
+ if (params.metrics) {
524
+ try {
525
+ const metrics = JSON.parse(params.metrics);
526
+ return await saveBenchmark(params.label, metrics);
527
+ } catch {
528
+ return `\u26A0\uFE0F Invalid metrics JSON: "${params.metrics.substring(0, 100)}"`;
529
+ }
530
+ }
531
+ return await getBenchmarkDiff(params.label, params.labelB);
532
+ }
533
+ return `\u26A0\uFE0F label required. Use: kuma_memory({ action: 'benchmark', label: 'phase-3', metrics: '{"tsc_errors": 245}' })`;
534
+ }
535
+ async function handleDecisionLog(params) {
536
+ if (params.target && params.status) {
537
+ const id = parseInt(params.target, 10);
538
+ if (!isNaN(id)) return await updateDecisionStatus(id, params.status);
539
+ }
540
+ if (params.title && params.rationale) {
541
+ return await recordDecisionLog({
542
+ title: params.title,
543
+ context: params.context,
544
+ rationale: params.rationale,
545
+ outcome: params.outcome,
546
+ status: params.status
547
+ });
548
+ }
549
+ return await listDecisionLog(params.status);
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
+ });
450
560
  }
451
561
 
452
562
  // src/engine/kumaLock.ts
@@ -1011,6 +1121,58 @@ async function safetyCheck(action, filePath, command) {
1011
1121
  } catch {
1012
1122
  checks.push({ name: "Knowledge Graph Health", passed: true, detail: "\u26A0\uFE0F Could not check" });
1013
1123
  }
1124
+ if (filePath) {
1125
+ try {
1126
+ const fs7 = await import("fs");
1127
+ const path7 = await import("path");
1128
+ const root = process.cwd();
1129
+ const fullPath = path7.resolve(root, filePath);
1130
+ if (fs7.existsSync(fullPath)) {
1131
+ const stat = fs7.statSync(fullPath);
1132
+ const sizeKB = Math.round(stat.size / 1024);
1133
+ if (sizeKB > 500) {
1134
+ checks.push({
1135
+ name: "File Size Warning",
1136
+ passed: false,
1137
+ detail: `\u{1F4CF} File is ${sizeKB}KB \u2014 large file edits are risky`
1138
+ });
1139
+ }
1140
+ if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
1141
+ const baseName = path7.basename(filePath, path7.extname(filePath));
1142
+ const testPatterns = [
1143
+ filePath.replace(/\.(ts|js)$/, ".test.$1"),
1144
+ filePath.replace(/\.(ts|js)$/, ".spec.$1"),
1145
+ filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
1146
+ `**/__tests__/**/${baseName}.test.${path7.extname(filePath).slice(1)}`
1147
+ ];
1148
+ let hasTests = false;
1149
+ const fg = await import("fast-glob");
1150
+ for (const pattern of testPatterns) {
1151
+ const matches = await fg.default(pattern, { cwd: root, ignore: ["**/node_modules/**"] });
1152
+ if (matches.length > 0) {
1153
+ hasTests = true;
1154
+ break;
1155
+ }
1156
+ }
1157
+ if (!hasTests) {
1158
+ checks.push({
1159
+ name: "Tests Check",
1160
+ passed: true,
1161
+ // Not a blocker, just informative
1162
+ detail: `\u{1F9EA} No test file found for "${path7.basename(filePath)}" \u2014 consider adding tests`
1163
+ });
1164
+ } else {
1165
+ checks.push({
1166
+ name: "Tests Check",
1167
+ passed: true,
1168
+ detail: "\u2705 Test file found"
1169
+ });
1170
+ }
1171
+ }
1172
+ }
1173
+ } catch {
1174
+ }
1175
+ }
1014
1176
  const failed = checks.filter((c) => !c.passed);
1015
1177
  const riskLevel = failed.length >= 3 ? "critical" : failed.length >= 2 ? "high" : failed.length >= 1 ? "medium" : "low";
1016
1178
  const allowed = failed.length === 0;
@@ -1458,6 +1620,8 @@ async function handleSafety(params) {
1458
1620
  switch (action) {
1459
1621
  case "guard":
1460
1622
  return handleGuard(params);
1623
+ case "verify":
1624
+ return handleVerify(params);
1461
1625
  case "check":
1462
1626
  return handleCheck(params);
1463
1627
  case "audit":
@@ -1468,8 +1632,18 @@ async function handleSafety(params) {
1468
1632
  return handleHealth2(params);
1469
1633
  case "override":
1470
1634
  return handleOverride(params);
1635
+ case "security":
1636
+ return handleSecurity(params);
1637
+ case "gc":
1638
+ return handleGc(params);
1639
+ case "doctor":
1640
+ return handleDoctor(params);
1641
+ case "portability":
1642
+ return handlePortability(params);
1643
+ case "gitignore":
1644
+ return handleGitignore(params);
1471
1645
  default:
1472
- return `Unknown action "${action}". Use: guard, check, audit, lock, health, override`;
1646
+ return `Unknown action "${action}".`;
1473
1647
  }
1474
1648
  }
1475
1649
  async function handleGuard(params) {
@@ -1479,15 +1653,10 @@ async function handleGuard(params) {
1479
1653
  });
1480
1654
  }
1481
1655
  async function handleCheck(params) {
1482
- const action = "check";
1483
- const filePath = params.filePath;
1484
- const command = params.command;
1485
- return await safetyCheck(action, filePath, command);
1656
+ return await safetyCheck("check", params.filePath, params.command);
1486
1657
  }
1487
1658
  async function handleAudit(params) {
1488
- if (params.limit === 0) {
1489
- return await auditStats();
1490
- }
1659
+ if (params.limit === 0) return await auditStats();
1491
1660
  return await queryAudit({
1492
1661
  toolName: params.toolName,
1493
1662
  riskLevel: params.riskLevel,
@@ -1516,18 +1685,86 @@ async function handleLock(params) {
1516
1685
  }
1517
1686
  async function handleHealth2(_params) {
1518
1687
  try {
1519
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-7FU7XSY5.js");
1688
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-QX2LBJ7H.js");
1520
1689
  const score = await computeSafetyScore();
1521
- const checksStr = JSON.stringify(score.checks);
1522
- await saveHealthSnapshot(score.score, score.risk, checksStr, score.summary);
1690
+ await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
1523
1691
  return formatSafetyScore(score);
1524
1692
  } catch (err) {
1525
1693
  return `Error computing health: ${err}`;
1526
1694
  }
1527
1695
  }
1528
1696
  function handleOverride(params) {
1529
- const tool = params.toolName || "unknown";
1530
- return safetyOverride(tool, params.reason);
1697
+ return safetyOverride(params.toolName || "unknown", params.reason);
1698
+ }
1699
+ async function handleSecurity(params) {
1700
+ sessionMemory.recordToolCall("kuma_safety_security", {});
1701
+ if (params.filePath) {
1702
+ try {
1703
+ const fs7 = await import("fs");
1704
+ const path7 = await import("path");
1705
+ const root = process.cwd();
1706
+ const fullPath = path7.resolve(root, params.filePath);
1707
+ if (fs7.existsSync(fullPath) && fs7.statSync(fullPath).isFile()) {
1708
+ const content = fs7.readFileSync(fullPath, "utf-8");
1709
+ const lines = content.split("\n");
1710
+ const findings = [];
1711
+ for (let i = 0; i < lines.length; i++) {
1712
+ const line = lines[i];
1713
+ const lc = line.toLowerCase();
1714
+ if (lc.includes("credentials: true")) {
1715
+ findings.push({ line: i + 1, pattern: "credentials-leak", severity: "high", message: "`credentials: true` in Prisma select may leak sensitive fields" });
1716
+ }
1717
+ if (lc.includes("console.log") && (lc.includes("token") || lc.includes("secret") || lc.includes("key") || lc.includes("password"))) {
1718
+ findings.push({ line: i + 1, pattern: "console-log-secret", severity: "high", message: "Potential secret logged to console" });
1719
+ }
1720
+ if (lc.includes("process.env") && (lc.includes("api_key") || lc.includes("secret") || lc.includes("token"))) {
1721
+ findings.push({ line: i + 1, pattern: "env-in-code", severity: "medium", message: "process.env.KEY referenced in code \u2014 ensure not in client bundle" });
1722
+ }
1723
+ }
1724
+ for (const f of findings) {
1725
+ await addSecurityFinding({
1726
+ filePath: params.filePath,
1727
+ lineNumber: f.line,
1728
+ pattern: f.pattern,
1729
+ severity: f.severity,
1730
+ message: f.message
1731
+ });
1732
+ }
1733
+ if (findings.length === 0) {
1734
+ return `\u{1F512} No security issues found in "${params.filePath}".`;
1735
+ }
1736
+ return `\u{1F512} **Security Scan** \u2014 ${findings.length} finding(s) in "${params.filePath}":
1737
+ ${findings.map((f) => ` ${f.severity === "high" ? "\u{1F534}" : "\u{1F7E1}"} L${f.line}: ${f.message}`).join("\n")}`;
1738
+ }
1739
+ } catch {
1740
+ }
1741
+ }
1742
+ return await getSecurityFindings();
1743
+ }
1744
+ async function handleGc(_params) {
1745
+ sessionMemory.recordToolCall("kuma_safety_gc", {});
1746
+ return await runGarbageCollection();
1747
+ }
1748
+ async function handleDoctor(_params) {
1749
+ sessionMemory.recordToolCall("kuma_safety_doctor", {});
1750
+ return await runDoctor();
1751
+ }
1752
+ async function handlePortability(_params) {
1753
+ sessionMemory.recordToolCall("kuma_safety_portability", {});
1754
+ return await checkPortability();
1755
+ }
1756
+ async function handleGitignore(_params) {
1757
+ sessionMemory.recordToolCall("kuma_safety_gitignore", {});
1758
+ return await ensureGitignore();
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
+ });
1531
1768
  }
1532
1769
 
1533
1770
  // src/manifest.ts
@@ -1536,7 +1773,7 @@ function registerAllTools(server) {
1536
1773
  "kuma_context",
1537
1774
  "**Call FIRST every session.** Understand your project before making changes. Actions: init (load project brief), research (5-step pipeline: cache\u2192staleness\u2192graph\u2192impact\u2192decision), impact (analyze change effects), navigate (trace code flow), changes (view change log), health (project health score 0-100). RESEARCH IS REQUIRED before editing unfamiliar code.",
1538
1775
  {
1539
- action: z.enum(["init", "research", "impact", "navigate", "changes", "health"]).describe("Action: init=project brief, research=5-step research pipeline (REQUIRED before edits), impact=analyze change effects, navigate=trace code flow, changes=view change log, health=project health score"),
1776
+ action: z.enum(["init", "research", "impact", "navigate", "changes", "health", "rollback", "researches"]).describe("Action: init=project brief, research=5-step research pipeline (REQUIRED before edits), impact=analyze change effects, navigate=trace code flow, changes=view change log, rollback=undo a change by ID, researches=list all cached research, health=project health score"),
1540
1777
  scope: z.string().optional().describe("Research scope for research action (e.g. 'auth', 'database', 'api')"),
1541
1778
  target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
1542
1779
  goal: z.string().optional().describe("Current goal (for init/health)"),
@@ -1561,17 +1798,18 @@ function registerAllTools(server) {
1561
1798
  );
1562
1799
  server.tool(
1563
1800
  "kuma_memory",
1564
- "Record and retrieve project knowledge. Actions: decision (ADR-style record/template/suggest), research_save (save research results), session (current session summary), heal (self-heal knowledge graph), search (search memory + graph), changes (view change log for selective undo).",
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).",
1565
1802
  {
1566
- action: z.enum(["decision", "research_save", "session", "heal", "search", "changes"]).describe("Memory action: decision=record ADR, research_save=save research, session=session summary, heal=graph repair, search=search all, changes=change log"),
1567
- scope: z.string().optional().describe("Scope for research_save/search"),
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"),
1568
1805
  query: z.string().optional().describe("Search query for search action"),
1569
- content: z.string().optional().describe("Content/notes for research_save"),
1806
+ content: z.string().optional().describe("Content/notes for research_save / context"),
1570
1807
  record: z.string().optional().describe("JSON record string for research_save"),
1571
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"),
1572
1810
  // Decision params
1573
1811
  decisionAction: z.enum(["template", "suggest", "record"]).optional().describe("Decision sub-action"),
1574
- title: z.string().optional().describe("Decision title"),
1812
+ title: z.string().optional().describe("Decision/todo/benchmark title"),
1575
1813
  context: z.string().optional().describe("Decision context"),
1576
1814
  rationale: z.string().optional().describe("Decision rationale"),
1577
1815
  outcome: z.string().optional().describe("Decision outcome"),
@@ -1579,9 +1817,21 @@ function registerAllTools(server) {
1579
1817
  healAction: z.enum(["check", "heal"]).optional().describe("Heal sub-action"),
1580
1818
  topic: z.string().optional().describe("Memory topic for session"),
1581
1819
  limit: z.number().min(1).max(100).optional().describe("Result limit"),
1582
- target: z.string().optional().describe("File path for changes"),
1820
+ target: z.string().optional().describe("File path for changes / decision_log ID"),
1583
1821
  since: z.number().optional().describe("Timestamp filter for changes"),
1584
- compact: z.boolean().optional().default(false).describe("Compact output mode")
1822
+ compact: z.boolean().optional().default(false).describe("Compact output mode"),
1823
+ // Todo params
1824
+ description: z.string().optional().describe("Todo description"),
1825
+ deps: z.string().optional().describe("Todo dependencies (JSON array)"),
1826
+ success_criteria: z.string().optional().describe("Todo success criteria"),
1827
+ status: z.string().optional().describe("Status for todo/decision_log"),
1828
+ todoId: z.number().optional().describe("Todo ID for status update"),
1829
+ // Context params
1830
+ source: z.string().optional().describe("Context source (e.g. 'slack', 'jira', 'meeting')"),
1831
+ // Benchmark params
1832
+ label: z.string().optional().describe("Benchmark label"),
1833
+ metrics: z.string().optional().describe("Benchmark metrics JSON"),
1834
+ labelB: z.string().optional().describe("Second benchmark label for diff")
1585
1835
  },
1586
1836
  async (params) => {
1587
1837
  try {
@@ -1592,6 +1842,7 @@ function registerAllTools(server) {
1592
1842
  content: params.content,
1593
1843
  record: params.record,
1594
1844
  confidence: params.confidence,
1845
+ confirm: params.confirm,
1595
1846
  decisionAction: params.decisionAction,
1596
1847
  title: params.title,
1597
1848
  context: params.context,
@@ -1611,14 +1862,16 @@ function registerAllTools(server) {
1611
1862
  );
1612
1863
  server.tool(
1613
1864
  "kuma_safety",
1614
- "Safety checks, policy enforcement, and multi-agent coordination. Actions: guard (anti-pattern detection), check (pre-exec safety check), audit (query audit trail), lock (multi-agent file lock), health (project health score), override (logged 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).",
1615
1866
  {
1616
- action: z.enum(["guard", "check", "audit", "lock", "health", "override"]).describe("Safety action: guard=anti-patterns, check=pre-exec safety, audit=query trail, lock=multi-agent, health=score, override=logged 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)"),
1617
1870
  // Guard params
1618
1871
  guardGoal: z.string().optional().describe("Goal for guard check"),
1619
1872
  guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
1620
1873
  // Check params
1621
- filePath: z.string().optional().describe("File path for check/lock"),
1874
+ filePath: z.string().optional().describe("File path for check/lock/security scan"),
1622
1875
  command: z.string().optional().describe("Command for check"),
1623
1876
  // Audit params
1624
1877
  toolName: z.string().optional().describe("Filter audit by tool name"),
@@ -1641,6 +1894,7 @@ function registerAllTools(server) {
1641
1894
  guardGoal: params.guardGoal,
1642
1895
  guardCheck: params.guardCheck,
1643
1896
  filePath: params.filePath,
1897
+ scope: params.scope,
1644
1898
  command: params.command,
1645
1899
  toolName: params.toolName,
1646
1900
  riskLevel: params.riskLevel,
@@ -1788,7 +2042,7 @@ async function main() {
1788
2042
  });
1789
2043
  (async () => {
1790
2044
  try {
1791
- const { generateInitMdContent } = await import("./init-O4MBXFZL.js");
2045
+ const { generateInitMdContent } = await import("./init-AJAESUZZ.js");
1792
2046
  const fs7 = await import("fs");
1793
2047
  const path7 = await import("path");
1794
2048
  const initMdPath = path7.resolve(process.cwd(), ".kuma/init.md");
@@ -1804,7 +2058,7 @@ async function main() {
1804
2058
  })();
1805
2059
  (async () => {
1806
2060
  try {
1807
- const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-FDRVUNJY.js");
2061
+ const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-G4RBMZ6X.js");
1808
2062
  const { generateSkill, getSecondaryFiles } = await import("./skillGenerator-F6YO4H5D.js");
1809
2063
  const fs7 = await import("fs");
1810
2064
  const path7 = await import("path");
@@ -1838,6 +2092,38 @@ async function main() {
1838
2092
  console.error(`[${SERVER_NAME}] Failed to auto-create skill file: ${err}`);
1839
2093
  }
1840
2094
  })();
2095
+ (async () => {
2096
+ try {
2097
+ console.error(`[${SERVER_NAME}] \u{1F504} Running cold start bootstrap...`);
2098
+ const sessionInfo = sessionMemory.loadSession();
2099
+ if (sessionInfo.hasPrevSession) {
2100
+ console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
2101
+ }
2102
+ try {
2103
+ const { buildFromSessionMemory } = await import("./kumaGraph-5IZ4FDZA.js");
2104
+ const edgeCount = await buildFromSessionMemory();
2105
+ if (edgeCount > 0) {
2106
+ console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
2107
+ }
2108
+ } catch (err) {
2109
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
2110
+ }
2111
+ try {
2112
+ const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-RC2EO3OB.js");
2113
+ const db = await getDb2();
2114
+ db.run(
2115
+ `INSERT INTO sessions (started_at, goal, tool_calls) VALUES (?, ?, ?)`,
2116
+ [Math.floor(Date.now() / 1e3), sessionMemory.getSummary().currentGoal || "Session start", sessionInfo.toolCallCount]
2117
+ );
2118
+ saveDb2(db);
2119
+ } catch (err) {
2120
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Session DB record: ${err}`);
2121
+ }
2122
+ console.error(`[${SERVER_NAME}] \u2705 Cold start bootstrap complete`);
2123
+ } catch (err) {
2124
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Cold start bootstrap error: ${err}`);
2125
+ }
2126
+ })();
1841
2127
  const server = new McpServer(
1842
2128
  {
1843
2129
  name: SERVER_NAME,