@hiveai/mcp 0.21.0 → 0.24.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/dist/index.js CHANGED
@@ -1151,7 +1151,15 @@ async function memTried(input, ctx) {
1151
1151
  throw new Error(`Memory already exists at ${file}`);
1152
1152
  }
1153
1153
  await writeFile8(file, serializeMemory7({ frontmatter, body }), "utf8");
1154
- return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
1154
+ const sensorGenerated = Boolean(sensor);
1155
+ const hint = sensorGenerated ? void 0 : input.paths.length === 0 ? "No sensor was generated (no `paths` given), so this lesson is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives to close the loop." : "No sensor could be derived from the wording (no distinctive code token). The lesson is briefed but not enforced; add a concrete forbidden token/value, or attach a sensor manually, to make the gate block the repeat.";
1156
+ return {
1157
+ id: frontmatter.id,
1158
+ scope: frontmatter.scope,
1159
+ file_path: file,
1160
+ sensor_generated: sensorGenerated,
1161
+ ...hint ? { hint } : {}
1162
+ };
1155
1163
  }
1156
1164
 
1157
1165
  // src/tools/ingest-findings.ts
@@ -1579,6 +1587,7 @@ async function memSessionEnd(input, ctx) {
1579
1587
  // src/tools/get-briefing.ts
1580
1588
  import { readFile as readFile5, writeFile as writeFile13 } from "fs/promises";
1581
1589
  import { existsSync as existsSync21 } from "fs";
1590
+ import path11 from "path";
1582
1591
  import {
1583
1592
  allocateBudget,
1584
1593
  briefingProofLine,
@@ -2022,6 +2031,7 @@ async function getBriefing(input, ctx) {
2022
2031
  const topSymbols = Object.entries(codeMap.files).flatMap(
2023
2032
  ([fp, entry]) => entry.exports.slice(0, 3).map((e) => `${e.name} (${fp.split("/").slice(-2).join("/")})`)
2024
2033
  ).slice(0, 15).join(", ");
2034
+ const commands = await detectRunCommands(ctx.paths.root);
2025
2035
  projectContext = `# Project context (auto-generated by hAIve)
2026
2036
 
2027
2037
  > \u26A0 This is a minimal auto-generated context based on the code-map. Invoke the \`bootstrap_project\` MCP prompt to replace it with a full analysis.
@@ -2031,7 +2041,10 @@ async function getBriefing(input, ctx) {
2031
2041
  - **Main file types:** ${topExts}
2032
2042
  - **Generated at:** ${codeMap.generated_at}
2033
2043
 
2034
- ## Key exports (sample)
2044
+ ` + (commands ? `## Commands
2045
+ ${commands}
2046
+
2047
+ ` : "") + `## Key exports (sample)
2035
2048
  ` + topSymbols + "\n";
2036
2049
  autoContextGenerated = true;
2037
2050
  setupWarnings.push(
@@ -2311,6 +2324,19 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2311
2324
  }
2312
2325
  };
2313
2326
  }
2327
+ async function detectRunCommands(root) {
2328
+ const pkgPath = path11.join(root, "package.json");
2329
+ if (!existsSync21(pkgPath)) return null;
2330
+ try {
2331
+ const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
2332
+ const scripts = pkg.scripts ?? {};
2333
+ const order = ["test", "build", "lint", "typecheck", "type-check", "dev", "start"];
2334
+ const lines = order.filter((name) => typeof scripts[name] === "string" && scripts[name].trim() !== "").map((name) => `- \`${name}\`: \`${scripts[name]}\``);
2335
+ return lines.length > 0 ? lines.join("\n") : null;
2336
+ } catch {
2337
+ return null;
2338
+ }
2339
+ }
2314
2340
 
2315
2341
  // src/tools/code-map.ts
2316
2342
  import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hiveai/core";
@@ -2550,7 +2576,7 @@ async function codeSearch(input, ctx) {
2550
2576
  // src/tools/why-this-file.ts
2551
2577
  import { existsSync as existsSync24 } from "fs";
2552
2578
  import { spawn } from "child_process";
2553
- import path11 from "path";
2579
+ import path12 from "path";
2554
2580
  import {
2555
2581
  deriveConfidence as deriveConfidence5,
2556
2582
  getUsage as getUsage7,
@@ -2568,7 +2594,7 @@ var WhyThisFileInputSchema = {
2568
2594
  memory_limit: z25.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
2569
2595
  };
2570
2596
  async function whyThisFile(input, ctx) {
2571
- const fileExists = existsSync24(path11.join(ctx.paths.root, input.path));
2597
+ const fileExists = existsSync24(path12.join(ctx.paths.root, input.path));
2572
2598
  const [commits, memories, codeMap] = await Promise.all([
2573
2599
  runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
2574
2600
  collectAnchoredMemories(ctx, input.path, input.memory_limit),
@@ -2667,7 +2693,6 @@ function runCommand(cmd, args, cwd) {
2667
2693
  import { existsSync as existsSync25 } from "fs";
2668
2694
  import {
2669
2695
  addedLinesFromDiff,
2670
- appendPreventionEvent,
2671
2696
  BRIDGE_TARGET_PATH,
2672
2697
  buildDocFrequency,
2673
2698
  CODE_STOPWORDS,
@@ -2679,9 +2704,8 @@ import {
2679
2704
  loadUsageIndex as loadUsageIndex10,
2680
2705
  literalMatchesAnyToken as literalMatchesAnyToken3,
2681
2706
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
2682
- recordPrevention,
2707
+ recordPreventionHits,
2683
2708
  runSensors,
2684
- saveUsageIndex as saveUsageIndex4,
2685
2709
  sensorTargetsFromDiff,
2686
2710
  tokenizeQuery as tokenizeQuery3
2687
2711
  } from "@hiveai/core";
@@ -2866,19 +2890,7 @@ async function antiPatternsCheck(input, ctx) {
2866
2890
  const strongCatches = warnings.filter(
2867
2891
  (w) => w.reasons.includes("sensor") || w.reasons.includes("anchor") && w.reasons.includes("literal")
2868
2892
  );
2869
- if (strongCatches.length > 0) {
2870
- const recordedIds = [];
2871
- for (const w of strongCatches) if (recordPrevention(usage, w.id)) recordedIds.push(w.id);
2872
- if (recordedIds.length > 0) {
2873
- await saveUsageIndex4(ctx.paths, usage).catch(() => {
2874
- });
2875
- const at = (/* @__PURE__ */ new Date()).toISOString();
2876
- for (const id of recordedIds) {
2877
- await appendPreventionEvent(ctx.paths, { at, id, source: "anti-pattern" }).catch(() => {
2878
- });
2879
- }
2880
- }
2881
- }
2893
+ await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
2882
2894
  return {
2883
2895
  scanned: negative.length,
2884
2896
  warnings
@@ -3635,7 +3647,7 @@ function repairTargetPathForWarning(warning, paths) {
3635
3647
  // src/tools/pattern-detect.ts
3636
3648
  import { mkdir as mkdir8, writeFile as writeFile14 } from "fs/promises";
3637
3649
  import { existsSync as existsSync29 } from "fs";
3638
- import path12 from "path";
3650
+ import path13 from "path";
3639
3651
  import { execSync as execSync2 } from "child_process";
3640
3652
  import {
3641
3653
  buildFrontmatter as buildFrontmatter5,
@@ -3684,13 +3696,13 @@ async function patternDetect(input, ctx) {
3684
3696
  try {
3685
3697
  const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
3686
3698
  const configFiles = changedFiles.filter(
3687
- (f) => CONFIG_PATTERNS.some((p) => path12.basename(f.toLowerCase()).includes(p))
3699
+ (f) => CONFIG_PATTERNS.some((p) => path13.basename(f.toLowerCase()).includes(p))
3688
3700
  );
3689
3701
  for (const file of configFiles.slice(0, 5)) {
3690
3702
  const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
3691
3703
  if (!diff) continue;
3692
- const parentDir = path12.basename(path12.dirname(file));
3693
- const baseName = path12.basename(file).replace(/\.[^.]+$/, "");
3704
+ const parentDir = path13.basename(path13.dirname(file));
3705
+ const baseName = path13.basename(file).replace(/\.[^.]+$/, "");
3694
3706
  const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
3695
3707
  matches.push({
3696
3708
  kind: "config_change",
@@ -3754,7 +3766,7 @@ async function patternDetect(input, ctx) {
3754
3766
  for (const [p, { count, tools }] of pathCounts) {
3755
3767
  if (count < HOT_FILE_MIN) continue;
3756
3768
  if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
3757
- if (CONFIG_PATTERNS.some((cp) => path12.basename(p).includes(cp))) continue;
3769
+ if (CONFIG_PATTERNS.some((cp) => path13.basename(p).includes(cp))) continue;
3758
3770
  const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
3759
3771
  matches.push({
3760
3772
  kind: "hot_file",
@@ -3801,7 +3813,7 @@ async function patternDetect(input, ctx) {
3801
3813
  void 0
3802
3814
  );
3803
3815
  if (existsSync29(file)) continue;
3804
- await mkdir8(path12.dirname(file), { recursive: true });
3816
+ await mkdir8(path13.dirname(file), { recursive: true });
3805
3817
  await writeFile14(
3806
3818
  file,
3807
3819
  serializeMemory12({ frontmatter: fm, body: match.proposed_body }),
@@ -3850,9 +3862,22 @@ import { existsSync as existsSync30 } from "fs";
3850
3862
  import {
3851
3863
  findLexicalConflictPairs,
3852
3864
  findTopicStatusConflictPairs,
3853
- loadMemoriesFromDir as loadMemoriesFromDir23
3865
+ loadMemoriesFromDir as loadMemoriesFromDir23,
3866
+ planConflictResolution
3854
3867
  } from "@hiveai/core";
3855
3868
  import { z as z32 } from "zod";
3869
+ function suggestResolution(byId, idA, idB) {
3870
+ const a = byId.get(idA);
3871
+ const b = byId.get(idB);
3872
+ if (!a || !b) return null;
3873
+ const plan = planConflictResolution(a, b);
3874
+ return {
3875
+ keep_id: plan.keep_id,
3876
+ supersede_id: plan.supersede_id,
3877
+ reason: plan.reason,
3878
+ command: `haive memory resolve-conflict ${plan.keep_id} ${plan.supersede_id} --yes`
3879
+ };
3880
+ }
3856
3881
  var MemConflictCandidatesInputSchema = {
3857
3882
  since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
3858
3883
  types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
@@ -3874,6 +3899,7 @@ async function memConflictCandidates(input, ctx) {
3874
3899
  };
3875
3900
  }
3876
3901
  const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
3902
+ const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
3877
3903
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
3878
3904
  sinceDays: input.since_days,
3879
3905
  types: input.types,
@@ -3882,8 +3908,22 @@ async function memConflictCandidates(input, ctx) {
3882
3908
  maxScan: input.max_scan
3883
3909
  });
3884
3910
  const topicStatusPairs = findTopicStatusConflictPairs(all, input.max_topic_pairs);
3911
+ const enrichedPairs = pairs.map((p) => ({
3912
+ ...p,
3913
+ suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
3914
+ }));
3915
+ const enrichedTopicStatusPairs = topicStatusPairs.map((p) => ({
3916
+ ...p,
3917
+ suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
3918
+ }));
3885
3919
  const notice = pairs.length === 0 && topicStatusPairs.length === 0 ? "No lexical or topic-status candidates \u2014 widen since_days/types or lower min_jaccard." : void 0;
3886
- return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
3920
+ return {
3921
+ pairs: enrichedPairs,
3922
+ topic_status_pairs: enrichedTopicStatusPairs,
3923
+ scanned,
3924
+ truncated,
3925
+ notice
3926
+ };
3887
3927
  }
3888
3928
 
3889
3929
  // src/tools/mem-resolve-project.ts
@@ -4230,7 +4270,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4230
4270
  // src/server.ts
4231
4271
  import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
4232
4272
  var SERVER_NAME = "haive";
4233
- var SERVER_VERSION = "0.21.0";
4273
+ var SERVER_VERSION = "0.24.0";
4234
4274
  function jsonResult(data) {
4235
4275
  return {
4236
4276
  content: [