@seanyao/roll 3.611.3 → 3.612.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/roll.mjs CHANGED
@@ -1528,12 +1528,12 @@ function appendRowsToTable(content, rows) {
1528
1528
  }
1529
1529
  }
1530
1530
  if (sepIdx === -1) {
1531
- const tail = rows.join("\n");
1531
+ const tail2 = rows.join("\n");
1532
1532
  if (content !== "" && !content.endsWith("\n"))
1533
1533
  return `${content}
1534
- ${tail}
1534
+ ${tail2}
1535
1535
  `;
1536
- return `${content}${tail}
1536
+ return `${content}${tail2}
1537
1537
  `;
1538
1538
  }
1539
1539
  let insertAt = sepIdx + 1;
@@ -4575,14 +4575,14 @@ function appendBacklogRow(content, row2) {
4575
4575
  }
4576
4576
  const at = anchor !== -1 ? anchor : lastTableRow;
4577
4577
  if (at === -1) {
4578
- const tail = `
4578
+ const tail2 = `
4579
4579
  ## Epic: ${row2.epic}
4580
4580
 
4581
4581
  | ID | Description | Status |
4582
4582
  |----|----|----|
4583
4583
  ${line}
4584
4584
  `;
4585
- return { content: content.replace(/\n*$/, "\n") + tail, appended: true };
4585
+ return { content: content.replace(/\n*$/, "\n") + tail2, appended: true };
4586
4586
  }
4587
4587
  lines2.splice(at + 1, 0, line);
4588
4588
  return { content: lines2.join("\n"), appended: true };
@@ -5136,8 +5136,8 @@ function parseAlertLogTail(body, n) {
5136
5136
  continue;
5137
5137
  }
5138
5138
  }
5139
- const tail = n >= rows.length ? rows : rows.slice(rows.length - n);
5140
- return tail.reverse();
5139
+ const tail2 = n >= rows.length ? rows : rows.slice(rows.length - n);
5140
+ return tail2.reverse();
5141
5141
  }
5142
5142
 
5143
5143
  // packages/core/dist/loop/ci-loop.js
@@ -5650,12 +5650,12 @@ function renderRebaseAttempts(timestamps) {
5650
5650
  var DEAD_TICK_NOTES = /* @__PURE__ */ new Set(["gh_error", "lock_fail"]);
5651
5651
  function deadTickVerdict(input) {
5652
5652
  const n = input.threshold ?? 5;
5653
- const tail = input.recentNotes.slice(-n);
5653
+ const tail2 = input.recentNotes.slice(-n);
5654
5654
  const last = input.recentNotes[input.recentNotes.length - 1];
5655
5655
  const lastAbnormal = last !== void 0 && DEAD_TICK_NOTES.has(last);
5656
5656
  if (input.alreadyAlerted)
5657
5657
  return lastAbnormal ? null : "recovered";
5658
- if (tail.length >= n && tail.every((t2) => DEAD_TICK_NOTES.has(t2)))
5658
+ if (tail2.length >= n && tail2.every((t2) => DEAD_TICK_NOTES.has(t2)))
5659
5659
  return "alert";
5660
5660
  return null;
5661
5661
  }
@@ -6310,15 +6310,15 @@ function boundTranscript(raw, opts = {}) {
6310
6310
  return { text: raw, truncated: false, totalLen, shownLen: totalLen };
6311
6311
  }
6312
6312
  const head = raw.slice(0, headLen);
6313
- const tail = raw.slice(totalLen - tailLen);
6314
- const elided = totalLen - head.length - tail.length;
6313
+ const tail2 = raw.slice(totalLen - tailLen);
6314
+ const elided = totalLen - head.length - tail2.length;
6315
6315
  const marker = `
6316
6316
 
6317
6317
  \u2026 \u4E2D\u95F4\u7701\u7565 ${elided} \u5B57\u7B26 / ${elided} chars elided\uFF08\u5B8C\u6574\u539F\u4EF6\u89C1\u4E0B\u65B9\u7D22\u5F15 \xB7 full log at the indexed path\uFF09\u2026
6318
6318
 
6319
6319
  `;
6320
- const text = head + marker + tail;
6321
- return { text, truncated: true, totalLen, shownLen: head.length + tail.length };
6320
+ const text = head + marker + tail2;
6321
+ return { text, truncated: true, totalLen, shownLen: head.length + tail2.length };
6322
6322
  }
6323
6323
 
6324
6324
  // packages/core/dist/policy/engine.js
