@hiveai/mcp 0.9.16 → 0.9.18

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/server.d.ts CHANGED
@@ -69,6 +69,11 @@ declare const GetBriefingZod: z.ZodObject<{
69
69
  budget_preset?: "quick" | "balanced" | "deep" | undefined;
70
70
  }>;
71
71
  type GetBriefingInput = z.infer<typeof GetBriefingZod>;
72
+ type BriefingMemoryPriority = "must_read" | "useful" | "background";
73
+ interface BriefingQuality {
74
+ level: "strong" | "thin" | "noisy";
75
+ reasons: string[];
76
+ }
72
77
  interface BriefingMemory {
73
78
  id: string;
74
79
  scope: string;
@@ -80,9 +85,11 @@ interface BriefingMemory {
80
85
  /** Present when confidence is 'low' or 'unverified' — AI should weight this memory cautiously. */
81
86
  unverified?: true;
82
87
  read_count: number;
83
- reasons: Array<"anchor" | "module" | "domain" | "semantic">;
88
+ reasons: Array<"anchor" | "module" | "domain" | "semantic" | "symbol">;
84
89
  match_quality: "exact" | "partial" | "semantic";
85
90
  semantic_score?: number;
91
+ /** Relevance tier for the current task. `must_read` should be consumed before edits. */
92
+ priority: BriefingMemoryPriority;
86
93
  /** Human/agent-readable explanation for why this record was surfaced. */
87
94
  why?: string[];
88
95
  body: string;
@@ -132,6 +139,7 @@ interface BriefingOutput {
132
139
  truncated: boolean;
133
140
  }>;
134
141
  memories: BriefingMemory[];
142
+ briefing_quality: BriefingQuality;
135
143
  symbol_locations?: CodeMapSymbolHit[];
136
144
  /**
137
145
  * Memories that require explicit human confirmation before any code action.
@@ -249,6 +257,7 @@ interface MemRelevantToOutput {
249
257
  task: string;
250
258
  search_mode: "semantic" | "literal_fallback" | "literal";
251
259
  memories: BriefingMemory[];
260
+ briefing_quality: BriefingQuality;
252
261
  hints?: string[];
253
262
  /**
254
263
  * True when the search returned zero memories — clients can skip surfacing
@@ -549,6 +558,8 @@ interface ClassifiedAntiPatternsWarning extends AntiPatternsWarning {
549
558
  */
550
559
  level: AntiPatternLevel;
551
560
  rationale: string;
561
+ affected_files: string[];
562
+ repair_command: string;
552
563
  }
553
564
  /**
554
565
  * One-shot "should I block this commit?" check.
package/dist/server.js CHANGED
@@ -222,7 +222,6 @@ async function memSave(input, ctx) {
222
222
  const { similarityWarning: simW, body_similar: bs } = bodySimilarWarnings(/* @__PURE__ */ new Set([fm.id]));
