@hiveai/mcp 0.2.16 → 0.3.2

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
@@ -10,11 +10,24 @@ interface CreateContextOptions {
10
10
  cwd?: string;
11
11
  }
12
12
 
13
+ declare class SessionTracker {
14
+ private events;
15
+ private startedAt;
16
+ private config;
17
+ private ctx;
18
+ private shutdownRegistered;
19
+ constructor(ctx: HaiveContext);
20
+ init(): Promise<void>;
21
+ record(tool: string, summary?: string): void;
22
+ private registerShutdownHandler;
23
+ }
24
+
13
25
  declare const SERVER_NAME = "haive";
14
26
  declare const SERVER_VERSION: string;
15
27
  declare function createHaiveServer(options?: CreateContextOptions): {
16
28
  server: McpServer;
17
29
  context: HaiveContext;
30
+ tracker: SessionTracker;
18
31
  };
19
32
 
20
33
  export { SERVER_NAME, SERVER_VERSION, createHaiveServer };
package/dist/server.js CHANGED
@@ -125,6 +125,7 @@ import { existsSync as existsSync4 } from "fs";
125
125
  import path3 from "path";
126
126
  import {
127
127
  buildFrontmatter,
128
+ loadConfig,
128
129
  loadMemoriesFromDir as loadMemoriesFromDir2,
129
130
  memoryFilePath,
130
131
  serializeMemory
@@ -202,10 +203,12 @@ async function memSave(input, ctx) {
202
203
  };
203
204
  }
204
205
  }
