@hivelore/core 0.57.5 → 0.57.7

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
@@ -50,6 +50,14 @@ var SensorSchema = z.object({
50
50
  absent: z.string().optional(),
51
51
  /** Regex flags (e.g. "i", "m"). Ignored for non-regex kinds. */
52
52
  flags: z.string().optional(),
53
+ /**
54
+ * kind=regex only: flip the sensor into a REQUIRED-PRESENCE invariant. `pattern` then names a line
55
+ * that must REMAIN present in the anchored file's final content; the sensor FIRES when a change
56
+ * removes it. A normal regex sensor scans ADDED lines and so structurally cannot see a deletion —
57
+ * this catches the "someone deleted the critical line" class (e.g. a `TimeZone.setDefault(UTC)`
58
+ * guard) that a diff-of-additions sensor misses (field report §3.5).
59
+ */
60
+ require_present: z.boolean().optional(),
53
61
  /** Shell/test command to run (for kind=shell|test). Executed by the CLI, never by core. */
54
62
  command: z.string().optional(),
55
63
  /** Max runtime for kind=shell|test commands (default 120000). The executor kills on expiry. */
@@ -936,7 +944,7 @@ function applyFeedbackAdjustment(fm, adjustment, now = /* @__PURE__ */ new Date(
936
944
  }
937
945
 
938
946
  // src/prevention.ts
939
- import { appendFile, mkdir as mkdir2, readFile as readFile4 } from "fs/promises";
947
+ import { appendFile, mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
940
948
  import { existsSync as existsSync4 } from "fs";
941
949
  import path6 from "path";
942
950
  function preventionLogPath(paths) {
@@ -964,8 +972,22 @@ async function recordPreventionHits(paths, firedIds, source, now = /* @__PURE__
964
972
  await appendPreventionEvent(paths, { at, id, source, ...details[id] }).catch(() => {
965
973
  });
966
974
  }
975
+ await stampSensorLastFired(paths, recordedIds, at).catch(() => {
976
+ });
967
977
  return recordedIds;
968
978
  }
979
+ async function stampSensorLastFired(paths, ids, at) {
980
+ if (ids.length === 0 || !existsSync4(paths.memoriesDir)) return;
981
+ const wanted = new Set(ids);
982
+ const loaded = await loadMemoriesFromDir(paths.memoriesDir);
983
+ for (const { memory, filePath } of loaded) {
984
+ const fm = memory.frontmatter;
985
+ if (!wanted.has(fm.id) || !fm.sensor || fm.sensor.last_fired === at) continue;
986
+ const next = { ...memory, frontmatter: { ...fm, sensor: { ...fm.sensor, last_fired: at } } };
987
+ await writeFile2(filePath, serializeMemory(next), "utf8").catch(() => {
988
+ });
989
+ }
990
+ }
969
991
  function buildPreventionReceipt(events, memories, usage, options) {
970
992
  const now = options.now ?? /* @__PURE__ */ new Date();
971
993
  const sinceMs = options.since.getTime();
@@ -1016,6 +1038,11 @@ function buildPreventionReceipt(events, memories, usage, options) {
1016
1038
  events: rows
1017
1039
  };
1018
1040
  }
1041
+ function trendClause(total, previous) {
1042
+ const counts = `${total} this window vs ${previous} previous window`;
1043
+ if (total + previous < 3) return counts;
1044
+ return `${counts} (${total <= previous ? "recurrences declining" : "recurrences rising"})`;
1045
+ }
1019
1046
  function renderPreventionReceipt(receipt) {
1020
1047
  const lines = [
1021
1048
  `Hivelore prevention receipt \u2014 last ${receipt.window_days} days`,
@@ -1033,9 +1060,7 @@ function renderPreventionReceipt(receipt) {
1033
1060
  const red = row.red_proven ? " \u2713 RED-proven" : "";
1034
1061
  lines.push(` \u2717\u2192\u2713 ${row.at.slice(0, 10)} ${row.id.padEnd(32)} (${kind}${exit}${stage})${incident}${red}`);
1035
1062
  }
1036
- lines.push(
1037
- ` Trend: ${receipt.total} this window vs ${receipt.previous_total} previous window (${receipt.total <= receipt.previous_total ? "recurrences declining" : "recurrences rising"}).`
1038
- );
1063
+ lines.push(` Trend: ${trendClause(receipt.total, receipt.previous_total)}.`);
1039
1064
  return lines.join("\n");
1040
1065
  }
1041
1066
  var HIVELORE_ATTRIBUTION = "\u{1F6E1}\uFE0F Generated by [Hivelore](https://github.com/Doucs91/hivelore) \u2014 the deterministic policy gate for agent-written code.";
@@ -1062,7 +1087,7 @@ function renderPreventionReceiptShare(receipt) {
1062
1087
  }
1063
1088
  lines.push(
1064
1089
  "",
1065
- `_Trend: ${receipt.total} this window vs ${receipt.previous_total} previous window (${receipt.total <= receipt.previous_total ? "recurrences declining" : "recurrences rising"})._`
1090
+ `_Trend: ${trendClause(receipt.total, receipt.previous_total)}._`
1066
1091
  );
1067
1092
  }
1068
1093
  lines.push("", `<sub>${HIVELORE_ATTRIBUTION}</sub>`);
@@ -1221,7 +1246,7 @@ function renderCaughtForYou(summary) {
1221
1246
 
1222
1247
  // src/context-throttle.ts
1223
1248
  import { createHash } from "crypto";
1224
- import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
1249
+ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
1225
1250
  import { existsSync as existsSync5 } from "fs";
1226
1251
  import path7 from "path";
1227
1252
  var PROJECT_CONTEXT_THROTTLE_MS = 8 * 60 * 1e3;
@@ -1246,12 +1271,12 @@ async function recordProjectContextEmission(paths, hash, now = Date.now()) {
1246
1271
  const file = throttleMarkerPath(paths);
1247
1272
  await mkdir3(path7.dirname(file), { recursive: true }).catch(() => {
1248
1273
  });
1249
- await writeFile2(file, JSON.stringify({ hash, at: new Date(now).toISOString() }), "utf8").catch(() => {
1274
+ await writeFile3(file, JSON.stringify({ hash, at: new Date(now).toISOString() }), "utf8").catch(() => {
1250
1275
  });
1251
1276
  }
1252
1277
 
1253
1278
  // src/gate-reminder.ts
1254
- import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1279
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
1255
1280
  import { existsSync as existsSync6 } from "fs";
1256
1281
  import path8 from "path";
1257
1282
  var GATE_REMINDER_WINDOW_MS = 24 * 60 * 60 * 1e3;
@@ -1280,7 +1305,7 @@ async function recordGateReminder(paths, key, now = Date.now()) {
1280
1305
  markers[key] = new Date(now).toISOString();
1281
1306
  await mkdir4(path8.dirname(file), { recursive: true }).catch(() => {
1282
1307
  });
1283
- await writeFile3(file, JSON.stringify(markers, null, 2), "utf8").catch(() => {
1308
+ await writeFile4(file, JSON.stringify(markers, null, 2), "utf8").catch(() => {
1284
1309
  });
1285
1310
  }
1286
1311
 
@@ -1821,20 +1846,137 @@ function compileAbsentRegex(sensor) {
1821
1846
  return null;
1822
1847
  }
1823
1848
  }
1849
+ var C_FAMILY = { line: ["//"], block: ["/*", "*/"], starContinuation: true };
1850
+ var HASH_FAMILY = { line: ["#"], block: null, starContinuation: false };
1851
+ var CSS_FAMILY = { line: [], block: ["/*", "*/"], starContinuation: true };
1852
+ var SCSS_FAMILY = { line: ["//"], block: ["/*", "*/"], starContinuation: true };
1853
+ var SQL_FAMILY = { line: ["--"], block: ["/*", "*/"], starContinuation: false };
1854
+ var HTML_FAMILY = { line: [], block: ["<!--", "-->"], starContinuation: false };
1855
+ var COMMENT_SYNTAX_BY_EXT = {
1856
+ ts: C_FAMILY,
1857
+ tsx: C_FAMILY,
1858
+ js: C_FAMILY,
1859
+ jsx: C_FAMILY,
1860
+ mjs: C_FAMILY,
1861
+ cjs: C_FAMILY,
1862
+ java: C_FAMILY,
1863
+ c: C_FAMILY,
1864
+ h: C_FAMILY,
1865
+ cpp: C_FAMILY,
1866
+ hpp: C_FAMILY,
1867
+ cc: C_FAMILY,
1868
+ hh: C_FAMILY,
1869
+ cs: C_FAMILY,
1870
+ go: C_FAMILY,
1871
+ rs: C_FAMILY,
1872
+ kt: C_FAMILY,
1873
+ kts: C_FAMILY,
1874
+ swift: C_FAMILY,
1875
+ scala: C_FAMILY,
1876
+ php: C_FAMILY,
1877
+ dart: C_FAMILY,
1878
+ m: C_FAMILY,
1879
+ mm: C_FAMILY,
1880
+ py: HASH_FAMILY,
1881
+ rb: HASH_FAMILY,
1882
+ sh: HASH_FAMILY,
1883
+ bash: HASH_FAMILY,
1884
+ zsh: HASH_FAMILY,
1885
+ yml: HASH_FAMILY,
1886
+ yaml: HASH_FAMILY,
1887
+ toml: HASH_FAMILY,
1888
+ properties: HASH_FAMILY,
1889
+ conf: HASH_FAMILY,
1890
+ cfg: HASH_FAMILY,
1891
+ pl: HASH_FAMILY,
1892
+ pm: HASH_FAMILY,
1893
+ r: HASH_FAMILY,
1894
+ css: CSS_FAMILY,
1895
+ scss: SCSS_FAMILY,
1896
+ less: SCSS_FAMILY,
1897
+ sql: SQL_FAMILY,
1898
+ html: HTML_FAMILY,
1899
+ htm: HTML_FAMILY,
1900
+ xml: HTML_FAMILY,
1901
+ vue: HTML_FAMILY,
1902
+ svelte: HTML_FAMILY,
1903
+ md: HTML_FAMILY,
1904
+ markdown: HTML_FAMILY
1905
+ };
1906
+ function commentSyntaxForPath(path22) {
1907
+ const base = path22.split(/[\\/]/).pop() ?? "";
1908
+ const dot = base.lastIndexOf(".");
1909
+ if (dot < 0) return null;
1910
+ return COMMENT_SYNTAX_BY_EXT[base.slice(dot + 1).toLowerCase()] ?? null;
1911
+ }
1912
+ function blankCommentsOnLine(line, syntax) {
1913
+ if (syntax.starContinuation && /^\*(\s|\/|$)/.test(line.trimStart())) {
1914
+ return " ".repeat(line.length);
1915
+ }
1916
+ let out = "";
1917
+ let stringDelim = null;
1918
+ for (let i = 0; i < line.length; i++) {
1919
+ const ch = line[i];
1920
+ if (stringDelim) {
1921
+ out += ch;
1922
+ if (ch === "\\" && i + 1 < line.length) {
1923
+ out += line[i + 1];
1924
+ i++;
1925
+ continue;
1926
+ }
1927
+ if (ch === stringDelim) stringDelim = null;
1928
+ continue;
1929
+ }
1930
+ if (ch === '"' || ch === "'" || ch === "`") {
1931
+ stringDelim = ch;
1932
+ out += ch;
1933
+ continue;
1934
+ }
1935
+ if (syntax.block) {
1936
+ const [open, close] = syntax.block;
1937
+ if (line.startsWith(open, i)) {
1938
+ const closeIdx = line.indexOf(close, i + open.length);
1939
+ const end = closeIdx === -1 ? line.length : closeIdx + close.length;
1940
+ out += " ".repeat(end - i);
1941
+ if (closeIdx === -1) break;
1942
+ i = end - 1;
1943
+ continue;
1944
+ }
1945
+ }
1946
+ let hitLineComment = false;
1947
+ for (const lc of syntax.line) {
1948
+ if (line.startsWith(lc, i)) {
1949
+ out += " ".repeat(line.length - i);
1950
+ hitLineComment = true;
1951
+ break;
1952
+ }
1953
+ }
1954
+ if (hitLineComment) break;
1955
+ out += ch;
1956
+ }
1957
+ return out;
1958
+ }
1959
+ function stripCommentsForScan(content, path22) {
1960
+ const syntax = commentSyntaxForPath(path22);
1961
+ if (!syntax) return content;
1962
+ return content.split("\n").map((l) => blankCommentsOnLine(l, syntax)).join("\n");
1963
+ }
1824
1964
  function runRegexSensor(memoryId, sensor, target) {
1825
1965
  const re = compileRegexSensor(sensor);
1826
1966
  if (!re) return null;
1827
1967
  const absentRe = compileAbsentRegex(sensor);
1828
- const lines = target.content.split("\n");
1829
- for (let i = 0; i < lines.length; i++) {
1830
- const rawLine = lines[i];
1968
+ const rawLines = target.content.split("\n");
1969
+ const scanLines = stripCommentsForScan(target.content, target.path).split("\n");
1970
+ for (let i = 0; i < rawLines.length; i++) {
1971
+ const rawLine = rawLines[i];
1972
+ const scanLine = scanLines[i] ?? rawLine;
1831
1973
  re.lastIndex = 0;
1832
- if (!re.test(rawLine)) continue;
1974
+ if (!re.test(scanLine)) continue;
1833
1975
  if (absentRe) {
1834
1976
  const from = Math.max(0, i - SENSOR_ABSENT_LOOKBACK);
1835
- const to = Math.min(lines.length, i + SENSOR_ABSENT_WINDOW + 1);
1977
+ const to = Math.min(scanLines.length, i + SENSOR_ABSENT_WINDOW + 1);
1836
1978
  absentRe.lastIndex = 0;
1837
- if (absentRe.test(lines.slice(from, to).join("\n"))) continue;
1979
+ if (absentRe.test(scanLines.slice(from, to).join("\n"))) continue;
1838
1980
  }
1839
1981
  const brittle = sensor.kind === "regex" && sensor.pattern ? sensorPatternBrittleness(sensor.pattern) : null;
1840
1982
  const severity = brittle ? "warn" : sensor.severity;
@@ -1854,6 +1996,7 @@ function runSensors(memories, targets) {
1854
1996
  for (const memory of memories) {
1855
1997
  const sensor = memory.frontmatter.sensor;
1856
1998
  if (!sensor || sensor.kind !== "regex") continue;
1999
+ if (sensor.require_present) continue;
1857
2000
  const anchorPaths = memory.frontmatter.anchor.paths;
1858
2001
  for (const target of targets) {
1859
2002
  if (!sensorAppliesToPath(sensor, anchorPaths, target.path)) continue;
@@ -1863,6 +2006,47 @@ function runSensors(memories, targets) {
1863
2006
  }
1864
2007
  return hits;
1865
2008
  }
2009
+ function changedPathsFromDiff(diff) {
2010
+ const out = /* @__PURE__ */ new Set();
2011
+ for (const m of diff.matchAll(/^diff --git a\/(.+?) b\/(.+)$/gm)) {
2012
+ out.add((m[2] ?? m[1] ?? "").trim());
2013
+ }
2014
+ for (const m of diff.matchAll(/^\+\+\+ b\/(.+)$/gm)) {
2015
+ const p = (m[1] ?? "").trim();
2016
+ if (p && p !== "/dev/null") out.add(p);
2017
+ }
2018
+ return [...out];
2019
+ }
2020
+ function runPresenceSensors(memories, finalTargets) {
2021
+ const hits = [];
2022
+ for (const memory of memories) {
2023
+ const sensor = memory.frontmatter.sensor;
2024
+ if (!sensor || sensor.kind !== "regex" || !sensor.require_present || !sensor.pattern) continue;
2025
+ let re;
2026
+ try {
2027
+ const flags = new Set(["m", ...(sensor.flags ?? "").split("")].filter(Boolean));
2028
+ re = new RegExp(sensor.pattern, [...flags].join(""));
2029
+ } catch {
2030
+ continue;
2031
+ }
2032
+ const anchorPaths = memory.frontmatter.anchor.paths;
2033
+ for (const target of finalTargets) {
2034
+ if (!sensorAppliesToPath(sensor, anchorPaths, target.path)) continue;
2035
+ re.lastIndex = 0;
2036
+ if (!re.test(target.content)) {
2037
+ hits.push({
2038
+ memory_id: memory.frontmatter.id,
2039
+ sensor,
2040
+ file: target.path,
2041
+ message: sensor.message,
2042
+ severity: sensor.severity
2043
+ });
2044
+ break;
2045
+ }
2046
+ }
2047
+ }
2048
+ return hits;
2049
+ }
1866
2050
  var COMMAND_ENV_EXACT = /* @__PURE__ */ new Set([
1867
2051
  "PATH",
1868
2052
  "HOME",
@@ -2104,14 +2288,14 @@ function sensorSelfCheck(sensor, input) {
2104
2288
  function judgeProposedSensor(sensor, input) {
2105
2289
  const brittle = sensor.kind === "regex" && sensor.pattern ? sensorPatternBrittleness(sensor.pattern) : null;
2106
2290
  const self_check = sensorSelfCheck(sensor, input);
2291
+ if (self_check.fires_on_correct === true) {
2292
+ return { accepted: false, reason: "fires-on-correct", self_check, brittle };
2293
+ }
2107
2294
  if (sensor.severity === "block") {
2108
2295
  if (brittle) return { accepted: false, reason: "brittle", self_check, brittle };
2109
2296
  if (input.currentTargets.length > 0 && !self_check.silent_on_current) {
2110
2297
  return { accepted: false, reason: "fires-on-current", self_check, brittle };
2111
2298
  }
2112
- if (self_check.fires_on_correct === true) {
2113
- return { accepted: false, reason: "fires-on-correct", self_check, brittle };
2114
- }
2115
2299
  if (self_check.fires_on_bad === false) {
2116
2300
  return { accepted: false, reason: "missed-bad-example", self_check, brittle };
2117
2301
  }
@@ -3099,7 +3283,7 @@ function allocateBudget(parts, maxTokens) {
3099
3283
  }
3100
3284
 
3101
3285
  // src/code-map.ts
3102
- import { mkdir as mkdir5, readFile as readFile7, readdir as readdir3, stat as stat2, writeFile as writeFile4 } from "fs/promises";
3286
+ import { mkdir as mkdir5, readFile as readFile7, readdir as readdir3, stat as stat2, writeFile as writeFile5 } from "fs/promises";
3103
3287
  import { createHash as createHash2 } from "crypto";
3104
3288
  import { existsSync as existsSync7 } from "fs";
3105
3289
  import { spawnSync } from "child_process";
@@ -3542,10 +3726,10 @@ async function saveCodeMap(paths, map) {
3542
3726
  const current = existsSync7(file) ? await readFile7(file, "utf8").catch(() => null) : null;
3543
3727
  if (current === payload) return;
3544
3728
  await mkdir5(path9.dirname(file), { recursive: true });
3545
- await writeFile4(file, payload, "utf8");
3729
+ await writeFile5(file, payload, "utf8");
3546
3730
  await mkdir5(paths.runtimeDir, { recursive: true }).catch(() => {
3547
3731
  });
3548
- await writeFile4(
3732
+ await writeFile5(
3549
3733
  codeMapMetaPath(paths),
3550
3734
  `${JSON.stringify({ generated_at: (/* @__PURE__ */ new Date()).toISOString(), content_hash: codeMapContentHash(map) }, null, 2)}
3551
3735
  `,
@@ -3914,7 +4098,7 @@ function queryCodeMap(map, options) {
3914
4098
  // src/config.ts
3915
4099
  import { existsSync as existsSync8 } from "fs";
3916
4100
  import { readFileSync } from "fs";
3917
- import { readFile as readFile8, rm, writeFile as writeFile5 } from "fs/promises";
4101
+ import { readFile as readFile8, rm, writeFile as writeFile6 } from "fs/promises";
3918
4102
  import path10 from "path";
3919
4103
  var CONFIG_FILE = "hivelore.config.json";
3920
4104
  var LEGACY_CONFIG_FILE = "haive.config.json";
@@ -4046,7 +4230,7 @@ function loadConfigSync(paths) {
4046
4230
  }
4047
4231
  }
4048
4232
  async function saveConfig(paths, config) {
4049
- await writeFile5(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
4233
+ await writeFile6(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
4050
4234
  const legacy = path10.join(paths.haiveDir, LEGACY_CONFIG_FILE);
4051
4235
  if (existsSync8(legacy)) {
4052
4236
  try {
@@ -4072,7 +4256,7 @@ function mergeConfig(base, override) {
4072
4256
 
4073
4257
  // src/cross-repo.ts
4074
4258
  import { existsSync as existsSync9 } from "fs";
4075
- import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
4259
+ import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
4076
4260
  import path11 from "path";
4077
4261
  import { spawnSync as spawnSync2 } from "child_process";
4078
4262
  async function loadImportMap(cacheDir) {
@@ -4085,7 +4269,7 @@ async function loadImportMap(cacheDir) {
4085
4269
  }
4086
4270
  }
4087
4271
  async function saveImportMap(cacheDir, map) {
4088
- await writeFile6(path11.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
4272
+ await writeFile7(path11.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
4089
4273
  }
4090
4274
  async function pullCrossRepoSources(paths, config, projectRoot) {
4091
4275
  const sources = config.crossRepoSources ?? [];
@@ -4171,7 +4355,7 @@ async function pullFromSource(paths, source, projectRoot) {
4171
4355
  }
4172
4356
  const updatedBody = importedBodyPrefix + memory.body;
4173
4357
  if (existingEntry) {
4174
- await writeFile6(
4358
+ await writeFile7(
4175
4359
  existingLocalPath,
4176
4360
  serializeMemory({ frontmatter: existingEntry.memory.frontmatter, body: updatedBody }),
4177
4361
  "utf8"
@@ -4196,7 +4380,7 @@ async function pullFromSource(paths, source, projectRoot) {
4196
4380
  });
4197
4381
  const body = importedBodyPrefix + memory.body;
4198
4382
  const destPath = path11.join(destDir, `${newFm.id}.md`);
4199
- await writeFile6(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
4383
+ await writeFile7(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
4200
4384
  importMap[sourceId] = destPath;
4201
4385
  dirty = true;
4202
4386
  report.imported.push(sourceId);
@@ -4234,7 +4418,7 @@ async function cloneOrFetchGitSource(source, paths, report) {
4234
4418
 
4235
4419
  // src/dep-tracker.ts
4236
4420
  import { existsSync as existsSync10 } from "fs";
4237
- import { readFile as readFile10, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
4421
+ import { readFile as readFile10, writeFile as writeFile8, mkdir as mkdir7 } from "fs/promises";
4238
4422
  import path12 from "path";
4239
4423
  function parsePackageJson(content) {
4240
4424
  try {
@@ -4347,7 +4531,7 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
4347
4531
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
4348
4532
  deps: currentDeps
4349
4533
  };
4350
- await writeFile7(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
4534
+ await writeFile8(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
4351
4535
  continue;
4352
4536
  }
4353
4537
  const snapshot = JSON.parse(await readFile10(lockPath, "utf8"));
@@ -4370,7 +4554,7 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
4370
4554
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
4371
4555
  deps: currentDeps
4372
4556
  };
4373
- await writeFile7(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
4557
+ await writeFile8(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
4374
4558
  }
4375
4559
  }
4376
4560
  return results;
@@ -4378,7 +4562,7 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
4378
4562
 
4379
4563
  // src/contract-watcher.ts
4380
4564
  import { existsSync as existsSync11 } from "fs";
4381
- import { readFile as readFile11, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
4565
+ import { readFile as readFile11, writeFile as writeFile9, mkdir as mkdir8 } from "fs/promises";
4382
4566
  import path13 from "path";
4383
4567
  import crypto from "crypto";
4384
4568
  function sha256(content) {
@@ -4601,7 +4785,7 @@ async function snapshotContract(projectRoot, haiveDir, contract) {
4601
4785
  };
4602
4786
  const contractsDir = path13.join(haiveDir, "contracts");
4603
4787
  await mkdir8(contractsDir, { recursive: true });
4604
- await writeFile8(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
4788
+ await writeFile9(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
4605
4789
  return snapshot;
4606
4790
  }
4607
4791
  async function diffContract(projectRoot, haiveDir, contract) {
@@ -4625,7 +4809,7 @@ async function diffContract(projectRoot, haiveDir, contract) {
4625
4809
  };
4626
4810
  const changes = diffSnapshots(beforeSnapshot, afterSnapshot);
4627
4811
  if (changes.length > 0) {
4628
- await writeFile8(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
4812
+ await writeFile9(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
4629
4813
  }
4630
4814
  return {
4631
4815
  contract: contract.name,
@@ -4719,7 +4903,7 @@ async function usageLogSize(paths) {
4719
4903
  }
4720
4904
 
4721
4905
  // src/friction.ts
4722
- import { appendFile as appendFile3, mkdir as mkdir10, readFile as readFile13, writeFile as writeFile9 } from "fs/promises";
4906
+ import { appendFile as appendFile3, mkdir as mkdir10, readFile as readFile13, writeFile as writeFile10 } from "fs/promises";
4723
4907
  import { existsSync as existsSync13 } from "fs";
4724
4908
  import { createHash as createHash3 } from "crypto";
4725
4909
  import path15 from "path";
@@ -4819,7 +5003,7 @@ async function loadFrictionState(paths) {
4819
5003
  }
4820
5004
  async function saveFrictionState(paths, state) {
4821
5005
  if (!existsSync13(paths.runtimeDir)) await mkdir10(paths.runtimeDir, { recursive: true });
4822
- await writeFile9(frictionStatePath(paths), JSON.stringify(state, null, 2) + "\n", "utf8");
5006
+ await writeFile10(frictionStatePath(paths), JSON.stringify(state, null, 2) + "\n", "utf8");
4823
5007
  }
4824
5008
  async function setFrictionStatus(paths, fingerprint, status, url) {
4825
5009
  const state = await loadFrictionState(paths);
@@ -4949,8 +5133,20 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
4949
5133
  buf.push(t);
4950
5134
  }
4951
5135
  if (buf.length) paragraphs.push(buf.join(" ").trim());
4952
- let out = paragraphs[0] ?? stripped.slice(0, maxChars);
4953
- if (!out.trim()) out = stripped.slice(0, maxChars);
5136
+ if (paragraphs.length === 0) {
5137
+ let out2 = stripped.slice(0, maxChars);
5138
+ if (out2.length > maxChars) out2 = out2.slice(0, maxChars).trimEnd() + "\u2026";
5139
+ return out2;
5140
+ }
5141
+ const collected = [];
5142
+ let length = 0;
5143
+ for (const paragraph of paragraphs) {
5144
+ const cost = (collected.length ? 2 : 0) + paragraph.length;
5145
+ if (collected.length > 0 && length + cost > maxChars) break;
5146
+ collected.push(paragraph);
5147
+ length += cost;
5148
+ }
5149
+ let out = collected.join("\n\n");
4954
5150
  if (out.length > maxChars) out = out.slice(0, maxChars).trimEnd() + "\u2026";
4955
5151
  return out;
4956
5152
  }
@@ -5279,7 +5475,7 @@ async function readRuntimeJournalTail(paths, limit) {
5279
5475
  }
5280
5476
 
5281
5477
  // src/enforcement.ts
5282
- import { mkdir as mkdir12, readdir as readdir4, readFile as readFile15, writeFile as writeFile10 } from "fs/promises";
5478
+ import { mkdir as mkdir12, readdir as readdir4, readFile as readFile15, writeFile as writeFile11 } from "fs/promises";
5283
5479
  import { existsSync as existsSync16 } from "fs";
5284
5480
  import path18 from "path";
5285
5481
  var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
@@ -5320,7 +5516,7 @@ async function writeBriefingMarker(paths, input) {
5320
5516
  root: paths.root
5321
5517
  };
5322
5518
  await mkdir12(briefingMarkersDir(paths), { recursive: true });
5323
- await writeFile10(
5519
+ await writeFile11(
5324
5520
  briefingMarkerPath(paths, marker.session_id),
5325
5521
  JSON.stringify(marker, null, 2) + "\n",
5326
5522
  "utf8"
@@ -5431,7 +5627,7 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
5431
5627
  // src/sensor-ledger.ts
5432
5628
  import { createHash as createHash4 } from "crypto";
5433
5629
  import { existsSync as existsSync17, readFileSync as readFileSync2 } from "fs";
5434
- import { appendFile as appendFile5, mkdir as mkdir13, readFile as readFile16, rename, writeFile as writeFile11 } from "fs/promises";
5630
+ import { appendFile as appendFile5, mkdir as mkdir13, readFile as readFile16, rename, writeFile as writeFile12 } from "fs/promises";
5435
5631
  import path19 from "path";
5436
5632
  var MAX_LINES = 1e4;
5437
5633
  var RETAINED_LINES = 8e3;
@@ -5454,7 +5650,7 @@ async function appendSensorEvaluations(paths, evaluations) {
5454
5650
  const lines = raw.split("\n").filter(Boolean);
5455
5651
  if (lines.length > MAX_LINES) {
5456
5652
  const temp = `${file}.${process.pid}.tmp`;
5457
- await writeFile11(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
5653
+ await writeFile12(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
5458
5654
  await rename(temp, file);
5459
5655
  }
5460
5656
  } catch {
@@ -7155,7 +7351,7 @@ ${trimmed}`;
7155
7351
  }
7156
7352
 
7157
7353
  // src/handoff.ts
7158
- import { writeFile as writeFile12, readFile as readFile18, stat as stat4 } from "fs/promises";
7354
+ import { writeFile as writeFile13, readFile as readFile18, stat as stat4 } from "fs/promises";
7159
7355
  import { existsSync as existsSync19 } from "fs";
7160
7356
  import path21 from "path";
7161
7357
  var HANDOFF_FILENAME = "NEXT.md";
@@ -7209,7 +7405,7 @@ function buildHandoffMarkdown(data) {
7209
7405
  }
7210
7406
  async function writeSessionHandoff(root, data) {
7211
7407
  const file = handoffFilePath(root);
7212
- await writeFile12(file, buildHandoffMarkdown(data), "utf8");
7408
+ await writeFile13(file, buildHandoffMarkdown(data), "utf8");
7213
7409
  return file;
7214
7410
  }
7215
7411
  async function readSessionHandoff(root) {
@@ -7434,6 +7630,7 @@ export {
7434
7630
  buildProposeCommand,
7435
7631
  buildReport,
7436
7632
  bumpRead,
7633
+ changedPathsFromDiff,
7437
7634
  churnForAnchors,
7438
7635
  classifyGithubRelease,
7439
7636
  classifyMemoryPriority,
@@ -7619,6 +7816,7 @@ export {
7619
7816
  retirementSignal,
7620
7817
  revertedShaFromCommit,
7621
7818
  reviewLearningsToDrafts,
7819
+ runPresenceSensors,
7622
7820
  runRegexSensor,
7623
7821
  runSensors,
7624
7822
  runTierContract,
@@ -7646,6 +7844,7 @@ export {
7646
7844
  shouldExpandGateReminder,
7647
7845
  snapshotContract,
7648
7846
  specificityScore,
7847
+ stripCommentsForScan,
7649
7848
  stripPrivate,
7650
7849
  suggestGate,
7651
7850
  suggestSensorFromMemory,