@hiveai/mcp 0.10.5 → 0.10.9
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 +276 -240
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +51 -51
- package/dist/server.js +276 -240
- package/dist/server.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1417,9 +1417,8 @@ async function memSessionEnd(input, ctx) {
|
|
|
1417
1417
|
}
|
|
1418
1418
|
|
|
1419
1419
|
// src/tools/get-briefing.ts
|
|
1420
|
-
import { readFile as
|
|
1421
|
-
import { existsSync as
|
|
1422
|
-
import path9 from "path";
|
|
1420
|
+
import { readFile as readFile4, writeFile as writeFile11 } from "fs/promises";
|
|
1421
|
+
import { existsSync as existsSync19 } from "fs";
|
|
1423
1422
|
import {
|
|
1424
1423
|
allocateBudget,
|
|
1425
1424
|
DEFAULT_AUTO_PROMOTE_RULE,
|
|
@@ -1428,11 +1427,9 @@ import {
|
|
|
1428
1427
|
extractActionsBriefBody,
|
|
1429
1428
|
getUsage as getUsage5,
|
|
1430
1429
|
inferModulesFromPaths as inferModulesFromPaths2,
|
|
1431
|
-
isGlobPath,
|
|
1432
|
-
isRetiredMemory,
|
|
1433
1430
|
isAutoPromoteEligible,
|
|
1434
1431
|
isDecaying,
|
|
1435
|
-
|
|
1432
|
+
isRetiredMemory,
|
|
1436
1433
|
literalMatchesAllTokens as literalMatchesAllTokens2,
|
|
1437
1434
|
literalMatchesAnyToken as literalMatchesAnyToken2,
|
|
1438
1435
|
loadCodeMap,
|
|
@@ -1440,7 +1437,6 @@ import {
|
|
|
1440
1437
|
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
1441
1438
|
loadUsageIndex as loadUsageIndex7,
|
|
1442
1439
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
1443
|
-
pathsOverlap,
|
|
1444
1440
|
queryCodeMap,
|
|
1445
1441
|
resolveBriefingBudget,
|
|
1446
1442
|
serializeMemory as serializeMemory9,
|
|
@@ -1452,6 +1448,152 @@ import {
|
|
|
1452
1448
|
writeBriefingMarker
|
|
1453
1449
|
} from "@hiveai/core";
|
|
1454
1450
|
import { z as z17 } from "zod";
|
|
1451
|
+
|
|
1452
|
+
// src/tools/briefing-helpers.ts
|
|
1453
|
+
import { readdir as readdir3, readFile as readFile3 } from "fs/promises";
|
|
1454
|
+
import { existsSync as existsSync18 } from "fs";
|
|
1455
|
+
import path9 from "path";
|
|
1456
|
+
import { isGlobPath, isStackPackSeed, pathsOverlap } from "@hiveai/core";
|
|
1457
|
+
function compactSummary(body) {
|
|
1458
|
+
for (const line of body.split("\n")) {
|
|
1459
|
+
const trimmed = line.replace(/^#+\s*/, "").trim();
|
|
1460
|
+
if (trimmed.length > 0) return trimmed.slice(0, 120);
|
|
1461
|
+
}
|
|
1462
|
+
return body.slice(0, 120);
|
|
1463
|
+
}
|
|
1464
|
+
function classifyMemoryPriority(memory, loaded, inputFiles, inputSymbols) {
|
|
1465
|
+
const fm = loaded?.memory.frontmatter;
|
|
1466
|
+
const directAnchor = Boolean(
|
|
1467
|
+
fm && inputFiles.length > 0 && fm.anchor.paths.some((p) => inputFiles.some((file) => pathsOverlap(p, file)))
|
|
1468
|
+
);
|
|
1469
|
+
const directSymbol = Boolean(
|
|
1470
|
+
fm && inputSymbols.length > 0 && fm.anchor.symbols.some(
|
|
1471
|
+
(sym) => inputSymbols.some((wanted) => wanted.toLowerCase() === sym.toLowerCase())
|
|
1472
|
+
)
|
|
1473
|
+
);
|
|
1474
|
+
const strongSemantic = (memory.semantic_score ?? 0) >= 0.65;
|
|
1475
|
+
const usefulSemantic = (memory.semantic_score ?? 0) >= 0.35;
|
|
1476
|
+
if (fm?.requires_human_approval || directAnchor || directSymbol || memory.type === "attempt" && (memory.match_quality === "exact" || strongSemantic) || memory.type === "skill" && (memory.match_quality === "exact" || strongSemantic)) {
|
|
1477
|
+
return "must_read";
|
|
1478
|
+
}
|
|
1479
|
+
if (isStackPackSeed(fm)) {
|
|
1480
|
+
return "background";
|
|
1481
|
+
}
|
|
1482
|
+
if (memory.type === "skill" || memory.reasons.includes("module") || memory.reasons.includes("domain") || memory.match_quality === "exact" || usefulSemantic) {
|
|
1483
|
+
return "useful";
|
|
1484
|
+
}
|
|
1485
|
+
return "background";
|
|
1486
|
+
}
|
|
1487
|
+
function priorityRank(priority) {
|
|
1488
|
+
return priority === "must_read" ? 3 : priority === "useful" ? 2 : 1;
|
|
1489
|
+
}
|
|
1490
|
+
function classifyBriefingQuality(memories, context) {
|
|
1491
|
+
const mustRead = memories.filter((m) => m.priority === "must_read").length;
|
|
1492
|
+
const useful = memories.filter((m) => m.priority === "useful").length;
|
|
1493
|
+
const background = memories.filter((m) => m.priority === "background").length;
|
|
1494
|
+
const weakSemantic = memories.filter(
|
|
1495
|
+
(m) => m.reasons.length === 1 && m.reasons.includes("semantic") && (m.semantic_score ?? 0) > 0 && (m.semantic_score ?? 0) < 0.35
|
|
1496
|
+
).length;
|
|
1497
|
+
const reasons = [];
|
|
1498
|
+
if (memories.length === 0) reasons.push("no memories matched the task or files");
|
|
1499
|
+
if (context.isTemplateContext && !context.autoContextGenerated) reasons.push("project context is still a template");
|
|
1500
|
+
if (!context.hasLastSession) reasons.push("no previous session recap");
|
|
1501
|
+
if (mustRead > 0) reasons.push(`${mustRead} must_read memor${mustRead === 1 ? "y" : "ies"} matched directly`);
|
|
1502
|
+
if (useful > 0) reasons.push(`${useful} useful memor${useful === 1 ? "y" : "ies"} matched`);
|
|
1503
|
+
if (background > useful + mustRead && background > 2) reasons.push(`${background} background memories dominate the result`);
|
|
1504
|
+
if (weakSemantic > 0) reasons.push(`${weakSemantic} weak semantic-only match${weakSemantic === 1 ? "" : "es"}`);
|
|
1505
|
+
if (context.searchMode === "literal_fallback") reasons.push("semantic index unavailable or empty; literal fallback used");
|
|
1506
|
+
if (memories.length === 0 || mustRead === 0 && useful === 0) {
|
|
1507
|
+
return { level: "thin", reasons };
|
|
1508
|
+
}
|
|
1509
|
+
if (background > useful + mustRead && background > 2) {
|
|
1510
|
+
return { level: "noisy", reasons };
|
|
1511
|
+
}
|
|
1512
|
+
return { level: "strong", reasons };
|
|
1513
|
+
}
|
|
1514
|
+
function explainWhySurfaced(memory, loaded, inputFiles, inferredModules) {
|
|
1515
|
+
const why = [];
|
|
1516
|
+
const fm = loaded?.memory.frontmatter;
|
|
1517
|
+
if (memory.reasons.includes("anchor") && fm) {
|
|
1518
|
+
const matching = fm.anchor.paths.filter(
|
|
1519
|
+
(p) => inputFiles.length === 0 || inputFiles.some((file) => pathsOverlap(p, file))
|
|
1520
|
+
);
|
|
1521
|
+
if (matching.length > 0) {
|
|
1522
|
+
const exact = matching.filter(
|
|
1523
|
+
(p) => !isGlobPath(p) && inputFiles.some((file) => p === file || pathsOverlap(p, file))
|
|
1524
|
+
);
|
|
1525
|
+
const glob = matching.filter((p) => isGlobPath(p));
|
|
1526
|
+
if (exact.length > 0) {
|
|
1527
|
+
why.push(`Exact/file anchor match: ${exact.slice(0, 4).join(", ")}`);
|
|
1528
|
+
}
|
|
1529
|
+
if (glob.length > 0) {
|
|
1530
|
+
why.push(`Glob anchor match: ${glob.slice(0, 4).join(", ")}`);
|
|
1531
|
+
}
|
|
1532
|
+
if (exact.length === 0 && glob.length === 0) {
|
|
1533
|
+
why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
|
|
1534
|
+
}
|
|
1535
|
+
} else if (fm.anchor.paths.length > 0) {
|
|
1536
|
+
why.push(`Pulled by related anchor: ${fm.anchor.paths.slice(0, 4).join(", ")}`);
|
|
1537
|
+
}
|
|
1538
|
+
if (fm.anchor.symbols.length > 0) {
|
|
1539
|
+
why.push(`Anchor symbol${fm.anchor.symbols.length === 1 ? "" : "s"}: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (memory.reasons.includes("symbol") && fm) {
|
|
1543
|
+
why.push(`Explicit symbol match: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
|
|
1544
|
+
}
|
|
1545
|
+
if (memory.reasons.includes("module")) {
|
|
1546
|
+
const moduleHints = [
|
|
1547
|
+
...memory.module ? [memory.module] : [],
|
|
1548
|
+
...memory.tags.filter((tag) => inferredModules.includes(tag))
|
|
1549
|
+
];
|
|
1550
|
+
const shown = moduleHints.length > 0 ? [...new Set(moduleHints)].join(", ") : inferredModules.join(", ");
|
|
1551
|
+
why.push(shown ? `Matched inferred module/tag: ${shown}` : "Matched inferred module context.");
|
|
1552
|
+
}
|
|
1553
|
+
if (memory.reasons.includes("domain")) {
|
|
1554
|
+
why.push("Matched inferred domain from the target file paths.");
|
|
1555
|
+
}
|
|
1556
|
+
if (memory.reasons.includes("semantic")) {
|
|
1557
|
+
const score = memory.semantic_score !== void 0 ? ` score=${Math.round(memory.semantic_score * 100) / 100}` : "";
|
|
1558
|
+
why.push(`${memory.match_quality === "exact" ? "Literal task match" : "Semantic/task relevance"}${score}.`);
|
|
1559
|
+
}
|
|
1560
|
+
why.push(`Confidence: ${memory.confidence}; read ${memory.read_count} time${memory.read_count === 1 ? "" : "s"}.`);
|
|
1561
|
+
if (memory.type === "attempt") why.push("Failed-approach record; read before repeating the same path.");
|
|
1562
|
+
if (memory.type === "skill") why.push("Skill (reusable procedure/playbook) \u2014 follow the steps described when doing this type of task.");
|
|
1563
|
+
if (memory.status === "proposed" || memory.status === "draft") {
|
|
1564
|
+
why.push("Unvalidated record; use cautiously or ask a human before treating it as policy.");
|
|
1565
|
+
}
|
|
1566
|
+
return why;
|
|
1567
|
+
}
|
|
1568
|
+
async function trySemanticHits(ctx, task, limit) {
|
|
1569
|
+
let mod;
|
|
1570
|
+
try {
|
|
1571
|
+
mod = await import("@hiveai/embeddings");
|
|
1572
|
+
} catch {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
const result = await mod.semanticSearch(ctx.paths, task, { limit });
|
|
1576
|
+
if (!result) return null;
|
|
1577
|
+
return result.hits.map((h) => ({ id: h.id, score: h.score }));
|
|
1578
|
+
}
|
|
1579
|
+
async function loadModuleContexts2(ctx, modules) {
|
|
1580
|
+
if (modules.length === 0) return [];
|
|
1581
|
+
if (!existsSync18(ctx.paths.modulesContextDir)) return [];
|
|
1582
|
+
const available = new Set(
|
|
1583
|
+
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
1584
|
+
);
|
|
1585
|
+
const out = [];
|
|
1586
|
+
for (const m of modules) {
|
|
1587
|
+
if (!available.has(m)) continue;
|
|
1588
|
+
const file = path9.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
1589
|
+
if (existsSync18(file)) {
|
|
1590
|
+
out.push({ name: m, content: await readFile3(file, "utf8") });
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return out;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// src/tools/get-briefing.ts
|
|
1455
1597
|
var GetBriefingInputSchema = {
|
|
1456
1598
|
task: z17.string().optional().describe(
|
|
1457
1599
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
@@ -1497,7 +1639,7 @@ async function getBriefing(input, ctx) {
|
|
|
1497
1639
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
1498
1640
|
let byId = /* @__PURE__ */ new Map();
|
|
1499
1641
|
let lastSession;
|
|
1500
|
-
if (
|
|
1642
|
+
if (existsSync19(ctx.paths.memoriesDir)) {
|
|
1501
1643
|
const allLoaded = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
|
|
1502
1644
|
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
1503
1645
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
@@ -1582,34 +1724,25 @@ async function getBriefing(input, ctx) {
|
|
|
1582
1724
|
if (input.task) {
|
|
1583
1725
|
const tokens = tokenizeQuery2(input.task);
|
|
1584
1726
|
const andHits = allMemories.filter((m) => literalMatchesAllTokens2(m.memory, tokens));
|
|
1585
|
-
for (const loaded of andHits)
|
|
1586
|
-
addOrUpdate(loaded, "semantic", void 0, "exact");
|
|
1587
|
-
}
|
|
1727
|
+
for (const loaded of andHits) addOrUpdate(loaded, "semantic", void 0, "exact");
|
|
1588
1728
|
if (andHits.length === 0 && tokens.length > 1) {
|
|
1589
1729
|
for (const loaded of allMemories) {
|
|
1590
|
-
if (literalMatchesAnyToken2(loaded.memory, tokens))
|
|
1591
|
-
addOrUpdate(loaded, "semantic", void 0, "partial");
|
|
1592
|
-
}
|
|
1730
|
+
if (literalMatchesAnyToken2(loaded.memory, tokens)) addOrUpdate(loaded, "semantic", void 0, "partial");
|
|
1593
1731
|
}
|
|
1594
1732
|
}
|
|
1595
1733
|
if (semanticHits) {
|
|
1596
1734
|
for (const hit of semanticHits) {
|
|
1597
|
-
if (hit.score < input.min_semantic_score)
|
|
1598
|
-
const existing = seen.get(hit.id);
|
|
1599
|
-
if (!existing) continue;
|
|
1600
|
-
}
|
|
1735
|
+
if (hit.score < input.min_semantic_score && !seen.has(hit.id)) continue;
|
|
1601
1736
|
const loaded = byId.get(hit.id);
|
|
1602
1737
|
if (loaded) addOrUpdate(loaded, "semantic", hit.score, "semantic");
|
|
1603
1738
|
}
|
|
1604
1739
|
}
|
|
1605
1740
|
}
|
|
1606
1741
|
const ranked = [...seen.values()].sort((a, b) => {
|
|
1607
|
-
const
|
|
1608
|
-
const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + // attempt = negative knowledge, surface first to prevent repeating mistakes
|
|
1609
|
-
(m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("symbol") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
|
|
1742
|
+
const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("symbol") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
|
|
1610
1743
|
const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
|
|
1611
|
-
const sa =
|
|
1612
|
-
const sb =
|
|
1744
|
+
const sa = priorityRank(classifyMemoryPriority(a, byId.get(a.id), input.files, input.symbols)) * 100 + reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
|
|
1745
|
+
const sb = priorityRank(classifyMemoryPriority(b, byId.get(b.id), input.files, input.symbols)) * 100 + reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
|
|
1613
1746
|
return sb - sa;
|
|
1614
1747
|
});
|
|
1615
1748
|
for (const mem of ranked.slice(0, briefingMaxMemories)) {
|
|
@@ -1638,11 +1771,7 @@ async function getBriefing(input, ctx) {
|
|
|
1638
1771
|
if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
|
|
1639
1772
|
const newFm = { ...loaded.memory.frontmatter, status: "validated" };
|
|
1640
1773
|
try {
|
|
1641
|
-
await writeFile11(
|
|
1642
|
-
loaded.filePath,
|
|
1643
|
-
serializeMemory9({ frontmatter: newFm, body: loaded.memory.body }),
|
|
1644
|
-
"utf8"
|
|
1645
|
-
);
|
|
1774
|
+
await writeFile11(loaded.filePath, serializeMemory9({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
|
|
1646
1775
|
m.status = "validated";
|
|
1647
1776
|
m.confidence = "trusted";
|
|
1648
1777
|
} catch {
|
|
@@ -1650,12 +1779,12 @@ async function getBriefing(input, ctx) {
|
|
|
1650
1779
|
}
|
|
1651
1780
|
}
|
|
1652
1781
|
}
|
|
1653
|
-
const projectContextRaw = input.include_project_context &&
|
|
1782
|
+
const projectContextRaw = input.include_project_context && existsSync19(ctx.paths.projectContext) ? await readFile4(ctx.paths.projectContext, "utf8") : "";
|
|
1654
1783
|
const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
|
|
1655
1784
|
const setupWarnings = [];
|
|
1656
1785
|
let autoContextGenerated = false;
|
|
1657
1786
|
let projectContext = isTemplateContext ? "" : projectContextRaw;
|
|
1658
|
-
if ((isTemplateContext || !
|
|
1787
|
+
if ((isTemplateContext || !existsSync19(ctx.paths.projectContext)) && input.include_project_context) {
|
|
1659
1788
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
1660
1789
|
if (haiveConfig.autoContext) {
|
|
1661
1790
|
const codeMap = await loadCodeMap(ctx.paths);
|
|
@@ -1691,15 +1820,9 @@ async function getBriefing(input, ctx) {
|
|
|
1691
1820
|
);
|
|
1692
1821
|
}
|
|
1693
1822
|
} else {
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
);
|
|
1698
|
-
} else {
|
|
1699
|
-
setupWarnings.push(
|
|
1700
|
-
"No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
|
|
1701
|
-
);
|
|
1702
|
-
}
|
|
1823
|
+
setupWarnings.push(
|
|
1824
|
+
isTemplateContext ? "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." : "No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
|
|
1825
|
+
);
|
|
1703
1826
|
}
|
|
1704
1827
|
}
|
|
1705
1828
|
const moduleContents = briefingIncludeModules ? await loadModuleContexts2(ctx, inferred) : [];
|
|
@@ -1762,10 +1885,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
1762
1885
|
const createdAt = loaded?.memory.frontmatter.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1763
1886
|
if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
|
|
1764
1887
|
}
|
|
1765
|
-
const formattedMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : input.format === "actions" ? trimmedMemories.map((m) => ({
|
|
1766
|
-
...m,
|
|
1767
|
-
body: extractActionsBriefBody(m.body)
|
|
1768
|
-
})) : trimmedMemories;
|
|
1888
|
+
const formattedMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : input.format === "actions" ? trimmedMemories.map((m) => ({ ...m, body: extractActionsBriefBody(m.body) })) : trimmedMemories;
|
|
1769
1889
|
const outputMemories = formattedMemories.map((m) => ({
|
|
1770
1890
|
...m,
|
|
1771
1891
|
priority: classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols),
|
|
@@ -1780,8 +1900,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
1780
1900
|
let symbolLocations;
|
|
1781
1901
|
const symbolsToLookup = new Set(input.symbols);
|
|
1782
1902
|
for (const m of outputMemories) {
|
|
1783
|
-
const
|
|
1784
|
-
for (const sym of loaded?.memory.frontmatter.anchor.symbols ?? []) {
|
|
1903
|
+
for (const sym of byId.get(m.id)?.memory.frontmatter.anchor.symbols ?? []) {
|
|
1785
1904
|
symbolsToLookup.add(sym);
|
|
1786
1905
|
}
|
|
1787
1906
|
}
|
|
@@ -1809,41 +1928,37 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
1809
1928
|
}
|
|
1810
1929
|
}
|
|
1811
1930
|
const actionRequired = [];
|
|
1812
|
-
|
|
1813
|
-
const
|
|
1814
|
-
if (!loaded?.memory.frontmatter.requires_human_approval) continue;
|
|
1815
|
-
const bodyLines = loaded.memory.body.split("\n");
|
|
1931
|
+
const extractActionItem = (id, body) => {
|
|
1932
|
+
const bodyLines = body.split("\n");
|
|
1816
1933
|
const quoteBlock = bodyLines.filter((l) => l.startsWith("> ")).map((l) => l.slice(2)).join(" ").replace(/^\*«\s*/, "").replace(/\s*»\*$/, "").trim();
|
|
1817
1934
|
const headingLine = bodyLines.find((l) => l.startsWith("## "));
|
|
1818
|
-
const summary = headingLine?.replace(/^##\s*/, "").trim() ??
|
|
1819
|
-
|
|
1820
|
-
id
|
|
1935
|
+
const summary = headingLine?.replace(/^##\s*/, "").trim() ?? id;
|
|
1936
|
+
return {
|
|
1937
|
+
id,
|
|
1821
1938
|
summary,
|
|
1822
|
-
developer_message: quoteBlock || `Une modification externe potentiellement incompatible a \xE9t\xE9 d\xE9tect\xE9e (${
|
|
1823
|
-
}
|
|
1939
|
+
developer_message: quoteBlock || `Une modification externe potentiellement incompatible a \xE9t\xE9 d\xE9tect\xE9e (${id}). Veux-tu que j'analyse l'impact et que je propose des mises \xE0 jour ?`
|
|
1940
|
+
};
|
|
1941
|
+
};
|
|
1942
|
+
for (const m of outputMemories) {
|
|
1943
|
+
const loaded = byId.get(m.id);
|
|
1944
|
+
if (loaded?.memory.frontmatter.requires_human_approval) {
|
|
1945
|
+
actionRequired.push(extractActionItem(m.id, loaded.memory.body));
|
|
1946
|
+
}
|
|
1824
1947
|
}
|
|
1825
|
-
if (
|
|
1948
|
+
if (existsSync19(ctx.paths.memoriesDir)) {
|
|
1826
1949
|
const allMems = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
|
|
1827
1950
|
for (const { memory } of allMems) {
|
|
1828
1951
|
const fm = memory.frontmatter;
|
|
1829
1952
|
if (!fm.requires_human_approval) continue;
|
|
1830
1953
|
if (fm.status === "rejected" || fm.status === "deprecated") continue;
|
|
1831
1954
|
if (actionRequired.some((a) => a.id === fm.id)) continue;
|
|
1832
|
-
|
|
1833
|
-
const quoteBlock = bodyLines.filter((l) => l.startsWith("> ")).map((l) => l.slice(2)).join(" ").replace(/^\*«\s*/, "").replace(/\s*»\*$/, "").trim();
|
|
1834
|
-
const headingLine = bodyLines.find((l) => l.startsWith("## "));
|
|
1835
|
-
const summary = headingLine?.replace(/^##\s*/, "").trim() ?? fm.id;
|
|
1836
|
-
actionRequired.push({
|
|
1837
|
-
id: fm.id,
|
|
1838
|
-
summary,
|
|
1839
|
-
developer_message: quoteBlock || `Une modification externe potentiellement incompatible a \xE9t\xE9 d\xE9tect\xE9e (${fm.id}). Veux-tu que j'analyse l'impact et que je propose des mises \xE0 jour ?`
|
|
1840
|
-
});
|
|
1955
|
+
actionRequired.push(extractActionItem(fm.id, memory.body));
|
|
1841
1956
|
}
|
|
1842
1957
|
}
|
|
1843
1958
|
const pendingDistillFile = pendingDistillPath(ctx);
|
|
1844
|
-
if (
|
|
1959
|
+
if (existsSync19(pendingDistillFile)) {
|
|
1845
1960
|
try {
|
|
1846
|
-
const raw = await
|
|
1961
|
+
const raw = await readFile4(pendingDistillFile, "utf8");
|
|
1847
1962
|
const pd = JSON.parse(raw);
|
|
1848
1963
|
const ageMs = Date.now() - new Date(pd.session_end).getTime();
|
|
1849
1964
|
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -1870,7 +1985,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
1870
1985
|
}
|
|
1871
1986
|
}
|
|
1872
1987
|
const memoriesEmpty = outputMemories.length === 0;
|
|
1873
|
-
const hasMemoriesDir =
|
|
1988
|
+
const hasMemoriesDir = existsSync19(ctx.paths.memoriesDir);
|
|
1874
1989
|
const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
|
|
1875
1990
|
const hasUnguessableSignal = outputMemories.some(
|
|
1876
1991
|
(m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
|
|
@@ -1917,7 +2032,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
1917
2032
|
"No team-specific policy matched these files/task \u2014 nothing here a capable model can't infer. The auto-generated project context was trimmed to keep this briefing near-zero-cost; proceed with normal Read/Grep."
|
|
1918
2033
|
);
|
|
1919
2034
|
}
|
|
1920
|
-
if (
|
|
2035
|
+
if (existsSync19(ctx.paths.haiveDir)) {
|
|
1921
2036
|
await writeBriefingMarker(ctx.paths, {
|
|
1922
2037
|
sessionId: process.env.HAIVE_SESSION_ID,
|
|
1923
2038
|
...input.task ? { task: input.task } : {},
|
|
@@ -1965,144 +2080,6 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
1965
2080
|
}
|
|
1966
2081
|
};
|
|
1967
2082
|
}
|
|
1968
|
-
function compactSummary(body) {
|
|
1969
|
-
for (const line of body.split("\n")) {
|
|
1970
|
-
const trimmed = line.replace(/^#+\s*/, "").trim();
|
|
1971
|
-
if (trimmed.length > 0) return trimmed.slice(0, 120);
|
|
1972
|
-
}
|
|
1973
|
-
return body.slice(0, 120);
|
|
1974
|
-
}
|
|
1975
|
-
function classifyMemoryPriority(memory, loaded, inputFiles, inputSymbols) {
|
|
1976
|
-
const fm = loaded?.memory.frontmatter;
|
|
1977
|
-
const directAnchor = Boolean(
|
|
1978
|
-
fm && inputFiles.length > 0 && fm.anchor.paths.some((p) => inputFiles.some((file) => pathsOverlap(p, file)))
|
|
1979
|
-
);
|
|
1980
|
-
const directSymbol = Boolean(
|
|
1981
|
-
fm && inputSymbols.length > 0 && fm.anchor.symbols.some(
|
|
1982
|
-
(sym) => inputSymbols.some((wanted) => wanted.toLowerCase() === sym.toLowerCase())
|
|
1983
|
-
)
|
|
1984
|
-
);
|
|
1985
|
-
const strongSemantic = (memory.semantic_score ?? 0) >= 0.65;
|
|
1986
|
-
const usefulSemantic = (memory.semantic_score ?? 0) >= 0.35;
|
|
1987
|
-
if (fm?.requires_human_approval || directAnchor || directSymbol || memory.type === "attempt" && (memory.match_quality === "exact" || strongSemantic) || memory.type === "skill" && (memory.match_quality === "exact" || strongSemantic)) {
|
|
1988
|
-
return "must_read";
|
|
1989
|
-
}
|
|
1990
|
-
if (isStackPackSeed(fm)) {
|
|
1991
|
-
return "background";
|
|
1992
|
-
}
|
|
1993
|
-
if (memory.type === "skill" || memory.reasons.includes("module") || memory.reasons.includes("domain") || memory.match_quality === "exact" || usefulSemantic) {
|
|
1994
|
-
return "useful";
|
|
1995
|
-
}
|
|
1996
|
-
return "background";
|
|
1997
|
-
}
|
|
1998
|
-
function priorityRank(priority) {
|
|
1999
|
-
return priority === "must_read" ? 3 : priority === "useful" ? 2 : 1;
|
|
2000
|
-
}
|
|
2001
|
-
function classifyBriefingQuality(memories, context) {
|
|
2002
|
-
const mustRead = memories.filter((m) => m.priority === "must_read").length;
|
|
2003
|
-
const useful = memories.filter((m) => m.priority === "useful").length;
|
|
2004
|
-
const background = memories.filter((m) => m.priority === "background").length;
|
|
2005
|
-
const weakSemantic = memories.filter(
|
|
2006
|
-
(m) => m.reasons.length === 1 && m.reasons.includes("semantic") && (m.semantic_score ?? 0) > 0 && (m.semantic_score ?? 0) < 0.35
|
|
2007
|
-
).length;
|
|
2008
|
-
const reasons = [];
|
|
2009
|
-
if (memories.length === 0) reasons.push("no memories matched the task or files");
|
|
2010
|
-
if (context.isTemplateContext && !context.autoContextGenerated) reasons.push("project context is still a template");
|
|
2011
|
-
if (!context.hasLastSession) reasons.push("no previous session recap");
|
|
2012
|
-
if (mustRead > 0) reasons.push(`${mustRead} must_read memor${mustRead === 1 ? "y" : "ies"} matched directly`);
|
|
2013
|
-
if (useful > 0) reasons.push(`${useful} useful memor${useful === 1 ? "y" : "ies"} matched`);
|
|
2014
|
-
if (background > useful + mustRead && background > 2) reasons.push(`${background} background memories dominate the result`);
|
|
2015
|
-
if (weakSemantic > 0) reasons.push(`${weakSemantic} weak semantic-only match${weakSemantic === 1 ? "" : "es"}`);
|
|
2016
|
-
if (context.searchMode === "literal_fallback") reasons.push("semantic index unavailable or empty; literal fallback used");
|
|
2017
|
-
if (memories.length === 0 || mustRead === 0 && useful === 0) {
|
|
2018
|
-
return { level: "thin", reasons };
|
|
2019
|
-
}
|
|
2020
|
-
if (background > useful + mustRead && background > 2) {
|
|
2021
|
-
return { level: "noisy", reasons };
|
|
2022
|
-
}
|
|
2023
|
-
return { level: "strong", reasons };
|
|
2024
|
-
}
|
|
2025
|
-
function explainWhySurfaced(memory, loaded, inputFiles, inferredModules) {
|
|
2026
|
-
const why = [];
|
|
2027
|
-
const fm = loaded?.memory.frontmatter;
|
|
2028
|
-
if (memory.reasons.includes("anchor") && fm) {
|
|
2029
|
-
const matching = fm.anchor.paths.filter(
|
|
2030
|
-
(p) => inputFiles.length === 0 || inputFiles.some((file) => pathsOverlap(p, file))
|
|
2031
|
-
);
|
|
2032
|
-
if (matching.length > 0) {
|
|
2033
|
-
const exact = matching.filter(
|
|
2034
|
-
(p) => !isGlobPath(p) && inputFiles.some((file) => p === file || pathsOverlap(p, file))
|
|
2035
|
-
);
|
|
2036
|
-
const glob = matching.filter((p) => isGlobPath(p));
|
|
2037
|
-
if (exact.length > 0) {
|
|
2038
|
-
why.push(`Exact/file anchor match: ${exact.slice(0, 4).join(", ")}`);
|
|
2039
|
-
}
|
|
2040
|
-
if (glob.length > 0) {
|
|
2041
|
-
why.push(`Glob anchor match: ${glob.slice(0, 4).join(", ")}`);
|
|
2042
|
-
}
|
|
2043
|
-
if (exact.length === 0 && glob.length === 0) {
|
|
2044
|
-
why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
|
|
2045
|
-
}
|
|
2046
|
-
} else if (fm.anchor.paths.length > 0) {
|
|
2047
|
-
why.push(`Pulled by related anchor: ${fm.anchor.paths.slice(0, 4).join(", ")}`);
|
|
2048
|
-
}
|
|
2049
|
-
if (fm.anchor.symbols.length > 0) {
|
|
2050
|
-
why.push(`Anchor symbol${fm.anchor.symbols.length === 1 ? "" : "s"}: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
if (memory.reasons.includes("symbol") && fm) {
|
|
2054
|
-
why.push(`Explicit symbol match: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
|
|
2055
|
-
}
|
|
2056
|
-
if (memory.reasons.includes("module")) {
|
|
2057
|
-
const moduleHints = [
|
|
2058
|
-
...memory.module ? [memory.module] : [],
|
|
2059
|
-
...memory.tags.filter((tag) => inferredModules.includes(tag))
|
|
2060
|
-
];
|
|
2061
|
-
const shown = moduleHints.length > 0 ? [...new Set(moduleHints)].join(", ") : inferredModules.join(", ");
|
|
2062
|
-
why.push(shown ? `Matched inferred module/tag: ${shown}` : "Matched inferred module context.");
|
|
2063
|
-
}
|
|
2064
|
-
if (memory.reasons.includes("domain")) {
|
|
2065
|
-
why.push("Matched inferred domain from the target file paths.");
|
|
2066
|
-
}
|
|
2067
|
-
if (memory.reasons.includes("semantic")) {
|
|
2068
|
-
const score = memory.semantic_score !== void 0 ? ` score=${Math.round(memory.semantic_score * 100) / 100}` : "";
|
|
2069
|
-
why.push(`${memory.match_quality === "exact" ? "Literal task match" : "Semantic/task relevance"}${score}.`);
|
|
2070
|
-
}
|
|
2071
|
-
why.push(`Confidence: ${memory.confidence}; read ${memory.read_count} time${memory.read_count === 1 ? "" : "s"}.`);
|
|
2072
|
-
if (memory.type === "attempt") why.push("Failed-approach record; read before repeating the same path.");
|
|
2073
|
-
if (memory.type === "skill") why.push("Skill (reusable procedure/playbook) \u2014 follow the steps described when doing this type of task.");
|
|
2074
|
-
if (memory.status === "proposed" || memory.status === "draft") {
|
|
2075
|
-
why.push("Unvalidated record; use cautiously or ask a human before treating it as policy.");
|
|
2076
|
-
}
|
|
2077
|
-
return why;
|
|
2078
|
-
}
|
|
2079
|
-
async function trySemanticHits(ctx, task, limit) {
|
|
2080
|
-
let mod;
|
|
2081
|
-
try {
|
|
2082
|
-
mod = await import("@hiveai/embeddings");
|
|
2083
|
-
} catch {
|
|
2084
|
-
return null;
|
|
2085
|
-
}
|
|
2086
|
-
const result = await mod.semanticSearch(ctx.paths, task, { limit });
|
|
2087
|
-
if (!result) return null;
|
|
2088
|
-
return result.hits.map((h) => ({ id: h.id, score: h.score }));
|
|
2089
|
-
}
|
|
2090
|
-
async function loadModuleContexts2(ctx, modules) {
|
|
2091
|
-
if (modules.length === 0) return [];
|
|
2092
|
-
if (!existsSync18(ctx.paths.modulesContextDir)) return [];
|
|
2093
|
-
const available = new Set(
|
|
2094
|
-
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
2095
|
-
);
|
|
2096
|
-
const out = [];
|
|
2097
|
-
for (const m of modules) {
|
|
2098
|
-
if (!available.has(m)) continue;
|
|
2099
|
-
const file = path9.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
2100
|
-
if (existsSync18(file)) {
|
|
2101
|
-
out.push({ name: m, content: await readFile3(file, "utf8") });
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
return out;
|
|
2105
|
-
}
|
|
2106
2083
|
|
|
2107
2084
|
// src/tools/code-map.ts
|
|
2108
2085
|
import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hiveai/core";
|
|
@@ -2187,7 +2164,7 @@ function estimateFileEntryTokens(f) {
|
|
|
2187
2164
|
}
|
|
2188
2165
|
|
|
2189
2166
|
// src/tools/mem-diff.ts
|
|
2190
|
-
import { existsSync as
|
|
2167
|
+
import { existsSync as existsSync20 } from "fs";
|
|
2191
2168
|
import { loadMemoriesFromDir as loadMemoriesFromDir14 } from "@hiveai/core";
|
|
2192
2169
|
import { z as z19 } from "zod";
|
|
2193
2170
|
var MemDiffInputSchema = {
|
|
@@ -2195,7 +2172,7 @@ var MemDiffInputSchema = {
|
|
|
2195
2172
|
id_b: z19.string().min(1).describe("Second memory id")
|
|
2196
2173
|
};
|
|
2197
2174
|
async function memDiff(input, ctx) {
|
|
2198
|
-
if (!
|
|
2175
|
+
if (!existsSync20(ctx.paths.memoriesDir)) {
|
|
2199
2176
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
2200
2177
|
}
|
|
2201
2178
|
const all = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
|
|
@@ -2232,7 +2209,7 @@ async function memDiff(input, ctx) {
|
|
|
2232
2209
|
}
|
|
2233
2210
|
|
|
2234
2211
|
// src/tools/get-recap.ts
|
|
2235
|
-
import { existsSync as
|
|
2212
|
+
import { existsSync as existsSync21 } from "fs";
|
|
2236
2213
|
import { loadMemoriesFromDir as loadMemoriesFromDir15 } from "@hiveai/core";
|
|
2237
2214
|
import { z as z20 } from "zod";
|
|
2238
2215
|
var GetRecapInputSchema = {
|
|
@@ -2241,7 +2218,7 @@ var GetRecapInputSchema = {
|
|
|
2241
2218
|
)
|
|
2242
2219
|
};
|
|
2243
2220
|
async function getRecap(input, ctx) {
|
|
2244
|
-
if (!
|
|
2221
|
+
if (!existsSync21(ctx.paths.memoriesDir)) {
|
|
2245
2222
|
return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
|
|
2246
2223
|
}
|
|
2247
2224
|
const all = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
|
|
@@ -2340,7 +2317,7 @@ async function codeSearch(input, ctx) {
|
|
|
2340
2317
|
}
|
|
2341
2318
|
|
|
2342
2319
|
// src/tools/why-this-file.ts
|
|
2343
|
-
import { existsSync as
|
|
2320
|
+
import { existsSync as existsSync22 } from "fs";
|
|
2344
2321
|
import { spawn } from "child_process";
|
|
2345
2322
|
import path10 from "path";
|
|
2346
2323
|
import {
|
|
@@ -2360,7 +2337,7 @@ var WhyThisFileInputSchema = {
|
|
|
2360
2337
|
memory_limit: z23.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
|
|
2361
2338
|
};
|
|
2362
2339
|
async function whyThisFile(input, ctx) {
|
|
2363
|
-
const fileExists =
|
|
2340
|
+
const fileExists = existsSync22(path10.join(ctx.paths.root, input.path));
|
|
2364
2341
|
const [commits, memories, codeMap] = await Promise.all([
|
|
2365
2342
|
runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
|
|
2366
2343
|
collectAnchoredMemories(ctx, input.path, input.memory_limit),
|
|
@@ -2401,7 +2378,7 @@ async function whyThisFile(input, ctx) {
|
|
|
2401
2378
|
};
|
|
2402
2379
|
}
|
|
2403
2380
|
async function collectAnchoredMemories(ctx, filePath, limit) {
|
|
2404
|
-
if (!
|
|
2381
|
+
if (!existsSync22(ctx.paths.memoriesDir)) return [];
|
|
2405
2382
|
const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
|
|
2406
2383
|
const usage = await loadUsageIndex8(ctx.paths);
|
|
2407
2384
|
const out = [];
|
|
@@ -2456,7 +2433,7 @@ function runCommand(cmd, args, cwd) {
|
|
|
2456
2433
|
}
|
|
2457
2434
|
|
|
2458
2435
|
// src/tools/anti-patterns-check.ts
|
|
2459
|
-
import { existsSync as
|
|
2436
|
+
import { existsSync as existsSync23 } from "fs";
|
|
2460
2437
|
import {
|
|
2461
2438
|
addedLinesFromDiff,
|
|
2462
2439
|
deriveConfidence as deriveConfidence6,
|
|
@@ -2550,7 +2527,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
2550
2527
|
notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
|
|
2551
2528
|
};
|
|
2552
2529
|
}
|
|
2553
|
-
if (!
|
|
2530
|
+
if (!existsSync23(ctx.paths.memoriesDir)) {
|
|
2554
2531
|
return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
|
|
2555
2532
|
}
|
|
2556
2533
|
const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
@@ -2585,6 +2562,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
2585
2562
|
reasons: [reason],
|
|
2586
2563
|
tags: fm.tags ?? [],
|
|
2587
2564
|
anchor_paths: fm.anchor?.paths ?? [],
|
|
2565
|
+
...fm.sensor != null ? { has_sensor: true } : {},
|
|
2588
2566
|
...score !== void 0 ? { semantic_score: score } : {}
|
|
2589
2567
|
});
|
|
2590
2568
|
};
|
|
@@ -2653,7 +2631,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
2653
2631
|
}
|
|
2654
2632
|
|
|
2655
2633
|
// src/tools/mem-distill.ts
|
|
2656
|
-
import { existsSync as
|
|
2634
|
+
import { existsSync as existsSync24 } from "fs";
|
|
2657
2635
|
import {
|
|
2658
2636
|
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
2659
2637
|
tokenizeQuery as tokenizeQuery4
|
|
@@ -2705,7 +2683,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
2705
2683
|
"error"
|
|
2706
2684
|
]);
|
|
2707
2685
|
async function memDistill(input, ctx) {
|
|
2708
|
-
if (!
|
|
2686
|
+
if (!existsSync24(ctx.paths.memoriesDir)) {
|
|
2709
2687
|
return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
|
|
2710
2688
|
}
|
|
2711
2689
|
const cutoff = Date.now() - input.since_days * MS_PER_DAY;
|
|
@@ -2813,7 +2791,7 @@ function firstHeading(body) {
|
|
|
2813
2791
|
}
|
|
2814
2792
|
|
|
2815
2793
|
// src/tools/why-this-decision.ts
|
|
2816
|
-
import { existsSync as
|
|
2794
|
+
import { existsSync as existsSync25 } from "fs";
|
|
2817
2795
|
import { spawn as spawn2 } from "child_process";
|
|
2818
2796
|
import {
|
|
2819
2797
|
deriveConfidence as deriveConfidence7,
|
|
@@ -2828,7 +2806,7 @@ var WhyThisDecisionInputSchema = {
|
|
|
2828
2806
|
git_log_limit: z26.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
|
|
2829
2807
|
};
|
|
2830
2808
|
async function whyThisDecision(input, ctx) {
|
|
2831
|
-
if (!
|
|
2809
|
+
if (!existsSync25(ctx.paths.memoriesDir)) {
|
|
2832
2810
|
return {
|
|
2833
2811
|
found: false,
|
|
2834
2812
|
related: [],
|
|
@@ -2960,7 +2938,7 @@ function runCommand2(cmd, args, cwd) {
|
|
|
2960
2938
|
}
|
|
2961
2939
|
|
|
2962
2940
|
// src/tools/mem-conflicts.ts
|
|
2963
|
-
import { existsSync as
|
|
2941
|
+
import { existsSync as existsSync26 } from "fs";
|
|
2964
2942
|
import {
|
|
2965
2943
|
deriveConfidence as deriveConfidence8,
|
|
2966
2944
|
getUsage as getUsage9,
|
|
@@ -2978,7 +2956,7 @@ var MemConflictsInputSchema = {
|
|
|
2978
2956
|
var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
|
|
2979
2957
|
var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
|
|
2980
2958
|
async function memConflicts(input, ctx) {
|
|
2981
|
-
if (!
|
|
2959
|
+
if (!existsSync26(ctx.paths.memoriesDir)) {
|
|
2982
2960
|
return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
|
|
2983
2961
|
}
|
|
2984
2962
|
const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
|
|
@@ -3171,7 +3149,9 @@ async function preCommitCheck(input, ctx) {
|
|
|
3171
3149
|
};
|
|
3172
3150
|
}
|
|
3173
3151
|
function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
3174
|
-
const affectedFiles = paths.filter(
|
|
3152
|
+
const affectedFiles = paths.filter(
|
|
3153
|
+
(p) => !p.startsWith(".ai/.usage/") && !p.startsWith(".ai/.cache/") && !p.startsWith(".ai/.runtime/")
|
|
3154
|
+
);
|
|
3175
3155
|
const repairCommand = repairCommandForWarning(warning, affectedFiles);
|
|
3176
3156
|
const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
|
|
3177
3157
|
if (fileDowngrade) {
|
|
@@ -3202,6 +3182,24 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
|
3202
3182
|
};
|
|
3203
3183
|
}
|
|
3204
3184
|
if (isBlockingWarning(warning)) {
|
|
3185
|
+
if (warning.scope === "personal") {
|
|
3186
|
+
return {
|
|
3187
|
+
...warning,
|
|
3188
|
+
level: "review",
|
|
3189
|
+
rationale: "personal anti-pattern memories are review guidance unless a deterministic block-severity sensor fires",
|
|
3190
|
+
affected_files: affectedFiles,
|
|
3191
|
+
repair_command: repairCommand
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
if (warning.has_sensor && !warning.reasons.includes("sensor")) {
|
|
3195
|
+
return {
|
|
3196
|
+
...warning,
|
|
3197
|
+
level: "review",
|
|
3198
|
+
rationale: "memory has a sensor that did not fire \u2014 sensor is the authoritative check; strong semantic match alone is insufficient to block",
|
|
3199
|
+
affected_files: affectedFiles,
|
|
3200
|
+
repair_command: repairCommand
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3205
3203
|
return {
|
|
3206
3204
|
...warning,
|
|
3207
3205
|
level: "blocking",
|
|
@@ -3213,7 +3211,16 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
|
3213
3211
|
const hasSemantic = warning.reasons.includes("semantic");
|
|
3214
3212
|
const semanticScore = warning.semantic_score ?? 0;
|
|
3215
3213
|
const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
|
|
3216
|
-
if (anchoredBlocks && highConfidence && warning.reasons.includes("anchor") && (warning.reasons.includes("literal") || hasSemantic && semanticScore >= 0.45)) {
|
|
3214
|
+
if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && (warning.reasons.includes("literal") || hasSemantic && semanticScore >= 0.45)) {
|
|
3215
|
+
if (warning.has_sensor && !warning.reasons.includes("sensor")) {
|
|
3216
|
+
return {
|
|
3217
|
+
...warning,
|
|
3218
|
+
level: "review",
|
|
3219
|
+
rationale: "memory has a sensor that did not fire \u2014 literal match alone is insufficient to block; sensor is the authoritative check",
|
|
3220
|
+
affected_files: affectedFiles,
|
|
3221
|
+
repair_command: repairCommand
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3217
3224
|
return {
|
|
3218
3225
|
...warning,
|
|
3219
3226
|
level: "blocking",
|
|
@@ -3338,13 +3345,25 @@ function isJsonConfigFile(base) {
|
|
|
3338
3345
|
return false;
|
|
3339
3346
|
}
|
|
3340
3347
|
function repairCommandForWarning(warning, paths) {
|
|
3341
|
-
const
|
|
3342
|
-
return
|
|
3348
|
+
const targetPath = repairTargetPathForWarning(warning, paths);
|
|
3349
|
+
return targetPath ? `haive briefing --files "${targetPath}" --task "review ${warning.id}"` : `haive memory show ${warning.id}`;
|
|
3350
|
+
}
|
|
3351
|
+
function repairTargetPathForWarning(warning, paths) {
|
|
3352
|
+
const usablePaths = paths.filter(
|
|
3353
|
+
(p) => !p.startsWith(".ai/.usage/") && !p.startsWith(".ai/.cache/") && !p.startsWith(".ai/.runtime/")
|
|
3354
|
+
);
|
|
3355
|
+
const anchors = warning.anchor_paths ?? [];
|
|
3356
|
+
for (const file of usablePaths) {
|
|
3357
|
+
if (anchors.some((anchor) => anchor === file || file.startsWith(`${anchor}/`) || anchor.startsWith(`${file}/`))) {
|
|
3358
|
+
return file;
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
return usablePaths[0];
|
|
3343
3362
|
}
|
|
3344
3363
|
|
|
3345
3364
|
// src/tools/pattern-detect.ts
|
|
3346
3365
|
import { mkdir as mkdir7, writeFile as writeFile12 } from "fs/promises";
|
|
3347
|
-
import { existsSync as
|
|
3366
|
+
import { existsSync as existsSync27 } from "fs";
|
|
3348
3367
|
import path11 from "path";
|
|
3349
3368
|
import { execSync as execSync2 } from "child_process";
|
|
3350
3369
|
import {
|
|
@@ -3381,7 +3400,7 @@ var PatternDetectInputSchema = {
|
|
|
3381
3400
|
scope: z29.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
|
|
3382
3401
|
};
|
|
3383
3402
|
async function patternDetect(input, ctx) {
|
|
3384
|
-
if (!
|
|
3403
|
+
if (!existsSync27(ctx.paths.haiveDir)) {
|
|
3385
3404
|
return {
|
|
3386
3405
|
scanned_events: 0,
|
|
3387
3406
|
matches: [],
|
|
@@ -3510,7 +3529,7 @@ async function patternDetect(input, ctx) {
|
|
|
3510
3529
|
fm.id,
|
|
3511
3530
|
void 0
|
|
3512
3531
|
);
|
|
3513
|
-
if (
|
|
3532
|
+
if (existsSync27(file)) continue;
|
|
3514
3533
|
await mkdir7(path11.dirname(file), { recursive: true });
|
|
3515
3534
|
await writeFile12(
|
|
3516
3535
|
file,
|
|
@@ -3556,7 +3575,7 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
3556
3575
|
}
|
|
3557
3576
|
|
|
3558
3577
|
// src/tools/mem-conflict-candidates.ts
|
|
3559
|
-
import { existsSync as
|
|
3578
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3560
3579
|
import {
|
|
3561
3580
|
findLexicalConflictPairs,
|
|
3562
3581
|
findTopicStatusConflictPairs,
|
|
@@ -3574,7 +3593,7 @@ var MemConflictCandidatesInputSchema = {
|
|
|
3574
3593
|
)
|
|
3575
3594
|
};
|
|
3576
3595
|
async function memConflictCandidates(input, ctx) {
|
|
3577
|
-
if (!
|
|
3596
|
+
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
3578
3597
|
return {
|
|
3579
3598
|
pairs: [],
|
|
3580
3599
|
topic_status_pairs: [],
|
|
@@ -3626,7 +3645,7 @@ async function memSuggestTopic(input, _ctx) {
|
|
|
3626
3645
|
}
|
|
3627
3646
|
|
|
3628
3647
|
// src/tools/mem-timeline.ts
|
|
3629
|
-
import { existsSync as
|
|
3648
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3630
3649
|
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hiveai/core";
|
|
3631
3650
|
import { z as z33 } from "zod";
|
|
3632
3651
|
var MemTimelineInputSchema = {
|
|
@@ -3635,7 +3654,7 @@ var MemTimelineInputSchema = {
|
|
|
3635
3654
|
limit: z33.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
3636
3655
|
};
|
|
3637
3656
|
async function memTimeline(input, ctx) {
|
|
3638
|
-
if (!
|
|
3657
|
+
if (!existsSync29(ctx.paths.memoriesDir)) {
|
|
3639
3658
|
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
3640
3659
|
}
|
|
3641
3660
|
const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
|
|
@@ -3842,7 +3861,19 @@ This creates/updates a single rolling recap that **get_briefing automatically su
|
|
|
3842
3861
|
|
|
3843
3862
|
Calling \`mem_session_end\` also **clears the pending-distill marker** (if any), confirming that this session's learnings have been properly captured rather than left as an auto-recap skeleton.
|
|
3844
3863
|
|
|
3845
|
-
|
|
3864
|
+
### 7. Verify the git/release exit protocol \u2014 always
|
|
3865
|
+
Run **\`haive enforce finish\`** before your final response.
|
|
3866
|
+
|
|
3867
|
+
This executable gate checks the multi-agent git-sync decision:
|
|
3868
|
+
- no completed work is left as an uncommitted local diff
|
|
3869
|
+
- shippable package changes have a lockstep version bump
|
|
3870
|
+
- the release tag \`vX.Y.Z\` exists when a version was bumped
|
|
3871
|
+
- commits and tags have been pushed
|
|
3872
|
+
- agents never run \`npm publish\` (publication remains human-owned)
|
|
3873
|
+
|
|
3874
|
+
If it blocks, fix the reported Git/version/tag/push issue before telling the developer the task is done.
|
|
3875
|
+
|
|
3876
|
+
When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved. hAIve finish gate passed."
|
|
3846
3877
|
`;
|
|
3847
3878
|
return {
|
|
3848
3879
|
description: "Post-task reflection: capture what you learned before closing the session",
|
|
@@ -3925,9 +3956,9 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
3925
3956
|
}
|
|
3926
3957
|
|
|
3927
3958
|
// src/server.ts
|
|
3928
|
-
import { loadConfigSync } from "@hiveai/core";
|
|
3959
|
+
import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
|
|
3929
3960
|
var SERVER_NAME = "haive";
|
|
3930
|
-
var SERVER_VERSION = "0.10.
|
|
3961
|
+
var SERVER_VERSION = "0.10.9";
|
|
3931
3962
|
function jsonResult(data) {
|
|
3932
3963
|
return {
|
|
3933
3964
|
content: [
|
|
@@ -4031,11 +4062,16 @@ function createHaiveServer(options = {}) {
|
|
|
4031
4062
|
return await handler(input);
|
|
4032
4063
|
}
|
|
4033
4064
|
if (requireBriefingFirst && MUTATING_TOOLS.has(name) && !briefingLoaded) {
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4065
|
+
const hasDiskMarker = await hasRecentBriefingMarker(context.paths).catch(() => false);
|
|
4066
|
+
if (hasDiskMarker) {
|
|
4067
|
+
briefingLoaded = true;
|
|
4068
|
+
} else {
|
|
4069
|
+
return jsonResult({
|
|
4070
|
+
error: "haive_briefing_required",
|
|
4071
|
+
message: "This hAIve project requires get_briefing or mem_relevant_to before state-changing hAIve tools. Call get_briefing({ task: '...' }) first.",
|
|
4072
|
+
tool: name
|
|
4073
|
+
});
|
|
4074
|
+
}
|
|
4039
4075
|
}
|
|
4040
4076
|
return await handler(input);
|
|
4041
4077
|
}
|