@hivelore/core 0.53.4 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,178 @@ 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
+
1207
1399
  // src/priority.ts
1208
1400
  var DEFAULT_PRIORITY_SIGNALS = {
1209
1401
  type: "",
@@ -1394,10 +1586,10 @@ function sensorPatternBrittleness(pattern) {
1394
1586
  function normalizeProjectPath(value) {
1395
1587
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
1396
1588
  }
1397
- function sensorAppliesToPath(sensor, anchorPaths, path20) {
1589
+ function sensorAppliesToPath(sensor, anchorPaths, path22) {
1398
1590
  const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
1399
1591
  if (scopes.length === 0) return true;
1400
- const target = normalizeProjectPath(path20);
1592
+ const target = normalizeProjectPath(path22);
1401
1593
  return scopes.some((rawScope) => {
1402
1594
  const scope = normalizeProjectPath(rawScope);
1403
1595
  if (!scope) return false;
@@ -1722,6 +1914,34 @@ function judgeProposedSensor(sensor, input) {
1722
1914
  }
1723
1915
  return { accepted: true, self_check, brittle };
1724
1916
  }
1917
+ function explainSensorRejection(verdict, context) {
1918
+ const retry = context.style === "cli" ? "re-run" : "re-propose";
1919
+ switch (verdict.reason) {
1920
+ case "fires-on-current": {
1921
+ const where = verdict.self_check.fired_on.join(", ");
1922
+ 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" })`;
1923
+ return [
1924
+ `A block sensor must be silent on the current code, and this one fires on: ${where}.`,
1925
+ "That means one of two things:",
1926
+ ` 1. The faulty pattern is STILL PRESENT \u2014 the usual case when you document a problem before`,
1927
+ ` fixing it. A block sensor cannot be armed yet. Arm it as a warning now, fix the code,`,
1928
+ ` then promote it:`,
1929
+ ` ${warnCommand}`,
1930
+ ` \u2026then re-run the same proposal with severity "block" once ${where} is clean.`,
1931
+ ` 2. The pattern also matches LEGITIMATE usage. Add or tighten the 'absent' companion so`,
1932
+ ` correct usage is excluded, then ${retry}.`
1933
+ ].join("\n");
1934
+ }
1935
+ case "fires-on-correct":
1936
+ 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}.`;
1937
+ case "missed-bad-example":
1938
+ return `The sensor did not match the bad example, so it won't catch the mistake. Adjust the pattern, then ${retry}.`;
1939
+ case "brittle":
1940
+ return `The pattern is brittle (${verdict.brittle}). Use a durable pattern (avoid hardcoded line numbers), then ${retry}.`;
1941
+ default:
1942
+ return `Re-propose with a discriminating pattern, then ${retry}.`;
1943
+ }
1944
+ }
1725
1945
  function isHarnessErrorOutput(output) {
1726
1946
  if (!output) return false;
1727
1947
  return HARNESS_ERROR_SIGNATURES.some((re) => re.test(output));
@@ -1884,7 +2104,33 @@ function suggestSensorSeed(body, anchorPaths, options = {}) {
1884
2104
  message: companion ? companionMessage(body, companion) : sensorMessageFromBody(body, token)
1885
2105
  };
1886
2106
  if (companion) seed.absent = escapeRegExp(companion.required);
1887
- return seed;
2107
+ return seedFiresOnCorrectUsage(seed, body) ? null : seed;
2108
+ }
2109
+ function seedFiresOnCorrectUsage(seed, body) {
2110
+ const correct = correctUsageText(body);
2111
+ if (!correct) return false;
2112
+ let re;
2113
+ try {
2114
+ re = new RegExp(seed.pattern, "m");
2115
+ } catch {
2116
+ return true;
2117
+ }
2118
+ if (!re.test(correct)) return false;
2119
+ if (seed.absent) {
2120
+ try {
2121
+ if (new RegExp(seed.absent, "m").test(correct)) return false;
2122
+ } catch {
2123
+ }
2124
+ }
2125
+ return true;
2126
+ }
2127
+ function correctUsageText(body) {
2128
+ const parts = [...extractCorrectApproachExamples(body)];
2129
+ 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;
2130
+ for (const match of body.matchAll(headingRe)) {
2131
+ if (match[1]) parts.push(match[1]);
2132
+ }
2133
+ return parts.join("\n").trim();
1888
2134
  }
1889
2135
  function suggestSensorFromMemory(body, anchorPaths, options = {}) {
1890
2136
  const seed = suggestSensorSeed(body, anchorPaths, options);
@@ -2649,10 +2895,11 @@ function allocateBudget(parts, maxTokens) {
2649
2895
  }
2650
2896
 
2651
2897
  // 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";
2898
+ import { mkdir as mkdir5, readFile as readFile7, readdir as readdir3, stat as stat2, writeFile as writeFile4 } from "fs/promises";
2899
+ import { createHash as createHash2 } from "crypto";
2900
+ import { existsSync as existsSync7 } from "fs";
2654
2901
  import { spawnSync } from "child_process";
2655
- import path8 from "path";
2902
+ import path9 from "path";
2656
2903
 
2657
2904
  // src/ast-parser.ts
2658
2905
  import { fileURLToPath } from "url";
@@ -3064,27 +3311,53 @@ var CODE_MAP_DEFAULT_EXCLUDE = [
3064
3311
  ];
3065
3312
  var TEST_FILE_RE = /\.(test|spec)\.[a-z]+$/i;
3066
3313
  function codeMapPath(paths) {
3067
- return path8.join(paths.haiveDir, CODE_MAP_FILE);
3314
+ return path9.join(paths.haiveDir, CODE_MAP_FILE);
3315
+ }
3316
+ function codeMapMetaPath(paths) {
3317
+ return path9.join(paths.runtimeDir, "code-map-meta.json");
3318
+ }
3319
+ function serializeCodeMap(map) {
3320
+ const files = {};
3321
+ for (const key of Object.keys(map.files).sort()) files[key] = map.files[key];
3322
+ return `${JSON.stringify({ version: map.version, files }, null, 2)}
3323
+ `;
3324
+ }
3325
+ function codeMapContentHash(map) {
3326
+ return createHash2("sha256").update(serializeCodeMap(map)).digest("hex").slice(0, 16);
3068
3327
  }
3069
3328
  async function loadCodeMap(paths) {
3070
3329
  const file = codeMapPath(paths);
3071
- if (!existsSync6(file)) return null;
3072
- return JSON.parse(await readFile6(file, "utf8"));
3330
+ if (!existsSync7(file)) return null;
3331
+ const parsed = JSON.parse(await readFile7(file, "utf8"));
3332
+ 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());
3333
+ return { ...parsed, root: parsed.root ?? paths.root, generated_at: generatedAt };
3073
3334
  }
3074
3335
  async function saveCodeMap(paths, map) {
3075
3336
  const file = codeMapPath(paths);
3076
- await mkdir4(path8.dirname(file), { recursive: true });
3077
- await writeFile3(file, JSON.stringify(map, null, 2), "utf8");
3337
+ const payload = serializeCodeMap(map);
3338
+ const current = existsSync7(file) ? await readFile7(file, "utf8").catch(() => null) : null;
3339
+ if (current === payload) return;
3340
+ await mkdir5(path9.dirname(file), { recursive: true });
3341
+ await writeFile4(file, payload, "utf8");
3342
+ await mkdir5(paths.runtimeDir, { recursive: true }).catch(() => {
3343
+ });
3344
+ await writeFile4(
3345
+ codeMapMetaPath(paths),
3346
+ `${JSON.stringify({ generated_at: (/* @__PURE__ */ new Date()).toISOString(), content_hash: codeMapContentHash(map) }, null, 2)}
3347
+ `,
3348
+ "utf8"
3349
+ ).catch(() => {
3350
+ });
3078
3351
  }
3079
3352
  async function buildCodeMap(root, options = {}) {
3080
3353
  const include = new Set(options.includeExtensions ?? CODE_MAP_DEFAULT_INCLUDE);
3081
3354
  const exclude = new Set(options.excludeDirs ?? CODE_MAP_DEFAULT_EXCLUDE);
3082
3355
  const files = {};
3083
3356
  for await (const abs of collectSourceFiles(root, include, exclude, options.includeUntracked)) {
3084
- const rel = path8.relative(root, abs).replace(/\\/g, "/");
3357
+ const rel = path9.relative(root, abs).replace(/\\/g, "/");
3085
3358
  if (rel.startsWith(".ai/")) continue;
3086
- const content = await readFile6(abs, "utf8");
3087
- const ext = path8.extname(abs).toLowerCase();
3359
+ const content = await readFile7(abs, "utf8");
3360
+ const ext = path9.extname(abs).toLowerCase();
3088
3361
  const entry = await parseFileEntry(content, ext);
3089
3362
  if (entry.exports.length > 0) files[rel] = entry;
3090
3363
  }
@@ -3105,11 +3378,11 @@ async function countSourceFilesOnDisk(root, options = {}) {
3105
3378
  async function* collectSourceFiles(root, include, exclude, includeUntracked) {
3106
3379
  const gitFiles = gitSourceFiles(root, include, exclude, includeUntracked === true);
3107
3380
  if (gitFiles) {
3108
- for (const rel of gitFiles) yield path8.join(root, rel);
3381
+ for (const rel of gitFiles) yield path9.join(root, rel);
3109
3382
  for await (const nested of findNestedGitRepos(root, exclude)) {
3110
3383
  const nestedFiles = gitSourceFiles(nested, include, exclude, includeUntracked === true);
3111
3384
  if (nestedFiles) {
3112
- for (const rel of nestedFiles) yield path8.join(nested, rel);
3385
+ for (const rel of nestedFiles) yield path9.join(nested, rel);
3113
3386
  }
3114
3387
  }
3115
3388
  return;
@@ -3128,8 +3401,8 @@ async function* findNestedGitRepos(root, exclude, depth = 0) {
3128
3401
  if (!entry.isDirectory()) continue;
3129
3402
  if (entry.name.startsWith(".")) continue;
3130
3403
  if (exclude.has(entry.name)) continue;
3131
- const full = path8.join(root, entry.name);
3132
- if (existsSync6(path8.join(full, ".git"))) {
3404
+ const full = path9.join(root, entry.name);
3405
+ if (existsSync7(path9.join(full, ".git"))) {
3133
3406
  yield full;
3134
3407
  } else {
3135
3408
  yield* findNestedGitRepos(full, exclude, depth + 1);
@@ -3158,11 +3431,11 @@ async function* walkSourceFiles(dir, include, exclude) {
3158
3431
  if (entry.isDirectory()) continue;
3159
3432
  }
3160
3433
  if (exclude.has(entry.name)) continue;
3161
- const full = path8.join(dir, entry.name);
3434
+ const full = path9.join(dir, entry.name);
3162
3435
  if (entry.isDirectory()) {
3163
3436
  yield* walkSourceFiles(full, include, exclude);
3164
3437
  } else if (entry.isFile()) {
3165
- const ext = path8.extname(entry.name).toLowerCase();
3438
+ const ext = path9.extname(entry.name).toLowerCase();
3166
3439
  if (include.has(ext) && !TEST_FILE_RE.test(entry.name)) yield full;
3167
3440
  }
3168
3441
  }
@@ -3173,7 +3446,7 @@ function isIncludedSourcePath(rel, include, exclude) {
3173
3446
  const parts = normalized.split("/");
3174
3447
  if (parts.some((part) => exclude.has(part))) return false;
3175
3448
  const base = parts.at(-1) ?? "";
3176
- const ext = path8.extname(base).toLowerCase();
3449
+ const ext = path9.extname(base).toLowerCase();
3177
3450
  return include.has(ext) && !TEST_FILE_RE.test(base);
3178
3451
  }
3179
3452
  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 +3708,10 @@ function queryCodeMap(map, options) {
3435
3708
  }
3436
3709
 
3437
3710
  // src/config.ts
3438
- import { existsSync as existsSync7 } from "fs";
3711
+ import { existsSync as existsSync8 } from "fs";
3439
3712
  import { readFileSync } from "fs";
3440
- import { readFile as readFile7, rm, writeFile as writeFile4 } from "fs/promises";
3441
- import path9 from "path";
3713
+ import { readFile as readFile8, rm, writeFile as writeFile5 } from "fs/promises";
3714
+ import path10 from "path";
3442
3715
  var CONFIG_FILE = "hivelore.config.json";
3443
3716
  var LEGACY_CONFIG_FILE = "haive.config.json";
3444
3717
  var DEFAULT_BRIEFING_EXCLUDE_TAGS = [
@@ -3534,19 +3807,19 @@ function antiPatternGateParams(gate) {
3534
3807
  }
3535
3808
  }
3536
3809
  function configPath(paths) {
3537
- return path9.join(paths.haiveDir, CONFIG_FILE);
3810
+ return path10.join(paths.haiveDir, CONFIG_FILE);
3538
3811
  }
3539
3812
  function resolveConfigPath(paths) {
3540
3813
  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;
3814
+ if (existsSync8(current)) return current;
3815
+ const legacy = path10.join(paths.haiveDir, LEGACY_CONFIG_FILE);
3816
+ return existsSync8(legacy) ? legacy : current;
3544
3817
  }
3545
3818
  async function loadConfig(paths) {
3546
3819
  const file = resolveConfigPath(paths);
3547
- if (!existsSync7(file)) return { ...DEFAULT_CONFIG };
3820
+ if (!existsSync8(file)) return { ...DEFAULT_CONFIG };
3548
3821
  try {
3549
- const raw = await readFile7(file, "utf8");
3822
+ const raw = await readFile8(file, "utf8");
3550
3823
  const parsed = JSON.parse(raw);
3551
3824
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
3552
3825
  if (merged.autopilot) {
@@ -3559,7 +3832,7 @@ async function loadConfig(paths) {
3559
3832
  }
3560
3833
  function loadConfigSync(paths) {
3561
3834
  const file = resolveConfigPath(paths);
3562
- if (!existsSync7(file)) return { ...DEFAULT_CONFIG };
3835
+ if (!existsSync8(file)) return { ...DEFAULT_CONFIG };
3563
3836
  try {
3564
3837
  const parsed = JSON.parse(readFileSync(file, "utf8"));
3565
3838
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
@@ -3569,9 +3842,9 @@ function loadConfigSync(paths) {
3569
3842
  }
3570
3843
  }
3571
3844
  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)) {
3845
+ await writeFile5(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
3846
+ const legacy = path10.join(paths.haiveDir, LEGACY_CONFIG_FILE);
3847
+ if (existsSync8(legacy)) {
3575
3848
  try {
3576
3849
  await rm(legacy, { force: true });
3577
3850
  } catch {
@@ -3594,21 +3867,21 @@ function mergeConfig(base, override) {
3594
3867
  }
3595
3868
 
3596
3869
  // 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";
3870
+ import { existsSync as existsSync9 } from "fs";
3871
+ import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
3872
+ import path11 from "path";
3600
3873
  import { spawnSync as spawnSync2 } from "child_process";
3601
3874
  async function loadImportMap(cacheDir) {
3602
- const mapPath = path10.join(cacheDir, "import-map.json");
3603
- if (!existsSync8(mapPath)) return {};
3875
+ const mapPath = path11.join(cacheDir, "import-map.json");
3876
+ if (!existsSync9(mapPath)) return {};
3604
3877
  try {
3605
- return JSON.parse(await readFile8(mapPath, "utf8"));
3878
+ return JSON.parse(await readFile9(mapPath, "utf8"));
3606
3879
  } catch {
3607
3880
  return {};
3608
3881
  }
3609
3882
  }
3610
3883
  async function saveImportMap(cacheDir, map) {
3611
- await writeFile5(path10.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
3884
+ await writeFile6(path11.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
3612
3885
  }
3613
3886
  async function pullCrossRepoSources(paths, config, projectRoot) {
3614
3887
  const sources = config.crossRepoSources ?? [];
@@ -3629,8 +3902,8 @@ async function pullFromSource(paths, source, projectRoot) {
3629
3902
  };
3630
3903
  let sourceRoot = null;
3631
3904
  if (source.path) {
3632
- const resolved = path10.resolve(projectRoot, source.path);
3633
- if (!existsSync8(resolved)) {
3905
+ const resolved = path11.resolve(projectRoot, source.path);
3906
+ if (!existsSync9(resolved)) {
3634
3907
  report.errors.push(`Path not found: ${resolved}`);
3635
3908
  return report;
3636
3909
  }
@@ -3643,7 +3916,7 @@ async function pullFromSource(paths, source, projectRoot) {
3643
3916
  return report;
3644
3917
  }
3645
3918
  const sourcePaths = resolveHaivePaths(sourceRoot);
3646
- if (!existsSync8(sourcePaths.memoriesDir)) {
3919
+ if (!existsSync9(sourcePaths.memoriesDir)) {
3647
3920
  report.errors.push(`No .ai/memories/ found at ${sourceRoot}`);
3648
3921
  return report;
3649
3922
  }
@@ -3666,10 +3939,10 @@ async function pullFromSource(paths, source, projectRoot) {
3666
3939
  report.skipped.push("no shared memories found in source");
3667
3940
  return report;
3668
3941
  }
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 });
3942
+ const destDir = path11.join(paths.memoriesDir, "shared", source.name);
3943
+ await mkdir6(destDir, { recursive: true });
3944
+ const cacheDir = path11.join(paths.haiveDir, ".cache", "cross-repo", source.name);
3945
+ await mkdir6(cacheDir, { recursive: true });
3673
3946
  const importMap = await loadImportMap(cacheDir);
3674
3947
  const mapDirty = false;
3675
3948
  let dirty = mapDirty;
@@ -3683,7 +3956,7 @@ async function pullFromSource(paths, source, projectRoot) {
3683
3956
 
3684
3957
  `;
3685
3958
  const existingLocalPath = importMap[sourceId];
3686
- if (existingLocalPath && existsSync8(existingLocalPath)) {
3959
+ if (existingLocalPath && existsSync9(existingLocalPath)) {
3687
3960
  const existingFiles = await loadMemoriesFromDir(destDir);
3688
3961
  const existingEntry = existingFiles.find(({ filePath }) => filePath === existingLocalPath);
3689
3962
  const sourceBodyStripped = memory.body.trim();
@@ -3694,7 +3967,7 @@ async function pullFromSource(paths, source, projectRoot) {
3694
3967
  }
3695
3968
  const updatedBody = importedBodyPrefix + memory.body;
3696
3969
  if (existingEntry) {
3697
- await writeFile5(
3970
+ await writeFile6(
3698
3971
  existingLocalPath,
3699
3972
  serializeMemory({ frontmatter: existingEntry.memory.frontmatter, body: updatedBody }),
3700
3973
  "utf8"
@@ -3718,8 +3991,8 @@ async function pullFromSource(paths, source, projectRoot) {
3718
3991
  topic: fm.topic ? `${source.name}:${fm.topic}` : void 0
3719
3992
  });
3720
3993
  const body = importedBodyPrefix + memory.body;
3721
- const destPath = path10.join(destDir, `${newFm.id}.md`);
3722
- await writeFile5(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
3994
+ const destPath = path11.join(destDir, `${newFm.id}.md`);
3995
+ await writeFile6(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
3723
3996
  importMap[sourceId] = destPath;
3724
3997
  dirty = true;
3725
3998
  report.imported.push(sourceId);
@@ -3729,9 +4002,9 @@ async function pullFromSource(paths, source, projectRoot) {
3729
4002
  return report;
3730
4003
  }
3731
4004
  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"))) {
4005
+ const cacheDir = path11.join(paths.haiveDir, ".cache", "cross-repo", source.name);
4006
+ await mkdir6(cacheDir, { recursive: true });
4007
+ if (existsSync9(path11.join(cacheDir, ".git"))) {
3735
4008
  const result = spawnSync2("git", ["fetch", "--depth=1", "origin"], {
3736
4009
  cwd: cacheDir,
3737
4010
  encoding: "utf8"
@@ -3756,9 +4029,9 @@ async function cloneOrFetchGitSource(source, paths, report) {
3756
4029
  }
3757
4030
 
3758
4031
  // 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";
4032
+ import { existsSync as existsSync10 } from "fs";
4033
+ import { readFile as readFile10, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
4034
+ import path12 from "path";
3762
4035
  function parsePackageJson(content) {
3763
4036
  try {
3764
4037
  const pkg = JSON.parse(content);
@@ -3830,7 +4103,7 @@ var KNOWN_MANIFESTS = [
3830
4103
  { name: "pom.xml", parser: parsePomXml }
3831
4104
  ];
3832
4105
  function getParser(file) {
3833
- const base = path11.basename(file);
4106
+ const base = path12.basename(file);
3834
4107
  return KNOWN_MANIFESTS.find((m) => m.name === base)?.parser ?? null;
3835
4108
  }
3836
4109
  function extractMajor(version) {
@@ -3848,32 +4121,32 @@ function isMajorBump(from, to) {
3848
4121
  }
3849
4122
  function resolveManifestFiles(projectRoot, configuredFiles) {
3850
4123
  if (configuredFiles !== void 0) {
3851
- return configuredFiles.map((f) => path11.resolve(projectRoot, f)).filter(existsSync9);
4124
+ return configuredFiles.map((f) => path12.resolve(projectRoot, f)).filter(existsSync10);
3852
4125
  }
3853
- return KNOWN_MANIFESTS.map(({ name }) => path11.join(projectRoot, name)).filter(existsSync9);
4126
+ return KNOWN_MANIFESTS.map(({ name }) => path12.join(projectRoot, name)).filter(existsSync10);
3854
4127
  }
3855
4128
  async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
3856
- const contractsDir = path11.join(haiveDir, "contracts");
3857
- await mkdir6(contractsDir, { recursive: true });
4129
+ const contractsDir = path12.join(haiveDir, "contracts");
4130
+ await mkdir7(contractsDir, { recursive: true });
3858
4131
  const results = [];
3859
4132
  for (const manifestPath of manifestFiles) {
3860
4133
  const parser2 = getParser(manifestPath);
3861
4134
  if (!parser2) continue;
3862
- const content = await readFile9(manifestPath, "utf8");
4135
+ const content = await readFile10(manifestPath, "utf8");
3863
4136
  const currentDeps = parser2(content);
3864
- const lockName = `deps-${path11.basename(manifestPath)}.lock`;
3865
- const lockPath = path11.join(contractsDir, lockName);
3866
- if (!existsSync9(lockPath)) {
4137
+ const lockName = `deps-${path12.basename(manifestPath)}.lock`;
4138
+ const lockPath = path12.join(contractsDir, lockName);
4139
+ if (!existsSync10(lockPath)) {
3867
4140
  const snapshot2 = {
3868
- file: path11.relative(projectRoot, manifestPath),
3869
- format: path11.basename(manifestPath),
4141
+ file: path12.relative(projectRoot, manifestPath),
4142
+ format: path12.basename(manifestPath),
3870
4143
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
3871
4144
  deps: currentDeps
3872
4145
  };
3873
- await writeFile6(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
4146
+ await writeFile7(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
3874
4147
  continue;
3875
4148
  }
3876
- const snapshot = JSON.parse(await readFile9(lockPath, "utf8"));
4149
+ const snapshot = JSON.parse(await readFile10(lockPath, "utf8"));
3877
4150
  const changes = [];
3878
4151
  for (const [name, currentVer] of Object.entries(currentDeps)) {
3879
4152
  const prevVer = snapshot.deps[name];
@@ -3887,22 +4160,22 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
3887
4160
  }
3888
4161
  }
3889
4162
  if (changes.length > 0) {
3890
- results.push({ file: path11.relative(projectRoot, manifestPath), changes });
4163
+ results.push({ file: path12.relative(projectRoot, manifestPath), changes });
3891
4164
  const updated = {
3892
4165
  ...snapshot,
3893
4166
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
3894
4167
  deps: currentDeps
3895
4168
  };
3896
- await writeFile6(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
4169
+ await writeFile7(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
3897
4170
  }
3898
4171
  }
3899
4172
  return results;
3900
4173
  }
3901
4174
 
3902
4175
  // 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";
4176
+ import { existsSync as existsSync11 } from "fs";
4177
+ import { readFile as readFile11, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
4178
+ import path13 from "path";
3906
4179
  import crypto from "crypto";
3907
4180
  function sha256(content) {
3908
4181
  return crypto.createHash("sha256").update(content).digest("hex");
@@ -4105,14 +4378,14 @@ function diffSnapshots(before, after) {
4105
4378
  return changes;
4106
4379
  }
4107
4380
  function contractLockPath(haiveDir, name) {
4108
- return path12.join(haiveDir, "contracts", `${name}.lock`);
4381
+ return path13.join(haiveDir, "contracts", `${name}.lock`);
4109
4382
  }
4110
4383
  async function snapshotContract(projectRoot, haiveDir, contract) {
4111
- const filePath = path12.resolve(projectRoot, contract.path);
4112
- if (!existsSync10(filePath)) {
4384
+ const filePath = path13.resolve(projectRoot, contract.path);
4385
+ if (!existsSync11(filePath)) {
4113
4386
  throw new Error(`Contract file not found: ${filePath}`);
4114
4387
  }
4115
- const content = await readFile10(filePath, "utf8");
4388
+ const content = await readFile11(filePath, "utf8");
4116
4389
  const parsed = parseByFormat(content, contract.format, filePath);
4117
4390
  const snapshot = {
4118
4391
  name: contract.name,
@@ -4122,23 +4395,23 @@ async function snapshotContract(projectRoot, haiveDir, contract) {
4122
4395
  hash: sha256(content),
4123
4396
  ...parsed
4124
4397
  };
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");
4398
+ const contractsDir = path13.join(haiveDir, "contracts");
4399
+ await mkdir8(contractsDir, { recursive: true });
4400
+ await writeFile8(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
4128
4401
  return snapshot;
4129
4402
  }
4130
4403
  async function diffContract(projectRoot, haiveDir, contract) {
4131
- const filePath = path12.resolve(projectRoot, contract.path);
4132
- if (!existsSync10(filePath)) {
4404
+ const filePath = path13.resolve(projectRoot, contract.path);
4405
+ if (!existsSync11(filePath)) {
4133
4406
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
4134
4407
  }
4135
4408
  const lockPath = contractLockPath(haiveDir, contract.name);
4136
- if (!existsSync10(lockPath)) {
4409
+ if (!existsSync11(lockPath)) {
4137
4410
  await snapshotContract(projectRoot, haiveDir, contract);
4138
4411
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
4139
4412
  }
4140
- const content = await readFile10(filePath, "utf8");
4141
- const beforeSnapshot = JSON.parse(await readFile10(lockPath, "utf8"));
4413
+ const content = await readFile11(filePath, "utf8");
4414
+ const beforeSnapshot = JSON.parse(await readFile11(lockPath, "utf8"));
4142
4415
  const afterParsed = parseByFormat(content, contract.format, filePath);
4143
4416
  const afterSnapshot = {
4144
4417
  ...beforeSnapshot,
@@ -4148,7 +4421,7 @@ async function diffContract(projectRoot, haiveDir, contract) {
4148
4421
  };
4149
4422
  const changes = diffSnapshots(beforeSnapshot, afterSnapshot);
4150
4423
  if (changes.length > 0) {
4151
- await writeFile7(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
4424
+ await writeFile8(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
4152
4425
  }
4153
4426
  return {
4154
4427
  contract: contract.name,
@@ -4166,27 +4439,27 @@ async function watchContracts(projectRoot, haiveDir, contractFiles) {
4166
4439
  }
4167
4440
 
4168
4441
  // 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";
4442
+ import { appendFile as appendFile2, mkdir as mkdir9, readFile as readFile12, stat as stat3 } from "fs/promises";
4443
+ import { existsSync as existsSync12 } from "fs";
4444
+ import path14 from "path";
4172
4445
  var USAGE_LOG_FILE = "tool-usage.jsonl";
4173
4446
  var USAGE_LOG_DIR = ".usage";
4174
4447
  function usageLogPath(paths) {
4175
- return path13.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
4448
+ return path14.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
4176
4449
  }
4177
4450
  async function appendUsageEvent(paths, event) {
4178
4451
  try {
4179
4452
  const file = usageLogPath(paths);
4180
- const dir = path13.dirname(file);
4181
- if (!existsSync11(dir)) await mkdir8(dir, { recursive: true });
4453
+ const dir = path14.dirname(file);
4454
+ if (!existsSync12(dir)) await mkdir9(dir, { recursive: true });
4182
4455
  await appendFile2(file, JSON.stringify(event) + "\n", "utf8");
4183
4456
  } catch {
4184
4457
  }
4185
4458
  }
4186
4459
  async function readUsageEvents(paths) {
4187
4460
  const file = usageLogPath(paths);
4188
- if (!existsSync11(file)) return [];
4189
- const raw = await readFile11(file, "utf8");
4461
+ if (!existsSync12(file)) return [];
4462
+ const raw = await readFile12(file, "utf8");
4190
4463
  const out = [];
4191
4464
  for (const line of raw.split("\n")) {
4192
4465
  if (!line) continue;
@@ -4235,12 +4508,184 @@ function parseSince(input) {
4235
4508
  }
4236
4509
  async function usageLogSize(paths) {
4237
4510
  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");
4511
+ if (!existsSync12(file)) return { exists: false, size_bytes: 0, lines: 0 };
4512
+ const st = await stat3(file);
4513
+ const raw = await readFile12(file, "utf8");
4241
4514
  return { exists: true, size_bytes: st.size, lines: raw.split("\n").filter((l) => l).length };
4242
4515
  }
4243
4516
 
4517
+ // src/friction.ts
4518
+ import { appendFile as appendFile3, mkdir as mkdir10, readFile as readFile13, writeFile as writeFile9 } from "fs/promises";
4519
+ import { existsSync as existsSync13 } from "fs";
4520
+ import { createHash as createHash3 } from "crypto";
4521
+ import path15 from "path";
4522
+ var FRICTION_LOG_FILE = "friction.jsonl";
4523
+ var FRICTION_STATE_FILE = "friction-state.json";
4524
+ var FRICTION_FIELD_MAX = 2e3;
4525
+ function frictionLogPath(paths) {
4526
+ return path15.join(paths.runtimeDir, FRICTION_LOG_FILE);
4527
+ }
4528
+ function frictionStatePath(paths) {
4529
+ return path15.join(paths.runtimeDir, FRICTION_STATE_FILE);
4530
+ }
4531
+ function normalizeFrictionSummary(value) {
4532
+ return value.toLowerCase().replace(/[a-z]?:?[\\/](?:[\w.-]+[\\/])+/g, "/").replace(/\d{3,}/g, "N").replace(/\s+/g, " ").trim();
4533
+ }
4534
+ function frictionFingerprint(input) {
4535
+ const basis = [
4536
+ input.kind,
4537
+ input.surface.trim().toLowerCase(),
4538
+ normalizeFrictionSummary(input.summary)
4539
+ ].join("|");
4540
+ return createHash3("sha256").update(basis).digest("hex").slice(0, 16);
4541
+ }
4542
+ function truncate(value) {
4543
+ if (value === void 0) return void 0;
4544
+ const trimmed = value.trim();
4545
+ if (!trimmed) return void 0;
4546
+ return trimmed.length > FRICTION_FIELD_MAX ? `${trimmed.slice(0, FRICTION_FIELD_MAX)}
4547
+ \u2026[truncated]` : trimmed;
4548
+ }
4549
+ function normalizeKind(kind, repro) {
4550
+ if (kind === "bug" && !truncate(repro)) {
4551
+ return { kind: "suggestion", downgraded_from: "bug" };
4552
+ }
4553
+ return { kind };
4554
+ }
4555
+ async function appendFrictionReport(paths, input) {
4556
+ const { kind, downgraded_from } = normalizeKind(input.kind, input.repro);
4557
+ const summary = truncate(input.summary) ?? "(no summary)";
4558
+ const surface = truncate(input.surface) ?? "(unknown)";
4559
+ const fingerprint = frictionFingerprint({ kind, surface, summary });
4560
+ const report = {
4561
+ at: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
4562
+ kind,
4563
+ surface,
4564
+ summary,
4565
+ ...truncate(input.expected) ? { expected: truncate(input.expected) } : {},
4566
+ ...truncate(input.observed) ? { observed: truncate(input.observed) } : {},
4567
+ ...truncate(input.repro) ? { repro: truncate(input.repro) } : {},
4568
+ ...input.version ? { version: input.version } : {},
4569
+ ...downgraded_from ? { downgraded_from } : {},
4570
+ fingerprint
4571
+ };
4572
+ const prior = (await readFrictionReports(paths)).filter((r) => r.fingerprint === fingerprint);
4573
+ try {
4574
+ if (!existsSync13(paths.runtimeDir)) await mkdir10(paths.runtimeDir, { recursive: true });
4575
+ await appendFile3(frictionLogPath(paths), JSON.stringify(report) + "\n", "utf8");
4576
+ } catch {
4577
+ }
4578
+ const state = await loadFrictionState(paths);
4579
+ return {
4580
+ report,
4581
+ occurrences: prior.length + 1,
4582
+ already_reported: prior.length > 0,
4583
+ ...state[fingerprint] ? { resolved_as: state[fingerprint] } : {}
4584
+ };
4585
+ }
4586
+ async function readFrictionReports(paths) {
4587
+ const file = frictionLogPath(paths);
4588
+ if (!existsSync13(file)) return [];
4589
+ let raw;
4590
+ try {
4591
+ raw = await readFile13(file, "utf8");
4592
+ } catch {
4593
+ return [];
4594
+ }
4595
+ const out = [];
4596
+ for (const line of raw.split("\n")) {
4597
+ if (!line.trim()) continue;
4598
+ try {
4599
+ const parsed = JSON.parse(line);
4600
+ if (parsed.fingerprint && parsed.at && parsed.summary) out.push(parsed);
4601
+ } catch {
4602
+ }
4603
+ }
4604
+ return out;
4605
+ }
4606
+ async function loadFrictionState(paths) {
4607
+ const file = frictionStatePath(paths);
4608
+ if (!existsSync13(file)) return {};
4609
+ try {
4610
+ const parsed = JSON.parse(await readFile13(file, "utf8"));
4611
+ return parsed && typeof parsed === "object" ? parsed : {};
4612
+ } catch {
4613
+ return {};
4614
+ }
4615
+ }
4616
+ async function saveFrictionState(paths, state) {
4617
+ if (!existsSync13(paths.runtimeDir)) await mkdir10(paths.runtimeDir, { recursive: true });
4618
+ await writeFile9(frictionStatePath(paths), JSON.stringify(state, null, 2) + "\n", "utf8");
4619
+ }
4620
+ async function setFrictionStatus(paths, fingerprint, status, url) {
4621
+ const state = await loadFrictionState(paths);
4622
+ const entry = {
4623
+ status,
4624
+ at: (/* @__PURE__ */ new Date()).toISOString(),
4625
+ ...url ? { url } : {}
4626
+ };
4627
+ state[fingerprint] = entry;
4628
+ await saveFrictionState(paths, state);
4629
+ return entry;
4630
+ }
4631
+ function groupFriction(reports, state = {}) {
4632
+ const byFingerprint = /* @__PURE__ */ new Map();
4633
+ for (const report of reports) {
4634
+ const bucket = byFingerprint.get(report.fingerprint);
4635
+ if (bucket) bucket.push(report);
4636
+ else byFingerprint.set(report.fingerprint, [report]);
4637
+ }
4638
+ const groups = [];
4639
+ for (const [fingerprint, bucket] of byFingerprint) {
4640
+ const sorted = [...bucket].sort((a, b) => a.at.localeCompare(b.at));
4641
+ const latest = sorted[sorted.length - 1];
4642
+ const resolution = state[fingerprint];
4643
+ groups.push({
4644
+ fingerprint,
4645
+ kind: latest.kind,
4646
+ surface: latest.surface,
4647
+ summary: latest.summary,
4648
+ count: sorted.length,
4649
+ first_seen: sorted[0].at,
4650
+ last_seen: latest.at,
4651
+ latest,
4652
+ status: resolution?.status ?? "open",
4653
+ ...resolution?.at ? { submitted_at: resolution.at } : {},
4654
+ ...resolution?.url ? { url: resolution.url } : {}
4655
+ });
4656
+ }
4657
+ return groups.sort(
4658
+ (a, b) => b.count - a.count || b.last_seen.localeCompare(a.last_seen)
4659
+ );
4660
+ }
4661
+ function formatFrictionIssue(group) {
4662
+ const title = `[${group.kind}] ${group.surface}: ${group.summary}`.slice(0, 120);
4663
+ const r = group.latest;
4664
+ const lines = [
4665
+ `**Surface:** \`${group.surface}\``,
4666
+ `**Reported:** ${group.count}\xD7 (first ${group.first_seen.slice(0, 10)}, last ${group.last_seen.slice(0, 10)})`,
4667
+ ...r.version ? [`**Version:** ${r.version}`] : [],
4668
+ "",
4669
+ "### What happened",
4670
+ group.summary
4671
+ ];
4672
+ if (r.expected) lines.push("", "### Expected", r.expected);
4673
+ if (r.observed) lines.push("", "### Observed", r.observed);
4674
+ if (r.repro) lines.push("", "### Reproduction", "```sh", r.repro, "```");
4675
+ if (r.downgraded_from === "bug") {
4676
+ lines.push(
4677
+ "",
4678
+ "> Reported as a bug but filed as a suggestion: no reproduction was supplied."
4679
+ );
4680
+ }
4681
+ lines.push(
4682
+ "",
4683
+ "---",
4684
+ "<sub>Captured by an AI agent session via `report_friction` and reviewed by a human before submission.</sub>"
4685
+ );
4686
+ return { title, body: lines.join("\n") };
4687
+ }
4688
+
4244
4689
  // src/briefing-preset.ts
4245
4690
  var BRIEFING_PRESET_DEFAULTS = {
4246
4691
  /** Fast session start — minimal tokens, skip module CONTEXT.md slices */
@@ -4307,21 +4752,21 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
4307
4752
  }
4308
4753
 
4309
4754
  // src/resolve-project.ts
4310
- import { existsSync as existsSync12 } from "fs";
4311
- import path14 from "path";
4755
+ import { existsSync as existsSync14 } from "fs";
4756
+ import path16 from "path";
4312
4757
  var ROOT_MARKERS2 = [".ai", ".git", "package.json"];
4313
4758
  function markersAtRoot(root) {
4314
4759
  const found = [];
4315
4760
  for (const m of ROOT_MARKERS2) {
4316
- if (existsSync12(path14.join(root, m))) found.push(m);
4761
+ if (existsSync14(path16.join(root, m))) found.push(m);
4317
4762
  }
4318
4763
  return found;
4319
4764
  }
4320
4765
  function resolveProjectInfo(opts = {}) {
4321
4766
  const env = opts.env ?? process.env;
4322
- const cwd = path14.resolve(opts.cwd ?? process.cwd());
4767
+ const cwd = path16.resolve(opts.cwd ?? process.cwd());
4323
4768
  const raw = env.HAIVE_PROJECT_ROOT;
4324
- const explicit = raw !== void 0 && raw !== "" ? path14.resolve(raw) : null;
4769
+ const explicit = raw !== void 0 && raw !== "" ? path16.resolve(raw) : null;
4325
4770
  const resolvedRoot = explicit ?? findProjectRoot(cwd);
4326
4771
  const paths = resolveHaivePaths(resolvedRoot);
4327
4772
  return {
@@ -4329,8 +4774,8 @@ function resolveProjectInfo(opts = {}) {
4329
4774
  resolved_root: resolvedRoot,
4330
4775
  haive_project_root_env: explicit,
4331
4776
  explicit_root: explicit != null,
4332
- haive_dir_exists: existsSync12(paths.haiveDir),
4333
- memories_dir_exists: existsSync12(paths.memoriesDir),
4777
+ haive_dir_exists: existsSync14(paths.haiveDir),
4778
+ memories_dir_exists: existsSync14(paths.memoriesDir),
4334
4779
  runtime_dir: paths.runtimeDir,
4335
4780
  markers_found: markersAtRoot(resolvedRoot)
4336
4781
  };
@@ -4585,16 +5030,16 @@ function findLexicalConflictPairs(memories, opts) {
4585
5030
  }
4586
5031
 
4587
5032
  // src/runtime-journal.ts
4588
- import { mkdir as mkdir9, readFile as readFile12, appendFile as appendFile3 } from "fs/promises";
4589
- import { existsSync as existsSync13 } from "fs";
4590
- import path15 from "path";
5033
+ import { mkdir as mkdir11, readFile as readFile14, appendFile as appendFile4 } from "fs/promises";
5034
+ import { existsSync as existsSync15 } from "fs";
5035
+ import path17 from "path";
4591
5036
  var RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
4592
5037
  function runtimeJournalPath(paths) {
4593
- return path15.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
5038
+ return path17.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
4594
5039
  }
4595
5040
  async function appendRuntimeJournalEntry(paths, entry) {
4596
5041
  try {
4597
- await mkdir9(paths.runtimeDir, { recursive: true });
5042
+ await mkdir11(paths.runtimeDir, { recursive: true });
4598
5043
  const line = {
4599
5044
  ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
4600
5045
  kind: entry.kind,
@@ -4602,7 +5047,7 @@ async function appendRuntimeJournalEntry(paths, entry) {
4602
5047
  ...entry.tool !== void 0 ? { tool: entry.tool } : {},
4603
5048
  ...entry.meta !== void 0 ? { meta: entry.meta } : {}
4604
5049
  };
4605
- await appendFile3(
5050
+ await appendFile4(
4606
5051
  runtimeJournalPath(paths),
4607
5052
  JSON.stringify(line) + "\n",
4608
5053
  "utf8"
@@ -4612,9 +5057,9 @@ async function appendRuntimeJournalEntry(paths, entry) {
4612
5057
  }
4613
5058
  async function readRuntimeJournalTail(paths, limit) {
4614
5059
  const file = runtimeJournalPath(paths);
4615
- if (!existsSync13(file) || limit <= 0) return [];
5060
+ if (!existsSync15(file) || limit <= 0) return [];
4616
5061
  try {
4617
- const raw = await readFile12(file, "utf8");
5062
+ const raw = await readFile14(file, "utf8");
4618
5063
  const lines = raw.trim().split("\n").filter(Boolean);
4619
5064
  const parsed = [];
4620
5065
  for (const line of lines.slice(-limit)) {
@@ -4630,22 +5075,22 @@ async function readRuntimeJournalTail(paths, limit) {
4630
5075
  }
4631
5076
 
4632
5077
  // src/enforcement.ts
4633
- import { mkdir as mkdir10, readdir as readdir4, readFile as readFile13, writeFile as writeFile8 } from "fs/promises";
4634
- import { existsSync as existsSync14 } from "fs";
4635
- import path16 from "path";
5078
+ import { mkdir as mkdir12, readdir as readdir4, readFile as readFile15, writeFile as writeFile10 } from "fs/promises";
5079
+ import { existsSync as existsSync16 } from "fs";
5080
+ import path18 from "path";
4636
5081
  var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
4637
5082
  var SESSION_RECAP_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
4638
5083
  function enforcementDir(paths) {
4639
- return path16.join(paths.runtimeDir, "enforcement");
5084
+ return path18.join(paths.runtimeDir, "enforcement");
4640
5085
  }
4641
5086
  function briefingMarkersDir(paths) {
4642
- return path16.join(enforcementDir(paths), "briefings");
5087
+ return path18.join(enforcementDir(paths), "briefings");
4643
5088
  }
4644
5089
  function normalizeSessionId(sessionId) {
4645
5090
  return (sessionId?.trim() || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 120);
4646
5091
  }
4647
5092
  function briefingMarkerPath(paths, sessionId) {
4648
- return path16.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
5093
+ return path18.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
4649
5094
  }
4650
5095
  async function writeBriefingMarker(paths, input) {
4651
5096
  const sessionId = normalizeSessionId(input.sessionId);
@@ -4670,8 +5115,8 @@ async function writeBriefingMarker(paths, input) {
4670
5115
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
4671
5116
  root: paths.root
4672
5117
  };
4673
- await mkdir10(briefingMarkersDir(paths), { recursive: true });
4674
- await writeFile8(
5118
+ await mkdir12(briefingMarkersDir(paths), { recursive: true });
5119
+ await writeFile10(
4675
5120
  briefingMarkerPath(paths, marker.session_id),
4676
5121
  JSON.stringify(marker, null, 2) + "\n",
4677
5122
  "utf8"
@@ -4680,9 +5125,9 @@ async function writeBriefingMarker(paths, input) {
4680
5125
  }
4681
5126
  async function readSessionBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER_TTL_MS) {
4682
5127
  const file = briefingMarkerPath(paths, sessionId);
4683
- if (!existsSync14(file)) return null;
5128
+ if (!existsSync16(file)) return null;
4684
5129
  try {
4685
- const marker = JSON.parse(await readFile13(file, "utf8"));
5130
+ const marker = JSON.parse(await readFile15(file, "utf8"));
4686
5131
  const created = Date.parse(marker.created_at);
4687
5132
  if (!Number.isFinite(created) || Date.now() - created > ttlMs) return null;
4688
5133
  return marker;
@@ -4694,18 +5139,18 @@ async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER
4694
5139
  const now = Date.now();
4695
5140
  const candidates = [];
4696
5141
  const exact = briefingMarkerPath(paths, sessionId);
4697
- if (existsSync14(exact)) candidates.push(exact);
5142
+ if (existsSync16(exact)) candidates.push(exact);
4698
5143
  try {
4699
5144
  const dir = briefingMarkersDir(paths);
4700
5145
  const files = await readdir4(dir);
4701
5146
  for (const file of files) {
4702
- if (file.endsWith(".json")) candidates.push(path16.join(dir, file));
5147
+ if (file.endsWith(".json")) candidates.push(path18.join(dir, file));
4703
5148
  }
4704
5149
  } catch {
4705
5150
  }
4706
5151
  for (const file of new Set(candidates)) {
4707
5152
  try {
4708
- const marker = JSON.parse(await readFile13(file, "utf8"));
5153
+ const marker = JSON.parse(await readFile15(file, "utf8"));
4709
5154
  const created = Date.parse(marker.created_at);
4710
5155
  if (Number.isFinite(created) && now - created <= ttlMs) return true;
4711
5156
  } catch {
@@ -4717,12 +5162,12 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
4717
5162
  const now = Date.now();
4718
5163
  const candidates = [];
4719
5164
  const exact = briefingMarkerPath(paths, sessionId);
4720
- if (existsSync14(exact)) candidates.push(exact);
5165
+ if (existsSync16(exact)) candidates.push(exact);
4721
5166
  try {
4722
5167
  const dir = briefingMarkersDir(paths);
4723
5168
  const files = await readdir4(dir);
4724
5169
  for (const file of files) {
4725
- if (file.endsWith(".json")) candidates.push(path16.join(dir, file));
5170
+ if (file.endsWith(".json")) candidates.push(path18.join(dir, file));
4726
5171
  }
4727
5172
  } catch {
4728
5173
  }
@@ -4730,7 +5175,7 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
4730
5175
  let freshestTs = 0;
4731
5176
  for (const file of new Set(candidates)) {
4732
5177
  try {
4733
- const marker = JSON.parse(await readFile13(file, "utf8"));
5178
+ const marker = JSON.parse(await readFile15(file, "utf8"));
4734
5179
  const created = Date.parse(marker.created_at);
4735
5180
  if (!Number.isFinite(created) || now - created > ttlMs) continue;
4736
5181
  if (created > freshestTs) {
@@ -4780,15 +5225,15 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
4780
5225
  }
4781
5226
 
4782
5227
  // src/sensor-ledger.ts
4783
- import { createHash as createHash2 } from "crypto";
4784
- import { existsSync as existsSync15, readFileSync as readFileSync2 } from "fs";
4785
- import { appendFile as appendFile4, mkdir as mkdir11, readFile as readFile14, rename, writeFile as writeFile9 } from "fs/promises";
4786
- import path17 from "path";
5228
+ import { createHash as createHash4 } from "crypto";
5229
+ import { existsSync as existsSync17, readFileSync as readFileSync2 } from "fs";
5230
+ import { appendFile as appendFile5, mkdir as mkdir13, readFile as readFile16, rename, writeFile as writeFile11 } from "fs/promises";
5231
+ import path19 from "path";
4787
5232
  var MAX_LINES = 1e4;
4788
5233
  var RETAINED_LINES = 8e3;
4789
5234
  var DAY_MS = 864e5;
4790
5235
  function sensorLedgerPath(paths) {
4791
- return path17.join(paths.runtimeDir, "enforcement", "sensor-ledger.ndjson");
5236
+ return path19.join(paths.runtimeDir, "enforcement", "sensor-ledger.ndjson");
4792
5237
  }
4793
5238
  function isEvaluation(value) {
4794
5239
  if (!value || typeof value !== "object") return false;
@@ -4799,13 +5244,13 @@ async function appendSensorEvaluations(paths, evaluations) {
4799
5244
  if (evaluations.length === 0) return;
4800
5245
  try {
4801
5246
  const file = sensorLedgerPath(paths);
4802
- await mkdir11(path17.dirname(file), { recursive: true });
4803
- await appendFile4(file, evaluations.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
4804
- const raw = await readFile14(file, "utf8");
5247
+ await mkdir13(path19.dirname(file), { recursive: true });
5248
+ await appendFile5(file, evaluations.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
5249
+ const raw = await readFile16(file, "utf8");
4805
5250
  const lines = raw.split("\n").filter(Boolean);
4806
5251
  if (lines.length > MAX_LINES) {
4807
5252
  const temp = `${file}.${process.pid}.tmp`;
4808
- await writeFile9(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
5253
+ await writeFile11(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
4809
5254
  await rename(temp, file);
4810
5255
  }
4811
5256
  } catch {
@@ -4814,9 +5259,9 @@ async function appendSensorEvaluations(paths, evaluations) {
4814
5259
  async function loadSensorLedger(paths, opts = {}) {
4815
5260
  try {
4816
5261
  const file = sensorLedgerPath(paths);
4817
- if (!existsSync15(file)) return [];
5262
+ if (!existsSync17(file)) return [];
4818
5263
  const since = opts.since ? Date.parse(opts.since) : Number.NEGATIVE_INFINITY;
4819
- const raw = await readFile14(file, "utf8");
5264
+ const raw = await readFile16(file, "utf8");
4820
5265
  const out = [];
4821
5266
  for (const line of raw.split("\n")) {
4822
5267
  if (!line.trim()) continue;
@@ -4838,11 +5283,11 @@ function computeScopeHash(root, scopedFiles) {
4838
5283
  try {
4839
5284
  const files = [...new Set(scopedFiles.map((f) => f.replace(/\\/g, "/")))].sort();
4840
5285
  if (files.length === 0) return "";
4841
- const hash = createHash2("sha256");
5286
+ const hash = createHash4("sha256");
4842
5287
  let included = 0;
4843
5288
  for (const rel of files) {
4844
- const abs = path17.resolve(root, rel);
4845
- if (!existsSync15(abs)) continue;
5289
+ const abs = path19.resolve(root, rel);
5290
+ if (!existsSync17(abs)) continue;
4846
5291
  try {
4847
5292
  hash.update(rel);
4848
5293
  hash.update("\0");
@@ -5576,8 +6021,8 @@ function normalizeFindingSeverity(raw) {
5576
6021
  return "info";
5577
6022
  }
5578
6023
  }
5579
- function findingKey(tool, ruleId, path20) {
5580
- return `${tool}:${ruleId}:${path20}`;
6024
+ function findingKey(tool, ruleId, path22) {
6025
+ return `${tool}:${ruleId}:${path22}`;
5581
6026
  }
5582
6027
  function coerceJson(input) {
5583
6028
  if (typeof input === "string") {
@@ -5611,8 +6056,8 @@ function parseSarif(input) {
5611
6056
  const physical = asRecord(location.physicalLocation);
5612
6057
  const artifact = asRecord(physical.artifactLocation);
5613
6058
  const region = asRecord(physical.region);
5614
- const path20 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
5615
- if (!path20) continue;
6059
+ const path22 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
6060
+ if (!path22) continue;
5616
6061
  const line = typeof region.startLine === "number" ? region.startLine : void 0;
5617
6062
  const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
5618
6063
  findings.push({
@@ -5620,10 +6065,10 @@ function parseSarif(input) {
5620
6065
  ruleId,
5621
6066
  message: message.trim(),
5622
6067
  severity,
5623
- path: path20,
6068
+ path: path22,
5624
6069
  ...line !== void 0 ? { line } : {},
5625
6070
  ...snippet ? { snippet } : {},
5626
- key: findingKey(tool, ruleId, path20)
6071
+ key: findingKey(tool, ruleId, path22)
5627
6072
  });
5628
6073
  }
5629
6074
  }
@@ -5642,17 +6087,17 @@ function parseSonar(input) {
5642
6087
  (typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
5643
6088
  );
5644
6089
  const component = typeof issue.component === "string" ? issue.component : "";
5645
- const path20 = componentToPath(component);
5646
- if (!path20) continue;
6090
+ const path22 = componentToPath(component);
6091
+ if (!path22) continue;
5647
6092
  const line = typeof issue.line === "number" ? issue.line : void 0;
5648
6093
  findings.push({
5649
6094
  tool: "sonar",
5650
6095
  ruleId,
5651
6096
  message,
5652
6097
  severity,
5653
- path: path20,
6098
+ path: path22,
5654
6099
  ...line !== void 0 ? { line } : {},
5655
- key: findingKey("sonar", ruleId, path20)
6100
+ key: findingKey("sonar", ruleId, path22)
5656
6101
  });
5657
6102
  }
5658
6103
  return findings;
@@ -5665,7 +6110,7 @@ function parseEslintJson(input, opts = {}) {
5665
6110
  const file = asRecord(fileRaw);
5666
6111
  const rawPath = typeof file.filePath === "string" ? file.filePath : "";
5667
6112
  if (!rawPath) continue;
5668
- const path20 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
6113
+ const path22 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
5669
6114
  for (const msgRaw of asArray(file.messages)) {
5670
6115
  const msg = asRecord(msgRaw);
5671
6116
  const ruleId = typeof msg.ruleId === "string" && msg.ruleId ? msg.ruleId : "parse-error";
@@ -5677,9 +6122,9 @@ function parseEslintJson(input, opts = {}) {
5677
6122
  ruleId,
5678
6123
  message,
5679
6124
  severity,
5680
- path: path20,
6125
+ path: path22,
5681
6126
  ...line !== void 0 ? { line } : {},
5682
- key: findingKey("eslint", ruleId, path20)
6127
+ key: findingKey("eslint", ruleId, path22)
5683
6128
  });
5684
6129
  }
5685
6130
  }
@@ -6129,7 +6574,7 @@ function tallyHotFiles(paths, source = "agent") {
6129
6574
  if (!norm) continue;
6130
6575
  counts.set(norm, (counts.get(norm) ?? 0) + 1);
6131
6576
  }
6132
- return [...counts.entries()].map(([path20, changes]) => ({ path: path20, changes, source })).sort((a, b) => b.changes - a.changes);
6577
+ return [...counts.entries()].map(([path22, changes]) => ({ path: path22, changes, source })).sort((a, b) => b.changes - a.changes);
6133
6578
  }
6134
6579
  function mergeHotFiles(a, b) {
6135
6580
  const merged = /* @__PURE__ */ new Map();
@@ -6149,21 +6594,21 @@ function mergeHotFiles(a, b) {
6149
6594
  }
6150
6595
 
6151
6596
  // src/eval-history.ts
6152
- import { appendFile as appendFile5, mkdir as mkdir12, readFile as readFile15 } from "fs/promises";
6153
- import { existsSync as existsSync16 } from "fs";
6154
- import path18 from "path";
6597
+ import { appendFile as appendFile6, mkdir as mkdir14, readFile as readFile17 } from "fs/promises";
6598
+ import { existsSync as existsSync18 } from "fs";
6599
+ import path20 from "path";
6155
6600
  function evalHistoryPath(paths) {
6156
- return path18.join(paths.haiveDir, ".cache", "eval-history.jsonl");
6601
+ return path20.join(paths.haiveDir, ".cache", "eval-history.jsonl");
6157
6602
  }
6158
6603
  async function appendEvalHistory(paths, entry) {
6159
6604
  const file = evalHistoryPath(paths);
6160
- await mkdir12(path18.dirname(file), { recursive: true });
6161
- await appendFile5(file, JSON.stringify(entry) + "\n", "utf8");
6605
+ await mkdir14(path20.dirname(file), { recursive: true });
6606
+ await appendFile6(file, JSON.stringify(entry) + "\n", "utf8");
6162
6607
  }
6163
6608
  async function loadEvalHistory(paths) {
6164
6609
  const file = evalHistoryPath(paths);
6165
- if (!existsSync16(file)) return [];
6166
- const raw = await readFile15(file, "utf8").catch(() => "");
6610
+ if (!existsSync18(file)) return [];
6611
+ const raw = await readFile17(file, "utf8").catch(() => "");
6167
6612
  const out = [];
6168
6613
  for (const line of raw.split("\n")) {
6169
6614
  const trimmed = line.trim();
@@ -6506,12 +6951,12 @@ ${trimmed}`;
6506
6951
  }
6507
6952
 
6508
6953
  // src/handoff.ts
6509
- import { writeFile as writeFile10, readFile as readFile16, stat as stat3 } from "fs/promises";
6510
- import { existsSync as existsSync17 } from "fs";
6511
- import path19 from "path";
6954
+ import { writeFile as writeFile12, readFile as readFile18, stat as stat4 } from "fs/promises";
6955
+ import { existsSync as existsSync19 } from "fs";
6956
+ import path21 from "path";
6512
6957
  var HANDOFF_FILENAME = "NEXT.md";
6513
6958
  function handoffFilePath(root) {
6514
- return path19.join(root, HANDOFF_FILENAME);
6959
+ return path21.join(root, HANDOFF_FILENAME);
6515
6960
  }
6516
6961
  function buildHandoffMarkdown(data) {
6517
6962
  const at = (data.at ?? /* @__PURE__ */ new Date()).toISOString();
@@ -6560,20 +7005,20 @@ function buildHandoffMarkdown(data) {
6560
7005
  }
6561
7006
  async function writeSessionHandoff(root, data) {
6562
7007
  const file = handoffFilePath(root);
6563
- await writeFile10(file, buildHandoffMarkdown(data), "utf8");
7008
+ await writeFile12(file, buildHandoffMarkdown(data), "utf8");
6564
7009
  return file;
6565
7010
  }
6566
7011
  async function readSessionHandoff(root) {
6567
7012
  const file = handoffFilePath(root);
6568
- if (!existsSync17(file)) return null;
6569
- const raw = await readFile16(file, "utf8").catch(() => "");
7013
+ if (!existsSync19(file)) return null;
7014
+ const raw = await readFile18(file, "utf8").catch(() => "");
6570
7015
  return raw.trim() ? raw : null;
6571
7016
  }
6572
7017
  async function handoffAgeMs(root, now = /* @__PURE__ */ new Date()) {
6573
7018
  const file = handoffFilePath(root);
6574
- if (!existsSync17(file)) return null;
7019
+ if (!existsSync19(file)) return null;
6575
7020
  try {
6576
- const s = await stat3(file);
7021
+ const s = await stat4(file);
6577
7022
  return Math.max(0, now.getTime() - s.mtimeMs);
6578
7023
  } catch {
6579
7024
  return null;
@@ -6698,6 +7143,7 @@ export {
6698
7143
  CODE_MAP_FILE,
6699
7144
  CODE_STOPWORDS,
6700
7145
  CONFIG_FILE,
7146
+ CONTENT_CATCH_CODES,
6701
7147
  CrossRepoProvenanceSchema,
6702
7148
  DECAY_DAYS,
6703
7149
  DEFAULT_AUTO_PROMOTE_RULE,
@@ -6705,8 +7151,13 @@ export {
6705
7151
  DEFAULT_CONFIDENCE_THRESHOLDS,
6706
7152
  DEFAULT_CONFIG,
6707
7153
  DEFAULT_DORMANT_DAYS,
7154
+ DEFAULT_POSTURE,
6708
7155
  DEFAULT_PRIORITY_SIGNALS,
6709
7156
  ENV_WORKAROUND_TAGS,
7157
+ FRICTION_FIELD_MAX,
7158
+ FRICTION_LOG_FILE,
7159
+ FRICTION_STATE_FILE,
7160
+ GATE_REMINDER_WINDOW_MS,
6710
7161
  GUESSABLE_THRESHOLD,
6711
7162
  HAIVE_DIR,
6712
7163
  HAIVE_OWNED_FILES,
@@ -6720,6 +7171,8 @@ export {
6720
7171
  MemoryStatusSchema,
6721
7172
  MemoryTypeSchema,
6722
7173
  PREVENTION_DEBOUNCE_MS,
7174
+ PREVENTION_RECEIPT_MARKER,
7175
+ PROCESS_GATE_CODES,
6723
7176
  PROJECT_CONTEXT_FILE,
6724
7177
  PROJECT_CONTEXT_THROTTLE_MS,
6725
7178
  REVIEW_LEARNING_MARKER,
@@ -6729,6 +7182,7 @@ export {
6729
7182
  SENSOR_ABSENT_LOOKBACK,
6730
7183
  SENSOR_ABSENT_WINDOW,
6731
7184
  SESSION_RECAP_TTL_MS,
7185
+ SETUP_GATE_CODES,
6732
7186
  STACK_PACK_TAG,
6733
7187
  SensorSchema,
6734
7188
  TEST_FRAMEWORKS,
@@ -6744,6 +7198,7 @@ export {
6744
7198
  anchorMatchesComponent,
6745
7199
  antiPatternGateParams,
6746
7200
  appendEvalHistory,
7201
+ appendFrictionReport,
6747
7202
  appendPreventionEvent,
6748
7203
  appendProposedRetrievalCases,
6749
7204
  appendRuntimeJournalEntry,
@@ -6760,6 +7215,7 @@ export {
6760
7215
  briefingMarkerPath,
6761
7216
  briefingMarkersDir,
6762
7217
  briefingProofLine,
7218
+ buildBaselineHealthFinding,
6763
7219
  buildCodeMap,
6764
7220
  buildCoverageIndex,
6765
7221
  buildDashboard,
@@ -6771,6 +7227,7 @@ export {
6771
7227
  buildReport,
6772
7228
  bumpRead,
6773
7229
  classifyMemoryPriority,
7230
+ codeMapContentHash,
6774
7231
  codeMapPath,
6775
7232
  collectTimelineEntries,
6776
7233
  compactAutoRecapBody,
@@ -6779,6 +7236,7 @@ export {
6779
7236
  compareImpact,
6780
7237
  compileRegexSensor,
6781
7238
  componentOf,
7239
+ computeBaselineHealth,
6782
7240
  computeEvalTrend,
6783
7241
  computeGatePrecision,
6784
7242
  computeImpact,
@@ -6788,8 +7246,11 @@ export {
6788
7246
  configPath,
6789
7247
  contractLockPath,
6790
7248
  countSourceFilesOnDisk,
7249
+ decideVerdict,
7250
+ dedupeRefusals,
6791
7251
  deriveConfidence,
6792
7252
  deriveMainAreas,
7253
+ describePosture,
6793
7254
  detectAgentContext,
6794
7255
  detectSensorWeakening,
6795
7256
  detectStacksFromManifests,
@@ -6805,6 +7266,7 @@ export {
6805
7266
  evalHistoryPath,
6806
7267
  evaluateSkillActivation,
6807
7268
  existingGateMissShas,
7269
+ explainSensorRejection,
6808
7270
  extractActionsBriefBody,
6809
7271
  extractCorrectApproachExamples,
6810
7272
  extractReferencedPaths,
@@ -6821,10 +7283,15 @@ export {
6821
7283
  findingBody,
6822
7284
  findingToDraft,
6823
7285
  firstMemoryOneLine,
7286
+ formatFrictionIssue,
7287
+ frictionFingerprint,
7288
+ frictionLogPath,
7289
+ frictionStatePath,
6824
7290
  gatePassedShas,
6825
7291
  generateBridges,
6826
7292
  getUsage,
6827
7293
  globToRegExp,
7294
+ groupFriction,
6828
7295
  handoffAgeMs,
6829
7296
  handoffFilePath,
6830
7297
  hasPendingTestMarker,
@@ -6861,6 +7328,7 @@ export {
6861
7328
  loadConfig,
6862
7329
  loadConfigSync,
6863
7330
  loadEvalHistory,
7331
+ loadFrictionState,
6864
7332
  loadMemoriesFromDir,
6865
7333
  loadMemoriesFromDirDetailed,
6866
7334
  loadMemory,
@@ -6879,6 +7347,8 @@ export {
6879
7347
  newMemoryId,
6880
7348
  normalizeFindingSeverity,
6881
7349
  normalizeFramework,
7350
+ normalizeFrictionSummary,
7351
+ normalizeKind,
6882
7352
  normalizeScaffoldStyle,
6883
7353
  normalizeSessionId,
6884
7354
  overallScore,
@@ -6907,12 +7377,14 @@ export {
6907
7377
  quarantineNote,
6908
7378
  queryCodeMap,
6909
7379
  rankMemoriesLexical,
7380
+ readFrictionReports,
6910
7381
  readRecentBriefingMarker,
6911
7382
  readRuntimeJournalTail,
6912
7383
  readSessionHandoff,
6913
7384
  readUsageEvents,
6914
7385
  recommendFeedbackAdjustment,
6915
7386
  recordApplied,
7387
+ recordGateReminder,
6916
7388
  recordPrevention,
6917
7389
  recordPreventionHits,
6918
7390
  recordProjectContextEmission,
@@ -6921,10 +7393,12 @@ export {
6921
7393
  renderBehaviourCoverageLine,
6922
7394
  renderBootstrapChecklist,
6923
7395
  renderCaughtForYou,
7396
+ renderPreventionComment,
6924
7397
  renderPreventionReceipt,
6925
7398
  renderPreventionReceiptShare,
6926
7399
  resolveBriefingBudget,
6927
7400
  resolveConfigPath,
7401
+ resolveGatePolicy,
6928
7402
  resolveHaivePaths,
6929
7403
  resolveManifestFiles,
6930
7404
  resolveProjectInfo,
@@ -6938,6 +7412,7 @@ export {
6938
7412
  runtimeJournalPath,
6939
7413
  saveCodeMap,
6940
7414
  saveConfig,
7415
+ saveFrictionState,
6941
7416
  saveUsageIndex,
6942
7417
  scaffoldPostIncidentTest,
6943
7418
  scannableSensorTargets,
@@ -6951,7 +7426,10 @@ export {
6951
7426
  sensorPromotedAtMap,
6952
7427
  sensorSelfCheck,
6953
7428
  sensorTargetsFromDiff,
7429
+ serializeCodeMap,
6954
7430
  serializeMemory,
7431
+ setFrictionStatus,
7432
+ shouldExpandGateReminder,
6955
7433
  snapshotContract,
6956
7434
  specificityScore,
6957
7435
  stripPrivate,