@hiveai/mcp 0.9.3 → 0.9.5
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/index.js +203 -16
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +59 -2
- package/dist/server.js +207 -16
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -321,6 +321,7 @@ import {
|
|
|
321
321
|
loadMemoriesFromDir as loadMemoriesFromDir3,
|
|
322
322
|
loadUsageIndex,
|
|
323
323
|
pickSnippetNeedle,
|
|
324
|
+
rankMemoriesLexical,
|
|
324
325
|
tokenizeQuery,
|
|
325
326
|
trackReads
|
|
326
327
|
} from "@hiveai/core";
|
|
@@ -337,6 +338,9 @@ var MemSearchInputSchema = {
|
|
|
337
338
|
semantic: z5.boolean().default(false).describe(
|
|
338
339
|
"Use semantic similarity from the embeddings index (requires `haive embeddings index`)."
|
|
339
340
|
),
|
|
341
|
+
lexical_rank: z5.boolean().default(false).describe(
|
|
342
|
+
"When true (and semantic is false), rank the filtered corpus with Okapi-BM25-style lexical scoring instead of literal AND/OR. Helps phrase-like queries without embeddings."
|
|
343
|
+
),
|
|
340
344
|
min_score: z5.number().min(0).max(1).default(0).describe("Minimum cosine similarity (semantic mode only)"),
|
|
341
345
|
track: z5.boolean().default(true).describe("Increment read_count on returned memories (used for passive validation)")
|
|
342
346
|
};
|
|
@@ -362,6 +366,27 @@ async function memSearch(input, ctx) {
|
|
|
362
366
|
notice: "Semantic search unavailable (embeddings index missing or @hiveai/embeddings not installed). Falling back to literal search."
|
|
363
367
|
};
|
|
364
368
|
}
|
|
369
|
+
} else if (input.lexical_rank && input.query.trim()) {
|
|
370
|
+
const { ranked, scores } = rankMemoriesLexical(
|
|
371
|
+
filtered,
|
|
372
|
+
input.query,
|
|
373
|
+
input.limit
|
|
374
|
+
);
|
|
375
|
+
if (ranked.length > 0) {
|
|
376
|
+
const snippetNeedle = pickSnippetNeedle(input.query);
|
|
377
|
+
result = {
|
|
378
|
+
matches: ranked.map(
|
|
379
|
+
(loaded, i) => lexicalHit(loaded, snippetNeedle, usage, scores[i])
|
|
380
|
+
),
|
|
381
|
+
total: ranked.length,
|
|
382
|
+
mode: "lexical_ranked"
|
|
383
|
+
};
|
|
384
|
+
} else {
|
|
385
|
+
result = {
|
|
386
|
+
...buildLiteralResult(input, filtered, usage),
|
|
387
|
+
notice: "Lexical ranking found no BM25-positive hits \u2014 showing literal matches instead."
|
|
388
|
+
};
|
|
389
|
+
}
|
|
365
390
|
} else {
|
|
366
391
|
result = buildLiteralResult(input, filtered, usage);
|
|
367
392
|
}
|
|
@@ -459,6 +484,9 @@ function toHit(loaded, needle, usage) {
|
|
|
459
484
|
file_path: loaded.filePath
|
|
460
485
|
};
|
|
461
486
|
}
|
|
487
|
+
function lexicalHit(loaded, needle, usage, lexicalScore) {
|
|
488
|
+
return { ...toHit(loaded, needle, usage), score: lexicalScore };
|
|
489
|
+
}
|
|
462
490
|
|
|
463
491
|
// src/tools/mem-verify.ts
|
|
464
492
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
@@ -3040,13 +3068,96 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
3040
3068
|
}
|
|
3041
3069
|
}
|
|
3042
3070
|
|
|
3043
|
-
// src/
|
|
3071
|
+
// src/tools/mem-conflict-candidates.ts
|
|
3072
|
+
import { existsSync as existsSync27 } from "fs";
|
|
3073
|
+
import { findLexicalConflictPairs, loadMemoriesFromDir as loadMemoriesFromDir21 } from "@hiveai/core";
|
|
3044
3074
|
import { z as z30 } from "zod";
|
|
3075
|
+
var MemConflictCandidatesInputSchema = {
|
|
3076
|
+
since_days: z30.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
3077
|
+
types: z30.array(z30.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
3078
|
+
min_jaccard: z30.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
|
|
3079
|
+
max_pairs: z30.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
|
|
3080
|
+
max_scan: z30.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort.")
|
|
3081
|
+
};
|
|
3082
|
+
async function memConflictCandidates(input, ctx) {
|
|
3083
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
3084
|
+
return {
|
|
3085
|
+
pairs: [],
|
|
3086
|
+
scanned: 0,
|
|
3087
|
+
truncated: false,
|
|
3088
|
+
notice: "No .ai/memories directory."
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
3092
|
+
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
3093
|
+
sinceDays: input.since_days,
|
|
3094
|
+
types: input.types,
|
|
3095
|
+
minJaccard: input.min_jaccard,
|
|
3096
|
+
maxPairs: input.max_pairs,
|
|
3097
|
+
maxScan: input.max_scan
|
|
3098
|
+
});
|
|
3099
|
+
const notice = pairs.length === 0 ? "No lexical candidate pairs \u2265 threshold \u2014 try lowering min_jaccard or widen since_days/types." : void 0;
|
|
3100
|
+
return { pairs, scanned, truncated, notice };
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
// src/tools/mem-resolve-project.ts
|
|
3104
|
+
import { resolveProjectInfo } from "@hiveai/core";
|
|
3105
|
+
import { z as z31 } from "zod";
|
|
3106
|
+
var MemResolveProjectInputSchema = {
|
|
3107
|
+
cwd: z31.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
3108
|
+
};
|
|
3109
|
+
async function memResolveProject(input, _ctx) {
|
|
3110
|
+
void _ctx;
|
|
3111
|
+
return {
|
|
3112
|
+
ok: true,
|
|
3113
|
+
info: resolveProjectInfo({
|
|
3114
|
+
cwd: input.cwd
|
|
3115
|
+
})
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// src/tools/mem-suggest-topic.ts
|
|
3120
|
+
import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
|
|
3121
|
+
import { z as z32 } from "zod";
|
|
3122
|
+
var MemSuggestTopicInputSchema = {
|
|
3123
|
+
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
3124
|
+
title: z32.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
3125
|
+
};
|
|
3126
|
+
async function memSuggestTopic(input, _ctx) {
|
|
3127
|
+
void _ctx;
|
|
3128
|
+
const suggestion = suggestTopicKey(input.type, input.title);
|
|
3129
|
+
return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// src/tools/mem-timeline.ts
|
|
3133
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3134
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hiveai/core";
|
|
3135
|
+
import { z as z33 } from "zod";
|
|
3136
|
+
var MemTimelineInputSchema = {
|
|
3137
|
+
memory_id: z33.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
|
|
3138
|
+
topic: z33.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
|
|
3139
|
+
limit: z33.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
3140
|
+
};
|
|
3141
|
+
async function memTimeline(input, ctx) {
|
|
3142
|
+
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
3143
|
+
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
3144
|
+
}
|
|
3145
|
+
const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
|
|
3146
|
+
const { entries, notice } = collectTimelineEntries(all, {
|
|
3147
|
+
memoryId: input.memory_id,
|
|
3148
|
+
topic: input.topic,
|
|
3149
|
+
limit: input.limit
|
|
3150
|
+
});
|
|
3151
|
+
return { entries, total: entries.length, notice };
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// src/prompts/bootstrap-project.ts
|
|
3155
|
+
import { z as z34 } from "zod";
|
|
3045
3156
|
var BootstrapProjectArgsSchema = {
|
|
3046
|
-
module:
|
|
3157
|
+
module: z34.string().optional().describe(
|
|
3047
3158
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
3048
3159
|
),
|
|
3049
|
-
focus:
|
|
3160
|
+
focus: z34.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
3050
3161
|
};
|
|
3051
3162
|
var ROOT_TEMPLATE = `# Project context
|
|
3052
3163
|
|
|
@@ -3128,10 +3239,10 @@ ${template}\`\`\`
|
|
|
3128
3239
|
}
|
|
3129
3240
|
|
|
3130
3241
|
// src/prompts/post-task.ts
|
|
3131
|
-
import { z as
|
|
3242
|
+
import { z as z35 } from "zod";
|
|
3132
3243
|
var PostTaskArgsSchema = {
|
|
3133
|
-
task_summary:
|
|
3134
|
-
files_touched:
|
|
3244
|
+
task_summary: z35.string().optional().describe("One sentence describing what you just did"),
|
|
3245
|
+
files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
|
|
3135
3246
|
};
|
|
3136
3247
|
function postTaskPrompt(args, ctx) {
|
|
3137
3248
|
const taskLine = args.task_summary ? `
|
|
@@ -3215,12 +3326,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
3215
3326
|
}
|
|
3216
3327
|
|
|
3217
3328
|
// src/prompts/import-docs.ts
|
|
3218
|
-
import { z as
|
|
3329
|
+
import { z as z36 } from "zod";
|
|
3219
3330
|
var ImportDocsArgsSchema = {
|
|
3220
|
-
content:
|
|
3221
|
-
source:
|
|
3222
|
-
scope:
|
|
3223
|
-
dry_run:
|
|
3331
|
+
content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
3332
|
+
source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
3333
|
+
scope: z36.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
3334
|
+
dry_run: z36.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
3224
3335
|
};
|
|
3225
3336
|
function importDocsPrompt(args, ctx) {
|
|
3226
3337
|
const sourceLine = args.source ? `
|
|
@@ -3285,7 +3396,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
3285
3396
|
|
|
3286
3397
|
// src/server.ts
|
|
3287
3398
|
var SERVER_NAME = "haive";
|
|
3288
|
-
var SERVER_VERSION = "0.9.
|
|
3399
|
+
var SERVER_VERSION = "0.9.5";
|
|
3289
3400
|
function jsonResult(data) {
|
|
3290
3401
|
return {
|
|
3291
3402
|
content: [
|
|
@@ -3336,6 +3447,23 @@ function createHaiveServer(options = {}) {
|
|
|
3336
3447
|
return jsonResult(await memSave(input, context));
|
|
3337
3448
|
}
|
|
3338
3449
|
);
|
|
3450
|
+
server.tool(
|
|
3451
|
+
"mem_suggest_topic",
|
|
3452
|
+
[
|
|
3453
|
+
"Propose a stable `topic` key (topic-upsert) from type + short title.",
|
|
3454
|
+
"",
|
|
3455
|
+
"USE BEFORE mem_save when you want deterministic updates to the same memory over time;",
|
|
3456
|
+
"families mimic Engram-style grouping (architecture/*, bug/*, decision/*, \u2026).",
|
|
3457
|
+
"",
|
|
3458
|
+
"PARAMETERS:",
|
|
3459
|
+
" type \u2014 convention | decision | gotcha | architecture | glossary | attempt | session_recap",
|
|
3460
|
+
" title \u2014 phrase to slugify under the suggested family prefix",
|
|
3461
|
+
"",
|
|
3462
|
+
"RETURNS: { topic_key, family, type }"
|
|
3463
|
+
].join("\n"),
|
|
3464
|
+
MemSuggestTopicInputSchema,
|
|
3465
|
+
async (input) => jsonResult(await memSuggestTopic(input, context))
|
|
3466
|
+
);
|
|
3339
3467
|
server.tool(
|
|
3340
3468
|
"mem_tried",
|
|
3341
3469
|
[
|
|
@@ -3427,8 +3555,12 @@ function createHaiveServer(options = {}) {
|
|
|
3427
3555
|
server.tool(
|
|
3428
3556
|
"get_briefing",
|
|
3429
3557
|
[
|
|
3430
|
-
"\u2B50
|
|
3431
|
-
"
|
|
3558
|
+
"\u2B50 DEFAULT-FIRST for coding agents on any repo where `haive init` ran: call this BEFORE",
|
|
3559
|
+
"changing source or project config for the current goal (unless the developer explicitly opts out).",
|
|
3560
|
+
"One-shot onboarding: everything relevant in a single call under a token budget.",
|
|
3561
|
+
"",
|
|
3562
|
+
"PROGRESSIVE DISCLOSURE \u2014 after this, drill down only if needed:",
|
|
3563
|
+
" mem_relevant_to / mem_search (compact lists) \u2192 mem_get (full body + anchors).",
|
|
3432
3564
|
"",
|
|
3433
3565
|
"RETURNS (in order of priority):",
|
|
3434
3566
|
" 0. action_required \u2014 \u26A0\uFE0F HANDLE THIS FIRST if non-empty (see protocol below)",
|
|
@@ -3463,7 +3595,7 @@ function createHaiveServer(options = {}) {
|
|
|
3463
3595
|
" low \u2014 proposed, few reads (take with caution)",
|
|
3464
3596
|
" unverified \u2014 draft (unverified: true flag set)",
|
|
3465
3597
|
"",
|
|
3466
|
-
"Replaces 4\u20135 separate tool calls.
|
|
3598
|
+
"Replaces 4\u20135 separate tool calls. Prefer this first; use mem_search / mem_get only for follow-up."
|
|
3467
3599
|
].join("\n"),
|
|
3468
3600
|
GetBriefingInputSchema,
|
|
3469
3601
|
async (input) => {
|
|
@@ -3482,6 +3614,8 @@ function createHaiveServer(options = {}) {
|
|
|
3482
3614
|
"SEARCH MODES:",
|
|
3483
3615
|
" Literal (default): AND search across id, tags, and body \u2014 all tokens must match.",
|
|
3484
3616
|
" Falls back to OR automatically if no AND results (partial match).",
|
|
3617
|
+
" Lexical rank (lexical_rank: true, semantic: false): Okapi-BM25-style scoring on the",
|
|
3618
|
+
" filtered corpus \u2014 good for phrase-like queries without embeddings.",
|
|
3485
3619
|
" Semantic (semantic: true): embedding-based similarity \u2014 finds related memories",
|
|
3486
3620
|
" even with different wording. Requires haive embeddings index to be built.",
|
|
3487
3621
|
"",
|
|
@@ -3490,6 +3624,7 @@ function createHaiveServer(options = {}) {
|
|
|
3490
3624
|
" scope \u2014 filter by personal | team | module",
|
|
3491
3625
|
" type \u2014 filter by convention | decision | gotcha | architecture | glossary",
|
|
3492
3626
|
" semantic \u2014 true for embedding-based search (requires @hiveai/embeddings)",
|
|
3627
|
+
" lexical_rank \u2014 BM25-style ranking (ignored when semantic is true)",
|
|
3493
3628
|
" limit \u2014 max results (default 10)",
|
|
3494
3629
|
"",
|
|
3495
3630
|
"RETURNS: array of { id, type, scope, status, confidence, body, match_quality }"
|
|
@@ -3500,6 +3635,22 @@ function createHaiveServer(options = {}) {
|
|
|
3500
3635
|
return jsonResult(await memSearch(input, context));
|
|
3501
3636
|
}
|
|
3502
3637
|
);
|
|
3638
|
+
server.tool(
|
|
3639
|
+
"mem_timeline",
|
|
3640
|
+
[
|
|
3641
|
+
"Chronological view of related memories: by shared frontmatter.topic OR expanded from a seed id",
|
|
3642
|
+
"(related_ids, same topic, overlapping anchor paths \u2014 one extra hop on related_ids).",
|
|
3643
|
+
"",
|
|
3644
|
+
"PARAMETERS:",
|
|
3645
|
+
" memory_id \u2014 optional seed memory id",
|
|
3646
|
+
" topic \u2014 optional topic key (required if memory_id omitted)",
|
|
3647
|
+
" limit \u2014 max entries (default 30)",
|
|
3648
|
+
"",
|
|
3649
|
+
"RETURNS: { entries: [{ id, type, scope, created_at, one_line, topic? }], total, notice? }"
|
|
3650
|
+
].join("\n"),
|
|
3651
|
+
MemTimelineInputSchema,
|
|
3652
|
+
async (input) => jsonResult(await memTimeline(input, context))
|
|
3653
|
+
);
|
|
3503
3654
|
server.tool(
|
|
3504
3655
|
"mem_for_files",
|
|
3505
3656
|
[
|
|
@@ -3529,7 +3680,7 @@ function createHaiveServer(options = {}) {
|
|
|
3529
3680
|
[
|
|
3530
3681
|
"Fetch a single memory by its full id with all details.",
|
|
3531
3682
|
"",
|
|
3532
|
-
"USE WHEN get_briefing returned a
|
|
3683
|
+
"USE WHEN get_briefing / mem_relevant_to / mem_search returned a compact hit and you need",
|
|
3533
3684
|
"the full body, or when you know the exact id of a memory.",
|
|
3534
3685
|
"",
|
|
3535
3686
|
"PARAMETERS:",
|
|
@@ -3617,6 +3768,22 @@ function createHaiveServer(options = {}) {
|
|
|
3617
3768
|
CodeMapInputSchema,
|
|
3618
3769
|
async (input) => jsonResult(await codeMapTool(input, context))
|
|
3619
3770
|
);
|
|
3771
|
+
server.tool(
|
|
3772
|
+
"mem_resolve_project",
|
|
3773
|
+
[
|
|
3774
|
+
"Diagnostics: resolve which project root hAIve is using (never throws).",
|
|
3775
|
+
"",
|
|
3776
|
+
"USE IN multi-root workspaces or when the agent CWD may not be the repo root \u2014",
|
|
3777
|
+
"mirrors HAIVE_PROJECT_ROOT, findProjectRoot markers, and presence of .ai/memories.",
|
|
3778
|
+
"",
|
|
3779
|
+
"PARAMETERS:",
|
|
3780
|
+
" cwd \u2014 optional directory used for discovery when HAIVE_PROJECT_ROOT is unset",
|
|
3781
|
+
"",
|
|
3782
|
+
"RETURNS: { ok: true, info: { cwd, resolved_root, haive_project_root_env, \u2026 } }"
|
|
3783
|
+
].join("\n"),
|
|
3784
|
+
MemResolveProjectInputSchema,
|
|
3785
|
+
async (input) => jsonResult(await memResolveProject(input, context))
|
|
3786
|
+
);
|
|
3620
3787
|
server.tool(
|
|
3621
3788
|
"mem_update",
|
|
3622
3789
|
[
|
|
@@ -3750,6 +3917,8 @@ function createHaiveServer(options = {}) {
|
|
|
3750
3917
|
"One-shot ranked memories for a task \u2014 use instead of get_briefing when",
|
|
3751
3918
|
"project context is already loaded and you only want the relevant memory layer.",
|
|
3752
3919
|
"",
|
|
3920
|
+
"Second step in progressive disclosure (after get_briefing): narrow here, then mem_get for full text.",
|
|
3921
|
+
"",
|
|
3753
3922
|
"Reuses the same ranking pipeline (anchor / module / literal / semantic) but",
|
|
3754
3923
|
"skips project_context, modules, action_required, etc.",
|
|
3755
3924
|
"",
|
|
@@ -3907,6 +4076,24 @@ function createHaiveServer(options = {}) {
|
|
|
3907
4076
|
return jsonResult(await memConflicts(input, context));
|
|
3908
4077
|
}
|
|
3909
4078
|
);
|
|
4079
|
+
server.tool(
|
|
4080
|
+
"mem_conflict_candidates",
|
|
4081
|
+
[
|
|
4082
|
+
"Bulk lexical scan for decision/architecture-like pairs that look similar (Jaccard on tokens).",
|
|
4083
|
+
"",
|
|
4084
|
+
"Advisory only \u2014 follow with mem_conflicts_with on specific ids for real contradiction checks.",
|
|
4085
|
+
"",
|
|
4086
|
+
"PARAMETERS:",
|
|
4087
|
+
" since_days, types, min_jaccard, max_pairs, max_scan",
|
|
4088
|
+
"",
|
|
4089
|
+
"RETURNS: { pairs: [{ id_a, id_b, jaccard }], scanned, truncated, notice? }"
|
|
4090
|
+
].join("\n"),
|
|
4091
|
+
MemConflictCandidatesInputSchema,
|
|
4092
|
+
async (input) => {
|
|
4093
|
+
tracker.record("mem_conflict_candidates", `${input.since_days}d`);
|
|
4094
|
+
return jsonResult(await memConflictCandidates(input, context));
|
|
4095
|
+
}
|
|
4096
|
+
);
|
|
3910
4097
|
server.tool(
|
|
3911
4098
|
"pre_commit_check",
|
|
3912
4099
|
[
|