@inerrata-corporation/errata 2.0.6-dev.2657 → 2.0.6-dev.2681

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.
Files changed (2) hide show
  1. package/errata.mjs +304 -66
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -71184,7 +71184,7 @@ var init_report_render = __esm({
71184
71184
  init_src6();
71185
71185
  import { closeSync as closeSync2, existsSync as existsSync34, openSync as openSync2, readFileSync as readFileSync34, renameSync as renameSync4, statSync as statSync8 } from "node:fs";
71186
71186
  import { join as join39, resolve as pathResolve2 } from "node:path";
71187
- import { spawn as spawn3 } from "node:child_process";
71187
+ import { spawn as spawn4 } from "node:child_process";
71188
71188
 
71189
71189
  // src/daemon.ts
71190
71190
  import { existsSync as existsSync26, writeFileSync as writeFileSync22 } from "node:fs";
@@ -78536,7 +78536,7 @@ function createWatchBreaker(deps) {
78536
78536
  }
78537
78537
 
78538
78538
  // src/engine.ts
78539
- var DAEMON_VERSION = true ? "2.0.6-dev.2657" : "2.0.0-alpha.0";
78539
+ var DAEMON_VERSION = true ? "2.0.6-dev.2681" : "2.0.0-alpha.0";
78540
78540
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
78541
78541
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
78542
78542
  var GIT_OP_MUTE_MS = 4e3;
@@ -82710,6 +82710,17 @@ function isGeneratedPath(path2) {
82710
82710
  function isReleaseSubject(subject) {
82711
82711
  return RELEASE_SUBJECT_RE.test(subject.trim());
82712
82712
  }
82713
+ var FORGE_MARK_RES = [/^See merge request \S+!\d+$/i, /^Merge pull request #\d+ from \S+$/i];
82714
+ var TRAILER_RES = [
82715
+ /^(Closes|Fixes|Resolves|Related to|Part of)\b.*$/i,
82716
+ /^(Co-Authored-By|Signed-off-by|Reviewed-by|Approved-by):/i
82717
+ ];
82718
+ function isForgeTrailerBody(body2) {
82719
+ const lines = body2.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
82720
+ if (!lines.some((l) => FORGE_MARK_RES.some((re) => re.test(l)))) return false;
82721
+ const own = lines.filter((l) => !FORGE_MARK_RES.some((re) => re.test(l)) && !TRAILER_RES.some((re) => re.test(l)));
82722
+ return own.length <= 1;
82723
+ }
82713
82724
  function classifySkip(subject, files, tags) {
82714
82725
  if (files.length > 0 && files.every((f) => f.binary || isLockfilePath(f.path) || isGeneratedPath(f.path))) {
82715
82726
  return "lockfile-only";
@@ -82718,6 +82729,7 @@ function classifySkip(subject, files, tags) {
82718
82729
  return void 0;
82719
82730
  }
82720
82731
  var GIT_OPTS = ["-c", "core.quotepath=false", "-c", "color.ui=never", "--no-pager"];
82732
+ var MERGE_SIDE_CONCURRENCY = 6;
82721
82733
  var META_FMT = ["%H", "%P", "%aI", "%s", "%b"].join("%x00") + "%x00";
82722
82734
  function parseMeta(head2) {
82723
82735
  const [sha2, parents, authorDate, subject, body2] = head2.split("\0");
@@ -82756,6 +82768,38 @@ async function readCommit(root, sha2) {
82756
82768
  const rest2 = parts2.slice(5).join("\0");
82757
82769
  return { ...parseMeta(head2), files: parseNumstat(rest2) };
82758
82770
  }
82771
+ async function readCommitsBulk(root, branch, since) {
82772
+ const out2 = await git(root, [
82773
+ ...GIT_OPTS,
82774
+ "log",
82775
+ "--numstat",
82776
+ "-M",
82777
+ "--diff-merges=first-parent",
82778
+ `--format=%x1e${META_FMT}`,
82779
+ ...since ? [`^${since}`] : [],
82780
+ branch
82781
+ ]);
82782
+ const map2 = /* @__PURE__ */ new Map();
82783
+ for (const rec of out2.split("")) {
82784
+ if (!rec) continue;
82785
+ const parts2 = rec.split("\0");
82786
+ const meta3 = parseMeta(parts2.slice(0, 5).join("\0"));
82787
+ if (meta3.sha) map2.set(meta3.sha, { ...meta3, files: parseNumstat(parts2.slice(5).join("\0")) });
82788
+ }
82789
+ return map2;
82790
+ }
82791
+ async function mapBounded(items, limit, fn) {
82792
+ const out2 = new Array(items.length);
82793
+ let next = 0;
82794
+ const worker = async () => {
82795
+ while (next < items.length) {
82796
+ const i2 = next++;
82797
+ out2[i2] = await fn(items[i2]);
82798
+ }
82799
+ };
82800
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
82801
+ return out2;
82802
+ }
82759
82803
  async function firstParentLine(root, branch) {
82760
82804
  const out2 = await git(root, [...GIT_OPTS, "rev-list", "--first-parent", "--reverse", "--parents", branch]);
82761
82805
  return out2.split(/\r?\n/).filter((l) => l.trim().length > 0).map((l) => {
@@ -82795,11 +82839,18 @@ async function enumerateUnits(root, opts = {}) {
82795
82839
  }
82796
82840
  const tags = await tagsByCommit(root);
82797
82841
  const tagsOf = (sha2) => tags.get(sha2) ?? [];
82842
+ if (start2 >= line.length) return [];
82843
+ const commits = await readCommitsBulk(root, branch, start2 > 0 ? line[start2 - 1].sha : null);
82844
+ const commit = async (sha2) => commits.get(sha2) ?? await readCommit(root, sha2);
82845
+ const merges = line.slice(start2).filter((c) => c.parents.length >= 2);
82846
+ const sides = /* @__PURE__ */ new Map();
82847
+ const sideLists = await mapBounded(merges, MERGE_SIDE_CONCURRENCY, (m) => mergeSide(root, m.parents[0], m.parents[1]));
82848
+ merges.forEach((m, i2) => sides.set(m.sha, sideLists[i2]));
82798
82849
  const units = [];
82799
82850
  for (let index = start2; index < line.length; index++) {
82800
82851
  const { sha: sha2, parents } = line[index];
82801
82852
  if (parents.length < 2) {
82802
- const c = await readCommit(root, sha2);
82853
+ const c = await commit(sha2);
82803
82854
  const skip = classifySkip(c.subject, c.files, tagsOf(sha2));
82804
82855
  units.push({
82805
82856
  index,
@@ -82815,11 +82866,11 @@ async function enumerateUnits(root, opts = {}) {
82815
82866
  });
82816
82867
  continue;
82817
82868
  }
82818
- const side = await mergeSide(root, parents[0], parents[1]);
82869
+ const side = sides.get(sha2) ?? await mergeSide(root, parents[0], parents[1]);
82819
82870
  const group = [];
82820
82871
  for (const s of side) {
82821
82872
  if (s.parents.length >= 2) continue;
82822
- const c = await readCommit(root, s.sha);
82873
+ const c = await commit(s.sha);
82823
82874
  const skip = classifySkip(c.subject, c.files, tagsOf(s.sha));
82824
82875
  group.push({
82825
82876
  index,
@@ -82835,8 +82886,9 @@ async function enumerateUnits(root, opts = {}) {
82835
82886
  groupSha: sha2
82836
82887
  });
82837
82888
  }
82838
- const merge2 = await readCommit(root, sha2);
82839
- if (merge2.body.length > 0) {
82889
+ const merge2 = await commit(sha2);
82890
+ const hasMembers = group.some((g) => !g.skip);
82891
+ if (merge2.body.length > 0 && !(hasMembers && isForgeTrailerBody(merge2.body))) {
82840
82892
  const skip = classifySkip(merge2.subject, merge2.files, tagsOf(sha2));
82841
82893
  group.push({
82842
82894
  index,
@@ -84068,13 +84120,115 @@ async function buildUnitBundle(root, unit, opts = {}) {
84068
84120
  };
84069
84121
  }
84070
84122
 
84123
+ // src/git-batch.ts
84124
+ import { spawn as spawn3 } from "node:child_process";
84125
+ var GitBlobReader = class {
84126
+ constructor(root, maxCached = 4096) {
84127
+ this.root = root;
84128
+ this.maxCached = maxCached;
84129
+ }
84130
+ proc = null;
84131
+ buf = Buffer.alloc(0);
84132
+ queue = [];
84133
+ cache = /* @__PURE__ */ new Map();
84134
+ failed = null;
84135
+ start() {
84136
+ if (this.proc) return this.proc;
84137
+ const proc = spawn3("git", ["-C", this.root, "cat-file", "--batch"], { windowsHide: true });
84138
+ proc.stdout.on("data", (chunk) => {
84139
+ this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);
84140
+ this.drain();
84141
+ });
84142
+ const fail = (err2) => {
84143
+ this.failed = err2;
84144
+ this.proc = null;
84145
+ for (const p of this.queue.splice(0)) p.reject(err2);
84146
+ };
84147
+ proc.on("error", fail);
84148
+ proc.stdin.on("error", fail);
84149
+ proc.on("exit", (code) => {
84150
+ if (this.queue.length > 0) fail(new Error(`git cat-file --batch exited (${code}) with ${this.queue.length} reads pending`));
84151
+ this.proc = null;
84152
+ });
84153
+ this.proc = proc;
84154
+ return proc;
84155
+ }
84156
+ drain() {
84157
+ for (; ; ) {
84158
+ const nl = this.buf.indexOf(10);
84159
+ if (nl < 0 || this.queue.length === 0) return;
84160
+ const header = this.buf.subarray(0, nl).toString("utf8");
84161
+ if (header.endsWith(" missing") || header.endsWith(" ambiguous")) {
84162
+ this.buf = this.buf.subarray(nl + 1);
84163
+ this.queue.shift().resolve(null);
84164
+ continue;
84165
+ }
84166
+ const size = Number(header.slice(header.lastIndexOf(" ") + 1));
84167
+ if (!Number.isFinite(size)) {
84168
+ const err2 = new Error(`git cat-file --batch: unexpected header ${JSON.stringify(header.slice(0, 120))}`);
84169
+ this.queue.shift().reject(err2);
84170
+ this.buf = this.buf.subarray(nl + 1);
84171
+ continue;
84172
+ }
84173
+ if (this.buf.length < nl + 1 + size + 1) return;
84174
+ const body2 = this.buf.subarray(nl + 1, nl + 1 + size).toString("utf8");
84175
+ this.buf = this.buf.subarray(nl + 1 + size + 1);
84176
+ const oid = header.slice(0, header.indexOf(" "));
84177
+ this.remember(oid, body2);
84178
+ this.queue.shift().resolve(body2);
84179
+ }
84180
+ }
84181
+ remember(oid, text) {
84182
+ if (this.cache.size >= this.maxCached) {
84183
+ const first = this.cache.keys().next().value;
84184
+ if (first !== void 0) this.cache.delete(first);
84185
+ }
84186
+ this.cache.set(oid, text);
84187
+ }
84188
+ /** Read one object by blob id (served from cache when seen) or `<rev>:<path>`. */
84189
+ read(spec) {
84190
+ const hit = this.cache.get(spec);
84191
+ if (hit !== void 0) return Promise.resolve(hit);
84192
+ if (this.failed) return Promise.reject(this.failed);
84193
+ if (/[\r\n]/.test(spec)) return Promise.resolve(null);
84194
+ const proc = this.start();
84195
+ return new Promise((resolve7, reject) => {
84196
+ this.queue.push({ resolve: resolve7, reject });
84197
+ proc.stdin.write(`${spec}
84198
+ `);
84199
+ });
84200
+ }
84201
+ /**
84202
+ * End the process and wait for it to exit: on Windows a live cat-file keeps
84203
+ * the repo's pack files open, so a caller that removes or moves the checkout
84204
+ * right after the walk (EPERM) must not race it. Bounded, never throws.
84205
+ */
84206
+ close(timeoutMs = 5e3) {
84207
+ const proc = this.proc;
84208
+ this.proc = null;
84209
+ if (!proc || proc.exitCode !== null) return Promise.resolve();
84210
+ return new Promise((resolve7) => {
84211
+ const timer = setTimeout(() => {
84212
+ proc.kill();
84213
+ resolve7();
84214
+ }, timeoutMs);
84215
+ proc.once("exit", () => {
84216
+ clearTimeout(timer);
84217
+ resolve7();
84218
+ });
84219
+ proc.stdin.end();
84220
+ });
84221
+ }
84222
+ };
84223
+
84071
84224
  // src/project-history.ts
84072
84225
  init_profile2();
84073
84226
  var execFileAsync = promisify(execFile3);
84074
- var HISTORY_EXTRACTOR_VERSION = "history-1";
84227
+ var HISTORY_EXTRACTOR_VERSION = "history-2";
84075
84228
  var LEASE_POLL_MS = 2e4;
84076
84229
  var RECOVERY_DEADLINE_MS = 15 * 6e4;
84077
84230
  var MAX_ANCHORS_PER_TOKEN = 20;
84231
+ var DEFAULT_LOOKAHEAD = 2;
84078
84232
  async function defaultGit(root, args2) {
84079
84233
  const { stdout } = await execFileAsync("git", ["-C", root, ...args2], { maxBuffer: 64 * 1024 * 1024, windowsHide: true });
84080
84234
  return stdout;
@@ -84087,27 +84241,38 @@ function readIgnoreLines(root, profile) {
84087
84241
  }
84088
84242
  return [...lines, ...profile.ignoreExtra ?? []].map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
84089
84243
  }
84090
- async function spineForUnit(git3, root, unit) {
84091
- const listing = await git3(root, ["ls-tree", "-r", "--name-only", unit.sha]);
84092
- const manifestPaths2 = listing.split("\n").map((l) => l.trim()).filter((p) => p && isManifestPath(p));
84093
- if (manifestPaths2.length === 0) return void 0;
84094
- const texts = /* @__PURE__ */ new Map();
84095
- for (const p of manifestPaths2) {
84244
+ function readBlobViaGit(git3, root) {
84245
+ return async (spec) => {
84096
84246
  try {
84097
- texts.set(p, await git3(root, ["show", `${unit.sha}:${p}`]));
84247
+ return await git3(root, ["show", spec]);
84098
84248
  } catch {
84249
+ return null;
84099
84250
  }
84251
+ };
84252
+ }
84253
+ function manifestBlobs(listing) {
84254
+ const out2 = /* @__PURE__ */ new Map();
84255
+ for (const line of listing.split("\n")) {
84256
+ const tab = line.indexOf(" ");
84257
+ if (tab < 0) continue;
84258
+ const path2 = line.slice(tab + 1).trim();
84259
+ const [, type, oid] = line.slice(0, tab).split(" ");
84260
+ if (type === "blob" && oid && path2 && isManifestPath(path2)) out2.set(path2, oid);
84100
84261
  }
84262
+ return out2;
84263
+ }
84264
+ async function spineForUnit(git3, root, unit, readBlob) {
84265
+ const blobs = manifestBlobs(await git3(root, ["ls-tree", "-r", unit.sha]));
84266
+ if (blobs.size === 0) return void 0;
84267
+ const texts = /* @__PURE__ */ new Map();
84268
+ const read2 = await Promise.all([...blobs].map(async ([p, oid]) => [p, await readBlob(oid).catch(() => null)]));
84269
+ for (const [p, text] of read2) if (text !== null) texts.set(p, text);
84101
84270
  const spine = buildSpine(unit.sha, [...texts.keys()], (p) => texts.get(p) ?? "");
84102
84271
  const parentTexts = /* @__PURE__ */ new Map();
84103
84272
  const readParent = (p) => parentTexts.get(p) ?? null;
84104
84273
  for (const f of unit.files) {
84105
84274
  if (!isManifestPath(f.path) || parentTexts.has(f.path)) continue;
84106
- try {
84107
- parentTexts.set(f.path, await git3(root, ["show", `${unit.sha}^:${f.path}`]));
84108
- } catch {
84109
- parentTexts.set(f.path, null);
84110
- }
84275
+ parentTexts.set(f.path, await readBlob(`${unit.sha}^:${f.path}`).catch(() => null));
84111
84276
  }
84112
84277
  const rings = depsForUnit(spine, unit.files.map((f) => f.path), readParent);
84113
84278
  const wire = (d) => ({ ecosystem: d.ecosystem, name: d.name, version: d.version, kind: d.kind, manifest: d.manifest });
@@ -84150,16 +84315,37 @@ function stage(profile, patch, now) {
84150
84315
  headSha: patch.headSha !== void 0 ? patch.headSha : prev?.headSha ?? null,
84151
84316
  startedAt: prev?.startedAt ?? at,
84152
84317
  updatedAt: at,
84153
- ...patch.lastError !== void 0 ? { lastError: patch.lastError } : prev?.lastError ? { lastError: prev.lastError } : {},
84318
+ // `lastError: undefined` in the patch CLEARS it (a resumed walk must not
84319
+ // keep showing the error it recovered from); an absent key keeps it.
84320
+ ..."lastError" in patch ? patch.lastError ? { lastError: patch.lastError } : {} : prev?.lastError ? { lastError: prev.lastError } : {},
84154
84321
  ...patch.counts ? { counts: patch.counts } : prev?.counts ? { counts: prev.counts } : {}
84155
84322
  }
84156
84323
  };
84157
84324
  }
84325
+ function unitTiming(postStarted, prepMs, receipt) {
84326
+ const s = (ms) => `${(ms / 1e3).toFixed(1)}s`;
84327
+ const parts2 = [`local ${s(prepMs)}`];
84328
+ const g = receipt.gate;
84329
+ if (g) parts2.push(g.pRoutine === null ? `jev ${g.error ?? "n/a"}` : `jev ${s(g.ms)} p=${g.pRoutine}${g.skipped ? " skipped" : ""}`);
84330
+ if (receipt.timing?.modelMs !== void 0) parts2.push(`model ${s(receipt.timing.modelMs)}`);
84331
+ if (receipt.timing?.orderWaitMs) parts2.push(`waited ${s(receipt.timing.orderWaitMs)} for the previous write`);
84332
+ if (receipt.timing?.ingestMs !== void 0) parts2.push(`ingest ${s(receipt.timing.ingestMs)}`);
84333
+ return ` in ${s(Date.now() - postStarted + prepMs)} (${parts2.join(", ")})`;
84334
+ }
84158
84335
  function addCounts(a, b) {
84159
84336
  const sum = (k) => (a[k] ?? 0) + (b[k] ?? 0);
84160
84337
  return { commitsWalked: sum("commitsWalked"), changesExtracted: sum("changesExtracted"), pairs: sum("pairs"), claims: sum("claims"), anchors: sum("anchors"), packages: sum("packages") };
84161
84338
  }
84162
84339
  async function runHistoryWalk(root, input, client, opts = {}) {
84340
+ const reader = opts.git ? null : new GitBlobReader(root);
84341
+ const readBlob = reader ? (spec) => reader.read(spec) : readBlobViaGit(opts.git ?? defaultGit, root);
84342
+ try {
84343
+ return await walk10(root, input, client, opts, readBlob);
84344
+ } finally {
84345
+ await reader?.close();
84346
+ }
84347
+ }
84348
+ async function walk10(root, input, client, opts, readBlob) {
84163
84349
  const log = opts.log ?? ((line) => console.warn(line));
84164
84350
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
84165
84351
  const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -84199,8 +84385,77 @@ async function runHistoryWalk(root, input, client, opts = {}) {
84199
84385
  let processed = 0;
84200
84386
  const byIndex = /* @__PURE__ */ new Map();
84201
84387
  for (const u of units) byIndex.set(u.index, [...byIndex.get(u.index) ?? [], u]);
84202
- for (const u of units) {
84203
- if (opts.maxUnits !== void 0 && processed >= opts.maxUnits) break;
84388
+ const lookahead = dryRun ? 0 : Math.max(0, opts.lookahead ?? DEFAULT_LOOKAHEAD);
84389
+ let doorOrders = false;
84390
+ const settle = (p) => p.then((res) => ({ res }), (err2) => ({ err: err2 }));
84391
+ const prepared = /* @__PURE__ */ new Map();
84392
+ const launched = /* @__PURE__ */ new Map();
84393
+ const cap = opts.maxUnits;
84394
+ const predecessor = (k) => {
84395
+ for (let j = k - 1; j >= 0; j--) if (!units[j].skip) return units[j];
84396
+ return void 0;
84397
+ };
84398
+ const prepare = (k) => {
84399
+ const hit = prepared.get(k);
84400
+ if (hit) return hit;
84401
+ const p = (async () => {
84402
+ const u = units[k];
84403
+ const prepStarted = Date.now();
84404
+ const group = byIndex.get(u.index) ?? [u];
84405
+ const bundle = await buildUnitBundle(root, u, {
84406
+ ignorePatterns: ignoreLines,
84407
+ narrative: narrative(u, u.isMerge ? group : void 0)
84408
+ });
84409
+ const prev = dryRun ? void 0 : predecessor(k);
84410
+ const body2 = {
84411
+ unit: {
84412
+ sha: u.sha,
84413
+ index: u.index,
84414
+ subIndex: u.subIndex,
84415
+ isMerge: u.isMerge,
84416
+ ...u.groupSha ? { groupSha: u.groupSha } : {},
84417
+ ...u.authorDate ? { authorDate: u.authorDate } : {}
84418
+ },
84419
+ narrative: bundle.narrative,
84420
+ fileList: bundle.fileList,
84421
+ diff: bundle.diff,
84422
+ pathTokens: Object.keys(bundle.pathTokens),
84423
+ extractorVersion: HISTORY_EXTRACTOR_VERSION,
84424
+ ...opts.frame ? { frame: opts.frame } : {},
84425
+ ...dryRun ? { dryRun: true } : {},
84426
+ ...prev ? { after: { sha: prev.sha, subIndex: prev.subIndex } } : {}
84427
+ };
84428
+ const spine = await spineForUnit(git3, root, u, readBlob);
84429
+ if (spine) body2.spine = spine;
84430
+ const anchors = anchorsFor(bundle.pathTokens, ignoreLines, projectId, opts.anchors);
84431
+ if (anchors) {
84432
+ body2.anchors = anchors;
84433
+ body2.observedAtCommit = headSha;
84434
+ }
84435
+ return { body: body2, prepMs: Date.now() - prepStarted };
84436
+ })();
84437
+ prepared.set(k, p);
84438
+ return p;
84439
+ };
84440
+ const launch = async (k) => {
84441
+ if (launched.has(k)) return;
84442
+ const { body: body2 } = await prepare(k);
84443
+ if (launched.has(k)) return;
84444
+ launched.set(k, { at: Date.now(), settled: settle(Promise.resolve().then(() => post(projectId, body2))) });
84445
+ };
84446
+ const launchAhead = async (k) => {
84447
+ if (!doorOrders || lookahead === 0) return;
84448
+ let ahead = 0;
84449
+ for (let j = k + 1; j < units.length && ahead < lookahead; j++) {
84450
+ if (cap !== void 0 && processed + (j - k) >= cap) break;
84451
+ if (units[j].skip) continue;
84452
+ ahead += 1;
84453
+ await launch(j);
84454
+ }
84455
+ };
84456
+ for (let k = 0; k < units.length; k++) {
84457
+ const u = units[k];
84458
+ if (cap !== void 0 && processed >= cap) break;
84204
84459
  const label = `${u.sha.slice(0, 12)}${u.subIndex ? `/${u.subIndex}` : ""}`;
84205
84460
  const doneSoFar = (profile.historyStage?.unitsDone ?? 0) + 1;
84206
84461
  const group = byIndex.get(u.index) ?? [u];
@@ -84214,34 +84469,7 @@ async function runHistoryWalk(root, input, client, opts = {}) {
84214
84469
  log(`[errata] history ${label} skipped (${u.skip})`);
84215
84470
  continue;
84216
84471
  }
84217
- const bundle = await buildUnitBundle(root, u, {
84218
- ignorePatterns: ignoreLines,
84219
- narrative: narrative(u, u.isMerge ? group : void 0)
84220
- });
84221
- const body2 = {
84222
- unit: {
84223
- sha: u.sha,
84224
- index: u.index,
84225
- subIndex: u.subIndex,
84226
- isMerge: u.isMerge,
84227
- ...u.groupSha ? { groupSha: u.groupSha } : {},
84228
- ...u.authorDate ? { authorDate: u.authorDate } : {}
84229
- },
84230
- narrative: bundle.narrative,
84231
- fileList: bundle.fileList,
84232
- diff: bundle.diff,
84233
- pathTokens: Object.keys(bundle.pathTokens),
84234
- extractorVersion: HISTORY_EXTRACTOR_VERSION,
84235
- ...opts.frame ? { frame: opts.frame } : {},
84236
- ...dryRun ? { dryRun: true } : {}
84237
- };
84238
- const spine = await spineForUnit(git3, root, u);
84239
- if (spine) body2.spine = spine;
84240
- const anchors = anchorsFor(bundle.pathTokens, ignoreLines, projectId, opts.anchors);
84241
- if (anchors) {
84242
- body2.anchors = anchors;
84243
- body2.observedAtCommit = headSha;
84244
- }
84472
+ const { body: body2, prepMs } = await prepare(k);
84245
84473
  profile = stage(profile, { state: dryRun ? "extracting" : "contributing", unitsDone: doneSoFar - 1, lastError: void 0 }, now);
84246
84474
  persist(profile);
84247
84475
  response = await reportHistoryStage(client, profile, log);
@@ -84251,15 +84479,21 @@ async function runHistoryWalk(root, input, client, opts = {}) {
84251
84479
  await reportHistoryStage(client, profile, log);
84252
84480
  return { profile, processed, state: "refused" };
84253
84481
  }
84254
- const started2 = Date.now();
84482
+ await launch(k);
84483
+ await launchAhead(k);
84484
+ const head2 = launched.get(k);
84485
+ launched.delete(k);
84486
+ prepared.delete(k);
84487
+ const started2 = head2.at;
84488
+ let first = head2.settled;
84255
84489
  let outcome = null;
84256
84490
  let attempt = 0;
84257
84491
  while (outcome === null) {
84258
84492
  attempt += 1;
84259
- let res;
84260
- try {
84261
- res = await post(projectId, body2);
84262
- } catch (err2) {
84493
+ const settled = await (first ?? settle(Promise.resolve().then(() => post(projectId, body2))));
84494
+ first = null;
84495
+ if ("err" in settled) {
84496
+ const err2 = settled.err;
84263
84497
  const status = err2 instanceof CloudError ? err2.status : 0;
84264
84498
  const elapsed = Math.round((Date.now() - started2) / 1e3);
84265
84499
  if (Date.now() - started2 > RECOVERY_DEADLINE_MS) {
@@ -84272,6 +84506,8 @@ async function runHistoryWalk(root, input, client, opts = {}) {
84272
84506
  await sleep(attempt === 1 ? 1e4 : Math.min(3e4 * attempt, 12e4));
84273
84507
  continue;
84274
84508
  }
84509
+ const res = settled.res;
84510
+ if (res.ordered) doorOrders = true;
84275
84511
  if (res.status === "in_progress") {
84276
84512
  const retryAt = new Date(Date.now() + res.retryAfterMs).toISOString();
84277
84513
  if (Date.now() - started2 > RECOVERY_DEADLINE_MS) {
@@ -84280,12 +84516,14 @@ async function runHistoryWalk(root, input, client, opts = {}) {
84280
84516
  await reportHistoryStage(client, profile, log);
84281
84517
  return { profile, processed, state: "paused" };
84282
84518
  }
84283
- log(`[errata] history ${label}: run held by the door (lease), polling in ${Math.min(LEASE_POLL_MS, res.retryAfterMs) / 1e3}s`);
84519
+ const why = res.waitingOn === "predecessor" ? `sent ahead, its predecessor ${res.predecessor === "failed" ? "failed" : "had not landed"} \u2014 re-POSTing in turn` : "run held by the door (lease)";
84520
+ log(`[errata] history ${label}: ${why}, polling in ${Math.min(LEASE_POLL_MS, res.retryAfterMs) / 1e3}s`);
84284
84521
  await sleep(Math.min(LEASE_POLL_MS, Math.max(1e3, res.retryAfterMs)));
84285
84522
  continue;
84286
84523
  }
84287
84524
  outcome = res;
84288
84525
  }
84526
+ await launchAhead(k);
84289
84527
  switch (outcome.status) {
84290
84528
  case "completed": {
84291
84529
  if (outcome.result.nodes.length !== outcome.counts.nodes || outcome.result.edges.length !== outcome.counts.edges) {
@@ -84296,7 +84534,7 @@ async function runHistoryWalk(root, input, client, opts = {}) {
84296
84534
  }
84297
84535
  const packages = new Set(outcome.result.nodes.filter((n) => n.decision !== "rejected" && n.canonicalId.startsWith("pkg:")).map((n) => n.canonicalId)).size;
84298
84536
  counts = addCounts(counts, { commitsWalked: 1, changesExtracted: 1, pairs: outcome.receipt.keptPairs, claims: outcome.receipt.claims, anchors: outcome.receipt.anchors, packages });
84299
- log(`[errata] history ${label}: ${outcome.result.nodes.length} nodes, ${outcome.result.edges.length} edges (${outcome.receipt.keptPairs} pairs, ${outcome.receipt.claims} claims)`);
84537
+ log(`[errata] history ${label}: ${outcome.result.nodes.length} nodes, ${outcome.result.edges.length} edges (${outcome.receipt.keptPairs} pairs, ${outcome.receipt.claims} claims)${unitTiming(started2, prepMs, outcome.receipt)}`);
84300
84538
  break;
84301
84539
  }
84302
84540
  case "replayed": {
@@ -84307,7 +84545,7 @@ async function runHistoryWalk(root, input, client, opts = {}) {
84307
84545
  case "empty":
84308
84546
  case "dry-run":
84309
84547
  counts = addCounts(counts, { commitsWalked: 1, changesExtracted: outcome.status === "dry-run" ? 1 : 0, pairs: outcome.receipt.keptPairs, claims: outcome.receipt.claims });
84310
- log(`[errata] history ${label}: ${outcome.status} (${outcome.receipt.keptPairs} pairs, dropped ${outcome.receipt.droppedByEvidence} by evidence, ${outcome.receipt.droppedByAnchor} by anchor)`);
84548
+ log(`[errata] history ${label}: ${outcome.status} (${outcome.receipt.keptPairs} pairs, dropped ${outcome.receipt.droppedByEvidence} by evidence, ${outcome.receipt.droppedByAnchor} by anchor)${unitTiming(started2, prepMs, outcome.receipt)}`);
84311
84549
  break;
84312
84550
  case "refused":
84313
84551
  profile = stage(profile, { state: "refused", lastError: `unit ${label} refused: ${outcome.reason}` }, now);
@@ -84389,12 +84627,12 @@ function applyStopRequest(profile, now = () => /* @__PURE__ */ new Date()) {
84389
84627
  }
84390
84628
  };
84391
84629
  }
84392
- async function startHistoryStage(root, input, client, walk10 = {}) {
84630
+ async function startHistoryStage(root, input, client, walk11 = {}) {
84393
84631
  let profile = input;
84394
84632
  if (!profile.projectId) return { started: false, reason: "no-project", profile };
84395
84633
  const response = await reportHistoryStage(client, profile);
84396
- if (response?.consents && walk10.applyConsents) {
84397
- profile = walk10.applyConsents(profile, response.consents);
84634
+ if (response?.consents && walk11.applyConsents) {
84635
+ profile = walk11.applyConsents(profile, response.consents);
84398
84636
  }
84399
84637
  const consents = historyConsents(profile);
84400
84638
  if (response?.stopRequested && profile.historyStage && profile.historyStage.state !== "refused") {
@@ -84403,7 +84641,7 @@ async function startHistoryStage(root, input, client, walk10 = {}) {
84403
84641
  return { started: false, reason: "stopped", profile: stopped };
84404
84642
  }
84405
84643
  if (!consents.mineHistory) return { started: false, reason: "consent-pending", profile };
84406
- const outcome = await runHistoryWalk(root, profile, client, walk10);
84644
+ const outcome = await runHistoryWalk(root, profile, client, walk11);
84407
84645
  return { started: true, profile: outcome.profile };
84408
84646
  }
84409
84647
 
@@ -87100,9 +87338,9 @@ function deriveAuthBase(verificationUri) {
87100
87338
  }
87101
87339
  function openInBrowser(url2) {
87102
87340
  try {
87103
- void import("node:child_process").then(({ spawn: spawn4 }) => {
87341
+ void import("node:child_process").then(({ spawn: spawn5 }) => {
87104
87342
  const [cmd2, args2] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : process.platform === "darwin" ? ["open", [url2]] : ["xdg-open", [url2]];
87105
- spawn4(cmd2, args2, { detached: true, stdio: "ignore" }).unref();
87343
+ spawn5(cmd2, args2, { detached: true, stdio: "ignore" }).unref();
87106
87344
  });
87107
87345
  } catch {
87108
87346
  }
@@ -89745,7 +89983,7 @@ function spawnDaemonDetached(options = {}) {
89745
89983
  out2 = openSync2(logPath, "a");
89746
89984
  } catch {
89747
89985
  }
89748
- spawn3(cmd2, args2, {
89986
+ spawn4(cmd2, args2, {
89749
89987
  detached: true,
89750
89988
  stdio: ["ignore", out2, out2],
89751
89989
  windowsHide: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.6-dev.2657",
3
+ "version": "2.0.6-dev.2681",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {