@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/server.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { HaivePaths, ConfidenceLevel } from '@hiveai/core';
2
+ import * as _hiveai_core from '@hiveai/core';
3
+ import { HaivePaths, ConfidenceLevel, resolveProjectInfo } from '@hiveai/core';
3
4
  import { z } from 'zod';
4
5
 
5
6
  interface HaiveContext {
@@ -565,6 +566,62 @@ interface PatternDetectOutput {
565
566
  }
566
567
  declare function patternDetect(input: PatternDetectInput, ctx: HaiveContext): Promise<PatternDetectOutput>;
567
568
 
569
+ /** Input is intentionally minimal — callers may pass cwd for multi-root clients. */
570
+ declare const MemResolveProjectInputSchema: {
571
+ cwd: z.ZodOptional<z.ZodString>;
572
+ };
573
+ type MemResolveProjectInput = {
574
+ [K in keyof typeof MemResolveProjectInputSchema]: z.infer<(typeof MemResolveProjectInputSchema)[K]>;
575
+ };
576
+ declare function memResolveProject(input: MemResolveProjectInput, _ctx: HaiveContext): Promise<{
577
+ info: ReturnType<typeof resolveProjectInfo>;
578
+ ok: true;
579
+ }>;
580
+
581
+ declare const MemSuggestTopicInputSchema: {
582
+ type: z.ZodEnum<["convention", "decision", "gotcha", "architecture", "glossary", "attempt", "session_recap"]>;
583
+ title: z.ZodString;
584
+ };
585
+ type MemSuggestTopicInput = {
586
+ [K in keyof typeof MemSuggestTopicInputSchema]: z.infer<(typeof MemSuggestTopicInputSchema)[K]>;
587
+ };
588
+ declare function memSuggestTopic(input: MemSuggestTopicInput, _ctx: HaiveContext): Promise<{
589
+ topic_key: string;
590
+ family: string;
591
+ type: string;
592
+ }>;
593
+
594
+ declare const MemTimelineInputSchema: {
595
+ memory_id: z.ZodOptional<z.ZodString>;
596
+ topic: z.ZodOptional<z.ZodString>;
597
+ limit: z.ZodDefault<z.ZodNumber>;
598
+ };
599
+ type MemTimelineInput = {
600
+ [K in keyof typeof MemTimelineInputSchema]: z.infer<(typeof MemTimelineInputSchema)[K]>;
601
+ };
602
+ declare function memTimeline(input: MemTimelineInput, ctx: HaiveContext): Promise<{
603
+ entries: _hiveai_core.TimelineEntry[];
604
+ total: number;
605
+ notice: string | undefined;
606
+ }>;
607
+
608
+ declare const MemConflictCandidatesInputSchema: {
609
+ since_days: z.ZodDefault<z.ZodNumber>;
610
+ types: z.ZodDefault<z.ZodArray<z.ZodEnum<["decision", "architecture", "convention", "gotcha"]>, "many">>;
611
+ min_jaccard: z.ZodDefault<z.ZodNumber>;
612
+ max_pairs: z.ZodDefault<z.ZodNumber>;
613
+ max_scan: z.ZodDefault<z.ZodNumber>;
614
+ };
615
+ type MemConflictCandidatesInput = {
616
+ [K in keyof typeof MemConflictCandidatesInputSchema]: z.infer<(typeof MemConflictCandidatesInputSchema)[K]>;
617
+ };
618
+ declare function memConflictCandidates(input: MemConflictCandidatesInput, ctx: HaiveContext): Promise<{
619
+ pairs: _hiveai_core.ConflictCandidatePair[];
620
+ scanned: number;
621
+ truncated: boolean;
622
+ notice: string | undefined;
623
+ }>;
624
+
568
625
  declare const SERVER_NAME = "haive";
569
626
  declare const SERVER_VERSION: string;
570
627
  declare function createHaiveServer(options?: CreateContextOptions): {
@@ -587,4 +644,4 @@ declare function runHaiveMcpStdio(options: {
587
644
  root?: string;
588
645
  }): Promise<void>;
589
646
 
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 };
647
+ export { type AntiPatternsCheckInput, type AntiPatternsCheckOutput, type BriefingOutput, type CodeMapInput, type CodeMapToolOutput, type CodeSearchInput, type CodeSearchOutput, type GetBriefingInput, type GetRecapInput, type GetRecapOutput, type MemConflictCandidatesInput, type MemConflictsInput, type MemConflictsOutput, type MemDistillInput, type MemDistillOutput, type MemRelevantToInput, type MemRelevantToOutput, type MemResolveProjectInput, type MemSuggestTopicInput, type MemTimelineInput, 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, memConflictCandidates, memConflicts, memDistill, memRelevantTo, memResolveProject, memSuggestTopic, memTimeline, parseMcpCliArgs, patternDetect, preCommitCheck, printHaiveMcpVersion, runHaiveMcpStdio, whyThisDecision, whyThisFile };
package/dist/server.js CHANGED
@@ -319,6 +319,7 @@ import {
319
319
  loadMemoriesFromDir as loadMemoriesFromDir3,
320
320
  loadUsageIndex,
321
321
  pickSnippetNeedle,
322
+ rankMemoriesLexical,
322
323
  tokenizeQuery,
323
324
  trackReads
324
325
  } from "@hiveai/core";
@@ -335,6 +336,9 @@ var MemSearchInputSchema = {
335
336
  semantic: z5.boolean().default(false).describe(
336
337
  "Use semantic similarity from the embeddings index (requires `haive embeddings index`)."
337
338
  ),
339
+ lexical_rank: z5.boolean().default(false).describe(
340
+ "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."
341
+ ),
338
342
  min_score: z5.number().min(0).max(1).default(0).describe("Minimum cosine similarity (semantic mode only)"),
339
343
  track: z5.boolean().default(true).describe("Increment read_count on returned memories (used for passive validation)")
340
344
  };
@@ -360,6 +364,27 @@ async function memSearch(input, ctx) {
360
364
  notice: "Semantic search unavailable (embeddings index missing or @hiveai/embeddings not installed). Falling back to literal search."
361
365
  };
362
366
  }