@@ -7048,6 +7048,7 @@ function planShip(facts) {
7048
7048
  var HEADING = /^(#{2,3})\s+(.*)$/;
7049
7049
  var STORY_ID = /\b((?:US|FIX|REFACTOR)-[A-Z0-9]+(?:-[A-Z0-9]+)*?-?\d+[a-z]?)\b/;
7050
7050
  var AC_OPEN = /^\*\*AC:\*\*\s*$/;
7051
+ var AC_HEADING = /^#{2,4}\s+(?:Acceptance Criteria|验收标准)\s*$/i;
7051
7052
  var ITEM = /^- \[([ xX])\]\s?(.*)$/;
7052
7053
  var CONTINUATION = /^\s{2,}(\S.*)$/;
7053
7054
  function parseAcBlocks(markdown) {
@@ -7065,6 +7066,11 @@ function parseAcBlocks(markdown) {
7065
7066
  };
7066
7067
  for (const raw of lines2) {
7067
7068
  const line = raw.replace(/\r$/, "");
7069
+ if (AC_HEADING.test(line)) {
7070
+ closeBlock();
7071
+ block = [];
7072
+ continue;
7073
+ }
7068
7074
  const h = HEADING.exec(line);
7069
7075
  if (h) {
7070
7076
  closeBlock();
@@ -7423,6 +7429,17 @@ function selfCaptureBlock(refs) {
7423
7429
  ${figs}
7424
7430
  </section>`;
7425
7431
  }
7432
+ function captureSkipBlock(skips) {
7433
+ if (skips === void 0 || skips.length === 0)
7434
+ return "";
7435
+ const rows = skips.map((s) => {
7436
+ const json = JSON.stringify({ kind: s.kind, out: s.out, taken: false, skipped: s.skipped }, null, 2);
7437
+ return `<details class="capture-skip"><summary>${bi("Capture skip", "\u622A\u56FE\u8DF3\u8FC7")} \xB7 ${esc(s.kind)}</summary><pre data-machine-capture-skip="true">${esc(json)}</pre></details>`;
7438
+ }).join("\n");
7439
+ return `<section class="capture-skips"><h2>${bi("Capture skip", "\u622A\u56FE\u8DF3\u8FC7")}</h2>
7440
+ ${rows}
7441
+ </section>`;
7442
+ }
7426
7443
  function evidenceIndexBlock(items, beforeAfter, selfCaptures) {
7427
7444
  const rows = [];
7428
7445
  const row2 = (ref) => {
@@ -7597,6 +7614,9 @@ details.selfscore ul { margin:8px 0 4px; padding-left:18px; }
7597
7614
  .selfscore-badge { display:inline-block; border:1px solid var(--line); border-radius:999px; padding:1px 8px; font-size:12px; font-weight:600; }
7598
7615
  .selfscore-good { color:var(--pass); } .selfscore-ok { color:var(--warn); } .selfscore-regression { color:var(--fail); }
7599
7616
  .selfscore-dims, .selfscore-trend { color:var(--muted); font-size:12.5px; margin-top:4px; }
7617
+ .capture-skip { margin:8px 0; border:1px solid var(--line); border-radius:6px; padding:6px 12px; background:rgba(127,110,70,.04); }
7618
+ .capture-skip summary { cursor:pointer; color:var(--muted); font-size:12.5px; font-weight:600; }
7619
+ .capture-skip pre { white-space:pre-wrap; font-size:12px; }
7600
7620
  details.tech { margin:8px 0 2px; border:1px solid var(--line); border-radius:6px; padding:6px 12px; background:rgba(127,110,70,.04); }
7601
7621
  details.tech summary { cursor:pointer; color:var(--muted); font-size:12.5px; font-weight:600; }
7602
7622
  details.tech[open] summary { margin-bottom:6px; }
@@ -7640,6 +7660,7 @@ ${cardContextBlock(input.context)}
7640
7660
  ${items.map(acSection).join("\n")}
7641
7661
  ${beforeAfterBlock(input.beforeAfter)}
7642
7662
  ${selfCaptureBlock(input.selfCaptures)}
7663
+ ${captureSkipBlock(input.captureSkips)}
7643
7664
  ${processTraceBlock(input.process)}
7644
7665
  ${closing}
7645
7666
  <footer>Roll \xB7 ${bi("Acceptance Evidence", "\u9A8C\u6536\u8BC1\u636E")} \xB7 <code>${esc(input.storyId)}</code></footer>
@@ -7769,6 +7790,8 @@ function runConsistencyAudit(s) {
7769
7790
  }
7770
7791
  if (!probe.report) {
7771
7792
  add("done-missing-attest", "fail", row2.id, `Done without an acceptance report${probe.acMap ? "" : " (no ac-map either)"} \u2014 attest_evidence anchor`);
7793
+ } else if (probe.acMap && !(probe.visualEvidence ?? false) && !(probe.machineSkip ?? false)) {
7794
+ add("done-attest-no-visual", "warn", row2.id, "Done acceptance evidence has a report/ac-map but no screenshot and no machine-generated capture skip \u2014 visual evidence floor (FIX-258)");
7772
7795
  }
7773
7796
  }
7774
7797
  for (const row2 of s.runs) {
@@ -9024,7 +9047,9 @@ async function collectEvidence(opts) {
9024
9047
  deploy,
9025
9048
  test_pass: testPass,
9026
9049
  screenshots: listDir("screenshots", /\.png$/i),
9027
- texts: listDir("evidence", /\.(txt|log)$/i)
9050
+ texts: listDir("evidence", /\.(txt|log)$/i),
9051
+ captures: [...opts.captures ?? []],
9052
+ capture_command: opts.captureCommand ?? null
9028
9053
  };
9029
9054
  }
9030
9055
  function writeEvidenceJson(manifest, runDir) {
@@ -9035,7 +9060,7 @@ function writeEvidenceJson(manifest, runDir) {
9035
9060
  }
9036
9061
 
9037
9062
  // packages/infra/dist/screenshot.js
9038
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, statSync as statSync5 } from "node:fs";
9063
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, rmSync as rmSync4, statSync as statSync5 } from "node:fs";
9039
9064
  import { execFile as execFile6 } from "node:child_process";
9040
9065
  import { promisify as promisify6 } from "node:util";
9041
9066
  import { join as join6 } from "node:path";
@@ -9105,6 +9130,8 @@ function fileNonEmpty(p) {
9105
9130
  }
9106
9131
  }
9107
9132
  var DEFAULT_REGION = "0,0,1280,800";
9133
+ var TERMINAL_DONE_TIMEOUT_MS = 12e4;
9134
+ var TERMINAL_DONE_POLL_MS = 100;
9108
9135
  function parseCaptureMarker(line) {
9109
9136
  const m7 = /^::roll-capture\s+(before|after|gate)\s+(web|mobile-ios|mobile-android|terminal)\s+([A-Za-z0-9_.-]+)(?:\s+(.+))?\s*$/.exec(line.trim());
9110
9137
  if (m7 === null)
@@ -9147,19 +9174,66 @@ function parseRegion(region) {
9147
9174
  return null;
9148
9175
  return { x, y, w, h };
9149
9176
  }
9150
- function terminalOpenScript(line, r) {
9151
- const esc6 = line.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
9177
+ function terminalOpenScript(line, r, title) {
9178
+ const esc6 = appleScriptString(line);
9179
+ const setTitle = title === void 0 ? [] : [` set custom title of front window to "${appleScriptString(title)}"`];
9152
9180
  return [
9153
9181
  'tell application "Terminal"',
9154
9182
  " activate",
9155
9183
  ` do script "${esc6}"`,
9184
+ ...setTitle,
9156
9185
  " delay 1.5",
9157
9186
  ` set bounds of front window to {${r.x}, ${r.y}, ${r.x + r.w}, ${r.y + r.h}}`,
9158
9187
  "end tell"
9159
9188
  ].join("\n");
9160
9189
  }
9161
- function terminalCloseScript() {
9162
- return 'tell application "Terminal" to close front window saving no';
9190
+ function terminalCloseScript(title) {
9191
+ if (title === void 0)
9192
+ return 'tell application "Terminal" to close front window saving no';
9193
+ const esc6 = appleScriptString(title);
9194
+ return [
9195
+ 'tell application "Terminal"',
9196
+ " repeat with w in windows",
9197
+ ` if custom title of w is "${esc6}" then`,
9198
+ " close w saving no",
9199
+ " exit repeat",
9200
+ " end if",
9201
+ " end repeat",
9202
+ "end tell"
9203
+ ].join("\n");
9204
+ }
9205
+ function shellQuote(value) {
9206
+ return `'${value.replace(/'/g, `'\\''`)}'`;
9207
+ }
9208
+ function appleScriptString(value) {
9209
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
9210
+ }
9211
+ function terminalWindowTitle(out3) {
9212
+ const safe = out3.replace(/[^A-Za-z0-9_.-]+/g, "-");
9213
+ return `roll-attest-${safe.slice(Math.max(0, safe.length - 80))}`;
9214
+ }
9215
+ function terminalCommandWithDoneFile(line, doneFile) {
9216
+ const done = shellQuote(doneFile);
9217
+ return [
9218
+ `(${line})`,
9219
+ "__roll_status=$?",
9220
+ `printf '%s' "$__roll_status" > ${done}`,
9221
+ 'exit "$__roll_status"'
9222
+ ].join("; ");
9223
+ }
9224
+ function terminalDoneWaitScript(doneFile) {
9225
+ const attempts = Math.ceil(TERMINAL_DONE_TIMEOUT_MS / TERMINAL_DONE_POLL_MS);
9226
+ const sleepSeconds = String(TERMINAL_DONE_POLL_MS / 1e3);
9227
+ const done = shellQuote(doneFile);
9228
+ return `i=0; while [ ! -f ${done} ]; do if [ "$i" -ge ${attempts} ]; then exit 1; fi; i=$((i + 1)); sleep ${sleepSeconds}; done`;
9229
+ }
9230
+ async function waitForTerminalCommandExit(doneFile, run) {
9231
+ const waited = await run("sh", ["-lc", terminalDoneWaitScript(doneFile)]);
9232
+ try {
9233
+ rmSync4(doneFile, { force: true });
9234
+ } catch {
9235
+ }
9236
+ return waited.code === 0;
9163
9237
  }
9164
9238
  async function captureScreenshot(req, deps = {}) {
9165
9239
  const run = deps.run ?? defaultRun2;
@@ -9197,8 +9271,8 @@ async function captureScreenshot(req, deps = {}) {
9197
9271
  return skip("ROLL_ATTEST_NO_TERMINAL=1");
9198
9272
  if (platform3 !== "darwin")
9199
9273
  return skip("not macOS");
9200
- const line = req.tmux !== void 0 && req.tmux !== "" ? `tmux attach -t ${req.tmux}` : req.command ?? "";
9201
- if (containsSecret(line))
9274
+ const rawLine = req.tmux !== void 0 && req.tmux !== "" ? `tmux attach -t ${req.tmux}` : req.command ?? "";
9275
+ if (containsSecret(rawLine))
9202
9276
  return skip("secret in capture command \u2014 redact & reshoot");
9203
9277
  const gui = await run("launchctl", ["managername"]);
9204
9278
  if (gui.code !== 0 || !gui.stdout.includes("Aqua"))
@@ -9206,15 +9280,34 @@ async function captureScreenshot(req, deps = {}) {
9206
9280
  const rect = parseRegion(req.region ?? DEFAULT_REGION);
9207
9281
  if (rect === null)
9208
9282
  return skip("bad region");
9209
- const opened = await run("osascript", ["-e", terminalOpenScript(line, rect)]);
9283
+ const commandDoneFile = req.tmux === void 0 && req.command !== void 0 && req.command !== "" ? `${req.out}.done` : void 0;
9284
+ if (commandDoneFile !== void 0) {
9285
+ try {
9286
+ rmSync4(commandDoneFile, { force: true });
9287
+ } catch {
9288
+ }
9289
+ }
9290
+ const line = commandDoneFile !== void 0 ? terminalCommandWithDoneFile(rawLine, commandDoneFile) : rawLine;
9291
+ const windowTitle = terminalWindowTitle(req.out);
9292
+ const opened = await run("osascript", ["-e", terminalOpenScript(line, rect, windowTitle)]);
9210
9293
  if (opened.code !== 0)
9211
9294
  return skip("osascript Terminal open failed");
9212
9295
  const shot = await run("screencapture", ["-x", "-R", req.region ?? DEFAULT_REGION, req.out]);
9213
9296
  if (shot.code !== 0) {
9214
- await run("osascript", ["-e", terminalCloseScript()]);
9297
+ if (commandDoneFile !== void 0 && !await waitForTerminalCommandExit(commandDoneFile, run)) {
9298
+ return skip("terminal command still running; window left open to avoid macOS termination prompt");
9299
+ }
9300
+ const closed2 = await run("osascript", ["-e", terminalCloseScript(windowTitle)]);
9301
+ if (closed2.code !== 0)
9302
+ return skip("screencapture failed; Terminal close failed");
9215
9303
  return skip("screencapture failed (screen-recording permission?)");
9216
9304
  }
9217
- await run("osascript", ["-e", terminalCloseScript()]);
9305
+ if (commandDoneFile !== void 0 && !await waitForTerminalCommandExit(commandDoneFile, run)) {
9306
+ return skip("terminal command still running; window left open to avoid macOS termination prompt");
9307
+ }
9308
+ const closed = await run("osascript", ["-e", terminalCloseScript(windowTitle)]);
9309
+ if (closed.code !== 0)
9310
+ return skip("Terminal close failed after capture");
9218
9311
  }
9219
9312
  } catch {
9220
9313
  return skip("capture errored");
@@ -10330,18 +10423,18 @@ function pyLjust(s, w) {
10330
10423
  function agentSummaryLine(records, windowCycles = 50, minSample = 5) {
10331
10424
  if (records.length === 0 || windowCycles <= 0)
10332
10425
  return "";
10333
- const tail = [];
10426
+ const tail2 = [];
10334
10427
  for (const rec of records.slice(-windowCycles)) {
10335
10428
  const agent = (rec ?? {}).agent ?? "";
10336
10429
  if (!agent)
10337
10430
  continue;
10338
- tail.push(rec);
10431
+ tail2.push(rec);
10339
10432
  }
10340
- if (tail.length === 0)
10433
+ if (tail2.length === 0)
10341
10434
  return "";
10342
10435
  const counts = /* @__PURE__ */ new Map();
10343
10436
  const order = [];
10344
- for (const rec of tail) {
10437
+ for (const rec of tail2) {
10345
10438
  const agent = rec.agent ?? "";
10346
10439
  if (!agent)
10347
10440
  continue;
@@ -11388,7 +11481,7 @@ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync10, readFileSy
11388
11481
  import { dirname as dirname9, join as join10 } from "node:path";
11389
11482
 
11390
11483
  // packages/cli/dist/commands/setup-shared.js
11391
- import { copyFileSync, existsSync as existsSync13, lstatSync, mkdirSync as mkdirSync9, readFileSync as readFileSync10, readdirSync as readdirSync4, readlinkSync, realpathSync as realpathSync2, rmSync as rmSync4, statSync as statSync7, symlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
11484
+ import { copyFileSync, existsSync as existsSync13, lstatSync, mkdirSync as mkdirSync9, readFileSync as readFileSync10, readdirSync as readdirSync4, readlinkSync, realpathSync as realpathSync2, rmSync as rmSync5, statSync as statSync7, symlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
11392
11485
  import { homedir as homedir4 } from "node:os";
11393
11486
  import { basename as basename4, dirname as dirname8, join as join9 } from "node:path";
11394
11487
  function rollHome() {
@@ -11535,7 +11628,7 @@ function pruneDir(installedDir, sourceDir) {
11535
11628
  if (!isFile)
11536
11629
  continue;
11537
11630
  if (!existsSync13(join9(sourceDir, name)))
11538
- rmSync4(installedF, { force: true });
11631
+ rmSync5(installedF, { force: true });
11539
11632
  }
11540
11633
  }
11541
11634
  function safeCopy(src, dst, force) {
@@ -11613,7 +11706,7 @@ function syncSkillTree(sourceDir, destDir) {
11613
11706
  const sourceNames = new Set(listDirEntries(sourceDir));
11614
11707
  for (const name of listDirEntries(destDir)) {
11615
11708
  if (!sourceNames.has(name))
11616
- rmSync4(join9(destDir, name), { recursive: true, force: true });
11709
+ rmSync5(join9(destDir, name), { recursive: true, force: true });
11617
11710
  }
11618
11711
  for (const name of sourceNames) {
11619
11712
  const src = join9(sourceDir, name);
@@ -11626,11 +11719,11 @@ function syncSkillTree(sourceDir, destDir) {
11626
11719
  }
11627
11720
  if (st.isDirectory()) {
11628
11721
  if (lstatType(dst) !== "dir" && lstatType(dst) !== "none")
11629
- rmSync4(dst, { recursive: true, force: true });
11722
+ rmSync5(dst, { recursive: true, force: true });
11630
11723
  syncSkillTree(src, dst);
11631
11724
  } else if (st.isFile()) {
11632
11725
  if (lstatType(dst) === "dir")
11633
- rmSync4(dst, { recursive: true, force: true });
11726
+ rmSync5(dst, { recursive: true, force: true });
11634
11727
  if (!sameFile(src, dst))
11635
11728
  copyFileSync(src, dst);
11636
11729
  }
@@ -11662,7 +11755,7 @@ function pullSkills() {
11662
11755
  continue;
11663
11756
  }
11664
11757
  if (!existsSync13(join9(pkgSkills, installedName))) {
11665
- rmSync4(installedDir, { recursive: true, force: true });
11758
+ rmSync5(installedDir, { recursive: true, force: true });
11666
11759
  }
11667
11760
  }
11668
11761
  }
@@ -11772,7 +11865,7 @@ function installLocal(force) {
11772
11865
  const cfg = rollConfig();
11773
11866
  if (existsSync13(cfg) && !/^ai_[a-z]+:/m.test(readFileSync10(cfg, "utf8"))) {
11774
11867
  copyFileSync(cfg, `${cfg}.bak`);
11775
- rmSync4(cfg, { force: true });
11868
+ rmSync5(cfg, { force: true });
11776
11869
  }
11777
11870
  if (!existsSync13(cfg)) {
11778
11871
  writeFileSync8(cfg, DEFAULT_CONFIG);
@@ -11851,7 +11944,7 @@ function linkSkills() {
11851
11944
  if (skillsReal !== null && skillsReal === rollSkillsReal)
11852
11945
  continue;
11853
11946
  if (skillsReal === null) {
11854
- rmSync4(skillsDir2, { force: true });
11947
+ rmSync5(skillsDir2, { force: true });
11855
11948
  } else {
11856
11949
  void target;
11857
11950
  continue;
@@ -11876,7 +11969,7 @@ function linkSkills() {
11876
11969
  target = "";
11877
11970
  }
11878
11971
  if (target.startsWith(homeSkillsPrefix) && !existsSync13(link)) {
11879
- rmSync4(link, { force: true });
11972
+ rmSync5(link, { force: true });
11880
11973
  }
11881
11974
  }
11882
11975
  const homeSkills = join9(rollHome(), "skills");
@@ -11898,7 +11991,7 @@ function linkSkills() {
11898
11991
  current = "";
11899
11992
  }
11900
11993
  if (current !== skillDir) {
11901
- rmSync4(skillLink, { force: true });
11994
+ rmSync5(skillLink, { force: true });
11902
11995
  symlinkSync(skillDir, skillLink);
11903
11996
  }
11904
11997
  } else if (t2 === "none") {
@@ -12278,7 +12371,7 @@ duration: ${facts.durationMs}ms
12278
12371
  }
12279
12372
 
12280
12373
  // packages/cli/dist/commands/alert.js
12281
- import { appendFileSync as appendFileSync3, existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync8 } from "node:fs";
12374
+ import { appendFileSync as appendFileSync3, existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync12, rmSync as rmSync6, statSync as statSync8 } from "node:fs";
12282
12375
  import { homedir as homedir5 } from "node:os";
12283
12376
  import { join as join11 } from "node:path";
12284
12377
  function palette() {
@@ -12385,11 +12478,11 @@ function alertCommand(args) {
12385
12478
  out3.push("");
12386
12479
  process.stdout.write(out3.join("\n") + "\n");
12387
12480
  process.stdout.write(action.contents);
12388
- const tail = [];
12389
- tail.push("");
12390
- tail.push(` Run '${p.CYAN}roll loop alert ack${p.NC}' to acknowledge, '${p.CYAN}roll loop alert resolve${p.NC}' to clear.`);
12391
- tail.push(t(v2Catalog, lang9, "alert.run_roll_alert_ack_to_acknowledge"));
12392
- process.stdout.write(tail.join("\n") + "\n");
12481
+ const tail2 = [];
12482
+ tail2.push("");
12483
+ tail2.push(` Run '${p.CYAN}roll loop alert ack${p.NC}' to acknowledge, '${p.CYAN}roll loop alert resolve${p.NC}' to clear.`);
12484
+ tail2.push(t(v2Catalog, lang9, "alert.run_roll_alert_ack_to_acknowledge"));
12485
+ process.stdout.write(tail2.join("\n") + "\n");
12393
12486
  return 0;
12394
12487
  }
12395
12488
  case "ack_none":
@@ -12403,7 +12496,7 @@ function alertCommand(args) {
12403
12496
  return 0;
12404
12497
  }
12405
12498
  case "remove":
12406
- rmSync5(file, { force: true });
12499
+ rmSync6(file, { force: true });
12407
12500
  ok2(t(v2Catalog, lang9, "alert.alert_resolved_and_cleared"), p);
12408
12501
  return 0;
12409
12502
  case "log":
@@ -12430,9 +12523,10 @@ function ackMessage(lang9, ts) {
12430
12523
  }
12431
12524
 
12432
12525
  // packages/cli/dist/commands/attest.js
12433
- import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync18, readdirSync as readdirSync9, realpathSync as realpathSync3, rmSync as rmSync6, statSync as statSync11, symlinkSync as symlinkSync2, writeFileSync as writeFileSync13 } from "node:fs";
12434
- import { execFileSync as execFileSync4 } from "node:child_process";
12526
+ import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync18, readdirSync as readdirSync9, realpathSync as realpathSync3, rmSync as rmSync7, statSync as statSync11, symlinkSync as symlinkSync2, writeFileSync as writeFileSync13 } from "node:fs";
12527
+ import { execFile as execFile7, execFileSync as execFileSync4 } from "node:child_process";
12435
12528
  import { basename as basename7, join as join16, relative as relative2 } from "node:path";
12529
+ import { promisify as promisify7 } from "node:util";
12436
12530
 
12437
12531
  // packages/cli/dist/lib/story-page.js
12438
12532
  var STORY_ID_RE = /^(FIX|US|IDEA|REFACTOR)-/;
@@ -15143,10 +15237,47 @@ function buildProcessArchive(storyId, readers) {
15143
15237
  return archive;
15144
15238
  }
15145
15239
  var STATUSES = ["pass", "readonly", "partial", "fail", "blocked", "claimed", "missing"];
15240
+ var execFileAsync7 = promisify7(execFile7);
15146
15241
  function warn2(msg6) {
15147
15242
  process.stderr.write(`[roll] attest WARN: ${msg6}
15148
15243
  `);
15149
15244
  }
15245
+ function shellQuote2(value) {
15246
+ return `'${value.replace(/'/g, `'\\''`)}'`;
15247
+ }
15248
+ function commandInProject(projectPath3, command) {
15249
+ return `cd ${shellQuote2(projectPath3)} && ${command}`;
15250
+ }
15251
+ function tail(text, max = 4e3) {
15252
+ return text.length <= max ? text : text.slice(text.length - max);
15253
+ }
15254
+ async function runShell(line, deps) {
15255
+ const injected = deps?.run;
15256
+ if (injected !== void 0)
15257
+ return injected("sh", ["-lc", line]);
15258
+ try {
15259
+ const { stdout, stderr } = await execFileAsync7("sh", ["-lc", line], {
15260
+ encoding: "utf8",
15261
+ maxBuffer: 4 * 1024 * 1024,
15262
+ timeout: 12e4
15263
+ });
15264
+ return { code: 0, stdout, stderr };
15265
+ } catch (e) {
15266
+ const err17 = e;
15267
+ return { code: typeof err17.code === "number" ? err17.code : 1, stdout: err17.stdout ?? "", stderr: err17.stderr ?? "" };
15268
+ }
15269
+ }
15270
+ async function captureCommandFact(projectPath3, command, deps) {
15271
+ const wrappedCommand = commandInProject(projectPath3, command);
15272
+ const r = await runShell(wrappedCommand, deps);
15273
+ return {
15274
+ command,
15275
+ wrappedCommand,
15276
+ exitCode: r.code,
15277
+ stdoutTail: tail(r.stdout),
15278
+ stderrTail: tail(r.stderr)
15279
+ };
15280
+ }
15150
15281
  var DOC_ALIGNMENT_PATTERNS = [
15151
15282
  /^README(?:_[A-Z]+)?\.md$/,
15152
15283
  /^AGENTS\.md$/,
@@ -15603,7 +15734,7 @@ async function attestCommand(args, deps = {}) {
15603
15734
  }
15604
15735
  const acItems = resolveStoryAcItems(projectPath3, storyId);
15605
15736
  if (acItems.length === 0)
15606
- warn2(`no **AC:** block for ${storyId} \u2014 report will carry facts only`);
15737
+ warn2(`no AC block for ${storyId} \u2014 report will carry facts only`);
15607
15738
  const now = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
15608
15739
  const p2 = (n) => String(n).padStart(2, "0");
15609
15740
  const generatedRunId = `${now.getFullYear()}-${p2(now.getMonth() + 1)}-${p2(now.getDate())}T${p2(now.getHours())}-${p2(now.getMinutes())}-${p2(now.getSeconds())}`;
@@ -15611,15 +15742,27 @@ async function attestCommand(args, deps = {}) {
15611
15742
  const runDir = providedRunDir ?? join16(storyDir, generatedRunId);
15612
15743
  openEvidenceFrame({ runDir });
15613
15744
  let selfCaptures = [];
15745
+ const captureFacts = [];
15746
+ let commandFact = null;
15614
15747
  if (captureTerminal) {
15615
15748
  mkdirSync14(join16(runDir, "screenshots"), { recursive: true });
15616
- const shot = await captureScreenshot({
15749
+ const out3 = join16(runDir, "screenshots", "terminal.png");
15750
+ if (captureCommand !== void 0) {
15751
+ commandFact = await captureCommandFact(projectPath3, captureCommand, deps.capture);
15752
+ }
15753
+ const shot = commandFact !== null && commandFact.exitCode !== 0 ? { kind: "terminal", out: out3, taken: false, skipped: `capture command exited ${commandFact.exitCode}` } : await captureScreenshot({
15617
15754
  kind: "terminal",
15618
- out: join16(runDir, "screenshots", "terminal.png"),
15755
+ out: out3,
15619
15756
  ...captureTmux !== void 0 ? { tmux: captureTmux } : {},
15620
- ...captureCommand !== void 0 ? { command: captureCommand } : {},
15757
+ ...captureCommand !== void 0 ? { command: commandInProject(projectPath3, captureCommand) } : {},
15621
15758
  ...captureRegion !== void 0 ? { region: captureRegion } : {}
15622
15759
  }, deps.capture ?? {});
15760
+ captureFacts.push({
15761
+ kind: shot.kind,
15762
+ out: shot.out,
15763
+ taken: shot.taken,
15764
+ ...shot.skipped !== void 0 ? { skipped: shot.skipped } : {}
15765
+ });
15623
15766
  const ref = screenshotEvidenceRef(shot, "screenshots/terminal.png");
15624
15767
  if (ref !== null)
15625
15768
  selfCaptures = [ref];
@@ -15633,7 +15776,9 @@ async function attestCommand(args, deps = {}) {
15633
15776
  ...deployUrl !== void 0 ? { deployUrl } : {},
15634
15777
  now: () => now.toISOString(),
15635
15778
  ...deps.run !== void 0 ? { run: deps.run } : {},
15636
- ...deps.ghProbe !== void 0 ? { ghProbe: deps.ghProbe } : {}
15779
+ ...deps.ghProbe !== void 0 ? { ghProbe: deps.ghProbe } : {},
15780
+ captures: captureFacts,
15781
+ captureCommand: commandFact
15637
15782
  });
15638
15783
  writeEvidenceJson(manifest, runDir);
15639
15784
  const acMap = readAcMap(storyDir) ?? readAcMap(join16(projectPath3, ".roll", "verification", storyId));
@@ -15676,13 +15821,14 @@ async function attestCommand(args, deps = {}) {
15676
15821
  ...docGap !== void 0 ? { docGap } : {},
15677
15822
  ...selfScores.length > 0 ? { selfScores } : {},
15678
15823
  ...selfScoreTrend !== void 0 ? { selfScoreTrend } : {},
15679
- ...selfCaptures.length > 0 ? { selfCaptures } : {}
15824
+ ...selfCaptures.length > 0 ? { selfCaptures } : {},
15825
+ ...captureFacts.some((x) => !x.taken && x.skipped !== void 0) ? { captureSkips: captureFacts.filter((x) => !x.taken && x.skipped !== void 0).map((x) => ({ kind: x.kind, out: x.out, skipped: x.skipped ?? "" })) } : {}
15680
15826
  });
15681
15827
  const reportPath = join16(runDir, reportFileName(storyId));
15682
15828
  writeFileSync13(reportPath, html);
15683
15829
  const latest = join16(storyDir, "latest");
15684
15830
  try {
15685
- rmSync6(latest, { force: true });
15831
+ rmSync7(latest, { force: true });
15686
15832
  symlinkSync2(relativeFromPhysical(storyDir, runDir), latest);
15687
15833
  } catch {
15688
15834
  warn2("latest symlink update failed (report still written)");
@@ -15715,6 +15861,10 @@ async function attestCommand(args, deps = {}) {
15715
15861
  warn2(`render smoke: ${p}`);
15716
15862
  return 2;
15717
15863
  }
15864
+ if (commandFact !== null && commandFact.exitCode !== 0) {
15865
+ warn2(`capture command failed with exit ${commandFact.exitCode}`);
15866
+ return 3;
15867
+ }
15718
15868
  return 0;
15719
15869
  }
15720
15870
 
@@ -17720,17 +17870,17 @@ function configCommand(args) {
17720
17870
  }
17721
17871
 
17722
17872
  // packages/cli/dist/commands/consistency.js
17723
- import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync10, statSync as statSync12 } from "node:fs";
17873
+ import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync11, statSync as statSync12 } from "node:fs";
17724
17874
  import { join as join24 } from "node:path";
17725
17875
 
17726
17876
  // packages/cli/dist/commands/consistency-audit.js
17727
- import { execFile as execFile7 } from "node:child_process";
17728
- import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync26, writeFileSync as writeFileSync19 } from "node:fs";
17729
- import { join as join23 } from "node:path";
17730
- import { promisify as promisify7 } from "node:util";
17877
+ import { execFile as execFile8 } from "node:child_process";
17878
+ import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync26, readdirSync as readdirSync10, writeFileSync as writeFileSync19 } from "node:fs";
17879
+ import { dirname as dirname16, join as join23 } from "node:path";
17880
+ import { promisify as promisify8 } from "node:util";
17731
17881
  var PROBE_CAP = 20;
17732
17882
  var COUNT_WINDOW_SEC = 72 * 3600;
17733
- var execFileAsync7 = promisify7(execFile7);
17883
+ var execFileAsync8 = promisify8(execFile8);
17734
17884
  function readJsonl2(path) {
17735
17885
  if (!existsSync28(path))
17736
17886
  return [];
@@ -17747,9 +17897,58 @@ function readJsonl2(path) {
17747
17897
  }
17748
17898
  return out3;
17749
17899
  }
17900
+ function readJson(path) {
17901
+ if (!existsSync28(path))
17902
+ return null;
17903
+ try {
17904
+ const parsed = JSON.parse(readFileSync26(path, "utf8"));
17905
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
17906
+ } catch {
17907
+ return null;
17908
+ }
17909
+ }
17910
+ function hasScreenshotArtifact(reportPath) {
17911
+ const runDir = dirname16(reportPath);
17912
+ const manifest = readJson(join23(runDir, "evidence.json"));
17913
+ if (manifest !== null) {
17914
+ const screenshots = manifest["screenshots"];
17915
+ if (Array.isArray(screenshots) && screenshots.some((x) => typeof x === "string" && x !== ""))
17916
+ return true;
17917
+ const captures = manifest["captures"];
17918
+ if (Array.isArray(captures)) {
17919
+ if (captures.some((raw) => {
17920
+ if (typeof raw !== "object" || raw === null)
17921
+ return false;
17922
+ return raw["taken"] === true;
17923
+ })) {
17924
+ return true;
17925
+ }
17926
+ }
17927
+ }
17928
+ const dir = join23(runDir, "screenshots");
17929
+ if (!existsSync28(dir))
17930
+ return false;
17931
+ try {
17932
+ return readdirSync10(dir).some((name) => /\.png$/i.test(name));
17933
+ } catch {
17934
+ return false;
17935
+ }
17936
+ }
17937
+ function hasMachineCaptureSkip(reportPath) {
17938
+ const manifest = readJson(join23(dirname16(reportPath), "evidence.json"));
17939
+ const captures = manifest?.["captures"];
17940
+ if (!Array.isArray(captures))
17941
+ return false;
17942
+ return captures.some((raw) => {
17943
+ if (typeof raw !== "object" || raw === null)
17944
+ return false;
17945
+ const row2 = raw;
17946
+ return row2["taken"] === false && typeof row2["skipped"] === "string" && row2["skipped"] !== "";
17947
+ });
17948
+ }
17750
17949
  async function gitLocalMainAhead(projectPath3) {
17751
17950
  try {
17752
- const { stdout } = await execFileAsync7("git", ["rev-list", "--count", "origin/main..main"], {
17951
+ const { stdout } = await execFileAsync8("git", ["rev-list", "--count", "origin/main..main"], {
17753
17952
  cwd: projectPath3,
17754
17953
  encoding: "utf8"
17755
17954
  });
@@ -17798,9 +17997,12 @@ async function gatherAuditSnapshot(projectPath3, runtimeDir6, deps = {}) {
17798
17997
  const card = cardArchiveDir(projectPath3, row2.id);
17799
17998
  if (!existsSync28(card))
17800
17999
  continue;
18000
+ const reportPath = join23(card, "latest", reportFileName(row2.id));
17801
18001
  snapshot.attest[row2.id] = {
17802
- report: existsSync28(join23(card, "latest", reportFileName(row2.id))),
17803
- acMap: existsSync28(join23(card, "ac-map.json"))
18002
+ report: existsSync28(reportPath),
18003
+ acMap: existsSync28(join23(card, "ac-map.json")),
18004
+ visualEvidence: existsSync28(reportPath) ? hasScreenshotArtifact(reportPath) : false,
18005
+ machineSkip: existsSync28(reportPath) ? hasMachineCaptureSkip(reportPath) : false
17804
18006
  };
17805
18007
  }
17806
18008
  const slug = deps.slug ?? ghRepoSlug(await remoteUrl(projectPath3));
@@ -17962,7 +18164,7 @@ function walkTextFiles(projectDir, relDir, extensions) {
17962
18164
  const dirAbs = join24(projectDir, dirRel);
17963
18165
  let entries;
17964
18166
  try {
17965
- entries = readdirSync10(dirAbs, { withFileTypes: true });
18167
+ entries = readdirSync11(dirAbs, { withFileTypes: true });
17966
18168
  } catch {
17967
18169
  return;
17968
18170
  }
@@ -18070,10 +18272,10 @@ function checkCards(projectDir) {
18070
18272
  return { status: "pass", gaps: [] };
18071
18273
  const cardEpic = /* @__PURE__ */ new Map();
18072
18274
  try {
18073
- for (const epic of readdirSync10(featuresDir, { withFileTypes: true })) {
18275
+ for (const epic of readdirSync11(featuresDir, { withFileTypes: true })) {
18074
18276
  if (!epic.isDirectory())
18075
18277
  continue;
18076
- for (const card of readdirSync10(join24(featuresDir, epic.name), { withFileTypes: true })) {
18278
+ for (const card of readdirSync11(join24(featuresDir, epic.name), { withFileTypes: true })) {
18077
18279
  if (!card.isDirectory())
18078
18280
  continue;
18079
18281
  if (existsSync29(join24(featuresDir, epic.name, card.name, "spec.md")) && !cardEpic.has(card.name)) {
@@ -18239,7 +18441,7 @@ function checkSite(projectDir) {
18239
18441
  }
18240
18442
  function listFiles2(dir) {
18241
18443
  try {
18242
- return readdirSync10(dir).filter((n) => {
18444
+ return readdirSync11(dir).filter((n) => {
18243
18445
  try {
18244
18446
  return statSync12(join24(dir, n)).isFile();
18245
18447
  } catch {
@@ -18270,7 +18472,7 @@ function checkI18n(projectDir) {
18270
18472
  const keysZh = /* @__PURE__ */ new Set();
18271
18473
  let shFiles = [];
18272
18474
  try {
18273
- shFiles = readdirSync10(i18nDir).filter((n) => n.endsWith(".sh")).sort();
18475
+ shFiles = readdirSync11(i18nDir).filter((n) => n.endsWith(".sh")).sort();
18274
18476
  } catch {
18275
18477
  shFiles = [];
18276
18478
  }
@@ -18310,7 +18512,7 @@ function rglobBats(dir) {
18310
18512
  const walk2 = (d) => {
18311
18513
  let entries = [];
18312
18514
  try {
18313
- entries = readdirSync10(d);
18515
+ entries = readdirSync11(d);
18314
18516
  } catch {
18315
18517
  return;
18316
18518
  }
@@ -18517,7 +18719,7 @@ function consistencyCommand(args, command = "roll consistency") {
18517
18719
  }
18518
18720
 
18519
18721
  // packages/cli/dist/commands/loop-runs.js
18520
- import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync11 } from "node:fs";
18722
+ import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync12 } from "node:fs";
18521
18723
  import { homedir as homedir9, platform as platform2 } from "node:os";
18522
18724
  import { basename as basename8, join as join25 } from "node:path";
18523
18725
  function lang3() {
@@ -18678,7 +18880,7 @@ function aggregateAllFiles() {
18678
18880
  const laDir = (process.env["_LAUNCHD_DIR"] ?? "").trim() || join25(homedir9(), "Library", "LaunchAgents");
18679
18881
  let plists = [];
18680
18882
  try {
18681
- plists = readdirSync11(laDir).filter((f) => f.startsWith("com.roll.loop.") && f.endsWith(".plist"));
18883
+ plists = readdirSync12(laDir).filter((f) => f.startsWith("com.roll.loop.") && f.endsWith(".plist"));
18682
18884
  } catch {
18683
18885
  plists = [];
18684
18886
  }
@@ -18823,7 +19025,7 @@ function loopRunsCommand(argv) {
18823
19025
 
18824
19026
  // packages/cli/dist/commands/loop-signals.js
18825
19027
  import { appendFileSync as appendFileSync7, existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync29, writeFileSync as writeFileSync20 } from "node:fs";
18826
- import { dirname as dirname16, join as join26 } from "node:path";
19028
+ import { dirname as dirname17, join as join26 } from "node:path";
18827
19029
  function lang4() {
18828
19030
  return resolveLang({ rollLang: process.env["ROLL_LANG"], lcAll: process.env["LC_ALL"], lang: process.env["LANG"] });
18829
19031
  }
@@ -18878,7 +19080,7 @@ function loopSignalsCommand(argv) {
18878
19080
  const seenFile = join26(rtDir, `signals-seen-${slug}`);
18879
19081
  const candFile = join26(projectPath3, ".roll", "signals", "candidates.md");
18880
19082
  mkdirSync20(rtDir, { recursive: true });
18881
- mkdirSync20(dirname16(candFile), { recursive: true });
19083
+ mkdirSync20(dirname17(candFile), { recursive: true });
18882
19084
  if (!existsSync31(seenFile))
18883
19085
  writeFileSync20(seenFile, "");
18884
19086
  let lastId = 0;
@@ -18935,7 +19137,7 @@ Options:
18935
19137
  `;
18936
19138
 
18937
19139
  // packages/cli/dist/commands/loop-log.js
18938
- import { existsSync as existsSync32, readFileSync as readFileSync30, readdirSync as readdirSync12 } from "node:fs";
19140
+ import { existsSync as existsSync32, readFileSync as readFileSync30, readdirSync as readdirSync13 } from "node:fs";
18939
19141
  import { basename as basename9, join as join27 } from "node:path";
18940
19142
  function lang5() {
18941
19143
  return resolveLang({ rollLang: process.env["ROLL_LANG"], lcAll: process.env["LC_ALL"], lang: process.env["LANG"] });
@@ -18961,7 +19163,7 @@ ${body}`);
18961
19163
  }
18962
19164
  function logFiles(logsDir) {
18963
19165
  try {
18964
- return readdirSync12(logsDir).filter((n) => n.endsWith(".log"));
19166
+ return readdirSync13(logsDir).filter((n) => n.endsWith(".log"));
18965
19167
  } catch {
18966
19168
  return [];
18967
19169
  }
@@ -19092,8 +19294,8 @@ async function loopGoalCommand(args, deps = realDeps2()) {
19092
19294
 
19093
19295
  // packages/cli/dist/commands/loop-go.js
19094
19296
  import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
19095
- import { appendFileSync as appendFileSync8, createReadStream, existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync32, renameSync as renameSync4, writeFileSync as writeFileSync21 } from "node:fs";
19096
- import { dirname as dirname17, join as join29 } from "node:path";
19297
+ import { appendFileSync as appendFileSync8, createReadStream, existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync32, renameSync as renameSync4, statSync as statSync13, writeFileSync as writeFileSync21 } from "node:fs";
19298
+ import { dirname as dirname18, join as join29 } from "node:path";
19097
19299
  import { createInterface } from "node:readline";
19098
19300
 
19099
19301
  // packages/cli/dist/lib/goal-progress.js
@@ -19349,6 +19551,7 @@ function parseOptions2(args) {
19349
19551
  let worker = false;
19350
19552
  let noTmux = false;
19351
19553
  let noWait = false;
19554
+ let scopeSpecified = false;
19352
19555
  let reviewMode = "auto";
19353
19556
  let reviewModeSpecified = false;
19354
19557
  for (let i = 0; i < args.length; i += 1) {
@@ -19367,15 +19570,19 @@ function parseOptions2(args) {
19367
19570
  }
19368
19571
  if (arg === "--epic") {
19369
19572
  const epic = args[i + 1]?.trim() ?? "";
19370
- if (epic !== "")
19573
+ if (epic !== "") {
19371
19574
  scope = { kind: "epic", epic };
19575
+ scopeSpecified = true;
19576
+ }
19372
19577
  i += 1;
19373
19578
  continue;
19374
19579
  }
19375
19580
  if (arg.startsWith("--epic=")) {
19376
19581
  const epic = arg.slice("--epic=".length).trim();
19377
- if (epic !== "")
19582
+ if (epic !== "") {
19378
19583
  scope = { kind: "epic", epic };
19584
+ scopeSpecified = true;
19585
+ }
19379
19586
  continue;
19380
19587
  }
19381
19588
  if (arg === "--budget") {
@@ -19437,13 +19644,16 @@ function parseOptions2(args) {
19437
19644
  if (!arg.startsWith("-"))
19438
19645
  cards.push(...parseCards(arg));
19439
19646
  }
19440
- if (cards.length > 0)
19647
+ if (cards.length > 0) {
19441
19648
  scope = { kind: "cards", cards };
19649
+ scopeSpecified = true;
19650
+ }
19442
19651
  return {
19443
19652
  worker,
19444
19653
  noTmux,
19445
19654
  noWait,
19446
19655
  scope,
19656
+ scopeSpecified,
19447
19657
  reviewMode,
19448
19658
  reviewModeSpecified,
19449
19659
  usageThreshold,
@@ -19548,7 +19758,7 @@ function parseRequiredDurationSeconds(value, flag2) {
19548
19758
  function parseCards(value) {
19549
19759
  return (value ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
19550
19760
  }
19551
- function shellQuote(value) {
19761
+ function shellQuote3(value) {
19552
19762
  return `'${value.replace(/'/g, `'\\''`)}'`;
19553
19763
  }
19554
19764
  function rollBin() {
@@ -19557,13 +19767,13 @@ function rollBin() {
19557
19767
  function startGoTmux(input) {
19558
19768
  const session = `roll-loop-${input.slug}`;
19559
19769
  const rt = runtimeDir2(input.projectPath);
19560
- const workerArgs = input.args.filter((arg) => arg !== "--worker" && arg !== "--no-tmux").concat("--worker").map(shellQuote).join(" ");
19561
- const watch = `printf 'roll goal \xB7 ${input.slug}\\n'; tail -n +1 -F ${shellQuote(join29(rt, "live.log"))} | ${shellQuote(input.rollBin)} loop fmt`;
19770
+ const workerArgs = input.args.filter((arg) => arg !== "--worker" && arg !== "--no-tmux").concat("--worker").map(shellQuote3).join(" ");
19771
+ const watch = `printf 'roll goal \xB7 ${input.slug}\\n'; tail -n +1 -F ${shellQuote3(join29(rt, "live.log"))} | ${shellQuote3(input.rollBin)} loop fmt`;
19562
19772
  try {
19563
19773
  if (spawnSync3("tmux", ["has-session", "-t", session], { stdio: "ignore" }).status !== 0) {
19564
19774
  spawnSync3("tmux", ["new-session", "-d", "-s", session, "-x", "200", "-y", "50", "-n", "watch", watch], { stdio: "ignore" });
19565
19775
  }
19566
- const command = `cd ${shellQuote(input.projectPath)} && ROLL_LOOP_GO_WORKER=1 ROLL_LOOP_NO_TMUX=1 ROLL_BIN=${shellQuote(input.rollBin)} ${shellQuote(input.rollBin)} loop go ${workerArgs}`;
19776
+ const command = `cd ${shellQuote3(input.projectPath)} && ROLL_LOOP_GO_WORKER=1 ROLL_LOOP_NO_TMUX=1 ROLL_BIN=${shellQuote3(input.rollBin)} ${shellQuote3(input.rollBin)} loop go ${workerArgs}`;
19567
19777
  return spawnSync3("tmux", ["new-window", "-d", "-t", session, "-n", "go", command], { stdio: "ignore" }).status === 0;
19568
19778
  } catch {
19569
19779
  return false;
@@ -19589,7 +19799,7 @@ function realRunOnce(input) {
19589
19799
  });
19590
19800
  }
19591
19801
  function writeGoal(path, goal) {
19592
- mkdirSync21(dirname17(path), { recursive: true });
19802
+ mkdirSync21(dirname18(path), { recursive: true });
19593
19803
  const tmp = `${path}.tmp-${process.pid}`;
19594
19804
  writeFileSync21(tmp, renderGoalYaml(goal), "utf8");
19595
19805
  renameSync4(tmp, path);
@@ -19700,7 +19910,7 @@ function alertPath(projectPath3, slug) {
19700
19910
  }
19701
19911
  function appendGoalAlert(projectPath3, slug, storyId, cycleId, at) {
19702
19912
  const path = alertPath(projectPath3, slug);
19703
- mkdirSync21(dirname17(path), { recursive: true });
19913
+ mkdirSync21(dirname18(path), { recursive: true });
19704
19914
  const cycleLine = cycleId === void 0 ? "" : ` cycle=${cycleId}`;
19705
19915
  appendFileSync8(path, `[${at}] goal card skipped: ${storyId}${cycleLine} reason=zero delivery streak
19706
19916
  `, "utf8");
@@ -20083,6 +20293,7 @@ function applyRunOptions(goal, opts, at) {
20083
20293
  const maxHours = opts.forSeconds !== void 0 ? opts.forSeconds / 3600 : goal.limits.maxHours;
20084
20294
  return {
20085
20295
  ...goal,
20296
+ scope: opts.scopeSpecified ? opts.scope : goal.scope,
20086
20297
  ...opts.budgetUsd !== void 0 ? { budgetUsd: opts.budgetUsd } : {},
20087
20298
  review: opts.reviewModeSpecified ? { mode: opts.reviewMode } : goal.review,
20088
20299
  limits: {
@@ -20093,6 +20304,24 @@ function applyRunOptions(goal, opts, at) {
20093
20304
  updatedAt: at
20094
20305
  };
20095
20306
  }
20307
+ function latestAlertSummary(projectPath3, slug, sinceSec) {
20308
+ const path = alertPath(projectPath3, slug);
20309
+ try {
20310
+ if (!existsSync34(path) || statSync13(path).mtimeMs / 1e3 < sinceSec)
20311
+ return void 0;
20312
+ const lines2 = readFileSync32(path, "utf8").split("\n").map((line2) => line2.trim()).filter((line2) => line2 !== "");
20313
+ const line = [...lines2].reverse().find((candidate) => /\b(ALERT|WARN|BLOCKED|refused|failed)\b/i.test(candidate));
20314
+ if (line === void 0)
20315
+ return void 0;
20316
+ return line.replace(/\s+/g, " ").slice(0, 220);
20317
+ } catch {
20318
+ return void 0;
20319
+ }
20320
+ }
20321
+ function noCycleTerminalReason(projectPath3, slug, sinceSec) {
20322
+ const alert = latestAlertSummary(projectPath3, slug, sinceSec);
20323
+ return alert === void 0 ? "no_cycle_terminal" : `no_cycle_terminal: ${alert}`;
20324
+ }
20096
20325
  function applyBudgetGate(projectPath3, bus, session, goal, deps) {
20097
20326
  const budgetUsd = goal.budgetUsd;
20098
20327
  if (budgetUsd === void 0)
@@ -20354,7 +20583,7 @@ async function runGoWorker(id, opts, deps) {
20354
20583
  break;
20355
20584
  }
20356
20585
  if (after.summary.cycles <= before.summary.cycles) {
20357
- stopReason = "no_cycle_terminal";
20586
+ stopReason = noCycleTerminalReason(id.path, id.slug, startedSec);
20358
20587
  break;
20359
20588
  }
20360
20589
  }
@@ -20430,13 +20659,13 @@ function loopBranchesRetired() {
20430
20659
 
20431
20660
  // packages/cli/dist/commands/doctor.js
20432
20661
  import { execFileSync as execFileSync11 } from "node:child_process";
20433
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync37, mkdtempSync as mkdtempSync3, readFileSync as readFileSync35, readdirSync as readdirSync14, statSync as statSync14, writeFileSync as writeFileSync23 } from "node:fs";
20662
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync37, mkdtempSync as mkdtempSync3, readFileSync as readFileSync35, readdirSync as readdirSync15, statSync as statSync15, writeFileSync as writeFileSync23 } from "node:fs";
20434
20663
  import { homedir as homedir10, tmpdir as tmpdir4 } from "node:os";
20435
20664
  import { delimiter as delimiter2, join as join32 } from "node:path";
20436
20665
 
20437
20666
  // packages/cli/dist/commands/skills.js
20438
20667
  import { execFileSync as execFileSync10 } from "node:child_process";
20439
- import { existsSync as existsSync36, mkdtempSync as mkdtempSync2, readFileSync as readFileSync34, readdirSync as readdirSync13, statSync as statSync13, writeFileSync as writeFileSync22 } from "node:fs";
20668
+ import { existsSync as existsSync36, mkdtempSync as mkdtempSync2, readFileSync as readFileSync34, readdirSync as readdirSync14, statSync as statSync14, writeFileSync as writeFileSync22 } from "node:fs";
20440
20669
  import { tmpdir as tmpdir3 } from "node:os";
20441
20670
  import { join as join31 } from "node:path";
20442
20671
  function pkgDir() {
@@ -20504,14 +20733,14 @@ function generateCatalog() {
20504
20733
  const dir = skillsDir();
20505
20734
  let entries = [];
20506
20735
  try {
20507
- entries = readdirSync13(dir).sort();
20736
+ entries = readdirSync14(dir).sort();
20508
20737
  } catch {
20509
20738
  entries = [];
20510
20739
  }
20511
20740
  for (const name of entries) {
20512
20741
  const skillDir = join31(dir, name);
20513
20742
  try {
20514
- if (!statSync13(skillDir).isDirectory())
20743
+ if (!statSync14(skillDir).isDirectory())
20515
20744
  continue;
20516
20745
  } catch {
20517
20746
  continue;
@@ -20644,7 +20873,7 @@ function commandOnPath2(bin) {
20644
20873
  if (dir === "")
20645
20874
  continue;
20646
20875
  try {
20647
- const st = statSync14(join32(dir, bin));
20876
+ const st = statSync15(join32(dir, bin));
20648
20877
  if (!st.isFile())
20649
20878
  continue;
20650
20879
  accessSync2(join32(dir, bin), constants2.X_OK);
@@ -20663,7 +20892,7 @@ function agentInstalledByName4(agent, dir) {
20663
20892
  const p = join32(home, ".opencode", "bin", "opencode");
20664
20893
  try {
20665
20894
  accessSync2(p, constants2.X_OK);
20666
- return statSync14(p).isFile();
20895
+ return statSync15(p).isFile();
20667
20896
  } catch {
20668
20897
  return false;
20669
20898
  }
@@ -20682,7 +20911,7 @@ function agentInstalledByName4(agent, dir) {
20682
20911
  }
20683
20912
  function safeIsDir(p) {
20684
20913
  try {
20685
- return statSync14(p).isDirectory();
20914
+ return statSync15(p).isDirectory();
20686
20915
  } catch {
20687
20916
  return false;
20688
20917
  }
@@ -20856,7 +21085,7 @@ function lanesSection(lang9, probe) {
20856
21085
  return lines2;
20857
21086
  let plists = [];
20858
21087
  try {
20859
- plists = readdirSync14(dir).filter((n) => n.startsWith("com.roll.") && n.endsWith(".plist")).sort();
21088
+ plists = readdirSync15(dir).filter((n) => n.startsWith("com.roll.") && n.endsWith(".plist")).sort();
20860
21089
  } catch {
20861
21090
  return lines2;
20862
21091
  }
@@ -20887,7 +21116,7 @@ function launchdStaleSection(lang9) {
20887
21116
  let found = false;
20888
21117
  let plists = [];
20889
21118
  try {
20890
- plists = readdirSync14(dir).filter((n) => n.startsWith("com.roll.") && n.endsWith(".plist")).sort();
21119
+ plists = readdirSync15(dir).filter((n) => n.startsWith("com.roll.") && n.endsWith(".plist")).sort();
20891
21120
  } catch {
20892
21121
  plists = [];
20893
21122
  }
@@ -21231,14 +21460,14 @@ function readSkillBody(projectPath3, opts) {
21231
21460
  }
21232
21461
 
21233
21462
  // packages/cli/dist/commands/gc.js
21234
- import { existsSync as existsSync39, readdirSync as readdirSync15, rmSync as rmSync7, statSync as statSync15 } from "node:fs";
21463
+ import { existsSync as existsSync39, readdirSync as readdirSync16, rmSync as rmSync8, statSync as statSync16 } from "node:fs";
21235
21464
  import { join as join35 } from "node:path";
21236
21465
  var RUN_ID_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/;
21237
21466
  function runsInDir(dir) {
21238
21467
  const out3 = [];
21239
21468
  let entries = [];
21240
21469
  try {
21241
- entries = readdirSync15(dir, { withFileTypes: true });
21470
+ entries = readdirSync16(dir, { withFileTypes: true });
21242
21471
  } catch {
21243
21472
  return out3;
21244
21473
  }
@@ -21246,7 +21475,7 @@ function runsInDir(dir) {
21246
21475
  if (!e.isDirectory() || !RUN_ID_RE.test(e.name))
21247
21476
  continue;
21248
21477
  try {
21249
- out3.push({ runId: e.name, mtimeSec: Math.floor(statSync15(join35(dir, e.name)).mtimeMs / 1e3) });
21478
+ out3.push({ runId: e.name, mtimeSec: Math.floor(statSync16(join35(dir, e.name)).mtimeMs / 1e3) });
21250
21479
  } catch {
21251
21480
  }
21252
21481
  }
@@ -21279,7 +21508,7 @@ function collectStoryArchives(projectPath3) {
21279
21508
  }
21280
21509
  function safeDirs(root) {
21281
21510
  try {
21282
- return readdirSync15(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
21511
+ return readdirSync16(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
21283
21512
  } catch {
21284
21513
  return [];
21285
21514
  }
@@ -21321,7 +21550,7 @@ function gcCommand(args, deps = {}) {
21321
21550
  lines2.push(` ${dryRun ? "would delete" : "deleted"}: ${sa.storyId}/${runId}`);
21322
21551
  if (!dryRun) {
21323
21552
  try {
21324
- rmSync7(join35(sa.dir, runId), { recursive: true, force: true });
21553
+ rmSync8(join35(sa.dir, runId), { recursive: true, force: true });
21325
21554
  } catch {
21326
21555
  }
21327
21556
  }
@@ -21639,9 +21868,9 @@ ${rowNote}`);
21639
21868
 
21640
21869
  // packages/cli/dist/commands/init.js
21641
21870
  import { spawnSync as spawnSync4 } from "node:child_process";
21642
- import { copyFileSync as copyFileSync2, existsSync as existsSync42, mkdirSync as mkdirSync25, readSync, readFileSync as readFileSync38, readdirSync as readdirSync16, realpathSync as realpathSync4, statSync as statSync16, writeFileSync as writeFileSync26 } from "node:fs";
21871
+ import { copyFileSync as copyFileSync2, existsSync as existsSync42, mkdirSync as mkdirSync25, readSync, readFileSync as readFileSync38, readdirSync as readdirSync17, realpathSync as realpathSync4, statSync as statSync17, writeFileSync as writeFileSync26 } from "node:fs";
21643
21872
  import { homedir as homedir12 } from "node:os";
21644
- import { dirname as dirname18, join as join40 } from "node:path";
21873
+ import { dirname as dirname19, join as join40 } from "node:path";
21645
21874
  function err10(line) {
21646
21875
  const { RED, NC } = pal3();
21647
21876
  process.stderr.write(`${RED}[roll]${NC} ${line}
@@ -21744,7 +21973,7 @@ function countNonEmptyFiles(dir) {
21744
21973
  function isLegacyProject(projectDir) {
21745
21974
  for (const dir of ["src", "app", "lib", "pkg", "cmd"]) {
21746
21975
  const p = join40(projectDir, dir);
21747
- if (existsSync42(p) && statSync16(p).isDirectory()) {
21976
+ if (existsSync42(p) && statSync17(p).isDirectory()) {
21748
21977
  if (countNonEmptyFiles(p) >= 10)
21749
21978
  return true;
21750
21979
  }
@@ -21790,7 +22019,7 @@ function legacyFileSummary(projectDir) {
21790
22019
  const parts = [];
21791
22020
  for (const dir of ["src", "app", "lib", "pkg", "cmd"]) {
21792
22021
  const p = join40(projectDir, dir);
21793
- if (existsSync42(p) && statSync16(p).isDirectory()) {
22022
+ if (existsSync42(p) && statSync17(p).isDirectory()) {
21794
22023
  const count = countNonEmptyFiles(p);
21795
22024
  if (count >= 10)
21796
22025
  parts.push(`${count} files in ${dir}/`);
@@ -21827,7 +22056,7 @@ function legacyFileSummary(projectDir) {
21827
22056
  }
21828
22057
  }
21829
22058
  try {
21830
- if (readdirSync16(projectDir).some((name) => name.endsWith(".tf")))
22059
+ if (readdirSync17(projectDir).some((name) => name.endsWith(".tf")))
21831
22060
  parts.push("Terraform .tf files");
21832
22061
  } catch {
21833
22062
  }
@@ -22186,12 +22415,12 @@ function writeBacklog(path, summary) {
22186
22415
  summary.push("unchanged|.roll/backlog.md");
22187
22416
  return;
22188
22417
  }
22189
- mkdirSync25(dirname18(path), { recursive: true });
22418
+ mkdirSync25(dirname19(path), { recursive: true });
22190
22419
  writeFileSync26(path, BACKLOG_TEMPLATE);
22191
22420
  summary.push("created|.roll/backlog.md");
22192
22421
  }
22193
22422
  function ensureFeaturesDir(path, summary) {
22194
- if (existsSync42(path) && statSync16(path).isDirectory()) {
22423
+ if (existsSync42(path) && statSync17(path).isDirectory()) {
22195
22424
  summary.push("unchanged|.roll/features/");
22196
22425
  return;
22197
22426
  }
@@ -22213,7 +22442,7 @@ function writeFeaturesMd(path, summary) {
22213
22442
  summary.push("unchanged|.roll/features.md");
22214
22443
  return;
22215
22444
  }
22216
- mkdirSync25(dirname18(path), { recursive: true });
22445
+ mkdirSync25(dirname19(path), { recursive: true });
22217
22446
  writeFileSync26(path, FEATURES_TEMPLATE);
22218
22447
  summary.push("created|.roll/features.md");
22219
22448
  }
@@ -22227,7 +22456,7 @@ function initSeedAgentRoutes(templateName, projectDir, summary) {
22227
22456
  if (!existsSync42(src)) {
22228
22457
  return 1;
22229
22458
  }
22230
- mkdirSync25(dirname18(dest), { recursive: true });
22459
+ mkdirSync25(dirname19(dest), { recursive: true });
22231
22460
  copyFileSync2(src, dest);
22232
22461
  summary.push("created|.roll/agent-routes.yaml");
22233
22462
  return 0;
@@ -22254,7 +22483,7 @@ function scaffoldPairing(projectDir, summary) {
22254
22483
  summary.push("unchanged|.roll/pairing.yaml");
22255
22484
  return;
22256
22485
  }
22257
- mkdirSync25(dirname18(path), { recursive: true });
22486
+ mkdirSync25(dirname19(path), { recursive: true });
22258
22487
  writeFileSync26(path, renderPairingConfig(defaultPairingConfig(agentsInstalled(realAgentEnv()))));
22259
22488
  summary.push("created|.roll/pairing.yaml");
22260
22489
  }
@@ -22289,7 +22518,7 @@ roll_version: "${changeset.rollVersion}"
22289
22518
  }
22290
22519
  function writeChangeset(projectDir, changeset) {
22291
22520
  const path = changesetPath(projectDir);
22292
- mkdirSync25(dirname18(path), { recursive: true });
22521
+ mkdirSync25(dirname19(path), { recursive: true });
22293
22522
  writeFileSync26(path, renderChangeset(changeset));
22294
22523
  }
22295
22524
  function beginChangeset(projectDir) {
@@ -22803,16 +23032,16 @@ async function loopFmtCommand(args) {
22803
23032
 
22804
23033
  // packages/cli/dist/commands/loop-maint.js
22805
23034
  import { spawnSync as spawnSync6 } from "node:child_process";
22806
- import { existsSync as existsSync44, mkdirSync as mkdirSync27, readFileSync as readFileSync40, readdirSync as readdirSync18, renameSync as renameSync6, rmSync as rmSync9, statSync as statSync17, writeFileSync as writeFileSync28 } from "node:fs";
23035
+ import { existsSync as existsSync44, mkdirSync as mkdirSync27, readFileSync as readFileSync40, readdirSync as readdirSync19, renameSync as renameSync6, rmSync as rmSync10, statSync as statSync18, writeFileSync as writeFileSync28 } from "node:fs";
22807
23036
  import { homedir as homedir14 } from "node:os";
22808
- import { basename as basename10, dirname as dirname20, join as join42 } from "node:path";
23037
+ import { basename as basename10, dirname as dirname21, join as join42 } from "node:path";
22809
23038
 
22810
23039
  // packages/cli/dist/commands/loop-sched.js
22811
23040
  import { createHash as createHash4 } from "node:crypto";
22812
23041
  import { spawn as spawn4, spawnSync as spawnSync5 } from "node:child_process";
22813
- import { existsSync as existsSync43, mkdirSync as mkdirSync26, readFileSync as readFileSync39, renameSync as renameSync5, rmSync as rmSync8, writeFileSync as writeFileSync27, readdirSync as readdirSync17 } from "node:fs";
23042
+ import { existsSync as existsSync43, mkdirSync as mkdirSync26, readFileSync as readFileSync39, renameSync as renameSync5, rmSync as rmSync9, writeFileSync as writeFileSync27, readdirSync as readdirSync18 } from "node:fs";
22814
23043
  import { homedir as homedir13 } from "node:os";
22815
- import { dirname as dirname19, join as join41 } from "node:path";
23044
+ import { dirname as dirname20, join as join41 } from "node:path";
22816
23045
  function realDeps5() {
22817
23046
  return {
22818
23047
  identity: () => projectIdentity(),
@@ -22839,12 +23068,12 @@ function realDeps5() {
22839
23068
  // the inner lock; Ctrl-C stops the TAIL only (the cycle lives in tmux).
22840
23069
  observe: (rt) => new Promise((resolve4) => {
22841
23070
  const lock = join41(rt, "inner.lock");
22842
- const tail = spawn4("tail", ["-n", "+1", "-F", join41(rt, "live.log")], { stdio: "inherit" });
23071
+ const tail2 = spawn4("tail", ["-n", "+1", "-F", join41(rt, "live.log")], { stdio: "inherit" });
22843
23072
  let sawLock = false;
22844
23073
  const t0 = Date.now();
22845
23074
  const finish = () => {
22846
23075
  try {
22847
- tail.kill("SIGTERM");
23076
+ tail2.kill("SIGTERM");
22848
23077
  } catch {
22849
23078
  }
22850
23079
  process.removeListener("SIGINT", finish);
@@ -23073,7 +23302,7 @@ function pathValue() {
23073
23302
  ].join(":");
23074
23303
  }
23075
23304
  function writeExecutable(path, content) {
23076
- mkdirSync26(dirname19(path), { recursive: true });
23305
+ mkdirSync26(dirname20(path), { recursive: true });
23077
23306
  writeFileSync27(path, content, { mode: 493 });
23078
23307
  }
23079
23308
  function pauseMarkerPath2(projectPath3, slug) {
@@ -23223,7 +23452,7 @@ async function loopOffCommand(_args, deps = realDeps5()) {
23223
23452
  continue;
23224
23453
  await deps.launchd.uninstall(uid, label4);
23225
23454
  try {
23226
- rmSync8(join41(launchAgentsDir2(), `${label4}.plist`), { force: true });
23455
+ rmSync9(join41(launchAgentsDir2(), `${label4}.plist`), { force: true });
23227
23456
  } catch {
23228
23457
  }
23229
23458
  process.stdout.write(` swept zombie lane: ${label4}
@@ -23239,7 +23468,7 @@ function launchAgentsDir2() {
23239
23468
  }
23240
23469
  function listRollLaneLabels(slug) {
23241
23470
  try {
23242
- return readdirSync17(launchAgentsDir2()).filter((n) => n.startsWith("com.roll.") && n.endsWith(`.${slug}.plist`)).map((n) => n.replace(/\.plist$/, "")).sort();
23471
+ return readdirSync18(launchAgentsDir2()).filter((n) => n.startsWith("com.roll.") && n.endsWith(`.${slug}.plist`)).map((n) => n.replace(/\.plist$/, "")).sort();
23243
23472
  } catch {
23244
23473
  return [];
23245
23474
  }
@@ -23247,7 +23476,7 @@ function listRollLaneLabels(slug) {
23247
23476
  async function loopPauseCommand(_args, deps = realDeps5()) {
23248
23477
  const id = await deps.identity();
23249
23478
  const marker = pauseMarkerPath2(id.path, id.slug);
23250
- mkdirSync26(dirname19(marker), { recursive: true });
23479
+ mkdirSync26(dirname20(marker), { recursive: true });
23251
23480
  const already = existsSync43(marker);
23252
23481
  if (!already)
23253
23482
  writeFileSync27(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
@@ -23264,7 +23493,7 @@ async function loopResumeCommand(_args, deps = realDeps5()) {
23264
23493
  const id = await deps.identity();
23265
23494
  const marker = pauseMarkerPath2(id.path, id.slug);
23266
23495
  const existed = existsSync43(marker);
23267
- rmSync8(marker, { force: true });
23496
+ rmSync9(marker, { force: true });
23268
23497
  const rt = join41(id.path, ".roll", "loop");
23269
23498
  const counterFile = join41(rt, "consecutive-fails");
23270
23499
  if (existsSync43(counterFile)) {
@@ -23284,7 +23513,7 @@ async function loopResumeCommand(_args, deps = realDeps5()) {
23284
23513
  }
23285
23514
  const healDir2 = join41((process.env["ROLL_LOOP_DIR"] ?? "").trim() || join41(homedir13(), ".shared", "roll", "loop"), "heal");
23286
23515
  try {
23287
- rmSync8(healDir2, { recursive: true, force: true });
23516
+ rmSync9(healDir2, { recursive: true, force: true });
23288
23517
  } catch {
23289
23518
  }
23290
23519
  if (existed) {
@@ -23404,25 +23633,25 @@ function loopResetCommand(_args = []) {
23404
23633
  const slug = projectSlug2();
23405
23634
  const state = stateFile(slug);
23406
23635
  if (existsSync44(state)) {
23407
- rmSync9(state, { force: true });
23636
+ rmSync10(state, { force: true });
23408
23637
  ok6(msg5("loop.loop_state_cleared_will_start_fresh"));
23409
23638
  } else {
23410
23639
  info4(msg5("loop.no_loop_state_to_clear"));
23411
23640
  }
23412
- rmSync9(healDir(), { recursive: true, force: true });
23641
+ rmSync10(healDir(), { recursive: true, force: true });
23413
23642
  return 0;
23414
23643
  }
23415
23644
  function loopMuteCommand(_args = []) {
23416
23645
  const slug = projectSlug2();
23417
23646
  const file = muteFile(slug);
23418
- mkdirSync27(dirname20(file), { recursive: true });
23647
+ mkdirSync27(dirname21(file), { recursive: true });
23419
23648
  writeFileSync28(file, "");
23420
23649
  ok6(msg5("loop.muted_auto_attach_disabled"));
23421
23650
  return 0;
23422
23651
  }
23423
23652
  function loopUnmuteCommand(_args = []) {
23424
23653
  const slug = projectSlug2();
23425
- rmSync9(muteFile(slug), { force: true });
23654
+ rmSync10(muteFile(slug), { force: true });
23426
23655
  ok6(msg5("loop.unmuted_auto_attach_live"));
23427
23656
  return 0;
23428
23657
  }
@@ -23438,7 +23667,7 @@ function realGcDeps() {
23438
23667
  }
23439
23668
  function fileMtimeSec(f) {
23440
23669
  try {
23441
- return Math.floor(statSync17(f).mtimeMs / 1e3);
23670
+ return Math.floor(statSync18(f).mtimeMs / 1e3);
23442
23671
  } catch {
23443
23672
  return 0;
23444
23673
  }
@@ -23453,7 +23682,7 @@ function plistWorkingDir(plistPath) {
23453
23682
  }
23454
23683
  function isDir2(p) {
23455
23684
  try {
23456
- return statSync17(p).isDirectory();
23685
+ return statSync18(p).isDirectory();
23457
23686
  } catch {
23458
23687
  return false;
23459
23688
  }
@@ -23464,7 +23693,7 @@ function gcStamp(d) {
23464
23693
  }
23465
23694
  function loopDirEntries(loopDir) {
23466
23695
  try {
23467
- return readdirSync18(loopDir);
23696
+ return readdirSync19(loopDir);
23468
23697
  } catch {
23469
23698
  return [];
23470
23699
  }
@@ -23538,7 +23767,7 @@ function loopGcCommand(args = [], deps = realGcDeps()) {
23538
23767
  `events-${slug}.ndjson`,
23539
23768
  `cron-${slug}.log`
23540
23769
  ]) {
23541
- rmSync9(join42(loopDir, rel), { force: true });
23770
+ rmSync10(join42(loopDir, rel), { force: true });
23542
23771
  }
23543
23772
  gcCount++;
23544
23773
  }
@@ -23554,7 +23783,7 @@ function loopGcCommand(args = [], deps = realGcDeps()) {
23554
23783
  process.stdout.write(`[DRY-RUN] tmp debris: ${basename10(f)}
23555
23784
  `);
23556
23785
  else
23557
- rmSync9(f, { force: true });
23786
+ rmSync10(f, { force: true });
23558
23787
  gcCount++;
23559
23788
  continue;
23560
23789
  }
@@ -23564,7 +23793,7 @@ function loopGcCommand(args = [], deps = realGcDeps()) {
23564
23793
  process.stdout.write(`[DRY-RUN] old backup: ${basename10(f)}
23565
23794
  `);
23566
23795
  else
23567
- rmSync9(f, { force: true });
23796
+ rmSync10(f, { force: true });
23568
23797
  gcCount++;
23569
23798
  }
23570
23799
  continue;
@@ -23575,7 +23804,7 @@ function loopGcCommand(args = [], deps = realGcDeps()) {
23575
23804
  process.stdout.write(`[DRY-RUN] old migrated: ${basename10(f)}
23576
23805
  `);
23577
23806
  else
23578
- rmSync9(f, { force: true });
23807
+ rmSync10(f, { force: true });
23579
23808
  gcCount++;
23580
23809
  }
23581
23810
  continue;
@@ -23586,7 +23815,7 @@ function loopGcCommand(args = [], deps = realGcDeps()) {
23586
23815
  process.stdout.write(`[DRY-RUN] old .bak: ${basename10(f)}
23587
23816
  `);
23588
23817
  else
23589
- rmSync9(f, { force: true });
23818
+ rmSync10(f, { force: true });
23590
23819
  gcCount++;
23591
23820
  }
23592
23821
  }
@@ -23598,7 +23827,7 @@ function loopGcCommand(args = [], deps = realGcDeps()) {
23598
23827
  }
23599
23828
  function statSafeIsFile(f) {
23600
23829
  try {
23601
- return statSync17(f).isFile();
23830
+ return statSync18(f).isFile();
23602
23831
  } catch {
23603
23832
  return false;
23604
23833
  }
@@ -23645,7 +23874,7 @@ function loopTestCommand(args = [], deps = realTestDeps()) {
23645
23874
  if (cmd === "")
23646
23875
  cmd = defaultSmokeCmd(agent);
23647
23876
  const testRunner = join42(loopDir, `run-${slug}-test.sh`);
23648
- mkdirSync27(dirname20(testRunner), { recursive: true });
23877
+ mkdirSync27(dirname21(testRunner), { recursive: true });
23649
23878
  writeFileSync28(testRunner, buildLoopTestRunnerScript({ projectPath: projectPath3, slug, cmd }), { mode: 493 });
23650
23879
  info4(msg5("loop.generating_test_runner_agent", agent));
23651
23880
  info4(msg5("loop.starting_smoke_test_agent", agent));
@@ -23663,14 +23892,14 @@ function loopTestCommand(args = [], deps = realTestDeps()) {
23663
23892
  }
23664
23893
 
23665
23894
  // packages/cli/dist/commands/loop-pr-inbox.js
23666
- import { appendFileSync as appendFileSync11, mkdirSync as mkdirSync29, readFileSync as readFileSync42, writeFileSync as writeFileSync30, existsSync as existsSync46, rmSync as rmSync11 } from "node:fs";
23667
- import { dirname as dirname22, join as join44 } from "node:path";
23895
+ import { appendFileSync as appendFileSync11, mkdirSync as mkdirSync29, readFileSync as readFileSync42, writeFileSync as writeFileSync30, existsSync as existsSync46, rmSync as rmSync12 } from "node:fs";
23896
+ import { dirname as dirname23, join as join44 } from "node:path";
23668
23897
 
23669
23898
  // packages/cli/dist/commands/loop-pr-heal.js
23670
23899
  import { execFileSync as execFileSync12, spawn as spawn5 } from "node:child_process";
23671
- import { appendFileSync as appendFileSync10, existsSync as existsSync45, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync4, readFileSync as readFileSync41, rmSync as rmSync10, writeFileSync as writeFileSync29 } from "node:fs";
23900
+ import { appendFileSync as appendFileSync10, existsSync as existsSync45, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync4, readFileSync as readFileSync41, rmSync as rmSync11, writeFileSync as writeFileSync29 } from "node:fs";
23672
23901
  import { tmpdir as tmpdir5 } from "node:os";
23673
- import { dirname as dirname21, join as join43 } from "node:path";
23902
+ import { dirname as dirname22, join as join43 } from "node:path";
23674
23903
  function runtimeDir3() {
23675
23904
  const o = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
23676
23905
  return o !== "" ? o : join43(process.cwd(), ".roll", "loop");
@@ -23703,7 +23932,7 @@ function realHealDeps() {
23703
23932
  healMax: () => resolveHealMax(process.env["ROLL_LOOP_NO_HEAL"], process.env["ROLL_LOOP_HEAL_MAX"]),
23704
23933
  prHealCount: (num4) => parseInt(stateGet(readState(), `heal_count.pr:${num4}`) || "0", 10) || 0,
23705
23934
  setPrHealCount: (num4, n) => {
23706
- mkdirSync28(dirname21(statePath()), { recursive: true });
23935
+ mkdirSync28(dirname22(statePath()), { recursive: true });
23707
23936
  writeFileSync29(statePath(), stateUpsert(readState(), `heal_count.pr:${num4}`, n));
23708
23937
  },
23709
23938
  lock: (num4) => {
@@ -23726,7 +23955,7 @@ function realHealDeps() {
23726
23955
  }
23727
23956
  return { lockPresent: true, lockPidAlive: alive };
23728
23957
  },
23729
- reclaimLock: (num4) => rmSync10(lockPath(num4), { force: true }),
23958
+ reclaimLock: (num4) => rmSync11(lockPath(num4), { force: true }),
23730
23959
  writeLock: (num4) => {
23731
23960
  mkdirSync28(healDir(), { recursive: true });
23732
23961
  writeFileSync29(lockPath(num4), String(process.pid));
@@ -23739,7 +23968,7 @@ function realHealDeps() {
23739
23968
  }
23740
23969
  },
23741
23970
  appendAlert: (line) => {
23742
- mkdirSync28(dirname21(alertPath2()), { recursive: true });
23971
+ mkdirSync28(dirname22(alertPath2()), { recursive: true });
23743
23972
  appendFileSync10(alertPath2(), line + "\n");
23744
23973
  },
23745
23974
  now: nowIso,
@@ -23819,7 +24048,7 @@ ${gh2(["-R", repo, "run", "view", runId, "--log-failed"]).split("\n").slice(-200
23819
24048
  execFileSync12("git", ["fetch", "origin", headRef], { stdio: "ignore" });
23820
24049
  execFileSync12("git", ["worktree", "add", wt, `origin/${headRef}`], { stdio: "ignore" });
23821
24050
  } catch {
23822
- rmSync10(tmpRoot, { recursive: true, force: true });
24051
+ rmSync11(tmpRoot, { recursive: true, force: true });
23823
24052
  return 1;
23824
24053
  }
23825
24054
  const agent = projectAgent() || "claude";
@@ -23841,7 +24070,7 @@ ${gh2(["-R", repo, "run", "view", runId, "--log-failed"]).split("\n").slice(-200
23841
24070
  execFileSync12("git", ["worktree", "remove", "--force", wt], { stdio: "ignore" });
23842
24071
  } catch {
23843
24072
  }
23844
- rmSync10(tmpRoot, { recursive: true, force: true });
24073
+ rmSync11(tmpRoot, { recursive: true, force: true });
23845
24074
  return 0;
23846
24075
  }
23847
24076
  function realRebaseDeps() {
@@ -23901,7 +24130,7 @@ function realRebaseDeps() {
23901
24130
  }
23902
24131
  },
23903
24132
  appendAlert: (line) => {
23904
- mkdirSync28(dirname21(alertPath2()), { recursive: true });
24133
+ mkdirSync28(dirname22(alertPath2()), { recursive: true });
23905
24134
  appendFileSync10(alertPath2(), line + "\n");
23906
24135
  },
23907
24136
  now: () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
@@ -24051,7 +24280,7 @@ function nowSec() {
24051
24280
  }
24052
24281
  function writeTickFile(tick) {
24053
24282
  const file = tickPath();
24054
- mkdirSync29(dirname22(file), { recursive: true });
24283
+ mkdirSync29(dirname23(file), { recursive: true });
24055
24284
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
24056
24285
  appendFileSync11(file, `${JSON.stringify({ ts, ...tick })}
24057
24286
  `);
@@ -24093,14 +24322,14 @@ function checkDeadTickStreak(file, alert, markerPath = deadTickMarkerPath()) {
24093
24322
  } else if (verdict === "recovered") {
24094
24323
  alert("pr-loop recovered: healthy tick after an alerted dead-tick streak (FIX-233)");
24095
24324
  try {
24096
- rmSync11(markerPath, { force: true });
24325
+ rmSync12(markerPath, { force: true });
24097
24326
  } catch {
24098
24327
  }
24099
24328
  }
24100
24329
  }
24101
24330
  function appendAlert(line) {
24102
24331
  const file = alertPath3();
24103
- mkdirSync29(dirname22(file), { recursive: true });
24332
+ mkdirSync29(dirname23(file), { recursive: true });
24104
24333
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
24105
24334
  appendFileSync11(file, `[${ts}] ${line}
24106
24335
  `);
@@ -24116,7 +24345,7 @@ function rebaseCircuitAllowed(num4) {
24116
24345
  writeRebaseAttempts(state, num4, verdict.freshTimestamps);
24117
24346
  if (!verdict.allowed) {
24118
24347
  const file = alertPath3();
24119
- mkdirSync29(dirname22(file), { recursive: true });
24348
+ mkdirSync29(dirname23(file), { recursive: true });
24120
24349
  const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
24121
24350
  writeFileSync30(file, [
24122
24351
  `# ALERT \u2014 PR rebase circuit breaker tripped`,
@@ -24172,7 +24401,7 @@ function upsertRebaseAttempts(stateBody, pr, value) {
24172
24401
  `;
24173
24402
  }
24174
24403
  function writeRebaseAttempts(state, pr, timestamps) {
24175
- mkdirSync29(dirname22(state), { recursive: true });
24404
+ mkdirSync29(dirname23(state), { recursive: true });
24176
24405
  let body = "";
24177
24406
  try {
24178
24407
  body = readFileSync42(state, "utf8");
@@ -24255,20 +24484,20 @@ async function loopPrInboxCommand(_args, deps = realDeps6()) {
24255
24484
 
24256
24485
  // packages/cli/dist/commands/loop-run-once.js
24257
24486
  import { appendFileSync as appendFileSync15, existsSync as existsSync55, mkdirSync as mkdirSync34, readFileSync as readFileSync51, writeFileSync as writeFileSync36 } from "node:fs";
24258
- import { dirname as dirname26, join as join54 } from "node:path";
24487
+ import { dirname as dirname28, join as join54 } from "node:path";
24259
24488
 
24260
24489
  // packages/cli/dist/runner/executor.js
24261
- import { execFile as execFile9 } from "node:child_process";
24262
- import { appendFileSync as appendFileSync13, existsSync as existsSync53, lstatSync as lstatSync2, mkdirSync as mkdirSync32, readdirSync as readdirSync23, readFileSync as readFileSync48, realpathSync as realpathSync5, rmSync as rmSync12, symlinkSync as symlinkSync3, unlinkSync, writeFileSync as writeFileSync33 } from "node:fs";
24263
- import { dirname as dirname24, join as join52 } from "node:path";
24264
- import { promisify as promisify9 } from "node:util";
24490
+ import { execFile as execFile10 } from "node:child_process";
24491
+ import { appendFileSync as appendFileSync13, existsSync as existsSync53, lstatSync as lstatSync2, mkdirSync as mkdirSync32, readdirSync as readdirSync24, readFileSync as readFileSync48, realpathSync as realpathSync5, rmSync as rmSync13, symlinkSync as symlinkSync3, unlinkSync, writeFileSync as writeFileSync33 } from "node:fs";
24492
+ import { dirname as dirname26, join as join52 } from "node:path";
24493
+ import { promisify as promisify10 } from "node:util";
24265
24494
 
24266
24495
  // packages/cli/dist/runner/peer-gate.js
24267
- import { execFile as execFile8 } from "node:child_process";
24268
- import { existsSync as existsSync47, readdirSync as readdirSync19 } from "node:fs";
24496
+ import { execFile as execFile9 } from "node:child_process";
24497
+ import { existsSync as existsSync47, readdirSync as readdirSync20 } from "node:fs";
24269
24498
  import { join as join45 } from "node:path";
24270
- import { promisify as promisify8 } from "node:util";
24271
- var execFileAsync8 = promisify8(execFile8);
24499
+ import { promisify as promisify9 } from "node:util";
24500
+ var execFileAsync9 = promisify9(execFile9);
24272
24501
  var HIGH_RISK = [/^\.github\/workflows\//, /^packages\/infra\/src\/(git|github|process)\.ts$/];
24273
24502
  function assessComplexity(files) {
24274
24503
  const reasons = [];
@@ -24289,7 +24518,7 @@ function assessComplexity(files) {
24289
24518
  }
24290
24519
  async function cycleChangedFiles(worktreeCwd) {
24291
24520
  try {
24292
- const { stdout } = await execFileAsync8("git", ["diff", "--name-only", "origin/main...HEAD"], { cwd: worktreeCwd, encoding: "utf8" });
24521
+ const { stdout } = await execFileAsync9("git", ["diff", "--name-only", "origin/main...HEAD"], { cwd: worktreeCwd, encoding: "utf8" });
24293
24522
  return stdout.split("\n").map((l) => l.trim()).filter((l) => l !== "");
24294
24523
  } catch {
24295
24524
  return [];
@@ -24300,7 +24529,7 @@ function peerEvidencePresent(runtimeDir6, cycleId) {
24300
24529
  if (!existsSync47(dir))
24301
24530
  return false;
24302
24531
  try {
24303
- return readdirSync19(dir).some((f) => f.startsWith(`cycle-${cycleId}.`));
24532
+ return readdirSync20(dir).some((f) => f.startsWith(`cycle-${cycleId}.`));
24304
24533
  } catch {
24305
24534
  return false;
24306
24535
  }
@@ -24324,8 +24553,8 @@ async function runPeerGate(worktreeCwd, runtimeDir6, cycleId, sinks) {
24324
24553
  }
24325
24554
 
24326
24555
  // packages/cli/dist/runner/attest-gate.js
24327
- import { existsSync as existsSync48, readFileSync as readFileSync43, readdirSync as readdirSync20, statSync as statSync18 } from "node:fs";
24328
- import { join as join46 } from "node:path";
24556
+ import { existsSync as existsSync48, readFileSync as readFileSync43, readdirSync as readdirSync21, statSync as statSync19 } from "node:fs";
24557
+ import { dirname as dirname24, join as join46 } from "node:path";
24329
24558
  function reportCandidates(worktreeCwd, storyId) {
24330
24559
  return [join46(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId))];
24331
24560
  }
@@ -24335,7 +24564,7 @@ function acMapCandidates(worktreeCwd, storyId) {
24335
24564
  function storySpecPath(worktreeCwd, storyId) {
24336
24565
  const featuresDir = join46(worktreeCwd, ".roll", "features");
24337
24566
  try {
24338
- for (const epic of readdirSync20(featuresDir, { withFileTypes: true })) {
24567
+ for (const epic of readdirSync21(featuresDir, { withFileTypes: true })) {
24339
24568
  if (!epic.isDirectory())
24340
24569
  continue;
24341
24570
  const spec = join46(featuresDir, epic.name, storyId, "spec.md");
@@ -24355,7 +24584,7 @@ function storyHasAcBlock(worktreeCwd, storyId) {
24355
24584
  if (spec === null)
24356
24585
  return null;
24357
24586
  try {
24358
- return /\*\*AC:\*\*[\s\S]*?-\s+\[[ xX]\]\s+/.test(readFileSync43(spec, "utf8"));
24587
+ return acForStory(readFileSync43(spec, "utf8"), storyId, { fileOwned: true }).length > 0;
24359
24588
  } catch {
24360
24589
  return null;
24361
24590
  }
@@ -24370,13 +24599,64 @@ function storyRequiresScreenshot(worktreeCwd, storyId) {
24370
24599
  return false;
24371
24600
  }
24372
24601
  }
24602
+ function readAcMapEntries(worktreeCwd, storyId) {
24603
+ const path = acMapCandidates(worktreeCwd, storyId)[0];
24604
+ if (path === void 0 || !existsSync48(path))
24605
+ return null;
24606
+ try {
24607
+ const parsed = JSON.parse(readFileSync43(path, "utf8"));
24608
+ return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "object" && x !== null) : null;
24609
+ } catch {
24610
+ return null;
24611
+ }
24612
+ }
24613
+ function evidenceManifest(worktreeCwd, storyId) {
24614
+ const report = existingReport(worktreeCwd, storyId);
24615
+ if (report === null)
24616
+ return null;
24617
+ const path = join46(dirname24(report), "evidence.json");
24618
+ if (!existsSync48(path))
24619
+ return null;
24620
+ try {
24621
+ const parsed = JSON.parse(readFileSync43(path, "utf8"));
24622
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
24623
+ } catch {
24624
+ return null;
24625
+ }
24626
+ }
24627
+ function hasMachineCaptureSkip2(worktreeCwd, storyId) {
24628
+ const manifest = evidenceManifest(worktreeCwd, storyId);
24629
+ if (manifest === null || !Array.isArray(manifest.captures))
24630
+ return false;
24631
+ return manifest.captures.some((raw) => {
24632
+ if (typeof raw !== "object" || raw === null)
24633
+ return false;
24634
+ const row2 = raw;
24635
+ return row2["taken"] === false && typeof row2["skipped"] === "string" && row2["skipped"] !== "";
24636
+ });
24637
+ }
24638
+ function passAcVisualFloor(worktreeCwd, storyId) {
24639
+ const entries = readAcMapEntries(worktreeCwd, storyId);
24640
+ if (entries === null)
24641
+ return { ok: true };
24642
+ const pass = entries.filter((e) => e.status === "pass");
24643
+ if (pass.length === 0)
24644
+ return { ok: true };
24645
+ const missing = pass.filter((e) => !(e.evidence ?? []).some((ev) => ev.kind === "screenshot" && typeof ev.href === "string" && ev.href !== ""));
24646
+ if (missing.length === 0)
24647
+ return { ok: true };
24648
+ if (hasMachineCaptureSkip2(worktreeCwd, storyId))
24649
+ return { ok: true, reason: "machine capture skip present" };
24650
+ const ids = missing.map((e) => e.ac ?? "?").join(", ");
24651
+ return { ok: false, reason: `pass AC(s) lack screenshot evidence or machine capture skip: ${ids}` };
24652
+ }
24373
24653
  function verificationReportPath(worktreeCwd, storyId) {
24374
24654
  return reportCandidates(worktreeCwd, storyId)[0];
24375
24655
  }
24376
24656
  function existingReport(worktreeCwd, storyId) {
24377
24657
  for (const p of reportCandidates(worktreeCwd, storyId)) {
24378
24658
  try {
24379
- if (statSync18(p).isFile())
24659
+ if (statSync19(p).isFile())
24380
24660
  return p;
24381
24661
  } catch {
24382
24662
  }
@@ -24390,7 +24670,7 @@ function verificationReportFresh(worktreeCwd, storyId, sinceSec) {
24390
24670
  if (p === null)
24391
24671
  return false;
24392
24672
  try {
24393
- const st = statSync18(p);
24673
+ const st = statSync19(p);
24394
24674
  if (sinceSec === void 0)
24395
24675
  return true;
24396
24676
  return st.mtimeMs / 1e3 >= sinceSec;
@@ -24424,8 +24704,10 @@ function verificationReportHasContent(worktreeCwd, storyId) {
24424
24704
  }
24425
24705
  if (positiveWithEvidence === 0)
24426
24706
  return false;
24707
+ if (!passAcVisualFloor(worktreeCwd, storyId).ok)
24708
+ return false;
24427
24709
  if (storyRequiresScreenshot(worktreeCwd, storyId)) {
24428
- return /<figure class="shot\b|href="screenshots\/|src="screenshots\/|taken":false|skipped|honest-skip/i.test(html);
24710
+ return /<figure class="shot\b|href="screenshots\/|src="screenshots\//i.test(html) || hasMachineCaptureSkip2(worktreeCwd, storyId);
24429
24711
  }
24430
24712
  return true;
24431
24713
  } catch {
@@ -24453,7 +24735,8 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks) {
24453
24735
  if (fresh && verificationReportHasContent(worktreeCwd, storyId)) {
24454
24736
  const score = evaluateSelfScoreGate(worktreeCwd, storyId);
24455
24737
  if (score.status === "pass") {
24456
- const reasons3 = ["fresh acceptance report present", score.reason];
24738
+ const visual = passAcVisualFloor(worktreeCwd, storyId);
24739
+ const reasons3 = ["fresh acceptance report present", score.reason, ...visual.reason !== void 0 ? [visual.reason] : []];
24457
24740
  sinks.event({ cycleId, verdict: "produced", reasons: reasons3 });
24458
24741
  return { verdict: "produced", mode, reasons: reasons3, blocked: false };
24459
24742
  }
@@ -24477,7 +24760,7 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks) {
24477
24760
  }
24478
24761
 
24479
24762
  // packages/cli/dist/runner/usage-recovery.js
24480
- import { readFileSync as readFileSync44, readdirSync as readdirSync21, statSync as statSync19 } from "node:fs";
24763
+ import { readFileSync as readFileSync44, readdirSync as readdirSync22, statSync as statSync20 } from "node:fs";
24481
24764
  import { homedir as homedir15 } from "node:os";
24482
24765
  import { join as join47 } from "node:path";
24483
24766
  function defaultPiSessionsRoot() {
@@ -24490,7 +24773,7 @@ function recoverPiUsage(worktreeCwd, sinceSec, sessionsRoot = defaultPiSessionsR
24490
24773
  const dir = piSessionsDirFor(sessionsRoot, worktreeCwd);
24491
24774
  let files;
24492
24775
  try {
24493
- files = readdirSync21(dir).filter((f) => f.endsWith(".jsonl"));
24776
+ files = readdirSync22(dir).filter((f) => f.endsWith(".jsonl"));
24494
24777
  } catch {
24495
24778
  return null;
24496
24779
  }
@@ -24498,7 +24781,7 @@ function recoverPiUsage(worktreeCwd, sinceSec, sessionsRoot = defaultPiSessionsR
24498
24781
  for (const f of files) {
24499
24782
  const p = join47(dir, f);
24500
24783
  try {
24501
- if (sinceSec !== void 0 && statSync19(p).mtimeMs / 1e3 < sinceSec)
24784
+ if (sinceSec !== void 0 && statSync20(p).mtimeMs / 1e3 < sinceSec)
24502
24785
  continue;
24503
24786
  summaries.push(sumPiSession(readFileSync44(p, "utf8").split("\n")));
24504
24787
  } catch {
@@ -24620,8 +24903,8 @@ function buildAcMapRemediationPrompt(worktreeCwd, storyId, runDir) {
24620
24903
  }
24621
24904
 
24622
24905
  // packages/cli/dist/runner/correction-actuator.js
24623
- import { appendFileSync as appendFileSync12, existsSync as existsSync51, mkdirSync as mkdirSync30, readFileSync as readFileSync46, readdirSync as readdirSync22, writeFileSync as writeFileSync31 } from "node:fs";
24624
- import { dirname as dirname23, join as join50 } from "node:path";
24906
+ import { appendFileSync as appendFileSync12, existsSync as existsSync51, mkdirSync as mkdirSync30, readFileSync as readFileSync46, readdirSync as readdirSync23, writeFileSync as writeFileSync31 } from "node:fs";
24907
+ import { dirname as dirname25, join as join50 } from "node:path";
24625
24908
  function readMode(projectPath3) {
24626
24909
  try {
24627
24910
  const path = join50(projectPath3, ".roll", "policy.yaml");
@@ -24641,7 +24924,7 @@ function readEvents2(eventsPath4) {
24641
24924
  }
24642
24925
  function appendEvent(eventsPath4, event) {
24643
24926
  try {
24644
- mkdirSync30(dirname23(eventsPath4), { recursive: true });
24927
+ mkdirSync30(dirname25(eventsPath4), { recursive: true });
24645
24928
  appendFileSync12(eventsPath4, `${JSON.stringify(event)}
24646
24929
  `, "utf8");
24647
24930
  } catch {
@@ -24649,7 +24932,7 @@ function appendEvent(eventsPath4, event) {
24649
24932
  }
24650
24933
  function appendAlert2(alertsPath, msg6) {
24651
24934
  try {
24652
- mkdirSync30(dirname23(alertsPath), { recursive: true });
24935
+ mkdirSync30(dirname25(alertsPath), { recursive: true });
24653
24936
  appendFileSync12(alertsPath, `${msg6}
24654
24937
  `, "utf8");
24655
24938
  } catch {
@@ -24681,7 +24964,7 @@ function storyEpic(projectPath3, storyId) {
24681
24964
  }
24682
24965
  try {
24683
24966
  const features = join50(projectPath3, ".roll", "features");
24684
- for (const d of readdirSync22(features, { withFileTypes: true })) {
24967
+ for (const d of readdirSync23(features, { withFileTypes: true })) {
24685
24968
  if (!d.isDirectory())
24686
24969
  continue;
24687
24970
  if (existsSync51(join50(features, d.name, storyId)))
@@ -24881,14 +25164,14 @@ async function runPairing(projectDir, worktreeCwd, runtimeDir6, cycleId, working
24881
25164
  }
24882
25165
 
24883
25166
  // packages/cli/dist/runner/executor.js
24884
- var execFileAsync9 = promisify9(execFile9);
25167
+ var execFileAsync10 = promisify10(execFile10);
24885
25168
  function createCaptureMarkerSink(runDir, capture) {
24886
25169
  let buf = "";
24887
25170
  const pending = [];
24888
25171
  const logPath = join52(runDir, "evidence", "capture-markers.log");
24889
25172
  const record2 = (marker, result) => {
24890
25173
  try {
24891
- mkdirSync32(dirname24(logPath), { recursive: true });
25174
+ mkdirSync32(dirname26(logPath), { recursive: true });
24892
25175
  appendFileSync13(logPath, JSON.stringify({ marker, result }) + "\n", "utf8");
24893
25176
  } catch {
24894
25177
  }
@@ -25008,7 +25291,7 @@ async function executeCommand(cmd, ports, ctx) {
25008
25291
  // execute: spawn the agent (TCR commits happen inside the worktree). The
25009
25292
  // exit code + timeout feed back as agent_exited; usage is captured for cost.
25010
25293
  case "spawn_agent": {
25011
- const livePath = join52(dirname24(ports.paths.eventsPath), "live.log");
25294
+ const livePath = join52(dirname26(ports.paths.eventsPath), "live.log");
25012
25295
  try {
25013
25296
  writeFileSync33(livePath, `\u2500\u2500 cycle ${ctx.cycleId ?? "?"} \xB7 ${ctx.storyId ?? "?"} \xB7 agent ${cmd.agent} \u2500\u2500
25014
25297
  `);
@@ -25038,7 +25321,7 @@ async function executeCommand(cmd, ports, ctx) {
25038
25321
  await captureSink?.flush();
25039
25322
  persistWorktreeAlerts(ports.paths.worktreePath, ports.paths.alertsPath, ports.events);
25040
25323
  try {
25041
- const logDir = join52(dirname24(ports.paths.eventsPath), "cycle-logs");
25324
+ const logDir = join52(dirname26(ports.paths.eventsPath), "cycle-logs");
25042
25325
  mkdirSync32(logDir, { recursive: true });
25043
25326
  writeFileSync33(join52(logDir, `${ctx.cycleId ?? "cycle"}.agent.log`), `# exit=${res.exitCode} timedOut=${res.timedOut}
25044
25327
  --- stdout ---
@@ -25100,7 +25383,7 @@ ${res.stderr}
25100
25383
  tcrCount = await ports.git.tcrCount(ports.paths.worktreePath);
25101
25384
  } catch {
25102
25385
  }
25103
- await runPeerGate(ports.paths.worktreePath, dirname24(ports.paths.eventsPath), ctx.cycleId ?? "", {
25386
+ await runPeerGate(ports.paths.worktreePath, dirname26(ports.paths.eventsPath), ctx.cycleId ?? "", {
25104
25387
  alert: (m7) => ports.events.appendAlert(ports.paths.alertsPath, m7),
25105
25388
  event: (p) => ports.events.appendEvent(ports.paths.eventsPath, {
25106
25389
  type: "peer:gate",
@@ -25155,7 +25438,7 @@ DIFF:
25155
25438
  changedFiles: cycleChangedFiles,
25156
25439
  diff: async (cwd) => {
25157
25440
  try {
25158
- const { stdout } = await execFileAsync9("git", ["diff", "origin/main...HEAD"], { cwd, encoding: "utf8" });
25441
+ const { stdout } = await execFileAsync10("git", ["diff", "origin/main...HEAD"], { cwd, encoding: "utf8" });
25159
25442
  return stdout.slice(0, 6e4);
25160
25443
  } catch {
25161
25444
  return "";
@@ -25165,7 +25448,7 @@ DIFF:
25165
25448
  now: () => ports.clock()
25166
25449
  };
25167
25450
  for (const stage of enabledPairingStages(ports.repoCwd)) {
25168
- await runPairing(ports.repoCwd, ports.paths.worktreePath, dirname24(ports.paths.eventsPath), ctx.cycleId ?? "", ctx.agent ?? "", stage, pairingDeps);
25451
+ await runPairing(ports.repoCwd, ports.paths.worktreePath, dirname26(ports.paths.eventsPath), ctx.cycleId ?? "", ctx.agent ?? "", stage, pairingDeps);
25169
25452
  }
25170
25453
  }
25171
25454
  const storyId = ctx.storyId ?? "";
@@ -25489,13 +25772,13 @@ function agentWritableRoots(repoCwd, alertsPath) {
25489
25772
  const rollDir = join52(repoCwd, ".roll");
25490
25773
  if (existsSync53(rollDir))
25491
25774
  add(rollDir);
25492
- add(dirname24(alertsPath));
25775
+ add(dirname26(alertsPath));
25493
25776
  return roots;
25494
25777
  }
25495
25778
  function persistWorktreeAlerts(worktreePath, alertsPath, events) {
25496
25779
  let names;
25497
25780
  try {
25498
- names = readdirSync23(worktreePath).filter((n) => /^ALERT.*\.md$/i.test(n));
25781
+ names = readdirSync24(worktreePath).filter((n) => /^ALERT.*\.md$/i.test(n));
25499
25782
  } catch {
25500
25783
  return;
25501
25784
  }
@@ -25527,16 +25810,16 @@ async function linkRollIntoWorktree(repoCwd, worktreePath) {
25527
25810
  const incompleteFossil = existsSync53(join52(src, "backlog.md")) && !existsSync53(join52(dst, "backlog.md"));
25528
25811
  if (!incompleteFossil)
25529
25812
  return;
25530
- rmSync12(dst, { recursive: true, force: true });
25813
+ rmSync13(dst, { recursive: true, force: true });
25531
25814
  }
25532
25815
  symlinkSync3(src, dst);
25533
- const common = (await execFileAsync9("git", ["-C", repoCwd, "rev-parse", "--path-format=absolute", "--git-common-dir"])).stdout.trim();
25816
+ const common = (await execFileAsync10("git", ["-C", repoCwd, "rev-parse", "--path-format=absolute", "--git-common-dir"])).stdout.trim();
25534
25817
  if (common === "")
25535
25818
  return;
25536
25819
  const exclude = join52(common, "info", "exclude");
25537
25820
  const cur = existsSync53(exclude) ? readFileSync48(exclude, "utf8") : "";
25538
25821
  if (!/^\.roll$/m.test(cur)) {
25539
- mkdirSync32(dirname24(exclude), { recursive: true });
25822
+ mkdirSync32(dirname26(exclude), { recursive: true });
25540
25823
  appendFileSync13(exclude, `${cur === "" || cur.endsWith("\n") ? "" : "\n"}.roll
25541
25824
  `, "utf8");
25542
25825
  }
@@ -25567,7 +25850,7 @@ function nodePorts(opts) {
25567
25850
  return push(repoCwd, branch);
25568
25851
  },
25569
25852
  async commitsAhead(worktreeCwd) {
25570
- const r = await execFileAsync9("git", ["rev-list", "--count", "origin/main..HEAD"], {
25853
+ const r = await execFileAsync10("git", ["rev-list", "--count", "origin/main..HEAD"], {
25571
25854
  cwd: worktreeCwd,
25572
25855
  encoding: "utf8"
25573
25856
  }).catch(() => ({ stdout: "0" }));
@@ -25575,7 +25858,7 @@ function nodePorts(opts) {
25575
25858
  return Number.isFinite(n) ? n : 0;
25576
25859
  },
25577
25860
  async mainAhead(repoCwd) {
25578
- const r = await execFileAsync9("git", ["rev-list", "--count", "origin/main..main"], {
25861
+ const r = await execFileAsync10("git", ["rev-list", "--count", "origin/main..main"], {
25579
25862
  cwd: repoCwd,
25580
25863
  encoding: "utf8"
25581
25864
  }).catch(() => ({ stdout: "0" }));
@@ -25583,7 +25866,7 @@ function nodePorts(opts) {
25583
25866
  return Number.isFinite(n) ? n : 0;
25584
25867
  },
25585
25868
  async tcrCount(worktreeCwd) {
25586
- const r = await execFileAsync9("git", ["log", "--oneline", "origin/main..HEAD"], {
25869
+ const r = await execFileAsync10("git", ["log", "--oneline", "origin/main..HEAD"], {
25587
25870
  cwd: worktreeCwd,
25588
25871
  encoding: "utf8"
25589
25872
  }).catch(() => ({ stdout: "" }));
@@ -25688,7 +25971,7 @@ function nodePorts(opts) {
25688
25971
  };
25689
25972
  }
25690
25973
  function appendAlertLine(alertsPath, message) {
25691
- mkdirSync32(dirname24(alertsPath), { recursive: true });
25974
+ mkdirSync32(dirname26(alertsPath), { recursive: true });
25692
25975
  appendFileSync13(alertsPath, `${message}
25693
25976
  `, "utf8");
25694
25977
  }
@@ -25866,7 +26149,7 @@ function describeCommand(cmd) {
25866
26149
 
25867
26150
  // packages/cli/dist/runner/correction-circuit.js
25868
26151
  import { appendFileSync as appendFileSync14, existsSync as existsSync54, mkdirSync as mkdirSync33, readFileSync as readFileSync49, writeFileSync as writeFileSync34 } from "node:fs";
25869
- import { dirname as dirname25, join as join53 } from "node:path";
26152
+ import { dirname as dirname27, join as join53 } from "node:path";
25870
26153
  function readLoopSafety(projectPath3) {
25871
26154
  try {
25872
26155
  const path = join53(projectPath3, ".roll", "policy.yaml");
@@ -25888,7 +26171,7 @@ function alreadyTripped(events, verdict) {
25888
26171
  return events.some((ev) => ev.type === "correction:circuit_breaker" && ev.signal === verdict.signal && ev.threshold === verdict.threshold && (ev.storyId ?? "") === (verdict.storyId ?? ""));
25889
26172
  }
25890
26173
  function appendEvent2(eventsPath4, event) {
25891
- mkdirSync33(dirname25(eventsPath4), { recursive: true });
26174
+ mkdirSync33(dirname27(eventsPath4), { recursive: true });
25892
26175
  appendFileSync14(eventsPath4, `${JSON.stringify(event)}
25893
26176
  `, "utf8");
25894
26177
  }
@@ -25911,10 +26194,10 @@ function applyCorrectionCircuitBreaker(projectPath3, slug, eventsPath4, alertsPa
25911
26194
  ` : "") + `**Action**: loop paused to prevent unattended correction oscillation. Resume manually with \`roll loop resume\`.
25912
26195
  `;
25913
26196
  try {
25914
- mkdirSync33(dirname25(pauseMarker), { recursive: true });
26197
+ mkdirSync33(dirname27(pauseMarker), { recursive: true });
25915
26198
  if (pauseWritten)
25916
26199
  writeFileSync34(pauseMarker, alertMsg, "utf8");
25917
- mkdirSync33(dirname25(alertsPath), { recursive: true });
26200
+ mkdirSync33(dirname27(alertsPath), { recursive: true });
25918
26201
  appendFileSync14(alertsPath, `${alertMsg}
25919
26202
  `, "utf8");
25920
26203
  appendEvent2(eventsPath4, {
@@ -26004,6 +26287,7 @@ async function backfillMergedRuns(projectPath3, runsPath3, deps = {}) {
26004
26287
  // packages/cli/dist/commands/loop-run-once.js
26005
26288
  import { execFileSync as execFileSync13, spawn as spawn6 } from "node:child_process";
26006
26289
  import { lookup } from "node:dns/promises";
26290
+ import { createConnection } from "node:net";
26007
26291
  function announceReport(projectPath3, slug, storyId, opener = (p) => {
26008
26292
  try {
26009
26293
  spawn6("open", [p], { stdio: "ignore", detached: true }).unref();
@@ -26256,7 +26540,7 @@ async function loopRunOnceCommand(args) {
26256
26540
  }
26257
26541
  const rt = runtimeDir5(id.path);
26258
26542
  const alertsPath = join54(rt, `ALERT-${id.slug}.md`);
26259
- mkdirSync34(dirname26(alertsPath), { recursive: true });
26543
+ mkdirSync34(dirname28(alertsPath), { recursive: true });
26260
26544
  const paths = {
26261
26545
  eventsPath: join54(rt, "events.ndjson"),
26262
26546
  runsPath: join54(rt, "runs.jsonl"),
@@ -26411,17 +26695,34 @@ async function isOffline(resolve4 = (h) => lookup(h)) {
26411
26695
  return true;
26412
26696
  }
26413
26697
  }
26414
- async function egressBlocked(resolve4 = (h) => lookup(h)) {
26698
+ function tcpConnect(host, port, timeoutMs) {
26699
+ return new Promise((resolve4, reject) => {
26700
+ const socket = createConnection({ host, port });
26701
+ let settled = false;
26702
+ const done = (err17) => {
26703
+ if (settled)
26704
+ return;
26705
+ settled = true;
26706
+ socket.removeAllListeners();
26707
+ socket.destroy();
26708
+ if (err17)
26709
+ reject(err17);
26710
+ else
26711
+ resolve4();
26712
+ };
26713
+ socket.setTimeout(timeoutMs, () => done(new Error("tcp timeout")));
26714
+ socket.once("connect", () => done());
26715
+ socket.once("error", done);
26716
+ });
26717
+ }
26718
+ async function egressBlocked(resolve4 = (h) => lookup(h), tcpProbe2 = () => tcpConnect("github.com", 443, 3e3)) {
26415
26719
  if (process.platform !== "darwin")
26416
26720
  return false;
26417
26721
  const offline = await isOffline(resolve4);
26418
26722
  if (offline)
26419
26723
  return false;
26420
26724
  try {
26421
- const { execFile: execFile10 } = await import("node:child_process");
26422
- const { promisify: promisify10 } = await import("node:util");
26423
- const execFileAsync10 = promisify10(execFile10);
26424
- await execFileAsync10("bash", ["-c", "timeout 3 bash -c 'echo >/dev/tcp/github.com/443' 2>/dev/null"], { timeout: 5e3 });
26725
+ await tcpProbe2();
26425
26726
  return false;
26426
26727
  } catch {
26427
26728
  return true;
@@ -26430,7 +26731,7 @@ async function egressBlocked(resolve4 = (h) => lookup(h)) {
26430
26731
 
26431
26732
  // packages/cli/dist/commands/offboard.js
26432
26733
  import { spawnSync as spawnSync7 } from "node:child_process";
26433
- import { existsSync as existsSync56, readFileSync as readFileSync52, realpathSync as realpathSync6, rmSync as rmSync13, writeFileSync as writeFileSync37 } from "node:fs";
26734
+ import { existsSync as existsSync56, readFileSync as readFileSync52, realpathSync as realpathSync6, rmSync as rmSync14, writeFileSync as writeFileSync37 } from "node:fs";
26434
26735
  import { homedir as homedir16 } from "node:os";
26435
26736
  import { join as join55, resolve as resolve2 } from "node:path";
26436
26737
  function pal5() {
@@ -26634,7 +26935,7 @@ function offboardCommand(args) {
26634
26935
  process.stdout.write(m4("offboard.applying_offboard") + "\n");
26635
26936
  for (const item of files) {
26636
26937
  try {
26637
- rmSync13(join55(projectDir, item), { force: true });
26938
+ rmSync14(join55(projectDir, item), { force: true });
26638
26939
  if (!existsSync56(join55(projectDir, item)))
26639
26940
  process.stdout.write(` removed file ${item}
26640
26941
  `);
@@ -26643,7 +26944,7 @@ function offboardCommand(args) {
26643
26944
  }
26644
26945
  for (const item of dirs) {
26645
26946
  try {
26646
- rmSync13(join55(projectDir, item), { recursive: true, force: true });
26947
+ rmSync14(join55(projectDir, item), { recursive: true, force: true });
26647
26948
  process.stdout.write(` removed dir ${item}
26648
26949
  `);
26649
26950
  } catch {
@@ -26667,19 +26968,19 @@ function offboardCommand(args) {
26667
26968
  if (r === 0)
26668
26969
  process.stdout.write(` unloaded ${item}
26669
26970
  `);
26670
- rmSync13(plistPath, { force: true });
26971
+ rmSync14(plistPath, { force: true });
26671
26972
  }
26672
- rmSync13(changeset, { force: true });
26973
+ rmSync14(changeset, { force: true });
26673
26974
  ok7(m4("offboard.offboard_complete_offboard"));
26674
26975
  return 0;
26675
26976
  }
26676
26977
 
26677
26978
  // packages/cli/dist/commands/prices.js
26678
- import { existsSync as existsSync58, readdirSync as readdirSync25, readFileSync as readFileSync54 } from "node:fs";
26979
+ import { existsSync as existsSync58, readdirSync as readdirSync26, readFileSync as readFileSync54 } from "node:fs";
26679
26980
  import { join as join57 } from "node:path";
26680
26981
 
26681
26982
  // packages/cli/dist/commands/prices-refresh.js
26682
- import { existsSync as existsSync57, mkdirSync as mkdirSync35, readdirSync as readdirSync24, readFileSync as readFileSync53, writeFileSync as writeFileSync38 } from "node:fs";
26983
+ import { existsSync as existsSync57, mkdirSync as mkdirSync35, readdirSync as readdirSync25, readFileSync as readFileSync53, writeFileSync as writeFileSync38 } from "node:fs";
26683
26984
  import { join as join56 } from "node:path";
26684
26985
  var FetchError = class extends Error {
26685
26986
  constructor(message) {
@@ -26903,7 +27204,7 @@ function vendorFromSnapshotName(name) {
26903
27204
  function latestSnapshotPath(snapshotDir, vendor) {
26904
27205
  if (!existsSync57(snapshotDir))
26905
27206
  return null;
26906
- const snaps = readdirSync24(snapshotDir).filter((name) => SNAPSHOT_RE.test(name) && vendorFromSnapshotName(name) === vendor).sort().map((name) => join56(snapshotDir, name));
27207
+ const snaps = readdirSync25(snapshotDir).filter((name) => SNAPSHOT_RE.test(name) && vendorFromSnapshotName(name) === vendor).sort().map((name) => join56(snapshotDir, name));
26907
27208
  return snaps[snaps.length - 1] ?? null;
26908
27209
  }
26909
27210
  function readPrices(path) {
@@ -27113,7 +27414,7 @@ function loadSnapshots() {
27113
27414
  if (!existsSync58(dir)) {
27114
27415
  throw new Error(`no price snapshots found in ${dir}; run \`roll prices refresh\``);
27115
27416
  }
27116
- const files = readdirSync25(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
27417
+ const files = readdirSync26(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
27117
27418
  return files.map((name) => {
27118
27419
  const data = JSON.parse(readFileSync54(join57(dir, name), "utf8"));
27119
27420
  for (const key of ["version", "effective_at", "source_url", "prices"]) {
@@ -27602,7 +27903,7 @@ function releaseWaiverCommand(args, deps = {}) {
27602
27903
 
27603
27904
  // packages/cli/dist/commands/setup.js
27604
27905
  import { spawnSync as spawnSync8 } from "node:child_process";
27605
- import { existsSync as existsSync60, lstatSync as lstatSync3, mkdirSync as mkdirSync36, readFileSync as readFileSync57, readdirSync as readdirSync26, readlinkSync as readlinkSync2, statSync as statSync20 } from "node:fs";
27906
+ import { existsSync as existsSync60, lstatSync as lstatSync3, mkdirSync as mkdirSync36, readFileSync as readFileSync57, readdirSync as readdirSync27, readlinkSync as readlinkSync2, statSync as statSync21 } from "node:fs";
27606
27907
  import { join as join61 } from "node:path";
27607
27908
  function err14(line) {
27608
27909
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
@@ -27628,7 +27929,7 @@ function setupSnapshot(watch) {
27628
27929
  continue;
27629
27930
  let isDir3 = false;
27630
27931
  try {
27631
- isDir3 = statSync20(d).isDirectory();
27932
+ isDir3 = statSync21(d).isDirectory();
27632
27933
  } catch {
27633
27934
  isDir3 = false;
27634
27935
  }
@@ -27641,7 +27942,7 @@ function setupSnapshot(watch) {
27641
27942
  function walk(dir, lines2) {
27642
27943
  let names;
27643
27944
  try {
27644
- names = readdirSync26(dir);
27945
+ names = readdirSync27(dir);
27645
27946
  } catch {
27646
27947
  return;
27647
27948
  }
@@ -27662,7 +27963,7 @@ function walk(dir, lines2) {
27662
27963
  }
27663
27964
  let st;
27664
27965
  try {
27665
- st = statSync20(p);
27966
+ st = statSync21(p);
27666
27967
  } catch {
27667
27968
  continue;
27668
27969
  }
@@ -27720,7 +28021,7 @@ function submoduleGuard() {
27720
28021
  const hasMods = existsSync60(join61(pkg, ".gitmodules"));
27721
28022
  let empty = true;
27722
28023
  try {
27723
- empty = readdirSync26(skills).length === 0;
28024
+ empty = readdirSync27(skills).length === 0;
27724
28025
  } catch {
27725
28026
  empty = true;
27726
28027
  }
@@ -27868,9 +28169,9 @@ function setupCommand(args) {
27868
28169
  // packages/cli/dist/commands/status.js
27869
28170
  import { execFileSync as execFileSync16 } from "node:child_process";
27870
28171
  import { createHash as createHash5 } from "node:crypto";
27871
- import { existsSync as existsSync61, lstatSync as lstatSync4, readdirSync as readdirSync27, readFileSync as readFileSync58, realpathSync as realpathSync7, statSync as statSync21 } from "node:fs";
28172
+ import { existsSync as existsSync61, lstatSync as lstatSync4, readdirSync as readdirSync28, readFileSync as readFileSync58, realpathSync as realpathSync7, statSync as statSync22 } from "node:fs";
27872
28173
  import { homedir as homedir17 } from "node:os";
27873
- import { basename as basename11, dirname as dirname27, join as join62 } from "node:path";
28174
+ import { basename as basename11, dirname as dirname29, join as join62 } from "node:path";
27874
28175
  function rollHome3() {
27875
28176
  return process.env["ROLL_HOME"] ?? join62(homedir17(), ".roll");
27876
28177
  }
@@ -27915,7 +28216,7 @@ function parseAiEntries() {
27915
28216
  const srcFile = (parts[2] ?? "").trim();
27916
28217
  let name = basename11(aiDir).replace(/^\.+/, "");
27917
28218
  if (name === "workspace" || name === "agent") {
27918
- name = basename11(dirname27(aiDir)).replace(/^\.+/, "");
28219
+ name = basename11(dirname29(aiDir)).replace(/^\.+/, "");
27919
28220
  }
27920
28221
  entries.push({ name, ai_dir: aiDir, cfg_file: cfgFile, src_file: srcFile });
27921
28222
  }
@@ -27949,7 +28250,7 @@ function aiSkillCount(e) {
27949
28250
  return 0;
27950
28251
  try {
27951
28252
  let n = 0;
27952
- for (const name of readdirSync27(skillsDir2)) {
28253
+ for (const name of readdirSync28(skillsDir2)) {
27953
28254
  if (!name.startsWith("roll-"))
27954
28255
  continue;
27955
28256
  const p = join62(skillsDir2, name);
@@ -27964,9 +28265,9 @@ function aiSkillCount(e) {
27964
28265
  }
27965
28266
  function countFilesRecursive(dir) {
27966
28267
  let n = 0;
27967
- for (const name of readdirSync27(dir)) {
28268
+ for (const name of readdirSync28(dir)) {
27968
28269
  const p = join62(dir, name);
27969
- const st = statSync21(p);
28270
+ const st = statSync22(p);
27970
28271
  if (st.isDirectory())
27971
28272
  n += countFilesRecursive(p);
27972
28273
  else if (st.isFile())
@@ -27989,7 +28290,7 @@ function skillsInstalled() {
27989
28290
  if (!existsSync61(sd))
27990
28291
  return 0;
27991
28292
  try {
27992
- return readdirSync27(sd).filter((n) => statSync21(join62(sd, n)).isDirectory()).length;
28293
+ return readdirSync28(sd).filter((n) => statSync22(join62(sd, n)).isDirectory()).length;
27993
28294
  } catch {
27994
28295
  return 0;
27995
28296
  }
@@ -28142,7 +28443,7 @@ function liveData() {
28142
28443
  const featDir = ".roll/features";
28143
28444
  let featCount = 0;
28144
28445
  if (existsSync61(featDir)) {
28145
- featCount = readdirSync27(featDir).filter((n) => n.endsWith(".md")).length;
28446
+ featCount = readdirSync28(featDir).filter((n) => n.endsWith(".md")).length;
28146
28447
  }
28147
28448
  return {
28148
28449
  conventions: globalConventions(),
@@ -28174,8 +28475,8 @@ function statusCommand(args) {
28174
28475
 
28175
28476
  // packages/cli/dist/commands/test.js
28176
28477
  import { spawnSync as spawnSync9 } from "node:child_process";
28177
- import { appendFileSync as appendFileSync16, existsSync as existsSync62, mkdirSync as mkdirSync37, readFileSync as readFileSync59, rmSync as rmSync14, writeFileSync as writeFileSync39 } from "node:fs";
28178
- import { dirname as dirname28, join as join63, resolve as resolve3 } from "node:path";
28478
+ import { appendFileSync as appendFileSync16, existsSync as existsSync62, mkdirSync as mkdirSync37, readFileSync as readFileSync59, rmSync as rmSync15, writeFileSync as writeFileSync39 } from "node:fs";
28479
+ import { dirname as dirname30, join as join63, resolve as resolve3 } from "node:path";
28179
28480
  function pal7() {
28180
28481
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
28181
28482
  return noColor2 ? { CYAN: "", GREEN: "", YELLOW: "", RED: "", NC: "" } : { CYAN: "\x1B[0;36m", GREEN: "\x1B[0;32m", YELLOW: "\x1B[0;33m", RED: "\x1B[0;31m", NC: "\x1B[0m" };
@@ -28311,13 +28612,13 @@ function resetAcquireLock() {
28311
28612
  const lock = resetLockPath();
28312
28613
  if (existsSync62(lock))
28313
28614
  return false;
28314
- mkdirSync37(dirname28(lock), { recursive: true });
28615
+ mkdirSync37(dirname30(lock), { recursive: true });
28315
28616
  writeFileSync39(lock, `${process.pid}
28316
28617
  `);
28317
28618
  return true;
28318
28619
  }
28319
28620
  function resetReleaseLock() {
28320
- rmSync14(resetLockPath(), { force: true });
28621
+ rmSync15(resetLockPath(), { force: true });
28321
28622
  }
28322
28623
  function runForward(cmd, argv) {
28323
28624
  const frame = currentEvidenceFrame();
@@ -28333,6 +28634,23 @@ function runForward(cmd, argv) {
28333
28634
  appendRollTestEvidence(frame, cmd, argv, status2, stdout, stderr);
28334
28635
  return status2;
28335
28636
  }
28637
+ function ensureSkillsSubmoduleReady() {
28638
+ const pkg = rollPkgDir();
28639
+ const required2 = join63(pkg, "skills", "roll-onboard", "SKILL.md");
28640
+ if (existsSync62(required2))
28641
+ return true;
28642
+ if (existsSync62(join63(pkg, ".git")) && existsSync62(join63(pkg, ".gitmodules"))) {
28643
+ spawnSync9("git", ["submodule", "update", "--init", "--recursive", "--quiet", "skills"], {
28644
+ cwd: pkg,
28645
+ stdio: "ignore"
28646
+ });
28647
+ if (existsSync62(required2))
28648
+ return true;
28649
+ }
28650
+ err15("roll test: skills submodule is empty");
28651
+ process.stderr.write(" run: git submodule update --init --recursive skills\n");
28652
+ return false;
28653
+ }
28336
28654
  function isolationDispatch(method, args) {
28337
28655
  const type = isolationGetType();
28338
28656
  if (type === "none" && !existsSync62(join63(process.cwd(), ".roll", "local.yaml"))) {
@@ -28424,6 +28742,8 @@ function testCommand(args) {
28424
28742
  let npmArgs = argv;
28425
28743
  if (npmArgs.length === 0)
28426
28744
  npmArgs = ["--affected"];
28745
+ if (!ensureSkillsSubmoduleReady())
28746
+ return 1;
28427
28747
  return isolationDispatch("exec", ["npm", "test", "--", ...npmArgs]);
28428
28748
  }
28429
28749
 
@@ -28612,7 +28932,7 @@ function tuneCommand(argv) {
28612
28932
 
28613
28933
  // packages/cli/dist/commands/update.js
28614
28934
  import { spawnSync as spawnSync10 } from "node:child_process";
28615
- import { existsSync as existsSync64, mkdtempSync as mkdtempSync5, readFileSync as readFileSync61, rmSync as rmSync15 } from "node:fs";
28935
+ import { existsSync as existsSync64, mkdtempSync as mkdtempSync5, readFileSync as readFileSync61, rmSync as rmSync16 } from "node:fs";
28616
28936
  import { tmpdir as tmpdir6 } from "node:os";
28617
28937
  import { join as join65 } from "node:path";
28618
28938
  function pal8() {
@@ -28702,7 +29022,7 @@ function downloadAndInstallCurl(tag) {
28702
29022
  }
28703
29023
  return { ok: true, newVersion: treeVersion(extractDir) };
28704
29024
  } finally {
28705
- rmSync15(tmpDir, { recursive: true, force: true });
29025
+ rmSync16(tmpDir, { recursive: true, force: true });
28706
29026
  }
28707
29027
  }
28708
29028
  function checkInstalledVersionOrRetry() {
@@ -28722,7 +29042,7 @@ function checkInstalledVersionOrRetry() {
28722
29042
  }
28723
29043
  }
28724
29044
  function invalidateUpdateCache() {
28725
- rmSync15(join65(rollHome(), ".update-check"), { force: true });
29045
+ rmSync16(join65(rollHome(), ".update-check"), { force: true });
28726
29046
  }
28727
29047
  function showChangelog() {
28728
29048
  const changelog = join65(rollPkgDir(), "CHANGELOG.md");