@seanyao/roll 4.719.2 → 4.719.3

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 (3) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/roll.mjs +366 -166
  3. package/package.json +1 -1
package/dist/roll.mjs CHANGED
@@ -7285,8 +7285,8 @@ function writeSyncBlock(original, block) {
7285
7285
  }
7286
7286
  }
7287
7287
  if (start === -1) {
7288
- const sep8 = text2.endsWith("\n\n") || text2 === "" ? "" : text2.endsWith("\n") ? "\n" : "\n\n";
7289
- return `${text2}${sep8}${block}
7288
+ const sep9 = text2.endsWith("\n\n") || text2 === "" ? "" : text2.endsWith("\n") ? "\n" : "\n\n";
7289
+ return `${text2}${sep9}${block}
7290
7290
  `;
7291
7291
  }
7292
7292
  let end = start + 1;
@@ -16195,7 +16195,7 @@ function parseFlowMap2(body) {
16195
16195
  }
16196
16196
  return out3;
16197
16197
  }
16198
- function splitTopLevel(s, sep8) {
16198
+ function splitTopLevel(s, sep9) {
16199
16199
  const parts = [];
16200
16200
  let depth = 0;
16201
16201
  let cur = "";
@@ -16204,7 +16204,7 @@ function splitTopLevel(s, sep8) {
16204
16204
  depth++;
16205
16205
  else if (ch === "}")
16206
16206
  depth = Math.max(0, depth - 1);
16207
- if (ch === sep8 && depth === 0) {
16207
+ if (ch === sep9 && depth === 0) {
16208
16208
  parts.push(cur);
16209
16209
  cur = "";
16210
16210
  } else {
@@ -31174,15 +31174,15 @@ var init_mcp = __esm({
31174
31174
  }
31175
31175
  }
31176
31176
  tryParseMessage() {
31177
- const sep8 = this.buffer.indexOf("\r\n\r\n");
31178
- if (sep8 < 0)
31177
+ const sep9 = this.buffer.indexOf("\r\n\r\n");
31178
+ if (sep9 < 0)
31179
31179
  return void 0;
31180
- const header = this.buffer.slice(0, sep8).toString("utf8");
31180
+ const header = this.buffer.slice(0, sep9).toString("utf8");
31181
31181
  const length = /^Content-Length:\s*(\d+)$/im.exec(header)?.[1];
31182
31182
  if (length === void 0)
31183
31183
  return void 0;
31184
31184
  const bodyLength = Number.parseInt(length, 10);
31185
- const start = sep8 + 4;
31185
+ const start = sep9 + 4;
31186
31186
  if (this.buffer.length < start + bodyLength)
31187
31187
  return void 0;
31188
31188
  const body = this.buffer.slice(start, start + bodyLength);
@@ -80311,11 +80311,11 @@ ${lanes.join("\n")}
80311
80311
  if (i < rootLength) {
80312
80312
  return void 0;
80313
80313
  }
80314
- const sep8 = directory.lastIndexOf(directorySeparator, i - 1);
80315
- if (sep8 === -1) {
80314
+ const sep9 = directory.lastIndexOf(directorySeparator, i - 1);
80315
+ if (sep9 === -1) {
80316
80316
  return void 0;
80317
80317
  }
80318
- return directory.substr(0, Math.max(sep8, rootLength));
80318
+ return directory.substr(0, Math.max(sep9, rootLength));
80319
80319
  }
80320
80320
  }
80321
80321
  }
@@ -244439,7 +244439,7 @@ __export(showcase_exports, {
244439
244439
  showcaseCommand: () => showcaseCommand
244440
244440
  });
244441
244441
  import { spawnSync as spawnSync17 } from "node:child_process";
244442
- import { cpSync as cpSync2, existsSync as existsSync140, mkdirSync as mkdirSync78, mkdtempSync as mkdtempSync8, readdirSync as readdirSync53, readFileSync as readFileSync141, rmSync as rmSync26, statSync as statSync40, writeFileSync as writeFileSync77 } from "node:fs";
244442
+ import { cpSync as cpSync2, existsSync as existsSync141, mkdirSync as mkdirSync78, mkdtempSync as mkdtempSync8, readdirSync as readdirSync54, readFileSync as readFileSync141, rmSync as rmSync27, statSync as statSync40, writeFileSync as writeFileSync77 } from "node:fs";
244443
244443
  import { tmpdir as tmpdir13 } from "node:os";
244444
244444
  import { dirname as dirname77, join as join164 } from "node:path";
244445
244445
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -244482,7 +244482,7 @@ function parseArgs3(args) {
244482
244482
  function packageRoot() {
244483
244483
  let dir = dirname77(fileURLToPath3(import.meta.url));
244484
244484
  for (let i = 0; i < 12; i++) {
244485
- if (existsSync140(join164(dir, "conventions")))
244485
+ if (existsSync141(join164(dir, "conventions")))
244486
244486
  return dir;
244487
244487
  const parent = dirname77(dir);
244488
244488
  if (parent === dir)
@@ -244493,7 +244493,7 @@ function packageRoot() {
244493
244493
  }
244494
244494
  function rollBin3() {
244495
244495
  const devPath = join164(packageRoot(), "packages", "cli", "bin", "roll.js");
244496
- if (existsSync140(devPath))
244496
+ if (existsSync141(devPath))
244497
244497
  return devPath;
244498
244498
  return "roll";
244499
244499
  }
@@ -244520,7 +244520,7 @@ function runRoll(sandbox, rollHome4, args, opts = {}) {
244520
244520
  }
244521
244521
  function fileNonEmpty3(p) {
244522
244522
  try {
244523
- return existsSync140(p) && statSync40(p).size > 0;
244523
+ return existsSync141(p) && statSync40(p).size > 0;
244524
244524
  } catch {
244525
244525
  return false;
244526
244526
  }
@@ -244532,7 +244532,7 @@ function makeSandbox(sourceProject) {
244532
244532
  mkdirSync78(sandbox, { recursive: true });
244533
244533
  mkdirSync78(rollHome4, { recursive: true });
244534
244534
  const srcRoll = join164(sourceProject, ".roll");
244535
- if (existsSync140(srcRoll)) {
244535
+ if (existsSync141(srcRoll)) {
244536
244536
  cpSync2(srcRoll, join164(sandbox, ".roll"), { recursive: true });
244537
244537
  } else {
244538
244538
  mkdirSync78(join164(sandbox, ".roll", "features"), { recursive: true });
@@ -244543,11 +244543,11 @@ function findCardSpec2(sandbox, card) {
244543
244543
  const featuresDir = join164(sandbox, ".roll", "features");
244544
244544
  const candidates = [];
244545
244545
  const walk2 = (dir, depth) => {
244546
- if (depth > 3 || !existsSync140(dir))
244546
+ if (depth > 3 || !existsSync141(dir))
244547
244547
  return;
244548
244548
  let entries = [];
244549
244549
  try {
244550
- entries = readdirSync53(dir);
244550
+ entries = readdirSync54(dir);
244551
244551
  } catch {
244552
244552
  return;
244553
244553
  }
@@ -244560,7 +244560,7 @@ function findCardSpec2(sandbox, card) {
244560
244560
  continue;
244561
244561
  }
244562
244562
  if (isDir4) {
244563
- if (name === card && existsSync140(join164(full, "spec.md")))
244563
+ if (name === card && existsSync141(join164(full, "spec.md")))
244564
244564
  candidates.push(join164(full, "spec.md"));
244565
244565
  walk2(full, depth + 1);
244566
244566
  }
@@ -244574,7 +244574,7 @@ function resetSandbox(sandbox, card) {
244574
244574
  let reset2 = false;
244575
244575
  let ok12 = false;
244576
244576
  const backlogPath = join164(sandbox, ".roll", "backlog.md");
244577
- if (existsSync140(backlogPath)) {
244577
+ if (existsSync141(backlogPath)) {
244578
244578
  const before = readFileSync141(backlogPath, "utf8");
244579
244579
  const cardRow = before.split("\n").find((line) => line.startsWith("|") && line.includes(card));
244580
244580
  const hadStatusToken = cardRow !== void 0 && statusMarkerRe(false).test(cardRow);
@@ -244603,8 +244603,8 @@ function resetSandbox(sandbox, card) {
244603
244603
  notes.push("backlog: .roll/backlog.md absent");
244604
244604
  }
244605
244605
  const pulseCmd = join164(sandbox, "packages", "cli", "src", "commands", "pulse.ts");
244606
- if (existsSync140(pulseCmd)) {
244607
- rmSync26(pulseCmd, { force: true });
244606
+ if (existsSync141(pulseCmd)) {
244607
+ rmSync27(pulseCmd, { force: true });
244608
244608
  notes.push("removed prior pulse.ts surface");
244609
244609
  }
244610
244610
  return { ok: ok12, reset: reset2, notes };
@@ -244636,7 +244636,7 @@ async function captureScreenshots(sandbox, card) {
244636
244636
  const web = await infra.captureScreenshot({
244637
244637
  kind: "web",
244638
244638
  out: webOut,
244639
- url: existsSync140(nowPage) ? `file://${nowPage}#now` : "about:blank"
244639
+ url: existsSync141(nowPage) ? `file://${nowPage}#now` : "about:blank"
244640
244640
  });
