@hiveai/mcp 0.3.0 → 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/README.md CHANGED
@@ -99,6 +99,7 @@ One-shot onboarding: returns project context + module contexts + ranked relevant
99
99
  {
100
100
  "task": "add a Stripe payment integration",
101
101
  "files": ["src/payments/PaymentService.ts"],
102
+ "symbols": ["PaymentService", "TenantFilter"],
102
103
  "max_tokens": 8000,
103
104
  "max_memories": 8,
104
105
  "format": "full",
@@ -112,6 +113,7 @@ One-shot onboarding: returns project context + module contexts + ranked relevant
112
113
  |---|---|---|
113
114
  | `task` | — | What you're about to do. Used to rank memories by relevance. |
114
115
  | `files` | `[]` | Files you're editing. Surfaces memories anchored to these files. |
116
+ | `symbols` | `[]` | Symbol names to look up in the code-map (e.g. `["PaymentService"]`). Returns file + line + kind without grepping. Requires `haive index code`. |
115
117
  | `max_tokens` | `8000` | Token budget for the entire response. Sections are truncated to fit. |
116
118
  | `max_memories` | `8` | Max memories to include. |
117
119
  | `format` | `"full"` | `"full"` = complete bodies · `"compact"` = 1-line summaries (call `mem_get` for details) |
@@ -120,10 +122,13 @@ One-shot onboarding: returns project context + module contexts + ranked relevant
120
122
  | `track` | `true` | Increment read_count for returned memories. |
121
123
 
122
124
  **Response includes:**
123
- - `project_context` — the contents of `.ai/project-context.md`
125
+ - `last_session` — most recent `haive session end` recap (surfaced first so agents start with fresh context)
126
+ - `project_context` — `.ai/project-context.md` (suppressed if still template — `is_template: true`)
124
127
  - `module_contexts` — relevant `.ai/modules/<name>/context.md` files
125
- - `memories` — ranked list of relevant memories with body, confidence, and match reason
126
- - `decay_warnings` — memory IDs not read in >90 days (review or deprecate)
128
+ - `memories` — ranked memories with `confidence`, `unverified` flag (for draft/proposed), and `match reason`
129
+ - `symbol_locations` — file:line:kind results for each requested symbol (from code-map)
130
+ - `decay_warnings` — memory IDs not read in >90 days
131
+ - `setup_warnings` — actionable warnings (e.g. template project-context, missing init)
127
132
  - `search_mode` — `"semantic"` | `"literal_fallback"` | `"literal"`
128
133
 
129
134
  ---
@@ -149,12 +154,15 @@ Save a new memory. For failed approaches, use `mem_tried` instead — it enforce
149
154
  | `slug` | ✅ | Short kebab-case identifier |
150
155
  | `scope` | — | `personal` (default) · `team` · `module` |
151
156
  | `body` | ✅ | Markdown content |
152
- | `paths` | — | File paths to anchor to (enables staleness detection) |
157
+ | `paths` | — | File paths to anchor to (enables staleness detection). **Warning returned if path doesn't exist in project.** |
153
158
  | `symbols` | — | Function/class names to anchor to |
154
159
  | `tags` | — | Tags for filtering |
160
+ | `topic` | — | Stable key for upsert: if a memory with this `topic`+`scope` already exists, it is updated in-place (`revision_count++`) |
155
161
  | `domain` | — | Business domain (e.g. `payments`) |
156
162
  | `author` | — | Author handle |
157
163
 
164
+ **Deduplication:** identical body content (same SHA-256 hash) within the same scope is rejected with an error. Use `mem_update` to modify it instead.
165
+
158
166
  ---
159
167
 
160
168
  ### `mem_tried` ⭐ Record failures immediately
@@ -263,6 +271,38 @@ Compare two memories side-by-side: shows frontmatter fields that differ and line
263
271
 
264
272
  ---
265
273
 
274
+ ### `mem_session_end`
275
+
276
+ Save a structured end-of-session recap. Topic-upsert: one recap per scope is kept and updated with `revision_count++`. Automatically surfaced at the top of the next `get_briefing`.
277
+
278
+ ```json
279
+ {
280
+ "goal": "Add Stripe payment integration",
281
+ "accomplished": "PaymentService done, tests passing, deployed to staging",
282
+ "discoveries": "Webhook signature requires raw body, not parsed JSON",
283
+ "files_touched": ["src/payments/PaymentService.ts", "src/payments/webhook.ts"],
284
+ "next_steps": "Add retry logic for failed webhooks",
285
+ "scope": "team"
286
+ }
287
+ ```
288
+
289
+ ---
290
+
291
+ ### `mem_observe`
292
+
293
+ Capture a code-level discovery in structured form (found-while, not a convention or decision).
294
+
295
+ ```json
296
+ {
297
+ "file": "src/payments/PaymentService.ts",
298
+ "symbol": "processPayment",
299
+ "observation": "This method calls the external API synchronously — any timeout blocks the entire request thread.",
300
+ "scope": "team"
301
+ }
302
+ ```
303
+
304
+ ---
305
+
266
306
  ### `mem_approve` / `mem_reject` / `mem_pending` / `mem_delete`
267
307
 
268
308
  Lifecycle operations:
package/dist/index.js CHANGED
@@ -130,6 +130,7 @@ import { existsSync as existsSync4 } from "fs";
130
130
  import path3 from "path";
131
131
  import {
132
132
  buildFrontmatter,
133
+ loadConfig,
133
134
  loadMemoriesFromDir as loadMemoriesFromDir2,
134
135
  memoryFilePath,
135
136
  serializeMemory
@@ -207,10 +208,12 @@ async function memSave(input, ctx) {
207
208
  };
208
209
  }
209
210
  }
