@inerrata-corporation/errata 2.0.14-dev.3081 → 2.0.14-dev.3125

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.
@@ -16188,6 +16188,71 @@ function installHistoryCacheFloor(db) {
16188
16188
  });
16189
16189
  }
16190
16190
 
16191
+ // ../../packages/local-graph/src/session-evidence.ts
16192
+ function repairSessionEvidence(input, aliases = /* @__PURE__ */ new Map()) {
16193
+ const mapping = new Map(aliases);
16194
+ const uuid3 = "[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}";
16195
+ const codex = new RegExp(`^codex:(?:rollout-.*?)?(${uuid3})$`, "i");
16196
+ const arrays = /* @__PURE__ */ new Set(["sources", "recurrenceSources", "contributors", "inferredContributors", "citeSessions"]);
16197
+ const scalars = /* @__PURE__ */ new Set(["discriminatorSource", "originSession", "sessionId", "sourceSession"]);
16198
+ const discover = (value, key = "") => {
16199
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
16200
+ const match = value.match(codex);
16201
+ if (match) {
16202
+ const canonical = `codex:${match[1].toLowerCase()}`;
16203
+ mapping.set(value, canonical);
16204
+ mapping.set(match[1].toLowerCase(), canonical);
16205
+ }
16206
+ } else if (Array.isArray(value)) value.forEach((v) => discover(v, key));
16207
+ else if (value && typeof value === "object") {
16208
+ for (const [k, v] of Object.entries(value)) discover(v, key === "citeAnchorHints" ? key : k);
16209
+ }
16210
+ };
16211
+ discover(input);
16212
+ const rewrite = (value, key = "") => {
16213
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
16214
+ return mapping.get(value) ?? mapping.get(value.toLowerCase()) ?? value;
16215
+ }
16216
+ if (Array.isArray(value)) {
16217
+ const values = value.map((v) => rewrite(v, key));
16218
+ return arrays.has(key) || key === "citeAnchorHints" ? [...new Set(values)] : values;
16219
+ }
16220
+ if (!value || typeof value !== "object") return value;
16221
+ const before = value;
16222
+ const after = Object.fromEntries(Object.entries(before).map(([k, v]) => [k, rewrite(v, key === "citeAnchorHints" ? key : k)]));
16223
+ for (const field of ["sources", "contributors"]) {
16224
+ const old = before[field], next = after[field];
16225
+ if (!Array.isArray(old) || !Array.isArray(next) || old.length <= next.length) continue;
16226
+ after["sessionAliasRepair"] = { originalCount: old.length, distinctCount: next.length };
16227
+ if (field === "sources" && typeof after["corroborations"] === "number") {
16228
+ after["corroborations"] = Math.min(after["corroborations"], Math.max(0, next.length - 1));
16229
+ if (after["corroborations"] === 0) after["provisional"] = true;
16230
+ }
16231
+ if (field === "contributors" && typeof after["confirmed"] === "number") {
16232
+ after["preAliasRepairConfirmed"] = after["confirmed"];
16233
+ after["confirmed"] = Math.min(after["confirmed"], next.length);
16234
+ }
16235
+ }
16236
+ return after;
16237
+ };
16238
+ const repaired = rewrite(input);
16239
+ const source = repaired["discriminatorSource"];
16240
+ if (typeof source === "string") {
16241
+ const global = repaired["perContext"]?.["*"];
16242
+ const contributors = Array.isArray(global?.["contributors"]) ? global["contributors"] : [];
16243
+ const inferred = new Set(Array.isArray(global?.["inferredContributors"]) ? global["inferredContributors"] : []);
16244
+ if (contributors.includes(source) || contributors.filter((id) => !inferred.has(id)).length < 2) {
16245
+ repaired["withdrawnDiscriminator"] = repaired["discriminator"];
16246
+ repaired["discriminatorWithdrawalReason"] = "insufficient-independent-witnesses";
16247
+ repaired["provisional"] = true;
16248
+ delete repaired["discriminator"];
16249
+ delete repaired["discriminatorSource"];
16250
+ delete repaired["discriminatorAsked"];
16251
+ }
16252
+ }
16253
+ return repaired;
16254
+ }
16255
+
16191
16256
  // ../../packages/local-graph/src/store.ts
