@hiveai/mcp 0.9.0 → 0.9.3

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
@@ -23,7 +23,8 @@ declare class SessionTracker {
23
23
  private registerShutdownHandler;
24
24
  }
25
25
 
26
- declare const GetBriefingInputSchema: {
26
+ /** Single inferred type — optional keys are optional (not `| undefined` required everywhere). */
27
+ declare const GetBriefingZod: z.ZodObject<{
27
28
  task: z.ZodOptional<z.ZodString>;
28
29
  files: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
29
30
  max_tokens: z.ZodDefault<z.ZodNumber>;
@@ -33,13 +34,40 @@ declare const GetBriefingInputSchema: {
33
34
  semantic: z.ZodDefault<z.ZodBoolean>;
34
35
  include_stale: z.ZodDefault<z.ZodBoolean>;
35
36
  track: z.ZodDefault<z.ZodBoolean>;
36
- format: z.ZodDefault<z.ZodEnum<["full", "compact"]>>;
37
+ format: z.ZodDefault<z.ZodEnum<["full", "compact", "actions"]>>;
37
38
  symbols: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
38
39
  min_semantic_score: z.ZodDefault<z.ZodNumber>;
39
- };
40
- type GetBriefingInput = {
41
- [K in keyof typeof GetBriefingInputSchema]: z.infer<(typeof GetBriefingInputSchema)[K]>;
42
- };
40
+ budget_preset: z.ZodOptional<z.ZodEnum<["quick", "balanced", "deep"]>>;
41
+ }, "strip", z.ZodTypeAny, {
42
+ symbols: string[];
43
+ files: string[];
44
+ max_tokens: number;
45
+ max_memories: number;
46
+ include_project_context: boolean;
47
+ include_module_contexts: boolean;
48
+ semantic: boolean;
49
+ include_stale: boolean;
50
+ track: boolean;
51
+ format: "full" | "compact" | "actions";
52
+ min_semantic_score: number;
53
+ task?: string | undefined;
54
+ budget_preset?: "quick" | "balanced" | "deep" | undefined;
55
+ }, {
56
+ symbols?: string[] | undefined;
57
+ task?: string | undefined;
58
+ files?: string[] | undefined;
59
+ max_tokens?: number | undefined;
60
+ max_memories?: number | undefined;
61
+ include_project_context?: boolean | undefined;
62
+ include_module_contexts?: boolean | undefined;
63
+ semantic?: boolean | undefined;
64
+ include_stale?: boolean | undefined;
65
+ track?: boolean | undefined;
66
+ format?: "full" | "compact" | "actions" | undefined;
67
+ min_semantic_score?: number | undefined;
68
+ budget_preset?: "quick" | "balanced" | "deep" | undefined;
69
+ }>;
70
+ type GetBriefingInput = z.infer<typeof GetBriefingZod>;
43
71
  interface BriefingMemory {
44
72
  id: string;
45
73
  scope: string;
@@ -133,6 +161,7 @@ interface BriefingOutput {
133
161
  modules: number;
134
162
  memories: number;
135
163
  };
164
+ preset_applied?: "quick" | "balanced" | "deep";
136
165
  };
137
166
  }
138
167
  declare function getBriefing(input: GetBriefingInput, ctx: HaiveContext): Promise<BriefingOutput>;
@@ -198,7 +227,7 @@ declare const MemRelevantToInputSchema: {
198
227
  files: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
199
228
  limit: z.ZodDefault<z.ZodNumber>;
200
229
  min_semantic_score: z.ZodDefault<z.ZodNumber>;
201
- format: z.ZodDefault<z.ZodEnum<["full", "compact"]>>;
230
+ format: z.ZodDefault<z.ZodEnum<["full", "compact", "actions"]>>;
202
231
  };