244641
244641
  shots.push({
244642
244642
  surface: "web",
@@ -244648,7 +244648,7 @@ async function captureScreenshots(sandbox, card) {
244648
244648
  }
244649
244649
  function readBacklogStatus(sandbox, card) {
244650
244650
  const backlogPath = join164(sandbox, ".roll", "backlog.md");
244651
- if (!existsSync140(backlogPath))
244651
+ if (!existsSync141(backlogPath))
244652
244652
  return void 0;
244653
244653
  for (const line of readFileSync141(backlogPath, "utf8").split("\n")) {
244654
244654
  if (line.startsWith("|") && line.includes(card)) {
@@ -244661,7 +244661,7 @@ function readBacklogStatus(sandbox, card) {
244661
244661
  }
244662
244662
  function readTruthLadder(sandbox, card) {
244663
244663
  const truthPath = join164(sandbox, ".roll", "features", "truth.json");
244664
- if (!existsSync140(truthPath))
244664
+ if (!existsSync141(truthPath))
244665
244665
  return void 0;
244666
244666
  try {
244667
244667
  const snap = JSON.parse(readFileSync141(truthPath, "utf8"));
@@ -244673,7 +244673,7 @@ function readTruthLadder(sandbox, card) {
244673
244673
  }
244674
244674
  function readTcrCommits(sandbox) {
244675
244675
  const runsPath3 = join164(sandbox, ".roll", "loop", "runs.jsonl");
244676
- if (!existsSync140(runsPath3))
244676
+ if (!existsSync141(runsPath3))
244677
244677
  return [];
244678
244678
  const out3 = [];
244679
244679
  try {
@@ -244816,7 +244816,7 @@ ROLL_HOME: ${rollHome4}
244816
244816
  return 1;
244817
244817
  } finally {
244818
244818
  if (!keepSandbox) {
244819
- rmSync26(dirname77(sandbox), { recursive: true, force: true });
244819
+ rmSync27(dirname77(sandbox), { recursive: true, force: true });
244820
244820
  }
244821
244821
  }
244822
244822
  }
@@ -259883,9 +259883,9 @@ function writeFeatureStub(issue, featuresDir, epic = "backlog-lifecycle") {
259883
259883
  if (existsSync47(path)) {
259884
259884
  const existing = readFileSync47(path, "utf8");
259885
259885
  const block = ac ? ac + "\n" : "";
259886
- const sep8 = existing.endsWith("\n") || existing === "" ? "" : "\n";
259886
+ const sep9 = existing.endsWith("\n") || existing === "" ? "" : "\n";
259887
259887
  if (block)
259888
- appendFileSync5(path, sep8 + block);
259888
+ appendFileSync5(path, sep9 + block);
259889
259889
  return path;
259890
259890
  }
259891
259891
  writeFileSync26(path, featureStubContent(issue));
@@ -262225,8 +262225,8 @@ function writeSet(runtimeDir11, set) {
262225
262225
  writeFileSync30(tmp, JSON.stringify([...set].sort(), null, 2), "utf8");
262226
262226
  writeFileSync30(path, readFileSync53(tmp, "utf8"), "utf8");
262227
262227
  try {
262228
- const { rmSync: rmSync30 } = __require("node:fs");
262229
- rmSync30(tmp, { force: true });
262228
+ const { rmSync: rmSync31 } = __require("node:fs");
262229
+ rmSync31(tmp, { force: true });
262230
262230
  } catch {
262231
262231
  }
262232
262232
  }
@@ -271420,8 +271420,8 @@ function topBar(input) {
271420
271420
  const switcher = multi ? `<div class="proj-switch" style="position:relative;flex:none;"><button type="button" id="proj-switch-btn" class="proj-switch-btn" aria-haspopup="menu" aria-expanded="false" style="display:flex;align-items:center;gap:9px;cursor:pointer;background:transparent;border:0;padding:5px 7px;border-radius:8px;">` + dot + projName + `<span class="proj-caret" style="${MONO}font-size:9px;color:#8f98ad;flex:none;transition:transform .18s;">\u25BE</span></button><div id="proj-menu" class="proj-menu" role="menu" aria-label="switch project \xB7 \u5207\u6362\u9879\u76EE" hidden style="position:absolute;top:42px;left:0;min-width:240px;background:rgba(27,34,56,.99);border:1px solid #313a55;border-radius:10px;padding:6px;box-shadow:0 10px 30px rgba(7,10,20,.5);z-index:40;"><div style="${MONO}font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:#6f7892;padding:6px 13px 8px;">${esc4(input.brand.name)} \xB7 ${bi("this machine", "\u8FD9\u53F0\u673A\u5668")}</div>` + menuItems + `</div></div>` : `<a href="${homeHref}" class="proj-switch-btn" style="display:flex;align-items:center;gap:9px;cursor:pointer;flex:none;text-decoration:none;padding:5px 7px;">` + dot + projName + `</a>`;
271421
271421
  const crumbsNav = `<nav style="flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:9px;${MONO}font-size:12px;color:#8f98ad;overflow:hidden;" aria-label="machine layer \xB7 \u673A\u5668\u5C42"><span style="${MONO}font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:#5b6480;flex:none;">${bi("Machine", "\u673A\u5668")}</span>` + MACHINE_NAV.map((m7, i) => {
271422
271422
  const active = input.machinePage === m7.key;
271423
- const sep8 = i > 0 ? `<span style="color:#3a4360;flex:none;">\xB7</span>` : "";
271424
- return sep8 + `<a class="mach-link${active ? " on" : ""}" data-machine="${m7.key}" href="${esc4(m7.href)}"${active ? ' aria-current="page"' : ""} style="text-decoration:none;color:${active ? "#fff" : "#8f98ad"};font-weight:${active ? "600" : "400"};white-space:nowrap;flex:none;">${bi(m7.en, m7.zh)}</a>`;
271423
+ const sep9 = i > 0 ? `<span style="color:#3a4360;flex:none;">\xB7</span>` : "";
271424
+ return sep9 + `<a class="mach-link${active ? " on" : ""}" data-machine="${m7.key}" href="${esc4(m7.href)}"${active ? ' aria-current="page"' : ""} style="text-decoration:none;color:${active ? "#fff" : "#8f98ad"};font-weight:${active ? "600" : "400"};white-space:nowrap;flex:none;">${bi(m7.en, m7.zh)}</a>`;
271425
271425
  }).join("") + `</nav>`;
271426
271426
  const releaseTag = input.snapshot.release?.latestTag;
271427
271427
  const releaseBadge = releaseTag !== void 0 && releaseTag !== "" ? `<span style="${MONO}font-size:11px;color:#6f7892;letter-spacing:.02em;white-space:nowrap;">${bi("release", "\u53D1\u7248")} <b style="color:#cfd5e3;font-weight:600;">${esc4(releaseTag)}</b></span>` : "";
@@ -277700,11 +277700,11 @@ function printMergeSummary(summary) {
277700
277700
  function summaryAction(summary, file) {
277701
277701
  for (let index = summary.length - 1; index >= 0; index -= 1) {
277702
277702
  const entry = summary[index] ?? "";
277703
- const sep8 = entry.indexOf("|");
277704
- if (sep8 < 0)
277703
+ const sep9 = entry.indexOf("|");
277704
+ if (sep9 < 0)
277705
277705
  continue;
277706
- if (entry.slice(sep8 + 1) === file)
277707
- return entry.slice(0, sep8);
277706
+ if (entry.slice(sep9 + 1) === file)
277707
+ return entry.slice(0, sep9);
277708
277708
  }
277709
277709
  return null;
277710
277710
  }
@@ -278249,10 +278249,10 @@ function emitInitUi(projectDir, hasAgents, syncStatus, summary, shouldNudge, mod
278249
278249
  lines3.push(" " + c(iconColor, icon) + " " + c(iconColor, fLabel, { bold: true }));
278250
278250
  const nudgeMsg = shouldNudge ? renderDesignNudge(msgLang6())[0] ?? "" : "";
278251
278251
  const nudgePair = shouldNudge && nudgeMsg !== "" ? (() => {
278252
- const sep8 = nudgeMsg.indexOf(" \u2014 ");
278253
- if (sep8 < 0)
278252
+ const sep9 = nudgeMsg.indexOf(" \u2014 ");
278253
+ if (sep9 < 0)
278254
278254
  return [nudgeMsg, ""];
278255
- return [nudgeMsg.slice(0, sep8), nudgeMsg.slice(sep8 + 3)];
278255
+ return [nudgeMsg.slice(0, sep9), nudgeMsg.slice(sep9 + 3)];
278256
278256
  })() : void 0;
278257
278257
  const nextItems = nextItemsOverride ?? (hasAgents ? [
278258
278258
  ...nudgePair ? [nudgePair] : [],
@@ -281224,7 +281224,7 @@ async function loopExhaustionSplitCommand(argv, deps = realExhaustionSplitDeps()
281224
281224
  init_dist2();
281225
281225
  init_dist();
281226
281226
  init_dist3();
281227
- import { appendFileSync as appendFileSync21, existsSync as existsSync134, mkdirSync as mkdirSync74, readdirSync as readdirSync48, readFileSync as readFileSync135, writeFileSync as writeFileSync72 } from "node:fs";
281227
+ import { appendFileSync as appendFileSync21, existsSync as existsSync135, mkdirSync as mkdirSync74, readdirSync as readdirSync49, readFileSync as readFileSync135, writeFileSync as writeFileSync72 } from "node:fs";
281228
281228
  import { dirname as dirname74, join as join158 } from "node:path";
281229
281229
 
281230
281230
  // packages/cli/dist/runner/executor.js
@@ -288367,7 +288367,7 @@ function applyCorrectionCircuitBreaker(projectPath3, slug3, eventsPath4, alertsP
288367
288367
  init_dist2();
288368
288368
  init_dist3();
288369
288369
  import { execFileSync as execFileSync31 } from "node:child_process";
288370
- import { readFileSync as readFileSync133 } from "node:fs";
288370
+ import { readFileSync as readFileSync133, readdirSync as readdirSync48, realpathSync as realpathSync20 } from "node:fs";
288371
288371
  import { homedir as homedir33 } from "node:os";
288372
288372
  import { basename as basename22, dirname as dirname72, join as join156, relative as relative13, resolve as resolve13 } from "node:path";
288373
288373
  function git3(args, cwd) {
@@ -288437,6 +288437,60 @@ function extractCycleId(dirName) {
288437
288437
  const m7 = /^(cycle-\d{8}-\d{6}-\d+)$/.exec(dirName);
288438
288438
  return m7 ? m7[1] : void 0;
288439
288439
  }
288440
+ var ORPHAN_DELIVERED_OUTCOMES = /* @__PURE__ */ new Set(["delivered", "merged"]);
288441
+ function realpathSafe2(p) {
288442
+ try {
288443
+ return realpathSync20(p);
288444
+ } catch {
288445
+ return p;
288446
+ }
288447
+ }
288448
+ function defaultReadDirNames(dir) {
288449
+ try {
288450
+ return readdirSync48(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
288451
+ } catch {
288452
+ return [];
288453
+ }
288454
+ }
288455
+ function scanOrphanLoopWorktrees(repoRoot3, registeredPaths, cycles, deps) {
288456
+ const worktreesDir = join156(repoRoot3, ".roll", "loop", "worktrees");
288457
+ const names = deps.readDir ? deps.readDir(worktreesDir) : defaultReadDirNames(worktreesDir);
288458
+ const out3 = [];
288459
+ for (const name of [...names].sort()) {
288460
+ const absPath = resolve13(join156(worktreesDir, name));
288461
+ if (registeredPaths.has(realpathSafe2(absPath)))
288462
+ continue;
288463
+ const cycleId = extractCycleId(name);
288464
+ const ce = cycleId ? cycles.get(cycleId) : void 0;
288465
+ const outcome = ce?.outcome;
288466
+ const active = isActiveCycle(cycleId, repoRoot3, deps);
288467
+ const rec = {
288468
+ path: absPath,
288469
+ owner: "loop",
288470
+ ...cycleId ? { cycleId } : {},
288471
+ ...ce?.storyId ? { storyId: ce.storyId } : {},
288472
+ ...outcome ? { outcome } : {},
288473
+ dirtyTracked: "unknown",
288474
+ // no git metadata for a deregistered dir
288475
+ dirtyUntracked: "unknown",
288476
+ ahead: null,
288477
+ mergeEvidence: { kind: "none" },
288478
+ active,
288479
+ disposition: "preserved_orphan",
288480
+ reason: ""
288481
+ };
288482
+ if (active) {
288483
+ rec.reason = "orphan loop dir with an active cycle lock; never reclaimed";
288484
+ } else if (outcome && ORPHAN_DELIVERED_OUTCOMES.has(outcome)) {
288485
+ rec.disposition = "orphan_reclaimable";
288486
+ rec.reason = `orphan loop dir (deregistered from git); owning cycle outcome '${outcome}' is delivered \u2014 bounded reclaim`;
288487
+ } else {
288488
+ rec.reason = `orphan loop dir (deregistered from git); delivery not provable (cycle outcome '${outcome ?? "unknown"}') \u2014 preserved, reclaim manually after review`;
288489
+ }
288490
+ out3.push(rec);
288491
+ }
288492
+ return out3;
288493
+ }
288440
288494
  function detectDirty(wtPath, deps) {
288441
288495
  try {
288442
288496
  const g = deps.git ?? git3;
@@ -288637,6 +288691,10 @@ function auditWorktrees(deps) {
288637
288691
  baseRec.reason = disp.reason;
288638
288692
  records.push(baseRec);
288639
288693
  }
288694
+ const registeredLoopPaths = new Set(records.map((r) => realpathSafe2(resolve13(r.path))));
288695
+ for (const orphan of scanOrphanLoopWorktrees(repoRoot3, registeredLoopPaths, cycles, deps)) {
288696
+ records.push(orphan);
288697
+ }
288640
288698
  let ephemeralBranches = [];
288641
288699
  try {
288642
288700
  const g = deps.git ?? git3;
@@ -288695,7 +288753,9 @@ function renderHuman(output) {
288695
288753
  "preserved_unpublished",
288696
288754
  "preserved_dirty_no_tcr",
288697
288755
  "preserved_needs_review",
288756
+ "preserved_orphan",
288698
288757
  "disposable_candidate",
288758
+ "orphan_reclaimable",
288699
288759
  "external_unmanaged"
288700
288760
  ];
288701
288761
  for (const disp of order) {
@@ -288767,12 +288827,24 @@ function worktreeAuditCommand(args, deps) {
288767
288827
  init_dist2();
288768
288828
  import { execFileSync as execFileSync32 } from "node:child_process";
288769
288829
  import { homedir as homedir34 } from "node:os";
288770
- import { appendFileSync as appendFileSync20, mkdirSync as mkdirSync73 } from "node:fs";
288771
- import { dirname as dirname73, join as join157, relative as relative14, resolve as resolve14 } from "node:path";
288830
+ import { appendFileSync as appendFileSync20, existsSync as existsSync134, mkdirSync as mkdirSync73, rmSync as rmSync25 } from "node:fs";
288831
+ import { dirname as dirname73, join as join157, relative as relative14, resolve as resolve14, sep as sep8 } from "node:path";
288772
288832
  var MERGED_KINDS = /* @__PURE__ */ new Set(["ancestor", "patch_equivalent"]);
288773
288833
  function isSafelyDisposable(rec) {
288774
288834
  return rec.owner === "loop" && rec.active === false && rec.dirtyTracked === false && rec.disposition === "disposable_candidate" && MERGED_KINDS.has(rec.mergeEvidence.kind) && typeof rec.head === "string" && rec.head.length > 0;
288775
288835
  }
288836
+ function isReclaimableOrphan(rec) {
288837
+ return rec.owner === "loop" && rec.active === false && rec.disposition === "orphan_reclaimable";
288838
+ }
288839
+ function isBoundedLoopWorktreeDir(repoRoot3, path) {
288840
+ const base = resolve14(join157(repoRoot3, ".roll", "loop", "worktrees"));
288841
+ const abs = resolve14(path);
288842
+ if (abs === base)
288843
+ return false;
288844
+ if (!abs.startsWith(base + sep8))
288845
+ return false;
288846
+ return dirname73(abs) === base;
288847
+ }
288776
288848
  var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["main", "master", "HEAD"]);
288777
288849
  function resolveStandaloneMergedBranches(audit, deps) {
288778
288850
  const out3 = [];
@@ -288797,20 +288869,25 @@ function planWorktreeCleanup(audit, threshold, standaloneMergedBranches = []) {
288797
288869
  const loopWorktrees = audit.records.filter((r) => r.owner === "loop");
288798
288870
  const canaryTotal = audit.ephemeralBranches.length + loopWorktrees.length;
288799
288871
  const excess = canaryTotal - threshold;
288800
- const pool = audit.records.filter(isSafelyDisposable).sort((a, b) => a.path.localeCompare(b.path));
288872
+ const pool = audit.records.filter((r) => isSafelyDisposable(r) || isReclaimableOrphan(r)).sort((a, b) => a.path.localeCompare(b.path));
288801
288873
  const branchPool = [...standaloneMergedBranches].sort((a, b) => a.branch.localeCompare(b.branch));
288802
288874
  const excessN = excess > 0 ? excess : 0;
288803
288875
  const chosen = pool.slice(0, Math.min(excessN, pool.length));
288804
288876
  const remainingExcess = excessN - chosen.length;
288805
288877
  const chosenBranches = branchPool.slice(0, Math.min(remainingExcess, branchPool.length));
288806
288878
  const chosenPaths = new Set(chosen.map((r) => r.path));
288807
- const candidates = chosen.map((r) => ({
288808
- path: r.path,
288809
- ...r.cycleId ? { cycleId: r.cycleId } : {},
288810
- ...r.branch ? { branch: r.branch } : {},
288811
- expectedHead: r.head,
288812
- reason: "disposable_candidate"
288813
- }));
288879
+ const candidates = chosen.map((r) => {
288880
+ const orphan = isReclaimableOrphan(r);
288881
+ return {
288882
+ path: r.path,
288883
+ ...r.cycleId ? { cycleId: r.cycleId } : {},
288884
+ ...r.branch ? { branch: r.branch } : {},
288885
+ // An orphan has no registered HEAD; apply skips the head check for rm_dir.
288886
+ expectedHead: orphan ? "" : r.head,
288887
+ reason: orphan ? "orphan_reclaimable" : "disposable_candidate",
288888
+ reclaim: orphan ? "rm_dir" : "git_worktree"
288889
+ };
288890
+ });
288814
288891
  const preserved = audit.records.filter((r) => !chosenPaths.has(r.path)).map((r) => ({ path: r.path, disposition: r.disposition, reason: r.reason }));
288815
288892
  const countedWorktrees = loopWorktrees.map((r) => ({
288816
288893
  path: r.path,
@@ -288841,13 +288918,47 @@ function defaultRemoveBranch(repositoryRoot, branch) {
288841
288918
  return { ok: true, detail: "" };
288842
288919
  }
288843
288920
  function defaultRemoveWorktree(repositoryRoot, path) {
288921
+ let gitErr = "";
288844
288922
  try {
288845
288923
  execFileSync32("git", ["-C", repositoryRoot, "worktree", "remove", "--force", path], {
288846
288924
  encoding: "utf8",
288847
288925
  stdio: ["ignore", "pipe", "pipe"]
288848
288926
  });
288849
288927
  } catch (err16) {
288850
- return { ok: false, detail: err16 instanceof Error ? err16.message : String(err16) };
288928
+ gitErr = err16 instanceof Error ? err16.message : String(err16);
288929
+ }
288930
+ if (existsSync134(path)) {
288931
+ if (!isBoundedLoopWorktreeDir(repositoryRoot, path)) {
288932
+ return { ok: false, detail: gitErr || `refused: ${path} is outside .roll/loop/worktrees` };
288933
+ }
288934
+ try {
288935
+ rmSync25(path, { recursive: true, force: true });
288936
+ } catch (e) {
288937
+ return { ok: false, detail: gitErr || (e instanceof Error ? e.message : String(e)) };
288938
+ }
288939
+ } else if (gitErr) {
288940
+ return { ok: false, detail: gitErr };
288941
+ }
288942
+ try {
288943
+ execFileSync32("git", ["-C", repositoryRoot, "worktree", "prune", "--expire", "now"], {
288944
+ encoding: "utf8",
288945
+ stdio: ["ignore", "pipe", "pipe"]
288946
+ });
288947
+ } catch {
288948
+ }
288949
+ return { ok: true, detail: "" };
288950
+ }
288951
+ function defaultReclaimOrphanDir(repositoryRoot, path) {
288952
+ if (!isBoundedLoopWorktreeDir(repositoryRoot, path)) {
288953
+ return { ok: false, detail: `refused: ${path} is outside .roll/loop/worktrees` };
288954
+ }
288955
+ if (!existsSync134(path)) {
288956
+ return { ok: false, detail: "missing: orphan dir already gone" };
288957
+ }
288958
+ try {
288959
+ rmSync25(path, { recursive: true, force: true });
288960
+ } catch (e) {
288961
+ return { ok: false, detail: e instanceof Error ? e.message : String(e) };
288851
288962
  }
288852
288963
  try {
288853
288964
  execFileSync32("git", ["-C", repositoryRoot, "worktree", "prune", "--expire", "now"], {
@@ -288862,6 +288973,7 @@ async function applyWorktreeCleanup(plan, options) {
288862
288973
  const repositoryRoot = resolve14(options.repositoryRoot);
288863
288974
  const auditFn = options.audit ?? (() => auditWorktrees({ repoRoot: repositoryRoot, home: homedir34() }));
288864
288975
  const removeFn = options.removeWorktree ?? defaultRemoveWorktree;
288976
+ const reclaimOrphanFn = options.reclaimOrphanDir ?? defaultReclaimOrphanDir;
288865
288977
  const removeBranchFn = options.removeBranch ?? defaultRemoveBranch;
288866
288978
  const emit4 = options.emit ?? (() => {
288867
288979
  });
@@ -288877,17 +288989,47 @@ async function applyWorktreeCleanup(plan, options) {
288877
288989
  emit4({ type: "worktree_cleanup_refused", path: candidate.path, reason, ts: nowMs2() });
288878
288990
  };
288879
288991
  if (!rec) {
288880
- refuse("missing: worktree no longer registered (already removed or pruned)");
288881
- continue;
288882
- }
288883
- if (rec.head !== candidate.expectedHead) {
288884
- refuse(`changed-head: expected ${candidate.expectedHead}, found ${rec.head ?? "none"}`);
288992
+ refuse(candidate.reclaim === "rm_dir" ? "missing: orphan dir no longer present (already reclaimed)" : "missing: worktree no longer registered (already removed or pruned)");
288885
288993
  continue;
288886
288994
  }
288887
288995
  if (rec.active) {
288888
288996
  refuse("active: worktree activated concurrently (fresh lock/heartbeat)");
288889
288997
  continue;
288890
288998
  }
288999
+ if (candidate.reclaim === "rm_dir") {
289000
+ if (!isReclaimableOrphan(rec)) {
289001
+ refuse(`disposition: fresh audit reports '${rec.disposition}' (orphan no longer provably delivered)`);
289002
+ continue;
289003
+ }
289004
+ const removal2 = {
289005
+ path: rec.path,
289006
+ expectedHead: "",
289007
+ reclaim: "rm_dir",
289008
+ ...rec.cycleId ? { cycleId: rec.cycleId } : {}
289009
+ };
289010
+ if (options.dryRun) {
289011
+ removed.push(removal2);
289012
+ continue;
289013
+ }
289014
+ const result3 = reclaimOrphanFn(repositoryRoot, rec.path);
289015
+ if (!result3.ok) {
289016
+ refuse(`reclaim-failed: ${result3.detail}`);
289017
+ continue;
289018
+ }
289019
+ removed.push(removal2);
289020
+ emit4({
289021
+ type: "worktree_cleanup_applied",
289022
+ path: rec.path,
289023
+ expectedHead: "",
289024
+ ...rec.cycleId ? { cycleId: rec.cycleId } : {},
289025
+ ts: nowMs2()
289026
+ });
289027
+ continue;
289028
+ }
289029
+ if (rec.head !== candidate.expectedHead) {
289030
+ refuse(`changed-head: expected ${candidate.expectedHead}, found ${rec.head ?? "none"}`);
289031
+ continue;
289032
+ }
288891
289033
  if (rec.dirtyTracked === true) {
288892
289034
  refuse("dirty: tracked changes appeared after planning");
288893
289035
  continue;
@@ -288903,6 +289045,7 @@ async function applyWorktreeCleanup(plan, options) {
288903
289045
  const removal = {
288904
289046
  path: rec.path,
288905
289047
  expectedHead: candidate.expectedHead,
289048
+ reclaim: "git_worktree",
288906
289049
  ...rec.branch ? { branch: rec.branch } : {},
288907
289050
  ...rec.cycleId ? { cycleId: rec.cycleId } : {}
288908
289051
  };
@@ -289058,12 +289201,22 @@ function renderPlanHuman(plan, mode) {
289058
289201
  lines3.push(`minimal candidate set (${plan.canaryTotal} \u2192 ${plan.projectedTotal})`);
289059
289202
  for (const c2 of plan.candidates) {
289060
289203
  const tags = [c2.branch, c2.cycleId].filter(Boolean).join(" ");
289061
- lines3.push(` worktree ${rel(c2.path)}${tags ? " " + tags : ""} [disposable_candidate]`);
289204
+ const label5 = c2.reclaim === "rm_dir" ? "orphan_reclaimable \xB7 bounded rm" : "disposable_candidate";
289205
+ const kind = c2.reclaim === "rm_dir" ? "orphan " : "worktree";
289206
+ lines3.push(` ${kind} ${rel(c2.path)}${tags ? " " + tags : ""} [${label5}]`);
289062
289207
  }
289063
289208
  for (const b of plan.branchCandidates) {
289064
289209
  lines3.push(` branch ${b.branch} ${b.expectedSha.slice(0, 9)} [merged: ${b.mergeKind}]`);
289065
289210
  }
289066
289211
  lines3.push("");
289212
+ const preservedOrphans = plan.preserved.filter((p) => p.disposition === "preserved_orphan");
289213
+ if (preservedOrphans.length > 0) {
289214
+ lines3.push(`preserved orphan dirs (${preservedOrphans.length}) \u2014 visible + counted, never auto-deleted`);
289215
+ for (const p of preservedOrphans)
289216
+ lines3.push(` ${rel(p.path)} \u2014 ${p.reason}`);
289217
+ lines3.push(" Reclaim one after review with: roll worktree cleanup --reclaim-orphan <path>");
289218
+ lines3.push("");
289219
+ }
289067
289220
  if (mode === "dry-run") {
289068
289221
  lines3.push("Dry run \u2014 no git state changed.");
289069
289222
  lines3.push("Apply the audited set with: roll worktree cleanup --apply");
@@ -289104,7 +289257,7 @@ function renderResultHuman(result2) {
289104
289257
  }
289105
289258
  return lines3.join("\n").trimEnd() + "\n";
289106
289259
  }
289107
- var CLEANUP_USAGE = "Usage: roll worktree cleanup [--dry-run | --apply] [--json] [--repo <path>]\n Safely recover from branch/worktree canary pressure using the worktree\n audit as the SOLE authority. Removes ONLY inactive, merged, clean\n `disposable_candidate` loop worktrees, plus (FIX-1454) standalone ephemeral\n branches that are verifiably merged (ancestor of the integration branch or a\n merged GitHub PR) and attached to no worktree \u2014 never a path/ref that is\n merely old or counted, and never a preserved (unpublished / dirty / active /\n external / current / protected / unmerged) one.\n\n Always dry-run first. Default (no flag) is --dry-run.\n --dry-run print counted refs/dirs, audit dispositions, and the minimal\n candidate set to clear pressure. Never mutates git state.\n --apply re-run the audit before EVERY removal; remove only revalidated\n candidates via git, prune registration, emit events. A changed\n head / new dirt / missing path / concurrent activation fails\n closed (no substitution). Then resume explicitly: roll loop resume\n --json emit the schema-1 plan (dry-run) or result (apply) as JSON\n --repo override the project root (default: current directory)\n\n \u5B89\u5168\u6E05\u7406:\u4EC5\u79FB\u9664\u5BA1\u8BA1\u5224\u5B9A\u4E3A\u5DF2\u5408\u5E76\u3001\u5E72\u51C0\u3001\u975E\u6D3B\u8DC3\u7684 disposable_candidate;\n \u5148\u8DD1 --dry-run,\u518D --apply,\u6700\u540E\u624B\u52A8 roll loop resume\u3002";
289260
+ var CLEANUP_USAGE = "Usage: roll worktree cleanup [--dry-run | --apply] [--json] [--repo <path>]\n Safely recover from branch/worktree canary pressure using the worktree\n audit as the SOLE authority. Removes ONLY inactive, merged, clean\n `disposable_candidate` loop worktrees, plus (FIX-1454) standalone ephemeral\n branches that are verifiably merged (ancestor of the integration branch or a\n merged GitHub PR) and attached to no worktree \u2014 never a path/ref that is\n merely old or counted, and never a preserved (unpublished / dirty / active /\n external / current / protected / unmerged) one.\n\n Always dry-run first. Default (no flag) is --dry-run.\n --dry-run print counted refs/dirs, audit dispositions, and the minimal\n candidate set to clear pressure. Never mutates git state.\n --apply re-run the audit before EVERY removal; remove only revalidated\n candidates via git, prune registration, emit events. A changed\n head / new dirt / missing path / concurrent activation fails\n closed (no substitution). Then resume explicitly: roll loop resume\n --json emit the schema-1 plan (dry-run) or result (apply) as JSON\n --repo override the project root (default: current directory)\n --reclaim-orphan <path> (FIX-1460) bounded-rm ONE named orphan loop dir\n (deregistered from git; delivery not auto-provable) after you\n review it. Fails closed unless it is an inactive loop orphan\n inside .roll/loop/worktrees. Auto-reclaim of provably-delivered\n orphans happens under --apply.\n\n \u5B89\u5168\u6E05\u7406:\u4EC5\u79FB\u9664\u5BA1\u8BA1\u5224\u5B9A\u4E3A\u5DF2\u5408\u5E76\u3001\u5E72\u51C0\u3001\u975E\u6D3B\u8DC3\u7684 disposable_candidate;\n \u5148\u8DD1 --dry-run,\u518D --apply,\u6700\u540E\u624B\u52A8 roll loop resume\u3002";
289108
289261
  function resolveThreshold() {
289109
289262
  const parsed = parseInt(process.env["ROLL_BRANCH_CANARY_MAX"] ?? "", 10);
289110
289263
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_BRANCH_CANARY_MAX;
@@ -289189,10 +289342,57 @@ async function worktreeCleanupCommand(args, deps) {
289189
289342
  home: deps?.home ?? homedir34(),
289190
289343
  git: deps?.git,
289191
289344
  readFile: deps?.readFile,
289345
+ readDir: deps?.readDir,
289192
289346
  nowISO: deps?.nowISO,
289193
289347
  nowSec: deps?.nowSec,
289194
289348
  integrationBranch: deps?.integrationBranch
289195
289349
  };
289350
+ const reclaimIdx = args.indexOf("--reclaim-orphan");
289351
+ if (reclaimIdx >= 0) {
289352
+ const named2 = args[reclaimIdx + 1];
289353
+ if (!named2) {
289354
+ process.stderr.write("roll worktree cleanup: --reclaim-orphan requires a <path>.\n");
289355
+ return 2;
289356
+ }
289357
+ const fresh = auditWorktrees(fullDeps);
289358
+ const target = resolve14(named2);
289359
+ const rec = fresh.records.find((r2) => resolve14(r2.path) === target);
289360
+ if (!rec) {
289361
+ process.stderr.write(`refused: ${named2} is not in the worktree audit.
289362
+ `);
289363
+ return 2;
289364
+ }
289365
+ if (rec.owner !== "loop") {
289366
+ process.stderr.write(`refused: ${named2} is not a loop worktree (owner=${rec.owner}).
289367
+ `);
289368
+ return 2;
289369
+ }
289370
+ if (rec.active) {
289371
+ process.stderr.write(`refused: ${named2} has an active cycle lock.
289372
+ `);
289373
+ return 2;
289374
+ }
289375
+ if (rec.disposition !== "orphan_reclaimable" && rec.disposition !== "preserved_orphan") {
289376
+ process.stderr.write(`refused: ${named2} audits as '${rec.disposition}', not an orphan dir \u2014 use --apply for registered worktrees.
289377
+ `);
289378
+ return 2;
289379
+ }
289380
+ if (!isBoundedLoopWorktreeDir(repoRoot3, rec.path)) {
289381
+ process.stderr.write(`refused: ${named2} is outside .roll/loop/worktrees.
289382
+ `);
289383
+ return 2;
289384
+ }
289385
+ const reclaimFn = deps?.reclaimOrphanDir ?? defaultReclaimOrphanDir;
289386
+ const r = reclaimFn(repoRoot3, rec.path);
289387
+ if (!r.ok) {
289388
+ process.stderr.write(`reclaim-failed: ${r.detail}
289389
+ `);
289390
+ return 1;
289391
+ }
289392
+ process.stdout.write(`reclaimed orphan dir: ${rec.path}
289393
+ `);
289394
+ return 0;
289395
+ }
289196
289396
  const threshold = resolveThreshold();
289197
289397
  const integrationBranch = fullDeps.integrationBranch ?? resolveIntegrationForCleanup(repoRoot3);
289198
289398
  const auditNow = auditWorktrees(fullDeps);
@@ -289330,13 +289530,13 @@ function announceReport(projectPath3, slug3, storyId, opener = (p) => {
289330
289530
  const latest = join158(cardArchiveDir(projectPath3, storyId), "latest");
289331
289531
  const review = join158(latest, reviewFileName(storyId));
289332
289532
  const legacyReport = join158(latest, reportFileName(storyId));
289333
- const report = existsSync134(review) ? review : existsSync134(legacyReport) ? legacyReport : null;
289533
+ const report = existsSync135(review) ? review : existsSync135(legacyReport) ? legacyReport : null;
289334
289534
  if (report === null)
289335
289535
  return null;
289336
289536
  const label5 = currentLang() === "zh" ? "\u9A8C\u6536 Review Page" : "Acceptance Review Page";
289337
289537
  process.stdout.write(`${label5}: ${report}
289338
289538
  `);
289339
- const muted = existsSync134(join158(projectPath3, ".roll", "loop", `mute-${slug3}`)) || existsSync134(join158(process.env["ROLL_SHARED_ROOT"] || join158(process.env["HOME"] ?? "", ".shared", "roll"), "loop", `mute-${slug3}`));
289539
+ const muted = existsSync135(join158(projectPath3, ".roll", "loop", `mute-${slug3}`)) || existsSync135(join158(process.env["ROLL_SHARED_ROOT"] || join158(process.env["HOME"] ?? "", ".shared", "roll"), "loop", `mute-${slug3}`));
289340
289540
  if (!muted)
289341
289541
  opener(report);
289342
289542
  return report;
@@ -289360,7 +289560,7 @@ function cycleSignalTeardown(paths, cycleId, branch, sig, deps = {}) {
289360
289560
  }
289361
289561
  let owned = false;
289362
289562
  try {
289363
- owned = existsSync134(paths.lockPath) && readLockOwner(paths.lockPath)?.pid === pid;
289563
+ owned = existsSync135(paths.lockPath) && readLockOwner(paths.lockPath)?.pid === pid;
289364
289564
  } catch {
289365
289565
  owned = false;
289366
289566
  }
@@ -289505,7 +289705,7 @@ function incrementConsecutiveFails(projectPath3, slug3, alertsPath, eventsPath4,
289505
289705
  const counterFile = join158(rt, "consecutive-fails");
289506
289706
  let count = 0;
289507
289707
  try {
289508
- if (existsSync134(counterFile)) {
289708
+ if (existsSync135(counterFile)) {
289509
289709
  count = parseInt(readFileSync135(counterFile, "utf8").trim(), 10) || 0;
289510
289710
  }
289511
289711
  } catch {
@@ -289519,7 +289719,7 @@ function incrementConsecutiveFails(projectPath3, slug3, alertsPath, eventsPath4,
289519
289719
  if (count < threshold)
289520
289720
  return;
289521
289721
  const pauseMarker = join158(projectPath3, ".roll", "loop", `PAUSE-${slug3}`);
289522
- if (existsSync134(pauseMarker))
289722
+ if (existsSync135(pauseMarker))
289523
289723
  return;
289524
289724
  const alertMsg = `# ALERT \u2014 loop auto-paused after ${count} consecutive failures
289525
289725
 
@@ -289555,7 +289755,7 @@ loop run-once: \u8FDE\u7EED ${count} \u6B21\u5931\u8D25\u540E\u81EA\u52A8\u6682\
289555
289755
  }
289556
289756
  function writeRootCausePause(projectPath3, slug3, alertsPath, eventsPath4, cycleId, attribution, count, snapshotPath2) {
289557
289757
  const pauseMarker = join158(projectPath3, ".roll", "loop", `PAUSE-${slug3}`);
289558
- if (existsSync134(pauseMarker))
289758
+ if (existsSync135(pauseMarker))
289559
289759
  return;
289560
289760
  const playbook = playbookForFailure(attribution.failureClass, attribution.rootCauseKey);
289561
289761
  const alertMsg = `# ALERT \u2014 loop auto-paused on ${attribution.failureClass} failure
@@ -289617,7 +289817,7 @@ function handleNonCardFailure(projectPath3, slug3, alertsPath, eventsPath4, runt
289617
289817
  function readFailurePauseThreshold(projectPath3) {
289618
289818
  try {
289619
289819
  const policy = join158(projectPath3, ".roll", "policy.yaml");
289620
- if (!existsSync134(policy))
289820
+ if (!existsSync135(policy))
289621
289821
  return PAUSE_THRESHOLD;
289622
289822
  return parsePolicy(readFileSync135(policy, "utf8")).loopSafety.maxConsecutiveFailures;
289623
289823
  } catch {
@@ -289647,7 +289847,7 @@ function incrementConsecutiveIdle(projectPath3, slug3) {
289647
289847
  const file = idleCounterPath(projectPath3, slug3);
289648
289848
  let count = 0;
289649
289849
  try {
289650
- if (existsSync134(file)) {
289850
+ if (existsSync135(file)) {
289651
289851
  count = parseInt(readFileSync135(file, "utf8").trim(), 10) || 0;
289652
289852
  }
289653
289853
  } catch {
@@ -289717,7 +289917,7 @@ function readExternalBlock(eventsPath4, cycleId) {
289717
289917
  const authDetails = [];
289718
289918
  const networkDetails = [];
289719
289919
  try {
289720
- if (!existsSync134(eventsPath4))
289920
+ if (!existsSync135(eventsPath4))
289721
289921
  return null;
289722
289922
  for (const line of readFileSync135(eventsPath4, "utf8").split("\n")) {
289723
289923
  if (line.trim() === "" || !line.includes("agent:blocked"))
@@ -289782,7 +289982,7 @@ function writeReviewerBlockedAlert(projectPath3, slug3, alertsPath, eventsPath4,
289782
289982
  }
289783
289983
  if (block.cause === "auth") {
289784
289984
  const pauseMarker = join158(projectPath3, ".roll", "loop", `PAUSE-${slug3}`);
289785
- if (!existsSync134(pauseMarker)) {
289985
+ if (!existsSync135(pauseMarker)) {
289786
289986
  try {
289787
289987
  writeFileSync72(pauseMarker, msg7, "utf8");
289788
289988
  bus.appendEvent(eventsPath4, { type: "policy:safety_pause", loop: "ci", reason: `agent auth block: ${agents}`, ts: ts2 });
@@ -289810,7 +290010,7 @@ function buildLoopRouteDeps(projectPath3) {
289810
290010
  }
289811
290011
  return readRouteSlot(text2, slot);
289812
290012
  } catch {
289813
- if (existsSync134(agentsYaml)) {
290013
+ if (existsSync135(agentsYaml)) {
289814
290014
  throw new Error("legacy agent configuration is unsupported; run `roll agent migrate`");
289815
290015
  }
289816
290016
  return void 0;
@@ -290088,7 +290288,7 @@ loop run-once: \u8BC4\u5BA1\u5224\u5B9A ${resizeStory} \u8303\u56F4\u8FC7\u5927(
290088
290288
  slug: id.slug,
290089
290289
  count: idleCount,
290090
290290
  resolveState: () => resolveLoopRunState(id.path, id.slug),
290091
- readBacklog: () => existsSync134(backlogFile) ? readFileSync135(backlogFile, "utf8") : "",
290291
+ readBacklog: () => existsSync135(backlogFile) ? readFileSync135(backlogFile, "utf8") : "",
290092
290292
  scheduler: createScheduler(process.platform, { uid: process.getuid?.() ?? 0 }),
290093
290293
  loopLabel: launchdLabel("loop", id.slug),
290094
290294
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
@@ -290277,7 +290477,7 @@ async function isOffline(resolve17 = (h) => lookup2(h)) {
290277
290477
  }
290278
290478
  }
290279
290479
  function isLoopPaused(projectPath3, slug3) {
290280
- return existsSync134(join158(projectPath3, ".roll", "loop", `PAUSE-${slug3}`));
290480
+ return existsSync135(join158(projectPath3, ".roll", "loop", `PAUSE-${slug3}`));
290281
290481
  }
290282
290482
  function branchCanaryTrips(projectPath3, slug3, rt, alertsPath) {
290283
290483
  let ephemeralBranchCount = 0;
@@ -290288,7 +290488,7 @@ function branchCanaryTrips(projectPath3, slug3, rt, alertsPath) {
290288
290488
  }
290289
290489
  let worktreeCount = 0;
290290
290490
  try {
290291
- worktreeCount = readdirSync48(join158(rt, "worktrees"), { withFileTypes: true }).filter((e) => e.isDirectory()).length;
290491
+ worktreeCount = readdirSync49(join158(rt, "worktrees"), { withFileTypes: true }).filter((e) => e.isDirectory()).length;
290292
290492
  } catch {
290293
290493
  }
290294
290494
  const parsed = parseInt(process.env["ROLL_BRANCH_CANARY_MAX"] ?? "", 10);
@@ -290396,7 +290596,7 @@ function checkCoreWorktreeContamination(cwd) {
290396
290596
  // packages/cli/dist/commands/offboard.js
290397
290597
  init_dist();
290398
290598
  import { spawnSync as spawnSync16 } from "node:child_process";
290399
- import { existsSync as existsSync135, readFileSync as readFileSync136, realpathSync as realpathSync20, renameSync as renameSync29, rmSync as rmSync25, writeFileSync as writeFileSync73 } from "node:fs";
290599
+ import { existsSync as existsSync136, readFileSync as readFileSync136, realpathSync as realpathSync21, renameSync as renameSync29, rmSync as rmSync26, writeFileSync as writeFileSync73 } from "node:fs";
290400
290600
  import { homedir as homedir35 } from "node:os";
290401
290601
  import { join as join159, resolve as resolve15 } from "node:path";
290402
290602
  function pal4() {
@@ -290497,7 +290697,7 @@ function writeFileAtomic2(path, text2) {
290497
290697
  writeFileSync73(tmp, text2);
290498
290698
  renameSync29(tmp, path);
290499
290699
  } catch (error) {
290500
- rmSync25(tmp, { force: true });
290700
+ rmSync26(tmp, { force: true });
290501
290701
  throw error;
290502
290702
  }
290503
290703
  }
@@ -290523,12 +290723,12 @@ function offboardCommand(args) {
290523
290723
  }
290524
290724
  let projectDir;
290525
290725
  try {
290526
- projectDir = realpathSync20(resolve15(process.cwd()));
290726
+ projectDir = realpathSync21(resolve15(process.cwd()));
290527
290727
  } catch {
290528
290728
  projectDir = process.cwd();
290529
290729
  }
290530
290730
  const changeset = changesetPath2(projectDir);
290531
- if (!existsSync135(changeset)) {
290731
+ if (!existsSync136(changeset)) {
290532
290732
  err10(m4("offboard.no_changeset_en"));
290533
290733
  err10(m4("offboard.no_changeset_zh"));
290534
290734
  process.stderr.write("\n");
@@ -290627,7 +290827,7 @@ function offboardCommand(args) {
290627
290827
  process.stdout.write(m4("offboard.applying_offboard") + "\n");
290628
290828
  for (const item of mergedFiles) {
290629
290829
  const path = resolveChangesetItem(projectDir, item);
290630
- if (!existsSync135(path))
290830
+ if (!existsSync136(path))
290631
290831
  continue;
290632
290832
  const before = readFileSync136(path, "utf8");
290633
290833
  const after = stripRollMergeBlocks(before);
@@ -290640,8 +290840,8 @@ function offboardCommand(args) {
290640
290840
  for (const item of files) {
290641
290841
  try {
290642
290842
  const path = resolveChangesetItem(projectDir, item);
290643
- rmSync25(path, { force: true });
290644
- if (!existsSync135(path))
290843
+ rmSync26(path, { force: true });
290844
+ if (!existsSync136(path))
290645
290845
  process.stdout.write(` removed file ${item}
290646
290846
  `);
290647
290847
  } catch {
@@ -290649,7 +290849,7 @@ function offboardCommand(args) {
290649
290849
  }
290650
290850
  for (const item of dirs) {
290651
290851
  try {
290652
- rmSync25(resolveChangesetItem(projectDir, item), { recursive: true, force: true });
290852
+ rmSync26(resolveChangesetItem(projectDir, item), { recursive: true, force: true });
290653
290853
  process.stdout.write(` removed dir ${item}
290654
290854
  `);
290655
290855
  } catch {
@@ -290657,7 +290857,7 @@ function offboardCommand(args) {
290657
290857
  }
290658
290858
  for (const item of giEntries) {
290659
290859
  const gi = join159(projectDir, ".gitignore");
290660
- if (existsSync135(gi)) {
290860
+ if (existsSync136(gi)) {
290661
290861
  const lines3 = readFileSync136(gi, "utf8").split("\n");
290662
290862
  if (lines3.includes(item)) {
290663
290863
  const kept = lines3.filter((l) => l !== item);
@@ -290673,21 +290873,21 @@ function offboardCommand(args) {
290673
290873
  if (r === 0)
290674
290874
  process.stdout.write(` unloaded ${item}
290675
290875
  `);
290676
- rmSync25(plistPath, { force: true });
290876
+ rmSync26(plistPath, { force: true });
290677
290877
  }
290678
- rmSync25(changeset, { force: true });
290878
+ rmSync26(changeset, { force: true });
290679
290879
  ok11(m4("offboard.offboard_complete_offboard"));
290680
290880
  return 0;
290681
290881
  }
290682
290882
 
290683
290883
  // packages/cli/dist/commands/prices.js
290684
290884
  init_dist();
290685
- import { existsSync as existsSync137, readdirSync as readdirSync50, readFileSync as readFileSync138 } from "node:fs";
290885
+ import { existsSync as existsSync138, readdirSync as readdirSync51, readFileSync as readFileSync138 } from "node:fs";
290686
290886
  import { join as join161 } from "node:path";
290687
290887
 
290688
290888
  // packages/cli/dist/commands/prices-refresh.js
290689
290889
  init_dist();
290690
- import { existsSync as existsSync136, mkdirSync as mkdirSync75, readdirSync as readdirSync49, readFileSync as readFileSync137, writeFileSync as writeFileSync74 } from "node:fs";
290890
+ import { existsSync as existsSync137, mkdirSync as mkdirSync75, readdirSync as readdirSync50, readFileSync as readFileSync137, writeFileSync as writeFileSync74 } from "node:fs";
290691
290891
  import { join as join160 } from "node:path";
290692
290892
  var FetchError = class extends Error {
290693
290893
  constructor(message) {
@@ -290909,9 +291109,9 @@ function vendorFromSnapshotName(name) {
290909
291109
  return match[2] ?? "anthropic";
290910
291110
  }
290911
291111
  function latestSnapshotPath(snapshotDir, vendor) {
290912
- if (!existsSync136(snapshotDir))
291112
+ if (!existsSync137(snapshotDir))
290913
291113
  return null;
290914
- const snaps = readdirSync49(snapshotDir).filter((name) => SNAPSHOT_RE.test(name) && vendorFromSnapshotName(name) === vendor).sort().map((name) => join160(snapshotDir, name));
291114
+ const snaps = readdirSync50(snapshotDir).filter((name) => SNAPSHOT_RE.test(name) && vendorFromSnapshotName(name) === vendor).sort().map((name) => join160(snapshotDir, name));
290915
291115
  return snaps[snaps.length - 1] ?? null;
290916
291116
  }
290917
291117
  function readPrices(path) {
@@ -291118,10 +291318,10 @@ async function pricesRefreshCommand(args, deps = {}) {
291118
291318
  // packages/cli/dist/commands/prices.js
291119
291319
  function loadSnapshots() {
291120
291320
  const dir = join161(repoRoot2(), "lib", "prices");
291121
- if (!existsSync137(dir)) {
291321
+ if (!existsSync138(dir)) {
291122
291322
  throw new Error(`no price snapshots found in ${dir}; run \`roll prices refresh\``);
291123
291323
  }
291124
- const files = readdirSync50(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
291324
+ const files = readdirSync51(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
291125
291325
  return files.map((name) => {
291126
291326
  const data = JSON.parse(readFileSync138(join161(dir, name), "utf8"));
291127
291327
  for (const key of ["version", "effective_at", "source_url", "prices"]) {
@@ -291302,7 +291502,7 @@ init_dist3();
291302
291502
  init_dist();
291303
291503
  init_render();
291304
291504
  import { execFileSync as execFileSync35 } from "node:child_process";
291305
- import { mkdirSync as mkdirSync79, readFileSync as readFileSync142, writeFileSync as writeFileSync78, existsSync as existsSync141 } from "node:fs";
291505
+ import { mkdirSync as mkdirSync79, readFileSync as readFileSync142, writeFileSync as writeFileSync78, existsSync as existsSync142 } from "node:fs";
291306
291506
  import { dirname as dirname78, join as join165 } from "node:path";
291307
291507
 
291308
291508
  // packages/cli/dist/lib/release-consistency.js
@@ -291311,7 +291511,7 @@ init_dist3();
291311
291511
  init_dist();
291312
291512
  init_render();
291313
291513
  import { execFileSync as execFileSync34 } from "node:child_process";
291314
- import { existsSync as existsSync139, mkdirSync as mkdirSync77, readFileSync as readFileSync140, readdirSync as readdirSync52, statSync as statSync39, writeFileSync as writeFileSync76 } from "node:fs";
291514
+ import { existsSync as existsSync140, mkdirSync as mkdirSync77, readFileSync as readFileSync140, readdirSync as readdirSync53, statSync as statSync39, writeFileSync as writeFileSync76 } from "node:fs";
291315
291515
  import { dirname as dirname76, join as join163 } from "node:path";
291316
291516
 
291317
291517
  // packages/cli/dist/lib/consistency-audit.js
@@ -291319,7 +291519,7 @@ init_dist2();
291319
291519
  init_dist3();
291320
291520
  init_dist();
291321
291521
  import { execFile as execFile19 } from "node:child_process";
291322
- import { existsSync as existsSync138, mkdirSync as mkdirSync76, readFileSync as readFileSync139, readdirSync as readdirSync51, writeFileSync as writeFileSync75 } from "node:fs";
291522
+ import { existsSync as existsSync139, mkdirSync as mkdirSync76, readFileSync as readFileSync139, readdirSync as readdirSync52, writeFileSync as writeFileSync75 } from "node:fs";
291323
291523
  import { dirname as dirname75, join as join162 } from "node:path";
291324
291524
  import { promisify as promisify18 } from "node:util";
291325
291525
  init_dist();
@@ -291328,7 +291528,7 @@ var PROBE_CAP = 20;
291328
291528
  var COUNT_WINDOW_SEC = 72 * 3600;
291329
291529
  var execFileAsync18 = promisify18(execFile19);
291330
291530
  function readJsonl(path) {
291331
- if (!existsSync138(path))
291531
+ if (!existsSync139(path))
291332
291532
  return [];
291333
291533
  const out3 = [];
291334
291534
  for (const line of readFileSync139(path, "utf8").split("\n")) {
@@ -291344,7 +291544,7 @@ function readJsonl(path) {
291344
291544
  return out3;
291345
291545
  }
291346
291546
  function readJson(path) {
291347
- if (!existsSync138(path))
291547
+ if (!existsSync139(path))
291348
291548
  return null;
291349
291549
  try {
291350
291550
  const parsed = JSON.parse(readFileSync139(path, "utf8"));
@@ -291372,10 +291572,10 @@ function hasScreenshotArtifact(reportPath) {
291372
291572
  }
291373
291573
  }
291374
291574
  const dir = join162(runDir, "screenshots");
291375
- if (!existsSync138(dir))
291575
+ if (!existsSync139(dir))
291376
291576
  return false;
291377
291577
  try {
291378
- return readdirSync51(dir).some((name) => /\.png$/i.test(name));
291578
+ return readdirSync52(dir).some((name) => /\.png$/i.test(name));
291379
291579
  } catch {
291380
291580
  return false;
291381
291581
  }
@@ -291409,7 +291609,7 @@ async function gatherAuditSnapshot(projectPath3, runtimeDir11, deps = {}) {
291409
291609
  const snapshot = emptyAuditSnapshot(nowSec, TERMINAL_SCHEMA_EPOCH_SEC);
291410
291610
  const skipped2 = [];
291411
291611
  const backlogPath = join162(projectPath3, ".roll", "backlog.md");
291412
- if (existsSync138(backlogPath)) {
291612
+ if (existsSync139(backlogPath)) {
291413
291613
  snapshot.backlog = parseBacklog(readFileSync139(backlogPath, "utf8")).map((r) => ({ id: r.id, status: r.status }));
291414
291614
  }
291415
291615
  snapshot.index = readIndex(projectPath3);
@@ -291418,7 +291618,7 @@ async function gatherAuditSnapshot(projectPath3, runtimeDir11, deps = {}) {
291418
291618
  let eventFailed = 0;
291419
291619
  const terminal = [];
291420
291620
  const eventsPath4 = join162(runtimeDir11, "events.ndjson");
291421
- if (existsSync138(eventsPath4)) {
291621
+ if (existsSync139(eventsPath4)) {
291422
291622
  for (const line of readFileSync139(eventsPath4, "utf8").split("\n")) {
291423
291623
  const e = parseEventLine(line);
291424
291624
  if (e === null)
@@ -291441,22 +291641,22 @@ async function gatherAuditSnapshot(projectPath3, runtimeDir11, deps = {}) {
291441
291641
  if (!row2.status.includes("\u2705"))
291442
291642
  continue;
291443
291643
  const card = cardArchiveDir(projectPath3, row2.id);
291444
- if (!existsSync138(card))
291644
+ if (!existsSync139(card))
291445
291645
  continue;
291446
291646
  const reportPath = join162(card, "latest", reportFileName(row2.id));
291447
291647
  const specPath = join162(card, "spec.md");
291448
291648
  let noVisualSurface = false;
291449
- if (existsSync138(specPath)) {
291649
+ if (existsSync139(specPath)) {
291450
291650
  try {
291451
291651
  noVisualSurface = !hasVisualEvidenceAc(readFileSync139(specPath, "utf8"));
291452
291652
  } catch {
291453
291653
  }
291454
291654
  }
291455
291655
  snapshot.attest[row2.id] = {
291456
- report: existsSync138(reportPath),
291457
- acMap: existsSync138(join162(card, "ac-map.json")),
291458
- visualEvidence: existsSync138(reportPath) ? hasScreenshotArtifact(reportPath) : false,
291459
- machineSkip: existsSync138(reportPath) ? hasMachineCaptureSkip2(reportPath) : false,
291656
+ report: existsSync139(reportPath),
291657
+ acMap: existsSync139(join162(card, "ac-map.json")),
291658
+ visualEvidence: existsSync139(reportPath) ? hasScreenshotArtifact(reportPath) : false,
291659
+ machineSkip: existsSync139(reportPath) ? hasMachineCaptureSkip2(reportPath) : false,
291460
291660
  noVisualSurface
291461
291661
  };
291462
291662
  }
@@ -291628,7 +291828,7 @@ function walkTextFiles(projectDir, relDir, extensions) {
291628
291828
  const dirAbs = join163(projectDir, dirRel);
291629
291829
  let entries;
291630
291830
  try {
291631
- entries = readdirSync52(dirAbs, { withFileTypes: true });
291831
+ entries = readdirSync53(dirAbs, { withFileTypes: true });
291632
291832
  } catch {
291633
291833
  return;
291634
291834
  }
@@ -291837,11 +292037,11 @@ function manualDirectMainDelivery(projectDir, id, mergeShas) {
291837
292037
  function findCardSpec(projectDir, id) {
291838
292038
  const featuresDir = join163(projectDir, ".roll", "features");
291839
292039
  try {
291840
- for (const epic of readdirSync52(featuresDir, { withFileTypes: true })) {
292040
+ for (const epic of readdirSync53(featuresDir, { withFileTypes: true })) {
291841
292041
  if (!epic.isDirectory())
291842
292042
  continue;
291843
292043
  const spec = join163(featuresDir, epic.name, id, "spec.md");
291844
- if (existsSync139(spec))
292044
+ if (existsSync140(spec))
291845
292045
  return spec;
291846
292046
  }
291847
292047
  } catch {
@@ -291860,12 +292060,12 @@ function cardChangelogExempt(projectDir, id) {
291860
292060
  }
291861
292061
  function checkFeaturesCatalog(projectDir) {
291862
292062
  const backlog = join163(projectDir, ".roll", "backlog.md");
291863
- if (!existsSync139(backlog))
292063
+ if (!existsSync140(backlog))
291864
292064
  return { status: "pass", gaps: [] };
291865
292065
  const backlogText = readText(backlog);
291866
292066
  const gaps = [];
291867
292067
  const features = join163(projectDir, ".roll", "features.md");
291868
- if (existsSync139(features)) {
292068
+ if (existsSync140(features)) {
291869
292069
  const featuresText = readText(features);
291870
292070
  for (const featName of readDoneFeatures(backlogText).keys()) {
291871
292071
  const escaped = escapeRegExp4(featName);
@@ -291889,7 +292089,7 @@ function checkFeaturesCatalog(projectDir) {
291889
292089
  }
291890
292090
  function checkTruthLive(projectDir) {
291891
292091
  const backlog = join163(projectDir, ".roll", "backlog.md");
291892
- if (!existsSync139(backlog))
292092
+ if (!existsSync140(backlog))
291893
292093
  return { status: "pass", gaps: [] };
291894
292094
  const backlogText = readText(backlog);
291895
292095
  const facts = backlogRowFacts(backlogText);
@@ -291934,17 +292134,17 @@ function checkTruthLive(projectDir) {
291934
292134
  function checkCards(projectDir) {
291935
292135
  const backlog = join163(projectDir, ".roll", "backlog.md");
291936
292136
  const featuresDir = join163(projectDir, ".roll", "features");
291937
- if (!existsSync139(backlog) || !existsSync139(featuresDir))
292137
+ if (!existsSync140(backlog) || !existsSync140(featuresDir))
291938
292138
  return { status: "pass", gaps: [] };
291939
292139
  const cardEpic = /* @__PURE__ */ new Map();
291940
292140
  try {
291941
- for (const epic of readdirSync52(featuresDir, { withFileTypes: true })) {
292141
+ for (const epic of readdirSync53(featuresDir, { withFileTypes: true })) {
291942
292142
  if (!epic.isDirectory())
291943
292143
  continue;
291944
- for (const card of readdirSync52(join163(featuresDir, epic.name), { withFileTypes: true })) {
292144
+ for (const card of readdirSync53(join163(featuresDir, epic.name), { withFileTypes: true })) {
291945
292145
  if (!card.isDirectory())
291946
292146
  continue;
291947
- if (existsSync139(join163(featuresDir, epic.name, card.name, "spec.md")) && !cardEpic.has(card.name)) {
292147
+ if (existsSync140(join163(featuresDir, epic.name, card.name, "spec.md")) && !cardEpic.has(card.name)) {
291948
292148
  cardEpic.set(card.name, epic.name);
291949
292149
  }
291950
292150
  }
@@ -291979,12 +292179,12 @@ function checkCards(projectDir) {
291979
292179
  const ev = /\[evidence\]\(([^)]+)\)/.exec(line);
291980
292180
  if (ev !== null) {
291981
292181
  const target = (ev[1] ?? "").replace(/^\.roll\//, "");
291982
- if (!existsSync139(join163(projectDir, ".roll", target))) {
292182
+ if (!existsSync140(join163(projectDir, ".roll", target))) {
291983
292183
  gaps.push(`Backlog row ${id} evidence link is broken: ${ev[1] ?? ""}`);
291984
292184
  }
291985
292185
  } else if (line.includes(STATUS_MARKER.done)) {
291986
292186
  const epic = cardEpic.get(id) ?? "";
291987
- if (!existsSync139(join163(featuresDir, epic, id, "latest", `${id}-report.html`))) {
292187
+ if (!existsSync140(join163(featuresDir, epic, id, "latest", `${id}-report.html`))) {
291988
292188
  if (hasAcBlock(epic, id)) {
291989
292189
  gaps.push(`Done backlog row ${id} has ACs but no attest report`);
291990
292190
  } else {
@@ -292007,7 +292207,7 @@ function checkCards(projectDir) {
292007
292207
  function checkDocs(projectDir) {
292008
292208
  const gaps = checkTopLevelCommands(activeDocsFiles(projectDir), DOC_RETIRED_TOP_LEVEL_COMMANDS);
292009
292209
  const changelogPath = join163(projectDir, "CHANGELOG.md");
292010
- if (existsSync139(changelogPath)) {
292210
+ if (existsSync140(changelogPath)) {
292011
292211
  const changelog = readText(changelogPath);
292012
292212
  for (const id of releaseDeltaCardIds(projectDir)) {
292013
292213
  const base = id.replace(/[a-z]$/, "");
@@ -292082,7 +292282,7 @@ function checkSite(projectDir) {
292082
292282
  const gaps = checkTopLevelCommands(activeSiteFiles(projectDir), SITE_HIDDEN_TOP_LEVEL_COMMANDS);
292083
292283
  const siteJs = join163(projectDir, "site", "roll-data.js");
292084
292284
  const backlog = join163(projectDir, ".roll", "backlog.md");
292085
- if (!existsSync139(siteJs) || !existsSync139(backlog))
292285
+ if (!existsSync140(siteJs) || !existsSync140(backlog))
292086
292286
  return { status: gaps.length === 0 ? "pass" : "fail", gaps };
292087
292287
  const siteText = readText(siteJs);
292088
292288
  const seenGuideRefs = /* @__PURE__ */ new Set();
@@ -292091,7 +292291,7 @@ function checkSite(projectDir) {
292091
292291
  if (seenGuideRefs.has(rel2))
292092
292292
  continue;
292093
292293
  seenGuideRefs.add(rel2);
292094
- if (!existsSync139(join163(projectDir, rel2))) {
292294
+ if (!existsSync140(join163(projectDir, rel2))) {
292095
292295
  gaps.push(`site/roll-data.js links a guide that does not exist: ${rel2}`);
292096
292296
  }
292097
292297
  }
@@ -292130,7 +292330,7 @@ function checkSite(projectDir) {
292130
292330
  }
292131
292331
  function listFiles2(dir) {
292132
292332
  try {
292133
- return readdirSync52(dir).filter((n) => {
292333
+ return readdirSync53(dir).filter((n) => {
292134
292334
  try {
292135
292335
  return statSync39(join163(dir, n)).isFile();
292136
292336
  } catch {
@@ -292145,7 +292345,7 @@ function checkI18n(projectDir) {
292145
292345
  const gaps = [];
292146
292346
  const guideEn = join163(projectDir, "guide", "en");
292147
292347
  const guideZh = join163(projectDir, "guide", "zh");
292148
- if (existsSync139(guideEn) && existsSync139(guideZh)) {
292348
+ if (existsSync140(guideEn) && existsSync140(guideZh)) {
292149
292349
  const enFiles = new Set(listFiles2(guideEn));
292150
292350
  const zhFiles = new Set(listFiles2(guideZh));
292151
292351
  const enOnly = [...enFiles].filter((f) => !zhFiles.has(f)).sort();
@@ -292156,12 +292356,12 @@ function checkI18n(projectDir) {
292156
292356
  gaps.push(`guide/zh/${f} has no corresponding guide/en/${f}`);
292157
292357
  }
292158
292358
  const i18nDir = join163(projectDir, "lib", "i18n");
292159
- if (existsSync139(i18nDir)) {
292359
+ if (existsSync140(i18nDir)) {
292160
292360
  const keysEn = /* @__PURE__ */ new Set();
292161
292361
  const keysZh = /* @__PURE__ */ new Set();
292162
292362
  let shFiles = [];
292163
292363
  try {
292164
- shFiles = readdirSync52(i18nDir).filter((n) => n.endsWith(".sh")).sort();
292364
+ shFiles = readdirSync53(i18nDir).filter((n) => n.endsWith(".sh")).sort();
292165
292365
  } catch {
292166
292366
  shFiles = [];
292167
292367
  }
@@ -292201,7 +292401,7 @@ function rglobBats(dir) {
292201
292401
  const walk2 = (d) => {
292202
292402
  let entries = [];
292203
292403
  try {
292204
- entries = readdirSync52(d);
292404
+ entries = readdirSync53(d);
292205
292405
  } catch {
292206
292406
  return;
292207
292407
  }
@@ -292226,7 +292426,7 @@ function checkTests(projectDir) {
292226
292426
  const gaps = [];
292227
292427
  const backlog = join163(projectDir, ".roll", "backlog.md");
292228
292428
  const testsDir = join163(projectDir, "tests");
292229
- if (!existsSync139(backlog))
292429
+ if (!existsSync140(backlog))
292230
292430
  return { status: "pass", gaps: [] };
292231
292431
  const backlogText = readText(backlog);
292232
292432
  const allFeatures = /* @__PURE__ */ new Set();
@@ -292251,7 +292451,7 @@ function checkTests(projectDir) {
292251
292451
  doneFeatures.push(currentFeature);
292252
292452
  }
292253
292453
  }
292254
- const testFiles = existsSync139(testsDir) ? rglobBats(testsDir) : [];
292454
+ const testFiles = existsSync140(testsDir) ? rglobBats(testsDir) : [];
292255
292455
  if (testFiles.length === 0)
292256
292456
  return { status: "pass", gaps: [] };
292257
292457
  for (const feat of doneFeatures) {
@@ -292603,7 +292803,7 @@ function writeReleaseTestProof(exec) {
292603
292803
  function assertTestProofFresh(exec) {
292604
292804
  const cwd = exec("git", ["rev-parse", "--show-toplevel"]).trim();
292605
292805
  const proofPath = join165(cwd, ".roll", "last-test-pass");
292606
- if (!existsSync141(proofPath)) {
292806
+ if (!existsSync142(proofPath)) {
292607
292807
  throw new Error("test-pass proof missing \u2014 run `roll test` before committing");
292608
292808
  }
292609
292809
  const body = readFileSync142(proofPath, "utf8");
@@ -292747,7 +292947,7 @@ function realReleaseDeps() {
292747
292947
  commitPushWithGate({
292748
292948
  branch,
292749
292949
  message,
292750
- rollManaged: existsSync141(join165(cwd, ".roll")),
292950
+ rollManaged: existsSync142(join165(cwd, ".roll")),
292751
292951
  exec: (cmd, args) => execFileSync35(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 6e5 })
292752
292952
  });
292753
292953
  },
@@ -293072,13 +293272,13 @@ ${c("dim", "\u2192 Running the golden-path showcase (real models; its verdict do
293072
293272
  init_dist();
293073
293273
  init_render();
293074
293274
  import { spawnSync as spawnSync19 } from "node:child_process";
293075
- import { existsSync as existsSync143, lstatSync as lstatSync13, mkdirSync as mkdirSync81, readFileSync as readFileSync144, readdirSync as readdirSync55, readlinkSync as readlinkSync3, statSync as statSync42 } from "node:fs";
293275
+ import { existsSync as existsSync144, lstatSync as lstatSync13, mkdirSync as mkdirSync81, readFileSync as readFileSync144, readdirSync as readdirSync56, readlinkSync as readlinkSync3, statSync as statSync42 } from "node:fs";
293076
293276
  import { join as join167 } from "node:path";
293077
293277
 
293078
293278
  // packages/cli/dist/lib/roll-capture-install.js
293079
293279
  init_dist();
293080
293280
  import { spawnSync as spawnSync18 } from "node:child_process";
293081
- import { chmodSync as chmodSync2, existsSync as existsSync142, lstatSync as lstatSync12, mkdirSync as mkdirSync80, mkdtempSync as mkdtempSync9, readdirSync as readdirSync54, readFileSync as readFileSync143, renameSync as renameSync30, rmSync as rmSync27, statSync as statSync41, writeFileSync as writeFileSync79 } from "node:fs";
293281
+ import { chmodSync as chmodSync2, existsSync as existsSync143, lstatSync as lstatSync12, mkdirSync as mkdirSync80, mkdtempSync as mkdtempSync9, readdirSync as readdirSync55, readFileSync as readFileSync143, renameSync as renameSync30, rmSync as rmSync28, statSync as statSync41, writeFileSync as writeFileSync79 } from "node:fs";
293082
293282
  import { homedir as homedir36 } from "node:os";
293083
293283
  import { join as join166 } from "node:path";
293084
293284
  var RELEASE_API = "https://api.github.com/repos/seanyao/roll-capture/releases/latest";
@@ -293105,7 +293305,7 @@ function defaultRollCaptureInstallDeps() {
293105
293305
  home: homedir36(),
293106
293306
  uid: typeof process.getuid === "function" ? process.getuid() : void 0,
293107
293307
  ...platform3 === "darwin" && !isCi3(process.env) && process.env["ROLL_SKIP_CAPTURE_INSTALL"] !== "1" ? { hasAquaGUI: macosHasAquaGUI3(execFile20) } : {},
293108
- exists: existsSync142,
293308
+ exists: existsSync143,
293109
293309
  renamePath: renameSync30,
293110
293310
  execFile: execFile20,
293111
293311
  fetchLatestRelease: (timeoutMs) => defaultFetchLatestRelease(timeoutMs, process.env, execFile20),
@@ -293177,7 +293377,7 @@ async function installRollCapture(deps = defaultRollCaptureInstallDeps()) {
293177
293377
  } catch (error) {
293178
293378
  return manual(`install failed: ${errorMessage2(error)}`, release.tagName);
293179
293379
  } finally {
293180
- rmSync27(workDir, { recursive: true, force: true });
293380
+ rmSync28(workDir, { recursive: true, force: true });
293181
293381
  }
293182
293382
  }
293183
293383
  function renderRollCaptureInstallResult(result2, lang10) {
@@ -293313,7 +293513,7 @@ function macosHasAquaGUI3(execFile20) {
293313
293513
  }
293314
293514
  function findExtractedApp(dir) {
293315
293515
  try {
293316
- for (const name of readdirSync54(dir)) {
293516
+ for (const name of readdirSync55(dir)) {
293317
293517
  const candidate = join166(dir, name);
293318
293518
  const st = lstatSync12(candidate);
293319
293519
  if (st.isSymbolicLink())
@@ -293334,7 +293534,7 @@ function findExtractedApp(dir) {
293334
293534
  function findBundleExecutable(appPath) {
293335
293535
  const macosDir = join166(appPath, "Contents", "MacOS");
293336
293536
  try {
293337
- for (const name of readdirSync54(macosDir)) {
293537
+ for (const name of readdirSync55(macosDir)) {
293338
293538
  const candidate = join166(macosDir, name);
293339
293539
  const st = lstatSync12(candidate);
293340
293540
  if (st.isSymbolicLink())
@@ -293351,7 +293551,7 @@ function replaceAppAtomically(app, targetApp, deps) {
293351
293551
  const renamePath = deps.renamePath ?? renameSync30;
293352
293552
  const backupApp = `${targetApp}.bak`;
293353
293553
  const hadExisting = lstatExists(targetApp);
293354
- rmSync27(backupApp, { recursive: true, force: true });
293554
+ rmSync28(backupApp, { recursive: true, force: true });
293355
293555
  if (!hadExisting) {
293356
293556
  renamePath(app, targetApp);
293357
293557
  return;
@@ -293363,10 +293563,10 @@ function replaceAppAtomically(app, targetApp, deps) {
293363
293563
  backupCreated = true;
293364
293564
  renamePath(app, targetApp);
293365
293565
  targetReplaced = true;
293366
- rmSync27(backupApp, { recursive: true, force: true });
293566
+ rmSync28(backupApp, { recursive: true, force: true });
293367
293567
  } catch (error) {
293368
293568
  if (targetReplaced)
293369
- rmSync27(targetApp, { recursive: true, force: true });
293569
+ rmSync28(targetApp, { recursive: true, force: true });
293370
293570
  if (backupCreated) {
293371
293571
  try {
293372
293572
  renamePath(backupApp, targetApp);
@@ -293542,7 +293742,7 @@ function setupSnapshot(watch) {
293542
293742
  function walk(dir, lines3) {
293543
293743
  let names;
293544
293744
  try {
293545
- names = readdirSync55(dir);
293745
+ names = readdirSync56(dir);
293546
293746
  } catch {
293547
293747
  return;
293548
293748
  }
@@ -293617,11 +293817,11 @@ function tmuxPresent() {
293617
293817
  function submoduleGuard() {
293618
293818
  const pkg = rollPkgDir();
293619
293819
  const skills = join167(pkg, "skills");
293620
- const hasGit = existsSync143(join167(pkg, ".git"));
293621
- const hasMods = existsSync143(join167(pkg, ".gitmodules"));
293820
+ const hasGit = existsSync144(join167(pkg, ".git"));
293821
+ const hasMods = existsSync144(join167(pkg, ".gitmodules"));
293622
293822
  let empty = true;
293623
293823
  try {
293624
- empty = readdirSync55(skills).length === 0;
293824
+ empty = readdirSync56(skills).length === 0;
293625
293825
  } catch {
293626
293826
  empty = true;
293627
293827
  }
@@ -293735,7 +293935,7 @@ async function setupCommand2(args) {
293735
293935
  return 1;
293736
293936
  }
293737
293937
  }
293738
- if (!existsSync143(rollPkgConventions())) {
293938
+ if (!existsSync144(rollPkgConventions())) {
293739
293939
  err13(m5("shared.convention_source_not_found_at_2", rollPkgConventions()));
293740
293940
  err13(m5("shared.run_this_from_the_roll_repo"));
293741
293941
  return 1;
@@ -293826,7 +294026,7 @@ init_showcase2();
293826
294026
  // packages/cli/dist/commands/test.js
293827
294027
  init_dist2();
293828
294028
  import { spawnSync as spawnSync20 } from "node:child_process";
293829
- import { appendFileSync as appendFileSync22, existsSync as existsSync144, mkdirSync as mkdirSync82, readFileSync as readFileSync145, rmSync as rmSync28, writeFileSync as writeFileSync80 } from "node:fs";
294029
+ import { appendFileSync as appendFileSync22, existsSync as existsSync145, mkdirSync as mkdirSync82, readFileSync as readFileSync145, rmSync as rmSync29, writeFileSync as writeFileSync80 } from "node:fs";
293830
294030
  import { dirname as dirname79, join as join168, resolve as resolve16 } from "node:path";
293831
294031
  function pal6() {
293832
294032
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
@@ -293848,7 +294048,7 @@ function currentEvidenceFrame() {
293848
294048
  if (raw === "")
293849
294049
  return null;
293850
294050
  const runDir = resolve16(raw);
293851
- if (!existsSync144(runDir))
294051
+ if (!existsSync145(runDir))
293852
294052
  return null;
293853
294053
  const frame = {
293854
294054
  runDir,
@@ -293905,7 +294105,7 @@ function appendRollTestEvidence(frame, cmd, argv, status2, stdout, stderr) {
293905
294105
  }
293906
294106
  function isolationGetType() {
293907
294107
  const file = join168(process.cwd(), ".roll", "local.yaml");
293908
- if (!existsSync144(file))
294108
+ if (!existsSync145(file))
293909
294109
  return "none";
293910
294110
  let text2;
293911
294111
  try {
@@ -293957,11 +294157,11 @@ function resetLockPath() {
293957
294157
  return ".roll/.iso-reset.lock";
293958
294158
  }
293959
294159
  function resetLockHeld() {
293960
- return existsSync144(resetLockPath());
294160
+ return existsSync145(resetLockPath());
293961
294161
  }
293962
294162
  function resetAcquireLock() {
293963
294163
  const lock = resetLockPath();
293964
- if (existsSync144(lock))
294164
+ if (existsSync145(lock))
293965
294165
  return false;
293966
294166
  mkdirSync82(dirname79(lock), { recursive: true });
293967
294167
  writeFileSync80(lock, `${process.pid}
@@ -293969,7 +294169,7 @@ function resetAcquireLock() {
293969
294169
  return true;
293970
294170
  }
293971
294171
  function resetReleaseLock() {
293972
- rmSync28(resetLockPath(), { force: true });
294172
+ rmSync29(resetLockPath(), { force: true });
293973
294173
  }
293974
294174
  function runForward(cmd, argv) {
293975
294175
  const frame = currentEvidenceFrame();
@@ -293987,7 +294187,7 @@ function runForward(cmd, argv) {
293987
294187
  }
293988
294188
  function readTestScript(cwd) {
293989
294189
  const p = join168(cwd, "package.json");
293990
- if (!existsSync144(p))
294190
+ if (!existsSync145(p))
293991
294191
  return void 0;
293992
294192
  try {
293993
294193
  const pkg = JSON.parse(readFileSync145(p, "utf8"));
@@ -294001,7 +294201,7 @@ function detectVitestVersion(cwd) {
294001
294201
  let dir = cwd;
294002
294202
  for (let i = 0; i < 6; i++) {
294003
294203
  const vp = join168(dir, "node_modules", "vitest", "package.json");
294004
- if (existsSync144(vp)) {
294204
+ if (existsSync145(vp)) {
294005
294205
  try {
294006
294206
  const v = JSON.parse(readFileSync145(vp, "utf8")).version;
294007
294207
  if (typeof v === "string")
@@ -294039,14 +294239,14 @@ function writeTestProof(cwd, mode, command) {
294039
294239
  function ensureSkillsSubmoduleReady() {
294040
294240
  const pkg = rollPkgDir();
294041
294241
  const required2 = join168(pkg, "skills", "roll-onboard", "SKILL.md");
294042
- if (existsSync144(required2))
294242
+ if (existsSync145(required2))
294043
294243
  return true;
294044
- if (existsSync144(join168(pkg, ".git")) && existsSync144(join168(pkg, ".gitmodules"))) {
294244
+ if (existsSync145(join168(pkg, ".git")) && existsSync145(join168(pkg, ".gitmodules"))) {
294045
294245
  spawnSync20("git", ["submodule", "update", "--init", "--recursive", "--quiet", "skills"], {
294046
294246
  cwd: pkg,
294047
294247
  stdio: "ignore"
294048
294248
  });
294049
- if (existsSync144(required2))
294249
+ if (existsSync145(required2))
294050
294250
  return true;
294051
294251
  }
294052
294252
  err14("roll test: skills submodule is empty");
@@ -294055,7 +294255,7 @@ function ensureSkillsSubmoduleReady() {
294055
294255
  }
294056
294256
  function ensureNoneIsolation() {
294057
294257
  const type = isolationGetType();
294058
- if (type === "none" && !existsSync144(join168(process.cwd(), ".roll", "local.yaml"))) {
294258
+ if (type === "none" && !existsSync145(join168(process.cwd(), ".roll", "local.yaml"))) {
294059
294259
  infoErr("isolation: no test_isolation config, falling back to type=none (host)");
294060
294260
  }
294061
294261
  if (!SUPPORTED_TYPES.includes(type)) {
@@ -294170,7 +294370,7 @@ function testCommand(args) {
294170
294370
  }
294171
294371
  const cwd = process.cwd();
294172
294372
  const resolution = resolveGateCommand({
294173
- hasPackageJson: existsSync144(join168(cwd, "package.json")),
294373
+ hasPackageJson: existsSync145(join168(cwd, "package.json")),
294174
294374
  testScript: readTestScript(cwd),
294175
294375
  vitestVersion: detectVitestVersion(cwd)
294176
294376
  });
@@ -294596,7 +294796,7 @@ function emitRepair(json, outcome) {
294596
294796
  // packages/cli/dist/commands/truth.js
294597
294797
  init_dist();
294598
294798
  init_dist2();
294599
- import { statSync as statSync43, readFileSync as readFileSync147, writeFileSync as writeFileSync82, mkdirSync as mkdirSync84, existsSync as existsSync145 } from "node:fs";
294799
+ import { statSync as statSync43, readFileSync as readFileSync147, writeFileSync as writeFileSync82, mkdirSync as mkdirSync84, existsSync as existsSync146 } from "node:fs";
294600
294800
  import { dirname as dirname81, join as join171 } from "node:path";
294601
294801
  var TRUTH_USAGE = "Usage: roll truth <command>\n query <storyId> Deterministic delivery-truth query (structured, zero markdown parse).\n audit Bidirectional drift audit: backlog Done \u2194 projection truth.\n\u547D\u4EE4:\n query <storyId> \u7ED3\u6784\u5316\u4EA4\u4ED8\u771F\u76F8\u67E5\u8BE2\uFF08\u786E\u5B9A\u6027\uFF0C\u4E0D\u89E3\u6790 markdown\uFF09\n audit \u53CC\u5411\u6F02\u79FB\u5BA1\u8BA1\uFF1Abacklog Done \u2194 \u6295\u5F71\u771F\u76F8";
294602
294802
  function formatTruth(t2, lang10) {
@@ -294644,7 +294844,7 @@ function truthAuditCommand(args, lang10) {
294644
294844
  const cwd = process.cwd();
294645
294845
  const nowSec = Math.floor(Date.now() / 1e3);
294646
294846
  const backlogPath = join171(cwd, ".roll", "backlog.md");
294647
- const backlogRows = existsSync145(backlogPath) ? parseBacklog(readFileSync147(backlogPath, "utf8")).map((r) => ({ id: r.id, status: r.status })) : [];
294847
+ const backlogRows = existsSync146(backlogPath) ? parseBacklog(readFileSync147(backlogPath, "utf8")).map((r) => ({ id: r.id, status: r.status })) : [];
294648
294848
  const deliveries = ensureDeliveriesFresh(cwd, nodeFreshnessPort4, nodeExecPort);
294649
294849
  const snapshot = emptyAuditSnapshot(nowSec, TERMINAL_SCHEMA_EPOCH_SEC);
294650
294850
  snapshot.backlog = backlogRows;
@@ -294729,7 +294929,7 @@ function truthCommand(args) {
294729
294929
 
294730
294930
  // packages/cli/dist/commands/tune.js
294731
294931
  init_dist2();
294732
- import { existsSync as existsSync146, readFileSync as readFileSync148 } from "node:fs";
294932
+ import { existsSync as existsSync147, readFileSync as readFileSync148 } from "node:fs";
294733
294933
  import { join as join172 } from "node:path";
294734
294934
  function projectPath2() {
294735
294935
  return (process.env["ROLL_MAIN_PROJECT"] ?? "").trim() || process.cwd();
@@ -294738,7 +294938,7 @@ function str6(v, dflt = "") {
294738
294938
  return typeof v === "string" ? v : dflt;
294739
294939
  }
294740
294940
  function parseJsonl(path) {
294741
- if (!existsSync146(path))
294941
+ if (!existsSync147(path))
294742
294942
  return [];
294743
294943
  let text2;
294744
294944
  try {
@@ -294914,7 +295114,7 @@ function tuneCommand(argv) {
294914
295114
  // packages/cli/dist/commands/update.js
294915
295115
  init_dist();
294916
295116
  import { spawnSync as spawnSync21 } from "node:child_process";
294917
- import { existsSync as existsSync147, mkdtempSync as mkdtempSync10, readFileSync as readFileSync149, rmSync as rmSync29 } from "node:fs";
295117
+ import { existsSync as existsSync148, mkdtempSync as mkdtempSync10, readFileSync as readFileSync149, rmSync as rmSync30 } from "node:fs";
294918
295118
  import { tmpdir as tmpdir14 } from "node:os";
294919
295119
  import { join as join173 } from "node:path";
294920
295120
  function pal7() {
@@ -295004,7 +295204,7 @@ function downloadAndInstallCurl(tag) {
295004
295204
  }
295005
295205
  return { ok: true, newVersion: treeVersion(extractDir) };
295006
295206
  } finally {
295007
- rmSync29(tmpDir, { recursive: true, force: true });
295207
+ rmSync30(tmpDir, { recursive: true, force: true });
295008
295208
  }
295009
295209
  }
295010
295210
  function checkInstalledVersionOrRetry() {
@@ -295024,11 +295224,11 @@ function checkInstalledVersionOrRetry() {
295024
295224
  }
295025
295225
  }
295026
295226
  function invalidateUpdateCache() {
295027
- rmSync29(join173(rollHome(), ".update-check"), { force: true });
295227
+ rmSync30(join173(rollHome(), ".update-check"), { force: true });
295028
295228
  }
295029
295229
  function showChangelog() {
295030
295230
  const changelog = join173(rollPkgDir(), "CHANGELOG.md");
295031
- if (!existsSync147(changelog))
295231
+ if (!existsSync148(changelog))
295032
295232
  return;
295033
295233
  const { BOLD: BOLD2, CYAN, NC } = pal7();
295034
295234
  process.stdout.write(`${BOLD2}${m6("changelog.heading")}:${NC}
@@ -295059,7 +295259,7 @@ async function updateCommand(args) {
295059
295259
  info5(m6("update.current_version", rollVersion()));
295060
295260
  let installMethod = "npm";
295061
295261
  const methodFile = join173(rollPkgDir(), ".install-method");
295062
- if (existsSync147(methodFile)) {
295262
+ if (existsSync148(methodFile)) {
295063
295263
  installMethod = readFileSync149(methodFile, "utf8").trim() || "npm";
295064
295264
  }
295065
295265
  if (installMethod === "curl") {