@hivelore/core 0.53.4 → 0.54.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
@@ -1394,10 +1394,10 @@ function sensorPatternBrittleness(pattern) {
1394
1394
  function normalizeProjectPath(value) {
1395
1395
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
1396
1396
  }
1397
- function sensorAppliesToPath(sensor, anchorPaths, path20) {
1397
+ function sensorAppliesToPath(sensor, anchorPaths, path21) {
1398
1398
  const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
1399
1399
  if (scopes.length === 0) return true;
1400
- const target = normalizeProjectPath(path20);
1400
+ const target = normalizeProjectPath(path21);
1401
1401
  return scopes.some((rawScope) => {
1402
1402
  const scope = normalizeProjectPath(rawScope);
1403
1403
  if (!scope) return false;
@@ -4241,6 +4241,178 @@ async function usageLogSize(paths) {
4241
4241
  return { exists: true, size_bytes: st.size, lines: raw.split("\n").filter((l) => l).length };
4242
4242
  }
4243
4243
 
4244
+ // 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";
4249
+ var FRICTION_LOG_FILE = "friction.jsonl";
4250
+ var FRICTION_STATE_FILE = "friction-state.json";
4251
+ var FRICTION_FIELD_MAX = 2e3;
4252
+ function frictionLogPath(paths) {
4253
+ return path14.join(paths.runtimeDir, FRICTION_LOG_FILE);
4254
+ }
4255
+ function frictionStatePath(paths) {
4256
+ return path14.join(paths.runtimeDir, FRICTION_STATE_FILE);
4257
+ }
4258
+ function normalizeFrictionSummary(value) {
4259
+ return value.toLowerCase().replace(/[a-z]?:?[\\/](?:[\w.-]+[\\/])+/g, "/").replace(/\d{3,}/g, "N").replace(/\s+/g, " ").trim();
4260
+ }
4261
+ function frictionFingerprint(input) {
4262
+ const basis = [
4263
+ input.kind,
4264
+ input.surface.trim().toLowerCase(),
4265
+ normalizeFrictionSummary(input.summary)
4266
+ ].join("|");
4267
+ return createHash2("sha256").update(basis).digest("hex").slice(0, 16);
4268
+ }
4269
+ function truncate(value) {
4270
+ if (value === void 0) return void 0;
4271
+ const trimmed = value.trim();
4272
+ if (!trimmed) return void 0;
4273
+ return trimmed.length > FRICTION_FIELD_MAX ? `${trimmed.slice(0, FRICTION_FIELD_MAX)}
4274
+ \u2026[truncated]` : trimmed;
4275
+ }
4276
+ function normalizeKind(kind, repro) {
4277
+ if (kind === "bug" && !truncate(repro)) {
4278
+ return { kind: "suggestion", downgraded_from: "bug" };
4279
+ }
4280
+ return { kind };
4281
+ }
4282
+ async function appendFrictionReport(paths, input) {
4283
+ const { kind, downgraded_from } = normalizeKind(input.kind, input.repro);
4284
+ const summary = truncate(input.summary) ?? "(no summary)";
4285
+ const surface = truncate(input.surface) ?? "(unknown)";
4286
+ const fingerprint = frictionFingerprint({ kind, surface, summary });
4287
+ const report = {
4288
+ at: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
4289
+ kind,
4290
+ surface,
4291
+ summary,
4292
+ ...truncate(input.expected) ? { expected: truncate(input.expected) } : {},
4293
+ ...truncate(input.observed) ? { observed: truncate(input.observed) } : {},
4294
+ ...truncate(input.repro) ? { repro: truncate(input.repro) } : {},
4295
+ ...input.version ? { version: input.version } : {},
4296
+ ...downgraded_from ? { downgraded_from } : {},
4297
+ fingerprint
4298
+ };
4299
+ const prior = (await readFrictionReports(paths)).filter((r) => r.fingerprint === fingerprint);
4300
+ try {
4301
+ if (!existsSync12(paths.runtimeDir)) await mkdir9(paths.runtimeDir, { recursive: true });
4302
+ await appendFile3(frictionLogPath(paths), JSON.stringify(report) + "\n", "utf8");
4303
+ } catch {
4304
+ }
4305
+ const state = await loadFrictionState(paths);
4306
+ return {
4307
+ report,
4308
+ occurrences: prior.length + 1,
4309
+ already_reported: prior.length > 0,
4310
+ ...state[fingerprint] ? { resolved_as: state[fingerprint] } : {}
4311
+ };
4312
+ }
4313
+ async function readFrictionReports(paths) {
4314
+ const file = frictionLogPath(paths);
4315
+ if (!existsSync12(file)) return [];
4316
+ let raw;
4317
+ try {
4318
+ raw = await readFile12(file, "utf8");
4319
+ } catch {
4320
+ return [];
4321
+ }
4322
+ const out = [];
4323
+ for (const line of raw.split("\n")) {
4324
+ if (!line.trim()) continue;
4325
+ try {
4326
+ const parsed = JSON.parse(line);
4327
+ if (parsed.fingerprint && parsed.at && parsed.summary) out.push(parsed);
4328
+ } catch {
4329
+ }
4330
+ }
4331
+ return out;
4332
+ }
4333
+ async function loadFrictionState(paths) {
4334
+ const file = frictionStatePath(paths);
4335
+ if (!existsSync12(file)) return {};
4336
+ try {
4337
+ const parsed = JSON.parse(await readFile12(file, "utf8"));
4338
+ return parsed && typeof parsed === "object" ? parsed : {};
4339
+ } catch {
4340
+ return {};
4341
+ }
4342
+ }
4343
+ 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");
4346
+ }
4347
+ async function setFrictionStatus(paths, fingerprint, status, url) {
4348
+ const state = await loadFrictionState(paths);
4349
+ const entry = {
4350
+ status,
4351
+ at: (/* @__PURE__ */ new Date()).toISOString(),
4352
+ ...url ? { url } : {}
4353
+ };
4354
+ state[fingerprint] = entry;
4355
+ await saveFrictionState(paths, state);
4356
+ return entry;
4357
+ }
4358
+ function groupFriction(reports, state = {}) {
4359
+ const byFingerprint = /* @__PURE__ */ new Map();
4360
+ for (const report of reports) {
4361
+ const bucket = byFingerprint.get(report.fingerprint);
4362
+ if (bucket) bucket.push(report);
4363
+ else byFingerprint.set(report.fingerprint, [report]);
4364
+ }
4365
+ const groups = [];
4366
+ for (const [fingerprint, bucket] of byFingerprint) {
4367
+ const sorted = [...bucket].sort((a, b) => a.at.localeCompare(b.at));
4368
+ const latest = sorted[sorted.length - 1];
4369
+ const resolution = state[fingerprint];
4370
+ groups.push({
4371
+ fingerprint,
4372
+ kind: latest.kind,
4373
+ surface: latest.surface,
4374
+ summary: latest.summary,
4375
+ count: sorted.length,
4376
+ first_seen: sorted[0].at,
4377
+ last_seen: latest.at,
4378
+ latest,
4379
+ status: resolution?.status ?? "open",
4380
+ ...resolution?.at ? { submitted_at: resolution.at } : {},
4381
+ ...resolution?.url ? { url: resolution.url } : {}
4382
+ });
4383
+ }
4384
+ return groups.sort(
4385
+ (a, b) => b.count - a.count || b.last_seen.localeCompare(a.last_seen)
4386
+ );
4387
+ }
4388
+ function formatFrictionIssue(group) {
4389
+ const title = `[${group.kind}] ${group.surface}: ${group.summary}`.slice(0, 120);
4390
+ const r = group.latest;
4391
+ const lines = [
4392
+ `**Surface:** \`${group.surface}\``,
4393
+ `**Reported:** ${group.count}\xD7 (first ${group.first_seen.slice(0, 10)}, last ${group.last_seen.slice(0, 10)})`,
4394
+ ...r.version ? [`**Version:** ${r.version}`] : [],
4395
+ "",
4396
+ "### What happened",
4397
+ group.summary
4398
+ ];
4399
+ if (r.expected) lines.push("", "### Expected", r.expected);
4400
+ if (r.observed) lines.push("", "### Observed", r.observed);
4401
+ if (r.repro) lines.push("", "### Reproduction", "```sh", r.repro, "```");
4402
+ if (r.downgraded_from === "bug") {
4403
+ lines.push(
4404
+ "",
4405
+ "> Reported as a bug but filed as a suggestion: no reproduction was supplied."
4406
+ );
4407
+ }
4408
+ lines.push(
4409
+ "",
4410
+ "---",
4411
+ "<sub>Captured by an AI agent session via `report_friction` and reviewed by a human before submission.</sub>"
4412
+ );
4413
+ return { title, body: lines.join("\n") };
4414
+ }
4415
+
4244
4416
  // src/briefing-preset.ts
4245
4417
  var BRIEFING_PRESET_DEFAULTS = {
4246
4418
  /** Fast session start — minimal tokens, skip module CONTEXT.md slices */
@@ -4307,21 +4479,21 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
4307
4479
  }
4308
4480
 
4309
4481
  // src/resolve-project.ts
4310
- import { existsSync as existsSync12 } from "fs";
4311
- import path14 from "path";
4482
+ import { existsSync as existsSync13 } from "fs";
4483
+ import path15 from "path";
4312
4484
  var ROOT_MARKERS2 = [".ai", ".git", "package.json"];
4313
4485
  function markersAtRoot(root) {
4314
4486
  const found = [];
4315
4487
  for (const m of ROOT_MARKERS2) {
4316
- if (existsSync12(path14.join(root, m))) found.push(m);
4488
+ if (existsSync13(path15.join(root, m))) found.push(m);
4317
4489
  }
4318
4490
  return found;
4319
4491
  }
4320
4492
  function resolveProjectInfo(opts = {}) {
4321
4493
  const env = opts.env ?? process.env;
4322
- const cwd = path14.resolve(opts.cwd ?? process.cwd());
4494
+ const cwd = path15.resolve(opts.cwd ?? process.cwd());
4323
4495
  const raw = env.HAIVE_PROJECT_ROOT;
4324
- const explicit = raw !== void 0 && raw !== "" ? path14.resolve(raw) : null;
4496
+ const explicit = raw !== void 0 && raw !== "" ? path15.resolve(raw) : null;
4325
4497
  const resolvedRoot = explicit ?? findProjectRoot(cwd);
4326
4498
  const paths = resolveHaivePaths(resolvedRoot);
4327
4499
  return {
@@ -4329,8 +4501,8 @@ function resolveProjectInfo(opts = {}) {
4329
4501
  resolved_root: resolvedRoot,
4330
4502
  haive_project_root_env: explicit,
4331
4503
  explicit_root: explicit != null,
4332
- haive_dir_exists: existsSync12(paths.haiveDir),
4333
- memories_dir_exists: existsSync12(paths.memoriesDir),
4504
+ haive_dir_exists: existsSync13(paths.haiveDir),
4505
+ memories_dir_exists: existsSync13(paths.memoriesDir),
4334
4506
  runtime_dir: paths.runtimeDir,
4335
4507
  markers_found: markersAtRoot(resolvedRoot)
4336
4508
  };
@@ -4585,16 +4757,16 @@ function findLexicalConflictPairs(memories, opts) {
4585
4757
  }
4586
4758
 
4587
4759
  // 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";
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";
4591
4763
  var RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
4592
4764
  function runtimeJournalPath(paths) {
4593
- return path15.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
4765
+ return path16.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
4594
4766
  }
4595
4767
  async function appendRuntimeJournalEntry(paths, entry) {
4596
4768
  try {
4597
- await mkdir9(paths.runtimeDir, { recursive: true });
4769
+ await mkdir10(paths.runtimeDir, { recursive: true });
4598
4770
  const line = {
4599
4771
  ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
4600
4772
  kind: entry.kind,
@@ -4602,7 +4774,7 @@ async function appendRuntimeJournalEntry(paths, entry) {
4602
4774
  ...entry.tool !== void 0 ? { tool: entry.tool } : {},
4603
4775
  ...entry.meta !== void 0 ? { meta: entry.meta } : {}
4604
4776
  };
4605
- await appendFile3(
4777
+ await appendFile4(
4606
4778
  runtimeJournalPath(paths),
4607
4779
  JSON.stringify(line) + "\n",
4608
4780
  "utf8"
@@ -4612,9 +4784,9 @@ async function appendRuntimeJournalEntry(paths, entry) {
4612
4784
  }
4613
4785
  async function readRuntimeJournalTail(paths, limit) {
4614
4786
  const file = runtimeJournalPath(paths);
4615
- if (!existsSync13(file) || limit <= 0) return [];
4787
+ if (!existsSync14(file) || limit <= 0) return [];
4616
4788
  try {
4617
- const raw = await readFile12(file, "utf8");
4789
+ const raw = await readFile13(file, "utf8");
4618
4790
  const lines = raw.trim().split("\n").filter(Boolean);
4619
4791
  const parsed = [];
4620
4792
  for (const line of lines.slice(-limit)) {
@@ -4630,22 +4802,22 @@ async function readRuntimeJournalTail(paths, limit) {
4630
4802
  }
4631
4803
 
4632
4804
  // 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";
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";
4636
4808
  var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
4637
4809
  var SESSION_RECAP_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
4638
4810
  function enforcementDir(paths) {
4639
- return path16.join(paths.runtimeDir, "enforcement");
4811
+ return path17.join(paths.runtimeDir, "enforcement");
4640
4812
  }
4641
4813
  function briefingMarkersDir(paths) {
4642
- return path16.join(enforcementDir(paths), "briefings");
4814
+ return path17.join(enforcementDir(paths), "briefings");
4643
4815
  }
4644
4816
  function normalizeSessionId(sessionId) {
4645
4817
  return (sessionId?.trim() || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 120);
4646
4818
  }
4647
4819
  function briefingMarkerPath(paths, sessionId) {
4648
- return path16.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
4820
+ return path17.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
4649
4821
  }
4650
4822
  async function writeBriefingMarker(paths, input) {
4651
4823
  const sessionId = normalizeSessionId(input.sessionId);
@@ -4670,8 +4842,8 @@ async function writeBriefingMarker(paths, input) {
4670
4842
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
4671
4843
  root: paths.root
4672
4844
  };
4673
- await mkdir10(briefingMarkersDir(paths), { recursive: true });
4674
- await writeFile8(
4845
+ await mkdir11(briefingMarkersDir(paths), { recursive: true });
4846
+ await writeFile9(
4675
4847
  briefingMarkerPath(paths, marker.session_id),
4676
4848
  JSON.stringify(marker, null, 2) + "\n",
4677
4849
  "utf8"
@@ -4680,9 +4852,9 @@ async function writeBriefingMarker(paths, input) {
4680
4852
  }
4681
4853
  async function readSessionBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER_TTL_MS) {
4682
4854
  const file = briefingMarkerPath(paths, sessionId);
4683
- if (!existsSync14(file)) return null;
4855
+ if (!existsSync15(file)) return null;
4684
4856
  try {
4685
- const marker = JSON.parse(await readFile13(file, "utf8"));
4857
+ const marker = JSON.parse(await readFile14(file, "utf8"));
4686
4858
  const created = Date.parse(marker.created_at);
4687
4859
  if (!Number.isFinite(created) || Date.now() - created > ttlMs) return null;
4688
4860
  return marker;
@@ -4694,18 +4866,18 @@ async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER
4694
4866
  const now = Date.now();
4695
4867
  const candidates = [];
4696
4868
  const exact = briefingMarkerPath(paths, sessionId);
4697
- if (existsSync14(exact)) candidates.push(exact);
4869
+ if (existsSync15(exact)) candidates.push(exact);
4698
4870
  try {
4699
4871
  const dir = briefingMarkersDir(paths);
4700
4872
  const files = await readdir4(dir);
4701
4873
  for (const file of files) {
4702
- if (file.endsWith(".json")) candidates.push(path16.join(dir, file));
4874
+ if (file.endsWith(".json")) candidates.push(path17.join(dir, file));
4703
4875
  }
4704
4876
  } catch {
4705
4877
  }
4706
4878
  for (const file of new Set(candidates)) {
4707
4879
  try {
4708
- const marker = JSON.parse(await readFile13(file, "utf8"));
4880
+ const marker = JSON.parse(await readFile14(file, "utf8"));
4709
4881
  const created = Date.parse(marker.created_at);
4710
4882
  if (Number.isFinite(created) && now - created <= ttlMs) return true;
4711
4883
  } catch {
@@ -4717,12 +4889,12 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
4717
4889
  const now = Date.now();
4718
4890
  const candidates = [];
4719
4891
  const exact = briefingMarkerPath(paths, sessionId);
4720
- if (existsSync14(exact)) candidates.push(exact);
4892
+ if (existsSync15(exact)) candidates.push(exact);
4721
4893
  try {
4722
4894
  const dir = briefingMarkersDir(paths);
4723
4895
  const files = await readdir4(dir);
4724
4896
  for (const file of files) {
4725
- if (file.endsWith(".json")) candidates.push(path16.join(dir, file));
4897
+ if (file.endsWith(".json")) candidates.push(path17.join(dir, file));
4726
4898
  }
4727
4899
  } catch {
4728
4900
  }
@@ -4730,7 +4902,7 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
4730
4902
  let freshestTs = 0;
4731
4903
  for (const file of new Set(candidates)) {
4732
4904
  try {
4733
- const marker = JSON.parse(await readFile13(file, "utf8"));
4905
+ const marker = JSON.parse(await readFile14(file, "utf8"));
4734
4906
  const created = Date.parse(marker.created_at);
4735
4907
  if (!Number.isFinite(created) || now - created > ttlMs) continue;
4736
4908
  if (created > freshestTs) {
@@ -4780,15 +4952,15 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
4780
4952
  }
4781
4953
 
4782
4954
  // 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";
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";
4787
4959
  var MAX_LINES = 1e4;
4788
4960
  var RETAINED_LINES = 8e3;
4789
4961
  var DAY_MS = 864e5;
4790
4962
  function sensorLedgerPath(paths) {
4791
- return path17.join(paths.runtimeDir, "enforcement", "sensor-ledger.ndjson");
4963
+ return path18.join(paths.runtimeDir, "enforcement", "sensor-ledger.ndjson");
4792
4964
  }
4793
4965
  function isEvaluation(value) {
4794
4966
  if (!value || typeof value !== "object") return false;
@@ -4799,13 +4971,13 @@ async function appendSensorEvaluations(paths, evaluations) {
4799
4971
  if (evaluations.length === 0) return;
4800
4972
  try {
4801
4973
  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");
4974
+ await mkdir12(path18.dirname(file), { recursive: true });
4975
+ await appendFile5(file, evaluations.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
4976
+ const raw = await readFile15(file, "utf8");
4805
4977
  const lines = raw.split("\n").filter(Boolean);
4806
4978
  if (lines.length > MAX_LINES) {
4807
4979
  const temp = `${file}.${process.pid}.tmp`;
4808
- await writeFile9(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
4980
+ await writeFile10(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
4809
4981
  await rename(temp, file);
4810
4982
  }
4811
4983
  } catch {
@@ -4814,9 +4986,9 @@ async function appendSensorEvaluations(paths, evaluations) {
4814
4986
  async function loadSensorLedger(paths, opts = {}) {
4815
4987
  try {
4816
4988
  const file = sensorLedgerPath(paths);
4817
- if (!existsSync15(file)) return [];
4989
+ if (!existsSync16(file)) return [];
4818
4990
  const since = opts.since ? Date.parse(opts.since) : Number.NEGATIVE_INFINITY;
4819
- const raw = await readFile14(file, "utf8");
4991
+ const raw = await readFile15(file, "utf8");
4820
4992
  const out = [];
4821
4993
  for (const line of raw.split("\n")) {
4822
4994
  if (!line.trim()) continue;
@@ -4838,11 +5010,11 @@ function computeScopeHash(root, scopedFiles) {
4838
5010
  try {
4839
5011
  const files = [...new Set(scopedFiles.map((f) => f.replace(/\\/g, "/")))].sort();
4840
5012
  if (files.length === 0) return "";
4841
- const hash = createHash2("sha256");
5013
+ const hash = createHash3("sha256");
4842
5014
  let included = 0;
4843
5015
  for (const rel of files) {
4844
- const abs = path17.resolve(root, rel);
4845
- if (!existsSync15(abs)) continue;
5016
+ const abs = path18.resolve(root, rel);
5017
+ if (!existsSync16(abs)) continue;
4846
5018
  try {
4847
5019
  hash.update(rel);
4848
5020
  hash.update("\0");
@@ -5576,8 +5748,8 @@ function normalizeFindingSeverity(raw) {
5576
5748
  return "info";
5577
5749
  }
5578
5750
  }
5579
- function findingKey(tool, ruleId, path20) {
5580
- return `${tool}:${ruleId}:${path20}`;
5751
+ function findingKey(tool, ruleId, path21) {
5752
+ return `${tool}:${ruleId}:${path21}`;
5581
5753
  }
5582
5754
  function coerceJson(input) {
5583
5755
  if (typeof input === "string") {
@@ -5611,8 +5783,8 @@ function parseSarif(input) {
5611
5783
  const physical = asRecord(location.physicalLocation);
5612
5784
  const artifact = asRecord(physical.artifactLocation);
5613
5785
  const region = asRecord(physical.region);
5614
- const path20 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
5615
- if (!path20) continue;
5786
+ const path21 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
5787
+ if (!path21) continue;
5616
5788
  const line = typeof region.startLine === "number" ? region.startLine : void 0;
5617
5789
  const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
5618
5790
  findings.push({
@@ -5620,10 +5792,10 @@ function parseSarif(input) {
5620
5792
  ruleId,
5621
5793
  message: message.trim(),
5622
5794
  severity,
5623
- path: path20,
5795
+ path: path21,
5624
5796
  ...line !== void 0 ? { line } : {},
5625
5797
  ...snippet ? { snippet } : {},
5626
- key: findingKey(tool, ruleId, path20)
5798
+ key: findingKey(tool, ruleId, path21)
5627
5799
  });
5628
5800
  }
5629
5801
  }
@@ -5642,17 +5814,17 @@ function parseSonar(input) {
5642
5814
  (typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
5643
5815
  );
5644
5816
  const component = typeof issue.component === "string" ? issue.component : "";
5645
- const path20 = componentToPath(component);
5646
- if (!path20) continue;
5817
+ const path21 = componentToPath(component);
5818
+ if (!path21) continue;
5647
5819
  const line = typeof issue.line === "number" ? issue.line : void 0;
5648
5820
  findings.push({
5649
5821
  tool: "sonar",
5650
5822
  ruleId,
5651
5823
  message,
5652
5824
  severity,
5653
- path: path20,
5825
+ path: path21,
5654
5826
  ...line !== void 0 ? { line } : {},
5655
- key: findingKey("sonar", ruleId, path20)
5827
+ key: findingKey("sonar", ruleId, path21)
5656
5828
  });
5657
5829
  }
5658
5830
  return findings;
@@ -5665,7 +5837,7 @@ function parseEslintJson(input, opts = {}) {
5665
5837
  const file = asRecord(fileRaw);
5666
5838
  const rawPath = typeof file.filePath === "string" ? file.filePath : "";
5667
5839
  if (!rawPath) continue;
5668
- const path20 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
5840
+ const path21 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
5669
5841
  for (const msgRaw of asArray(file.messages)) {
5670
5842
  const msg = asRecord(msgRaw);
5671
5843
  const ruleId = typeof msg.ruleId === "string" && msg.ruleId ? msg.ruleId : "parse-error";
@@ -5677,9 +5849,9 @@ function parseEslintJson(input, opts = {}) {
5677
5849
  ruleId,
5678
5850
  message,
5679
5851
  severity,
5680
- path: path20,
5852
+ path: path21,
5681
5853
  ...line !== void 0 ? { line } : {},
5682
- key: findingKey("eslint", ruleId, path20)
5854
+ key: findingKey("eslint", ruleId, path21)
5683
5855
  });
5684
5856
  }
5685
5857
  }
@@ -6129,7 +6301,7 @@ function tallyHotFiles(paths, source = "agent") {
6129
6301
  if (!norm) continue;
6130
6302
  counts.set(norm, (counts.get(norm) ?? 0) + 1);
6131
6303
  }
6132
- return [...counts.entries()].map(([path20, changes]) => ({ path: path20, changes, source })).sort((a, b) => b.changes - a.changes);
6304
+ return [...counts.entries()].map(([path21, changes]) => ({ path: path21, changes, source })).sort((a, b) => b.changes - a.changes);
6133
6305
  }
6134
6306
  function mergeHotFiles(a, b) {
6135
6307
  const merged = /* @__PURE__ */ new Map();
@@ -6149,21 +6321,21 @@ function mergeHotFiles(a, b) {
6149
6321
  }
6150
6322
 
6151
6323
  // 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";
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";
6155
6327
  function evalHistoryPath(paths) {
6156
- return path18.join(paths.haiveDir, ".cache", "eval-history.jsonl");
6328
+ return path19.join(paths.haiveDir, ".cache", "eval-history.jsonl");
6157
6329
  }
6158
6330
  async function appendEvalHistory(paths, entry) {
6159
6331
  const file = evalHistoryPath(paths);
6160
- await mkdir12(path18.dirname(file), { recursive: true });
6161
- await appendFile5(file, JSON.stringify(entry) + "\n", "utf8");
6332
+ await mkdir13(path19.dirname(file), { recursive: true });
6333
+ await appendFile6(file, JSON.stringify(entry) + "\n", "utf8");
6162
6334
  }
6163
6335
  async function loadEvalHistory(paths) {
6164
6336
  const file = evalHistoryPath(paths);
6165
- if (!existsSync16(file)) return [];
6166
- const raw = await readFile15(file, "utf8").catch(() => "");
6337
+ if (!existsSync17(file)) return [];
6338
+ const raw = await readFile16(file, "utf8").catch(() => "");
6167
6339
  const out = [];
6168
6340
  for (const line of raw.split("\n")) {
6169
6341
  const trimmed = line.trim();
@@ -6506,12 +6678,12 @@ ${trimmed}`;
6506
6678
  }
6507
6679
 
6508
6680
  // 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";
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";
6512
6684
  var HANDOFF_FILENAME = "NEXT.md";
6513
6685
  function handoffFilePath(root) {
6514
- return path19.join(root, HANDOFF_FILENAME);
6686
+ return path20.join(root, HANDOFF_FILENAME);
6515
6687
  }
6516
6688
  function buildHandoffMarkdown(data) {
6517
6689
  const at = (data.at ?? /* @__PURE__ */ new Date()).toISOString();
@@ -6560,18 +6732,18 @@ function buildHandoffMarkdown(data) {
6560
6732
  }
6561
6733
  async function writeSessionHandoff(root, data) {
6562
6734
  const file = handoffFilePath(root);
6563
- await writeFile10(file, buildHandoffMarkdown(data), "utf8");
6735
+ await writeFile11(file, buildHandoffMarkdown(data), "utf8");
6564
6736
  return file;
6565
6737
  }
6566
6738
  async function readSessionHandoff(root) {
6567
6739
  const file = handoffFilePath(root);
6568
- if (!existsSync17(file)) return null;
6569
- const raw = await readFile16(file, "utf8").catch(() => "");
6740
+ if (!existsSync18(file)) return null;
6741
+ const raw = await readFile17(file, "utf8").catch(() => "");
6570
6742
  return raw.trim() ? raw : null;
6571
6743
  }
6572
6744
  async function handoffAgeMs(root, now = /* @__PURE__ */ new Date()) {
6573
6745
  const file = handoffFilePath(root);
6574
- if (!existsSync17(file)) return null;
6746
+ if (!existsSync18(file)) return null;
6575
6747
  try {
6576
6748
  const s = await stat3(file);
6577
6749
  return Math.max(0, now.getTime() - s.mtimeMs);
@@ -6707,6 +6879,9 @@ export {
6707
6879
  DEFAULT_DORMANT_DAYS,
6708
6880
  DEFAULT_PRIORITY_SIGNALS,
6709
6881
  ENV_WORKAROUND_TAGS,
6882
+ FRICTION_FIELD_MAX,
6883
+ FRICTION_LOG_FILE,
6884
+ FRICTION_STATE_FILE,
6710
6885
  GUESSABLE_THRESHOLD,
6711
6886
  HAIVE_DIR,
6712
6887
  HAIVE_OWNED_FILES,
@@ -6744,6 +6919,7 @@ export {
6744
6919
  anchorMatchesComponent,
6745
6920
  antiPatternGateParams,
6746
6921
  appendEvalHistory,
6922
+ appendFrictionReport,
6747
6923
  appendPreventionEvent,
6748
6924
  appendProposedRetrievalCases,
6749
6925
  appendRuntimeJournalEntry,
@@ -6821,10 +6997,15 @@ export {
6821
6997
  findingBody,
6822
6998
  findingToDraft,
6823
6999
  firstMemoryOneLine,
7000
+ formatFrictionIssue,
7001
+ frictionFingerprint,
7002
+ frictionLogPath,
7003
+ frictionStatePath,
6824
7004
  gatePassedShas,
6825
7005
  generateBridges,
6826
7006
  getUsage,
6827
7007
  globToRegExp,
7008
+ groupFriction,
6828
7009
  handoffAgeMs,
6829
7010
  handoffFilePath,
6830
7011
  hasPendingTestMarker,
@@ -6861,6 +7042,7 @@ export {
6861
7042
  loadConfig,
6862
7043
  loadConfigSync,
6863
7044
  loadEvalHistory,
7045
+ loadFrictionState,
6864
7046
  loadMemoriesFromDir,
6865
7047
  loadMemoriesFromDirDetailed,
6866
7048
  loadMemory,
@@ -6879,6 +7061,8 @@ export {
6879
7061
  newMemoryId,
6880
7062
  normalizeFindingSeverity,
6881
7063
  normalizeFramework,
7064
+ normalizeFrictionSummary,
7065
+ normalizeKind,
6882
7066
  normalizeScaffoldStyle,
6883
7067
  normalizeSessionId,
6884
7068
  overallScore,
@@ -6907,6 +7091,7 @@ export {
6907
7091
  quarantineNote,
6908
7092
  queryCodeMap,
6909
7093
  rankMemoriesLexical,
7094
+ readFrictionReports,
6910
7095
  readRecentBriefingMarker,
6911
7096
  readRuntimeJournalTail,
6912
7097
  readSessionHandoff,
@@ -6938,6 +7123,7 @@ export {
6938
7123
  runtimeJournalPath,
6939
7124
  saveCodeMap,
6940
7125
  saveConfig,
7126
+ saveFrictionState,
6941
7127
  saveUsageIndex,
6942
7128
  scaffoldPostIncidentTest,
6943
7129
  scannableSensorTargets,
@@ -6952,6 +7138,7 @@ export {
6952
7138
  sensorSelfCheck,
6953
7139
  sensorTargetsFromDiff,
6954
7140
  serializeMemory,
7141
+ setFrictionStatus,
6955
7142
  snapshotContract,
6956
7143
  specificityScore,
6957
7144
  stripPrivate,