367
+ } else if (input.lexical_rank && input.query.trim()) {
368
+ const { ranked, scores } = rankMemoriesLexical(
369
+ filtered,
370
+ input.query,
371
+ input.limit
372
+ );
373
+ if (ranked.length > 0) {
374
+ const snippetNeedle = pickSnippetNeedle(input.query);
375
+ result = {
376
+ matches: ranked.map(
377
+ (loaded, i) => lexicalHit(loaded, snippetNeedle, usage, scores[i])
378
+ ),
379
+ total: ranked.length,
380
+ mode: "lexical_ranked"
381
+ };
382
+ } else {
383
+ result = {
384
+ ...buildLiteralResult(input, filtered, usage),
385
+ notice: "Lexical ranking found no BM25-positive hits \u2014 showing literal matches instead."
386
+ };
387
+ }
363
388
  } else {
364
389
  result = buildLiteralResult(input, filtered, usage);
365
390
  }
@@ -457,6 +482,9 @@ function toHit(loaded, needle, usage) {
457
482
  file_path: loaded.filePath
458
483
  };
459
484
  }
485
+ function lexicalHit(loaded, needle, usage, lexicalScore) {
486
+ return { ...toHit(loaded, needle, usage), score: lexicalScore };
487
+ }
460
488
 
461
489
  // src/tools/mem-verify.ts
462
490
  import { writeFile as writeFile3 } from "fs/promises";
@@ -3038,13 +3066,96 @@ function gitFileDiff(root, file, sinceDays) {
3038
3066
  }
3039
3067
  }
3040
3068
 
3041
- // src/prompts/bootstrap-project.ts
3069
+ // src/tools/mem-conflict-candidates.ts
3070
+ import { existsSync as existsSync27 } from "fs";
3071
+ import { findLexicalConflictPairs, loadMemoriesFromDir as loadMemoriesFromDir21 } from "@hiveai/core";
3042
3072
  import { z as z30 } from "zod";
