@plumpslabs/kuma 2.4.5 → 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.
package/dist/index.js CHANGED
@@ -5339,15 +5339,17 @@ async function addGotcha(entry) {
5339
5339
  const severity = entry.severity || "medium";
5340
5340
  const status = entry.status || "active";
5341
5341
  const contentHash = hashFileContent(entry.filePath);
5342
+ const trap = entry.description;
5343
+ const ruleFix = entry.workaround || "Review implementation carefully before modifying.";
5342
5344
  const formatted = [
5343
- `### ${entry.filePath} \u2014 ${entry.description}`,
5344
- `- **Issue**: ${entry.description}`,
5345
- `- **Severity**: ${severity}`,
5346
- entry.workaround ? `- **Workaround**: ${entry.workaround}` : "",
5347
- entry.triggerCommand ? `- **Trigger**: when running \`${entry.triggerCommand}\`` : "",
5348
- entry.status ? `- **Status**: ${entry.status}` : "",
5349
- entry.scopePackage ? `- **Scope Package**: ${entry.scopePackage}` : "",
5350
- `- **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]}`
5351
5353
  ].filter(Boolean).join("\n");
5352
5354
  const checkStmt = db.prepare(
5353
5355
  `SELECT id FROM known_gotchas WHERE file_path = ? AND description LIKE ?`
@@ -10589,6 +10591,8 @@ var MEMORY_ALIASES = {
10589
10591
  "resolve_gotcha": "gotcha",
10590
10592
  "resolve-gotcha": "gotcha",
10591
10593
  "resolve": "gotcha",
10594
+ "gotcha_resolve": "gotcha",
10595
+ "gotcha-resolve": "gotcha",
10592
10596
  "deprecate_gotcha": "gotcha",
10593
10597
  "deprecate-gotcha": "gotcha",
10594
10598
  "deprecate": "gotcha"
@@ -10784,7 +10788,7 @@ async function handleArchFlow(params) {
10784
10788
  async function handleGotchaAction(params) {
10785
10789
  const rawStatus = (params.status || "").toLowerCase().trim();
10786
10790
  const rawAction = (params.action || "").toLowerCase().trim();
10787
- 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";
10788
10792
  const isDeprecation = rawStatus === "deprecated" || rawStatus === "deprecate" || rawAction === "deprecate_gotcha" || rawAction === "deprecate";
10789
10793
  const target = params.target || params.id || params.scope;
10790
10794
  if (isResolution && target) {
@@ -11449,127 +11453,123 @@ async function doInit() {
11449
11453
  // src/manifest.ts
11450
11454
  var CORE_NOTE = "\n\nCORE ACTIONS (use these): ";
11451
11455
  function registerAllTools(server) {
11452
- server.tool(
11453
- "kuma_context",
11454
- "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).",
11455
- {
11456
- action: z.enum(["init", "research", "history", "flow", "map", "impact"]).describe(
11457
- "init=project brief + session restore, research=research pipeline, history=file rationale + fresh gotchas, flow=read recorded domain flow, map=repo topology, impact=blast radius"
11458
- ),
11459
- scope: z.string().optional().describe("Research scope or target for impact"),
11460
- target: z.string().optional().describe("Target symbol/file for history/flow/impact"),
11461
- goal: z.string().optional().describe("Current goal (for init)")
11462
- },
11463
- async (params) => {
11464
- try {
11465
- await ensureInitialized();
11466
- const text = await handleContext({
11467
- action: params.action,
11468
- scope: params.scope,
11469
- target: params.target,
11470
- goal: params.goal
11471
- });
11472
- return { content: [{ type: "text", text }] };
11473
- } catch (err) {
11474
- return { content: [{ type: "text", text: `Error in kuma_context: ${err}` }], isError: true };
11475
- }
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 };
11476
11477
  }
11477
- );
11478
- server.tool(
11479
- "kuma_memory",
11480
- "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).",
11481
- {
11482
- action: z.enum(["gotcha", "decision", "arch_flow", "research_save", "search"]).describe(
11483
- "gotcha=record bug/quirk, decision=ADR, arch_flow=record architecture flow, research_save=save findings, search=quick memory+graph lookup"
11484
- ),
11485
- scope: z.string().optional().describe("File path for gotcha \u2014 or scope for research_save/search"),
11486
- target: z.string().optional().describe("Target file, gotcha ID, or component"),
11487
- trigger_command: z.string().optional().describe("Gotcha trigger: shell command that hits this gotcha"),
11488
- query: z.string().optional().describe("Search query for search action"),
11489
- content: z.string().optional().describe("Content/notes for research_save / gotcha description / arch_flow record"),
11490
- record: z.string().optional().describe("JSON record string for research_save"),
11491
- confidence: z.number().min(0).max(1).optional().describe("Confidence for research_save (0-1)"),
11492
- title: z.string().optional().describe("Decision title (optional if content/target provided)"),
11493
- context: z.string().optional().describe("Decision context"),
11494
- rationale: z.string().optional().describe("Decision rationale (optional if content provided)"),
11495
- outcome: z.string().optional().describe("Decision outcome (default: implemented)"),
11496
- status: z.string().optional().describe("Gotcha severity (low|medium|high|critical) or status (resolved|deprecated)"),
11497
- severity: z.string().optional().describe("Gotcha severity (low|medium|high|critical)"),
11498
- description: z.string().optional().describe("Gotcha workaround / rationale"),
11499
- id: z.union([z.number(), z.string()]).optional().describe("Gotcha ID for resolution or deprecation"),
11500
- limit: z.number().min(1).max(100).optional().describe("Result limit for search")
11501
- },
11502
- async (params) => {
11503
- try {
11504
- await ensureInitialized();
11505
- const text = await handleMemory({
11506
- action: params.action,
11507
- scope: params.scope,
11508
- target: params.target,
11509
- query: params.query,
11510
- content: params.content,
11511
- record: params.record,
11512
- confidence: params.confidence,
11513
- title: params.title,
11514
- context: params.context,
11515
- rationale: params.rationale,
11516
- outcome: params.outcome,
11517
- limit: params.limit,
11518
- status: params.status,
11519
- severity: params.severity,
11520
- description: params.description,
11521
- trigger_command: params.trigger_command,
11522
- id: params.id
11523
- });
11524
- return { content: [{ type: "text", text }] };
11525
- } catch (err) {
11526
- return { content: [{ type: "text", text: `Error in kuma_memory: ${err}` }], isError: true };
11527
- }
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 };
11528
11528
  }
11529
- );
11530
- server.tool(
11531
- "kuma_safety",
11532
- "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
- {
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
- 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 };
11567
- }
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 };
11568
11567
  }
11569
- );
11570
- 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_*).");
11571
11572
  console.error("[Manifest] Auto-init hooks installed on all tools.");
11572
- console.error("[Manifest] Namespace aliases active (kuma_kuma_* \u2192 kuma_*, context \u2192 kuma_context).");
11573
11573
  }
11574
11574
 
11575
11575
  // src/index.ts
@@ -11624,6 +11624,64 @@ async function main() {
11624
11624
  printHelp();
11625
11625
  process.exit(0);
11626
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
+ }
11627
11685
  if (args[0] === "stop" && (args[1] === "--force" || args[1] === "-f")) {
11628
11686
  console.error(`\u{1F43B} Kuma v${SERVER_VERSION} \u2014 Kill Switch`);
11629
11687
  console.error("");
@@ -1,37 +1,90 @@
1
1
  /**
2
- * 🐻 Kuma MCP — Antigravity Lifecycle Hook
3
- * Intercepts file writes to warn about known gotchas and fragile files.
2
+ * 🐻 Kuma MCP — JIT Gotcha Guard Hook
3
+ * Intercepts file writes/edits in real time to inject active gotcha warnings.
4
4
  */
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
7
 
8
- function runHook() {
9
- try {
10
- const input = process.env.AGY_TOOL_INPUT ? JSON.parse(process.env.AGY_TOOL_INPUT) : {};
11
- const targetFile = input.TargetFile || input.file || "";
8
+ function findGotchasForFile(targetPath) {
9
+ if (!targetPath) return [];
10
+ const results = [];
11
+ const normalized = targetPath.replace(/\\/g, "/");
12
+ const baseName = path.basename(normalized);
12
13
 
13
- if (!targetFile) {
14
- process.stdout.write(JSON.stringify({ decision: "allow" }) + "\n");
15
- return;
16
- }
17
-
18
- const gotchasFile = path.resolve(process.cwd(), ".kuma", "KNOWN_GOTCHAS.md");
19
- if (fs.existsSync(gotchasFile)) {
20
- const content = fs.readFileSync(gotchasFile, "utf-8");
21
- const baseName = path.basename(targetFile);
22
- if (content.includes(baseName) || content.includes(targetFile)) {
23
- process.stdout.write(JSON.stringify({
24
- decision: "allow",
25
- systemMessage: `⚠️ Kuma Gotcha Warning: ${baseName} has recorded gotchas in .kuma/KNOWN_GOTCHAS.md. Review before editing!`
26
- }) + "\n");
27
- return;
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
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}`);
29
58
  }
59
+ lines.push("💡 Review before modifying to prevent known regressions.");
30
60
 
31
- process.stdout.write(JSON.stringify({ decision: "allow" }) + "\n");
32
- } catch {
33
- process.stdout.write(JSON.stringify({ decision: "allow" }) + "\n");
61
+ const message = lines.join("\n");
62
+ return {
63
+ decision: "allow",
64
+ systemMessage: message,
65
+ additionalContext: message,
66
+ };
34
67
  }
68
+
69
+ return { decision: "allow" };
35
70
  }
36
71
 
37
- runHook();
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 CHANGED
@@ -3,11 +3,11 @@
3
3
  "enabled": true,
4
4
  "PreToolUse": [
5
5
  {
6
- "matcher": "replace_file_content|write_to_file",
6
+ "matcher": "replace_file_content|write_to_file|Edit|Write|MultiEdit|edit_file",
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "node -e \"import('fs').then(fs=>{const p=['hooks/kuma-agy-hooks.js','.agents/hooks/kuma-agy-hooks.js','../hooks/kuma-agy-hooks.js'].find(x=>fs.existsSync(x));if(p)import('./'+p);else process.stdout.write(JSON.stringify({decision:'allow'})+'\\n');}).catch(()=>process.stdout.write(JSON.stringify({decision:'allow'})+'\\n'))\"",
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
11
  "timeout": 15
12
12
  }
13
13
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/kuma",
3
- "version": "2.4.5",
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",
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.5",
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",