223
223
  const newFrontmatter = {
224
224
  ...fm,
225
- body: input.body,
226
225
  tags: input.tags.length ? input.tags : fm.tags,
227
226
  revision_count: (fm.revision_count ?? 0) + 1,
228
227
  anchor: {
@@ -238,6 +237,7 @@ async function memSave(input, ctx) {
238
237
  );
239
238
  const mergedTw = [
240
239
  invalidPaths.length > 0 ? `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by haive sync.` : null,
240
+ criticalAnchorWarning(input.type, fm.status, newFrontmatter.anchor.paths, newFrontmatter.anchor.symbols),
241
241
  simW ?? null
242
242
  ].filter(Boolean).join(" \u2014 ") || void 0;
243
243
  return {
@@ -293,6 +293,7 @@ async function memSave(input, ctx) {
293
293
  const { similarityWarning: simWarnNew, body_similar: bsNew } = bodySimilarWarnings();
294
294
  const finalWarning = [
295
295
  invalidPaths.length > 0 ? `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by \`haive sync\`.` : null,
296
+ criticalAnchorWarning(frontmatter.type, frontmatter.status, frontmatter.anchor.paths, frontmatter.anchor.symbols),
296
297
  warning ?? null,
297
298
  simWarnNew ?? null
298
299
  ].filter(Boolean).join(" \u2014 ") || void 0;
@@ -307,6 +308,12 @@ async function memSave(input, ctx) {
307
308
  ...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
308
309
  };
309
310
  }
311
+ function criticalAnchorWarning(type, status, paths, symbols) {
312
+ if (!["decision", "gotcha", "architecture"].includes(type)) return null;
313
+ if (status !== "validated") return null;
314
+ if (paths.length > 0 || symbols.length > 0) return null;
315
+ return `${type} is validated without paths or symbols; add anchors so hAIve can detect drift.`;
316
+ }
310
317
 
311
318
  // src/tools/mem-search.ts
312
319
  import { existsSync as existsSync5 } from "fs";
@@ -1383,6 +1390,7 @@ import {
1383
1390
  extractActionsBriefBody,
1384
1391
  getUsage as getUsage5,
1385
1392
  inferModulesFromPaths as inferModulesFromPaths2,
1393
+ isGlobPath,
1386
1394
  isAutoPromoteEligible,
1387
1395
  isDecaying,
1388
1396
  literalMatchesAllTokens as literalMatchesAllTokens2,
@@ -1504,6 +1512,7 @@ async function getBriefing(input, ctx) {
1504
1512
  reasons: [reason],
1505
1513
  match_quality: matchQuality ?? "partial",
1506
1514
  ...score !== void 0 ? { semantic_score: score } : {},
1515
+ priority: "background",
1507
1516
  body: loaded.memory.body,
1508
1517
  file_path: loaded.filePath
1509
1518
  });
@@ -1519,6 +1528,13 @@ async function getBriefing(input, ctx) {
1519
1528
  if (fm.tags.some((t) => inferred.includes(t))) addOrUpdate(loaded, "module", void 0, "partial");
1520
1529
  }
1521
1530
  }
1531
+ if (input.symbols.length > 0) {
1532
+ const wanted = new Set(input.symbols.map((s) => s.toLowerCase()));
1533
+ for (const loaded of allMemories) {
1534
+ const symbols = loaded.memory.frontmatter.anchor.symbols.map((s) => s.toLowerCase());
1535
+ if (symbols.some((s) => wanted.has(s))) addOrUpdate(loaded, "symbol", void 0, "exact");
1536
+ }
1537
+ }
1522
1538
  if (input.task) {
1523
1539
  const tokens = tokenizeQuery2(input.task);
1524
1540
  const andHits = allMemories.filter((m) => literalMatchesAllTokens2(m.memory, tokens));
@@ -1544,11 +1560,12 @@ async function getBriefing(input, ctx) {
1544
1560
  }
1545
1561
  }
1546
1562
  const ranked = [...seen.values()].sort((a, b) => {
1563
+ const priorityScore = (m) => priorityRank(classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols));
1547
1564
  const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + // attempt = negative knowledge, surface first to prevent repeating mistakes
1548
- (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
1565
+ (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);
1549
1566
  const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
1550
- const sa = reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
1551
- const sb = reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
1567
+ const sa = priorityScore(a) * 100 + reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
1568
+ const sb = priorityScore(b) * 100 + reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
1552
1569
  return sb - sa;
1553
1570
  });
1554
1571
  for (const mem of ranked.slice(0, briefingMaxMemories)) {
@@ -1707,8 +1724,15 @@ ${m.content}`).join("\n\n---\n\n"),
1707
1724
  })) : trimmedMemories;
1708
1725
  const outputMemories = formattedMemories.map((m) => ({
1709
1726
  ...m,
1727
+ priority: classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols),
1710
1728
  why: explainWhySurfaced(m, byId.get(m.id), input.files, inferred)
1711
1729
  }));
1730
+ const briefingQuality = classifyBriefingQuality(outputMemories, {
1731
+ isTemplateContext,
1732
+ autoContextGenerated,
1733
+ hasLastSession: Boolean(lastSession),
1734
+ searchMode
1735
+ });
1712
1736
  let symbolLocations;
1713
1737
  const symbolsToLookup = new Set(input.symbols);
1714
1738
  for (const m of outputMemories) {
@@ -1849,6 +1873,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
1849
1873
  } : null,
1850
1874
  module_contexts: trimmedModules,
1851
1875
  memories: outputMemories,
1876
+ briefing_quality: briefingQuality,
1852
1877
  ...symbolLocations ? { symbol_locations: symbolLocations } : {},
1853
1878
  action_required: actionRequired,
1854
1879
  decay_warnings: decayWarnings,
@@ -1874,6 +1899,53 @@ function compactSummary(body) {
1874
1899
  }
1875
1900
  return body.slice(0, 120);
1876
1901
  }
1902
+ function classifyMemoryPriority(memory, loaded, inputFiles, inputSymbols) {
1903
+ const fm = loaded?.memory.frontmatter;
1904
+ const directAnchor = Boolean(
1905
+ fm && inputFiles.length > 0 && fm.anchor.paths.some((p) => inputFiles.some((file) => pathsOverlap(p, file)))
1906
+ );
1907
+ const directSymbol = Boolean(
1908
+ fm && inputSymbols.length > 0 && fm.anchor.symbols.some(
1909
+ (sym) => inputSymbols.some((wanted) => wanted.toLowerCase() === sym.toLowerCase())
1910
+ )
1911
+ );
1912
+ const strongSemantic = (memory.semantic_score ?? 0) >= 0.65;
1913
+ const usefulSemantic = (memory.semantic_score ?? 0) >= 0.35;
1914
+ if (fm?.requires_human_approval || directAnchor || directSymbol || memory.type === "attempt" && (memory.match_quality === "exact" || strongSemantic)) {
1915
+ return "must_read";
1916
+ }
1917
+ if (memory.reasons.includes("module") || memory.reasons.includes("domain") || memory.match_quality === "exact" || usefulSemantic) {
1918
+ return "useful";
1919
+ }
1920
+ return "background";
1921
+ }
1922
+ function priorityRank(priority) {
1923
+ return priority === "must_read" ? 3 : priority === "useful" ? 2 : 1;
1924
+ }
1925
+ function classifyBriefingQuality(memories, context) {
1926
+ const mustRead = memories.filter((m) => m.priority === "must_read").length;
1927
+ const useful = memories.filter((m) => m.priority === "useful").length;
1928
+ const background = memories.filter((m) => m.priority === "background").length;
1929
+ const weakSemantic = memories.filter(
1930
+ (m) => m.reasons.length === 1 && m.reasons.includes("semantic") && (m.semantic_score ?? 0) > 0 && (m.semantic_score ?? 0) < 0.35
1931
+ ).length;
1932
+ const reasons = [];
1933
+ if (memories.length === 0) reasons.push("no memories matched the task or files");
1934
+ if (context.isTemplateContext && !context.autoContextGenerated) reasons.push("project context is still a template");
1935
+ if (!context.hasLastSession) reasons.push("no previous session recap");
1936
+ if (mustRead > 0) reasons.push(`${mustRead} must_read memor${mustRead === 1 ? "y" : "ies"} matched directly`);
1937
+ if (useful > 0) reasons.push(`${useful} useful memor${useful === 1 ? "y" : "ies"} matched`);
1938
+ if (background > useful + mustRead && background > 2) reasons.push(`${background} background memories dominate the result`);
1939
+ if (weakSemantic > 0) reasons.push(`${weakSemantic} weak semantic-only match${weakSemantic === 1 ? "" : "es"}`);
1940
+ if (context.searchMode === "literal_fallback") reasons.push("semantic index unavailable or empty; literal fallback used");
1941
+ if (memories.length === 0 || mustRead === 0 && useful === 0) {
1942
+ return { level: "thin", reasons };
1943
+ }
1944
+ if (background > useful + mustRead && background > 2) {
1945
+ return { level: "noisy", reasons };
1946
+ }
1947
+ return { level: "strong", reasons };
1948
+ }
1877
1949
  function explainWhySurfaced(memory, loaded, inputFiles, inferredModules) {
1878
1950
  const why = [];
1879
1951
  const fm = loaded?.memory.frontmatter;
@@ -1882,7 +1954,19 @@ function explainWhySurfaced(memory, loaded, inputFiles, inferredModules) {
1882
1954
  (p) => inputFiles.length === 0 || inputFiles.some((file) => pathsOverlap(p, file))
1883
1955
  );
1884
1956
  if (matching.length > 0) {
1885
- why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
1957
+ const exact = matching.filter(
1958
+ (p) => !isGlobPath(p) && inputFiles.some((file) => p === file || pathsOverlap(p, file))
1959
+ );
1960
+ const glob = matching.filter((p) => isGlobPath(p));
1961
+ if (exact.length > 0) {
1962
+ why.push(`Exact/file anchor match: ${exact.slice(0, 4).join(", ")}`);
1963
+ }
1964
+ if (glob.length > 0) {
1965
+ why.push(`Glob anchor match: ${glob.slice(0, 4).join(", ")}`);
1966
+ }
1967
+ if (exact.length === 0 && glob.length === 0) {
1968
+ why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
1969
+ }
1886
1970
  } else if (fm.anchor.paths.length > 0) {
1887
1971
  why.push(`Pulled by related anchor: ${fm.anchor.paths.slice(0, 4).join(", ")}`);
1888
1972
  }
@@ -1890,6 +1974,9 @@ function explainWhySurfaced(memory, loaded, inputFiles, inferredModules) {
1890
1974
  why.push(`Anchor symbol${fm.anchor.symbols.length === 1 ? "" : "s"}: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
1891
1975
  }
1892
1976
  }
1977
+ if (memory.reasons.includes("symbol") && fm) {
1978
+ why.push(`Explicit symbol match: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
1979
+ }
1893
1980
  if (memory.reasons.includes("module")) {
1894
1981
  const moduleHints = [
1895
1982
  ...memory.module ? [memory.module] : [],
@@ -2131,7 +2218,8 @@ async function memRelevantTo(input, ctx) {
2131
2218
  const out = {
2132
2219
  task: input.task,
2133
2220
  search_mode: briefing.search_mode,
2134
- memories: briefing.memories
2221
+ memories: briefing.memories,
2222
+ briefing_quality: briefing.briefing_quality
2135
2223
  };
2136
2224
  if (briefing.hints && briefing.hints.length > 0) out.hints = briefing.hints;
2137
2225
  if (briefing.memories.length === 0) out.empty = true;
@@ -2878,7 +2966,7 @@ async function preCommitCheck(input, ctx) {
2878
2966
  const filesTouching = new Set(relevantMatches.map((m) => m.id));
2879
2967
  const staleHits = verifyResult.results.filter((r) => r.stale && filesTouching.has(r.id));
2880
2968
  const blockOn = input.block_on;
2881
- const classifiedWarnings = apResult.warnings.map(classifyWarning);
2969
+ const classifiedWarnings = apResult.warnings.map((warning) => classifyWarning(warning, input.paths));
2882
2970
  const blockingWarnings = classifiedWarnings.filter((w) => w.level === "blocking");
2883
2971
  const reviewWarnings = classifiedWarnings.filter((w) => w.level === "review");
2884
2972
  const infoWarnings = classifiedWarnings.filter((w) => w.level === "info");
@@ -2913,12 +3001,26 @@ async function preCommitCheck(input, ctx) {
2913
3001
  }))
2914
3002
  };
2915
3003
  }
2916
- function classifyWarning(warning) {
3004
+ function classifyWarning(warning, paths) {
3005
+ const affectedFiles = paths.filter((p) => !p.startsWith(".ai/.usage/"));
3006
+ const repairCommand = repairCommandForWarning(warning, affectedFiles);
3007
+ const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
3008
+ if (fileDowngrade) {
3009
+ return {
3010
+ ...warning,
3011
+ level: "info",
3012
+ rationale: fileDowngrade,
3013
+ affected_files: affectedFiles,
3014
+ repair_command: repairCommand
3015
+ };
3016
+ }
2917
3017
  if (isBlockingWarning(warning)) {
2918
3018
  return {
2919
3019
  ...warning,
2920
3020
  level: "blocking",
2921
- rationale: "authoritative/trusted memory plus strong semantic match to the diff (score >= 0.65)"
3021
+ rationale: "authoritative/trusted memory plus strong semantic match to the diff (score >= 0.65)",
3022
+ affected_files: affectedFiles,
3023
+ repair_command: repairCommand
2922
3024
  };
2923
3025
  }
2924
3026
  const hasSemantic = warning.reasons.includes("semantic");
@@ -2928,13 +3030,17 @@ function classifyWarning(warning) {
2928
3030
  return {
2929
3031
  ...warning,
2930
3032
  level: "review",
2931
- rationale: hasSemantic ? "semantic match is plausible but below blocking threshold" : "anchored high-confidence memory also matched diff tokens, but no strong semantic proof"
3033
+ rationale: hasSemantic ? "semantic match is plausible but below blocking threshold" : "anchored high-confidence memory also matched diff tokens, but no strong semantic proof",
3034
+ affected_files: affectedFiles,
3035
+ repair_command: repairCommand
2932
3036
  };
2933
3037
  }
2934
3038
  return {
2935
3039
  ...warning,
2936
3040
  level: "info",
2937
- rationale: "weak signal only (literal/anchor/low semantic evidence); surfaced for audit, hidden in concise CLI output"
3041
+ rationale: "weak signal only (literal/anchor/low semantic evidence); surfaced for audit, hidden in concise CLI output",
3042
+ affected_files: affectedFiles,
3043
+ repair_command: repairCommand
2938
3044
  };
2939
3045
  }
2940
3046
  function isBlockingWarning(warning) {
@@ -2942,6 +3048,40 @@ function isBlockingWarning(warning) {
2942
3048
  if (!highConfidence) return false;
2943
3049
  return warning.reasons.includes("semantic") && (warning.semantic_score ?? 0) >= 0.65;
2944
3050
  }
3051
+ function fileTypeDowngradeReason(warning, paths) {
3052
+ if (paths.length === 0) return null;
3053
+ if (paths.every((p) => p.startsWith(".ai/.usage/") || p === ".ai/.usage/tool-usage.jsonl")) {
3054
+ return ".ai usage logs are local telemetry and never block commits.";
3055
+ }
3056
+ const docsOnly = paths.every(isDocLikePath);
3057
+ if (docsOnly && !hasStrongSemantic(warning)) {
3058
+ return "docs/changelog-only change; anti-pattern is downgraded unless semantic evidence is strong.";
3059
+ }
3060
+ const configOnly = paths.every(isPackageOrConfigPath);
3061
+ if (configOnly && looksRuntimeSpecific(warning) && !warning.reasons.includes("anchor") && !hasStrongSemantic(warning)) {
3062
+ return "package/config-only change; runtime-specific gotcha is not anchored or semantically strong.";
3063
+ }
3064
+ return null;
3065
+ }
3066
+ function hasStrongSemantic(warning) {
3067
+ return warning.reasons.includes("semantic") && (warning.semantic_score ?? 0) >= 0.65;
3068
+ }
3069
+ function isDocLikePath(file) {
3070
+ const lower = file.toLowerCase();
3071
+ return lower.endsWith(".md") || lower.includes("changelog") || lower.startsWith("docs/") || lower.startsWith(".github/") && lower.endsWith(".md");
3072
+ }
3073
+ function isPackageOrConfigPath(file) {
3074
+ const lower = file.toLowerCase();
3075
+ 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/");
3076
+ }
3077
+ function looksRuntimeSpecific(warning) {
3078
+ const text = `${warning.body_preview} ${warning.id}`.toLowerCase();
3079
+ return /\b(runtime|controller|request|response|database|transaction|auth|cache|production|service|api|endpoint)\b/.test(text);
3080
+ }
3081
+ function repairCommandForWarning(warning, paths) {
3082
+ const firstPath = paths[0];
3083
+ return firstPath ? `haive briefing --files "${firstPath}" --task "review ${warning.id}"` : `haive memory show ${warning.id}`;
3084
+ }
2945
3085
 
2946
3086
  // src/tools/pattern-detect.ts
2947
3087
  import { mkdir as mkdir7, writeFile as writeFile12 } from "fs/promises";
@@ -3528,7 +3668,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
3528
3668
  // src/server.ts
3529
3669
  import { loadConfigSync } from "@hiveai/core";
3530
3670
  var SERVER_NAME = "haive";
3531
- var SERVER_VERSION = "0.9.16";
3671
+ var SERVER_VERSION = "0.9.18";
3532
3672
  function jsonResult(data) {
3533
3673
  return {
3534
3674
  content: [