3073
+ var MemConflictCandidatesInputSchema = {
3074
+ since_days: z30.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
3075
+ types: z30.array(z30.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
3076
+ min_jaccard: z30.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
3077
+ max_pairs: z30.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
3078
+ 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.")
3079
+ };
3080
+ async function memConflictCandidates(input, ctx) {
3081
+ if (!existsSync27(ctx.paths.memoriesDir)) {
3082
+ return {
3083
+ pairs: [],
3084
+ scanned: 0,
3085
+ truncated: false,
3086
+ notice: "No .ai/memories directory."
3087
+ };
3088
+ }
3089
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
3090
+ const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
3091
+ sinceDays: input.since_days,
3092
+ types: input.types,
3093
+ minJaccard: input.min_jaccard,
3094
+ maxPairs: input.max_pairs,
3095
+ maxScan: input.max_scan
3096
+ });
3097
+ const notice = pairs.length === 0 ? "No lexical candidate pairs \u2265 threshold \u2014 try lowering min_jaccard or widen since_days/types." : void 0;
3098
+ return { pairs, scanned, truncated, notice };
3099
+ }
3100
+
3101
+ // src/tools/mem-resolve-project.ts
3102
+ import { resolveProjectInfo } from "@hiveai/core";
3103
+ import { z as z31 } from "zod";
3104
+ var MemResolveProjectInputSchema = {
3105
+ cwd: z31.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
3106
+ };
3107
+ async function memResolveProject(input, _ctx) {
3108
+ void _ctx;
3109
+ return {
3110
+ ok: true,
3111
+ info: resolveProjectInfo({
3112
+ cwd: input.cwd
3113
+ })
3114
+ };
3115
+ }
3116
+
3117
+ // src/tools/mem-suggest-topic.ts
3118
+ import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
3119
+ import { z as z32 } from "zod";
3120
+ var MemSuggestTopicInputSchema = {
3121
+ type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
3122
+ title: z32.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
3123
+ };
3124
+ async function memSuggestTopic(input, _ctx) {
3125
+ void _ctx;
3126
+ const suggestion = suggestTopicKey(input.type, input.title);
3127
+ return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
3128
+ }
3129
+
3130
+ // src/tools/mem-timeline.ts
3131
+ import { existsSync as existsSync28 } from "fs";
3132
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hiveai/core";
3133
+ import { z as z33 } from "zod";
3134
+ var MemTimelineInputSchema = {
3135
+ memory_id: z33.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
3136
+ topic: z33.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
3137
+ limit: z33.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
3138
+ };
3139
+ async function memTimeline(input, ctx) {
3140
+ if (!existsSync28(ctx.paths.memoriesDir)) {
3141
+ return { entries: [], total: 0, notice: "No .ai/memories directory." };
3142
+ }
3143
+ const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
3144
+ const { entries, notice } = collectTimelineEntries(all, {
3145
+ memoryId: input.memory_id,
3146
+ topic: input.topic,
3147
+ limit: input.limit
3148
+ });
3149
+ return { entries, total: entries.length, notice };
3150
+ }
3151
+
3152
+ // src/prompts/bootstrap-project.ts
3153
+ import { z as z34 } from "zod";
3043
3154
  var BootstrapProjectArgsSchema = {
3044
- module: z30.string().optional().describe(
3155
+ module: z34.string().optional().describe(
3045
3156
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
3046
3157
  ),
3047
- focus: z30.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
3158
+ focus: z34.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
3048
3159
  };
3049
3160
  var ROOT_TEMPLATE = `# Project context
3050
3161
 
@@ -3126,10 +3237,10 @@ ${template}\`\`\`
3126
3237
  }
3127
3238
 
3128
3239
  // src/prompts/post-task.ts
3129
- import { z as z31 } from "zod";
3240
+ import { z as z35 } from "zod";
3130
3241
  var PostTaskArgsSchema = {
3131
- task_summary: z31.string().optional().describe("One sentence describing what you just did"),
3132
- files_touched: z31.array(z31.string()).optional().describe("Files you created or modified during the task")
3242
+ task_summary: z35.string().optional().describe("One sentence describing what you just did"),
3243
+ files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
3133
3244
  };
3134
3245
  function postTaskPrompt(args, ctx) {
3135
3246
  const taskLine = args.task_summary ? `
@@ -3213,12 +3324,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
3213
3324
  }
3214
3325
 
3215
3326
  // src/prompts/import-docs.ts
3216
- import { z as z32 } from "zod";
3327
+ import { z as z36 } from "zod";
3217
3328
  var ImportDocsArgsSchema = {
3218
- content: z32.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
3219
- source: z32.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
3220
- scope: z32.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
3221
- dry_run: z32.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
3329
+ content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
3330
+ source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
3331
+ scope: z36.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
3332
+ dry_run: z36.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
3222
3333
  };
