@seanyao/roll 3.606.3 → 3.607.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
@@ -5439,6 +5439,91 @@ var ANSI_CSS = `
5439
5439
  }
5440
5440
  `.trim();
5441
5441
 
5442
+ // packages/core/dist/html/chrome.js
5443
+ function bi(en, zh) {
5444
+ return `<span class="lang-en">${en}</span><span class="lang-zh">${zh}</span>`;
5445
+ }
5446
+ var DARK_VARS = `--bg:#161410; --bg-raise:#1d1a14; --fg:#e9e2d0; --muted:#99907a; --line:#38332c; --accent:#e05b3e; --grain:rgba(255,255,255,.018); --pass:#57ab5a; --info:#539bf5; --warn:#c69026; --claim:#e0823d; --fail:#e5534b; --block:#986ee2;`;
5447
+ var CHROME_CSS = `
5448
+ :root { color-scheme: light dark;
5449
+ --bg:#f6f2e9; --bg-raise:#fdfbf5; --fg:#211d14; --muted:#79705d; --line:#d9d1bd; --accent:#a83825;
5450
+ --grain:rgba(60,40,10,.03);
5451
+ --pass:#2f7d3b; --info:#2c6cb0; --warn:#9a7b00; --claim:#c4602c; --fail:#c03328; --block:#6e40c9;
5452
+ --serif:"Iowan Old Style","Palatino","Book Antiqua",Georgia,"Songti SC","Noto Serif CJK SC",serif;
5453
+ --sans:-apple-system,"PingFang SC","Segoe UI","Microsoft YaHei",sans-serif;
5454
+ --mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace; }
5455
+ :root[data-theme="dark"] { ${DARK_VARS} }
5456
+ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { ${DARK_VARS} } }
5457
+ html { background:var(--bg); }
5458
+ body { margin:0 auto; max-width:880px; padding:30px 20px 80px; background:var(--bg); color:var(--fg);
5459
+ font:15px/1.7 var(--sans); }
5460
+ body::after { content:""; position:fixed; inset:0; pointer-events:none; z-index:1;
5461
+ background-image:radial-gradient(var(--grain) 1px, transparent 1px); background-size:3px 3px; }
5462
+ .kicker { font:11.5px/1 var(--serif); letter-spacing:.24em; text-transform:uppercase; color:var(--accent);
5463
+ border-top:3px double var(--line); padding-top:10px; margin:0 0 10px; }
5464
+ h1 { font:600 26px/1.3 var(--serif); margin:0 0 4px; letter-spacing:.01em; }
5465
+ h2 { font:600 17px/1.4 var(--serif); border-bottom:1px solid var(--line); padding-bottom:6px; letter-spacing:.02em; }
5466
+ code { font-family:var(--mono); background:rgba(127,110,70,.10); padding:1px 6px; border-radius:4px; font-size:.9em; }
5467
+ pre { background:rgba(127,110,70,.07); padding:12px; border-radius:6px; overflow-x:auto; }
5468
+ a { color:var(--accent); text-decoration-color:color-mix(in srgb, var(--accent) 40%, transparent); }
5469
+ section { border:1px solid var(--line); border-radius:8px; padding:14px 18px; margin:14px 0;
5470
+ background:var(--bg-raise); position:relative; z-index:2; }
5471
+ .empty { color:var(--muted); font-style:italic; }
5472
+ .meta, .muted { color:var(--muted); font-size:13px; }
5473
+ footer { color:var(--muted); font-size:12.5px; font-family:var(--serif); letter-spacing:.04em;
5474
+ margin-top:40px; border-top:3px double var(--line); padding-top:12px; }
5475
+ /* language switching \u2014 both sides render until the chrome script picks one */
5476
+ [data-lang="en"] .lang-zh { display:none; } [data-lang="zh"] .lang-en { display:none; }
5477
+ /* chrome bar */
5478
+ .chrome { position:fixed; top:14px; right:14px; z-index:10; display:flex; gap:8px; font-family:var(--sans); }
5479
+ .chrome .seg { display:flex; border:1px solid var(--line); border-radius:999px; overflow:hidden;
5480
+ background:color-mix(in srgb, var(--bg-raise) 82%, transparent); backdrop-filter:blur(6px); }
5481
+ .chrome button { all:unset; cursor:pointer; font-size:12px; line-height:1; padding:7px 11px; color:var(--muted); }
5482
+ .chrome button.on { background:var(--accent); color:#fdfbf5; }
5483
+ .chrome button:not(.on):hover { color:var(--fg); }
5484
+ @media print { body { max-width:none; padding:0; } body::after, .chrome { display:none; }
5485
+ section { break-inside:avoid; background:none; } }
5486
+ `;
5487
+ var CHROME_CONTROLS = `<nav class="chrome" aria-label="page controls">
5488
+ <div class="seg" role="group" aria-label="language"><button type="button" data-set-lang="en">EN</button><button type="button" data-set-lang="zh">\u4E2D</button></div>
5489
+ <div class="seg" role="group" aria-label="theme"><button type="button" data-set-theme="light" aria-label="light">\u2600</button><button type="button" data-set-theme="dark" aria-label="dark">\u263E</button></div>
5490
+ </nav>`;
5491
+ var CHROME_SCRIPT = `<script>
5492
+ (function () {
5493
+ var d = document.documentElement;
5494
+ function get(k) { try { return localStorage.getItem(k); } catch (e) { return null; } }
5495
+ function set(k, v) { try { localStorage.setItem(k, v); } catch (e) {} }
5496
+ var lang = get("roll-lang") || ((navigator.language || "").toLowerCase().indexOf("zh") === 0 ? "zh" : "en");
5497
+ var theme = get("roll-theme") || (window.matchMedia && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
5498
+ function apply() {
5499
+ d.setAttribute("data-lang", lang);
5500
+ d.setAttribute("lang", lang === "zh" ? "zh-CN" : "en");
5501
+ d.setAttribute("data-theme", theme);
5502
+ var bs = document.querySelectorAll("[data-set-lang],[data-set-theme]");
5503
+ for (var i = 0; i < bs.length; i++) {
5504
+ var b = bs[i];
5505
+ var on = b.getAttribute("data-set-lang") === lang || b.getAttribute("data-set-theme") === theme;
5506
+ b.classList.toggle("on", on);
5507
+ b.setAttribute("aria-pressed", String(on));
5508
+ }
5509
+ }
5510
+ apply();
5511
+ document.addEventListener("DOMContentLoaded", function () {
5512
+ var bs = document.querySelectorAll("[data-set-lang],[data-set-theme]");
5513
+ for (var i = 0; i < bs.length; i++) {
5514
+ bs[i].addEventListener("click", function () {
5515
+ var l = this.getAttribute("data-set-lang");
5516
+ var t = this.getAttribute("data-set-theme");
5517
+ if (l) { lang = l; set("roll-lang", l); }
5518
+ if (t) { theme = t; set("roll-theme", t); }
5519
+ apply();
5520
+ });
5521
+ }
5522
+ apply();
5523
+ });
5524
+ })();
5525
+ </script>`;
5526
+
5442
5527
  // packages/core/dist/attest/report.js