206
+ const haiveConfig = await loadConfig(ctx.paths);
207
+ const resolvedScope = input.scope !== "personal" ? input.scope : haiveConfig.defaultScope ?? "personal";
205
208
  const frontmatter = buildFrontmatter({
206
209
  type: input.type,
207
210
  slug: input.slug,
208
- scope: input.scope,
211
+ scope: resolvedScope,
209
212
  module: input.module,
210
213
  tags: input.tags,
211
214
  domain: input.domain,
@@ -213,7 +216,8 @@ async function memSave(input, ctx) {
213
216
  paths: input.paths,
214
217
  symbols: input.symbols,
215
218
  commit: input.commit,
216
- topic: input.topic
219
+ topic: input.topic,
220
+ status: haiveConfig.defaultStatus === "validated" ? "validated" : void 0
217
221
  });
218
222
  const file = memoryFilePath(
219
223
  ctx.paths,
@@ -1159,9 +1163,12 @@ import {
1159
1163
  isDecaying,
1160
1164
  literalMatchesAllTokens as literalMatchesAllTokens2,
1161
1165
  literalMatchesAnyToken as literalMatchesAnyToken2,
1166
+ loadCodeMap,
1167
+ loadConfig as loadConfig2,
1162
1168
  loadMemoriesFromDir as loadMemoriesFromDir13,
1163
1169
  loadUsageIndex as loadUsageIndex7,
1164
1170
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
1171
+ queryCodeMap,
1165
1172
  tokenizeQuery as tokenizeQuery2,
1166
1173
  trackReads as trackReads3,
1167
1174
  truncateToTokens
@@ -1185,6 +1192,9 @@ var GetBriefingInputSchema = {
1185
1192
  track: z17.boolean().default(true).describe("Increment read_count on returned memories"),
1186
1193
  format: z17.enum(["full", "compact"]).default("full").describe(
1187
1194
  "Output format: 'full' returns complete memory bodies; 'compact' returns id + 1-line summary only (call mem_get for details)."
1195
+ ),
1196
+ symbols: z17.array(z17.string()).default([]).describe(
1197
+ "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."
1188
1198
  )
1189
1199
  };
1190
1200
  async function getBriefing(input, ctx) {
@@ -1246,6 +1256,7 @@ async function getBriefing(input, ctx) {
1246
1256
  tags: fm.tags,
1247
1257
  status: fm.status,
1248
1258
  confidence: deriveConfidence4(fm, u),
1259
+ ...fm.status === "draft" || fm.status === "proposed" ? { unverified: true } : {},
1249
1260
  read_count: u.read_count,
1250
1261
  reasons: [reason],
1251
1262
  match_quality: matchQuality ?? "partial",
@@ -1311,17 +1322,55 @@ async function getBriefing(input, ctx) {
1311
1322
  }
1312
1323
  const projectContextRaw = input.include_project_context && existsSync17(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
1313
1324
  const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
1314
- const projectContext = isTemplateContext ? "" : projectContextRaw;
1315
1325
  const setupWarnings = [];
1316
- if (isTemplateContext) {
1317
- setupWarnings.push(
1318
- "project-context.md still contains the default template. Invoke the bootstrap_project MCP prompt to auto-fill it from your codebase. Until then, get_briefing returns no project context."
1319
- );
1320
- }
1321
- if (!existsSync17(ctx.paths.projectContext)) {
1322
- setupWarnings.push(
1323
- "No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
1324
- );
1326
+ let autoContextGenerated = false;
1327
+ let projectContext = isTemplateContext ? "" : projectContextRaw;
1328
+ if ((isTemplateContext || !existsSync17(ctx.paths.projectContext)) && input.include_project_context) {
1329
+ const haiveConfig = await loadConfig2(ctx.paths);
1330
+ if (haiveConfig.autoContext) {
1331
+ const codeMap = await loadCodeMap(ctx.paths);
1332
+ if (codeMap) {
1333
+ const totalFiles = Object.keys(codeMap.files).length;
1334
+ const extensions = /* @__PURE__ */ new Map();
1335
+ for (const filePath of Object.keys(codeMap.files)) {
1336
+ const ext = filePath.slice(filePath.lastIndexOf(".") + 1).toLowerCase();
1337
+ extensions.set(ext, (extensions.get(ext) ?? 0) + 1);
1338
+ }
1339
+ const topExts = [...extensions.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([e, n]) => `${e} (${n})`).join(", ");
1340
+ const topSymbols = Object.entries(codeMap.files).flatMap(
1341
+ ([fp, entry]) => entry.exports.slice(0, 3).map((e) => `${e.name} (${fp.split("/").slice(-2).join("/")})`)
1342
+ ).slice(0, 15).join(", ");
1343
+ projectContext = `# Project context (auto-generated by hAIve)
1344
+
1345
+ > \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.
1346
+
1347
+ ## Codebase overview
1348
+ - **${totalFiles} files** indexed in code-map
1349
+ - **Main file types:** ${topExts}
1350
+ - **Generated at:** ${codeMap.generated_at}
1351
+
1352
+ ## Key exports (sample)
1353
+ ` + topSymbols + "\n";
1354
+ autoContextGenerated = true;
1355
+ setupWarnings.push(
1356
+ "project-context.md is still the default template. A minimal auto-generated context has been injected from the code-map. Invoke bootstrap_project to replace it with a full AI-analyzed context."
1357
+ );
1358
+ } else {
1359
+ setupWarnings.push(
1360
+ "project-context.md is still the default template and no code-map found. Run `haive index code` then invoke bootstrap_project for a full context."
1361
+ );
1362
+ }
1363
+ } else {
1364
+ if (isTemplateContext) {
1365
+ setupWarnings.push(
1366
+ "project-context.md still contains the default template. Invoke the bootstrap_project MCP prompt to auto-fill it from your codebase. Until then, get_briefing returns no project context."
1367
+ );
1368
+ } else {
1369
+ setupWarnings.push(
1370
+ "No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
1371
+ );
1372
+ }
1373
+ }
1325
1374
  }
1326
1375
  const moduleContents = input.include_module_contexts ? await loadModuleContexts2(ctx, inferred) : [];
1327
1376
  const memoriesText = memories.map((m) => {
@@ -1384,18 +1433,51 @@ ${m.content}`).join("\n\n---\n\n"),
1384
1433
  if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
1385
1434
  }
1386
1435
  const outputMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : trimmedMemories;
1436
+ let symbolLocations;
1437
+ const symbolsToLookup = new Set(input.symbols);
1438
+ for (const m of outputMemories) {
1439
+ const loaded = byId.get(m.id);
1440
+ for (const sym of loaded?.memory.frontmatter.anchor.symbols ?? []) {
1441
+ symbolsToLookup.add(sym);
1442
+ }
1443
+ }
1444
+ if (symbolsToLookup.size > 0) {
1445
+ const codeMap = await loadCodeMap(ctx.paths);
1446
+ if (codeMap) {
1447
+ symbolLocations = [];
1448
+ for (const sym of symbolsToLookup) {
1449
+ const { files } = queryCodeMap(codeMap, { symbol: sym });
1450
+ if (files.length > 0) {
1451
+ symbolLocations.push({
1452
+ symbol: sym,
1453
+ locations: files.flatMap(
1454
+ (f) => f.entry.exports.filter((e) => e.name.toLowerCase().includes(sym.toLowerCase())).map((e) => ({
1455
+ file: f.path,
1456
+ kind: e.kind,
1457
+ line: e.line,
1458
+ ...e.description ? { description: e.description } : {}
1459
+ }))
1460
+ )
1461
+ });
1462
+ }
1463
+ }
1464
+ if (symbolLocations.length === 0) symbolLocations = void 0;
1465
+ }
1466
+ }
1387
1467
  return {
1388
1468
  ...input.task ? { task: input.task } : {},
1389
1469
  search_mode: searchMode,
1390
1470
  inferred_modules: inferred,
1391
1471
  ...lastSession ? { last_session: lastSession } : {},
1392
- project_context: projectContextRaw ? {
1472
+ project_context: projectContextRaw || autoContextGenerated ? {
1393
1473
  content: projectSlice.text,
1394
1474
  truncated: projectSlice.truncated,
1395
- ...isTemplateContext ? { is_template: true } : {}
1475
+ ...isTemplateContext && !autoContextGenerated ? { is_template: true } : {},
1476
+ ...autoContextGenerated ? { auto_generated: true } : {}
1396
1477
  } : null,
1397
1478
  module_contexts: trimmedModules,
1398
1479
  memories: outputMemories,
1480
+ ...symbolLocations ? { symbol_locations: symbolLocations } : {},
1399
1481
  decay_warnings: decayWarnings,
1400
1482
  setup_warnings: setupWarnings,
1401
1483
  estimated_tokens: totalTokens,
@@ -1445,7 +1527,7 @@ async function loadModuleContexts2(ctx, modules) {
1445
1527
  }
1446
1528
 
1447
1529
  // src/tools/code-map.ts
1448
- import { loadCodeMap, queryCodeMap } from "@hiveai/core";
1530
+ import { loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hiveai/core";
1449
1531
  import { z as z18 } from "zod";
1450
1532
  var CodeMapInputSchema = {
1451
1533
  file: z18.string().optional().describe("Filter to files whose path contains this substring"),
@@ -1453,7 +1535,7 @@ var CodeMapInputSchema = {
1453
1535
  max_files: z18.number().int().positive().default(40).describe("Cap on returned files")
1454
1536
  };
1455
1537
  async function codeMapTool(input, ctx) {
1456
- const map = await loadCodeMap(ctx.paths);
1538
+ const map = await loadCodeMap2(ctx.paths);
1457
1539
  if (!map) {
1458
1540
  return {
1459
1541
  available: false,
@@ -1461,7 +1543,7 @@ async function codeMapTool(input, ctx) {
1461
1543
  notice: "No code map found. Run `haive index code` to generate `.ai/code-map.json`."
1462
1544
  };
1463
1545
  }
1464
- const { files } = queryCodeMap(map, { file: input.file, symbol: input.symbol });
1546
+ const { files } = queryCodeMap2(map, { file: input.file, symbol: input.symbol });
1465
1547
  return {
1466
1548
  available: true,
1467
1549
  generated_at: map.generated_at,
@@ -1761,9 +1843,78 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
1761
1843
  };
1762
1844
  }
1763
1845
 
1846
+ // src/session-tracker.ts
1847
+ import { loadConfig as loadConfig3 } from "@hiveai/core";
1848
+ var SessionTracker = class {
1849
+ events = [];
1850
+ startedAt = (/* @__PURE__ */ new Date()).toISOString();
1851
+ config = null;
1852
+ ctx;
1853
+ shutdownRegistered = false;
1854
+ constructor(ctx) {
1855
+ this.ctx = ctx;
1856
+ }
1857
+ async init() {
1858
+ this.config = await loadConfig3(this.ctx.paths);
1859
+ if (this.config.autoSessionEnd) {
1860
+ this.registerShutdownHandler();
1861
+ }
1862
+ }
1863
+ record(tool, summary) {
1864
+ this.events.push({ tool, at: (/* @__PURE__ */ new Date()).toISOString(), summary });
1865
+ }
1866
+ registerShutdownHandler() {
1867
+ if (this.shutdownRegistered) return;
1868
+ this.shutdownRegistered = true;
1869
+ const save = async () => {
1870
+ const writingTools = this.events.filter(
1871
+ (e) => ["mem_save", "mem_tried", "mem_observe", "mem_update", "bootstrap_project_save"].includes(e.tool)
1872
+ );
1873
+ const totalCalls = this.events.length;
1874
+ if (totalCalls === 0) return;
1875
+ const toolSummary = summarizeTools(this.events);
1876
+ const filesSet = /* @__PURE__ */ new Set();
1877
+ for (const e of this.events) {
1878
+ if (e.summary) {
1879
+ const matches = e.summary.match(/[^\s"',]+\.[a-zA-Z]{1,6}/g) ?? [];
1880
+ for (const m of matches) filesSet.add(m);
1881
+ }
1882
+ }
1883
+ try {
1884
+ await memSessionEnd(
1885
+ {
1886
+ goal: `Auto-captured session (${totalCalls} tool call${totalCalls === 1 ? "" : "s"})`,
1887
+ accomplished: toolSummary,
1888
+ discoveries: writingTools.length > 0 ? `${writingTools.length} memor${writingTools.length === 1 ? "y" : "ies"} saved during this session.` : "No new memories saved this session.",
1889
+ files_touched: [...filesSet].slice(0, 10),
1890
+ next_steps: "",
1891
+ scope: this.config?.defaultScope ?? "personal",
1892
+ module: void 0
1893
+ },
1894
+ this.ctx
1895
+ );
1896
+ } catch {
1897
+ }
1898
+ };
1899
+ process.once("SIGTERM", () => {
1900
+ void save().finally(() => process.exit(0));
1901
+ });
1902
+ process.once("SIGINT", () => {
1903
+ void save().finally(() => process.exit(0));
1904
+ });
1905
+ }
1906
+ };
1907
+ function summarizeTools(events) {
1908
+ const counts = /* @__PURE__ */ new Map();
1909
+ for (const e of events) {
1910
+ counts.set(e.tool, (counts.get(e.tool) ?? 0) + 1);
1911
+ }
1912
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
1913
+ }
1914
+
1764
1915
  // src/server.ts
1765
1916
  var SERVER_NAME = "haive";
1766
- var SERVER_VERSION = "0.2.16";
1917
+ var SERVER_VERSION = "0.3.2";
1767
1918
  function jsonResult(data) {
1768
1919
  return {
1769
1920
  content: [
@@ -1776,6 +1927,8 @@ function jsonResult(data) {
1776
1927
  }
1777
1928
  function createHaiveServer(options = {}) {
1778
1929
  const context = createContext(options);
1930
+ const tracker = new SessionTracker(context);
1931
+ void tracker.init();
1779
1932
  const server = new McpServer(
1780
1933
  { name: SERVER_NAME, version: SERVER_VERSION },
1781
1934
  { capabilities: { tools: {}, prompts: {} } }
@@ -1784,7 +1937,10 @@ function createHaiveServer(options = {}) {
1784
1937
  "mem_save",
1785
1938
  "Save a new memory (convention, decision, gotcha, architecture, glossary). For failed approaches use mem_tried instead \u2014 it enforces a structured format that is more useful to future agents. Use scope=team to share with the whole team.",
1786
1939
  MemSaveInputSchema,
1787
- async (input) => jsonResult(await memSave(input, context))
1940
+ async (input) => {
1941
+ tracker.record("mem_save", input.slug);
1942
+ return jsonResult(await memSave(input, context));
1943
+ }
1788
1944
  );
1789
1945
  server.tool(
1790
1946
  "mem_search",
@@ -1808,7 +1964,10 @@ function createHaiveServer(options = {}) {
1808
1964
  "get_briefing",
1809
1965
  "One-shot onboarding: returns project context + module contexts + ranked relevant memories under a token budget. Replaces 4\u20135 separate calls when an agent starts a task.",
1810
1966
  GetBriefingInputSchema,
1811
- async (input) => jsonResult(await getBriefing(input, context))
1967
+ async (input) => {
1968
+ tracker.record("get_briefing", input.task ?? "");
1969
+ return jsonResult(await getBriefing(input, context));
1970
+ }
1812
1971
  );
1813
1972
  server.tool(
1814
1973
  "code_map",
@@ -1874,7 +2033,10 @@ function createHaiveServer(options = {}) {
1874
2033
  "mem_tried",
1875
2034
  "Preferred way to record a failed approach. Enforces a structured what/why_failed/instead format that is immediately actionable for future agents. Auto-validated (no approval cycle). Use whenever you tried an approach and it failed \u2014 prevents the same mistake from happening in the next session.",
1876
2035
  MemTriedInputSchema,
1877
- async (input) => jsonResult(await memTried(input, context))
2036
+ async (input) => {
2037
+ tracker.record("mem_tried", input.what.slice(0, 80));
2038
+ return jsonResult(await memTried(input, context));
2039
+ }
1878
2040
  );
1879
2041
  server.tool(
1880
2042
  "mem_diff",
@@ -1886,13 +2048,19 @@ function createHaiveServer(options = {}) {
1886
2048
  "mem_observe",
1887
2049
  "Capture a code-level discovery made during exploration: a bug, inconsistency, missing config, or security gap found by reading existing code that was NOT in the briefing. Use this whenever you read code and spot something that could silently break in production. Auto-validated, anchored to file paths for staleness detection. Prefer this over mem_save for reactive discoveries during code reading.",
1888
2050
  MemObserveInputSchema,
1889
- async (input) => jsonResult(await memObserve(input, context))
2051
+ async (input) => {
2052
+ tracker.record("mem_observe", input.where);
2053
+ return jsonResult(await memObserve(input, context));
2054
+ }
1890
2055
  );
1891
2056
  server.tool(
1892
2057
  "mem_session_end",
1893
2058
  "Save a structured end-of-session recap (goal / accomplished / discoveries / next steps). Uses topic-upsert: one recap per scope is kept and updated in-place so the next session always has fresh context. Call this before closing every significant session. get_briefing automatically surfaces the latest recap at the top of the next session's briefing.",
1894
2059
  MemSessionEndInputSchema,
1895
- async (input) => jsonResult(await memSessionEnd(input, context))
2060
+ async (input) => {
2061
+ tracker.record("mem_session_end", input.goal.slice(0, 80));
2062
+ return jsonResult(await memSessionEnd(input, context));
2063
+ }
1896
2064
  );
1897
2065
  server.prompt(
1898
2066
  "bootstrap_project",
@@ -1912,7 +2080,7 @@ function createHaiveServer(options = {}) {
1912
2080
  ImportDocsArgsSchema,
1913
2081
  (args) => importDocsPrompt(args, context)
1914
2082
  );
1915
- return { server, context };
2083
+ return { server, context, tracker };
1916
2084
  }
1917
2085
  export {
1918
2086
  SERVER_NAME,