@plumpslabs/kuma 2.4.4 β†’ 2.4.6

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.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: kuma-guardian
3
+ description: Runtime safety guardian. Pre-flight check, architecture boundary audit, circular dependency detection, and anti-pattern prevention. Read-only.
4
+ mode: subagent
5
+ mainAgent: false
6
+ subagent: true
7
+ permission:
8
+ read: allow
9
+ grep: allow
10
+ glob: allow
11
+ list: allow
12
+ bash: deny
13
+ webfetch: deny
14
+ websearch: deny
15
+ task: deny
16
+ edit:
17
+ "*": deny
18
+ disallowedTools: Write, Edit, Task
19
+ ---
20
+
21
+ <agent_persona>
22
+ You are the Kuma Safety Guardian. Your directive is pre-modification safety enforcement and architectural protection.
23
+ You analyze proposed changes, detect anti-patterns, circular dependencies, and verify that the agent does not edit code blindly.
24
+ </agent_persona>
25
+
26
+ <strict_boundaries>
27
+ - READ-ONLY: Never modify any source code files.
28
+ - Inspect codebase via kuma_context, kuma_safety, grep, glob, and read tools.
29
+ - If high risk or circular dependency is detected, raise explicit safety blocking recommendation.
30
+ </strict_boundaries>
31
+
32
+ <workflow>
33
+ 1. Run `kuma_safety({ action: "guard" })` to check session state and edit loops.
34
+ 2. Check file boundaries and package dependencies using `kuma_context({ action: "map" })`.
35
+ 3. If circular imports or boundary violations are found, report exact import chains.
36
+ 4. Render verdict: PASS or BLOCK.
37
+ </workflow>
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: kuma-researcher
3
+ description: Codebase intelligence & blast radius researcher. Maps monorepo packages, traces dependency trees, inspects past decisions, and gathers gotchas before changes. Read-only.
4
+ mode: subagent
5
+ mainAgent: false
6
+ subagent: true
7
+ permission:
8
+ read: allow
9
+ grep: allow
10
+ glob: allow
11
+ list: allow
12
+ bash: deny
13
+ webfetch: deny
14
+ websearch: deny
15
+ task: deny
16
+ edit:
17
+ "*": deny
18
+ disallowedTools: Write, Edit, Task
19
+ ---
20
+
21
+ <agent_persona>
22
+ You are the Kuma Codebase Researcher. You explore unfamiliar code, compute blast radius, discover domain flows, and extract historical gotchas so the main agent has 100% clarity before modifying code.
23
+ </agent_persona>
24
+
25
+ <strict_boundaries>
26
+ - READ-ONLY: Never modify any source code files.
27
+ - Focus strictly on research, dependency traversal, and impact scoring.
28
+ </strict_boundaries>
29
+
30
+ <workflow>
31
+ 1. Initialize context via `kuma_context({ action: "init" })`.
32
+ 2. Inspect area via `kuma_context({ action: "research", scope: "<target>" })`.
33
+ 3. Calculate blast radius via `kuma_context({ action: "impact", target: "<target>" })`.
34
+ 4. Check cross-session history via `kuma_context({ action: "history", target: "<file>" })`.
35
+ 5. Return structured report with affected files, consumers, and known gotchas.
36
+ </workflow>
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: "πŸ“Έ Kuma Checkpoint β€” Create an atomic named snapshot before risky work"
3
+ ---
4
+ # /kuma:checkpoint
5
+
6
+ Take an atomic rollback snapshot of current files and knowledge graph.
7
+
8
+ ```javascript
9
+ kuma_safety({ action: "checkpoint", label: "[pre-refactor-feature-x]" })
10
+ ```
11
+
12
+ Creates `<label>/` snapshot with `kuma.db` + modified files.
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: "πŸ›‘οΈ Kuma Guard β€” Pre-flight safety check against anti-patterns, drift, and circular deps"
3
+ ---
4
+ # /kuma:guard
5
+
6
+ Verify safety boundaries before applying complex refactors or multi-file edits.
7
+
8
+ ```javascript
9
+ kuma_safety({ action: "guard", guardGoal: "[what you are doing]" })
10
+ ```
11
+
12
+ **What it checks:**
13
+ - Missing pre-modification research
14
+ - Architecture boundary violations & circular imports
15
+ - Scope drift (editing files outside declared goal)
16
+ - Runaway execution loops
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: "πŸ’₯ Kuma Impact β€” Calculate blast radius, affected consumers, and safety risk score"
3
+ ---
4
+ # /kuma:impact
5
+
6
+ Evaluate blast radius before modifying high-leverage files or shared packages.
7
+
8
+ ```javascript
9
+ kuma_context({ action: "impact", target: "[file or package path]" })
10
+ ```
11
+
12
+ **What it returns:**
13
+ - Direct and transitive consumers
14
+ - Downstream affected packages in monorepos
15
+ - Mapped test suites to run
16
+ - Safety risk level (`low`, `medium`, `high`, `critical`)
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: "🐻 Kuma Init β€” Initialize session context, lean brief (<500 tokens), and branch tracking"
3
+ ---
4
+ # /kuma:init
5
+
6
+ Initialize Kuma session context before writing any code.
7
+
8
+ ```javascript
9
+ kuma_context({ action: "init", goal: "[current task goal]" })
10
+ ```
11
+
12
+ **What it does:**
13
+ - Restores cross-session shadow memory
14
+ - Loads fresh gotchas for the active branch
15
+ - Evaluates repository rules and path constraints
16
+ - Informs about branch switches
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: "πŸ”¬ Kuma Research β€” 5-step pre-edit research pipeline before editing unfamiliar code"
3
+ ---
4
+ # /kuma:research
5
+
6
+ Run research pipeline before editing code you haven't touched before.
7
+
8
+ ```javascript
9
+ kuma_context({ action: "research", scope: "[module or feature area]" })
10
+ ```
11
+
12
+ **Pipeline Stages:**
13
+ 1. Check research cache in `.kuma/kuma.db`
14
+ 2. Query knowledge graph for related nodes and edges
15
+ 3. Scan references and entry points
16
+ 4. Compute blast radius and test coverage
17
+ 5. Retrieve historical ADR decisions and gotchas
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: "βͺ Kuma Rollback β€” Restore codebase from a labeled checkpoint snapshot"
3
+ ---
4
+ # /kuma:rollback
5
+
6
+ Restore codebase to the exact state captured in a previous checkpoint.
7
+
8
+ ```javascript
9
+ kuma_safety({ action: "rollback_label", label: "[label-name]" })
10
+ ```
11
+
12
+ If the label is not found, Kuma lists all available snapshots.
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: "πŸ“Š Kuma Studio β€” Web dashboard for gotchas, flows, workspace matrix, and metrics"
3
+ ---
4
+ # /kuma:studio
5
+
6
+ Launch or view Kuma Studio web dashboard.
7
+
8
+ ```bash
9
+ npx @plumpslabs/kuma studio
10
+ ```
11
+
12
+ Opens `http://localhost:3322` featuring:
13
+ - **Gotcha Shield** (status filters & 1-click workaround copy)
14
+ - **Domain Sequence Flows**
15
+ - **Workspace & Blast Radius Simulator**
16
+ - **Health & Efficiency Metrics**
17
+ - **Calm Knowledge Graph** (physics toggle)
@@ -0,0 +1,15 @@
1
+ ---
2
+ description: "πŸ§ͺ Kuma Verify β€” Post-edit scoped test verification and validation"
3
+ ---
4
+ # /kuma:verify
5
+
6
+ Run scoped tests and verify that changes did not introduce regressions.
7
+
8
+ ```javascript
9
+ kuma_safety({ action: "verify", scope: "[modified scope or file]" })
10
+ ```
11
+
12
+ **What it does:**
13
+ - Automatically identifies appropriate test runner (Jest, Vitest, Pytest, Go test, Cargo)
14
+ - Executes tests restricted to the modified blast radius
15
+ - Records pass/fail metrics in session memory
package/dist/index.js CHANGED
@@ -3297,7 +3297,8 @@ __export(kumaGraph_exports, {
3297
3297
  });