211
+ const haiveConfig = await loadConfig(ctx.paths);
212
+ const resolvedScope = input.scope !== "personal" ? input.scope : haiveConfig.defaultScope ?? "personal";
210
213
  const frontmatter = buildFrontmatter({
211
214
  type: input.type,
212
215
  slug: input.slug,
213
- scope: input.scope,
216
+ scope: resolvedScope,
214
217
  module: input.module,
215
218
  tags: input.tags,
216
219
  domain: input.domain,
@@ -218,7 +221,8 @@ async function memSave(input, ctx) {
218
221
  paths: input.paths,
219
222
  symbols: input.symbols,
220
223
  commit: input.commit,
221
- topic: input.topic
224
+ topic: input.topic,
225
+ status: haiveConfig.defaultStatus === "validated" ? "validated" : void 0
222
226
  });
223
227
  const file = memoryFilePath(
224
228
  ctx.paths,
@@ -1165,6 +1169,7 @@ import {
1165
1169
  literalMatchesAllTokens as literalMatchesAllTokens2,
1166
1170
  literalMatchesAnyToken as literalMatchesAnyToken2,
1167
1171
  loadCodeMap,
1172
+ loadConfig as loadConfig2,
1168
1173
  loadMemoriesFromDir as loadMemoriesFromDir13,
1169
1174
  loadUsageIndex as loadUsageIndex7,
1170
1175
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
@@ -1322,17 +1327,55 @@ async function getBriefing(input, ctx) {
1322
1327
  }
1323
1328
  const projectContextRaw = input.include_project_context && existsSync17(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
1324
1329
  const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
1325
- const projectContext = isTemplateContext ? "" : projectContextRaw;
1326
1330
  const setupWarnings = [];
1327
- if (isTemplateContext) {
1328
- setupWarnings.push(
1329
- "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."
1330
- );
1331
- }
1332
- if (!existsSync17(ctx.paths.projectContext)) {
1333
- setupWarnings.push(
1334
- "No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
1335
- );
1331
+ let autoContextGenerated = false;
1332
+ let projectContext = isTemplateContext ? "" : projectContextRaw;
1333
+ if ((isTemplateContext || !existsSync17(ctx.paths.projectContext)) && input.include_project_context) {
1334
+ const haiveConfig = await loadConfig2(ctx.paths);
1335
+ if (haiveConfig.autoContext) {
1336
+ const codeMap = await loadCodeMap(ctx.paths);
1337
+ if (codeMap) {
1338
+ const totalFiles = Object.keys(codeMap.files).length;
1339
+ const extensions = /* @__PURE__ */ new Map();
1340
+ for (const filePath of Object.keys(codeMap.files)) {
1341
+ const ext = filePath.slice(filePath.lastIndexOf(".") + 1).toLowerCase();
1342
+ extensions.set(ext, (extensions.get(ext) ?? 0) + 1);
1343
+ }
1344
+ const topExts = [...extensions.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([e, n]) => `${e} (${n})`).join(", ");
1345
+ const topSymbols = Object.entries(codeMap.files).flatMap(
1346
+ ([fp, entry]) => entry.exports.slice(0, 3).map((e) => `${e.name} (${fp.split("/").slice(-2).join("/")})`)
1347
+ ).slice(0, 15).join(", ");
1348
+ projectContext = `# Project context (auto-generated by hAIve)
1349
+
1350
+ > \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.
1351
+
1352
+ ## Codebase overview
1353
+ - **${totalFiles} files** indexed in code-map
1354
+ - **Main file types:** ${topExts}
1355
+ - **Generated at:** ${codeMap.generated_at}
1356
+
1357
+ ## Key exports (sample)
1358
+ ` + topSymbols + "\n";
1359
+ autoContextGenerated = true;
1360
+ setupWarnings.push(
1361
+ "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."
1362
+ );
1363
+ } else {
1364
+ setupWarnings.push(
1365
+ "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."
1366
+ );
1367
+ }
1368
+ } else {
1369
+ if (isTemplateContext) {
1370
+ setupWarnings.push(
1371
+ "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."
1372
+ );
1373
+ } else {
1374
+ setupWarnings.push(
1375
+ "No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
1376
+ );
1377
+ }
1378
+ }
1336
1379
  }
1337
1380
  const moduleContents = input.include_module_contexts ? await loadModuleContexts2(ctx, inferred) : [];
1338
1381
  const memoriesText = memories.map((m) => {
@@ -1431,10 +1474,11 @@ ${m.content}`).join("\n\n---\n\n"),
1431
1474
  search_mode: searchMode,
1432
1475
  inferred_modules: inferred,
1433
1476
  ...lastSession ? { last_session: lastSession } : {},
1434
- project_context: projectContextRaw ? {
1477
+ project_context: projectContextRaw || autoContextGenerated ? {
1435
1478
  content: projectSlice.text,
1436
1479
  truncated: projectSlice.truncated,
1437
- ...isTemplateContext ? { is_template: true } : {}
1480
+ ...isTemplateContext && !autoContextGenerated ? { is_template: true } : {},
1481
+ ...autoContextGenerated ? { auto_generated: true } : {}
1438
1482
  } : null,
1439
1483
  module_contexts: trimmedModules,
1440
1484
  memories: outputMemories,
@@ -1804,9 +1848,78 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
1804
1848
  };
1805
1849
  }
1806
1850
 
1851
+ // src/session-tracker.ts
1852
+ import { loadConfig as loadConfig3 } from "@hiveai/core";
1853
+ var SessionTracker = class {
1854
+ events = [];
1855
+ startedAt = (/* @__PURE__ */ new Date()).toISOString();
1856
+ config = null;
1857
+ ctx;
1858
+ shutdownRegistered = false;
1859
+ constructor(ctx) {
1860
+ this.ctx = ctx;
1861
+ }
1862
+ async init() {
1863
+ this.config = await loadConfig3(this.ctx.paths);
1864
+ if (this.config.autoSessionEnd) {
1865
+ this.registerShutdownHandler();
1866
+ }
1867
+ }
1868
+ record(tool, summary) {
1869
+ this.events.push({ tool, at: (/* @__PURE__ */ new Date()).toISOString(), summary });
1870
+ }
1871
+ registerShutdownHandler() {
1872
+ if (this.shutdownRegistered) return;
1873
+ this.shutdownRegistered = true;
1874
+ const save = async () => {
1875
+ const writingTools = this.events.filter(
1876
+ (e) => ["mem_save", "mem_tried", "mem_observe", "mem_update", "bootstrap_project_save"].includes(e.tool)
1877
+ );
1878
+ const totalCalls = this.events.length;
1879
+ if (totalCalls === 0) return;
1880
+ const toolSummary = summarizeTools(this.events);
1881
+ const filesSet = /* @__PURE__ */ new Set();
1882
+ for (const e of this.events) {
1883
+ if (e.summary) {
1884
+ const matches = e.summary.match(/[^\s"',]+\.[a-zA-Z]{1,6}/g) ?? [];
1885
+ for (const m of matches) filesSet.add(m);
1886
+ }
1887
+ }
1888
+ try {
1889
+ await memSessionEnd(
1890
+ {
1891
+ goal: `Auto-captured session (${totalCalls} tool call${totalCalls === 1 ? "" : "s"})`,
1892
+ accomplished: toolSummary,
1893
+ discoveries: writingTools.length > 0 ? `${writingTools.length} memor${writingTools.length === 1 ? "y" : "ies"} saved during this session.` : "No new memories saved this session.",
1894
+ files_touched: [...filesSet].slice(0, 10),
1895
+ next_steps: "",
1896
+ scope: this.config?.defaultScope ?? "personal",
1897
+ module: void 0
1898
+ },
1899
+ this.ctx
1900
+ );
1901
+ } catch {
1902
+ }
1903
+ };
1904
+ process.once("SIGTERM", () => {
1905
+ void save().finally(() => process.exit(0));
1906
+ });
1907
+ process.once("SIGINT", () => {
1908
+ void save().finally(() => process.exit(0));
1909
+ });
1910
+ }
1911
+ };
1912
+ function summarizeTools(events) {
1913
+ const counts = /* @__PURE__ */ new Map();
1914
+ for (const e of events) {
1915
+ counts.set(e.tool, (counts.get(e.tool) ?? 0) + 1);
1916
+ }
1917
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
1918
+ }
1919
+
1807
1920
  // src/server.ts
1808
1921
  var SERVER_NAME = "haive";
1809
- var SERVER_VERSION = "0.3.0";
1922
+ var SERVER_VERSION = "0.3.2";
1810
1923
  function jsonResult(data) {
1811
1924
  return {
1812
1925
  content: [
@@ -1819,6 +1932,8 @@ function jsonResult(data) {
1819
1932
  }
1820
1933
  function createHaiveServer(options = {}) {
1821
1934
  const context = createContext(options);
1935
+ const tracker = new SessionTracker(context);
1936
+ void tracker.init();
1822
1937
  const server = new McpServer(
1823
1938
  { name: SERVER_NAME, version: SERVER_VERSION },
1824
1939
  { capabilities: { tools: {}, prompts: {} } }
@@ -1827,7 +1942,10 @@ function createHaiveServer(options = {}) {
1827
1942
  "mem_save",
1828
1943
  "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.",
1829
1944
  MemSaveInputSchema,
1830
- async (input) => jsonResult(await memSave(input, context))
1945
+ async (input) => {
1946
+ tracker.record("mem_save", input.slug);
1947
+ return jsonResult(await memSave(input, context));
1948
+ }
1831
1949
  );
1832
1950
  server.tool(
1833
1951
  "mem_search",
@@ -1851,7 +1969,10 @@ function createHaiveServer(options = {}) {
1851
1969
  "get_briefing",
1852
1970
  "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.",
1853
1971
  GetBriefingInputSchema,
1854
- async (input) => jsonResult(await getBriefing(input, context))
1972
+ async (input) => {
1973
+ tracker.record("get_briefing", input.task ?? "");
1974
+ return jsonResult(await getBriefing(input, context));
1975
+ }
1855
1976
  );
1856
1977
  server.tool(
1857
1978
  "code_map",
@@ -1917,7 +2038,10 @@ function createHaiveServer(options = {}) {
1917
2038
  "mem_tried",
1918
2039
  "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.",
1919
2040
  MemTriedInputSchema,
1920
- async (input) => jsonResult(await memTried(input, context))
2041
+ async (input) => {
2042
+ tracker.record("mem_tried", input.what.slice(0, 80));
2043
+ return jsonResult(await memTried(input, context));
2044
+ }
1921
2045
  );
1922
2046
  server.tool(
1923
2047
  "mem_diff",
@@ -1929,13 +2053,19 @@ function createHaiveServer(options = {}) {
1929
2053
  "mem_observe",
1930
2054
  "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.",
1931
2055
  MemObserveInputSchema,
1932
- async (input) => jsonResult(await memObserve(input, context))
2056
+ async (input) => {
2057
+ tracker.record("mem_observe", input.where);
2058
+ return jsonResult(await memObserve(input, context));
2059
+ }
1933
2060
  );
1934
2061
  server.tool(
1935
2062
  "mem_session_end",
1936
2063
  "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.",
1937
2064
  MemSessionEndInputSchema,
1938
- async (input) => jsonResult(await memSessionEnd(input, context))
2065
+ async (input) => {
2066
+ tracker.record("mem_session_end", input.goal.slice(0, 80));
2067
+ return jsonResult(await memSessionEnd(input, context));
2068
+ }
1939
2069
  );
1940
2070
  server.prompt(
1941
2071
  "bootstrap_project",
@@ -1955,7 +2085,7 @@ function createHaiveServer(options = {}) {
1955
2085
  ImportDocsArgsSchema,
1956
2086
  (args) => importDocsPrompt(args, context)
1957
2087
  );
1958
- return { server, context };
2088
+ return { server, context, tracker };
1959
2089
  }
1960
2090
 
1961
2091
  // src/index.ts