@seanyao/roll 3.606.3 → 3.607.2
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 +33 -0
- package/bin/roll +9 -6
- package/dist/roll.mjs +1086 -478
- 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
|
@@ -3028,6 +3028,21 @@ function agentBinNames(agent) {
|
|
|
3028
3028
|
return null;
|
|
3029
3029
|
}
|
|
3030
3030
|
}
|
|
3031
|
+
function realAgentEnv() {
|
|
3032
|
+
return {
|
|
3033
|
+
home: homedir(),
|
|
3034
|
+
commandOnPath,
|
|
3035
|
+
dirExists: (p) => existsSync2(p),
|
|
3036
|
+
fileExecutable: (p) => {
|
|
3037
|
+
try {
|
|
3038
|
+
accessSync(p, constants.X_OK);
|
|
3039
|
+
return statSync(p).isFile();
|
|
3040
|
+
} catch {
|
|
3041
|
+
return false;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3031
3046
|
function commandOnPath(bin) {
|
|
3032
3047
|
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
3033
3048
|
if (dir === "")
|
|
@@ -3130,6 +3145,115 @@ var nodeFileStore = {
|
|
|
3130
3145
|
}
|
|
3131
3146
|
};
|
|
3132
3147
|
|
|
3148
|
+
// packages/core/dist/agent/registry.js
|
|
3149
|
+
function agentBinNames2(agent) {
|
|
3150
|
+
switch (agent) {
|
|
3151
|
+
case "claude":
|
|
3152
|
+
return ["claude"];
|
|
3153
|
+
case "codex":
|
|
3154
|
+
case "openai":
|
|
3155
|
+
return ["codex"];
|
|
3156
|
+
case "agy":
|
|
3157
|
+
case "gemini":
|
|
3158
|
+
return ["agy", "gemini"];
|
|
3159
|
+
case "kimi":
|
|
3160
|
+
return ["kimi-code", "kimi-cli", "kimi"];
|
|
3161
|
+
case "deepseek":
|
|
3162
|
+
return ["deepseek"];
|
|
3163
|
+
case "qwen":
|
|
3164
|
+
return ["qwen"];
|
|
3165
|
+
case "pi":
|
|
3166
|
+
return ["pi"];
|
|
3167
|
+
default:
|
|
3168
|
+
return null;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
function joinPath(...parts) {
|
|
3172
|
+
return parts.join("/");
|
|
3173
|
+
}
|
|
3174
|
+
function agentInstalledByName2(env, agent, dir) {
|
|
3175
|
+
const home = env.home;
|
|
3176
|
+
switch (agent) {
|
|
3177
|
+
case "trae":
|
|
3178
|
+
return env.dirExists(joinPath(home, "Library", "Application Support", "Trae")) || env.dirExists(joinPath(home, ".config", "Trae"));
|
|
3179
|
+
case "opencode":
|
|
3180
|
+
return env.fileExecutable(joinPath(home, ".opencode", "bin", "opencode"));
|
|
3181
|
+
case "cursor":
|
|
3182
|
+
return env.commandOnPath("cursor") || env.dirExists(joinPath(home, ".cursor"));
|
|
3183
|
+
case "openclaw":
|
|
3184
|
+
return env.dirExists(joinPath(home, ".openclaw", "workspace"));
|
|
3185
|
+
}
|
|
3186
|
+
const bins = agentBinNames2(agent);
|
|
3187
|
+
if (bins !== null)
|
|
3188
|
+
return bins.some((b) => env.commandOnPath(b));
|
|
3189
|
+
return dir !== void 0 && dir !== "" && env.dirExists(dir);
|
|
3190
|
+
}
|
|
3191
|
+
var FIRST_INSTALLED_ORDER = [
|
|
3192
|
+
"claude",
|
|
3193
|
+
"codex",
|
|
3194
|
+
"kimi",
|
|
3195
|
+
"deepseek",
|
|
3196
|
+
"qwen",
|
|
3197
|
+
"agy",
|
|
3198
|
+
"pi",
|
|
3199
|
+
"cursor",
|
|
3200
|
+
"opencode",
|
|
3201
|
+
"trae",
|
|
3202
|
+
"openclaw"
|
|
3203
|
+
];
|
|
3204
|
+
function firstInstalledAgent(env) {
|
|
3205
|
+
return FIRST_INSTALLED_ORDER.find((a) => agentInstalledByName2(env, a));
|
|
3206
|
+
}
|
|
3207
|
+
function lineAgentValue(line) {
|
|
3208
|
+
const s = line.replace(/\t/g, " ");
|
|
3209
|
+
if (s.startsWith("agent:"))
|
|
3210
|
+
return s.slice("agent:".length);
|
|
3211
|
+
const m7 = /[ ,{]agent:/.exec(s);
|
|
3212
|
+
if (m7 === null)
|
|
3213
|
+
return void 0;
|
|
3214
|
+
return s.slice(m7.index + m7[0].length);
|
|
3215
|
+
}
|
|
3216
|
+
function cleanAgentValue(raw) {
|
|
3217
|
+
let v = raw;
|
|
3218
|
+
const hash = v.indexOf("#");
|
|
3219
|
+
if (hash >= 0)
|
|
3220
|
+
v = v.slice(0, hash);
|
|
3221
|
+
v = v.replace(/[{}",']/g, "").replace(/,/g, "");
|
|
3222
|
+
return v.trim();
|
|
3223
|
+
}
|
|
3224
|
+
function readSlotFromText(text, slot) {
|
|
3225
|
+
let inBlock = false;
|
|
3226
|
+
let agent = "";
|
|
3227
|
+
let found = false;
|
|
3228
|
+
const slotHeader = `${slot}:`;
|
|
3229
|
+
for (const raw of text.split("\n")) {
|
|
3230
|
+
const line = raw.replace(/\r$/, "");
|
|
3231
|
+
if (line === slotHeader || line.startsWith(`${slot}: `) || line.startsWith(`${slot}:{`)) {
|
|
3232
|
+
inBlock = true;
|
|
3233
|
+
const v = lineAgentValue(line);
|
|
3234
|
+
if (v !== void 0) {
|
|
3235
|
+
agent = v;
|
|
3236
|
+
found = true;
|
|
3237
|
+
}
|
|
3238
|
+
} else if (line.length > 0 && line[0] !== " ") {
|
|
3239
|
+
if (inBlock)
|
|
3240
|
+
break;
|
|
3241
|
+
} else if (inBlock && !found) {
|
|
3242
|
+
const v = lineAgentValue(line);
|
|
3243
|
+
if (v !== void 0) {
|
|
3244
|
+
agent = v;
|
|
3245
|
+
found = true;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
if (found)
|
|
3249
|
+
break;
|
|
3250
|
+
}
|
|
3251
|
+
if (!inBlock)
|
|
3252
|
+
return void 0;
|
|
3253
|
+
const cleaned = cleanAgentValue(agent);
|
|
3254
|
+
return cleaned === "" ? void 0 : cleaned;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3133
3257
|
// packages/core/dist/agent/router.js
|
|
3134
3258
|
var EASY_MAX_MIN = 8;
|
|
3135
3259
|
var HARD_MIN_MIN = 20;
|
|
@@ -5439,6 +5563,91 @@ var ANSI_CSS = `
|
|
|
5439
5563
|
}
|
|
5440
5564
|
`.trim();
|
|
5441
5565
|
|
|
5566
|
+
// packages/core/dist/html/chrome.js
|
|
5567
|
+
function bi(en, zh) {
|
|
5568
|
+
return `<span class="lang-en">${en}</span><span class="lang-zh">${zh}</span>`;
|
|
5569
|
+
}
|
|
5570
|
+
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;`;
|
|
5571
|
+
var CHROME_CSS = `
|
|
5572
|
+
:root { color-scheme: light dark;
|
|
5573
|
+
--bg:#f6f2e9; --bg-raise:#fdfbf5; --fg:#211d14; --muted:#79705d; --line:#d9d1bd; --accent:#a83825;
|
|
5574
|
+
--grain:rgba(60,40,10,.03);
|
|
5575
|
+
--pass:#2f7d3b; --info:#2c6cb0; --warn:#9a7b00; --claim:#c4602c; --fail:#c03328; --block:#6e40c9;
|
|
5576
|
+
--serif:"Iowan Old Style","Palatino","Book Antiqua",Georgia,"Songti SC","Noto Serif CJK SC",serif;
|
|
5577
|
+
--sans:-apple-system,"PingFang SC","Segoe UI","Microsoft YaHei",sans-serif;
|
|
5578
|
+
--mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace; }
|
|
5579
|
+
:root[data-theme="dark"] { ${DARK_VARS} }
|
|
5580
|
+
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { ${DARK_VARS} } }
|
|
5581
|
+
html { background:var(--bg); }
|
|
5582
|
+
body { margin:0 auto; max-width:880px; padding:30px 20px 80px; background:var(--bg); color:var(--fg);
|
|
5583
|
+
font:15px/1.7 var(--sans); }
|
|
5584
|
+
body::after { content:""; position:fixed; inset:0; pointer-events:none; z-index:1;
|
|
5585
|
+
background-image:radial-gradient(var(--grain) 1px, transparent 1px); background-size:3px 3px; }
|
|
5586
|
+
.kicker { font:11.5px/1 var(--serif); letter-spacing:.24em; text-transform:uppercase; color:var(--accent);
|
|
5587
|
+
border-top:3px double var(--line); padding-top:10px; margin:0 0 10px; }
|
|
5588
|
+
h1 { font:600 26px/1.3 var(--serif); margin:0 0 4px; letter-spacing:.01em; }
|
|
5589
|
+
h2 { font:600 17px/1.4 var(--serif); border-bottom:1px solid var(--line); padding-bottom:6px; letter-spacing:.02em; }
|
|
5590
|
+
code { font-family:var(--mono); background:rgba(127,110,70,.10); padding:1px 6px; border-radius:4px; font-size:.9em; }
|
|
5591
|
+
pre { background:rgba(127,110,70,.07); padding:12px; border-radius:6px; overflow-x:auto; }
|
|
5592
|
+
a { color:var(--accent); text-decoration-color:color-mix(in srgb, var(--accent) 40%, transparent); }
|
|
5593
|
+
section { border:1px solid var(--line); border-radius:8px; padding:14px 18px; margin:14px 0;
|
|
5594
|
+
background:var(--bg-raise); position:relative; z-index:2; }
|
|
5595
|
+
.empty { color:var(--muted); font-style:italic; }
|
|
5596
|
+
.meta, .muted { color:var(--muted); font-size:13px; }
|
|
5597
|
+
footer { color:var(--muted); font-size:12.5px; font-family:var(--serif); letter-spacing:.04em;
|
|
5598
|
+
margin-top:40px; border-top:3px double var(--line); padding-top:12px; }
|
|
5599
|
+
/* language switching \u2014 both sides render until the chrome script picks one */
|
|
5600
|
+
[data-lang="en"] .lang-zh { display:none; } [data-lang="zh"] .lang-en { display:none; }
|
|
5601
|
+
/* chrome bar */
|
|
5602
|
+
.chrome { position:fixed; top:14px; right:14px; z-index:10; display:flex; gap:8px; font-family:var(--sans); }
|
|
5603
|
+
.chrome .seg { display:flex; border:1px solid var(--line); border-radius:999px; overflow:hidden;
|
|
5604
|
+
background:color-mix(in srgb, var(--bg-raise) 82%, transparent); backdrop-filter:blur(6px); }
|
|
5605
|
+
.chrome button { all:unset; cursor:pointer; font-size:12px; line-height:1; padding:7px 11px; color:var(--muted); }
|
|
5606
|
+
.chrome button.on { background:var(--accent); color:#fdfbf5; }
|
|
5607
|
+
.chrome button:not(.on):hover { color:var(--fg); }
|
|
5608
|
+
@media print { body { max-width:none; padding:0; } body::after, .chrome { display:none; }
|
|
5609
|
+
section { break-inside:avoid; background:none; } }
|
|
5610
|
+
`;
|
|
5611
|
+
var CHROME_CONTROLS = `<nav class="chrome" aria-label="page controls">
|
|
5612
|
+
<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>
|
|
5613
|
+
<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>
|
|
5614
|
+
</nav>`;
|
|
5615
|
+
var CHROME_SCRIPT = `<script>
|
|
5616
|
+
(function () {
|
|
5617
|
+
var d = document.documentElement;
|
|
5618
|
+
function get(k) { try { return localStorage.getItem(k); } catch (e) { return null; } }
|
|
5619
|
+
function set(k, v) { try { localStorage.setItem(k, v); } catch (e) {} }
|
|
5620
|
+
var lang = get("roll-lang") || ((navigator.language || "").toLowerCase().indexOf("zh") === 0 ? "zh" : "en");
|
|
5621
|
+
var theme = get("roll-theme") || (window.matchMedia && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
|
5622
|
+
function apply() {
|
|
5623
|
+
d.setAttribute("data-lang", lang);
|
|
5624
|
+
d.setAttribute("lang", lang === "zh" ? "zh-CN" : "en");
|
|
5625
|
+
d.setAttribute("data-theme", theme);
|
|
5626
|
+
var bs = document.querySelectorAll("[data-set-lang],[data-set-theme]");
|
|
5627
|
+
for (var i = 0; i < bs.length; i++) {
|
|
5628
|
+
var b = bs[i];
|
|
5629
|
+
var on = b.getAttribute("data-set-lang") === lang || b.getAttribute("data-set-theme") === theme;
|
|
5630
|
+
b.classList.toggle("on", on);
|
|
5631
|
+
b.setAttribute("aria-pressed", String(on));
|
|
5632
|
+
}
|
|
5633
|
+
}
|
|
5634
|
+
apply();
|
|
5635
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
5636
|
+
var bs = document.querySelectorAll("[data-set-lang],[data-set-theme]");
|
|
5637
|
+
for (var i = 0; i < bs.length; i++) {
|
|
5638
|
+
bs[i].addEventListener("click", function () {
|
|
5639
|
+
var l = this.getAttribute("data-set-lang");
|
|
5640
|
+
var t = this.getAttribute("data-set-theme");
|
|
5641
|
+
if (l) { lang = l; set("roll-lang", l); }
|
|
5642
|
+
if (t) { theme = t; set("roll-theme", t); }
|
|
5643
|
+
apply();
|
|
5644
|
+
});
|
|
5645
|
+
}
|
|
5646
|
+
apply();
|
|
5647
|
+
});
|
|
5648
|
+
})();
|
|
5649
|
+
</script>`;
|
|
5650
|
+
|
|
5442
5651
|
// packages/core/dist/attest/report.js
|
|
5443
5652
|
var BADGE = {
|
|
5444
5653
|
pass: { icon: "\u2705", en: "Pass", zh: "\u901A\u8FC7", cls: "s-pass" },
|
|
@@ -5476,9 +5685,9 @@ function deliveryBlock(d) {
|
|
|
5476
5685
|
if (d.cycleId !== void 0 && d.cycleId !== "")
|
|
5477
5686
|
parts.push(`<dt>Cycle</dt><dd><code>${esc(d.cycleId)}</code></dd>`);
|
|
5478
5687
|
if (d.timeline !== void 0 && d.timeline !== "")
|
|
5479
|
-
parts.push(`<dt
|
|
5688
|
+
parts.push(`<dt>${bi("Timeline", "\u65F6\u95F4\u7EBF")}</dt><dd>${esc(d.timeline)}</dd>`);
|
|
5480
5689
|
if (d.cost !== void 0 && d.cost !== "")
|
|
5481
|
-
parts.push(`<dt
|
|
5690
|
+
parts.push(`<dt>${bi("Cost", "\u6210\u672C")}</dt><dd>${esc(d.cost)}</dd>`);
|
|
5482
5691
|
return parts.length > 0 ? `<dl class="delivery">${parts.join("")}</dl>` : "";
|
|
5483
5692
|
}
|
|
5484
5693
|
function cardContextBlock(ctx) {
|
|
@@ -5501,7 +5710,7 @@ function cardContextBlock(ctx) {
|
|
|
5501
5710
|
rows.push(delivery);
|
|
5502
5711
|
if (rows.length === 0)
|
|
5503
5712
|
return "";
|
|
5504
|
-
return `<section class="card-context"><h2
|
|
5713
|
+
return `<section class="card-context"><h2>${bi("Context", "\u5361\u4E0A\u4E0B\u6587")}</h2>
|
|
5505
5714
|
${rows.join("\n")}
|
|
5506
5715
|
</section>`;
|
|
5507
5716
|
}
|
|
@@ -5511,11 +5720,11 @@ function acSection(item) {
|
|
|
5511
5720
|
const business = item.evidence.filter((e) => e.kind !== "text");
|
|
5512
5721
|
const technical = item.evidence.filter((e) => e.kind === "text");
|
|
5513
5722
|
const bizHtml = business.map(evidenceCard).join("\n");
|
|
5514
|
-
const techHtml = technical.length > 0 ? `<details class="tech"><summary
|
|
5723
|
+
const techHtml = technical.length > 0 ? `<details class="tech"><summary>${bi(`Technical detail (${technical.length})`, `\u6280\u672F\u7EC6\u8282\uFF08${technical.length}\uFF09`)}</summary>
|
|
5515
5724
|
${technical.map(evidenceCard).join("\n")}
|
|
5516
5725
|
</details>` : "";
|
|
5517
5726
|
return `<section class="ac ${b.cls}" id="${esc(item.id)}">
|
|
5518
|
-
<h3><span class="badge">${b.icon} ${b.en
|
|
5727
|
+
<h3><span class="badge">${b.icon} ${bi(b.en, b.zh)}</span> <code>${esc(item.id)}</code></h3>
|
|
5519
5728
|
<p class="ac-text">${esc(item.text)}</p>
|
|
5520
5729
|
${note}
|
|
5521
5730
|
${bizHtml}
|
|
@@ -5527,15 +5736,15 @@ function beforeAfterBlock(pairs) {
|
|
|
5527
5736
|
return "";
|
|
5528
5737
|
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
5738
|
const groups = pairs.map((p) => {
|
|
5530
|
-
const before = fig(p.before, "Before \
|
|
5531
|
-
const after = fig(p.after, "After \
|
|
5739
|
+
const before = fig(p.before, bi("Before", "\u6539\u524D"), "ba-before");
|
|
5740
|
+
const after = fig(p.after, bi("After", "\u6539\u540E"), "ba-after");
|
|
5532
5741
|
if (before === "" && after === "")
|
|
5533
5742
|
return "";
|
|
5534
5743
|
return `<div class="before-after"><h3>${esc(p.label)}</h3><div class="ba-pair">${before}${after}</div></div>`;
|
|
5535
5744
|
}).filter((s) => s !== "");
|
|
5536
5745
|
if (groups.length === 0)
|
|
5537
5746
|
return "";
|
|
5538
|
-
return `<section class="before-after-section"><h2
|
|
5747
|
+
return `<section class="before-after-section"><h2>${bi("Before / After", "\u5BF9\u7167\u5B9E\u62CD")}</h2>
|
|
5539
5748
|
${groups.join("\n")}
|
|
5540
5749
|
</section>`;
|
|
5541
5750
|
}
|
|
@@ -5543,7 +5752,7 @@ function selfCaptureBlock(refs) {
|
|
|
5543
5752
|
if (refs === void 0 || refs.length === 0)
|
|
5544
5753
|
return "";
|
|
5545
5754
|
const figs = refs.map(evidenceCard).join("\n");
|
|
5546
|
-
return `<section class="self-capture"><h2
|
|
5755
|
+
return `<section class="self-capture"><h2>${bi("Gate self-capture", "Gate \u81EA\u4EA7\u5B9E\u62CD")}</h2>
|
|
5547
5756
|
${figs}
|
|
5548
5757
|
</section>`;
|
|
5549
5758
|
}
|
|
@@ -5567,7 +5776,7 @@ function evidenceIndexBlock(items, beforeAfter, selfCaptures) {
|
|
|
5567
5776
|
row2(r);
|
|
5568
5777
|
if (rows.length === 0)
|
|
5569
5778
|
return "";
|
|
5570
|
-
return `<section class="evidence-index"><h2
|
|
5779
|
+
return `<section class="evidence-index"><h2>${bi("Evidence index", "\u8BC1\u636E\u7D22\u5F15")}</h2>
|
|
5571
5780
|
<table class="ev-index"><thead><tr><th>Kind</th><th>Label</th><th>Locator</th></tr></thead>
|
|
5572
5781
|
<tbody>${rows.join("\n")}</tbody></table></section>`;
|
|
5573
5782
|
}
|
|
@@ -5581,7 +5790,7 @@ function processTraceBlock(p) {
|
|
|
5581
5790
|
if (p === void 0)
|
|
5582
5791
|
return "";
|
|
5583
5792
|
const rows = [];
|
|
5584
|
-
const mode = p.delivery === "manual" ? `<p class="delivery-mode">\u{1F9D1}\u200D\u{1F527} conductor \u624B\u5DE5\u4EA4\u4ED8
|
|
5793
|
+
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
5794
|
rows.push(mode);
|
|
5586
5795
|
if (p.timeline !== void 0 && p.timeline.length > 0) {
|
|
5587
5796
|
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 +5799,18 @@ ${li}
|
|
|
5590
5799
|
</ol>`);
|
|
5591
5800
|
}
|
|
5592
5801
|
if (p.missing !== void 0 && p.missing.length > 0) {
|
|
5593
|
-
rows.push(`<p class="trace-missing"
|
|
5802
|
+
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
5803
|
}
|
|
5595
5804
|
if (p.transcript !== void 0) {
|
|
5596
5805
|
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
|
|
5806
|
+
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`);
|
|
5807
|
+
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>` : "";
|
|
5808
|
+
rows.push(`<details class="transcript"><summary>${bi("Full transcript", "\u5B8C\u6574\u8F6C\u5F55")}\uFF08${note}\uFF09</summary>
|
|
5600
5809
|
${idx}
|
|
5601
5810
|
${t2.inlineHtml}
|
|
5602
5811
|
</details>`);
|
|
5603
5812
|
}
|
|
5604
|
-
return `<section class="process-trace"><h2
|
|
5813
|
+
return `<section class="process-trace"><h2>${bi("Process trace", "\u8FC7\u7A0B\u6863\u6848")}</h2>
|
|
5605
5814
|
${rows.join("\n")}
|
|
5606
5815
|
</section>`;
|
|
5607
5816
|
}
|
|
@@ -5609,7 +5818,7 @@ function selfScoreBlock(entries) {
|
|
|
5609
5818
|
if (entries === void 0 || entries.length === 0)
|
|
5610
5819
|
return "";
|
|
5611
5820
|
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
|
|
5821
|
+
return `<details class="selfscore"><summary>${bi("Self-Score", "\u81EA\u8BC4")}\uFF08${entries.length}\uFF09</summary>
|
|
5613
5822
|
<ul>
|
|
5614
5823
|
${li}
|
|
5615
5824
|
</ul>
|
|
@@ -5622,13 +5831,13 @@ function renderReport(input) {
|
|
|
5622
5831
|
const counts = /* @__PURE__ */ new Map();
|
|
5623
5832
|
for (const it of items)
|
|
5624
5833
|
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
|
|
5834
|
+
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
5835
|
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
|
|
5836
|
+
const disc = discrepancies.length > 0 ? `<section class="discrepancies"><h2>${bi("Discrepancies", "\u8BC1\u636E\u7F3A\u53E3")}</h2>
|
|
5837
|
+
<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
5838
|
<ul>${discrepancies.map((d) => `<li><a href="#${esc(d.id)}"><code>${esc(d.id)}</code></a> ${esc(d.text)}</li>`).join("\n")}</ul>
|
|
5630
5839
|
</section>` : "";
|
|
5631
|
-
const gate = facts !== "" ? `<h2
|
|
5840
|
+
const gate = facts !== "" ? `<h2>${bi("Quality gate", "\u8D28\u91CF\u95E8\u7981")}</h2>
|
|
5632
5841
|
${facts}` : "";
|
|
5633
5842
|
const evIndex = evidenceIndexBlock(items, input.beforeAfter, input.selfCaptures);
|
|
5634
5843
|
const selfScore = selfScoreBlock(input.selfScores);
|
|
@@ -5643,31 +5852,31 @@ ${closingInner}
|
|
|
5643
5852
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5644
5853
|
<title>${esc(input.storyId)} \u2014 Acceptance Evidence \xB7 \u9A8C\u6536\u8BC1\u636E</title>
|
|
5645
5854
|
<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; }
|
|
5855
|
+
${CHROME_CSS}
|
|
5652
5856
|
.badge { display:inline-block; padding:2px 10px; border-radius:999px; font-size:12.5px; border:1px solid var(--line); }
|
|
5653
|
-
.
|
|
5654
|
-
|
|
5857
|
+
.facts, .note { color:var(--muted); font-size:13px; }
|
|
5858
|
+
header.doc { position:relative; padding-right:96px; }
|
|
5859
|
+
.seal { position:absolute; right:0; top:2px; width:74px; height:74px; border:2px solid var(--accent);
|
|
5860
|
+
border-radius:50%; transform:rotate(-12deg); color:var(--accent); font-family:var(--serif);
|
|
5861
|
+
display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1px;
|
|
5862
|
+
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); }
|
|
5863
|
+
.seal span { font-size:9.5px; letter-spacing:.34em; text-indent:.34em; }
|
|
5655
5864
|
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:
|
|
5865
|
+
.s-pass { border-left:4px solid var(--pass); } .s-readonly { border-left:4px solid var(--info); }
|
|
5866
|
+
.s-partial { border-left:4px solid var(--warn); } .s-claimed { border-left:4px solid var(--claim); }
|
|
5867
|
+
.s-fail { border-left:4px solid var(--fail); } .s-blocked { border-left:4px solid var(--block); }
|
|
5868
|
+
.s-missing { border-left:4px solid var(--fail); }
|
|
5869
|
+
figure.shot { margin:10px 0; } figure.shot img { max-width:100%; border:1px solid var(--line); border-radius:6px; }
|
|
5661
5870
|
figure.shot figcaption { color:var(--muted); font-size:12.5px; }
|
|
5662
5871
|
.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:
|
|
5872
|
+
.discrepancies { border:1px dashed var(--claim); border-radius:8px; padding:8px 16px; margin-top:28px; }
|
|
5873
|
+
details.selfscore { margin-top:28px; border:1px solid var(--line); border-radius:8px; padding:8px 16px; background:var(--bg-raise); }
|
|
5665
5874
|
details.selfscore summary { cursor:pointer; font-weight:600; }
|
|
5666
5875
|
details.selfscore ul { margin:8px 0 4px; padding-left:18px; }
|
|
5667
|
-
details.tech { margin:8px 0 2px; border:1px solid var(--line); border-radius:
|
|
5876
|
+
details.tech { margin:8px 0 2px; border:1px solid var(--line); border-radius:6px; padding:6px 12px; background:rgba(127,110,70,.04); }
|
|
5668
5877
|
details.tech summary { cursor:pointer; color:var(--muted); font-size:12.5px; font-weight:600; }
|
|
5669
5878
|
details.tech[open] summary { margin-bottom:6px; }
|
|
5670
|
-
section.card-context {
|
|
5879
|
+
section.card-context { padding:6px 18px 12px; }
|
|
5671
5880
|
section.card-context .one-liner { font-size:15.5px; font-weight:600; }
|
|
5672
5881
|
section.card-context .ctx-meta { color:var(--muted); font-size:13px; }
|
|
5673
5882
|
dl.delivery { display:grid; grid-template-columns:auto 1fr; gap:2px 12px; margin:8px 0 0; font-size:13.5px; }
|
|
@@ -5676,27 +5885,32 @@ dl.delivery dt { color:var(--muted); } dl.delivery dd { margin:0; }
|
|
|
5676
5885
|
.ba-pair { display:flex; flex-wrap:wrap; gap:12px; } .ba-pair figure.shot { flex:1 1 280px; margin:0; }
|
|
5677
5886
|
table.ev-index { width:100%; border-collapse:collapse; font-size:13px; margin-top:8px; }
|
|
5678
5887
|
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; }
|
|
5888
|
+
table.ev-index th { color:var(--muted); font-weight:600; font-family:var(--serif); letter-spacing:.04em; }
|
|
5680
5889
|
table.ev-index td a { word-break:break-all; }
|
|
5681
|
-
section.process-trace { margin-top:28px;
|
|
5890
|
+
section.process-trace { margin-top:28px; padding:6px 18px 12px; }
|
|
5682
5891
|
.delivery-mode { font-size:13.5px; color:var(--muted); }
|
|
5683
5892
|
ol.timeline { list-style:none; margin:8px 0; padding:0; font-size:13.5px; }
|
|
5684
5893
|
ol.timeline li { padding:3px 0 3px 10px; border-left:2px solid var(--line); }
|
|
5685
|
-
ol.timeline li.tl-signal { border-left:3px solid
|
|
5894
|
+
ol.timeline li.tl-signal { border-left:3px solid var(--info); font-weight:600; }
|
|
5686
5895
|
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:
|
|
5896
|
+
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); }
|
|
5897
|
+
.trace-missing { color:var(--warn); font-size:13px; }
|
|
5898
|
+
details.transcript { margin-top:8px; border:1px solid var(--line); border-radius:6px; padding:6px 12px; background:rgba(127,110,70,.04); }
|
|
5690
5899
|
details.transcript summary { cursor:pointer; color:var(--muted); font-size:12.5px; font-weight:600; }
|
|
5691
5900
|
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; } }
|
|
5901
|
+
section.closing { margin-top:32px; border:none; background:none; border-radius:0; padding:8px 0 0; border-top:3px double var(--line); }
|
|
5694
5902
|
${ANSI_CSS}
|
|
5695
5903
|
</style>
|
|
5904
|
+
${CHROME_SCRIPT}
|
|
5696
5905
|
</head>
|
|
5697
5906
|
<body>
|
|
5907
|
+
${CHROME_CONTROLS}
|
|
5908
|
+
<header class="doc">
|
|
5909
|
+
<p class="kicker">Roll \xB7 ${bi("Delivery Dossier", "\u4EA4\u4ED8\u6863\u6848")}</p>
|
|
5698
5910
|
<h1>${esc(input.title)}</h1>
|
|
5699
|
-
<p class="meta"><code>${esc(input.storyId)}</code> \xB7 generated ${esc(input.generatedAt)} \xB7 Gate: PASS</p>
|
|
5911
|
+
<p class="meta"><code>${esc(input.storyId)}</code> \xB7 ${bi("generated", "\u751F\u6210\u4E8E")} ${esc(input.generatedAt)} \xB7 Gate: PASS</p>
|
|
5912
|
+
<div class="seal" aria-hidden="true"><span>ROLL</span>\u9A8C\u8BAB</div>
|
|
5913
|
+
</header>
|
|
5700
5914
|
${cardContextBlock(input.context)}
|
|
5701
5915
|
<p>${summary}</p>
|
|
5702
5916
|
${items.map(acSection).join("\n")}
|
|
@@ -5704,6 +5918,7 @@ ${beforeAfterBlock(input.beforeAfter)}
|
|
|
5704
5918
|
${selfCaptureBlock(input.selfCaptures)}
|
|
5705
5919
|
${processTraceBlock(input.process)}
|
|
5706
5920
|
${closing}
|
|
5921
|
+
<footer>Roll \xB7 ${bi("Acceptance Evidence", "\u9A8C\u6536\u8BC1\u636E")} \xB7 <code>${esc(input.storyId)}</code></footer>
|
|
5707
5922
|
</body>
|
|
5708
5923
|
</html>
|
|
5709
5924
|
`;
|
|
@@ -5773,6 +5988,9 @@ function loopAlertPath() {
|
|
|
5773
5988
|
const rt = process.env["ROLL_PROJECT_RUNTIME_DIR"];
|
|
5774
5989
|
if (rt !== void 0 && rt !== "")
|
|
5775
5990
|
return join4(rt, name);
|
|
5991
|
+
const local = join4(".roll", "loop", name);
|
|
5992
|
+
if (existsSync5(local))
|
|
5993
|
+
return local;
|
|
5776
5994
|
const shared = process.env["_SHARED_ROOT"] ?? join4(homedir2(), ".shared", "roll");
|
|
5777
5995
|
return join4(shared, "loop", name);
|
|
5778
5996
|
}
|
|
@@ -5934,14 +6152,22 @@ function findFeatureFile(projectPath, storyId) {
|
|
|
5934
6152
|
function reportFileName(storyId) {
|
|
5935
6153
|
return `${storyId}-report.html`;
|
|
5936
6154
|
}
|
|
6155
|
+
function isEpicName(name) {
|
|
6156
|
+
return name !== "" && name !== "." && name !== "features";
|
|
6157
|
+
}
|
|
6158
|
+
function epicFromFeaturePath(featureFile) {
|
|
6159
|
+
const storyDir = dirname4(featureFile);
|
|
6160
|
+
const epic = basename3(dirname4(storyDir));
|
|
6161
|
+
if (isEpicName(epic))
|
|
6162
|
+
return epic;
|
|
6163
|
+
const legacy = basename3(storyDir);
|
|
6164
|
+
return isEpicName(legacy) ? legacy : null;
|
|
6165
|
+
}
|
|
5937
6166
|
function liveEpicOf(projectPath, storyId) {
|
|
5938
6167
|
const file = findFeatureFile(projectPath, storyId);
|
|
5939
6168
|
if (file === null)
|
|
5940
6169
|
return null;
|
|
5941
|
-
|
|
5942
|
-
if (epic === "" || epic === "." || epic === "features")
|
|
5943
|
-
return null;
|
|
5944
|
-
return epic;
|
|
6170
|
+
return epicFromFeaturePath(file);
|
|
5945
6171
|
}
|
|
5946
6172
|
function readIndex(projectPath) {
|
|
5947
6173
|
const p = join5(projectPath, ".roll", "index.json");
|
|
@@ -7149,7 +7375,70 @@ function screenshotEvidenceRef(result, href) {
|
|
|
7149
7375
|
|
|
7150
7376
|
// packages/cli/dist/commands/attest.js
|
|
7151
7377
|
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
|
-
import { basename as basename4,
|
|
7378
|
+
import { basename as basename4, join as join10, relative } from "node:path";
|
|
7379
|
+
|
|
7380
|
+
// packages/cli/dist/lib/story-page.js
|
|
7381
|
+
var STORY_ID_RE = /^(FIX|US|IDEA|REFACTOR)-/;
|
|
7382
|
+
function storyFamilyOf(storyId) {
|
|
7383
|
+
const m7 = STORY_ID_RE.exec(storyId);
|
|
7384
|
+
return m7 === null ? null : m7[1];
|
|
7385
|
+
}
|
|
7386
|
+
var STORY_PHASES = [
|
|
7387
|
+
{ key: "design", en: "Design", zh: "\u8BBE\u8BA1", emptyEn: "Not yet started", emptyZh: "\u5C1A\u672A\u5F00\u59CB" },
|
|
7388
|
+
{ key: "execution", en: "Execution", zh: "\u6267\u884C", emptyEn: "No cycles yet", emptyZh: "\u6682\u65E0\u5468\u671F" },
|
|
7389
|
+
{ key: "delivery", en: "Delivery", zh: "\u4EA4\u4ED8", emptyEn: "Not yet delivered", emptyZh: "\u5C1A\u672A\u4EA4\u4ED8" },
|
|
7390
|
+
{ key: "retrospective", en: "Retrospective", zh: "\u590D\u76D8", emptyEn: "Not yet written", emptyZh: "\u5C1A\u672A\u64B0\u5199" }
|
|
7391
|
+
];
|
|
7392
|
+
function renderSpecMd(meta) {
|
|
7393
|
+
const type = meta.type ?? storyFamilyOf(meta.id)?.toLowerCase() ?? "unknown";
|
|
7394
|
+
return `---
|
|
7395
|
+
id: ${meta.id}
|
|
7396
|
+
` + (meta.title !== void 0 ? `title: ${meta.title}
|
|
7397
|
+
` : "") + `type: ${type}
|
|
7398
|
+
` + (meta.epic !== void 0 ? `epic: ${meta.epic}
|
|
7399
|
+
` : "") + `created: ${meta.created}
|
|
7400
|
+
---
|
|
7401
|
+
|
|
7402
|
+
# ${meta.id}${meta.title !== void 0 ? ` \u2014 ${meta.title}` : ""}
|
|
7403
|
+
` + (meta.note !== void 0 ? `
|
|
7404
|
+
> ${meta.note}
|
|
7405
|
+
` : "");
|
|
7406
|
+
}
|
|
7407
|
+
function renderStoryPage(meta) {
|
|
7408
|
+
const q = (s) => s.replace(/"/g, """);
|
|
7409
|
+
const badge = storyFamilyOf(meta.id) ?? meta.id.split("-")[0];
|
|
7410
|
+
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");
|
|
7411
|
+
return `<!DOCTYPE html>
|
|
7412
|
+
<html lang="zh-CN">
|
|
7413
|
+
<head>
|
|
7414
|
+
<meta charset="UTF-8">
|
|
7415
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7416
|
+
<title>${meta.title !== void 0 ? `${meta.id} \u2014 ${q(meta.title)}` : meta.id}</title>
|
|
7417
|
+
<style>
|
|
7418
|
+
${CHROME_CSS}.phase-done { border-left:4px solid var(--pass); } .phase-pending { border-left:4px solid var(--line); }
|
|
7419
|
+
</style>
|
|
7420
|
+
${CHROME_SCRIPT}
|
|
7421
|
+
</head>
|
|
7422
|
+
<body>
|
|
7423
|
+
${CHROME_CONTROLS}
|
|
7424
|
+
<p class="kicker">Roll \xB7 ${bi("Story Dossier", "\u6545\u4E8B\u6863\u6848")}</p>
|
|
7425
|
+
<h1>${meta.id}</h1>
|
|
7426
|
+
<p class="meta"><code>${badge}</code> \xB7 ${bi("Created", "\u521B\u5EFA\u4E8E")} ${meta.created}</p>
|
|
7427
|
+
` + (meta.title !== void 0 ? `<p>${q(meta.title)}</p>
|
|
7428
|
+
` : "") + `${sections}
|
|
7429
|
+
<footer>Roll \xB7 <a href="spec.md">spec.md</a></footer>
|
|
7430
|
+
</body>
|
|
7431
|
+
</html>
|
|
7432
|
+
`;
|
|
7433
|
+
}
|
|
7434
|
+
function markPhaseDone(html, phaseKey, innerHtml) {
|
|
7435
|
+
const p = STORY_PHASES.find((x) => x.key === phaseKey);
|
|
7436
|
+
if (p === void 0)
|
|
7437
|
+
return html;
|
|
7438
|
+
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>`);
|
|
7439
|
+
}
|
|
7440
|
+
|
|
7441
|
+
// packages/cli/dist/commands/attest.js
|
|
7153
7442
|
function defaultProcessReaders(projectPath, env) {
|
|
7154
7443
|
const rt = (env.ROLL_PROJECT_RUNTIME_DIR ?? "").trim() || join10(projectPath, ".roll", "loop");
|
|
7155
7444
|
const bus = new EventBus();
|
|
@@ -7380,11 +7669,11 @@ function buildCardContext(projectPath, featureFile, storyId, env) {
|
|
|
7380
7669
|
summary = extractSummary(readFileSync9(featureFile, "utf8"));
|
|
7381
7670
|
} catch {
|
|
7382
7671
|
}
|
|
7383
|
-
const epic =
|
|
7672
|
+
const epic = epicFromFeaturePath(featureFile);
|
|
7384
7673
|
const cycleId = env.LOOP_CYCLE_ID;
|
|
7385
7674
|
const ctx = {
|
|
7386
7675
|
...oneLiner !== void 0 && oneLiner !== "" ? { oneLiner } : {},
|
|
7387
|
-
...epic !==
|
|
7676
|
+
...epic !== null ? { epic } : {},
|
|
7388
7677
|
...summary !== void 0 ? { summary } : {},
|
|
7389
7678
|
...row2.status !== void 0 ? { backlogStatus: row2.status } : {},
|
|
7390
7679
|
...cycleId !== void 0 && cycleId !== "" ? { delivery: { cycleId } } : {}
|
|
@@ -7535,6 +7824,18 @@ async function attestCommand(args, deps = {}) {
|
|
|
7535
7824
|
} catch {
|
|
7536
7825
|
warn2("latest symlink update failed (report still written)");
|
|
7537
7826
|
}
|
|
7827
|
+
const indexPath = join10(storyDir, "index.html");
|
|
7828
|
+
if (existsSync14(indexPath)) {
|
|
7829
|
+
try {
|
|
7830
|
+
const reportRel = join10(runId, reportFileName(storyId));
|
|
7831
|
+
const deliveryHtml = `<p><a href="${reportRel}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a></p>
|
|
7832
|
+
<p class="muted">${bi("Delivered", "\u4EA4\u4ED8\u4E8E")} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}</p>
|
|
7833
|
+
`;
|
|
7834
|
+
const idx = markPhaseDone(readFileSync9(indexPath, "utf8"), "delivery", deliveryHtml);
|
|
7835
|
+
writeFileSync7(indexPath, idx, "utf8");
|
|
7836
|
+
} catch {
|
|
7837
|
+
}
|
|
7838
|
+
}
|
|
7538
7839
|
const smoke = smokeCheckReport(html, (rel) => existsSync14(join10(runDir, rel)));
|
|
7539
7840
|
process.stdout.write(`Acceptance report written
|
|
7540
7841
|
\u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210
|
|
@@ -8274,7 +8575,7 @@ import { spawnSync as spawnSync3 } from "node:child_process";
|
|
|
8274
8575
|
// packages/cli/dist/commands/setup-shared.js
|
|
8275
8576
|
import { copyFileSync, existsSync as existsSync18, lstatSync, mkdirSync as mkdirSync10, readFileSync as readFileSync13, readdirSync as readdirSync7, readlinkSync as readlinkSync2, realpathSync as realpathSync2, rmSync as rmSync6, statSync as statSync8, symlinkSync as symlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
|
|
8276
8577
|
import { homedir as homedir5 } from "node:os";
|
|
8277
|
-
import { basename as basename5, dirname as
|
|
8578
|
+
import { basename as basename5, dirname as dirname9, join as join12 } from "node:path";
|
|
8278
8579
|
function rollHome() {
|
|
8279
8580
|
return process.env["ROLL_HOME"] ?? join12(homedir5(), ".roll");
|
|
8280
8581
|
}
|
|
@@ -8322,7 +8623,7 @@ function canonicalDir(path) {
|
|
|
8322
8623
|
return null;
|
|
8323
8624
|
}
|
|
8324
8625
|
}
|
|
8325
|
-
function
|
|
8626
|
+
function agentBinNames3(agent) {
|
|
8326
8627
|
switch (agent) {
|
|
8327
8628
|
case "claude":
|
|
8328
8629
|
return ["claude"];
|
|
@@ -8359,7 +8660,7 @@ function onPath(bin) {
|
|
|
8359
8660
|
}
|
|
8360
8661
|
return false;
|
|
8361
8662
|
}
|
|
8362
|
-
function
|
|
8663
|
+
function agentInstalledByName3(agent, dir = "") {
|
|
8363
8664
|
const home = homedir5();
|
|
8364
8665
|
switch (agent) {
|
|
8365
8666
|
case "trae":
|
|
@@ -8379,7 +8680,7 @@ function agentInstalledByName2(agent, dir = "") {
|
|
|
8379
8680
|
default:
|
|
8380
8681
|
break;
|
|
8381
8682
|
}
|
|
8382
|
-
const bins =
|
|
8683
|
+
const bins = agentBinNames3(agent);
|
|
8383
8684
|
if (bins !== null) {
|
|
8384
8685
|
return bins.some((b) => onPath(b));
|
|
8385
8686
|
}
|
|
@@ -8388,13 +8689,13 @@ function agentInstalledByName2(agent, dir = "") {
|
|
|
8388
8689
|
function isAiInstalled(aiDir) {
|
|
8389
8690
|
let bn = basename5(aiDir).replace(/^\./, "");
|
|
8390
8691
|
if (bn === "agent" || bn === "workspace") {
|
|
8391
|
-
bn = basename5(
|
|
8692
|
+
bn = basename5(dirname9(aiDir)).replace(/^\./, "");
|
|
8392
8693
|
}
|
|
8393
8694
|
if (bn === "gemini")
|
|
8394
8695
|
bn = "agy";
|
|
8395
8696
|
if (bn === "kimi-code")
|
|
8396
8697
|
bn = "kimi";
|
|
8397
|
-
return
|
|
8698
|
+
return agentInstalledByName3(bn, aiDir);
|
|
8398
8699
|
}
|
|
8399
8700
|
function listDirEntries(dir) {
|
|
8400
8701
|
try {
|
|
@@ -8425,7 +8726,7 @@ function pruneDir(installedDir, sourceDir) {
|
|
|
8425
8726
|
function safeCopy(src, dst, force) {
|
|
8426
8727
|
if (!existsSync18(src))
|
|
8427
8728
|
return;
|
|
8428
|
-
mkdirSync10(
|
|
8729
|
+
mkdirSync10(dirname9(dst), { recursive: true });
|
|
8429
8730
|
if (existsSync18(dst) && !force) {
|
|
8430
8731
|
if (sameFile(src, dst))
|
|
8431
8732
|
return;
|
|
@@ -8602,7 +8903,7 @@ loop_dream_hour: 3
|
|
|
8602
8903
|
# loop_dream_minute: 10 # omit to auto-derive
|
|
8603
8904
|
primary_agent: claude
|
|
8604
8905
|
`;
|
|
8605
|
-
function
|
|
8906
|
+
function firstInstalledAgent2() {
|
|
8606
8907
|
for (const agent of [
|
|
8607
8908
|
"claude",
|
|
8608
8909
|
"codex",
|
|
@@ -8616,7 +8917,7 @@ function firstInstalledAgent() {
|
|
|
8616
8917
|
"trae",
|
|
8617
8918
|
"openclaw"
|
|
8618
8919
|
]) {
|
|
8619
|
-
if (
|
|
8920
|
+
if (agentInstalledByName3(agent))
|
|
8620
8921
|
return agent;
|
|
8621
8922
|
}
|
|
8622
8923
|
return null;
|
|
@@ -8641,7 +8942,7 @@ function installLocal(force) {
|
|
|
8641
8942
|
}
|
|
8642
8943
|
if (!existsSync18(cfg)) {
|
|
8643
8944
|
writeFileSync9(cfg, DEFAULT_CONFIG);
|
|
8644
|
-
const detected =
|
|
8945
|
+
const detected = firstInstalledAgent2();
|
|
8645
8946
|
if (detected !== null && detected !== "claude")
|
|
8646
8947
|
replacePrimaryAgent(detected);
|
|
8647
8948
|
}
|
|
@@ -8651,7 +8952,7 @@ function installLocal(force) {
|
|
|
8651
8952
|
function syncConventionForTool(src, mainDst, force) {
|
|
8652
8953
|
if (!existsSync18(src))
|
|
8653
8954
|
return;
|
|
8654
|
-
const dstDir =
|
|
8955
|
+
const dstDir = dirname9(mainDst);
|
|
8655
8956
|
if (dstDir !== join12(homedir5(), ".claude") && !isAiInstalled(dstDir) && !existsSync18(dstDir)) {
|
|
8656
8957
|
return;
|
|
8657
8958
|
}
|
|
@@ -10044,11 +10345,16 @@ function detectLiveCycle(rtDir, cycles, now, pidAlive = systemPidAlive) {
|
|
|
10044
10345
|
const hb = livenessVerdict(join15(rtDir, "heartbeat"), { now: () => nowSec2 });
|
|
10045
10346
|
if (!hb.alive)
|
|
10046
10347
|
return dead;
|
|
10047
|
-
const
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10348
|
+
const ZOMBIE_THRESHOLD_SEC = 2 * 3600;
|
|
10349
|
+
for (const cy of cycles) {
|
|
10350
|
+
if (cy.start === null || cy.end !== null)
|
|
10351
|
+
continue;
|
|
10352
|
+
const elapsedSec = Math.max(0, Math.trunc((now.getTime() - cy.start.getTime()) / 1e3));
|
|
10353
|
+
if (elapsedSec > ZOMBIE_THRESHOLD_SEC)
|
|
10354
|
+
continue;
|
|
10355
|
+
return { running: true, story: cy.story, elapsedSec };
|
|
10356
|
+
}
|
|
10357
|
+
return dead;
|
|
10052
10358
|
}
|
|
10053
10359
|
function loadRuns(slug) {
|
|
10054
10360
|
const rtDir = loopRuntimeDir(slug);
|
|
@@ -10785,8 +11091,17 @@ function nextCronHint(zh) {
|
|
|
10785
11091
|
const nxtSh = toShanghai(new Date(nxtEpoch));
|
|
10786
11092
|
return `${pad22(nxtSh.getUTCHours())}:${pad22(nxtSh.getUTCMinutes())} \xB7 in ${mins}m ${pad22(secs)}s`;
|
|
10787
11093
|
}
|
|
11094
|
+
function renderNow() {
|
|
11095
|
+
const v = process.env["ROLL_RENDER_NOW"] ?? "";
|
|
11096
|
+
if (v !== "") {
|
|
11097
|
+
const ms = /^\d+$/.test(v) ? Number(v) : Date.parse(v);
|
|
11098
|
+
if (!Number.isNaN(ms))
|
|
11099
|
+
return new Date(ms);
|
|
11100
|
+
}
|
|
11101
|
+
return /* @__PURE__ */ new Date();
|
|
11102
|
+
}
|
|
10788
11103
|
function fixtureData() {
|
|
10789
|
-
const now =
|
|
11104
|
+
const now = renderNow();
|
|
10790
11105
|
const events = [];
|
|
10791
11106
|
const cron = [];
|
|
10792
11107
|
let cycleId = 0;
|
|
@@ -10989,7 +11304,7 @@ function render(out2, events, cron, state, backlog, args) {
|
|
|
10989
11304
|
out2.push(" " + c("dim", agentLine));
|
|
10990
11305
|
let skillLine = "";
|
|
10991
11306
|
try {
|
|
10992
|
-
skillLine = selfScoreSummaryLine();
|
|
11307
|
+
skillLine = selfScoreSummaryLine(process.env["ROLL_NOTES_DIR"]);
|
|
10993
11308
|
} catch {
|
|
10994
11309
|
skillLine = "";
|
|
10995
11310
|
}
|
|
@@ -11380,7 +11695,7 @@ roll-loop-status.py: error: unrecognized arguments: ${args.unknown.join(" ")}
|
|
|
11380
11695
|
}
|
|
11381
11696
|
renderState.useColor = !args.noColor && (process.env["NO_COLOR"] ?? "") === "" && (process.stdout.isTTY === true || (process.env["FORCE_COLOR"] ?? "") !== "");
|
|
11382
11697
|
const lang4 = args.en ? "en" : args.zh ? "zh" : "both";
|
|
11383
|
-
const now =
|
|
11698
|
+
const now = renderNow();
|
|
11384
11699
|
let events;
|
|
11385
11700
|
let cron;
|
|
11386
11701
|
let state;
|
|
@@ -11727,7 +12042,7 @@ function loopRunsCommand(argv) {
|
|
|
11727
12042
|
|
|
11728
12043
|
// packages/cli/dist/commands/loop-signals.js
|
|
11729
12044
|
import { appendFileSync as appendFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
|
|
11730
|
-
import { dirname as
|
|
12045
|
+
import { dirname as dirname10, join as join17 } from "node:path";
|
|
11731
12046
|
function lang2() {
|
|
11732
12047
|
return resolveLang({ rollLang: process.env["ROLL_LANG"], lcAll: process.env["LC_ALL"], lang: process.env["LANG"] });
|
|
11733
12048
|
}
|
|
@@ -11782,7 +12097,7 @@ function loopSignalsCommand(argv) {
|
|
|
11782
12097
|
const seenFile = join17(rtDir, `signals-seen-${slug}`);
|
|
11783
12098
|
const candFile = join17(projectPath, ".roll", "signals", "candidates.md");
|
|
11784
12099
|
mkdirSync11(rtDir, { recursive: true });
|
|
11785
|
-
mkdirSync11(
|
|
12100
|
+
mkdirSync11(dirname10(candFile), { recursive: true });
|
|
11786
12101
|
if (!existsSync22(seenFile))
|
|
11787
12102
|
writeFileSync10(seenFile, "");
|
|
11788
12103
|
let lastId = 0;
|
|
@@ -12040,7 +12355,7 @@ var out = { lines: [] };
|
|
|
12040
12355
|
function emit(line) {
|
|
12041
12356
|
out.lines.push(line);
|
|
12042
12357
|
}
|
|
12043
|
-
function
|
|
12358
|
+
function agentBinNames4(agent) {
|
|
12044
12359
|
switch (agent) {
|
|
12045
12360
|
case "claude":
|
|
12046
12361
|
return ["claude"];
|
|
@@ -12077,7 +12392,7 @@ function commandOnPath2(bin) {
|
|
|
12077
12392
|
}
|
|
12078
12393
|
return false;
|
|
12079
12394
|
}
|
|
12080
|
-
function
|
|
12395
|
+
function agentInstalledByName4(agent, dir) {
|
|
12081
12396
|
const home = homedir9();
|
|
12082
12397
|
switch (agent) {
|
|
12083
12398
|
case "trae":
|
|
@@ -12096,7 +12411,7 @@ function agentInstalledByName3(agent, dir) {
|
|
|
12096
12411
|
case "openclaw":
|
|
12097
12412
|
return existsSync24(join19(home, ".openclaw", "workspace"));
|
|
12098
12413
|
default: {
|
|
12099
|
-
const bins =
|
|
12414
|
+
const bins = agentBinNames4(agent);
|
|
12100
12415
|
if (bins !== null)
|
|
12101
12416
|
return bins.some(commandOnPath2);
|
|
12102
12417
|
return dir !== "" && existsSync24(dir) && safeIsDir(dir);
|
|
@@ -12145,7 +12460,7 @@ function agentSection(p) {
|
|
|
12145
12460
|
dir = dir.replace(/^ /, "");
|
|
12146
12461
|
if (dir.startsWith("~"))
|
|
12147
12462
|
dir = home + dir.slice(1);
|
|
12148
|
-
const installed =
|
|
12463
|
+
const installed = agentInstalledByName4(name, dir) ? t(v2Catalog, msgLang6(), "doctor.agent_installed") : t(v2Catalog, msgLang6(), "doctor.agent_missing");
|
|
12149
12464
|
const dirExists = safeIsDir(dir) ? t(v2Catalog, msgLang6(), "doctor.agent_dir_exists") : t(v2Catalog, msgLang6(), "doctor.agent_dir_missing");
|
|
12150
12465
|
const tag = name === primary ? ` (${t(v2Catalog, msgLang6(), "doctor.agent_primary_label")})` : "";
|
|
12151
12466
|
emit(` ${padEnd(name, 10)} ${padEnd(installed, 14)} ${dirExists}${tag}`);
|
|
@@ -12351,16 +12666,8 @@ function killLiveAgents(signal2 = "SIGKILL") {
|
|
|
12351
12666
|
}
|
|
12352
12667
|
var AUTORUN_DIRECTIVE = "[roll \u81EA\u4E3B\u6A21\u5F0F] \u4F60\u6B63\u5728\u65E0\u4EBA\u503C\u5B88\u7684\u81EA\u52A8\u5316\u5FAA\u73AF\u4E2D\u8FD0\u884C,\u8FD9\u4E0D\u662F\u5BF9\u8BDD\u3002\u8BF7\u7ACB\u5373\u3001\u5B8C\u6574\u5730\u6267\u884C\u4E0B\u9762\u8FD9\u4EFD\u6280\u80FD\u6587\u6863\u63CF\u8FF0\u7684\u5DE5\u4F5C\u6D41,\u76F4\u5230\u5B8C\u6210\u4EA4\u4ED8\u6216\u5199\u51FA ALERT \u4E3A\u6B62;\u4E25\u7981\u53CD\u95EE\u3001\u4E25\u7981\u7B49\u5F85\u786E\u8BA4\u3001\u4E25\u7981\u53EA\u590D\u8FF0\u6216\u603B\u7ED3\u800C\u4E0D\u52A8\u624B\u3002\u6280\u80FD\u6587\u6863\u5982\u4E0B: ";
|
|
12353
12668
|
var AGENT_ARGV_TODO = {
|
|
12354
|
-
kimi: "kimi-code|kimi-cli|kimi -p <prompt> (FIX-126/133; no stream-json splice)",
|
|
12355
|
-
pi: "pi -p <prompt> (text mode; no usage on stdout \u2014 cost via session file)",
|
|
12356
|
-
deepseek: "deepseek <prompt> (positional)",
|
|
12357
|
-
codex: "codex exec <prompt>",
|
|
12358
12669
|
openai: "codex exec <prompt>",
|
|
12359
|
-
opencode: "opencode run <prompt>"
|
|
12360
|
-
gemini: "agy -p --dangerously-skip-permissions <prompt> (FIX-153)",
|
|
12361
|
-
agy: "agy -p --dangerously-skip-permissions <prompt> (FIX-153)",
|
|
12362
|
-
antigravity: "agy -p --dangerously-skip-permissions <prompt> (FIX-153)",
|
|
12363
|
-
qwen: "qwen <prompt> (positional)"
|
|
12670
|
+
opencode: "opencode run <prompt>"
|
|
12364
12671
|
};
|
|
12365
12672
|
function storyPinDirective(storyId) {
|
|
12366
12673
|
return `[\u672C\u5468\u671F\u6307\u5B9A\u6545\u4E8B] \u8C03\u5EA6\u5668\u5DF2\u9501\u5B9A ${storyId} \u5E76\u5728 backlog \u6807\u8BB0 \u{1F528} In Progress\u2014\u2014\u53EA\u6267\u884C\u8FD9\u4E00\u4E2A\u6545\u4E8B,\u4E25\u7981\u91CD\u65B0\u6311\u9009\u6216\u987A\u624B\u505A\u522B\u7684;\u82E5\u5B83\u786E\u5B9E\u4E0D\u53EF\u6267\u884C,\u5199 ALERT \u8BF4\u660E\u539F\u56E0\u540E\u5E72\u51C0\u9000\u51FA\u3002
|
|
@@ -12371,7 +12678,7 @@ function buildClaudeArgv(input) {
|
|
|
12371
12678
|
const bin = input.bin ?? "claude";
|
|
12372
12679
|
const pin = input.storyId !== void 0 && input.storyId !== "" ? storyPinDirective(input.storyId) : "";
|
|
12373
12680
|
const prompt = `${AUTORUN_DIRECTIVE}${pin}${input.skillBody}`;
|
|
12374
|
-
const args = [
|
|
12681
|
+
const args = input.interactive ? ["-p", prompt, "--dangerously-skip-permissions", "--add-dir", input.worktree] : [
|
|
12375
12682
|
"-p",
|
|
12376
12683
|
prompt,
|
|
12377
12684
|
"--verbose",
|
|
@@ -12383,20 +12690,42 @@ function buildClaudeArgv(input) {
|
|
|
12383
12690
|
];
|
|
12384
12691
|
return { bin, args };
|
|
12385
12692
|
}
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12693
|
+
function buildSpawnCommand(agent, opts) {
|
|
12694
|
+
const prompt = `${AUTORUN_DIRECTIVE}${opts.storyId !== void 0 && opts.storyId !== "" ? storyPinDirective(opts.storyId) : ""}${opts.skillBody}`;
|
|
12695
|
+
if (agent === "claude") {
|
|
12696
|
+
return buildClaudeArgv({
|
|
12697
|
+
worktree: opts.cwd,
|
|
12698
|
+
skillBody: opts.skillBody,
|
|
12699
|
+
...opts.storyId !== void 0 ? { storyId: opts.storyId } : {},
|
|
12700
|
+
bin: opts.bin,
|
|
12701
|
+
interactive: opts.interactive
|
|
12702
|
+
});
|
|
12390
12703
|
}
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
bin: opts.bin
|
|
12396
|
-
}
|
|
12704
|
+
if (agent === "pi") {
|
|
12705
|
+
return { bin: opts.bin ?? "pi", args: ["-p", prompt] };
|
|
12706
|
+
}
|
|
12707
|
+
if (agent === "kimi") {
|
|
12708
|
+
return { bin: opts.bin ?? "kimi", args: ["-p", prompt] };
|
|
12709
|
+
}
|
|
12710
|
+
if (agent === "codex") {
|
|
12711
|
+
return { bin: opts.bin ?? "codex", args: ["exec", prompt] };
|
|
12712
|
+
}
|
|
12713
|
+
if (agent === "deepseek") {
|
|
12714
|
+
return { bin: opts.bin ?? "deepseek", args: [prompt] };
|
|
12715
|
+
}
|
|
12716
|
+
if (agent === "qwen") {
|
|
12717
|
+
return { bin: opts.bin ?? "qwen", args: [prompt] };
|
|
12718
|
+
}
|
|
12719
|
+
if (agent === "agy" || agent === "gemini" || agent === "antigravity") {
|
|
12720
|
+
return { bin: opts.bin ?? "agy", args: ["-p", "--dangerously-skip-permissions", prompt] };
|
|
12721
|
+
}
|
|
12722
|
+
const hint = AGENT_ARGV_TODO[agent] ?? "unknown agent";
|
|
12723
|
+
throw new Error(`runner: agent '${agent}' argv not yet ported. v2 shape: ${hint}`);
|
|
12724
|
+
}
|
|
12725
|
+
function spawnAndWait(bin, args, opts) {
|
|
12397
12726
|
process.stderr.write(`[runner] spawn ${bin} argv=${JSON.stringify(args.map((a) => a.length > 80 ? `${a.slice(0, 77)}...` : a))}
|
|
12398
12727
|
`);
|
|
12399
|
-
return new Promise((resolve3
|
|
12728
|
+
return new Promise((resolve3) => {
|
|
12400
12729
|
const child = spawn(bin, args, {
|
|
12401
12730
|
cwd: opts.cwd,
|
|
12402
12731
|
env: opts.env ?? process.env,
|
|
@@ -12442,16 +12771,22 @@ var realAgentSpawn = (agent, opts) => {
|
|
|
12442
12771
|
settle({ stdout, stderr, exitCode: code ?? 1, timedOut });
|
|
12443
12772
|
});
|
|
12444
12773
|
});
|
|
12774
|
+
}
|
|
12775
|
+
var realAgentSpawn = (agent, opts) => {
|
|
12776
|
+
const { bin, args } = buildSpawnCommand(agent, opts);
|
|
12777
|
+
return spawnAndWait(bin, args, opts);
|
|
12445
12778
|
};
|
|
12446
12779
|
|
|
12447
12780
|
// packages/cli/dist/runner/skill-body.js
|
|
12448
12781
|
import { existsSync as existsSync25, readFileSync as readFileSync20 } from "node:fs";
|
|
12782
|
+
import { homedir as homedir10 } from "node:os";
|
|
12449
12783
|
import { join as join20 } from "node:path";
|
|
12450
12784
|
function readSkillBody(projectPath, opts) {
|
|
12451
12785
|
const candidates = [
|
|
12452
12786
|
opts.envOverride ?? "",
|
|
12453
12787
|
join20(projectPath, ".roll", "skills", opts.skillName, "SKILL.md"),
|
|
12454
|
-
join20(projectPath, "skills", opts.skillName, "SKILL.md")
|
|
12788
|
+
join20(projectPath, "skills", opts.skillName, "SKILL.md"),
|
|
12789
|
+
join20(homedir10(), ".roll", "skills", opts.skillName, "SKILL.md")
|
|
12455
12790
|
].filter((p) => p !== "");
|
|
12456
12791
|
for (const p of candidates) {
|
|
12457
12792
|
if (!existsSync25(p))
|
|
@@ -12527,7 +12862,7 @@ dream run-once: \u627E\u4E0D\u5230 roll-.dream SKILL.md \u2014 \u62D2\u7EDD\u76F
|
|
|
12527
12862
|
// packages/cli/dist/commands/feedback.js
|
|
12528
12863
|
import { execFileSync as execFileSync6, spawnSync as spawnSync4 } from "node:child_process";
|
|
12529
12864
|
import { existsSync as existsSync26, readFileSync as readFileSync22 } from "node:fs";
|
|
12530
|
-
import { homedir as
|
|
12865
|
+
import { homedir as homedir11 } from "node:os";
|
|
12531
12866
|
import { basename as basename8, join as join23 } from "node:path";
|
|
12532
12867
|
|
|
12533
12868
|
// packages/cli/dist/commands/version.js
|
|
@@ -12571,7 +12906,7 @@ function err8(line) {
|
|
|
12571
12906
|
`);
|
|
12572
12907
|
}
|
|
12573
12908
|
function projectAgent2() {
|
|
12574
|
-
const
|
|
12909
|
+
const readField2 = (file, key) => {
|
|
12575
12910
|
if (!existsSync26(file))
|
|
12576
12911
|
return void 0;
|
|
12577
12912
|
let text;
|
|
@@ -12590,14 +12925,14 @@ function projectAgent2() {
|
|
|
12590
12925
|
};
|
|
12591
12926
|
const local = ".roll/local.yaml";
|
|
12592
12927
|
if (existsSync26(local) && /^agent:/m.test(safeRead(local))) {
|
|
12593
|
-
return
|
|
12928
|
+
return readField2(local, /^agent:/) ?? "claude";
|
|
12594
12929
|
}
|
|
12595
12930
|
if (existsSync26(".roll.yaml") && /^agent:/m.test(safeRead(".roll.yaml"))) {
|
|
12596
|
-
return
|
|
12931
|
+
return readField2(".roll.yaml", /^agent:/) ?? "claude";
|
|
12597
12932
|
}
|
|
12598
12933
|
const cfg = rollConfig2();
|
|
12599
12934
|
if (existsSync26(cfg) && /primary_agent:/.test(safeRead(cfg))) {
|
|
12600
|
-
return
|
|
12935
|
+
return readField2(cfg, /primary_agent:/) ?? "claude";
|
|
12601
12936
|
}
|
|
12602
12937
|
return "claude";
|
|
12603
12938
|
}
|
|
@@ -12609,7 +12944,7 @@ function safeRead(p) {
|
|
|
12609
12944
|
}
|
|
12610
12945
|
}
|
|
12611
12946
|
function rollConfig2() {
|
|
12612
|
-
const rollHome4 = process.env["ROLL_HOME"] ?? join23(
|
|
12947
|
+
const rollHome4 = process.env["ROLL_HOME"] ?? join23(homedir11(), ".roll");
|
|
12613
12948
|
return join23(rollHome4, "config.yaml");
|
|
12614
12949
|
}
|
|
12615
12950
|
function feedbackYamlField(file, field) {
|
|
@@ -12656,7 +12991,7 @@ function feedbackDefaultRepo() {
|
|
|
12656
12991
|
let v = feedbackYamlField(".roll/local.yaml", "feedback_repo");
|
|
12657
12992
|
if (v)
|
|
12658
12993
|
return v;
|
|
12659
|
-
v = feedbackYamlField(join23(
|
|
12994
|
+
v = feedbackYamlField(join23(homedir11(), ".roll", "config.yaml"), "feedback_repo");
|
|
12660
12995
|
if (v)
|
|
12661
12996
|
return v;
|
|
12662
12997
|
return feedbackOriginRepo();
|
|
@@ -12922,7 +13257,8 @@ function gcCommand(args, deps = {}) {
|
|
|
12922
13257
|
}
|
|
12923
13258
|
|
|
12924
13259
|
// packages/cli/dist/commands/idea.js
|
|
12925
|
-
import { existsSync as existsSync28 } from "node:fs";
|
|
13260
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "node:fs";
|
|
13261
|
+
import { join as join25 } from "node:path";
|
|
12926
13262
|
var BACKLOG_PATH2 = ".roll/backlog.md";
|
|
12927
13263
|
function label2(lang4, key, ...args) {
|
|
12928
13264
|
if (v3Catalog[key] !== void 0)
|
|
@@ -12989,29 +13325,127 @@ ${c("green", "\u{1F4DD} " + label2(lang4, "ideav3.recorded", plan.id))}
|
|
|
12989
13325
|
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.text") + ":")} ${text}
|
|
12990
13326
|
|
|
12991
13327
|
`);
|
|
13328
|
+
const projectPath = process.cwd();
|
|
13329
|
+
const cardDir = join25(projectPath, ".roll", "features", "uncategorized", plan.id);
|
|
13330
|
+
try {
|
|
13331
|
+
mkdirSync13(cardDir, { recursive: true });
|
|
13332
|
+
const card = {
|
|
13333
|
+
id: plan.id,
|
|
13334
|
+
title: text,
|
|
13335
|
+
type: plan.kind,
|
|
13336
|
+
created: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
13337
|
+
};
|
|
13338
|
+
writeFileSync13(join25(cardDir, "spec.md"), renderSpecMd(card), "utf8");
|
|
13339
|
+
writeFileSync13(join25(cardDir, "index.html"), renderStoryPage(card), "utf8");
|
|
13340
|
+
} catch {
|
|
13341
|
+
}
|
|
12992
13342
|
return 0;
|
|
12993
13343
|
}
|
|
12994
13344
|
|
|
12995
13345
|
// packages/cli/dist/commands/index-gen.js
|
|
13346
|
+
import { existsSync as existsSync29, readdirSync as readdirSync14, statSync as statSync14, writeFileSync as writeFileSync14 } from "node:fs";
|
|
13347
|
+
import { join as join26 } from "node:path";
|
|
12996
13348
|
function indexCommand(args) {
|
|
12997
13349
|
if (args.includes("--help") || args.includes("-h")) {
|
|
12998
|
-
process.stdout.write("Usage: roll index\n Regenerate .roll/index.json
|
|
13350
|
+
process.stdout.write("Usage: roll index\n Regenerate .roll/index.json + .roll/features/index.html\n");
|
|
12999
13351
|
return 0;
|
|
13000
13352
|
}
|
|
13001
|
-
const
|
|
13353
|
+
const cwd = process.cwd();
|
|
13354
|
+
const stories = generateIndex(cwd);
|
|
13002
13355
|
const n = Object.keys(stories).length;
|
|
13003
13356
|
process.stdout.write(`index.json regenerated
|
|
13004
13357
|
\u7D22\u5F15\u5DF2\u91CD\u5EFA
|
|
13005
13358
|
${n} stories mapped to epics (.roll/index.json)
|
|
13006
13359
|
`);
|
|
13360
|
+
const featuresDir = join26(cwd, ".roll", "features");
|
|
13361
|
+
if (existsSync29(featuresDir)) {
|
|
13362
|
+
const epics = /* @__PURE__ */ new Map();
|
|
13363
|
+
for (const [sid, epic] of Object.entries(stories)) {
|
|
13364
|
+
if (!epics.has(epic))
|
|
13365
|
+
epics.set(epic, /* @__PURE__ */ new Set());
|
|
13366
|
+
epics.get(epic).add(sid);
|
|
13367
|
+
}
|
|
13368
|
+
try {
|
|
13369
|
+
for (const d of readdirSync14(featuresDir)) {
|
|
13370
|
+
const epicDir = join26(featuresDir, d);
|
|
13371
|
+
if (!statSync14(epicDir).isDirectory())
|
|
13372
|
+
continue;
|
|
13373
|
+
if (!epics.has(d))
|
|
13374
|
+
epics.set(d, /* @__PURE__ */ new Set());
|
|
13375
|
+
for (const s of readdirSync14(epicDir)) {
|
|
13376
|
+
const storyDir = join26(epicDir, s);
|
|
13377
|
+
if (!statSync14(storyDir).isDirectory())
|
|
13378
|
+
continue;
|
|
13379
|
+
if (STORY_ID_RE.test(s))
|
|
13380
|
+
epics.get(d).add(s);
|
|
13381
|
+
}
|
|
13382
|
+
}
|
|
13383
|
+
} catch {
|
|
13384
|
+
}
|
|
13385
|
+
const rows = [];
|
|
13386
|
+
for (const epic of [...epics.keys()].sort()) {
|
|
13387
|
+
const unique = [...epics.get(epic)].sort();
|
|
13388
|
+
let delivered = 0;
|
|
13389
|
+
for (const s of unique) {
|
|
13390
|
+
const latest = join26(featuresDir, epic, s, "latest");
|
|
13391
|
+
try {
|
|
13392
|
+
if (statSync14(latest).isDirectory())
|
|
13393
|
+
delivered++;
|
|
13394
|
+
} catch {
|
|
13395
|
+
}
|
|
13396
|
+
}
|
|
13397
|
+
const links = unique.slice(0, 20).map((s) => `<a href="${epic}/${s}/index.html">${s}</a>`).join(", ");
|
|
13398
|
+
const more = unique.length > 20 ? ` ${bi(`\u2026 +${unique.length - 20} more`, `\u2026 \u53E6 ${unique.length - 20} \u4E2A`)}` : "";
|
|
13399
|
+
rows.push(`<tr>
|
|
13400
|
+
<td><strong><a href="${epic}/">${epic}</a></strong></td>
|
|
13401
|
+
<td>${bi(`${unique.length} stories (${delivered} delivered)`, `${unique.length} \u4E2A\u6545\u4E8B\uFF08\u5DF2\u4EA4\u4ED8 ${delivered}\uFF09`)}</td>
|
|
13402
|
+
<td style="font-size:0.85em">${links}${more}</td>
|
|
13403
|
+
</tr>`);
|
|
13404
|
+
}
|
|
13405
|
+
const html = `<!DOCTYPE html>
|
|
13406
|
+
<html lang="zh-CN">
|
|
13407
|
+
<head>
|
|
13408
|
+
<meta charset="UTF-8">
|
|
13409
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
13410
|
+
<title>Roll Features Index \xB7 \u529F\u80FD\u6863\u6848</title>
|
|
13411
|
+
<style>
|
|
13412
|
+
${CHROME_CSS}body { max-width:1000px; }
|
|
13413
|
+
table { width:100%; border-collapse:collapse; position:relative; z-index:2; }
|
|
13414
|
+
th, td { padding:8px 12px; text-align:left; border-bottom:1px solid var(--line); vertical-align:top; }
|
|
13415
|
+
th { font-family:var(--serif); letter-spacing:.04em; color:var(--muted); }
|
|
13416
|
+
tr:hover td { background:var(--bg-raise); }
|
|
13417
|
+
td code, td strong a { font-family:var(--mono); }
|
|
13418
|
+
</style>
|
|
13419
|
+
${CHROME_SCRIPT}
|
|
13420
|
+
</head>
|
|
13421
|
+
<body>
|
|
13422
|
+
${CHROME_CONTROLS}
|
|
13423
|
+
<p class="kicker">Roll \xB7 ${bi("Delivery Dossier", "\u4EA4\u4ED8\u6863\u6848")}</p>
|
|
13424
|
+
<h1>${bi("Features Index", "\u529F\u80FD\u6863\u6848")}</h1>
|
|
13425
|
+
<p class="meta">${bi(`${epics.size} epics \xB7 ${n} stories`, `${epics.size} \u4E2A\u53F2\u8BD7 \xB7 ${n} \u4E2A\u6545\u4E8B`)}</p>
|
|
13426
|
+
<table><thead><tr><th>Epic</th><th>${bi("Stories", "\u6545\u4E8B")}</th><th>${bi("Recent", "\u6700\u8FD1")}</th></tr></thead>
|
|
13427
|
+
<tbody>
|
|
13428
|
+
${rows.join("\n")}
|
|
13429
|
+
</tbody></table>
|
|
13430
|
+
<footer>${bi("Generated by", "\u751F\u6210\u81EA")} <code>roll index</code></footer>
|
|
13431
|
+
</body>
|
|
13432
|
+
</html>
|
|
13433
|
+
`;
|
|
13434
|
+
try {
|
|
13435
|
+
writeFileSync14(join26(featuresDir, "index.html"), html, "utf8");
|
|
13436
|
+
process.stdout.write(`features/index.html regenerated
|
|
13437
|
+
`);
|
|
13438
|
+
} catch {
|
|
13439
|
+
}
|
|
13440
|
+
}
|
|
13007
13441
|
return 0;
|
|
13008
13442
|
}
|
|
13009
13443
|
|
|
13010
13444
|
// packages/cli/dist/commands/init.js
|
|
13011
13445
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
13012
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
13013
|
-
import { homedir as
|
|
13014
|
-
import { dirname as
|
|
13446
|
+
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";
|
|
13447
|
+
import { homedir as homedir12 } from "node:os";
|
|
13448
|
+
import { dirname as dirname11, join as join27 } from "node:path";
|
|
13015
13449
|
function err9(line) {
|
|
13016
13450
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
13017
13451
|
const RED = noColor2 ? "" : "\x1B[0;31m";
|
|
@@ -13020,19 +13454,19 @@ function err9(line) {
|
|
|
13020
13454
|
`);
|
|
13021
13455
|
}
|
|
13022
13456
|
function rollHome2() {
|
|
13023
|
-
return process.env["ROLL_HOME"] ??
|
|
13457
|
+
return process.env["ROLL_HOME"] ?? join27(homedir12(), ".roll");
|
|
13024
13458
|
}
|
|
13025
13459
|
function rollGlobal2() {
|
|
13026
|
-
return
|
|
13460
|
+
return join27(rollHome2(), "conventions", "global");
|
|
13027
13461
|
}
|
|
13028
13462
|
function rollTemplates2() {
|
|
13029
|
-
return
|
|
13463
|
+
return join27(rollHome2(), "conventions", "templates");
|
|
13030
13464
|
}
|
|
13031
13465
|
function scanProjectType(dir) {
|
|
13032
13466
|
let hasFrontend = false;
|
|
13033
13467
|
let hasBackend = false;
|
|
13034
13468
|
let hasCli = false;
|
|
13035
|
-
const pkg =
|
|
13469
|
+
const pkg = join27(dir, "package.json");
|
|
13036
13470
|
const readPkg = () => {
|
|
13037
13471
|
try {
|
|
13038
13472
|
return readFileSync23(pkg, "utf8");
|
|
@@ -13040,23 +13474,23 @@ function scanProjectType(dir) {
|
|
|
13040
13474
|
return "";
|
|
13041
13475
|
}
|
|
13042
13476
|
};
|
|
13043
|
-
if (
|
|
13477
|
+
if (existsSync30(pkg)) {
|
|
13044
13478
|
if (/"react"|"vue"|"next"|"nuxt"|"vite"|"svelte"/i.test(readPkg()))
|
|
13045
13479
|
hasFrontend = true;
|
|
13046
13480
|
}
|
|
13047
|
-
if (["src", "app", "pages", "components"].some((d) =>
|
|
13481
|
+
if (["src", "app", "pages", "components"].some((d) => existsSync30(join27(dir, d))))
|
|
13048
13482
|
hasFrontend = true;
|
|
13049
|
-
if (["server", "api", "backend"].some((d) =>
|
|
13483
|
+
if (["server", "api", "backend"].some((d) => existsSync30(join27(dir, d))))
|
|
13050
13484
|
hasBackend = true;
|
|
13051
|
-
if (["go.mod", "main.go", "main.py", "app.py", "Cargo.toml", "requirements.txt", "pyproject.toml"].some((f) =>
|
|
13485
|
+
if (["go.mod", "main.go", "main.py", "app.py", "Cargo.toml", "requirements.txt", "pyproject.toml"].some((f) => existsSync30(join27(dir, f))))
|
|
13052
13486
|
hasBackend = true;
|
|
13053
|
-
if (
|
|
13487
|
+
if (existsSync30(pkg)) {
|
|
13054
13488
|
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
13489
|
hasBackend = true;
|
|
13056
13490
|
}
|
|
13057
|
-
if (
|
|
13491
|
+
if (existsSync30(join27(dir, "prisma", "schema.prisma")))
|
|
13058
13492
|
hasBackend = true;
|
|
13059
|
-
if (
|
|
13493
|
+
if (existsSync30(join27(dir, "bin")) || existsSync30(join27(dir, "cmd")))
|
|
13060
13494
|
hasCli = true;
|
|
13061
13495
|
if (hasFrontend && hasBackend)
|
|
13062
13496
|
return "fullstack";
|
|
@@ -13076,8 +13510,8 @@ function countNonEmptyFiles(dir) {
|
|
|
13076
13510
|
}
|
|
13077
13511
|
function isLegacyProject(projectDir) {
|
|
13078
13512
|
for (const dir of ["src", "app", "lib", "pkg", "cmd"]) {
|
|
13079
|
-
const p =
|
|
13080
|
-
if (
|
|
13513
|
+
const p = join27(projectDir, dir);
|
|
13514
|
+
if (existsSync30(p) && statSync15(p).isDirectory()) {
|
|
13081
13515
|
if (countNonEmptyFiles(p) >= 10)
|
|
13082
13516
|
return true;
|
|
13083
13517
|
}
|
|
@@ -13107,12 +13541,12 @@ function isLegacyProject(projectDir) {
|
|
|
13107
13541
|
"deno.jsonc"
|
|
13108
13542
|
];
|
|
13109
13543
|
for (const man of manifests)
|
|
13110
|
-
if (
|
|
13544
|
+
if (existsSync30(join27(projectDir, man)))
|
|
13111
13545
|
return true;
|
|
13112
13546
|
const tf = spawnSync5("bash", ["-c", `compgen -G '${projectDir.replace(/'/g, "'\\''")}/*.tf' >/dev/null 2>&1`]);
|
|
13113
13547
|
if (tf.status === 0)
|
|
13114
13548
|
return true;
|
|
13115
|
-
if (
|
|
13549
|
+
if (existsSync30(join27(projectDir, ".git"))) {
|
|
13116
13550
|
const g = spawnSync5("git", ["rev-parse", "--verify", "HEAD"], { cwd: projectDir, stdio: "ignore" });
|
|
13117
13551
|
if (g.status === 0)
|
|
13118
13552
|
return true;
|
|
@@ -13120,9 +13554,9 @@ function isLegacyProject(projectDir) {
|
|
|
13120
13554
|
return false;
|
|
13121
13555
|
}
|
|
13122
13556
|
function mergeGlobalToProject(projectDir, summary) {
|
|
13123
|
-
const src =
|
|
13124
|
-
const dst =
|
|
13125
|
-
if (!
|
|
13557
|
+
const src = join27(rollGlobal2(), "AGENTS.md");
|
|
13558
|
+
const dst = join27(projectDir, "AGENTS.md");
|
|
13559
|
+
if (!existsSync30(src)) {
|
|
13126
13560
|
return;
|
|
13127
13561
|
}
|
|
13128
13562
|
const projectType = scanProjectType(projectDir);
|
|
@@ -13131,7 +13565,7 @@ function mergeGlobalToProject(projectDir, summary) {
|
|
|
13131
13565
|
const srcText = readFileSync23(src, "utf8");
|
|
13132
13566
|
const srcLines = srcText.split("\n");
|
|
13133
13567
|
const lines = srcText.endsWith("\n") ? srcLines.slice(0, -1) : srcLines;
|
|
13134
|
-
if (!
|
|
13568
|
+
if (!existsSync30(dst)) {
|
|
13135
13569
|
let out2 = "";
|
|
13136
13570
|
let fcH = "";
|
|
13137
13571
|
let fcB = "";
|
|
@@ -13160,7 +13594,7 @@ ${fcB}`;
|
|
|
13160
13594
|
}
|
|
13161
13595
|
}
|
|
13162
13596
|
flush();
|
|
13163
|
-
|
|
13597
|
+
writeFileSync15(dst, out2);
|
|
13164
13598
|
summary.push("created|AGENTS.md");
|
|
13165
13599
|
return;
|
|
13166
13600
|
}
|
|
@@ -13194,7 +13628,7 @@ ${curB}`;
|
|
|
13194
13628
|
}
|
|
13195
13629
|
tryAppend();
|
|
13196
13630
|
if (appendBuffer !== "")
|
|
13197
|
-
|
|
13631
|
+
writeFileSync15(dst, dstText + appendBuffer);
|
|
13198
13632
|
if (added > 0)
|
|
13199
13633
|
summary.push("merged|AGENTS.md");
|
|
13200
13634
|
else
|
|
@@ -13202,13 +13636,13 @@ ${curB}`;
|
|
|
13202
13636
|
}
|
|
13203
13637
|
function mergeClaudeToProject(projectDir, summary) {
|
|
13204
13638
|
const projectType = scanProjectType(projectDir);
|
|
13205
|
-
const tplFile =
|
|
13206
|
-
if (!
|
|
13639
|
+
const tplFile = join27(rollTemplates2(), projectType, "CLAUDE.md");
|
|
13640
|
+
if (!existsSync30(tplFile))
|
|
13207
13641
|
return;
|
|
13208
|
-
const claudeDir =
|
|
13209
|
-
const outFile =
|
|
13210
|
-
|
|
13211
|
-
if (!
|
|
13642
|
+
const claudeDir = join27(projectDir, ".claude");
|
|
13643
|
+
const outFile = join27(claudeDir, "CLAUDE.md");
|
|
13644
|
+
mkdirSync14(claudeDir, { recursive: true });
|
|
13645
|
+
if (!existsSync30(outFile)) {
|
|
13212
13646
|
copyFileSync2(tplFile, outFile);
|
|
13213
13647
|
summary.push("created|.claude/CLAUDE.md");
|
|
13214
13648
|
return;
|
|
@@ -13240,7 +13674,7 @@ ${curB}`;
|
|
|
13240
13674
|
}
|
|
13241
13675
|
tryAppend();
|
|
13242
13676
|
if (appendBuffer !== "")
|
|
13243
|
-
|
|
13677
|
+
writeFileSync15(outFile, outText + appendBuffer);
|
|
13244
13678
|
if (added > 0)
|
|
13245
13679
|
summary.push("merged|.claude/CLAUDE.md");
|
|
13246
13680
|
else
|
|
@@ -13257,20 +13691,20 @@ var BACKLOG_TEMPLATE = `# Project Backlog
|
|
|
13257
13691
|
|----|---------|--------|
|
|
13258
13692
|
`;
|
|
13259
13693
|
function writeBacklog(path, summary) {
|
|
13260
|
-
if (
|
|
13694
|
+
if (existsSync30(path)) {
|
|
13261
13695
|
summary.push("unchanged|.roll/backlog.md");
|
|
13262
13696
|
return;
|
|
13263
13697
|
}
|
|
13264
|
-
|
|
13265
|
-
|
|
13698
|
+
mkdirSync14(dirname11(path), { recursive: true });
|
|
13699
|
+
writeFileSync15(path, BACKLOG_TEMPLATE);
|
|
13266
13700
|
summary.push("created|.roll/backlog.md");
|
|
13267
13701
|
}
|
|
13268
13702
|
function ensureFeaturesDir(path, summary) {
|
|
13269
|
-
if (
|
|
13703
|
+
if (existsSync30(path) && statSync15(path).isDirectory()) {
|
|
13270
13704
|
summary.push("unchanged|.roll/features/");
|
|
13271
13705
|
return;
|
|
13272
13706
|
}
|
|
13273
|
-
|
|
13707
|
+
mkdirSync14(path, { recursive: true });
|
|
13274
13708
|
summary.push("created|.roll/features/");
|
|
13275
13709
|
}
|
|
13276
13710
|
var FEATURES_TEMPLATE = `# Features
|
|
@@ -13284,38 +13718,38 @@ var FEATURES_TEMPLATE = `# Features
|
|
|
13284
13718
|
<!-- Add feature entries here as epics are completed -->
|
|
13285
13719
|
`;
|
|
13286
13720
|
function writeFeaturesMd(path, summary) {
|
|
13287
|
-
if (
|
|
13721
|
+
if (existsSync30(path)) {
|
|
13288
13722
|
summary.push("unchanged|.roll/features.md");
|
|
13289
13723
|
return;
|
|
13290
13724
|
}
|
|
13291
|
-
|
|
13292
|
-
|
|
13725
|
+
mkdirSync14(dirname11(path), { recursive: true });
|
|
13726
|
+
writeFileSync15(path, FEATURES_TEMPLATE);
|
|
13293
13727
|
summary.push("created|.roll/features.md");
|
|
13294
13728
|
}
|
|
13295
13729
|
function initSeedAgentRoutes(templateName, projectDir, summary) {
|
|
13296
|
-
const dest =
|
|
13297
|
-
if (
|
|
13730
|
+
const dest = join27(projectDir, ".roll", "agent-routes.yaml");
|
|
13731
|
+
if (existsSync30(dest)) {
|
|
13298
13732
|
summary.push("unchanged|.roll/agent-routes.yaml");
|
|
13299
13733
|
return 0;
|
|
13300
13734
|
}
|
|
13301
|
-
const src =
|
|
13302
|
-
if (!
|
|
13735
|
+
const src = join27(rollTemplates2(), "agent-routes", `${templateName}.yaml`);
|
|
13736
|
+
if (!existsSync30(src)) {
|
|
13303
13737
|
return 1;
|
|
13304
13738
|
}
|
|
13305
|
-
|
|
13739
|
+
mkdirSync14(dirname11(dest), { recursive: true });
|
|
13306
13740
|
copyFileSync2(src, dest);
|
|
13307
13741
|
summary.push("created|.roll/agent-routes.yaml");
|
|
13308
13742
|
return 0;
|
|
13309
13743
|
}
|
|
13310
13744
|
function writeVersionStamp(projectDir, summary) {
|
|
13311
|
-
const stampPath =
|
|
13312
|
-
if (
|
|
13745
|
+
const stampPath = join27(projectDir, ".roll", ".version");
|
|
13746
|
+
if (existsSync30(stampPath)) {
|
|
13313
13747
|
summary.push("unchanged|.roll/.version");
|
|
13314
13748
|
return;
|
|
13315
13749
|
}
|
|
13316
|
-
|
|
13750
|
+
mkdirSync14(join27(projectDir, ".roll"), { recursive: true });
|
|
13317
13751
|
const installedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13318
|
-
|
|
13752
|
+
writeFileSync15(stampPath, `# Roll project version stamp \u2014 written by \`roll init\` (US-ONBOARD-019).
|
|
13319
13753
|
# Used by \`_check_structure\` to recognise a previously-onboarded Roll project
|
|
13320
13754
|
# without depending on directory-name heuristics.
|
|
13321
13755
|
roll_version: "${rollVersion() || "unknown"}"
|
|
@@ -13453,7 +13887,7 @@ function initCommand(args) {
|
|
|
13453
13887
|
return null;
|
|
13454
13888
|
if (args[0] !== void 0 && args[0].startsWith("-"))
|
|
13455
13889
|
return null;
|
|
13456
|
-
if (!
|
|
13890
|
+
if (!existsSync30(rollTemplates2()))
|
|
13457
13891
|
return null;
|
|
13458
13892
|
let projectDir;
|
|
13459
13893
|
try {
|
|
@@ -13463,16 +13897,16 @@ function initCommand(args) {
|
|
|
13463
13897
|
}
|
|
13464
13898
|
let hasAgents = false;
|
|
13465
13899
|
const summary = [];
|
|
13466
|
-
if (
|
|
13900
|
+
if (existsSync30(join27(projectDir, "AGENTS.md"))) {
|
|
13467
13901
|
hasAgents = true;
|
|
13468
13902
|
} else if (isLegacyProject(projectDir)) {
|
|
13469
13903
|
return null;
|
|
13470
13904
|
}
|
|
13471
13905
|
mergeGlobalToProject(projectDir, summary);
|
|
13472
13906
|
mergeClaudeToProject(projectDir, summary);
|
|
13473
|
-
writeBacklog(
|
|
13474
|
-
ensureFeaturesDir(
|
|
13475
|
-
writeFeaturesMd(
|
|
13907
|
+
writeBacklog(join27(projectDir, ".roll", "backlog.md"), summary);
|
|
13908
|
+
ensureFeaturesDir(join27(projectDir, ".roll", "features"), summary);
|
|
13909
|
+
writeFeaturesMd(join27(projectDir, ".roll", "features.md"), summary);
|
|
13476
13910
|
const routesTemplate = process.env["ROLL_AGENT_ROUTES_TEMPLATE"] ?? "default";
|
|
13477
13911
|
initSeedAgentRoutes(routesTemplate, projectDir, summary);
|
|
13478
13912
|
writeVersionStamp(projectDir, summary);
|
|
@@ -13486,16 +13920,16 @@ function initCommand(args) {
|
|
|
13486
13920
|
|
|
13487
13921
|
// packages/cli/dist/commands/lang.js
|
|
13488
13922
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
13489
|
-
import { existsSync as
|
|
13490
|
-
import { homedir as
|
|
13491
|
-
import { dirname as
|
|
13923
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readFileSync as readFileSync24, renameSync as renameSync2, writeFileSync as writeFileSync16 } from "node:fs";
|
|
13924
|
+
import { homedir as homedir13, tmpdir as tmpdir3 } from "node:os";
|
|
13925
|
+
import { dirname as dirname12, join as join28 } from "node:path";
|
|
13492
13926
|
function rollConfigPath4() {
|
|
13493
|
-
const rollHome4 = process.env["ROLL_HOME"] ??
|
|
13494
|
-
return
|
|
13927
|
+
const rollHome4 = process.env["ROLL_HOME"] ?? join28(homedir13(), ".roll");
|
|
13928
|
+
return join28(rollHome4, "config.yaml");
|
|
13495
13929
|
}
|
|
13496
13930
|
function configLang2() {
|
|
13497
13931
|
const cfg = rollConfigPath4();
|
|
13498
|
-
if (!
|
|
13932
|
+
if (!existsSync31(cfg))
|
|
13499
13933
|
return void 0;
|
|
13500
13934
|
for (const line of readFileSync24(cfg, "utf8").split("\n")) {
|
|
13501
13935
|
const m7 = /^lang:\s*(.*)$/.exec(line);
|
|
@@ -13509,7 +13943,7 @@ function configLang2() {
|
|
|
13509
13943
|
}
|
|
13510
13944
|
function configHasLangLine() {
|
|
13511
13945
|
const cfg = rollConfigPath4();
|
|
13512
|
-
if (!
|
|
13946
|
+
if (!existsSync31(cfg))
|
|
13513
13947
|
return false;
|
|
13514
13948
|
return readFileSync24(cfg, "utf8").split("\n").some((l) => /^lang:/.test(l));
|
|
13515
13949
|
}
|
|
@@ -13545,7 +13979,7 @@ function resolveSource() {
|
|
|
13545
13979
|
const env = process.env;
|
|
13546
13980
|
if ((env["ROLL_LANG"] ?? "") !== "")
|
|
13547
13981
|
return "ROLL_LANG env";
|
|
13548
|
-
if (
|
|
13982
|
+
if (existsSync31(rollConfigPath4()) && configHasLangLine())
|
|
13549
13983
|
return `config (${rollConfigPath4()})`;
|
|
13550
13984
|
if ((env["LC_ALL"] ?? "") !== "" || (env["LANG"] ?? "") !== "")
|
|
13551
13985
|
return "LC_ALL/LANG";
|
|
@@ -13570,28 +14004,28 @@ function err10(line) {
|
|
|
13570
14004
|
}
|
|
13571
14005
|
function writeLang(value) {
|
|
13572
14006
|
const cfg = rollConfigPath4();
|
|
13573
|
-
|
|
13574
|
-
const existing =
|
|
14007
|
+
mkdirSync15(dirname12(cfg), { recursive: true });
|
|
14008
|
+
const existing = existsSync31(cfg) ? readFileSync24(cfg, "utf8") : "";
|
|
13575
14009
|
const kept = existing === "" ? [] : existing.split("\n").filter((l) => !/^lang:/.test(l));
|
|
13576
14010
|
if (kept.length > 0 && kept[kept.length - 1] === "")
|
|
13577
14011
|
kept.pop();
|
|
13578
14012
|
const body = kept.length > 0 ? kept.join("\n") + "\n" : "";
|
|
13579
|
-
const tmp =
|
|
13580
|
-
|
|
14013
|
+
const tmp = join28(mkdtempSync3(join28(tmpdir3(), "roll-lang-")), "config.yaml");
|
|
14014
|
+
writeFileSync16(tmp, `${body}lang: ${value}
|
|
13581
14015
|
`);
|
|
13582
14016
|
renameSync2(tmp, cfg);
|
|
13583
14017
|
}
|
|
13584
14018
|
function clearLang() {
|
|
13585
14019
|
const cfg = rollConfigPath4();
|
|
13586
|
-
if (!
|
|
14020
|
+
if (!existsSync31(cfg))
|
|
13587
14021
|
return;
|
|
13588
14022
|
const existing = readFileSync24(cfg, "utf8");
|
|
13589
14023
|
const kept = existing.split("\n").filter((l) => !/^lang:/.test(l));
|
|
13590
14024
|
if (kept.length > 0 && kept[kept.length - 1] === "")
|
|
13591
14025
|
kept.pop();
|
|
13592
14026
|
const body = kept.length > 0 ? kept.join("\n") + "\n" : "";
|
|
13593
|
-
const tmp =
|
|
13594
|
-
|
|
14027
|
+
const tmp = join28(mkdtempSync3(join28(tmpdir3(), "roll-lang-")), "config.yaml");
|
|
14028
|
+
writeFileSync16(tmp, body);
|
|
13595
14029
|
renameSync2(tmp, cfg);
|
|
13596
14030
|
}
|
|
13597
14031
|
function langCommand(args) {
|
|
@@ -13691,9 +14125,9 @@ async function loopFmtCommand(args) {
|
|
|
13691
14125
|
|
|
13692
14126
|
// packages/cli/dist/commands/loop-pr-inbox.js
|
|
13693
14127
|
import { spawn as spawn2 } from "node:child_process";
|
|
13694
|
-
import { appendFileSync as appendFileSync5, existsSync as
|
|
13695
|
-
import { homedir as
|
|
13696
|
-
import { dirname as
|
|
14128
|
+
import { appendFileSync as appendFileSync5, existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
|
|
14129
|
+
import { homedir as homedir14 } from "node:os";
|
|
14130
|
+
import { dirname as dirname13, join as join29 } from "node:path";
|
|
13697
14131
|
function reducePrView(raw) {
|
|
13698
14132
|
const reviews = raw.reviews ?? [];
|
|
13699
14133
|
const botReviews = reviews.filter((r) => r.authorAssociation === "BOT" || r.authorAssociation === "APP");
|
|
@@ -13776,7 +14210,7 @@ function runtimeDir() {
|
|
|
13776
14210
|
const override = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
|
|
13777
14211
|
if (override !== "")
|
|
13778
14212
|
return override;
|
|
13779
|
-
return
|
|
14213
|
+
return join29(process.cwd(), ".roll", "loop");
|
|
13780
14214
|
}
|
|
13781
14215
|
function projSlug() {
|
|
13782
14216
|
const override = (process.env["ROLL_MAIN_SLUG"] ?? "").trim();
|
|
@@ -13785,26 +14219,26 @@ function projSlug() {
|
|
|
13785
14219
|
return process.cwd().split("/").filter(Boolean).pop() ?? "default";
|
|
13786
14220
|
}
|
|
13787
14221
|
function alertPath() {
|
|
13788
|
-
return
|
|
14222
|
+
return join29(runtimeDir(), `ALERT-${projSlug()}.md`);
|
|
13789
14223
|
}
|
|
13790
14224
|
function statePath() {
|
|
13791
|
-
return
|
|
14225
|
+
return join29(runtimeDir(), `state-${projSlug()}.yaml`);
|
|
13792
14226
|
}
|
|
13793
14227
|
function tickPath() {
|
|
13794
|
-
return
|
|
14228
|
+
return join29(runtimeDir(), "pr-tick.jsonl");
|
|
13795
14229
|
}
|
|
13796
14230
|
function engineBin() {
|
|
13797
|
-
let dir =
|
|
14231
|
+
let dir = dirname13(new URL(import.meta.url).pathname);
|
|
13798
14232
|
for (let i = 0; i < 10; i++) {
|
|
13799
|
-
const candidate =
|
|
13800
|
-
if (
|
|
14233
|
+
const candidate = join29(dir, "bin", "roll");
|
|
14234
|
+
if (existsSync32(candidate))
|
|
13801
14235
|
return candidate;
|
|
13802
|
-
const parent =
|
|
14236
|
+
const parent = dirname13(dir);
|
|
13803
14237
|
if (parent === dir)
|
|
13804
14238
|
break;
|
|
13805
14239
|
dir = parent;
|
|
13806
14240
|
}
|
|
13807
|
-
return
|
|
14241
|
+
return join29(homedir14(), ".local", "lib", "roll", "bin", "roll");
|
|
13808
14242
|
}
|
|
13809
14243
|
function pal2() {
|
|
13810
14244
|
return (process.env["NO_COLOR"] ?? "") !== "" ? { yellow: "", nc: "" } : { yellow: "\x1B[0;33m", nc: "\x1B[0m" };
|
|
@@ -13814,21 +14248,21 @@ function nowSec() {
|
|
|
13814
14248
|
}
|
|
13815
14249
|
function writeTickFile(tick) {
|
|
13816
14250
|
const file = tickPath();
|
|
13817
|
-
|
|
14251
|
+
mkdirSync16(dirname13(file), { recursive: true });
|
|
13818
14252
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13819
14253
|
appendFileSync5(file, `${JSON.stringify({ ts, ...tick })}
|
|
13820
14254
|
`);
|
|
13821
14255
|
try {
|
|
13822
14256
|
const lines = readFileSync25(file, "utf8").split("\n").filter((l) => l !== "");
|
|
13823
14257
|
if (lines.length > 500)
|
|
13824
|
-
|
|
14258
|
+
writeFileSync17(file, `${lines.slice(-500).join("\n")}
|
|
13825
14259
|
`);
|
|
13826
14260
|
} catch {
|
|
13827
14261
|
}
|
|
13828
14262
|
}
|
|
13829
14263
|
function appendAlert(line) {
|
|
13830
14264
|
const file = alertPath();
|
|
13831
|
-
|
|
14265
|
+
mkdirSync16(dirname13(file), { recursive: true });
|
|
13832
14266
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13833
14267
|
appendFileSync5(file, `[${ts}] ${line}
|
|
13834
14268
|
`);
|
|
@@ -13844,9 +14278,9 @@ function rebaseCircuitAllowed(num2) {
|
|
|
13844
14278
|
writeRebaseAttempts(state, num2, verdict.freshTimestamps);
|
|
13845
14279
|
if (!verdict.allowed) {
|
|
13846
14280
|
const file = alertPath();
|
|
13847
|
-
|
|
14281
|
+
mkdirSync16(dirname13(file), { recursive: true });
|
|
13848
14282
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
|
|
13849
|
-
|
|
14283
|
+
writeFileSync17(file, [
|
|
13850
14284
|
`# ALERT \u2014 PR rebase circuit breaker tripped`,
|
|
13851
14285
|
``,
|
|
13852
14286
|
`**Time**: ${stamp}`,
|
|
@@ -13900,13 +14334,13 @@ function upsertRebaseAttempts(stateBody, pr, value) {
|
|
|
13900
14334
|
`;
|
|
13901
14335
|
}
|
|
13902
14336
|
function writeRebaseAttempts(state, pr, timestamps) {
|
|
13903
|
-
|
|
14337
|
+
mkdirSync16(dirname13(state), { recursive: true });
|
|
13904
14338
|
let body = "";
|
|
13905
14339
|
try {
|
|
13906
14340
|
body = readFileSync25(state, "utf8");
|
|
13907
14341
|
} catch {
|
|
13908
14342
|
}
|
|
13909
|
-
|
|
14343
|
+
writeFileSync17(state, upsertRebaseAttempts(body, pr, renderRebaseAttempts(timestamps)));
|
|
13910
14344
|
}
|
|
13911
14345
|
function bridgeBash(args) {
|
|
13912
14346
|
return new Promise((resolve3) => {
|
|
@@ -13993,19 +14427,19 @@ async function loopPrInboxCommand(_args, deps = realDeps2()) {
|
|
|
13993
14427
|
}
|
|
13994
14428
|
|
|
13995
14429
|
// packages/cli/dist/commands/loop-run-once.js
|
|
13996
|
-
import { appendFileSync as appendFileSync7, existsSync as
|
|
13997
|
-
import { dirname as
|
|
14430
|
+
import { appendFileSync as appendFileSync7, existsSync as existsSync36, mkdirSync as mkdirSync18, readFileSync as readFileSync28, writeFileSync as writeFileSync19 } from "node:fs";
|
|
14431
|
+
import { dirname as dirname15, join as join33 } from "node:path";
|
|
13998
14432
|
|
|
13999
14433
|
// packages/cli/dist/runner/executor.js
|
|
14000
14434
|
import { execFile as execFile8 } from "node:child_process";
|
|
14001
|
-
import { appendFileSync as appendFileSync6, existsSync as
|
|
14002
|
-
import { dirname as
|
|
14435
|
+
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";
|
|
14436
|
+
import { dirname as dirname14, join as join32 } from "node:path";
|
|
14003
14437
|
import { promisify as promisify8 } from "node:util";
|
|
14004
14438
|
|
|
14005
14439
|
// packages/cli/dist/runner/peer-gate.js
|
|
14006
14440
|
import { execFile as execFile7 } from "node:child_process";
|
|
14007
|
-
import { existsSync as
|
|
14008
|
-
import { join as
|
|
14441
|
+
import { existsSync as existsSync33, readdirSync as readdirSync15 } from "node:fs";
|
|
14442
|
+
import { join as join30 } from "node:path";
|
|
14009
14443
|
import { promisify as promisify7 } from "node:util";
|
|
14010
14444
|
var execFileAsync7 = promisify7(execFile7);
|
|
14011
14445
|
var HIGH_RISK = [/^\.github\/workflows\//, /^packages\/infra\/src\/(git|github|process)\.ts$/];
|
|
@@ -14035,11 +14469,11 @@ async function cycleChangedFiles(worktreeCwd) {
|
|
|
14035
14469
|
}
|
|
14036
14470
|
}
|
|
14037
14471
|
function peerEvidencePresent(runtimeDir3, cycleId) {
|
|
14038
|
-
const dir =
|
|
14039
|
-
if (!
|
|
14472
|
+
const dir = join30(runtimeDir3, "peer");
|
|
14473
|
+
if (!existsSync33(dir))
|
|
14040
14474
|
return false;
|
|
14041
14475
|
try {
|
|
14042
|
-
return
|
|
14476
|
+
return readdirSync15(dir).some((f) => f.startsWith(`cycle-${cycleId}.`));
|
|
14043
14477
|
} catch {
|
|
14044
14478
|
return false;
|
|
14045
14479
|
}
|
|
@@ -14063,24 +14497,24 @@ async function runPeerGate(worktreeCwd, runtimeDir3, cycleId, sinks) {
|
|
|
14063
14497
|
}
|
|
14064
14498
|
|
|
14065
14499
|
// packages/cli/dist/runner/attest-gate.js
|
|
14066
|
-
import { existsSync as
|
|
14067
|
-
import { join as
|
|
14500
|
+
import { existsSync as existsSync34, readFileSync as readFileSync26, statSync as statSync16 } from "node:fs";
|
|
14501
|
+
import { join as join31 } from "node:path";
|
|
14068
14502
|
function reportCandidates(worktreeCwd, storyId) {
|
|
14069
14503
|
return [
|
|
14070
|
-
|
|
14071
|
-
|
|
14504
|
+
join31(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId)),
|
|
14505
|
+
join31(legacyArchiveDir(worktreeCwd, storyId), "latest", "report.html")
|
|
14072
14506
|
];
|
|
14073
14507
|
}
|
|
14074
14508
|
function acMapCandidates(worktreeCwd, storyId) {
|
|
14075
14509
|
return [
|
|
14076
|
-
|
|
14077
|
-
|
|
14510
|
+
join31(cardArchiveDir(worktreeCwd, storyId), "ac-map.json"),
|
|
14511
|
+
join31(legacyArchiveDir(worktreeCwd, storyId), "ac-map.json")
|
|
14078
14512
|
];
|
|
14079
14513
|
}
|
|
14080
14514
|
function existingReport(worktreeCwd, storyId) {
|
|
14081
14515
|
for (const p of reportCandidates(worktreeCwd, storyId)) {
|
|
14082
14516
|
try {
|
|
14083
|
-
if (
|
|
14517
|
+
if (statSync16(p).isFile())
|
|
14084
14518
|
return p;
|
|
14085
14519
|
} catch {
|
|
14086
14520
|
}
|
|
@@ -14094,7 +14528,7 @@ function verificationReportFresh(worktreeCwd, storyId, sinceSec) {
|
|
|
14094
14528
|
if (p === null)
|
|
14095
14529
|
return false;
|
|
14096
14530
|
try {
|
|
14097
|
-
const st =
|
|
14531
|
+
const st = statSync16(p);
|
|
14098
14532
|
if (sinceSec === void 0)
|
|
14099
14533
|
return true;
|
|
14100
14534
|
return st.mtimeMs / 1e3 >= sinceSec;
|
|
@@ -14111,7 +14545,7 @@ function verificationReportHasContent(worktreeCwd, storyId) {
|
|
|
14111
14545
|
try {
|
|
14112
14546
|
const html = readFileSync26(p, "utf8");
|
|
14113
14547
|
const hasAc = /<section class="ac[\s">]/.test(html);
|
|
14114
|
-
const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) =>
|
|
14548
|
+
const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) => existsSync34(m7));
|
|
14115
14549
|
return hasAc && hasMap;
|
|
14116
14550
|
} catch {
|
|
14117
14551
|
return false;
|
|
@@ -14119,8 +14553,8 @@ function verificationReportHasContent(worktreeCwd, storyId) {
|
|
|
14119
14553
|
}
|
|
14120
14554
|
function readAttestGateMode(repoCwd) {
|
|
14121
14555
|
try {
|
|
14122
|
-
const p =
|
|
14123
|
-
if (!
|
|
14556
|
+
const p = join31(repoCwd, ".roll", "policy.yaml");
|
|
14557
|
+
if (!existsSync34(p))
|
|
14124
14558
|
return "soft";
|
|
14125
14559
|
return parsePolicy(readFileSync26(p, "utf8")).loopSafety.attestGate === "hard" ? "hard" : "soft";
|
|
14126
14560
|
} catch {
|
|
@@ -14136,7 +14570,7 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks) {
|
|
|
14136
14570
|
return { verdict: "produced", mode, reasons: reasons2, blocked: false };
|
|
14137
14571
|
}
|
|
14138
14572
|
const reasons = [
|
|
14139
|
-
fresh ? `acceptance report at .roll/
|
|
14573
|
+
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
14574
|
];
|
|
14141
14575
|
const blocked = mode === "hard";
|
|
14142
14576
|
const lead = fresh ? `delivery with an empty-shell acceptance report (no AC content / no ac-map)` : `delivery without a fresh acceptance report`;
|
|
@@ -14230,9 +14664,9 @@ async function executeCommand(cmd, ports, ctx) {
|
|
|
14230
14664
|
// execute: spawn the agent (TCR commits happen inside the worktree). The
|
|
14231
14665
|
// exit code + timeout feed back as agent_exited; usage is captured for cost.
|
|
14232
14666
|
case "spawn_agent": {
|
|
14233
|
-
const livePath =
|
|
14667
|
+
const livePath = join32(dirname14(ports.paths.eventsPath), "live.log");
|
|
14234
14668
|
try {
|
|
14235
|
-
|
|
14669
|
+
writeFileSync18(livePath, `\u2500\u2500 cycle ${ctx.cycleId ?? "?"} \xB7 ${ctx.storyId ?? "?"} \xB7 agent ${cmd.agent} \u2500\u2500
|
|
14236
14670
|
`);
|
|
14237
14671
|
} catch {
|
|
14238
14672
|
}
|
|
@@ -14250,9 +14684,9 @@ async function executeCommand(cmd, ports, ctx) {
|
|
|
14250
14684
|
}
|
|
14251
14685
|
});
|
|
14252
14686
|
try {
|
|
14253
|
-
const logDir =
|
|
14254
|
-
|
|
14255
|
-
|
|
14687
|
+
const logDir = join32(dirname14(ports.paths.eventsPath), "cycle-logs");
|
|
14688
|
+
mkdirSync17(logDir, { recursive: true });
|
|
14689
|
+
writeFileSync18(join32(logDir, `${ctx.cycleId ?? "cycle"}.agent.log`), `# exit=${res.exitCode} timedOut=${res.timedOut}
|
|
14256
14690
|
--- stdout ---
|
|
14257
14691
|
${res.stdout}
|
|
14258
14692
|
--- stderr ---
|
|
@@ -14299,7 +14733,7 @@ ${res.stderr}
|
|
|
14299
14733
|
tcrCount = await ports.git.tcrCount(ports.paths.worktreePath);
|
|
14300
14734
|
} catch {
|
|
14301
14735
|
}
|
|
14302
|
-
await runPeerGate(ports.paths.worktreePath,
|
|
14736
|
+
await runPeerGate(ports.paths.worktreePath, dirname14(ports.paths.eventsPath), ctx.cycleId ?? "", {
|
|
14303
14737
|
alert: (m7) => ports.events.appendAlert(ports.paths.alertsPath, m7),
|
|
14304
14738
|
event: (p) => ports.events.appendEvent(ports.paths.eventsPath, {
|
|
14305
14739
|
type: "peer:gate",
|
|
@@ -14375,7 +14809,7 @@ ${res.stderr}
|
|
|
14375
14809
|
// _worktree_cleanup (tolerant). Side effect; no feedback (terminal path).
|
|
14376
14810
|
case "cleanup_worktree":
|
|
14377
14811
|
try {
|
|
14378
|
-
const dst =
|
|
14812
|
+
const dst = join32(ports.paths.worktreePath, ".roll");
|
|
14379
14813
|
if (lstatSync2(dst, { throwIfNoEntry: false })?.isSymbolicLink() === true)
|
|
14380
14814
|
unlinkSync(dst);
|
|
14381
14815
|
} catch {
|
|
@@ -14447,7 +14881,7 @@ function buildRunRow(cmd, ctx, nowSec2) {
|
|
|
14447
14881
|
}
|
|
14448
14882
|
function readRunsRows(runsPath) {
|
|
14449
14883
|
try {
|
|
14450
|
-
if (!
|
|
14884
|
+
if (!existsSync35(runsPath))
|
|
14451
14885
|
return [];
|
|
14452
14886
|
return readFileSync27(runsPath, "utf8").split("\n").filter((l) => l.trim() !== "").map((l) => {
|
|
14453
14887
|
try {
|
|
@@ -14472,15 +14906,15 @@ function sleep(ms) {
|
|
|
14472
14906
|
}
|
|
14473
14907
|
async function linkRollIntoWorktree(repoCwd, worktreePath) {
|
|
14474
14908
|
try {
|
|
14475
|
-
const src =
|
|
14476
|
-
const dst =
|
|
14477
|
-
if (!
|
|
14909
|
+
const src = join32(repoCwd, ".roll");
|
|
14910
|
+
const dst = join32(worktreePath, ".roll");
|
|
14911
|
+
if (!existsSync35(src))
|
|
14478
14912
|
return;
|
|
14479
14913
|
const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
|
|
14480
14914
|
if (dstStat) {
|
|
14481
14915
|
if (dstStat.isSymbolicLink())
|
|
14482
14916
|
return;
|
|
14483
|
-
const incompleteFossil =
|
|
14917
|
+
const incompleteFossil = existsSync35(join32(src, "backlog.md")) && !existsSync35(join32(dst, "backlog.md"));
|
|
14484
14918
|
if (!incompleteFossil)
|
|
14485
14919
|
return;
|
|
14486
14920
|
rmSync8(dst, { recursive: true, force: true });
|
|
@@ -14489,10 +14923,10 @@ async function linkRollIntoWorktree(repoCwd, worktreePath) {
|
|
|
14489
14923
|
const common = (await execFileAsync8("git", ["-C", repoCwd, "rev-parse", "--path-format=absolute", "--git-common-dir"])).stdout.trim();
|
|
14490
14924
|
if (common === "")
|
|
14491
14925
|
return;
|
|
14492
|
-
const exclude =
|
|
14493
|
-
const cur =
|
|
14926
|
+
const exclude = join32(common, "info", "exclude");
|
|
14927
|
+
const cur = existsSync35(exclude) ? readFileSync27(exclude, "utf8") : "";
|
|
14494
14928
|
if (!/^\.roll$/m.test(cur)) {
|
|
14495
|
-
|
|
14929
|
+
mkdirSync17(dirname14(exclude), { recursive: true });
|
|
14496
14930
|
appendFileSync6(exclude, `${cur === "" || cur.endsWith("\n") ? "" : "\n"}.roll
|
|
14497
14931
|
`, "utf8");
|
|
14498
14932
|
}
|
|
@@ -14581,8 +15015,8 @@ function nodePorts(opts) {
|
|
|
14581
15015
|
},
|
|
14582
15016
|
backlog: {
|
|
14583
15017
|
read(projectCwd) {
|
|
14584
|
-
const p =
|
|
14585
|
-
if (!
|
|
15018
|
+
const p = join32(projectCwd, ".roll", "backlog.md");
|
|
15019
|
+
if (!existsSync35(p))
|
|
14586
15020
|
return [];
|
|
14587
15021
|
return parseBacklog(readFileSync27(p, "utf8"));
|
|
14588
15022
|
},
|
|
@@ -14592,8 +15026,8 @@ function nodePorts(opts) {
|
|
|
14592
15026
|
// never kill the cycle, the reconcile pass is the safety net.
|
|
14593
15027
|
markStatus(projectCwd, id, status2) {
|
|
14594
15028
|
try {
|
|
14595
|
-
const p =
|
|
14596
|
-
if (!
|
|
15029
|
+
const p = join32(projectCwd, ".roll", "backlog.md");
|
|
15030
|
+
if (!existsSync35(p))
|
|
14597
15031
|
return;
|
|
14598
15032
|
const store = new BacklogStore();
|
|
14599
15033
|
const snap = store.readBacklog(p);
|
|
@@ -14613,7 +15047,7 @@ function nodePorts(opts) {
|
|
|
14613
15047
|
};
|
|
14614
15048
|
}
|
|
14615
15049
|
function appendAlertLine(alertsPath, message) {
|
|
14616
|
-
|
|
15050
|
+
mkdirSync17(dirname14(alertsPath), { recursive: true });
|
|
14617
15051
|
appendFileSync6(alertsPath, `${message}
|
|
14618
15052
|
`, "utf8");
|
|
14619
15053
|
}
|
|
@@ -14798,13 +15232,13 @@ function announceReport(projectPath, slug, storyId, opener = (p) => {
|
|
|
14798
15232
|
}) {
|
|
14799
15233
|
if (storyId === "")
|
|
14800
15234
|
return null;
|
|
14801
|
-
const report =
|
|
14802
|
-
if (!
|
|
15235
|
+
const report = join33(projectPath, ".roll", "verification", storyId, "latest", "report.html");
|
|
15236
|
+
if (!existsSync36(report))
|
|
14803
15237
|
return null;
|
|
14804
15238
|
process.stdout.write(`evidence: ${report}
|
|
14805
15239
|
\u9A8C\u6536\u62A5\u544A: ${report}
|
|
14806
15240
|
`);
|
|
14807
|
-
const muted =
|
|
15241
|
+
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
15242
|
if (!muted)
|
|
14809
15243
|
opener(report);
|
|
14810
15244
|
return report;
|
|
@@ -14821,7 +15255,7 @@ function cycleSignalTeardown(paths, cycleId, branch, sig, deps = {}) {
|
|
|
14821
15255
|
}
|
|
14822
15256
|
let owned = false;
|
|
14823
15257
|
try {
|
|
14824
|
-
owned =
|
|
15258
|
+
owned = existsSync36(paths.lockPath) && parseLock(readFileSync28(paths.lockPath, "utf8")).pid === pid;
|
|
14825
15259
|
} catch {
|
|
14826
15260
|
owned = false;
|
|
14827
15261
|
}
|
|
@@ -14866,7 +15300,51 @@ function makeCycleId(now = /* @__PURE__ */ new Date(), pid = process.pid) {
|
|
|
14866
15300
|
}
|
|
14867
15301
|
function runtimeDir2(projectPath) {
|
|
14868
15302
|
const env = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
|
|
14869
|
-
return env !== "" ? env :
|
|
15303
|
+
return env !== "" ? env : join33(projectPath, ".roll", "loop");
|
|
15304
|
+
}
|
|
15305
|
+
var PAUSE_THRESHOLD = 3;
|
|
15306
|
+
function incrementConsecutiveFails(projectPath, slug, alertsPath, cycleId, storyId, terminal) {
|
|
15307
|
+
const rt = runtimeDir2(projectPath);
|
|
15308
|
+
const counterFile = join33(rt, "consecutive-fails");
|
|
15309
|
+
let count = 0;
|
|
15310
|
+
try {
|
|
15311
|
+
if (existsSync36(counterFile)) {
|
|
15312
|
+
count = parseInt(readFileSync28(counterFile, "utf8").trim(), 10) || 0;
|
|
15313
|
+
}
|
|
15314
|
+
} catch {
|
|
15315
|
+
}
|
|
15316
|
+
count += 1;
|
|
15317
|
+
try {
|
|
15318
|
+
writeFileSync19(counterFile, String(count), "utf8");
|
|
15319
|
+
} catch {
|
|
15320
|
+
}
|
|
15321
|
+
if (count < PAUSE_THRESHOLD)
|
|
15322
|
+
return;
|
|
15323
|
+
const pauseMarker = join33(projectPath, ".roll", "loop", `PAUSE-${slug}`);
|
|
15324
|
+
const alertMsg = `# ALERT \u2014 loop auto-paused after ${count} consecutive failures
|
|
15325
|
+
|
|
15326
|
+
**Cycle**: ${cycleId}
|
|
15327
|
+
**Story**: ${storyId}
|
|
15328
|
+
**Terminal**: ${terminal}
|
|
15329
|
+
**Action**: ${count} consecutive cycles failed \u2014 loop paused to prevent burn.
|
|
15330
|
+
Resolve the root cause, then: \`roll loop resume\`
|
|
15331
|
+
`;
|
|
15332
|
+
try {
|
|
15333
|
+
writeFileSync19(pauseMarker, alertMsg, "utf8");
|
|
15334
|
+
appendFileSync7(alertsPath, `${alertMsg}
|
|
15335
|
+
`, "utf8");
|
|
15336
|
+
} catch {
|
|
15337
|
+
}
|
|
15338
|
+
process.stderr.write(`loop run-once: auto-PAUSED after ${count} consecutive failures \u2014 PAUSE marker written
|
|
15339
|
+
loop run-once: \u8FDE\u7EED ${count} \u6B21\u5931\u8D25\u540E\u81EA\u52A8\u6682\u505C \u2014 \u5DF2\u5199 PAUSE \u6807\u8BB0
|
|
15340
|
+
`);
|
|
15341
|
+
}
|
|
15342
|
+
function resetConsecutiveFails(projectPath) {
|
|
15343
|
+
const rt = runtimeDir2(projectPath);
|
|
15344
|
+
try {
|
|
15345
|
+
writeFileSync19(join33(rt, "consecutive-fails"), "0", "utf8");
|
|
15346
|
+
} catch {
|
|
15347
|
+
}
|
|
14870
15348
|
}
|
|
14871
15349
|
function readSkillBody2(projectPath) {
|
|
14872
15350
|
return readSkillBody(projectPath, {
|
|
@@ -14874,6 +15352,38 @@ function readSkillBody2(projectPath) {
|
|
|
14874
15352
|
envOverride: process.env["ROLL_LOOP_SKILL"]
|
|
14875
15353
|
});
|
|
14876
15354
|
}
|
|
15355
|
+
function buildLoopRouteDeps(projectPath) {
|
|
15356
|
+
function readSlot(slot) {
|
|
15357
|
+
const agentsYaml = join33(projectPath, ".roll", "agents.yaml");
|
|
15358
|
+
try {
|
|
15359
|
+
return readSlotFromText(readFileSync28(agentsYaml, "utf8"), slot);
|
|
15360
|
+
} catch {
|
|
15361
|
+
return void 0;
|
|
15362
|
+
}
|
|
15363
|
+
}
|
|
15364
|
+
function firstInstalled() {
|
|
15365
|
+
const fromLocal = readField(join33(projectPath, ".roll", "local.yaml"), /^agent:/);
|
|
15366
|
+
if (fromLocal !== void 0)
|
|
15367
|
+
return fromLocal;
|
|
15368
|
+
return firstInstalledAgent(realAgentEnv());
|
|
15369
|
+
}
|
|
15370
|
+
return { readSlot, firstInstalled };
|
|
15371
|
+
}
|
|
15372
|
+
function readField(path, re) {
|
|
15373
|
+
try {
|
|
15374
|
+
const text = readFileSync28(path, "utf8");
|
|
15375
|
+
for (const line of text.split("\n")) {
|
|
15376
|
+
const m7 = line.match(re);
|
|
15377
|
+
if (m7 !== null) {
|
|
15378
|
+
const v = line.slice((m7.index ?? 0) + m7[0].length).trim();
|
|
15379
|
+
if (v !== "")
|
|
15380
|
+
return v.replace(/^["']|["']$/g, "");
|
|
15381
|
+
}
|
|
15382
|
+
}
|
|
15383
|
+
} catch {
|
|
15384
|
+
}
|
|
15385
|
+
return void 0;
|
|
15386
|
+
}
|
|
14877
15387
|
async function loopRunOnceCommand(args) {
|
|
14878
15388
|
const dryRun = args.includes("--dry-run");
|
|
14879
15389
|
const id = await projectIdentity();
|
|
@@ -14897,37 +15407,40 @@ async function loopRunOnceCommand(args) {
|
|
|
14897
15407
|
return 0;
|
|
14898
15408
|
}
|
|
14899
15409
|
const rt = runtimeDir2(id.path);
|
|
15410
|
+
const alertsPath = join33(rt, `ALERT-${id.slug}.md`);
|
|
15411
|
+
mkdirSync18(dirname15(alertsPath), { recursive: true });
|
|
14900
15412
|
const paths = {
|
|
14901
|
-
eventsPath:
|
|
14902
|
-
runsPath:
|
|
14903
|
-
alertsPath
|
|
14904
|
-
lockPath:
|
|
14905
|
-
heartbeatPath:
|
|
14906
|
-
worktreePath:
|
|
15413
|
+
eventsPath: join33(rt, "events.ndjson"),
|
|
15414
|
+
runsPath: join33(rt, "runs.jsonl"),
|
|
15415
|
+
alertsPath,
|
|
15416
|
+
lockPath: join33(rt, "inner.lock"),
|
|
15417
|
+
heartbeatPath: join33(rt, "heartbeat"),
|
|
15418
|
+
worktreePath: join33(rt, "worktrees", `cycle-${cycleId}`)
|
|
14907
15419
|
};
|
|
14908
15420
|
const skillBody = readSkillBody2(id.path);
|
|
14909
15421
|
if (skillBody === null) {
|
|
14910
15422
|
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
15423
|
try {
|
|
14912
|
-
|
|
14913
|
-
appendFileSync7(paths.alertsPath, `${msg2}
|
|
15424
|
+
appendFileSync7(alertsPath, `${msg2}
|
|
14914
15425
|
`, "utf8");
|
|
14915
15426
|
} catch {
|
|
14916
15427
|
}
|
|
14917
15428
|
process.stderr.write(`loop run-once: roll-loop SKILL.md not found \u2014 refusing to spawn a blind agent (ALERT written)
|
|
14918
15429
|
loop run-once: \u627E\u4E0D\u5230 roll-loop SKILL.md \u2014 \u62D2\u7EDD\u76F2\u5F00 agent(\u5DF2\u5199 ALERT)
|
|
14919
15430
|
`);
|
|
15431
|
+
incrementConsecutiveFails(id.path, id.slug, alertsPath, cycleId, "", "skill_missing");
|
|
14920
15432
|
return 1;
|
|
14921
15433
|
}
|
|
14922
|
-
const routeDeps =
|
|
14923
|
-
|
|
14924
|
-
firstInstalled: () => process.env["ROLL_LOOP_AGENT"] ?? "claude"
|
|
14925
|
-
};
|
|
15434
|
+
const routeDeps = buildLoopRouteDeps(id.path);
|
|
15435
|
+
const isInteractive = (process.env["ROLL_LOOP_FORCE"] ?? "").trim() !== "";
|
|
14926
15436
|
const ports = nodePorts({
|
|
14927
15437
|
repoCwd: id.path,
|
|
14928
15438
|
paths,
|
|
14929
15439
|
skillBody,
|
|
14930
|
-
routeDeps
|
|
15440
|
+
routeDeps,
|
|
15441
|
+
...isInteractive ? {
|
|
15442
|
+
agentSpawn: (agent, opts) => realAgentSpawn(agent, { ...opts, interactive: true })
|
|
15443
|
+
} : {}
|
|
14931
15444
|
});
|
|
14932
15445
|
const disposeSignals = installCycleSignalTeardown(paths, cycleId, branch);
|
|
14933
15446
|
let result;
|
|
@@ -14946,22 +15459,28 @@ loop run-once: \u627E\u4E0D\u5230 roll-loop SKILL.md \u2014 \u62D2\u7EDD\u76F2\u
|
|
|
14946
15459
|
if (result.terminal === "done") {
|
|
14947
15460
|
const storyId = (result.state?.ctx?.storyId ?? "").trim();
|
|
14948
15461
|
announceReport(id.path, id.slug, storyId);
|
|
15462
|
+
resetConsecutiveFails(id.path);
|
|
14949
15463
|
}
|
|
14950
|
-
|
|
15464
|
+
const isFail = result.terminal === "failed" || result.terminal === "blocked";
|
|
15465
|
+
if (isFail) {
|
|
15466
|
+
const storyId = (result.state?.ctx?.storyId ?? "").trim();
|
|
15467
|
+
incrementConsecutiveFails(id.path, id.slug, alertsPath, cycleId, storyId, result.terminal ?? "unknown");
|
|
15468
|
+
}
|
|
15469
|
+
return isFail ? 1 : 0;
|
|
14951
15470
|
}
|
|
14952
15471
|
|
|
14953
15472
|
// packages/cli/dist/commands/loop-sched.js
|
|
14954
15473
|
import { createHash as createHash4 } from "node:crypto";
|
|
14955
15474
|
import { spawn as spawn4, spawnSync as spawnSync6 } from "node:child_process";
|
|
14956
|
-
import { existsSync as
|
|
14957
|
-
import { homedir as
|
|
14958
|
-
import { dirname as
|
|
15475
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync19, readFileSync as readFileSync29, rmSync as rmSync9, writeFileSync as writeFileSync20 } from "node:fs";
|
|
15476
|
+
import { homedir as homedir15 } from "node:os";
|
|
15477
|
+
import { dirname as dirname16, join as join34 } from "node:path";
|
|
14959
15478
|
function realDeps3() {
|
|
14960
15479
|
return {
|
|
14961
15480
|
identity: () => projectIdentity(),
|
|
14962
15481
|
uid: () => process.getuid?.() ?? 501,
|
|
14963
|
-
sharedRoot: () => process.env["ROLL_SHARED_ROOT"] ||
|
|
14964
|
-
launchdDir: () =>
|
|
15482
|
+
sharedRoot: () => process.env["ROLL_SHARED_ROOT"] || join34(homedir15(), ".shared", "roll"),
|
|
15483
|
+
launchdDir: () => join34(homedir15(), "Library", "LaunchAgents"),
|
|
14965
15484
|
launchd: { reinstall, uninstall, isLoaded },
|
|
14966
15485
|
execRunner: (runner) => new Promise((resolve3) => {
|
|
14967
15486
|
const child = spawn4("bash", [runner], {
|
|
@@ -14981,8 +15500,8 @@ function realDeps3() {
|
|
|
14981
15500
|
// The `loop now` inline observation: tail live.log while the cycle holds
|
|
14982
15501
|
// the inner lock; Ctrl-C stops the TAIL only (the cycle lives in tmux).
|
|
14983
15502
|
observe: (rt) => new Promise((resolve3) => {
|
|
14984
|
-
const lock =
|
|
14985
|
-
const tail = spawn4("tail", ["-n", "+1", "-F",
|
|
15503
|
+
const lock = join34(rt, "inner.lock");
|
|
15504
|
+
const tail = spawn4("tail", ["-n", "+1", "-F", join34(rt, "live.log")], { stdio: "inherit" });
|
|
14986
15505
|
let sawLock = false;
|
|
14987
15506
|
const t0 = Date.now();
|
|
14988
15507
|
const finish = () => {
|
|
@@ -14994,9 +15513,9 @@ function realDeps3() {
|
|
|
14994
15513
|
resolve3();
|
|
14995
15514
|
};
|
|
14996
15515
|
const timer = setInterval(() => {
|
|
14997
|
-
if (
|
|
15516
|
+
if (existsSync37(lock))
|
|
14998
15517
|
sawLock = true;
|
|
14999
|
-
const done = sawLock ? !
|
|
15518
|
+
const done = sawLock ? !existsSync37(lock) : Date.now() - t0 > 3e4;
|
|
15000
15519
|
if (done) {
|
|
15001
15520
|
clearInterval(timer);
|
|
15002
15521
|
finish();
|
|
@@ -15153,7 +15672,7 @@ function parseLoopPeriodMinutes(text) {
|
|
|
15153
15672
|
return 30;
|
|
15154
15673
|
}
|
|
15155
15674
|
function pathValue() {
|
|
15156
|
-
const home =
|
|
15675
|
+
const home = homedir15();
|
|
15157
15676
|
return [
|
|
15158
15677
|
"/opt/homebrew/bin",
|
|
15159
15678
|
`${home}/.local/bin`,
|
|
@@ -15165,11 +15684,11 @@ function pathValue() {
|
|
|
15165
15684
|
].join(":");
|
|
15166
15685
|
}
|
|
15167
15686
|
function writeExecutable(path, content) {
|
|
15168
|
-
|
|
15169
|
-
|
|
15687
|
+
mkdirSync19(dirname16(path), { recursive: true });
|
|
15688
|
+
writeFileSync20(path, content, { mode: 493 });
|
|
15170
15689
|
}
|
|
15171
15690
|
function pauseMarkerPath(projectPath, slug) {
|
|
15172
|
-
return
|
|
15691
|
+
return join34(projectPath, ".roll", "loop", `PAUSE-${slug}`);
|
|
15173
15692
|
}
|
|
15174
15693
|
var LOOP_SERVICES = ["loop", "dream", "pr"];
|
|
15175
15694
|
async function mountService(deps, uid, label4, plist) {
|
|
@@ -15188,16 +15707,16 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15188
15707
|
const shared = deps.sharedRoot();
|
|
15189
15708
|
const ld = deps.launchdDir();
|
|
15190
15709
|
const uid = deps.uid();
|
|
15191
|
-
|
|
15710
|
+
mkdirSync19(ld, { recursive: true });
|
|
15192
15711
|
let period = 30;
|
|
15193
|
-
const localYaml =
|
|
15194
|
-
if (
|
|
15712
|
+
const localYaml = join34(id.path, ".roll", "local.yaml");
|
|
15713
|
+
if (existsSync37(localYaml)) {
|
|
15195
15714
|
try {
|
|
15196
15715
|
period = parseLoopPeriodMinutes(readFileSync29(localYaml, "utf8"));
|
|
15197
15716
|
} catch {
|
|
15198
15717
|
}
|
|
15199
15718
|
}
|
|
15200
|
-
const loopRunner =
|
|
15719
|
+
const loopRunner = join34(shared, "loop", `run-${id.slug}.sh`);
|
|
15201
15720
|
const rollBinOverride = (process.env["ROLL_RUNNER_ROLL_BIN"] ?? "").trim();
|
|
15202
15721
|
writeExecutable(loopRunner, buildLoopRunnerScript({
|
|
15203
15722
|
projectPath: id.path,
|
|
@@ -15208,7 +15727,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15208
15727
|
}));
|
|
15209
15728
|
const loopLabel = launchdLabel("loop", id.slug);
|
|
15210
15729
|
const loopPlist = launchdPlistPath("loop", id.slug, ld);
|
|
15211
|
-
|
|
15730
|
+
writeFileSync20(loopPlist, plistContent({
|
|
15212
15731
|
label: loopLabel,
|
|
15213
15732
|
runnerScript: loopRunner,
|
|
15214
15733
|
projectPath: id.path,
|
|
@@ -15216,14 +15735,14 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15216
15735
|
schedule: { kind: "interval", periodMinutes: period }
|
|
15217
15736
|
}));
|
|
15218
15737
|
const loopMount = await mountService(deps, uid, loopLabel, loopPlist);
|
|
15219
|
-
const prRunner =
|
|
15738
|
+
const prRunner = join34(shared, "pr", `run-${id.slug}.sh`);
|
|
15220
15739
|
writeExecutable(prRunner, buildPrRunnerScript({
|
|
15221
15740
|
projectPath: id.path,
|
|
15222
15741
|
...rollBinOverride !== "" ? { rollBin: rollBinOverride } : {}
|
|
15223
15742
|
}));
|
|
15224
15743
|
const prLabel = launchdLabel("pr", id.slug);
|
|
15225
15744
|
const prPlist = launchdPlistPath("pr", id.slug, ld);
|
|
15226
|
-
|
|
15745
|
+
writeFileSync20(prPlist, plistContent({
|
|
15227
15746
|
label: prLabel,
|
|
15228
15747
|
runnerScript: prRunner,
|
|
15229
15748
|
projectPath: id.path,
|
|
@@ -15232,7 +15751,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15232
15751
|
}));
|
|
15233
15752
|
const prMount = await mountService(deps, uid, prLabel, prPlist);
|
|
15234
15753
|
const dream = dreamScheduleFor(id.path);
|
|
15235
|
-
const dreamRunner =
|
|
15754
|
+
const dreamRunner = join34(shared, "dream", `run-${id.slug}.sh`);
|
|
15236
15755
|
writeExecutable(dreamRunner, buildDreamRunnerScript({
|
|
15237
15756
|
projectPath: id.path,
|
|
15238
15757
|
slug: id.slug,
|
|
@@ -15240,7 +15759,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15240
15759
|
}));
|
|
15241
15760
|
const dreamLabel = launchdLabel("dream", id.slug);
|
|
15242
15761
|
const dreamPlist = launchdPlistPath("dream", id.slug, ld);
|
|
15243
|
-
|
|
15762
|
+
writeFileSync20(dreamPlist, plistContent({
|
|
15244
15763
|
label: dreamLabel,
|
|
15245
15764
|
runnerScript: dreamRunner,
|
|
15246
15765
|
projectPath: id.path,
|
|
@@ -15292,10 +15811,10 @@ Loop \u5DF2\u505C\u7528(loop/dream/pr \u5747\u5DF2\u5378\u8F7D)
|
|
|
15292
15811
|
async function loopPauseCommand(_args, deps = realDeps3()) {
|
|
15293
15812
|
const id = await deps.identity();
|
|
15294
15813
|
const marker = pauseMarkerPath(id.path, id.slug);
|
|
15295
|
-
|
|
15296
|
-
const already =
|
|
15814
|
+
mkdirSync19(dirname16(marker), { recursive: true });
|
|
15815
|
+
const already = existsSync37(marker);
|
|
15297
15816
|
if (!already)
|
|
15298
|
-
|
|
15817
|
+
writeFileSync20(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
15299
15818
|
`);
|
|
15300
15819
|
process.stdout.write(already ? `Loop already paused
|
|
15301
15820
|
Loop \u5DF2\u5904\u4E8E\u6682\u505C
|
|
@@ -15307,7 +15826,7 @@ Loop \u5DF2\u6682\u505C \u2014 \u540E\u7EED\u6392\u7A0B\u5468\u671F\u5C06\u8DF3\
|
|
|
15307
15826
|
async function loopResumeCommand(_args, deps = realDeps3()) {
|
|
15308
15827
|
const id = await deps.identity();
|
|
15309
15828
|
const marker = pauseMarkerPath(id.path, id.slug);
|
|
15310
|
-
const existed =
|
|
15829
|
+
const existed = existsSync37(marker);
|
|
15311
15830
|
rmSync9(marker, { force: true });
|
|
15312
15831
|
process.stdout.write(existed ? `Loop resumed \u2014 scheduling active again
|
|
15313
15832
|
Loop \u5DF2\u6062\u590D \u2014 \u6392\u7A0B\u91CD\u65B0\u751F\u6548
|
|
@@ -15325,16 +15844,16 @@ function isLegacyRunner(text) {
|
|
|
15325
15844
|
}
|
|
15326
15845
|
async function loopNowCommand(_args, deps = realDeps3()) {
|
|
15327
15846
|
const id = await deps.identity();
|
|
15328
|
-
const runner =
|
|
15847
|
+
const runner = join34(deps.sharedRoot(), "loop", `run-${id.slug}.sh`);
|
|
15329
15848
|
let legacy = false;
|
|
15330
|
-
if (
|
|
15849
|
+
if (existsSync37(runner)) {
|
|
15331
15850
|
try {
|
|
15332
15851
|
legacy = isLegacyRunner(readFileSync29(runner, "utf8"));
|
|
15333
15852
|
} catch {
|
|
15334
15853
|
legacy = true;
|
|
15335
15854
|
}
|
|
15336
15855
|
}
|
|
15337
|
-
if (!
|
|
15856
|
+
if (!existsSync37(runner) || legacy) {
|
|
15338
15857
|
process.stdout.write(legacy ? `Legacy v2 runner detected \u2014 regenerating templates (FIX-197)
|
|
15339
15858
|
\u68C0\u6D4B\u5230 v2 \u65E7\u7248 runner \u2014 \u6B63\u5728\u518D\u751F\u6210\u6A21\u677F\uFF08FIX-197\uFF09
|
|
15340
15859
|
` : `No runner yet \u2014 generating templates
|
|
@@ -15362,7 +15881,7 @@ live transcript below \u2014 Ctrl-C stops watching, never the cycle
|
|
|
15362
15881
|
}
|
|
15363
15882
|
const rc = await exec(runner);
|
|
15364
15883
|
if (rc === 0 && useTmux && deps.observe !== void 0) {
|
|
15365
|
-
await deps.observe(
|
|
15884
|
+
await deps.observe(join34(id.path, ".roll", "loop"));
|
|
15366
15885
|
process.stdout.write(`
|
|
15367
15886
|
cycle finished \u2014 logs: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
15368
15887
|
\u5468\u671F\u7ED3\u675F \u2014 \u65E5\u5FD7: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
@@ -15373,8 +15892,8 @@ cycle finished \u2014 logs: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
|
15373
15892
|
|
|
15374
15893
|
// packages/cli/dist/commands/migrate.js
|
|
15375
15894
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
15376
|
-
import { existsSync as
|
|
15377
|
-
import { dirname as
|
|
15895
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync20 } from "node:fs";
|
|
15896
|
+
import { dirname as dirname17 } from "node:path";
|
|
15378
15897
|
function colors() {
|
|
15379
15898
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
15380
15899
|
return noColor2 ? { RED: "", GREEN: "", YELLOW: "", CYAN: "", NC: "" } : {
|
|
@@ -15493,9 +16012,9 @@ function migrateExecute(activeMoves) {
|
|
|
15493
16012
|
for (const move of activeMoves) {
|
|
15494
16013
|
const src = srcOf(move);
|
|
15495
16014
|
const tgt = tgtOf(move);
|
|
15496
|
-
const targetDir =
|
|
15497
|
-
if (!
|
|
15498
|
-
|
|
16015
|
+
const targetDir = dirname17(tgt);
|
|
16016
|
+
if (!existsSync38(targetDir))
|
|
16017
|
+
mkdirSync20(targetDir, { recursive: true });
|
|
15499
16018
|
const r = git3(["mv", src, tgt]);
|
|
15500
16019
|
if (r.status !== 0) {
|
|
15501
16020
|
err11(`git mv failed: ${src} \u2192 ${tgt}`);
|
|
@@ -15504,7 +16023,7 @@ function migrateExecute(activeMoves) {
|
|
|
15504
16023
|
}
|
|
15505
16024
|
moved += 1;
|
|
15506
16025
|
}
|
|
15507
|
-
if (
|
|
16026
|
+
if (existsSync38("docs")) {
|
|
15508
16027
|
spawnSync7("find", ["docs", "-type", "d", "-empty", "-delete"], { stdio: "ignore" });
|
|
15509
16028
|
}
|
|
15510
16029
|
git3([
|
|
@@ -15545,10 +16064,10 @@ function migrateCommand(args) {
|
|
|
15545
16064
|
const moves = buildMoves();
|
|
15546
16065
|
let hasNew = false;
|
|
15547
16066
|
let hasOld = false;
|
|
15548
|
-
if (
|
|
16067
|
+
if (existsSync38(".roll"))
|
|
15549
16068
|
hasNew = true;
|
|
15550
16069
|
for (const move of moves) {
|
|
15551
|
-
if (
|
|
16070
|
+
if (existsSync38(srcOf(move))) {
|
|
15552
16071
|
hasOld = true;
|
|
15553
16072
|
break;
|
|
15554
16073
|
}
|
|
@@ -15560,7 +16079,7 @@ function migrateCommand(args) {
|
|
|
15560
16079
|
for (const move of moves) {
|
|
15561
16080
|
const src = srcOf(move);
|
|
15562
16081
|
const tgt = tgtOf(move);
|
|
15563
|
-
if (
|
|
16082
|
+
if (existsSync38(src) && existsSync38(tgt)) {
|
|
15564
16083
|
process.stderr.write(` - ${src} AND ${tgt} both exist
|
|
15565
16084
|
`);
|
|
15566
16085
|
}
|
|
@@ -15577,7 +16096,7 @@ function migrateCommand(args) {
|
|
|
15577
16096
|
info3(m2("migrate.no_old_structure_detected_nothing_to"));
|
|
15578
16097
|
return 0;
|
|
15579
16098
|
}
|
|
15580
|
-
const activeMoves = moves.filter((move) =>
|
|
16099
|
+
const activeMoves = moves.filter((move) => existsSync38(srcOf(move)));
|
|
15581
16100
|
if (activeMoves.length === 0) {
|
|
15582
16101
|
warn4(m2("migrate.old_structure_markers_found_but_no"));
|
|
15583
16102
|
return 0;
|
|
@@ -15593,11 +16112,99 @@ function migrateCommand(args) {
|
|
|
15593
16112
|
return migrateExecute(activeMoves);
|
|
15594
16113
|
}
|
|
15595
16114
|
|
|
16115
|
+
// packages/cli/dist/commands/migrate-features.js
|
|
16116
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync21, readFileSync as readFileSync30, readlinkSync as readlinkSync3, statSync as statSync17, writeFileSync as writeFileSync21 } from "node:fs";
|
|
16117
|
+
import { join as join35 } from "node:path";
|
|
16118
|
+
function specFrontmatter(specFile, storyId) {
|
|
16119
|
+
try {
|
|
16120
|
+
const lines = readFileSync30(specFile, "utf8").split("\n").slice(0, 12);
|
|
16121
|
+
const pick = (key) => {
|
|
16122
|
+
const row2 = lines.find((l) => l.startsWith(`${key}: `));
|
|
16123
|
+
return row2?.slice(key.length + 2).trim();
|
|
16124
|
+
};
|
|
16125
|
+
let title = pick("title");
|
|
16126
|
+
if (title === void 0) {
|
|
16127
|
+
const heading = lines.find((l) => l.startsWith("# "));
|
|
16128
|
+
const m7 = heading?.match(new RegExp(`^# ${storyId}\\s*[\u2014\u2013-]+\\s*(.+)$`));
|
|
16129
|
+
if (m7 !== void 0 && m7 !== null)
|
|
16130
|
+
title = m7[1];
|
|
16131
|
+
}
|
|
16132
|
+
return { ...title !== void 0 ? { title } : {}, ...pick("created") !== void 0 ? { created: pick("created") } : {} };
|
|
16133
|
+
} catch {
|
|
16134
|
+
return {};
|
|
16135
|
+
}
|
|
16136
|
+
}
|
|
16137
|
+
function migrateFeaturesCommand(args) {
|
|
16138
|
+
const refresh = args.includes("--refresh-html");
|
|
16139
|
+
const cwd = process.cwd();
|
|
16140
|
+
if (!existsSync39(join35(cwd, ".roll", "index.json"))) {
|
|
16141
|
+
process.stderr.write("migrate-features: .roll/index.json not found\n");
|
|
16142
|
+
return 1;
|
|
16143
|
+
}
|
|
16144
|
+
const stories = readIndex(cwd);
|
|
16145
|
+
const root = join35(cwd, ".roll", "features");
|
|
16146
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
16147
|
+
let specCreated = 0;
|
|
16148
|
+
let htmlWritten = 0;
|
|
16149
|
+
let skipped = 0;
|
|
16150
|
+
for (const [storyId, epic] of Object.entries(stories)) {
|
|
16151
|
+
const storyDir = join35(root, epic, storyId);
|
|
16152
|
+
const specFile = join35(storyDir, "spec.md");
|
|
16153
|
+
if (!existsSync39(storyDir)) {
|
|
16154
|
+
const mdFile = join35(root, epic, `${storyId}.md`);
|
|
16155
|
+
if (existsSync39(mdFile)) {
|
|
16156
|
+
try {
|
|
16157
|
+
mkdirSync21(storyDir, { recursive: true });
|
|
16158
|
+
} catch {
|
|
16159
|
+
skipped++;
|
|
16160
|
+
continue;
|
|
16161
|
+
}
|
|
16162
|
+
} else {
|
|
16163
|
+
skipped++;
|
|
16164
|
+
continue;
|
|
16165
|
+
}
|
|
16166
|
+
}
|
|
16167
|
+
if (!existsSync39(specFile)) {
|
|
16168
|
+
writeFileSync21(specFile, renderSpecMd({
|
|
16169
|
+
id: storyId,
|
|
16170
|
+
epic,
|
|
16171
|
+
created: today,
|
|
16172
|
+
note: "Auto-generated by migrate-features (US-META-007)."
|
|
16173
|
+
}), "utf8");
|
|
16174
|
+
specCreated++;
|
|
16175
|
+
}
|
|
16176
|
+
const htmlFile = join35(storyDir, "index.html");
|
|
16177
|
+
if (!existsSync39(htmlFile) || refresh) {
|
|
16178
|
+
const fm = specFrontmatter(specFile, storyId);
|
|
16179
|
+
const card = { id: storyId, epic, created: fm.created ?? today, ...fm.title !== void 0 ? { title: fm.title } : {} };
|
|
16180
|
+
let html = renderStoryPage(card);
|
|
16181
|
+
const latest = join35(storyDir, "latest");
|
|
16182
|
+
const report = join35(latest, reportFileName(storyId));
|
|
16183
|
+
if (existsSync39(report)) {
|
|
16184
|
+
let runRel = "latest";
|
|
16185
|
+
try {
|
|
16186
|
+
runRel = readlinkSync3(latest);
|
|
16187
|
+
} catch {
|
|
16188
|
+
}
|
|
16189
|
+
const deliveredOn = statSync17(report).mtime.toISOString().slice(0, 10);
|
|
16190
|
+
html = markPhaseDone(html, "delivery", `<p><a href="${runRel}/${reportFileName(storyId)}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a></p>
|
|
16191
|
+
<p class="muted">${bi("Delivered", "\u4EA4\u4ED8\u4E8E")} ${deliveredOn}</p>
|
|
16192
|
+
`);
|
|
16193
|
+
}
|
|
16194
|
+
writeFileSync21(htmlFile, html, "utf8");
|
|
16195
|
+
htmlWritten++;
|
|
16196
|
+
}
|
|
16197
|
+
}
|
|
16198
|
+
process.stdout.write(`migrate-features: ${specCreated} spec.md + ${htmlWritten} index.html ${refresh ? "written" : "created"}, ${skipped} skipped
|
|
16199
|
+
`);
|
|
16200
|
+
return 0;
|
|
16201
|
+
}
|
|
16202
|
+
|
|
15596
16203
|
// packages/cli/dist/commands/offboard.js
|
|
15597
16204
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
15598
|
-
import { existsSync as
|
|
15599
|
-
import { homedir as
|
|
15600
|
-
import { join as
|
|
16205
|
+
import { existsSync as existsSync40, readFileSync as readFileSync31, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync22 } from "node:fs";
|
|
16206
|
+
import { homedir as homedir16 } from "node:os";
|
|
16207
|
+
import { join as join36, resolve as resolve2 } from "node:path";
|
|
15601
16208
|
function pal3() {
|
|
15602
16209
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
15603
16210
|
return noColor2 ? { RED: "", GREEN: "", YELLOW: "", CYAN: "", BOLD: "", NC: "" } : {
|
|
@@ -15635,7 +16242,7 @@ function m3(key, ...args) {
|
|
|
15635
16242
|
return t(v2Catalog, msgLang9(), key, ...args);
|
|
15636
16243
|
}
|
|
15637
16244
|
function changesetPath(projectDir) {
|
|
15638
|
-
return
|
|
16245
|
+
return join36(projectDir, ".roll", "onboard-changeset.yaml");
|
|
15639
16246
|
}
|
|
15640
16247
|
function loopInCycle() {
|
|
15641
16248
|
return (process.env["ROLL_LOOP_AGENT"] ?? "") !== "" || (process.env["ROLL_CYCLE_LOG_RAW"] ?? "") !== "";
|
|
@@ -15645,7 +16252,7 @@ function runLaunchctl(args) {
|
|
|
15645
16252
|
if (args[0] !== void 0 && readOnly.has(args[0])) {
|
|
15646
16253
|
return spawnSync8("launchctl", args, { stdio: "inherit" }).status ?? 1;
|
|
15647
16254
|
}
|
|
15648
|
-
const canonical =
|
|
16255
|
+
const canonical = join36(homedir16(), "Library", "LaunchAgents");
|
|
15649
16256
|
const launchdDir = process.env["_LAUNCHD_DIR"] ?? canonical;
|
|
15650
16257
|
if (launchdDir !== canonical)
|
|
15651
16258
|
return 0;
|
|
@@ -15709,7 +16316,7 @@ function offboardCommand(args) {
|
|
|
15709
16316
|
projectDir = process.cwd();
|
|
15710
16317
|
}
|
|
15711
16318
|
const changeset = changesetPath(projectDir);
|
|
15712
|
-
if (!
|
|
16319
|
+
if (!existsSync40(changeset)) {
|
|
15713
16320
|
err12(m3("offboard.no_changeset_en"));
|
|
15714
16321
|
err12(m3("offboard.no_changeset_zh"));
|
|
15715
16322
|
process.stderr.write("\n");
|
|
@@ -15723,7 +16330,7 @@ function offboardCommand(args) {
|
|
|
15723
16330
|
`);
|
|
15724
16331
|
return 1;
|
|
15725
16332
|
}
|
|
15726
|
-
const parsed = parseChangeset(
|
|
16333
|
+
const parsed = parseChangeset(readFileSync31(changeset, "utf8"));
|
|
15727
16334
|
if (!parsed.ok) {
|
|
15728
16335
|
err12(m3("offboard.failed_to_parse_changeset"));
|
|
15729
16336
|
return 1;
|
|
@@ -15799,8 +16406,8 @@ function offboardCommand(args) {
|
|
|
15799
16406
|
process.stdout.write(m3("offboard.applying_offboard") + "\n");
|
|
15800
16407
|
for (const item of files) {
|
|
15801
16408
|
try {
|
|
15802
|
-
rmSync10(
|
|
15803
|
-
if (!
|
|
16409
|
+
rmSync10(join36(projectDir, item), { force: true });
|
|
16410
|
+
if (!existsSync40(join36(projectDir, item)))
|
|
15804
16411
|
process.stdout.write(` removed file ${item}
|
|
15805
16412
|
`);
|
|
15806
16413
|
} catch {
|
|
@@ -15808,26 +16415,26 @@ function offboardCommand(args) {
|
|
|
15808
16415
|
}
|
|
15809
16416
|
for (const item of dirs) {
|
|
15810
16417
|
try {
|
|
15811
|
-
rmSync10(
|
|
16418
|
+
rmSync10(join36(projectDir, item), { recursive: true, force: true });
|
|
15812
16419
|
process.stdout.write(` removed dir ${item}
|
|
15813
16420
|
`);
|
|
15814
16421
|
} catch {
|
|
15815
16422
|
}
|
|
15816
16423
|
}
|
|
15817
16424
|
for (const item of giEntries) {
|
|
15818
|
-
const gi =
|
|
15819
|
-
if (
|
|
15820
|
-
const lines =
|
|
16425
|
+
const gi = join36(projectDir, ".gitignore");
|
|
16426
|
+
if (existsSync40(gi)) {
|
|
16427
|
+
const lines = readFileSync31(gi, "utf8").split("\n");
|
|
15821
16428
|
if (lines.includes(item)) {
|
|
15822
16429
|
const kept = lines.filter((l) => l !== item);
|
|
15823
|
-
|
|
16430
|
+
writeFileSync22(gi, kept.join("\n"));
|
|
15824
16431
|
process.stdout.write(` .gitignore - ${item}
|
|
15825
16432
|
`);
|
|
15826
16433
|
}
|
|
15827
16434
|
}
|
|
15828
16435
|
}
|
|
15829
16436
|
for (const item of plists) {
|
|
15830
|
-
const plistPath =
|
|
16437
|
+
const plistPath = join36(homedir16(), "Library", "LaunchAgents", item);
|
|
15831
16438
|
const r = runLaunchctl(["unload", "-w", plistPath]);
|
|
15832
16439
|
if (r === 0)
|
|
15833
16440
|
process.stdout.write(` unloaded ${item}
|
|
@@ -15840,16 +16447,16 @@ function offboardCommand(args) {
|
|
|
15840
16447
|
}
|
|
15841
16448
|
|
|
15842
16449
|
// packages/cli/dist/commands/prices.js
|
|
15843
|
-
import { existsSync as
|
|
15844
|
-
import { join as
|
|
16450
|
+
import { existsSync as existsSync41, readdirSync as readdirSync16, readFileSync as readFileSync32 } from "node:fs";
|
|
16451
|
+
import { join as join37 } from "node:path";
|
|
15845
16452
|
function loadSnapshots() {
|
|
15846
|
-
const dir =
|
|
15847
|
-
if (!
|
|
16453
|
+
const dir = join37(repoRoot(), "lib", "prices");
|
|
16454
|
+
if (!existsSync41(dir)) {
|
|
15848
16455
|
throw new Error(`no price snapshots found in ${dir}; run \`roll prices refresh\``);
|
|
15849
16456
|
}
|
|
15850
|
-
const files =
|
|
16457
|
+
const files = readdirSync16(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
|
|
15851
16458
|
return files.map((name) => {
|
|
15852
|
-
const data = JSON.parse(
|
|
16459
|
+
const data = JSON.parse(readFileSync32(join37(dir, name), "utf8"));
|
|
15853
16460
|
for (const key of ["version", "effective_at", "source_url", "prices"]) {
|
|
15854
16461
|
if (data[key] === void 0)
|
|
15855
16462
|
throw new Error(`snapshot ${name} missing required key ${key}`);
|
|
@@ -15942,8 +16549,8 @@ function pricesCommand(args) {
|
|
|
15942
16549
|
}
|
|
15943
16550
|
|
|
15944
16551
|
// packages/cli/dist/commands/release.js
|
|
15945
|
-
import { existsSync as
|
|
15946
|
-
import { join as
|
|
16552
|
+
import { existsSync as existsSync42, readFileSync as readFileSync33 } from "node:fs";
|
|
16553
|
+
import { join as join38 } from "node:path";
|
|
15947
16554
|
function label3(lang4, key, ...args) {
|
|
15948
16555
|
if (v3Catalog[key] !== void 0)
|
|
15949
16556
|
return t(v3Catalog, lang4, key, ...args);
|
|
@@ -15954,17 +16561,17 @@ function dateOf(d) {
|
|
|
15954
16561
|
}
|
|
15955
16562
|
function currentVersion(cwd) {
|
|
15956
16563
|
try {
|
|
15957
|
-
const pkg = JSON.parse(
|
|
16564
|
+
const pkg = JSON.parse(readFileSync33(join38(cwd, "package.json"), "utf8"));
|
|
15958
16565
|
return typeof pkg.version === "string" ? pkg.version : "";
|
|
15959
16566
|
} catch {
|
|
15960
16567
|
return "";
|
|
15961
16568
|
}
|
|
15962
16569
|
}
|
|
15963
16570
|
function changelogReady(cwd) {
|
|
15964
|
-
const path =
|
|
15965
|
-
if (!
|
|
16571
|
+
const path = join38(cwd, "CHANGELOG.md");
|
|
16572
|
+
if (!existsSync42(path))
|
|
15966
16573
|
return false;
|
|
15967
|
-
const text =
|
|
16574
|
+
const text = readFileSync33(path, "utf8");
|
|
15968
16575
|
const idx = text.indexOf("## Unreleased");
|
|
15969
16576
|
if (idx === -1)
|
|
15970
16577
|
return false;
|
|
@@ -16031,8 +16638,8 @@ function releaseCommand(args, now) {
|
|
|
16031
16638
|
|
|
16032
16639
|
// packages/cli/dist/commands/setup.js
|
|
16033
16640
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
16034
|
-
import { existsSync as
|
|
16035
|
-
import { join as
|
|
16641
|
+
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";
|
|
16642
|
+
import { join as join39 } from "node:path";
|
|
16036
16643
|
function err13(line) {
|
|
16037
16644
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
16038
16645
|
const RED = noColor2 ? "" : "\x1B[0;31m";
|
|
@@ -16057,7 +16664,7 @@ function setupSnapshot(watch) {
|
|
|
16057
16664
|
continue;
|
|
16058
16665
|
let isDir4 = false;
|
|
16059
16666
|
try {
|
|
16060
|
-
isDir4 =
|
|
16667
|
+
isDir4 = statSync18(d).isDirectory();
|
|
16061
16668
|
} catch {
|
|
16062
16669
|
isDir4 = false;
|
|
16063
16670
|
}
|
|
@@ -16070,12 +16677,12 @@ function setupSnapshot(watch) {
|
|
|
16070
16677
|
function walk(dir, lines) {
|
|
16071
16678
|
let names;
|
|
16072
16679
|
try {
|
|
16073
|
-
names =
|
|
16680
|
+
names = readdirSync17(dir);
|
|
16074
16681
|
} catch {
|
|
16075
16682
|
return;
|
|
16076
16683
|
}
|
|
16077
16684
|
for (const name of names) {
|
|
16078
|
-
const p =
|
|
16685
|
+
const p = join39(dir, name);
|
|
16079
16686
|
let isLink = false;
|
|
16080
16687
|
try {
|
|
16081
16688
|
isLink = lstatSync3(p).isSymbolicLink();
|
|
@@ -16084,14 +16691,14 @@ function walk(dir, lines) {
|
|
|
16084
16691
|
}
|
|
16085
16692
|
if (isLink) {
|
|
16086
16693
|
try {
|
|
16087
|
-
lines.push(`L ${p} -> ${
|
|
16694
|
+
lines.push(`L ${p} -> ${readlinkSync4(p)}`);
|
|
16088
16695
|
} catch {
|
|
16089
16696
|
}
|
|
16090
16697
|
continue;
|
|
16091
16698
|
}
|
|
16092
16699
|
let st;
|
|
16093
16700
|
try {
|
|
16094
|
-
st =
|
|
16701
|
+
st = statSync18(p);
|
|
16095
16702
|
} catch {
|
|
16096
16703
|
continue;
|
|
16097
16704
|
}
|
|
@@ -16105,7 +16712,7 @@ function walk(dir, lines) {
|
|
|
16105
16712
|
}
|
|
16106
16713
|
function fileFingerprint(p) {
|
|
16107
16714
|
try {
|
|
16108
|
-
const buf =
|
|
16715
|
+
const buf = readFileSync34(p);
|
|
16109
16716
|
let sum = 0;
|
|
16110
16717
|
for (let i = 0; i < buf.length; i++)
|
|
16111
16718
|
sum = sum * 31 + (buf[i] ?? 0) >>> 0;
|
|
@@ -16135,21 +16742,21 @@ function ensureHooksPath(repoPath) {
|
|
|
16135
16742
|
}
|
|
16136
16743
|
}
|
|
16137
16744
|
function peerEnsureStateDir() {
|
|
16138
|
-
const base =
|
|
16139
|
-
|
|
16140
|
-
|
|
16745
|
+
const base = join39(rollHome(), ".peer-state");
|
|
16746
|
+
mkdirSync22(base, { recursive: true });
|
|
16747
|
+
mkdirSync22(join39(base, "logs"), { recursive: true });
|
|
16141
16748
|
}
|
|
16142
16749
|
function tmuxPresent() {
|
|
16143
16750
|
return onPath("tmux");
|
|
16144
16751
|
}
|
|
16145
16752
|
function submoduleGuard() {
|
|
16146
16753
|
const pkg = rollPkgDir();
|
|
16147
|
-
const skills =
|
|
16148
|
-
const hasGit =
|
|
16149
|
-
const hasMods =
|
|
16754
|
+
const skills = join39(pkg, "skills");
|
|
16755
|
+
const hasGit = existsSync43(join39(pkg, ".git"));
|
|
16756
|
+
const hasMods = existsSync43(join39(pkg, ".gitmodules"));
|
|
16150
16757
|
let empty = true;
|
|
16151
16758
|
try {
|
|
16152
|
-
empty =
|
|
16759
|
+
empty = readdirSync17(skills).length === 0;
|
|
16153
16760
|
} catch {
|
|
16154
16761
|
empty = true;
|
|
16155
16762
|
}
|
|
@@ -16239,7 +16846,7 @@ function setupCommand(args) {
|
|
|
16239
16846
|
return 1;
|
|
16240
16847
|
}
|
|
16241
16848
|
}
|
|
16242
|
-
if (!
|
|
16849
|
+
if (!existsSync43(rollPkgConventions()))
|
|
16243
16850
|
return null;
|
|
16244
16851
|
submoduleGuard();
|
|
16245
16852
|
const home = rollHome();
|
|
@@ -16258,7 +16865,7 @@ function setupCommand(args) {
|
|
|
16258
16865
|
".qwen"
|
|
16259
16866
|
];
|
|
16260
16867
|
const homeDir = process.env["HOME"] ?? "";
|
|
16261
|
-
const aiDirs = aiDirsList.map((d) =>
|
|
16868
|
+
const aiDirs = aiDirsList.map((d) => join39(homeDir, d)).join(":");
|
|
16262
16869
|
const steps = [];
|
|
16263
16870
|
const s1 = runSetupStep(home, () => {
|
|
16264
16871
|
installLocal(force);
|
|
@@ -16272,7 +16879,7 @@ function setupCommand(args) {
|
|
|
16272
16879
|
syncSkills(force);
|
|
16273
16880
|
});
|
|
16274
16881
|
steps.push({ num: 3, label: "Install skills to ~/.claude", status: stateToMarker(s3, force) });
|
|
16275
|
-
const s4 = runSetupStep(
|
|
16882
|
+
const s4 = runSetupStep(join39(home, ".peer-state"), () => {
|
|
16276
16883
|
peerEnsureStateDir();
|
|
16277
16884
|
});
|
|
16278
16885
|
steps.push({ num: 4, label: "Initialize peer-review state directory", status: stateToMarker(s4, force) });
|
|
@@ -16293,12 +16900,12 @@ function setupCommand(args) {
|
|
|
16293
16900
|
|
|
16294
16901
|
// packages/cli/dist/commands/slides/index.js
|
|
16295
16902
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
16296
|
-
import { mkdirSync as
|
|
16297
|
-
import { join as
|
|
16903
|
+
import { mkdirSync as mkdirSync23, readFileSync as readFileSync37, readdirSync as readdirSync19, rmSync as rmSync11, statSync as statSync21, writeFileSync as writeFileSync23 } from "node:fs";
|
|
16904
|
+
import { join as join41 } from "node:path";
|
|
16298
16905
|
|
|
16299
16906
|
// packages/cli/dist/commands/slides/render.js
|
|
16300
|
-
import { readFileSync as
|
|
16301
|
-
import { join as
|
|
16907
|
+
import { readFileSync as readFileSync35, existsSync as existsSync44, readdirSync as readdirSync18, statSync as statSync19 } from "node:fs";
|
|
16908
|
+
import { join as join40, dirname as dirname18, basename as basename9 } from "node:path";
|
|
16302
16909
|
function coerceScalar(v) {
|
|
16303
16910
|
if (v.length >= 2 && v[0] === v[v.length - 1] && (v[0] === "'" || v[0] === '"')) {
|
|
16304
16911
|
return v.slice(1, -1);
|
|
@@ -16504,8 +17111,8 @@ function parseSequence(block, baseIndent) {
|
|
|
16504
17111
|
i += 1;
|
|
16505
17112
|
continue;
|
|
16506
17113
|
}
|
|
16507
|
-
const
|
|
16508
|
-
if (
|
|
17114
|
+
const bi2 = indentOf2(bl);
|
|
17115
|
+
if (bi2 <= baseIndent)
|
|
16509
17116
|
break;
|
|
16510
17117
|
sub.push(bl);
|
|
16511
17118
|
i += 1;
|
|
@@ -16783,12 +17390,12 @@ var DEFAULT_LAYOUT = "plain";
|
|
|
16783
17390
|
var LayoutResolver = class {
|
|
16784
17391
|
componentsDir;
|
|
16785
17392
|
constructor(componentsDir) {
|
|
16786
|
-
this.componentsDir = componentsDir ??
|
|
17393
|
+
this.componentsDir = componentsDir ?? join40(libDirFor(import.meta.url), "slides", "components");
|
|
16787
17394
|
}
|
|
16788
17395
|
available() {
|
|
16789
17396
|
if (!isDir2(this.componentsDir))
|
|
16790
17397
|
return [];
|
|
16791
|
-
const names =
|
|
17398
|
+
const names = readdirSync18(this.componentsDir).filter((f) => f.endsWith(".html")).map((f) => basename9(f, ".html"));
|
|
16792
17399
|
const rest = names.filter((n) => n !== DEFAULT_LAYOUT).sort();
|
|
16793
17400
|
const head = names.includes(DEFAULT_LAYOUT) ? [DEFAULT_LAYOUT] : [];
|
|
16794
17401
|
return [...head, ...rest];
|
|
@@ -16797,7 +17404,7 @@ var LayoutResolver = class {
|
|
|
16797
17404
|
if (!/^[a-z0-9-]+$/.test(layout)) {
|
|
16798
17405
|
throw new ValueError(`Unknown layout: ${layout}; available: ${this.available().join(", ")}`);
|
|
16799
17406
|
}
|
|
16800
|
-
const path =
|
|
17407
|
+
const path = join40(this.componentsDir, `${layout}.html`);
|
|
16801
17408
|
if (!isFile(path)) {
|
|
16802
17409
|
throw new ValueError(`Unknown layout: ${layout}; available: ${this.available().join(", ")}`);
|
|
16803
17410
|
}
|
|
@@ -16807,7 +17414,7 @@ var LayoutResolver = class {
|
|
|
16807
17414
|
function renderSlideInner(slide, resolver) {
|
|
16808
17415
|
const layout = typeof slide["layout"] === "string" && slide["layout"] || DEFAULT_LAYOUT;
|
|
16809
17416
|
const partialPath = resolver.resolve(String(layout));
|
|
16810
|
-
let partial =
|
|
17417
|
+
let partial = readFileSync35(partialPath, "utf8");
|
|
16811
17418
|
partial = partial.replace(/^\s*<!--[\s\S]*?-->(?:\s*\n)?/, "");
|
|
16812
17419
|
const rendered = mustache(partial, slide);
|
|
16813
17420
|
return stripNewlines(rendered);
|
|
@@ -16855,30 +17462,30 @@ function pyRepr(s) {
|
|
|
16855
17462
|
}
|
|
16856
17463
|
function isDir2(p) {
|
|
16857
17464
|
try {
|
|
16858
|
-
return
|
|
17465
|
+
return statSync19(p).isDirectory();
|
|
16859
17466
|
} catch {
|
|
16860
17467
|
return false;
|
|
16861
17468
|
}
|
|
16862
17469
|
}
|
|
16863
17470
|
function isFile(p) {
|
|
16864
17471
|
try {
|
|
16865
|
-
return
|
|
17472
|
+
return statSync19(p).isFile();
|
|
16866
17473
|
} catch {
|
|
16867
17474
|
return false;
|
|
16868
17475
|
}
|
|
16869
17476
|
}
|
|
16870
17477
|
function libDirFor(_metaUrl) {
|
|
16871
17478
|
void _metaUrl;
|
|
16872
|
-
void
|
|
16873
|
-
void
|
|
16874
|
-
return
|
|
17479
|
+
void existsSync44;
|
|
17480
|
+
void dirname18;
|
|
17481
|
+
return join40(repoLibFallback(), "lib");
|
|
16875
17482
|
}
|
|
16876
17483
|
function repoLibFallback() {
|
|
16877
17484
|
let dir = process.cwd();
|
|
16878
17485
|
for (let i = 0; i < 12; i++) {
|
|
16879
|
-
if (
|
|
17486
|
+
if (existsSync44(join40(dir, "bin", "roll")))
|
|
16880
17487
|
return dir;
|
|
16881
|
-
const parent =
|
|
17488
|
+
const parent = dirname18(dir);
|
|
16882
17489
|
if (parent === dir)
|
|
16883
17490
|
break;
|
|
16884
17491
|
dir = parent;
|
|
@@ -16887,7 +17494,7 @@ function repoLibFallback() {
|
|
|
16887
17494
|
}
|
|
16888
17495
|
|
|
16889
17496
|
// packages/cli/dist/commands/slides/validate.js
|
|
16890
|
-
import { readFileSync as
|
|
17497
|
+
import { readFileSync as readFileSync36, statSync as statSync20 } from "node:fs";
|
|
16891
17498
|
var REQUIRED_FRONTMATTER = [
|
|
16892
17499
|
"template",
|
|
16893
17500
|
"slug",
|
|
@@ -17091,7 +17698,7 @@ function validateDeckFile(path, errSink, componentsDir) {
|
|
|
17091
17698
|
let fm;
|
|
17092
17699
|
let slides;
|
|
17093
17700
|
try {
|
|
17094
|
-
src =
|
|
17701
|
+
src = readFileSync36(path, "utf8");
|
|
17095
17702
|
[fm] = parseFrontmatter(src);
|
|
17096
17703
|
const [, body] = parseFrontmatter(src);
|
|
17097
17704
|
slides = parseSlides(body);
|
|
@@ -17125,7 +17732,7 @@ function validateDeckFile(path, errSink, componentsDir) {
|
|
|
17125
17732
|
}
|
|
17126
17733
|
function isFile2(p) {
|
|
17127
17734
|
try {
|
|
17128
|
-
return
|
|
17735
|
+
return statSync20(p).isFile();
|
|
17129
17736
|
} catch {
|
|
17130
17737
|
return false;
|
|
17131
17738
|
}
|
|
@@ -17204,13 +17811,13 @@ function slidesHelp(out2) {
|
|
|
17204
17811
|
out2(SLIDES_HELP);
|
|
17205
17812
|
}
|
|
17206
17813
|
function slidesLib() {
|
|
17207
|
-
return
|
|
17814
|
+
return join41(rollPkgDir(), "lib");
|
|
17208
17815
|
}
|
|
17209
17816
|
function slidesTemplatePath(name) {
|
|
17210
|
-
const projTpl =
|
|
17817
|
+
const projTpl = join41(".roll", "slides", "templates", `${name}.html`);
|
|
17211
17818
|
if (isFile3(projTpl))
|
|
17212
17819
|
return projTpl;
|
|
17213
|
-
const tpl =
|
|
17820
|
+
const tpl = join41(rollPkgDir(), "lib", "slides", "templates", `${name}.html`);
|
|
17214
17821
|
if (isFile3(tpl))
|
|
17215
17822
|
return tpl;
|
|
17216
17823
|
return null;
|
|
@@ -17218,7 +17825,7 @@ function slidesTemplatePath(name) {
|
|
|
17218
17825
|
function slidesTemplateForDeck(deckPath) {
|
|
17219
17826
|
let tpl = "";
|
|
17220
17827
|
try {
|
|
17221
|
-
const src =
|
|
17828
|
+
const src = readFileSync37(deckPath, "utf8");
|
|
17222
17829
|
let d = 0;
|
|
17223
17830
|
for (const line of src.split("\n")) {
|
|
17224
17831
|
if (/^---[ \t]*$/.test(line)) {
|
|
@@ -17244,21 +17851,21 @@ function slidesTemplateForDeck(deckPath) {
|
|
|
17244
17851
|
return tpl;
|
|
17245
17852
|
}
|
|
17246
17853
|
function slidesEnsureGitignore() {
|
|
17247
|
-
const gi =
|
|
17248
|
-
|
|
17854
|
+
const gi = join41(".roll", ".gitignore");
|
|
17855
|
+
mkdirSync23(".roll", { recursive: true });
|
|
17249
17856
|
if (isFile3(gi)) {
|
|
17250
|
-
const content =
|
|
17857
|
+
const content = readFileSync37(gi, "utf8");
|
|
17251
17858
|
if (content.split("\n").some((l) => /^slides\/\*\.html$/.test(l)))
|
|
17252
17859
|
return;
|
|
17253
17860
|
if (content.length > 0 && !content.endsWith("\n")) {
|
|
17254
|
-
|
|
17861
|
+
writeFileSync23(gi, content + "\n");
|
|
17255
17862
|
}
|
|
17256
17863
|
}
|
|
17257
17864
|
appendFile(gi, "slides/*.html\n");
|
|
17258
17865
|
}
|
|
17259
17866
|
function appendFile(path, data) {
|
|
17260
|
-
const prev = isFile3(path) ?
|
|
17261
|
-
|
|
17867
|
+
const prev = isFile3(path) ? readFileSync37(path, "utf8") : "";
|
|
17868
|
+
writeFileSync23(path, prev + data);
|
|
17262
17869
|
}
|
|
17263
17870
|
function slidesOpenCmd() {
|
|
17264
17871
|
const sys = spawnSync10("uname", ["-s"], { encoding: "utf8" });
|
|
@@ -17317,7 +17924,7 @@ function cmdBuild(args) {
|
|
|
17317
17924
|
process.stderr.write(m5("slides_build.usage_roll_slides_build_slug_no") + "\n");
|
|
17318
17925
|
return 1;
|
|
17319
17926
|
}
|
|
17320
|
-
const deck =
|
|
17927
|
+
const deck = join41(".roll", "slides", slug, "deck.md");
|
|
17321
17928
|
if (!isFile3(deck)) {
|
|
17322
17929
|
err15(`Deck not found: ${deck}`);
|
|
17323
17930
|
process.stderr.write(m5("slides_build.en_deck", deck) + "\n");
|
|
@@ -17327,14 +17934,14 @@ function cmdBuild(args) {
|
|
|
17327
17934
|
return 1;
|
|
17328
17935
|
}
|
|
17329
17936
|
const libDir = slidesLib();
|
|
17330
|
-
const validator =
|
|
17331
|
-
const renderer =
|
|
17937
|
+
const validator = join41(libDir, "slides-validate.py");
|
|
17938
|
+
const renderer = join41(libDir, "slides-render.py");
|
|
17332
17939
|
if (!isFile3(validator) || !isFile3(renderer)) {
|
|
17333
17940
|
err15(m5("slides_build.slides_toolchain_missing_re_run_roll"));
|
|
17334
17941
|
return 1;
|
|
17335
17942
|
}
|
|
17336
|
-
const errFile =
|
|
17337
|
-
const componentsDir =
|
|
17943
|
+
const errFile = join41(".roll", "slides", slug, ".last-build.err");
|
|
17944
|
+
const componentsDir = join41(libDir, "slides", "components");
|
|
17338
17945
|
const valLines = [];
|
|
17339
17946
|
const valExit = validateDeckFile(deck, (l) => valLines.push(l), componentsDir);
|
|
17340
17947
|
const valOut = valLines.join("\n");
|
|
@@ -17343,8 +17950,8 @@ function cmdBuild(args) {
|
|
|
17343
17950
|
`);
|
|
17344
17951
|
} else if (valExit !== 0) {
|
|
17345
17952
|
const ts = utcTs();
|
|
17346
|
-
|
|
17347
|
-
|
|
17953
|
+
mkdirSync23(join41(".roll", "slides", slug), { recursive: true });
|
|
17954
|
+
writeFileSync23(errFile, `[${ts}] stage=validate
|
|
17348
17955
|
${valOut}
|
|
17349
17956
|
`);
|
|
17350
17957
|
if (valOut !== "")
|
|
@@ -17361,15 +17968,15 @@ ${valOut}
|
|
|
17361
17968
|
const tplPath = slidesTemplatePath(tplName);
|
|
17362
17969
|
if (tplPath === null) {
|
|
17363
17970
|
const ts = utcTs();
|
|
17364
|
-
|
|
17365
|
-
|
|
17971
|
+
mkdirSync23(join41(".roll", "slides", slug), { recursive: true });
|
|
17972
|
+
writeFileSync23(errFile, `[${ts}] stage=template
|
|
17366
17973
|
template not found: ${tplName}
|
|
17367
17974
|
`);
|
|
17368
17975
|
const { RED, NC } = pal4();
|
|
17369
17976
|
process.stderr.write(`${RED}[FAIL]${NC} ${m5("slides_build.template_not_found", tplName)}
|
|
17370
17977
|
`);
|
|
17371
17978
|
process.stderr.write(" " + m5("slides_build.available_templates") + "\n");
|
|
17372
|
-
const builtinDir =
|
|
17979
|
+
const builtinDir = join41(rollPkgDir(), "lib", "slides", "templates");
|
|
17373
17980
|
if (isDir3(builtinDir)) {
|
|
17374
17981
|
for (const t2 of listHtml(builtinDir)) {
|
|
17375
17982
|
const n = basenameNoHtml(t2);
|
|
@@ -17377,7 +17984,7 @@ template not found: ${tplName}
|
|
|
17377
17984
|
`);
|
|
17378
17985
|
}
|
|
17379
17986
|
}
|
|
17380
|
-
const projDir =
|
|
17987
|
+
const projDir = join41(".roll", "slides", "templates");
|
|
17381
17988
|
if (isDir3(projDir)) {
|
|
17382
17989
|
for (const t2 of listHtml(projDir)) {
|
|
17383
17990
|
const n = basenameNoHtml(t2);
|
|
@@ -17388,18 +17995,18 @@ template not found: ${tplName}
|
|
|
17388
17995
|
process.stderr.write(" " + m5("slides_build.templates_list_hint") + "\n");
|
|
17389
17996
|
return 1;
|
|
17390
17997
|
}
|
|
17391
|
-
const out2 =
|
|
17392
|
-
|
|
17998
|
+
const out2 = join41(".roll", "slides", `${slug}.html`);
|
|
17999
|
+
mkdirSync23(join41(".roll", "slides"), { recursive: true });
|
|
17393
18000
|
let htmlOut;
|
|
17394
18001
|
try {
|
|
17395
|
-
const src =
|
|
17396
|
-
const template =
|
|
18002
|
+
const src = readFileSync37(deck, "utf8");
|
|
18003
|
+
const template = readFileSync37(tplPath, "utf8");
|
|
17397
18004
|
htmlOut = renderDeck(src, template, { componentsDir });
|
|
17398
18005
|
} catch (e) {
|
|
17399
18006
|
const renderOut = renderErrorBlock(e);
|
|
17400
18007
|
const ts = utcTs();
|
|
17401
|
-
|
|
17402
|
-
|
|
18008
|
+
mkdirSync23(join41(".roll", "slides", slug), { recursive: true });
|
|
18009
|
+
writeFileSync23(errFile, `[${ts}] stage=render
|
|
17403
18010
|
${renderOut}
|
|
17404
18011
|
`);
|
|
17405
18012
|
const { RED, NC } = pal4();
|
|
@@ -17413,7 +18020,7 @@ ${renderOut}
|
|
|
17413
18020
|
}
|
|
17414
18021
|
return 1;
|
|
17415
18022
|
}
|
|
17416
|
-
|
|
18023
|
+
writeFileSync23(out2, htmlOut);
|
|
17417
18024
|
try {
|
|
17418
18025
|
rmSync11(errFile, { force: true });
|
|
17419
18026
|
} catch {
|
|
@@ -17438,7 +18045,7 @@ function tailLines(s, n) {
|
|
|
17438
18045
|
}
|
|
17439
18046
|
function frontmatterField2(deckPath, field) {
|
|
17440
18047
|
try {
|
|
17441
|
-
const src =
|
|
18048
|
+
const src = readFileSync37(deckPath, "utf8");
|
|
17442
18049
|
let d = 0;
|
|
17443
18050
|
const pat = new RegExp("^" + field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "[ ]*:[ ]*");
|
|
17444
18051
|
for (const line of src.split("\n")) {
|
|
@@ -17481,7 +18088,7 @@ function cmdList(args) {
|
|
|
17481
18088
|
err15(m5("slides_list.unexpected_argument_1"));
|
|
17482
18089
|
return 1;
|
|
17483
18090
|
}
|
|
17484
|
-
const slidesDir =
|
|
18091
|
+
const slidesDir = join41(".roll", "slides");
|
|
17485
18092
|
if (!isDir3(slidesDir)) {
|
|
17486
18093
|
info5(m5("slides_list.no_decks_found_under_roll_slides"));
|
|
17487
18094
|
process.stdout.write(` Hint: run 'roll slides new "<topic>"' to create one.
|
|
@@ -17490,9 +18097,9 @@ function cmdList(args) {
|
|
|
17490
18097
|
return 0;
|
|
17491
18098
|
}
|
|
17492
18099
|
const slugs = [];
|
|
17493
|
-
for (const entry of
|
|
17494
|
-
const d =
|
|
17495
|
-
if (isDir3(d) && isFile3(
|
|
18100
|
+
for (const entry of readdirSync19(slidesDir)) {
|
|
18101
|
+
const d = join41(slidesDir, entry);
|
|
18102
|
+
if (isDir3(d) && isFile3(join41(d, "deck.md")))
|
|
17496
18103
|
slugs.push(entry);
|
|
17497
18104
|
}
|
|
17498
18105
|
if (slugs.length === 0) {
|
|
@@ -17506,9 +18113,9 @@ function cmdList(args) {
|
|
|
17506
18113
|
process.stdout.write(rowSix("slug", "template", "total_slides", "created", "built", "size") + "\n");
|
|
17507
18114
|
process.stdout.write(rowSix("----", "--------", "------------", "-------", "------", "----") + "\n");
|
|
17508
18115
|
for (const s of sorted) {
|
|
17509
|
-
const deck =
|
|
17510
|
-
const html =
|
|
17511
|
-
const errFile =
|
|
18116
|
+
const deck = join41(slidesDir, s, "deck.md");
|
|
18117
|
+
const html = join41(slidesDir, `${s}.html`);
|
|
18118
|
+
const errFile = join41(slidesDir, s, ".last-build.err");
|
|
17512
18119
|
let template = frontmatterField2(deck, "template");
|
|
17513
18120
|
if (template === "")
|
|
17514
18121
|
template = "-";
|
|
@@ -17531,7 +18138,7 @@ function cmdList(args) {
|
|
|
17531
18138
|
built = "\u2713 built";
|
|
17532
18139
|
let bytes = 0;
|
|
17533
18140
|
try {
|
|
17534
|
-
bytes =
|
|
18141
|
+
bytes = statSync21(html).size;
|
|
17535
18142
|
} catch {
|
|
17536
18143
|
bytes = 0;
|
|
17537
18144
|
}
|
|
@@ -17547,7 +18154,7 @@ function cmdList(args) {
|
|
|
17547
18154
|
}
|
|
17548
18155
|
function isNewer(a, b) {
|
|
17549
18156
|
try {
|
|
17550
|
-
return
|
|
18157
|
+
return statSync21(a).mtimeMs > statSync21(b).mtimeMs;
|
|
17551
18158
|
} catch {
|
|
17552
18159
|
return false;
|
|
17553
18160
|
}
|
|
@@ -17582,7 +18189,7 @@ function cmdPreview(args) {
|
|
|
17582
18189
|
process.stderr.write(m5("slides_preview.usage_roll_slides_preview_slug_no") + "\n");
|
|
17583
18190
|
return 1;
|
|
17584
18191
|
}
|
|
17585
|
-
const html =
|
|
18192
|
+
const html = join41(".roll", "slides", `${slug}.html`);
|
|
17586
18193
|
if (!isFile3(html)) {
|
|
17587
18194
|
err15(`Rendered HTML not found: ${html}`);
|
|
17588
18195
|
process.stderr.write(m5("slides_preview.en_html", html) + "\n");
|
|
@@ -17623,9 +18230,9 @@ function cmdLogs(args) {
|
|
|
17623
18230
|
process.stderr.write(m5("slides_logs.usage_roll_slides_logs_slug") + "\n");
|
|
17624
18231
|
return 1;
|
|
17625
18232
|
}
|
|
17626
|
-
const deckDir =
|
|
17627
|
-
const errFile =
|
|
17628
|
-
if (!isDir3(deckDir) || !isFile3(
|
|
18233
|
+
const deckDir = join41(".roll", "slides", slug);
|
|
18234
|
+
const errFile = join41(deckDir, ".last-build.err");
|
|
18235
|
+
if (!isDir3(deckDir) || !isFile3(join41(deckDir, "deck.md"))) {
|
|
17629
18236
|
err15(m5("slides_logs.deck_not_found", slug));
|
|
17630
18237
|
return 1;
|
|
17631
18238
|
}
|
|
@@ -17633,7 +18240,7 @@ function cmdLogs(args) {
|
|
|
17633
18240
|
info5(m5("slides_logs.no_failure_records_for", slug));
|
|
17634
18241
|
return 0;
|
|
17635
18242
|
}
|
|
17636
|
-
process.stdout.write(
|
|
18243
|
+
process.stdout.write(readFileSync37(errFile, "utf8"));
|
|
17637
18244
|
return 0;
|
|
17638
18245
|
}
|
|
17639
18246
|
function cmdDelete(args) {
|
|
@@ -17666,9 +18273,9 @@ function cmdDelete(args) {
|
|
|
17666
18273
|
process.stderr.write(m5("slides_delete.usage_roll_slides_delete_slug_force") + "\n");
|
|
17667
18274
|
return 1;
|
|
17668
18275
|
}
|
|
17669
|
-
const deckDir =
|
|
17670
|
-
const html =
|
|
17671
|
-
if (!isDir3(deckDir) || !isFile3(
|
|
18276
|
+
const deckDir = join41(".roll", "slides", slug);
|
|
18277
|
+
const html = join41(".roll", "slides", `${slug}.html`);
|
|
18278
|
+
if (!isDir3(deckDir) || !isFile3(join41(deckDir, "deck.md"))) {
|
|
17672
18279
|
err15(m5("slides_delete.deck_not_found", slug));
|
|
17673
18280
|
return 1;
|
|
17674
18281
|
}
|
|
@@ -17707,7 +18314,7 @@ function cmdTemplates(args) {
|
|
|
17707
18314
|
let found = false;
|
|
17708
18315
|
process.stdout.write(rowThree("name", "source", "path") + "\n");
|
|
17709
18316
|
process.stdout.write(rowThree("----", "------", "----") + "\n");
|
|
17710
|
-
const builtinDir =
|
|
18317
|
+
const builtinDir = join41(rollPkgDir(), "lib", "slides", "templates");
|
|
17711
18318
|
if (isDir3(builtinDir)) {
|
|
17712
18319
|
for (const tpl of listHtml(builtinDir)) {
|
|
17713
18320
|
const name = basenameNoHtml(tpl);
|
|
@@ -17715,11 +18322,11 @@ function cmdTemplates(args) {
|
|
|
17715
18322
|
found = true;
|
|
17716
18323
|
}
|
|
17717
18324
|
}
|
|
17718
|
-
const projDir =
|
|
18325
|
+
const projDir = join41(".roll", "slides", "templates");
|
|
17719
18326
|
if (isDir3(projDir)) {
|
|
17720
18327
|
for (const tpl of listHtml(projDir)) {
|
|
17721
18328
|
const name = basenameNoHtml(tpl);
|
|
17722
|
-
const source = isFile3(
|
|
18329
|
+
const source = isFile3(join41(builtinDir, `${name}.html`)) ? "project (override)" : "project";
|
|
17723
18330
|
process.stdout.write(rowThree(name, source, tpl) + "\n");
|
|
17724
18331
|
found = true;
|
|
17725
18332
|
}
|
|
@@ -17767,20 +18374,20 @@ function slidesCommand(args) {
|
|
|
17767
18374
|
}
|
|
17768
18375
|
function isFile3(p) {
|
|
17769
18376
|
try {
|
|
17770
|
-
return
|
|
18377
|
+
return statSync21(p).isFile();
|
|
17771
18378
|
} catch {
|
|
17772
18379
|
return false;
|
|
17773
18380
|
}
|
|
17774
18381
|
}
|
|
17775
18382
|
function isDir3(p) {
|
|
17776
18383
|
try {
|
|
17777
|
-
return
|
|
18384
|
+
return statSync21(p).isDirectory();
|
|
17778
18385
|
} catch {
|
|
17779
18386
|
return false;
|
|
17780
18387
|
}
|
|
17781
18388
|
}
|
|
17782
18389
|
function listHtml(dir) {
|
|
17783
|
-
return
|
|
18390
|
+
return readdirSync19(dir).filter((f) => f.endsWith(".html")).sort().map((f) => join41(dir, f));
|
|
17784
18391
|
}
|
|
17785
18392
|
function basenameNoHtml(path) {
|
|
17786
18393
|
const b = path.slice(path.lastIndexOf("/") + 1);
|
|
@@ -17800,15 +18407,15 @@ function rowThree(a, b, c2) {
|
|
|
17800
18407
|
// packages/cli/dist/commands/status.js
|
|
17801
18408
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
17802
18409
|
import { createHash as createHash5 } from "node:crypto";
|
|
17803
|
-
import { existsSync as
|
|
17804
|
-
import { homedir as
|
|
17805
|
-
import { basename as basename10, dirname as
|
|
18410
|
+
import { existsSync as existsSync45, lstatSync as lstatSync4, readdirSync as readdirSync20, readFileSync as readFileSync38, realpathSync as realpathSync6, statSync as statSync22 } from "node:fs";
|
|
18411
|
+
import { homedir as homedir17 } from "node:os";
|
|
18412
|
+
import { basename as basename10, dirname as dirname19, join as join42 } from "node:path";
|
|
17806
18413
|
function rollHome3() {
|
|
17807
|
-
return process.env["ROLL_HOME"] ??
|
|
18414
|
+
return process.env["ROLL_HOME"] ?? join42(homedir17(), ".roll");
|
|
17808
18415
|
}
|
|
17809
|
-
var globalDir = () =>
|
|
17810
|
-
var templatesDir = () =>
|
|
17811
|
-
var configPath = () =>
|
|
18416
|
+
var globalDir = () => join42(rollHome3(), "conventions", "global");
|
|
18417
|
+
var templatesDir = () => join42(rollHome3(), "conventions", "templates");
|
|
18418
|
+
var configPath = () => join42(rollHome3(), "config.yaml");
|
|
17812
18419
|
function projectSlugPy() {
|
|
17813
18420
|
let path = realpathSync6(process.cwd());
|
|
17814
18421
|
try {
|
|
@@ -17827,18 +18434,18 @@ function projectSlugPy() {
|
|
|
17827
18434
|
var CONVENTION_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md", ".cursor-rules", "project_rules.md"];
|
|
17828
18435
|
var TEMPLATES = ["fullstack", "frontend-only", "backend-service", "cli"];
|
|
17829
18436
|
function globalConventions() {
|
|
17830
|
-
return CONVENTION_FILES.map((f) => [f,
|
|
18437
|
+
return CONVENTION_FILES.map((f) => [f, existsSync45(join42(globalDir(), f))]);
|
|
17831
18438
|
}
|
|
17832
18439
|
function parseAiEntries() {
|
|
17833
18440
|
const cfg = configPath();
|
|
17834
|
-
if (!
|
|
18441
|
+
if (!existsSync45(cfg))
|
|
17835
18442
|
return [];
|
|
17836
18443
|
const entries = [];
|
|
17837
|
-
for (const line of
|
|
18444
|
+
for (const line of readFileSync38(cfg, "utf8").split("\n")) {
|
|
17838
18445
|
const m7 = /^ai_[a-z]+:\s*(.+)/.exec(line);
|
|
17839
18446
|
if (m7 === null)
|
|
17840
18447
|
continue;
|
|
17841
|
-
const val = (m7[1] ?? "").trim().replaceAll("~",
|
|
18448
|
+
const val = (m7[1] ?? "").trim().replaceAll("~", homedir17());
|
|
17842
18449
|
const parts = val.split("|");
|
|
17843
18450
|
if (parts.length < 3)
|
|
17844
18451
|
continue;
|
|
@@ -17847,28 +18454,28 @@ function parseAiEntries() {
|
|
|
17847
18454
|
const srcFile = (parts[2] ?? "").trim();
|
|
17848
18455
|
let name = basename10(aiDir).replace(/^\.+/, "");
|
|
17849
18456
|
if (name === "workspace" || name === "agent") {
|
|
17850
|
-
name = basename10(
|
|
18457
|
+
name = basename10(dirname19(aiDir)).replace(/^\.+/, "");
|
|
17851
18458
|
}
|
|
17852
18459
|
entries.push({ name, ai_dir: aiDir, cfg_file: cfgFile, src_file: srcFile });
|
|
17853
18460
|
}
|
|
17854
18461
|
return entries;
|
|
17855
18462
|
}
|
|
17856
18463
|
function aiSyncStatus(e) {
|
|
17857
|
-
const cfgFile =
|
|
17858
|
-
const rollMd =
|
|
17859
|
-
const src =
|
|
17860
|
-
if (!
|
|
18464
|
+
const cfgFile = join42(e.ai_dir, e.cfg_file);
|
|
18465
|
+
const rollMd = join42(e.ai_dir, "roll.md");
|
|
18466
|
+
const src = join42(globalDir(), e.src_file);
|
|
18467
|
+
if (!existsSync45(cfgFile))
|
|
17861
18468
|
return "missing";
|
|
17862
|
-
if (!
|
|
18469
|
+
if (!existsSync45(rollMd))
|
|
17863
18470
|
return "out-of-sync";
|
|
17864
18471
|
try {
|
|
17865
|
-
if (
|
|
18472
|
+
if (existsSync45(src) && !readFileSync38(rollMd).equals(readFileSync38(src)))
|
|
17866
18473
|
return "out-of-sync";
|
|
17867
18474
|
} catch {
|
|
17868
18475
|
return "out-of-sync";
|
|
17869
18476
|
}
|
|
17870
18477
|
try {
|
|
17871
|
-
if (!
|
|
18478
|
+
if (!readFileSync38(cfgFile, "utf8").includes("@roll.md"))
|
|
17872
18479
|
return "out-of-sync";
|
|
17873
18480
|
} catch {
|
|
17874
18481
|
return "out-of-sync";
|
|
@@ -17876,15 +18483,15 @@ function aiSyncStatus(e) {
|
|
|
17876
18483
|
return "sync";
|
|
17877
18484
|
}
|
|
17878
18485
|
function aiSkillCount(e) {
|
|
17879
|
-
const skillsDir2 =
|
|
17880
|
-
if (!
|
|
18486
|
+
const skillsDir2 = join42(e.ai_dir, "skills");
|
|
18487
|
+
if (!existsSync45(skillsDir2))
|
|
17881
18488
|
return 0;
|
|
17882
18489
|
try {
|
|
17883
18490
|
let n = 0;
|
|
17884
|
-
for (const name of
|
|
18491
|
+
for (const name of readdirSync20(skillsDir2)) {
|
|
17885
18492
|
if (!name.startsWith("roll-"))
|
|
17886
18493
|
continue;
|
|
17887
|
-
const p =
|
|
18494
|
+
const p = join42(skillsDir2, name);
|
|
17888
18495
|
const st = lstatSync4(p);
|
|
17889
18496
|
if (st.isSymbolicLink() || st.isDirectory())
|
|
17890
18497
|
n++;
|
|
@@ -17896,9 +18503,9 @@ function aiSkillCount(e) {
|
|
|
17896
18503
|
}
|
|
17897
18504
|
function countFilesRecursive(dir) {
|
|
17898
18505
|
let n = 0;
|
|
17899
|
-
for (const name of
|
|
17900
|
-
const p =
|
|
17901
|
-
const st =
|
|
18506
|
+
for (const name of readdirSync20(dir)) {
|
|
18507
|
+
const p = join42(dir, name);
|
|
18508
|
+
const st = statSync22(p);
|
|
17902
18509
|
if (st.isDirectory())
|
|
17903
18510
|
n += countFilesRecursive(p);
|
|
17904
18511
|
else if (st.isFile())
|
|
@@ -17907,8 +18514,8 @@ function countFilesRecursive(dir) {
|
|
|
17907
18514
|
return n;
|
|
17908
18515
|
}
|
|
17909
18516
|
function templateCount(tpl) {
|
|
17910
|
-
const d =
|
|
17911
|
-
if (!
|
|
18517
|
+
const d = join42(templatesDir(), tpl);
|
|
18518
|
+
if (!existsSync45(d))
|
|
17912
18519
|
return 0;
|
|
17913
18520
|
try {
|
|
17914
18521
|
return countFilesRecursive(d);
|
|
@@ -17917,19 +18524,19 @@ function templateCount(tpl) {
|
|
|
17917
18524
|
}
|
|
17918
18525
|
}
|
|
17919
18526
|
function skillsInstalled() {
|
|
17920
|
-
const sd =
|
|
17921
|
-
if (!
|
|
18527
|
+
const sd = join42(rollHome3(), "skills");
|
|
18528
|
+
if (!existsSync45(sd))
|
|
17922
18529
|
return 0;
|
|
17923
18530
|
try {
|
|
17924
|
-
return
|
|
18531
|
+
return readdirSync20(sd).filter((n) => statSync22(join42(sd, n)).isDirectory()).length;
|
|
17925
18532
|
} catch {
|
|
17926
18533
|
return 0;
|
|
17927
18534
|
}
|
|
17928
18535
|
}
|
|
17929
18536
|
function launchdState(service, slug) {
|
|
17930
18537
|
const label4 = `com.roll.${service}.${slug}`;
|
|
17931
|
-
const plist =
|
|
17932
|
-
if (!
|
|
18538
|
+
const plist = join42(homedir17(), "Library", "LaunchAgents", `${label4}.plist`);
|
|
18539
|
+
if (!existsSync45(plist))
|
|
17933
18540
|
return "not-installed";
|
|
17934
18541
|
try {
|
|
17935
18542
|
const out2 = execFileSync8("launchctl", ["list", label4], {
|
|
@@ -18063,26 +18670,26 @@ function renderThisProject(out2, d) {
|
|
|
18063
18670
|
}
|
|
18064
18671
|
function liveData() {
|
|
18065
18672
|
const slug = projectSlugPy();
|
|
18066
|
-
const home =
|
|
18673
|
+
const home = homedir17();
|
|
18067
18674
|
const aiClients = parseAiEntries().map((e) => ({
|
|
18068
18675
|
name: e.name,
|
|
18069
18676
|
cfg_file: e.cfg_file,
|
|
18070
|
-
path:
|
|
18677
|
+
path: join42(e.ai_dir, e.cfg_file).replaceAll(home, "~"),
|
|
18071
18678
|
sync: aiSyncStatus(e),
|
|
18072
18679
|
skills: aiSkillCount(e)
|
|
18073
18680
|
}));
|
|
18074
18681
|
const featDir = ".roll/features";
|
|
18075
18682
|
let featCount = 0;
|
|
18076
|
-
if (
|
|
18077
|
-
featCount =
|
|
18683
|
+
if (existsSync45(featDir)) {
|
|
18684
|
+
featCount = readdirSync20(featDir).filter((n) => n.endsWith(".md")).length;
|
|
18078
18685
|
}
|
|
18079
18686
|
return {
|
|
18080
18687
|
conventions: globalConventions(),
|
|
18081
18688
|
ai_clients: aiClients,
|
|
18082
18689
|
templates: TEMPLATES.map((t2) => [t2, templateCount(t2)]),
|
|
18083
18690
|
skills_installed: skillsInstalled(),
|
|
18084
|
-
project_has_agents:
|
|
18085
|
-
project_has_backlog:
|
|
18691
|
+
project_has_agents: existsSync45("AGENTS.md"),
|
|
18692
|
+
project_has_backlog: existsSync45(".roll/backlog.md"),
|
|
18086
18693
|
project_features_count: featCount,
|
|
18087
18694
|
loop_state: launchdState("loop", slug),
|
|
18088
18695
|
dream_state: launchdState("dream", slug)
|
|
@@ -18106,8 +18713,8 @@ function statusCommand(args) {
|
|
|
18106
18713
|
|
|
18107
18714
|
// packages/cli/dist/commands/test.js
|
|
18108
18715
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
18109
|
-
import { existsSync as
|
|
18110
|
-
import { dirname as
|
|
18716
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync24, readFileSync as readFileSync39, rmSync as rmSync12, writeFileSync as writeFileSync24 } from "node:fs";
|
|
18717
|
+
import { dirname as dirname20, join as join43 } from "node:path";
|
|
18111
18718
|
function pal5() {
|
|
18112
18719
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
18113
18720
|
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 +18731,12 @@ function err16(line) {
|
|
|
18124
18731
|
}
|
|
18125
18732
|
var SUPPORTED_TYPES = ["none"];
|
|
18126
18733
|
function isolationGetType() {
|
|
18127
|
-
const file =
|
|
18128
|
-
if (!
|
|
18734
|
+
const file = join43(process.cwd(), ".roll", "local.yaml");
|
|
18735
|
+
if (!existsSync46(file))
|
|
18129
18736
|
return "none";
|
|
18130
18737
|
let text;
|
|
18131
18738
|
try {
|
|
18132
|
-
text =
|
|
18739
|
+
text = readFileSync39(file, "utf8");
|
|
18133
18740
|
} catch {
|
|
18134
18741
|
return "none";
|
|
18135
18742
|
}
|
|
@@ -18177,14 +18784,14 @@ function resetLockPath() {
|
|
|
18177
18784
|
return ".roll/.iso-reset.lock";
|
|
18178
18785
|
}
|
|
18179
18786
|
function resetLockHeld() {
|
|
18180
|
-
return
|
|
18787
|
+
return existsSync46(resetLockPath());
|
|
18181
18788
|
}
|
|
18182
18789
|
function resetAcquireLock() {
|
|
18183
18790
|
const lock = resetLockPath();
|
|
18184
|
-
if (
|
|
18791
|
+
if (existsSync46(lock))
|
|
18185
18792
|
return false;
|
|
18186
|
-
|
|
18187
|
-
|
|
18793
|
+
mkdirSync24(dirname20(lock), { recursive: true });
|
|
18794
|
+
writeFileSync24(lock, `${process.pid}
|
|
18188
18795
|
`);
|
|
18189
18796
|
return true;
|
|
18190
18797
|
}
|
|
@@ -18201,7 +18808,7 @@ function runForward(cmd, argv) {
|
|
|
18201
18808
|
}
|
|
18202
18809
|
function isolationDispatch(method, args) {
|
|
18203
18810
|
const type = isolationGetType();
|
|
18204
|
-
if (type === "none" && !
|
|
18811
|
+
if (type === "none" && !existsSync46(join43(process.cwd(), ".roll", "local.yaml"))) {
|
|
18205
18812
|
infoErr("isolation: no test_isolation config, falling back to type=none (host)");
|
|
18206
18813
|
}
|
|
18207
18814
|
if (!SUPPORTED_TYPES.includes(type)) {
|
|
@@ -18295,9 +18902,9 @@ function testCommand(args) {
|
|
|
18295
18902
|
|
|
18296
18903
|
// packages/cli/dist/commands/update.js
|
|
18297
18904
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18298
|
-
import { existsSync as
|
|
18905
|
+
import { existsSync as existsSync47, mkdtempSync as mkdtempSync4, readFileSync as readFileSync40, rmSync as rmSync13 } from "node:fs";
|
|
18299
18906
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
18300
|
-
import { join as
|
|
18907
|
+
import { join as join44 } from "node:path";
|
|
18301
18908
|
function pal6() {
|
|
18302
18909
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
18303
18910
|
return noColor2 ? { CYAN: "", GREEN: "", YELLOW: "", RED: "", BOLD: "", NC: "" } : {
|
|
@@ -18361,20 +18968,20 @@ function resolveRemoteVersion() {
|
|
|
18361
18968
|
}
|
|
18362
18969
|
function downloadAndInstallCurl(tag) {
|
|
18363
18970
|
const url = `https://github.com/seanyao/roll/archive/refs/tags/${tag}.tar.gz`;
|
|
18364
|
-
const tmpDir = mkdtempSync4(
|
|
18971
|
+
const tmpDir = mkdtempSync4(join44(tmpdir4(), "roll-update-"));
|
|
18365
18972
|
try {
|
|
18366
18973
|
info6(`[roll] Downloading roll ${tag} ...`);
|
|
18367
|
-
const dl = runForward2("curl", ["-fsSL", url, "-o",
|
|
18974
|
+
const dl = runForward2("curl", ["-fsSL", url, "-o", join44(tmpDir, "roll.tar.gz")]);
|
|
18368
18975
|
if (dl !== 0) {
|
|
18369
18976
|
err17(m6("update.curl_download_failed"));
|
|
18370
18977
|
return { ok: false };
|
|
18371
18978
|
}
|
|
18372
18979
|
info6("[roll] Extracting ...");
|
|
18373
|
-
const extractDir =
|
|
18980
|
+
const extractDir = join44(tmpDir, "extract");
|
|
18374
18981
|
spawnSync12("mkdir", ["-p", extractDir], { stdio: "ignore" });
|
|
18375
18982
|
const ex = runForward2("tar", [
|
|
18376
18983
|
"-xzf",
|
|
18377
|
-
|
|
18984
|
+
join44(tmpDir, "roll.tar.gz"),
|
|
18378
18985
|
"--strip-components=1",
|
|
18379
18986
|
"-C",
|
|
18380
18987
|
extractDir
|
|
@@ -18391,7 +18998,7 @@ function downloadAndInstallCurl(tag) {
|
|
|
18391
18998
|
function checkInstalledVersionOrRetry() {
|
|
18392
18999
|
const expected = (spawnSync12("npm", ["view", "@seanyao/roll", "version"], { encoding: "utf8" }).stdout ?? "").trim();
|
|
18393
19000
|
const pkgRoot = (spawnSync12("npm", ["root", "-g"], { encoding: "utf8" }).stdout ?? "").trim();
|
|
18394
|
-
const installedTree =
|
|
19001
|
+
const installedTree = join44(pkgRoot, "@seanyao", "roll");
|
|
18395
19002
|
const installed = treeVersion(installedTree);
|
|
18396
19003
|
if (expected === "" || installed === "")
|
|
18397
19004
|
return;
|
|
@@ -18405,18 +19012,18 @@ function checkInstalledVersionOrRetry() {
|
|
|
18405
19012
|
}
|
|
18406
19013
|
}
|
|
18407
19014
|
function invalidateUpdateCache() {
|
|
18408
|
-
rmSync13(
|
|
19015
|
+
rmSync13(join44(rollHome(), ".update-check"), { force: true });
|
|
18409
19016
|
}
|
|
18410
19017
|
function showChangelog() {
|
|
18411
|
-
const changelog =
|
|
18412
|
-
if (!
|
|
19018
|
+
const changelog = join44(rollPkgDir(), "CHANGELOG.md");
|
|
19019
|
+
if (!existsSync47(changelog))
|
|
18413
19020
|
return;
|
|
18414
19021
|
const { BOLD: BOLD2, CYAN, NC } = pal6();
|
|
18415
19022
|
process.stdout.write(`${BOLD2}${m6("changelog.heading")}:${NC}
|
|
18416
19023
|
`);
|
|
18417
19024
|
let count = 0;
|
|
18418
19025
|
let inSection = false;
|
|
18419
|
-
for (const line of
|
|
19026
|
+
for (const line of readFileSync40(changelog, "utf8").split("\n")) {
|
|
18420
19027
|
if (/^## /.test(line)) {
|
|
18421
19028
|
count += 1;
|
|
18422
19029
|
if (count > 3)
|
|
@@ -18436,9 +19043,9 @@ function updateCommand(args) {
|
|
|
18436
19043
|
void args;
|
|
18437
19044
|
info6(m6("update.current_version", rollVersion()));
|
|
18438
19045
|
let installMethod = "npm";
|
|
18439
|
-
const methodFile =
|
|
18440
|
-
if (
|
|
18441
|
-
installMethod =
|
|
19046
|
+
const methodFile = join44(rollPkgDir(), ".install-method");
|
|
19047
|
+
if (existsSync47(methodFile)) {
|
|
19048
|
+
installMethod = readFileSync40(methodFile, "utf8").trim() || "npm";
|
|
18442
19049
|
}
|
|
18443
19050
|
if (installMethod === "curl") {
|
|
18444
19051
|
info6(m6("update.upgrading_via_curl"));
|
|
@@ -18528,6 +19135,7 @@ function registerAll() {
|
|
|
18528
19135
|
const r = initCommand(args);
|
|
18529
19136
|
return r ?? fallbackToBash(["init", ...args]).status;
|
|
18530
19137
|
});
|
|
19138
|
+
registerPorted("migrate-features", migrateFeaturesCommand);
|
|
18531
19139
|
registerPorted("migrate", migrateCommand);
|
|
18532
19140
|
registerPorted("offboard", offboardCommand);
|
|
18533
19141
|
registerPorted("setup", (args) => {
|