3298
3298
  function nodeId(type, name) {
3299
3299
  if (type === "file") {
3300
- return `file::${name}`;
3300
+ const normalized = normalizeScope(name) || name;
3301
+ return `file::${normalized}`;
3301
3302
  }
3302
3303
  return generateNodeId(type, name);
3303
3304
  }
@@ -3358,7 +3359,9 @@ async function pruneGraphIfNeeded() {
3358
3359
  async function upsertNode(node) {
3359
3360
  try {
3360
3361
  const db = await getDb();
3361
- const id = node.id || nodeId(node.type, node.name);
3362
+ const normName = node.type === "file" ? normalizeScope(node.name) || node.name : node.name;
3363
+ const normFilePath = node.filePath ? normalizeScope(node.filePath) || node.filePath : node.type === "file" ? normName : void 0;
3364
+ const id = node.id || nodeId(node.type, normName);
3362
3365
  const metadata = JSON.stringify(node.metadata ?? {});
3363
3366
  const severity = node.metadata?.severity || "medium";
3364
3367
  const confidence = node.metadata?.confidence || 0.8;
@@ -3373,19 +3376,19 @@ async function upsertNode(node) {
3373
3376
  severity = excluded.severity,
3374
3377
  confidence = excluded.confidence,
3375
3378
  updated_at = strftime('%s','now')
3376
- `, [id, node.type, node.name, node.filePath || null, metadata, severity, confidence]);
3379
+ `, [id, node.type, normName, normFilePath || null, metadata, severity, confidence]);
3377
3380
  try {
3378
- db.run(`INSERT INTO nodes_fts (rowid, name, metadata) VALUES (last_insert_rowid(), ?, ?)`, [node.name, metadata]);
3381
+ db.run(`INSERT INTO nodes_fts (rowid, name, metadata) VALUES (last_insert_rowid(), ?, ?)`, [normName, metadata]);
3379
3382
  } catch {
3380
3383
  }
3381
- if (node.filePath && node.type !== "file") {
3382
- const fileNodeId = nodeId("file", node.filePath);
3384
+ if (normFilePath && node.type !== "file") {
3385
+ const fileNodeId = nodeId("file", normFilePath);
3383
3386
  try {
3384
3387
  db.run(`
3385
3388
  INSERT INTO nodes (id, type, name, file_path, metadata, updated_at)
3386
3389
  VALUES (?, 'file', ?, ?, '{}', strftime('%s','now'))
3387
3390
  ON CONFLICT(id) DO NOTHING
3388
- `, [fileNodeId, node.filePath, node.filePath]);
3391
+ `, [fileNodeId, normFilePath, normFilePath]);
3389
3392
  db.run(`
3390
3393
  INSERT INTO edges (source_id, target_id, type, weight, metadata, created_at)
3391
3394
  VALUES (?, ?, 'contains', 1.0, '{}', strftime('%s','now'))
@@ -4411,6 +4414,7 @@ var init_kumaGraph = __esm({
4411
4414
  init_kumaDb();
4412
4415
  init_kumaSelfHeal();
4413
4416
  init_languageSupport();
4417
+ init_pathValidator();
4414
4418
  GRAPH_LIMITS = {
4415
4419
  maxNodes: 5e3,
4416
4420
  // Max nodes before pruning
@@ -5335,15 +5339,17 @@ async function addGotcha(entry) {
5335
5339
  const severity = entry.severity || "medium";
5336
5340
  const status = entry.status || "active";
5337
5341
  const contentHash = hashFileContent(entry.filePath);
5342
+ const trap = entry.description;
5343
+ const ruleFix = entry.workaround || "Review implementation carefully before modifying.";
5338
5344
  const formatted = [
5339
- `### ${entry.filePath} \u2014 ${entry.description}`,
5340
- `- **Issue**: ${entry.description}`,
5341
- `- **Severity**: ${severity}`,
5342
- entry.workaround ? `- **Workaround**: ${entry.workaround}` : "",
5343
- entry.triggerCommand ? `- **Trigger**: when running \`${entry.triggerCommand}\`` : "",
5344
- entry.status ? `- **Status**: ${entry.status}` : "",
5345
- entry.scopePackage ? `- **Scope Package**: ${entry.scopePackage}` : "",
5346
- `- **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
5345
+ `### ${entry.filePath} \u2014 ${trap}`,
5346
+ `- \u{1FAA4} **Trap**: ${trap}`,
5347
+ `- \u{1F6E1}\uFE0F **Rule/Fix**: ${ruleFix}`,
5348
+ `- \u26A1 **Severity**: ${severity}`,
5349
+ entry.triggerCommand ? `- \u2328\uFE0F **Trigger**: when running \`${entry.triggerCommand}\`` : "",
5350
+ entry.status ? `- \u{1F3F7}\uFE0F **Status**: ${status}` : "",
5351
+ entry.scopePackage ? `- \u{1F4E6} **Scope Package**: ${entry.scopePackage}` : "",
5352
+ `- \u{1F4C5} **Added**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
5347
5353
  ].filter(Boolean).join("\n");
5348
5354
  const checkStmt = db.prepare(
5349
5355
  `SELECT id FROM known_gotchas WHERE file_path = ? AND description LIKE ?`
@@ -7539,10 +7545,16 @@ function detectTestRunner(root = process.cwd()) {
7539
7545
  if (fs24.existsSync(pkgPath)) {
7540
7546
  try {
7541
7547
  const pkg = JSON.parse(fs24.readFileSync(pkgPath, "utf-8"));
7548
+ const isJest = Boolean(
7549
+ pkg.devDependencies?.jest || pkg.dependencies?.jest || pkg.scripts?.test && pkg.scripts.test.includes("jest") || fs24.existsSync(path25.join(root, "jest.config.js")) || fs24.existsSync(path25.join(root, "jest.config.ts")) || fs24.existsSync(path25.join(root, "jest.config.mjs")) || fs24.existsSync(path25.join(root, "jest.config.json"))
7550
+ );
7551
+ const isVitest = Boolean(
7552
+ pkg.devDependencies?.vitest || pkg.dependencies?.vitest || pkg.scripts?.test && pkg.scripts.test.includes("vitest") || fs24.existsSync(path25.join(root, "vitest.config.ts")) || fs24.existsSync(path25.join(root, "vitest.config.js")) || fs24.existsSync(path25.join(root, "vitest.config.mjs"))
7553
+ );
7542
7554
  if (pkg.scripts && pkg.scripts.test) {
7543
- if (fs24.existsSync(path25.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test" };
7544
- if (fs24.existsSync(path25.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test" };
7545
- return { runner: "npm", baseCommand: "npm test" };
7555
+ if (fs24.existsSync(path25.join(root, "pnpm-lock.yaml"))) return { runner: "pnpm", baseCommand: "pnpm test", isJest, isVitest };
7556
+ if (fs24.existsSync(path25.join(root, "yarn.lock"))) return { runner: "yarn", baseCommand: "yarn test", isJest, isVitest };
7557
+ return { runner: "npm", baseCommand: "npm test", isJest, isVitest };
7546
7558
  }
7547
7559
  } catch {
7548
7560
  }
@@ -7622,7 +7634,7 @@ async function runAutoVerification(options = {}) {
7622
7634
  }
7623
7635
  }
7624
7636
  _localRunning = true;
7625
- const { runner, baseCommand } = detectTestRunner(root);
7637
+ const { runner, baseCommand, isJest, isVitest } = detectTestRunner(root);
7626
7638
  if (runner === "unknown" || !baseCommand) {
7627
7639
  _localRunning = false;
7628
7640
  releaseFileLock(root);
@@ -7644,49 +7656,88 @@ async function runAutoVerification(options = {}) {
7644
7656
  }
7645
7657
  let fullCommand = baseCommand;
7646
7658
  let testFiles = [];
7647
- const modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
7648
- let workspaceScoped = false;
7649
- try {
7650
- const { getWorkspaceInfo: getWorkspaceInfo2, getAffectedPackages: getAffectedPackages2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
7651
- const wsInfo = await getWorkspaceInfo2(root);
7652
- if (wsInfo.isWorkspace && modified.length > 0 && !options.scope) {
7653
- const affected = getAffectedPackages2(modified, wsInfo);
7654
- if (affected.affectedTestCommands.length > 0) {
7655
- fullCommand = affected.affectedTestCommands[0];
7656
- workspaceScoped = true;
7659
+ let modified = sessionMemory.getModifiedFiles().map((f) => f.filePath);
7660
+ if (modified.length === 0) {
7661
+ try {
7662
+ const { execSync: execSync6 } = await import("child_process");
7663
+ const gitDiff = execSync6("git diff --name-only HEAD", { encoding: "utf-8", timeout: 3e3, cwd: root }).trim();
7664
+ if (gitDiff) {
7665
+ modified = gitDiff.split("\n").map((f) => f.trim()).filter(Boolean);
7657
7666
  }
7667
+ } catch {
7658
7668
  }
7659
- } catch {
7660
7669
  }
7661
- if (!workspaceScoped) {
7662
- if (options.scope) {
7663
- const term = options.scope.toLowerCase();
7664
- testFiles = modified.filter((f) => f.toLowerCase().includes(term));
7665
- if (testFiles.length === 0) {
7666
- try {
7667
- const { default: glob } = await import("fast-glob");
7668
- testFiles = await glob([`**/*${term}*test*.*`, `**/*test*/*${term}*.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
7669
- } catch {
7670
+ const targetScope = (options.scope || options.target || "").trim();
7671
+ if (options.command) {
7672
+ const cmd = options.command.trim();
7673
+ const isTestCmd = /^(npm|pnpm|yarn|bun|npx|pytest|cargo|go|make|jest|vitest)\s+/i.test(cmd);
7674
+ if (isTestCmd) {
7675
+ fullCommand = cmd;
7676
+ }
7677
+ } else {
7678
+ let workspaceScoped = false;
7679
+ try {
7680
+ const { getWorkspaceInfo: getWorkspaceInfo2, getAffectedPackages: getAffectedPackages2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
7681
+ const wsInfo = await getWorkspaceInfo2(root);
7682
+ if (wsInfo.isWorkspace && modified.length > 0 && !targetScope) {
7683
+ const affected = getAffectedPackages2(modified, wsInfo);
7684
+ if (affected.affectedTestCommands.length > 0) {
7685
+ fullCommand = affected.affectedTestCommands[0];
7686
+ workspaceScoped = true;
7670
7687
  }
7671
7688
  }
7672
- } else {
7673
- const directTests = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
7674
- if (directTests.length > 0) {
7675
- testFiles = directTests;
7676
- } else if (modified.length > 0) {
7677
- try {
7678
- const { default: glob } = await import("fast-glob");
7679
- for (const f of modified.slice(0, 5)) {
7680
- const base = path25.basename(f, path25.extname(f));
7681
- const matches = await glob([`**/*${base}*.test.*`, `**/*${base}*.spec.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
7682
- testFiles.push(...matches);
7689
+ } catch {
7690
+ }
7691
+ if (!workspaceScoped) {
7692
+ if (targetScope && targetScope !== "session-impact") {
7693
+ const resolved = path25.resolve(root, targetScope);
7694
+ if (fs24.existsSync(resolved) && fs24.statSync(resolved).isFile()) {
7695
+ testFiles = [path25.relative(root, resolved)];
7696
+ } else {
7697
+ const term = targetScope.toLowerCase();
7698
+ testFiles = modified.filter((f) => f.toLowerCase().includes(term));
7699
+ if (testFiles.length === 0) {
7700
+ try {
7701
+ const { default: glob } = await import("fast-glob");
7702
+ const baseName = path25.basename(targetScope, path25.extname(targetScope));
7703
+ testFiles = await glob([
7704
+ `**/*${baseName}*.test.*`,
7705
+ `**/*${baseName}*.spec.*`,
7706
+ `**/*${term}*test*.*`,
7707
+ `**/*test*/*${term}*.*`,
7708
+ `**/${targetScope}`
7709
+ ], { cwd: root, ignore: ["node_modules/**", "dist/**", ".git/**"] });
7710
+ } catch {
7711
+ }
7712
+ }
7713
+ }
7714
+ } else {
7715
+ const directTests = modified.filter((f) => f.includes(".test.") || f.includes(".spec.") || f.includes("_test."));
7716
+ if (directTests.length > 0) {
7717
+ testFiles = directTests;
7718
+ } else if (modified.length > 0) {
7719
+ try {
7720
+ const { default: glob } = await import("fast-glob");
7721
+ for (const f of modified.slice(0, 5)) {
7722
+ const base = path25.basename(f, path25.extname(f));
7723
+ const matches = await glob([`**/*${base}*.test.*`, `**/*${base}*.spec.*`], { cwd: root, ignore: ["node_modules/**", "dist/**"] });
7724
+ testFiles.push(...matches);
7725
+ }
7726
+ } catch {
7683
7727
  }
7684
- } catch {
7685
7728
  }
7686
7729
  }
7687
- }
7688
- if (testFiles.length > 0 && (runner === "npm" || runner === "pnpm" || runner === "yarn")) {
7689
- fullCommand = `${baseCommand} -- ${Array.from(new Set(testFiles)).map((f) => `"${f}"`).join(" ")}`;
7730
+ if (testFiles.length > 0 && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
7731
+ fullCommand = `${baseCommand} -- ${Array.from(new Set(testFiles)).map((f) => `"${f}"`).join(" ")}`;
7732
+ } else if (testFiles.length === 0 && modified.length > 0 && isJest && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
7733
+ const relFiles = Array.from(new Set(modified.slice(0, 15))).map((f) => `"${f}"`).join(" ");
7734
+ fullCommand = `${baseCommand} -- --findRelatedTests ${relFiles}`;
7735
+ } else if (testFiles.length === 0 && modified.length > 0 && isVitest && (runner === "npm" || runner === "pnpm" || runner === "yarn" || runner === "bun")) {
7736
+ const relFiles = Array.from(new Set(modified.slice(0, 15))).map((f) => `"${f}"`).join(" ");
7737
+ fullCommand = `${baseCommand} -- related ${relFiles} --run`;
7738
+ } else if (testFiles.length > 0 && runner === "pytest") {
7739
+ fullCommand = `pytest ${Array.from(new Set(testFiles)).map((f) => `"${f}"`).join(" ")}`;
7740
+ }
7690
7741
  }
7691
7742
  }
7692
7743
  return await new Promise((resolve) => {
@@ -7761,9 +7812,11 @@ async function runAutoVerification(options = {}) {
7761
7812
  _currentProcess = null;
7762
7813
  releaseFileLock(root);
7763
7814
  clearInterval(heartbeatTimer);
7764
- const statusSymbol = passed ? "\u2705" : "\u{1F534}";
7815
+ const isTimedOut = Boolean(error && error.killed);
7816
+ const statusSymbol = passed ? "\u2705" : isTimedOut ? "\u23F1\uFE0F" : "\u{1F534}";
7817
+ const statusTitle = passed ? "PASSED" : isTimedOut ? "TIMED_OUT" : "FAILED";
7765
7818
  const lines = [
7766
- `${statusSymbol} **Verification ${passed ? "PASSED" : "FAILED"}**`,
7819
+ `${statusSymbol} **Verification ${statusTitle}**`,
7767
7820
  `\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`,
7768
7821
  "",
7769
7822
  `\u{1F6E0}\uFE0F **Runner**: \`${runner}\``,
@@ -7776,12 +7829,14 @@ async function runAutoVerification(options = {}) {
7776
7829
  if (isNoTests) {
7777
7830
  lines[0] = `\u26AA **Verification: NO TESTS**`;
7778
7831
  lines.push("\u2139\uFE0F No tests ran. Install Jest, Vitest, pytest, or similar to enable verification.");
7832
+ } else if (isTimedOut) {
7833
+ lines.push(`\u23F0 **Process was killed after ${timeoutMs}ms timeout (TIMED_OUT)**`);
7834
+ lines.push("\u2139\uFE0F Tests did not fail; the test execution exceeded the allotted timeout threshold.");
7835
+ lines.push(`\u{1F4A1} *Tip*: Run a specific test file via \`scope: 'tests/my-test.test.ts'\`, specify a custom command via \`command: '...' \`, or increase timeout via \`timeoutMs: ${Math.max(timeoutMs * 2, 12e4)}\`.`);
7836
+ if (rawOutput) lines.push("```text", rawOutput.substring(0, 1500), "```");
7779
7837
  } else {
7780
7838
  lines.push("\u26A0\uFE0F **Verification failed!** Please fix test failures before shipping.");
7781
- if (error && error.killed) {
7782
- lines.push(`\u23F0 **Process was killed after ${timeoutMs}ms timeout**`);
7783
- }
7784
- lines.push("```text", rawOutput.substring(0, 1500), "```");
7839
+ if (rawOutput) lines.push("```text", rawOutput.substring(0, 1500), "```");
7785
7840
  }
7786
7841
  } else {
7787
7842
  lines.push("\u{1F389} All scoped tests passed!");
@@ -7815,7 +7870,7 @@ var init_kumaVerifier = __esm({
7815
7870
  _localRunning = false;
7816
7871
  _currentProcess = null;
7817
7872
  STALE_RESULT_MS = 3e5;
7818
- DEFAULT_TIMEOUT_MS = 3e4;
7873
+ DEFAULT_TIMEOUT_MS = 6e4;
7819
7874
  RUNAWAY_WINDOW_MS = 3e5;
7820
7875
  RUNAWAY_MAX_CALLS = 3;
7821
7876
  _verifyCallTimestamps = [];
@@ -10255,7 +10310,8 @@ async function handleInit(_params) {
10255
10310
  lines.push("**Active Gotchas (fresh & budgeted)**");
10256
10311
  for (const g of budgetedGotchas) {
10257
10312
  const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
10258
- lines.push(` ${icon} [${g.severity}] ${g.filePath} \u2014 ${g.description.substring(0, 90)}`);
10313
+ const desc = g.description.length <= 180 ? g.description : g.description.substring(0, 177).replace(/\s+\S*$/, "") + "...";
10314
+ lines.push(` ${icon} [${g.severity}] ${g.filePath} \u2014 ${desc}`);
10259
10315
  }
10260
10316
  if (totalActiveCount > budgetedGotchas.length) {
10261
10317
  lines.push(` \u2139\uFE0F +${totalActiveCount - budgetedGotchas.length} more active gotchas in DB (query: kuma_memory({ action: 'gotcha' }))`);
@@ -10433,7 +10489,10 @@ async function handleHistory(params) {
10433
10489
  const icon = g.severity === "critical" ? "\u{1F534}" : g.severity === "high" ? "\u{1F7E0}" : g.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
10434
10490
  lines.push(` ${icon} [${g.severity}] ${g.description}`);
10435
10491
  if (g.triggerCommand) lines.push(` \u2328\uFE0F when running: \`${g.triggerCommand}\``);
10436
- if (g.workaround) lines.push(` \u{1F4A1} ${g.workaround.substring(0, 140)}`);
10492
+ if (g.workaround) {
10493
+ const work = g.workaround.length <= 180 ? g.workaround : g.workaround.substring(0, 177).replace(/\s+\S*$/, "") + "...";
10494
+ lines.push(` \u{1F4A1} ${work}`);
10495
+ }
10437
10496
  }
10438
10497
  lines.push("");
10439
10498
  }
@@ -10447,7 +10506,10 @@ async function handleHistory(params) {
10447
10506
  stmt.free();
10448
10507
  if (resolved.length > 0) {
10449
10508
  lines.push("\u2705 **Resolved gotchas** (fixed):");
10450
- for (const r of resolved) lines.push(` \u2705 ${r.description.substring(0, 100)}`);
10509
+ for (const r of resolved) {
10510
+ const rdesc = r.description.length <= 140 ? r.description : r.description.substring(0, 137).replace(/\s+\S*$/, "") + "...";
10511
+ lines.push(` \u2705 ${rdesc}`);
10512
+ }
10451
10513
  lines.push("");
10452
10514
  }
10453
10515
  } catch {
@@ -10529,6 +10591,8 @@ var MEMORY_ALIASES = {
10529
10591
  "resolve_gotcha": "gotcha",
10530
10592
  "resolve-gotcha": "gotcha",
10531
10593
  "resolve": "gotcha",
10594
+ "gotcha_resolve": "gotcha",
10595
+ "gotcha-resolve": "gotcha",
10532
10596
  "deprecate_gotcha": "gotcha",
10533
10597
  "deprecate-gotcha": "gotcha",
10534
10598
  "deprecate": "gotcha"
@@ -10554,13 +10618,15 @@ async function handleMemory(params) {
10554
10618
  }
10555
10619
  }
10556
10620
  async function handleDecision(params) {
10557
- if (!params.title) return "\u26A0\uFE0F `title` is required. Use: kuma_memory({ action: 'decision', title: '...', rationale: '...' })";
10558
- if (!params.rationale) return "\u26A0\uFE0F `rationale` is required. Why did you choose this option?";
10621
+ const title = params.title || params.content || (params.target ? `Decision regarding ${params.target}` : params.scope ? `Decision for ${params.scope}` : "");
10622
+ const rationale = params.rationale || params.description || (params.content && params.content !== title ? params.content : "");
10623
+ if (!title) return "\u26A0\uFE0F `title` is required (or provide `content`/`target`). Use: kuma_memory({ action: 'decision', title: '...', rationale: '...' })";
10624
+ if (!rationale) return "\u26A0\uFE0F `rationale` is required (or provide `content`/`description`). Why did you choose this option?";
10559
10625
  const result = await recordDecision({
10560
- title: params.title,
10561
- context: params.context || "",
10626
+ title,
10627
+ context: params.context || (params.target ? `Target: ${params.target}` : params.scope ? `Scope: ${params.scope}` : ""),
10562
10628
  options: [],
10563
- rationale: params.rationale,
10629
+ rationale,
10564
10630
  outcome: params.outcome || "implemented",
10565
10631
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
10566
10632
  });
@@ -10722,7 +10788,7 @@ async function handleArchFlow(params) {
10722
10788
  async function handleGotchaAction(params) {
10723
10789
  const rawStatus = (params.status || "").toLowerCase().trim();
10724
10790
  const rawAction = (params.action || "").toLowerCase().trim();
10725
- const isResolution = rawStatus === "resolved" || rawStatus === "resolve" || rawAction === "resolve_gotcha" || rawAction === "resolve";
10791
+ const isResolution = rawStatus === "resolved" || rawStatus === "resolve" || rawAction === "resolve_gotcha" || rawAction === "gotcha_resolve" || rawAction === "gotcha-resolve" || rawAction === "resolve";
10726
10792
  const isDeprecation = rawStatus === "deprecated" || rawStatus === "deprecate" || rawAction === "deprecate_gotcha" || rawAction === "deprecate";
10727
10793
  const target = params.target || params.id || params.scope;
10728
10794
  if (isResolution && target) {
@@ -11287,13 +11353,13 @@ async function handleRollbackLabel(params) {
11287
11353
  const { rollbackToCheckpoint: rollbackToCheckpoint2 } = await Promise.resolve().then(() => (init_kumaCheckpoint(), kumaCheckpoint_exports));
11288
11354
  return await rollbackToCheckpoint2(params.label);
11289
11355
  }
11290
- var _lastVerifyCall = 0;
11291
11356
  var VERIFY_COOLDOWN_MS = 3e4;
11357
+ var _lastVerifyCall = 0;
11292
11358
  async function handleVerify(params) {
11293
11359
  const now = Date.now();
11294
- if (_lastVerifyCall > 0 && now - _lastVerifyCall < VERIFY_COOLDOWN_MS) {
11360
+ if (now - _lastVerifyCall < VERIFY_COOLDOWN_MS && !params.force) {
11295
11361
  const remaining = Math.ceil((VERIFY_COOLDOWN_MS - (now - _lastVerifyCall)) / 1e3);
11296
- return `\u23F3 **Handler rate limit** \u2014 verify was just called ${Math.floor((now - _lastVerifyCall) / 1e3)}s ago. Please wait ${remaining}s before calling verify again.`;
11362
+ return `\u23F3 Verification cooldown active. Please wait ${remaining}s before calling verify again, or use force: true.`;
11297
11363
  }
11298
11364
  _lastVerifyCall = now;
11299
11365
  const recordingSummary = sessionMemory.getRecordingSummary();
@@ -11312,12 +11378,16 @@ async function handleVerify(params) {
11312
11378
 
11313
11379
  \u2705 **Recordings:** ${recordingSummary.total} total (${recordingSummary.archFlows} arch_flow, ${recordingSummary.gotchas} gotcha, ${recordingSummary.decisions} decision, ${recordingSummary.researchSaves} research_save)`;
11314
11380
  }
11315
- sessionMemory.recordToolCall("kuma_safety_verify", { scope: params.scope });
11381
+ const effectiveScope = params.scope || params.target;
11382
+ const timeoutMs = typeof params.timeoutMs === "number" && params.timeoutMs > 0 ? params.timeoutMs : typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 : process.env.KUMA_VERIFY_TIMEOUT_MS ? parseInt(process.env.KUMA_VERIFY_TIMEOUT_MS, 10) : 6e4;
11383
+ sessionMemory.recordToolCall("kuma_safety_verify", { scope: effectiveScope, timeoutMs });
11316
11384
  const { runAutoVerification: runAutoVerification2 } = await Promise.resolve().then(() => (init_kumaVerifier(), kumaVerifier_exports));
11317
11385
  const verifyResult = await runAutoVerification2({
11318
- scope: params.scope,
11386
+ scope: effectiveScope,
11387
+ target: params.target,
11388
+ command: params.command,
11319
11389
  force: params.force,
11320
- timeoutMs: 3e4
11390
+ timeoutMs
11321
11391
  });
11322
11392
  return verifyResult + recordingWarning;
11323
11393
  }
@@ -11383,113 +11453,123 @@ async function doInit() {
11383
11453
  // src/manifest.ts
11384
11454
  var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
11385
11455
  function registerAllTools(server) {
11386
- server.tool(
11387
- "kuma_context",
11388
- "Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow).",
11389
- {
11390
- action: z.enum(["init", "research", "history", "flow"]).describe(
11391
- "init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow"
11392
- ),
11393
- scope: z.string().optional().describe("Research scope for research action"),
11394
- target: z.string().optional().describe("Target symbol/file for history/flow"),
11395
- goal: z.string().optional().describe("Current goal (for init)")
11396
- },
11397
- async (params) => {
11398
- try {
11399
- await ensureInitialized();
11400
- const text = await handleContext({
11401
- action: params.action,
11402
- scope: params.scope,
11403
- target: params.target,
11404
- goal: params.goal
11405
- });
11406
- return { content: [{ type: "text", text }] };
11407
- } catch (err) {
11408
- return { content: [{ type: "text", text: `Error in kuma_context: ${err}` }], isError: true };
11409
- }
11456
+ const contextDesc = "Context & memory recall. Call FIRST each session. Lean \u2014 each call returns only what you need." + CORE_NOTE + "`init` (start of session: project brief + session state), `research` (before editing unfamiliar code), `history` (why is this file written this way), `flow` (read a recorded architecture flow), `map` (repository topology), `impact` (blast radius analysis).";
11457
+ const contextSchema = {
11458
+ action: z.enum(["init", "research", "history", "flow", "map", "impact"]).describe(
11459
+ "init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow, map=repo topology, impact=blast radius"
11460
+ ),
11461
+ scope: z.string().optional().describe("Research scope or target for impact"),
11462
+ target: z.string().optional().describe("Target symbol/file for history/flow/impact"),
11463
+ goal: z.string().optional().describe("Current goal (for init)")
11464
+ };
11465
+ const contextHandler = async (params) => {
11466
+ try {
11467
+ await ensureInitialized();
11468
+ const text = await handleContext({
11469
+ action: params.action,
11470
+ scope: params.scope,
11471
+ target: params.target,
11472
+ goal: params.goal
11473
+ });
11474
+ return { content: [{ type: "text", text }] };
11475
+ } catch (err) {
11476
+ return { content: [{ type: "text", text: `Error in kuma_context: ${err}` }], isError: true };
11410
11477
  }
11411
- );
11412
- server.tool(
11413
- "kuma_memory",
11414
- "Persistent knowledge. Record what matters, skip what doesn't. This is what saves time in future sessions. Don't record what grep/glob answers faster (functions, imports, types, components)." + CORE_NOTE + "`gotcha` (IMMEDIATELY when you find a bug/quirk), `decision` (when choosing between options), `arch_flow` (after tracing a complete flow, max 5 core files), `research_save` (after exploring an area), `search` (quick lookup of memory + knowledge graph).",
11415
- {
11416
- action: z.enum(["gotcha", "decision", "arch_flow", "research_save", "search"]).describe(
11417
- "gotcha=record bug/quirk, decision=ADR, arch_flow=record architecture flow, research_save=save findings, search=quick memory+graph lookup"
11418
- ),
11419
- scope: z.string().optional().describe("File path for gotcha \u2014 or scope for research_save/search"),
11420
- trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha"),
11421
- query: z.string().optional().describe("Search query for search action"),
11422
- content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
11423
- record: z.string().optional().describe("JSON record string for research_save"),
11424
- confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
11425
- title: z.string().optional().describe("Decision title (required for decision)"),
11426
- context: z.string().optional().describe("Decision context"),
11427
- rationale: z.string().optional().describe("Decision rationale (required for decision)"),
11428
- outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
11429
- status: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
11430
- description: z.string().optional().describe("Gotcha workaround"),
11431
- limit: z.number().min(1).max(100).optional().describe("Result limit for search")
11432
- },
11433
- async (params) => {
11434
- try {
11435
- await ensureInitialized();
11436
- const text = await handleMemory({
11437
- action: params.action,
11438
- scope: params.scope,
11439
- query: params.query,
11440
- content: params.content,
11441
- record: params.record,
11442
- confidence: params.confidence,
11443
- title: params.title,
11444
- context: params.context,
11445
- rationale: params.rationale,
11446
- outcome: params.outcome,
11447
- limit: params.limit,
11448
- status: params.status,
11449
- description: params.description,
11450
- trigger_command: params.trigger_command
11451
- });
11452
- return { content: [{ type: "text", text }] };
11453
- } catch (err) {
11454
- return { content: [{ type: "text", text: `Error in kuma_memory: ${err}` }], isError: true };
11455
- }
11478
+ };
11479
+ server.tool("kuma_context", contextDesc, contextSchema, contextHandler);
11480
+ server.tool("kuma_kuma_context", contextDesc, contextSchema, contextHandler);
11481
+ const memoryDesc = "Persistent knowledge. Record what matters, skip what doesn't. This is what saves time in future sessions. Don't record what grep/glob answers faster (functions, imports, types, components)." + CORE_NOTE + "`gotcha` (IMMEDIATELY when you find a bug/quirk), `decision` (when choosing between options), `arch_flow` (after tracing a complete flow, max 5 core files), `research_save` (after exploring an area), `search` (quick lookup of memory + knowledge graph), `gotcha_resolve` (archive fixed bugs).";
11482
+ const memorySchema = {
11483
+ action: z.enum(["gotcha", "decision", "arch_flow", "research_save", "search", "gotcha_resolve"]).describe(
11484
+ "gotcha=record bug/quirk, decision=ADR, arch_flow=record architecture flow, research_save=save findings, search=quick memory+graph lookup, gotcha_resolve=archive fixed bug"
11485
+ ),
11486
+ scope: z.string().optional().describe("File path for gotcha \u2014 or scope for research_save/search"),
11487
+ target: z.string().optional().describe("Target file, gotcha ID, or component"),
11488
+ trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha"),
11489
+ query: z.string().optional().describe("Search query for search action"),
11490
+ content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
11491
+ record: z.string().optional().describe("JSON record string for research_save"),
11492
+ confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
11493
+ title: z.string().optional().describe("Decision title (optional if content/target provided)"),
11494
+ context: z.string().optional().describe("Decision context"),
11495
+ rationale: z.string().optional().describe("Decision rationale (optional if content provided)"),
11496
+ outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
11497
+ status: z.string().optional().describe("Gotcha severity (low|medium|high|critical) or status (resolved|deprecated)"),
11498
+ severity: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
11499
+ description: z.string().optional().describe("Gotcha workaround / rationale"),
11500
+ id: z.union([z.number(), z.string()]).optional().describe("Gotcha ID for resolution or deprecation"),
11501
+ limit: z.number().min(1).max(100).optional().describe("Result limit for search")
11502
+ };
11503
+ const memoryHandler = async (params) => {
11504
+ try {
11505
+ await ensureInitialized();
11506
+ const text = await handleMemory({
11507
+ action: params.action,
11508
+ scope: params.scope,
11509
+ target: params.target,
11510
+ query: params.query,
11511
+ content: params.content,
11512
+ record: params.record,
11513
+ confidence: params.confidence,
11514
+ title: params.title,
11515
+ context: params.context,
11516
+ rationale: params.rationale,
11517
+ outcome: params.outcome,
11518
+ limit: params.limit,
11519
+ status: params.status,
11520
+ severity: params.severity,
11521
+ description: params.description,
11522
+ trigger_command: params.trigger_command,
11523
+ id: params.id
11524
+ });
11525
+ return { content: [{ type: "text", text }] };
11526
+ } catch (err) {
11527
+ return { content: [{ type: "text", text: `Error in kuma_memory: ${err}` }], isError: true };
11456
11528
  }
11457
- );
11458
- server.tool(
11459
- "kuma_safety",
11460
- "Safety & verification. Use at task boundaries, not on every edit." + CORE_NOTE + "`guard` (before risky work: anti-patterns, drift, loops), `verify` (after edits: scoped tests + validation), `checkpoint` + `rollback_label` (the one rollback mechanism: snapshot before risky work, restore after).",
11461
- {
11462
- action: z.enum(["guard", "verify", "checkpoint", "rollback_label"]).describe(
11463
- "guard=anti-patterns/drift/loops before risky work, verify=scoped tests after edits, checkpoint=labeled snapshot before risky work, rollback_label=restore a labeled snapshot"
11464
- ),
11465
- scope: z.string().optional().describe("Scope for verify (e.g. 'auth', file path)"),
11466
- force: z.boolean().optional().describe("Force re-run even if cache is fresh (verify)"),
11467
- guardGoal: z.string().optional().describe("Goal for guard check"),
11468
- guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
11469
- label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label"),
11470
- description: z.string().optional().describe("Description for checkpoint")
11471
- },
11472
- async (params) => {
11473
- try {
11474
- await ensureInitialized();
11475
- const text = await handleSafety({
11476
- action: params.action,
11477
- guardGoal: params.guardGoal,
11478
- guardCheck: params.guardCheck,
11479
- scope: params.scope,
11480
- force: params.force,
11481
- label: params.label,
11482
- description: params.description
11483
- });
11484
- return { content: [{ type: "text", text }] };
11485
- } catch (err) {
11486
- return { content: [{ type: "text", text: `Error in kuma_safety: ${err}` }], isError: true };
11487
- }
11529
+ };
11530
+ server.tool("kuma_memory", memoryDesc, memorySchema, memoryHandler);
11531
+ server.tool("kuma_kuma_memory", memoryDesc, memorySchema, memoryHandler);
11532
+ const safetyDesc = "Safety & verification. Use at task boundaries, not on every edit." + CORE_NOTE + "`guard` (before risky work: anti-patterns, drift, loops), `verify` (after edits: scoped tests + validation), `checkpoint` + `rollback_label` (the one rollback mechanism: snapshot before risky work, restore after).";
11533
+ const safetySchema = {
11534
+ action: z.enum(["guard", "verify", "checkpoint", "rollback_label"]).describe(
11535
+ "guard=anti-patterns/drift/loops before risky work, verify=scoped tests after edits, checkpoint=labeled snapshot before risky work, rollback_label=restore a labeled snapshot"
11536
+ ),
11537
+ scope: z.string().optional().describe("Scope for verify (e.g. 'auth', file path)"),
11538
+ target: z.string().optional().describe("Target test file or scope for verify"),
11539
+ command: z.string().optional().describe("Custom test command to run during verify"),
11540
+ timeoutMs: z.number().optional().describe("Custom timeout in milliseconds for verify (default: 60000)"),
11541
+ timeout: z.number().optional().describe("Custom timeout in seconds for verify"),
11542
+ force: z.boolean().optional().describe("Force re-run even if cache is fresh or rate limited (verify)"),
11543
+ guardGoal: z.string().optional().describe("Goal for guard check"),
11544
+ guardCheck: z.enum(["all", "anti-pattern", "loop", "drift", "context"]).optional().describe("Guard check type"),
11545
+ label: z.string().optional().describe("Checkpoint label for checkpoint/rollback_label"),
11546
+ description: z.string().optional().describe("Description for checkpoint")
11547
+ };
11548
+ const safetyHandler = async (params) => {
11549
+ try {
11550
+ await ensureInitialized();
11551
+ const text = await handleSafety({
11552
+ action: params.action,
11553
+ guardGoal: params.guardGoal,
11554
+ guardCheck: params.guardCheck,
11555
+ scope: params.scope,
11556
+ target: params.target,
11557
+ command: params.command,
11558
+ timeoutMs: params.timeoutMs,
11559
+ timeout: params.timeout,
11560
+ force: params.force,
11561
+ label: params.label,
11562
+ description: params.description
11563
+ });
11564
+ return { content: [{ type: "text", text }] };
11565
+ } catch (err) {
11566
+ return { content: [{ type: "text", text: `Error in kuma_safety: ${err}` }], isError: true };
11488
11567
  }
11489
- );
11490
- console.error("[Manifest] Registered 3 coarse-grained tools with 13 core actions (init/research/history/flow \xB7 gotcha/decision/arch_flow/research_save/search \xB7 guard/verify/checkpoint/rollback_label).");
11568
+ };
11569
+ server.tool("kuma_safety", safetyDesc, safetySchema, safetyHandler);
11570
+ server.tool("kuma_kuma_safety", safetyDesc, safetySchema, safetyHandler);
11571
+ console.error("[Manifest] Registered 3 core tools with universal dual-prefix aliases (kuma_* and kuma_kuma_*).");
11491
11572
  console.error("[Manifest] Auto-init hooks installed on all tools.");
11492
- console.error("[Manifest] Namespace aliases active (kuma_kuma_* \u2192 kuma_*, context \u2192 kuma_context).");
11493
11573
  }
11494
11574
 
11495
11575
  // src/index.ts
@@ -11544,6 +11624,64 @@ async function main() {
11544
11624
  printHelp();
11545
11625
  process.exit(0);
11546
11626
  }
11627
+ if (args[0] === "status") {
11628
+ console.log(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Health & Memory Status`);
11629
+ console.log(`\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`);
11630
+ try {
11631
+ const { getProjectRoot: getProjectRoot2 } = await Promise.resolve().then(() => (init_pathValidator(), pathValidator_exports));
11632
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_kumaDb(), kumaDb_exports));
11633
+ const root = getProjectRoot2();
11634
+ console.log(`\u{1F4C1} Project Root: ${root}`);
11635
+ const db = await getDb2();
11636
+ let decisionsCount = 0;
11637
+ try {
11638
+ const dRes = db.exec("SELECT COUNT(*) FROM nodes WHERE type = 'decision'");
11639
+ decisionsCount = dRes[0]?.values[0]?.[0] || 0;
11640
+ } catch {
11641
+ }
11642
+ let activeGotchas = 0;
11643
+ let resolvedGotchas = 0;
11644
+ let candidateGotchas = 0;
11645
+ try {
11646
+ const gRes = db.exec("SELECT status, COUNT(*) FROM known_gotchas GROUP BY status");
11647
+ for (const row of gRes[0]?.values || []) {
11648
+ const status = String(row[0]);
11649
+ const cnt = Number(row[1]);
11650
+ if (status === "active" || status === "verified") activeGotchas += cnt;
11651
+ else if (status === "resolved") resolvedGotchas += cnt;
11652
+ else if (status === "candidate") candidateGotchas += cnt;
11653
+ }
11654
+ } catch {
11655
+ }
11656
+ let shadowInjections = 0;
11657
+ let savedMinutes = 0;
11658
+ try {
11659
+ const { getInjectionStats: getInjectionStats2 } = await Promise.resolve().then(() => (init_kumaGotchas(), kumaGotchas_exports));
11660
+ const stats = getInjectionStats2(24);
11661
+ shadowInjections = stats.count;
11662
+ savedMinutes = Math.round(stats.savedMs / 6e4);
11663
+ } catch {
11664
+ }
11665
+ let workspaceDesc = "Single project";
11666
+ try {
11667
+ const { getWorkspaceInfo: getWorkspaceInfo2 } = await Promise.resolve().then(() => (init_workspaceIntelligence(), workspaceIntelligence_exports));
11668
+ const wsInfo = await getWorkspaceInfo2(root);
11669
+ if (wsInfo.isWorkspace) {
11670
+ workspaceDesc = `${wsInfo.packages.length} package(s) (${wsInfo.type.toUpperCase()})`;
11671
+ }
11672
+ } catch {
11673
+ }
11674
+ console.log(`\u{1F3DB}\uFE0F Decisions Recorded: ${decisionsCount}`);
11675
+ console.log(`\u26A0\uFE0F Active Gotchas: ${activeGotchas} active \xB7 ${resolvedGotchas} resolved \xB7 ${candidateGotchas} candidate`);
11676
+ console.log(`\u{1FA84} Shadow Memory: ${shadowInjections} prevention(s) in last 24h (~${savedMinutes} min saved)`);
11677
+ console.log(`\u{1F4E6} Workspace: ${workspaceDesc}`);
11678
+ console.log(`\u{1F6E1}\uFE0F Safety Hooks: Active`);
11679
+ console.log(`\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`);
11680
+ } catch (err) {
11681
+ console.error(`\u274C Failed to retrieve status: ${err}`);
11682
+ }
11683
+ process.exit(0);
11684
+ }
11547
11685
  if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
11548
11686
  console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
11549
11687
  console.error("");
@@ -0,0 +1,90 @@
1
+ /**
2
+ * 🐻 Kuma MCP β€” JIT Gotcha Guard Hook
3
+ * Intercepts file writes/edits in real time to inject active gotcha warnings.
4
+ */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+
8
+ function findGotchasForFile(targetPath) {
9
+ if (!targetPath) return [];
10
+ const results = [];
11
+ const normalized = targetPath.replace(/\\/g, "/");
12
+ const baseName = path.basename(normalized);
13
+
14
+ const gotchasMd = path.resolve(process.cwd(), ".kuma", "KNOWN_GOTCHAS.md");
15
+ if (fs.existsSync(gotchasMd)) {
16
+ try {
17
+ const content = fs.readFileSync(gotchasMd, "utf-8");
18
+ const sections = content.split(/(?=^###\s+)/m);
19
+ for (const sec of sections) {
20
+ if (sec.includes(baseName) || sec.includes(normalized)) {
21
+ const firstLine = sec.split("\n")[0].replace(/^###\s+/, "").trim();
22
+ const trapMatch = sec.match(/- \S*\s*\*\*Trap\*\*:\s*([^\n]+)/i) || sec.match(/- \*\*Issue\*\*:\s*([^\n]+)/i);
23
+ const ruleMatch = sec.match(/- \S*\s*\*\*Rule(?:\/Fix)?\*\*:\s*([^\n]+)/i) || sec.match(/- \*\*Workaround\*\*:\s*([^\n]+)/i);
24
+ const trap = trapMatch ? trapMatch[1].trim() : firstLine;
25
+ const rule = ruleMatch ? ruleMatch[1].trim() : "";
26
+ results.push({ file: baseName, trap, rule });
27
+ }
28
+ }
29
+ } catch {}
30
+ }
31
+ return results;
32
+ }
33
+
34
+ function processPayload(rawInput) {
35
+ let data = {};
36
+ if (rawInput && typeof rawInput === "string") {
37
+ try { data = JSON.parse(rawInput); } catch {}
38
+ } else if (rawInput && typeof rawInput === "object") {
39
+ data = rawInput;
40
+ }
41
+
42
+ const input = data.input || data;
43
+ const targetFile = input.TargetFile || input.targetFile || input.file_path || input.path || input.file || "";
44
+
45
+ if (!targetFile) {
46
+ return { decision: "allow" };
47
+ }
48
+
49
+ const gotchas = findGotchasForFile(targetFile);
50
+ if (gotchas.length > 0) {
51
+ const baseName = path.basename(targetFile);
52
+ const lines = [
53
+ `⚠️ [🐻 Kuma JIT Warning] File "${baseName}" has ${gotchas.length} known gotcha(s):`
54
+ ];
55
+ for (const g of gotchas.slice(0, 3)) {
56
+ lines.push(` β€’ πŸͺ€ Trap: ${g.trap}`);
57
+ if (g.rule) lines.push(` πŸ›‘οΈ Rule: ${g.rule}`);
58
+ }
59
+ lines.push("πŸ’‘ Review before modifying to prevent known regressions.");
60
+
61
+ const message = lines.join("\n");
62
+ return {
63
+ decision: "allow",
64
+ systemMessage: message,
65
+ additionalContext: message,
66
+ };
67
+ }
68
+
69
+ return { decision: "allow" };
70
+ }
71
+
72
+ let inputBuffer = "";
73
+ const timer = setTimeout(() => {
74
+ const envInput = process.env.AGY_TOOL_INPUT || process.env.TOOL_INPUT || "";
75
+ const result = processPayload(envInput);
76
+ process.stdout.write(JSON.stringify(result) + "\n");
77
+ process.exit(0);
78
+ }, 150);
79
+
80
+ process.stdin.setEncoding("utf-8");
81
+ process.stdin.on("data", (chunk) => {
82
+ clearTimeout(timer);
83
+ inputBuffer += chunk;
84
+ });
85
+ process.stdin.on("end", () => {
86
+ clearTimeout(timer);
87
+ const result = processPayload(inputBuffer || process.env.AGY_TOOL_INPUT || "");
88
+ process.stdout.write(JSON.stringify(result) + "\n");
89
+ process.exit(0);
90
+ });
package/hooks.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "kuma-safety": {
3
+ "enabled": true,
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "replace_file_content|write_to_file|Edit|Write|MultiEdit|edit_file",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "node -e \"import('fs').then(fs=>{const p=['hooks/kuma-agy-hooks.js','.agents/hooks/kuma-agy-hooks.js',process.env.HOME+'/.gemini/config/plugins/kuma/hooks/kuma-agy-hooks.js'].find(x=>fs.existsSync(x));if(p)import(p.startsWith('/')?p:'./'+p);else process.stdout.write(JSON.stringify({decision:'allow'})+'\\n');}).catch(()=>process.stdout.write(JSON.stringify({decision:'allow'})+'\\n'))\"",
11
+ "timeout": 15
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
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",
@@ -16,6 +16,11 @@
16
16
  "mcp_config.json",
17
17
  "install.sh",
18
18
  "INSTALL.md",
19
+ "hooks.json",
20
+ "hooks/",
21
+ "skills/",
22
+ "commands/",
23
+ "agents/",
19
24
  ".claude-plugin/"
20
25
  ],
21
26
  "dependencies": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://antigravity.google/schemas/v1/plugin.json",
3
3
  "name": "kuma",
4
- "version": "2.4.4",
4
+ "version": "2.4.6",
5
5
  "description": "Safety-first context & orchestration engine for AI coding agents. Shadow memory, gotcha lifecycle, blast radius impact analysis, and test verification.",
6
6
  "author": {
7
7
  "name": "plumpslabs",
@@ -11,8 +11,50 @@
11
11
  "skills": [
12
12
  {
13
13
  "name": "kuma-mcp",
14
- "description": "Kuma MCP \u2014 safety toolkit for AI coding agents",
15
- "file": ".agents/skills/kuma/SKILL.md"
14
+ "description": "Kuma MCP β€” safety toolkit for AI coding agents",
15
+ "file": "skills/kuma/SKILL.md"
16
+ }
17
+ ],
18
+ "commands": [
19
+ {
20
+ "name": "kuma:init",
21
+ "description": "Initialize Kuma session context, lean brief (<500 tokens), and branch tracking",
22
+ "file": "commands/kuma:init.md"
23
+ },
24
+ {
25
+ "name": "kuma:research",
26
+ "description": "Run 5-step research pipeline before editing unfamiliar code",
27
+ "file": "commands/kuma:research.md"
28
+ },
29
+ {
30
+ "name": "kuma:impact",
31
+ "description": "Calculate blast radius, affected consumers, and safety risk score",
32
+ "file": "commands/kuma:impact.md"
33
+ },
34
+ {
35
+ "name": "kuma:guard",
36
+ "description": "Pre-flight safety check against anti-patterns, drift, and circular deps",
37
+ "file": "commands/kuma:guard.md"
38
+ },
39
+ {
40
+ "name": "kuma:verify",
41
+ "description": "Post-edit scoped test verification and validation",
42
+ "file": "commands/kuma:verify.md"
43
+ },
44
+ {
45
+ "name": "kuma:checkpoint",
46
+ "description": "Create an atomic named snapshot before risky work",
47
+ "file": "commands/kuma:checkpoint.md"
48
+ },
49
+ {
50
+ "name": "kuma:rollback",
51
+ "description": "Restore codebase from a labeled checkpoint snapshot",
52
+ "file": "commands/kuma:rollback.md"
53
+ },
54
+ {
55
+ "name": "kuma:studio",
56
+ "description": "Launch or view Kuma Studio web dashboard",
57
+ "file": "commands/kuma:studio.md"
16
58
  }
17
59
  ]
18
60
  }
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: kuma-mcp
3
+ description: Kuma MCP β€” safety toolkit for AI coding agents
4
+ ---
5
+
6
+ Kuma MCP tools: kuma_context, kuma_memory, kuma_safety.
7
+ Record what matters, skip what doesn't.
8
+
9
+ <kuma_obedience>
10
+ ## πŸ”’ Obedience (non-negotiable)
11
+ - MUST call `kuma_context({ action: "init" })` at session start.
12
+ - MUST check blast radius `kuma_context({ action: "impact", target: "<file>" })` before touching core/shared modules.
13
+ - MUST record gotchas IMMEDIATELY when a bug/quirk is found.
14
+ - MUST call `kuma_safety({ action: "verify" })` after edits.
15
+ - MUST NOT call actions outside the 15 core actions.
16
+ </kuma_obedience>
17
+
18
+ <kuma_workflow>
19
+ 🧠 **Before coding:** `kuma_context({ action: "init" })`
20
+ πŸ—ΊοΈ **Monorepo map:** `kuma_context({ action: "map" })`
21
+ πŸ”¬ **Unfamiliar code / blast radius:** `kuma_context({ action: "research", scope: "<area>" })` or `kuma_context({ action: "impact", target: "<file>" })`
22
+ πŸ› **Found a bug/quirk:** `kuma_memory({ action: "gotcha" })` (IMMEDIATELY)
23
+ 🧭 **Chose between options:** `kuma_memory({ action: "decision" })`
24
+ πŸ”€ **Traced a flow:** `kuma_memory({ action: "arch_flow" })` (max 5 files)
25
+ πŸ›‘οΈ **Before risky work / after edits:** `kuma_safety({ action: "guard" | "verify" })`
26
+ </kuma_workflow>
27
+
28
+ πŸ“– Full rules: `.kuma/init.md`