@hiveai/mcp 0.5.0 → 0.7.0

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.js CHANGED
@@ -2060,13 +2060,520 @@ async function antiPatternsCheck(input, ctx) {
2060
2060
  };
2061
2061
  }
2062
2062
 
2063
- // src/prompts/bootstrap-project.ts
2063
+ // src/tools/mem-distill.ts
2064
+ import { existsSync as existsSync22 } from "fs";
2065
+ import {
2066
+ loadMemoriesFromDir as loadMemoriesFromDir18,
2067
+ tokenizeQuery as tokenizeQuery4
2068
+ } from "@hiveai/core";
2064
2069
  import { z as z25 } from "zod";
2070
+ var MemDistillInputSchema = {
2071
+ since_days: z25.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
2072
+ min_cluster: z25.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
2073
+ type_filter: z25.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
2074
+ "Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
2075
+ ),
2076
+ scope: z25.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
2077
+ };
2078
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
2079
+ var STOP_WORDS = /* @__PURE__ */ new Set([
2080
+ "the",
2081
+ "and",
2082
+ "for",
2083
+ "with",
2084
+ "that",
2085
+ "this",
2086
+ "from",
2087
+ "into",
2088
+ "when",
2089
+ "then",
2090
+ "also",
2091
+ "must",
2092
+ "have",
2093
+ "does",
2094
+ "not",
2095
+ "but",
2096
+ "you",
2097
+ "your",
2098
+ "its",
2099
+ "because",
2100
+ "why",
2101
+ "how",
2102
+ "what",
2103
+ "use",
2104
+ "using",
2105
+ "used",
2106
+ "add",
2107
+ "added",
2108
+ "make",
2109
+ "made",
2110
+ "fix",
2111
+ "fixed",
2112
+ "bug",
2113
+ "error"
2114
+ ]);
2115
+ async function memDistill(input, ctx) {
2116
+ if (!existsSync22(ctx.paths.memoriesDir)) {
2117
+ return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
2118
+ }
2119
+ const cutoff = Date.now() - input.since_days * MS_PER_DAY;
2120
+ const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
2121
+ const candidates = all.filter(({ memory }) => {
2122
+ const fm = memory.frontmatter;
2123
+ if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
2124
+ if (input.scope !== "any" && fm.scope !== input.scope) return false;
2125
+ if (input.type_filter === "gotcha" && fm.type !== "gotcha") return false;
2126
+ if (input.type_filter === "attempt" && fm.type !== "attempt") return false;
2127
+ if (input.type_filter === "all" && fm.type !== "gotcha" && fm.type !== "attempt") return false;
2128
+ if (Date.parse(fm.created_at) < cutoff) return false;
2129
+ return true;
2130
+ });
2131
+ if (candidates.length < input.min_cluster) {
2132
+ return {
2133
+ scanned: candidates.length,
2134
+ singletons: candidates.length,
2135
+ clusters: [],
2136
+ notice: candidates.length === 0 ? `No matching memories in the last ${input.since_days} days.` : `Only ${candidates.length} candidate${candidates.length === 1 ? "" : "s"} \u2014 below min_cluster=${input.min_cluster}.`
2137
+ };
2138
+ }
2139
+ const features = candidates.map((loaded) => ({
2140
+ loaded,
2141
+ keywords: keywordSet(loaded),
2142
+ paths: new Set(loaded.memory.frontmatter.anchor.paths)
2143
+ }));
2144
+ const parent = features.map((_, i) => i);
2145
+ const find = (i) => parent[i] === i ? i : parent[i] = find(parent[i] ?? 0);
2146
+ const union = (a, b) => {
2147
+ const ra = find(a), rb = find(b);
2148
+ if (ra !== rb) parent[ra] = rb;
2149
+ };
2150
+ for (let i = 0; i < features.length; i++) {
2151
+ for (let j = i + 1; j < features.length; j++) {
2152
+ const fi = features[i], fj = features[j];
2153
+ const pathSim = jaccard(fi.paths, fj.paths);
2154
+ const kwSim = jaccard(fi.keywords, fj.keywords);
2155
+ if (pathSim >= 0.5 || kwSim >= 0.4) union(i, j);
2156
+ }
2157
+ }
2158
+ const groups = /* @__PURE__ */ new Map();
2159
+ for (let i = 0; i < features.length; i++) {
2160
+ const root = find(i);
2161
+ const arr = groups.get(root) ?? [];
2162
+ arr.push(i);
2163
+ groups.set(root, arr);
2164
+ }
2165
+ const clusters = [];
2166
+ let singletons = 0;
2167
+ for (const indices of groups.values()) {
2168
+ if (indices.length < input.min_cluster) {
2169
+ singletons += indices.length;
2170
+ continue;
2171
+ }
2172
+ const members = indices.map((i) => features[i]);
2173
+ const allPaths = /* @__PURE__ */ new Set();
2174
+ const allKeywords = /* @__PURE__ */ new Map();
2175
+ let latest = 0;
2176
+ for (const m of members) {
2177
+ for (const p of m.paths) allPaths.add(p);
2178
+ for (const k of m.keywords) allKeywords.set(k, (allKeywords.get(k) ?? 0) + 1);
2179
+ const t = Date.parse(m.loaded.memory.frontmatter.created_at);
2180
+ if (t > latest) latest = t;
2181
+ }
2182
+ const commonKeywords = [...allKeywords.entries()].filter(([, n]) => n >= Math.max(2, Math.floor(members.length / 2))).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k]) => k);
2183
+ const titles = members.map((m) => firstHeading(m.loaded.memory.body) ?? m.loaded.memory.frontmatter.id).slice(0, 5);
2184
+ const suggestedType = members.every((m) => m.loaded.memory.frontmatter.type === "attempt") ? "gotcha" : "convention";
2185
+ clusters.push({
2186
+ suggested_topic: commonKeywords.slice(0, 3).join("-") || "merged-observations",
2187
+ suggested_type: suggestedType,
2188
+ member_ids: members.map((m) => m.loaded.memory.frontmatter.id),
2189
+ overlapping_paths: [...allPaths].slice(0, 10),
2190
+ common_keywords: commonKeywords,
2191
+ sample_titles: titles,
2192
+ latest_at: new Date(latest).toISOString()
2193
+ });
2194
+ }
2195
+ clusters.sort((a, b) => b.member_ids.length - a.member_ids.length);
2196
+ return {
2197
+ scanned: candidates.length,
2198
+ singletons,
2199
+ clusters
2200
+ };
2201
+ }
2202
+ function keywordSet(loaded) {
2203
+ const text = (loaded.memory.body + " " + loaded.memory.frontmatter.tags.join(" ")).slice(0, 800);
2204
+ const tokens = tokenizeQuery4(text).filter((t) => t.length >= 4 && !STOP_WORDS.has(t));
2205
+ return new Set(tokens);
2206
+ }
2207
+ function jaccard(a, b) {
2208
+ if (a.size === 0 && b.size === 0) return 0;
2209
+ let intersect = 0;
2210
+ for (const x of a) if (b.has(x)) intersect++;
2211
+ const union = a.size + b.size - intersect;
2212
+ return union === 0 ? 0 : intersect / union;
2213
+ }
2214
+ function firstHeading(body) {
2215
+ for (const line of body.split("\n")) {
2216
+ const t = line.trim();
2217
+ if (t.startsWith("#")) return t.replace(/^#+\s*/, "").slice(0, 80);
2218
+ if (t.length > 0) return t.slice(0, 80);
2219
+ }
2220
+ return void 0;
2221
+ }
2222
+
2223
+ // src/tools/why-this-decision.ts
2224
+ import { existsSync as existsSync23 } from "fs";
2225
+ import { spawn as spawn2 } from "child_process";
2226
+ import {
2227
+ deriveConfidence as deriveConfidence7,
2228
+ getUsage as getUsage8,
2229
+ loadMemoriesFromDir as loadMemoriesFromDir19,
2230
+ loadUsageIndex as loadUsageIndex10,
2231
+ pathsOverlap as singlePathsOverlap
2232
+ } from "@hiveai/core";
2233
+ import { z as z26 } from "zod";
2234
+ var WhyThisDecisionInputSchema = {
2235
+ id: z26.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
2236
+ git_log_limit: z26.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
2237
+ };
2238
+ async function whyThisDecision(input, ctx) {
2239
+ if (!existsSync23(ctx.paths.memoriesDir)) {
2240
+ return {
2241
+ found: false,
2242
+ related: [],
2243
+ path_neighbors: [],
2244
+ recent_commits: [],
2245
+ notice: "No .ai/memories directory."
2246
+ };
2247
+ }
2248
+ const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
2249
+ const usage = await loadUsageIndex10(ctx.paths);
2250
+ const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
2251
+ if (!target) {
2252
+ return {
2253
+ found: false,
2254
+ related: [],
2255
+ path_neighbors: [],
2256
+ recent_commits: [],
2257
+ notice: `Memory '${input.id}' not found.`
2258
+ };
2259
+ }
2260
+ const fm = target.memory.frontmatter;
2261
+ const targetUsage = getUsage8(usage, fm.id);
2262
+ const decision = {
2263
+ id: fm.id,
2264
+ type: fm.type,
2265
+ scope: fm.scope,
2266
+ status: fm.status,
2267
+ confidence: deriveConfidence7(fm, targetUsage),
2268
+ body: target.memory.body,
2269
+ created_at: fm.created_at
2270
+ };
2271
+ const relatedSet = new Set(fm.related_ids ?? []);
2272
+ const related = [];
2273
+ for (const { memory } of all) {
2274
+ if (memory.frontmatter.id === fm.id) continue;
2275
+ const isExplicit = relatedSet.has(memory.frontmatter.id);
2276
+ const isBackLink = (memory.frontmatter.related_ids ?? []).includes(fm.id);
2277
+ if (!isExplicit && !isBackLink) continue;
2278
+ const u = getUsage8(usage, memory.frontmatter.id);
2279
+ related.push({
2280
+ id: memory.frontmatter.id,
2281
+ type: memory.frontmatter.type,
2282
+ scope: memory.frontmatter.scope,
2283
+ confidence: deriveConfidence7(memory.frontmatter, u),
2284
+ body_preview: memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
2285
+ relation: isExplicit ? "explicit" : "back-link"
2286
+ });
2287
+ }
2288
+ const targetPaths = fm.anchor.paths;
2289
+ const path_neighbors = [];
2290
+ if (targetPaths.length > 0) {
2291
+ for (const { memory } of all) {
2292
+ if (memory.frontmatter.id === fm.id) continue;
2293
+ if (relatedSet.has(memory.frontmatter.id)) continue;
2294
+ const overlappingPaths = memory.frontmatter.anchor.paths.filter(
2295
+ (p) => targetPaths.some((tp) => singlePathsOverlap(p, tp))
2296
+ );
2297
+ if (overlappingPaths.length === 0) continue;
2298
+ const u = getUsage8(usage, memory.frontmatter.id);
2299
+ path_neighbors.push({
2300
+ id: memory.frontmatter.id,
2301
+ type: memory.frontmatter.type,
2302
+ scope: memory.frontmatter.scope,
2303
+ confidence: deriveConfidence7(memory.frontmatter, u),
2304
+ overlap: overlappingPaths,
2305
+ body_preview: memory.body.split("\n").slice(0, 3).join("\n").slice(0, 200)
2306
+ });
2307
+ if (path_neighbors.length >= 10) break;
2308
+ }
2309
+ }
2310
+ const recent_commits = [];
2311
+ for (const p of targetPaths.slice(0, 5)) {
2312
+ try {
2313
+ const commits = await runGitLog2(ctx.paths.root, p, input.git_log_limit);
2314
+ for (const c of commits) recent_commits.push({ path: p, ...c });
2315
+ } catch {
2316
+ }
2317
+ }
2318
+ const hints = [];
2319
+ if (decision.confidence === "low" || decision.confidence === "stale") {
2320
+ hints.push(`\u26A0\uFE0F Confidence is ${decision.confidence}. Verify this decision still applies before quoting it.`);
2321
+ }
2322
+ if (related.length === 0 && path_neighbors.length === 0 && targetPaths.length === 0) {
2323
+ hints.push("No related memories and no anchored paths \u2014 this decision is isolated; consider adding related_ids or paths.");
2324
+ }
2325
+ if (fm.type !== "decision" && fm.type !== "architecture") {
2326
+ hints.push(`Memory type is '${fm.type}', not 'decision'/'architecture' \u2014 output may be less informative.`);
2327
+ }
2328
+ return {
2329
+ found: true,
2330
+ decision,
2331
+ related,
2332
+ path_neighbors,
2333
+ recent_commits,
2334
+ ...hints.length > 0 ? { hints } : {}
2335
+ };
2336
+ }
2337
+ async function runGitLog2(cwd, filePath, limit) {
2338
+ const sep = "<<HV>>";
2339
+ const fmt = `%h${sep}%an${sep}%ar${sep}%s`;
2340
+ const output = await runCommand2(
2341
+ "git",
2342
+ ["log", "-n", String(limit), `--pretty=format:${fmt}`, "--", filePath],
2343
+ cwd
2344
+ );
2345
+ if (!output.trim()) return [];
2346
+ return output.split("\n").map((line) => {
2347
+ const [sha = "", author = "", relative_date = "", subject = ""] = line.split(sep);
2348
+ return { sha, author, relative_date, subject };
2349
+ }).filter((c) => c.sha);
2350
+ }
2351
+ function runCommand2(cmd, args, cwd) {
2352
+ return new Promise((resolve, reject) => {
2353
+ const proc = spawn2(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
2354
+ let stdout = "";
2355
+ let stderr = "";
2356
+ proc.stdout.on("data", (chunk) => {
2357
+ stdout += chunk.toString();
2358
+ });
2359
+ proc.stderr.on("data", (chunk) => {
2360
+ stderr += chunk.toString();
2361
+ });
2362
+ proc.on("error", reject);
2363
+ proc.on("close", (code) => {
2364
+ if (code === 0) resolve(stdout);
2365
+ else reject(new Error(stderr || `${cmd} exited with code ${code}`));
2366
+ });
2367
+ });
2368
+ }
2369
+
2370
+ // src/tools/mem-conflicts.ts
2371
+ import { existsSync as existsSync24 } from "fs";
2372
+ import {
2373
+ deriveConfidence as deriveConfidence8,
2374
+ getUsage as getUsage9,
2375
+ loadMemoriesFromDir as loadMemoriesFromDir20,
2376
+ loadUsageIndex as loadUsageIndex11,
2377
+ pathsOverlap,
2378
+ tokenizeQuery as tokenizeQuery5
2379
+ } from "@hiveai/core";
2380
+ import { z as z27 } from "zod";
2381
+ var MemConflictsInputSchema = {
2382
+ id: z27.string().min(1).describe("Memory id to check for conflicts."),
2383
+ min_score: z27.number().min(0).max(1).default(0.5).describe("Minimum cosine similarity to consider a memory as a potential conflict (semantic mode)."),
2384
+ semantic: z27.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
2385
+ };
2386
+ var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
2387
+ var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
2388
+ async function memConflicts(input, ctx) {
2389
+ if (!existsSync24(ctx.paths.memoriesDir)) {
2390
+ return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
2391
+ }
2392
+ const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
2393
+ const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
2394
+ if (!target) {
2395
+ return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
2396
+ }
2397
+ const usage = await loadUsageIndex11(ctx.paths);
2398
+ const others = all.filter(
2399
+ ({ memory }) => memory.frontmatter.id !== input.id && memory.frontmatter.type !== "session_recap"
2400
+ );
2401
+ const simScores = input.semantic ? await trySemanticSimilarities(ctx, target, others) : null;
2402
+ const targetText = (target.memory.body + " " + target.memory.frontmatter.tags.join(" ")).toLowerCase();
2403
+ const targetTokens = new Set(tokenizeQuery5(targetText));
2404
+ const targetPolarity = polarity(targetText);
2405
+ const targetPaths = target.memory.frontmatter.anchor.paths;
2406
+ const explicitContradicts = extractContradictsTags(target.memory.body);
2407
+ const conflicts = [];
2408
+ for (const other of others) {
2409
+ const fm = other.memory.frontmatter;
2410
+ const otherText = (other.memory.body + " " + fm.tags.join(" ")).toLowerCase();
2411
+ const reasons = [];
2412
+ const sim = simScores?.get(fm.id) ?? null;
2413
+ const hasPathOverlap = fm.anchor.paths.some((p) => targetPaths.some((tp) => pathsOverlap(p, tp)));
2414
+ const otherTokens = new Set(tokenizeQuery5(otherText));
2415
+ const tokenOverlap = countIntersection(targetTokens, otherTokens);
2416
+ const isSemanticNeighbor = sim !== null && sim >= input.min_score;
2417
+ if (!hasPathOverlap && tokenOverlap < 4 && !isSemanticNeighbor) continue;
2418
+ const otherContradicts = extractContradictsTags(other.memory.body);
2419
+ if (explicitContradicts.has(fm.id) || otherContradicts.has(input.id)) {
2420
+ reasons.push("explicit-contradiction-tag");
2421
+ }
2422
+ if (target.memory.frontmatter.status === "validated" && fm.status === "rejected" || target.memory.frontmatter.status === "rejected" && fm.status === "validated") {
2423
+ if (tokenOverlap >= 4 || isSemanticNeighbor) reasons.push("opposite-status");
2424
+ }
2425
+ if (hasPathOverlap) {
2426
+ const tType = target.memory.frontmatter.type;
2427
+ const oType = fm.type;
2428
+ const isAttemptVsRule = tType === "attempt" && (oType === "convention" || oType === "decision") || oType === "attempt" && (tType === "convention" || tType === "decision");
2429
+ if (isAttemptVsRule) reasons.push("attempt-vs-convention-same-paths");
2430
+ }
2431
+ if (isSemanticNeighbor) {
2432
+ const otherPolarity = polarity(otherText);
2433
+ if (targetPolarity === "positive" && otherPolarity === "negative" || targetPolarity === "negative" && otherPolarity === "positive") {
2434
+ reasons.push("polarity-keywords");
2435
+ }
2436
+ }
2437
+ if (reasons.length === 0) continue;
2438
+ const u = getUsage9(usage, fm.id);
2439
+ conflicts.push({
2440
+ id: fm.id,
2441
+ type: fm.type,
2442
+ scope: fm.scope,
2443
+ status: fm.status,
2444
+ confidence: deriveConfidence8(fm, u),
2445
+ body_preview: other.memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
2446
+ similarity: sim,
2447
+ reasons,
2448
+ shared_paths: fm.anchor.paths.filter((p) => targetPaths.some((tp) => pathsOverlap(p, tp)))
2449
+ });
2450
+ }
2451
+ conflicts.sort((a, b) => {
2452
+ const score = (c) => (c.reasons.includes("explicit-contradiction-tag") ? 100 : 0) + (c.reasons.includes("opposite-status") ? 50 : 0) + (c.reasons.includes("attempt-vs-convention-same-paths") ? 25 : 0) + (c.reasons.includes("polarity-keywords") ? 10 : 0) + (c.similarity ?? 0) * 5;
2453
+ return score(b) - score(a);
2454
+ });
2455
+ return {
2456
+ found: true,
2457
+ target: {
2458
+ id: target.memory.frontmatter.id,
2459
+ type: target.memory.frontmatter.type,
2460
+ status: target.memory.frontmatter.status
2461
+ },
2462
+ scanned: others.length,
2463
+ conflicts: conflicts.slice(0, 10)
2464
+ };
2465
+ }
2466
+ function polarity(text) {
2467
+ const neg = NEGATIVE_PATTERNS.test(text);
2468
+ const pos = POSITIVE_PATTERNS.test(text);
2469
+ if (neg && !pos) return "negative";
2470
+ if (pos && !neg) return "positive";
2471
+ return "neutral";
2472
+ }
2473
+ function extractContradictsTags(body) {
2474
+ const out = /* @__PURE__ */ new Set();
2475
+ for (const m of body.matchAll(/#contradicts:([\w-]+)/g)) {
2476
+ if (m[1]) out.add(m[1]);
2477
+ }
2478
+ return out;
2479
+ }
2480
+ function countIntersection(a, b) {
2481
+ let n = 0;
2482
+ for (const x of a) if (b.has(x)) n++;
2483
+ return n;
2484
+ }
2485
+ async function trySemanticSimilarities(ctx, target, others) {
2486
+ let mod;
2487
+ try {
2488
+ mod = await import("@hiveai/embeddings");
2489
+ } catch {
2490
+ return null;
2491
+ }
2492
+ const result = await mod.semanticSearch(
2493
+ ctx.paths,
2494
+ target.memory.body,
2495
+ { limit: others.length }
2496
+ );
2497
+ if (!result) return null;
2498
+ const map = /* @__PURE__ */ new Map();
2499
+ for (const hit of result.hits) map.set(hit.id, hit.score);
2500
+ return map;
2501
+ }
2502
+
2503
+ // src/tools/precommit-check.ts
2504
+ import { z as z28 } from "zod";
2505
+ var PreCommitCheckInputSchema = {
2506
+ diff: z28.string().optional().describe(
2507
+ "Raw unified diff text to scan. If omitted, only `paths` is used. When called from a pre-commit hook, pipe the output of `git diff --cached`."
2508
+ ),
2509
+ paths: z28.array(z28.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
2510
+ block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
2511
+ "When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
2512
+ ),
2513
+ semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index).")
2514
+ };
2515
+ async function preCommitCheck(input, ctx) {
2516
+ if (!input.diff && input.paths.length === 0) {
2517
+ return {
2518
+ should_block: false,
2519
+ summary: { anti_patterns: 0, relevant_memories: 0, stale_anchors: 0 },
2520
+ warnings: [],
2521
+ relevant_memories: [],
2522
+ stale_anchors: [],
2523
+ notice: "Nothing to check \u2014 provide either `diff` or `paths`."
2524
+ };
2525
+ }
2526
+ const apResult = await antiPatternsCheck({
2527
+ diff: input.diff,
2528
+ paths: input.paths,
2529
+ limit: 20,
2530
+ semantic: input.semantic
2531
+ }, ctx);
2532
+ const relevant = input.paths.length > 0 ? await memForFiles({ files: input.paths, include_module_contexts: false, track: false }, ctx) : { by_anchor: [], by_module: [], by_domain: [], module_contexts: [], inferred_modules: [] };
2533
+ const relevantMatches = [...relevant.by_anchor, ...relevant.by_module];
2534
+ const verifyResult = input.paths.length > 0 ? await memVerify({ update: false, id: void 0 }, ctx) : { results: [], summary: { checked: 0, fresh: 0, stale: 0, anchorless_skipped: 0, updated: 0 } };
2535
+ const filesTouching = new Set(relevantMatches.map((m) => m.id));
2536
+ const staleHits = verifyResult.results.filter((r) => r.stale && filesTouching.has(r.id));
2537
+ const blockOn = input.block_on;
2538
+ let should_block = false;
2539
+ if (blockOn !== "never") {
2540
+ const high = apResult.warnings.filter(
2541
+ (w) => w.confidence === "authoritative" || w.confidence === "trusted"
2542
+ );
2543
+ if (blockOn === "any" && (apResult.warnings.length > 0 || staleHits.length > 0)) should_block = true;
2544
+ if (blockOn === "high-confidence" && (high.length > 0 || staleHits.length > 0)) should_block = true;
2545
+ }
2546
+ const relevant_memories = relevantMatches.slice(0, 8).map((m) => ({
2547
+ id: m.id,
2548
+ type: m.type,
2549
+ confidence: String(m.confidence),
2550
+ body_preview: (m.body ?? "").split("\n").slice(0, 4).join("\n").slice(0, 250)
2551
+ }));
2552
+ return {
2553
+ should_block,
2554
+ summary: {
2555
+ anti_patterns: apResult.warnings.length,
2556
+ relevant_memories: relevant_memories.length,
2557
+ stale_anchors: staleHits.length
2558
+ },
2559
+ warnings: apResult.warnings,
2560
+ relevant_memories,
2561
+ stale_anchors: staleHits.map((r) => ({
2562
+ id: r.id,
2563
+ // The matching `relevantMatches` entry tells us which paths overlap.
2564
+ paths: relevantMatches.find((m) => m.id === r.id) ? input.paths.filter((p) => relevantMatches.some((m) => m.id === r.id)) : [],
2565
+ body_preview: r.reason ?? "anchored code drifted; verify before relying on this memory"
2566
+ }))
2567
+ };
2568
+ }
2569
+
2570
+ // src/prompts/bootstrap-project.ts
2571
+ import { z as z29 } from "zod";
2065
2572
  var BootstrapProjectArgsSchema = {
2066
- module: z25.string().optional().describe(
2573
+ module: z29.string().optional().describe(
2067
2574
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
2068
2575
  ),
2069
- focus: z25.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
2576
+ focus: z29.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
2070
2577
  };
2071
2578
  var ROOT_TEMPLATE = `# Project context
2072
2579
 
@@ -2148,10 +2655,10 @@ ${template}\`\`\`
2148
2655
  }
2149
2656
 
2150
2657
  // src/prompts/post-task.ts
2151
- import { z as z26 } from "zod";
2658
+ import { z as z30 } from "zod";
2152
2659
  var PostTaskArgsSchema = {
2153
- task_summary: z26.string().optional().describe("One sentence describing what you just did"),
2154
- files_touched: z26.array(z26.string()).optional().describe("Files you created or modified during the task")
2660
+ task_summary: z30.string().optional().describe("One sentence describing what you just did"),
2661
+ files_touched: z30.array(z30.string()).optional().describe("Files you created or modified during the task")
2155
2662
  };
2156
2663
  function postTaskPrompt(args, ctx) {
2157
2664
  const taskLine = args.task_summary ? `
@@ -2233,12 +2740,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
2233
2740
  }
2234
2741
 
2235
2742
  // src/prompts/import-docs.ts
2236
- import { z as z27 } from "zod";
2743
+ import { z as z31 } from "zod";
2237
2744
  var ImportDocsArgsSchema = {
2238
- content: z27.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
2239
- source: z27.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
2240
- scope: z27.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
2241
- dry_run: z27.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
2745
+ content: z31.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
2746
+ source: z31.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
2747
+ scope: z31.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
2748
+ dry_run: z31.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
2242
2749
  };
2243
2750
  function importDocsPrompt(args, ctx) {
2244
2751
  const sourceLine = args.source ? `
@@ -2374,7 +2881,7 @@ function summarizeTools(events) {
2374
2881
 
2375
2882
  // src/server.ts
2376
2883
  var SERVER_NAME = "haive";
2377
- var SERVER_VERSION = "0.5.0";
2884
+ var SERVER_VERSION = "0.7.0";
2378
2885
  function jsonResult(data) {
2379
2886
  return {
2380
2887
  content: [
@@ -2921,6 +3428,104 @@ function createHaiveServer(options = {}) {
2921
3428
  return jsonResult(await antiPatternsCheck(input, context));
2922
3429
  }
2923
3430
  );
3431
+ server.tool(
3432
+ "mem_distill",
3433
+ [
3434
+ "Cluster recurring observations / failed attempts so a human can collapse",
3435
+ "N similar memories into one richer convention/gotcha. Cheap heuristic",
3436
+ "(anchor path overlap + body keyword overlap) \u2014 no embeddings required.",
3437
+ "",
3438
+ "USE periodically (e.g. monthly) to prevent memory pollution from agents",
3439
+ "saving the same observation many times.",
3440
+ "",
3441
+ "PARAMETERS:",
3442
+ " since_days \u2014 only consider memories from the last N days (default 30)",
3443
+ " min_cluster \u2014 minimum cluster size to surface (default 3)",
3444
+ " type_filter \u2014 'gotcha' | 'attempt' | 'all' (default 'gotcha')",
3445
+ " scope \u2014 'personal' | 'team' | 'module' | 'any' (default 'any')",
3446
+ "",
3447
+ "RETURNS: { scanned, singletons, clusters: [{ suggested_topic, member_ids, ... }] }",
3448
+ "Output is advisory \u2014 nothing is written to disk."
3449
+ ].join("\n"),
3450
+ MemDistillInputSchema,
3451
+ async (input) => {
3452
+ tracker.record("mem_distill", `${input.type_filter}/since=${input.since_days}d`);
3453
+ return jsonResult(await memDistill(input, context));
3454
+ }
3455
+ );
3456
+ server.tool(
3457
+ "why_this_decision",
3458
+ [
3459
+ "Trace the genealogy of a memory (especially decision/architecture):",
3460
+ "the memory itself + memories explicitly linked via related_ids + memories",
3461
+ "anchored to overlapping paths + recent commits touching those paths.",
3462
+ "",
3463
+ "USE WHEN you find a memory and need to understand WHY it was made and",
3464
+ "what surrounds it. One call instead of 4-5 manual lookups.",
3465
+ "",
3466
+ "PARAMETERS:",
3467
+ " id \u2014 memory id (required)",
3468
+ " git_log_limit \u2014 how many recent commits per anchor path (default 5)",
3469
+ "",
3470
+ "RETURNS: { decision, related: [...], path_neighbors: [...], recent_commits: [...] }"
3471
+ ].join("\n"),
3472
+ WhyThisDecisionInputSchema,
3473
+ async (input) => {
3474
+ tracker.record("why_this_decision", input.id);
3475
+ return jsonResult(await whyThisDecision(input, context));
3476
+ }
3477
+ );
3478
+ server.tool(
3479
+ "mem_conflicts_with",
3480
+ [
3481
+ "Detect memories that potentially CONTRADICT a given memory.",
3482
+ "",
3483
+ "USE BEFORE relying on a memory's advice \u2014 surfaces 'another memory says",
3484
+ "the opposite'. Detection uses several heuristics layered together:",
3485
+ "",
3486
+ " 1. Opposite status \u2014 validated vs rejected on overlapping topic",
3487
+ " 2. attempt-vs-convention on overlapping anchor paths",
3488
+ " 3. Polarity keywords \u2014 'use X' vs 'do not use X' among semantic neighbors",
3489
+ " 4. Explicit #contradicts:<id> tags in either body",
3490
+ "",
3491
+ "PARAMETERS:",
3492
+ " id \u2014 memory id to check (required)",
3493
+ " min_score \u2014 minimum cosine similarity for semantic neighbors (default 0.5)",
3494
+ " semantic \u2014 use embeddings (default true)",
3495
+ "",
3496
+ "RETURNS: { found, target, scanned, conflicts: [{ id, reasons, similarity, ... }] }"
3497
+ ].join("\n"),
3498
+ MemConflictsInputSchema,
3499
+ async (input) => {
3500
+ tracker.record("mem_conflicts_with", input.id);
3501
+ return jsonResult(await memConflicts(input, context));
3502
+ }
3503
+ );
3504
+ server.tool(
3505
+ "pre_commit_check",
3506
+ [
3507
+ "One-shot 'should I block this commit?' check. Combines three signals:",
3508
+ "",
3509
+ " 1. anti_patterns_check \u2014 known gotchas/attempts that match the diff",
3510
+ " 2. mem_for_files \u2014 conventions/decisions anchored to touched files",
3511
+ " 3. mem_verify \u2014 memories whose anchors are stale (knowledge may be wrong)",
3512
+ "",
3513
+ "USE FROM A GIT HOOK or before finalizing a non-trivial change.",
3514
+ "",
3515
+ "PARAMETERS:",
3516
+ " diff \u2014 raw unified diff text (e.g. `git diff --cached`)",
3517
+ " paths \u2014 affected file paths (project-relative)",
3518
+ " block_on \u2014 'any' | 'high-confidence' (default) | 'never'",
3519
+ " semantic \u2014 use embeddings in anti_patterns_check (default true)",
3520
+ "",
3521
+ "RETURNS: { should_block, summary, warnings, relevant_memories, stale_anchors }"
3522
+ ].join("\n"),
3523
+ PreCommitCheckInputSchema,
3524
+ async (input) => {
3525
+ tracker.record("pre_commit_check", `${input.paths.length}p`);
3526
+ return jsonResult(await preCommitCheck(input, context));
3527
+ }
3528
+ );
2924
3529
  server.tool(
2925
3530
  "mem_diff",
2926
3531
  [
@@ -2983,7 +3588,11 @@ export {
2983
3588
  createHaiveServer,
2984
3589
  getBriefing,
2985
3590
  getRecap,
3591
+ memConflicts,
3592
+ memDistill,
2986
3593
  memRelevantTo,
3594
+ preCommitCheck,
3595
+ whyThisDecision,
2987
3596
  whyThisFile
2988
3597
  };
2989
3598
  //# sourceMappingURL=server.js.map