@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/CHANGELOG.md +15 -0
- package/bin/roll +9 -6
- package/dist/roll.mjs +814 -400
- package/package.json +1 -1
- package/skills/roll-build/SKILL.md +5 -5
- package/skills/roll-design/SKILL.md +35 -27
- package/skills/roll-fix/SKILL.md +3 -3
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 \
|
|
5531
|
-
const after = fig(p.after, "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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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
|
|
5598
|
-
const idx = t2.originalPath !== void 0 && t2.originalPath !== "" ? `<p class="orig-path"
|
|
5599
|
-
rows.push(`<details class="transcript"><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
|
|
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
|
|
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
|
|
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
|
|
5628
|
-
<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
|
|
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
|
-
|
|
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
|
-
.
|
|
5654
|
-
|
|
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
|
|
5657
|
-
.s-partial { border-left:4px solid
|
|
5658
|
-
.s-fail { border-left:4px solid
|
|
5659
|
-
.s-missing { border-left:4px solid
|
|
5660
|
-
figure.shot { margin:10px 0; } figure.shot img { max-width:100%; border:1px solid var(--line); border-radius:
|
|
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
|
|
5664
|
-
details.selfscore { margin-top:28px; border:1px solid var(--line); border-radius:
|
|
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:
|
|
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 {
|
|
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;
|
|
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
|
|
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
|
|
5689
|
-
details.transcript { margin-top:8px; border:1px solid var(--line); border-radius:
|
|
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:
|
|
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, """);
|
|
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
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"] ??
|
|
13296
|
+
return process.env["ROLL_HOME"] ?? join27(homedir11(), ".roll");
|
|
13024
13297
|
}
|
|
13025
13298
|
function rollGlobal2() {
|
|
13026
|
-
return
|
|
13299
|
+
return join27(rollHome2(), "conventions", "global");
|
|
13027
13300
|
}
|
|
13028
13301
|
function rollTemplates2() {
|
|
13029
|
-
return
|
|
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 =
|
|
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 (
|
|
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) =>
|
|
13320
|
+
if (["src", "app", "pages", "components"].some((d) => existsSync30(join27(dir, d))))
|
|
13048
13321
|
hasFrontend = true;
|
|
13049
|
-
if (["server", "api", "backend"].some((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) =>
|
|
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 (
|
|
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 (
|
|
13330
|
+
if (existsSync30(join27(dir, "prisma", "schema.prisma")))
|
|
13058
13331
|
hasBackend = true;
|
|
13059
|
-
if (
|
|
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 =
|
|
13080
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
13124
|
-
const dst =
|
|
13125
|
-
if (!
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
13206
|
-
if (!
|
|
13478
|
+
const tplFile = join27(rollTemplates2(), projectType, "CLAUDE.md");
|
|
13479
|
+
if (!existsSync30(tplFile))
|
|
13207
13480
|
return;
|
|
13208
|
-
const claudeDir =
|
|
13209
|
-
const outFile =
|
|
13210
|
-
|
|
13211
|
-
if (!
|
|
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
|
-
|
|
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 (
|
|
13533
|
+
if (existsSync30(path)) {
|
|
13261
13534
|
summary.push("unchanged|.roll/backlog.md");
|
|
13262
13535
|
return;
|
|
13263
13536
|
}
|
|
13264
|
-
|
|
13265
|
-
|
|
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 (
|
|
13542
|
+
if (existsSync30(path) && statSync15(path).isDirectory()) {
|
|
13270
13543
|
summary.push("unchanged|.roll/features/");
|
|
13271
13544
|
return;
|
|
13272
13545
|
}
|
|
13273
|
-
|
|
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 (
|
|
13560
|
+
if (existsSync30(path)) {
|
|
13288
13561
|
summary.push("unchanged|.roll/features.md");
|
|
13289
13562
|
return;
|
|
13290
13563
|
}
|
|
13291
|
-
|
|
13292
|
-
|
|
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 =
|
|
13297
|
-
if (
|
|
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 =
|
|
13302
|
-
if (!
|
|
13574
|
+
const src = join27(rollTemplates2(), "agent-routes", `${templateName}.yaml`);
|
|
13575
|
+
if (!existsSync30(src)) {
|
|
13303
13576
|
return 1;
|
|
13304
13577
|
}
|
|
13305
|
-
|
|
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 =
|
|
13312
|
-
if (
|
|
13584
|
+
const stampPath = join27(projectDir, ".roll", ".version");
|
|
13585
|
+
if (existsSync30(stampPath)) {
|
|
13313
13586
|
summary.push("unchanged|.roll/.version");
|
|
13314
13587
|
return;
|
|
13315
13588
|
}
|
|
13316
|
-
|
|
13589
|
+
mkdirSync14(join27(projectDir, ".roll"), { recursive: true });
|
|
13317
13590
|
const installedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13318
|
-
|
|
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 (!
|
|
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 (
|
|
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(
|
|
13474
|
-
ensureFeaturesDir(
|
|
13475
|
-
writeFeaturesMd(
|
|
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
|
|
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
|
|
13764
|
+
import { dirname as dirname13, join as join28 } from "node:path";
|
|
13492
13765
|
function rollConfigPath4() {
|
|
13493
|
-
const rollHome4 = process.env["ROLL_HOME"] ??
|
|
13494
|
-
return
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
13574
|
-
const existing =
|
|
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 =
|
|
13580
|
-
|
|
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 (!
|
|
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 =
|
|
13594
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
14061
|
+
return join29(runtimeDir(), `ALERT-${projSlug()}.md`);
|
|
13789
14062
|
}
|
|
13790
14063
|
function statePath() {
|
|
13791
|
-
return
|
|
14064
|
+
return join29(runtimeDir(), `state-${projSlug()}.yaml`);
|
|
13792
14065
|
}
|
|
13793
14066
|
function tickPath() {
|
|
13794
|
-
return
|
|
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 =
|
|
13800
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14120
|
+
mkdirSync16(dirname14(file), { recursive: true });
|
|
13848
14121
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
|
|
13849
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
13997
|
-
import { dirname as dirname16, join as
|
|
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
|
|
14002
|
-
import { dirname as dirname15, join as
|
|
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
|
|
14008
|
-
import { join as
|
|
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 =
|
|
14039
|
-
if (!
|
|
14311
|
+
const dir = join30(runtimeDir3, "peer");
|
|
14312
|
+
if (!existsSync33(dir))
|
|
14040
14313
|
return false;
|
|
14041
14314
|
try {
|
|
14042
|
-
return
|
|
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
|
|
14067
|
-
import { join as
|
|
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
|
-
|
|
14071
|
-
|
|
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
|
-
|
|
14077
|
-
|
|
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 (
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
14123
|
-
if (!
|
|
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/
|
|
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 =
|
|
14506
|
+
const livePath = join32(dirname15(ports.paths.eventsPath), "live.log");
|
|
14234
14507
|
try {
|
|
14235
|
-
|
|
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 =
|
|
14254
|
-
|
|
14255
|
-
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
14476
|
-
const dst =
|
|
14477
|
-
if (!
|
|
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 =
|
|
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 =
|
|
14493
|
-
const cur =
|
|
14765
|
+
const exclude = join32(common, "info", "exclude");
|
|
14766
|
+
const cur = existsSync35(exclude) ? readFileSync27(exclude, "utf8") : "";
|
|
14494
14767
|
if (!/^\.roll$/m.test(cur)) {
|
|
14495
|
-
|
|
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 =
|
|
14585
|
-
if (!
|
|
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 =
|
|
14596
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
14802
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 :
|
|
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:
|
|
14902
|
-
runsPath:
|
|
14903
|
-
alertsPath
|
|
14904
|
-
lockPath:
|
|
14905
|
-
heartbeatPath:
|
|
14906
|
-
worktreePath:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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"] ||
|
|
14964
|
-
launchdDir: () =>
|
|
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 =
|
|
14985
|
-
const tail = spawn4("tail", ["-n", "+1", "-F",
|
|
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 (
|
|
15322
|
+
if (existsSync37(lock))
|
|
14998
15323
|
sawLock = true;
|
|
14999
|
-
const done = sawLock ? !
|
|
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
|
-
|
|
15169
|
-
|
|
15493
|
+
mkdirSync19(dirname17(path), { recursive: true });
|
|
15494
|
+
writeFileSync20(path, content, { mode: 493 });
|
|
15170
15495
|
}
|
|
15171
15496
|
function pauseMarkerPath(projectPath, slug) {
|
|
15172
|
-
return
|
|
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
|
-
|
|
15516
|
+
mkdirSync19(ld, { recursive: true });
|
|
15192
15517
|
let period = 30;
|
|
15193
|
-
const localYaml =
|
|
15194
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
15296
|
-
const already =
|
|
15620
|
+
mkdirSync19(dirname17(marker), { recursive: true });
|
|
15621
|
+
const already = existsSync37(marker);
|
|
15297
15622
|
if (!already)
|
|
15298
|
-
|
|
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 =
|
|
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 =
|
|
15653
|
+
const runner = join34(deps.sharedRoot(), "loop", `run-${id.slug}.sh`);
|
|
15329
15654
|
let legacy = false;
|
|
15330
|
-
if (
|
|
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 (!
|
|
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(
|
|
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
|
|
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 (!
|
|
15498
|
-
|
|
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 (
|
|
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 (
|
|
15873
|
+
if (existsSync38(".roll"))
|
|
15549
15874
|
hasNew = true;
|
|
15550
15875
|
for (const move of moves) {
|
|
15551
|
-
if (
|
|
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 (
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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(
|
|
15803
|
-
if (!
|
|
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(
|
|
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 =
|
|
15819
|
-
if (
|
|
15820
|
-
const lines =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
15844
|
-
import { join as
|
|
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 =
|
|
15847
|
-
if (!
|
|
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 =
|
|
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(
|
|
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
|
|
15946
|
-
import { join as
|
|
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(
|
|
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 =
|
|
15965
|
-
if (!
|
|
16377
|
+
const path = join38(cwd, "CHANGELOG.md");
|
|
16378
|
+
if (!existsSync42(path))
|
|
15966
16379
|
return false;
|
|
15967
|
-
const text =
|
|
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
|
|
16035
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
16486
|
+
names = readdirSync17(dir);
|
|
16074
16487
|
} catch {
|
|
16075
16488
|
return;
|
|
16076
16489
|
}
|
|
16077
16490
|
for (const name of names) {
|
|
16078
|
-
const p =
|
|
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} -> ${
|
|
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 =
|
|
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 =
|
|
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 =
|
|
16139
|
-
|
|
16140
|
-
|
|
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 =
|
|
16148
|
-
const hasGit =
|
|
16149
|
-
const hasMods =
|
|
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 =
|
|
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 (!
|
|
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) =>
|
|
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(
|
|
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
|
|
16297
|
-
import { join as
|
|
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
|
|
16301
|
-
import { join as
|
|
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
|
|
16508
|
-
if (
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
17285
|
+
void existsSync44;
|
|
16873
17286
|
void dirname19;
|
|
16874
|
-
return
|
|
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 (
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
17620
|
+
return join41(rollPkgDir(), "lib");
|
|
17208
17621
|
}
|
|
17209
17622
|
function slidesTemplatePath(name) {
|
|
17210
|
-
const projTpl =
|
|
17623
|
+
const projTpl = join41(".roll", "slides", "templates", `${name}.html`);
|
|
17211
17624
|
if (isFile3(projTpl))
|
|
17212
17625
|
return projTpl;
|
|
17213
|
-
const tpl =
|
|
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 =
|
|
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 =
|
|
17248
|
-
|
|
17660
|
+
const gi = join41(".roll", ".gitignore");
|
|
17661
|
+
mkdirSync23(".roll", { recursive: true });
|
|
17249
17662
|
if (isFile3(gi)) {
|
|
17250
|
-
const content =
|
|
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
|
-
|
|
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) ?
|
|
17261
|
-
|
|
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 =
|
|
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 =
|
|
17331
|
-
const renderer =
|
|
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 =
|
|
17337
|
-
const componentsDir =
|
|
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
|
-
|
|
17347
|
-
|
|
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
|
-
|
|
17365
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
17392
|
-
|
|
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 =
|
|
17396
|
-
const template =
|
|
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
|
-
|
|
17402
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
17494
|
-
const d =
|
|
17495
|
-
if (isDir3(d) && isFile3(
|
|
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 =
|
|
17510
|
-
const html =
|
|
17511
|
-
const errFile =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
17627
|
-
const errFile =
|
|
17628
|
-
if (!isDir3(deckDir) || !isFile3(
|
|
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(
|
|
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 =
|
|
17670
|
-
const html =
|
|
17671
|
-
if (!isDir3(deckDir) || !isFile3(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
18190
|
+
return statSync21(p).isDirectory();
|
|
17778
18191
|
} catch {
|
|
17779
18192
|
return false;
|
|
17780
18193
|
}
|
|
17781
18194
|
}
|
|
17782
18195
|
function listHtml(dir) {
|
|
17783
|
-
return
|
|
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
|
|
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
|
|
18218
|
+
import { basename as basename10, dirname as dirname20, join as join42 } from "node:path";
|
|
17806
18219
|
function rollHome3() {
|
|
17807
|
-
return process.env["ROLL_HOME"] ??
|
|
18220
|
+
return process.env["ROLL_HOME"] ?? join42(homedir16(), ".roll");
|
|
17808
18221
|
}
|
|
17809
|
-
var globalDir = () =>
|
|
17810
|
-
var templatesDir = () =>
|
|
17811
|
-
var configPath = () =>
|
|
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,
|
|
18243
|
+
return CONVENTION_FILES.map((f) => [f, existsSync45(join42(globalDir(), f))]);
|
|
17831
18244
|
}
|
|
17832
18245
|
function parseAiEntries() {
|
|
17833
18246
|
const cfg = configPath();
|
|
17834
|
-
if (!
|
|
18247
|
+
if (!existsSync45(cfg))
|
|
17835
18248
|
return [];
|
|
17836
18249
|
const entries = [];
|
|
17837
|
-
for (const line of
|
|
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 =
|
|
17858
|
-
const rollMd =
|
|
17859
|
-
const src =
|
|
17860
|
-
if (!
|
|
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 (!
|
|
18275
|
+
if (!existsSync45(rollMd))
|
|
17863
18276
|
return "out-of-sync";
|
|
17864
18277
|
try {
|
|
17865
|
-
if (
|
|
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 (!
|
|
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 =
|
|
17880
|
-
if (!
|
|
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
|
|
18297
|
+
for (const name of readdirSync20(skillsDir2)) {
|
|
17885
18298
|
if (!name.startsWith("roll-"))
|
|
17886
18299
|
continue;
|
|
17887
|
-
const p =
|
|
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
|
|
17900
|
-
const p =
|
|
17901
|
-
const st =
|
|
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 =
|
|
17911
|
-
if (!
|
|
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 =
|
|
17921
|
-
if (!
|
|
18333
|
+
const sd = join42(rollHome3(), "skills");
|
|
18334
|
+
if (!existsSync45(sd))
|
|
17922
18335
|
return 0;
|
|
17923
18336
|
try {
|
|
17924
|
-
return
|
|
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 =
|
|
17932
|
-
if (!
|
|
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:
|
|
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 (
|
|
18077
|
-
featCount =
|
|
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:
|
|
18085
|
-
project_has_backlog:
|
|
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
|
|
18110
|
-
import { dirname as dirname21, join as
|
|
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 =
|
|
18128
|
-
if (!
|
|
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 =
|
|
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
|
|
18593
|
+
return existsSync46(resetLockPath());
|
|
18181
18594
|
}
|
|
18182
18595
|
function resetAcquireLock() {
|
|
18183
18596
|
const lock = resetLockPath();
|
|
18184
|
-
if (
|
|
18597
|
+
if (existsSync46(lock))
|
|
18185
18598
|
return false;
|
|
18186
|
-
|
|
18187
|
-
|
|
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" && !
|
|
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
|
|
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
|
|
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(
|
|
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",
|
|
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 =
|
|
18786
|
+
const extractDir = join44(tmpDir, "extract");
|
|
18374
18787
|
spawnSync12("mkdir", ["-p", extractDir], { stdio: "ignore" });
|
|
18375
18788
|
const ex = runForward2("tar", [
|
|
18376
18789
|
"-xzf",
|
|
18377
|
-
|
|
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 =
|
|
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(
|
|
18821
|
+
rmSync13(join44(rollHome(), ".update-check"), { force: true });
|
|
18409
18822
|
}
|
|
18410
18823
|
function showChangelog() {
|
|
18411
|
-
const changelog =
|
|
18412
|
-
if (!
|
|
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
|
|
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 =
|
|
18440
|
-
if (
|
|
18441
|
-
installMethod =
|
|
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) => {
|