@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 +44 -4
- package/dist/index.js +151 -21
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +13 -0
- package/dist/server.js +151 -21
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
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
|
-
- `
|
|
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
|
|
126
|
-
- `
|
|
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:
|
|
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
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
);
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
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.
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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
|