203
232
  type MemRelevantToInput = {
204
233
  [K in keyof typeof MemRelevantToInputSchema]: z.infer<(typeof MemRelevantToInputSchema)[K]>;
@@ -543,5 +572,19 @@ declare function createHaiveServer(options?: CreateContextOptions): {
543
572
  context: HaiveContext;
544
573
  tracker: SessionTracker;
545
574
  };
575
+ /** Parse argv for the standalone haive-mcp binary / CLI subprocess parity. */
576
+ declare function parseMcpCliArgs(argv: string[]): {
577
+ root?: string;
578
+ versionOnly: boolean;
579
+ };
580
+ /** Print MCP server version (same as haive CLI when bundled together). */
581
+ declare function printHaiveMcpVersion(): void;
582
+ /**
583
+ * Run the MCP server over stdio. Used by `haive-mcp` and by `haive mcp --stdio`
584
+ * when the MCP implementation is bundled into the CLI.
585
+ */
586
+ declare function runHaiveMcpStdio(options: {
587
+ root?: string;
588
+ }): Promise<void>;
546
589
 
547
- export { type AntiPatternsCheckInput, type AntiPatternsCheckOutput, type BriefingOutput, type CodeMapInput, type CodeMapToolOutput, type CodeSearchInput, type CodeSearchOutput, type GetBriefingInput, type GetRecapInput, type GetRecapOutput, type MemConflictsInput, type MemConflictsOutput, type MemDistillInput, type MemDistillOutput, type MemRelevantToInput, type MemRelevantToOutput, type PatternDetectInput, type PatternDetectOutput, type PreCommitCheckInput, type PreCommitCheckOutput, SERVER_NAME, SERVER_VERSION, type WhyThisDecisionInput, type WhyThisDecisionOutput, type WhyThisFileInput, type WhyThisFileOutput, antiPatternsCheck, codeMapTool, codeSearch, createHaiveServer, getBriefing, getRecap, memConflicts, memDistill, memRelevantTo, patternDetect, preCommitCheck, whyThisDecision, whyThisFile };
590
+ export { type AntiPatternsCheckInput, type AntiPatternsCheckOutput, type BriefingOutput, type CodeMapInput, type CodeMapToolOutput, type CodeSearchInput, type CodeSearchOutput, type GetBriefingInput, type GetRecapInput, type GetRecapOutput, type MemConflictsInput, type MemConflictsOutput, type MemDistillInput, type MemDistillOutput, type MemRelevantToInput, type MemRelevantToOutput, type PatternDetectInput, type PatternDetectOutput, type PreCommitCheckInput, type PreCommitCheckOutput, SERVER_NAME, SERVER_VERSION, type WhyThisDecisionInput, type WhyThisDecisionOutput, type WhyThisFileInput, type WhyThisFileOutput, antiPatternsCheck, codeMapTool, codeSearch, createHaiveServer, getBriefing, getRecap, memConflicts, memDistill, memRelevantTo, parseMcpCliArgs, patternDetect, preCommitCheck, printHaiveMcpVersion, runHaiveMcpStdio, whyThisDecision, whyThisFile };
package/dist/server.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/server.ts
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
4
 
4
5
  // src/context.ts
5
6
  import { findProjectRoot, resolveHaivePaths } from "@hiveai/core";
@@ -137,7 +138,9 @@ var MemSaveInputSchema = {
137
138
  ),
138
139
  slug: z4.string().min(1).describe("Short human-readable identifier \u2014 becomes part of the filename"),
139
140
  body: z4.string().describe("Markdown body of the memory"),
140
- scope: z4.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope: personal | team | module"),
141
+ scope: z4.enum(["personal", "team", "module"]).optional().describe(
142
+ "Visibility scope: personal | team | module. When omitted, falls back to defaultScope in haive.config.json (default: personal)."
143
+ ),
141
144
  module: z4.string().optional().describe("Module name (required when scope=module)"),
142
145
  tags: z4.array(z4.string()).default([]).describe("Tags for filtering"),
143
146
  domain: z4.string().optional().describe("Domain (e.g. transactions, billing)"),
@@ -152,6 +155,30 @@ var MemSaveInputSchema = {
152
155
  function bodyHash(body) {
153
156
  return createHash("sha256").update(body.trim()).digest("hex").slice(0, 12);
154
157
  }
158
+ var WORD_RE = /\b[a-z0-9]{3,}\b/gi;
159
+ function bodyTokenSet(body) {
160
+ const raw = body.toLowerCase().match(WORD_RE) ?? [];
161
+ return new Set(raw);
162
+ }
163
+ function maxBodySimilarity(incomingTokens, memories, scope, type, excludeIds) {
164
+ if (incomingTokens.size < 6) return null;
165
+ let best = null;
166
+ const skip = excludeIds ?? /* @__PURE__ */ new Set();
167
+ for (const { memory } of memories) {
168
+ const fm = memory.frontmatter;
169
+ if (skip.has(fm.id)) continue;
170
+ if (fm.scope !== scope || fm.type !== type) continue;
171
+ if (fm.status === "rejected" || fm.status === "deprecated") continue;
172
+ const other = bodyTokenSet(memory.body);
173
+ if (other.size === 0) continue;
174
+ let inter = 0;
175
+ for (const t of incomingTokens) if (other.has(t)) inter++;
176
+ const uni = incomingTokens.size + other.size - inter;
177
+ const j = uni === 0 ? 0 : inter / uni;
178
+ if (j >= 0.72 && (!best || j > best.score)) best = { score: j, id: fm.id };
179
+ }
180
+ return best;
181
+ }
155
182
  async function memSave(input, ctx) {
156
183
  if (!existsSync4(ctx.paths.haiveDir)) {
157
184
  throw new Error(
@@ -159,24 +186,40 @@ async function memSave(input, ctx) {
159
186
  );
160
187
  }
161
188
  const existing = existsSync4(ctx.paths.memoriesDir) ? await loadMemoriesFromDir2(ctx.paths.memoriesDir) : [];
189
+ const haiveConfig = await loadConfig(ctx.paths);
190
+ const resolvedScope = input.scope ?? haiveConfig.defaultScope ?? "personal";
162
191
  const invalidPaths = input.paths.filter(
163
192
  (p) => !existsSync4(path3.resolve(ctx.paths.root, p))
164
193
  );
165
194
  const incomingHash = bodyHash(input.body);
166
195
  const hashDuplicate = existing.find(
167
- ({ memory }) => bodyHash(memory.body) === incomingHash && memory.frontmatter.scope === input.scope
196
+ ({ memory }) => bodyHash(memory.body) === incomingHash && memory.frontmatter.scope === resolvedScope
168
197
  );
169
198
  if (hashDuplicate) {
170
199
  throw new Error(
171
200
  `Duplicate content detected \u2014 identical body already saved as "${hashDuplicate.memory.frontmatter.id}". Use mem_update to modify it, or change the body to add new information.`
172
201
  );
173
202
  }
203
+ const incomingTokens = bodyTokenSet(input.body);
204
+ function bodySimilarWarnings(excludeIds) {
205
+ const dup = maxBodySimilarity(incomingTokens, existing, resolvedScope, input.type, excludeIds);
206
+ if (!dup?.id) return {};
207
+ const body_similar = {
208
+ id: dup.id,
209
+ score: Math.round(dup.score * 100) / 100
210
+ };
211
+ return {
212
+ similarityWarning: `Body is ~${Math.round(dup.score * 100)}% similar (token overlap) to existing "${dup.id}" \u2014 consolidate if redundant.`,
213
+ body_similar
214
+ };
215
+ }
174
216
  if (input.topic) {
175
217
  const topicMatch = existing.find(
176
- ({ memory }) => memory.frontmatter.topic === input.topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
218
+ ({ memory }) => memory.frontmatter.topic === input.topic && memory.frontmatter.scope === resolvedScope && (!input.module || memory.frontmatter.module === input.module)
177
219
  );
178
220
  if (topicMatch) {
179
221
  const fm = topicMatch.memory.frontmatter;
222
+ const { similarityWarning: simW, body_similar: bs } = bodySimilarWarnings(/* @__PURE__ */ new Set([fm.id]));
180
223
  const newFrontmatter = {
181
224
  ...fm,
182
225
  body: input.body,
@@ -193,18 +236,22 @@ async function memSave(input, ctx) {
193
236
  serializeMemory({ frontmatter: newFrontmatter, body: input.body }),
194
237
  "utf8"
195
238
  );
239
+ const mergedTw = [
240
+ invalidPaths.length > 0 ? `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by haive sync.` : null,
241
+ simW ?? null
242
+ ].filter(Boolean).join(" \u2014 ") || void 0;
196
243
  return {
197
244
  id: fm.id,
198
245
  scope: fm.scope,
199
246
  file_path: topicMatch.filePath,
200
247
  action: "updated",
201
248
  revision_count: newFrontmatter.revision_count,
202
- ...invalidPaths.length > 0 ? { invalid_paths: invalidPaths, warning: `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by haive sync.` } : {}
249
+ ...mergedTw ? { warning: mergedTw } : {},
250
+ ...bs ? { body_similar: bs } : {},
251
+ ...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
203
252
  };
204
253
  }
205
254
  }
206
- const haiveConfig = await loadConfig(ctx.paths);
207
- const resolvedScope = input.scope !== "personal" ? input.scope : haiveConfig.defaultScope ?? "personal";
208
255
  const frontmatter = buildFrontmatter({
209
256
  type: input.type,
210
257
  slug: input.slug,
@@ -243,9 +290,11 @@ async function memSave(input, ctx) {
243
290
  }
244
291
  }
245
292
  await writeFile2(file, serializeMemory({ frontmatter, body: input.body }), "utf8");
293
+ const { similarityWarning: simWarnNew, body_similar: bsNew } = bodySimilarWarnings();
246
294
  const finalWarning = [
247
295
  invalidPaths.length > 0 ? `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by \`haive sync\`.` : null,
248
- warning ?? null
296
+ warning ?? null,
297
+ simWarnNew ?? null
249
298
  ].filter(Boolean).join(" \u2014 ") || void 0;
250
299
  return {
251
300
  id: frontmatter.id,
@@ -254,6 +303,7 @@ async function memSave(input, ctx) {
254
303
  action: "created",
255
304
  ...finalWarning ? { warning: finalWarning } : {},
256
305
  ...similar_found ? { similar_found } : {},
306
+ ...bsNew ? { body_similar: bsNew } : {},
257
307
  ...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
258
308
  };
259
309
  }
@@ -1289,6 +1339,7 @@ import {
1289
1339
  DEFAULT_AUTO_PROMOTE_RULE,
1290
1340
  deriveConfidence as deriveConfidence4,
1291
1341
  estimateTokens,
1342
+ extractActionsBriefBody,
1292
1343
  getUsage as getUsage5,
1293
1344
  inferModulesFromPaths as inferModulesFromPaths2,
1294
1345
  isAutoPromoteEligible,
@@ -1301,6 +1352,7 @@ import {
1301
1352
  loadUsageIndex as loadUsageIndex7,
1302
1353
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
1303
1354
  queryCodeMap,
1355
+ resolveBriefingBudget,
1304
1356
  serializeMemory as serializeMemory9,
1305
1357
  tokenizeQuery as tokenizeQuery2,
1306
1358
  trackReads as trackReads3,
@@ -1323,17 +1375,29 @@ var GetBriefingInputSchema = {
1323
1375
  ),
1324
1376
  include_stale: z17.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
1325
1377
  track: z17.boolean().default(true).describe("Increment read_count on returned memories"),
1326
- format: z17.enum(["full", "compact"]).default("full").describe(
1327
- "Output format: 'full' returns complete memory bodies; 'compact' returns id + 1-line summary only (call mem_get for details)."
1378
+ format: z17.enum(["full", "compact", "actions"]).default("full").describe(
1379
+ "Output format: 'full' returns memory bodies (honors token budget via truncation); 'compact' returns a 1-line summary per memory (call mem_get for detail); 'actions' squeezes bodies to actionable bullet lines \u2014 fewer tokens vs full."
1328
1380
  ),
1329
1381
  symbols: z17.array(z17.string()).default([]).describe(
1330
1382
  "Symbol names to look up in the code-map (e.g. ['PaymentService', 'TenantFilter']). Returns the file(s) exporting each symbol so agents don't need to grep. Requires `haive index code` to have been run."
1331
1383
  ),
1332
1384
  min_semantic_score: z17.number().min(0).max(1).default(0).describe(
1333
1385
  "Drop semantic-only memory hits whose cosine score is below this threshold. Useful to avoid weakly-related noise when the task is short or the corpus is broad. Has no effect on memories matched via anchor/module/literal \u2014 those are always kept. Try 0.25\u20130.4 for stricter matching."
1386
+ ),
1387
+ budget_preset: z17.enum(["quick", "balanced", "deep"]).optional().describe(
1388
+ "Shortcut token budget: 'quick' minimizes tokens/skip module CONTEXT slices; 'balanced' mirrors historical defaults; 'deep' uses a larger briefing. When set, overrides max_tokens, max_memories, and include_module_contexts."
1334
1389
  )
1335
1390
  };
1391
+ var GetBriefingZod = z17.object(GetBriefingInputSchema);
1336
1392
  async function getBriefing(input, ctx) {
1393
+ const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
1394
+ max_tokens: input.max_tokens,
1395
+ max_memories: input.max_memories,
1396
+ include_module_contexts: input.include_module_contexts
1397
+ });
1398
+ const briefingMaxTokens = resolvedBudget.max_tokens;
1399
+ const briefingMaxMemories = resolvedBudget.max_memories;
1400
+ const briefingIncludeModules = resolvedBudget.include_module_contexts;
1337
1401
  const inferred = inferModulesFromPaths2(input.files);
1338
1402
  const memories = [];
1339
1403
  let searchMode = "literal";
@@ -1445,8 +1509,8 @@ async function getBriefing(input, ctx) {
1445
1509
  const sb = reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
1446
1510
  return sb - sa;
1447
1511
  });
1448
- for (const mem of ranked.slice(0, input.max_memories)) {
1449
- if (seen.size >= input.max_memories * 2) break;
1512
+ for (const mem of ranked.slice(0, briefingMaxMemories)) {
1513
+ if (seen.size >= briefingMaxMemories * 2) break;
1450
1514
  const loaded = byId.get(mem.id);
1451
1515
  if (!loaded) continue;
1452
1516
  for (const relId of loaded.memory.frontmatter.related_ids ?? []) {
@@ -1455,12 +1519,13 @@ async function getBriefing(input, ctx) {
1455
1519
  if (related) addOrUpdate(related, "anchor", void 0, "partial");
1456
1520
  }
1457
1521
  }
1458
- memories.push(...ranked.slice(0, input.max_memories));
1522
+ memories.push(...ranked.slice(0, briefingMaxMemories));
1459
1523
  if (input.track && memories.length > 0) {
1460
1524
  await trackReads3(ctx.paths, memories.map((m) => m.id));
1461
1525
  const freshUsage = await loadUsageIndex7(ctx.paths);
1526
+ const cfg = await loadConfig3(ctx.paths);
1462
1527
  const rule = {
1463
- minReads: DEFAULT_AUTO_PROMOTE_RULE.minReads,
1528
+ minReads: cfg.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE.minReads,
1464
1529
  maxRejections: DEFAULT_AUTO_PROMOTE_RULE.maxRejections
1465
1530
  };
1466
1531
  for (const m of memories) {
@@ -1534,7 +1599,7 @@ async function getBriefing(input, ctx) {
1534
1599
  }
1535
1600
  }
1536
1601
  }
1537
- const moduleContents = input.include_module_contexts ? await loadModuleContexts2(ctx, inferred) : [];
1602
+ const moduleContents = briefingIncludeModules ? await loadModuleContexts2(ctx, inferred) : [];
1538
1603
  const memoriesText = memories.map((m) => {
1539
1604
  const unverified = m.status === "proposed" ? " [UNVERIFIED \u2014 not yet validated]" : "";
1540
1605
  return `### ${m.id} (${m.scope}/${m.type}, ${m.confidence})${unverified}
@@ -1552,7 +1617,7 @@ ${m.content}`).join("\n\n---\n\n"),
1552
1617
  },
1553
1618
  { key: "memories", text: memoriesText, weight: 4, mode: "head" }
1554
1619
  ],
1555
- input.max_tokens
1620
+ briefingMaxTokens
1556
1621
  );
1557
1622
  const projectSlice = slices.find((s) => s.key === "project");
1558
1623
  const modulesSlice = slices.find((s) => s.key === "modules");
@@ -1594,7 +1659,10 @@ ${m.content}`).join("\n\n---\n\n"),
1594
1659
  const createdAt = loaded?.memory.frontmatter.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
1595
1660
  if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
1596
1661
  }
1597
- const outputMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : trimmedMemories;
1662
+ const outputMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : input.format === "actions" ? trimmedMemories.map((m) => ({
1663
+ ...m,
1664
+ body: extractActionsBriefBody(m.body)
1665
+ })) : trimmedMemories;
1598
1666
  let symbolLocations;
1599
1667
  const symbolsToLookup = new Set(input.symbols);
1600
1668
  for (const m of outputMemories) {
@@ -1716,6 +1784,11 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
1716
1784
  "After completing the task: capture new gotchas with mem_observe, failed approaches with mem_tried, validated patterns with mem_save."
1717
1785
  );
1718
1786
  }
1787
+ if (outputMemories.length > 2 && !input.budget_preset && input.task && !hints.some((h) => h.includes("budget_preset"))) {
1788
+ hints.push(
1789
+ "For tighter token budgets on small tasks pass budget_preset:'quick'; for refactor-sized work use budget_preset:'deep'."
1790
+ );
1791
+ }
1719
1792
  }
1720
1793
  return {
1721
1794
  ...input.task ? { task: input.task } : {},
@@ -1738,7 +1811,8 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
1738
1811
  ...hints.length > 0 ? { hints } : {},
1739
1812
  estimated_tokens: totalTokens,
1740
1813
  budget: {
1741
- max_tokens: input.max_tokens,
1814
+ max_tokens: briefingMaxTokens,
1815
+ ...input.budget_preset ? { preset_applied: input.budget_preset } : {},
1742
1816
  spent: {
1743
1817
  project: projectSlice.estimatedTokens,
1744
1818
  modules: modulesSlice.estimatedTokens,
@@ -1951,7 +2025,7 @@ var MemRelevantToInputSchema = {
1951
2025
  files: z21.array(z21.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
1952
2026
  limit: z21.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
1953
2027
  min_semantic_score: z21.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
1954
- format: z21.enum(["full", "compact"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies.")
2028
+ format: z21.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
1955
2029
  };
1956
2030
  async function memRelevantTo(input, ctx) {
1957
2031
  const briefingInput = {
@@ -2808,7 +2882,9 @@ async function patternDetect(input, ctx) {
2808
2882
  for (const file of configFiles.slice(0, 5)) {
2809
2883
  const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
2810
2884
  if (!diff) continue;
2811
- const slug = path11.basename(file).replace(/\.[^.]+$/, "").replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
2885
+ const parentDir = path11.basename(path11.dirname(file));
2886
+ const baseName = path11.basename(file).replace(/\.[^.]+$/, "");
2887
+ const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
2812
2888
  matches.push({
2813
2889
  kind: "config_change",
2814
2890
  signal: `Config file modified: ${file}`,
@@ -3207,7 +3283,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
3207
3283
 
3208
3284
  // src/server.ts
3209
3285
  var SERVER_NAME = "haive";
3210
- var SERVER_VERSION = "0.9.0";
3286
+ var SERVER_VERSION = "0.9.3";
3211
3287
  function jsonResult(data) {
3212
3288
  return {
3213
3289
  content: [
@@ -3373,7 +3449,8 @@ function createHaiveServer(options = {}) {
3373
3449
  " task \u2014 what you are about to do (1\u20132 sentences) \u2014 ALWAYS provide this",
3374
3450
  " files \u2014 files you are about to edit \u2014 surfaces anchored memories",
3375
3451
  " symbols \u2014 symbol names to look up in the code-map (e.g. ['PaymentService'])",
3376
- " format \u2014 'full' (default) | 'compact' (1-line summaries, use when token budget is tight)",
3452
+ " format \u2014 'full' (default) | 'compact' (1-line) | 'actions' (bullet-first excerpts)",
3453
+ " budget_preset \u2014 'quick' | 'balanced' | 'deep' \u2014 scales max_tokens/memories/module contexts",
3377
3454
  "",
3378
3455
  "EXAMPLE USAGE:",
3379
3456
  " get_briefing({ task: 'add a Stripe payment integration', files: ['src/payments/'], symbols: ['PaymentService'] })",
@@ -3679,6 +3756,7 @@ function createHaiveServer(options = {}) {
3679
3756
  " files \u2014 files you'll edit (surfaces anchored memories)",
3680
3757
  " limit \u2014 cap on returned memories (default 8)",
3681
3758
  " min_semantic_score \u2014 drop weak semantic hits below this cosine (default 0.25)",
3759
+ " format \u2014 'full' | 'compact' | 'actions' (inherits get_briefing memory framing)",
3682
3760
  "",
3683
3761
  "RETURNS: { task, search_mode, memories: [...], hints?: [...], empty?: true }"
3684
3762
  ].join("\n"),
@@ -3936,6 +4014,34 @@ function createHaiveServer(options = {}) {
3936
4014
  );
3937
4015
  return { server, context, tracker };
3938
4016
  }
4017
+ function parseMcpCliArgs(argv) {
4018
+ for (let i = 2; i < argv.length; i++) {
4019
+ const arg = argv[i];
4020
+ if (arg === "--version" || arg === "-V") {
4021
+ return { versionOnly: true };
4022
+ }
4023
+ }
4024
+ const out = {};
4025
+ for (let i = 2; i < argv.length; i++) {
4026
+ const arg = argv[i];
4027
+ if (arg === "--root" || arg === "-r") {
4028
+ out.root = argv[++i];
4029
+ } else if (arg?.startsWith("--root=")) {
4030
+ out.root = arg.slice("--root=".length);
4031
+ }
4032
+ }
4033
+ return { root: out.root, versionOnly: false };
4034
+ }
4035
+ function printHaiveMcpVersion() {
4036
+ console.log(SERVER_VERSION);
4037
+ }
4038
+ async function runHaiveMcpStdio(options) {
4039
+ const { server, context } = createHaiveServer({ root: options.root });
4040
+ console.error(
4041
+ `[haive-mcp] starting server v${SERVER_VERSION} (project root: ${context.paths.root})`
4042
+ );
4043
+ await server.connect(new StdioServerTransport());
4044
+ }
3939
4045
  export {
3940
4046
  SERVER_NAME,
3941
4047
  SERVER_VERSION,
@@ -3948,8 +4054,11 @@ export {
3948
4054
  memConflicts,
3949
4055
  memDistill,
3950
4056
  memRelevantTo,
4057
+ parseMcpCliArgs,
3951
4058
  patternDetect,
3952
4059
  preCommitCheck,
4060
+ printHaiveMcpVersion,
4061
+ runHaiveMcpStdio,
3953
4062
  whyThisDecision,
3954
4063
  whyThisFile
3955
4064
  };