@plumpslabs/kuma 2.3.0 → 2.3.1

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
@@ -9,39 +9,57 @@ import {
9
9
  getGraphStats,
10
10
  searchGraph,
11
11
  traceFlow
12
- } from "./chunk-IMO4QAFJ.js";
12
+ } from "./chunk-ZDQVSBFB.js";
13
13
  import {
14
+ addContextNote,
15
+ addSecurityFinding,
16
+ addTodo,
17
+ checkPortability,
18
+ ensureGitignore,
19
+ getBenchmarkDiff,
14
20
  getChanges,
15
21
  getDb,
16
22
  getResearchCache,
23
+ getSecurityFindings,
24
+ listContextNotes,
25
+ listDecisionLog,
26
+ listResearchCache,
27
+ listTodos,
28
+ recordDecisionLog,
29
+ rollbackChange,
30
+ runDoctor,
31
+ runGarbageCollection,
32
+ saveBenchmark,
17
33
  saveDb,
18
34
  saveHealthSnapshot,
19
- saveResearchCache
20
- } from "./chunk-6NEMSUKP.js";
35
+ saveResearchCache,
36
+ updateDecisionStatus,
37
+ updateTodoStatus
38
+ } from "./chunk-2IIDVJPW.js";
21
39
  import {
22
40
  formatDecisionTemplate,
23
41
  getProactiveMemories,
24
42
  recordDecision,
25
43
  scoreMemoryRelevance
26
- } from "./chunk-OUPFJQKW.js";
44
+ } from "./chunk-4CKTTORL.js";
27
45
  import {
28
46
  buildDriftMessages,
29
47
  getGitDiffStat,
30
48
  getSessionStats,
31
49
  getUnresolvedCount
32
- } from "./chunk-WP3WRPDC.js";
50
+ } from "./chunk-WG47POMW.js";
33
51
  import {
34
52
  sessionMemory
35
- } from "./chunk-TT37TE4P.js";
53
+ } from "./chunk-6W7YV4AF.js";
36
54
  import {
37
55
  ALL_CONFIG_TYPES,
38
56
  formatInitResults,
39
57
  runInit
40
- } from "./chunk-5A6AKUJZ.js";
58
+ } from "./chunk-5WSPGLXS.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-OQFYLLP7.js");
159
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-PA7XVERC.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-OQFYLLP7.js");
333
+ const { saveHealthSnapshot: saveHealthSnapshot2 } = await import("./kumaDb-PA7XVERC.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);
@@ -308,8 +377,16 @@ async function handleMemory(params) {
308
377
  return handleSearch(params);
309
378
  case "changes":
310
379
  return handleChanges2(params);
380
+ case "todo":
381
+ return handleTodo(params);
382
+ case "context":
383
+ return handleContext2(params);
384
+ case "benchmark":
385
+ return handleBenchmark(params);
386
+ case "decision_log":
387
+ return handleDecisionLog(params);
311
388
  default:
312
- return `Unknown action "${action}". Use: decision, research_save, session, heal, search, changes`;
389
+ return `Unknown action "${action}".`;
313
390
  }
314
391
  }
