@hivelore/mcp 0.43.2 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -420,7 +420,7 @@ Post-task reflection checklist. Guides the AI through capturing failed approache
420
420
  ```
421
421
  Use the post_task prompt with:
422
422
  task_summary: "Added Stripe payment integration"
423
- files_touched: ["src/payments/StripeService.ts", "src/payments/PaymentController.ts"]
423
+ files_touched: '["src/payments/StripeService.ts", "src/payments/PaymentController.ts"]'
424
424
  ```
425
425
 
426
426
  ### `bootstrap_project`
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@
3
3
  // src/server.ts
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { mkdir as mkdir8, writeFile as writeFile15 } from "fs/promises";
7
+ import path15 from "path";
6
8
 
7
9
  // src/context.ts
8
10
  import { findProjectRoot, resolveHaivePaths } from "@hivelore/core";
@@ -1793,15 +1795,24 @@ async function memTried(input, ctx) {
1793
1795
  import { existsSync as existsSync17, statSync } from "fs";
1794
1796
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
1795
1797
  import path8 from "path";
1798
+ import { execFile } from "child_process";
1799
+ import { promisify } from "util";
1796
1800
  import { z as z17 } from "zod";
1797
1801
  import {
1798
1802
  buildProposeCommand,
1803
+ incidentHintsFromDiff,
1799
1804
  loadMemoriesFromDir as loadMemoriesFromDir14,
1800
1805
  normalizeFramework,
1801
1806
  parseLessonFields,
1802
1807
  pickTestFramework,
1803
1808
  scaffoldPostIncidentTest
1804
1809
  } from "@hivelore/core";
1810
+ var execFileAsync = promisify(execFile);
1811
+ async function gitDiffText(root, redRef, paths) {
1812
+ const args = ["diff", redRef, "HEAD", "--", ...paths];
1813
+ const { stdout } = await execFileAsync("git", args, { cwd: root, maxBuffer: 64 * 1024 * 1024 });
1814
+ return stdout;
1815
+ }
1805
1816
  var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
1806
1817
  async function detectForAnchor(root, rel) {
1807
1818
  let dir = path8.resolve(root, rel);
@@ -1848,6 +1859,9 @@ var ScaffoldTestInputSchema = {
1848
1859
  memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
1849
1860
  framework: z17.enum(["vitest", "jest", "pytest", "gotest"]).optional().describe("Test framework. Auto-detected from the package that owns the lesson's anchor paths when omitted."),
1850
1861
  out_path: z17.string().optional().describe("Override the generated test file path (repo-relative)."),
1862
+ red_ref: z17.string().optional().describe(
1863
+ "Pre-fix incident commit/ref. When set, the scaffold names the symbols the fix (<red_ref>..HEAD) touched within the lesson's anchor scope and pre-fills the example around them, so the assertion is a targeted edit rather than a blank page. A bad ref falls back to the generic template."
1864
+ ),
1851
1865
  write: z17.boolean().default(true).describe("Write the file to disk (default). false = return the content for preview without writing.")
1852
1866
  };
1853
1867
  async function scaffoldTest(input, ctx) {
@@ -1861,13 +1875,23 @@ async function scaffoldTest(input, ctx) {
1861
1875
  const groups = input.out_path ? allGroups.slice(0, 1) : allGroups;
1862
1876
  const frameworkFor = (detected) => input.framework ? normalizeFramework(input.framework) ?? detected : detected;
1863
1877
  const fields = parseLessonFields(found.memory.body);
1878
+ let incidentHints;
1879
+ if (input.red_ref) {
1880
+ try {
1881
+ const diff = await gitDiffText(ctx.paths.root, input.red_ref, anchorPaths);
1882
+ const hints = incidentHintsFromDiff(diff, { redRef: input.red_ref });
1883
+ if (hints.changedSymbols.length > 0 || hints.changedFiles.length > 0) incidentHints = hints;
1884
+ } catch {
1885
+ }
1886
+ }
1864
1887
  const lesson = {
1865
1888
  memoryId: input.memory_id,
1866
1889
  title: fields.title || input.memory_id,
1867
1890
  whyFailed: fields.whyFailed,
1868
1891
  instead: fields.instead,
1869
1892
  incident: found.memory.frontmatter.sensor?.incident,
1870
- paths: anchorPaths
1893
+ paths: anchorPaths,
1894
+ incidentHints
1871
1895
  };
1872
1896
  let scaffolds = groups.map(
1873
1897
  (g) => scaffoldPostIncidentTest(lesson, { framework: frameworkFor(g.framework), outPath: input.out_path, baseDir: g.baseDir })
@@ -3191,7 +3215,7 @@ function oneLine(value) {
3191
3215
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
3192
3216
  }
3193
3217
  function serverVersion() {
3194
- return true ? "0.43.2" : "dev";
3218
+ return true ? "0.46.0" : "dev";
3195
3219
  }
3196
3220
 
3197
3221
  // src/tools/code-map.ts
@@ -3477,7 +3501,8 @@ var AntiPatternsCheckInputSchema = {
3477
3501
  ),
3478
3502
  min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
3479
3503
  "Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
3480
- )
3504
+ ),
3505
+ track: z26.boolean().default(true).describe("Record real prevention outcomes. Set false for eval/selftest probes so synthetic cases never inflate ROI.")
3481
3506
  };
3482
3507
  function tokenizeDiffForLiteral(diff) {
3483
3508
  const lines = diff.split("\n");
@@ -3505,6 +3530,7 @@ function isHaiveOwnedPath(p) {
3505
3530
  if (/^\.github\/workflows\/haive-.*\.ya?ml$/.test(p)) return true;
3506
3531
  return false;
3507
3532
  }
3533
+ var MAX_FUZZY_SCAN_LINES = 2e4;
3508
3534
  var TEST_PATH_RE = /(?:^|\/)(?:tests?|__tests__|__mocks__|e2e|fixtures)\/|\.(?:test|spec)\.[cm]?[jt]sx?$/i;
3509
3535
  function isTestPath(p) {
3510
3536
  return TEST_PATH_RE.test(p);
@@ -3515,7 +3541,7 @@ function stripTestHunks(diff) {
3515
3541
  let block = [];
3516
3542
  let keep = true;
3517
3543
  const flush = () => {
3518
- if (keep) out.push(...block);
3544
+ if (keep) for (const l of block) out.push(l);
3519
3545
  block = [];
3520
3546
  keep = true;
3521
3547
  };
@@ -3536,7 +3562,7 @@ function stripAiDirHunks(diff) {
3536
3562
  let block = [];
3537
3563
  let keep = true;
3538
3564
  const flush = () => {
3539
- if (keep) out.push(...block);
3565
+ if (keep) for (const l of block) out.push(l);
3540
3566
  block = [];
3541
3567
  keep = true;
3542
3568
  };
@@ -3607,7 +3633,10 @@ async function antiPatternsCheck(input, ctx) {
3607
3633
  }
3608
3634
  }
3609
3635
  const scanDiff = input.diff ? stripTestHunks(stripAiDirHunks(input.diff)) : input.diff;
3610
- if (scanDiff) {
3636
+ const scanAddedLineCount = scanDiff ? addedLinesFromDiff(scanDiff).split("\n").length : 0;
3637
+ const fuzzyScanTooLarge = scanAddedLineCount > MAX_FUZZY_SCAN_LINES;
3638
+ const notice = fuzzyScanTooLarge ? `Diff is very large (${scanAddedLineCount.toLocaleString()} added lines) \u2014 literal/semantic corroboration skipped for performance; anchored lessons and deterministic sensors were still evaluated in full. If this staged node_modules or a build artifact, unstage it.` : void 0;
3639
+ if (scanDiff && !fuzzyScanTooLarge) {
3611
3640
  const tokens = tokenizeDiffForLiteral(scanDiff);
3612
3641
  const added = addedLinesFromDiff(scanDiff);
3613
3642
  const addedText = added.trim().length > 0 ? added : scanDiff;
@@ -3642,7 +3671,7 @@ async function antiPatternsCheck(input, ctx) {
3642
3671
  }
3643
3672
  }
3644
3673
  }
3645
- if (input.semantic && scanDiff) {
3674
+ if (input.semantic && scanDiff && !fuzzyScanTooLarge) {
3646
3675
  try {
3647
3676
  const mod = await import("@hivelore/embeddings");
3648
3677
  const added = addedLinesFromDiff(scanDiff);
@@ -3670,10 +3699,13 @@ async function antiPatternsCheck(input, ctx) {
3670
3699
  }).slice(0, input.limit);
3671
3700
  const isHardBlockCatch = (w) => w.reasons.includes("sensor");
3672
3701
  const strongCatches = warnings.filter(isHardBlockCatch);
3673
- await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
3702
+ if (input.track !== false) {
3703
+ await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
3704
+ }
3674
3705
  return {
3675
3706
  scanned: negative.length,
3676
- warnings
3707
+ warnings,
3708
+ ...notice ? { notice } : {}
3677
3709
  };
3678
3710
  }
3679
3711
 
@@ -3901,6 +3933,7 @@ async function preCommitCheck(input, ctx) {
3901
3933
  relevant_memories: relevant_memories.length,
3902
3934
  stale_anchors: staleHits.length
3903
3935
  },
3936
+ ...apResult.notice ? { notice: apResult.notice } : {},
3904
3937
  warnings: classifiedWarnings,
3905
3938
  relevant_memories,
3906
3939
  stale_anchors: staleHits.map((r) => {
@@ -4452,13 +4485,14 @@ Main code areas detected: ${areas}
4452
4485
  import { z as z35 } from "zod";
4453
4486
  var PostTaskArgsSchema = {
4454
4487
  task_summary: z35.string().optional().describe("One sentence describing what you just did"),
4455
- files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
4488
+ files_touched: z35.string().optional().describe("Files you created or modified during the task, as CSV or a JSON array string")
4456
4489
  };
4457
4490
  function postTaskPrompt(args, ctx) {
4458
4491
  const taskLine = args.task_summary ? `
4459
4492
  Task just completed: **${args.task_summary}**` : "";
4460
- const filesLine = args.files_touched && args.files_touched.length > 0 ? `
4461
- Files touched: ${args.files_touched.map((f) => `\`${f}\``).join(", ")}` : "";
4493
+ const filesTouched = parsePromptFilesTouched(args.files_touched);
4494
+ const filesLine = filesTouched.length > 0 ? `
4495
+ Files touched: ${filesTouched.map((f) => `\`${f}\``).join(", ")}` : "";
4462
4496
  const text = `You have just finished a task. Before closing this session, take 60 seconds to capture what you learned.
4463
4497
  ${taskLine}${filesLine}
4464
4498
 
@@ -4547,6 +4581,20 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
4547
4581
  ]
4548
4582
  };
4549
4583
  }
4584
+ function parsePromptFilesTouched(input) {
4585
+ const raw = input?.trim();
4586
+ if (!raw) return [];
4587
+ if (raw.startsWith("[")) {
4588
+ try {
4589
+ const parsed2 = JSON.parse(raw);
4590
+ if (Array.isArray(parsed2)) {
4591
+ return parsed2.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean);
4592
+ }
4593
+ } catch {
4594
+ }
4595
+ }
4596
+ return raw.split(",").map((value) => value.trim()).filter(Boolean);
4597
+ }
4550
4598
 
4551
4599
  // src/prompts/import-docs.ts
4552
4600
  import { z as z36 } from "zod";
@@ -4620,7 +4668,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4620
4668
  // src/server.ts
4621
4669
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
4622
4670
  var SERVER_NAME = "hivelore";
4623
- var SERVER_VERSION = "0.43.2";
4671
+ var SERVER_VERSION = "0.46.0";
4624
4672
  function jsonResult(data) {
4625
4673
  return {
4626
4674
  content: [
@@ -5559,11 +5607,22 @@ function printHaiveMcpVersion() {
5559
5607
  }
5560
5608
  async function runHaiveMcpStdio(options) {
5561
5609
  const { server, context } = createHaiveServer({ root: options.root, env: process.env });
5610
+ await writeMcpRuntimeMarker(context).catch(() => {
5611
+ });
5562
5612
  console.error(
5563
5613
  `[haive-mcp] starting server v${SERVER_VERSION} (project root: ${context.paths.root})`
5564
5614
  );
5565
5615
  await server.connect(new StdioServerTransport());
5566
5616
  }
5617
+ async function writeMcpRuntimeMarker(context) {
5618
+ await mkdir8(context.paths.runtimeDir, { recursive: true });
5619
+ await writeFile15(path15.join(context.paths.runtimeDir, "mcp-server.json"), JSON.stringify({
5620
+ version: SERVER_VERSION,
5621
+ pid: process.pid,
5622
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
5623
+ command: "hivelore mcp --stdio"
5624
+ }, null, 2), "utf8");
5625
+ }
5567
5626
 
5568
5627
  // src/index.ts
5569
5628
  var parsed = parseMcpCliArgs(process.argv);