3223
3334
  function importDocsPrompt(args, ctx) {
3224
3335
  const sourceLine = args.source ? `
@@ -3283,7 +3394,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
3283
3394
 
3284
3395
  // src/server.ts
3285
3396
  var SERVER_NAME = "haive";
3286
- var SERVER_VERSION = "0.9.3";
3397
+ var SERVER_VERSION = "0.9.5";
3287
3398
  function jsonResult(data) {
3288
3399
  return {
3289
3400
  content: [
@@ -3334,6 +3445,23 @@ function createHaiveServer(options = {}) {
3334
3445
  return jsonResult(await memSave(input, context));
3335
3446
  }
3336
3447
  );
3448
+ server.tool(
3449
+ "mem_suggest_topic",
3450
+ [
3451
+ "Propose a stable `topic` key (topic-upsert) from type + short title.",
3452
+ "",
3453
+ "USE BEFORE mem_save when you want deterministic updates to the same memory over time;",
3454
+ "families mimic Engram-style grouping (architecture/*, bug/*, decision/*, \u2026).",
3455
+ "",
3456
+ "PARAMETERS:",
3457
+ " type \u2014 convention | decision | gotcha | architecture | glossary | attempt | session_recap",
3458
+ " title \u2014 phrase to slugify under the suggested family prefix",
3459
+ "",
3460
+ "RETURNS: { topic_key, family, type }"
3461
+ ].join("\n"),
3462
+ MemSuggestTopicInputSchema,
3463
+ async (input) => jsonResult(await memSuggestTopic(input, context))
3464
+ );
3337
3465
  server.tool(
3338
3466
  "mem_tried",
3339
3467
  [
@@ -3425,8 +3553,12 @@ function createHaiveServer(options = {}) {
3425
3553
  server.tool(
3426
3554
  "get_briefing",
3427
3555
  [
3428
- "\u2B50 CALL THIS FIRST at the start of every task. One-shot onboarding that returns",
3429
- "everything relevant in a single call under a token budget.",
3556
+ "\u2B50 DEFAULT-FIRST for coding agents on any repo where `haive init` ran: call this BEFORE",
3557
+ "changing source or project config for the current goal (unless the developer explicitly opts out).",
3558
+ "One-shot onboarding: everything relevant in a single call under a token budget.",
3559
+ "",
3560
+ "PROGRESSIVE DISCLOSURE \u2014 after this, drill down only if needed:",
3561
+ " mem_relevant_to / mem_search (compact lists) \u2192 mem_get (full body + anchors).",
3430
3562
  "",
3431
3563
  "RETURNS (in order of priority):",
3432
3564
  " 0. action_required \u2014 \u26A0\uFE0F HANDLE THIS FIRST if non-empty (see protocol below)",
@@ -3461,7 +3593,7 @@ function createHaiveServer(options = {}) {
3461
3593
  " low \u2014 proposed, few reads (take with caution)",
3462
3594
  " unverified \u2014 draft (unverified: true flag set)",
3463
3595
  "",
3464
- "Replaces 4\u20135 separate tool calls. Always call this before any other tool."
3596
+ "Replaces 4\u20135 separate tool calls. Prefer this first; use mem_search / mem_get only for follow-up."
3465
3597
  ].join("\n"),
3466
3598
  GetBriefingInputSchema,
3467
3599
  async (input) => {
@@ -3480,6 +3612,8 @@ function createHaiveServer(options = {}) {
3480
3612
  "SEARCH MODES:",
3481
3613
  " Literal (default): AND search across id, tags, and body \u2014 all tokens must match.",
3482
3614
  " Falls back to OR automatically if no AND results (partial match).",
3615
+ " Lexical rank (lexical_rank: true, semantic: false): Okapi-BM25-style scoring on the",
3616
+ " filtered corpus \u2014 good for phrase-like queries without embeddings.",
3483
3617
  " Semantic (semantic: true): embedding-based similarity \u2014 finds related memories",
3484
3618
  " even with different wording. Requires haive embeddings index to be built.",
3485
3619
  "",
@@ -3488,6 +3622,7 @@ function createHaiveServer(options = {}) {
3488
3622
  " scope \u2014 filter by personal | team | module",
3489
3623
  " type \u2014 filter by convention | decision | gotcha | architecture | glossary",
3490
3624
  " semantic \u2014 true for embedding-based search (requires @hiveai/embeddings)",
3625
+ " lexical_rank \u2014 BM25-style ranking (ignored when semantic is true)",
3491
3626
  " limit \u2014 max results (default 10)",
3492
3627
  "",
3493
3628
  "RETURNS: array of { id, type, scope, status, confidence, body, match_quality }"
@@ -3498,6 +3633,22 @@ function createHaiveServer(options = {}) {
3498
3633
  return jsonResult(await memSearch(input, context));
3499
3634
  }
3500
3635
  );
3636
+ server.tool(
3637
+ "mem_timeline",
3638
+ [
3639
+ "Chronological view of related memories: by shared frontmatter.topic OR expanded from a seed id",
3640
+ "(related_ids, same topic, overlapping anchor paths \u2014 one extra hop on related_ids).",
3641
+ "",
3642
+ "PARAMETERS:",
3643
+ " memory_id \u2014 optional seed memory id",
3644
+ " topic \u2014 optional topic key (required if memory_id omitted)",
3645
+ " limit \u2014 max entries (default 30)",
3646
+ "",
3647
+ "RETURNS: { entries: [{ id, type, scope, created_at, one_line, topic? }], total, notice? }"
3648
+ ].join("\n"),
3649
+ MemTimelineInputSchema,
3650
+ async (input) => jsonResult(await memTimeline(input, context))
3651
+ );
3501
3652
  server.tool(
3502
3653
  "mem_for_files",
3503
3654
  [
@@ -3527,7 +3678,7 @@ function createHaiveServer(options = {}) {
3527
3678
  [
3528
3679
  "Fetch a single memory by its full id with all details.",
3529
3680
  "",
3530
- "USE WHEN get_briefing returned a memory in 'compact' format and you need",
3681
+ "USE WHEN get_briefing / mem_relevant_to / mem_search returned a compact hit and you need",
3531
3682
  "the full body, or when you know the exact id of a memory.",
3532
3683
  "",
3533
3684
  "PARAMETERS:",
@@ -3615,6 +3766,22 @@ function createHaiveServer(options = {}) {
3615
3766
  CodeMapInputSchema,
3616
3767
  async (input) => jsonResult(await codeMapTool(input, context))
3617
3768
  );
3769
+ server.tool(
3770
+ "mem_resolve_project",
3771
+ [
3772
+ "Diagnostics: resolve which project root hAIve is using (never throws).",
3773
+ "",
3774
+ "USE IN multi-root workspaces or when the agent CWD may not be the repo root \u2014",
3775
+ "mirrors HAIVE_PROJECT_ROOT, findProjectRoot markers, and presence of .ai/memories.",
3776
+ "",
3777
+ "PARAMETERS:",
3778
+ " cwd \u2014 optional directory used for discovery when HAIVE_PROJECT_ROOT is unset",
3779
+ "",
3780
+ "RETURNS: { ok: true, info: { cwd, resolved_root, haive_project_root_env, \u2026 } }"
3781
+ ].join("\n"),
3782
+ MemResolveProjectInputSchema,
3783
+ async (input) => jsonResult(await memResolveProject(input, context))
3784
+ );
3618
3785
  server.tool(
3619
3786
  "mem_update",
3620
3787
  [
@@ -3748,6 +3915,8 @@ function createHaiveServer(options = {}) {
3748
3915
  "One-shot ranked memories for a task \u2014 use instead of get_briefing when",
3749
3916
  "project context is already loaded and you only want the relevant memory layer.",
3750
3917
  "",
3918
+ "Second step in progressive disclosure (after get_briefing): narrow here, then mem_get for full text.",
3919
+ "",
3751
3920
  "Reuses the same ranking pipeline (anchor / module / literal / semantic) but",
3752
3921
  "skips project_context, modules, action_required, etc.",
3753
3922
  "",
@@ -3905,6 +4074,24 @@ function createHaiveServer(options = {}) {
3905
4074
  return jsonResult(await memConflicts(input, context));
3906
4075
  }
3907
4076
  );
4077
+ server.tool(
4078
+ "mem_conflict_candidates",
4079
+ [
4080
+ "Bulk lexical scan for decision/architecture-like pairs that look similar (Jaccard on tokens).",
4081
+ "",
4082
+ "Advisory only \u2014 follow with mem_conflicts_with on specific ids for real contradiction checks.",
4083
+ "",
4084
+ "PARAMETERS:",
4085
+ " since_days, types, min_jaccard, max_pairs, max_scan",
4086
+ "",
4087
+ "RETURNS: { pairs: [{ id_a, id_b, jaccard }], scanned, truncated, notice? }"
4088
+ ].join("\n"),
4089
+ MemConflictCandidatesInputSchema,
4090
+ async (input) => {
4091
+ tracker.record("mem_conflict_candidates", `${input.since_days}d`);
4092
+ return jsonResult(await memConflictCandidates(input, context));
4093
+ }
4094
+ );
3908
4095
  server.tool(
3909
4096
  "pre_commit_check",
3910
4097
  [
@@ -4051,9 +4238,13 @@ export {
4051
4238
  createHaiveServer,
4052
4239
  getBriefing,
4053
4240
  getRecap,
4241
+ memConflictCandidates,
4054
4242
  memConflicts,
4055
4243
  memDistill,
4056
4244
  memRelevantTo,
4245
+ memResolveProject,
4246
+ memSuggestTopic,
4247
+ memTimeline,
4057
4248
  parseMcpCliArgs,
4058
4249
  patternDetect,
4059
4250
  preCommitCheck,