315
392
  async function handleDecision(params) {
@@ -318,15 +395,13 @@ async function handleDecision(params) {
318
395
  case "template":
319
396
  return formatDecisionTemplate();
320
397
  case "suggest": {
321
- const { shouldRecordDecision } = await import("./kumaMemory-IO7TVE7W.js");
398
+ const { shouldRecordDecision } = await import("./kumaMemory-YEIEXGNW.js");
322
399
  const check = shouldRecordDecision();
323
400
  return check.worth ? `\u{1F4A1} Decision suggested: "${check.title}"
324
401
  Use kuma_memory({ action: 'decision', title: '...', context: '...', rationale: '...', outcome: '...' }) to record.` : "\u2705 No decision needed at this time.";
325
402
  }
326
403
  case "record":
327
- if (!params.title || !params.rationale) {
328
- return "\u26A0\uFE0F title and rationale are required. Optionally: context, outcome.";
329
- }
404
+ if (!params.title || !params.rationale) return "\u26A0\uFE0F title and rationale are required.";
330
405
  return recordDecision({
331
406
  title: params.title,
332
407
  context: params.context || "",
@@ -350,68 +425,44 @@ async function handleResearchSave(params) {
350
425
  await saveResearchCache(scope, record, void 0, params.confidence);
351
426
  try {
352
427
  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
- );
428
+ if (!fs2.existsSync(researchDir)) fs2.mkdirSync(researchDir, { recursive: true });
429
+ fs2.writeFileSync(path2.join(researchDir, `${scope}.json`), JSON.stringify(JSON.parse(record), null, 2), "utf-8");
361
430
  } catch {
362
431
  }
363
- return `\u2705 Research "${scope}" saved to cache and .kuma/research/${scope}.json`;
432
+ return `\u2705 Research "${scope}" saved.`;
364
433
  }
365
434
  async function handleSession(params) {
366
435
  const topic = params.topic;
367
436
  const summary = sessionMemory.getSummary(topic);
368
- if (topic) {
369
- return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
370
- }
437
+ if (topic) return typeof summary.content === "string" ? summary.content : JSON.stringify(summary, null, 2);
371
438
  const modifiedFiles = summary.modifiedFiles || [];
372
439
  const failures = summary.unresolvedFailures || [];
373
440
  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
- "",
441
+ "\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
442
  `\u{1F3AF} Goal: ${summary.currentGoal || "not set"}`,
378
443
  `\u{1F550} Duration: ${summary.sessionDuration}`,
379
- `\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}`,
380
- ""
444
+ `\u{1F6E0}\uFE0F Tool calls: ${summary.toolCallCount}
445
+ `
381
446
  ];
382
447
  if (modifiedFiles.length > 0) {
383
448
  lines.push(`**Modified Files** (${modifiedFiles.length}):`);
384
449
  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`);
450
+ lines.push(` ${f.status === "created" ? "\u2728" : f.status === "modified" ? "\u{1F4DD}" : "\u274C"} ${f.filePath}`);
390
451
  }
452
+ if (modifiedFiles.length > 10) lines.push(` ... and ${modifiedFiles.length - 10} more`);
391
453
  lines.push("");
392
454
  }
393
455
  if (failures.length > 0) {
394
456
  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
- }
457
+ for (const f of failures.slice(0, 5)) lines.push(` \u274C ${f.task}: ${f.error.substring(0, 80)}`);
406
458
  lines.push("");
407
459
  }
408
- lines.push("\u{1F4A1} Use kuma_memory({ action: 'decision', decisionAction: 'record', ... }) to document decisions.");
409
460
  return lines.join("\n");
410
461
  }
411
462
  async function handleHeal(params) {
412
463
  if (params.healAction === "check") {
413
464
  const stale = await detectStaleNodes();
414
- if (stale.length === 0) return "\u2705 No stale entries found in knowledge graph.";
465
+ if (stale.length === 0) return "\u2705 No stale entries found.";
415
466
  return `\u{1F50D} ${stale.length} stale entr${stale.length > 1 ? "ies" : "y"} found. Use kuma_memory({ action: 'heal' }) to repair.`;
416
467
  }
417
468
  const result = await autoHeal();
@@ -422,31 +473,77 @@ async function handleSearch(params) {
422
473
  if (!query) return "\u26A0\uFE0F query or scope parameter required.";
423
474
  const limit = params.limit || 20;
424
475
  const memResults = sessionMemory.searchMemory(query, limit);
425
- const { searchGraph: searchGraph2 } = await import("./kumaGraph-GW5SWOHQ.js");
476
+ const { searchGraph: searchGraph2 } = await import("./kumaGraph-VUTLTOFY.js");
426
477
  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
- ];
478
+ const lines = [`\u{1F50D} **Search Results** \u2014 "${query}"
479
+ \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
480
+ `];
432
481
  if (memResults.length > 0) {
433
482
  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
- }
483
+ for (const r of memResults.slice(0, 5)) lines.push(` \u2022 ${r.content.substring(0, 120)}`);
437
484
  lines.push("");
438
485
  }
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
- }
486
+ lines.push("**Knowledge Graph:**\n" + graphResults);
444
487
  return lines.join("\n");
445
488
  }
446
489
  async function handleChanges2(params) {
447
- const filePath = params.target;
448
- const since = params.since;
449
- return await getChanges({ filePath, since });
490
+ return await getChanges({ filePath: params.target, since: params.since });
491
+ }
492
+ async function handleTodo(params) {
493
+ if (params.todoId && params.status) {
494
+ return await updateTodoStatus(params.todoId, params.status);
495
+ }
496
+ if (params.title) {
497
+ return await addTodo({
498
+ title: params.title,
499
+ description: params.description,
500
+ scope: params.scope,
501
+ deps: params.deps,
502
+ successCriteria: params.success_criteria
503
+ });
504
+ }
505
+ return await listTodos(params.scope, params.status);
506
+ }
507
+ async function handleContext2(params) {
508
+ if (params.content && params.source) {
509
+ return await addContextNote({
510
+ source: params.source,
511
+ content: params.content,
512
+ scope: params.scope,
513
+ filePaths: params.target ? JSON.stringify([params.target]) : void 0
514
+ });
515
+ }
516
+ return await listContextNotes(params.scope);
517
+ }
518
+ async function handleBenchmark(params) {
519
+ if (params.label) {
520
+ if (params.metrics) {
521
+ try {
522
+ const metrics = JSON.parse(params.metrics);
523
+ return await saveBenchmark(params.label, metrics);
524
+ } catch {
525
+ return `\u26A0\uFE0F Invalid metrics JSON: "${params.metrics.substring(0, 100)}"`;
526
+ }
527
+ }
528
+ return await getBenchmarkDiff(params.label, params.labelB);
529
+ }
530
+ return `\u26A0\uFE0F label required. Use: kuma_memory({ action: 'benchmark', label: 'phase-3', metrics: '{"tsc_errors": 245}' })`;
531
+ }
532
+ async function handleDecisionLog(params) {
533
+ if (params.target && params.status) {
534
+ const id = parseInt(params.target, 10);
535
+ if (!isNaN(id)) return await updateDecisionStatus(id, params.status);
536
+ }
537
+ if (params.title && params.rationale) {
538
+ return await recordDecisionLog({
539
+ title: params.title,
540
+ context: params.context,
541
+ rationale: params.rationale,
542
+ outcome: params.outcome,
543
+ status: params.status
544
+ });
545
+ }
546
+ return await listDecisionLog(params.status);
450
547
  }
451
548
 
452
549
  // src/engine/kumaLock.ts
@@ -1011,6 +1108,58 @@ async function safetyCheck(action, filePath, command) {
1011
1108
  } catch {
1012
1109
  checks.push({ name: "Knowledge Graph Health", passed: true, detail: "\u26A0\uFE0F Could not check" });
1013
1110
  }
1111
+ if (filePath) {
1112
+ try {
1113
+ const fs7 = await import("fs");
1114
+ const path7 = await import("path");
1115
+ const root = process.cwd();
1116
+ const fullPath = path7.resolve(root, filePath);
1117
+ if (fs7.existsSync(fullPath)) {
1118
+ const stat = fs7.statSync(fullPath);
1119
+ const sizeKB = Math.round(stat.size / 1024);
1120
+ if (sizeKB > 500) {
1121
+ checks.push({
1122
+ name: "File Size Warning",
1123
+ passed: false,
1124
+ detail: `\u{1F4CF} File is ${sizeKB}KB \u2014 large file edits are risky`
1125
+ });
1126
+ }
1127
+ if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
1128
+ const baseName = path7.basename(filePath, path7.extname(filePath));
1129
+ const testPatterns = [
1130
+ filePath.replace(/\.(ts|js)$/, ".test.$1"),
1131
+ filePath.replace(/\.(ts|js)$/, ".spec.$1"),
1132
+ filePath.replace(/^src\//, "tests/").replace(/\.(ts|js)$/, ".test.$1"),
1133
+ `**/__tests__/**/${baseName}.test.${path7.extname(filePath).slice(1)}`
1134
+ ];
1135
+ let hasTests = false;
1136
+ const fg = await import("fast-glob");
1137
+ for (const pattern of testPatterns) {
1138
+ const matches = await fg.default(pattern, { cwd: root, ignore: ["**/node_modules/**"] });
1139
+ if (matches.length > 0) {
1140
+ hasTests = true;
1141
+ break;
1142
+ }
1143
+ }
1144
+ if (!hasTests) {
1145
+ checks.push({
1146
+ name: "Tests Check",
1147
+ passed: true,
1148
+ // Not a blocker, just informative
1149
+ detail: `\u{1F9EA} No test file found for "${path7.basename(filePath)}" \u2014 consider adding tests`
1150
+ });
1151
+ } else {
1152
+ checks.push({
1153
+ name: "Tests Check",
1154
+ passed: true,
1155
+ detail: "\u2705 Test file found"
1156
+ });
1157
+ }
1158
+ }
1159
+ }
1160
+ } catch {
1161
+ }
1162
+ }
1014
1163
  const failed = checks.filter((c) => !c.passed);
1015
1164
  const riskLevel = failed.length >= 3 ? "critical" : failed.length >= 2 ? "high" : failed.length >= 1 ? "medium" : "low";
1016
1165
  const allowed = failed.length === 0;
@@ -1468,8 +1617,18 @@ async function handleSafety(params) {
1468
1617
  return handleHealth2(params);
1469
1618
  case "override":
1470
1619
  return handleOverride(params);
1620
+ case "security":
1621
+ return handleSecurity(params);
1622
+ case "gc":
1623
+ return handleGc(params);
1624
+ case "doctor":
1625
+ return handleDoctor(params);
1626
+ case "portability":
1627
+ return handlePortability(params);
1628
+ case "gitignore":
1629
+ return handleGitignore(params);
1471
1630
  default:
1472
- return `Unknown action "${action}". Use: guard, check, audit, lock, health, override`;
1631
+ return `Unknown action "${action}".`;
1473
1632
  }
1474
1633
  }
1475
1634
  async function handleGuard(params) {
@@ -1479,15 +1638,10 @@ async function handleGuard(params) {
1479
1638
  });
1480
1639
  }
1481
1640
  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);
1641
+ return await safetyCheck("check", params.filePath, params.command);
1486
1642
  }
1487
1643
  async function handleAudit(params) {
1488
- if (params.limit === 0) {
1489
- return await auditStats();
1490
- }
1644
+ if (params.limit === 0) return await auditStats();
1491
1645
  return await queryAudit({
1492
1646
  toolName: params.toolName,
1493
1647
  riskLevel: params.riskLevel,
@@ -1516,18 +1670,77 @@ async function handleLock(params) {
1516
1670
  }
1517
1671
  async function handleHealth2(_params) {
1518
1672
  try {
1519
- const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-7FU7XSY5.js");
1673
+ const { computeSafetyScore, formatSafetyScore } = await import("./safetyScore-OQFYLLP7.js");
1520
1674
  const score = await computeSafetyScore();
1521
- const checksStr = JSON.stringify(score.checks);
1522
- await saveHealthSnapshot(score.score, score.risk, checksStr, score.summary);
1675
+ await saveHealthSnapshot(score.score, score.risk, JSON.stringify(score.checks), score.summary);
1523
1676
  return formatSafetyScore(score);
1524
1677
  } catch (err) {
1525
1678
  return `Error computing health: ${err}`;
1526
1679
  }
1527
1680
  }
1528
1681
  function handleOverride(params) {
1529
- const tool = params.toolName || "unknown";
1530
- return safetyOverride(tool, params.reason);
1682
+ return safetyOverride(params.toolName || "unknown", params.reason);
1683
+ }
1684
+ async function handleSecurity(params) {
1685
+ sessionMemory.recordToolCall("kuma_safety_security", {});
1686
+ if (params.filePath) {
1687
+ try {
1688
+ const fs7 = await import("fs");
1689
+ const path7 = await import("path");
1690
+ const root = process.cwd();
1691
+ const fullPath = path7.resolve(root, params.filePath);
1692
+ if (fs7.existsSync(fullPath) && fs7.statSync(fullPath).isFile()) {
1693
+ const content = fs7.readFileSync(fullPath, "utf-8");
1694
+ const lines = content.split("\n");
1695
+ const findings = [];
1696
+ for (let i = 0; i < lines.length; i++) {
1697
+ const line = lines[i];
1698
+ const lc = line.toLowerCase();
1699
+ if (lc.includes("credentials: true")) {
1700
+ findings.push({ line: i + 1, pattern: "credentials-leak", severity: "high", message: "`credentials: true` in Prisma select may leak sensitive fields" });
1701
+ }
1702
+ if (lc.includes("console.log") && (lc.includes("token") || lc.includes("secret") || lc.includes("key") || lc.includes("password"))) {
1703
+ findings.push({ line: i + 1, pattern: "console-log-secret", severity: "high", message: "Potential secret logged to console" });
1704
+ }
1705
+ if (lc.includes("process.env") && (lc.includes("api_key") || lc.includes("secret") || lc.includes("token"))) {
1706
+ findings.push({ line: i + 1, pattern: "env-in-code", severity: "medium", message: "process.env.KEY referenced in code \u2014 ensure not in client bundle" });
1707
+ }
1708
+ }
1709
+ for (const f of findings) {
1710
+ await addSecurityFinding({
1711
+ filePath: params.filePath,
1712
+ lineNumber: f.line,
1713
+ pattern: f.pattern,
1714
+ severity: f.severity,
1715
+ message: f.message
1716
+ });
1717
+ }
1718
+ if (findings.length === 0) {
1719
+ return `\u{1F512} No security issues found in "${params.filePath}".`;
1720
+ }
1721
+ return `\u{1F512} **Security Scan** \u2014 ${findings.length} finding(s) in "${params.filePath}":
1722
+ ${findings.map((f) => ` ${f.severity === "high" ? "\u{1F534}" : "\u{1F7E1}"} L${f.line}: ${f.message}`).join("\n")}`;
1723
+ }
1724
+ } catch {
1725
+ }
1726
+ }
1727
+ return await getSecurityFindings();
1728
+ }
1729
+ async function handleGc(_params) {
1730
+ sessionMemory.recordToolCall("kuma_safety_gc", {});
1731
+ return await runGarbageCollection();
1732
+ }
1733
+ async function handleDoctor(_params) {
1734
+ sessionMemory.recordToolCall("kuma_safety_doctor", {});
1735
+ return await runDoctor();
1736
+ }
1737
+ async function handlePortability(_params) {
1738
+ sessionMemory.recordToolCall("kuma_safety_portability", {});
1739
+ return await checkPortability();
1740
+ }
1741
+ async function handleGitignore(_params) {
1742
+ sessionMemory.recordToolCall("kuma_safety_gitignore", {});
1743
+ return await ensureGitignore();
1531
1744
  }
1532
1745
 
1533
1746
  // src/manifest.ts
@@ -1536,7 +1749,7 @@ function registerAllTools(server) {
1536
1749
  "kuma_context",
1537
1750
  "**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
1751
  {
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"),
1752
+ 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
1753
  scope: z.string().optional().describe("Research scope for research action (e.g. 'auth', 'database', 'api')"),
1541
1754
  target: z.string().optional().describe("Target symbol/file for impact/navigate/changes"),
1542
1755
  goal: z.string().optional().describe("Current goal (for init/health)"),
@@ -1561,17 +1774,17 @@ function registerAllTools(server) {
1561
1774
  );
1562
1775
  server.tool(
1563
1776
  "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).",
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).",
1565
1778
  {
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"),
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"),
1568
1781
  query: z.string().optional().describe("Search query for search action"),
1569
- content: z.string().optional().describe("Content/notes for research_save"),
1782
+ content: z.string().optional().describe("Content/notes for research_save / context"),
1570
1783
  record: z.string().optional().describe("JSON record string for research_save"),
1571
1784
  confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
1572
1785
  // Decision params
1573
1786
  decisionAction: z.enum(["template", "suggest", "record"]).optional().describe("Decision sub-action"),
1574
- title: z.string().optional().describe("Decision title"),
1787
+ title: z.string().optional().describe("Decision/todo/benchmark title"),
1575
1788
  context: z.string().optional().describe("Decision context"),
1576
1789
  rationale: z.string().optional().describe("Decision rationale"),
1577
1790
  outcome: z.string().optional().describe("Decision outcome"),
@@ -1579,9 +1792,21 @@ function registerAllTools(server) {
1579
1792
  healAction: z.enum(["check", "heal"]).optional().describe("Heal sub-action"),
1580
1793
  topic: z.string().optional().describe("Memory topic for session"),
1581
1794
  limit: z.number().min(1).max(100).optional().describe("Result limit"),
1582
- target: z.string().optional().describe("File path for changes"),
1795
+ target: z.string().optional().describe("File path for changes / decision_log ID"),
1583
1796
  since: z.number().optional().describe("Timestamp filter for changes"),
1584
- compact: z.boolean().optional().default(false).describe("Compact output mode")
1797
+ compact: z.boolean().optional().default(false).describe("Compact output mode"),
1798
+ // Todo params
1799
+ description: z.string().optional().describe("Todo description"),
1800
+ deps: z.string().optional().describe("Todo dependencies (JSON array)"),
1801
+ success_criteria: z.string().optional().describe("Todo success criteria"),
1802
+ status: z.string().optional().describe("Status for todo/decision_log"),
1803
+ todoId: z.number().optional().describe("Todo ID for status update"),
1804
+ // Context params
1805
+ source: z.string().optional().describe("Context source (e.g. 'slack', 'jira', 'meeting')"),
1806
+ // Benchmark params
1807
+ label: z.string().optional().describe("Benchmark label"),
1808
+ metrics: z.string().optional().describe("Benchmark metrics JSON"),
1809
+ labelB: z.string().optional().describe("Second benchmark label for diff")
1585
1810
  },
1586
1811
  async (params) => {
1587
1812
  try {
@@ -1611,14 +1836,14 @@ function registerAllTools(server) {
1611
1836
  );
1612
1837
  server.tool(
1613
1838
  "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).",
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).",
1615
1840
  {
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"),
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"),
1617
1842
  // Guard params
1618
1843
  guardGoal: z.string().optional().describe("Goal for guard check"),
1619
1844
  guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
1620
1845
  // Check params
1621
- filePath: z.string().optional().describe("File path for check/lock"),
1846
+ filePath: z.string().optional().describe("File path for check/lock/security scan"),
1622
1847
  command: z.string().optional().describe("Command for check"),
1623
1848
  // Audit params
1624
1849
  toolName: z.string().optional().describe("Filter audit by tool name"),
@@ -1788,7 +2013,7 @@ async function main() {
1788
2013
  });
1789
2014
  (async () => {
1790
2015
  try {
1791
- const { generateInitMdContent } = await import("./init-O4MBXFZL.js");
2016
+ const { generateInitMdContent } = await import("./init-3MBHSTWD.js");
1792
2017
  const fs7 = await import("fs");
1793
2018
  const path7 = await import("path");
1794
2019
  const initMdPath = path7.resolve(process.cwd(), ".kuma/init.md");
@@ -1804,7 +2029,7 @@ async function main() {
1804
2029
  })();
1805
2030
  (async () => {
1806
2031
  try {
1807
- const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-FDRVUNJY.js");
2032
+ const { detectAgent, getSkillPath, getAgentLabel } = await import("./agentDetector-G4RBMZ6X.js");
1808
2033
  const { generateSkill, getSecondaryFiles } = await import("./skillGenerator-F6YO4H5D.js");
1809
2034
  const fs7 = await import("fs");
1810
2035
  const path7 = await import("path");
@@ -1838,6 +2063,38 @@ async function main() {
1838
2063
  console.error(`[${SERVER_NAME}] Failed to auto-create skill file: ${err}`);
1839
2064
  }
1840
2065
  })();
2066
+ (async () => {
2067
+ try {
2068
+ console.error(`[${SERVER_NAME}] \u{1F504} Running cold start bootstrap...`);
2069
+ const sessionInfo = sessionMemory.loadSession();
2070
+ if (sessionInfo.hasPrevSession) {
2071
+ console.error(`[${SERVER_NAME}] \u2705 Restored session (${sessionInfo.toolCallCount} previous tool calls)`);
2072
+ }
2073
+ try {
2074
+ const { buildFromSessionMemory } = await import("./kumaGraph-VUTLTOFY.js");
2075
+ const edgeCount = await buildFromSessionMemory();
2076
+ if (edgeCount > 0) {
2077
+ console.error(`[${SERVER_NAME}] \u2705 Graph auto-populated with ${edgeCount} entries from session memory`);
2078
+ }
2079
+ } catch (err) {
2080
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Graph auto-population: ${err}`);
2081
+ }
2082
+ try {
2083
+ const { getDb: getDb2, saveDb: saveDb2 } = await import("./kumaDb-PA7XVERC.js");
2084
+ const db = await getDb2();
2085
+ db.run(
2086
+ `INSERT INTO sessions (started_at, goal, tool_calls) VALUES (?, ?, ?)`,
2087
+ [Math.floor(Date.now() / 1e3), sessionMemory.getSummary().currentGoal || "Session start", sessionInfo.toolCallCount]
2088
+ );
2089
+ saveDb2(db);
2090
+ } catch (err) {
2091
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Session DB record: ${err}`);
2092
+ }
2093
+ console.error(`[${SERVER_NAME}] \u2705 Cold start bootstrap complete`);
2094
+ } catch (err) {
2095
+ console.error(`[${SERVER_NAME}] \u26A0\uFE0F Cold start bootstrap error: ${err}`);
2096
+ }
2097
+ })();
1841
2098
  const server = new McpServer(
1842
2099
  {
1843
2100
  name: SERVER_NAME,