16192
16257
  var LOCAL_RULE_OVERRIDES = {
16193
16258
  ANCHORED_AT: {
@@ -16432,6 +16497,12 @@ var SqliteGraphStore = class {
16432
16497
  }
16433
16498
  this.db.exec(TEMPORAL_INDEX_SQL);
16434
16499
  installHistoryCacheFloor(this.db);
16500
+ if (!this.db.prepare("SELECT value FROM store_meta WHERE key = 'session-evidence-v1'").get()) {
16501
+ this.db.transaction(() => {
16502
+ this.repairSessionAliases();
16503
+ this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES ('session-evidence-v1', 'done')").run();
16504
+ });
16505
+ }
16435
16506
  this.stmts = {
16436
16507
  insertNode: this.db.prepare(`
16437
16508
  INSERT INTO nodes (
@@ -16680,6 +16751,23 @@ var SqliteGraphStore = class {
16680
16751
  this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
16681
16752
  });
16682
16753
  }
16754
+ repairSessionAliases(canonical, aliases = []) {
16755
+ const mapping = new Map(aliases.map((alias) => [alias, canonical]));
16756
+ let changed = 0;
16757
+ for (const table of ["nodes", "edges"]) {
16758
+ const predicates = canonical ? aliases.map(() => "instr(attrs_json, ?) > 0") : ["instr(attrs_json, 'codex:') > 0", "instr(attrs_json, 'discriminatorSource') > 0"];
16759
+ if (!predicates.length) continue;
16760
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM ${table} WHERE ${predicates.join(" OR ")}`).all(...canonical ? aliases : []);
16761
+ const update = this.db.prepare(`UPDATE ${table} SET attrs_json = ?${table === "edges" ? ", type = CASE WHEN type = 'CONFIRMS' AND json_extract(?, '$.provisional') = 1 THEN 'INDICATES' ELSE type END" : ""} WHERE id = ?`);
16762
+ for (const row of rows) {
16763
+ const attrs = JSON.stringify(repairSessionEvidence(JSON.parse(row.attrs_json), mapping));
16764
+ if (attrs === row.attrs_json) continue;
16765
+ update.run(...table === "edges" ? [attrs, attrs, row.id] : [attrs, row.id]);
16766
+ changed++;
16767
+ }
16768
+ }
16769
+ return changed;
16770
+ }
16683
16771
  deleteNode(id) {
16684
16772
  this.stmts.deleteNode.run(id);
16685
16773
  }
@@ -17761,6 +17849,7 @@ function recordTriageObservation(l2, obs, ts) {
17761
17849
  }
17762
17850
  const discriminator = obs.discriminator ?? existing?.attrs["discriminator"];
17763
17851
  const attrs = {
17852
+ ...existing?.attrs,
17764
17853
  driftKind: obs.driftKind ?? existing?.attrs["driftKind"] ?? DRIFT_KIND.RELOCATION,
17765
17854
  perContext,
17766
17855
  // A discriminator (now or previously supplied) promotes the route to a router.
package/errata.mjs CHANGED
@@ -15495,10 +15495,9 @@ function looksLikeSecretValue(raw2, quoted) {
15495
15495
  if (quoted) return raw2.length > 0;
15496
15496
  const lower = raw2.toLowerCase();
15497
15497
  if (ASSIGNMENT_STOPWORDS.has(lower)) return false;
15498
- if (!/^[A-Za-z0-9+/=_-]{8,}$/.test(raw2)) return false;
15499
- if (/\d/.test(raw2)) return true;
15500
- if (raw2.length >= 12) return true;
15501
- return /[a-z]/.test(raw2) && /[A-Z]/.test(raw2);
15498
+ if (/^(?:process\.env\.|os\.environ(?:\[|\.)|(?:request|response|config|settings|options)\.)/.test(raw2)) return false;
15499
+ if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*\(/.test(raw2)) return false;
15500
+ return raw2.length > 0;
15502
15501
  }
15503
15502
  function applyRule(text, rule, removed) {
15504
15503
  let count = 0;
@@ -15564,7 +15563,7 @@ var HISTORY_SCRUBBER_VERSION, REDACTED_CREDENTIAL, REDACTED_EMAIL, cred, ASSIGNM
15564
15563
  var init_history_text = __esm({
15565
15564
  "../../packages/shared/src/history-text.ts"() {
15566
15565
  "use strict";
15567
- HISTORY_SCRUBBER_VERSION = "history-scrub-3";
15566
+ HISTORY_SCRUBBER_VERSION = "history-scrub-4";
15568
15567
  REDACTED_CREDENTIAL = "[redacted:credential]";
15569
15568
  REDACTED_EMAIL = "[redacted:email]";
15570
15569
  cred = () => REDACTED_CREDENTIAL;
@@ -15639,9 +15638,15 @@ var init_history_text = __esm({
15639
15638
  },
15640
15639
  // Userinfo in connection URLs must be removed before the email rule runs.
15641
15640
  {
15642
- re: /\b([a-z][a-z0-9+.-]*:\/\/)([^\s/@:]+):([^\s/@]+)@/gi,
15641
+ re: /\b([a-z][a-z0-9+.-]*:\/\/)([^\s/@:]*):([^\s/@]+)@/gi,
15643
15642
  replace: (m) => m[2] === "[redacted" && m[3] === "credential]" ? null : `${m[1]}${REDACTED_CREDENTIAL}@`
15644
15643
  },
15644
+ // MySQL's short password option attaches the value directly to -p; other
15645
+ // command flags (such as a port) must not be treated as passwords.
15646
+ {
15647
+ re: /\b((?:mysql|mariadb)(?:\s+[^\s]+)*?\s+-p)([^\s"'`,;)}\]]+)/gi,
15648
+ replace: (m) => m[2]?.startsWith("[redacted:") ? null : `${m[1]}${REDACTED_CREDENTIAL}`
15649
+ },
15645
15650
  // Seeded login prose commonly writes email/password without an assignment.
15646
15651
  {
15647
15652
  re: /(\b[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+|\[redacted:email\])(\s*\/\s*)(["'\x60]?)([^\s"'\x60,;)}\]]+)/gi,
@@ -18002,6 +18007,76 @@ var init_history_cache_floor = __esm({
18002
18007
  }
18003
18008
  });
18004
18009
 
18010
+ // ../../packages/local-graph/src/session-evidence.ts
18011
+ function repairSessionEvidence(input, aliases = /* @__PURE__ */ new Map()) {
18012
+ const mapping = new Map(aliases);
18013
+ const uuid3 = "[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}";
18014
+ const codex = new RegExp(`^codex:(?:rollout-.*?)?(${uuid3})$`, "i");
18015
+ const arrays = /* @__PURE__ */ new Set(["sources", "recurrenceSources", "contributors", "inferredContributors", "citeSessions"]);
18016
+ const scalars = /* @__PURE__ */ new Set(["discriminatorSource", "originSession", "sessionId", "sourceSession"]);
18017
+ const discover = (value, key = "") => {
18018
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
18019
+ const match2 = value.match(codex);
18020
+ if (match2) {
18021
+ const canonical = `codex:${match2[1].toLowerCase()}`;
18022
+ mapping.set(value, canonical);
18023
+ mapping.set(match2[1].toLowerCase(), canonical);
18024
+ }
18025
+ } else if (Array.isArray(value)) value.forEach((v) => discover(v, key));
18026
+ else if (value && typeof value === "object") {
18027
+ for (const [k, v] of Object.entries(value)) discover(v, key === "citeAnchorHints" ? key : k);
18028
+ }
18029
+ };
18030
+ discover(input);
18031
+ const rewrite = (value, key = "") => {
18032
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
18033
+ return mapping.get(value) ?? mapping.get(value.toLowerCase()) ?? value;
18034
+ }
18035
+ if (Array.isArray(value)) {
18036
+ const values = value.map((v) => rewrite(v, key));
18037
+ return arrays.has(key) || key === "citeAnchorHints" ? [...new Set(values)] : values;
18038
+ }
18039
+ if (!value || typeof value !== "object") return value;
18040
+ const before = value;
18041
+ const after = Object.fromEntries(Object.entries(before).map(([k, v]) => [k, rewrite(v, key === "citeAnchorHints" ? key : k)]));
18042
+ for (const field of ["sources", "contributors"]) {
18043
+ const old = before[field], next = after[field];
18044
+ if (!Array.isArray(old) || !Array.isArray(next) || old.length <= next.length) continue;
18045
+ after["sessionAliasRepair"] = { originalCount: old.length, distinctCount: next.length };
18046
+ if (field === "sources" && typeof after["corroborations"] === "number") {
18047
+ after["corroborations"] = Math.min(after["corroborations"], Math.max(0, next.length - 1));
18048
+ if (after["corroborations"] === 0) after["provisional"] = true;
18049
+ }
18050
+ if (field === "contributors" && typeof after["confirmed"] === "number") {
18051
+ after["preAliasRepairConfirmed"] = after["confirmed"];
18052
+ after["confirmed"] = Math.min(after["confirmed"], next.length);
18053
+ }
18054
+ }
18055
+ return after;
18056
+ };
18057
+ const repaired = rewrite(input);
18058
+ const source = repaired["discriminatorSource"];
18059
+ if (typeof source === "string") {
18060
+ const global2 = repaired["perContext"]?.["*"];
18061
+ const contributors = Array.isArray(global2?.["contributors"]) ? global2["contributors"] : [];
18062
+ const inferred = new Set(Array.isArray(global2?.["inferredContributors"]) ? global2["inferredContributors"] : []);
18063
+ if (contributors.includes(source) || contributors.filter((id) => !inferred.has(id)).length < 2) {
18064
+ repaired["withdrawnDiscriminator"] = repaired["discriminator"];
18065
+ repaired["discriminatorWithdrawalReason"] = "insufficient-independent-witnesses";
18066
+ repaired["provisional"] = true;
18067
+ delete repaired["discriminator"];
18068
+ delete repaired["discriminatorSource"];
18069
+ delete repaired["discriminatorAsked"];
18070
+ }
18071
+ }
18072
+ return repaired;
18073
+ }
18074
+ var init_session_evidence = __esm({
18075
+ "../../packages/local-graph/src/session-evidence.ts"() {
18076
+ "use strict";
18077
+ }
18078
+ });
18079
+
18005
18080
  // ../../packages/local-graph/src/store.ts
18006
18081
  function localEdgeViolation(fromLabel, type, toLabel) {
18007
18082
  if (type in LOCAL_RULE_OVERRIDES) {
@@ -18085,6 +18160,7 @@ var init_store = __esm({
18085
18160
  init_src2();
18086
18161
  init_src();
18087
18162
  init_history_cache_floor();
18163
+ init_session_evidence();
18088
18164
  LOCAL_RULE_OVERRIDES = {
18089
18165
  ANCHORED_AT: {
18090
18166
  from: EDGE_RULES["ANCHORED_AT"]?.from,
@@ -18256,6 +18332,12 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18256
18332
  }
18257
18333
  this.db.exec(TEMPORAL_INDEX_SQL);
18258
18334
  installHistoryCacheFloor(this.db);
18335
+ if (!this.db.prepare("SELECT value FROM store_meta WHERE key = 'session-evidence-v1'").get()) {
18336
+ this.db.transaction(() => {
18337
+ this.repairSessionAliases();
18338
+ this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES ('session-evidence-v1', 'done')").run();
18339
+ });
18340
+ }
18259
18341
  this.stmts = {
18260
18342
  insertNode: this.db.prepare(`
18261
18343
  INSERT INTO nodes (
@@ -18504,6 +18586,23 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18504
18586
  this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
18505
18587
  });
18506
18588
  }
18589
+ repairSessionAliases(canonical, aliases = []) {
18590
+ const mapping = new Map(aliases.map((alias) => [alias, canonical]));
18591
+ let changed = 0;
18592
+ for (const table of ["nodes", "edges"]) {
18593
+ const predicates = canonical ? aliases.map(() => "instr(attrs_json, ?) > 0") : ["instr(attrs_json, 'codex:') > 0", "instr(attrs_json, 'discriminatorSource') > 0"];
18594
+ if (!predicates.length) continue;
18595
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM ${table} WHERE ${predicates.join(" OR ")}`).all(...canonical ? aliases : []);
18596
+ const update = this.db.prepare(`UPDATE ${table} SET attrs_json = ?${table === "edges" ? ", type = CASE WHEN type = 'CONFIRMS' AND json_extract(?, '$.provisional') = 1 THEN 'INDICATES' ELSE type END" : ""} WHERE id = ?`);
18597
+ for (const row of rows) {
18598
+ const attrs = JSON.stringify(repairSessionEvidence(JSON.parse(row.attrs_json), mapping));
18599
+ if (attrs === row.attrs_json) continue;
18600
+ update.run(...table === "edges" ? [attrs, attrs, row.id] : [attrs, row.id]);
18601
+ changed++;
18602
+ }
18603
+ }
18604
+ return changed;
18605
+ }
18507
18606
  deleteNode(id) {
18508
18607
  this.stmts.deleteNode.run(id);
18509
18608
  }
@@ -23022,6 +23121,7 @@ function recordTriageObservation(l2, obs, ts) {
23022
23121
  }
23023
23122
  const discriminator = obs.discriminator ?? existing?.attrs["discriminator"];
23024
23123
  const attrs = {
23124
+ ...existing?.attrs,
23025
23125
  driftKind: obs.driftKind ?? existing?.attrs["driftKind"] ?? DRIFT_KIND.RELOCATION,
23026
23126
  perContext,
23027
23127
  // A discriminator (now or previously supplied) promotes the route to a router.
@@ -23260,7 +23360,7 @@ function induceTriage(l2, opts) {
23260
23360
  return report;
23261
23361
  }
23262
23362
  function pendingDiscriminators(l2, opts = {}) {
23263
- const minSources = opts.minSources ?? 1;
23363
+ const minSources = opts.minSources ?? 2;
23264
23364
  const minObservations = opts.minObservations ?? 2;
23265
23365
  const out2 = [];
23266
23366
  for (const triage of l2.findNodesByLabel("Triage")) {
@@ -23295,6 +23395,12 @@ function promoteRouteWithDiscriminator(l2, routeId, discriminator, ts, source) {
23295
23395
  if (isUnfilledPlaceholder(test)) return false;
23296
23396
  const existing = l2.getEdge(routeId);
23297
23397
  if (!existing || !ROUTE_EDGE_TYPES.includes(existing.type)) return false;
23398
+ const global2 = (existing.attrs["perContext"] ?? {})[TRIAGE_GLOBAL_BUCKET] ?? {};
23399
+ const contributors = new Set(global2.contributors ?? []);
23400
+ const inferred = new Set(global2.inferredContributors ?? []);
23401
+ const witnesses = [...contributors].filter((id) => !inferred.has(id));
23402
+ if (!source || contributors.has(source) || witnesses.length < 2) return false;
23403
+ if (existing.attrs["discriminator"] && existing.attrs["discriminator"] !== test) return false;
23298
23404
  const attrs = {
23299
23405
  ...existing.attrs,
23300
23406
  discriminator: test,
@@ -24860,8 +24966,8 @@ var init_src5 = __esm({
24860
24966
  });
24861
24967
 
24862
24968
  // ../../packages/context-writer/src/render.ts
24863
- function isPassiveInjectable(node2) {
24864
- if (!historyReadPolicy(node2, { mode: "passive" }).allow) return false;
24969
+ function isPassiveInjectable(node2, atCommit) {
24970
+ if (!historyReadPolicy(node2, { mode: "passive", atCommit }).allow) return false;
24865
24971
  if (node2.label === "Pattern" || node2.label === "Technique" || node2.label === "AntiPattern") {
24866
24972
  return true;
24867
24973
  }
@@ -24871,7 +24977,7 @@ function isPassiveInjectable(node2) {
24871
24977
  return false;
24872
24978
  }
24873
24979
  function buildSnapshot(opts) {
24874
- const problems = opts.store.findNodesByLabel("Problem").filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive" }).allow);
24980
+ const problems = opts.store.findNodesByLabel("Problem").filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive", atCommit: opts.atCommit }).allow);
24875
24981
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
24876
24982
  const allProblems = stillOpen.filter((p) => !isConstraintProblem(p));
24877
24983
  allProblems.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
@@ -24901,11 +25007,11 @@ function buildSnapshot(opts) {
24901
25007
  ...opts.store.findNodesByLabel("Pattern"),
24902
25008
  ...opts.store.findNodesByLabel("Technique"),
24903
25009
  ...opts.store.findNodesByLabel("AntiPattern")
24904
- ].filter((n) => historyReadPolicy(n, { mode: "passive" }).allow).sort((a, b) => b.pageRank - a.pageRank).slice(0, 10);
24905
- const workingFile = opts.workingFile ? sliceForFile(opts.store, opts.workingFile) : void 0;
25010
+ ].filter((n) => historyReadPolicy(n, { mode: "passive", atCommit: opts.atCommit }).allow).sort((a, b) => b.pageRank - a.pageRank).slice(0, 10);
25011
+ const workingFile = opts.workingFile ? sliceForFile(opts.store, opts.workingFile, opts.atCommit) : void 0;
24906
25012
  const byEpisode = /* @__PURE__ */ new Map();
24907
25013
  for (const sol of opts.store.findNodesByLabel("Solution")) {
24908
- if (!historyReadPolicy(sol, { mode: "passive" }).allow) continue;
25014
+ if (!historyReadPolicy(sol, { mode: "passive", atCommit: opts.atCommit }).allow) continue;
24909
25015
  if (sol.attrs["enrichmentPending"] !== true) continue;
24910
25016
  const ep = sol.attrs["episodeId"] ?? sol.id;
24911
25017
  const list = byEpisode.get(ep) ?? [];
@@ -24977,7 +25083,7 @@ function buildSnapshot(opts) {
24977
25083
  generatedAt: (opts.now ?? /* @__PURE__ */ new Date()).toISOString()
24978
25084
  };
24979
25085
  }
24980
- function sliceForFile(store, relPath) {
25086
+ function sliceForFile(store, relPath, atCommit) {
24981
25087
  const candidates = store.findNodesByLabel("File");
24982
25088
  const file2 = candidates.find((n) => n.description === relPath);
24983
25089
  if (!file2) return void 0;
@@ -25016,7 +25122,7 @@ function sliceForFile(store, relPath) {
25016
25122
  }
25017
25123
  const fp = priorsForFile(store, relPath);
25018
25124
  if (fp) {
25019
- const allowed = (n) => historyReadPolicy(n, { mode: "passive" }).allow;
25125
+ const allowed = (n) => historyReadPolicy(n, { mode: "passive", atCommit }).allow;
25020
25126
  fp.openProblems = fp.openProblems.filter(allowed);
25021
25127
  fp.constraints = fp.constraints.filter(allowed);
25022
25128
  fp.resolvedProblems = fp.resolvedProblems.filter(allowed);
@@ -25426,6 +25532,7 @@ function budgetSnapshot(s, maxChars) {
25426
25532
  function assembleAgentContext(opts) {
25427
25533
  const snapshot = opts.snapshot ?? buildSnapshot({
25428
25534
  store: opts.store,
25535
+ atCommit: opts.atCommit,
25429
25536
  profile: opts.profile,
25430
25537
  reviewCount: opts.reviewCount,
25431
25538
  reviewUiUrl: opts.reviewUiUrl,
@@ -25441,7 +25548,7 @@ function assembleAgentContext(opts) {
25441
25548
  if (opts.remote && opts.remote.length > 0) {
25442
25549
  const seen = /* @__PURE__ */ new Set();
25443
25550
  const remote = opts.remote.filter((n) => {
25444
- if (!isPassiveInjectable(n)) return false;
25551
+ if (!isPassiveInjectable(n, opts.atCommit)) return false;
25445
25552
  if (opts.store.getNode(n.id)) return false;
25446
25553
  if (seen.has(n.id)) return false;
25447
25554
  seen.add(n.id);
@@ -25643,7 +25750,7 @@ function selectDurableMemory(store, profile, opts = {}) {
25643
25750
  const minContributors = opts.minContributors ?? 1;
25644
25751
  const wsLangs = new Set(profile.languages.map((l) => l.toLowerCase()));
25645
25752
  return store.findNodesByLabel("Claim").filter(
25646
- (n) => historyReadPolicy(n, { mode: "passive" }).allow && Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) === ABSTRACTION_LEVEL.PRINCIPLE && n.attrs["provisional"] === false && contributorsOf(n) >= minContributors && // Lang-relevance: universal principles (no scope.lang) bleed everywhere; a
25753
+ (n) => historyReadPolicy(n, { mode: "passive", atCommit: opts.atCommit }).allow && Number(n.attrs["abstractionLevel"] ?? ABSTRACTION_LEVEL.CONCRETE) === ABSTRACTION_LEVEL.PRINCIPLE && n.attrs["provisional"] === false && contributorsOf(n) >= minContributors && // Lang-relevance: universal principles (no scope.lang) bleed everywhere; a
25647
25754
  // language-scoped principle only bleeds into repos that USE that language —
25648
25755
  // so a Python principle never lands a dead `**/*.py` rule in a TS repo.
25649
25756
  (scopeLangOf(n) == null || wsLangs.has(scopeLangOf(n)))
@@ -31158,26 +31265,26 @@ function buildFileLexicon(store) {
31158
31265
  for (const n of store.findNodesByLabel("File")) {
31159
31266
  const relPath = typeof n.attrs["relPath"] === "string" && n.attrs["relPath"].length > 0 ? n.attrs["relPath"] : n.description;
31160
31267
  if (!relPath) continue;
31161
- const basename8 = relPath.split("/").pop();
31162
- if (!basename8.includes(".")) continue;
31163
- entries.push({ relPath, basename: basename8 });
31164
- basenameCount.set(basename8, (basenameCount.get(basename8) ?? 0) + 1);
31165
- const stem = basename8.slice(0, basename8.indexOf("."));
31268
+ const basename9 = relPath.split("/").pop();
31269
+ if (!basename9.includes(".")) continue;
31270
+ entries.push({ relPath, basename: basename9 });
31271
+ basenameCount.set(basename9, (basenameCount.get(basename9) ?? 0) + 1);
31272
+ const stem = basename9.slice(0, basename9.indexOf("."));
31166
31273
  if (isBareMatchableStem(stem)) {
31167
31274
  let s = stemRelPaths.get(stem);
31168
31275
  if (!s) stemRelPaths.set(stem, s = /* @__PURE__ */ new Set());
31169
31276
  s.add(relPath);
31170
31277
  }
31171
31278
  }
31172
- for (const { relPath, basename: basename8 } of entries) {
31279
+ for (const { relPath, basename: basename9 } of entries) {
31173
31280
  const entry = { relPath };
31174
- if (relPath !== basename8) byKey.set(relPath, entry);
31175
- const conventional = CONVENTION_FILENAMES.has(basename8) || (basenameCount.get(basename8) ?? 0) >= CONVENTION_BASENAME_MIN_FILES;
31176
- if (!conventional && !byKey.has(basename8)) byKey.set(basename8, entry);
31177
- const list = byBasename.get(basename8);
31281
+ if (relPath !== basename9) byKey.set(relPath, entry);
31282
+ const conventional = CONVENTION_FILENAMES.has(basename9) || (basenameCount.get(basename9) ?? 0) >= CONVENTION_BASENAME_MIN_FILES;
31283
+ if (!conventional && !byKey.has(basename9)) byKey.set(basename9, entry);
31284
+ const list = byBasename.get(basename9);
31178
31285
  if (list) list.push(entry);
31179
- else byBasename.set(basename8, [entry]);
31180
- const stem = basename8.slice(0, basename8.indexOf("."));
31286
+ else byBasename.set(basename9, [entry]);
31287
+ const stem = basename9.slice(0, basename9.indexOf("."));
31181
31288
  if (isBareMatchableStem(stem) && !CONVENTION_STEMS.has(stem) && !byStem.has(stem)) {
31182
31289
  const dirStems = new Set(
31183
31290
  [...stemRelPaths.get(stem) ?? []].map((p) => {
@@ -31194,8 +31301,8 @@ function resolveFileToken(token, lexicon) {
31194
31301
  const exact = lexicon.byKey.get(token);
31195
31302
  if (exact) return exact;
31196
31303
  if (!token.includes("/")) return null;
31197
- const basename8 = token.split("/").pop();
31198
- for (const entry of lexicon.byBasename.get(basename8) ?? []) {
31304
+ const basename9 = token.split("/").pop();
31305
+ for (const entry of lexicon.byBasename.get(basename9) ?? []) {
31199
31306
  if (entry.relPath === token || entry.relPath.endsWith(`/${token}`)) return entry;
31200
31307
  }
31201
31308
  return null;
@@ -31220,9 +31327,9 @@ function fileRolePhrase(relPath, level = 1) {
31220
31327
  return "a source file";
31221
31328
  }
31222
31329
  function isSourceFileToken(token) {
31223
- const basename8 = token.split(/[/\\]/).pop() ?? token;
31224
- if (CONVENTION_FILENAMES.has(basename8)) return false;
31225
- const ext = basename8.split(".").pop();
31330
+ const basename9 = token.split(/[/\\]/).pop() ?? token;
31331
+ if (CONVENTION_FILENAMES.has(basename9)) return false;
31332
+ const ext = basename9.split(".").pop();
31226
31333
  return ext !== void 0 && SOURCE_EXTENSIONS.has(ext.toLowerCase());
31227
31334
  }
31228
31335
  function generalizeFileRefs(text, fileLexicon, level = 1) {
@@ -72758,7 +72865,7 @@ init_config();
72758
72865
  // src/engine.ts
72759
72866
  import { execFileSync as execFileSync4 } from "node:child_process";
72760
72867
  import { existsSync as existsSync25, statSync as statSync8, appendFileSync as appendFileSync4, readdirSync as readdirSync10, renameSync as renameSync3, readFileSync as readFileSync28, writeFileSync as writeFileSync21 } from "node:fs";
72761
- import { join as join34, relative as relative6, sep as sep4 } from "node:path";
72868
+ import { basename as basename7, join as join34, relative as relative6, sep as sep4 } from "node:path";
72762
72869
 
72763
72870
  // src/source-watcher.ts
72764
72871
  import { EventEmitter as EventEmitter2 } from "node:events";
@@ -72918,10 +73025,10 @@ var ReaddirpStream = class extends Readable2 {
72918
73025
  }
72919
73026
  async _formatEntry(dirent, path2) {
72920
73027
  let entry;
72921
- const basename8 = this._isDirent ? dirent.name : dirent;
73028
+ const basename9 = this._isDirent ? dirent.name : dirent;
72922
73029
  try {
72923
- const fullPath = presolve(pjoin(path2, basename8));
72924
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename8 };
73030
+ const fullPath = presolve(pjoin(path2, basename9));
73031
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
72925
73032
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
72926
73033
  } catch (err2) {
72927
73034
  this._onError(err2);
@@ -73460,9 +73567,9 @@ var NodeFsHandler = class {
73460
73567
  _watchWithNodeFs(path2, listener) {
73461
73568
  const opts = this.fsw.options;
73462
73569
  const directory = sysPath.dirname(path2);
73463
- const basename8 = sysPath.basename(path2);
73570
+ const basename9 = sysPath.basename(path2);
73464
73571
  const parent = this.fsw._getWatchedDir(directory);
73465
- parent.add(basename8);
73572
+ parent.add(basename9);
73466
73573
  const absolutePath = sysPath.resolve(path2);
73467
73574
  const options = {
73468
73575
  persistent: opts.persistent
@@ -73472,7 +73579,7 @@ var NodeFsHandler = class {
73472
73579
  let closer;
73473
73580
  if (opts.usePolling) {
73474
73581
  const enableBin = opts.interval !== opts.binaryInterval;
73475
- options.interval = enableBin && isBinaryPath(basename8) ? opts.binaryInterval : opts.interval;
73582
+ options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
73476
73583
  closer = setFsWatchFileListener(path2, absolutePath, options, {
73477
73584
  listener,
73478
73585
  rawEmitter: this.fsw._emitRaw
@@ -73495,10 +73602,10 @@ var NodeFsHandler = class {
73495
73602
  return;
73496
73603
  }
73497
73604
  const dirname14 = sysPath.dirname(file2);
73498
- const basename8 = sysPath.basename(file2);
73605
+ const basename9 = sysPath.basename(file2);
73499
73606
  const parent = this.fsw._getWatchedDir(dirname14);
73500
73607
  let prevStats = stats;
73501
- if (parent.has(basename8))
73608
+ if (parent.has(basename9))
73502
73609
  return;
73503
73610
  const listener = async (path2, newStats) => {
73504
73611
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
@@ -73523,9 +73630,9 @@ var NodeFsHandler = class {
73523
73630
  prevStats = newStats2;
73524
73631
  }
73525
73632
  } catch (error48) {
73526
- this.fsw._remove(dirname14, basename8);
73633
+ this.fsw._remove(dirname14, basename9);
73527
73634
  }
73528
- } else if (parent.has(basename8)) {
73635
+ } else if (parent.has(basename9)) {
73529
73636
  const at = newStats.atimeMs;
73530
73637
  const mt = newStats.mtimeMs;
73531
73638
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -74805,6 +74912,16 @@ import { closeSync, existsSync as existsSync16, fstatSync, openSync, readdirSync
74805
74912
  import { basename as basename5, delimiter, dirname as dirname10, join as join20, normalize as normalize3 } from "node:path";
74806
74913
  import { homedir as homedir4 } from "node:os";
74807
74914
  import { createHash as createHash16 } from "node:crypto";
74915
+ function completeJsonlPrefix(buf) {
74916
+ const end = buf.lastIndexOf(10) + 1;
74917
+ if (end === buf.length) return end;
74918
+ try {
74919
+ JSON.parse(buf.subarray(end).toString("utf8"));
74920
+ return buf.length;
74921
+ } catch {
74922
+ return end;
74923
+ }
74924
+ }
74808
74925
  function readFrom(path2, fromByte, maxBytes) {
74809
74926
  let fd;
74810
74927
  try {
@@ -74821,11 +74938,12 @@ function readFrom(path2, fromByte, maxBytes) {
74821
74938
  for (; ; ) {
74822
74939
  const len = Math.min(cap, remaining);
74823
74940
  const buf = Buffer.allocUnsafe(len);
74824
- readSync(fd, buf, 0, len, start2);
74825
- if (len >= remaining) {
74826
- return { text: buf.toString("utf8"), nextOffset: size, more: false, ioFailed: false };
74941
+ const read2 = readSync(fd, buf, 0, len, start2);
74942
+ if (len >= remaining || read2 < len) {
74943
+ const end = completeJsonlPrefix(buf.subarray(0, read2));
74944
+ return { text: buf.subarray(0, end).toString("utf8"), nextOffset: start2 + end, more: false, ioFailed: false };
74827
74945
  }
74828
- const nl = buf.lastIndexOf(10);
74946
+ const nl = buf.subarray(0, read2).lastIndexOf(10);
74829
74947
  if (nl >= 0) {
74830
74948
  return {
74831
74949
  text: buf.subarray(0, nl + 1).toString("utf8"),
@@ -74842,41 +74960,30 @@ function readFrom(path2, fromByte, maxBytes) {
74842
74960
  closeSync(fd);
74843
74961
  }
74844
74962
  }
74845
- function transcriptSize(path2) {
74846
- try {
74847
- const fd = openSync(path2, "r");
74848
- try {
74849
- return fstatSync(fd).size;
74850
- } finally {
74851
- closeSync(fd);
74852
- }
74853
- } catch {
74854
- return 0;
74855
- }
74856
- }
74857
74963
  function readTail(path2, maxBytes) {
74858
74964
  return readTailWithLosses(path2, maxBytes).text;
74859
74965
  }
74860
- function readTailWithLosses(path2, maxBytes) {
74966
+ function readTailWithLosses(path2, maxBytes, completeLinesOnly = false) {
74861
74967
  let fd;
74862
74968
  try {
74863
74969
  fd = openSync(path2, "r");
74864
74970
  } catch {
74865
- return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true };
74971
+ return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true, nextOffset: 0 };
74866
74972
  }
74867
74973
  try {
74868
74974
  const size = fstatSync(fd).size;
74869
74975
  const start2 = Math.max(0, size - maxBytes);
74870
74976
  const len = size - start2;
74871
- if (len <= 0) return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: false };
74977
+ if (len <= 0) return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: false, nextOffset: 0 };
74872
74978
  const buf = Buffer.allocUnsafe(len);
74873
- readSync(fd, buf, 0, len, start2);
74874
- const text = buf.toString("utf8");
74979
+ const read2 = readSync(fd, buf, 0, len, start2);
74980
+ const end = completeLinesOnly ? completeJsonlPrefix(buf.subarray(0, read2)) : read2;
74981
+ const text = buf.subarray(0, end).toString("utf8");
74875
74982
  const firstNewline = buf.indexOf(10);
74876
74983
  const parseStartByte = firstNewline < 0 ? start2 : start2 + firstNewline + 1 - Buffer.byteLength(buf.subarray(0, firstNewline + 1).toString("utf8"));
74877
- return { text, bytesUnreachable: start2, parseStartByte, ioFailed: false };
74984
+ return { text, bytesUnreachable: start2, parseStartByte, ioFailed: false, nextOffset: start2 + end };
74878
74985
  } catch {
74879
- return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true };
74986
+ return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true, nextOffset: 0 };
74880
74987
  } finally {
74881
74988
  closeSync(fd);
74882
74989
  }
@@ -74939,10 +75046,11 @@ function isUserTurnBoundary(obj) {
74939
75046
  return blocks.some((b) => b["type"] === "text");
74940
75047
  }
74941
75048
  function readAssistantTurnsWithLosses(transcriptPath, includeThinking = true, maxBytes = 2e6) {
74942
- const tail = readTailWithLosses(transcriptPath, maxBytes);
75049
+ const tail = readTailWithLosses(transcriptPath, maxBytes, true);
74943
75050
  const parsed = parseAssistantTurns(tail.text, includeThinking, false, tail.parseStartByte);
74944
75051
  return {
74945
75052
  turns: parsed.turns,
75053
+ nextOffset: tail.nextOffset,
74946
75054
  losses: {
74947
75055
  parseFailures: parsed.parseFailures,
74948
75056
  ioFailed: tail.ioFailed,
@@ -75253,7 +75361,13 @@ function codexSessionRoots(env2 = process.env, home = homedir4()) {
75253
75361
  roots.add(join20(home, ".codex", "sessions"));
75254
75362
  return [...roots];
75255
75363
  }
75256
- function readCodexRolloutCwd(path2) {
75364
+ function readCodexRolloutIdentity(path2) {
75365
+ const legacy = `codex:${basename5(path2, ".jsonl")}`;
75366
+ const identity = (cwd, id) => {
75367
+ const uuid3 = typeof id === "string" && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(id) ? id.toLowerCase() : basename5(path2, ".jsonl").match(/([a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12})$/i)?.[1]?.toLowerCase();
75368
+ const sessionId = uuid3 ? `codex:${uuid3}` : legacy;
75369
+ return { cwd, sessionId, aliases: [legacy, ...uuid3 ? [uuid3] : []].filter((s) => s !== sessionId) };
75370
+ };
75257
75371
  let fd;
75258
75372
  try {
75259
75373
  fd = openSync(path2, "r");
@@ -75276,11 +75390,11 @@ function readCodexRolloutCwd(path2) {
75276
75390
  const o = JSON.parse(firstLine);
75277
75391
  const meta3 = o?.type === "session_meta" ? o.payload ?? o : o.payload ?? o;
75278
75392
  const cwd = meta3?.cwd ?? o?.cwd;
75279
- if (typeof cwd === "string") return cwd;
75393
+ if (typeof cwd === "string") return identity(cwd, o.type === "session_meta" ? meta3.id : void 0);
75280
75394
  } catch {
75281
75395
  }
75282
75396
  const m = firstLine.match(/"cwd"\s*:\s*"((?:[^"\\]|\\.)*)"/);
75283
- return m ? JSON.parse(`"${m[1]}"`) : null;
75397
+ return m ? identity(JSON.parse(`"${m[1]}"`)) : null;
75284
75398
  } catch {
75285
75399
  return null;
75286
75400
  } finally {
@@ -75338,15 +75452,16 @@ function codexRolloutsForCwd(cwd, opts = {}) {
75338
75452
  );
75339
75453
  activity.set(full, { size: st.size, activeAt: activityAtMs });
75340
75454
  if (activityAtMs < sinceMs) continue;
75341
- const sessionCwd = readCodexRolloutCwd(full);
75342
- if (sessionCwd === null) continue;
75455
+ const identity = readCodexRolloutIdentity(full);
75456
+ if (identity === null) continue;
75457
+ const sessionCwd = identity.cwd;
75343
75458
  let sessionOwner = owners.get(sessionCwd);
75344
75459
  if (sessionOwner === void 0) {
75345
75460
  sessionOwner = checkoutRootOf(sessionCwd);
75346
75461
  owners.set(sessionCwd, sessionOwner);
75347
75462
  }
75348
75463
  if (sessionOwner !== owner) continue;
75349
- out2.push({ path: full, sessionId: `codex:${basename5(name2, ".jsonl")}`, mtimeMs: st.mtimeMs, activityAtMs });
75464
+ out2.push({ path: full, sessionId: identity.sessionId, mtimeMs: st.mtimeMs, activityAtMs });
75350
75465
  }
75351
75466
  }
75352
75467
  for (const path2 of activity.keys()) if (!observed.has(path2)) activity.delete(path2);
@@ -80912,7 +81027,7 @@ function createWatchBreaker(deps) {
80912
81027
  }
80913
81028
 
80914
81029
  // src/engine.ts
80915
- var DAEMON_VERSION = true ? "2.0.14-dev.3081" : "2.0.0-alpha.0";
81030
+ var DAEMON_VERSION = true ? "2.0.14-dev.3125" : "2.0.0-alpha.0";
80916
81031
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
80917
81032
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
80918
81033
  var GIT_OP_MUTE_MS = 4e3;
@@ -81368,6 +81483,7 @@ function createWorkspaceEngine(opts) {
81368
81483
  const elicit = isEdgeElicitationEnabled();
81369
81484
  const snapOpts = {
81370
81485
  profile,
81486
+ atCommit: readGitRefState(join34(opts.workspaceRoot, ".git")).sha ?? void 0,
81371
81487
  reviewCount: pendingCount(paths),
81372
81488
  reviewUiUrl: opts.reviewUrl(),
81373
81489
  workingFile: workingFiles.mostRecent() ?? void 0,
@@ -81381,6 +81497,10 @@ function createWorkspaceEngine(opts) {
81381
81497
  );
81382
81498
  return null;
81383
81499
  }) : null;
81500
+ if (snapOpts.atCommit !== (readGitRefState(join34(opts.workspaceRoot, ".git")).sha ?? void 0)) {
81501
+ contextDirty = true;
81502
+ return;
81503
+ }
81384
81504
  const doneRender = markPass(`${prebuilt ? "context-render" : "context-render-inline"}:${profile.name}`);
81385
81505
  const pendingUpdate = opts.pendingUpdate?.() ?? null;
81386
81506
  const daemonDistress = opts.daemonDistress?.() ?? null;
@@ -82640,7 +82760,7 @@ function createWorkspaceEngine(opts) {
82640
82760
  readLosses.sliceGuardHits = 0;
82641
82761
  }
82642
82762
  };
82643
- const harvestTurns = async (sessionId, transcriptPath) => {
82763
+ const harvestTurnsUnqueued = async (sessionId, transcriptPath) => {
82644
82764
  const known = turnOffset.get(sessionId);
82645
82765
  let turns;
82646
82766
  let nextOffset;
@@ -82648,7 +82768,7 @@ function createWorkspaceEngine(opts) {
82648
82768
  if (known === void 0) {
82649
82769
  const first = readAssistantTurnsWithLosses(transcriptPath);
82650
82770
  turns = first.turns;
82651
- nextOffset = transcriptSize(transcriptPath);
82771
+ nextOffset = first.nextOffset;
82652
82772
  readLosses.bytesUnreachable += first.losses.bytesUnreachable;
82653
82773
  readLosses.parseFailures += first.losses.parseFailures;
82654
82774
  if (first.losses.ioFailed) readLosses.ioFailures++;
@@ -82708,6 +82828,37 @@ function createWorkspaceEngine(opts) {
82708
82828
  }
82709
82829
  setImmediate(() => void harvestTexts(e.sessionId, [{ text: e.text }]));
82710
82830
  };
82831
+ const harvestInFlight = /* @__PURE__ */ new Map();
82832
+ const repairedSessions = /* @__PURE__ */ new Set();
82833
+ const harvestTurns = async (inputSessionId, transcriptPath) => {
82834
+ const identity = basename7(transcriptPath).startsWith("rollout-") ? readCodexRolloutIdentity(transcriptPath) : null;
82835
+ const sessionId = identity?.sessionId ?? inputSessionId;
82836
+ const work = (harvestInFlight.get(sessionId) ?? Promise.resolve()).catch(() => {
82837
+ }).then(async () => {
82838
+ if (identity && !repairedSessions.has(sessionId)) {
82839
+ store.repairSessionAliases(sessionId, identity.aliases);
82840
+ opts.sharedStore?.repairSessionAliases(sessionId, identity.aliases);
82841
+ repairedSessions.add(sessionId);
82842
+ }
82843
+ for (const alias of identity?.aliases ?? []) {
82844
+ const offset = turnOffset.get(alias);
82845
+ if (offset !== void 0 && offset > (turnOffset.get(sessionId) ?? -1)) {
82846
+ turnOffset.set(sessionId, offset);
82847
+ const uuid3 = lastTurnUuid.get(alias);
82848
+ if (uuid3) lastTurnUuid.set(sessionId, uuid3);
82849
+ }
82850
+ turnOffset.delete(alias);
82851
+ lastTurnUuid.delete(alias);
82852
+ }
82853
+ await harvestTurnsUnqueued(sessionId, transcriptPath);
82854
+ });
82855
+ harvestInFlight.set(sessionId, work);
82856
+ try {
82857
+ await work;
82858
+ } finally {
82859
+ if (harvestInFlight.get(sessionId) === work) harvestInFlight.delete(sessionId);
82860
+ }
82861
+ };
82711
82862
  const harvestSession = async (sessionId, transcriptPath) => {
82712
82863
  await harvestTurns(sessionId, transcriptPath);
82713
82864
  for (const sub of subagentTranscripts(transcriptPath, sessionId)) {
@@ -85384,12 +85535,12 @@ var GENERATED_PATH_RES = [
85384
85535
  ];
85385
85536
  var RELEASE_SUBJECT_RE = /^(release|bump|chore\(release\)|v?\d+\.\d+\.\d+)/i;
85386
85537
  var RELEASE_TAG_RE = /^v?\d+\.\d+/;
85387
- function basename7(path2) {
85538
+ function basename8(path2) {
85388
85539
  const i2 = path2.lastIndexOf("/");
85389
85540
  return i2 < 0 ? path2 : path2.slice(i2 + 1);
85390
85541
  }
85391
85542
  function isLockfilePath(path2) {
85392
- const name2 = basename7(path2);
85543
+ const name2 = basename8(path2);
85393
85544
  return LOCKFILE_NAMES.has(name2) || LOCKFILE_SUFFIX_RE.test(name2);
85394
85545
  }
85395
85546
  function isGeneratedPath(path2) {
@@ -89408,7 +89559,7 @@ function buildCodexHooksToml(port, options) {
89408
89559
  const quote = (value) => {
89409
89560
  if (platform === "win32") {
89410
89561
  if (/["\r\n]/.test(value)) throw new Error("Unsupported character in Codex hook path");
89411
- return "'" + value.replace(/'/g, "''") + "'";
89562
+ return "'" + value.replace(/['\u2018-\u201B]/g, (quote2) => quote2 + quote2) + "'";
89412
89563
  }
89413
89564
  return "'" + value.replace(/'/g, "'\\''") + "'";
89414
89565
  };
@@ -89426,6 +89577,9 @@ import { readFileSync } from 'node:fs';
89426
89577
 
89427
89578
  try {
89428
89579
  const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
89580
+ if (typeof payload.session_id === 'string' && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(payload.session_id)) {
89581
+ payload.session_id = 'codex:' + payload.session_id.toLowerCase();
89582
+ }
89429
89583
  const port = Number(process.argv[2]);
89430
89584
  const event = payload.hook_event_name;
89431
89585
  if (!Number.isInteger(port) || port < 1 || port > 65535) process.exit(0);
@@ -92739,7 +92893,9 @@ async function cmdDash(args2) {
92739
92893
  for (const r of handle2.records) {
92740
92894
  await yieldToLoop2();
92741
92895
  try {
92742
- const items = selectDurableMemory(handle2.sharedStore, r.engine.profile);
92896
+ const items = selectDurableMemory(handle2.sharedStore, r.engine.profile, {
92897
+ atCommit: readGitRefState(join43(r.root, ".git")).sha ?? void 0
92898
+ });
92743
92899
  const res = bleedRules(join43(r.root, ".claude", "rules"), items);
92744
92900
  if (res.written || res.pruned) {
92745
92901
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.14-dev.3081",
3
+ "version": "2.0.14-dev.3125",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -24605,6 +24605,71 @@ function installHistoryCacheFloor(db) {
24605
24605
  });
24606
24606
  }
24607
24607
 
24608
+ // ../../packages/local-graph/src/session-evidence.ts
24609
+ function repairSessionEvidence(input, aliases = /* @__PURE__ */ new Map()) {
24610
+ const mapping = new Map(aliases);
24611
+ const uuid3 = "[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}";
24612
+ const codex = new RegExp(`^codex:(?:rollout-.*?)?(${uuid3})$`, "i");
24613
+ const arrays = /* @__PURE__ */ new Set(["sources", "recurrenceSources", "contributors", "inferredContributors", "citeSessions"]);
24614
+ const scalars = /* @__PURE__ */ new Set(["discriminatorSource", "originSession", "sessionId", "sourceSession"]);
24615
+ const discover = (value, key = "") => {
24616
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
24617
+ const match = value.match(codex);
24618
+ if (match) {
24619
+ const canonical = `codex:${match[1].toLowerCase()}`;
24620
+ mapping.set(value, canonical);
24621
+ mapping.set(match[1].toLowerCase(), canonical);
24622
+ }
24623
+ } else if (Array.isArray(value)) value.forEach((v) => discover(v, key));
24624
+ else if (value && typeof value === "object") {
24625
+ for (const [k, v] of Object.entries(value)) discover(v, key === "citeAnchorHints" ? key : k);
24626
+ }
24627
+ };
24628
+ discover(input);
24629
+ const rewrite = (value, key = "") => {
24630
+ if (typeof value === "string" && (arrays.has(key) || scalars.has(key) || key === "citeAnchorHints")) {
24631
+ return mapping.get(value) ?? mapping.get(value.toLowerCase()) ?? value;
24632
+ }
24633
+ if (Array.isArray(value)) {
24634
+ const values = value.map((v) => rewrite(v, key));
24635
+ return arrays.has(key) || key === "citeAnchorHints" ? [...new Set(values)] : values;
24636
+ }
24637
+ if (!value || typeof value !== "object") return value;
24638
+ const before = value;
24639
+ const after = Object.fromEntries(Object.entries(before).map(([k, v]) => [k, rewrite(v, key === "citeAnchorHints" ? key : k)]));
24640
+ for (const field of ["sources", "contributors"]) {
24641
+ const old = before[field], next = after[field];
24642
+ if (!Array.isArray(old) || !Array.isArray(next) || old.length <= next.length) continue;
24643
+ after["sessionAliasRepair"] = { originalCount: old.length, distinctCount: next.length };
24644
+ if (field === "sources" && typeof after["corroborations"] === "number") {
24645
+ after["corroborations"] = Math.min(after["corroborations"], Math.max(0, next.length - 1));
24646
+ if (after["corroborations"] === 0) after["provisional"] = true;
24647
+ }
24648
+ if (field === "contributors" && typeof after["confirmed"] === "number") {
24649
+ after["preAliasRepairConfirmed"] = after["confirmed"];
24650
+ after["confirmed"] = Math.min(after["confirmed"], next.length);
24651
+ }
24652
+ }
24653
+ return after;
24654
+ };
24655
+ const repaired = rewrite(input);
24656
+ const source = repaired["discriminatorSource"];
24657
+ if (typeof source === "string") {
24658
+ const global = repaired["perContext"]?.["*"];
24659
+ const contributors = Array.isArray(global?.["contributors"]) ? global["contributors"] : [];
24660
+ const inferred = new Set(Array.isArray(global?.["inferredContributors"]) ? global["inferredContributors"] : []);
24661
+ if (contributors.includes(source) || contributors.filter((id) => !inferred.has(id)).length < 2) {
24662
+ repaired["withdrawnDiscriminator"] = repaired["discriminator"];
24663
+ repaired["discriminatorWithdrawalReason"] = "insufficient-independent-witnesses";
24664
+ repaired["provisional"] = true;
24665
+ delete repaired["discriminator"];
24666
+ delete repaired["discriminatorSource"];
24667
+ delete repaired["discriminatorAsked"];
24668
+ }
24669
+ }
24670
+ return repaired;
24671
+ }
24672
+
24608
24673
  // ../../packages/local-graph/src/store.ts
24609
24674
  var LOCAL_RULE_OVERRIDES = {
24610
24675
  ANCHORED_AT: {
@@ -24849,6 +24914,12 @@ var SqliteGraphStore = class {
24849
24914
  }
24850
24915
  this.db.exec(TEMPORAL_INDEX_SQL);
24851
24916
  installHistoryCacheFloor(this.db);
24917
+ if (!this.db.prepare("SELECT value FROM store_meta WHERE key = 'session-evidence-v1'").get()) {
24918
+ this.db.transaction(() => {
24919
+ this.repairSessionAliases();
24920
+ this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES ('session-evidence-v1', 'done')").run();
24921
+ });
24922
+ }
24852
24923
  this.stmts = {
24853
24924
  insertNode: this.db.prepare(`
24854
24925
  INSERT INTO nodes (
@@ -25097,6 +25168,23 @@ var SqliteGraphStore = class {
25097
25168
  this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
25098
25169
  });
25099
25170
  }
25171
+ repairSessionAliases(canonical, aliases = []) {
25172
+ const mapping = new Map(aliases.map((alias) => [alias, canonical]));
25173
+ let changed = 0;
25174
+ for (const table of ["nodes", "edges"]) {
25175
+ const predicates = canonical ? aliases.map(() => "instr(attrs_json, ?) > 0") : ["instr(attrs_json, 'codex:') > 0", "instr(attrs_json, 'discriminatorSource') > 0"];
25176
+ if (!predicates.length) continue;
25177
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM ${table} WHERE ${predicates.join(" OR ")}`).all(...canonical ? aliases : []);
25178
+ const update = this.db.prepare(`UPDATE ${table} SET attrs_json = ?${table === "edges" ? ", type = CASE WHEN type = 'CONFIRMS' AND json_extract(?, '$.provisional') = 1 THEN 'INDICATES' ELSE type END" : ""} WHERE id = ?`);
25179
+ for (const row of rows) {
25180
+ const attrs = JSON.stringify(repairSessionEvidence(JSON.parse(row.attrs_json), mapping));
25181
+ if (attrs === row.attrs_json) continue;
25182
+ update.run(...table === "edges" ? [attrs, attrs, row.id] : [attrs, row.id]);
25183
+ changed++;
25184
+ }
25185
+ }
25186
+ return changed;
25187
+ }
25100
25188
  deleteNode(id) {
25101
25189
  this.stmts.deleteNode.run(id);
25102
25190
  }
@@ -27543,7 +27631,7 @@ var AGENTS_POINTER_BODY = [
27543
27631
  init_src2();
27544
27632
  init_src();
27545
27633
  function buildSnapshot(opts) {
27546
- const problems = opts.store.findNodesByLabel("Problem").filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive" }).allow);
27634
+ const problems = opts.store.findNodesByLabel("Problem").filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive", atCommit: opts.atCommit }).allow);
27547
27635
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
27548
27636
  const allProblems = stillOpen.filter((p) => !isConstraintProblem(p));
27549
27637
  allProblems.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
@@ -27573,11 +27661,11 @@ function buildSnapshot(opts) {
27573
27661
  ...opts.store.findNodesByLabel("Pattern"),
27574
27662
  ...opts.store.findNodesByLabel("Technique"),
27575
27663
  ...opts.store.findNodesByLabel("AntiPattern")
27576
- ].filter((n) => historyReadPolicy(n, { mode: "passive" }).allow).sort((a, b) => b.pageRank - a.pageRank).slice(0, 10);
27577
- const workingFile = opts.workingFile ? sliceForFile(opts.store, opts.workingFile) : void 0;
27664
+ ].filter((n) => historyReadPolicy(n, { mode: "passive", atCommit: opts.atCommit }).allow).sort((a, b) => b.pageRank - a.pageRank).slice(0, 10);
27665
+ const workingFile = opts.workingFile ? sliceForFile(opts.store, opts.workingFile, opts.atCommit) : void 0;
27578
27666
  const byEpisode = /* @__PURE__ */ new Map();
27579
27667
  for (const sol of opts.store.findNodesByLabel("Solution")) {
27580
- if (!historyReadPolicy(sol, { mode: "passive" }).allow) continue;
27668
+ if (!historyReadPolicy(sol, { mode: "passive", atCommit: opts.atCommit }).allow) continue;
27581
27669
  if (sol.attrs["enrichmentPending"] !== true) continue;
27582
27670
  const ep = sol.attrs["episodeId"] ?? sol.id;
27583
27671
  const list = byEpisode.get(ep) ?? [];
@@ -27649,7 +27737,7 @@ function buildSnapshot(opts) {
27649
27737
  generatedAt: (opts.now ?? /* @__PURE__ */ new Date()).toISOString()
27650
27738
  };
27651
27739
  }
27652
- function sliceForFile(store2, relPath) {
27740
+ function sliceForFile(store2, relPath, atCommit) {
27653
27741
  const candidates = store2.findNodesByLabel("File");
27654
27742
  const file2 = candidates.find((n) => n.description === relPath);
27655
27743
  if (!file2) return void 0;
@@ -27688,7 +27776,7 @@ function sliceForFile(store2, relPath) {
27688
27776
  }
27689
27777
  const fp = priorsForFile(store2, relPath);
27690
27778
  if (fp) {
27691
- const allowed = (n) => historyReadPolicy(n, { mode: "passive" }).allow;
27779
+ const allowed = (n) => historyReadPolicy(n, { mode: "passive", atCommit }).allow;
27692
27780
  fp.openProblems = fp.openProblems.filter(allowed);
27693
27781
  fp.constraints = fp.constraints.filter(allowed);
27694
27782
  fp.resolvedProblems = fp.resolvedProblems.filter(allowed);
@@ -27913,6 +28001,7 @@ function runPass(msg) {
27913
28001
  const p = msg.payload;
27914
28002
  return buildSnapshot({
27915
28003
  store,
28004
+ atCommit: p.atCommit,
27916
28005
  profile: p.profile,
27917
28006
  reviewCount: p.reviewCount,
27918
28007
  reviewUiUrl: p.reviewUiUrl,