5443
5528
  var BADGE = {
5444
5529
  pass: { icon: "\u2705", en: "Pass", zh: "\u901A\u8FC7", cls: "s-pass" },
@@ -5476,9 +5561,9 @@ function deliveryBlock(d) {
5476
5561
  if (d.cycleId !== void 0 && d.cycleId !== "")
5477
5562
  parts.push(`<dt>Cycle</dt><dd><code>${esc(d.cycleId)}</code></dd>`);
5478
5563
  if (d.timeline !== void 0 && d.timeline !== "")
5479
- parts.push(`<dt>\u65F6\u95F4\u7EBF \xB7 Timeline</dt><dd>${esc(d.timeline)}</dd>`);
5564
+ parts.push(`<dt>${bi("Timeline", "\u65F6\u95F4\u7EBF")}</dt><dd>${esc(d.timeline)}</dd>`);
5480
5565
  if (d.cost !== void 0 && d.cost !== "")
5481
- parts.push(`<dt>\u6210\u672C \xB7 Cost</dt><dd>${esc(d.cost)}</dd>`);
5566
+ parts.push(`<dt>${bi("Cost", "\u6210\u672C")}</dt><dd>${esc(d.cost)}</dd>`);
5482
5567
  return parts.length > 0 ? `<dl class="delivery">${parts.join("")}</dl>` : "";
5483
5568
  }
5484
5569
  function cardContextBlock(ctx) {
@@ -5501,7 +5586,7 @@ function cardContextBlock(ctx) {
5501
5586
  rows.push(delivery);
5502
5587
  if (rows.length === 0)
5503
5588
  return "";
5504
- return `<section class="card-context"><h2>\u5361\u4E0A\u4E0B\u6587 \xB7 Context</h2>
5589
+ return `<section class="card-context"><h2>${bi("Context", "\u5361\u4E0A\u4E0B\u6587")}</h2>
5505
5590
  ${rows.join("\n")}
5506
5591
  </section>`;
5507
5592
  }
@@ -5511,11 +5596,11 @@ function acSection(item) {
5511
5596
  const business = item.evidence.filter((e) => e.kind !== "text");
5512
5597
  const technical = item.evidence.filter((e) => e.kind === "text");
5513
5598
  const bizHtml = business.map(evidenceCard).join("\n");
5514
- const techHtml = technical.length > 0 ? `<details class="tech"><summary>\u6280\u672F\u7EC6\u8282 \xB7 Technical detail\uFF08${technical.length}\uFF09</summary>
5599
+ const techHtml = technical.length > 0 ? `<details class="tech"><summary>${bi(`Technical detail (${technical.length})`, `\u6280\u672F\u7EC6\u8282\uFF08${technical.length}\uFF09`)}</summary>
5515
5600
  ${technical.map(evidenceCard).join("\n")}
5516
5601
  </details>` : "";
5517
5602
  return `<section class="ac ${b.cls}" id="${esc(item.id)}">
5518
- <h3><span class="badge">${b.icon} ${b.en} \xB7 ${b.zh}</span> <code>${esc(item.id)}</code></h3>
5603
+ <h3><span class="badge">${b.icon} ${bi(b.en, b.zh)}</span> <code>${esc(item.id)}</code></h3>
5519
5604
  <p class="ac-text">${esc(item.text)}</p>
5520
5605
  ${note}
5521
5606
  ${bizHtml}
@@ -5527,15 +5612,15 @@ function beforeAfterBlock(pairs) {
5527
5612
  return "";
5528
5613
  const fig = (ref, side, cls) => ref.href !== void 0 ? `<figure class="shot ${cls}"><img src="${esc(ref.href)}" alt="${esc(ref.label)}"><figcaption>${side}\uFF1A${esc(ref.label)}</figcaption></figure>` : "";
5529
5614
  const groups = pairs.map((p) => {
5530
- const before = fig(p.before, "Before \xB7 \u6539\u524D", "ba-before");
5531
- const after = fig(p.after, "After \xB7 \u6539\u540E", "ba-after");
5615
+ const before = fig(p.before, bi("Before", "\u6539\u524D"), "ba-before");
5616
+ const after = fig(p.after, bi("After", "\u6539\u540E"), "ba-after");
5532
5617
  if (before === "" && after === "")
5533
5618
  return "";
5534
5619
  return `<div class="before-after"><h3>${esc(p.label)}</h3><div class="ba-pair">${before}${after}</div></div>`;
5535
5620
  }).filter((s) => s !== "");
5536
5621
  if (groups.length === 0)
5537
5622
  return "";
5538
- return `<section class="before-after-section"><h2>\u5BF9\u7167\u5B9E\u62CD \xB7 Before / After</h2>
5623
+ return `<section class="before-after-section"><h2>${bi("Before / After", "\u5BF9\u7167\u5B9E\u62CD")}</h2>
5539
5624
  ${groups.join("\n")}
5540
5625
  </section>`;
5541
5626
  }
@@ -5543,7 +5628,7 @@ function selfCaptureBlock(refs) {
5543
5628
  if (refs === void 0 || refs.length === 0)
5544
5629
  return "";
5545
5630
  const figs = refs.map(evidenceCard).join("\n");
5546
- return `<section class="self-capture"><h2>Gate self-capture \xB7 \u81EA\u4EA7\u5B9E\u62CD</h2>
5631
+ return `<section class="self-capture"><h2>${bi("Gate self-capture", "Gate \u81EA\u4EA7\u5B9E\u62CD")}</h2>
5547
5632
  ${figs}
5548
5633
  </section>`;
5549
5634
  }
@@ -5567,7 +5652,7 @@ function evidenceIndexBlock(items, beforeAfter, selfCaptures) {
5567
5652
  row2(r);
5568
5653
  if (rows.length === 0)
5569
5654
  return "";
5570
- return `<section class="evidence-index"><h2>\u8BC1\u636E\u7D22\u5F15 \xB7 Evidence index</h2>
5655
+ return `<section class="evidence-index"><h2>${bi("Evidence index", "\u8BC1\u636E\u7D22\u5F15")}</h2>
5571
5656
  <table class="ev-index"><thead><tr><th>Kind</th><th>Label</th><th>Locator</th></tr></thead>
5572
5657
  <tbody>${rows.join("\n")}</tbody></table></section>`;
5573
5658
  }
@@ -5581,7 +5666,7 @@ function processTraceBlock(p) {
5581
5666
  if (p === void 0)
5582
5667
  return "";
5583
5668
  const rows = [];
5584
- const mode = p.delivery === "manual" ? `<p class="delivery-mode">\u{1F9D1}\u200D\u{1F527} conductor \u624B\u5DE5\u4EA4\u4ED8 \xB7 delivered by hand\uFF08\u65E0\u81EA\u52A8\u5468\u671F \xB7 no loop cycle\uFF09</p>` : `<p class="delivery-mode">\u{1F501} loop cycle${p.cycleId !== void 0 && p.cycleId !== "" ? ` <code>${esc(p.cycleId)}</code>` : ""}${p.agent !== void 0 && p.agent !== "" ? ` \xB7 agent <code>${esc(p.agent)}</code>` : ""}</p>`;
5669
+ const mode = p.delivery === "manual" ? `<p class="delivery-mode">\u{1F9D1}\u200D\u{1F527} ${bi("delivered by hand (no loop cycle)", "conductor \u624B\u5DE5\u4EA4\u4ED8\uFF08\u65E0\u81EA\u52A8\u5468\u671F\uFF09")}</p>` : `<p class="delivery-mode">\u{1F501} loop cycle${p.cycleId !== void 0 && p.cycleId !== "" ? ` <code>${esc(p.cycleId)}</code>` : ""}${p.agent !== void 0 && p.agent !== "" ? ` \xB7 agent <code>${esc(p.agent)}</code>` : ""}</p>`;
5585
5670
  rows.push(mode);
5586
5671
  if (p.timeline !== void 0 && p.timeline.length > 0) {
5587
5672
  const li = p.timeline.map((t2) => `<li class="tl-${t2.layer === "signal" ? "signal" : "outline"}"><span class="tl-offset">${esc(fmtOffset(t2.offsetSec))}</span> <span class="tl-label">${esc(t2.label)}</span></li>`).join("\n");
@@ -5590,18 +5675,18 @@ ${li}
5590
5675
  </ol>`);
5591
5676
  }
5592
5677
  if (p.missing !== void 0 && p.missing.length > 0) {
5593
- rows.push(`<p class="trace-missing">\u8FC7\u7A0B\u6570\u636E\u7F3A\u5931 \xB7 missing process data\uFF1A${p.missing.map(esc).join(" \xB7 ")}</p>`);
5678
+ rows.push(`<p class="trace-missing">${bi("missing process data", "\u8FC7\u7A0B\u6570\u636E\u7F3A\u5931")}\uFF1A${p.missing.map(esc).join(" \xB7 ")}</p>`);
5594
5679
  }
5595
5680
  if (p.transcript !== void 0) {
5596
5681
  const t2 = p.transcript;
5597
- const note = t2.truncated ? `\u5DF2\u622A\u65AD \xB7 truncated\uFF08\u5C55\u793A ${t2.shownLen} / ${t2.totalLen} \u5B57\u7B26 \xB7 chars shown\uFF09` : `\u5B8C\u6574\u5185\u8054 \xB7 full inline\uFF08${t2.totalLen} \u5B57\u7B26 \xB7 chars\uFF09`;
5598
- const idx = t2.originalPath !== void 0 && t2.originalPath !== "" ? `<p class="orig-path">\u673A\u5668\u539F\u4EF6 \xB7 machine original\uFF1A<code>${esc(t2.originalPath)}</code></p>` : "";
5599
- rows.push(`<details class="transcript"><summary>\u5B8C\u6574\u8F6C\u5F55 \xB7 Full transcript\uFF08${esc(note)}\uFF09</summary>
5682
+ const note = t2.truncated ? bi(`truncated \u2014 ${t2.shownLen} / ${t2.totalLen} chars shown`, `\u5DF2\u622A\u65AD\uFF08\u5C55\u793A ${t2.shownLen} / ${t2.totalLen} \u5B57\u7B26\uFF09`) : bi(`full inline \u2014 ${t2.totalLen} chars`, `\u5B8C\u6574\u5185\u8054\uFF08${t2.totalLen} \u5B57\u7B26\uFF09`);
5683
+ const idx = t2.originalPath !== void 0 && t2.originalPath !== "" ? `<p class="orig-path">${bi("machine original", "\u673A\u5668\u539F\u4EF6")}\uFF1A<code>${esc(t2.originalPath)}</code></p>` : "";
5684
+ rows.push(`<details class="transcript"><summary>${bi("Full transcript", "\u5B8C\u6574\u8F6C\u5F55")}\uFF08${note}\uFF09</summary>
5600
5685
  ${idx}
5601
5686
  ${t2.inlineHtml}
5602
5687
  </details>`);
5603
5688
  }
5604
- return `<section class="process-trace"><h2>\u8FC7\u7A0B\u6863\u6848 \xB7 Process trace</h2>
5689
+ return `<section class="process-trace"><h2>${bi("Process trace", "\u8FC7\u7A0B\u6863\u6848")}</h2>
5605
5690
  ${rows.join("\n")}
5606
5691
  </section>`;
5607
5692
  }
@@ -5609,7 +5694,7 @@ function selfScoreBlock(entries) {
5609
5694
  if (entries === void 0 || entries.length === 0)
5610
5695
  return "";
5611
5696
  const li = entries.map((e) => `<li><b>${esc(String(e.score))}</b>/10 \xB7 ${esc(e.verdict)} \xB7 <code>${esc(e.skill)}</code> \xB7 <span class="meta">${esc(e.ts)}</span>${e.note !== "" ? `<br><span class="note">${esc(e.note)}</span>` : ""}</li>`).join("\n");
5612
- return `<details class="selfscore"><summary>Self-Score \xB7 \u81EA\u8BC4\uFF08${entries.length}\uFF09</summary>
5697
+ return `<details class="selfscore"><summary>${bi("Self-Score", "\u81EA\u8BC4")}\uFF08${entries.length}\uFF09</summary>
5613
5698
  <ul>
5614
5699
  ${li}
5615
5700
  </ul>
@@ -5622,13 +5707,13 @@ function renderReport(input) {
5622
5707
  const counts = /* @__PURE__ */ new Map();
5623
5708
  for (const it of items)
5624
5709
  counts.set(it.status, (counts.get(it.status) ?? 0) + 1);
5625
- const summary = Object.keys(BADGE).filter((s) => (counts.get(s) ?? 0) > 0).map((s) => `<span class="badge ${BADGE[s].cls}">${BADGE[s].icon} ${BADGE[s].en} ${BADGE[s].zh} \xD7 ${counts.get(s)}</span>`).join(" ");
5710
+ const summary = Object.keys(BADGE).filter((s) => (counts.get(s) ?? 0) > 0).map((s) => `<span class="badge ${BADGE[s].cls}">${BADGE[s].icon} ${bi(BADGE[s].en, BADGE[s].zh)} \xD7 ${counts.get(s)}</span>`).join(" ");
5626
5711
  const facts = input.facts !== void 0 ? `<p class="facts">TCR commits: <b>${input.facts.tcrCount}</b> \xB7 CI: <b>${esc(input.facts.ciConclusion || "\u2014")}</b> \xB7 test-pass: <b>${esc(input.facts.testPassAge)}</b></p>` : "";
5627
- const disc = discrepancies.length > 0 ? `<section class="discrepancies"><h2>Discrepancies \xB7 \u8BC1\u636E\u7F3A\u53E3</h2>
5628
- <p>\u4E0B\u5217 AC \u56E0<strong>\u6CA1\u6709\u4EFB\u4F55\u8BC1\u636E\u6761\u76EE</strong>\u88AB\u5F3A\u5236\u964D\u7EA7\u4E3A \u{1F7E7} Claimed\uFF08\u7EA2\u7EBF\uFF0C\u6E32\u67D3\u5C42\u5F3A\u5236\uFF09\uFF1A</p>
5712
+ const disc = discrepancies.length > 0 ? `<section class="discrepancies"><h2>${bi("Discrepancies", "\u8BC1\u636E\u7F3A\u53E3")}</h2>
5713
+ <p>${bi("The ACs below carried <strong>zero evidence entries</strong> and were force-downgraded to \u{1F7E7} Claimed (red line, enforced by the renderer):", "\u4E0B\u5217 AC \u56E0<strong>\u6CA1\u6709\u4EFB\u4F55\u8BC1\u636E\u6761\u76EE</strong>\u88AB\u5F3A\u5236\u964D\u7EA7\u4E3A \u{1F7E7} Claimed\uFF08\u7EA2\u7EBF\uFF0C\u6E32\u67D3\u5C42\u5F3A\u5236\uFF09\uFF1A")}</p>
5629
5714
  <ul>${discrepancies.map((d) => `<li><a href="#${esc(d.id)}"><code>${esc(d.id)}</code></a> ${esc(d.text)}</li>`).join("\n")}</ul>
5630
5715
  </section>` : "";
5631
- const gate = facts !== "" ? `<h2>\u8D28\u91CF\u95E8\u7981 \xB7 Quality gate</h2>
5716
+ const gate = facts !== "" ? `<h2>${bi("Quality gate", "\u8D28\u91CF\u95E8\u7981")}</h2>
5632
5717
  ${facts}` : "";
5633
5718
  const evIndex = evidenceIndexBlock(items, input.beforeAfter, input.selfCaptures);
5634
5719
  const selfScore = selfScoreBlock(input.selfScores);
@@ -5643,31 +5728,31 @@ ${closingInner}
5643
5728
  <meta name="viewport" content="width=device-width, initial-scale=1">
5644
5729
  <title>${esc(input.storyId)} \u2014 Acceptance Evidence \xB7 \u9A8C\u6536\u8BC1\u636E</title>
5645
5730
  <style>
5646
- :root { color-scheme: light dark; --fg:#1f2328; --bg:#ffffff; --muted:#57606a; --line:#d0d7de; }
5647
- @media (prefers-color-scheme: dark) { :root { --fg:#e6edf3; --bg:#0d1117; --muted:#8b949e; --line:#30363d; } }
5648
- body { margin:0 auto; max-width:880px; padding:32px 20px 80px; background:var(--bg); color:var(--fg);
5649
- font:15px/1.65 -apple-system, "PingFang SC", "Segoe UI", sans-serif; }
5650
- h1 { font-size:22px; } h2 { font-size:18px; border-bottom:1px solid var(--line); padding-bottom:6px; }
5651
- code { background:rgba(127,127,127,.12); padding:1px 6px; border-radius:6px; font-size:.92em; }
5731
+ ${CHROME_CSS}
5652
5732
  .badge { display:inline-block; padding:2px 10px; border-radius:999px; font-size:12.5px; border:1px solid var(--line); }
5653
- .meta, .facts, .note { color:var(--muted); font-size:13px; }
5654
- section.ac { border:1px solid var(--line); border-radius:10px; padding:14px 16px; margin:14px 0; }
5733
+ .facts, .note { color:var(--muted); font-size:13px; }
5734
+ header.doc { position:relative; padding-right:96px; }
5735
+ .seal { position:absolute; right:0; top:2px; width:74px; height:74px; border:2px solid var(--accent);
5736
+ border-radius:50%; transform:rotate(-12deg); color:var(--accent); font-family:var(--serif);
5737
+ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1px;
5738
+ font-size:17px; letter-spacing:.12em; opacity:.85; box-shadow:inset 0 0 0 2px transparent, inset 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
5739
+ .seal span { font-size:9.5px; letter-spacing:.34em; text-indent:.34em; }
5655
5740
  section.ac h3 { margin:0 0 6px; font-size:14.5px; }
5656
- .s-pass { border-left:4px solid #2da44e; } .s-readonly { border-left:4px solid #218bff; }
5657
- .s-partial { border-left:4px solid #d4a72c; } .s-claimed { border-left:4px solid #e8793a; }
5658
- .s-fail { border-left:4px solid #cf222e; } .s-blocked { border-left:4px solid #8250df; }
5659
- .s-missing { border-left:4px solid #cf222e; }
5660
- figure.shot { margin:10px 0; } figure.shot img { max-width:100%; border:1px solid var(--line); border-radius:8px; }
5741
+ .s-pass { border-left:4px solid var(--pass); } .s-readonly { border-left:4px solid var(--info); }
5742
+ .s-partial { border-left:4px solid var(--warn); } .s-claimed { border-left:4px solid var(--claim); }
5743
+ .s-fail { border-left:4px solid var(--fail); } .s-blocked { border-left:4px solid var(--block); }
5744
+ .s-missing { border-left:4px solid var(--fail); }
5745
+ figure.shot { margin:10px 0; } figure.shot img { max-width:100%; border:1px solid var(--line); border-radius:6px; }
5661
5746
  figure.shot figcaption { color:var(--muted); font-size:12.5px; }
5662
5747
  .ev { margin:6px 0; font-size:13.5px; } .ev-label { color:var(--muted); font-size:12.5px; margin-bottom:4px; }
5663
- .discrepancies { border:1px dashed #e8793a; border-radius:10px; padding:8px 16px; margin-top:28px; }
5664
- details.selfscore { margin-top:28px; border:1px solid var(--line); border-radius:10px; padding:8px 16px; }
5748
+ .discrepancies { border:1px dashed var(--claim); border-radius:8px; padding:8px 16px; margin-top:28px; }
5749
+ details.selfscore { margin-top:28px; border:1px solid var(--line); border-radius:8px; padding:8px 16px; background:var(--bg-raise); }
5665
5750
  details.selfscore summary { cursor:pointer; font-weight:600; }
5666
5751
  details.selfscore ul { margin:8px 0 4px; padding-left:18px; }
5667
- details.tech { margin:8px 0 2px; border:1px solid var(--line); border-radius:8px; padding:6px 12px; background:rgba(127,127,127,.04); }
5752
+ details.tech { margin:8px 0 2px; border:1px solid var(--line); border-radius:6px; padding:6px 12px; background:rgba(127,110,70,.04); }
5668
5753
  details.tech summary { cursor:pointer; color:var(--muted); font-size:12.5px; font-weight:600; }
5669
5754
  details.tech[open] summary { margin-bottom:6px; }
5670
- section.card-context { border:1px solid var(--line); border-radius:10px; padding:6px 16px 12px; margin:14px 0; }
5755
+ section.card-context { padding:6px 18px 12px; }
5671
5756
  section.card-context .one-liner { font-size:15.5px; font-weight:600; }
5672
5757
  section.card-context .ctx-meta { color:var(--muted); font-size:13px; }
5673
5758
  dl.delivery { display:grid; grid-template-columns:auto 1fr; gap:2px 12px; margin:8px 0 0; font-size:13.5px; }
@@ -5676,27 +5761,32 @@ dl.delivery dt { color:var(--muted); } dl.delivery dd { margin:0; }
5676
5761
  .ba-pair { display:flex; flex-wrap:wrap; gap:12px; } .ba-pair figure.shot { flex:1 1 280px; margin:0; }
5677
5762
  table.ev-index { width:100%; border-collapse:collapse; font-size:13px; margin-top:8px; }
5678
5763
  table.ev-index th, table.ev-index td { border:1px solid var(--line); padding:4px 8px; text-align:left; vertical-align:top; }
5679
- table.ev-index th { color:var(--muted); font-weight:600; }
5764
+ table.ev-index th { color:var(--muted); font-weight:600; font-family:var(--serif); letter-spacing:.04em; }
5680
5765
  table.ev-index td a { word-break:break-all; }
5681
- section.process-trace { margin-top:28px; border:1px solid var(--line); border-radius:10px; padding:6px 16px 12px; }
5766
+ section.process-trace { margin-top:28px; padding:6px 18px 12px; }
5682
5767
  .delivery-mode { font-size:13.5px; color:var(--muted); }
5683
5768
  ol.timeline { list-style:none; margin:8px 0; padding:0; font-size:13.5px; }
5684
5769
  ol.timeline li { padding:3px 0 3px 10px; border-left:2px solid var(--line); }
5685
- ol.timeline li.tl-signal { border-left:3px solid #218bff; font-weight:600; }
5770
+ ol.timeline li.tl-signal { border-left:3px solid var(--info); font-weight:600; }
5686
5771
  ol.timeline li.tl-outline { color:var(--muted); }
5687
- ol.timeline .tl-offset { display:inline-block; min-width:58px; color:var(--muted); font-variant-numeric:tabular-nums; font-size:12.5px; }
5688
- .trace-missing { color:#bf6a02; font-size:13px; }
5689
- details.transcript { margin-top:8px; border:1px solid var(--line); border-radius:8px; padding:6px 12px; background:rgba(127,127,127,.04); }
5772
+ ol.timeline .tl-offset { display:inline-block; min-width:58px; color:var(--muted); font-variant-numeric:tabular-nums; font-size:12.5px; font-family:var(--mono); }
5773
+ .trace-missing { color:var(--warn); font-size:13px; }
5774
+ details.transcript { margin-top:8px; border:1px solid var(--line); border-radius:6px; padding:6px 12px; background:rgba(127,110,70,.04); }
5690
5775
  details.transcript summary { cursor:pointer; color:var(--muted); font-size:12.5px; font-weight:600; }
5691
5776
  details.transcript .orig-path { font-size:12.5px; color:var(--muted); margin:6px 0; }
5692
- section.closing { margin-top:32px; border-top:2px solid var(--line); padding-top:8px; }
5693
- @media print { body { max-width:none; padding:0; } section.ac { break-inside:avoid; } }
5777
+ section.closing { margin-top:32px; border:none; background:none; border-radius:0; padding:8px 0 0; border-top:3px double var(--line); }
5694
5778
  ${ANSI_CSS}
5695
5779
  </style>
5780
+ ${CHROME_SCRIPT}
5696
5781
  </head>
5697
5782
  <body>
5783
+ ${CHROME_CONTROLS}
5784
+ <header class="doc">
5785
+ <p class="kicker">Roll \xB7 ${bi("Delivery Dossier", "\u4EA4\u4ED8\u6863\u6848")}</p>
5698
5786
  <h1>${esc(input.title)}</h1>
5699
- <p class="meta"><code>${esc(input.storyId)}</code> \xB7 generated ${esc(input.generatedAt)} \xB7 Gate: PASS</p>
5787
+ <p class="meta"><code>${esc(input.storyId)}</code> \xB7 ${bi("generated", "\u751F\u6210\u4E8E")} ${esc(input.generatedAt)} \xB7 Gate: PASS</p>
5788
+ <div class="seal" aria-hidden="true"><span>ROLL</span>\u9A8C\u8BAB</div>
5789
+ </header>
5700
5790
  ${cardContextBlock(input.context)}
5701
5791
  <p>${summary}</p>
5702
5792
  ${items.map(acSection).join("\n")}
@@ -5704,6 +5794,7 @@ ${beforeAfterBlock(input.beforeAfter)}
5704
5794
  ${selfCaptureBlock(input.selfCaptures)}
5705
5795
  ${processTraceBlock(input.process)}
5706
5796
  ${closing}
5797
+ <footer>Roll \xB7 ${bi("Acceptance Evidence", "\u9A8C\u6536\u8BC1\u636E")} \xB7 <code>${esc(input.storyId)}</code></footer>
5707
5798
  </body>
5708
5799
  </html>
5709
5800
  `;
@@ -5773,6 +5864,9 @@ function loopAlertPath() {
5773
5864
  const rt = process.env["ROLL_PROJECT_RUNTIME_DIR"];
5774
5865
  if (rt !== void 0 && rt !== "")
5775
5866
  return join4(rt, name);
5867
+ const local = join4(".roll", "loop", name);
5868
+ if (existsSync5(local))
5869
+ return local;
5776
5870
  const shared = process.env["_SHARED_ROOT"] ?? join4(homedir2(), ".shared", "roll");
5777
5871
  return join4(shared, "loop", name);
5778
5872
  }
@@ -7150,6 +7244,69 @@ function screenshotEvidenceRef(result, href) {
7150
7244
  // packages/cli/dist/commands/attest.js
7151
7245
  import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync9, readdirSync as readdirSync6, rmSync as rmSync5, symlinkSync as symlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
7152
7246
  import { basename as basename4, dirname as dirname9, join as join10, relative } from "node:path";
7247
+
7248
+ // packages/cli/dist/lib/story-page.js
7249
+ var STORY_ID_RE = /^(FIX|US|IDEA|REFACTOR)-/;
7250
+ function storyFamilyOf(storyId) {
7251
+ const m7 = STORY_ID_RE.exec(storyId);
7252
+ return m7 === null ? null : m7[1];
7253
+ }
7254
+ var STORY_PHASES = [
7255
+ { key: "design", en: "Design", zh: "\u8BBE\u8BA1", emptyEn: "Not yet started", emptyZh: "\u5C1A\u672A\u5F00\u59CB" },
7256
+ { key: "execution", en: "Execution", zh: "\u6267\u884C", emptyEn: "No cycles yet", emptyZh: "\u6682\u65E0\u5468\u671F" },
7257
+ { key: "delivery", en: "Delivery", zh: "\u4EA4\u4ED8", emptyEn: "Not yet delivered", emptyZh: "\u5C1A\u672A\u4EA4\u4ED8" },
7258
+ { key: "retrospective", en: "Retrospective", zh: "\u590D\u76D8", emptyEn: "Not yet written", emptyZh: "\u5C1A\u672A\u64B0\u5199" }
7259
+ ];
7260
+ function renderSpecMd(meta) {
7261
+ const type = meta.type ?? storyFamilyOf(meta.id)?.toLowerCase() ?? "unknown";
7262
+ return `---
7263
+ id: ${meta.id}
7264
+ ` + (meta.title !== void 0 ? `title: ${meta.title}
7265
+ ` : "") + `type: ${type}
7266
+ ` + (meta.epic !== void 0 ? `epic: ${meta.epic}
7267
+ ` : "") + `created: ${meta.created}
7268
+ ---
7269
+
7270
+ # ${meta.id}${meta.title !== void 0 ? ` \u2014 ${meta.title}` : ""}
7271
+ ` + (meta.note !== void 0 ? `
7272
+ > ${meta.note}
7273
+ ` : "");
7274
+ }
7275
+ function renderStoryPage(meta) {
7276
+ const q = (s) => s.replace(/"/g, "&quot;");
7277
+ const badge = storyFamilyOf(meta.id) ?? meta.id.split("-")[0];
7278
+ const sections = STORY_PHASES.map((p) => `<section class="phase phase-pending" data-phase="${p.key}"><h2>${bi(p.en, p.zh)}</h2><p class="empty">${bi(p.emptyEn, p.emptyZh)}</p></section>`).join("\n");
7279
+ return `<!DOCTYPE html>
7280
+ <html lang="zh-CN">
7281
+ <head>
7282
+ <meta charset="UTF-8">
7283
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7284
+ <title>${meta.title !== void 0 ? `${meta.id} \u2014 ${q(meta.title)}` : meta.id}</title>
7285
+ <style>
7286
+ ${CHROME_CSS}.phase-done { border-left:4px solid var(--pass); } .phase-pending { border-left:4px solid var(--line); }
7287
+ </style>
7288
+ ${CHROME_SCRIPT}
7289
+ </head>
7290
+ <body>
7291
+ ${CHROME_CONTROLS}
7292
+ <p class="kicker">Roll \xB7 ${bi("Story Dossier", "\u6545\u4E8B\u6863\u6848")}</p>
7293
+ <h1>${meta.id}</h1>
7294
+ <p class="meta"><code>${badge}</code> \xB7 ${bi("Created", "\u521B\u5EFA\u4E8E")} ${meta.created}</p>
7295
+ ` + (meta.title !== void 0 ? `<p>${q(meta.title)}</p>
7296
+ ` : "") + `${sections}
7297
+ <footer>Roll \xB7 <a href="spec.md">spec.md</a></footer>
7298
+ </body>
7299
+ </html>
7300
+ `;
7301
+ }
7302
+ function markPhaseDone(html, phaseKey, innerHtml) {
7303
+ const p = STORY_PHASES.find((x) => x.key === phaseKey);
7304
+ if (p === void 0)
7305
+ return html;
7306
+ return html.replace(new RegExp(`<section class="phase phase-pending" data-phase="${p.key}">[\\s\\S]*?</section>`), `<section class="phase phase-done" data-phase="${p.key}"><h2>${bi(p.en, p.zh)}</h2>${innerHtml}</section>`);
7307
+ }
7308
+
7309
+ // packages/cli/dist/commands/attest.js
7153
7310
  function defaultProcessReaders(projectPath, env) {
7154
7311
  const rt = (env.ROLL_PROJECT_RUNTIME_DIR ?? "").trim() || join10(projectPath, ".roll", "loop");
7155
7312
  const bus = new EventBus();
@@ -7535,6 +7692,18 @@ async function attestCommand(args, deps = {}) {
7535
7692
  } catch {
7536
7693
  warn2("latest symlink update failed (report still written)");
7537
7694
  }
7695
+ const indexPath = join10(storyDir, "index.html");
7696
+ if (existsSync14(indexPath)) {
7697
+ try {
7698
+ const reportRel = join10(runId, reportFileName(storyId));
7699
+ const deliveryHtml = `<p><a href="${reportRel}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a></p>
7700
+ <p class="muted">${bi("Delivered", "\u4EA4\u4ED8\u4E8E")} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}</p>
7701
+ `;
7702
+ const idx = markPhaseDone(readFileSync9(indexPath, "utf8"), "delivery", deliveryHtml);
7703
+ writeFileSync7(indexPath, idx, "utf8");
7704
+ } catch {
7705
+ }
7706
+ }
7538
7707
  const smoke = smokeCheckReport(html, (rel) => existsSync14(join10(runDir, rel)));
7539
7708
  process.stdout.write(`Acceptance report written
7540
7709
  \u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210
@@ -10044,11 +10213,16 @@ function detectLiveCycle(rtDir, cycles, now, pidAlive = systemPidAlive) {
10044
10213
  const hb = livenessVerdict(join15(rtDir, "heartbeat"), { now: () => nowSec2 });
10045
10214
  if (!hb.alive)
10046
10215
  return dead;
10047
- const open = cycles.find((cy) => cy.start !== null && cy.end === null);
10048
- if (open === void 0 || open.start === null)
10049
- return dead;
10050
- const elapsedSec = Math.max(0, Math.trunc((now.getTime() - open.start.getTime()) / 1e3));
10051
- return { running: true, story: open.story, elapsedSec };
10216
+ const ZOMBIE_THRESHOLD_SEC = 2 * 3600;
10217
+ for (const cy of cycles) {
10218
+ if (cy.start === null || cy.end !== null)
10219
+ continue;
10220
+ const elapsedSec = Math.max(0, Math.trunc((now.getTime() - cy.start.getTime()) / 1e3));
10221
+ if (elapsedSec > ZOMBIE_THRESHOLD_SEC)
10222
+ continue;
10223
+ return { running: true, story: cy.story, elapsedSec };
10224
+ }
10225
+ return dead;
10052
10226
  }
10053
10227
  function loadRuns(slug) {
10054
10228
  const rtDir = loopRuntimeDir(slug);
@@ -12922,7 +13096,8 @@ function gcCommand(args, deps = {}) {
12922
13096
  }
12923
13097
 
12924
13098
  // packages/cli/dist/commands/idea.js
12925
- import { existsSync as existsSync28 } from "node:fs";
13099
+ import { existsSync as existsSync28, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "node:fs";
13100
+ import { join as join25 } from "node:path";
12926
13101
  var BACKLOG_PATH2 = ".roll/backlog.md";
12927
13102
  function label2(lang4, key, ...args) {
12928
13103
  if (v3Catalog[key] !== void 0)
@@ -12989,29 +13164,127 @@ ${c("green", "\u{1F4DD} " + label2(lang4, "ideav3.recorded", plan.id))}
12989
13164
  process.stdout.write(` ${c("dim", label2(lang4, "ideav3.text") + ":")} ${text}
12990
13165
 
12991
13166
  `);
13167
+ const projectPath = process.cwd();
13168
+ const cardDir = join25(projectPath, ".roll", "features", "uncategorized", plan.id);
13169
+ try {
13170
+ mkdirSync13(cardDir, { recursive: true });
13171
+ const card = {
13172
+ id: plan.id,
13173
+ title: text,
13174
+ type: plan.kind,
13175
+ created: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
13176
+ };
13177
+ writeFileSync13(join25(cardDir, "spec.md"), renderSpecMd(card), "utf8");
13178
+ writeFileSync13(join25(cardDir, "index.html"), renderStoryPage(card), "utf8");
13179
+ } catch {
13180
+ }
12992
13181
  return 0;
12993
13182
  }
12994
13183
 
12995
13184
  // packages/cli/dist/commands/index-gen.js
13185
+ import { existsSync as existsSync29, readdirSync as readdirSync14, statSync as statSync14, writeFileSync as writeFileSync14 } from "node:fs";
13186
+ import { join as join26 } from "node:path";
12996
13187
  function indexCommand(args) {
12997
13188
  if (args.includes("--help") || args.includes("-h")) {
12998
- process.stdout.write("Usage: roll index\n Regenerate .roll/index.json (ID\u2192epic map) from .roll/backlog.md.\n");
13189
+ process.stdout.write("Usage: roll index\n Regenerate .roll/index.json + .roll/features/index.html\n");
12999
13190
  return 0;
13000
13191
  }
13001
- const stories = generateIndex(process.cwd());
13192
+ const cwd = process.cwd();
13193
+ const stories = generateIndex(cwd);
13002
13194
  const n = Object.keys(stories).length;
13003
13195
  process.stdout.write(`index.json regenerated
13004
13196
  \u7D22\u5F15\u5DF2\u91CD\u5EFA
13005
13197
  ${n} stories mapped to epics (.roll/index.json)
13006
13198
  `);
13199
+ const featuresDir = join26(cwd, ".roll", "features");
13200
+ if (existsSync29(featuresDir)) {
13201
+ const epics = /* @__PURE__ */ new Map();
13202
+ for (const [sid, epic] of Object.entries(stories)) {
13203
+ if (!epics.has(epic))
13204
+ epics.set(epic, /* @__PURE__ */ new Set());
13205
+ epics.get(epic).add(sid);
13206
+ }
13207
+ try {
13208
+ for (const d of readdirSync14(featuresDir)) {
13209
+ const epicDir = join26(featuresDir, d);
13210
+ if (!statSync14(epicDir).isDirectory())
13211
+ continue;
13212
+ if (!epics.has(d))
13213
+ epics.set(d, /* @__PURE__ */ new Set());
13214
+ for (const s of readdirSync14(epicDir)) {
13215
+ const storyDir = join26(epicDir, s);
13216
+ if (!statSync14(storyDir).isDirectory())
13217
+ continue;
13218
+ if (STORY_ID_RE.test(s))
13219
+ epics.get(d).add(s);
13220
+ }
13221
+ }
13222
+ } catch {
13223
+ }
13224
+ const rows = [];
13225
+ for (const epic of [...epics.keys()].sort()) {
13226
+ const unique = [...epics.get(epic)].sort();
13227
+ let delivered = 0;
13228
+ for (const s of unique) {
13229
+ const latest = join26(featuresDir, epic, s, "latest");
13230
+ try {
13231
+ if (statSync14(latest).isDirectory())
13232
+ delivered++;
13233
+ } catch {
13234
+ }
13235
+ }
13236
+ const links = unique.slice(0, 20).map((s) => `<a href="${epic}/${s}/index.html">${s}</a>`).join(", ");
13237
+ const more = unique.length > 20 ? ` ${bi(`\u2026 +${unique.length - 20} more`, `\u2026 \u53E6 ${unique.length - 20} \u4E2A`)}` : "";
13238
+ rows.push(`<tr>
13239
+ <td><strong><a href="${epic}/">${epic}</a></strong></td>
13240
+ <td>${bi(`${unique.length} stories (${delivered} delivered)`, `${unique.length} \u4E2A\u6545\u4E8B\uFF08\u5DF2\u4EA4\u4ED8 ${delivered}\uFF09`)}</td>
13241
+ <td style="font-size:0.85em">${links}${more}</td>
13242
+ </tr>`);
13243
+ }
13244
+ const html = `<!DOCTYPE html>
13245
+ <html lang="zh-CN">
13246
+ <head>
13247
+ <meta charset="UTF-8">
13248
+ <meta name="viewport" content="width=device-width, initial-scale=1">
13249
+ <title>Roll Features Index \xB7 \u529F\u80FD\u6863\u6848</title>
13250
+ <style>
13251
+ ${CHROME_CSS}body { max-width:1000px; }
13252
+ table { width:100%; border-collapse:collapse; position:relative; z-index:2; }
13253
+ th, td { padding:8px 12px; text-align:left; border-bottom:1px solid var(--line); vertical-align:top; }
13254
+ th { font-family:var(--serif); letter-spacing:.04em; color:var(--muted); }
13255
+ tr:hover td { background:var(--bg-raise); }
13256
+ td code, td strong a { font-family:var(--mono); }
13257
+ </style>
13258
+ ${CHROME_SCRIPT}
13259
+ </head>
13260
+ <body>
13261
+ ${CHROME_CONTROLS}
13262
+ <p class="kicker">Roll \xB7 ${bi("Delivery Dossier", "\u4EA4\u4ED8\u6863\u6848")}</p>
13263
+ <h1>${bi("Features Index", "\u529F\u80FD\u6863\u6848")}</h1>
13264
+ <p class="meta">${bi(`${epics.size} epics \xB7 ${n} stories`, `${epics.size} \u4E2A\u53F2\u8BD7 \xB7 ${n} \u4E2A\u6545\u4E8B`)}</p>
13265
+ <table><thead><tr><th>Epic</th><th>${bi("Stories", "\u6545\u4E8B")}</th><th>${bi("Recent", "\u6700\u8FD1")}</th></tr></thead>
13266
+ <tbody>
13267
+ ${rows.join("\n")}
13268
+ </tbody></table>
13269
+ <footer>${bi("Generated by", "\u751F\u6210\u81EA")} <code>roll index</code></footer>
13270
+ </body>
13271
+ </html>
13272
+ `;
13273
+ try {
13274
+ writeFileSync14(join26(featuresDir, "index.html"), html, "utf8");
13275
+ process.stdout.write(`features/index.html regenerated
13276
+ `);
13277
+ } catch {
13278
+ }
13279
+ }
13007
13280
  return 0;
13008
13281
  }
13009
13282
 
13010
13283
  // packages/cli/dist/commands/init.js
13011
13284
  import { spawnSync as spawnSync5 } from "node:child_process";
13012
- import { copyFileSync as copyFileSync2, existsSync as existsSync29, mkdirSync as mkdirSync13, readFileSync as readFileSync23, realpathSync as realpathSync4, statSync as statSync14, writeFileSync as writeFileSync13 } from "node:fs";
13285
+ import { copyFileSync as copyFileSync2, existsSync as existsSync30, mkdirSync as mkdirSync14, readFileSync as readFileSync23, realpathSync as realpathSync4, statSync as statSync15, writeFileSync as writeFileSync15 } from "node:fs";
13013
13286
  import { homedir as homedir11 } from "node:os";
13014
- import { dirname as dirname12, join as join25 } from "node:path";
13287
+ import { dirname as dirname12, join as join27 } from "node:path";
13015
13288
  function err9(line) {
13016
13289
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
13017
13290
  const RED = noColor2 ? "" : "\x1B[0;31m";
@@ -13020,19 +13293,19 @@ function err9(line) {
13020
13293
  `);
13021
13294
  }
13022
13295
  function rollHome2() {
13023
- return process.env["ROLL_HOME"] ?? join25(homedir11(), ".roll");
13296
+ return process.env["ROLL_HOME"] ?? join27(homedir11(), ".roll");
13024
13297
  }
13025
13298
  function rollGlobal2() {
13026
- return join25(rollHome2(), "conventions", "global");
13299
+ return join27(rollHome2(), "conventions", "global");
13027
13300
  }
13028
13301
  function rollTemplates2() {
13029
- return join25(rollHome2(), "conventions", "templates");
13302
+ return join27(rollHome2(), "conventions", "templates");
13030
13303
  }
13031
13304
  function scanProjectType(dir) {
13032
13305
  let hasFrontend = false;
13033
13306
  let hasBackend = false;
13034
13307
  let hasCli = false;
13035
- const pkg = join25(dir, "package.json");
13308
+ const pkg = join27(dir, "package.json");
13036
13309
  const readPkg = () => {
13037
13310
  try {
13038
13311
  return readFileSync23(pkg, "utf8");
@@ -13040,23 +13313,23 @@ function scanProjectType(dir) {
13040
13313
  return "";
13041
13314
  }
13042
13315
  };
13043
- if (existsSync29(pkg)) {
13316
+ if (existsSync30(pkg)) {
13044
13317
  if (/"react"|"vue"|"next"|"nuxt"|"vite"|"svelte"/i.test(readPkg()))
13045
13318
  hasFrontend = true;
13046
13319
  }
13047
- if (["src", "app", "pages", "components"].some((d) => existsSync29(join25(dir, d))))
13320
+ if (["src", "app", "pages", "components"].some((d) => existsSync30(join27(dir, d))))
13048
13321
  hasFrontend = true;
13049
- if (["server", "api", "backend"].some((d) => existsSync29(join25(dir, d))))
13322
+ if (["server", "api", "backend"].some((d) => existsSync30(join27(dir, d))))
13050
13323
  hasBackend = true;
13051
- if (["go.mod", "main.go", "main.py", "app.py", "Cargo.toml", "requirements.txt", "pyproject.toml"].some((f) => existsSync29(join25(dir, f))))
13324
+ if (["go.mod", "main.go", "main.py", "app.py", "Cargo.toml", "requirements.txt", "pyproject.toml"].some((f) => existsSync30(join27(dir, f))))
13052
13325
  hasBackend = true;
13053
- if (existsSync29(pkg)) {
13326
+ if (existsSync30(pkg)) {
13054
13327
  if (/"prisma"|"@prisma\/client"|"typeorm"|"sequelize"|"mongoose"|"drizzle-orm"|"@neondatabase\/serverless"|"pg"|"mysql2"|"mongodb"|"redis"|"ioredis"|"express"|"fastify"|"koa"|"hapi"|"@hapi\/hapi"|"apollo-server"|"graphql-yoga"|"trpc"/i.test(readPkg()))
13055
13328
  hasBackend = true;
13056
13329
  }
13057
- if (existsSync29(join25(dir, "prisma", "schema.prisma")))
13330
+ if (existsSync30(join27(dir, "prisma", "schema.prisma")))
13058
13331
  hasBackend = true;
13059
- if (existsSync29(join25(dir, "bin")) || existsSync29(join25(dir, "cmd")))
13332
+ if (existsSync30(join27(dir, "bin")) || existsSync30(join27(dir, "cmd")))
13060
13333
  hasCli = true;
13061
13334
  if (hasFrontend && hasBackend)
13062
13335
  return "fullstack";
@@ -13076,8 +13349,8 @@ function countNonEmptyFiles(dir) {
13076
13349
  }
13077
13350
  function isLegacyProject(projectDir) {
13078
13351
  for (const dir of ["src", "app", "lib", "pkg", "cmd"]) {
13079
- const p = join25(projectDir, dir);
13080
- if (existsSync29(p) && statSync14(p).isDirectory()) {
13352
+ const p = join27(projectDir, dir);
13353
+ if (existsSync30(p) && statSync15(p).isDirectory()) {
13081
13354
  if (countNonEmptyFiles(p) >= 10)
13082
13355
  return true;
13083
13356
  }
@@ -13107,12 +13380,12 @@ function isLegacyProject(projectDir) {
13107
13380
  "deno.jsonc"
13108
13381
  ];
13109
13382
  for (const man of manifests)
13110
- if (existsSync29(join25(projectDir, man)))
13383
+ if (existsSync30(join27(projectDir, man)))
13111
13384
  return true;
13112
13385
  const tf = spawnSync5("bash", ["-c", `compgen -G '${projectDir.replace(/'/g, "'\\''")}/*.tf' >/dev/null 2>&1`]);
13113
13386
  if (tf.status === 0)
13114
13387
  return true;
13115
- if (existsSync29(join25(projectDir, ".git"))) {
13388
+ if (existsSync30(join27(projectDir, ".git"))) {
13116
13389
  const g = spawnSync5("git", ["rev-parse", "--verify", "HEAD"], { cwd: projectDir, stdio: "ignore" });
13117
13390
  if (g.status === 0)
13118
13391
  return true;
@@ -13120,9 +13393,9 @@ function isLegacyProject(projectDir) {
13120
13393
  return false;
13121
13394
  }
13122
13395
  function mergeGlobalToProject(projectDir, summary) {
13123
- const src = join25(rollGlobal2(), "AGENTS.md");
13124
- const dst = join25(projectDir, "AGENTS.md");
13125
- if (!existsSync29(src)) {
13396
+ const src = join27(rollGlobal2(), "AGENTS.md");
13397
+ const dst = join27(projectDir, "AGENTS.md");
13398
+ if (!existsSync30(src)) {
13126
13399
  return;
13127
13400
  }
13128
13401
  const projectType = scanProjectType(projectDir);
@@ -13131,7 +13404,7 @@ function mergeGlobalToProject(projectDir, summary) {
13131
13404
  const srcText = readFileSync23(src, "utf8");
13132
13405
  const srcLines = srcText.split("\n");
13133
13406
  const lines = srcText.endsWith("\n") ? srcLines.slice(0, -1) : srcLines;
13134
- if (!existsSync29(dst)) {
13407
+ if (!existsSync30(dst)) {
13135
13408
  let out2 = "";
13136
13409
  let fcH = "";
13137
13410
  let fcB = "";
@@ -13160,7 +13433,7 @@ ${fcB}`;
13160
13433
  }
13161
13434
  }
13162
13435
  flush();
13163
- writeFileSync13(dst, out2);
13436
+ writeFileSync15(dst, out2);
13164
13437
  summary.push("created|AGENTS.md");
13165
13438
  return;
13166
13439
  }
@@ -13194,7 +13467,7 @@ ${curB}`;
13194
13467
  }
13195
13468
  tryAppend();
13196
13469
  if (appendBuffer !== "")
13197
- writeFileSync13(dst, dstText + appendBuffer);
13470
+ writeFileSync15(dst, dstText + appendBuffer);
13198
13471
  if (added > 0)
13199
13472
  summary.push("merged|AGENTS.md");
13200
13473
  else
@@ -13202,13 +13475,13 @@ ${curB}`;
13202
13475
  }
13203
13476
  function mergeClaudeToProject(projectDir, summary) {
13204
13477
  const projectType = scanProjectType(projectDir);
13205
- const tplFile = join25(rollTemplates2(), projectType, "CLAUDE.md");
13206
- if (!existsSync29(tplFile))
13478
+ const tplFile = join27(rollTemplates2(), projectType, "CLAUDE.md");
13479
+ if (!existsSync30(tplFile))
13207
13480
  return;
13208
- const claudeDir = join25(projectDir, ".claude");
13209
- const outFile = join25(claudeDir, "CLAUDE.md");
13210
- mkdirSync13(claudeDir, { recursive: true });
13211
- if (!existsSync29(outFile)) {
13481
+ const claudeDir = join27(projectDir, ".claude");
13482
+ const outFile = join27(claudeDir, "CLAUDE.md");
13483
+ mkdirSync14(claudeDir, { recursive: true });
13484
+ if (!existsSync30(outFile)) {
13212
13485
  copyFileSync2(tplFile, outFile);
13213
13486
  summary.push("created|.claude/CLAUDE.md");
13214
13487
  return;
@@ -13240,7 +13513,7 @@ ${curB}`;
13240
13513
  }
13241
13514
  tryAppend();
13242
13515
  if (appendBuffer !== "")
13243
- writeFileSync13(outFile, outText + appendBuffer);
13516
+ writeFileSync15(outFile, outText + appendBuffer);
13244
13517
  if (added > 0)
13245
13518
  summary.push("merged|.claude/CLAUDE.md");
13246
13519
  else
@@ -13257,20 +13530,20 @@ var BACKLOG_TEMPLATE = `# Project Backlog
13257
13530
  |----|---------|--------|
13258
13531
  `;
13259
13532
  function writeBacklog(path, summary) {
13260
- if (existsSync29(path)) {
13533
+ if (existsSync30(path)) {
13261
13534
  summary.push("unchanged|.roll/backlog.md");
13262
13535
  return;
13263
13536
  }
13264
- mkdirSync13(dirname12(path), { recursive: true });
13265
- writeFileSync13(path, BACKLOG_TEMPLATE);
13537
+ mkdirSync14(dirname12(path), { recursive: true });
13538
+ writeFileSync15(path, BACKLOG_TEMPLATE);
13266
13539
  summary.push("created|.roll/backlog.md");
13267
13540
  }
13268
13541
  function ensureFeaturesDir(path, summary) {
13269
- if (existsSync29(path) && statSync14(path).isDirectory()) {
13542
+ if (existsSync30(path) && statSync15(path).isDirectory()) {
13270
13543
  summary.push("unchanged|.roll/features/");
13271
13544
  return;
13272
13545
  }
13273
- mkdirSync13(path, { recursive: true });
13546
+ mkdirSync14(path, { recursive: true });
13274
13547
  summary.push("created|.roll/features/");
13275
13548
  }
13276
13549
  var FEATURES_TEMPLATE = `# Features
@@ -13284,38 +13557,38 @@ var FEATURES_TEMPLATE = `# Features
13284
13557
  <!-- Add feature entries here as epics are completed -->
13285
13558
  `;
13286
13559
  function writeFeaturesMd(path, summary) {
13287
- if (existsSync29(path)) {
13560
+ if (existsSync30(path)) {
13288
13561
  summary.push("unchanged|.roll/features.md");
13289
13562
  return;
13290
13563
  }
13291
- mkdirSync13(dirname12(path), { recursive: true });
13292
- writeFileSync13(path, FEATURES_TEMPLATE);
13564
+ mkdirSync14(dirname12(path), { recursive: true });
13565
+ writeFileSync15(path, FEATURES_TEMPLATE);
13293
13566
  summary.push("created|.roll/features.md");
13294
13567
  }
13295
13568
  function initSeedAgentRoutes(templateName, projectDir, summary) {
13296
- const dest = join25(projectDir, ".roll", "agent-routes.yaml");
13297
- if (existsSync29(dest)) {
13569
+ const dest = join27(projectDir, ".roll", "agent-routes.yaml");
13570
+ if (existsSync30(dest)) {
13298
13571
  summary.push("unchanged|.roll/agent-routes.yaml");
13299
13572
  return 0;
13300
13573
  }
13301
- const src = join25(rollTemplates2(), "agent-routes", `${templateName}.yaml`);
13302
- if (!existsSync29(src)) {
13574
+ const src = join27(rollTemplates2(), "agent-routes", `${templateName}.yaml`);
13575
+ if (!existsSync30(src)) {
13303
13576
  return 1;
13304
13577
  }
13305
- mkdirSync13(dirname12(dest), { recursive: true });
13578
+ mkdirSync14(dirname12(dest), { recursive: true });
13306
13579
  copyFileSync2(src, dest);
13307
13580
  summary.push("created|.roll/agent-routes.yaml");
13308
13581
  return 0;
13309
13582
  }
13310
13583
  function writeVersionStamp(projectDir, summary) {
13311
- const stampPath = join25(projectDir, ".roll", ".version");
13312
- if (existsSync29(stampPath)) {
13584
+ const stampPath = join27(projectDir, ".roll", ".version");
13585
+ if (existsSync30(stampPath)) {
13313
13586
  summary.push("unchanged|.roll/.version");
13314
13587
  return;
13315
13588
  }
13316
- mkdirSync13(join25(projectDir, ".roll"), { recursive: true });
13589
+ mkdirSync14(join27(projectDir, ".roll"), { recursive: true });
13317
13590
  const installedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
13318
- writeFileSync13(stampPath, `# Roll project version stamp \u2014 written by \`roll init\` (US-ONBOARD-019).
13591
+ writeFileSync15(stampPath, `# Roll project version stamp \u2014 written by \`roll init\` (US-ONBOARD-019).
13319
13592
  # Used by \`_check_structure\` to recognise a previously-onboarded Roll project
13320
13593
  # without depending on directory-name heuristics.
13321
13594
  roll_version: "${rollVersion() || "unknown"}"
@@ -13453,7 +13726,7 @@ function initCommand(args) {
13453
13726
  return null;
13454
13727
  if (args[0] !== void 0 && args[0].startsWith("-"))
13455
13728
  return null;
13456
- if (!existsSync29(rollTemplates2()))
13729
+ if (!existsSync30(rollTemplates2()))
13457
13730
  return null;
13458
13731
  let projectDir;
13459
13732
  try {
@@ -13463,16 +13736,16 @@ function initCommand(args) {
13463
13736
  }
13464
13737
  let hasAgents = false;
13465
13738
  const summary = [];
13466
- if (existsSync29(join25(projectDir, "AGENTS.md"))) {
13739
+ if (existsSync30(join27(projectDir, "AGENTS.md"))) {
13467
13740
  hasAgents = true;
13468
13741
  } else if (isLegacyProject(projectDir)) {
13469
13742
  return null;
13470
13743
  }
13471
13744
  mergeGlobalToProject(projectDir, summary);
13472
13745
  mergeClaudeToProject(projectDir, summary);
13473
- writeBacklog(join25(projectDir, ".roll", "backlog.md"), summary);
13474
- ensureFeaturesDir(join25(projectDir, ".roll", "features"), summary);
13475
- writeFeaturesMd(join25(projectDir, ".roll", "features.md"), summary);
13746
+ writeBacklog(join27(projectDir, ".roll", "backlog.md"), summary);
13747
+ ensureFeaturesDir(join27(projectDir, ".roll", "features"), summary);
13748
+ writeFeaturesMd(join27(projectDir, ".roll", "features.md"), summary);
13476
13749
  const routesTemplate = process.env["ROLL_AGENT_ROUTES_TEMPLATE"] ?? "default";
13477
13750
  initSeedAgentRoutes(routesTemplate, projectDir, summary);
13478
13751
  writeVersionStamp(projectDir, summary);
@@ -13486,16 +13759,16 @@ function initCommand(args) {
13486
13759
 
13487
13760
  // packages/cli/dist/commands/lang.js
13488
13761
  import { execFileSync as execFileSync7 } from "node:child_process";
13489
- import { existsSync as existsSync30, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync3, readFileSync as readFileSync24, renameSync as renameSync2, writeFileSync as writeFileSync14 } from "node:fs";
13762
+ import { existsSync as existsSync31, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readFileSync as readFileSync24, renameSync as renameSync2, writeFileSync as writeFileSync16 } from "node:fs";
13490
13763
  import { homedir as homedir12, tmpdir as tmpdir3 } from "node:os";
13491
- import { dirname as dirname13, join as join26 } from "node:path";
13764
+ import { dirname as dirname13, join as join28 } from "node:path";
13492
13765
  function rollConfigPath4() {
13493
- const rollHome4 = process.env["ROLL_HOME"] ?? join26(homedir12(), ".roll");
13494
- return join26(rollHome4, "config.yaml");
13766
+ const rollHome4 = process.env["ROLL_HOME"] ?? join28(homedir12(), ".roll");
13767
+ return join28(rollHome4, "config.yaml");
13495
13768
  }
13496
13769
  function configLang2() {
13497
13770
  const cfg = rollConfigPath4();
13498
- if (!existsSync30(cfg))
13771
+ if (!existsSync31(cfg))
13499
13772
  return void 0;
13500
13773
  for (const line of readFileSync24(cfg, "utf8").split("\n")) {
13501
13774
  const m7 = /^lang:\s*(.*)$/.exec(line);
@@ -13509,7 +13782,7 @@ function configLang2() {
13509
13782
  }
13510
13783
  function configHasLangLine() {
13511
13784
  const cfg = rollConfigPath4();
13512
- if (!existsSync30(cfg))
13785
+ if (!existsSync31(cfg))
13513
13786
  return false;
13514
13787
  return readFileSync24(cfg, "utf8").split("\n").some((l) => /^lang:/.test(l));
13515
13788
  }
@@ -13545,7 +13818,7 @@ function resolveSource() {
13545
13818
  const env = process.env;
13546
13819
  if ((env["ROLL_LANG"] ?? "") !== "")
13547
13820
  return "ROLL_LANG env";
13548
- if (existsSync30(rollConfigPath4()) && configHasLangLine())
13821
+ if (existsSync31(rollConfigPath4()) && configHasLangLine())
13549
13822
  return `config (${rollConfigPath4()})`;
13550
13823
  if ((env["LC_ALL"] ?? "") !== "" || (env["LANG"] ?? "") !== "")
13551
13824
  return "LC_ALL/LANG";
@@ -13570,28 +13843,28 @@ function err10(line) {
13570
13843
  }
13571
13844
  function writeLang(value) {
13572
13845
  const cfg = rollConfigPath4();
13573
- mkdirSync14(dirname13(cfg), { recursive: true });
13574
- const existing = existsSync30(cfg) ? readFileSync24(cfg, "utf8") : "";
13846
+ mkdirSync15(dirname13(cfg), { recursive: true });
13847
+ const existing = existsSync31(cfg) ? readFileSync24(cfg, "utf8") : "";
13575
13848
  const kept = existing === "" ? [] : existing.split("\n").filter((l) => !/^lang:/.test(l));
13576
13849
  if (kept.length > 0 && kept[kept.length - 1] === "")
13577
13850
  kept.pop();
13578
13851
  const body = kept.length > 0 ? kept.join("\n") + "\n" : "";
13579
- const tmp = join26(mkdtempSync3(join26(tmpdir3(), "roll-lang-")), "config.yaml");
13580
- writeFileSync14(tmp, `${body}lang: ${value}
13852
+ const tmp = join28(mkdtempSync3(join28(tmpdir3(), "roll-lang-")), "config.yaml");
13853
+ writeFileSync16(tmp, `${body}lang: ${value}
13581
13854
  `);
13582
13855
  renameSync2(tmp, cfg);
13583
13856
  }
13584
13857
  function clearLang() {
13585
13858
  const cfg = rollConfigPath4();
13586
- if (!existsSync30(cfg))
13859
+ if (!existsSync31(cfg))
13587
13860
  return;
13588
13861
  const existing = readFileSync24(cfg, "utf8");
13589
13862
  const kept = existing.split("\n").filter((l) => !/^lang:/.test(l));
13590
13863
  if (kept.length > 0 && kept[kept.length - 1] === "")
13591
13864
  kept.pop();
13592
13865
  const body = kept.length > 0 ? kept.join("\n") + "\n" : "";
13593
- const tmp = join26(mkdtempSync3(join26(tmpdir3(), "roll-lang-")), "config.yaml");
13594
- writeFileSync14(tmp, body);
13866
+ const tmp = join28(mkdtempSync3(join28(tmpdir3(), "roll-lang-")), "config.yaml");
13867
+ writeFileSync16(tmp, body);
13595
13868
  renameSync2(tmp, cfg);
13596
13869
  }
13597
13870
  function langCommand(args) {
@@ -13691,9 +13964,9 @@ async function loopFmtCommand(args) {
13691
13964
 
13692
13965
  // packages/cli/dist/commands/loop-pr-inbox.js
13693
13966
  import { spawn as spawn2 } from "node:child_process";
13694
- import { appendFileSync as appendFileSync5, existsSync as existsSync31, mkdirSync as mkdirSync15, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "node:fs";
13967
+ import { appendFileSync as appendFileSync5, existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
13695
13968
  import { homedir as homedir13 } from "node:os";
13696
- import { dirname as dirname14, join as join27 } from "node:path";
13969
+ import { dirname as dirname14, join as join29 } from "node:path";
13697
13970
  function reducePrView(raw) {
13698
13971
  const reviews = raw.reviews ?? [];
13699
13972
  const botReviews = reviews.filter((r) => r.authorAssociation === "BOT" || r.authorAssociation === "APP");
@@ -13776,7 +14049,7 @@ function runtimeDir() {
13776
14049
  const override = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
13777
14050
  if (override !== "")
13778
14051
  return override;
13779
- return join27(process.cwd(), ".roll", "loop");
14052
+ return join29(process.cwd(), ".roll", "loop");
13780
14053
  }
13781
14054
  function projSlug() {
13782
14055
  const override = (process.env["ROLL_MAIN_SLUG"] ?? "").trim();
@@ -13785,26 +14058,26 @@ function projSlug() {
13785
14058
  return process.cwd().split("/").filter(Boolean).pop() ?? "default";
13786
14059
  }
13787
14060
  function alertPath() {
13788
- return join27(runtimeDir(), `ALERT-${projSlug()}.md`);
14061
+ return join29(runtimeDir(), `ALERT-${projSlug()}.md`);
13789
14062
  }
13790
14063
  function statePath() {
13791
- return join27(runtimeDir(), `state-${projSlug()}.yaml`);
14064
+ return join29(runtimeDir(), `state-${projSlug()}.yaml`);
13792
14065
  }
13793
14066
  function tickPath() {
13794
- return join27(runtimeDir(), "pr-tick.jsonl");
14067
+ return join29(runtimeDir(), "pr-tick.jsonl");
13795
14068
  }
13796
14069
  function engineBin() {
13797
14070
  let dir = dirname14(new URL(import.meta.url).pathname);
13798
14071
  for (let i = 0; i < 10; i++) {
13799
- const candidate = join27(dir, "bin", "roll");
13800
- if (existsSync31(candidate))
14072
+ const candidate = join29(dir, "bin", "roll");
14073
+ if (existsSync32(candidate))
13801
14074
  return candidate;
13802
14075
  const parent = dirname14(dir);
13803
14076
  if (parent === dir)
13804
14077
  break;
13805
14078
  dir = parent;
13806
14079
  }
13807
- return join27(homedir13(), ".local", "lib", "roll", "bin", "roll");
14080
+ return join29(homedir13(), ".local", "lib", "roll", "bin", "roll");
13808
14081
  }
13809
14082
  function pal2() {
13810
14083
  return (process.env["NO_COLOR"] ?? "") !== "" ? { yellow: "", nc: "" } : { yellow: "\x1B[0;33m", nc: "\x1B[0m" };
@@ -13814,21 +14087,21 @@ function nowSec() {
13814
14087
  }
13815
14088
  function writeTickFile(tick) {
13816
14089
  const file = tickPath();
13817
- mkdirSync15(dirname14(file), { recursive: true });
14090
+ mkdirSync16(dirname14(file), { recursive: true });
13818
14091
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
13819
14092
  appendFileSync5(file, `${JSON.stringify({ ts, ...tick })}
13820
14093
  `);
13821
14094
  try {
13822
14095
  const lines = readFileSync25(file, "utf8").split("\n").filter((l) => l !== "");
13823
14096
  if (lines.length > 500)
13824
- writeFileSync15(file, `${lines.slice(-500).join("\n")}
14097
+ writeFileSync17(file, `${lines.slice(-500).join("\n")}
13825
14098
  `);
13826
14099
  } catch {
13827
14100
  }
13828
14101
  }
13829
14102
  function appendAlert(line) {
13830
14103
  const file = alertPath();
13831
- mkdirSync15(dirname14(file), { recursive: true });
14104
+ mkdirSync16(dirname14(file), { recursive: true });
13832
14105
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
13833
14106
  appendFileSync5(file, `[${ts}] ${line}
13834
14107
  `);
@@ -13844,9 +14117,9 @@ function rebaseCircuitAllowed(num2) {
13844
14117
  writeRebaseAttempts(state, num2, verdict.freshTimestamps);
13845
14118
  if (!verdict.allowed) {
13846
14119
  const file = alertPath();
13847
- mkdirSync15(dirname14(file), { recursive: true });
14120
+ mkdirSync16(dirname14(file), { recursive: true });
13848
14121
  const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
13849
- writeFileSync15(file, [
14122
+ writeFileSync17(file, [
13850
14123
  `# ALERT \u2014 PR rebase circuit breaker tripped`,
13851
14124
  ``,
13852
14125
  `**Time**: ${stamp}`,
@@ -13900,13 +14173,13 @@ function upsertRebaseAttempts(stateBody, pr, value) {
13900
14173
  `;
13901
14174
  }
13902
14175
  function writeRebaseAttempts(state, pr, timestamps) {
13903
- mkdirSync15(dirname14(state), { recursive: true });
14176
+ mkdirSync16(dirname14(state), { recursive: true });
13904
14177
  let body = "";
13905
14178
  try {
13906
14179
  body = readFileSync25(state, "utf8");
13907
14180
  } catch {
13908
14181
  }
13909
- writeFileSync15(state, upsertRebaseAttempts(body, pr, renderRebaseAttempts(timestamps)));
14182
+ writeFileSync17(state, upsertRebaseAttempts(body, pr, renderRebaseAttempts(timestamps)));
13910
14183
  }
13911
14184
  function bridgeBash(args) {
13912
14185
  return new Promise((resolve3) => {
@@ -13993,19 +14266,19 @@ async function loopPrInboxCommand(_args, deps = realDeps2()) {
13993
14266
  }
13994
14267
 
13995
14268
  // packages/cli/dist/commands/loop-run-once.js
13996
- import { appendFileSync as appendFileSync7, existsSync as existsSync35, mkdirSync as mkdirSync17, readFileSync as readFileSync28 } from "node:fs";
13997
- import { dirname as dirname16, join as join31 } from "node:path";
14269
+ import { appendFileSync as appendFileSync7, existsSync as existsSync36, mkdirSync as mkdirSync18, readFileSync as readFileSync28, writeFileSync as writeFileSync19 } from "node:fs";
14270
+ import { dirname as dirname16, join as join33 } from "node:path";
13998
14271
 
13999
14272
  // packages/cli/dist/runner/executor.js
14000
14273
  import { execFile as execFile8 } from "node:child_process";
14001
- import { appendFileSync as appendFileSync6, existsSync as existsSync34, lstatSync as lstatSync2, mkdirSync as mkdirSync16, readFileSync as readFileSync27, rmSync as rmSync8, symlinkSync as symlinkSync4, unlinkSync, writeFileSync as writeFileSync16 } from "node:fs";
14002
- import { dirname as dirname15, join as join30 } from "node:path";
14274
+ import { appendFileSync as appendFileSync6, existsSync as existsSync35, lstatSync as lstatSync2, mkdirSync as mkdirSync17, readFileSync as readFileSync27, rmSync as rmSync8, symlinkSync as symlinkSync4, unlinkSync, writeFileSync as writeFileSync18 } from "node:fs";
14275
+ import { dirname as dirname15, join as join32 } from "node:path";
14003
14276
  import { promisify as promisify8 } from "node:util";
14004
14277
 
14005
14278
  // packages/cli/dist/runner/peer-gate.js
14006
14279
  import { execFile as execFile7 } from "node:child_process";
14007
- import { existsSync as existsSync32, readdirSync as readdirSync14 } from "node:fs";
14008
- import { join as join28 } from "node:path";
14280
+ import { existsSync as existsSync33, readdirSync as readdirSync15 } from "node:fs";
14281
+ import { join as join30 } from "node:path";
14009
14282
  import { promisify as promisify7 } from "node:util";
14010
14283
  var execFileAsync7 = promisify7(execFile7);
14011
14284
  var HIGH_RISK = [/^\.github\/workflows\//, /^packages\/infra\/src\/(git|github|process)\.ts$/];
@@ -14035,11 +14308,11 @@ async function cycleChangedFiles(worktreeCwd) {
14035
14308
  }
14036
14309
  }
14037
14310
  function peerEvidencePresent(runtimeDir3, cycleId) {
14038
- const dir = join28(runtimeDir3, "peer");
14039
- if (!existsSync32(dir))
14311
+ const dir = join30(runtimeDir3, "peer");
14312
+ if (!existsSync33(dir))
14040
14313
  return false;
14041
14314
  try {
14042
- return readdirSync14(dir).some((f) => f.startsWith(`cycle-${cycleId}.`));
14315
+ return readdirSync15(dir).some((f) => f.startsWith(`cycle-${cycleId}.`));
14043
14316
  } catch {
14044
14317
  return false;
14045
14318
  }
@@ -14063,24 +14336,24 @@ async function runPeerGate(worktreeCwd, runtimeDir3, cycleId, sinks) {
14063
14336
  }
14064
14337
 
14065
14338
  // packages/cli/dist/runner/attest-gate.js
14066
- import { existsSync as existsSync33, readFileSync as readFileSync26, statSync as statSync15 } from "node:fs";
14067
- import { join as join29 } from "node:path";
14339
+ import { existsSync as existsSync34, readFileSync as readFileSync26, statSync as statSync16 } from "node:fs";
14340
+ import { join as join31 } from "node:path";
14068
14341
  function reportCandidates(worktreeCwd, storyId) {
14069
14342
  return [
14070
- join29(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId)),
14071
- join29(legacyArchiveDir(worktreeCwd, storyId), "latest", "report.html")
14343
+ join31(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId)),
14344
+ join31(legacyArchiveDir(worktreeCwd, storyId), "latest", "report.html")
14072
14345
  ];
14073
14346
  }
14074
14347
  function acMapCandidates(worktreeCwd, storyId) {
14075
14348
  return [
14076
- join29(cardArchiveDir(worktreeCwd, storyId), "ac-map.json"),
14077
- join29(legacyArchiveDir(worktreeCwd, storyId), "ac-map.json")
14349
+ join31(cardArchiveDir(worktreeCwd, storyId), "ac-map.json"),
14350
+ join31(legacyArchiveDir(worktreeCwd, storyId), "ac-map.json")
14078
14351
  ];
14079
14352
  }
14080
14353
  function existingReport(worktreeCwd, storyId) {
14081
14354
  for (const p of reportCandidates(worktreeCwd, storyId)) {
14082
14355
  try {
14083
- if (statSync15(p).isFile())
14356
+ if (statSync16(p).isFile())
14084
14357
  return p;
14085
14358
  } catch {
14086
14359
  }
@@ -14094,7 +14367,7 @@ function verificationReportFresh(worktreeCwd, storyId, sinceSec) {
14094
14367
  if (p === null)
14095
14368
  return false;
14096
14369
  try {
14097
- const st = statSync15(p);
14370
+ const st = statSync16(p);
14098
14371
  if (sinceSec === void 0)
14099
14372
  return true;
14100
14373
  return st.mtimeMs / 1e3 >= sinceSec;
@@ -14111,7 +14384,7 @@ function verificationReportHasContent(worktreeCwd, storyId) {
14111
14384
  try {
14112
14385
  const html = readFileSync26(p, "utf8");
14113
14386
  const hasAc = /<section class="ac[\s">]/.test(html);
14114
- const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) => existsSync33(m7));
14387
+ const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) => existsSync34(m7));
14115
14388
  return hasAc && hasMap;
14116
14389
  } catch {
14117
14390
  return false;
@@ -14119,8 +14392,8 @@ function verificationReportHasContent(worktreeCwd, storyId) {
14119
14392
  }
14120
14393
  function readAttestGateMode(repoCwd) {
14121
14394
  try {
14122
- const p = join29(repoCwd, ".roll", "policy.yaml");
14123
- if (!existsSync33(p))
14395
+ const p = join31(repoCwd, ".roll", "policy.yaml");
14396
+ if (!existsSync34(p))
14124
14397
  return "soft";
14125
14398
  return parsePolicy(readFileSync26(p, "utf8")).loopSafety.attestGate === "hard" ? "hard" : "soft";
14126
14399
  } catch {
@@ -14136,7 +14409,7 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks) {
14136
14409
  return { verdict: "produced", mode, reasons: reasons2, blocked: false };
14137
14410
  }
14138
14411
  const reasons = [
14139
- fresh ? `acceptance report at .roll/verification/${storyId}/latest/report.html is an empty shell (no AC content / no ac-map)` : `no fresh acceptance report at .roll/verification/${storyId}/latest/report.html`
14412
+ fresh ? `acceptance report at .roll/features/<epic>/${storyId}/latest/${storyId}-report.html is an empty shell (no AC content / no ac-map)` : `no fresh acceptance report for ${storyId} (checked card archive + legacy verification paths)`
14140
14413
  ];
14141
14414
  const blocked = mode === "hard";
14142
14415
  const lead = fresh ? `delivery with an empty-shell acceptance report (no AC content / no ac-map)` : `delivery without a fresh acceptance report`;
@@ -14230,9 +14503,9 @@ async function executeCommand(cmd, ports, ctx) {
14230
14503
  // execute: spawn the agent (TCR commits happen inside the worktree). The
14231
14504
  // exit code + timeout feed back as agent_exited; usage is captured for cost.
14232
14505
  case "spawn_agent": {
14233
- const livePath = join30(dirname15(ports.paths.eventsPath), "live.log");
14506
+ const livePath = join32(dirname15(ports.paths.eventsPath), "live.log");
14234
14507
  try {
14235
- writeFileSync16(livePath, `\u2500\u2500 cycle ${ctx.cycleId ?? "?"} \xB7 ${ctx.storyId ?? "?"} \xB7 agent ${cmd.agent} \u2500\u2500
14508
+ writeFileSync18(livePath, `\u2500\u2500 cycle ${ctx.cycleId ?? "?"} \xB7 ${ctx.storyId ?? "?"} \xB7 agent ${cmd.agent} \u2500\u2500
14236
14509
  `);
14237
14510
  } catch {
14238
14511
  }
@@ -14250,9 +14523,9 @@ async function executeCommand(cmd, ports, ctx) {
14250
14523
  }
14251
14524
  });
14252
14525
  try {
14253
- const logDir = join30(dirname15(ports.paths.eventsPath), "cycle-logs");
14254
- mkdirSync16(logDir, { recursive: true });
14255
- writeFileSync16(join30(logDir, `${ctx.cycleId ?? "cycle"}.agent.log`), `# exit=${res.exitCode} timedOut=${res.timedOut}
14526
+ const logDir = join32(dirname15(ports.paths.eventsPath), "cycle-logs");
14527
+ mkdirSync17(logDir, { recursive: true });
14528
+ writeFileSync18(join32(logDir, `${ctx.cycleId ?? "cycle"}.agent.log`), `# exit=${res.exitCode} timedOut=${res.timedOut}
14256
14529
  --- stdout ---
14257
14530
  ${res.stdout}
14258
14531
  --- stderr ---
@@ -14375,7 +14648,7 @@ ${res.stderr}
14375
14648
  // _worktree_cleanup (tolerant). Side effect; no feedback (terminal path).
14376
14649
  case "cleanup_worktree":
14377
14650
  try {
14378
- const dst = join30(ports.paths.worktreePath, ".roll");
14651
+ const dst = join32(ports.paths.worktreePath, ".roll");
14379
14652
  if (lstatSync2(dst, { throwIfNoEntry: false })?.isSymbolicLink() === true)
14380
14653
  unlinkSync(dst);
14381
14654
  } catch {
@@ -14447,7 +14720,7 @@ function buildRunRow(cmd, ctx, nowSec2) {
14447
14720
  }
14448
14721
  function readRunsRows(runsPath) {
14449
14722
  try {
14450
- if (!existsSync34(runsPath))
14723
+ if (!existsSync35(runsPath))
14451
14724
  return [];
14452
14725
  return readFileSync27(runsPath, "utf8").split("\n").filter((l) => l.trim() !== "").map((l) => {
14453
14726
  try {
@@ -14472,15 +14745,15 @@ function sleep(ms) {
14472
14745
  }
14473
14746
  async function linkRollIntoWorktree(repoCwd, worktreePath) {
14474
14747
  try {
14475
- const src = join30(repoCwd, ".roll");
14476
- const dst = join30(worktreePath, ".roll");
14477
- if (!existsSync34(src))
14748
+ const src = join32(repoCwd, ".roll");
14749
+ const dst = join32(worktreePath, ".roll");
14750
+ if (!existsSync35(src))
14478
14751
  return;
14479
14752
  const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
14480
14753
  if (dstStat) {
14481
14754
  if (dstStat.isSymbolicLink())
14482
14755
  return;
14483
- const incompleteFossil = existsSync34(join30(src, "backlog.md")) && !existsSync34(join30(dst, "backlog.md"));
14756
+ const incompleteFossil = existsSync35(join32(src, "backlog.md")) && !existsSync35(join32(dst, "backlog.md"));
14484
14757
  if (!incompleteFossil)
14485
14758
  return;
14486
14759
  rmSync8(dst, { recursive: true, force: true });
@@ -14489,10 +14762,10 @@ async function linkRollIntoWorktree(repoCwd, worktreePath) {
14489
14762
  const common = (await execFileAsync8("git", ["-C", repoCwd, "rev-parse", "--path-format=absolute", "--git-common-dir"])).stdout.trim();
14490
14763
  if (common === "")
14491
14764
  return;
14492
- const exclude = join30(common, "info", "exclude");
14493
- const cur = existsSync34(exclude) ? readFileSync27(exclude, "utf8") : "";
14765
+ const exclude = join32(common, "info", "exclude");
14766
+ const cur = existsSync35(exclude) ? readFileSync27(exclude, "utf8") : "";
14494
14767
  if (!/^\.roll$/m.test(cur)) {
14495
- mkdirSync16(dirname15(exclude), { recursive: true });
14768
+ mkdirSync17(dirname15(exclude), { recursive: true });
14496
14769
  appendFileSync6(exclude, `${cur === "" || cur.endsWith("\n") ? "" : "\n"}.roll
14497
14770
  `, "utf8");
14498
14771
  }
@@ -14581,8 +14854,8 @@ function nodePorts(opts) {
14581
14854
  },
14582
14855
  backlog: {
14583
14856
  read(projectCwd) {
14584
- const p = join30(projectCwd, ".roll", "backlog.md");
14585
- if (!existsSync34(p))
14857
+ const p = join32(projectCwd, ".roll", "backlog.md");
14858
+ if (!existsSync35(p))
14586
14859
  return [];
14587
14860
  return parseBacklog(readFileSync27(p, "utf8"));
14588
14861
  },
@@ -14592,8 +14865,8 @@ function nodePorts(opts) {
14592
14865
  // never kill the cycle, the reconcile pass is the safety net.
14593
14866
  markStatus(projectCwd, id, status2) {
14594
14867
  try {
14595
- const p = join30(projectCwd, ".roll", "backlog.md");
14596
- if (!existsSync34(p))
14868
+ const p = join32(projectCwd, ".roll", "backlog.md");
14869
+ if (!existsSync35(p))
14597
14870
  return;
14598
14871
  const store = new BacklogStore();
14599
14872
  const snap = store.readBacklog(p);
@@ -14613,7 +14886,7 @@ function nodePorts(opts) {
14613
14886
  };
14614
14887
  }
14615
14888
  function appendAlertLine(alertsPath, message) {
14616
- mkdirSync16(dirname15(alertsPath), { recursive: true });
14889
+ mkdirSync17(dirname15(alertsPath), { recursive: true });
14617
14890
  appendFileSync6(alertsPath, `${message}
14618
14891
  `, "utf8");
14619
14892
  }
@@ -14798,13 +15071,13 @@ function announceReport(projectPath, slug, storyId, opener = (p) => {
14798
15071
  }) {
14799
15072
  if (storyId === "")
14800
15073
  return null;
14801
- const report = join31(projectPath, ".roll", "verification", storyId, "latest", "report.html");
14802
- if (!existsSync35(report))
15074
+ const report = join33(projectPath, ".roll", "verification", storyId, "latest", "report.html");
15075
+ if (!existsSync36(report))
14803
15076
  return null;
14804
15077
  process.stdout.write(`evidence: ${report}
14805
15078
  \u9A8C\u6536\u62A5\u544A: ${report}
14806
15079
  `);
14807
- const muted = existsSync35(join31(projectPath, ".roll", "loop", `mute-${slug}`)) || existsSync35(join31(process.env["ROLL_SHARED_ROOT"] || join31(process.env["HOME"] ?? "", ".shared", "roll"), "loop", `mute-${slug}`));
15080
+ const muted = existsSync36(join33(projectPath, ".roll", "loop", `mute-${slug}`)) || existsSync36(join33(process.env["ROLL_SHARED_ROOT"] || join33(process.env["HOME"] ?? "", ".shared", "roll"), "loop", `mute-${slug}`));
14808
15081
  if (!muted)
14809
15082
  opener(report);
14810
15083
  return report;
@@ -14821,7 +15094,7 @@ function cycleSignalTeardown(paths, cycleId, branch, sig, deps = {}) {
14821
15094
  }
14822
15095
  let owned = false;
14823
15096
  try {
14824
- owned = existsSync35(paths.lockPath) && parseLock(readFileSync28(paths.lockPath, "utf8")).pid === pid;
15097
+ owned = existsSync36(paths.lockPath) && parseLock(readFileSync28(paths.lockPath, "utf8")).pid === pid;
14825
15098
  } catch {
14826
15099
  owned = false;
14827
15100
  }
@@ -14866,7 +15139,51 @@ function makeCycleId(now = /* @__PURE__ */ new Date(), pid = process.pid) {
14866
15139
  }
14867
15140
  function runtimeDir2(projectPath) {
14868
15141
  const env = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
14869
- return env !== "" ? env : join31(projectPath, ".roll", "loop");
15142
+ return env !== "" ? env : join33(projectPath, ".roll", "loop");
15143
+ }
15144
+ var PAUSE_THRESHOLD = 3;
15145
+ function incrementConsecutiveFails(projectPath, slug, alertsPath, cycleId, storyId, terminal) {
15146
+ const rt = runtimeDir2(projectPath);
15147
+ const counterFile = join33(rt, "consecutive-fails");
15148
+ let count = 0;
15149
+ try {
15150
+ if (existsSync36(counterFile)) {
15151
+ count = parseInt(readFileSync28(counterFile, "utf8").trim(), 10) || 0;
15152
+ }
15153
+ } catch {
15154
+ }
15155
+ count += 1;
15156
+ try {
15157
+ writeFileSync19(counterFile, String(count), "utf8");
15158
+ } catch {
15159
+ }
15160
+ if (count < PAUSE_THRESHOLD)
15161
+ return;
15162
+ const pauseMarker = join33(projectPath, ".roll", "loop", `PAUSE-${slug}`);
15163
+ const alertMsg = `# ALERT \u2014 loop auto-paused after ${count} consecutive failures
15164
+
15165
+ **Cycle**: ${cycleId}
15166
+ **Story**: ${storyId}
15167
+ **Terminal**: ${terminal}
15168
+ **Action**: ${count} consecutive cycles failed \u2014 loop paused to prevent burn.
15169
+ Resolve the root cause, then: \`roll loop resume\`
15170
+ `;
15171
+ try {
15172
+ writeFileSync19(pauseMarker, alertMsg, "utf8");
15173
+ appendFileSync7(alertsPath, `${alertMsg}
15174
+ `, "utf8");
15175
+ } catch {
15176
+ }
15177
+ process.stderr.write(`loop run-once: auto-PAUSED after ${count} consecutive failures \u2014 PAUSE marker written
15178
+ loop run-once: \u8FDE\u7EED ${count} \u6B21\u5931\u8D25\u540E\u81EA\u52A8\u6682\u505C \u2014 \u5DF2\u5199 PAUSE \u6807\u8BB0
15179
+ `);
15180
+ }
15181
+ function resetConsecutiveFails(projectPath) {
15182
+ const rt = runtimeDir2(projectPath);
15183
+ try {
15184
+ writeFileSync19(join33(rt, "consecutive-fails"), "0", "utf8");
15185
+ } catch {
15186
+ }
14870
15187
  }
14871
15188
  function readSkillBody2(projectPath) {
14872
15189
  return readSkillBody(projectPath, {
@@ -14897,26 +15214,28 @@ async function loopRunOnceCommand(args) {
14897
15214
  return 0;
14898
15215
  }
14899
15216
  const rt = runtimeDir2(id.path);
15217
+ const alertsPath = join33(rt, `ALERT-${id.slug}.md`);
15218
+ mkdirSync18(dirname16(alertsPath), { recursive: true });
14900
15219
  const paths = {
14901
- eventsPath: join31(rt, "events.ndjson"),
14902
- runsPath: join31(rt, "runs.jsonl"),
14903
- alertsPath: join31(rt, "alerts.log"),
14904
- lockPath: join31(rt, "inner.lock"),
14905
- heartbeatPath: join31(rt, "heartbeat"),
14906
- worktreePath: join31(rt, "worktrees", `cycle-${cycleId}`)
15220
+ eventsPath: join33(rt, "events.ndjson"),
15221
+ runsPath: join33(rt, "runs.jsonl"),
15222
+ alertsPath,
15223
+ lockPath: join33(rt, "inner.lock"),
15224
+ heartbeatPath: join33(rt, "heartbeat"),
15225
+ worktreePath: join33(rt, "worktrees", `cycle-${cycleId}`)
14907
15226
  };
14908
15227
  const skillBody = readSkillBody2(id.path);
14909
15228
  if (skillBody === null) {
14910
15229
  const msg2 = `[${(/* @__PURE__ */ new Date()).toISOString()}] ALERT loop run-once: roll-loop SKILL.md not found (checked ROLL_LOOP_SKILL, .roll/skills/, skills/) \u2014 cycle ${cycleId} refused to start`;
14911
15230
  try {
14912
- mkdirSync17(dirname16(paths.alertsPath), { recursive: true });
14913
- appendFileSync7(paths.alertsPath, `${msg2}
15231
+ appendFileSync7(alertsPath, `${msg2}
14914
15232
  `, "utf8");
14915
15233
  } catch {
14916
15234
  }
14917
15235
  process.stderr.write(`loop run-once: roll-loop SKILL.md not found \u2014 refusing to spawn a blind agent (ALERT written)
14918
15236
  loop run-once: \u627E\u4E0D\u5230 roll-loop SKILL.md \u2014 \u62D2\u7EDD\u76F2\u5F00 agent(\u5DF2\u5199 ALERT)
14919
15237
  `);
15238
+ incrementConsecutiveFails(id.path, id.slug, alertsPath, cycleId, "", "skill_missing");
14920
15239
  return 1;
14921
15240
  }
14922
15241
  const routeDeps = {
@@ -14946,22 +15265,28 @@ loop run-once: \u627E\u4E0D\u5230 roll-loop SKILL.md \u2014 \u62D2\u7EDD\u76F2\u
14946
15265
  if (result.terminal === "done") {
14947
15266
  const storyId = (result.state?.ctx?.storyId ?? "").trim();
14948
15267
  announceReport(id.path, id.slug, storyId);
15268
+ resetConsecutiveFails(id.path);
15269
+ }
15270
+ const isFail = result.terminal === "failed" || result.terminal === "blocked";
15271
+ if (isFail) {
15272
+ const storyId = (result.state?.ctx?.storyId ?? "").trim();
15273
+ incrementConsecutiveFails(id.path, id.slug, alertsPath, cycleId, storyId, result.terminal ?? "unknown");
14949
15274
  }
14950
- return result.terminal === "failed" || result.terminal === "blocked" ? 1 : 0;
15275
+ return isFail ? 1 : 0;
14951
15276
  }
14952
15277
 
14953
15278
  // packages/cli/dist/commands/loop-sched.js
14954
15279
  import { createHash as createHash4 } from "node:crypto";
14955
15280
  import { spawn as spawn4, spawnSync as spawnSync6 } from "node:child_process";
14956
- import { existsSync as existsSync36, mkdirSync as mkdirSync18, readFileSync as readFileSync29, rmSync as rmSync9, writeFileSync as writeFileSync17 } from "node:fs";
15281
+ import { existsSync as existsSync37, mkdirSync as mkdirSync19, readFileSync as readFileSync29, rmSync as rmSync9, writeFileSync as writeFileSync20 } from "node:fs";
14957
15282
  import { homedir as homedir14 } from "node:os";
14958
- import { dirname as dirname17, join as join32 } from "node:path";
15283
+ import { dirname as dirname17, join as join34 } from "node:path";
14959
15284
  function realDeps3() {
14960
15285
  return {
14961
15286
  identity: () => projectIdentity(),
14962
15287
  uid: () => process.getuid?.() ?? 501,
14963
- sharedRoot: () => process.env["ROLL_SHARED_ROOT"] || join32(homedir14(), ".shared", "roll"),
14964
- launchdDir: () => join32(homedir14(), "Library", "LaunchAgents"),
15288
+ sharedRoot: () => process.env["ROLL_SHARED_ROOT"] || join34(homedir14(), ".shared", "roll"),
15289
+ launchdDir: () => join34(homedir14(), "Library", "LaunchAgents"),
14965
15290
  launchd: { reinstall, uninstall, isLoaded },
14966
15291
  execRunner: (runner) => new Promise((resolve3) => {
14967
15292
  const child = spawn4("bash", [runner], {
@@ -14981,8 +15306,8 @@ function realDeps3() {
14981
15306
  // The `loop now` inline observation: tail live.log while the cycle holds
14982
15307
  // the inner lock; Ctrl-C stops the TAIL only (the cycle lives in tmux).
14983
15308
  observe: (rt) => new Promise((resolve3) => {
14984
- const lock = join32(rt, "inner.lock");
14985
- const tail = spawn4("tail", ["-n", "+1", "-F", join32(rt, "live.log")], { stdio: "inherit" });
15309
+ const lock = join34(rt, "inner.lock");
15310
+ const tail = spawn4("tail", ["-n", "+1", "-F", join34(rt, "live.log")], { stdio: "inherit" });
14986
15311
  let sawLock = false;
14987
15312
  const t0 = Date.now();
14988
15313
  const finish = () => {
@@ -14994,9 +15319,9 @@ function realDeps3() {
14994
15319
  resolve3();
14995
15320
  };
14996
15321
  const timer = setInterval(() => {
14997
- if (existsSync36(lock))
15322
+ if (existsSync37(lock))
14998
15323
  sawLock = true;
14999
- const done = sawLock ? !existsSync36(lock) : Date.now() - t0 > 3e4;
15324
+ const done = sawLock ? !existsSync37(lock) : Date.now() - t0 > 3e4;
15000
15325
  if (done) {
15001
15326
  clearInterval(timer);
15002
15327
  finish();
@@ -15165,11 +15490,11 @@ function pathValue() {
15165
15490
  ].join(":");
15166
15491
  }
15167
15492
  function writeExecutable(path, content) {
15168
- mkdirSync18(dirname17(path), { recursive: true });
15169
- writeFileSync17(path, content, { mode: 493 });
15493
+ mkdirSync19(dirname17(path), { recursive: true });
15494
+ writeFileSync20(path, content, { mode: 493 });
15170
15495
  }
15171
15496
  function pauseMarkerPath(projectPath, slug) {
15172
- return join32(projectPath, ".roll", "loop", `PAUSE-${slug}`);
15497
+ return join34(projectPath, ".roll", "loop", `PAUSE-${slug}`);
15173
15498
  }
15174
15499
  var LOOP_SERVICES = ["loop", "dream", "pr"];
15175
15500
  async function mountService(deps, uid, label4, plist) {
@@ -15188,16 +15513,16 @@ async function loopOnCommand(_args, deps = realDeps3()) {
15188
15513
  const shared = deps.sharedRoot();
15189
15514
  const ld = deps.launchdDir();
15190
15515
  const uid = deps.uid();
15191
- mkdirSync18(ld, { recursive: true });
15516
+ mkdirSync19(ld, { recursive: true });
15192
15517
  let period = 30;
15193
- const localYaml = join32(id.path, ".roll", "local.yaml");
15194
- if (existsSync36(localYaml)) {
15518
+ const localYaml = join34(id.path, ".roll", "local.yaml");
15519
+ if (existsSync37(localYaml)) {
15195
15520
  try {
15196
15521
  period = parseLoopPeriodMinutes(readFileSync29(localYaml, "utf8"));
15197
15522
  } catch {
15198
15523
  }
15199
15524
  }
15200
- const loopRunner = join32(shared, "loop", `run-${id.slug}.sh`);
15525
+ const loopRunner = join34(shared, "loop", `run-${id.slug}.sh`);
15201
15526
  const rollBinOverride = (process.env["ROLL_RUNNER_ROLL_BIN"] ?? "").trim();
15202
15527
  writeExecutable(loopRunner, buildLoopRunnerScript({
15203
15528
  projectPath: id.path,
@@ -15208,7 +15533,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
15208
15533
  }));
15209
15534
  const loopLabel = launchdLabel("loop", id.slug);
15210
15535
  const loopPlist = launchdPlistPath("loop", id.slug, ld);
15211
- writeFileSync17(loopPlist, plistContent({
15536
+ writeFileSync20(loopPlist, plistContent({
15212
15537
  label: loopLabel,
15213
15538
  runnerScript: loopRunner,
15214
15539
  projectPath: id.path,
@@ -15216,14 +15541,14 @@ async function loopOnCommand(_args, deps = realDeps3()) {
15216
15541
  schedule: { kind: "interval", periodMinutes: period }
15217
15542
  }));
15218
15543
  const loopMount = await mountService(deps, uid, loopLabel, loopPlist);
15219
- const prRunner = join32(shared, "pr", `run-${id.slug}.sh`);
15544
+ const prRunner = join34(shared, "pr", `run-${id.slug}.sh`);
15220
15545
  writeExecutable(prRunner, buildPrRunnerScript({
15221
15546
  projectPath: id.path,
15222
15547
  ...rollBinOverride !== "" ? { rollBin: rollBinOverride } : {}
15223
15548
  }));
15224
15549
  const prLabel = launchdLabel("pr", id.slug);
15225
15550
  const prPlist = launchdPlistPath("pr", id.slug, ld);
15226
- writeFileSync17(prPlist, plistContent({
15551
+ writeFileSync20(prPlist, plistContent({
15227
15552
  label: prLabel,
15228
15553
  runnerScript: prRunner,
15229
15554
  projectPath: id.path,
@@ -15232,7 +15557,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
15232
15557
  }));
15233
15558
  const prMount = await mountService(deps, uid, prLabel, prPlist);
15234
15559
  const dream = dreamScheduleFor(id.path);
15235
- const dreamRunner = join32(shared, "dream", `run-${id.slug}.sh`);
15560
+ const dreamRunner = join34(shared, "dream", `run-${id.slug}.sh`);
15236
15561
  writeExecutable(dreamRunner, buildDreamRunnerScript({
15237
15562
  projectPath: id.path,
15238
15563
  slug: id.slug,
@@ -15240,7 +15565,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
15240
15565
  }));
15241
15566
  const dreamLabel = launchdLabel("dream", id.slug);
15242
15567
  const dreamPlist = launchdPlistPath("dream", id.slug, ld);
15243
- writeFileSync17(dreamPlist, plistContent({
15568
+ writeFileSync20(dreamPlist, plistContent({
15244
15569
  label: dreamLabel,
15245
15570
  runnerScript: dreamRunner,
15246
15571
  projectPath: id.path,
@@ -15292,10 +15617,10 @@ Loop \u5DF2\u505C\u7528(loop/dream/pr \u5747\u5DF2\u5378\u8F7D)
15292
15617
  async function loopPauseCommand(_args, deps = realDeps3()) {
15293
15618
  const id = await deps.identity();
15294
15619
  const marker = pauseMarkerPath(id.path, id.slug);
15295
- mkdirSync18(dirname17(marker), { recursive: true });
15296
- const already = existsSync36(marker);
15620
+ mkdirSync19(dirname17(marker), { recursive: true });
15621
+ const already = existsSync37(marker);
15297
15622
  if (!already)
15298
- writeFileSync17(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
15623
+ writeFileSync20(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
15299
15624
  `);
15300
15625
  process.stdout.write(already ? `Loop already paused
15301
15626
  Loop \u5DF2\u5904\u4E8E\u6682\u505C
@@ -15307,7 +15632,7 @@ Loop \u5DF2\u6682\u505C \u2014 \u540E\u7EED\u6392\u7A0B\u5468\u671F\u5C06\u8DF3\
15307
15632
  async function loopResumeCommand(_args, deps = realDeps3()) {
15308
15633
  const id = await deps.identity();
15309
15634
  const marker = pauseMarkerPath(id.path, id.slug);
15310
- const existed = existsSync36(marker);
15635
+ const existed = existsSync37(marker);
15311
15636
  rmSync9(marker, { force: true });
15312
15637
  process.stdout.write(existed ? `Loop resumed \u2014 scheduling active again
15313
15638
  Loop \u5DF2\u6062\u590D \u2014 \u6392\u7A0B\u91CD\u65B0\u751F\u6548
@@ -15325,16 +15650,16 @@ function isLegacyRunner(text) {
15325
15650
  }
15326
15651
  async function loopNowCommand(_args, deps = realDeps3()) {
15327
15652
  const id = await deps.identity();
15328
- const runner = join32(deps.sharedRoot(), "loop", `run-${id.slug}.sh`);
15653
+ const runner = join34(deps.sharedRoot(), "loop", `run-${id.slug}.sh`);
15329
15654
  let legacy = false;
15330
- if (existsSync36(runner)) {
15655
+ if (existsSync37(runner)) {
15331
15656
  try {
15332
15657
  legacy = isLegacyRunner(readFileSync29(runner, "utf8"));
15333
15658
  } catch {
15334
15659
  legacy = true;
15335
15660
  }
15336
15661
  }
15337
- if (!existsSync36(runner) || legacy) {
15662
+ if (!existsSync37(runner) || legacy) {
15338
15663
  process.stdout.write(legacy ? `Legacy v2 runner detected \u2014 regenerating templates (FIX-197)
15339
15664
  \u68C0\u6D4B\u5230 v2 \u65E7\u7248 runner \u2014 \u6B63\u5728\u518D\u751F\u6210\u6A21\u677F\uFF08FIX-197\uFF09
15340
15665
  ` : `No runner yet \u2014 generating templates
@@ -15362,7 +15687,7 @@ live transcript below \u2014 Ctrl-C stops watching, never the cycle
15362
15687
  }
15363
15688
  const rc = await exec(runner);
15364
15689
  if (rc === 0 && useTmux && deps.observe !== void 0) {
15365
- await deps.observe(join32(id.path, ".roll", "loop"));
15690
+ await deps.observe(join34(id.path, ".roll", "loop"));
15366
15691
  process.stdout.write(`
15367
15692
  cycle finished \u2014 logs: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
15368
15693
  \u5468\u671F\u7ED3\u675F \u2014 \u65E5\u5FD7: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
@@ -15373,7 +15698,7 @@ cycle finished \u2014 logs: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
15373
15698
 
15374
15699
  // packages/cli/dist/commands/migrate.js
15375
15700
  import { spawnSync as spawnSync7 } from "node:child_process";
15376
- import { existsSync as existsSync37, mkdirSync as mkdirSync19 } from "node:fs";
15701
+ import { existsSync as existsSync38, mkdirSync as mkdirSync20 } from "node:fs";
15377
15702
  import { dirname as dirname18 } from "node:path";
15378
15703
  function colors() {
15379
15704
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
@@ -15494,8 +15819,8 @@ function migrateExecute(activeMoves) {
15494
15819
  const src = srcOf(move);
15495
15820
  const tgt = tgtOf(move);
15496
15821
  const targetDir = dirname18(tgt);
15497
- if (!existsSync37(targetDir))
15498
- mkdirSync19(targetDir, { recursive: true });
15822
+ if (!existsSync38(targetDir))
15823
+ mkdirSync20(targetDir, { recursive: true });
15499
15824
  const r = git3(["mv", src, tgt]);
15500
15825
  if (r.status !== 0) {
15501
15826
  err11(`git mv failed: ${src} \u2192 ${tgt}`);
@@ -15504,7 +15829,7 @@ function migrateExecute(activeMoves) {
15504
15829
  }
15505
15830
  moved += 1;
15506
15831
  }
15507
- if (existsSync37("docs")) {
15832
+ if (existsSync38("docs")) {
15508
15833
  spawnSync7("find", ["docs", "-type", "d", "-empty", "-delete"], { stdio: "ignore" });
15509
15834
  }
15510
15835
  git3([
@@ -15545,10 +15870,10 @@ function migrateCommand(args) {
15545
15870
  const moves = buildMoves();
15546
15871
  let hasNew = false;
15547
15872
  let hasOld = false;
15548
- if (existsSync37(".roll"))
15873
+ if (existsSync38(".roll"))
15549
15874
  hasNew = true;
15550
15875
  for (const move of moves) {
15551
- if (existsSync37(srcOf(move))) {
15876
+ if (existsSync38(srcOf(move))) {
15552
15877
  hasOld = true;
15553
15878
  break;
15554
15879
  }
@@ -15560,7 +15885,7 @@ function migrateCommand(args) {
15560
15885
  for (const move of moves) {
15561
15886
  const src = srcOf(move);
15562
15887
  const tgt = tgtOf(move);
15563
- if (existsSync37(src) && existsSync37(tgt)) {
15888
+ if (existsSync38(src) && existsSync38(tgt)) {
15564
15889
  process.stderr.write(` - ${src} AND ${tgt} both exist
15565
15890
  `);
15566
15891
  }
@@ -15577,7 +15902,7 @@ function migrateCommand(args) {
15577
15902
  info3(m2("migrate.no_old_structure_detected_nothing_to"));
15578
15903
  return 0;
15579
15904
  }
15580
- const activeMoves = moves.filter((move) => existsSync37(srcOf(move)));
15905
+ const activeMoves = moves.filter((move) => existsSync38(srcOf(move)));
15581
15906
  if (activeMoves.length === 0) {
15582
15907
  warn4(m2("migrate.old_structure_markers_found_but_no"));
15583
15908
  return 0;
@@ -15593,11 +15918,99 @@ function migrateCommand(args) {
15593
15918
  return migrateExecute(activeMoves);
15594
15919
  }
15595
15920
 
15921
+ // packages/cli/dist/commands/migrate-features.js
15922
+ import { existsSync as existsSync39, mkdirSync as mkdirSync21, readFileSync as readFileSync30, readlinkSync as readlinkSync3, statSync as statSync17, writeFileSync as writeFileSync21 } from "node:fs";
15923
+ import { join as join35 } from "node:path";
15924
+ function specFrontmatter(specFile, storyId) {
15925
+ try {
15926
+ const lines = readFileSync30(specFile, "utf8").split("\n").slice(0, 12);
15927
+ const pick = (key) => {
15928
+ const row2 = lines.find((l) => l.startsWith(`${key}: `));
15929
+ return row2?.slice(key.length + 2).trim();
15930
+ };
15931
+ let title = pick("title");
15932
+ if (title === void 0) {
15933
+ const heading = lines.find((l) => l.startsWith("# "));
15934
+ const m7 = heading?.match(new RegExp(`^# ${storyId}\\s*[\u2014\u2013-]+\\s*(.+)$`));
15935
+ if (m7 !== void 0 && m7 !== null)
15936
+ title = m7[1];
15937
+ }
15938
+ return { ...title !== void 0 ? { title } : {}, ...pick("created") !== void 0 ? { created: pick("created") } : {} };
15939
+ } catch {
15940
+ return {};
15941
+ }
15942
+ }
15943
+ function migrateFeaturesCommand(args) {
15944
+ const refresh = args.includes("--refresh-html");
15945
+ const cwd = process.cwd();
15946
+ if (!existsSync39(join35(cwd, ".roll", "index.json"))) {
15947
+ process.stderr.write("migrate-features: .roll/index.json not found\n");
15948
+ return 1;
15949
+ }
15950
+ const stories = readIndex(cwd);
15951
+ const root = join35(cwd, ".roll", "features");
15952
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
15953
+ let specCreated = 0;
15954
+ let htmlWritten = 0;
15955
+ let skipped = 0;
15956
+ for (const [storyId, epic] of Object.entries(stories)) {
15957
+ const storyDir = join35(root, epic, storyId);
15958
+ const specFile = join35(storyDir, "spec.md");
15959
+ if (!existsSync39(storyDir)) {
15960
+ const mdFile = join35(root, epic, `${storyId}.md`);
15961
+ if (existsSync39(mdFile)) {
15962
+ try {
15963
+ mkdirSync21(storyDir, { recursive: true });
15964
+ } catch {
15965
+ skipped++;
15966
+ continue;
15967
+ }
15968
+ } else {
15969
+ skipped++;
15970
+ continue;
15971
+ }
15972
+ }
15973
+ if (!existsSync39(specFile)) {
15974
+ writeFileSync21(specFile, renderSpecMd({
15975
+ id: storyId,
15976
+ epic,
15977
+ created: today,
15978
+ note: "Auto-generated by migrate-features (US-META-007)."
15979
+ }), "utf8");
15980
+ specCreated++;
15981
+ }
15982
+ const htmlFile = join35(storyDir, "index.html");
15983
+ if (!existsSync39(htmlFile) || refresh) {
15984
+ const fm = specFrontmatter(specFile, storyId);
15985
+ const card = { id: storyId, epic, created: fm.created ?? today, ...fm.title !== void 0 ? { title: fm.title } : {} };
15986
+ let html = renderStoryPage(card);
15987
+ const latest = join35(storyDir, "latest");
15988
+ const report = join35(latest, reportFileName(storyId));
15989
+ if (existsSync39(report)) {
15990
+ let runRel = "latest";
15991
+ try {
15992
+ runRel = readlinkSync3(latest);
15993
+ } catch {
15994
+ }
15995
+ const deliveredOn = statSync17(report).mtime.toISOString().slice(0, 10);
15996
+ html = markPhaseDone(html, "delivery", `<p><a href="${runRel}/${reportFileName(storyId)}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a></p>
15997
+ <p class="muted">${bi("Delivered", "\u4EA4\u4ED8\u4E8E")} ${deliveredOn}</p>
15998
+ `);
15999
+ }
16000
+ writeFileSync21(htmlFile, html, "utf8");
16001
+ htmlWritten++;
16002
+ }
16003
+ }
16004
+ process.stdout.write(`migrate-features: ${specCreated} spec.md + ${htmlWritten} index.html ${refresh ? "written" : "created"}, ${skipped} skipped
16005
+ `);
16006
+ return 0;
16007
+ }
16008
+
15596
16009
  // packages/cli/dist/commands/offboard.js
15597
16010
  import { spawnSync as spawnSync8 } from "node:child_process";
15598
- import { existsSync as existsSync38, readFileSync as readFileSync30, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync18 } from "node:fs";
16011
+ import { existsSync as existsSync40, readFileSync as readFileSync31, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync22 } from "node:fs";
15599
16012
  import { homedir as homedir15 } from "node:os";
15600
- import { join as join33, resolve as resolve2 } from "node:path";
16013
+ import { join as join36, resolve as resolve2 } from "node:path";
15601
16014
  function pal3() {
15602
16015
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
15603
16016
  return noColor2 ? { RED: "", GREEN: "", YELLOW: "", CYAN: "", BOLD: "", NC: "" } : {
@@ -15635,7 +16048,7 @@ function m3(key, ...args) {
15635
16048
  return t(v2Catalog, msgLang9(), key, ...args);
15636
16049
  }
15637
16050
  function changesetPath(projectDir) {
15638
- return join33(projectDir, ".roll", "onboard-changeset.yaml");
16051
+ return join36(projectDir, ".roll", "onboard-changeset.yaml");
15639
16052
  }
15640
16053
  function loopInCycle() {
15641
16054
  return (process.env["ROLL_LOOP_AGENT"] ?? "") !== "" || (process.env["ROLL_CYCLE_LOG_RAW"] ?? "") !== "";
@@ -15645,7 +16058,7 @@ function runLaunchctl(args) {
15645
16058
  if (args[0] !== void 0 && readOnly.has(args[0])) {
15646
16059
  return spawnSync8("launchctl", args, { stdio: "inherit" }).status ?? 1;
15647
16060
  }
15648
- const canonical = join33(homedir15(), "Library", "LaunchAgents");
16061
+ const canonical = join36(homedir15(), "Library", "LaunchAgents");
15649
16062
  const launchdDir = process.env["_LAUNCHD_DIR"] ?? canonical;
15650
16063
  if (launchdDir !== canonical)
15651
16064
  return 0;
@@ -15709,7 +16122,7 @@ function offboardCommand(args) {
15709
16122
  projectDir = process.cwd();
15710
16123
  }
15711
16124
  const changeset = changesetPath(projectDir);
15712
- if (!existsSync38(changeset)) {
16125
+ if (!existsSync40(changeset)) {
15713
16126
  err12(m3("offboard.no_changeset_en"));
15714
16127
  err12(m3("offboard.no_changeset_zh"));
15715
16128
  process.stderr.write("\n");
@@ -15723,7 +16136,7 @@ function offboardCommand(args) {
15723
16136
  `);
15724
16137
  return 1;
15725
16138
  }
15726
- const parsed = parseChangeset(readFileSync30(changeset, "utf8"));
16139
+ const parsed = parseChangeset(readFileSync31(changeset, "utf8"));
15727
16140
  if (!parsed.ok) {
15728
16141
  err12(m3("offboard.failed_to_parse_changeset"));
15729
16142
  return 1;
@@ -15799,8 +16212,8 @@ function offboardCommand(args) {
15799
16212
  process.stdout.write(m3("offboard.applying_offboard") + "\n");
15800
16213
  for (const item of files) {
15801
16214
  try {
15802
- rmSync10(join33(projectDir, item), { force: true });
15803
- if (!existsSync38(join33(projectDir, item)))
16215
+ rmSync10(join36(projectDir, item), { force: true });
16216
+ if (!existsSync40(join36(projectDir, item)))
15804
16217
  process.stdout.write(` removed file ${item}
15805
16218
  `);
15806
16219
  } catch {
@@ -15808,26 +16221,26 @@ function offboardCommand(args) {
15808
16221
  }
15809
16222
  for (const item of dirs) {
15810
16223
  try {
15811
- rmSync10(join33(projectDir, item), { recursive: true, force: true });
16224
+ rmSync10(join36(projectDir, item), { recursive: true, force: true });
15812
16225
  process.stdout.write(` removed dir ${item}
15813
16226
  `);
15814
16227
  } catch {
15815
16228
  }
15816
16229
  }
15817
16230
  for (const item of giEntries) {
15818
- const gi = join33(projectDir, ".gitignore");
15819
- if (existsSync38(gi)) {
15820
- const lines = readFileSync30(gi, "utf8").split("\n");
16231
+ const gi = join36(projectDir, ".gitignore");
16232
+ if (existsSync40(gi)) {
16233
+ const lines = readFileSync31(gi, "utf8").split("\n");
15821
16234
  if (lines.includes(item)) {
15822
16235
  const kept = lines.filter((l) => l !== item);
15823
- writeFileSync18(gi, kept.join("\n"));
16236
+ writeFileSync22(gi, kept.join("\n"));
15824
16237
  process.stdout.write(` .gitignore - ${item}
15825
16238
  `);
15826
16239
  }
15827
16240
  }
15828
16241
  }
15829
16242
  for (const item of plists) {
15830
- const plistPath = join33(homedir15(), "Library", "LaunchAgents", item);
16243
+ const plistPath = join36(homedir15(), "Library", "LaunchAgents", item);
15831
16244
  const r = runLaunchctl(["unload", "-w", plistPath]);
15832
16245
  if (r === 0)
15833
16246
  process.stdout.write(` unloaded ${item}
@@ -15840,16 +16253,16 @@ function offboardCommand(args) {
15840
16253
  }
15841
16254
 
15842
16255
  // packages/cli/dist/commands/prices.js
15843
- import { existsSync as existsSync39, readdirSync as readdirSync15, readFileSync as readFileSync31 } from "node:fs";
15844
- import { join as join34 } from "node:path";
16256
+ import { existsSync as existsSync41, readdirSync as readdirSync16, readFileSync as readFileSync32 } from "node:fs";
16257
+ import { join as join37 } from "node:path";
15845
16258
  function loadSnapshots() {
15846
- const dir = join34(repoRoot(), "lib", "prices");
15847
- if (!existsSync39(dir)) {
16259
+ const dir = join37(repoRoot(), "lib", "prices");
16260
+ if (!existsSync41(dir)) {
15848
16261
  throw new Error(`no price snapshots found in ${dir}; run \`roll prices refresh\``);
15849
16262
  }
15850
- const files = readdirSync15(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
16263
+ const files = readdirSync16(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
15851
16264
  return files.map((name) => {
15852
- const data = JSON.parse(readFileSync31(join34(dir, name), "utf8"));
16265
+ const data = JSON.parse(readFileSync32(join37(dir, name), "utf8"));
15853
16266
  for (const key of ["version", "effective_at", "source_url", "prices"]) {
15854
16267
  if (data[key] === void 0)
15855
16268
  throw new Error(`snapshot ${name} missing required key ${key}`);
@@ -15942,8 +16355,8 @@ function pricesCommand(args) {
15942
16355
  }
15943
16356
 
15944
16357
  // packages/cli/dist/commands/release.js
15945
- import { existsSync as existsSync40, readFileSync as readFileSync32 } from "node:fs";
15946
- import { join as join35 } from "node:path";
16358
+ import { existsSync as existsSync42, readFileSync as readFileSync33 } from "node:fs";
16359
+ import { join as join38 } from "node:path";
15947
16360
  function label3(lang4, key, ...args) {
15948
16361
  if (v3Catalog[key] !== void 0)
15949
16362
  return t(v3Catalog, lang4, key, ...args);
@@ -15954,17 +16367,17 @@ function dateOf(d) {
15954
16367
  }
15955
16368
  function currentVersion(cwd) {
15956
16369
  try {
15957
- const pkg = JSON.parse(readFileSync32(join35(cwd, "package.json"), "utf8"));
16370
+ const pkg = JSON.parse(readFileSync33(join38(cwd, "package.json"), "utf8"));
15958
16371
  return typeof pkg.version === "string" ? pkg.version : "";
15959
16372
  } catch {
15960
16373
  return "";
15961
16374
  }
15962
16375
  }
15963
16376
  function changelogReady(cwd) {
15964
- const path = join35(cwd, "CHANGELOG.md");
15965
- if (!existsSync40(path))
16377
+ const path = join38(cwd, "CHANGELOG.md");
16378
+ if (!existsSync42(path))
15966
16379
  return false;
15967
- const text = readFileSync32(path, "utf8");
16380
+ const text = readFileSync33(path, "utf8");
15968
16381
  const idx = text.indexOf("## Unreleased");
15969
16382
  if (idx === -1)
15970
16383
  return false;
@@ -16031,8 +16444,8 @@ function releaseCommand(args, now) {
16031
16444
 
16032
16445
  // packages/cli/dist/commands/setup.js
16033
16446
  import { spawnSync as spawnSync9 } from "node:child_process";
16034
- import { existsSync as existsSync41, lstatSync as lstatSync3, mkdirSync as mkdirSync20, readFileSync as readFileSync33, readdirSync as readdirSync16, readlinkSync as readlinkSync3, statSync as statSync16 } from "node:fs";
16035
- import { join as join36 } from "node:path";
16447
+ import { existsSync as existsSync43, lstatSync as lstatSync3, mkdirSync as mkdirSync22, readFileSync as readFileSync34, readdirSync as readdirSync17, readlinkSync as readlinkSync4, statSync as statSync18 } from "node:fs";
16448
+ import { join as join39 } from "node:path";
16036
16449
  function err13(line) {
16037
16450
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
16038
16451
  const RED = noColor2 ? "" : "\x1B[0;31m";
@@ -16057,7 +16470,7 @@ function setupSnapshot(watch) {
16057
16470
  continue;
16058
16471
  let isDir4 = false;
16059
16472
  try {
16060
- isDir4 = statSync16(d).isDirectory();
16473
+ isDir4 = statSync18(d).isDirectory();
16061
16474
  } catch {
16062
16475
  isDir4 = false;
16063
16476
  }
@@ -16070,12 +16483,12 @@ function setupSnapshot(watch) {
16070
16483
  function walk(dir, lines) {
16071
16484
  let names;
16072
16485
  try {
16073
- names = readdirSync16(dir);
16486
+ names = readdirSync17(dir);
16074
16487
  } catch {
16075
16488
  return;
16076
16489
  }
16077
16490
  for (const name of names) {
16078
- const p = join36(dir, name);
16491
+ const p = join39(dir, name);
16079
16492
  let isLink = false;
16080
16493
  try {
16081
16494
  isLink = lstatSync3(p).isSymbolicLink();
@@ -16084,14 +16497,14 @@ function walk(dir, lines) {
16084
16497
  }
16085
16498
  if (isLink) {
16086
16499
  try {
16087
- lines.push(`L ${p} -> ${readlinkSync3(p)}`);
16500
+ lines.push(`L ${p} -> ${readlinkSync4(p)}`);
16088
16501
  } catch {
16089
16502
  }
16090
16503
  continue;
16091
16504
  }
16092
16505
  let st;
16093
16506
  try {
16094
- st = statSync16(p);
16507
+ st = statSync18(p);
16095
16508
  } catch {
16096
16509
  continue;
16097
16510
  }
@@ -16105,7 +16518,7 @@ function walk(dir, lines) {
16105
16518
  }
16106
16519
  function fileFingerprint(p) {
16107
16520
  try {
16108
- const buf = readFileSync33(p);
16521
+ const buf = readFileSync34(p);
16109
16522
  let sum = 0;
16110
16523
  for (let i = 0; i < buf.length; i++)
16111
16524
  sum = sum * 31 + (buf[i] ?? 0) >>> 0;
@@ -16135,21 +16548,21 @@ function ensureHooksPath(repoPath) {
16135
16548
  }
16136
16549
  }
16137
16550
  function peerEnsureStateDir() {
16138
- const base = join36(rollHome(), ".peer-state");
16139
- mkdirSync20(base, { recursive: true });
16140
- mkdirSync20(join36(base, "logs"), { recursive: true });
16551
+ const base = join39(rollHome(), ".peer-state");
16552
+ mkdirSync22(base, { recursive: true });
16553
+ mkdirSync22(join39(base, "logs"), { recursive: true });
16141
16554
  }
16142
16555
  function tmuxPresent() {
16143
16556
  return onPath("tmux");
16144
16557
  }
16145
16558
  function submoduleGuard() {
16146
16559
  const pkg = rollPkgDir();
16147
- const skills = join36(pkg, "skills");
16148
- const hasGit = existsSync41(join36(pkg, ".git"));
16149
- const hasMods = existsSync41(join36(pkg, ".gitmodules"));
16560
+ const skills = join39(pkg, "skills");
16561
+ const hasGit = existsSync43(join39(pkg, ".git"));
16562
+ const hasMods = existsSync43(join39(pkg, ".gitmodules"));
16150
16563
  let empty = true;
16151
16564
  try {
16152
- empty = readdirSync16(skills).length === 0;
16565
+ empty = readdirSync17(skills).length === 0;
16153
16566
  } catch {
16154
16567
  empty = true;
16155
16568
  }
@@ -16239,7 +16652,7 @@ function setupCommand(args) {
16239
16652
  return 1;
16240
16653
  }
16241
16654
  }
16242
- if (!existsSync41(rollPkgConventions()))
16655
+ if (!existsSync43(rollPkgConventions()))
16243
16656
  return null;
16244
16657
  submoduleGuard();
16245
16658
  const home = rollHome();
@@ -16258,7 +16671,7 @@ function setupCommand(args) {
16258
16671
  ".qwen"
16259
16672
  ];
16260
16673
  const homeDir = process.env["HOME"] ?? "";
16261
- const aiDirs = aiDirsList.map((d) => join36(homeDir, d)).join(":");
16674
+ const aiDirs = aiDirsList.map((d) => join39(homeDir, d)).join(":");
16262
16675
  const steps = [];
16263
16676
  const s1 = runSetupStep(home, () => {
16264
16677
  installLocal(force);
@@ -16272,7 +16685,7 @@ function setupCommand(args) {
16272
16685
  syncSkills(force);
16273
16686
  });
16274
16687
  steps.push({ num: 3, label: "Install skills to ~/.claude", status: stateToMarker(s3, force) });
16275
- const s4 = runSetupStep(join36(home, ".peer-state"), () => {
16688
+ const s4 = runSetupStep(join39(home, ".peer-state"), () => {
16276
16689
  peerEnsureStateDir();
16277
16690
  });
16278
16691
  steps.push({ num: 4, label: "Initialize peer-review state directory", status: stateToMarker(s4, force) });
@@ -16293,12 +16706,12 @@ function setupCommand(args) {
16293
16706
 
16294
16707
  // packages/cli/dist/commands/slides/index.js
16295
16708
  import { spawnSync as spawnSync10 } from "node:child_process";
16296
- import { mkdirSync as mkdirSync21, readFileSync as readFileSync36, readdirSync as readdirSync18, rmSync as rmSync11, statSync as statSync19, writeFileSync as writeFileSync19 } from "node:fs";
16297
- import { join as join38 } from "node:path";
16709
+ import { mkdirSync as mkdirSync23, readFileSync as readFileSync37, readdirSync as readdirSync19, rmSync as rmSync11, statSync as statSync21, writeFileSync as writeFileSync23 } from "node:fs";
16710
+ import { join as join41 } from "node:path";
16298
16711
 
16299
16712
  // packages/cli/dist/commands/slides/render.js
16300
- import { readFileSync as readFileSync34, existsSync as existsSync42, readdirSync as readdirSync17, statSync as statSync17 } from "node:fs";
16301
- import { join as join37, dirname as dirname19, basename as basename9 } from "node:path";
16713
+ import { readFileSync as readFileSync35, existsSync as existsSync44, readdirSync as readdirSync18, statSync as statSync19 } from "node:fs";
16714
+ import { join as join40, dirname as dirname19, basename as basename9 } from "node:path";
16302
16715
  function coerceScalar(v) {
16303
16716
  if (v.length >= 2 && v[0] === v[v.length - 1] && (v[0] === "'" || v[0] === '"')) {
16304
16717
  return v.slice(1, -1);
@@ -16504,8 +16917,8 @@ function parseSequence(block, baseIndent) {
16504
16917
  i += 1;
16505
16918
  continue;
16506
16919
  }
16507
- const bi = indentOf2(bl);
16508
- if (bi <= baseIndent)
16920
+ const bi2 = indentOf2(bl);
16921
+ if (bi2 <= baseIndent)
16509
16922
  break;
16510
16923
  sub.push(bl);
16511
16924
  i += 1;
@@ -16783,12 +17196,12 @@ var DEFAULT_LAYOUT = "plain";
16783
17196
  var LayoutResolver = class {
16784
17197
  componentsDir;
16785
17198
  constructor(componentsDir) {
16786
- this.componentsDir = componentsDir ?? join37(libDirFor(import.meta.url), "slides", "components");
17199
+ this.componentsDir = componentsDir ?? join40(libDirFor(import.meta.url), "slides", "components");
16787
17200
  }
16788
17201
  available() {
16789
17202
  if (!isDir2(this.componentsDir))
16790
17203
  return [];
16791
- const names = readdirSync17(this.componentsDir).filter((f) => f.endsWith(".html")).map((f) => basename9(f, ".html"));
17204
+ const names = readdirSync18(this.componentsDir).filter((f) => f.endsWith(".html")).map((f) => basename9(f, ".html"));
16792
17205
  const rest = names.filter((n) => n !== DEFAULT_LAYOUT).sort();
16793
17206
  const head = names.includes(DEFAULT_LAYOUT) ? [DEFAULT_LAYOUT] : [];
16794
17207
  return [...head, ...rest];
@@ -16797,7 +17210,7 @@ var LayoutResolver = class {
16797
17210
  if (!/^[a-z0-9-]+$/.test(layout)) {
16798
17211
  throw new ValueError(`Unknown layout: ${layout}; available: ${this.available().join(", ")}`);
16799
17212
  }
16800
- const path = join37(this.componentsDir, `${layout}.html`);
17213
+ const path = join40(this.componentsDir, `${layout}.html`);
16801
17214
  if (!isFile(path)) {
16802
17215
  throw new ValueError(`Unknown layout: ${layout}; available: ${this.available().join(", ")}`);
16803
17216
  }
@@ -16807,7 +17220,7 @@ var LayoutResolver = class {
16807
17220
  function renderSlideInner(slide, resolver) {
16808
17221
  const layout = typeof slide["layout"] === "string" && slide["layout"] || DEFAULT_LAYOUT;
16809
17222
  const partialPath = resolver.resolve(String(layout));
16810
- let partial = readFileSync34(partialPath, "utf8");
17223
+ let partial = readFileSync35(partialPath, "utf8");
16811
17224
  partial = partial.replace(/^\s*<!--[\s\S]*?-->(?:\s*\n)?/, "");
16812
17225
  const rendered = mustache(partial, slide);
16813
17226
  return stripNewlines(rendered);
@@ -16855,28 +17268,28 @@ function pyRepr(s) {
16855
17268
  }
16856
17269
  function isDir2(p) {
16857
17270
  try {
16858
- return statSync17(p).isDirectory();
17271
+ return statSync19(p).isDirectory();
16859
17272
  } catch {
16860
17273
  return false;
16861
17274
  }
16862
17275
  }
16863
17276
  function isFile(p) {
16864
17277
  try {
16865
- return statSync17(p).isFile();
17278
+ return statSync19(p).isFile();
16866
17279
  } catch {
16867
17280
  return false;
16868
17281
  }
16869
17282
  }
16870
17283
  function libDirFor(_metaUrl) {
16871
17284
  void _metaUrl;
16872
- void existsSync42;
17285
+ void existsSync44;
16873
17286
  void dirname19;
16874
- return join37(repoLibFallback(), "lib");
17287
+ return join40(repoLibFallback(), "lib");
16875
17288
  }
16876
17289
  function repoLibFallback() {
16877
17290
  let dir = process.cwd();
16878
17291
  for (let i = 0; i < 12; i++) {
16879
- if (existsSync42(join37(dir, "bin", "roll")))
17292
+ if (existsSync44(join40(dir, "bin", "roll")))
16880
17293
  return dir;
16881
17294
  const parent = dirname19(dir);
16882
17295
  if (parent === dir)
@@ -16887,7 +17300,7 @@ function repoLibFallback() {
16887
17300
  }
16888
17301
 
16889
17302
  // packages/cli/dist/commands/slides/validate.js
16890
- import { readFileSync as readFileSync35, statSync as statSync18 } from "node:fs";
17303
+ import { readFileSync as readFileSync36, statSync as statSync20 } from "node:fs";
16891
17304
  var REQUIRED_FRONTMATTER = [
16892
17305
  "template",
16893
17306
  "slug",
@@ -17091,7 +17504,7 @@ function validateDeckFile(path, errSink, componentsDir) {
17091
17504
  let fm;
17092
17505
  let slides;
17093
17506
  try {
17094
- src = readFileSync35(path, "utf8");
17507
+ src = readFileSync36(path, "utf8");
17095
17508
  [fm] = parseFrontmatter(src);
17096
17509
  const [, body] = parseFrontmatter(src);
17097
17510
  slides = parseSlides(body);
@@ -17125,7 +17538,7 @@ function validateDeckFile(path, errSink, componentsDir) {
17125
17538
  }
17126
17539
  function isFile2(p) {
17127
17540
  try {
17128
- return statSync18(p).isFile();
17541
+ return statSync20(p).isFile();
17129
17542
  } catch {
17130
17543
  return false;
17131
17544
  }
@@ -17204,13 +17617,13 @@ function slidesHelp(out2) {
17204
17617
  out2(SLIDES_HELP);
17205
17618
  }
17206
17619
  function slidesLib() {
17207
- return join38(rollPkgDir(), "lib");
17620
+ return join41(rollPkgDir(), "lib");
17208
17621
  }
17209
17622
  function slidesTemplatePath(name) {
17210
- const projTpl = join38(".roll", "slides", "templates", `${name}.html`);
17623
+ const projTpl = join41(".roll", "slides", "templates", `${name}.html`);
17211
17624
  if (isFile3(projTpl))
17212
17625
  return projTpl;
17213
- const tpl = join38(rollPkgDir(), "lib", "slides", "templates", `${name}.html`);
17626
+ const tpl = join41(rollPkgDir(), "lib", "slides", "templates", `${name}.html`);
17214
17627
  if (isFile3(tpl))
17215
17628
  return tpl;
17216
17629
  return null;
@@ -17218,7 +17631,7 @@ function slidesTemplatePath(name) {
17218
17631
  function slidesTemplateForDeck(deckPath) {
17219
17632
  let tpl = "";
17220
17633
  try {
17221
- const src = readFileSync36(deckPath, "utf8");
17634
+ const src = readFileSync37(deckPath, "utf8");
17222
17635
  let d = 0;
17223
17636
  for (const line of src.split("\n")) {
17224
17637
  if (/^---[ \t]*$/.test(line)) {
@@ -17244,21 +17657,21 @@ function slidesTemplateForDeck(deckPath) {
17244
17657
  return tpl;
17245
17658
  }
17246
17659
  function slidesEnsureGitignore() {
17247
- const gi = join38(".roll", ".gitignore");
17248
- mkdirSync21(".roll", { recursive: true });
17660
+ const gi = join41(".roll", ".gitignore");
17661
+ mkdirSync23(".roll", { recursive: true });
17249
17662
  if (isFile3(gi)) {
17250
- const content = readFileSync36(gi, "utf8");
17663
+ const content = readFileSync37(gi, "utf8");
17251
17664
  if (content.split("\n").some((l) => /^slides\/\*\.html$/.test(l)))
17252
17665
  return;
17253
17666
  if (content.length > 0 && !content.endsWith("\n")) {
17254
- writeFileSync19(gi, content + "\n");
17667
+ writeFileSync23(gi, content + "\n");
17255
17668
  }
17256
17669
  }
17257
17670
  appendFile(gi, "slides/*.html\n");
17258
17671
  }
17259
17672
  function appendFile(path, data) {
17260
- const prev = isFile3(path) ? readFileSync36(path, "utf8") : "";
17261
- writeFileSync19(path, prev + data);
17673
+ const prev = isFile3(path) ? readFileSync37(path, "utf8") : "";
17674
+ writeFileSync23(path, prev + data);
17262
17675
  }
17263
17676
  function slidesOpenCmd() {
17264
17677
  const sys = spawnSync10("uname", ["-s"], { encoding: "utf8" });
@@ -17317,7 +17730,7 @@ function cmdBuild(args) {
17317
17730
  process.stderr.write(m5("slides_build.usage_roll_slides_build_slug_no") + "\n");
17318
17731
  return 1;
17319
17732
  }
17320
- const deck = join38(".roll", "slides", slug, "deck.md");
17733
+ const deck = join41(".roll", "slides", slug, "deck.md");
17321
17734
  if (!isFile3(deck)) {
17322
17735
  err15(`Deck not found: ${deck}`);
17323
17736
  process.stderr.write(m5("slides_build.en_deck", deck) + "\n");
@@ -17327,14 +17740,14 @@ function cmdBuild(args) {
17327
17740
  return 1;
17328
17741
  }
17329
17742
  const libDir = slidesLib();
17330
- const validator = join38(libDir, "slides-validate.py");
17331
- const renderer = join38(libDir, "slides-render.py");
17743
+ const validator = join41(libDir, "slides-validate.py");
17744
+ const renderer = join41(libDir, "slides-render.py");
17332
17745
  if (!isFile3(validator) || !isFile3(renderer)) {
17333
17746
  err15(m5("slides_build.slides_toolchain_missing_re_run_roll"));
17334
17747
  return 1;
17335
17748
  }
17336
- const errFile = join38(".roll", "slides", slug, ".last-build.err");
17337
- const componentsDir = join38(libDir, "slides", "components");
17749
+ const errFile = join41(".roll", "slides", slug, ".last-build.err");
17750
+ const componentsDir = join41(libDir, "slides", "components");
17338
17751
  const valLines = [];
17339
17752
  const valExit = validateDeckFile(deck, (l) => valLines.push(l), componentsDir);
17340
17753
  const valOut = valLines.join("\n");
@@ -17343,8 +17756,8 @@ function cmdBuild(args) {
17343
17756
  `);
17344
17757
  } else if (valExit !== 0) {
17345
17758
  const ts = utcTs();
17346
- mkdirSync21(join38(".roll", "slides", slug), { recursive: true });
17347
- writeFileSync19(errFile, `[${ts}] stage=validate
17759
+ mkdirSync23(join41(".roll", "slides", slug), { recursive: true });
17760
+ writeFileSync23(errFile, `[${ts}] stage=validate
17348
17761
  ${valOut}
17349
17762
  `);
17350
17763
  if (valOut !== "")
@@ -17361,15 +17774,15 @@ ${valOut}
17361
17774
  const tplPath = slidesTemplatePath(tplName);
17362
17775
  if (tplPath === null) {
17363
17776
  const ts = utcTs();
17364
- mkdirSync21(join38(".roll", "slides", slug), { recursive: true });
17365
- writeFileSync19(errFile, `[${ts}] stage=template
17777
+ mkdirSync23(join41(".roll", "slides", slug), { recursive: true });
17778
+ writeFileSync23(errFile, `[${ts}] stage=template
17366
17779
  template not found: ${tplName}
17367
17780
  `);
17368
17781
  const { RED, NC } = pal4();
17369
17782
  process.stderr.write(`${RED}[FAIL]${NC} ${m5("slides_build.template_not_found", tplName)}
17370
17783
  `);
17371
17784
  process.stderr.write(" " + m5("slides_build.available_templates") + "\n");
17372
- const builtinDir = join38(rollPkgDir(), "lib", "slides", "templates");
17785
+ const builtinDir = join41(rollPkgDir(), "lib", "slides", "templates");
17373
17786
  if (isDir3(builtinDir)) {
17374
17787
  for (const t2 of listHtml(builtinDir)) {
17375
17788
  const n = basenameNoHtml(t2);
@@ -17377,7 +17790,7 @@ template not found: ${tplName}
17377
17790
  `);
17378
17791
  }
17379
17792
  }
17380
- const projDir = join38(".roll", "slides", "templates");
17793
+ const projDir = join41(".roll", "slides", "templates");
17381
17794
  if (isDir3(projDir)) {
17382
17795
  for (const t2 of listHtml(projDir)) {
17383
17796
  const n = basenameNoHtml(t2);
@@ -17388,18 +17801,18 @@ template not found: ${tplName}
17388
17801
  process.stderr.write(" " + m5("slides_build.templates_list_hint") + "\n");
17389
17802
  return 1;
17390
17803
  }
17391
- const out2 = join38(".roll", "slides", `${slug}.html`);
17392
- mkdirSync21(join38(".roll", "slides"), { recursive: true });
17804
+ const out2 = join41(".roll", "slides", `${slug}.html`);
17805
+ mkdirSync23(join41(".roll", "slides"), { recursive: true });
17393
17806
  let htmlOut;
17394
17807
  try {
17395
- const src = readFileSync36(deck, "utf8");
17396
- const template = readFileSync36(tplPath, "utf8");
17808
+ const src = readFileSync37(deck, "utf8");
17809
+ const template = readFileSync37(tplPath, "utf8");
17397
17810
  htmlOut = renderDeck(src, template, { componentsDir });
17398
17811
  } catch (e) {
17399
17812
  const renderOut = renderErrorBlock(e);
17400
17813
  const ts = utcTs();
17401
- mkdirSync21(join38(".roll", "slides", slug), { recursive: true });
17402
- writeFileSync19(errFile, `[${ts}] stage=render
17814
+ mkdirSync23(join41(".roll", "slides", slug), { recursive: true });
17815
+ writeFileSync23(errFile, `[${ts}] stage=render
17403
17816
  ${renderOut}
17404
17817
  `);
17405
17818
  const { RED, NC } = pal4();
@@ -17413,7 +17826,7 @@ ${renderOut}
17413
17826
  }
17414
17827
  return 1;
17415
17828
  }
17416
- writeFileSync19(out2, htmlOut);
17829
+ writeFileSync23(out2, htmlOut);
17417
17830
  try {
17418
17831
  rmSync11(errFile, { force: true });
17419
17832
  } catch {
@@ -17438,7 +17851,7 @@ function tailLines(s, n) {
17438
17851
  }
17439
17852
  function frontmatterField2(deckPath, field) {
17440
17853
  try {
17441
- const src = readFileSync36(deckPath, "utf8");
17854
+ const src = readFileSync37(deckPath, "utf8");
17442
17855
  let d = 0;
17443
17856
  const pat = new RegExp("^" + field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "[ ]*:[ ]*");
17444
17857
  for (const line of src.split("\n")) {
@@ -17481,7 +17894,7 @@ function cmdList(args) {
17481
17894
  err15(m5("slides_list.unexpected_argument_1"));
17482
17895
  return 1;
17483
17896
  }
17484
- const slidesDir = join38(".roll", "slides");
17897
+ const slidesDir = join41(".roll", "slides");
17485
17898
  if (!isDir3(slidesDir)) {
17486
17899
  info5(m5("slides_list.no_decks_found_under_roll_slides"));
17487
17900
  process.stdout.write(` Hint: run 'roll slides new "<topic>"' to create one.
@@ -17490,9 +17903,9 @@ function cmdList(args) {
17490
17903
  return 0;
17491
17904
  }
17492
17905
  const slugs = [];
17493
- for (const entry of readdirSync18(slidesDir)) {
17494
- const d = join38(slidesDir, entry);
17495
- if (isDir3(d) && isFile3(join38(d, "deck.md")))
17906
+ for (const entry of readdirSync19(slidesDir)) {
17907
+ const d = join41(slidesDir, entry);
17908
+ if (isDir3(d) && isFile3(join41(d, "deck.md")))
17496
17909
  slugs.push(entry);
17497
17910
  }
17498
17911
  if (slugs.length === 0) {
@@ -17506,9 +17919,9 @@ function cmdList(args) {
17506
17919
  process.stdout.write(rowSix("slug", "template", "total_slides", "created", "built", "size") + "\n");
17507
17920
  process.stdout.write(rowSix("----", "--------", "------------", "-------", "------", "----") + "\n");
17508
17921
  for (const s of sorted) {
17509
- const deck = join38(slidesDir, s, "deck.md");
17510
- const html = join38(slidesDir, `${s}.html`);
17511
- const errFile = join38(slidesDir, s, ".last-build.err");
17922
+ const deck = join41(slidesDir, s, "deck.md");
17923
+ const html = join41(slidesDir, `${s}.html`);
17924
+ const errFile = join41(slidesDir, s, ".last-build.err");
17512
17925
  let template = frontmatterField2(deck, "template");
17513
17926
  if (template === "")
17514
17927
  template = "-";
@@ -17531,7 +17944,7 @@ function cmdList(args) {
17531
17944
  built = "\u2713 built";
17532
17945
  let bytes = 0;
17533
17946
  try {
17534
- bytes = statSync19(html).size;
17947
+ bytes = statSync21(html).size;
17535
17948
  } catch {
17536
17949
  bytes = 0;
17537
17950
  }
@@ -17547,7 +17960,7 @@ function cmdList(args) {
17547
17960
  }
17548
17961
  function isNewer(a, b) {
17549
17962
  try {
17550
- return statSync19(a).mtimeMs > statSync19(b).mtimeMs;
17963
+ return statSync21(a).mtimeMs > statSync21(b).mtimeMs;
17551
17964
  } catch {
17552
17965
  return false;
17553
17966
  }
@@ -17582,7 +17995,7 @@ function cmdPreview(args) {
17582
17995
  process.stderr.write(m5("slides_preview.usage_roll_slides_preview_slug_no") + "\n");
17583
17996
  return 1;
17584
17997
  }
17585
- const html = join38(".roll", "slides", `${slug}.html`);
17998
+ const html = join41(".roll", "slides", `${slug}.html`);
17586
17999
  if (!isFile3(html)) {
17587
18000
  err15(`Rendered HTML not found: ${html}`);
17588
18001
  process.stderr.write(m5("slides_preview.en_html", html) + "\n");
@@ -17623,9 +18036,9 @@ function cmdLogs(args) {
17623
18036
  process.stderr.write(m5("slides_logs.usage_roll_slides_logs_slug") + "\n");
17624
18037
  return 1;
17625
18038
  }
17626
- const deckDir = join38(".roll", "slides", slug);
17627
- const errFile = join38(deckDir, ".last-build.err");
17628
- if (!isDir3(deckDir) || !isFile3(join38(deckDir, "deck.md"))) {
18039
+ const deckDir = join41(".roll", "slides", slug);
18040
+ const errFile = join41(deckDir, ".last-build.err");
18041
+ if (!isDir3(deckDir) || !isFile3(join41(deckDir, "deck.md"))) {
17629
18042
  err15(m5("slides_logs.deck_not_found", slug));
17630
18043
  return 1;
17631
18044
  }
@@ -17633,7 +18046,7 @@ function cmdLogs(args) {
17633
18046
  info5(m5("slides_logs.no_failure_records_for", slug));
17634
18047
  return 0;
17635
18048
  }
17636
- process.stdout.write(readFileSync36(errFile, "utf8"));
18049
+ process.stdout.write(readFileSync37(errFile, "utf8"));
17637
18050
  return 0;
17638
18051
  }
17639
18052
  function cmdDelete(args) {
@@ -17666,9 +18079,9 @@ function cmdDelete(args) {
17666
18079
  process.stderr.write(m5("slides_delete.usage_roll_slides_delete_slug_force") + "\n");
17667
18080
  return 1;
17668
18081
  }
17669
- const deckDir = join38(".roll", "slides", slug);
17670
- const html = join38(".roll", "slides", `${slug}.html`);
17671
- if (!isDir3(deckDir) || !isFile3(join38(deckDir, "deck.md"))) {
18082
+ const deckDir = join41(".roll", "slides", slug);
18083
+ const html = join41(".roll", "slides", `${slug}.html`);
18084
+ if (!isDir3(deckDir) || !isFile3(join41(deckDir, "deck.md"))) {
17672
18085
  err15(m5("slides_delete.deck_not_found", slug));
17673
18086
  return 1;
17674
18087
  }
@@ -17707,7 +18120,7 @@ function cmdTemplates(args) {
17707
18120
  let found = false;
17708
18121
  process.stdout.write(rowThree("name", "source", "path") + "\n");
17709
18122
  process.stdout.write(rowThree("----", "------", "----") + "\n");
17710
- const builtinDir = join38(rollPkgDir(), "lib", "slides", "templates");
18123
+ const builtinDir = join41(rollPkgDir(), "lib", "slides", "templates");
17711
18124
  if (isDir3(builtinDir)) {
17712
18125
  for (const tpl of listHtml(builtinDir)) {
17713
18126
  const name = basenameNoHtml(tpl);
@@ -17715,11 +18128,11 @@ function cmdTemplates(args) {
17715
18128
  found = true;
17716
18129
  }
17717
18130
  }
17718
- const projDir = join38(".roll", "slides", "templates");
18131
+ const projDir = join41(".roll", "slides", "templates");
17719
18132
  if (isDir3(projDir)) {
17720
18133
  for (const tpl of listHtml(projDir)) {
17721
18134
  const name = basenameNoHtml(tpl);
17722
- const source = isFile3(join38(builtinDir, `${name}.html`)) ? "project (override)" : "project";
18135
+ const source = isFile3(join41(builtinDir, `${name}.html`)) ? "project (override)" : "project";
17723
18136
  process.stdout.write(rowThree(name, source, tpl) + "\n");
17724
18137
  found = true;
17725
18138
  }
@@ -17767,20 +18180,20 @@ function slidesCommand(args) {
17767
18180
  }
17768
18181
  function isFile3(p) {
17769
18182
  try {
17770
- return statSync19(p).isFile();
18183
+ return statSync21(p).isFile();
17771
18184
  } catch {
17772
18185
  return false;
17773
18186
  }
17774
18187
  }
17775
18188
  function isDir3(p) {
17776
18189
  try {
17777
- return statSync19(p).isDirectory();
18190
+ return statSync21(p).isDirectory();
17778
18191
  } catch {
17779
18192
  return false;
17780
18193
  }
17781
18194
  }
17782
18195
  function listHtml(dir) {
17783
- return readdirSync18(dir).filter((f) => f.endsWith(".html")).sort().map((f) => join38(dir, f));
18196
+ return readdirSync19(dir).filter((f) => f.endsWith(".html")).sort().map((f) => join41(dir, f));
17784
18197
  }
17785
18198
  function basenameNoHtml(path) {
17786
18199
  const b = path.slice(path.lastIndexOf("/") + 1);
@@ -17800,15 +18213,15 @@ function rowThree(a, b, c2) {
17800
18213
  // packages/cli/dist/commands/status.js
17801
18214
  import { execFileSync as execFileSync8 } from "node:child_process";
17802
18215
  import { createHash as createHash5 } from "node:crypto";
17803
- import { existsSync as existsSync43, lstatSync as lstatSync4, readdirSync as readdirSync19, readFileSync as readFileSync37, realpathSync as realpathSync6, statSync as statSync20 } from "node:fs";
18216
+ import { existsSync as existsSync45, lstatSync as lstatSync4, readdirSync as readdirSync20, readFileSync as readFileSync38, realpathSync as realpathSync6, statSync as statSync22 } from "node:fs";
17804
18217
  import { homedir as homedir16 } from "node:os";
17805
- import { basename as basename10, dirname as dirname20, join as join39 } from "node:path";
18218
+ import { basename as basename10, dirname as dirname20, join as join42 } from "node:path";
17806
18219
  function rollHome3() {
17807
- return process.env["ROLL_HOME"] ?? join39(homedir16(), ".roll");
18220
+ return process.env["ROLL_HOME"] ?? join42(homedir16(), ".roll");
17808
18221
  }
17809
- var globalDir = () => join39(rollHome3(), "conventions", "global");
17810
- var templatesDir = () => join39(rollHome3(), "conventions", "templates");
17811
- var configPath = () => join39(rollHome3(), "config.yaml");
18222
+ var globalDir = () => join42(rollHome3(), "conventions", "global");
18223
+ var templatesDir = () => join42(rollHome3(), "conventions", "templates");
18224
+ var configPath = () => join42(rollHome3(), "config.yaml");
17812
18225
  function projectSlugPy() {
17813
18226
  let path = realpathSync6(process.cwd());
17814
18227
  try {
@@ -17827,14 +18240,14 @@ function projectSlugPy() {
17827
18240
  var CONVENTION_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md", ".cursor-rules", "project_rules.md"];
17828
18241
  var TEMPLATES = ["fullstack", "frontend-only", "backend-service", "cli"];
17829
18242
  function globalConventions() {
17830
- return CONVENTION_FILES.map((f) => [f, existsSync43(join39(globalDir(), f))]);
18243
+ return CONVENTION_FILES.map((f) => [f, existsSync45(join42(globalDir(), f))]);
17831
18244
  }
17832
18245
  function parseAiEntries() {
17833
18246
  const cfg = configPath();
17834
- if (!existsSync43(cfg))
18247
+ if (!existsSync45(cfg))
17835
18248
  return [];
17836
18249
  const entries = [];
17837
- for (const line of readFileSync37(cfg, "utf8").split("\n")) {
18250
+ for (const line of readFileSync38(cfg, "utf8").split("\n")) {
17838
18251
  const m7 = /^ai_[a-z]+:\s*(.+)/.exec(line);
17839
18252
  if (m7 === null)
17840
18253
  continue;
@@ -17854,21 +18267,21 @@ function parseAiEntries() {
17854
18267
  return entries;
17855
18268
  }
17856
18269
  function aiSyncStatus(e) {
17857
- const cfgFile = join39(e.ai_dir, e.cfg_file);
17858
- const rollMd = join39(e.ai_dir, "roll.md");
17859
- const src = join39(globalDir(), e.src_file);
17860
- if (!existsSync43(cfgFile))
18270
+ const cfgFile = join42(e.ai_dir, e.cfg_file);
18271
+ const rollMd = join42(e.ai_dir, "roll.md");
18272
+ const src = join42(globalDir(), e.src_file);
18273
+ if (!existsSync45(cfgFile))
17861
18274
  return "missing";
17862
- if (!existsSync43(rollMd))
18275
+ if (!existsSync45(rollMd))
17863
18276
  return "out-of-sync";
17864
18277
  try {
17865
- if (existsSync43(src) && !readFileSync37(rollMd).equals(readFileSync37(src)))
18278
+ if (existsSync45(src) && !readFileSync38(rollMd).equals(readFileSync38(src)))
17866
18279
  return "out-of-sync";
17867
18280
  } catch {
17868
18281
  return "out-of-sync";
17869
18282
  }
17870
18283
  try {
17871
- if (!readFileSync37(cfgFile, "utf8").includes("@roll.md"))
18284
+ if (!readFileSync38(cfgFile, "utf8").includes("@roll.md"))
17872
18285
  return "out-of-sync";
17873
18286
  } catch {
17874
18287
  return "out-of-sync";
@@ -17876,15 +18289,15 @@ function aiSyncStatus(e) {
17876
18289
  return "sync";
17877
18290
  }
17878
18291
  function aiSkillCount(e) {
17879
- const skillsDir2 = join39(e.ai_dir, "skills");
17880
- if (!existsSync43(skillsDir2))
18292
+ const skillsDir2 = join42(e.ai_dir, "skills");
18293
+ if (!existsSync45(skillsDir2))
17881
18294
  return 0;
17882
18295
  try {
17883
18296
  let n = 0;
17884
- for (const name of readdirSync19(skillsDir2)) {
18297
+ for (const name of readdirSync20(skillsDir2)) {
17885
18298
  if (!name.startsWith("roll-"))
17886
18299
  continue;
17887
- const p = join39(skillsDir2, name);
18300
+ const p = join42(skillsDir2, name);
17888
18301
  const st = lstatSync4(p);
17889
18302
  if (st.isSymbolicLink() || st.isDirectory())
17890
18303
  n++;
@@ -17896,9 +18309,9 @@ function aiSkillCount(e) {
17896
18309
  }
17897
18310
  function countFilesRecursive(dir) {
17898
18311
  let n = 0;
17899
- for (const name of readdirSync19(dir)) {
17900
- const p = join39(dir, name);
17901
- const st = statSync20(p);
18312
+ for (const name of readdirSync20(dir)) {
18313
+ const p = join42(dir, name);
18314
+ const st = statSync22(p);
17902
18315
  if (st.isDirectory())
17903
18316
  n += countFilesRecursive(p);
17904
18317
  else if (st.isFile())
@@ -17907,8 +18320,8 @@ function countFilesRecursive(dir) {
17907
18320
  return n;
17908
18321
  }
17909
18322
  function templateCount(tpl) {
17910
- const d = join39(templatesDir(), tpl);
17911
- if (!existsSync43(d))
18323
+ const d = join42(templatesDir(), tpl);
18324
+ if (!existsSync45(d))
17912
18325
  return 0;
17913
18326
  try {
17914
18327
  return countFilesRecursive(d);
@@ -17917,19 +18330,19 @@ function templateCount(tpl) {
17917
18330
  }
17918
18331
  }
17919
18332
  function skillsInstalled() {
17920
- const sd = join39(rollHome3(), "skills");
17921
- if (!existsSync43(sd))
18333
+ const sd = join42(rollHome3(), "skills");
18334
+ if (!existsSync45(sd))
17922
18335
  return 0;
17923
18336
  try {
17924
- return readdirSync19(sd).filter((n) => statSync20(join39(sd, n)).isDirectory()).length;
18337
+ return readdirSync20(sd).filter((n) => statSync22(join42(sd, n)).isDirectory()).length;
17925
18338
  } catch {
17926
18339
  return 0;
17927
18340
  }
17928
18341
  }
17929
18342
  function launchdState(service, slug) {
17930
18343
  const label4 = `com.roll.${service}.${slug}`;
17931
- const plist = join39(homedir16(), "Library", "LaunchAgents", `${label4}.plist`);
17932
- if (!existsSync43(plist))
18344
+ const plist = join42(homedir16(), "Library", "LaunchAgents", `${label4}.plist`);
18345
+ if (!existsSync45(plist))
17933
18346
  return "not-installed";
17934
18347
  try {
17935
18348
  const out2 = execFileSync8("launchctl", ["list", label4], {
@@ -18067,22 +18480,22 @@ function liveData() {
18067
18480
  const aiClients = parseAiEntries().map((e) => ({
18068
18481
  name: e.name,
18069
18482
  cfg_file: e.cfg_file,
18070
- path: join39(e.ai_dir, e.cfg_file).replaceAll(home, "~"),
18483
+ path: join42(e.ai_dir, e.cfg_file).replaceAll(home, "~"),
18071
18484
  sync: aiSyncStatus(e),
18072
18485
  skills: aiSkillCount(e)
18073
18486
  }));
18074
18487
  const featDir = ".roll/features";
18075
18488
  let featCount = 0;
18076
- if (existsSync43(featDir)) {
18077
- featCount = readdirSync19(featDir).filter((n) => n.endsWith(".md")).length;
18489
+ if (existsSync45(featDir)) {
18490
+ featCount = readdirSync20(featDir).filter((n) => n.endsWith(".md")).length;
18078
18491
  }
18079
18492
  return {
18080
18493
  conventions: globalConventions(),
18081
18494
  ai_clients: aiClients,
18082
18495
  templates: TEMPLATES.map((t2) => [t2, templateCount(t2)]),
18083
18496
  skills_installed: skillsInstalled(),
18084
- project_has_agents: existsSync43("AGENTS.md"),
18085
- project_has_backlog: existsSync43(".roll/backlog.md"),
18497
+ project_has_agents: existsSync45("AGENTS.md"),
18498
+ project_has_backlog: existsSync45(".roll/backlog.md"),
18086
18499
  project_features_count: featCount,
18087
18500
  loop_state: launchdState("loop", slug),
18088
18501
  dream_state: launchdState("dream", slug)
@@ -18106,8 +18519,8 @@ function statusCommand(args) {
18106
18519
 
18107
18520
  // packages/cli/dist/commands/test.js
18108
18521
  import { spawnSync as spawnSync11 } from "node:child_process";
18109
- import { existsSync as existsSync44, mkdirSync as mkdirSync22, readFileSync as readFileSync38, rmSync as rmSync12, writeFileSync as writeFileSync20 } from "node:fs";
18110
- import { dirname as dirname21, join as join40 } from "node:path";
18522
+ import { existsSync as existsSync46, mkdirSync as mkdirSync24, readFileSync as readFileSync39, rmSync as rmSync12, writeFileSync as writeFileSync24 } from "node:fs";
18523
+ import { dirname as dirname21, join as join43 } from "node:path";
18111
18524
  function pal5() {
18112
18525
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
18113
18526
  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" };
@@ -18124,12 +18537,12 @@ function err16(line) {
18124
18537
  }
18125
18538
  var SUPPORTED_TYPES = ["none"];
18126
18539
  function isolationGetType() {
18127
- const file = join40(process.cwd(), ".roll", "local.yaml");
18128
- if (!existsSync44(file))
18540
+ const file = join43(process.cwd(), ".roll", "local.yaml");
18541
+ if (!existsSync46(file))
18129
18542
  return "none";
18130
18543
  let text;
18131
18544
  try {
18132
- text = readFileSync38(file, "utf8");
18545
+ text = readFileSync39(file, "utf8");
18133
18546
  } catch {
18134
18547
  return "none";
18135
18548
  }
@@ -18177,14 +18590,14 @@ function resetLockPath() {
18177
18590
  return ".roll/.iso-reset.lock";
18178
18591
  }
18179
18592
  function resetLockHeld() {
18180
- return existsSync44(resetLockPath());
18593
+ return existsSync46(resetLockPath());
18181
18594
  }
18182
18595
  function resetAcquireLock() {
18183
18596
  const lock = resetLockPath();
18184
- if (existsSync44(lock))
18597
+ if (existsSync46(lock))
18185
18598
  return false;
18186
- mkdirSync22(dirname21(lock), { recursive: true });
18187
- writeFileSync20(lock, `${process.pid}
18599
+ mkdirSync24(dirname21(lock), { recursive: true });
18600
+ writeFileSync24(lock, `${process.pid}
18188
18601
  `);
18189
18602
  return true;
18190
18603
  }
@@ -18201,7 +18614,7 @@ function runForward(cmd, argv) {
18201
18614
  }
18202
18615
  function isolationDispatch(method, args) {
18203
18616
  const type = isolationGetType();
18204
- if (type === "none" && !existsSync44(join40(process.cwd(), ".roll", "local.yaml"))) {
18617
+ if (type === "none" && !existsSync46(join43(process.cwd(), ".roll", "local.yaml"))) {
18205
18618
  infoErr("isolation: no test_isolation config, falling back to type=none (host)");
18206
18619
  }
18207
18620
  if (!SUPPORTED_TYPES.includes(type)) {
@@ -18295,9 +18708,9 @@ function testCommand(args) {
18295
18708
 
18296
18709
  // packages/cli/dist/commands/update.js
18297
18710
  import { spawnSync as spawnSync12 } from "node:child_process";
18298
- import { existsSync as existsSync45, mkdtempSync as mkdtempSync4, readFileSync as readFileSync39, rmSync as rmSync13 } from "node:fs";
18711
+ import { existsSync as existsSync47, mkdtempSync as mkdtempSync4, readFileSync as readFileSync40, rmSync as rmSync13 } from "node:fs";
18299
18712
  import { tmpdir as tmpdir4 } from "node:os";
18300
- import { join as join41 } from "node:path";
18713
+ import { join as join44 } from "node:path";
18301
18714
  function pal6() {
18302
18715
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
18303
18716
  return noColor2 ? { CYAN: "", GREEN: "", YELLOW: "", RED: "", BOLD: "", NC: "" } : {
@@ -18361,20 +18774,20 @@ function resolveRemoteVersion() {
18361
18774
  }
18362
18775
  function downloadAndInstallCurl(tag) {
18363
18776
  const url = `https://github.com/seanyao/roll/archive/refs/tags/${tag}.tar.gz`;
18364
- const tmpDir = mkdtempSync4(join41(tmpdir4(), "roll-update-"));
18777
+ const tmpDir = mkdtempSync4(join44(tmpdir4(), "roll-update-"));
18365
18778
  try {
18366
18779
  info6(`[roll] Downloading roll ${tag} ...`);
18367
- const dl = runForward2("curl", ["-fsSL", url, "-o", join41(tmpDir, "roll.tar.gz")]);
18780
+ const dl = runForward2("curl", ["-fsSL", url, "-o", join44(tmpDir, "roll.tar.gz")]);
18368
18781
  if (dl !== 0) {
18369
18782
  err17(m6("update.curl_download_failed"));
18370
18783
  return { ok: false };
18371
18784
  }
18372
18785
  info6("[roll] Extracting ...");
18373
- const extractDir = join41(tmpDir, "extract");
18786
+ const extractDir = join44(tmpDir, "extract");
18374
18787
  spawnSync12("mkdir", ["-p", extractDir], { stdio: "ignore" });
18375
18788
  const ex = runForward2("tar", [
18376
18789
  "-xzf",
18377
- join41(tmpDir, "roll.tar.gz"),
18790
+ join44(tmpDir, "roll.tar.gz"),
18378
18791
  "--strip-components=1",
18379
18792
  "-C",
18380
18793
  extractDir
@@ -18391,7 +18804,7 @@ function downloadAndInstallCurl(tag) {
18391
18804
  function checkInstalledVersionOrRetry() {
18392
18805
  const expected = (spawnSync12("npm", ["view", "@seanyao/roll", "version"], { encoding: "utf8" }).stdout ?? "").trim();
18393
18806
  const pkgRoot = (spawnSync12("npm", ["root", "-g"], { encoding: "utf8" }).stdout ?? "").trim();
18394
- const installedTree = join41(pkgRoot, "@seanyao", "roll");
18807
+ const installedTree = join44(pkgRoot, "@seanyao", "roll");
18395
18808
  const installed = treeVersion(installedTree);
18396
18809
  if (expected === "" || installed === "")
18397
18810
  return;
@@ -18405,18 +18818,18 @@ function checkInstalledVersionOrRetry() {
18405
18818
  }
18406
18819
  }
18407
18820
  function invalidateUpdateCache() {
18408
- rmSync13(join41(rollHome(), ".update-check"), { force: true });
18821
+ rmSync13(join44(rollHome(), ".update-check"), { force: true });
18409
18822
  }
18410
18823
  function showChangelog() {
18411
- const changelog = join41(rollPkgDir(), "CHANGELOG.md");
18412
- if (!existsSync45(changelog))
18824
+ const changelog = join44(rollPkgDir(), "CHANGELOG.md");
18825
+ if (!existsSync47(changelog))
18413
18826
  return;
18414
18827
  const { BOLD: BOLD2, CYAN, NC } = pal6();
18415
18828
  process.stdout.write(`${BOLD2}${m6("changelog.heading")}:${NC}
18416
18829
  `);
18417
18830
  let count = 0;
18418
18831
  let inSection = false;
18419
- for (const line of readFileSync39(changelog, "utf8").split("\n")) {
18832
+ for (const line of readFileSync40(changelog, "utf8").split("\n")) {
18420
18833
  if (/^## /.test(line)) {
18421
18834
  count += 1;
18422
18835
  if (count > 3)
@@ -18436,9 +18849,9 @@ function updateCommand(args) {
18436
18849
  void args;
18437
18850
  info6(m6("update.current_version", rollVersion()));
18438
18851
  let installMethod = "npm";
18439
- const methodFile = join41(rollPkgDir(), ".install-method");
18440
- if (existsSync45(methodFile)) {
18441
- installMethod = readFileSync39(methodFile, "utf8").trim() || "npm";
18852
+ const methodFile = join44(rollPkgDir(), ".install-method");
18853
+ if (existsSync47(methodFile)) {
18854
+ installMethod = readFileSync40(methodFile, "utf8").trim() || "npm";
18442
18855
  }
18443
18856
  if (installMethod === "curl") {
18444
18857
  info6(m6("update.upgrading_via_curl"));
@@ -18528,6 +18941,7 @@ function registerAll() {
18528
18941
  const r = initCommand(args);
18529
18942
  return r ?? fallbackToBash(["init", ...args]).status;
18530
18943
  });
18944
+ registerPorted("migrate-features", migrateFeaturesCommand);
18531
18945
  registerPorted("migrate", migrateCommand);
18532
18946
  registerPorted("offboard", offboardCommand);
18533
18947
  registerPorted("setup", (args) => {