@hivelore/core 0.54.0 → 0.57.1

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 CHANGED
@@ -1042,6 +1042,26 @@ function renderPreventionReceiptShare(receipt) {
1042
1042
  lines.push("", `<sub>${HIVELORE_ATTRIBUTION}</sub>`);
1043
1043
  return lines.join("\n");
1044
1044
  }
1045
+ var PREVENTION_RECEIPT_MARKER = "<!-- haive:prevention-receipt -->";
1046
+ function renderPreventionComment(receipt, findings = []) {
1047
+ const fired = findings.filter((f) => f.code === "sensor-block" || f.code === "sensor-warn");
1048
+ const lines = [PREVENTION_RECEIPT_MARKER, "", "## Hivelore prevention receipt", ""];
1049
+ if (fired.length === 0) {
1050
+ lines.push("No documented sensor fired on this PR.", "");
1051
+ } else {
1052
+ lines.push("### Fired on this PR", "");
1053
+ for (const f of fired) {
1054
+ const where = f.file ? ` \u2014 \`${f.file}\`` : "";
1055
+ lines.push(`- **${f.memory_ids?.[0] ?? "sensor"}**${where} \u2014 ${f.message ?? "sensor fired"}`);
1056
+ if (f.matched_line) lines.push(` \`\`\`
1057
+ ${f.matched_line}
1058
+ \`\`\``);
1059
+ }
1060
+ lines.push("");
1061
+ }
1062
+ lines.push(renderPreventionReceiptShare(receipt));
1063
+ return lines.join("\n");
1064
+ }
1045
1065
  async function loadPreventionEvents(paths) {
1046
1066
  const file = preventionLogPath(paths);
1047
1067
  if (!existsSync4(file)) return [];
@@ -1204,6 +1224,244 @@ async function recordProjectContextEmission(paths, hash, now = Date.now()) {
1204
1224
  });
1205
1225
  }
1206
1226
 
