@hiveai/mcp 0.9.16 → 0.9.17

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
@@ -224,7 +224,6 @@ async function memSave(input, ctx) {
224
224
  const { similarityWarning: simW, body_similar: bs } = bodySimilarWarnings(/* @__PURE__ */ new Set([fm.id]));
225
225
  const newFrontmatter = {
226
226
  ...fm,
227
- body: input.body,
228
227
  tags: input.tags.length ? input.tags : fm.tags,
229
228
  revision_count: (fm.revision_count ?? 0) + 1,
230
229
  anchor: {
@@ -240,6 +239,7 @@ async function memSave(input, ctx) {
240
239
  );
241
240
  const mergedTw = [
242
241
  invalidPaths.length > 0 ? `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by haive sync.` : null,
242
+ criticalAnchorWarning(input.type, fm.status, newFrontmatter.anchor.paths, newFrontmatter.anchor.symbols),
243
243
  simW ?? null
244
244
  ].filter(Boolean).join(" \u2014 ") || void 0;
245
245
  return {
@@ -295,6 +295,7 @@ async function memSave(input, ctx) {
295
295
  const { similarityWarning: simWarnNew, body_similar: bsNew } = bodySimilarWarnings();
296
296
  const finalWarning = [
297
297
  invalidPaths.length > 0 ? `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by \`haive sync\`.` : null,
298
+ criticalAnchorWarning(frontmatter.type, frontmatter.status, frontmatter.anchor.paths, frontmatter.anchor.symbols),
298
299
  warning ?? null,
299
300
  simWarnNew ?? null
300
301
  ].filter(Boolean).join(" \u2014 ") || void 0;
@@ -309,6 +310,12 @@ async function memSave(input, ctx) {
309
310
  ...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
310
311
  };
311
312
  }
313
+ function criticalAnchorWarning(type, status, paths, symbols) {
314
+ if (!["decision", "gotcha", "architecture"].includes(type)) return null;
315
+ if (status !== "validated") return null;
316
+ if (paths.length > 0 || symbols.length > 0) return null;
317
+ return `${type} is validated without paths or symbols; add anchors so hAIve can detect drift.`;
318
+ }
312
319
 
313
320
  // src/tools/mem-search.ts
314
321
  import { existsSync as existsSync5 } from "fs";
@@ -1385,6 +1392,7 @@ import {
1385
1392
  extractActionsBriefBody,
1386
1393
  getUsage as getUsage5,
1387
1394
  inferModulesFromPaths as inferModulesFromPaths2,
1395
+ isGlobPath,
1388
1396
  isAutoPromoteEligible,
1389
1397
  isDecaying,
1390
1398
  literalMatchesAllTokens as literalMatchesAllTokens2,
@@ -1506,6 +1514,7 @@ async function getBriefing(input, ctx) {
1506
1514
  reasons: [reason],
1507
1515
  match_quality: matchQuality ?? "partial",
1508
1516
  ...score !== void 0 ? { semantic_score: score } : {},
1517
+ priority: "background",
1509
1518
  body: loaded.memory.body,
1510
1519
  file_path: loaded.filePath
1511
1520
  });
@@ -1521,6 +1530,13 @@ async function getBriefing(input, ctx) {
1521
1530
  if (fm.tags.some((t) => inferred.includes(t))) addOrUpdate(loaded, "module", void 0, "partial");
1522
1531
  }
1523
1532
  }
1533
+ if (input.symbols.length > 0) {
1534
+ const wanted = new Set(input.symbols.map((s) => s.toLowerCase()));
1535
+ for (const loaded of allMemories) {
1536
+ const symbols = loaded.memory.frontmatter.anchor.symbols.map((s) => s.toLowerCase());
1537
+ if (symbols.some((s) => wanted.has(s))) addOrUpdate(loaded, "symbol", void 0, "exact");
1538
+ }
1539
+ }
1524
1540
  if (input.task) {
1525
1541
  const tokens = tokenizeQuery2(input.task);
1526
1542
  const andHits = allMemories.filter((m) => literalMatchesAllTokens2(m.memory, tokens));
@@ -1546,11 +1562,12 @@ async function getBriefing(input, ctx) {
1546
1562
  }
1547
1563
  }
1548
1564
  const ranked = [...seen.values()].sort((a, b) => {
1565
+ const priorityScore = (m) => priorityRank(classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols));
1549
1566
  const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + // attempt = negative knowledge, surface first to prevent repeating mistakes
1550
- (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
1567
+ (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("symbol") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
1551
1568
  const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
1552
- const sa = reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
1553
- const sb = reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
1569
+ const sa = priorityScore(a) * 100 + reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
1570
+ const sb = priorityScore(b) * 100 + reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
1554
1571
  return sb - sa;
1555
1572
  });
1556
1573
  for (const mem of ranked.slice(0, briefingMaxMemories)) {
@@ -1709,8 +1726,15 @@ ${m.content}`).join("\n\n---\n\n"),
1709
1726
  })) : trimmedMemories;
1710
1727
  const outputMemories = formattedMemories.map((m) => ({
1711
1728
  ...m,
1729
+ priority: classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols),
1712
1730
  why: explainWhySurfaced(m, byId.get(m.id), input.files, inferred)
1713
1731
  }));
1732
+ const briefingQuality = classifyBriefingQuality(outputMemories, {
1733
+ isTemplateContext,
1734
+ autoContextGenerated,
1735
+ hasLastSession: Boolean(lastSession),
1736
+ searchMode
1737
+ });
1714
1738
  let symbolLocations;
1715
1739
  const symbolsToLookup = new Set(input.symbols);
1716
1740
  for (const m of outputMemories) {
@@ -1851,6 +1875,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
1851
1875
  } : null,
1852
1876
  module_contexts: trimmedModules,
1853
1877
  memories: outputMemories,
1878
+ briefing_quality: briefingQuality,
1854
1879
  ...symbolLocations ? { symbol_locations: symbolLocations } : {},
1855
1880
  action_required: actionRequired,
1856
1881
  decay_warnings: decayWarnings,
@@ -1876,6 +1901,53 @@ function compactSummary(body) {
1876
1901
  }
1877
1902
  return body.slice(0, 120);
1878
1903
  }
1904
+ function classifyMemoryPriority(memory, loaded, inputFiles, inputSymbols) {
1905
+ const fm = loaded?.memory.frontmatter;
1906
+ const directAnchor = Boolean(
1907
+ fm && inputFiles.length > 0 && fm.anchor.paths.some((p) => inputFiles.some((file) => pathsOverlap(p, file)))
1908
+ );
1909
+ const directSymbol = Boolean(
1910
+ fm && inputSymbols.length > 0 && fm.anchor.symbols.some(
1911
+ (sym) => inputSymbols.some((wanted) => wanted.toLowerCase() === sym.toLowerCase())
1912
+ )
1913
+ );
1914
+ const strongSemantic = (memory.semantic_score ?? 0) >= 0.65;
1915
+ const usefulSemantic = (memory.semantic_score ?? 0) >= 0.35;
1916
+ if (fm?.requires_human_approval || directAnchor || directSymbol || memory.type === "attempt" && (memory.match_quality === "exact" || strongSemantic)) {
1917
+ return "must_read";
1918
+ }
1919
+ if (memory.reasons.includes("module") || memory.reasons.includes("domain") || memory.match_quality === "exact" || usefulSemantic) {
1920
+ return "useful";
1921
+ }
1922
+ return "background";
1923
+ }
1924
+ function priorityRank(priority) {
1925
+ return priority === "must_read" ? 3 : priority === "useful" ? 2 : 1;
1926
+ }
1927
+ function classifyBriefingQuality(memories, context) {
1928
+ const mustRead = memories.filter((m) => m.priority === "must_read").length;
1929
+ const useful = memories.filter((m) => m.priority === "useful").length;
1930
+ const background = memories.filter((m) => m.priority === "background").length;
1931
+ const weakSemantic = memories.filter(
1932
+ (m) => m.reasons.length === 1 && m.reasons.includes("semantic") && (m.semantic_score ?? 0) > 0 && (m.semantic_score ?? 0) < 0.35
1933
+ ).length;
1934
+ const reasons = [];
1935
+ if (memories.length === 0) reasons.push("no memories matched the task or files");
1936
+ if (context.isTemplateContext && !context.autoContextGenerated) reasons.push("project context is still a template");
1937
+ if (!context.hasLastSession) reasons.push("no previous session recap");
1938
+ if (mustRead > 0) reasons.push(`${mustRead} must_read memor${mustRead === 1 ? "y" : "ies"} matched directly`);
1939
+ if (useful > 0) reasons.push(`${useful} useful memor${useful === 1 ? "y" : "ies"} matched`);
1940
+ if (background > useful + mustRead && background > 2) reasons.push(`${background} background memories dominate the result`);
1941
+ if (weakSemantic > 0) reasons.push(`${weakSemantic} weak semantic-only match${weakSemantic === 1 ? "" : "es"}`);
1942
+ if (context.searchMode === "literal_fallback") reasons.push("semantic index unavailable or empty; literal fallback used");
1943
+ if (memories.length === 0 || mustRead === 0 && useful === 0) {
1944
+ return { level: "thin", reasons };
1945
+ }
1946
+ if (background > useful + mustRead && background > 2) {
1947
+ return { level: "noisy", reasons };
1948
+ }
1949
+ return { level: "strong", reasons };
1950
+ }
1879
1951
  function explainWhySurfaced(memory, loaded, inputFiles, inferredModules) {
1880
1952
  const why = [];
1881
1953
  const fm = loaded?.memory.frontmatter;
@@ -1884,7 +1956,19 @@ function explainWhySurfaced(memory, loaded, inputFiles, inferredModules) {
1884
1956
  (p) => inputFiles.length === 0 || inputFiles.some((file) => pathsOverlap(p, file))
1885
1957
  );
1886
1958
  if (matching.length > 0) {
1887
- why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
1959
+ const exact = matching.filter(
1960
+ (p) => !isGlobPath(p) && inputFiles.some((file) => p === file || pathsOverlap(p, file))
1961
+ );
1962
+ const glob = matching.filter((p) => isGlobPath(p));
1963
+ if (exact.length > 0) {
1964
+ why.push(`Exact/file anchor match: ${exact.slice(0, 4).join(", ")}`);
1965
+ }
1966
+ if (glob.length > 0) {
1967
+ why.push(`Glob anchor match: ${glob.slice(0, 4).join(", ")}`);
1968
+ }
1969
+ if (exact.length === 0 && glob.length === 0) {
1970
+ why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
1971
+ }
1888
1972
  } else if (fm.anchor.paths.length > 0) {
1889
1973
  why.push(`Pulled by related anchor: ${fm.anchor.paths.slice(0, 4).join(", ")}`);
1890
1974
  }
@@ -1892,6 +1976,9 @@ function explainWhySurfaced(memory, loaded, inputFiles, inferredModules) {
1892
1976
  why.push(`Anchor symbol${fm.anchor.symbols.length === 1 ? "" : "s"}: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
1893
1977
  }
1894
1978
  }
1979
+ if (memory.reasons.includes("symbol") && fm) {
1980
+ why.push(`Explicit symbol match: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
1981
+ }
1895
1982
  if (memory.reasons.includes("module")) {
1896
1983
  const moduleHints = [
1897
1984
  ...memory.module ? [memory.module] : [],
@@ -2133,7 +2220,8 @@ async function memRelevantTo(input, ctx) {
2133
2220
  const out = {
2134
2221
  task: input.task,
2135
2222
  search_mode: briefing.search_mode,
2136
- memories: briefing.memories
2223
+ memories: briefing.memories,
2224
+ briefing_quality: briefing.briefing_quality
2137
2225
  };
2138
2226
  if (briefing.hints && briefing.hints.length > 0) out.hints = briefing.hints;
2139
2227
  if (briefing.memories.length === 0) out.empty = true;
@@ -2880,7 +2968,7 @@ async function preCommitCheck(input, ctx) {
2880
2968
  const filesTouching = new Set(relevantMatches.map((m) => m.id));
2881
2969
  const staleHits = verifyResult.results.filter((r) => r.stale && filesTouching.has(r.id));
2882
2970
  const blockOn = input.block_on;
2883
- const classifiedWarnings = apResult.warnings.map(classifyWarning);
2971
+ const classifiedWarnings = apResult.warnings.map((warning) => classifyWarning(warning, input.paths));
2884
2972
  const blockingWarnings = classifiedWarnings.filter((w) => w.level === "blocking");
2885
2973
  const reviewWarnings = classifiedWarnings.filter((w) => w.level === "review");
2886
2974
  const infoWarnings = classifiedWarnings.filter((w) => w.level === "info");
@@ -2915,12 +3003,26 @@ async function preCommitCheck(input, ctx) {
2915
3003
  }))
2916
3004
  };
2917
3005
  }
2918
- function classifyWarning(warning) {
3006
+ function classifyWarning(warning, paths) {
3007
+ const affectedFiles = paths.filter((p) => !p.startsWith(".ai/.usage/"));
3008
+ const repairCommand = repairCommandForWarning(warning, affectedFiles);
3009
+ const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
3010
+ if (fileDowngrade) {
3011
+ return {
3012
+ ...warning,
3013
+ level: "info",
3014
+ rationale: fileDowngrade,
3015
+ affected_files: affectedFiles,
3016
+ repair_command: repairCommand
3017
+ };
3018
+ }
2919
3019
  if (isBlockingWarning(warning)) {
2920
3020
  return {
2921
3021
  ...warning,
2922
3022
  level: "blocking",
2923
- rationale: "authoritative/trusted memory plus strong semantic match to the diff (score >= 0.65)"
3023
+ rationale: "authoritative/trusted memory plus strong semantic match to the diff (score >= 0.65)",
3024
+ affected_files: affectedFiles,
3025
+ repair_command: repairCommand
2924
3026
  };
2925
3027
  }
2926
3028
  const hasSemantic = warning.reasons.includes("semantic");
@@ -2930,13 +3032,17 @@ function classifyWarning(warning) {
2930
3032
  return {
2931
3033
  ...warning,
2932
3034
  level: "review",
2933
- rationale: hasSemantic ? "semantic match is plausible but below blocking threshold" : "anchored high-confidence memory also matched diff tokens, but no strong semantic proof"
3035
+ rationale: hasSemantic ? "semantic match is plausible but below blocking threshold" : "anchored high-confidence memory also matched diff tokens, but no strong semantic proof",
3036
+ affected_files: affectedFiles,
3037
+ repair_command: repairCommand
2934
3038
  };
2935
3039
  }
2936
3040
  return {
2937
3041
  ...warning,
2938
3042
  level: "info",
2939
- rationale: "weak signal only (literal/anchor/low semantic evidence); surfaced for audit, hidden in concise CLI output"
3043
+ rationale: "weak signal only (literal/anchor/low semantic evidence); surfaced for audit, hidden in concise CLI output",
3044
+ affected_files: affectedFiles,
3045
+ repair_command: repairCommand
2940
3046
  };
2941
3047
  }
2942
3048
  function isBlockingWarning(warning) {
@@ -2944,6 +3050,40 @@ function isBlockingWarning(warning) {
2944
3050
  if (!highConfidence) return false;
2945
3051
  return warning.reasons.includes("semantic") && (warning.semantic_score ?? 0) >= 0.65;
2946
3052
  }
3053
+ function fileTypeDowngradeReason(warning, paths) {
3054
+ if (paths.length === 0) return null;
3055
+ if (paths.every((p) => p.startsWith(".ai/.usage/") || p === ".ai/.usage/tool-usage.jsonl")) {
3056
+ return ".ai usage logs are local telemetry and never block commits.";
3057
+ }
3058
+ const docsOnly = paths.every(isDocLikePath);
3059
+ if (docsOnly && !hasStrongSemantic(warning)) {
3060
+ return "docs/changelog-only change; anti-pattern is downgraded unless semantic evidence is strong.";
3061
+ }
3062
+ const configOnly = paths.every(isPackageOrConfigPath);
3063
+ if (configOnly && looksRuntimeSpecific(warning) && !warning.reasons.includes("anchor") && !hasStrongSemantic(warning)) {
3064
+ return "package/config-only change; runtime-specific gotcha is not anchored or semantically strong.";
3065
+ }
3066
+ return null;
3067
+ }
3068
+ function hasStrongSemantic(warning) {
3069
+ return warning.reasons.includes("semantic") && (warning.semantic_score ?? 0) >= 0.65;
3070
+ }
3071
+ function isDocLikePath(file) {
3072
+ const lower = file.toLowerCase();
3073
+ return lower.endsWith(".md") || lower.includes("changelog") || lower.startsWith("docs/") || lower.startsWith(".github/") && lower.endsWith(".md");
3074
+ }
3075
+ function isPackageOrConfigPath(file) {
3076
+ const lower = file.toLowerCase();
3077
+ return lower.endsWith("package.json") || lower.endsWith("package-lock.json") || lower.endsWith("pnpm-lock.yaml") || lower.endsWith("yarn.lock") || lower.endsWith("bun.lockb") || lower.endsWith(".config.ts") || lower.endsWith(".config.js") || lower.endsWith(".json") || lower.endsWith(".yml") || lower.endsWith(".yaml") || lower.startsWith(".github/workflows/");
3078
+ }
3079
+ function looksRuntimeSpecific(warning) {
3080
+ const text = `${warning.body_preview} ${warning.id}`.toLowerCase();
3081
+ return /\b(runtime|controller|request|response|database|transaction|auth|cache|production|service|api|endpoint)\b/.test(text);
3082
+ }
3083
+ function repairCommandForWarning(warning, paths) {
3084
+ const firstPath = paths[0];
3085
+ return firstPath ? `haive briefing --files "${firstPath}" --task "review ${warning.id}"` : `haive memory show ${warning.id}`;
3086
+ }
2947
3087
 
2948
3088
  // src/tools/pattern-detect.ts
2949
3089
  import { mkdir as mkdir7, writeFile as writeFile12 } from "fs/promises";
@@ -3530,7 +3670,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
3530
3670
  // src/server.ts
3531
3671
  import { loadConfigSync } from "@hiveai/core";
3532
3672
  var SERVER_NAME = "haive";
3533
- var SERVER_VERSION = "0.9.16";
3673
+ var SERVER_VERSION = "0.9.17";
3534
3674
  function jsonResult(data) {
3535
3675
  return {
3536
3676
  content: [