1227
+ // src/gate-reminder.ts
1228
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1229
+ import { existsSync as existsSync6 } from "fs";
1230
+ import path8 from "path";
1231
+ var GATE_REMINDER_WINDOW_MS = 24 * 60 * 60 * 1e3;
1232
+ function reminderMarkerPath(paths) {
1233
+ return path8.join(paths.haiveDir, ".cache", "gate-reminders.json");
1234
+ }
1235
+ async function readMarkers(paths) {
1236
+ const file = reminderMarkerPath(paths);
1237
+ if (!existsSync6(file)) return {};
1238
+ try {
1239
+ const parsed = JSON.parse(await readFile6(file, "utf8"));
1240
+ return parsed && typeof parsed === "object" ? parsed : {};
1241
+ } catch {
1242
+ return {};
1243
+ }
1244
+ }
1245
+ async function shouldExpandGateReminder(paths, key, now = Date.now(), windowMs = GATE_REMINDER_WINDOW_MS) {
1246
+ const last = (await readMarkers(paths))[key];
1247
+ if (!last) return true;
1248
+ const at = Date.parse(last);
1249
+ return !Number.isFinite(at) || now - at >= windowMs;
1250
+ }
1251
+ async function recordGateReminder(paths, key, now = Date.now()) {
1252
+ const file = reminderMarkerPath(paths);
1253
+ const markers = await readMarkers(paths);
1254
+ markers[key] = new Date(now).toISOString();
1255
+ await mkdir4(path8.dirname(file), { recursive: true }).catch(() => {
1256
+ });
1257
+ await writeFile3(file, JSON.stringify(markers, null, 2), "utf8").catch(() => {
1258
+ });
1259
+ }
1260
+
1261
+ // src/gate-verdict.ts
1262
+ var PROCESS_GATE_CODES = /* @__PURE__ */ new Set([
1263
+ "briefing-missing",
1264
+ "session-recap-missing",
1265
+ "decision-coverage-missing",
1266
+ "bootstrap-incomplete"
1267
+ ]);
1268
+ var CONTENT_CATCH_CODES = /* @__PURE__ */ new Set(["sensor-block", "precommit-policy-block"]);
1269
+ var CONTENT_CODES = /* @__PURE__ */ new Set([...CONTENT_CATCH_CODES, "sensor-warn"]);
1270
+ var SETUP_GATE_CODES = /* @__PURE__ */ new Set([
1271
+ ...PROCESS_GATE_CODES,
1272
+ "enforcement-score-below-threshold"
1273
+ ]);
1274
+ var DEFAULT_POSTURE = "balanced";
1275
+ var POSTURE_DEFAULTS = {
1276
+ // Report everything, refuse nothing. For adopting Hivelore on a repo mid-flight.
1277
+ advisory: { mode: "advisory", processGate: "warn", humanCommits: "relaxed" },
1278
+ // Refuse on deterministic, code-bound evidence only. The default.
1279
+ balanced: { mode: "strict", processGate: "warn", humanCommits: "relaxed" },
1280
+ // Process gates bind too, at the sharing points. For teams that want the workflow enforced.
1281
+ strict: { mode: "strict", processGate: "block", humanCommits: "strict" }
1282
+ };
1283
+ function resolveGatePolicy(input) {
1284
+ const cfg = input ?? {};
1285
+ const posture = cfg.posture ?? DEFAULT_POSTURE;
1286
+ const base = POSTURE_DEFAULTS[posture] ?? POSTURE_DEFAULTS[DEFAULT_POSTURE];
1287
+ const overrides = [];
1288
+ for (const key of ["mode", "processGate", "humanCommits"]) {
1289
+ if (cfg[key] !== void 0) overrides.push(key);
1290
+ }
1291
+ return {
1292
+ posture,
1293
+ mode: cfg.mode ?? base.mode,
1294
+ processGate: cfg.processGate ?? base.processGate,
1295
+ humanCommits: cfg.humanCommits ?? base.humanCommits,
1296
+ scoreThreshold: cfg.scoreThreshold ?? 80,
1297
+ overrides
1298
+ };
1299
+ }
1300
+ function describePosture(policy) {
1301
+ const base = policy.posture === "advisory" ? "reports everything, refuses nothing" : policy.posture === "strict" ? "refuses on deterministic findings AND on process gates at pre-push/CI" : "refuses on deterministic findings only (sensors, anti-patterns, stale anchors on touched files)";
1302
+ const pinned = policy.overrides.length > 0 ? ` \xB7 overridden: ${policy.overrides.join(", ")}` : "";
1303
+ return `${policy.posture} \u2014 ${base}${pinned}`;
1304
+ }
1305
+ function processGateDecision(policy, stage, isAgent) {
1306
+ if (policy.processGate !== "block") {
1307
+ return {
1308
+ refuses: false,
1309
+ reason: `advisory: process gates report, they do not refuse \u2014 only sensors and other deterministic findings block. Set enforcement.posture="strict" (or processGate="block") to change that.`
1310
+ };
1311
+ }
1312
+ if (stage === "pre-commit" || stage === "local") {
1313
+ return {
1314
+ refuses: false,
1315
+ reason: "advisory at commit time: process gates bind the sharing points (pre-push, CI), not local iteration."
1316
+ };
1317
+ }
1318
+ if (stage !== "ci" && !isAgent && policy.humanCommits === "relaxed") {
1319
+ return {
1320
+ refuses: false,
1321
+ reason: `relaxed to a warning: no agent harness detected, so this human commit is not bound by agent process gates \u2014 set enforcement.humanCommits="strict" to change that.`
1322
+ };
1323
+ }
1324
+ return { refuses: true, reason: "enforced: process gates bind at this sharing point." };
1325
+ }
1326
+ function penaltyOf(finding) {
1327
+ if (finding.severity === "error") return finding.impact ?? 25;
1328
+ if (finding.severity === "warn") return finding.impact ?? 8;
1329
+ return 0;
1330
+ }
1331
+ function computeBaselineHealth(findings, threshold) {
1332
+ const baseline = findings.filter((f) => !CONTENT_CODES.has(f.code));
1333
+ const penalty = baseline.reduce((sum, f) => sum + penaltyOf(f), 0);
1334
+ return {
1335
+ score: Math.max(0, Math.min(100, 100 - penalty)),
1336
+ threshold,
1337
+ checks: {
1338
+ total: findings.length,
1339
+ ok: findings.filter((f) => f.severity === "ok").length,
1340
+ warn: findings.filter((f) => f.severity === "warn").length,
1341
+ error: findings.filter((f) => f.severity === "error").length
1342
+ }
1343
+ };
1344
+ }
1345
+ function dedupeRefusals(findings) {
1346
+ const byMemory = /* @__PURE__ */ new Map();
1347
+ const out = [];
1348
+ for (const finding of findings) {
1349
+ if (!CONTENT_CATCH_CODES.has(finding.code) || finding.severity !== "error") continue;
1350
+ const key = finding.memory_ids?.[0];
1351
+ if (!key) {
1352
+ out.push(finding);
1353
+ continue;
1354
+ }
1355
+ const existing = byMemory.get(key);
1356
+ if (!existing || !existing.matched_line && finding.matched_line) byMemory.set(key, finding);
1357
+ }
1358
+ return [...byMemory.values(), ...out];
1359
+ }
1360
+ function decideVerdict(input) {
1361
+ const { policy, stage, isAgent } = input;
1362
+ const process2 = processGateDecision(policy, stage, isAgent);
1363
+ const findings = input.findings.map((finding) => {
1364
+ if (finding.severity !== "error" || !PROCESS_GATE_CODES.has(finding.code)) return finding;
1365
+ if (process2.refuses) return finding;
1366
+ return {
1367
+ ...finding,
1368
+ severity: "warn",
1369
+ // Capped so a downgraded gate cannot dominate the health score it no longer refuses on.
1370
+ impact: Math.min(finding.impact ?? 8, 8),
1371
+ reason: process2.reason,
1372
+ message: `${finding.message} (${process2.reason})`
1373
+ };
1374
+ });
1375
+ const baselineHealth = computeBaselineHealth(findings, policy.scoreThreshold);
1376
+ const refusals = dedupeRefusals(findings);
1377
+ const hasErrors = findings.some((f) => f.severity === "error");
1378
+ return {
1379
+ findings,
1380
+ should_block: policy.mode === "strict" && hasErrors,
1381
+ actor: isAgent ? `agent (${(input.agentSignals ?? []).join(", ")})` : process2.refuses ? "human \u2014 strict (enforcement.humanCommits)" : "human \u2014 process gates relaxed",
1382
+ baseline_health: baselineHealth,
1383
+ refusals,
1384
+ process_gate_reason: process2.reason
1385
+ };
1386
+ }
1387
+ function buildBaselineHealthFinding(findings, health, shouldBlock) {
1388
+ if (shouldBlock || health.score >= health.threshold) return null;
1389
+ const topPenalties = findings.filter((f) => !CONTENT_CODES.has(f.code)).map((f) => ({ code: f.code, penalty: penaltyOf(f) })).filter((p) => p.penalty > 0).sort((a, b) => b.penalty - a.penalty).slice(0, 3);
1390
+ return {
1391
+ severity: "warn",
1392
+ code: "enforcement-score-below-threshold",
1393
+ message: `Repo knowledge-layer health ${health.score}% is below the ${health.threshold}% target` + (topPenalties.length > 0 ? ` \u2014 top gaps: ${topPenalties.map((p) => `${p.code} (\u2212${p.penalty})`).join(", ")}` : "") + ". This measures the repo's baseline, not this change; it never blocks.",
1394
+ fix: "Fill the gaps above (bootstrap, briefing, recap), then rerun `hivelore enforce check`.",
1395
+ impact: 0
1396
+ };
1397
+ }
1398
+
1399
+ // src/anchor-specificity.ts
1400
+ var WEAK_ANCHOR_CHURN_RATIO = 0.35;
1401
+ var DEFAULT_SPECIFICITY = 1;
1402
+ function anchorSpecificity(matchedPaths, churn, totalCommits) {
1403
+ if (totalCommits <= 0 || matchedPaths.length === 0) return DEFAULT_SPECIFICITY;
1404
+ let best = 0;
1405
+ let sawKnown = false;
1406
+ for (const path22 of matchedPaths) {
1407
+ const touched = churn.get(normalizeChurnPath(path22));
1408
+ if (touched === void 0) continue;
1409
+ sawKnown = true;
1410
+ best = Math.max(best, 1 - Math.min(1, touched / totalCommits));
1411
+ }
1412
+ return sawKnown ? best : DEFAULT_SPECIFICITY;
1413
+ }
1414
+ function isWeakAnchor(specificity) {
1415
+ return specificity < 1 - WEAK_ANCHOR_CHURN_RATIO;
1416
+ }
1417
+ function normalizeChurnPath(value) {
1418
+ return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
1419
+ }
1420
+ function churnForAnchors(anchorPaths, fileChurn) {
1421
+ const out = /* @__PURE__ */ new Map();
1422
+ for (const raw of anchorPaths) {
1423
+ const anchor = normalizeChurnPath(raw);
1424
+ const direct = fileChurn.get(anchor);
1425
+ if (direct !== void 0) {
1426
+ out.set(anchor, direct);
1427
+ continue;
1428
+ }
1429
+ let max = 0;
1430
+ let matched = false;
1431
+ for (const [file, count] of fileChurn) {
1432
+ if (!pathCoveredByAnchor(anchor, file)) continue;
1433
+ matched = true;
1434
+ max = Math.max(max, count);
1435
+ }
1436
+ if (matched) out.set(anchor, max);
1437
+ }
1438
+ return out;
1439
+ }
1440
+ function pathCoveredByAnchor(anchor, file) {
1441
+ if (anchor === file) return true;
1442
+ if (!anchor.includes("*")) return file.startsWith(`${anchor}/`);
1443
+ const pattern = anchor.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(//g, ".*");
1444
+ try {
1445
+ return new RegExp(`^${pattern}$`).test(file);
1446
+ } catch {
1447
+ return false;
1448
+ }
1449
+ }
1450
+ function auditAnchorSpecificity(memories, fileChurn, totalCommits) {
1451
+ if (totalCommits <= 0) return [];
1452
+ const rows = [];
1453
+ for (const memory of memories) {
1454
+ if (memory.anchorPaths.length === 0) continue;
1455
+ const rolled = churnForAnchors(memory.anchorPaths, fileChurn);
1456
+ if (rolled.size === 0) continue;
1457
+ const specificity = anchorSpecificity(memory.anchorPaths, rolled, totalCommits);
1458
+ if (!isWeakAnchor(specificity)) continue;
1459
+ const broad = [...rolled].map(([path22, count]) => ({ path: path22, ratio: count / totalCommits })).filter((entry) => entry.ratio > WEAK_ANCHOR_CHURN_RATIO).sort((a, b) => b.ratio - a.ratio);
1460
+ rows.push({ id: memory.id, specificity, broad });
1461
+ }
1462
+ return rows.sort((a, b) => a.specificity - b.specificity);
1463
+ }
1464
+
1207
1465
  // src/priority.ts
1208
1466
  var DEFAULT_PRIORITY_SIGNALS = {
1209
1467
  type: "",
@@ -1215,7 +1473,8 @@ var DEFAULT_PRIORITY_SIGNALS = {
1215
1473
  strongSemantic: false,
1216
1474
  usefulSemantic: false,
1217
1475
  moduleOrDomainMatch: false,
1218
- tagTaskMatch: false
1476
+ tagTaskMatch: false,
1477
+ anchorSpecificity: DEFAULT_SPECIFICITY
1219
1478
  };
1220
1479
  function prioritySignals(partial) {
1221
1480
  return { ...DEFAULT_PRIORITY_SIGNALS, ...partial };
@@ -1223,9 +1482,13 @@ function prioritySignals(partial) {
1223
1482
  function classifyMemoryPriority(signals) {
1224
1483
  const isNegative = signals.type === "attempt";
1225
1484
  const isSkill2 = signals.type === "skill";
1226
- if (signals.requiresHumanApproval || signals.directAnchor || signals.directSymbol || isNegative && (signals.exactTaskMatch || signals.strongSemantic) || isSkill2 && (signals.exactTaskMatch || signals.strongSemantic)) {
1485
+ const weakAnchor = signals.directAnchor && isWeakAnchor(signals.anchorSpecificity ?? DEFAULT_SPECIFICITY);
1486
+ const strongAnchor = signals.directAnchor && !weakAnchor;
1487
+ const corroborated = signals.strongSemantic || signals.directSymbol;
1488
+ if (signals.requiresHumanApproval || strongAnchor || signals.directSymbol || weakAnchor && corroborated || isNegative && (signals.exactTaskMatch || signals.strongSemantic) || isSkill2 && (signals.exactTaskMatch || signals.strongSemantic)) {
1227
1489
  return "must_read";
1228
1490
  }
1491
+ if (weakAnchor) return "useful";
1229
1492
  if (isStackPackSeed({ tags: signals.tags }) || isEnvWorkaroundMemory({ tags: signals.tags })) {
1230
1493
  if (isStackPackSeed({ tags: signals.tags }) && (signals.exactTaskMatch || signals.strongSemantic)) {
1231
1494
  return "useful";
@@ -1394,10 +1657,10 @@ function sensorPatternBrittleness(pattern) {
1394
1657
  function normalizeProjectPath(value) {
1395
1658
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
1396
1659
  }
1397
- function sensorAppliesToPath(sensor, anchorPaths, path21) {
1660
+ function sensorAppliesToPath(sensor, anchorPaths, path22) {
1398
1661
  const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
1399
1662
  if (scopes.length === 0) return true;
1400
- const target = normalizeProjectPath(path21);
1663
+ const target = normalizeProjectPath(path22);
1401
1664
  return scopes.some((rawScope) => {
1402
1665
  const scope = normalizeProjectPath(rawScope);
1403
1666
  if (!scope) return false;
@@ -1722,6 +1985,34 @@ function judgeProposedSensor(sensor, input) {
1722
1985
  }
1723
1986
  return { accepted: true, self_check, brittle };
1724
1987
  }
1988
+ function explainSensorRejection(verdict, context) {
1989
+ const retry = context.style === "cli" ? "re-run" : "re-propose";
1990
+ switch (verdict.reason) {
1991
+ case "fires-on-current": {
1992
+ const where = verdict.self_check.fired_on.join(", ");
1993
+ const warnCommand = context.style === "cli" ? `hivelore sensors propose ${context.memoryId ?? "<memory-id>"} --pattern '<same>' --severity warn` : `propose_sensor({ memory_id: "${context.memoryId ?? "<memory-id>"}", pattern: "<same>", severity: "warn" })`;
1994
+ return [
1995
+ `A block sensor must be silent on the current code, and this one fires on: ${where}.`,
1996
+ "That means one of two things:",
1997
+ ` 1. The faulty pattern is STILL PRESENT \u2014 the usual case when you document a problem before`,
1998
+ ` fixing it. A block sensor cannot be armed yet. Arm it as a warning now, fix the code,`,
1999
+ ` then promote it:`,
2000
+ ` ${warnCommand}`,
2001
+ ` \u2026then re-run the same proposal with severity "block" once ${where} is clean.`,
2002
+ ` 2. The pattern also matches LEGITIMATE usage. Add or tighten the 'absent' companion so`,
2003
+ ` correct usage is excluded, then ${retry}.`
2004
+ ].join("\n");
2005
+ }
2006
+ case "fires-on-correct":
2007
+ return `Inverted: the pattern matches the lesson's OWN recommended fix (its \`Instead, use:\` approach) \u2014 it would block correct code and never the mistake. Point the pattern at the FAULTY usage, then ${retry}.`;
2008
+ case "missed-bad-example":
2009
+ return `The sensor did not match the bad example, so it won't catch the mistake. Adjust the pattern, then ${retry}.`;
2010
+ case "brittle":
2011
+ return `The pattern is brittle (${verdict.brittle}). Use a durable pattern (avoid hardcoded line numbers), then ${retry}.`;
2012
+ default:
2013
+ return `Re-propose with a discriminating pattern, then ${retry}.`;
2014
+ }
2015
+ }
1725
2016
  function isHarnessErrorOutput(output) {
1726
2017
  if (!output) return false;
1727
2018
  return HARNESS_ERROR_SIGNATURES.some((re) => re.test(output));
@@ -1884,7 +2175,33 @@ function suggestSensorSeed(body, anchorPaths, options = {}) {
1884
2175
  message: companion ? companionMessage(body, companion) : sensorMessageFromBody(body, token)
1885
2176
  };
1886
2177
  if (companion) seed.absent = escapeRegExp(companion.required);
1887
- return seed;
2178
+ return seedFiresOnCorrectUsage(seed, body) ? null : seed;
2179
+ }
2180
+ function seedFiresOnCorrectUsage(seed, body) {
2181
+ const correct = correctUsageText(body);
2182
+ if (!correct) return false;
2183
+ let re;
2184
+ try {
2185
+ re = new RegExp(seed.pattern, "m");
2186
+ } catch {
2187
+ return true;
2188
+ }
2189
+ if (!re.test(correct)) return false;
2190
+ if (seed.absent) {
2191
+ try {
2192
+ if (new RegExp(seed.absent, "m").test(correct)) return false;
2193
+ } catch {
2194
+ }
2195
+ }
2196
+ return true;
2197
+ }
2198
+ function correctUsageText(body) {
2199
+ const parts = [...extractCorrectApproachExamples(body)];
2200
+ const headingRe = /^#{1,6}\s+(?:how to apply|instead|correct|the fix|fix|recommended|do this|guidance)\b[^\n]*\n([\s\S]*?)(?=^#{1,6}\s|(?![\s\S]))/gim;
2201
+ for (const match of body.matchAll(headingRe)) {
2202
+ if (match[1]) parts.push(match[1]);
2203
+ }
2204
+ return parts.join("\n").trim();
1888
2205
  }
1889
2206
  function suggestSensorFromMemory(body, anchorPaths, options = {}) {
1890
2207
  const seed = suggestSensorSeed(body, anchorPaths, options);
@@ -2649,10 +2966,11 @@ function allocateBudget(parts, maxTokens) {
2649
2966
  }
2650
2967
 
2651
2968
  // src/code-map.ts
2652
- import { mkdir as mkdir4, readFile as readFile6, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
2653
- import { existsSync as existsSync6 } from "fs";
2969
+ import { mkdir as mkdir5, readFile as readFile7, readdir as readdir3, stat as stat2, writeFile as writeFile4 } from "fs/promises";
2970
+ import { createHash as createHash2 } from "crypto";
2971
+ import { existsSync as existsSync7 } from "fs";
2654
2972
  import { spawnSync } from "child_process";
2655
- import path8 from "path";
2973
+ import path9 from "path";
2656
2974
 
2657
2975
  // src/ast-parser.ts
2658
2976
  import { fileURLToPath } from "url";
@@ -3064,27 +3382,53 @@ var CODE_MAP_DEFAULT_EXCLUDE = [
3064
3382
  ];
3065
3383
  var TEST_FILE_RE = /\.(test|spec)\.[a-z]+$/i;
3066
3384
  function codeMapPath(paths) {
3067
- return path8.join(paths.haiveDir, CODE_MAP_FILE);
3385
+ return path9.join(paths.haiveDir, CODE_MAP_FILE);
3386
+ }
3387
+ function codeMapMetaPath(paths) {
3388
+ return path9.join(paths.runtimeDir, "code-map-meta.json");
3389
+ }
3390
+ function serializeCodeMap(map) {
3391
+ const files = {};
3392
+ for (const key of Object.keys(map.files).sort()) files[key] = map.files[key];
3393
+ return `${JSON.stringify({ version: map.version, files }, null, 2)}
3394
+ `;
3395
+ }
3396
+ function codeMapContentHash(map) {
3397
+ return createHash2("sha256").update(serializeCodeMap(map)).digest("hex").slice(0, 16);
3068
3398
  }
3069
3399
  async function loadCodeMap(paths) {
3070
3400
  const file = codeMapPath(paths);
3071
- if (!existsSync6(file)) return null;
3072
- return JSON.parse(await readFile6(file, "utf8"));
3401
+ if (!existsSync7(file)) return null;
3402
+ const parsed = JSON.parse(await readFile7(file, "utf8"));
3403
+ const generatedAt = parsed.generated_at ?? await readFile7(codeMapMetaPath(paths), "utf8").then((raw) => JSON.parse(raw).generated_at).catch(() => void 0) ?? await stat2(file).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
3404
+ return { ...parsed, root: parsed.root ?? paths.root, generated_at: generatedAt };
3073
3405
  }
3074
3406
  async function saveCodeMap(paths, map) {
3075
3407
  const file = codeMapPath(paths);
3076
- await mkdir4(path8.dirname(file), { recursive: true });
3077
- await writeFile3(file, JSON.stringify(map, null, 2), "utf8");
3408
+ const payload = serializeCodeMap(map);
3409
+ const current = existsSync7(file) ? await readFile7(file, "utf8").catch(() => null) : null;
3410
+ if (current === payload) return;
3411
+ await mkdir5(path9.dirname(file), { recursive: true });
3412
+ await writeFile4(file, payload, "utf8");
3413
+ await mkdir5(paths.runtimeDir, { recursive: true }).catch(() => {
3414
+ });
3415
+ await writeFile4(
3416
+ codeMapMetaPath(paths),
3417
+ `${JSON.stringify({ generated_at: (/* @__PURE__ */ new Date()).toISOString(), content_hash: codeMapContentHash(map) }, null, 2)}
3418
+ `,
3419
+ "utf8"
3420
+ ).catch(() => {
3421
+ });
3078
3422
  }
3079
3423
  async function buildCodeMap(root, options = {}) {
3080
3424
  const include = new Set(options.includeExtensions ?? CODE_MAP_DEFAULT_INCLUDE);
3081
3425
  const exclude = new Set(options.excludeDirs ?? CODE_MAP_DEFAULT_EXCLUDE);
3082
3426
  const files = {};
3083
3427
  for await (const abs of collectSourceFiles(root, include, exclude, options.includeUntracked)) {
3084
- const rel = path8.relative(root, abs).replace(/\\/g, "/");
3428
+ const rel = path9.relative(root, abs).replace(/\\/g, "/");
3085
3429
  if (rel.startsWith(".ai/")) continue;
3086
- const content = await readFile6(abs, "utf8");
3087
- const ext = path8.extname(abs).toLowerCase();
3430
+ const content = await readFile7(abs, "utf8");
3431
+ const ext = path9.extname(abs).toLowerCase();
3088
3432
  const entry = await parseFileEntry(content, ext);
3089
3433
  if (entry.exports.length > 0) files[rel] = entry;
3090
3434
  }
@@ -3105,11 +3449,11 @@ async function countSourceFilesOnDisk(root, options = {}) {
3105
3449
  async function* collectSourceFiles(root, include, exclude, includeUntracked) {
3106
3450
  const gitFiles = gitSourceFiles(root, include, exclude, includeUntracked === true);
3107
3451
  if (gitFiles) {
3108
- for (const rel of gitFiles) yield path8.join(root, rel);
3452
+ for (const rel of gitFiles) yield path9.join(root, rel);
3109
3453
  for await (const nested of findNestedGitRepos(root, exclude)) {
3110
3454
  const nestedFiles = gitSourceFiles(nested, include, exclude, includeUntracked === true);
3111
3455
  if (nestedFiles) {
3112
- for (const rel of nestedFiles) yield path8.join(nested, rel);
3456
+ for (const rel of nestedFiles) yield path9.join(nested, rel);
3113
3457
  }
3114
3458
  }
3115
3459
  return;
@@ -3128,8 +3472,8 @@ async function* findNestedGitRepos(root, exclude, depth = 0) {
3128
3472
  if (!entry.isDirectory()) continue;
3129
3473
  if (entry.name.startsWith(".")) continue;
3130
3474
  if (exclude.has(entry.name)) continue;
3131
- const full = path8.join(root, entry.name);
3132
- if (existsSync6(path8.join(full, ".git"))) {
3475
+ const full = path9.join(root, entry.name);
3476
+ if (existsSync7(path9.join(full, ".git"))) {
3133
3477
  yield full;
3134
3478
  } else {
3135
3479
  yield* findNestedGitRepos(full, exclude, depth + 1);
@@ -3158,11 +3502,11 @@ async function* walkSourceFiles(dir, include, exclude) {
3158
3502
  if (entry.isDirectory()) continue;
3159
3503
  }
3160
3504
  if (exclude.has(entry.name)) continue;
3161
- const full = path8.join(dir, entry.name);
3505
+ const full = path9.join(dir, entry.name);
3162
3506
  if (entry.isDirectory()) {
3163
3507
  yield* walkSourceFiles(full, include, exclude);
3164
3508
  } else if (entry.isFile()) {
3165
- const ext = path8.extname(entry.name).toLowerCase();
3509
+ const ext = path9.extname(entry.name).toLowerCase();
3166
3510
  if (include.has(ext) && !TEST_FILE_RE.test(entry.name)) yield full;
3167
3511
  }
3168
3512
  }
@@ -3173,7 +3517,7 @@ function isIncludedSourcePath(rel, include, exclude) {
3173
3517
  const parts = normalized.split("/");
3174
3518
  if (parts.some((part) => exclude.has(part))) return false;
3175
3519
  const base = parts.at(-1) ?? "";
3176
- const ext = path8.extname(base).toLowerCase();
3520
+ const ext = path9.extname(base).toLowerCase();
3177
3521
  return include.has(ext) && !TEST_FILE_RE.test(base);
3178
3522
  }
3179
3523
  var EXPORT_RE = /(?:^|;)[ \t]*export\s+(?:default\s+)?(async\s+)?(function|class|interface|type|const|let|var|enum)\s+(\*?)\s*([A-Za-z_$][\w$]*)/gm;
@@ -3435,10 +3779,10 @@ function queryCodeMap(map, options) {
3435
3779
  }
3436
3780
 
3437
3781
  // src/config.ts
3438
- import { existsSync as existsSync7 } from "fs";
3782
+ import { existsSync as existsSync8 } from "fs";
3439
3783
  import { readFileSync } from "fs";
3440
- import { readFile as readFile7, rm, writeFile as writeFile4 } from "fs/promises";
3441
- import path9 from "path";
3784
+ import { readFile as readFile8, rm, writeFile as writeFile5 } from "fs/promises";
3785
+ import path10 from "path";
3442
3786
  var CONFIG_FILE = "hivelore.config.json";
3443
3787
  var LEGACY_CONFIG_FILE = "haive.config.json";
3444
3788
  var DEFAULT_BRIEFING_EXCLUDE_TAGS = [
@@ -3534,19 +3878,19 @@ function antiPatternGateParams(gate) {
3534
3878
  }
3535
3879
  }
3536
3880
  function configPath(paths) {
3537
- return path9.join(paths.haiveDir, CONFIG_FILE);
3881
+ return path10.join(paths.haiveDir, CONFIG_FILE);
3538
3882
  }
3539
3883
  function resolveConfigPath(paths) {
3540
3884
  const current = configPath(paths);
3541
- if (existsSync7(current)) return current;
3542
- const legacy = path9.join(paths.haiveDir, LEGACY_CONFIG_FILE);
3543
- return existsSync7(legacy) ? legacy : current;
3885
+ if (existsSync8(current)) return current;
3886
+ const legacy = path10.join(paths.haiveDir, LEGACY_CONFIG_FILE);
3887
+ return existsSync8(legacy) ? legacy : current;
3544
3888
  }
3545
3889
  async function loadConfig(paths) {
3546
3890
  const file = resolveConfigPath(paths);
3547
- if (!existsSync7(file)) return { ...DEFAULT_CONFIG };
3891
+ if (!existsSync8(file)) return { ...DEFAULT_CONFIG };
3548
3892
  try {
3549
- const raw = await readFile7(file, "utf8");
3893
+ const raw = await readFile8(file, "utf8");
3550
3894
  const parsed = JSON.parse(raw);
3551
3895
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
3552
3896
  if (merged.autopilot) {
@@ -3559,7 +3903,7 @@ async function loadConfig(paths) {
3559
3903
  }
3560
3904
  function loadConfigSync(paths) {
3561
3905
  const file = resolveConfigPath(paths);
3562
- if (!existsSync7(file)) return { ...DEFAULT_CONFIG };
3906
+ if (!existsSync8(file)) return { ...DEFAULT_CONFIG };
3563
3907
  try {
3564
3908
  const parsed = JSON.parse(readFileSync(file, "utf8"));
3565
3909
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
@@ -3569,9 +3913,9 @@ function loadConfigSync(paths) {
3569
3913
  }
3570
3914
  }
3571
3915
  async function saveConfig(paths, config) {
3572
- await writeFile4(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
3573
- const legacy = path9.join(paths.haiveDir, LEGACY_CONFIG_FILE);
3574
- if (existsSync7(legacy)) {
3916
+ await writeFile5(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
3917
+ const legacy = path10.join(paths.haiveDir, LEGACY_CONFIG_FILE);
3918
+ if (existsSync8(legacy)) {
3575
3919
  try {
3576
3920
  await rm(legacy, { force: true });
3577
3921
  } catch {
@@ -3594,21 +3938,21 @@ function mergeConfig(base, override) {
3594
3938
  }
3595
3939
 
3596
3940
  // src/cross-repo.ts
3597
- import { existsSync as existsSync8 } from "fs";
3598
- import { mkdir as mkdir5, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
3599
- import path10 from "path";
3941
+ import { existsSync as existsSync9 } from "fs";
3942
+ import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
3943
+ import path11 from "path";
3600
3944
  import { spawnSync as spawnSync2 } from "child_process";
3601
3945
  async function loadImportMap(cacheDir) {
3602
- const mapPath = path10.join(cacheDir, "import-map.json");
3603
- if (!existsSync8(mapPath)) return {};
3946
+ const mapPath = path11.join(cacheDir, "import-map.json");
3947
+ if (!existsSync9(mapPath)) return {};
3604
3948
  try {
3605
- return JSON.parse(await readFile8(mapPath, "utf8"));
3949
+ return JSON.parse(await readFile9(mapPath, "utf8"));
3606
3950
  } catch {
3607
3951
  return {};
3608
3952
  }
3609
3953
  }
3610
3954
  async function saveImportMap(cacheDir, map) {
3611
- await writeFile5(path10.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
3955
+ await writeFile6(path11.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
3612
3956
  }
3613
3957
  async function pullCrossRepoSources(paths, config, projectRoot) {
3614
3958
  const sources = config.crossRepoSources ?? [];
@@ -3629,8 +3973,8 @@ async function pullFromSource(paths, source, projectRoot) {
3629
3973
  };
3630
3974
  let sourceRoot = null;
3631
3975
  if (source.path) {
3632
- const resolved = path10.resolve(projectRoot, source.path);
3633
- if (!existsSync8(resolved)) {
3976
+ const resolved = path11.resolve(projectRoot, source.path);
3977
+ if (!existsSync9(resolved)) {
3634
3978
  report.errors.push(`Path not found: ${resolved}`);
3635
3979
  return report;
3636
3980
  }
@@ -3643,7 +3987,7 @@ async function pullFromSource(paths, source, projectRoot) {
3643
3987
  return report;
3644
3988
  }
3645
3989
  const sourcePaths = resolveHaivePaths(sourceRoot);
3646
- if (!existsSync8(sourcePaths.memoriesDir)) {
3990
+ if (!existsSync9(sourcePaths.memoriesDir)) {
3647
3991
  report.errors.push(`No .ai/memories/ found at ${sourceRoot}`);
3648
3992
  return report;
3649
3993
  }
@@ -3666,10 +4010,10 @@ async function pullFromSource(paths, source, projectRoot) {
3666
4010
  report.skipped.push("no shared memories found in source");
3667
4011
  return report;
3668
4012
  }
3669
- const destDir = path10.join(paths.memoriesDir, "shared", source.name);
3670
- await mkdir5(destDir, { recursive: true });
3671
- const cacheDir = path10.join(paths.haiveDir, ".cache", "cross-repo", source.name);
3672
- await mkdir5(cacheDir, { recursive: true });
4013
+ const destDir = path11.join(paths.memoriesDir, "shared", source.name);
4014
+ await mkdir6(destDir, { recursive: true });
4015
+ const cacheDir = path11.join(paths.haiveDir, ".cache", "cross-repo", source.name);
4016
+ await mkdir6(cacheDir, { recursive: true });
3673
4017
  const importMap = await loadImportMap(cacheDir);
3674
4018
  const mapDirty = false;
3675
4019
  let dirty = mapDirty;
@@ -3683,7 +4027,7 @@ async function pullFromSource(paths, source, projectRoot) {
3683
4027
 
3684
4028
  `;
3685
4029
  const existingLocalPath = importMap[sourceId];
3686
- if (existingLocalPath && existsSync8(existingLocalPath)) {
4030
+ if (existingLocalPath && existsSync9(existingLocalPath)) {
3687
4031
  const existingFiles = await loadMemoriesFromDir(destDir);
3688
4032
  const existingEntry = existingFiles.find(({ filePath }) => filePath === existingLocalPath);
3689
4033
  const sourceBodyStripped = memory.body.trim();
@@ -3694,7 +4038,7 @@ async function pullFromSource(paths, source, projectRoot) {
3694
4038
  }
3695
4039
  const updatedBody = importedBodyPrefix + memory.body;
3696
4040
  if (existingEntry) {
3697
- await writeFile5(
4041
+ await writeFile6(
3698
4042
  existingLocalPath,
3699
4043
  serializeMemory({ frontmatter: existingEntry.memory.frontmatter, body: updatedBody }),
3700
4044
  "utf8"
@@ -3718,8 +4062,8 @@ async function pullFromSource(paths, source, projectRoot) {
3718
4062
  topic: fm.topic ? `${source.name}:${fm.topic}` : void 0
3719
4063
  });
3720
4064
  const body = importedBodyPrefix + memory.body;
3721
- const destPath = path10.join(destDir, `${newFm.id}.md`);
3722
- await writeFile5(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
4065
+ const destPath = path11.join(destDir, `${newFm.id}.md`);
4066
+ await writeFile6(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
3723
4067
  importMap[sourceId] = destPath;
3724
4068
  dirty = true;
3725
4069
  report.imported.push(sourceId);
@@ -3729,9 +4073,9 @@ async function pullFromSource(paths, source, projectRoot) {
3729
4073
  return report;
3730
4074
  }
3731
4075
  async function cloneOrFetchGitSource(source, paths, report) {
3732
- const cacheDir = path10.join(paths.haiveDir, ".cache", "cross-repo", source.name);
3733
- await mkdir5(cacheDir, { recursive: true });
3734
- if (existsSync8(path10.join(cacheDir, ".git"))) {
4076
+ const cacheDir = path11.join(paths.haiveDir, ".cache", "cross-repo", source.name);
4077
+ await mkdir6(cacheDir, { recursive: true });
4078
+ if (existsSync9(path11.join(cacheDir, ".git"))) {
3735
4079
  const result = spawnSync2("git", ["fetch", "--depth=1", "origin"], {
3736
4080
  cwd: cacheDir,
3737
4081
  encoding: "utf8"
@@ -3756,9 +4100,9 @@ async function cloneOrFetchGitSource(source, paths, report) {
3756
4100
  }
3757
4101
 
3758
4102
  // src/dep-tracker.ts
3759
- import { existsSync as existsSync9 } from "fs";
3760
- import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
3761
- import path11 from "path";
4103
+ import { existsSync as existsSync10 } from "fs";
4104
+ import { readFile as readFile10, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
4105
+ import path12 from "path";
3762
4106
  function parsePackageJson(content) {
3763
4107
  try {
3764
4108
  const pkg = JSON.parse(content);
@@ -3830,7 +4174,7 @@ var KNOWN_MANIFESTS = [
3830
4174
  { name: "pom.xml", parser: parsePomXml }
3831
4175
  ];
3832
4176
  function getParser(file) {
3833
- const base = path11.basename(file);
4177
+ const base = path12.basename(file);
3834
4178
  return KNOWN_MANIFESTS.find((m) => m.name === base)?.parser ?? null;
3835
4179
  }
3836
4180
  function extractMajor(version) {
@@ -3848,32 +4192,32 @@ function isMajorBump(from, to) {
3848
4192
  }
3849
4193
  function resolveManifestFiles(projectRoot, configuredFiles) {
3850
4194
  if (configuredFiles !== void 0) {
3851
- return configuredFiles.map((f) => path11.resolve(projectRoot, f)).filter(existsSync9);
4195
+ return configuredFiles.map((f) => path12.resolve(projectRoot, f)).filter(existsSync10);
3852
4196
  }
3853
- return KNOWN_MANIFESTS.map(({ name }) => path11.join(projectRoot, name)).filter(existsSync9);
4197
+ return KNOWN_MANIFESTS.map(({ name }) => path12.join(projectRoot, name)).filter(existsSync10);
3854
4198
  }
3855
4199
  async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
3856
- const contractsDir = path11.join(haiveDir, "contracts");
3857
- await mkdir6(contractsDir, { recursive: true });
4200
+ const contractsDir = path12.join(haiveDir, "contracts");
4201
+ await mkdir7(contractsDir, { recursive: true });
3858
4202
  const results = [];
3859
4203
  for (const manifestPath of manifestFiles) {
3860
4204
  const parser2 = getParser(manifestPath);
3861
4205
  if (!parser2) continue;
3862
- const content = await readFile9(manifestPath, "utf8");
4206
+ const content = await readFile10(manifestPath, "utf8");
3863
4207
  const currentDeps = parser2(content);
3864
- const lockName = `deps-${path11.basename(manifestPath)}.lock`;
3865
- const lockPath = path11.join(contractsDir, lockName);
3866
- if (!existsSync9(lockPath)) {
4208
+ const lockName = `deps-${path12.basename(manifestPath)}.lock`;
4209
+ const lockPath = path12.join(contractsDir, lockName);
4210
+ if (!existsSync10(lockPath)) {
3867
4211
  const snapshot2 = {
3868
- file: path11.relative(projectRoot, manifestPath),
3869
- format: path11.basename(manifestPath),
4212
+ file: path12.relative(projectRoot, manifestPath),
4213
+ format: path12.basename(manifestPath),
3870
4214
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
3871
4215
  deps: currentDeps
3872
4216
  };
3873
- await writeFile6(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
4217
+ await writeFile7(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
3874
4218
  continue;
3875
4219
  }
3876
- const snapshot = JSON.parse(await readFile9(lockPath, "utf8"));
4220
+ const snapshot = JSON.parse(await readFile10(lockPath, "utf8"));
3877
4221
  const changes = [];
3878
4222
  for (const [name, currentVer] of Object.entries(currentDeps)) {
3879
4223
  const prevVer = snapshot.deps[name];
@@ -3887,22 +4231,22 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
3887
4231
  }
3888
4232
  }
3889
4233
  if (changes.length > 0) {
3890
- results.push({ file: path11.relative(projectRoot, manifestPath), changes });
4234
+ results.push({ file: path12.relative(projectRoot, manifestPath), changes });
3891
4235
  const updated = {
3892
4236
  ...snapshot,
3893
4237
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
3894
4238
  deps: currentDeps
3895
4239
  };
3896
- await writeFile6(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
4240
+ await writeFile7(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
3897
4241
  }
3898
4242
  }
3899
4243
  return results;
3900
4244
  }
3901
4245
 
3902
4246
  // src/contract-watcher.ts
3903
- import { existsSync as existsSync10 } from "fs";
3904
- import { readFile as readFile10, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
3905
- import path12 from "path";
4247
+ import { existsSync as existsSync11 } from "fs";
4248
+ import { readFile as readFile11, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
4249
+ import path13 from "path";
3906
4250
  import crypto from "crypto";
3907
4251
  function sha256(content) {
3908
4252
  return crypto.createHash("sha256").update(content).digest("hex");
@@ -4105,14 +4449,14 @@ function diffSnapshots(before, after) {
4105
4449
  return changes;
4106
4450
  }
4107
4451
  function contractLockPath(haiveDir, name) {
4108
- return path12.join(haiveDir, "contracts", `${name}.lock`);
4452
+ return path13.join(haiveDir, "contracts", `${name}.lock`);
4109
4453
  }
4110
4454
  async function snapshotContract(projectRoot, haiveDir, contract) {
4111
- const filePath = path12.resolve(projectRoot, contract.path);
4112
- if (!existsSync10(filePath)) {
4455
+ const filePath = path13.resolve(projectRoot, contract.path);
4456
+ if (!existsSync11(filePath)) {
4113
4457
  throw new Error(`Contract file not found: ${filePath}`);
4114
4458
  }
4115
- const content = await readFile10(filePath, "utf8");
4459
+ const content = await readFile11(filePath, "utf8");
4116
4460
  const parsed = parseByFormat(content, contract.format, filePath);
4117
4461
  const snapshot = {
4118
4462
  name: contract.name,
@@ -4122,23 +4466,23 @@ async function snapshotContract(projectRoot, haiveDir, contract) {
4122
4466
  hash: sha256(content),
4123
4467
  ...parsed
4124
4468
  };
4125
- const contractsDir = path12.join(haiveDir, "contracts");
4126
- await mkdir7(contractsDir, { recursive: true });
4127
- await writeFile7(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
4469
+ const contractsDir = path13.join(haiveDir, "contracts");
4470
+ await mkdir8(contractsDir, { recursive: true });
4471
+ await writeFile8(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
4128
4472
  return snapshot;
4129
4473
  }
4130
4474
  async function diffContract(projectRoot, haiveDir, contract) {
4131
- const filePath = path12.resolve(projectRoot, contract.path);
4132
- if (!existsSync10(filePath)) {
4475
+ const filePath = path13.resolve(projectRoot, contract.path);
4476
+ if (!existsSync11(filePath)) {
4133
4477
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
4134
4478
  }
4135
4479
  const lockPath = contractLockPath(haiveDir, contract.name);
4136
- if (!existsSync10(lockPath)) {
4480
+ if (!existsSync11(lockPath)) {
4137
4481
  await snapshotContract(projectRoot, haiveDir, contract);
4138
4482
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
4139
4483
  }
4140
- const content = await readFile10(filePath, "utf8");
4141
- const beforeSnapshot = JSON.parse(await readFile10(lockPath, "utf8"));
4484
+ const content = await readFile11(filePath, "utf8");
4485
+ const beforeSnapshot = JSON.parse(await readFile11(lockPath, "utf8"));
4142
4486
  const afterParsed = parseByFormat(content, contract.format, filePath);
4143
4487
  const afterSnapshot = {
4144
4488
  ...beforeSnapshot,
@@ -4148,7 +4492,7 @@ async function diffContract(projectRoot, haiveDir, contract) {
4148
4492
  };
4149
4493
  const changes = diffSnapshots(beforeSnapshot, afterSnapshot);
4150
4494
  if (changes.length > 0) {
4151
- await writeFile7(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
4495
+ await writeFile8(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
4152
4496
  }
4153
4497
  return {
4154
4498
  contract: contract.name,
@@ -4166,27 +4510,27 @@ async function watchContracts(projectRoot, haiveDir, contractFiles) {
4166
4510
  }
4167
4511
 
4168
4512
  // src/usage-log.ts
4169
- import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile11, stat as stat2 } from "fs/promises";
4170
- import { existsSync as existsSync11 } from "fs";
4171
- import path13 from "path";
4513
+ import { appendFile as appendFile2, mkdir as mkdir9, readFile as readFile12, stat as stat3 } from "fs/promises";
4514
+ import { existsSync as existsSync12 } from "fs";
4515
+ import path14 from "path";
4172
4516
  var USAGE_LOG_FILE = "tool-usage.jsonl";
4173
4517
  var USAGE_LOG_DIR = ".usage";
4174
4518
  function usageLogPath(paths) {
4175
- return path13.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
4519
+ return path14.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
4176
4520
  }
4177
4521
  async function appendUsageEvent(paths, event) {
4178
4522
  try {
4179
4523
  const file = usageLogPath(paths);
4180
- const dir = path13.dirname(file);
4181
- if (!existsSync11(dir)) await mkdir8(dir, { recursive: true });
4524
+ const dir = path14.dirname(file);
4525
+ if (!existsSync12(dir)) await mkdir9(dir, { recursive: true });
4182
4526
  await appendFile2(file, JSON.stringify(event) + "\n", "utf8");
4183
4527
  } catch {
4184
4528
  }
4185
4529
  }
4186
4530
  async function readUsageEvents(paths) {
4187
4531
  const file = usageLogPath(paths);
4188
- if (!existsSync11(file)) return [];
4189
- const raw = await readFile11(file, "utf8");
4532
+ if (!existsSync12(file)) return [];
4533
+ const raw = await readFile12(file, "utf8");
4190
4534
  const out = [];
4191
4535
  for (const line of raw.split("\n")) {
4192
4536
  if (!line) continue;
@@ -4235,25 +4579,25 @@ function parseSince(input) {
4235
4579
  }
4236
4580
  async function usageLogSize(paths) {
4237
4581
  const file = usageLogPath(paths);
4238
- if (!existsSync11(file)) return { exists: false, size_bytes: 0, lines: 0 };
4239
- const st = await stat2(file);
4240
- const raw = await readFile11(file, "utf8");
4582
+ if (!existsSync12(file)) return { exists: false, size_bytes: 0, lines: 0 };
4583
+ const st = await stat3(file);
4584
+ const raw = await readFile12(file, "utf8");
4241
4585
  return { exists: true, size_bytes: st.size, lines: raw.split("\n").filter((l) => l).length };
4242
4586
  }
4243
4587
 
4244
4588
  // src/friction.ts
4245
- import { appendFile as appendFile3, mkdir as mkdir9, readFile as readFile12, writeFile as writeFile8 } from "fs/promises";
4246
- import { existsSync as existsSync12 } from "fs";
4247
- import { createHash as createHash2 } from "crypto";
4248
- import path14 from "path";
4589
+ import { appendFile as appendFile3, mkdir as mkdir10, readFile as readFile13, writeFile as writeFile9 } from "fs/promises";
4590
+ import { existsSync as existsSync13 } from "fs";
4591
+ import { createHash as createHash3 } from "crypto";
4592
+ import path15 from "path";
4249
4593
  var FRICTION_LOG_FILE = "friction.jsonl";
4250
4594
  var FRICTION_STATE_FILE = "friction-state.json";
4251
4595
  var FRICTION_FIELD_MAX = 2e3;
4252
4596
  function frictionLogPath(paths) {
4253
- return path14.join(paths.runtimeDir, FRICTION_LOG_FILE);
4597
+ return path15.join(paths.runtimeDir, FRICTION_LOG_FILE);
4254
4598
  }
4255
4599
  function frictionStatePath(paths) {
4256
- return path14.join(paths.runtimeDir, FRICTION_STATE_FILE);
4600
+ return path15.join(paths.runtimeDir, FRICTION_STATE_FILE);
4257
4601
  }
4258
4602
  function normalizeFrictionSummary(value) {
4259
4603
  return value.toLowerCase().replace(/[a-z]?:?[\\/](?:[\w.-]+[\\/])+/g, "/").replace(/\d{3,}/g, "N").replace(/\s+/g, " ").trim();
@@ -4264,7 +4608,7 @@ function frictionFingerprint(input) {
4264
4608
  input.surface.trim().toLowerCase(),
4265
4609
  normalizeFrictionSummary(input.summary)
4266
4610
  ].join("|");
4267
- return createHash2("sha256").update(basis).digest("hex").slice(0, 16);
4611
+ return createHash3("sha256").update(basis).digest("hex").slice(0, 16);
4268
4612
  }
4269
4613
  function truncate(value) {
4270
4614
  if (value === void 0) return void 0;
@@ -4298,7 +4642,7 @@ async function appendFrictionReport(paths, input) {
4298
4642
  };
4299
4643
  const prior = (await readFrictionReports(paths)).filter((r) => r.fingerprint === fingerprint);
4300
4644
  try {
4301
- if (!existsSync12(paths.runtimeDir)) await mkdir9(paths.runtimeDir, { recursive: true });
4645
+ if (!existsSync13(paths.runtimeDir)) await mkdir10(paths.runtimeDir, { recursive: true });
4302
4646
  await appendFile3(frictionLogPath(paths), JSON.stringify(report) + "\n", "utf8");
4303
4647
  } catch {
4304
4648
  }
@@ -4312,10 +4656,10 @@ async function appendFrictionReport(paths, input) {
4312
4656
  }
4313
4657
  async function readFrictionReports(paths) {
4314
4658
  const file = frictionLogPath(paths);
4315
- if (!existsSync12(file)) return [];
4659
+ if (!existsSync13(file)) return [];
4316
4660
  let raw;
4317
4661
  try {
4318
- raw = await readFile12(file, "utf8");
4662
+ raw = await readFile13(file, "utf8");
4319
4663
  } catch {
4320
4664
  return [];
4321
4665
  }
@@ -4332,17 +4676,17 @@ async function readFrictionReports(paths) {
4332
4676
  }
4333
4677
  async function loadFrictionState(paths) {
4334
4678
  const file = frictionStatePath(paths);
4335
- if (!existsSync12(file)) return {};
4679
+ if (!existsSync13(file)) return {};
4336
4680
  try {
4337
- const parsed = JSON.parse(await readFile12(file, "utf8"));
4681
+ const parsed = JSON.parse(await readFile13(file, "utf8"));
4338
4682
  return parsed && typeof parsed === "object" ? parsed : {};
4339
4683
  } catch {
4340
4684
  return {};
4341
4685
  }
4342
4686
  }
4343
4687
  async function saveFrictionState(paths, state) {
4344
- if (!existsSync12(paths.runtimeDir)) await mkdir9(paths.runtimeDir, { recursive: true });
4345
- await writeFile8(frictionStatePath(paths), JSON.stringify(state, null, 2) + "\n", "utf8");
4688
+ if (!existsSync13(paths.runtimeDir)) await mkdir10(paths.runtimeDir, { recursive: true });
4689
+ await writeFile9(frictionStatePath(paths), JSON.stringify(state, null, 2) + "\n", "utf8");
4346
4690
  }
4347
4691
  async function setFrictionStatus(paths, fingerprint, status, url) {
4348
4692
  const state = await loadFrictionState(paths);
@@ -4479,21 +4823,21 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
4479
4823
  }
4480
4824
 
4481
4825
  // src/resolve-project.ts
4482
- import { existsSync as existsSync13 } from "fs";
4483
- import path15 from "path";
4826
+ import { existsSync as existsSync14 } from "fs";
4827
+ import path16 from "path";
4484
4828
  var ROOT_MARKERS2 = [".ai", ".git", "package.json"];
4485
4829
  function markersAtRoot(root) {
4486
4830
  const found = [];
4487
4831
  for (const m of ROOT_MARKERS2) {
4488
- if (existsSync13(path15.join(root, m))) found.push(m);
4832
+ if (existsSync14(path16.join(root, m))) found.push(m);
4489
4833
  }
4490
4834
  return found;
4491
4835
  }
4492
4836
  function resolveProjectInfo(opts = {}) {
4493
4837
  const env = opts.env ?? process.env;
4494
- const cwd = path15.resolve(opts.cwd ?? process.cwd());
4838
+ const cwd = path16.resolve(opts.cwd ?? process.cwd());
4495
4839
  const raw = env.HAIVE_PROJECT_ROOT;
4496
- const explicit = raw !== void 0 && raw !== "" ? path15.resolve(raw) : null;
4840
+ const explicit = raw !== void 0 && raw !== "" ? path16.resolve(raw) : null;
4497
4841
  const resolvedRoot = explicit ?? findProjectRoot(cwd);
4498
4842
  const paths = resolveHaivePaths(resolvedRoot);
4499
4843
  return {
@@ -4501,8 +4845,8 @@ function resolveProjectInfo(opts = {}) {
4501
4845
  resolved_root: resolvedRoot,
4502
4846
  haive_project_root_env: explicit,
4503
4847
  explicit_root: explicit != null,
4504
- haive_dir_exists: existsSync13(paths.haiveDir),
4505
- memories_dir_exists: existsSync13(paths.memoriesDir),
4848
+ haive_dir_exists: existsSync14(paths.haiveDir),
4849
+ memories_dir_exists: existsSync14(paths.memoriesDir),
4506
4850
  runtime_dir: paths.runtimeDir,
4507
4851
  markers_found: markersAtRoot(resolvedRoot)
4508
4852
  };
@@ -4757,16 +5101,16 @@ function findLexicalConflictPairs(memories, opts) {
4757
5101
  }
4758
5102
 
4759
5103
  // src/runtime-journal.ts
4760
- import { mkdir as mkdir10, readFile as readFile13, appendFile as appendFile4 } from "fs/promises";
4761
- import { existsSync as existsSync14 } from "fs";
4762
- import path16 from "path";
5104
+ import { mkdir as mkdir11, readFile as readFile14, appendFile as appendFile4 } from "fs/promises";
5105
+ import { existsSync as existsSync15 } from "fs";
5106
+ import path17 from "path";
4763
5107
  var RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
4764
5108
  function runtimeJournalPath(paths) {
4765
- return path16.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
5109
+ return path17.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
4766
5110
  }
4767
5111
  async function appendRuntimeJournalEntry(paths, entry) {
4768
5112
  try {
4769
- await mkdir10(paths.runtimeDir, { recursive: true });
5113
+ await mkdir11(paths.runtimeDir, { recursive: true });
4770
5114
  const line = {
4771
5115
  ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
4772
5116
  kind: entry.kind,
@@ -4784,9 +5128,9 @@ async function appendRuntimeJournalEntry(paths, entry) {
4784
5128
  }
4785
5129
  async function readRuntimeJournalTail(paths, limit) {
4786
5130
  const file = runtimeJournalPath(paths);
4787
- if (!existsSync14(file) || limit <= 0) return [];
5131
+ if (!existsSync15(file) || limit <= 0) return [];
4788
5132
  try {
4789
- const raw = await readFile13(file, "utf8");
5133
+ const raw = await readFile14(file, "utf8");
4790
5134
  const lines = raw.trim().split("\n").filter(Boolean);
4791
5135
  const parsed = [];
4792
5136
  for (const line of lines.slice(-limit)) {
@@ -4802,22 +5146,22 @@ async function readRuntimeJournalTail(paths, limit) {
4802
5146
  }
4803
5147
 
4804
5148
  // src/enforcement.ts
4805
- import { mkdir as mkdir11, readdir as readdir4, readFile as readFile14, writeFile as writeFile9 } from "fs/promises";
4806
- import { existsSync as existsSync15 } from "fs";
4807
- import path17 from "path";
5149
+ import { mkdir as mkdir12, readdir as readdir4, readFile as readFile15, writeFile as writeFile10 } from "fs/promises";
5150
+ import { existsSync as existsSync16 } from "fs";
5151
+ import path18 from "path";
4808
5152
  var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
4809
5153
  var SESSION_RECAP_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
4810
5154
  function enforcementDir(paths) {
4811
- return path17.join(paths.runtimeDir, "enforcement");
5155
+ return path18.join(paths.runtimeDir, "enforcement");
4812
5156
  }
4813
5157
  function briefingMarkersDir(paths) {
4814
- return path17.join(enforcementDir(paths), "briefings");
5158
+ return path18.join(enforcementDir(paths), "briefings");
4815
5159
  }
4816
5160
  function normalizeSessionId(sessionId) {
4817
5161
  return (sessionId?.trim() || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 120);
4818
5162
  }
4819
5163
  function briefingMarkerPath(paths, sessionId) {
4820
- return path17.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
5164
+ return path18.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
4821
5165
  }
4822
5166
  async function writeBriefingMarker(paths, input) {
4823
5167
  const sessionId = normalizeSessionId(input.sessionId);
@@ -4842,8 +5186,8 @@ async function writeBriefingMarker(paths, input) {
4842
5186
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
4843
5187
  root: paths.root
4844
5188
  };
4845
- await mkdir11(briefingMarkersDir(paths), { recursive: true });
4846
- await writeFile9(
5189
+ await mkdir12(briefingMarkersDir(paths), { recursive: true });
5190
+ await writeFile10(
4847
5191
  briefingMarkerPath(paths, marker.session_id),
4848
5192
  JSON.stringify(marker, null, 2) + "\n",
4849
5193
  "utf8"
@@ -4852,9 +5196,9 @@ async function writeBriefingMarker(paths, input) {
4852
5196
  }
4853
5197
  async function readSessionBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER_TTL_MS) {
4854
5198
  const file = briefingMarkerPath(paths, sessionId);
4855
- if (!existsSync15(file)) return null;
5199
+ if (!existsSync16(file)) return null;
4856
5200
  try {
4857
- const marker = JSON.parse(await readFile14(file, "utf8"));
5201
+ const marker = JSON.parse(await readFile15(file, "utf8"));
4858
5202
  const created = Date.parse(marker.created_at);
4859
5203
  if (!Number.isFinite(created) || Date.now() - created > ttlMs) return null;
4860
5204
  return marker;
@@ -4866,18 +5210,18 @@ async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER
4866
5210
  const now = Date.now();
4867
5211
  const candidates = [];
4868
5212
  const exact = briefingMarkerPath(paths, sessionId);
4869
- if (existsSync15(exact)) candidates.push(exact);
5213
+ if (existsSync16(exact)) candidates.push(exact);
4870
5214
  try {
4871
5215
  const dir = briefingMarkersDir(paths);
4872
5216
  const files = await readdir4(dir);
4873
5217
  for (const file of files) {
4874
- if (file.endsWith(".json")) candidates.push(path17.join(dir, file));
5218
+ if (file.endsWith(".json")) candidates.push(path18.join(dir, file));
4875
5219
  }
4876
5220
  } catch {
4877
5221
  }
4878
5222
  for (const file of new Set(candidates)) {
4879
5223
  try {
4880
- const marker = JSON.parse(await readFile14(file, "utf8"));
5224
+ const marker = JSON.parse(await readFile15(file, "utf8"));
4881
5225
  const created = Date.parse(marker.created_at);
4882
5226
  if (Number.isFinite(created) && now - created <= ttlMs) return true;
4883
5227
  } catch {
@@ -4889,12 +5233,12 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
4889
5233
  const now = Date.now();
4890
5234
  const candidates = [];
4891
5235
  const exact = briefingMarkerPath(paths, sessionId);
4892
- if (existsSync15(exact)) candidates.push(exact);
5236
+ if (existsSync16(exact)) candidates.push(exact);
4893
5237
  try {
4894
5238
  const dir = briefingMarkersDir(paths);
4895
5239
  const files = await readdir4(dir);
4896
5240
  for (const file of files) {
4897
- if (file.endsWith(".json")) candidates.push(path17.join(dir, file));
5241
+ if (file.endsWith(".json")) candidates.push(path18.join(dir, file));
4898
5242
  }
4899
5243
  } catch {
4900
5244
  }
@@ -4902,7 +5246,7 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
4902
5246
  let freshestTs = 0;
4903
5247
  for (const file of new Set(candidates)) {
4904
5248
  try {
4905
- const marker = JSON.parse(await readFile14(file, "utf8"));
5249
+ const marker = JSON.parse(await readFile15(file, "utf8"));
4906
5250
  const created = Date.parse(marker.created_at);
4907
5251
  if (!Number.isFinite(created) || now - created > ttlMs) continue;
4908
5252
  if (created > freshestTs) {
@@ -4952,15 +5296,15 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
4952
5296
  }
4953
5297
 
4954
5298
  // src/sensor-ledger.ts
4955
- import { createHash as createHash3 } from "crypto";
4956
- import { existsSync as existsSync16, readFileSync as readFileSync2 } from "fs";
4957
- import { appendFile as appendFile5, mkdir as mkdir12, readFile as readFile15, rename, writeFile as writeFile10 } from "fs/promises";
4958
- import path18 from "path";
5299
+ import { createHash as createHash4 } from "crypto";
5300
+ import { existsSync as existsSync17, readFileSync as readFileSync2 } from "fs";
5301
+ import { appendFile as appendFile5, mkdir as mkdir13, readFile as readFile16, rename, writeFile as writeFile11 } from "fs/promises";
5302
+ import path19 from "path";
4959
5303
  var MAX_LINES = 1e4;
4960
5304
  var RETAINED_LINES = 8e3;
4961
5305
  var DAY_MS = 864e5;
4962
5306
  function sensorLedgerPath(paths) {
4963
- return path18.join(paths.runtimeDir, "enforcement", "sensor-ledger.ndjson");
5307
+ return path19.join(paths.runtimeDir, "enforcement", "sensor-ledger.ndjson");
4964
5308
  }
4965
5309
  function isEvaluation(value) {
4966
5310
  if (!value || typeof value !== "object") return false;
@@ -4971,13 +5315,13 @@ async function appendSensorEvaluations(paths, evaluations) {
4971
5315
  if (evaluations.length === 0) return;
4972
5316
  try {
4973
5317
  const file = sensorLedgerPath(paths);
4974
- await mkdir12(path18.dirname(file), { recursive: true });
5318
+ await mkdir13(path19.dirname(file), { recursive: true });
4975
5319
  await appendFile5(file, evaluations.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
4976
- const raw = await readFile15(file, "utf8");
5320
+ const raw = await readFile16(file, "utf8");
4977
5321
  const lines = raw.split("\n").filter(Boolean);
4978
5322
  if (lines.length > MAX_LINES) {
4979
5323
  const temp = `${file}.${process.pid}.tmp`;
4980
- await writeFile10(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
5324
+ await writeFile11(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
4981
5325
  await rename(temp, file);
4982
5326
  }
4983
5327
  } catch {
@@ -4986,9 +5330,9 @@ async function appendSensorEvaluations(paths, evaluations) {
4986
5330
  async function loadSensorLedger(paths, opts = {}) {
4987
5331
  try {
4988
5332
  const file = sensorLedgerPath(paths);
4989
- if (!existsSync16(file)) return [];
5333
+ if (!existsSync17(file)) return [];
4990
5334
  const since = opts.since ? Date.parse(opts.since) : Number.NEGATIVE_INFINITY;
4991
- const raw = await readFile15(file, "utf8");
5335
+ const raw = await readFile16(file, "utf8");
4992
5336
  const out = [];
4993
5337
  for (const line of raw.split("\n")) {
4994
5338
  if (!line.trim()) continue;
@@ -5010,11 +5354,11 @@ function computeScopeHash(root, scopedFiles) {
5010
5354
  try {
5011
5355
  const files = [...new Set(scopedFiles.map((f) => f.replace(/\\/g, "/")))].sort();
5012
5356
  if (files.length === 0) return "";
5013
- const hash = createHash3("sha256");
5357
+ const hash = createHash4("sha256");
5014
5358
  let included = 0;
5015
5359
  for (const rel of files) {
5016
- const abs = path18.resolve(root, rel);
5017
- if (!existsSync16(abs)) continue;
5360
+ const abs = path19.resolve(root, rel);
5361
+ if (!existsSync17(abs)) continue;
5018
5362
  try {
5019
5363
  hash.update(rel);
5020
5364
  hash.update("\0");
@@ -5748,8 +6092,8 @@ function normalizeFindingSeverity(raw) {
5748
6092
  return "info";
5749
6093
  }
5750
6094
  }
5751
- function findingKey(tool, ruleId, path21) {
5752
- return `${tool}:${ruleId}:${path21}`;
6095
+ function findingKey(tool, ruleId, path22) {
6096
+ return `${tool}:${ruleId}:${path22}`;
5753
6097
  }
5754
6098
  function coerceJson(input) {
5755
6099
  if (typeof input === "string") {
@@ -5783,8 +6127,8 @@ function parseSarif(input) {
5783
6127
  const physical = asRecord(location.physicalLocation);
5784
6128
  const artifact = asRecord(physical.artifactLocation);
5785
6129
  const region = asRecord(physical.region);
5786
- const path21 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
5787
- if (!path21) continue;
6130
+ const path22 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
6131
+ if (!path22) continue;
5788
6132
  const line = typeof region.startLine === "number" ? region.startLine : void 0;
5789
6133
  const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
5790
6134
  findings.push({
@@ -5792,10 +6136,10 @@ function parseSarif(input) {
5792
6136
  ruleId,
5793
6137
  message: message.trim(),
5794
6138
  severity,
5795
- path: path21,
6139
+ path: path22,
5796
6140
  ...line !== void 0 ? { line } : {},
5797
6141
  ...snippet ? { snippet } : {},
5798
- key: findingKey(tool, ruleId, path21)
6142
+ key: findingKey(tool, ruleId, path22)
5799
6143
  });
5800
6144
  }
5801
6145
  }
@@ -5814,17 +6158,17 @@ function parseSonar(input) {
5814
6158
  (typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
5815
6159
  );
5816
6160
  const component = typeof issue.component === "string" ? issue.component : "";
5817
- const path21 = componentToPath(component);
5818
- if (!path21) continue;
6161
+ const path22 = componentToPath(component);
6162
+ if (!path22) continue;
5819
6163
  const line = typeof issue.line === "number" ? issue.line : void 0;
5820
6164
  findings.push({
5821
6165
  tool: "sonar",
5822
6166
  ruleId,
5823
6167
  message,
5824
6168
  severity,
5825
- path: path21,
6169
+ path: path22,
5826
6170
  ...line !== void 0 ? { line } : {},
5827
- key: findingKey("sonar", ruleId, path21)
6171
+ key: findingKey("sonar", ruleId, path22)
5828
6172
  });
5829
6173
  }
5830
6174
  return findings;
@@ -5837,7 +6181,7 @@ function parseEslintJson(input, opts = {}) {
5837
6181
  const file = asRecord(fileRaw);
5838
6182
  const rawPath = typeof file.filePath === "string" ? file.filePath : "";
5839
6183
  if (!rawPath) continue;
5840
- const path21 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
6184
+ const path22 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
5841
6185
  for (const msgRaw of asArray(file.messages)) {
5842
6186
  const msg = asRecord(msgRaw);
5843
6187
  const ruleId = typeof msg.ruleId === "string" && msg.ruleId ? msg.ruleId : "parse-error";
@@ -5849,9 +6193,9 @@ function parseEslintJson(input, opts = {}) {
5849
6193
  ruleId,
5850
6194
  message,
5851
6195
  severity,
5852
- path: path21,
6196
+ path: path22,
5853
6197
  ...line !== void 0 ? { line } : {},
5854
- key: findingKey("eslint", ruleId, path21)
6198
+ key: findingKey("eslint", ruleId, path22)
5855
6199
  });
5856
6200
  }
5857
6201
  }
@@ -6301,7 +6645,7 @@ function tallyHotFiles(paths, source = "agent") {
6301
6645
  if (!norm) continue;
6302
6646
  counts.set(norm, (counts.get(norm) ?? 0) + 1);
6303
6647
  }
6304
- return [...counts.entries()].map(([path21, changes]) => ({ path: path21, changes, source })).sort((a, b) => b.changes - a.changes);
6648
+ return [...counts.entries()].map(([path22, changes]) => ({ path: path22, changes, source })).sort((a, b) => b.changes - a.changes);
6305
6649
  }
6306
6650
  function mergeHotFiles(a, b) {
6307
6651
  const merged = /* @__PURE__ */ new Map();
@@ -6321,21 +6665,21 @@ function mergeHotFiles(a, b) {
6321
6665
  }
6322
6666
 
6323
6667
  // src/eval-history.ts
6324
- import { appendFile as appendFile6, mkdir as mkdir13, readFile as readFile16 } from "fs/promises";
6325
- import { existsSync as existsSync17 } from "fs";
6326
- import path19 from "path";
6668
+ import { appendFile as appendFile6, mkdir as mkdir14, readFile as readFile17 } from "fs/promises";
6669
+ import { existsSync as existsSync18 } from "fs";
6670
+ import path20 from "path";
6327
6671
  function evalHistoryPath(paths) {
6328
- return path19.join(paths.haiveDir, ".cache", "eval-history.jsonl");
6672
+ return path20.join(paths.haiveDir, ".cache", "eval-history.jsonl");
6329
6673
  }
6330
6674
  async function appendEvalHistory(paths, entry) {
6331
6675
  const file = evalHistoryPath(paths);
6332
- await mkdir13(path19.dirname(file), { recursive: true });
6676
+ await mkdir14(path20.dirname(file), { recursive: true });
6333
6677
  await appendFile6(file, JSON.stringify(entry) + "\n", "utf8");
6334
6678
  }
6335
6679
  async function loadEvalHistory(paths) {
6336
6680
  const file = evalHistoryPath(paths);
6337
- if (!existsSync17(file)) return [];
6338
- const raw = await readFile16(file, "utf8").catch(() => "");
6681
+ if (!existsSync18(file)) return [];
6682
+ const raw = await readFile17(file, "utf8").catch(() => "");
6339
6683
  const out = [];
6340
6684
  for (const line of raw.split("\n")) {
6341
6685
  const trimmed = line.trim();
@@ -6678,12 +7022,12 @@ ${trimmed}`;
6678
7022
  }
6679
7023
 
6680
7024
  // src/handoff.ts
6681
- import { writeFile as writeFile11, readFile as readFile17, stat as stat3 } from "fs/promises";
6682
- import { existsSync as existsSync18 } from "fs";
6683
- import path20 from "path";
7025
+ import { writeFile as writeFile12, readFile as readFile18, stat as stat4 } from "fs/promises";
7026
+ import { existsSync as existsSync19 } from "fs";
7027
+ import path21 from "path";
6684
7028
  var HANDOFF_FILENAME = "NEXT.md";
6685
7029
  function handoffFilePath(root) {
6686
- return path20.join(root, HANDOFF_FILENAME);
7030
+ return path21.join(root, HANDOFF_FILENAME);
6687
7031
  }
6688
7032
  function buildHandoffMarkdown(data) {
6689
7033
  const at = (data.at ?? /* @__PURE__ */ new Date()).toISOString();
@@ -6732,20 +7076,20 @@ function buildHandoffMarkdown(data) {
6732
7076
  }
6733
7077
  async function writeSessionHandoff(root, data) {
6734
7078
  const file = handoffFilePath(root);
6735
- await writeFile11(file, buildHandoffMarkdown(data), "utf8");
7079
+ await writeFile12(file, buildHandoffMarkdown(data), "utf8");
6736
7080
  return file;
6737
7081
  }
6738
7082
  async function readSessionHandoff(root) {
6739
7083
  const file = handoffFilePath(root);
6740
- if (!existsSync18(file)) return null;
6741
- const raw = await readFile17(file, "utf8").catch(() => "");
7084
+ if (!existsSync19(file)) return null;
7085
+ const raw = await readFile18(file, "utf8").catch(() => "");
6742
7086
  return raw.trim() ? raw : null;
6743
7087
  }
6744
7088
  async function handoffAgeMs(root, now = /* @__PURE__ */ new Date()) {
6745
7089
  const file = handoffFilePath(root);
6746
- if (!existsSync18(file)) return null;
7090
+ if (!existsSync19(file)) return null;
6747
7091
  try {
6748
- const s = await stat3(file);
7092
+ const s = await stat4(file);
6749
7093
  return Math.max(0, now.getTime() - s.mtimeMs);
6750
7094
  } catch {
6751
7095
  return null;
@@ -6870,6 +7214,7 @@ export {
6870
7214
  CODE_MAP_FILE,
6871
7215
  CODE_STOPWORDS,
6872
7216
  CONFIG_FILE,
7217
+ CONTENT_CATCH_CODES,
6873
7218
  CrossRepoProvenanceSchema,
6874
7219
  DECAY_DAYS,
6875
7220
  DEFAULT_AUTO_PROMOTE_RULE,
@@ -6877,11 +7222,14 @@ export {
6877
7222
  DEFAULT_CONFIDENCE_THRESHOLDS,
6878
7223
  DEFAULT_CONFIG,
6879
7224
  DEFAULT_DORMANT_DAYS,
7225
+ DEFAULT_POSTURE,
6880
7226
  DEFAULT_PRIORITY_SIGNALS,
7227
+ DEFAULT_SPECIFICITY,
6881
7228
  ENV_WORKAROUND_TAGS,
6882
7229
  FRICTION_FIELD_MAX,
6883
7230
  FRICTION_LOG_FILE,
6884
7231
  FRICTION_STATE_FILE,
7232
+ GATE_REMINDER_WINDOW_MS,
6885
7233
  GUESSABLE_THRESHOLD,
6886
7234
  HAIVE_DIR,
6887
7235
  HAIVE_OWNED_FILES,
@@ -6895,6 +7243,8 @@ export {
6895
7243
  MemoryStatusSchema,
6896
7244
  MemoryTypeSchema,
6897
7245
  PREVENTION_DEBOUNCE_MS,
7246
+ PREVENTION_RECEIPT_MARKER,
7247
+ PROCESS_GATE_CODES,
6898
7248
  PROJECT_CONTEXT_FILE,
6899
7249
  PROJECT_CONTEXT_THROTTLE_MS,
6900
7250
  REVIEW_LEARNING_MARKER,
@@ -6904,12 +7254,14 @@ export {
6904
7254
  SENSOR_ABSENT_LOOKBACK,
6905
7255
  SENSOR_ABSENT_WINDOW,
6906
7256
  SESSION_RECAP_TTL_MS,
7257
+ SETUP_GATE_CODES,
6907
7258
  STACK_PACK_TAG,
6908
7259
  SensorSchema,
6909
7260
  TEST_FRAMEWORKS,
6910
7261
  USAGE_FILE,
6911
7262
  USAGE_LOG_DIR,
6912
7263
  USAGE_LOG_FILE,
7264
+ WEAK_ANCHOR_CHURN_RATIO,
6913
7265
  addedLineNumbersFromDiff,
6914
7266
  addedLinesFromDiff,
6915
7267
  aggregateRetrieval,
@@ -6917,6 +7269,7 @@ export {
6917
7269
  aggregateUsage,
6918
7270
  allocateBudget,
6919
7271
  anchorMatchesComponent,
7272
+ anchorSpecificity,
6920
7273
  antiPatternGateParams,
6921
7274
  appendEvalHistory,
6922
7275
  appendFrictionReport,
@@ -6932,10 +7285,12 @@ export {
6932
7285
  assessBootstrapState,
6933
7286
  assessScaffoldLoop,
6934
7287
  assessSensorHealth,
7288
+ auditAnchorSpecificity,
6935
7289
  bridgeMemorySummary,
6936
7290
  briefingMarkerPath,
6937
7291
  briefingMarkersDir,
6938
7292
  briefingProofLine,
7293
+ buildBaselineHealthFinding,
6939
7294
  buildCodeMap,
6940
7295
  buildCoverageIndex,
6941
7296
  buildDashboard,
@@ -6946,7 +7301,9 @@ export {
6946
7301
  buildProposeCommand,
6947
7302
  buildReport,
6948
7303
  bumpRead,
7304
+ churnForAnchors,
6949
7305
  classifyMemoryPriority,
7306
+ codeMapContentHash,
6950
7307
  codeMapPath,
6951
7308
  collectTimelineEntries,
6952
7309
  compactAutoRecapBody,
@@ -6955,6 +7312,7 @@ export {
6955
7312
  compareImpact,
6956
7313
  compileRegexSensor,
6957
7314
  componentOf,
7315
+ computeBaselineHealth,
6958
7316
  computeEvalTrend,
6959
7317
  computeGatePrecision,
6960
7318
  computeImpact,
@@ -6964,8 +7322,11 @@ export {
6964
7322
  configPath,
6965
7323
  contractLockPath,
6966
7324
  countSourceFilesOnDisk,
7325
+ decideVerdict,
7326
+ dedupeRefusals,
6967
7327
  deriveConfidence,
6968
7328
  deriveMainAreas,
7329
+ describePosture,
6969
7330
  detectAgentContext,
6970
7331
  detectSensorWeakening,
6971
7332
  detectStacksFromManifests,
@@ -6981,6 +7342,7 @@ export {
6981
7342
  evalHistoryPath,
6982
7343
  evaluateSkillActivation,
6983
7344
  existingGateMissShas,
7345
+ explainSensorRejection,
6984
7346
  extractActionsBriefBody,
6985
7347
  extractCorrectApproachExamples,
6986
7348
  extractReferencedPaths,
@@ -7033,6 +7395,7 @@ export {
7033
7395
  isStackPackSeed,
7034
7396
  isStylisticRule,
7035
7397
  isTemplateProjectContext,
7398
+ isWeakAnchor,
7036
7399
  judgeProposedSensor,
7037
7400
  lessonShortName,
7038
7401
  listMarkdownFilesRecursive,
@@ -7059,6 +7422,7 @@ export {
7059
7422
  mineSensorSeedFromDiff,
7060
7423
  moduleNameOf,
7061
7424
  newMemoryId,
7425
+ normalizeChurnPath,
7062
7426
  normalizeFindingSeverity,
7063
7427
  normalizeFramework,
7064
7428
  normalizeFrictionSummary,
@@ -7098,6 +7462,7 @@ export {
7098
7462
  readUsageEvents,
7099
7463
  recommendFeedbackAdjustment,
7100
7464
  recordApplied,
7465
+ recordGateReminder,
7101
7466
  recordPrevention,
7102
7467
  recordPreventionHits,
7103
7468
  recordProjectContextEmission,
@@ -7106,10 +7471,12 @@ export {
7106
7471
  renderBehaviourCoverageLine,
7107
7472
  renderBootstrapChecklist,
7108
7473
  renderCaughtForYou,
7474
+ renderPreventionComment,
7109
7475
  renderPreventionReceipt,
7110
7476
  renderPreventionReceiptShare,
7111
7477
  resolveBriefingBudget,
7112
7478
  resolveConfigPath,
7479
+ resolveGatePolicy,
7113
7480
  resolveHaivePaths,
7114
7481
  resolveManifestFiles,
7115
7482
  resolveProjectInfo,
@@ -7137,8 +7504,10 @@ export {
7137
7504
  sensorPromotedAtMap,
7138
7505
  sensorSelfCheck,
7139
7506
  sensorTargetsFromDiff,
7507
+ serializeCodeMap,
7140
7508
  serializeMemory,
7141
7509
  setFrictionStatus,
7510
+ shouldExpandGateReminder,
7142
7511
  snapshotContract,
7143
7512
  specificityScore,
7144
7513
  stripPrivate,