@seanyao/roll 3.607.2 → 3.608.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +4 -1
- package/dist/roll.mjs +1160 -534
- package/package.json +3 -4
- package/skills/roll-.changelog/SKILL.md +2 -2
- package/skills/roll-build/SKILL.md +1 -1
- package/skills/roll-fix/SKILL.md +1 -1
- package/skills/roll-loop/SKILL.md +3 -3
package/dist/roll.mjs
CHANGED
|
@@ -6112,11 +6112,11 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
6112
6112
|
import { dirname as dirname5, join as join7 } from "node:path";
|
|
6113
6113
|
|
|
6114
6114
|
// packages/cli/dist/lib/archive-migrate.js
|
|
6115
|
-
import { existsSync as existsSync7, readdirSync as readdirSync3, readlinkSync, statSync as
|
|
6115
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readlinkSync, statSync as statSync5 } from "node:fs";
|
|
6116
6116
|
import { join as join6 } from "node:path";
|
|
6117
6117
|
|
|
6118
6118
|
// packages/cli/dist/lib/archive.js
|
|
6119
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
6119
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
6120
6120
|
import { basename as basename3, dirname as dirname4, join as join5 } from "node:path";
|
|
6121
6121
|
var UNCATEGORIZED = "uncategorized";
|
|
6122
6122
|
function findFeatureFile(projectPath, storyId) {
|
|
@@ -6130,7 +6130,8 @@ function findFeatureFile(projectPath, storyId) {
|
|
|
6130
6130
|
if (e.isDirectory())
|
|
6131
6131
|
walk2(p);
|
|
6132
6132
|
else if (e.isFile() && e.name.endsWith(".md")) {
|
|
6133
|
-
|
|
6133
|
+
const idOwned = e.name === `${storyId}.md` || e.name === "spec.md" && basename3(dir) === storyId;
|
|
6134
|
+
if (idOwned)
|
|
6134
6135
|
hits.unshift(p);
|
|
6135
6136
|
else {
|
|
6136
6137
|
try {
|
|
@@ -6210,9 +6211,6 @@ function cardArchiveDir(projectPath, storyId) {
|
|
|
6210
6211
|
const epic = epicForStory(projectPath, storyId) ?? UNCATEGORIZED;
|
|
6211
6212
|
return join5(projectPath, ".roll", "features", epic, storyId);
|
|
6212
6213
|
}
|
|
6213
|
-
function legacyArchiveDir(projectPath, storyId) {
|
|
6214
|
-
return join5(projectPath, ".roll", "verification", storyId);
|
|
6215
|
-
}
|
|
6216
6214
|
function buildStoryIndex(ids, epicOf) {
|
|
6217
6215
|
const out2 = {};
|
|
6218
6216
|
for (const id of ids) {
|
|
@@ -6228,6 +6226,69 @@ function serializeIndex(stories) {
|
|
|
6228
6226
|
sorted[k] = stories[k];
|
|
6229
6227
|
return JSON.stringify({ stories: sorted }, null, 2) + "\n";
|
|
6230
6228
|
}
|
|
6229
|
+
function specMeta(specPath) {
|
|
6230
|
+
let text;
|
|
6231
|
+
try {
|
|
6232
|
+
text = readFileSync6(specPath, "utf8");
|
|
6233
|
+
} catch {
|
|
6234
|
+
return {};
|
|
6235
|
+
}
|
|
6236
|
+
const out2 = {};
|
|
6237
|
+
const fm = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
6238
|
+
if (fm !== null) {
|
|
6239
|
+
const t2 = /^title:\s*(.+)$/m.exec(fm[1] ?? "");
|
|
6240
|
+
const c2 = /^created:\s*(.+)$/m.exec(fm[1] ?? "");
|
|
6241
|
+
if (t2 !== null)
|
|
6242
|
+
out2.title = (t2[1] ?? "").trim();
|
|
6243
|
+
if (c2 !== null)
|
|
6244
|
+
out2.created = (c2[1] ?? "").trim();
|
|
6245
|
+
}
|
|
6246
|
+
if (out2.title === void 0) {
|
|
6247
|
+
const h1 = /^#\s+\S+\s*(?:[—·:-]\s*)?(.*)$/m.exec(text);
|
|
6248
|
+
if (h1 !== null && (h1[1] ?? "").trim() !== "")
|
|
6249
|
+
out2.title = (h1[1] ?? "").trim().replace(/\s*[✅🚫🔨].*$/u, "");
|
|
6250
|
+
}
|
|
6251
|
+
return out2;
|
|
6252
|
+
}
|
|
6253
|
+
function collectDossier(projectPath) {
|
|
6254
|
+
const root = join5(projectPath, ".roll", "features");
|
|
6255
|
+
if (!existsSync6(root))
|
|
6256
|
+
return [];
|
|
6257
|
+
const epics = [];
|
|
6258
|
+
let epicDirs = [];
|
|
6259
|
+
try {
|
|
6260
|
+
epicDirs = readdirSync2(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
6261
|
+
} catch {
|
|
6262
|
+
return [];
|
|
6263
|
+
}
|
|
6264
|
+
for (const epic of epicDirs) {
|
|
6265
|
+
const stories = [];
|
|
6266
|
+
let entries = [];
|
|
6267
|
+
try {
|
|
6268
|
+
entries = readdirSync2(join5(root, epic), { withFileTypes: true }).filter((e) => e.isDirectory() && /^[A-Z][A-Z0-9]*-/.test(e.name)).map((e) => e.name).sort();
|
|
6269
|
+
} catch {
|
|
6270
|
+
continue;
|
|
6271
|
+
}
|
|
6272
|
+
for (const id of entries) {
|
|
6273
|
+
const dir = join5(root, epic, id);
|
|
6274
|
+
let delivered = false;
|
|
6275
|
+
try {
|
|
6276
|
+
delivered = statSync4(join5(dir, "latest")).isDirectory();
|
|
6277
|
+
} catch {
|
|
6278
|
+
}
|
|
6279
|
+
stories.push({
|
|
6280
|
+
id,
|
|
6281
|
+
epic,
|
|
6282
|
+
type: (id.split("-")[0] ?? id).toUpperCase(),
|
|
6283
|
+
...specMeta(join5(dir, "spec.md")),
|
|
6284
|
+
delivered
|
|
6285
|
+
});
|
|
6286
|
+
}
|
|
6287
|
+
if (stories.length > 0)
|
|
6288
|
+
epics.push({ name: epic, stories, delivered: stories.filter((s) => s.delivered).length });
|
|
6289
|
+
}
|
|
6290
|
+
return epics;
|
|
6291
|
+
}
|
|
6231
6292
|
|
|
6232
6293
|
// packages/cli/dist/lib/archive-migrate.js
|
|
6233
6294
|
var RUN_ID_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/;
|
|
@@ -6243,7 +6304,7 @@ function runsInDir(absDir) {
|
|
|
6243
6304
|
if (!e.isDirectory() || !RUN_ID_RE.test(e.name))
|
|
6244
6305
|
continue;
|
|
6245
6306
|
try {
|
|
6246
|
-
out2.push({ runId: e.name, mtimeSec: Math.floor(
|
|
6307
|
+
out2.push({ runId: e.name, mtimeSec: Math.floor(statSync5(join6(absDir, e.name)).mtimeMs / 1e3) });
|
|
6247
6308
|
} catch {
|
|
6248
6309
|
}
|
|
6249
6310
|
}
|
|
@@ -6950,7 +7011,7 @@ async function runPublishPlan(plan, opts = {}) {
|
|
|
6950
7011
|
}
|
|
6951
7012
|
|
|
6952
7013
|
// packages/infra/dist/process.js
|
|
6953
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync4, statSync as
|
|
7014
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync4, statSync as statSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6954
7015
|
import { dirname as dirname8 } from "node:path";
|
|
6955
7016
|
var OUTER_LOCK_STALE_SEC = 900;
|
|
6956
7017
|
var INNER_LOCK_STALE_SEC = 14400;
|
|
@@ -7126,7 +7187,7 @@ import { promisify as promisify4 } from "node:util";
|
|
|
7126
7187
|
var execFileAsync4 = promisify4(execFile4);
|
|
7127
7188
|
|
|
7128
7189
|
// packages/infra/dist/evidence.js
|
|
7129
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync5, statSync as
|
|
7190
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync5, statSync as statSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7130
7191
|
import { join as join9 } from "node:path";
|
|
7131
7192
|
import { execFile as execFile5 } from "node:child_process";
|
|
7132
7193
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -7183,13 +7244,13 @@ async function collectEvidence(opts) {
|
|
|
7183
7244
|
const proof = join9(opts.projectPath, ".roll", "last-test-pass");
|
|
7184
7245
|
if (existsSync12(proof)) {
|
|
7185
7246
|
try {
|
|
7186
|
-
const age = Math.max(0, Math.round((Date.parse(opts.now()) -
|
|
7247
|
+
const age = Math.max(0, Math.round((Date.parse(opts.now()) - statSync7(proof).mtimeMs) / 1e3));
|
|
7187
7248
|
testPass = { present: true, age_seconds: age };
|
|
7188
7249
|
} catch {
|
|
7189
7250
|
testPass = { present: true, age_seconds: -1 };
|
|
7190
7251
|
}
|
|
7191
7252
|
}
|
|
7192
|
-
const
|
|
7253
|
+
const listDir2 = (sub, ext) => {
|
|
7193
7254
|
const dir = join9(opts.runDir, sub);
|
|
7194
7255
|
if (!existsSync12(dir))
|
|
7195
7256
|
return [];
|
|
@@ -7206,8 +7267,8 @@ async function collectEvidence(opts) {
|
|
|
7206
7267
|
ci,
|
|
7207
7268
|
deploy,
|
|
7208
7269
|
test_pass: testPass,
|
|
7209
|
-
screenshots:
|
|
7210
|
-
texts:
|
|
7270
|
+
screenshots: listDir2("screenshots", /\.png$/i),
|
|
7271
|
+
texts: listDir2("evidence", /\.(txt|log)$/i)
|
|
7211
7272
|
};
|
|
7212
7273
|
}
|
|
7213
7274
|
function writeEvidenceJson(manifest, runDir) {
|
|
@@ -7218,7 +7279,7 @@ function writeEvidenceJson(manifest, runDir) {
|
|
|
7218
7279
|
}
|
|
7219
7280
|
|
|
7220
7281
|
// packages/infra/dist/screenshot.js
|
|
7221
|
-
import { existsSync as existsSync13, statSync as
|
|
7282
|
+
import { existsSync as existsSync13, statSync as statSync8 } from "node:fs";
|
|
7222
7283
|
import { execFile as execFile6 } from "node:child_process";
|
|
7223
7284
|
import { promisify as promisify6 } from "node:util";
|
|
7224
7285
|
|
|
@@ -7280,7 +7341,7 @@ var defaultRun2 = async (cmd, argv) => {
|
|
|
7280
7341
|
};
|
|
7281
7342
|
function fileNonEmpty(p) {
|
|
7282
7343
|
try {
|
|
7283
|
-
return existsSync13(p) &&
|
|
7344
|
+
return existsSync13(p) && statSync8(p).size > 0;
|
|
7284
7345
|
} catch {
|
|
7285
7346
|
return false;
|
|
7286
7347
|
}
|
|
@@ -7296,11 +7357,11 @@ function parseRegion(region) {
|
|
|
7296
7357
|
return { x, y, w, h };
|
|
7297
7358
|
}
|
|
7298
7359
|
function terminalOpenScript(line, r) {
|
|
7299
|
-
const
|
|
7360
|
+
const esc5 = line.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
7300
7361
|
return [
|
|
7301
7362
|
'tell application "Terminal"',
|
|
7302
7363
|
" activate",
|
|
7303
|
-
` do script "${
|
|
7364
|
+
` do script "${esc5}"`,
|
|
7304
7365
|
" delay 1.5",
|
|
7305
7366
|
` set bounds of front window to {${r.x}, ${r.y}, ${r.x + r.w}, ${r.y + r.h}}`,
|
|
7306
7367
|
"end tell"
|
|
@@ -7841,6 +7902,10 @@ async function attestCommand(args, deps = {}) {
|
|
|
7841
7902
|
\u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210
|
|
7842
7903
|
${relative(projectPath, reportPath)}
|
|
7843
7904
|
`);
|
|
7905
|
+
try {
|
|
7906
|
+
generateIndex(projectPath);
|
|
7907
|
+
} catch {
|
|
7908
|
+
}
|
|
7844
7909
|
if (!smoke.ok) {
|
|
7845
7910
|
for (const p of smoke.problems)
|
|
7846
7911
|
warn2(`render smoke: ${p}`);
|
|
@@ -8573,7 +8638,7 @@ function writeUnreleased(draft, cl) {
|
|
|
8573
8638
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
8574
8639
|
|
|
8575
8640
|
// packages/cli/dist/commands/setup-shared.js
|
|
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
|
|
8641
|
+
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 statSync9, symlinkSync as symlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
|
|
8577
8642
|
import { homedir as homedir5 } from "node:os";
|
|
8578
8643
|
import { basename as basename5, dirname as dirname9, join as join12 } from "node:path";
|
|
8579
8644
|
function rollHome() {
|
|
@@ -8616,7 +8681,7 @@ function aiField(entry, idx) {
|
|
|
8616
8681
|
}
|
|
8617
8682
|
function canonicalDir(path) {
|
|
8618
8683
|
try {
|
|
8619
|
-
if (!
|
|
8684
|
+
if (!statSync9(path).isDirectory())
|
|
8620
8685
|
return null;
|
|
8621
8686
|
return realpathSync2(path);
|
|
8622
8687
|
} catch {
|
|
@@ -8652,7 +8717,7 @@ function onPath(bin) {
|
|
|
8652
8717
|
continue;
|
|
8653
8718
|
const p = join12(dir, bin);
|
|
8654
8719
|
try {
|
|
8655
|
-
const st =
|
|
8720
|
+
const st = statSync9(p);
|
|
8656
8721
|
if (st.isFile() && (st.mode & 73) !== 0)
|
|
8657
8722
|
return true;
|
|
8658
8723
|
} catch {
|
|
@@ -8668,7 +8733,7 @@ function agentInstalledByName3(agent, dir = "") {
|
|
|
8668
8733
|
case "opencode": {
|
|
8669
8734
|
const p = join12(home, ".opencode", "bin", "opencode");
|
|
8670
8735
|
try {
|
|
8671
|
-
return existsSync18(p) && (
|
|
8736
|
+
return existsSync18(p) && (statSync9(p).mode & 73) !== 0;
|
|
8672
8737
|
} catch {
|
|
8673
8738
|
return false;
|
|
8674
8739
|
}
|
|
@@ -8713,7 +8778,7 @@ function pruneDir(installedDir, sourceDir) {
|
|
|
8713
8778
|
const installedF = join12(installedDir, name);
|
|
8714
8779
|
let isFile4 = false;
|
|
8715
8780
|
try {
|
|
8716
|
-
isFile4 =
|
|
8781
|
+
isFile4 = statSync9(installedF).isFile();
|
|
8717
8782
|
} catch {
|
|
8718
8783
|
isFile4 = false;
|
|
8719
8784
|
}
|
|
@@ -8751,7 +8816,7 @@ function listFiles(dir, includeDot) {
|
|
|
8751
8816
|
continue;
|
|
8752
8817
|
const p = join12(dir, name);
|
|
8753
8818
|
try {
|
|
8754
|
-
if (
|
|
8819
|
+
if (statSync9(p).isFile())
|
|
8755
8820
|
out2.push(name);
|
|
8756
8821
|
} catch {
|
|
8757
8822
|
}
|
|
@@ -8778,7 +8843,7 @@ function pullConventions(force) {
|
|
|
8778
8843
|
for (const tplName of listDirEntries(tplSrcRoot)) {
|
|
8779
8844
|
const tplDir = join12(tplSrcRoot, tplName);
|
|
8780
8845
|
try {
|
|
8781
|
-
if (!
|
|
8846
|
+
if (!statSync9(tplDir).isDirectory())
|
|
8782
8847
|
continue;
|
|
8783
8848
|
} catch {
|
|
8784
8849
|
continue;
|
|
@@ -8802,7 +8867,7 @@ function pullSkills() {
|
|
|
8802
8867
|
for (const skillName of listDirEntries(pkgSkills)) {
|
|
8803
8868
|
const skillDir = join12(pkgSkills, skillName);
|
|
8804
8869
|
try {
|
|
8805
|
-
if (!
|
|
8870
|
+
if (!statSync9(skillDir).isDirectory())
|
|
8806
8871
|
continue;
|
|
8807
8872
|
} catch {
|
|
8808
8873
|
continue;
|
|
@@ -8812,7 +8877,7 @@ function pullSkills() {
|
|
|
8812
8877
|
for (const name of listDirEntries(skillDir)) {
|
|
8813
8878
|
const f = join12(skillDir, name);
|
|
8814
8879
|
try {
|
|
8815
|
-
if (
|
|
8880
|
+
if (statSync9(f).isFile())
|
|
8816
8881
|
copyFileSync(f, join12(destDir, name));
|
|
8817
8882
|
} catch {
|
|
8818
8883
|
}
|
|
@@ -8822,7 +8887,7 @@ function pullSkills() {
|
|
|
8822
8887
|
for (const installedName of listDirEntries(homeSkills)) {
|
|
8823
8888
|
const installedDir = join12(homeSkills, installedName);
|
|
8824
8889
|
try {
|
|
8825
|
-
if (!
|
|
8890
|
+
if (!statSync9(installedDir).isDirectory())
|
|
8826
8891
|
continue;
|
|
8827
8892
|
} catch {
|
|
8828
8893
|
continue;
|
|
@@ -9049,7 +9114,7 @@ function linkSkills() {
|
|
|
9049
9114
|
for (const skillName of listDirEntries(homeSkills)) {
|
|
9050
9115
|
const skillDir = `${join12(homeSkills, skillName)}/`;
|
|
9051
9116
|
try {
|
|
9052
|
-
if (!
|
|
9117
|
+
if (!statSync9(join12(homeSkills, skillName)).isDirectory())
|
|
9053
9118
|
continue;
|
|
9054
9119
|
} catch {
|
|
9055
9120
|
continue;
|
|
@@ -9467,9 +9532,9 @@ function configCommand(args) {
|
|
|
9467
9532
|
}
|
|
9468
9533
|
|
|
9469
9534
|
// packages/cli/dist/commands/consistency.js
|
|
9470
|
-
import { existsSync as existsSync19, readFileSync as readFileSync14, readdirSync as readdirSync8, statSync as
|
|
9535
|
+
import { existsSync as existsSync19, readFileSync as readFileSync14, readdirSync as readdirSync8, statSync as statSync10 } from "node:fs";
|
|
9471
9536
|
import { join as join14 } from "node:path";
|
|
9472
|
-
var DIMENSIONS2 = ["code", "docs", "i18n", "tests", "site"];
|
|
9537
|
+
var DIMENSIONS2 = ["code", "cards", "docs", "i18n", "tests", "site"];
|
|
9473
9538
|
function err6(line) {
|
|
9474
9539
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
9475
9540
|
const RED = noColor2 ? "" : "\x1B[0;31m";
|
|
@@ -9530,6 +9595,64 @@ function checkFeaturesCatalog(projectDir) {
|
|
|
9530
9595
|
}
|
|
9531
9596
|
return { status: gaps.length === 0 ? "pass" : "fail", gaps };
|
|
9532
9597
|
}
|
|
9598
|
+
function checkCards(projectDir) {
|
|
9599
|
+
const backlog = join14(projectDir, ".roll", "backlog.md");
|
|
9600
|
+
const featuresDir = join14(projectDir, ".roll", "features");
|
|
9601
|
+
if (!existsSync19(backlog) || !existsSync19(featuresDir))
|
|
9602
|
+
return { status: "pass", gaps: [] };
|
|
9603
|
+
const cardEpic = /* @__PURE__ */ new Map();
|
|
9604
|
+
try {
|
|
9605
|
+
for (const epic of readdirSync8(featuresDir, { withFileTypes: true })) {
|
|
9606
|
+
if (!epic.isDirectory())
|
|
9607
|
+
continue;
|
|
9608
|
+
for (const card of readdirSync8(join14(featuresDir, epic.name), { withFileTypes: true })) {
|
|
9609
|
+
if (!card.isDirectory())
|
|
9610
|
+
continue;
|
|
9611
|
+
if (existsSync19(join14(featuresDir, epic.name, card.name, "spec.md")) && !cardEpic.has(card.name)) {
|
|
9612
|
+
cardEpic.set(card.name, epic.name);
|
|
9613
|
+
}
|
|
9614
|
+
}
|
|
9615
|
+
}
|
|
9616
|
+
} catch {
|
|
9617
|
+
return { status: "pass", gaps: [], note: "features/ unreadable \u2014 skipped" };
|
|
9618
|
+
}
|
|
9619
|
+
const gaps = [];
|
|
9620
|
+
let doneNoReport = 0;
|
|
9621
|
+
let doneNoFolder = 0;
|
|
9622
|
+
for (const line of readText(backlog).split("\n")) {
|
|
9623
|
+
const row2 = /^\|\s*\[?((?:US|FIX|REFACTOR|IDEA)-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\]?/.exec(line);
|
|
9624
|
+
if (row2 === null)
|
|
9625
|
+
continue;
|
|
9626
|
+
const id = row2[1] ?? "";
|
|
9627
|
+
if (!cardEpic.has(id)) {
|
|
9628
|
+
if (line.includes("\u2705 Done"))
|
|
9629
|
+
doneNoFolder += 1;
|
|
9630
|
+
else
|
|
9631
|
+
gaps.push(`Live backlog row ${id} has no card folder (features/<epic>/${id}/spec.md)`);
|
|
9632
|
+
continue;
|
|
9633
|
+
}
|
|
9634
|
+
const ev = /\[evidence\]\(([^)]+)\)/.exec(line);
|
|
9635
|
+
if (ev !== null) {
|
|
9636
|
+
const target = (ev[1] ?? "").replace(/^\.roll\//, "");
|
|
9637
|
+
if (!existsSync19(join14(projectDir, ".roll", target))) {
|
|
9638
|
+
gaps.push(`Backlog row ${id} evidence link is broken: ${ev[1] ?? ""}`);
|
|
9639
|
+
}
|
|
9640
|
+
} else if (line.includes("\u2705 Done")) {
|
|
9641
|
+
const epic = cardEpic.get(id) ?? "";
|
|
9642
|
+
if (!existsSync19(join14(featuresDir, epic, id, "latest", `${id}-report.html`)))
|
|
9643
|
+
doneNoReport += 1;
|
|
9644
|
+
}
|
|
9645
|
+
}
|
|
9646
|
+
const result = { status: gaps.length === 0 ? "pass" : "fail", gaps };
|
|
9647
|
+
const notes = [];
|
|
9648
|
+
if (doneNoFolder > 0)
|
|
9649
|
+
notes.push(`${doneNoFolder} pre-card-era Done rows without a card folder`);
|
|
9650
|
+
if (doneNoReport > 0)
|
|
9651
|
+
notes.push(`${doneNoReport} Done rows without an attest report`);
|
|
9652
|
+
if (notes.length > 0)
|
|
9653
|
+
result.note = `${notes.join("; ")} (informational)`;
|
|
9654
|
+
return result;
|
|
9655
|
+
}
|
|
9533
9656
|
var SITE_INTERNAL_FEATURES = /* @__PURE__ */ new Set([
|
|
9534
9657
|
"cycle-meta-sync",
|
|
9535
9658
|
"loop-log-locality",
|
|
@@ -9628,7 +9751,7 @@ function listFiles2(dir) {
|
|
|
9628
9751
|
try {
|
|
9629
9752
|
return readdirSync8(dir).filter((n) => {
|
|
9630
9753
|
try {
|
|
9631
|
-
return
|
|
9754
|
+
return statSync10(join14(dir, n)).isFile();
|
|
9632
9755
|
} catch {
|
|
9633
9756
|
return false;
|
|
9634
9757
|
}
|
|
@@ -9705,7 +9828,7 @@ function rglobBats(dir) {
|
|
|
9705
9828
|
const full = join14(d, e);
|
|
9706
9829
|
let st;
|
|
9707
9830
|
try {
|
|
9708
|
-
st =
|
|
9831
|
+
st = statSync10(full);
|
|
9709
9832
|
} catch {
|
|
9710
9833
|
continue;
|
|
9711
9834
|
}
|
|
@@ -9782,6 +9905,8 @@ function runAll(projectDir) {
|
|
|
9782
9905
|
let result;
|
|
9783
9906
|
if (dim === "code")
|
|
9784
9907
|
result = checkFeaturesCatalog(projectDir);
|
|
9908
|
+
else if (dim === "cards")
|
|
9909
|
+
result = checkCards(projectDir);
|
|
9785
9910
|
else if (dim === "i18n")
|
|
9786
9911
|
result = checkI18n(projectDir);
|
|
9787
9912
|
else if (dim === "tests")
|
|
@@ -9896,7 +10021,7 @@ function consistencyCommand(args) {
|
|
|
9896
10021
|
// packages/cli/dist/commands/dashboard.js
|
|
9897
10022
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
9898
10023
|
import { createHash as createHash3 } from "node:crypto";
|
|
9899
|
-
import { existsSync as existsSync20, readFileSync as readFileSync15, readdirSync as readdirSync9, realpathSync as realpathSync3, statSync as
|
|
10024
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, readdirSync as readdirSync9, realpathSync as realpathSync3, statSync as statSync11 } from "node:fs";
|
|
9900
10025
|
import { homedir as homedir7, platform } from "node:os";
|
|
9901
10026
|
import { basename as basename6, join as join15 } from "node:path";
|
|
9902
10027
|
var TZ_OFFSET_MS2 = 8 * 3600 * 1e3;
|
|
@@ -9989,7 +10114,7 @@ function sharedRoot() {
|
|
|
9989
10114
|
}
|
|
9990
10115
|
function isDir(p) {
|
|
9991
10116
|
try {
|
|
9992
|
-
return
|
|
10117
|
+
return statSync11(p).isDirectory();
|
|
9993
10118
|
} catch {
|
|
9994
10119
|
return false;
|
|
9995
10120
|
}
|
|
@@ -10564,8 +10689,8 @@ function loadClaudeSessionUsage(label4, slug) {
|
|
|
10564
10689
|
if (entries.length === 0)
|
|
10565
10690
|
return null;
|
|
10566
10691
|
entries.sort((a, b) => {
|
|
10567
|
-
const sa =
|
|
10568
|
-
const sb =
|
|
10692
|
+
const sa = statSync11(join15(projDir, a)).size;
|
|
10693
|
+
const sb = statSync11(join15(projDir, b)).size;
|
|
10569
10694
|
return sb - sa;
|
|
10570
10695
|
});
|
|
10571
10696
|
const path = join15(projDir, entries[0] ?? "");
|
|
@@ -10717,17 +10842,34 @@ function rollupForDay(dayCycles) {
|
|
|
10717
10842
|
}
|
|
10718
10843
|
return r;
|
|
10719
10844
|
}
|
|
10720
|
-
function selfScoreSummaryLine(notesDir = ".roll/notes", windowN = 14) {
|
|
10721
|
-
|
|
10722
|
-
return "";
|
|
10723
|
-
let files;
|
|
10845
|
+
function selfScoreSummaryLine(notesDir = ".roll/notes", windowN = 14, featuresDir = ".roll/features") {
|
|
10846
|
+
const entries = [];
|
|
10724
10847
|
try {
|
|
10725
|
-
|
|
10848
|
+
for (const n of readdirSync9(notesDir))
|
|
10849
|
+
if (n.endsWith(".md"))
|
|
10850
|
+
entries.push({ name: n, path: join15(notesDir, n) });
|
|
10851
|
+
} catch {
|
|
10852
|
+
}
|
|
10853
|
+
try {
|
|
10854
|
+
for (const epic of readdirSync9(featuresDir, { withFileTypes: true })) {
|
|
10855
|
+
if (!epic.isDirectory())
|
|
10856
|
+
continue;
|
|
10857
|
+
for (const card of readdirSync9(join15(featuresDir, epic.name), { withFileTypes: true })) {
|
|
10858
|
+
if (!card.isDirectory())
|
|
10859
|
+
continue;
|
|
10860
|
+
const nd = join15(featuresDir, epic.name, card.name, "notes");
|
|
10861
|
+
try {
|
|
10862
|
+
for (const n of readdirSync9(nd))
|
|
10863
|
+
if (n.endsWith(".md"))
|
|
10864
|
+
entries.push({ name: n, path: join15(nd, n) });
|
|
10865
|
+
} catch {
|
|
10866
|
+
}
|
|
10867
|
+
}
|
|
10868
|
+
}
|
|
10726
10869
|
} catch {
|
|
10727
|
-
return "";
|
|
10728
10870
|
}
|
|
10729
|
-
|
|
10730
|
-
files =
|
|
10871
|
+
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
10872
|
+
const files = entries.slice(-windowN);
|
|
10731
10873
|
if (files.length === 0)
|
|
10732
10874
|
return "";
|
|
10733
10875
|
let total = 0;
|
|
@@ -10739,7 +10881,7 @@ function selfScoreSummaryLine(notesDir = ".roll/notes", windowN = 14) {
|
|
|
10739
10881
|
let verdict = null;
|
|
10740
10882
|
let content;
|
|
10741
10883
|
try {
|
|
10742
|
-
content = readFileSync15(
|
|
10884
|
+
content = readFileSync15(f.path, "utf8");
|
|
10743
10885
|
} catch {
|
|
10744
10886
|
content = "";
|
|
10745
10887
|
}
|
|
@@ -11304,7 +11446,7 @@ function render(out2, events, cron, state, backlog, args) {
|
|
|
11304
11446
|
out2.push(" " + c("dim", agentLine));
|
|
11305
11447
|
let skillLine = "";
|
|
11306
11448
|
try {
|
|
11307
|
-
skillLine = selfScoreSummaryLine(process.env["ROLL_NOTES_DIR"]);
|
|
11449
|
+
skillLine = selfScoreSummaryLine(process.env["ROLL_NOTES_DIR"], 14, process.env["ROLL_FEATURES_DIR"]);
|
|
11308
11450
|
} catch {
|
|
11309
11451
|
skillLine = "";
|
|
11310
11452
|
}
|
|
@@ -12168,13 +12310,13 @@ function loopAttachRetired() {
|
|
|
12168
12310
|
|
|
12169
12311
|
// packages/cli/dist/commands/doctor.js
|
|
12170
12312
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
12171
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync24, mkdtempSync as mkdtempSync2, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as
|
|
12313
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync24, mkdtempSync as mkdtempSync2, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync13, writeFileSync as writeFileSync12 } from "node:fs";
|
|
12172
12314
|
import { homedir as homedir9, tmpdir as tmpdir2 } from "node:os";
|
|
12173
12315
|
import { delimiter as delimiter2, join as join19 } from "node:path";
|
|
12174
12316
|
|
|
12175
12317
|
// packages/cli/dist/commands/skills.js
|
|
12176
12318
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
12177
|
-
import { existsSync as existsSync23, mkdtempSync, readFileSync as readFileSync18, readdirSync as readdirSync11, statSync as
|
|
12319
|
+
import { existsSync as existsSync23, mkdtempSync, readFileSync as readFileSync18, readdirSync as readdirSync11, statSync as statSync12, writeFileSync as writeFileSync11 } from "node:fs";
|
|
12178
12320
|
import { tmpdir } from "node:os";
|
|
12179
12321
|
import { join as join18 } from "node:path";
|
|
12180
12322
|
function pkgDir() {
|
|
@@ -12249,7 +12391,7 @@ function generateCatalog() {
|
|
|
12249
12391
|
for (const name of entries) {
|
|
12250
12392
|
const skillDir = join18(dir, name);
|
|
12251
12393
|
try {
|
|
12252
|
-
if (!
|
|
12394
|
+
if (!statSync12(skillDir).isDirectory())
|
|
12253
12395
|
continue;
|
|
12254
12396
|
} catch {
|
|
12255
12397
|
continue;
|
|
@@ -12382,7 +12524,7 @@ function commandOnPath2(bin) {
|
|
|
12382
12524
|
if (dir === "")
|
|
12383
12525
|
continue;
|
|
12384
12526
|
try {
|
|
12385
|
-
const st =
|
|
12527
|
+
const st = statSync13(join19(dir, bin));
|
|
12386
12528
|
if (!st.isFile())
|
|
12387
12529
|
continue;
|
|
12388
12530
|
accessSync2(join19(dir, bin), constants2.X_OK);
|
|
@@ -12401,7 +12543,7 @@ function agentInstalledByName4(agent, dir) {
|
|
|
12401
12543
|
const p = join19(home, ".opencode", "bin", "opencode");
|
|
12402
12544
|
try {
|
|
12403
12545
|
accessSync2(p, constants2.X_OK);
|
|
12404
|
-
return
|
|
12546
|
+
return statSync13(p).isFile();
|
|
12405
12547
|
} catch {
|
|
12406
12548
|
return false;
|
|
12407
12549
|
}
|
|
@@ -12420,7 +12562,7 @@ function agentInstalledByName4(agent, dir) {
|
|
|
12420
12562
|
}
|
|
12421
12563
|
function safeIsDir(p) {
|
|
12422
12564
|
try {
|
|
12423
|
-
return
|
|
12565
|
+
return statSync13(p).isDirectory();
|
|
12424
12566
|
} catch {
|
|
12425
12567
|
return false;
|
|
12426
12568
|
}
|
|
@@ -12656,7 +12798,7 @@ function killLiveAgents(signal2 = "SIGKILL") {
|
|
|
12656
12798
|
let n = 0;
|
|
12657
12799
|
for (const c2 of liveAgents) {
|
|
12658
12800
|
try {
|
|
12659
|
-
if (c2
|
|
12801
|
+
if (killHard(c2, signal2))
|
|
12660
12802
|
n += 1;
|
|
12661
12803
|
} catch {
|
|
12662
12804
|
}
|
|
@@ -12664,6 +12806,16 @@ function killLiveAgents(signal2 = "SIGKILL") {
|
|
|
12664
12806
|
liveAgents.clear();
|
|
12665
12807
|
return n;
|
|
12666
12808
|
}
|
|
12809
|
+
function killHard(c2, signal2) {
|
|
12810
|
+
if (c2.pid !== void 0) {
|
|
12811
|
+
try {
|
|
12812
|
+
process.kill(-c2.pid, signal2);
|
|
12813
|
+
return true;
|
|
12814
|
+
} catch {
|
|
12815
|
+
}
|
|
12816
|
+
}
|
|
12817
|
+
return c2.kill(signal2);
|
|
12818
|
+
}
|
|
12667
12819
|
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: ";
|
|
12668
12820
|
var AGENT_ARGV_TODO = {
|
|
12669
12821
|
openai: "codex exec <prompt>",
|
|
@@ -12722,14 +12874,22 @@ function buildSpawnCommand(agent, opts) {
|
|
|
12722
12874
|
const hint = AGENT_ARGV_TODO[agent] ?? "unknown agent";
|
|
12723
12875
|
throw new Error(`runner: agent '${agent}' argv not yet ported. v2 shape: ${hint}`);
|
|
12724
12876
|
}
|
|
12725
|
-
function
|
|
12877
|
+
function withPtyWrap(cmd, agent, platform3 = process.platform) {
|
|
12878
|
+
if (agent === "claude" || platform3 !== "darwin")
|
|
12879
|
+
return { ...cmd, pty: false };
|
|
12880
|
+
return { bin: "script", args: ["-q", "/dev/null", cmd.bin, ...cmd.args], pty: true };
|
|
12881
|
+
}
|
|
12882
|
+
function spawnAndWait(bin, args, opts, pty = false) {
|
|
12726
12883
|
process.stderr.write(`[runner] spawn ${bin} argv=${JSON.stringify(args.map((a) => a.length > 80 ? `${a.slice(0, 77)}...` : a))}
|
|
12727
12884
|
`);
|
|
12728
12885
|
return new Promise((resolve3) => {
|
|
12729
12886
|
const child = spawn(bin, args, {
|
|
12730
12887
|
cwd: opts.cwd,
|
|
12731
12888
|
env: opts.env ?? process.env,
|
|
12732
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
12889
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12890
|
+
// FIX-224: the PTY-wrapped `script` leads its own process group so the
|
|
12891
|
+
// timeout/teardown can reap script AND the agent under it (killHard).
|
|
12892
|
+
detached: pty
|
|
12733
12893
|
});
|
|
12734
12894
|
let stdout = "";
|
|
12735
12895
|
let stderr = "";
|
|
@@ -12738,7 +12898,7 @@ function spawnAndWait(bin, args, opts) {
|
|
|
12738
12898
|
if (opts.timeoutMs !== void 0 && opts.timeoutMs > 0) {
|
|
12739
12899
|
timer = setTimeout(() => {
|
|
12740
12900
|
timedOut = true;
|
|
12741
|
-
child
|
|
12901
|
+
killHard(child, "SIGKILL");
|
|
12742
12902
|
}, opts.timeoutMs);
|
|
12743
12903
|
}
|
|
12744
12904
|
child.stdout?.on("data", (d) => {
|
|
@@ -12773,8 +12933,8 @@ function spawnAndWait(bin, args, opts) {
|
|
|
12773
12933
|
});
|
|
12774
12934
|
}
|
|
12775
12935
|
var realAgentSpawn = (agent, opts) => {
|
|
12776
|
-
const { bin, args } = buildSpawnCommand(agent, opts);
|
|
12777
|
-
return spawnAndWait(bin, args, opts);
|
|
12936
|
+
const { bin, args, pty } = withPtyWrap(buildSpawnCommand(agent, opts), agent);
|
|
12937
|
+
return spawnAndWait(bin, args, opts, pty);
|
|
12778
12938
|
};
|
|
12779
12939
|
|
|
12780
12940
|
// packages/cli/dist/runner/skill-body.js
|
|
@@ -13151,7 +13311,7 @@ function feedbackCommand(args) {
|
|
|
13151
13311
|
}
|
|
13152
13312
|
|
|
13153
13313
|
// packages/cli/dist/commands/gc.js
|
|
13154
|
-
import { existsSync as existsSync27, readdirSync as readdirSync13, rmSync as rmSync7, statSync as
|
|
13314
|
+
import { existsSync as existsSync27, readdirSync as readdirSync13, rmSync as rmSync7, statSync as statSync14 } from "node:fs";
|
|
13155
13315
|
import { join as join24 } from "node:path";
|
|
13156
13316
|
var RUN_ID_RE2 = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/;
|
|
13157
13317
|
function runsInDir2(dir) {
|
|
@@ -13166,7 +13326,7 @@ function runsInDir2(dir) {
|
|
|
13166
13326
|
if (!e.isDirectory() || !RUN_ID_RE2.test(e.name))
|
|
13167
13327
|
continue;
|
|
13168
13328
|
try {
|
|
13169
|
-
out2.push({ runId: e.name, mtimeSec: Math.floor(
|
|
13329
|
+
out2.push({ runId: e.name, mtimeSec: Math.floor(statSync14(join24(dir, e.name)).mtimeMs / 1e3) });
|
|
13170
13330
|
} catch {
|
|
13171
13331
|
}
|
|
13172
13332
|
}
|
|
@@ -13313,14 +13473,14 @@ ${label2(lang4, "ideav3.usage")}
|
|
|
13313
13473
|
throw e;
|
|
13314
13474
|
}
|
|
13315
13475
|
const kindLabel = label2(lang4, plan.kind === "bug" ? "ideav3.kind_bug" : "ideav3.kind_idea");
|
|
13316
|
-
const
|
|
13476
|
+
const section2 = IDEA_SECTIONS[plan.kind].replace(/^#+\s*/, "");
|
|
13317
13477
|
process.stdout.write(`
|
|
13318
13478
|
${c("green", "\u{1F4DD} " + label2(lang4, "ideav3.recorded", plan.id))}
|
|
13319
13479
|
|
|
13320
13480
|
`);
|
|
13321
13481
|
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.type") + ":")} ${kindLabel}
|
|
13322
13482
|
`);
|
|
13323
|
-
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.section") + ":")} ${
|
|
13483
|
+
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.section") + ":")} ${section2}
|
|
13324
13484
|
`);
|
|
13325
13485
|
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.text") + ":")} ${text}
|
|
13326
13486
|
|
|
@@ -13343,109 +13503,546 @@ ${c("green", "\u{1F4DD} " + label2(lang4, "ideav3.recorded", plan.id))}
|
|
|
13343
13503
|
}
|
|
13344
13504
|
|
|
13345
13505
|
// packages/cli/dist/commands/index-gen.js
|
|
13346
|
-
import { existsSync as existsSync29,
|
|
13506
|
+
import { existsSync as existsSync29, writeFileSync as writeFileSync14 } from "node:fs";
|
|
13347
13507
|
import { join as join26 } from "node:path";
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
|
|
13378
|
-
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
|
|
13508
|
+
|
|
13509
|
+
// packages/cli/dist/lib/dossier-css.js
|
|
13510
|
+
var DOSSIER_CSS = `
|
|
13511
|
+
/* \u2500\u2500 masthead \u2500\u2500 */
|
|
13512
|
+
.lede { font:16.5px/1.75 var(--serif); color:var(--fg); max-width:640px; margin:6px 0 18px; }
|
|
13513
|
+
.lede em { font-style:italic; color:var(--accent); }
|
|
13514
|
+
.masthead .crumb { font:12px/1 var(--mono); color:var(--muted); margin:0 0 14px; }
|
|
13515
|
+
.masthead .crumb a { color:var(--muted); }
|
|
13516
|
+
.kv { display:flex; gap:18px; flex-wrap:wrap; font-size:12.5px; color:var(--muted); margin:8px 0 0; }
|
|
13517
|
+
.kv b { color:var(--fg); font-weight:600; }
|
|
13518
|
+
|
|
13519
|
+
/* \u2500\u2500 ledger: four figures + wish\u2192truth bar \u2500\u2500 */
|
|
13520
|
+
.ledger { border:1px solid var(--line); border-radius:8px; background:var(--bg-raise);
|
|
13521
|
+
padding:16px 18px; margin:18px 0; position:relative; z-index:2; }
|
|
13522
|
+
.figures { display:grid; grid-template-columns:repeat(4, 1fr); gap:12px; }
|
|
13523
|
+
.figure .num { font:600 30px/1.1 var(--serif); letter-spacing:.01em; }
|
|
13524
|
+
.figure .num.truth { color:var(--pass); }
|
|
13525
|
+
.figure .lbl { font-size:11.5px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); }
|
|
13526
|
+
.wt-bar { margin-top:14px; height:10px; border:1px solid var(--line); border-radius:999px; overflow:hidden;
|
|
13527
|
+
background:repeating-linear-gradient(135deg, transparent 0 4px, color-mix(in srgb, var(--muted) 28%, transparent) 4px 5px); }
|
|
13528
|
+
.wt-bar .truth { display:block; height:100%; background:var(--pass); border-radius:999px 0 0 999px; }
|
|
13529
|
+
.wt-legend { display:flex; justify-content:space-between; font-size:11.5px; color:var(--muted); margin-top:5px; }
|
|
13530
|
+
|
|
13531
|
+
/* \u2500\u2500 lifecycle spine (full) + mini-spine (rows) \u2500\u2500 */
|
|
13532
|
+
.spine { display:flex; align-items:center; margin:18px 0; position:relative; z-index:2; }
|
|
13533
|
+
.spine .node { display:flex; flex-direction:column; align-items:center; gap:6px; flex:0 0 auto; text-align:center; }
|
|
13534
|
+
.spine .dot { width:13px; height:13px; border-radius:50%; border:2px solid var(--line); background:var(--bg-raise); box-sizing:border-box; }
|
|
13535
|
+
.spine .node.done .dot { border-color:var(--accent); background:var(--accent); }
|
|
13536
|
+
.spine .node.truth .dot { border-color:var(--pass); background:var(--pass); }
|
|
13537
|
+
.spine .node .tag { font-size:10.5px; letter-spacing:.05em; text-transform:uppercase; color:var(--muted); white-space:nowrap; }
|
|
13538
|
+
.spine .node.done .tag, .spine .node.truth .tag { color:var(--fg); }
|
|
13539
|
+
.spine .seg { flex:1 1 0; height:2px; background:var(--line); margin:0 6px 18px; }
|
|
13540
|
+
.spine .seg.done { background:var(--accent); }
|
|
13541
|
+
.mini-spine { display:inline-flex; align-items:center; vertical-align:middle; }
|
|
13542
|
+
.mini-spine i { width:7px; height:7px; border-radius:50%; background:none; border:1.5px solid var(--line); box-sizing:border-box; }
|
|
13543
|
+
.mini-spine i.done { border-color:var(--accent); background:var(--accent); }
|
|
13544
|
+
.mini-spine i.truth { border-color:var(--pass); background:var(--pass); }
|
|
13545
|
+
.mini-spine s { width:8px; height:1.5px; background:var(--line); text-decoration:none; }
|
|
13546
|
+
.mini-spine s.done { background:var(--accent); }
|
|
13547
|
+
|
|
13548
|
+
/* \u2500\u2500 toolbar: search + only-shipping \u2500\u2500 */
|
|
13549
|
+
.toolbar { display:flex; gap:10px; align-items:center; margin:18px 0 8px; position:relative; z-index:2; }
|
|
13550
|
+
.toolbar input[type="search"] { flex:1 1 auto; font:13.5px/1 var(--sans); color:var(--fg);
|
|
13551
|
+
background:var(--bg-raise); border:1px solid var(--line); border-radius:999px; padding:8px 14px; outline:none; }
|
|
13552
|
+
.toolbar input[type="search"]:focus { border-color:var(--accent); }
|
|
13553
|
+
.toolbar label.only { display:flex; gap:6px; align-items:center; font-size:12.5px; color:var(--muted);
|
|
13554
|
+
cursor:pointer; white-space:nowrap; }
|
|
13555
|
+
[data-filtered="1"] .filtered-out { display:none; }
|
|
13556
|
+
|
|
13557
|
+
/* \u2500\u2500 epic cards + chips \u2500\u2500 */
|
|
13558
|
+
.epic-grid { display:grid; grid-template-columns:repeat(2, 1fr); gap:14px; position:relative; z-index:2; }
|
|
13559
|
+
.epic-card { border:1px solid var(--line); border-radius:8px; background:var(--bg-raise); padding:14px 16px; }
|
|
13560
|
+
.epic-card h3 { font:600 15.5px/1.3 var(--serif); margin:0 0 2px; }
|
|
13561
|
+
.epic-card h3 a { color:var(--fg); text-decoration:none; }
|
|
13562
|
+
.epic-card h3 a:hover { color:var(--accent); }
|
|
13563
|
+
.epic-card .stat { font-size:12px; color:var(--muted); }
|
|
13564
|
+
.epic-bar { height:7px; border:1px solid var(--line); border-radius:999px; overflow:hidden; margin:8px 0;
|
|
13565
|
+
background:repeating-linear-gradient(135deg, transparent 0 4px, color-mix(in srgb, var(--muted) 28%, transparent) 4px 5px); }
|
|
13566
|
+
.epic-bar .truth { display:block; height:100%; background:var(--pass); }
|
|
13567
|
+
.chips { display:flex; flex-wrap:wrap; gap:5px; margin-top:8px; }
|
|
13568
|
+
.chip { font:11px/1 var(--mono); border:1px solid var(--line); border-radius:5px; padding:4px 7px;
|
|
13569
|
+
color:var(--muted); text-decoration:none; }
|
|
13570
|
+
.chip:hover { border-color:var(--accent); color:var(--accent); }
|
|
13571
|
+
.chip.truth { border-color:color-mix(in srgb, var(--pass) 55%, transparent); color:var(--pass); }
|
|
13572
|
+
|
|
13573
|
+
/* \u2500\u2500 type chips + status pills \u2500\u2500 */
|
|
13574
|
+
.type { font:10.5px/1 var(--mono); letter-spacing:.05em; border-radius:4px; padding:3px 6px; }
|
|
13575
|
+
.type-US { color:var(--info); border:1px solid color-mix(in srgb, var(--info) 45%, transparent); }
|
|
13576
|
+
.type-FIX { color:var(--claim); border:1px solid color-mix(in srgb, var(--claim) 45%, transparent); }
|
|
13577
|
+
.type-REFACTOR { color:var(--block); border:1px solid color-mix(in srgb, var(--block) 45%, transparent); }
|
|
13578
|
+
.type-IDEA { color:var(--warn); border:1px solid color-mix(in srgb, var(--warn) 45%, transparent); }
|
|
13579
|
+
.pill { font-size:11px; line-height:1; border-radius:999px; padding:4px 9px; white-space:nowrap; }
|
|
13580
|
+
.pill.merged { color:var(--pass); border:1px solid color-mix(in srgb, var(--pass) 50%, transparent); }
|
|
13581
|
+
.pill.cycle { color:var(--warn); border:1px solid color-mix(in srgb, var(--warn) 50%, transparent); }
|
|
13582
|
+
.pill.backlog { color:var(--muted); border:1px solid var(--line); }
|
|
13583
|
+
|
|
13584
|
+
/* \u2500\u2500 story rows (epic page) \u2500\u2500 */
|
|
13585
|
+
.story-rows { position:relative; z-index:2; }
|
|
13586
|
+
.story-row { display:flex; gap:10px; align-items:center; padding:9px 6px; border-bottom:1px solid var(--line);
|
|
13587
|
+
text-decoration:none; color:var(--fg); }
|
|
13588
|
+
.story-row:hover { background:var(--bg-raise); }
|
|
13589
|
+
.story-row .id { font:12.5px/1 var(--mono); min-width:130px; }
|
|
13590
|
+
.story-row .title { flex:1 1 auto; font-size:13.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
13591
|
+
|
|
13592
|
+
/* \u2500\u2500 story dossier blocks \u2500\u2500 */
|
|
13593
|
+
.wish-quote { border-left:3px solid var(--accent); padding:6px 14px; margin:10px 0;
|
|
13594
|
+
font:15.5px/1.75 var(--serif); font-style:italic; }
|
|
13595
|
+
.attest-banner { display:flex; gap:12px; align-items:center; border:1px solid color-mix(in srgb, var(--pass) 45%, transparent);
|
|
13596
|
+
border-radius:8px; padding:10px 14px; margin:10px 0; }
|
|
13597
|
+
.attest-banner .mark { font-size:18px; color:var(--pass); }
|
|
13598
|
+
.ac-table { width:100%; border-collapse:collapse; font-size:13px; }
|
|
13599
|
+
.ac-table th, .ac-table td { text-align:left; padding:6px 8px; border-bottom:1px solid var(--line); vertical-align:top; }
|
|
13600
|
+
.ac-table th { font-family:var(--serif); color:var(--muted); letter-spacing:.04em; }
|
|
13601
|
+
|
|
13602
|
+
/* \u2500\u2500 responsive \u2500\u2500 */
|
|
13603
|
+
@media (max-width:680px) {
|
|
13604
|
+
.figures { grid-template-columns:repeat(2, 1fr); }
|
|
13605
|
+
.epic-grid { grid-template-columns:1fr; }
|
|
13606
|
+
.story-row .id { min-width:0; }
|
|
13607
|
+
.story-row .title { white-space:normal; }
|
|
13608
|
+
}
|
|
13609
|
+
`;
|
|
13610
|
+
var DOSSIER_FILTER_SCRIPT = `<script>
|
|
13611
|
+
(function () {
|
|
13612
|
+
function norm(s) { return (s || "").toLowerCase(); }
|
|
13613
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
13614
|
+
var q = document.querySelector("[data-dossier-search]");
|
|
13615
|
+
var only = document.querySelector("[data-dossier-only]");
|
|
13616
|
+
var rows = document.querySelectorAll("[data-search]");
|
|
13617
|
+
function apply() {
|
|
13618
|
+
var needle = norm(q && q.value);
|
|
13619
|
+
var ship = !!(only && only.checked);
|
|
13620
|
+
for (var i = 0; i < rows.length; i++) {
|
|
13621
|
+
var r = rows[i];
|
|
13622
|
+
var hit = !needle || norm(r.getAttribute("data-search")).indexOf(needle) !== -1;
|
|
13623
|
+
if (ship && r.getAttribute("data-truth") !== "1") hit = false;
|
|
13624
|
+
r.classList.toggle("filtered-out", !hit);
|
|
13382
13625
|
}
|
|
13383
|
-
|
|
13626
|
+
document.documentElement.setAttribute("data-filtered", needle || ship ? "1" : "0");
|
|
13384
13627
|
}
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13628
|
+
if (q) q.addEventListener("input", apply);
|
|
13629
|
+
if (only) only.addEventListener("change", apply);
|
|
13630
|
+
apply();
|
|
13631
|
+
});
|
|
13632
|
+
})();
|
|
13633
|
+
</script>`;
|
|
13634
|
+
|
|
13635
|
+
// packages/cli/dist/lib/dossier-index.js
|
|
13636
|
+
var SPINE_STAGES = [
|
|
13637
|
+
{ key: "definition", en: "Definition", zh: "\u7ACB\u9879" },
|
|
13638
|
+
{ key: "design", en: "Design", zh: "\u8BBE\u8BA1" },
|
|
13639
|
+
{ key: "execution", en: "Execution", zh: "\u6267\u884C" },
|
|
13640
|
+
{ key: "delivery", en: "Delivery", zh: "\u4EA4\u4ED8" },
|
|
13641
|
+
{ key: "retrospective", en: "Retrospective", zh: "\u590D\u76D8" }
|
|
13642
|
+
];
|
|
13643
|
+
var esc2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
13644
|
+
function spineMotif() {
|
|
13645
|
+
const nodes = SPINE_STAGES.map((s, i) => {
|
|
13646
|
+
const cls = s.key === "delivery" ? " truth" : "";
|
|
13647
|
+
const seg = i < SPINE_STAGES.length - 1 ? `<span class="seg"></span>` : "";
|
|
13648
|
+
return `<span class="node${cls}"><span class="dot"></span><span class="tag">${bi(s.en, s.zh)}</span></span>${seg}`;
|
|
13649
|
+
});
|
|
13650
|
+
return `<div class="spine" aria-hidden="true">${nodes.join("")}</div>`;
|
|
13651
|
+
}
|
|
13652
|
+
function ledger(epics) {
|
|
13653
|
+
const total = epics.reduce((n, e) => n + e.stories.length, 0);
|
|
13654
|
+
const truth = epics.reduce((n, e) => n + e.delivered, 0);
|
|
13655
|
+
const shipping = epics.filter((e) => e.delivered > 0).length;
|
|
13656
|
+
const pct = total > 0 ? Math.round(truth / total * 100) : 0;
|
|
13657
|
+
const fig = (num2, en, zh, truthy = false) => `<div class="figure"><div class="num${truthy ? " truth" : ""}">${num2}</div><div class="lbl">${bi(en, zh)}</div></div>`;
|
|
13658
|
+
return `<div class="ledger"><div class="figures">` + fig(String(epics.length), "Epics", "\u53F2\u8BD7") + fig(String(total), "Stories tracked", "\u5728\u518C\u6545\u4E8B") + fig(String(truth), "Merged to main", "\u5DF2\u5408\u4E3B\u5E72", true) + fig(String(shipping), "Epics shipping", "\u4EA4\u4ED8\u4E2D\u53F2\u8BD7") + `</div><div class="wt-bar" role="img" aria-label="${pct}% merged"><span class="truth" style="width:${pct}%"></span></div><div class="wt-legend"><span>${bi("wish \u2014 backlog", "\u613F\u671B \xB7 \u5F85\u529E")}</span><span>${pct}%</span><span>${bi("truth \u2014 merged", "\u4E8B\u5B9E \xB7 \u5DF2\u5408")}</span></div></div>`;
|
|
13659
|
+
}
|
|
13660
|
+
function epicCard(e) {
|
|
13661
|
+
const pct = e.stories.length > 0 ? Math.round(e.delivered / e.stories.length * 100) : 0;
|
|
13662
|
+
const chips = e.stories.map((s) => `<a class="chip${s.delivered ? " truth" : ""}" href="${encodeURIComponent(e.name)}/${encodeURIComponent(s.id)}/index.html" title="${esc2(s.title ?? s.id)}">${esc2(s.id)}</a>`).join("");
|
|
13663
|
+
return `<div class="epic-card" data-search="${esc2(`${e.name} ${e.stories.map((s) => `${s.id} ${s.title ?? ""}`).join(" ")}`)}" data-truth="${e.delivered > 0 ? "1" : "0"}"><h3><a href="${encodeURIComponent(e.name)}/index.html">${esc2(e.name)}</a></h3><div class="stat">${bi(`${e.delivered} / ${e.stories.length} delivered`, `${e.delivered} / ${e.stories.length} \u5DF2\u4EA4\u4ED8`)}</div><div class="epic-bar"><span class="truth" style="width:${pct}%"></span></div><div class="chips">${chips}</div></div>`;
|
|
13664
|
+
}
|
|
13665
|
+
function renderFeaturesIndex(epics) {
|
|
13666
|
+
const shipping = epics.filter((e) => e.delivered > 0);
|
|
13667
|
+
const backlog = epics.filter((e) => e.delivered === 0);
|
|
13668
|
+
const group = (title, zh, list) => list.length === 0 ? "" : `<h2>${bi(title, zh)}</h2>
|
|
13669
|
+
<div class="epic-grid">${list.map(epicCard).join("\n")}</div>
|
|
13670
|
+
`;
|
|
13671
|
+
return `<!DOCTYPE html>
|
|
13406
13672
|
<html lang="zh-CN">
|
|
13407
13673
|
<head>
|
|
13408
13674
|
<meta charset="UTF-8">
|
|
13409
13675
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
13410
|
-
<title>Roll
|
|
13676
|
+
<title>Roll \xB7 Delivery Dossier</title>
|
|
13411
13677
|
<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); }
|
|
13678
|
+
${CHROME_CSS}${DOSSIER_CSS}body { max-width:1000px; }
|
|
13418
13679
|
</style>
|
|
13419
13680
|
${CHROME_SCRIPT}
|
|
13681
|
+
${DOSSIER_FILTER_SCRIPT}
|
|
13420
13682
|
</head>
|
|
13421
13683
|
<body>
|
|
13422
13684
|
${CHROME_CONTROLS}
|
|
13685
|
+
<div class="masthead">
|
|
13423
13686
|
<p class="kicker">Roll \xB7 ${bi("Delivery Dossier", "\u4EA4\u4ED8\u6863\u6848")}</p>
|
|
13424
13687
|
<h1>${bi("Features Index", "\u529F\u80FD\u6863\u6848")}</h1>
|
|
13425
|
-
<p class="
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
</
|
|
13430
|
-
|
|
13688
|
+
<p class="lede">${bi("The backlog is a <em>wish</em>; main is the <em>truth</em>. A story is done only when it has merged \u2014 this ledger keeps the two honest.", "\u5F85\u529E\u662F<em>\u613F\u671B</em>\uFF0C\u4E3B\u5E72\u662F<em>\u4E8B\u5B9E</em>\u3002\u6545\u4E8B\u53EA\u6709\u5408\u5165\u4E3B\u5E72\u624D\u7B97\u5B8C\u6210\u2014\u2014\u8FD9\u672C\u8D26\u8BA9\u4E24\u8005\u4E92\u76F8\u5BF9\u5F97\u4E0A\u3002")}</p>
|
|
13689
|
+
</div>
|
|
13690
|
+
` + ledger(epics) + spineMotif() + `<div class="toolbar"><input type="search" data-dossier-search placeholder="Search \xB7 \u641C\u7D22" aria-label="search"><label class="only"><input type="checkbox" data-dossier-only>${bi("Only shipping", "\u53EA\u770B\u4EA4\u4ED8\u4E2D")}</label></div>
|
|
13691
|
+
` + group("Shipping to main", "\u4EA4\u4ED8\u4E2D", shipping) + group("In backlog", "\u4ECD\u5728\u5F85\u529E", backlog) + `<footer>${bi("Generated by", "\u751F\u6210\u81EA")} <code>roll index</code></footer>
|
|
13692
|
+
</body>
|
|
13693
|
+
</html>
|
|
13694
|
+
`;
|
|
13695
|
+
}
|
|
13696
|
+
|
|
13697
|
+
// packages/cli/dist/lib/epic-page.js
|
|
13698
|
+
var esc3 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
13699
|
+
function storyState(s) {
|
|
13700
|
+
if (s.delivered)
|
|
13701
|
+
return "merged";
|
|
13702
|
+
if (s.inCycle === true)
|
|
13703
|
+
return "cycle";
|
|
13704
|
+
return "backlog";
|
|
13705
|
+
}
|
|
13706
|
+
function miniSpine(s) {
|
|
13707
|
+
const done = s.delivered ? SPINE_STAGES.length : 1 + (s.phasesDone ?? 0);
|
|
13708
|
+
const bits = SPINE_STAGES.map((st, i) => {
|
|
13709
|
+
const cls = s.delivered && st.key === "delivery" ? "truth" : i < done ? "done" : "";
|
|
13710
|
+
const seg = i < SPINE_STAGES.length - 1 ? `<s${i < done - 1 ? ' class="done"' : ""}></s>` : "";
|
|
13711
|
+
return `<i${cls !== "" ? ` class="${cls}"` : ""}></i>${seg}`;
|
|
13712
|
+
});
|
|
13713
|
+
return `<span class="mini-spine" aria-hidden="true">${bits.join("")}</span>`;
|
|
13714
|
+
}
|
|
13715
|
+
function pill(state) {
|
|
13716
|
+
if (state === "merged")
|
|
13717
|
+
return `<span class="pill merged">${bi("merged", "\u5DF2\u5408\u4E3B\u5E72")}</span>`;
|
|
13718
|
+
if (state === "cycle")
|
|
13719
|
+
return `<span class="pill cycle">${bi("in a cycle", "\u5468\u671F\u4E2D")}</span>`;
|
|
13720
|
+
return `<span class="pill backlog">${bi("in backlog", "\u5F85\u529E")}</span>`;
|
|
13721
|
+
}
|
|
13722
|
+
function storyRow(s) {
|
|
13723
|
+
const st = storyState(s);
|
|
13724
|
+
return `<a class="story-row" href="${encodeURIComponent(s.id)}/index.html" data-search="${esc3(`${s.id} ${s.title ?? ""}`)}" data-truth="${s.delivered ? "1" : "0"}"><span class="id">${esc3(s.id)}</span><span class="type type-${esc3(s.type)}">${esc3(s.type)}</span><span class="title">${esc3(s.title ?? "")}</span>` + miniSpine(s) + pill(st) + `</a>`;
|
|
13725
|
+
}
|
|
13726
|
+
function epicLedger(e) {
|
|
13727
|
+
const total = e.stories.length;
|
|
13728
|
+
const pct = total > 0 ? Math.round(e.delivered / total * 100) : 0;
|
|
13729
|
+
const byType = new Set(e.stories.map((s) => s.type)).size;
|
|
13730
|
+
const fig = (num2, en, zh, truthy = false) => `<div class="figure"><div class="num${truthy ? " truth" : ""}">${num2}</div><div class="lbl">${bi(en, zh)}</div></div>`;
|
|
13731
|
+
return `<div class="ledger"><div class="figures">` + fig(String(total), "Stories", "\u6545\u4E8B") + fig(String(e.delivered), "Merged to main", "\u5DF2\u5408\u4E3B\u5E72", true) + fig(String(total - e.delivered), "Still wish", "\u4ECD\u662F\u613F\u671B") + fig(String(byType), "Work types", "\u5DE5\u79CD") + `</div><div class="wt-bar" role="img" aria-label="${pct}% merged"><span class="truth" style="width:${pct}%"></span></div><div class="wt-legend"><span>${bi("wish \u2014 backlog", "\u613F\u671B \xB7 \u5F85\u529E")}</span><span>${pct}%</span><span>${bi("truth \u2014 merged", "\u4E8B\u5B9E \xB7 \u5DF2\u5408")}</span></div></div>`;
|
|
13732
|
+
}
|
|
13733
|
+
function renderEpicPage(e) {
|
|
13734
|
+
const merged = e.stories.filter((s) => storyState(s) === "merged");
|
|
13735
|
+
const cycle = e.stories.filter((s) => storyState(s) === "cycle");
|
|
13736
|
+
const backlog = e.stories.filter((s) => storyState(s) === "backlog");
|
|
13737
|
+
const group = (en, zh, list) => list.length === 0 ? "" : `<h2>${bi(en, zh)}</h2>
|
|
13738
|
+
<div class="story-rows">${list.map(storyRow).join("\n")}</div>
|
|
13739
|
+
`;
|
|
13740
|
+
return `<!DOCTYPE html>
|
|
13741
|
+
<html lang="zh-CN">
|
|
13742
|
+
<head>
|
|
13743
|
+
<meta charset="UTF-8">
|
|
13744
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
13745
|
+
<title>${esc3(e.name)} \xB7 Delivery Dossier</title>
|
|
13746
|
+
<style>
|
|
13747
|
+
${CHROME_CSS}${DOSSIER_CSS}body { max-width:1000px; }
|
|
13748
|
+
</style>
|
|
13749
|
+
${CHROME_SCRIPT}
|
|
13750
|
+
</head>
|
|
13751
|
+
<body>
|
|
13752
|
+
${CHROME_CONTROLS}
|
|
13753
|
+
<div class="masthead">
|
|
13754
|
+
<p class="crumb"><a href="../index.html">${bi("Features Index", "\u529F\u80FD\u6863\u6848")}</a> / ${esc3(e.name)}</p>
|
|
13755
|
+
<p class="kicker">Roll \xB7 ${bi("Epic Dossier", "\u53F2\u8BD7\u6863\u6848")}</p>
|
|
13756
|
+
<h1>${esc3(e.name)}</h1>
|
|
13757
|
+
</div>
|
|
13758
|
+
` + epicLedger(e) + group("Merged to main", "\u5DF2\u5408\u4E3B\u5E72", merged) + group("In a cycle", "\u5468\u671F\u4E2D", cycle) + group("In backlog", "\u4ECD\u5728\u5F85\u529E", backlog) + `<footer>${bi("Generated by", "\u751F\u6210\u81EA")} <code>roll index</code></footer>
|
|
13431
13759
|
</body>
|
|
13432
13760
|
</html>
|
|
13433
13761
|
`;
|
|
13762
|
+
}
|
|
13763
|
+
|
|
13764
|
+
// packages/cli/dist/lib/story-dossier.js
|
|
13765
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
13766
|
+
import { readFileSync as readFileSync23, readdirSync as readdirSync14, statSync as statSync15 } from "node:fs";
|
|
13767
|
+
import { join as joinPath2 } from "node:path";
|
|
13768
|
+
var esc4 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
13769
|
+
function stationsDone(d) {
|
|
13770
|
+
const done = /* @__PURE__ */ new Set(["definition"]);
|
|
13771
|
+
if ((d.design?.length ?? 0) > 0)
|
|
13772
|
+
done.add("design");
|
|
13773
|
+
if ((d.commits?.length ?? 0) > 0)
|
|
13774
|
+
done.add("execution");
|
|
13775
|
+
if (d.story.delivered)
|
|
13776
|
+
done.add("delivery");
|
|
13777
|
+
if (d.retro !== void 0 && d.retro !== "")
|
|
13778
|
+
done.add("retrospective");
|
|
13779
|
+
return done;
|
|
13780
|
+
}
|
|
13781
|
+
function storySpine(d) {
|
|
13782
|
+
const done = stationsDone(d);
|
|
13783
|
+
const nodes = SPINE_STAGES.map((s, i) => {
|
|
13784
|
+
const cls = done.has(s.key) ? s.key === "delivery" ? " truth" : " done" : "";
|
|
13785
|
+
const segDone = done.has(s.key) && i < SPINE_STAGES.length - 1 && done.has(SPINE_STAGES[i + 1].key);
|
|
13786
|
+
const seg = i < SPINE_STAGES.length - 1 ? `<span class="seg${segDone ? " done" : ""}"></span>` : "";
|
|
13787
|
+
return `<span class="node${cls}"><span class="dot"></span><span class="tag">${bi(s.en, s.zh)}</span></span>${seg}`;
|
|
13788
|
+
});
|
|
13789
|
+
return `<div class="spine">${nodes.join("")}</div>`;
|
|
13790
|
+
}
|
|
13791
|
+
var AC_BADGE = {
|
|
13792
|
+
pass: ["\u2713 pass", "\u2713 \u901A\u8FC7"],
|
|
13793
|
+
readonly: ["\u25C6 readonly", "\u25C6 \u53EA\u8BFB"],
|
|
13794
|
+
partial: ["\u25D1 partial", "\u25D1 \u90E8\u5206"],
|
|
13795
|
+
fail: ["\u2717 fail", "\u2717 \u5931\u8D25"],
|
|
13796
|
+
blocked: ["\u25A0 blocked", "\u25A0 \u53D7\u963B"],
|
|
13797
|
+
claimed: ["\u25B3 claimed", "\u25B3 \u4EC5\u58F0\u79F0"],
|
|
13798
|
+
missing: ["\u25CB missing", "\u25CB \u7F3A\u5931"]
|
|
13799
|
+
};
|
|
13800
|
+
function section(en, zh, body, empty) {
|
|
13801
|
+
return `<section class="phase ${empty ? "phase-pending" : "phase-done"}"><h2>${bi(en, zh)}</h2>${body}</section>`;
|
|
13802
|
+
}
|
|
13803
|
+
function renderStoryDossier(d) {
|
|
13804
|
+
const s = d.story;
|
|
13805
|
+
const definition = d.wish !== void 0 && d.wish !== "" ? `<div class="wish-quote">${esc4(d.wish)}</div>` : `<p class="empty">${bi("No wish recorded in spec.md", "spec.md \u672A\u8BB0\u5F55\u613F\u671B")}</p>`;
|
|
13806
|
+
const design = (d.design?.length ?? 0) > 0 ? `<ul>${(d.design ?? []).map((b) => `<li>${esc4(b)}</li>`).join("")}</ul>` : `<p class="empty">${bi("Not yet designed", "\u5C1A\u672A\u8BBE\u8BA1")}</p>`;
|
|
13807
|
+
const execution = (d.commits?.length ?? 0) > 0 ? `<p>${bi(`${(d.commits ?? []).length} TCR commits`, `${(d.commits ?? []).length} \u4E2A TCR \u63D0\u4EA4`)}</p><ul class="muted">${(d.commits ?? []).map((c2) => `<li><code>${esc4(c2)}</code></li>`).join("")}</ul>` : `<p class="empty">${bi("No cycles yet", "\u6682\u65E0\u5468\u671F")}</p>`;
|
|
13808
|
+
const banner = s.delivered ? `<div class="attest-banner"><span class="mark">\u2713</span><div>${bi("Merged to main \u2014 attested", "\u5DF2\u5408\u4E3B\u5E72 \xB7 \u5DF2\u9A8C\u6536")}` + (d.reportHref !== void 0 ? ` \xB7 <a href="${esc4(d.reportHref)}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a>` : "") + `</div></div>` : "";
|
|
13809
|
+
const acTable = (d.acRows?.length ?? 0) > 0 ? `<table class="ac-table"><thead><tr><th>AC</th><th>${bi("Status", "\u72B6\u6001")}</th><th>${bi("Note", "\u5907\u6CE8")}</th></tr></thead><tbody>` + (d.acRows ?? []).map((r) => {
|
|
13810
|
+
const [en, zh] = AC_BADGE[r.status] ?? [r.status, r.status];
|
|
13811
|
+
return `<tr><td><code>${esc4(r.ac)}</code></td><td>${bi(en, zh)}</td><td>${esc4(r.note ?? "")}</td></tr>`;
|
|
13812
|
+
}).join("") + `</tbody></table>` : "";
|
|
13813
|
+
const delivery = s.delivered || (d.acRows?.length ?? 0) > 0 ? banner + acTable : `<p class="empty">${bi("Not yet delivered", "\u5C1A\u672A\u4EA4\u4ED8")}</p>`;
|
|
13814
|
+
const retro = d.retro !== void 0 && d.retro !== "" ? `<p>${esc4(d.retro)}</p>` : `<p class="empty">${bi("Not yet written", "\u5C1A\u672A\u64B0\u5199")}</p>`;
|
|
13815
|
+
return `<!DOCTYPE html>
|
|
13816
|
+
<html lang="zh-CN">
|
|
13817
|
+
<head>
|
|
13818
|
+
<meta charset="UTF-8">
|
|
13819
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
13820
|
+
<title>${esc4(s.id)}${s.title !== void 0 ? ` \u2014 ${esc4(s.title)}` : ""}</title>
|
|
13821
|
+
<style>
|
|
13822
|
+
${CHROME_CSS}${DOSSIER_CSS}.phase-done { border-left:4px solid var(--pass); } .phase-pending { border-left:4px solid var(--line); }
|
|
13823
|
+
</style>
|
|
13824
|
+
${CHROME_SCRIPT}
|
|
13825
|
+
</head>
|
|
13826
|
+
<body>
|
|
13827
|
+
${CHROME_CONTROLS}
|
|
13828
|
+
<div class="masthead">
|
|
13829
|
+
<p class="crumb"><a href="../../index.html">${bi("Features Index", "\u529F\u80FD\u6863\u6848")}</a> / <a href="../index.html">${esc4(s.epic)}</a> / ${esc4(s.id)}</p>
|
|
13830
|
+
<p class="kicker">Roll \xB7 ${bi("Story Dossier", "\u6545\u4E8B\u6863\u6848")}</p>
|
|
13831
|
+
<h1><code>${esc4(s.id)}</code></h1>
|
|
13832
|
+
` + (s.title !== void 0 ? `<p class="lede">${esc4(s.title)}</p>
|
|
13833
|
+
` : "") + `<div class="kv"><span class="type type-${esc4(s.type)}">${esc4(s.type)}</span><span>${bi("epic", "\u53F2\u8BD7")} <b>${esc4(s.epic)}</b></span>` + (s.created !== void 0 ? `<span>${bi("created", "\u521B\u5EFA")} <b>${esc4(s.created)}</b></span>` : "") + `</div>
|
|
13834
|
+
</div>
|
|
13835
|
+
` + storySpine(d) + section("Definition", "\u7ACB\u9879", definition, d.wish === void 0 || d.wish === "") + section("Design", "\u8BBE\u8BA1", design, (d.design?.length ?? 0) === 0) + section("Execution", "\u6267\u884C", execution, (d.commits?.length ?? 0) === 0) + section("Delivery", "\u4EA4\u4ED8", delivery, !(s.delivered || (d.acRows?.length ?? 0) > 0)) + section("Retrospective", "\u590D\u76D8", retro, d.retro === void 0 || d.retro === "") + `<footer>Roll \xB7 <a href="spec.md">spec.md</a></footer>
|
|
13836
|
+
</body>
|
|
13837
|
+
</html>
|
|
13838
|
+
`;
|
|
13839
|
+
}
|
|
13840
|
+
function collectStoryDossierInput(projectPath, story) {
|
|
13841
|
+
const dir = joinPath2(projectPath, ".roll", "features", story.epic, story.id);
|
|
13842
|
+
const out2 = { story };
|
|
13843
|
+
try {
|
|
13844
|
+
const spec = readFile(joinPath2(dir, "spec.md"));
|
|
13845
|
+
const quote = [];
|
|
13846
|
+
for (const l of spec.split("\n")) {
|
|
13847
|
+
const m7 = /^>\s?(.*)$/.exec(l);
|
|
13848
|
+
if (m7 !== null)
|
|
13849
|
+
quote.push((m7[1] ?? "").trim());
|
|
13850
|
+
else if (quote.length > 0)
|
|
13851
|
+
break;
|
|
13852
|
+
}
|
|
13853
|
+
const wish = quote.join(" ").replace(/\s+/g, " ").trim();
|
|
13854
|
+
if (wish !== "")
|
|
13855
|
+
out2.wish = wish;
|
|
13856
|
+
const design = [];
|
|
13857
|
+
let inDesign = false;
|
|
13858
|
+
for (const l of spec.split("\n")) {
|
|
13859
|
+
if (/^#{2,3}\s*(方案|Solution|Design)/i.test(l))
|
|
13860
|
+
inDesign = true;
|
|
13861
|
+
else if (/^#{1,3}\s/.test(l))
|
|
13862
|
+
inDesign = false;
|
|
13863
|
+
else if (inDesign) {
|
|
13864
|
+
const m7 = /^[-*]\s+(.*)$/.exec(l.trim());
|
|
13865
|
+
if (m7 !== null)
|
|
13866
|
+
design.push((m7[1] ?? "").trim());
|
|
13867
|
+
}
|
|
13868
|
+
}
|
|
13869
|
+
if (design.length > 0)
|
|
13870
|
+
out2.design = design;
|
|
13871
|
+
} catch {
|
|
13872
|
+
}
|
|
13873
|
+
try {
|
|
13874
|
+
const rows = JSON.parse(readFile(joinPath2(dir, "ac-map.json")));
|
|
13875
|
+
if (Array.isArray(rows)) {
|
|
13876
|
+
const acRows = rows.filter((r) => typeof r.ac === "string" && typeof r.status === "string").map((r) => ({ ac: r.ac, status: r.status, ...r.note !== void 0 ? { note: r.note } : {} }));
|
|
13877
|
+
if (acRows.length > 0)
|
|
13878
|
+
out2.acRows = acRows;
|
|
13879
|
+
}
|
|
13880
|
+
} catch {
|
|
13881
|
+
}
|
|
13882
|
+
try {
|
|
13883
|
+
if (statDir(joinPath2(dir, "latest", `${story.id}-report.html`)))
|
|
13884
|
+
out2.reportHref = `latest/${story.id}-report.html`;
|
|
13885
|
+
} catch {
|
|
13886
|
+
}
|
|
13887
|
+
try {
|
|
13888
|
+
const cardNotes = joinPath2(dir, "notes");
|
|
13889
|
+
const legacyNotes = joinPath2(projectPath, ".roll", "notes");
|
|
13890
|
+
let notesDir = cardNotes;
|
|
13891
|
+
let notes = [];
|
|
13434
13892
|
try {
|
|
13435
|
-
|
|
13436
|
-
|
|
13893
|
+
notes = listDir(cardNotes).filter((f) => f.endsWith(".md")).sort();
|
|
13894
|
+
} catch {
|
|
13895
|
+
}
|
|
13896
|
+
if (notes.length === 0) {
|
|
13897
|
+
notesDir = legacyNotes;
|
|
13898
|
+
notes = listDir(legacyNotes).filter((f) => f.includes(`-${story.id}-`) && f.endsWith(".md")).sort();
|
|
13899
|
+
}
|
|
13900
|
+
const last = notes[notes.length - 1];
|
|
13901
|
+
if (last !== void 0) {
|
|
13902
|
+
const body = readFile(joinPath2(notesDir, last));
|
|
13903
|
+
const score = /^score:\s*(.+)$/m.exec(body);
|
|
13904
|
+
const verdict = /^verdict:\s*(.+)$/m.exec(body);
|
|
13905
|
+
const para = body.split(/\n{2,}/).map((p) => p.trim()).find((p) => p !== "" && !p.startsWith("---") && !/^score:|^verdict:/m.test(p));
|
|
13906
|
+
out2.retro = [
|
|
13907
|
+
score !== null ? `score ${(score[1] ?? "").trim()}` : "",
|
|
13908
|
+
verdict !== null ? `\xB7 ${(verdict[1] ?? "").trim()}` : "",
|
|
13909
|
+
para !== void 0 ? `\u2014 ${para.replace(/\s+/g, " ").slice(0, 240)}` : ""
|
|
13910
|
+
].join(" ").trim();
|
|
13911
|
+
}
|
|
13912
|
+
} catch {
|
|
13913
|
+
}
|
|
13914
|
+
try {
|
|
13915
|
+
const log = execGit(projectPath, ["log", "--format=%s", "--fixed-strings", "--grep", story.id, "--reverse"]);
|
|
13916
|
+
const commits = log.split("\n").map((l) => l.trim()).filter((l) => l !== "").slice(0, 40);
|
|
13917
|
+
if (commits.length > 0)
|
|
13918
|
+
out2.commits = commits;
|
|
13919
|
+
} catch {
|
|
13920
|
+
}
|
|
13921
|
+
return out2;
|
|
13922
|
+
}
|
|
13923
|
+
function readFile(p) {
|
|
13924
|
+
return readFileSync23(p, "utf8");
|
|
13925
|
+
}
|
|
13926
|
+
function listDir(p) {
|
|
13927
|
+
return readdirSync14(p);
|
|
13928
|
+
}
|
|
13929
|
+
function statDir(p) {
|
|
13930
|
+
return statSync15(p).isFile();
|
|
13931
|
+
}
|
|
13932
|
+
function execGit(cwd, args) {
|
|
13933
|
+
return execFileSync7("git", args, { cwd, encoding: "utf8", timeout: 1e4 });
|
|
13934
|
+
}
|
|
13935
|
+
|
|
13936
|
+
// packages/cli/dist/commands/index-gen.js
|
|
13937
|
+
function indexCommand(args) {
|
|
13938
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
13939
|
+
process.stdout.write("Usage: roll index\n Regenerate .roll/index.json + the Delivery Dossier pages\n (features/index.html, every epic page, every story dossier)\n");
|
|
13940
|
+
return 0;
|
|
13941
|
+
}
|
|
13942
|
+
const cwd = process.cwd();
|
|
13943
|
+
const stories = generateIndex(cwd);
|
|
13944
|
+
const n = Object.keys(stories).length;
|
|
13945
|
+
process.stdout.write(`index.json regenerated
|
|
13946
|
+
\u7D22\u5F15\u5DF2\u91CD\u5EFA
|
|
13947
|
+
${n} stories mapped to epics (.roll/index.json)
|
|
13437
13948
|
`);
|
|
13949
|
+
const featuresDir = join26(cwd, ".roll", "features");
|
|
13950
|
+
if (existsSync29(featuresDir)) {
|
|
13951
|
+
const epics = collectDossier(cwd);
|
|
13952
|
+
let pages = 0;
|
|
13953
|
+
try {
|
|
13954
|
+
writeFileSync14(join26(featuresDir, "index.html"), renderFeaturesIndex(epics), "utf8");
|
|
13955
|
+
pages += 1;
|
|
13438
13956
|
} catch {
|
|
13439
13957
|
}
|
|
13958
|
+
for (const epic of epics) {
|
|
13959
|
+
try {
|
|
13960
|
+
writeFileSync14(join26(featuresDir, epic.name, "index.html"), renderEpicPage(epic), "utf8");
|
|
13961
|
+
pages += 1;
|
|
13962
|
+
} catch {
|
|
13963
|
+
}
|
|
13964
|
+
for (const story of epic.stories) {
|
|
13965
|
+
try {
|
|
13966
|
+
writeFileSync14(join26(featuresDir, epic.name, story.id, "index.html"), renderStoryDossier(collectStoryDossierInput(cwd, story)), "utf8");
|
|
13967
|
+
pages += 1;
|
|
13968
|
+
} catch {
|
|
13969
|
+
}
|
|
13970
|
+
}
|
|
13971
|
+
}
|
|
13972
|
+
process.stdout.write(`Delivery Dossier regenerated (${pages} pages)
|
|
13973
|
+
\u4EA4\u4ED8\u6863\u6848\u5DF2\u91CD\u5EFA\uFF08${pages} \u9875\uFF09
|
|
13974
|
+
`);
|
|
13975
|
+
}
|
|
13976
|
+
return 0;
|
|
13977
|
+
}
|
|
13978
|
+
|
|
13979
|
+
// packages/cli/dist/commands/story-new.js
|
|
13980
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync14, writeFileSync as writeFileSync15 } from "node:fs";
|
|
13981
|
+
import { join as join27 } from "node:path";
|
|
13982
|
+
function todayYmd() {
|
|
13983
|
+
const d = /* @__PURE__ */ new Date();
|
|
13984
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
13985
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
13986
|
+
}
|
|
13987
|
+
function flagValue(args, flag) {
|
|
13988
|
+
const i = args.indexOf(flag);
|
|
13989
|
+
if (i === -1)
|
|
13990
|
+
return void 0;
|
|
13991
|
+
return args[i + 1];
|
|
13992
|
+
}
|
|
13993
|
+
function storyNewCommand(args) {
|
|
13994
|
+
if (args[0] === "--help" || args[0] === "-h" || args[0] === void 0) {
|
|
13995
|
+
process.stdout.write("Usage: roll story new <ID> --title <text> [--epic <epic>] [--note <text>]\n Mint the card folder: features/<epic>/<ID>/spec.md + index.html, refresh index.json\n");
|
|
13996
|
+
return args[0] === void 0 ? 1 : 0;
|
|
13997
|
+
}
|
|
13998
|
+
const id = args[0];
|
|
13999
|
+
if (!STORY_ID_RE.test(id)) {
|
|
14000
|
+
process.stderr.write(`story new: '${id}' is not a story id (US-/FIX-/REFACTOR-/IDEA-\u2026)
|
|
14001
|
+
story new: '${id}' \u4E0D\u662F\u5408\u6CD5\u6545\u4E8B ID
|
|
14002
|
+
`);
|
|
14003
|
+
return 2;
|
|
14004
|
+
}
|
|
14005
|
+
const title = flagValue(args, "--title");
|
|
14006
|
+
if (title === void 0 || title === "") {
|
|
14007
|
+
process.stderr.write("story new: --title is required\nstory new: \u5FC5\u987B\u63D0\u4F9B --title\n");
|
|
14008
|
+
return 2;
|
|
14009
|
+
}
|
|
14010
|
+
const epic = flagValue(args, "--epic") ?? UNCATEGORIZED;
|
|
14011
|
+
const note = flagValue(args, "--note");
|
|
14012
|
+
const cwd = process.cwd();
|
|
14013
|
+
const dir = join27(cwd, ".roll", "features", epic, id);
|
|
14014
|
+
if (existsSync30(join27(dir, "spec.md"))) {
|
|
14015
|
+
process.stderr.write(`story new: ${epic}/${id}/spec.md already exists \u2014 cards are born once
|
|
14016
|
+
story new: \u5361\u5DF2\u5B58\u5728\uFF0C\u4E0D\u53EF\u8986\u76D6
|
|
14017
|
+
`);
|
|
14018
|
+
return 1;
|
|
14019
|
+
}
|
|
14020
|
+
const meta = {
|
|
14021
|
+
id,
|
|
14022
|
+
title,
|
|
14023
|
+
created: todayYmd(),
|
|
14024
|
+
...epic !== UNCATEGORIZED ? { epic } : {},
|
|
14025
|
+
...note !== void 0 && note !== "" ? { note } : {}
|
|
14026
|
+
};
|
|
14027
|
+
mkdirSync14(dir, { recursive: true });
|
|
14028
|
+
writeFileSync15(join27(dir, "spec.md"), renderSpecMd(meta), "utf8");
|
|
14029
|
+
writeFileSync15(join27(dir, "index.html"), renderStoryPage(meta), "utf8");
|
|
14030
|
+
try {
|
|
14031
|
+
generateIndex(cwd);
|
|
14032
|
+
} catch {
|
|
13440
14033
|
}
|
|
14034
|
+
process.stdout.write(`card minted
|
|
14035
|
+
\u5361\u5DF2\u5EFA\u6863
|
|
14036
|
+
.roll/features/${epic}/${id}/spec.md
|
|
14037
|
+
`);
|
|
13441
14038
|
return 0;
|
|
13442
14039
|
}
|
|
13443
14040
|
|
|
13444
14041
|
// packages/cli/dist/commands/init.js
|
|
13445
14042
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
13446
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
14043
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync31, mkdirSync as mkdirSync15, readFileSync as readFileSync24, realpathSync as realpathSync4, statSync as statSync16, writeFileSync as writeFileSync16 } from "node:fs";
|
|
13447
14044
|
import { homedir as homedir12 } from "node:os";
|
|
13448
|
-
import { dirname as dirname11, join as
|
|
14045
|
+
import { dirname as dirname11, join as join28 } from "node:path";
|
|
13449
14046
|
function err9(line) {
|
|
13450
14047
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
13451
14048
|
const RED = noColor2 ? "" : "\x1B[0;31m";
|
|
@@ -13454,43 +14051,43 @@ function err9(line) {
|
|
|
13454
14051
|
`);
|
|
13455
14052
|
}
|
|
13456
14053
|
function rollHome2() {
|
|
13457
|
-
return process.env["ROLL_HOME"] ??
|
|
14054
|
+
return process.env["ROLL_HOME"] ?? join28(homedir12(), ".roll");
|
|
13458
14055
|
}
|
|
13459
14056
|
function rollGlobal2() {
|
|
13460
|
-
return
|
|
14057
|
+
return join28(rollHome2(), "conventions", "global");
|
|
13461
14058
|
}
|
|
13462
14059
|
function rollTemplates2() {
|
|
13463
|
-
return
|
|
14060
|
+
return join28(rollHome2(), "conventions", "templates");
|
|
13464
14061
|
}
|
|
13465
14062
|
function scanProjectType(dir) {
|
|
13466
14063
|
let hasFrontend = false;
|
|
13467
14064
|
let hasBackend = false;
|
|
13468
14065
|
let hasCli = false;
|
|
13469
|
-
const pkg =
|
|
14066
|
+
const pkg = join28(dir, "package.json");
|
|
13470
14067
|
const readPkg = () => {
|
|
13471
14068
|
try {
|
|
13472
|
-
return
|
|
14069
|
+
return readFileSync24(pkg, "utf8");
|
|
13473
14070
|
} catch {
|
|
13474
14071
|
return "";
|
|
13475
14072
|
}
|
|
13476
14073
|
};
|
|
13477
|
-
if (
|
|
14074
|
+
if (existsSync31(pkg)) {
|
|
13478
14075
|
if (/"react"|"vue"|"next"|"nuxt"|"vite"|"svelte"/i.test(readPkg()))
|
|
13479
14076
|
hasFrontend = true;
|
|
13480
14077
|
}
|
|
13481
|
-
if (["src", "app", "pages", "components"].some((d) =>
|
|
14078
|
+
if (["src", "app", "pages", "components"].some((d) => existsSync31(join28(dir, d))))
|
|
13482
14079
|
hasFrontend = true;
|
|
13483
|
-
if (["server", "api", "backend"].some((d) =>
|
|
14080
|
+
if (["server", "api", "backend"].some((d) => existsSync31(join28(dir, d))))
|
|
13484
14081
|
hasBackend = true;
|
|
13485
|
-
if (["go.mod", "main.go", "main.py", "app.py", "Cargo.toml", "requirements.txt", "pyproject.toml"].some((f) =>
|
|
14082
|
+
if (["go.mod", "main.go", "main.py", "app.py", "Cargo.toml", "requirements.txt", "pyproject.toml"].some((f) => existsSync31(join28(dir, f))))
|
|
13486
14083
|
hasBackend = true;
|
|
13487
|
-
if (
|
|
14084
|
+
if (existsSync31(pkg)) {
|
|
13488
14085
|
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()))
|
|
13489
14086
|
hasBackend = true;
|
|
13490
14087
|
}
|
|
13491
|
-
if (
|
|
14088
|
+
if (existsSync31(join28(dir, "prisma", "schema.prisma")))
|
|
13492
14089
|
hasBackend = true;
|
|
13493
|
-
if (
|
|
14090
|
+
if (existsSync31(join28(dir, "bin")) || existsSync31(join28(dir, "cmd")))
|
|
13494
14091
|
hasCli = true;
|
|
13495
14092
|
if (hasFrontend && hasBackend)
|
|
13496
14093
|
return "fullstack";
|
|
@@ -13510,8 +14107,8 @@ function countNonEmptyFiles(dir) {
|
|
|
13510
14107
|
}
|
|
13511
14108
|
function isLegacyProject(projectDir) {
|
|
13512
14109
|
for (const dir of ["src", "app", "lib", "pkg", "cmd"]) {
|
|
13513
|
-
const p =
|
|
13514
|
-
if (
|
|
14110
|
+
const p = join28(projectDir, dir);
|
|
14111
|
+
if (existsSync31(p) && statSync16(p).isDirectory()) {
|
|
13515
14112
|
if (countNonEmptyFiles(p) >= 10)
|
|
13516
14113
|
return true;
|
|
13517
14114
|
}
|
|
@@ -13541,12 +14138,12 @@ function isLegacyProject(projectDir) {
|
|
|
13541
14138
|
"deno.jsonc"
|
|
13542
14139
|
];
|
|
13543
14140
|
for (const man of manifests)
|
|
13544
|
-
if (
|
|
14141
|
+
if (existsSync31(join28(projectDir, man)))
|
|
13545
14142
|
return true;
|
|
13546
14143
|
const tf = spawnSync5("bash", ["-c", `compgen -G '${projectDir.replace(/'/g, "'\\''")}/*.tf' >/dev/null 2>&1`]);
|
|
13547
14144
|
if (tf.status === 0)
|
|
13548
14145
|
return true;
|
|
13549
|
-
if (
|
|
14146
|
+
if (existsSync31(join28(projectDir, ".git"))) {
|
|
13550
14147
|
const g = spawnSync5("git", ["rev-parse", "--verify", "HEAD"], { cwd: projectDir, stdio: "ignore" });
|
|
13551
14148
|
if (g.status === 0)
|
|
13552
14149
|
return true;
|
|
@@ -13554,18 +14151,18 @@ function isLegacyProject(projectDir) {
|
|
|
13554
14151
|
return false;
|
|
13555
14152
|
}
|
|
13556
14153
|
function mergeGlobalToProject(projectDir, summary) {
|
|
13557
|
-
const src =
|
|
13558
|
-
const dst =
|
|
13559
|
-
if (!
|
|
14154
|
+
const src = join28(rollGlobal2(), "AGENTS.md");
|
|
14155
|
+
const dst = join28(projectDir, "AGENTS.md");
|
|
14156
|
+
if (!existsSync31(src)) {
|
|
13560
14157
|
return;
|
|
13561
14158
|
}
|
|
13562
14159
|
const projectType = scanProjectType(projectDir);
|
|
13563
14160
|
const skipFrontend = ["cli", "backend-service", "unknown"].includes(projectType);
|
|
13564
14161
|
const FRONTEND_HEAD = "## 7. Frontend Default Stack";
|
|
13565
|
-
const srcText =
|
|
14162
|
+
const srcText = readFileSync24(src, "utf8");
|
|
13566
14163
|
const srcLines = srcText.split("\n");
|
|
13567
14164
|
const lines = srcText.endsWith("\n") ? srcLines.slice(0, -1) : srcLines;
|
|
13568
|
-
if (!
|
|
14165
|
+
if (!existsSync31(dst)) {
|
|
13569
14166
|
let out2 = "";
|
|
13570
14167
|
let fcH = "";
|
|
13571
14168
|
let fcB = "";
|
|
@@ -13594,11 +14191,11 @@ ${fcB}`;
|
|
|
13594
14191
|
}
|
|
13595
14192
|
}
|
|
13596
14193
|
flush();
|
|
13597
|
-
|
|
14194
|
+
writeFileSync16(dst, out2);
|
|
13598
14195
|
summary.push("created|AGENTS.md");
|
|
13599
14196
|
return;
|
|
13600
14197
|
}
|
|
13601
|
-
const dstText =
|
|
14198
|
+
const dstText = readFileSync24(dst, "utf8");
|
|
13602
14199
|
let added = 0;
|
|
13603
14200
|
let curH = "";
|
|
13604
14201
|
let curB = "";
|
|
@@ -13628,7 +14225,7 @@ ${curB}`;
|
|
|
13628
14225
|
}
|
|
13629
14226
|
tryAppend();
|
|
13630
14227
|
if (appendBuffer !== "")
|
|
13631
|
-
|
|
14228
|
+
writeFileSync16(dst, dstText + appendBuffer);
|
|
13632
14229
|
if (added > 0)
|
|
13633
14230
|
summary.push("merged|AGENTS.md");
|
|
13634
14231
|
else
|
|
@@ -13636,20 +14233,20 @@ ${curB}`;
|
|
|
13636
14233
|
}
|
|
13637
14234
|
function mergeClaudeToProject(projectDir, summary) {
|
|
13638
14235
|
const projectType = scanProjectType(projectDir);
|
|
13639
|
-
const tplFile =
|
|
13640
|
-
if (!
|
|
14236
|
+
const tplFile = join28(rollTemplates2(), projectType, "CLAUDE.md");
|
|
14237
|
+
if (!existsSync31(tplFile))
|
|
13641
14238
|
return;
|
|
13642
|
-
const claudeDir =
|
|
13643
|
-
const outFile =
|
|
13644
|
-
|
|
13645
|
-
if (!
|
|
14239
|
+
const claudeDir = join28(projectDir, ".claude");
|
|
14240
|
+
const outFile = join28(claudeDir, "CLAUDE.md");
|
|
14241
|
+
mkdirSync15(claudeDir, { recursive: true });
|
|
14242
|
+
if (!existsSync31(outFile)) {
|
|
13646
14243
|
copyFileSync2(tplFile, outFile);
|
|
13647
14244
|
summary.push("created|.claude/CLAUDE.md");
|
|
13648
14245
|
return;
|
|
13649
14246
|
}
|
|
13650
|
-
const tplText =
|
|
14247
|
+
const tplText = readFileSync24(tplFile, "utf8");
|
|
13651
14248
|
const lines = tplText.endsWith("\n") ? tplText.split("\n").slice(0, -1) : tplText.split("\n");
|
|
13652
|
-
const outText =
|
|
14249
|
+
const outText = readFileSync24(outFile, "utf8");
|
|
13653
14250
|
let added = 0;
|
|
13654
14251
|
let curH = "";
|
|
13655
14252
|
let curB = "";
|
|
@@ -13674,7 +14271,7 @@ ${curB}`;
|
|
|
13674
14271
|
}
|
|
13675
14272
|
tryAppend();
|
|
13676
14273
|
if (appendBuffer !== "")
|
|
13677
|
-
|
|
14274
|
+
writeFileSync16(outFile, outText + appendBuffer);
|
|
13678
14275
|
if (added > 0)
|
|
13679
14276
|
summary.push("merged|.claude/CLAUDE.md");
|
|
13680
14277
|
else
|
|
@@ -13691,20 +14288,20 @@ var BACKLOG_TEMPLATE = `# Project Backlog
|
|
|
13691
14288
|
|----|---------|--------|
|
|
13692
14289
|
`;
|
|
13693
14290
|
function writeBacklog(path, summary) {
|
|
13694
|
-
if (
|
|
14291
|
+
if (existsSync31(path)) {
|
|
13695
14292
|
summary.push("unchanged|.roll/backlog.md");
|
|
13696
14293
|
return;
|
|
13697
14294
|
}
|
|
13698
|
-
|
|
13699
|
-
|
|
14295
|
+
mkdirSync15(dirname11(path), { recursive: true });
|
|
14296
|
+
writeFileSync16(path, BACKLOG_TEMPLATE);
|
|
13700
14297
|
summary.push("created|.roll/backlog.md");
|
|
13701
14298
|
}
|
|
13702
14299
|
function ensureFeaturesDir(path, summary) {
|
|
13703
|
-
if (
|
|
14300
|
+
if (existsSync31(path) && statSync16(path).isDirectory()) {
|
|
13704
14301
|
summary.push("unchanged|.roll/features/");
|
|
13705
14302
|
return;
|
|
13706
14303
|
}
|
|
13707
|
-
|
|
14304
|
+
mkdirSync15(path, { recursive: true });
|
|
13708
14305
|
summary.push("created|.roll/features/");
|
|
13709
14306
|
}
|
|
13710
14307
|
var FEATURES_TEMPLATE = `# Features
|
|
@@ -13718,38 +14315,38 @@ var FEATURES_TEMPLATE = `# Features
|
|
|
13718
14315
|
<!-- Add feature entries here as epics are completed -->
|
|
13719
14316
|
`;
|
|
13720
14317
|
function writeFeaturesMd(path, summary) {
|
|
13721
|
-
if (
|
|
14318
|
+
if (existsSync31(path)) {
|
|
13722
14319
|
summary.push("unchanged|.roll/features.md");
|
|
13723
14320
|
return;
|
|
13724
14321
|
}
|
|
13725
|
-
|
|
13726
|
-
|
|
14322
|
+
mkdirSync15(dirname11(path), { recursive: true });
|
|
14323
|
+
writeFileSync16(path, FEATURES_TEMPLATE);
|
|
13727
14324
|
summary.push("created|.roll/features.md");
|
|
13728
14325
|
}
|
|
13729
14326
|
function initSeedAgentRoutes(templateName, projectDir, summary) {
|
|
13730
|
-
const dest =
|
|
13731
|
-
if (
|
|
14327
|
+
const dest = join28(projectDir, ".roll", "agent-routes.yaml");
|
|
14328
|
+
if (existsSync31(dest)) {
|
|
13732
14329
|
summary.push("unchanged|.roll/agent-routes.yaml");
|
|
13733
14330
|
return 0;
|
|
13734
14331
|
}
|
|
13735
|
-
const src =
|
|
13736
|
-
if (!
|
|
14332
|
+
const src = join28(rollTemplates2(), "agent-routes", `${templateName}.yaml`);
|
|
14333
|
+
if (!existsSync31(src)) {
|
|
13737
14334
|
return 1;
|
|
13738
14335
|
}
|
|
13739
|
-
|
|
14336
|
+
mkdirSync15(dirname11(dest), { recursive: true });
|
|
13740
14337
|
copyFileSync2(src, dest);
|
|
13741
14338
|
summary.push("created|.roll/agent-routes.yaml");
|
|
13742
14339
|
return 0;
|
|
13743
14340
|
}
|
|
13744
14341
|
function writeVersionStamp(projectDir, summary) {
|
|
13745
|
-
const stampPath =
|
|
13746
|
-
if (
|
|
14342
|
+
const stampPath = join28(projectDir, ".roll", ".version");
|
|
14343
|
+
if (existsSync31(stampPath)) {
|
|
13747
14344
|
summary.push("unchanged|.roll/.version");
|
|
13748
14345
|
return;
|
|
13749
14346
|
}
|
|
13750
|
-
|
|
14347
|
+
mkdirSync15(join28(projectDir, ".roll"), { recursive: true });
|
|
13751
14348
|
const installedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13752
|
-
|
|
14349
|
+
writeFileSync16(stampPath, `# Roll project version stamp \u2014 written by \`roll init\` (US-ONBOARD-019).
|
|
13753
14350
|
# Used by \`_check_structure\` to recognise a previously-onboarded Roll project
|
|
13754
14351
|
# without depending on directory-name heuristics.
|
|
13755
14352
|
roll_version: "${rollVersion() || "unknown"}"
|
|
@@ -13887,7 +14484,7 @@ function initCommand(args) {
|
|
|
13887
14484
|
return null;
|
|
13888
14485
|
if (args[0] !== void 0 && args[0].startsWith("-"))
|
|
13889
14486
|
return null;
|
|
13890
|
-
if (!
|
|
14487
|
+
if (!existsSync31(rollTemplates2()))
|
|
13891
14488
|
return null;
|
|
13892
14489
|
let projectDir;
|
|
13893
14490
|
try {
|
|
@@ -13897,16 +14494,16 @@ function initCommand(args) {
|
|
|
13897
14494
|
}
|
|
13898
14495
|
let hasAgents = false;
|
|
13899
14496
|
const summary = [];
|
|
13900
|
-
if (
|
|
14497
|
+
if (existsSync31(join28(projectDir, "AGENTS.md"))) {
|
|
13901
14498
|
hasAgents = true;
|
|
13902
14499
|
} else if (isLegacyProject(projectDir)) {
|
|
13903
14500
|
return null;
|
|
13904
14501
|
}
|
|
13905
14502
|
mergeGlobalToProject(projectDir, summary);
|
|
13906
14503
|
mergeClaudeToProject(projectDir, summary);
|
|
13907
|
-
writeBacklog(
|
|
13908
|
-
ensureFeaturesDir(
|
|
13909
|
-
writeFeaturesMd(
|
|
14504
|
+
writeBacklog(join28(projectDir, ".roll", "backlog.md"), summary);
|
|
14505
|
+
ensureFeaturesDir(join28(projectDir, ".roll", "features"), summary);
|
|
14506
|
+
writeFeaturesMd(join28(projectDir, ".roll", "features.md"), summary);
|
|
13910
14507
|
const routesTemplate = process.env["ROLL_AGENT_ROUTES_TEMPLATE"] ?? "default";
|
|
13911
14508
|
initSeedAgentRoutes(routesTemplate, projectDir, summary);
|
|
13912
14509
|
writeVersionStamp(projectDir, summary);
|
|
@@ -13919,19 +14516,19 @@ function initCommand(args) {
|
|
|
13919
14516
|
}
|
|
13920
14517
|
|
|
13921
14518
|
// packages/cli/dist/commands/lang.js
|
|
13922
|
-
import { execFileSync as
|
|
13923
|
-
import { existsSync as
|
|
14519
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
14520
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync3, readFileSync as readFileSync25, renameSync as renameSync2, writeFileSync as writeFileSync17 } from "node:fs";
|
|
13924
14521
|
import { homedir as homedir13, tmpdir as tmpdir3 } from "node:os";
|
|
13925
|
-
import { dirname as dirname12, join as
|
|
14522
|
+
import { dirname as dirname12, join as join29 } from "node:path";
|
|
13926
14523
|
function rollConfigPath4() {
|
|
13927
|
-
const rollHome4 = process.env["ROLL_HOME"] ??
|
|
13928
|
-
return
|
|
14524
|
+
const rollHome4 = process.env["ROLL_HOME"] ?? join29(homedir13(), ".roll");
|
|
14525
|
+
return join29(rollHome4, "config.yaml");
|
|
13929
14526
|
}
|
|
13930
14527
|
function configLang2() {
|
|
13931
14528
|
const cfg = rollConfigPath4();
|
|
13932
|
-
if (!
|
|
14529
|
+
if (!existsSync32(cfg))
|
|
13933
14530
|
return void 0;
|
|
13934
|
-
for (const line of
|
|
14531
|
+
for (const line of readFileSync25(cfg, "utf8").split("\n")) {
|
|
13935
14532
|
const m7 = /^lang:\s*(.*)$/.exec(line);
|
|
13936
14533
|
if (m7 !== null) {
|
|
13937
14534
|
const v = (m7[1] ?? "").replace(/\s*#.*$/, "").trim();
|
|
@@ -13943,15 +14540,15 @@ function configLang2() {
|
|
|
13943
14540
|
}
|
|
13944
14541
|
function configHasLangLine() {
|
|
13945
14542
|
const cfg = rollConfigPath4();
|
|
13946
|
-
if (!
|
|
14543
|
+
if (!existsSync32(cfg))
|
|
13947
14544
|
return false;
|
|
13948
|
-
return
|
|
14545
|
+
return readFileSync25(cfg, "utf8").split("\n").some((l) => /^lang:/.test(l));
|
|
13949
14546
|
}
|
|
13950
14547
|
function appleLang2() {
|
|
13951
14548
|
if (process.platform !== "darwin")
|
|
13952
14549
|
return void 0;
|
|
13953
14550
|
try {
|
|
13954
|
-
const out2 =
|
|
14551
|
+
const out2 = execFileSync8("defaults", ["read", "-g", "AppleLanguages"], {
|
|
13955
14552
|
encoding: "utf8",
|
|
13956
14553
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13957
14554
|
});
|
|
@@ -13979,7 +14576,7 @@ function resolveSource() {
|
|
|
13979
14576
|
const env = process.env;
|
|
13980
14577
|
if ((env["ROLL_LANG"] ?? "") !== "")
|
|
13981
14578
|
return "ROLL_LANG env";
|
|
13982
|
-
if (
|
|
14579
|
+
if (existsSync32(rollConfigPath4()) && configHasLangLine())
|
|
13983
14580
|
return `config (${rollConfigPath4()})`;
|
|
13984
14581
|
if ((env["LC_ALL"] ?? "") !== "" || (env["LANG"] ?? "") !== "")
|
|
13985
14582
|
return "LC_ALL/LANG";
|
|
@@ -14004,28 +14601,28 @@ function err10(line) {
|
|
|
14004
14601
|
}
|
|
14005
14602
|
function writeLang(value) {
|
|
14006
14603
|
const cfg = rollConfigPath4();
|
|
14007
|
-
|
|
14008
|
-
const existing =
|
|
14604
|
+
mkdirSync16(dirname12(cfg), { recursive: true });
|
|
14605
|
+
const existing = existsSync32(cfg) ? readFileSync25(cfg, "utf8") : "";
|
|
14009
14606
|
const kept = existing === "" ? [] : existing.split("\n").filter((l) => !/^lang:/.test(l));
|
|
14010
14607
|
if (kept.length > 0 && kept[kept.length - 1] === "")
|
|
14011
14608
|
kept.pop();
|
|
14012
14609
|
const body = kept.length > 0 ? kept.join("\n") + "\n" : "";
|
|
14013
|
-
const tmp =
|
|
14014
|
-
|
|
14610
|
+
const tmp = join29(mkdtempSync3(join29(tmpdir3(), "roll-lang-")), "config.yaml");
|
|
14611
|
+
writeFileSync17(tmp, `${body}lang: ${value}
|
|
14015
14612
|
`);
|
|
14016
14613
|
renameSync2(tmp, cfg);
|
|
14017
14614
|
}
|
|
14018
14615
|
function clearLang() {
|
|
14019
14616
|
const cfg = rollConfigPath4();
|
|
14020
|
-
if (!
|
|
14617
|
+
if (!existsSync32(cfg))
|
|
14021
14618
|
return;
|
|
14022
|
-
const existing =
|
|
14619
|
+
const existing = readFileSync25(cfg, "utf8");
|
|
14023
14620
|
const kept = existing.split("\n").filter((l) => !/^lang:/.test(l));
|
|
14024
14621
|
if (kept.length > 0 && kept[kept.length - 1] === "")
|
|
14025
14622
|
kept.pop();
|
|
14026
14623
|
const body = kept.length > 0 ? kept.join("\n") + "\n" : "";
|
|
14027
|
-
const tmp =
|
|
14028
|
-
|
|
14624
|
+
const tmp = join29(mkdtempSync3(join29(tmpdir3(), "roll-lang-")), "config.yaml");
|
|
14625
|
+
writeFileSync17(tmp, body);
|
|
14029
14626
|
renameSync2(tmp, cfg);
|
|
14030
14627
|
}
|
|
14031
14628
|
function langCommand(args) {
|
|
@@ -14125,9 +14722,9 @@ async function loopFmtCommand(args) {
|
|
|
14125
14722
|
|
|
14126
14723
|
// packages/cli/dist/commands/loop-pr-inbox.js
|
|
14127
14724
|
import { spawn as spawn2 } from "node:child_process";
|
|
14128
|
-
import { appendFileSync as appendFileSync5, existsSync as
|
|
14725
|
+
import { appendFileSync as appendFileSync5, existsSync as existsSync33, mkdirSync as mkdirSync17, readFileSync as readFileSync26, writeFileSync as writeFileSync18 } from "node:fs";
|
|
14129
14726
|
import { homedir as homedir14 } from "node:os";
|
|
14130
|
-
import { dirname as dirname13, join as
|
|
14727
|
+
import { dirname as dirname13, join as join30 } from "node:path";
|
|
14131
14728
|
function reducePrView(raw) {
|
|
14132
14729
|
const reviews = raw.reviews ?? [];
|
|
14133
14730
|
const botReviews = reviews.filter((r) => r.authorAssociation === "BOT" || r.authorAssociation === "APP");
|
|
@@ -14210,7 +14807,7 @@ function runtimeDir() {
|
|
|
14210
14807
|
const override = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
|
|
14211
14808
|
if (override !== "")
|
|
14212
14809
|
return override;
|
|
14213
|
-
return
|
|
14810
|
+
return join30(process.cwd(), ".roll", "loop");
|
|
14214
14811
|
}
|
|
14215
14812
|
function projSlug() {
|
|
14216
14813
|
const override = (process.env["ROLL_MAIN_SLUG"] ?? "").trim();
|
|
@@ -14219,26 +14816,26 @@ function projSlug() {
|
|
|
14219
14816
|
return process.cwd().split("/").filter(Boolean).pop() ?? "default";
|
|
14220
14817
|
}
|
|
14221
14818
|
function alertPath() {
|
|
14222
|
-
return
|
|
14819
|
+
return join30(runtimeDir(), `ALERT-${projSlug()}.md`);
|
|
14223
14820
|
}
|
|
14224
14821
|
function statePath() {
|
|
14225
|
-
return
|
|
14822
|
+
return join30(runtimeDir(), `state-${projSlug()}.yaml`);
|
|
14226
14823
|
}
|
|
14227
14824
|
function tickPath() {
|
|
14228
|
-
return
|
|
14825
|
+
return join30(runtimeDir(), "pr-tick.jsonl");
|
|
14229
14826
|
}
|
|
14230
14827
|
function engineBin() {
|
|
14231
14828
|
let dir = dirname13(new URL(import.meta.url).pathname);
|
|
14232
14829
|
for (let i = 0; i < 10; i++) {
|
|
14233
|
-
const candidate =
|
|
14234
|
-
if (
|
|
14830
|
+
const candidate = join30(dir, "bin", "roll");
|
|
14831
|
+
if (existsSync33(candidate))
|
|
14235
14832
|
return candidate;
|
|
14236
14833
|
const parent = dirname13(dir);
|
|
14237
14834
|
if (parent === dir)
|
|
14238
14835
|
break;
|
|
14239
14836
|
dir = parent;
|
|
14240
14837
|
}
|
|
14241
|
-
return
|
|
14838
|
+
return join30(homedir14(), ".local", "lib", "roll", "bin", "roll");
|
|
14242
14839
|
}
|
|
14243
14840
|
function pal2() {
|
|
14244
14841
|
return (process.env["NO_COLOR"] ?? "") !== "" ? { yellow: "", nc: "" } : { yellow: "\x1B[0;33m", nc: "\x1B[0m" };
|
|
@@ -14248,21 +14845,21 @@ function nowSec() {
|
|
|
14248
14845
|
}
|
|
14249
14846
|
function writeTickFile(tick) {
|
|
14250
14847
|
const file = tickPath();
|
|
14251
|
-
|
|
14848
|
+
mkdirSync17(dirname13(file), { recursive: true });
|
|
14252
14849
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
14253
14850
|
appendFileSync5(file, `${JSON.stringify({ ts, ...tick })}
|
|
14254
14851
|
`);
|
|
14255
14852
|
try {
|
|
14256
|
-
const lines =
|
|
14853
|
+
const lines = readFileSync26(file, "utf8").split("\n").filter((l) => l !== "");
|
|
14257
14854
|
if (lines.length > 500)
|
|
14258
|
-
|
|
14855
|
+
writeFileSync18(file, `${lines.slice(-500).join("\n")}
|
|
14259
14856
|
`);
|
|
14260
14857
|
} catch {
|
|
14261
14858
|
}
|
|
14262
14859
|
}
|
|
14263
14860
|
function appendAlert(line) {
|
|
14264
14861
|
const file = alertPath();
|
|
14265
|
-
|
|
14862
|
+
mkdirSync17(dirname13(file), { recursive: true });
|
|
14266
14863
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
14267
14864
|
appendFileSync5(file, `[${ts}] ${line}
|
|
14268
14865
|
`);
|
|
@@ -14271,16 +14868,16 @@ function rebaseCircuitAllowed(num2) {
|
|
|
14271
14868
|
const state = statePath();
|
|
14272
14869
|
let body = "";
|
|
14273
14870
|
try {
|
|
14274
|
-
body =
|
|
14871
|
+
body = readFileSync26(state, "utf8");
|
|
14275
14872
|
} catch {
|
|
14276
14873
|
}
|
|
14277
14874
|
const verdict = rebaseCircuitVerdict(parseRebaseAttempts(body, num2), nowSec());
|
|
14278
14875
|
writeRebaseAttempts(state, num2, verdict.freshTimestamps);
|
|
14279
14876
|
if (!verdict.allowed) {
|
|
14280
14877
|
const file = alertPath();
|
|
14281
|
-
|
|
14878
|
+
mkdirSync17(dirname13(file), { recursive: true });
|
|
14282
14879
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
|
|
14283
|
-
|
|
14880
|
+
writeFileSync18(file, [
|
|
14284
14881
|
`# ALERT \u2014 PR rebase circuit breaker tripped`,
|
|
14285
14882
|
``,
|
|
14286
14883
|
`**Time**: ${stamp}`,
|
|
@@ -14334,13 +14931,13 @@ function upsertRebaseAttempts(stateBody, pr, value) {
|
|
|
14334
14931
|
`;
|
|
14335
14932
|
}
|
|
14336
14933
|
function writeRebaseAttempts(state, pr, timestamps) {
|
|
14337
|
-
|
|
14934
|
+
mkdirSync17(dirname13(state), { recursive: true });
|
|
14338
14935
|
let body = "";
|
|
14339
14936
|
try {
|
|
14340
|
-
body =
|
|
14937
|
+
body = readFileSync26(state, "utf8");
|
|
14341
14938
|
} catch {
|
|
14342
14939
|
}
|
|
14343
|
-
|
|
14940
|
+
writeFileSync18(state, upsertRebaseAttempts(body, pr, renderRebaseAttempts(timestamps)));
|
|
14344
14941
|
}
|
|
14345
14942
|
function bridgeBash(args) {
|
|
14346
14943
|
return new Promise((resolve3) => {
|
|
@@ -14427,19 +15024,19 @@ async function loopPrInboxCommand(_args, deps = realDeps2()) {
|
|
|
14427
15024
|
}
|
|
14428
15025
|
|
|
14429
15026
|
// packages/cli/dist/commands/loop-run-once.js
|
|
14430
|
-
import { appendFileSync as appendFileSync7, existsSync as
|
|
14431
|
-
import { dirname as dirname15, join as
|
|
15027
|
+
import { appendFileSync as appendFileSync7, existsSync as existsSync37, mkdirSync as mkdirSync19, readFileSync as readFileSync29, writeFileSync as writeFileSync20 } from "node:fs";
|
|
15028
|
+
import { dirname as dirname15, join as join34 } from "node:path";
|
|
14432
15029
|
|
|
14433
15030
|
// packages/cli/dist/runner/executor.js
|
|
14434
15031
|
import { execFile as execFile8 } from "node:child_process";
|
|
14435
|
-
import { appendFileSync as appendFileSync6, existsSync as
|
|
14436
|
-
import { dirname as dirname14, join as
|
|
15032
|
+
import { appendFileSync as appendFileSync6, existsSync as existsSync36, lstatSync as lstatSync2, mkdirSync as mkdirSync18, readFileSync as readFileSync28, rmSync as rmSync8, symlinkSync as symlinkSync4, unlinkSync, writeFileSync as writeFileSync19 } from "node:fs";
|
|
15033
|
+
import { dirname as dirname14, join as join33 } from "node:path";
|
|
14437
15034
|
import { promisify as promisify8 } from "node:util";
|
|
14438
15035
|
|
|
14439
15036
|
// packages/cli/dist/runner/peer-gate.js
|
|
14440
15037
|
import { execFile as execFile7 } from "node:child_process";
|
|
14441
|
-
import { existsSync as
|
|
14442
|
-
import { join as
|
|
15038
|
+
import { existsSync as existsSync34, readdirSync as readdirSync15 } from "node:fs";
|
|
15039
|
+
import { join as join31 } from "node:path";
|
|
14443
15040
|
import { promisify as promisify7 } from "node:util";
|
|
14444
15041
|
var execFileAsync7 = promisify7(execFile7);
|
|
14445
15042
|
var HIGH_RISK = [/^\.github\/workflows\//, /^packages\/infra\/src\/(git|github|process)\.ts$/];
|
|
@@ -14469,8 +15066,8 @@ async function cycleChangedFiles(worktreeCwd) {
|
|
|
14469
15066
|
}
|
|
14470
15067
|
}
|
|
14471
15068
|
function peerEvidencePresent(runtimeDir3, cycleId) {
|
|
14472
|
-
const dir =
|
|
14473
|
-
if (!
|
|
15069
|
+
const dir = join31(runtimeDir3, "peer");
|
|
15070
|
+
if (!existsSync34(dir))
|
|
14474
15071
|
return false;
|
|
14475
15072
|
try {
|
|
14476
15073
|
return readdirSync15(dir).some((f) => f.startsWith(`cycle-${cycleId}.`));
|
|
@@ -14497,24 +15094,18 @@ async function runPeerGate(worktreeCwd, runtimeDir3, cycleId, sinks) {
|
|
|
14497
15094
|
}
|
|
14498
15095
|
|
|
14499
15096
|
// packages/cli/dist/runner/attest-gate.js
|
|
14500
|
-
import { existsSync as
|
|
14501
|
-
import { join as
|
|
15097
|
+
import { existsSync as existsSync35, readFileSync as readFileSync27, statSync as statSync17 } from "node:fs";
|
|
15098
|
+
import { join as join32 } from "node:path";
|
|
14502
15099
|
function reportCandidates(worktreeCwd, storyId) {
|
|
14503
|
-
return [
|
|
14504
|
-
join31(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId)),
|
|
14505
|
-
join31(legacyArchiveDir(worktreeCwd, storyId), "latest", "report.html")
|
|
14506
|
-
];
|
|
15100
|
+
return [join32(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId))];
|
|
14507
15101
|
}
|
|
14508
15102
|
function acMapCandidates(worktreeCwd, storyId) {
|
|
14509
|
-
return [
|
|
14510
|
-
join31(cardArchiveDir(worktreeCwd, storyId), "ac-map.json"),
|
|
14511
|
-
join31(legacyArchiveDir(worktreeCwd, storyId), "ac-map.json")
|
|
14512
|
-
];
|
|
15103
|
+
return [join32(cardArchiveDir(worktreeCwd, storyId), "ac-map.json")];
|
|
14513
15104
|
}
|
|
14514
15105
|
function existingReport(worktreeCwd, storyId) {
|
|
14515
15106
|
for (const p of reportCandidates(worktreeCwd, storyId)) {
|
|
14516
15107
|
try {
|
|
14517
|
-
if (
|
|
15108
|
+
if (statSync17(p).isFile())
|
|
14518
15109
|
return p;
|
|
14519
15110
|
} catch {
|
|
14520
15111
|
}
|
|
@@ -14528,7 +15119,7 @@ function verificationReportFresh(worktreeCwd, storyId, sinceSec) {
|
|
|
14528
15119
|
if (p === null)
|
|
14529
15120
|
return false;
|
|
14530
15121
|
try {
|
|
14531
|
-
const st =
|
|
15122
|
+
const st = statSync17(p);
|
|
14532
15123
|
if (sinceSec === void 0)
|
|
14533
15124
|
return true;
|
|
14534
15125
|
return st.mtimeMs / 1e3 >= sinceSec;
|
|
@@ -14543,9 +15134,9 @@ function verificationReportHasContent(worktreeCwd, storyId) {
|
|
|
14543
15134
|
if (p === null)
|
|
14544
15135
|
return false;
|
|
14545
15136
|
try {
|
|
14546
|
-
const html =
|
|
15137
|
+
const html = readFileSync27(p, "utf8");
|
|
14547
15138
|
const hasAc = /<section class="ac[\s">]/.test(html);
|
|
14548
|
-
const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) =>
|
|
15139
|
+
const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) => existsSync35(m7));
|
|
14549
15140
|
return hasAc && hasMap;
|
|
14550
15141
|
} catch {
|
|
14551
15142
|
return false;
|
|
@@ -14553,10 +15144,10 @@ function verificationReportHasContent(worktreeCwd, storyId) {
|
|
|
14553
15144
|
}
|
|
14554
15145
|
function readAttestGateMode(repoCwd) {
|
|
14555
15146
|
try {
|
|
14556
|
-
const p =
|
|
14557
|
-
if (!
|
|
15147
|
+
const p = join32(repoCwd, ".roll", "policy.yaml");
|
|
15148
|
+
if (!existsSync35(p))
|
|
14558
15149
|
return "soft";
|
|
14559
|
-
return parsePolicy(
|
|
15150
|
+
return parsePolicy(readFileSync27(p, "utf8")).loopSafety.attestGate === "hard" ? "hard" : "soft";
|
|
14560
15151
|
} catch {
|
|
14561
15152
|
return "soft";
|
|
14562
15153
|
}
|
|
@@ -14664,9 +15255,9 @@ async function executeCommand(cmd, ports, ctx) {
|
|
|
14664
15255
|
// execute: spawn the agent (TCR commits happen inside the worktree). The
|
|
14665
15256
|
// exit code + timeout feed back as agent_exited; usage is captured for cost.
|
|
14666
15257
|
case "spawn_agent": {
|
|
14667
|
-
const livePath =
|
|
15258
|
+
const livePath = join33(dirname14(ports.paths.eventsPath), "live.log");
|
|
14668
15259
|
try {
|
|
14669
|
-
|
|
15260
|
+
writeFileSync19(livePath, `\u2500\u2500 cycle ${ctx.cycleId ?? "?"} \xB7 ${ctx.storyId ?? "?"} \xB7 agent ${cmd.agent} \u2500\u2500
|
|
14670
15261
|
`);
|
|
14671
15262
|
} catch {
|
|
14672
15263
|
}
|
|
@@ -14684,9 +15275,9 @@ async function executeCommand(cmd, ports, ctx) {
|
|
|
14684
15275
|
}
|
|
14685
15276
|
});
|
|
14686
15277
|
try {
|
|
14687
|
-
const logDir =
|
|
14688
|
-
|
|
14689
|
-
|
|
15278
|
+
const logDir = join33(dirname14(ports.paths.eventsPath), "cycle-logs");
|
|
15279
|
+
mkdirSync18(logDir, { recursive: true });
|
|
15280
|
+
writeFileSync19(join33(logDir, `${ctx.cycleId ?? "cycle"}.agent.log`), `# exit=${res.exitCode} timedOut=${res.timedOut}
|
|
14690
15281
|
--- stdout ---
|
|
14691
15282
|
${res.stdout}
|
|
14692
15283
|
--- stderr ---
|
|
@@ -14809,7 +15400,7 @@ ${res.stderr}
|
|
|
14809
15400
|
// _worktree_cleanup (tolerant). Side effect; no feedback (terminal path).
|
|
14810
15401
|
case "cleanup_worktree":
|
|
14811
15402
|
try {
|
|
14812
|
-
const dst =
|
|
15403
|
+
const dst = join33(ports.paths.worktreePath, ".roll");
|
|
14813
15404
|
if (lstatSync2(dst, { throwIfNoEntry: false })?.isSymbolicLink() === true)
|
|
14814
15405
|
unlinkSync(dst);
|
|
14815
15406
|
} catch {
|
|
@@ -14881,9 +15472,9 @@ function buildRunRow(cmd, ctx, nowSec2) {
|
|
|
14881
15472
|
}
|
|
14882
15473
|
function readRunsRows(runsPath) {
|
|
14883
15474
|
try {
|
|
14884
|
-
if (!
|
|
15475
|
+
if (!existsSync36(runsPath))
|
|
14885
15476
|
return [];
|
|
14886
|
-
return
|
|
15477
|
+
return readFileSync28(runsPath, "utf8").split("\n").filter((l) => l.trim() !== "").map((l) => {
|
|
14887
15478
|
try {
|
|
14888
15479
|
return JSON.parse(l);
|
|
14889
15480
|
} catch {
|
|
@@ -14906,15 +15497,15 @@ function sleep(ms) {
|
|
|
14906
15497
|
}
|
|
14907
15498
|
async function linkRollIntoWorktree(repoCwd, worktreePath) {
|
|
14908
15499
|
try {
|
|
14909
|
-
const src =
|
|
14910
|
-
const dst =
|
|
14911
|
-
if (!
|
|
15500
|
+
const src = join33(repoCwd, ".roll");
|
|
15501
|
+
const dst = join33(worktreePath, ".roll");
|
|
15502
|
+
if (!existsSync36(src))
|
|
14912
15503
|
return;
|
|
14913
15504
|
const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
|
|
14914
15505
|
if (dstStat) {
|
|
14915
15506
|
if (dstStat.isSymbolicLink())
|
|
14916
15507
|
return;
|
|
14917
|
-
const incompleteFossil =
|
|
15508
|
+
const incompleteFossil = existsSync36(join33(src, "backlog.md")) && !existsSync36(join33(dst, "backlog.md"));
|
|
14918
15509
|
if (!incompleteFossil)
|
|
14919
15510
|
return;
|
|
14920
15511
|
rmSync8(dst, { recursive: true, force: true });
|
|
@@ -14923,10 +15514,10 @@ async function linkRollIntoWorktree(repoCwd, worktreePath) {
|
|
|
14923
15514
|
const common = (await execFileAsync8("git", ["-C", repoCwd, "rev-parse", "--path-format=absolute", "--git-common-dir"])).stdout.trim();
|
|
14924
15515
|
if (common === "")
|
|
14925
15516
|
return;
|
|
14926
|
-
const exclude =
|
|
14927
|
-
const cur =
|
|
15517
|
+
const exclude = join33(common, "info", "exclude");
|
|
15518
|
+
const cur = existsSync36(exclude) ? readFileSync28(exclude, "utf8") : "";
|
|
14928
15519
|
if (!/^\.roll$/m.test(cur)) {
|
|
14929
|
-
|
|
15520
|
+
mkdirSync18(dirname14(exclude), { recursive: true });
|
|
14930
15521
|
appendFileSync6(exclude, `${cur === "" || cur.endsWith("\n") ? "" : "\n"}.roll
|
|
14931
15522
|
`, "utf8");
|
|
14932
15523
|
}
|
|
@@ -15015,10 +15606,10 @@ function nodePorts(opts) {
|
|
|
15015
15606
|
},
|
|
15016
15607
|
backlog: {
|
|
15017
15608
|
read(projectCwd) {
|
|
15018
|
-
const p =
|
|
15019
|
-
if (!
|
|
15609
|
+
const p = join33(projectCwd, ".roll", "backlog.md");
|
|
15610
|
+
if (!existsSync36(p))
|
|
15020
15611
|
return [];
|
|
15021
|
-
return parseBacklog(
|
|
15612
|
+
return parseBacklog(readFileSync28(p, "utf8"));
|
|
15022
15613
|
},
|
|
15023
15614
|
// FIX-198: the production binding was MISSING entirely (the optional
|
|
15024
15615
|
// chain made every In-Progress claim a silent no-op). ID-anchored mark
|
|
@@ -15026,8 +15617,8 @@ function nodePorts(opts) {
|
|
|
15026
15617
|
// never kill the cycle, the reconcile pass is the safety net.
|
|
15027
15618
|
markStatus(projectCwd, id, status2) {
|
|
15028
15619
|
try {
|
|
15029
|
-
const p =
|
|
15030
|
-
if (!
|
|
15620
|
+
const p = join33(projectCwd, ".roll", "backlog.md");
|
|
15621
|
+
if (!existsSync36(p))
|
|
15031
15622
|
return;
|
|
15032
15623
|
const store = new BacklogStore();
|
|
15033
15624
|
const snap = store.readBacklog(p);
|
|
@@ -15047,7 +15638,7 @@ function nodePorts(opts) {
|
|
|
15047
15638
|
};
|
|
15048
15639
|
}
|
|
15049
15640
|
function appendAlertLine(alertsPath, message) {
|
|
15050
|
-
|
|
15641
|
+
mkdirSync18(dirname14(alertsPath), { recursive: true });
|
|
15051
15642
|
appendFileSync6(alertsPath, `${message}
|
|
15052
15643
|
`, "utf8");
|
|
15053
15644
|
}
|
|
@@ -15224,6 +15815,7 @@ function describeCommand(cmd) {
|
|
|
15224
15815
|
|
|
15225
15816
|
// packages/cli/dist/commands/loop-run-once.js
|
|
15226
15817
|
import { spawn as spawn3 } from "node:child_process";
|
|
15818
|
+
import { lookup } from "node:dns/promises";
|
|
15227
15819
|
function announceReport(projectPath, slug, storyId, opener = (p) => {
|
|
15228
15820
|
try {
|
|
15229
15821
|
spawn3("open", [p], { stdio: "ignore", detached: true }).unref();
|
|
@@ -15232,13 +15824,13 @@ function announceReport(projectPath, slug, storyId, opener = (p) => {
|
|
|
15232
15824
|
}) {
|
|
15233
15825
|
if (storyId === "")
|
|
15234
15826
|
return null;
|
|
15235
|
-
const report =
|
|
15236
|
-
if (!
|
|
15827
|
+
const report = join34(cardArchiveDir(projectPath, storyId), "latest", reportFileName(storyId));
|
|
15828
|
+
if (!existsSync37(report))
|
|
15237
15829
|
return null;
|
|
15238
15830
|
process.stdout.write(`evidence: ${report}
|
|
15239
15831
|
\u9A8C\u6536\u62A5\u544A: ${report}
|
|
15240
15832
|
`);
|
|
15241
|
-
const muted =
|
|
15833
|
+
const muted = existsSync37(join34(projectPath, ".roll", "loop", `mute-${slug}`)) || existsSync37(join34(process.env["ROLL_SHARED_ROOT"] || join34(process.env["HOME"] ?? "", ".shared", "roll"), "loop", `mute-${slug}`));
|
|
15242
15834
|
if (!muted)
|
|
15243
15835
|
opener(report);
|
|
15244
15836
|
return report;
|
|
@@ -15255,7 +15847,7 @@ function cycleSignalTeardown(paths, cycleId, branch, sig, deps = {}) {
|
|
|
15255
15847
|
}
|
|
15256
15848
|
let owned = false;
|
|
15257
15849
|
try {
|
|
15258
|
-
owned =
|
|
15850
|
+
owned = existsSync37(paths.lockPath) && parseLock(readFileSync29(paths.lockPath, "utf8")).pid === pid;
|
|
15259
15851
|
} catch {
|
|
15260
15852
|
owned = false;
|
|
15261
15853
|
}
|
|
@@ -15300,27 +15892,27 @@ function makeCycleId(now = /* @__PURE__ */ new Date(), pid = process.pid) {
|
|
|
15300
15892
|
}
|
|
15301
15893
|
function runtimeDir2(projectPath) {
|
|
15302
15894
|
const env = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
|
|
15303
|
-
return env !== "" ? env :
|
|
15895
|
+
return env !== "" ? env : join34(projectPath, ".roll", "loop");
|
|
15304
15896
|
}
|
|
15305
15897
|
var PAUSE_THRESHOLD = 3;
|
|
15306
15898
|
function incrementConsecutiveFails(projectPath, slug, alertsPath, cycleId, storyId, terminal) {
|
|
15307
15899
|
const rt = runtimeDir2(projectPath);
|
|
15308
|
-
const counterFile =
|
|
15900
|
+
const counterFile = join34(rt, "consecutive-fails");
|
|
15309
15901
|
let count = 0;
|
|
15310
15902
|
try {
|
|
15311
|
-
if (
|
|
15312
|
-
count = parseInt(
|
|
15903
|
+
if (existsSync37(counterFile)) {
|
|
15904
|
+
count = parseInt(readFileSync29(counterFile, "utf8").trim(), 10) || 0;
|
|
15313
15905
|
}
|
|
15314
15906
|
} catch {
|
|
15315
15907
|
}
|
|
15316
15908
|
count += 1;
|
|
15317
15909
|
try {
|
|
15318
|
-
|
|
15910
|
+
writeFileSync20(counterFile, String(count), "utf8");
|
|
15319
15911
|
} catch {
|
|
15320
15912
|
}
|
|
15321
15913
|
if (count < PAUSE_THRESHOLD)
|
|
15322
15914
|
return;
|
|
15323
|
-
const pauseMarker =
|
|
15915
|
+
const pauseMarker = join34(projectPath, ".roll", "loop", `PAUSE-${slug}`);
|
|
15324
15916
|
const alertMsg = `# ALERT \u2014 loop auto-paused after ${count} consecutive failures
|
|
15325
15917
|
|
|
15326
15918
|
**Cycle**: ${cycleId}
|
|
@@ -15330,7 +15922,7 @@ function incrementConsecutiveFails(projectPath, slug, alertsPath, cycleId, story
|
|
|
15330
15922
|
Resolve the root cause, then: \`roll loop resume\`
|
|
15331
15923
|
`;
|
|
15332
15924
|
try {
|
|
15333
|
-
|
|
15925
|
+
writeFileSync20(pauseMarker, alertMsg, "utf8");
|
|
15334
15926
|
appendFileSync7(alertsPath, `${alertMsg}
|
|
15335
15927
|
`, "utf8");
|
|
15336
15928
|
} catch {
|
|
@@ -15342,7 +15934,7 @@ loop run-once: \u8FDE\u7EED ${count} \u6B21\u5931\u8D25\u540E\u81EA\u52A8\u6682\
|
|
|
15342
15934
|
function resetConsecutiveFails(projectPath) {
|
|
15343
15935
|
const rt = runtimeDir2(projectPath);
|
|
15344
15936
|
try {
|
|
15345
|
-
|
|
15937
|
+
writeFileSync20(join34(rt, "consecutive-fails"), "0", "utf8");
|
|
15346
15938
|
} catch {
|
|
15347
15939
|
}
|
|
15348
15940
|
}
|
|
@@ -15354,15 +15946,15 @@ function readSkillBody2(projectPath) {
|
|
|
15354
15946
|
}
|
|
15355
15947
|
function buildLoopRouteDeps(projectPath) {
|
|
15356
15948
|
function readSlot(slot) {
|
|
15357
|
-
const agentsYaml =
|
|
15949
|
+
const agentsYaml = join34(projectPath, ".roll", "agents.yaml");
|
|
15358
15950
|
try {
|
|
15359
|
-
return readSlotFromText(
|
|
15951
|
+
return readSlotFromText(readFileSync29(agentsYaml, "utf8"), slot);
|
|
15360
15952
|
} catch {
|
|
15361
15953
|
return void 0;
|
|
15362
15954
|
}
|
|
15363
15955
|
}
|
|
15364
15956
|
function firstInstalled() {
|
|
15365
|
-
const fromLocal = readField(
|
|
15957
|
+
const fromLocal = readField(join34(projectPath, ".roll", "local.yaml"), /^agent:/);
|
|
15366
15958
|
if (fromLocal !== void 0)
|
|
15367
15959
|
return fromLocal;
|
|
15368
15960
|
return firstInstalledAgent(realAgentEnv());
|
|
@@ -15371,7 +15963,7 @@ function buildLoopRouteDeps(projectPath) {
|
|
|
15371
15963
|
}
|
|
15372
15964
|
function readField(path, re) {
|
|
15373
15965
|
try {
|
|
15374
|
-
const text =
|
|
15966
|
+
const text = readFileSync29(path, "utf8");
|
|
15375
15967
|
for (const line of text.split("\n")) {
|
|
15376
15968
|
const m7 = line.match(re);
|
|
15377
15969
|
if (m7 !== null) {
|
|
@@ -15407,15 +15999,15 @@ async function loopRunOnceCommand(args) {
|
|
|
15407
15999
|
return 0;
|
|
15408
16000
|
}
|
|
15409
16001
|
const rt = runtimeDir2(id.path);
|
|
15410
|
-
const alertsPath =
|
|
15411
|
-
|
|
16002
|
+
const alertsPath = join34(rt, `ALERT-${id.slug}.md`);
|
|
16003
|
+
mkdirSync19(dirname15(alertsPath), { recursive: true });
|
|
15412
16004
|
const paths = {
|
|
15413
|
-
eventsPath:
|
|
15414
|
-
runsPath:
|
|
16005
|
+
eventsPath: join34(rt, "events.ndjson"),
|
|
16006
|
+
runsPath: join34(rt, "runs.jsonl"),
|
|
15415
16007
|
alertsPath,
|
|
15416
|
-
lockPath:
|
|
15417
|
-
heartbeatPath:
|
|
15418
|
-
worktreePath:
|
|
16008
|
+
lockPath: join34(rt, "inner.lock"),
|
|
16009
|
+
heartbeatPath: join34(rt, "heartbeat"),
|
|
16010
|
+
worktreePath: join34(rt, "worktrees", `cycle-${cycleId}`)
|
|
15419
16011
|
};
|
|
15420
16012
|
const skillBody = readSkillBody2(id.path);
|
|
15421
16013
|
if (skillBody === null) {
|
|
@@ -15463,24 +16055,43 @@ loop run-once: \u627E\u4E0D\u5230 roll-loop SKILL.md \u2014 \u62D2\u7EDD\u76F2\u
|
|
|
15463
16055
|
}
|
|
15464
16056
|
const isFail = result.terminal === "failed" || result.terminal === "blocked";
|
|
15465
16057
|
if (isFail) {
|
|
16058
|
+
if (await isOffline()) {
|
|
16059
|
+
process.stderr.write("loop run-once: network unreachable \u2014 degraded to local-only delivery (commits stay on the branch; push/PR catch up when back online)\nloop run-once: \u7F51\u7EDC\u4E0D\u53EF\u8FBE\u2014\u2014\u5DF2\u964D\u7EA7\u4E3A\u672C\u5730\u4EA4\u4ED8\uFF08\u63D0\u4EA4\u4FDD\u7559\u5728\u5206\u652F\u4E0A\uFF0C\u8054\u7F51\u540E push/PR \u81EA\u7136\u8865\u4E0A\uFF09\n");
|
|
16060
|
+
return 0;
|
|
16061
|
+
}
|
|
15466
16062
|
const storyId = (result.state?.ctx?.storyId ?? "").trim();
|
|
15467
16063
|
incrementConsecutiveFails(id.path, id.slug, alertsPath, cycleId, storyId, result.terminal ?? "unknown");
|
|
15468
16064
|
}
|
|
15469
16065
|
return isFail ? 1 : 0;
|
|
15470
16066
|
}
|
|
16067
|
+
async function isOffline(resolve3 = (h) => lookup(h)) {
|
|
16068
|
+
try {
|
|
16069
|
+
await Promise.race([
|
|
16070
|
+
resolve3("github.com"),
|
|
16071
|
+
new Promise((_, rej) => {
|
|
16072
|
+
const t2 = setTimeout(() => rej(new Error("dns timeout")), 1500);
|
|
16073
|
+
if (typeof t2 === "object")
|
|
16074
|
+
t2.unref();
|
|
16075
|
+
})
|
|
16076
|
+
]);
|
|
16077
|
+
return false;
|
|
16078
|
+
} catch {
|
|
16079
|
+
return true;
|
|
16080
|
+
}
|
|
16081
|
+
}
|
|
15471
16082
|
|
|
15472
16083
|
// packages/cli/dist/commands/loop-sched.js
|
|
15473
16084
|
import { createHash as createHash4 } from "node:crypto";
|
|
15474
16085
|
import { spawn as spawn4, spawnSync as spawnSync6 } from "node:child_process";
|
|
15475
|
-
import { existsSync as
|
|
16086
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync20, readFileSync as readFileSync30, rmSync as rmSync9, writeFileSync as writeFileSync21 } from "node:fs";
|
|
15476
16087
|
import { homedir as homedir15 } from "node:os";
|
|
15477
|
-
import { dirname as dirname16, join as
|
|
16088
|
+
import { dirname as dirname16, join as join35 } from "node:path";
|
|
15478
16089
|
function realDeps3() {
|
|
15479
16090
|
return {
|
|
15480
16091
|
identity: () => projectIdentity(),
|
|
15481
16092
|
uid: () => process.getuid?.() ?? 501,
|
|
15482
|
-
sharedRoot: () => process.env["ROLL_SHARED_ROOT"] ||
|
|
15483
|
-
launchdDir: () =>
|
|
16093
|
+
sharedRoot: () => process.env["ROLL_SHARED_ROOT"] || join35(homedir15(), ".shared", "roll"),
|
|
16094
|
+
launchdDir: () => join35(homedir15(), "Library", "LaunchAgents"),
|
|
15484
16095
|
launchd: { reinstall, uninstall, isLoaded },
|
|
15485
16096
|
execRunner: (runner) => new Promise((resolve3) => {
|
|
15486
16097
|
const child = spawn4("bash", [runner], {
|
|
@@ -15500,8 +16111,8 @@ function realDeps3() {
|
|
|
15500
16111
|
// The `loop now` inline observation: tail live.log while the cycle holds
|
|
15501
16112
|
// the inner lock; Ctrl-C stops the TAIL only (the cycle lives in tmux).
|
|
15502
16113
|
observe: (rt) => new Promise((resolve3) => {
|
|
15503
|
-
const lock =
|
|
15504
|
-
const tail = spawn4("tail", ["-n", "+1", "-F",
|
|
16114
|
+
const lock = join35(rt, "inner.lock");
|
|
16115
|
+
const tail = spawn4("tail", ["-n", "+1", "-F", join35(rt, "live.log")], { stdio: "inherit" });
|
|
15505
16116
|
let sawLock = false;
|
|
15506
16117
|
const t0 = Date.now();
|
|
15507
16118
|
const finish = () => {
|
|
@@ -15513,9 +16124,9 @@ function realDeps3() {
|
|
|
15513
16124
|
resolve3();
|
|
15514
16125
|
};
|
|
15515
16126
|
const timer = setInterval(() => {
|
|
15516
|
-
if (
|
|
16127
|
+
if (existsSync38(lock))
|
|
15517
16128
|
sawLock = true;
|
|
15518
|
-
const done = sawLock ? !
|
|
16129
|
+
const done = sawLock ? !existsSync38(lock) : Date.now() - t0 > 3e4;
|
|
15519
16130
|
if (done) {
|
|
15520
16131
|
clearInterval(timer);
|
|
15521
16132
|
finish();
|
|
@@ -15684,11 +16295,11 @@ function pathValue() {
|
|
|
15684
16295
|
].join(":");
|
|
15685
16296
|
}
|
|
15686
16297
|
function writeExecutable(path, content) {
|
|
15687
|
-
|
|
15688
|
-
|
|
16298
|
+
mkdirSync20(dirname16(path), { recursive: true });
|
|
16299
|
+
writeFileSync21(path, content, { mode: 493 });
|
|
15689
16300
|
}
|
|
15690
16301
|
function pauseMarkerPath(projectPath, slug) {
|
|
15691
|
-
return
|
|
16302
|
+
return join35(projectPath, ".roll", "loop", `PAUSE-${slug}`);
|
|
15692
16303
|
}
|
|
15693
16304
|
var LOOP_SERVICES = ["loop", "dream", "pr"];
|
|
15694
16305
|
async function mountService(deps, uid, label4, plist) {
|
|
@@ -15707,16 +16318,16 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15707
16318
|
const shared = deps.sharedRoot();
|
|
15708
16319
|
const ld = deps.launchdDir();
|
|
15709
16320
|
const uid = deps.uid();
|
|
15710
|
-
|
|
16321
|
+
mkdirSync20(ld, { recursive: true });
|
|
15711
16322
|
let period = 30;
|
|
15712
|
-
const localYaml =
|
|
15713
|
-
if (
|
|
16323
|
+
const localYaml = join35(id.path, ".roll", "local.yaml");
|
|
16324
|
+
if (existsSync38(localYaml)) {
|
|
15714
16325
|
try {
|
|
15715
|
-
period = parseLoopPeriodMinutes(
|
|
16326
|
+
period = parseLoopPeriodMinutes(readFileSync30(localYaml, "utf8"));
|
|
15716
16327
|
} catch {
|
|
15717
16328
|
}
|
|
15718
16329
|
}
|
|
15719
|
-
const loopRunner =
|
|
16330
|
+
const loopRunner = join35(shared, "loop", `run-${id.slug}.sh`);
|
|
15720
16331
|
const rollBinOverride = (process.env["ROLL_RUNNER_ROLL_BIN"] ?? "").trim();
|
|
15721
16332
|
writeExecutable(loopRunner, buildLoopRunnerScript({
|
|
15722
16333
|
projectPath: id.path,
|
|
@@ -15727,7 +16338,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15727
16338
|
}));
|
|
15728
16339
|
const loopLabel = launchdLabel("loop", id.slug);
|
|
15729
16340
|
const loopPlist = launchdPlistPath("loop", id.slug, ld);
|
|
15730
|
-
|
|
16341
|
+
writeFileSync21(loopPlist, plistContent({
|
|
15731
16342
|
label: loopLabel,
|
|
15732
16343
|
runnerScript: loopRunner,
|
|
15733
16344
|
projectPath: id.path,
|
|
@@ -15735,14 +16346,14 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15735
16346
|
schedule: { kind: "interval", periodMinutes: period }
|
|
15736
16347
|
}));
|
|
15737
16348
|
const loopMount = await mountService(deps, uid, loopLabel, loopPlist);
|
|
15738
|
-
const prRunner =
|
|
16349
|
+
const prRunner = join35(shared, "pr", `run-${id.slug}.sh`);
|
|
15739
16350
|
writeExecutable(prRunner, buildPrRunnerScript({
|
|
15740
16351
|
projectPath: id.path,
|
|
15741
16352
|
...rollBinOverride !== "" ? { rollBin: rollBinOverride } : {}
|
|
15742
16353
|
}));
|
|
15743
16354
|
const prLabel = launchdLabel("pr", id.slug);
|
|
15744
16355
|
const prPlist = launchdPlistPath("pr", id.slug, ld);
|
|
15745
|
-
|
|
16356
|
+
writeFileSync21(prPlist, plistContent({
|
|
15746
16357
|
label: prLabel,
|
|
15747
16358
|
runnerScript: prRunner,
|
|
15748
16359
|
projectPath: id.path,
|
|
@@ -15751,7 +16362,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15751
16362
|
}));
|
|
15752
16363
|
const prMount = await mountService(deps, uid, prLabel, prPlist);
|
|
15753
16364
|
const dream = dreamScheduleFor(id.path);
|
|
15754
|
-
const dreamRunner =
|
|
16365
|
+
const dreamRunner = join35(shared, "dream", `run-${id.slug}.sh`);
|
|
15755
16366
|
writeExecutable(dreamRunner, buildDreamRunnerScript({
|
|
15756
16367
|
projectPath: id.path,
|
|
15757
16368
|
slug: id.slug,
|
|
@@ -15759,7 +16370,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15759
16370
|
}));
|
|
15760
16371
|
const dreamLabel = launchdLabel("dream", id.slug);
|
|
15761
16372
|
const dreamPlist = launchdPlistPath("dream", id.slug, ld);
|
|
15762
|
-
|
|
16373
|
+
writeFileSync21(dreamPlist, plistContent({
|
|
15763
16374
|
label: dreamLabel,
|
|
15764
16375
|
runnerScript: dreamRunner,
|
|
15765
16376
|
projectPath: id.path,
|
|
@@ -15811,10 +16422,10 @@ Loop \u5DF2\u505C\u7528(loop/dream/pr \u5747\u5DF2\u5378\u8F7D)
|
|
|
15811
16422
|
async function loopPauseCommand(_args, deps = realDeps3()) {
|
|
15812
16423
|
const id = await deps.identity();
|
|
15813
16424
|
const marker = pauseMarkerPath(id.path, id.slug);
|
|
15814
|
-
|
|
15815
|
-
const already =
|
|
16425
|
+
mkdirSync20(dirname16(marker), { recursive: true });
|
|
16426
|
+
const already = existsSync38(marker);
|
|
15816
16427
|
if (!already)
|
|
15817
|
-
|
|
16428
|
+
writeFileSync21(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
15818
16429
|
`);
|
|
15819
16430
|
process.stdout.write(already ? `Loop already paused
|
|
15820
16431
|
Loop \u5DF2\u5904\u4E8E\u6682\u505C
|
|
@@ -15826,7 +16437,7 @@ Loop \u5DF2\u6682\u505C \u2014 \u540E\u7EED\u6392\u7A0B\u5468\u671F\u5C06\u8DF3\
|
|
|
15826
16437
|
async function loopResumeCommand(_args, deps = realDeps3()) {
|
|
15827
16438
|
const id = await deps.identity();
|
|
15828
16439
|
const marker = pauseMarkerPath(id.path, id.slug);
|
|
15829
|
-
const existed =
|
|
16440
|
+
const existed = existsSync38(marker);
|
|
15830
16441
|
rmSync9(marker, { force: true });
|
|
15831
16442
|
process.stdout.write(existed ? `Loop resumed \u2014 scheduling active again
|
|
15832
16443
|
Loop \u5DF2\u6062\u590D \u2014 \u6392\u7A0B\u91CD\u65B0\u751F\u6548
|
|
@@ -15844,16 +16455,16 @@ function isLegacyRunner(text) {
|
|
|
15844
16455
|
}
|
|
15845
16456
|
async function loopNowCommand(_args, deps = realDeps3()) {
|
|
15846
16457
|
const id = await deps.identity();
|
|
15847
|
-
const runner =
|
|
16458
|
+
const runner = join35(deps.sharedRoot(), "loop", `run-${id.slug}.sh`);
|
|
15848
16459
|
let legacy = false;
|
|
15849
|
-
if (
|
|
16460
|
+
if (existsSync38(runner)) {
|
|
15850
16461
|
try {
|
|
15851
|
-
legacy = isLegacyRunner(
|
|
16462
|
+
legacy = isLegacyRunner(readFileSync30(runner, "utf8"));
|
|
15852
16463
|
} catch {
|
|
15853
16464
|
legacy = true;
|
|
15854
16465
|
}
|
|
15855
16466
|
}
|
|
15856
|
-
if (!
|
|
16467
|
+
if (!existsSync38(runner) || legacy) {
|
|
15857
16468
|
process.stdout.write(legacy ? `Legacy v2 runner detected \u2014 regenerating templates (FIX-197)
|
|
15858
16469
|
\u68C0\u6D4B\u5230 v2 \u65E7\u7248 runner \u2014 \u6B63\u5728\u518D\u751F\u6210\u6A21\u677F\uFF08FIX-197\uFF09
|
|
15859
16470
|
` : `No runner yet \u2014 generating templates
|
|
@@ -15881,7 +16492,7 @@ live transcript below \u2014 Ctrl-C stops watching, never the cycle
|
|
|
15881
16492
|
}
|
|
15882
16493
|
const rc = await exec(runner);
|
|
15883
16494
|
if (rc === 0 && useTmux && deps.observe !== void 0) {
|
|
15884
|
-
await deps.observe(
|
|
16495
|
+
await deps.observe(join35(id.path, ".roll", "loop"));
|
|
15885
16496
|
process.stdout.write(`
|
|
15886
16497
|
cycle finished \u2014 logs: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
15887
16498
|
\u5468\u671F\u7ED3\u675F \u2014 \u65E5\u5FD7: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
@@ -15892,7 +16503,7 @@ cycle finished \u2014 logs: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
|
15892
16503
|
|
|
15893
16504
|
// packages/cli/dist/commands/migrate.js
|
|
15894
16505
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
15895
|
-
import { existsSync as
|
|
16506
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync21 } from "node:fs";
|
|
15896
16507
|
import { dirname as dirname17 } from "node:path";
|
|
15897
16508
|
function colors() {
|
|
15898
16509
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
@@ -16013,8 +16624,8 @@ function migrateExecute(activeMoves) {
|
|
|
16013
16624
|
const src = srcOf(move);
|
|
16014
16625
|
const tgt = tgtOf(move);
|
|
16015
16626
|
const targetDir = dirname17(tgt);
|
|
16016
|
-
if (!
|
|
16017
|
-
|
|
16627
|
+
if (!existsSync39(targetDir))
|
|
16628
|
+
mkdirSync21(targetDir, { recursive: true });
|
|
16018
16629
|
const r = git3(["mv", src, tgt]);
|
|
16019
16630
|
if (r.status !== 0) {
|
|
16020
16631
|
err11(`git mv failed: ${src} \u2192 ${tgt}`);
|
|
@@ -16023,7 +16634,7 @@ function migrateExecute(activeMoves) {
|
|
|
16023
16634
|
}
|
|
16024
16635
|
moved += 1;
|
|
16025
16636
|
}
|
|
16026
|
-
if (
|
|
16637
|
+
if (existsSync39("docs")) {
|
|
16027
16638
|
spawnSync7("find", ["docs", "-type", "d", "-empty", "-delete"], { stdio: "ignore" });
|
|
16028
16639
|
}
|
|
16029
16640
|
git3([
|
|
@@ -16064,10 +16675,10 @@ function migrateCommand(args) {
|
|
|
16064
16675
|
const moves = buildMoves();
|
|
16065
16676
|
let hasNew = false;
|
|
16066
16677
|
let hasOld = false;
|
|
16067
|
-
if (
|
|
16678
|
+
if (existsSync39(".roll"))
|
|
16068
16679
|
hasNew = true;
|
|
16069
16680
|
for (const move of moves) {
|
|
16070
|
-
if (
|
|
16681
|
+
if (existsSync39(srcOf(move))) {
|
|
16071
16682
|
hasOld = true;
|
|
16072
16683
|
break;
|
|
16073
16684
|
}
|
|
@@ -16079,7 +16690,7 @@ function migrateCommand(args) {
|
|
|
16079
16690
|
for (const move of moves) {
|
|
16080
16691
|
const src = srcOf(move);
|
|
16081
16692
|
const tgt = tgtOf(move);
|
|
16082
|
-
if (
|
|
16693
|
+
if (existsSync39(src) && existsSync39(tgt)) {
|
|
16083
16694
|
process.stderr.write(` - ${src} AND ${tgt} both exist
|
|
16084
16695
|
`);
|
|
16085
16696
|
}
|
|
@@ -16096,7 +16707,7 @@ function migrateCommand(args) {
|
|
|
16096
16707
|
info3(m2("migrate.no_old_structure_detected_nothing_to"));
|
|
16097
16708
|
return 0;
|
|
16098
16709
|
}
|
|
16099
|
-
const activeMoves = moves.filter((move) =>
|
|
16710
|
+
const activeMoves = moves.filter((move) => existsSync39(srcOf(move)));
|
|
16100
16711
|
if (activeMoves.length === 0) {
|
|
16101
16712
|
warn4(m2("migrate.old_structure_markers_found_but_no"));
|
|
16102
16713
|
return 0;
|
|
@@ -16113,11 +16724,11 @@ function migrateCommand(args) {
|
|
|
16113
16724
|
}
|
|
16114
16725
|
|
|
16115
16726
|
// packages/cli/dist/commands/migrate-features.js
|
|
16116
|
-
import { existsSync as
|
|
16117
|
-
import { join as
|
|
16727
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync22, readFileSync as readFileSync31, readlinkSync as readlinkSync3, statSync as statSync18, writeFileSync as writeFileSync22 } from "node:fs";
|
|
16728
|
+
import { join as join36 } from "node:path";
|
|
16118
16729
|
function specFrontmatter(specFile, storyId) {
|
|
16119
16730
|
try {
|
|
16120
|
-
const lines =
|
|
16731
|
+
const lines = readFileSync31(specFile, "utf8").split("\n").slice(0, 12);
|
|
16121
16732
|
const pick = (key) => {
|
|
16122
16733
|
const row2 = lines.find((l) => l.startsWith(`${key}: `));
|
|
16123
16734
|
return row2?.slice(key.length + 2).trim();
|
|
@@ -16137,24 +16748,24 @@ function specFrontmatter(specFile, storyId) {
|
|
|
16137
16748
|
function migrateFeaturesCommand(args) {
|
|
16138
16749
|
const refresh = args.includes("--refresh-html");
|
|
16139
16750
|
const cwd = process.cwd();
|
|
16140
|
-
if (!
|
|
16751
|
+
if (!existsSync40(join36(cwd, ".roll", "index.json"))) {
|
|
16141
16752
|
process.stderr.write("migrate-features: .roll/index.json not found\n");
|
|
16142
16753
|
return 1;
|
|
16143
16754
|
}
|
|
16144
16755
|
const stories = readIndex(cwd);
|
|
16145
|
-
const root =
|
|
16756
|
+
const root = join36(cwd, ".roll", "features");
|
|
16146
16757
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
16147
16758
|
let specCreated = 0;
|
|
16148
16759
|
let htmlWritten = 0;
|
|
16149
16760
|
let skipped = 0;
|
|
16150
16761
|
for (const [storyId, epic] of Object.entries(stories)) {
|
|
16151
|
-
const storyDir =
|
|
16152
|
-
const specFile =
|
|
16153
|
-
if (!
|
|
16154
|
-
const mdFile =
|
|
16155
|
-
if (
|
|
16762
|
+
const storyDir = join36(root, epic, storyId);
|
|
16763
|
+
const specFile = join36(storyDir, "spec.md");
|
|
16764
|
+
if (!existsSync40(storyDir)) {
|
|
16765
|
+
const mdFile = join36(root, epic, `${storyId}.md`);
|
|
16766
|
+
if (existsSync40(mdFile)) {
|
|
16156
16767
|
try {
|
|
16157
|
-
|
|
16768
|
+
mkdirSync22(storyDir, { recursive: true });
|
|
16158
16769
|
} catch {
|
|
16159
16770
|
skipped++;
|
|
16160
16771
|
continue;
|
|
@@ -16164,8 +16775,8 @@ function migrateFeaturesCommand(args) {
|
|
|
16164
16775
|
continue;
|
|
16165
16776
|
}
|
|
16166
16777
|
}
|
|
16167
|
-
if (!
|
|
16168
|
-
|
|
16778
|
+
if (!existsSync40(specFile)) {
|
|
16779
|
+
writeFileSync22(specFile, renderSpecMd({
|
|
16169
16780
|
id: storyId,
|
|
16170
16781
|
epic,
|
|
16171
16782
|
created: today,
|
|
@@ -16173,25 +16784,25 @@ function migrateFeaturesCommand(args) {
|
|
|
16173
16784
|
}), "utf8");
|
|
16174
16785
|
specCreated++;
|
|
16175
16786
|
}
|
|
16176
|
-
const htmlFile =
|
|
16177
|
-
if (!
|
|
16787
|
+
const htmlFile = join36(storyDir, "index.html");
|
|
16788
|
+
if (!existsSync40(htmlFile) || refresh) {
|
|
16178
16789
|
const fm = specFrontmatter(specFile, storyId);
|
|
16179
16790
|
const card = { id: storyId, epic, created: fm.created ?? today, ...fm.title !== void 0 ? { title: fm.title } : {} };
|
|
16180
16791
|
let html = renderStoryPage(card);
|
|
16181
|
-
const latest =
|
|
16182
|
-
const report =
|
|
16183
|
-
if (
|
|
16792
|
+
const latest = join36(storyDir, "latest");
|
|
16793
|
+
const report = join36(latest, reportFileName(storyId));
|
|
16794
|
+
if (existsSync40(report)) {
|
|
16184
16795
|
let runRel = "latest";
|
|
16185
16796
|
try {
|
|
16186
16797
|
runRel = readlinkSync3(latest);
|
|
16187
16798
|
} catch {
|
|
16188
16799
|
}
|
|
16189
|
-
const deliveredOn =
|
|
16800
|
+
const deliveredOn = statSync18(report).mtime.toISOString().slice(0, 10);
|
|
16190
16801
|
html = markPhaseDone(html, "delivery", `<p><a href="${runRel}/${reportFileName(storyId)}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a></p>
|
|
16191
16802
|
<p class="muted">${bi("Delivered", "\u4EA4\u4ED8\u4E8E")} ${deliveredOn}</p>
|
|
16192
16803
|
`);
|
|
16193
16804
|
}
|
|
16194
|
-
|
|
16805
|
+
writeFileSync22(htmlFile, html, "utf8");
|
|
16195
16806
|
htmlWritten++;
|
|
16196
16807
|
}
|
|
16197
16808
|
}
|
|
@@ -16202,9 +16813,9 @@ function migrateFeaturesCommand(args) {
|
|
|
16202
16813
|
|
|
16203
16814
|
// packages/cli/dist/commands/offboard.js
|
|
16204
16815
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
16205
|
-
import { existsSync as
|
|
16816
|
+
import { existsSync as existsSync41, readFileSync as readFileSync32, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync23 } from "node:fs";
|
|
16206
16817
|
import { homedir as homedir16 } from "node:os";
|
|
16207
|
-
import { join as
|
|
16818
|
+
import { join as join37, resolve as resolve2 } from "node:path";
|
|
16208
16819
|
function pal3() {
|
|
16209
16820
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
16210
16821
|
return noColor2 ? { RED: "", GREEN: "", YELLOW: "", CYAN: "", BOLD: "", NC: "" } : {
|
|
@@ -16242,7 +16853,7 @@ function m3(key, ...args) {
|
|
|
16242
16853
|
return t(v2Catalog, msgLang9(), key, ...args);
|
|
16243
16854
|
}
|
|
16244
16855
|
function changesetPath(projectDir) {
|
|
16245
|
-
return
|
|
16856
|
+
return join37(projectDir, ".roll", "onboard-changeset.yaml");
|
|
16246
16857
|
}
|
|
16247
16858
|
function loopInCycle() {
|
|
16248
16859
|
return (process.env["ROLL_LOOP_AGENT"] ?? "") !== "" || (process.env["ROLL_CYCLE_LOG_RAW"] ?? "") !== "";
|
|
@@ -16252,7 +16863,7 @@ function runLaunchctl(args) {
|
|
|
16252
16863
|
if (args[0] !== void 0 && readOnly.has(args[0])) {
|
|
16253
16864
|
return spawnSync8("launchctl", args, { stdio: "inherit" }).status ?? 1;
|
|
16254
16865
|
}
|
|
16255
|
-
const canonical =
|
|
16866
|
+
const canonical = join37(homedir16(), "Library", "LaunchAgents");
|
|
16256
16867
|
const launchdDir = process.env["_LAUNCHD_DIR"] ?? canonical;
|
|
16257
16868
|
if (launchdDir !== canonical)
|
|
16258
16869
|
return 0;
|
|
@@ -16316,7 +16927,7 @@ function offboardCommand(args) {
|
|
|
16316
16927
|
projectDir = process.cwd();
|
|
16317
16928
|
}
|
|
16318
16929
|
const changeset = changesetPath(projectDir);
|
|
16319
|
-
if (!
|
|
16930
|
+
if (!existsSync41(changeset)) {
|
|
16320
16931
|
err12(m3("offboard.no_changeset_en"));
|
|
16321
16932
|
err12(m3("offboard.no_changeset_zh"));
|
|
16322
16933
|
process.stderr.write("\n");
|
|
@@ -16330,7 +16941,7 @@ function offboardCommand(args) {
|
|
|
16330
16941
|
`);
|
|
16331
16942
|
return 1;
|
|
16332
16943
|
}
|
|
16333
|
-
const parsed = parseChangeset(
|
|
16944
|
+
const parsed = parseChangeset(readFileSync32(changeset, "utf8"));
|
|
16334
16945
|
if (!parsed.ok) {
|
|
16335
16946
|
err12(m3("offboard.failed_to_parse_changeset"));
|
|
16336
16947
|
return 1;
|
|
@@ -16406,8 +17017,8 @@ function offboardCommand(args) {
|
|
|
16406
17017
|
process.stdout.write(m3("offboard.applying_offboard") + "\n");
|
|
16407
17018
|
for (const item of files) {
|
|
16408
17019
|
try {
|
|
16409
|
-
rmSync10(
|
|
16410
|
-
if (!
|
|
17020
|
+
rmSync10(join37(projectDir, item), { force: true });
|
|
17021
|
+
if (!existsSync41(join37(projectDir, item)))
|
|
16411
17022
|
process.stdout.write(` removed file ${item}
|
|
16412
17023
|
`);
|
|
16413
17024
|
} catch {
|
|
@@ -16415,26 +17026,26 @@ function offboardCommand(args) {
|
|
|
16415
17026
|
}
|
|
16416
17027
|
for (const item of dirs) {
|
|
16417
17028
|
try {
|
|
16418
|
-
rmSync10(
|
|
17029
|
+
rmSync10(join37(projectDir, item), { recursive: true, force: true });
|
|
16419
17030
|
process.stdout.write(` removed dir ${item}
|
|
16420
17031
|
`);
|
|
16421
17032
|
} catch {
|
|
16422
17033
|
}
|
|
16423
17034
|
}
|
|
16424
17035
|
for (const item of giEntries) {
|
|
16425
|
-
const gi =
|
|
16426
|
-
if (
|
|
16427
|
-
const lines =
|
|
17036
|
+
const gi = join37(projectDir, ".gitignore");
|
|
17037
|
+
if (existsSync41(gi)) {
|
|
17038
|
+
const lines = readFileSync32(gi, "utf8").split("\n");
|
|
16428
17039
|
if (lines.includes(item)) {
|
|
16429
17040
|
const kept = lines.filter((l) => l !== item);
|
|
16430
|
-
|
|
17041
|
+
writeFileSync23(gi, kept.join("\n"));
|
|
16431
17042
|
process.stdout.write(` .gitignore - ${item}
|
|
16432
17043
|
`);
|
|
16433
17044
|
}
|
|
16434
17045
|
}
|
|
16435
17046
|
}
|
|
16436
17047
|
for (const item of plists) {
|
|
16437
|
-
const plistPath =
|
|
17048
|
+
const plistPath = join37(homedir16(), "Library", "LaunchAgents", item);
|
|
16438
17049
|
const r = runLaunchctl(["unload", "-w", plistPath]);
|
|
16439
17050
|
if (r === 0)
|
|
16440
17051
|
process.stdout.write(` unloaded ${item}
|
|
@@ -16447,16 +17058,16 @@ function offboardCommand(args) {
|
|
|
16447
17058
|
}
|
|
16448
17059
|
|
|
16449
17060
|
// packages/cli/dist/commands/prices.js
|
|
16450
|
-
import { existsSync as
|
|
16451
|
-
import { join as
|
|
17061
|
+
import { existsSync as existsSync42, readdirSync as readdirSync16, readFileSync as readFileSync33 } from "node:fs";
|
|
17062
|
+
import { join as join38 } from "node:path";
|
|
16452
17063
|
function loadSnapshots() {
|
|
16453
|
-
const dir =
|
|
16454
|
-
if (!
|
|
17064
|
+
const dir = join38(repoRoot(), "lib", "prices");
|
|
17065
|
+
if (!existsSync42(dir)) {
|
|
16455
17066
|
throw new Error(`no price snapshots found in ${dir}; run \`roll prices refresh\``);
|
|
16456
17067
|
}
|
|
16457
17068
|
const files = readdirSync16(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
|
|
16458
17069
|
return files.map((name) => {
|
|
16459
|
-
const data = JSON.parse(
|
|
17070
|
+
const data = JSON.parse(readFileSync33(join38(dir, name), "utf8"));
|
|
16460
17071
|
for (const key of ["version", "effective_at", "source_url", "prices"]) {
|
|
16461
17072
|
if (data[key] === void 0)
|
|
16462
17073
|
throw new Error(`snapshot ${name} missing required key ${key}`);
|
|
@@ -16549,8 +17160,8 @@ function pricesCommand(args) {
|
|
|
16549
17160
|
}
|
|
16550
17161
|
|
|
16551
17162
|
// packages/cli/dist/commands/release.js
|
|
16552
|
-
import { existsSync as
|
|
16553
|
-
import { join as
|
|
17163
|
+
import { existsSync as existsSync43, readFileSync as readFileSync34 } from "node:fs";
|
|
17164
|
+
import { join as join39 } from "node:path";
|
|
16554
17165
|
function label3(lang4, key, ...args) {
|
|
16555
17166
|
if (v3Catalog[key] !== void 0)
|
|
16556
17167
|
return t(v3Catalog, lang4, key, ...args);
|
|
@@ -16561,25 +17172,34 @@ function dateOf(d) {
|
|
|
16561
17172
|
}
|
|
16562
17173
|
function currentVersion(cwd) {
|
|
16563
17174
|
try {
|
|
16564
|
-
const pkg = JSON.parse(
|
|
17175
|
+
const pkg = JSON.parse(readFileSync34(join39(cwd, "package.json"), "utf8"));
|
|
16565
17176
|
return typeof pkg.version === "string" ? pkg.version : "";
|
|
16566
17177
|
} catch {
|
|
16567
17178
|
return "";
|
|
16568
17179
|
}
|
|
16569
17180
|
}
|
|
16570
|
-
function changelogReady(cwd) {
|
|
16571
|
-
const path =
|
|
16572
|
-
if (!
|
|
17181
|
+
function changelogReady(cwd, current) {
|
|
17182
|
+
const path = join39(cwd, "CHANGELOG.md");
|
|
17183
|
+
if (!existsSync43(path))
|
|
17184
|
+
return false;
|
|
17185
|
+
const text = readFileSync34(path, "utf8");
|
|
17186
|
+
const sectionAfter = (idx, headingLen) => {
|
|
17187
|
+
let section2 = text.slice(idx + headingLen);
|
|
17188
|
+
const nextHeading = section2.search(/\n## /);
|
|
17189
|
+
if (nextHeading !== -1)
|
|
17190
|
+
section2 = section2.slice(0, nextHeading);
|
|
17191
|
+
return section2;
|
|
17192
|
+
};
|
|
17193
|
+
const hasBullet = (s) => /^\s*-\s+\S/m.test(s);
|
|
17194
|
+
const unreleased = text.indexOf("## Unreleased");
|
|
17195
|
+
if (unreleased !== -1 && hasBullet(sectionAfter(unreleased, "## Unreleased".length)))
|
|
17196
|
+
return true;
|
|
17197
|
+
const m7 = /^## v(\d+\.\d+\.\d+)\b.*$/m.exec(text);
|
|
17198
|
+
if (m7 === null)
|
|
16573
17199
|
return false;
|
|
16574
|
-
|
|
16575
|
-
const idx = text.indexOf("## Unreleased");
|
|
16576
|
-
if (idx === -1)
|
|
17200
|
+
if (m7[1] === current)
|
|
16577
17201
|
return false;
|
|
16578
|
-
|
|
16579
|
-
const nextHeading = section.search(/\n## /);
|
|
16580
|
-
if (nextHeading !== -1)
|
|
16581
|
-
section = section.slice(0, nextHeading);
|
|
16582
|
-
return /^\s*-\s+\S/m.test(section);
|
|
17202
|
+
return hasBullet(sectionAfter(m7.index, m7[0].length));
|
|
16583
17203
|
}
|
|
16584
17204
|
function releaseCommand(args, now) {
|
|
16585
17205
|
const noColor2 = args.includes("--no-color") || !process.stdout.isTTY || (process.env["NO_COLOR"] ?? "") !== "";
|
|
@@ -16601,7 +17221,7 @@ function releaseCommand(args, now) {
|
|
|
16601
17221
|
`);
|
|
16602
17222
|
return 1;
|
|
16603
17223
|
}
|
|
16604
|
-
const ready = changelogReady(cwd);
|
|
17224
|
+
const ready = changelogReady(cwd, cur);
|
|
16605
17225
|
const plan = planRelease({
|
|
16606
17226
|
currentVersion: cur,
|
|
16607
17227
|
date: now ?? dateOf(/* @__PURE__ */ new Date()),
|
|
@@ -16638,8 +17258,8 @@ function releaseCommand(args, now) {
|
|
|
16638
17258
|
|
|
16639
17259
|
// packages/cli/dist/commands/setup.js
|
|
16640
17260
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
16641
|
-
import { existsSync as
|
|
16642
|
-
import { join as
|
|
17261
|
+
import { existsSync as existsSync44, lstatSync as lstatSync3, mkdirSync as mkdirSync23, readFileSync as readFileSync35, readdirSync as readdirSync17, readlinkSync as readlinkSync4, statSync as statSync19 } from "node:fs";
|
|
17262
|
+
import { join as join40 } from "node:path";
|
|
16643
17263
|
function err13(line) {
|
|
16644
17264
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
16645
17265
|
const RED = noColor2 ? "" : "\x1B[0;31m";
|
|
@@ -16664,7 +17284,7 @@ function setupSnapshot(watch) {
|
|
|
16664
17284
|
continue;
|
|
16665
17285
|
let isDir4 = false;
|
|
16666
17286
|
try {
|
|
16667
|
-
isDir4 =
|
|
17287
|
+
isDir4 = statSync19(d).isDirectory();
|
|
16668
17288
|
} catch {
|
|
16669
17289
|
isDir4 = false;
|
|
16670
17290
|
}
|
|
@@ -16682,7 +17302,7 @@ function walk(dir, lines) {
|
|
|
16682
17302
|
return;
|
|
16683
17303
|
}
|
|
16684
17304
|
for (const name of names) {
|
|
16685
|
-
const p =
|
|
17305
|
+
const p = join40(dir, name);
|
|
16686
17306
|
let isLink = false;
|
|
16687
17307
|
try {
|
|
16688
17308
|
isLink = lstatSync3(p).isSymbolicLink();
|
|
@@ -16698,7 +17318,7 @@ function walk(dir, lines) {
|
|
|
16698
17318
|
}
|
|
16699
17319
|
let st;
|
|
16700
17320
|
try {
|
|
16701
|
-
st =
|
|
17321
|
+
st = statSync19(p);
|
|
16702
17322
|
} catch {
|
|
16703
17323
|
continue;
|
|
16704
17324
|
}
|
|
@@ -16712,7 +17332,7 @@ function walk(dir, lines) {
|
|
|
16712
17332
|
}
|
|
16713
17333
|
function fileFingerprint(p) {
|
|
16714
17334
|
try {
|
|
16715
|
-
const buf =
|
|
17335
|
+
const buf = readFileSync35(p);
|
|
16716
17336
|
let sum = 0;
|
|
16717
17337
|
for (let i = 0; i < buf.length; i++)
|
|
16718
17338
|
sum = sum * 31 + (buf[i] ?? 0) >>> 0;
|
|
@@ -16742,18 +17362,18 @@ function ensureHooksPath(repoPath) {
|
|
|
16742
17362
|
}
|
|
16743
17363
|
}
|
|
16744
17364
|
function peerEnsureStateDir() {
|
|
16745
|
-
const base =
|
|
16746
|
-
|
|
16747
|
-
|
|
17365
|
+
const base = join40(rollHome(), ".peer-state");
|
|
17366
|
+
mkdirSync23(base, { recursive: true });
|
|
17367
|
+
mkdirSync23(join40(base, "logs"), { recursive: true });
|
|
16748
17368
|
}
|
|
16749
17369
|
function tmuxPresent() {
|
|
16750
17370
|
return onPath("tmux");
|
|
16751
17371
|
}
|
|
16752
17372
|
function submoduleGuard() {
|
|
16753
17373
|
const pkg = rollPkgDir();
|
|
16754
|
-
const skills =
|
|
16755
|
-
const hasGit =
|
|
16756
|
-
const hasMods =
|
|
17374
|
+
const skills = join40(pkg, "skills");
|
|
17375
|
+
const hasGit = existsSync44(join40(pkg, ".git"));
|
|
17376
|
+
const hasMods = existsSync44(join40(pkg, ".gitmodules"));
|
|
16757
17377
|
let empty = true;
|
|
16758
17378
|
try {
|
|
16759
17379
|
empty = readdirSync17(skills).length === 0;
|
|
@@ -16846,7 +17466,7 @@ function setupCommand(args) {
|
|
|
16846
17466
|
return 1;
|
|
16847
17467
|
}
|
|
16848
17468
|
}
|
|
16849
|
-
if (!
|
|
17469
|
+
if (!existsSync44(rollPkgConventions()))
|
|
16850
17470
|
return null;
|
|
16851
17471
|
submoduleGuard();
|
|
16852
17472
|
const home = rollHome();
|
|
@@ -16865,7 +17485,7 @@ function setupCommand(args) {
|
|
|
16865
17485
|
".qwen"
|
|
16866
17486
|
];
|
|
16867
17487
|
const homeDir = process.env["HOME"] ?? "";
|
|
16868
|
-
const aiDirs = aiDirsList.map((d) =>
|
|
17488
|
+
const aiDirs = aiDirsList.map((d) => join40(homeDir, d)).join(":");
|
|
16869
17489
|
const steps = [];
|
|
16870
17490
|
const s1 = runSetupStep(home, () => {
|
|
16871
17491
|
installLocal(force);
|
|
@@ -16879,7 +17499,7 @@ function setupCommand(args) {
|
|
|
16879
17499
|
syncSkills(force);
|
|
16880
17500
|
});
|
|
16881
17501
|
steps.push({ num: 3, label: "Install skills to ~/.claude", status: stateToMarker(s3, force) });
|
|
16882
|
-
const s4 = runSetupStep(
|
|
17502
|
+
const s4 = runSetupStep(join40(home, ".peer-state"), () => {
|
|
16883
17503
|
peerEnsureStateDir();
|
|
16884
17504
|
});
|
|
16885
17505
|
steps.push({ num: 4, label: "Initialize peer-review state directory", status: stateToMarker(s4, force) });
|
|
@@ -16900,12 +17520,12 @@ function setupCommand(args) {
|
|
|
16900
17520
|
|
|
16901
17521
|
// packages/cli/dist/commands/slides/index.js
|
|
16902
17522
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
16903
|
-
import { mkdirSync as
|
|
16904
|
-
import { join as
|
|
17523
|
+
import { mkdirSync as mkdirSync24, readFileSync as readFileSync38, readdirSync as readdirSync19, rmSync as rmSync11, statSync as statSync22, writeFileSync as writeFileSync24 } from "node:fs";
|
|
17524
|
+
import { join as join42 } from "node:path";
|
|
16905
17525
|
|
|
16906
17526
|
// packages/cli/dist/commands/slides/render.js
|
|
16907
|
-
import { readFileSync as
|
|
16908
|
-
import { join as
|
|
17527
|
+
import { readFileSync as readFileSync36, existsSync as existsSync45, readdirSync as readdirSync18, statSync as statSync20 } from "node:fs";
|
|
17528
|
+
import { join as join41, dirname as dirname18, basename as basename9 } from "node:path";
|
|
16909
17529
|
function coerceScalar(v) {
|
|
16910
17530
|
if (v.length >= 2 && v[0] === v[v.length - 1] && (v[0] === "'" || v[0] === '"')) {
|
|
16911
17531
|
return v.slice(1, -1);
|
|
@@ -17221,7 +17841,7 @@ function pyTruthy(v) {
|
|
|
17221
17841
|
return v.length > 0;
|
|
17222
17842
|
return Object.keys(v).length > 0;
|
|
17223
17843
|
}
|
|
17224
|
-
function
|
|
17844
|
+
function lookup2(ctxStack, key) {
|
|
17225
17845
|
for (let i = ctxStack.length - 1; i >= 0; i--) {
|
|
17226
17846
|
const ctx = ctxStack[i];
|
|
17227
17847
|
if (isPlainObject(ctx) && Object.prototype.hasOwnProperty.call(ctx, key)) {
|
|
@@ -17280,19 +17900,19 @@ function mustache(template, context) {
|
|
|
17280
17900
|
const tagKey = m7[3];
|
|
17281
17901
|
const matchEnd = m7.index + m7[0].length;
|
|
17282
17902
|
if (rawKey) {
|
|
17283
|
-
buf.push(pyStr(
|
|
17903
|
+
buf.push(pyStr(lookup2(ctxStack, rawKey)));
|
|
17284
17904
|
i = matchEnd;
|
|
17285
17905
|
continue;
|
|
17286
17906
|
}
|
|
17287
17907
|
if (sigil === "") {
|
|
17288
|
-
buf.push(escapeHtml2(pyStr(
|
|
17908
|
+
buf.push(escapeHtml2(pyStr(lookup2(ctxStack, tagKey))));
|
|
17289
17909
|
i = matchEnd;
|
|
17290
17910
|
continue;
|
|
17291
17911
|
}
|
|
17292
17912
|
if (sigil === "#" || sigil === "^") {
|
|
17293
17913
|
const closeIdx = findClose(chunk, matchEnd, tagKey);
|
|
17294
17914
|
const inner = chunk.slice(matchEnd, closeIdx);
|
|
17295
|
-
const val =
|
|
17915
|
+
const val = lookup2(ctxStack, tagKey);
|
|
17296
17916
|
if (sigil === "#") {
|
|
17297
17917
|
if (Array.isArray(val)) {
|
|
17298
17918
|
for (const item of val) {
|
|
@@ -17390,7 +18010,7 @@ var DEFAULT_LAYOUT = "plain";
|
|
|
17390
18010
|
var LayoutResolver = class {
|
|
17391
18011
|
componentsDir;
|
|
17392
18012
|
constructor(componentsDir) {
|
|
17393
|
-
this.componentsDir = componentsDir ??
|
|
18013
|
+
this.componentsDir = componentsDir ?? join41(libDirFor(import.meta.url), "slides", "components");
|
|
17394
18014
|
}
|
|
17395
18015
|
available() {
|
|
17396
18016
|
if (!isDir2(this.componentsDir))
|
|
@@ -17404,7 +18024,7 @@ var LayoutResolver = class {
|
|
|
17404
18024
|
if (!/^[a-z0-9-]+$/.test(layout)) {
|
|
17405
18025
|
throw new ValueError(`Unknown layout: ${layout}; available: ${this.available().join(", ")}`);
|
|
17406
18026
|
}
|
|
17407
|
-
const path =
|
|
18027
|
+
const path = join41(this.componentsDir, `${layout}.html`);
|
|
17408
18028
|
if (!isFile(path)) {
|
|
17409
18029
|
throw new ValueError(`Unknown layout: ${layout}; available: ${this.available().join(", ")}`);
|
|
17410
18030
|
}
|
|
@@ -17414,7 +18034,7 @@ var LayoutResolver = class {
|
|
|
17414
18034
|
function renderSlideInner(slide, resolver) {
|
|
17415
18035
|
const layout = typeof slide["layout"] === "string" && slide["layout"] || DEFAULT_LAYOUT;
|
|
17416
18036
|
const partialPath = resolver.resolve(String(layout));
|
|
17417
|
-
let partial =
|
|
18037
|
+
let partial = readFileSync36(partialPath, "utf8");
|
|
17418
18038
|
partial = partial.replace(/^\s*<!--[\s\S]*?-->(?:\s*\n)?/, "");
|
|
17419
18039
|
const rendered = mustache(partial, slide);
|
|
17420
18040
|
return stripNewlines(rendered);
|
|
@@ -17462,28 +18082,28 @@ function pyRepr(s) {
|
|
|
17462
18082
|
}
|
|
17463
18083
|
function isDir2(p) {
|
|
17464
18084
|
try {
|
|
17465
|
-
return
|
|
18085
|
+
return statSync20(p).isDirectory();
|
|
17466
18086
|
} catch {
|
|
17467
18087
|
return false;
|
|
17468
18088
|
}
|
|
17469
18089
|
}
|
|
17470
18090
|
function isFile(p) {
|
|
17471
18091
|
try {
|
|
17472
|
-
return
|
|
18092
|
+
return statSync20(p).isFile();
|
|
17473
18093
|
} catch {
|
|
17474
18094
|
return false;
|
|
17475
18095
|
}
|
|
17476
18096
|
}
|
|
17477
18097
|
function libDirFor(_metaUrl) {
|
|
17478
18098
|
void _metaUrl;
|
|
17479
|
-
void
|
|
18099
|
+
void existsSync45;
|
|
17480
18100
|
void dirname18;
|
|
17481
|
-
return
|
|
18101
|
+
return join41(repoLibFallback(), "lib");
|
|
17482
18102
|
}
|
|
17483
18103
|
function repoLibFallback() {
|
|
17484
18104
|
let dir = process.cwd();
|
|
17485
18105
|
for (let i = 0; i < 12; i++) {
|
|
17486
|
-
if (
|
|
18106
|
+
if (existsSync45(join41(dir, "bin", "roll")))
|
|
17487
18107
|
return dir;
|
|
17488
18108
|
const parent = dirname18(dir);
|
|
17489
18109
|
if (parent === dir)
|
|
@@ -17494,7 +18114,7 @@ function repoLibFallback() {
|
|
|
17494
18114
|
}
|
|
17495
18115
|
|
|
17496
18116
|
// packages/cli/dist/commands/slides/validate.js
|
|
17497
|
-
import { readFileSync as
|
|
18117
|
+
import { readFileSync as readFileSync37, statSync as statSync21 } from "node:fs";
|
|
17498
18118
|
var REQUIRED_FRONTMATTER = [
|
|
17499
18119
|
"template",
|
|
17500
18120
|
"slug",
|
|
@@ -17698,7 +18318,7 @@ function validateDeckFile(path, errSink, componentsDir) {
|
|
|
17698
18318
|
let fm;
|
|
17699
18319
|
let slides;
|
|
17700
18320
|
try {
|
|
17701
|
-
src =
|
|
18321
|
+
src = readFileSync37(path, "utf8");
|
|
17702
18322
|
[fm] = parseFrontmatter(src);
|
|
17703
18323
|
const [, body] = parseFrontmatter(src);
|
|
17704
18324
|
slides = parseSlides(body);
|
|
@@ -17732,7 +18352,7 @@ function validateDeckFile(path, errSink, componentsDir) {
|
|
|
17732
18352
|
}
|
|
17733
18353
|
function isFile2(p) {
|
|
17734
18354
|
try {
|
|
17735
|
-
return
|
|
18355
|
+
return statSync21(p).isFile();
|
|
17736
18356
|
} catch {
|
|
17737
18357
|
return false;
|
|
17738
18358
|
}
|
|
@@ -17811,13 +18431,13 @@ function slidesHelp(out2) {
|
|
|
17811
18431
|
out2(SLIDES_HELP);
|
|
17812
18432
|
}
|
|
17813
18433
|
function slidesLib() {
|
|
17814
|
-
return
|
|
18434
|
+
return join42(rollPkgDir(), "lib");
|
|
17815
18435
|
}
|
|
17816
18436
|
function slidesTemplatePath(name) {
|
|
17817
|
-
const projTpl =
|
|
18437
|
+
const projTpl = join42(".roll", "slides", "templates", `${name}.html`);
|
|
17818
18438
|
if (isFile3(projTpl))
|
|
17819
18439
|
return projTpl;
|
|
17820
|
-
const tpl =
|
|
18440
|
+
const tpl = join42(rollPkgDir(), "lib", "slides", "templates", `${name}.html`);
|
|
17821
18441
|
if (isFile3(tpl))
|
|
17822
18442
|
return tpl;
|
|
17823
18443
|
return null;
|
|
@@ -17825,7 +18445,7 @@ function slidesTemplatePath(name) {
|
|
|
17825
18445
|
function slidesTemplateForDeck(deckPath) {
|
|
17826
18446
|
let tpl = "";
|
|
17827
18447
|
try {
|
|
17828
|
-
const src =
|
|
18448
|
+
const src = readFileSync38(deckPath, "utf8");
|
|
17829
18449
|
let d = 0;
|
|
17830
18450
|
for (const line of src.split("\n")) {
|
|
17831
18451
|
if (/^---[ \t]*$/.test(line)) {
|
|
@@ -17851,21 +18471,21 @@ function slidesTemplateForDeck(deckPath) {
|
|
|
17851
18471
|
return tpl;
|
|
17852
18472
|
}
|
|
17853
18473
|
function slidesEnsureGitignore() {
|
|
17854
|
-
const gi =
|
|
17855
|
-
|
|
18474
|
+
const gi = join42(".roll", ".gitignore");
|
|
18475
|
+
mkdirSync24(".roll", { recursive: true });
|
|
17856
18476
|
if (isFile3(gi)) {
|
|
17857
|
-
const content =
|
|
18477
|
+
const content = readFileSync38(gi, "utf8");
|
|
17858
18478
|
if (content.split("\n").some((l) => /^slides\/\*\.html$/.test(l)))
|
|
17859
18479
|
return;
|
|
17860
18480
|
if (content.length > 0 && !content.endsWith("\n")) {
|
|
17861
|
-
|
|
18481
|
+
writeFileSync24(gi, content + "\n");
|
|
17862
18482
|
}
|
|
17863
18483
|
}
|
|
17864
18484
|
appendFile(gi, "slides/*.html\n");
|
|
17865
18485
|
}
|
|
17866
18486
|
function appendFile(path, data) {
|
|
17867
|
-
const prev = isFile3(path) ?
|
|
17868
|
-
|
|
18487
|
+
const prev = isFile3(path) ? readFileSync38(path, "utf8") : "";
|
|
18488
|
+
writeFileSync24(path, prev + data);
|
|
17869
18489
|
}
|
|
17870
18490
|
function slidesOpenCmd() {
|
|
17871
18491
|
const sys = spawnSync10("uname", ["-s"], { encoding: "utf8" });
|
|
@@ -17924,7 +18544,7 @@ function cmdBuild(args) {
|
|
|
17924
18544
|
process.stderr.write(m5("slides_build.usage_roll_slides_build_slug_no") + "\n");
|
|
17925
18545
|
return 1;
|
|
17926
18546
|
}
|
|
17927
|
-
const deck =
|
|
18547
|
+
const deck = join42(".roll", "slides", slug, "deck.md");
|
|
17928
18548
|
if (!isFile3(deck)) {
|
|
17929
18549
|
err15(`Deck not found: ${deck}`);
|
|
17930
18550
|
process.stderr.write(m5("slides_build.en_deck", deck) + "\n");
|
|
@@ -17934,14 +18554,14 @@ function cmdBuild(args) {
|
|
|
17934
18554
|
return 1;
|
|
17935
18555
|
}
|
|
17936
18556
|
const libDir = slidesLib();
|
|
17937
|
-
const validator =
|
|
17938
|
-
const renderer =
|
|
18557
|
+
const validator = join42(libDir, "slides-validate.py");
|
|
18558
|
+
const renderer = join42(libDir, "slides-render.py");
|
|
17939
18559
|
if (!isFile3(validator) || !isFile3(renderer)) {
|
|
17940
18560
|
err15(m5("slides_build.slides_toolchain_missing_re_run_roll"));
|
|
17941
18561
|
return 1;
|
|
17942
18562
|
}
|
|
17943
|
-
const errFile =
|
|
17944
|
-
const componentsDir =
|
|
18563
|
+
const errFile = join42(".roll", "slides", slug, ".last-build.err");
|
|
18564
|
+
const componentsDir = join42(libDir, "slides", "components");
|
|
17945
18565
|
const valLines = [];
|
|
17946
18566
|
const valExit = validateDeckFile(deck, (l) => valLines.push(l), componentsDir);
|
|
17947
18567
|
const valOut = valLines.join("\n");
|
|
@@ -17950,8 +18570,8 @@ function cmdBuild(args) {
|
|
|
17950
18570
|
`);
|
|
17951
18571
|
} else if (valExit !== 0) {
|
|
17952
18572
|
const ts = utcTs();
|
|
17953
|
-
|
|
17954
|
-
|
|
18573
|
+
mkdirSync24(join42(".roll", "slides", slug), { recursive: true });
|
|
18574
|
+
writeFileSync24(errFile, `[${ts}] stage=validate
|
|
17955
18575
|
${valOut}
|
|
17956
18576
|
`);
|
|
17957
18577
|
if (valOut !== "")
|
|
@@ -17968,15 +18588,15 @@ ${valOut}
|
|
|
17968
18588
|
const tplPath = slidesTemplatePath(tplName);
|
|
17969
18589
|
if (tplPath === null) {
|
|
17970
18590
|
const ts = utcTs();
|
|
17971
|
-
|
|
17972
|
-
|
|
18591
|
+
mkdirSync24(join42(".roll", "slides", slug), { recursive: true });
|
|
18592
|
+
writeFileSync24(errFile, `[${ts}] stage=template
|
|
17973
18593
|
template not found: ${tplName}
|
|
17974
18594
|
`);
|
|
17975
18595
|
const { RED, NC } = pal4();
|
|
17976
18596
|
process.stderr.write(`${RED}[FAIL]${NC} ${m5("slides_build.template_not_found", tplName)}
|
|
17977
18597
|
`);
|
|
17978
18598
|
process.stderr.write(" " + m5("slides_build.available_templates") + "\n");
|
|
17979
|
-
const builtinDir =
|
|
18599
|
+
const builtinDir = join42(rollPkgDir(), "lib", "slides", "templates");
|
|
17980
18600
|
if (isDir3(builtinDir)) {
|
|
17981
18601
|
for (const t2 of listHtml(builtinDir)) {
|
|
17982
18602
|
const n = basenameNoHtml(t2);
|
|
@@ -17984,7 +18604,7 @@ template not found: ${tplName}
|
|
|
17984
18604
|
`);
|
|
17985
18605
|
}
|
|
17986
18606
|
}
|
|
17987
|
-
const projDir =
|
|
18607
|
+
const projDir = join42(".roll", "slides", "templates");
|
|
17988
18608
|
if (isDir3(projDir)) {
|
|
17989
18609
|
for (const t2 of listHtml(projDir)) {
|
|
17990
18610
|
const n = basenameNoHtml(t2);
|
|
@@ -17995,18 +18615,18 @@ template not found: ${tplName}
|
|
|
17995
18615
|
process.stderr.write(" " + m5("slides_build.templates_list_hint") + "\n");
|
|
17996
18616
|
return 1;
|
|
17997
18617
|
}
|
|
17998
|
-
const out2 =
|
|
17999
|
-
|
|
18618
|
+
const out2 = join42(".roll", "slides", `${slug}.html`);
|
|
18619
|
+
mkdirSync24(join42(".roll", "slides"), { recursive: true });
|
|
18000
18620
|
let htmlOut;
|
|
18001
18621
|
try {
|
|
18002
|
-
const src =
|
|
18003
|
-
const template =
|
|
18622
|
+
const src = readFileSync38(deck, "utf8");
|
|
18623
|
+
const template = readFileSync38(tplPath, "utf8");
|
|
18004
18624
|
htmlOut = renderDeck(src, template, { componentsDir });
|
|
18005
18625
|
} catch (e) {
|
|
18006
18626
|
const renderOut = renderErrorBlock(e);
|
|
18007
18627
|
const ts = utcTs();
|
|
18008
|
-
|
|
18009
|
-
|
|
18628
|
+
mkdirSync24(join42(".roll", "slides", slug), { recursive: true });
|
|
18629
|
+
writeFileSync24(errFile, `[${ts}] stage=render
|
|
18010
18630
|
${renderOut}
|
|
18011
18631
|
`);
|
|
18012
18632
|
const { RED, NC } = pal4();
|
|
@@ -18020,7 +18640,7 @@ ${renderOut}
|
|
|
18020
18640
|
}
|
|
18021
18641
|
return 1;
|
|
18022
18642
|
}
|
|
18023
|
-
|
|
18643
|
+
writeFileSync24(out2, htmlOut);
|
|
18024
18644
|
try {
|
|
18025
18645
|
rmSync11(errFile, { force: true });
|
|
18026
18646
|
} catch {
|
|
@@ -18045,7 +18665,7 @@ function tailLines(s, n) {
|
|
|
18045
18665
|
}
|
|
18046
18666
|
function frontmatterField2(deckPath, field) {
|
|
18047
18667
|
try {
|
|
18048
|
-
const src =
|
|
18668
|
+
const src = readFileSync38(deckPath, "utf8");
|
|
18049
18669
|
let d = 0;
|
|
18050
18670
|
const pat = new RegExp("^" + field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "[ ]*:[ ]*");
|
|
18051
18671
|
for (const line of src.split("\n")) {
|
|
@@ -18088,7 +18708,7 @@ function cmdList(args) {
|
|
|
18088
18708
|
err15(m5("slides_list.unexpected_argument_1"));
|
|
18089
18709
|
return 1;
|
|
18090
18710
|
}
|
|
18091
|
-
const slidesDir =
|
|
18711
|
+
const slidesDir = join42(".roll", "slides");
|
|
18092
18712
|
if (!isDir3(slidesDir)) {
|
|
18093
18713
|
info5(m5("slides_list.no_decks_found_under_roll_slides"));
|
|
18094
18714
|
process.stdout.write(` Hint: run 'roll slides new "<topic>"' to create one.
|
|
@@ -18098,8 +18718,8 @@ function cmdList(args) {
|
|
|
18098
18718
|
}
|
|
18099
18719
|
const slugs = [];
|
|
18100
18720
|
for (const entry of readdirSync19(slidesDir)) {
|
|
18101
|
-
const d =
|
|
18102
|
-
if (isDir3(d) && isFile3(
|
|
18721
|
+
const d = join42(slidesDir, entry);
|
|
18722
|
+
if (isDir3(d) && isFile3(join42(d, "deck.md")))
|
|
18103
18723
|
slugs.push(entry);
|
|
18104
18724
|
}
|
|
18105
18725
|
if (slugs.length === 0) {
|
|
@@ -18113,9 +18733,9 @@ function cmdList(args) {
|
|
|
18113
18733
|
process.stdout.write(rowSix("slug", "template", "total_slides", "created", "built", "size") + "\n");
|
|
18114
18734
|
process.stdout.write(rowSix("----", "--------", "------------", "-------", "------", "----") + "\n");
|
|
18115
18735
|
for (const s of sorted) {
|
|
18116
|
-
const deck =
|
|
18117
|
-
const html =
|
|
18118
|
-
const errFile =
|
|
18736
|
+
const deck = join42(slidesDir, s, "deck.md");
|
|
18737
|
+
const html = join42(slidesDir, `${s}.html`);
|
|
18738
|
+
const errFile = join42(slidesDir, s, ".last-build.err");
|
|
18119
18739
|
let template = frontmatterField2(deck, "template");
|
|
18120
18740
|
if (template === "")
|
|
18121
18741
|
template = "-";
|
|
@@ -18138,7 +18758,7 @@ function cmdList(args) {
|
|
|
18138
18758
|
built = "\u2713 built";
|
|
18139
18759
|
let bytes = 0;
|
|
18140
18760
|
try {
|
|
18141
|
-
bytes =
|
|
18761
|
+
bytes = statSync22(html).size;
|
|
18142
18762
|
} catch {
|
|
18143
18763
|
bytes = 0;
|
|
18144
18764
|
}
|
|
@@ -18154,7 +18774,7 @@ function cmdList(args) {
|
|
|
18154
18774
|
}
|
|
18155
18775
|
function isNewer(a, b) {
|
|
18156
18776
|
try {
|
|
18157
|
-
return
|
|
18777
|
+
return statSync22(a).mtimeMs > statSync22(b).mtimeMs;
|
|
18158
18778
|
} catch {
|
|
18159
18779
|
return false;
|
|
18160
18780
|
}
|
|
@@ -18189,7 +18809,7 @@ function cmdPreview(args) {
|
|
|
18189
18809
|
process.stderr.write(m5("slides_preview.usage_roll_slides_preview_slug_no") + "\n");
|
|
18190
18810
|
return 1;
|
|
18191
18811
|
}
|
|
18192
|
-
const html =
|
|
18812
|
+
const html = join42(".roll", "slides", `${slug}.html`);
|
|
18193
18813
|
if (!isFile3(html)) {
|
|
18194
18814
|
err15(`Rendered HTML not found: ${html}`);
|
|
18195
18815
|
process.stderr.write(m5("slides_preview.en_html", html) + "\n");
|
|
@@ -18230,9 +18850,9 @@ function cmdLogs(args) {
|
|
|
18230
18850
|
process.stderr.write(m5("slides_logs.usage_roll_slides_logs_slug") + "\n");
|
|
18231
18851
|
return 1;
|
|
18232
18852
|
}
|
|
18233
|
-
const deckDir =
|
|
18234
|
-
const errFile =
|
|
18235
|
-
if (!isDir3(deckDir) || !isFile3(
|
|
18853
|
+
const deckDir = join42(".roll", "slides", slug);
|
|
18854
|
+
const errFile = join42(deckDir, ".last-build.err");
|
|
18855
|
+
if (!isDir3(deckDir) || !isFile3(join42(deckDir, "deck.md"))) {
|
|
18236
18856
|
err15(m5("slides_logs.deck_not_found", slug));
|
|
18237
18857
|
return 1;
|
|
18238
18858
|
}
|
|
@@ -18240,7 +18860,7 @@ function cmdLogs(args) {
|
|
|
18240
18860
|
info5(m5("slides_logs.no_failure_records_for", slug));
|
|
18241
18861
|
return 0;
|
|
18242
18862
|
}
|
|
18243
|
-
process.stdout.write(
|
|
18863
|
+
process.stdout.write(readFileSync38(errFile, "utf8"));
|
|
18244
18864
|
return 0;
|
|
18245
18865
|
}
|
|
18246
18866
|
function cmdDelete(args) {
|
|
@@ -18273,9 +18893,9 @@ function cmdDelete(args) {
|
|
|
18273
18893
|
process.stderr.write(m5("slides_delete.usage_roll_slides_delete_slug_force") + "\n");
|
|
18274
18894
|
return 1;
|
|
18275
18895
|
}
|
|
18276
|
-
const deckDir =
|
|
18277
|
-
const html =
|
|
18278
|
-
if (!isDir3(deckDir) || !isFile3(
|
|
18896
|
+
const deckDir = join42(".roll", "slides", slug);
|
|
18897
|
+
const html = join42(".roll", "slides", `${slug}.html`);
|
|
18898
|
+
if (!isDir3(deckDir) || !isFile3(join42(deckDir, "deck.md"))) {
|
|
18279
18899
|
err15(m5("slides_delete.deck_not_found", slug));
|
|
18280
18900
|
return 1;
|
|
18281
18901
|
}
|
|
@@ -18314,7 +18934,7 @@ function cmdTemplates(args) {
|
|
|
18314
18934
|
let found = false;
|
|
18315
18935
|
process.stdout.write(rowThree("name", "source", "path") + "\n");
|
|
18316
18936
|
process.stdout.write(rowThree("----", "------", "----") + "\n");
|
|
18317
|
-
const builtinDir =
|
|
18937
|
+
const builtinDir = join42(rollPkgDir(), "lib", "slides", "templates");
|
|
18318
18938
|
if (isDir3(builtinDir)) {
|
|
18319
18939
|
for (const tpl of listHtml(builtinDir)) {
|
|
18320
18940
|
const name = basenameNoHtml(tpl);
|
|
@@ -18322,11 +18942,11 @@ function cmdTemplates(args) {
|
|
|
18322
18942
|
found = true;
|
|
18323
18943
|
}
|
|
18324
18944
|
}
|
|
18325
|
-
const projDir =
|
|
18945
|
+
const projDir = join42(".roll", "slides", "templates");
|
|
18326
18946
|
if (isDir3(projDir)) {
|
|
18327
18947
|
for (const tpl of listHtml(projDir)) {
|
|
18328
18948
|
const name = basenameNoHtml(tpl);
|
|
18329
|
-
const source = isFile3(
|
|
18949
|
+
const source = isFile3(join42(builtinDir, `${name}.html`)) ? "project (override)" : "project";
|
|
18330
18950
|
process.stdout.write(rowThree(name, source, tpl) + "\n");
|
|
18331
18951
|
found = true;
|
|
18332
18952
|
}
|
|
@@ -18374,20 +18994,20 @@ function slidesCommand(args) {
|
|
|
18374
18994
|
}
|
|
18375
18995
|
function isFile3(p) {
|
|
18376
18996
|
try {
|
|
18377
|
-
return
|
|
18997
|
+
return statSync22(p).isFile();
|
|
18378
18998
|
} catch {
|
|
18379
18999
|
return false;
|
|
18380
19000
|
}
|
|
18381
19001
|
}
|
|
18382
19002
|
function isDir3(p) {
|
|
18383
19003
|
try {
|
|
18384
|
-
return
|
|
19004
|
+
return statSync22(p).isDirectory();
|
|
18385
19005
|
} catch {
|
|
18386
19006
|
return false;
|
|
18387
19007
|
}
|
|
18388
19008
|
}
|
|
18389
19009
|
function listHtml(dir) {
|
|
18390
|
-
return readdirSync19(dir).filter((f) => f.endsWith(".html")).sort().map((f) =>
|
|
19010
|
+
return readdirSync19(dir).filter((f) => f.endsWith(".html")).sort().map((f) => join42(dir, f));
|
|
18391
19011
|
}
|
|
18392
19012
|
function basenameNoHtml(path) {
|
|
18393
19013
|
const b = path.slice(path.lastIndexOf("/") + 1);
|
|
@@ -18405,21 +19025,21 @@ function rowThree(a, b, c2) {
|
|
|
18405
19025
|
}
|
|
18406
19026
|
|
|
18407
19027
|
// packages/cli/dist/commands/status.js
|
|
18408
|
-
import { execFileSync as
|
|
19028
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
18409
19029
|
import { createHash as createHash5 } from "node:crypto";
|
|
18410
|
-
import { existsSync as
|
|
19030
|
+
import { existsSync as existsSync46, lstatSync as lstatSync4, readdirSync as readdirSync20, readFileSync as readFileSync39, realpathSync as realpathSync6, statSync as statSync23 } from "node:fs";
|
|
18411
19031
|
import { homedir as homedir17 } from "node:os";
|
|
18412
|
-
import { basename as basename10, dirname as dirname19, join as
|
|
19032
|
+
import { basename as basename10, dirname as dirname19, join as join43 } from "node:path";
|
|
18413
19033
|
function rollHome3() {
|
|
18414
|
-
return process.env["ROLL_HOME"] ??
|
|
19034
|
+
return process.env["ROLL_HOME"] ?? join43(homedir17(), ".roll");
|
|
18415
19035
|
}
|
|
18416
|
-
var globalDir = () =>
|
|
18417
|
-
var templatesDir = () =>
|
|
18418
|
-
var configPath = () =>
|
|
19036
|
+
var globalDir = () => join43(rollHome3(), "conventions", "global");
|
|
19037
|
+
var templatesDir = () => join43(rollHome3(), "conventions", "templates");
|
|
19038
|
+
var configPath = () => join43(rollHome3(), "config.yaml");
|
|
18419
19039
|
function projectSlugPy() {
|
|
18420
19040
|
let path = realpathSync6(process.cwd());
|
|
18421
19041
|
try {
|
|
18422
|
-
const common =
|
|
19042
|
+
const common = execFileSync9("git", ["-C", path, "rev-parse", "--git-common-dir"], {
|
|
18423
19043
|
encoding: "utf8",
|
|
18424
19044
|
stdio: ["ignore", "pipe", "ignore"]
|
|
18425
19045
|
}).trim();
|
|
@@ -18434,14 +19054,14 @@ function projectSlugPy() {
|
|
|
18434
19054
|
var CONVENTION_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md", ".cursor-rules", "project_rules.md"];
|
|
18435
19055
|
var TEMPLATES = ["fullstack", "frontend-only", "backend-service", "cli"];
|
|
18436
19056
|
function globalConventions() {
|
|
18437
|
-
return CONVENTION_FILES.map((f) => [f,
|
|
19057
|
+
return CONVENTION_FILES.map((f) => [f, existsSync46(join43(globalDir(), f))]);
|
|
18438
19058
|
}
|
|
18439
19059
|
function parseAiEntries() {
|
|
18440
19060
|
const cfg = configPath();
|
|
18441
|
-
if (!
|
|
19061
|
+
if (!existsSync46(cfg))
|
|
18442
19062
|
return [];
|
|
18443
19063
|
const entries = [];
|
|
18444
|
-
for (const line of
|
|
19064
|
+
for (const line of readFileSync39(cfg, "utf8").split("\n")) {
|
|
18445
19065
|
const m7 = /^ai_[a-z]+:\s*(.+)/.exec(line);
|
|
18446
19066
|
if (m7 === null)
|
|
18447
19067
|
continue;
|
|
@@ -18461,21 +19081,21 @@ function parseAiEntries() {
|
|
|
18461
19081
|
return entries;
|
|
18462
19082
|
}
|
|
18463
19083
|
function aiSyncStatus(e) {
|
|
18464
|
-
const cfgFile =
|
|
18465
|
-
const rollMd =
|
|
18466
|
-
const src =
|
|
18467
|
-
if (!
|
|
19084
|
+
const cfgFile = join43(e.ai_dir, e.cfg_file);
|
|
19085
|
+
const rollMd = join43(e.ai_dir, "roll.md");
|
|
19086
|
+
const src = join43(globalDir(), e.src_file);
|
|
19087
|
+
if (!existsSync46(cfgFile))
|
|
18468
19088
|
return "missing";
|
|
18469
|
-
if (!
|
|
19089
|
+
if (!existsSync46(rollMd))
|
|
18470
19090
|
return "out-of-sync";
|
|
18471
19091
|
try {
|
|
18472
|
-
if (
|
|
19092
|
+
if (existsSync46(src) && !readFileSync39(rollMd).equals(readFileSync39(src)))
|
|
18473
19093
|
return "out-of-sync";
|
|
18474
19094
|
} catch {
|
|
18475
19095
|
return "out-of-sync";
|
|
18476
19096
|
}
|
|
18477
19097
|
try {
|
|
18478
|
-
if (!
|
|
19098
|
+
if (!readFileSync39(cfgFile, "utf8").includes("@roll.md"))
|
|
18479
19099
|
return "out-of-sync";
|
|
18480
19100
|
} catch {
|
|
18481
19101
|
return "out-of-sync";
|
|
@@ -18483,15 +19103,15 @@ function aiSyncStatus(e) {
|
|
|
18483
19103
|
return "sync";
|
|
18484
19104
|
}
|
|
18485
19105
|
function aiSkillCount(e) {
|
|
18486
|
-
const skillsDir2 =
|
|
18487
|
-
if (!
|
|
19106
|
+
const skillsDir2 = join43(e.ai_dir, "skills");
|
|
19107
|
+
if (!existsSync46(skillsDir2))
|
|
18488
19108
|
return 0;
|
|
18489
19109
|
try {
|
|
18490
19110
|
let n = 0;
|
|
18491
19111
|
for (const name of readdirSync20(skillsDir2)) {
|
|
18492
19112
|
if (!name.startsWith("roll-"))
|
|
18493
19113
|
continue;
|
|
18494
|
-
const p =
|
|
19114
|
+
const p = join43(skillsDir2, name);
|
|
18495
19115
|
const st = lstatSync4(p);
|
|
18496
19116
|
if (st.isSymbolicLink() || st.isDirectory())
|
|
18497
19117
|
n++;
|
|
@@ -18504,8 +19124,8 @@ function aiSkillCount(e) {
|
|
|
18504
19124
|
function countFilesRecursive(dir) {
|
|
18505
19125
|
let n = 0;
|
|
18506
19126
|
for (const name of readdirSync20(dir)) {
|
|
18507
|
-
const p =
|
|
18508
|
-
const st =
|
|
19127
|
+
const p = join43(dir, name);
|
|
19128
|
+
const st = statSync23(p);
|
|
18509
19129
|
if (st.isDirectory())
|
|
18510
19130
|
n += countFilesRecursive(p);
|
|
18511
19131
|
else if (st.isFile())
|
|
@@ -18514,8 +19134,8 @@ function countFilesRecursive(dir) {
|
|
|
18514
19134
|
return n;
|
|
18515
19135
|
}
|
|
18516
19136
|
function templateCount(tpl) {
|
|
18517
|
-
const d =
|
|
18518
|
-
if (!
|
|
19137
|
+
const d = join43(templatesDir(), tpl);
|
|
19138
|
+
if (!existsSync46(d))
|
|
18519
19139
|
return 0;
|
|
18520
19140
|
try {
|
|
18521
19141
|
return countFilesRecursive(d);
|
|
@@ -18524,22 +19144,22 @@ function templateCount(tpl) {
|
|
|
18524
19144
|
}
|
|
18525
19145
|
}
|
|
18526
19146
|
function skillsInstalled() {
|
|
18527
|
-
const sd =
|
|
18528
|
-
if (!
|
|
19147
|
+
const sd = join43(rollHome3(), "skills");
|
|
19148
|
+
if (!existsSync46(sd))
|
|
18529
19149
|
return 0;
|
|
18530
19150
|
try {
|
|
18531
|
-
return readdirSync20(sd).filter((n) =>
|
|
19151
|
+
return readdirSync20(sd).filter((n) => statSync23(join43(sd, n)).isDirectory()).length;
|
|
18532
19152
|
} catch {
|
|
18533
19153
|
return 0;
|
|
18534
19154
|
}
|
|
18535
19155
|
}
|
|
18536
19156
|
function launchdState(service, slug) {
|
|
18537
19157
|
const label4 = `com.roll.${service}.${slug}`;
|
|
18538
|
-
const plist =
|
|
18539
|
-
if (!
|
|
19158
|
+
const plist = join43(homedir17(), "Library", "LaunchAgents", `${label4}.plist`);
|
|
19159
|
+
if (!existsSync46(plist))
|
|
18540
19160
|
return "not-installed";
|
|
18541
19161
|
try {
|
|
18542
|
-
const out2 =
|
|
19162
|
+
const out2 = execFileSync9("launchctl", ["list", label4], {
|
|
18543
19163
|
encoding: "utf8",
|
|
18544
19164
|
stdio: ["ignore", "pipe", "ignore"]
|
|
18545
19165
|
});
|
|
@@ -18674,13 +19294,13 @@ function liveData() {
|
|
|
18674
19294
|
const aiClients = parseAiEntries().map((e) => ({
|
|
18675
19295
|
name: e.name,
|
|
18676
19296
|
cfg_file: e.cfg_file,
|
|
18677
|
-
path:
|
|
19297
|
+
path: join43(e.ai_dir, e.cfg_file).replaceAll(home, "~"),
|
|
18678
19298
|
sync: aiSyncStatus(e),
|
|
18679
19299
|
skills: aiSkillCount(e)
|
|
18680
19300
|
}));
|
|
18681
19301
|
const featDir = ".roll/features";
|
|
18682
19302
|
let featCount = 0;
|
|
18683
|
-
if (
|
|
19303
|
+
if (existsSync46(featDir)) {
|
|
18684
19304
|
featCount = readdirSync20(featDir).filter((n) => n.endsWith(".md")).length;
|
|
18685
19305
|
}
|
|
18686
19306
|
return {
|
|
@@ -18688,8 +19308,8 @@ function liveData() {
|
|
|
18688
19308
|
ai_clients: aiClients,
|
|
18689
19309
|
templates: TEMPLATES.map((t2) => [t2, templateCount(t2)]),
|
|
18690
19310
|
skills_installed: skillsInstalled(),
|
|
18691
|
-
project_has_agents:
|
|
18692
|
-
project_has_backlog:
|
|
19311
|
+
project_has_agents: existsSync46("AGENTS.md"),
|
|
19312
|
+
project_has_backlog: existsSync46(".roll/backlog.md"),
|
|
18693
19313
|
project_features_count: featCount,
|
|
18694
19314
|
loop_state: launchdState("loop", slug),
|
|
18695
19315
|
dream_state: launchdState("dream", slug)
|
|
@@ -18713,8 +19333,8 @@ function statusCommand(args) {
|
|
|
18713
19333
|
|
|
18714
19334
|
// packages/cli/dist/commands/test.js
|
|
18715
19335
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
18716
|
-
import { existsSync as
|
|
18717
|
-
import { dirname as dirname20, join as
|
|
19336
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync25, readFileSync as readFileSync40, rmSync as rmSync12, writeFileSync as writeFileSync25 } from "node:fs";
|
|
19337
|
+
import { dirname as dirname20, join as join44 } from "node:path";
|
|
18718
19338
|
function pal5() {
|
|
18719
19339
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
18720
19340
|
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" };
|
|
@@ -18731,12 +19351,12 @@ function err16(line) {
|
|
|
18731
19351
|
}
|
|
18732
19352
|
var SUPPORTED_TYPES = ["none"];
|
|
18733
19353
|
function isolationGetType() {
|
|
18734
|
-
const file =
|
|
18735
|
-
if (!
|
|
19354
|
+
const file = join44(process.cwd(), ".roll", "local.yaml");
|
|
19355
|
+
if (!existsSync47(file))
|
|
18736
19356
|
return "none";
|
|
18737
19357
|
let text;
|
|
18738
19358
|
try {
|
|
18739
|
-
text =
|
|
19359
|
+
text = readFileSync40(file, "utf8");
|
|
18740
19360
|
} catch {
|
|
18741
19361
|
return "none";
|
|
18742
19362
|
}
|
|
@@ -18784,14 +19404,14 @@ function resetLockPath() {
|
|
|
18784
19404
|
return ".roll/.iso-reset.lock";
|
|
18785
19405
|
}
|
|
18786
19406
|
function resetLockHeld() {
|
|
18787
|
-
return
|
|
19407
|
+
return existsSync47(resetLockPath());
|
|
18788
19408
|
}
|
|
18789
19409
|
function resetAcquireLock() {
|
|
18790
19410
|
const lock = resetLockPath();
|
|
18791
|
-
if (
|
|
19411
|
+
if (existsSync47(lock))
|
|
18792
19412
|
return false;
|
|
18793
|
-
|
|
18794
|
-
|
|
19413
|
+
mkdirSync25(dirname20(lock), { recursive: true });
|
|
19414
|
+
writeFileSync25(lock, `${process.pid}
|
|
18795
19415
|
`);
|
|
18796
19416
|
return true;
|
|
18797
19417
|
}
|
|
@@ -18808,7 +19428,7 @@ function runForward(cmd, argv) {
|
|
|
18808
19428
|
}
|
|
18809
19429
|
function isolationDispatch(method, args) {
|
|
18810
19430
|
const type = isolationGetType();
|
|
18811
|
-
if (type === "none" && !
|
|
19431
|
+
if (type === "none" && !existsSync47(join44(process.cwd(), ".roll", "local.yaml"))) {
|
|
18812
19432
|
infoErr("isolation: no test_isolation config, falling back to type=none (host)");
|
|
18813
19433
|
}
|
|
18814
19434
|
if (!SUPPORTED_TYPES.includes(type)) {
|
|
@@ -18902,9 +19522,9 @@ function testCommand(args) {
|
|
|
18902
19522
|
|
|
18903
19523
|
// packages/cli/dist/commands/update.js
|
|
18904
19524
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18905
|
-
import { existsSync as
|
|
19525
|
+
import { existsSync as existsSync48, mkdtempSync as mkdtempSync4, readFileSync as readFileSync41, rmSync as rmSync13 } from "node:fs";
|
|
18906
19526
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
18907
|
-
import { join as
|
|
19527
|
+
import { join as join45 } from "node:path";
|
|
18908
19528
|
function pal6() {
|
|
18909
19529
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
18910
19530
|
return noColor2 ? { CYAN: "", GREEN: "", YELLOW: "", RED: "", BOLD: "", NC: "" } : {
|
|
@@ -18968,20 +19588,20 @@ function resolveRemoteVersion() {
|
|
|
18968
19588
|
}
|
|
18969
19589
|
function downloadAndInstallCurl(tag) {
|
|
18970
19590
|
const url = `https://github.com/seanyao/roll/archive/refs/tags/${tag}.tar.gz`;
|
|
18971
|
-
const tmpDir = mkdtempSync4(
|
|
19591
|
+
const tmpDir = mkdtempSync4(join45(tmpdir4(), "roll-update-"));
|
|
18972
19592
|
try {
|
|
18973
19593
|
info6(`[roll] Downloading roll ${tag} ...`);
|
|
18974
|
-
const dl = runForward2("curl", ["-fsSL", url, "-o",
|
|
19594
|
+
const dl = runForward2("curl", ["-fsSL", url, "-o", join45(tmpDir, "roll.tar.gz")]);
|
|
18975
19595
|
if (dl !== 0) {
|
|
18976
19596
|
err17(m6("update.curl_download_failed"));
|
|
18977
19597
|
return { ok: false };
|
|
18978
19598
|
}
|
|
18979
19599
|
info6("[roll] Extracting ...");
|
|
18980
|
-
const extractDir =
|
|
19600
|
+
const extractDir = join45(tmpDir, "extract");
|
|
18981
19601
|
spawnSync12("mkdir", ["-p", extractDir], { stdio: "ignore" });
|
|
18982
19602
|
const ex = runForward2("tar", [
|
|
18983
19603
|
"-xzf",
|
|
18984
|
-
|
|
19604
|
+
join45(tmpDir, "roll.tar.gz"),
|
|
18985
19605
|
"--strip-components=1",
|
|
18986
19606
|
"-C",
|
|
18987
19607
|
extractDir
|
|
@@ -18998,7 +19618,7 @@ function downloadAndInstallCurl(tag) {
|
|
|
18998
19618
|
function checkInstalledVersionOrRetry() {
|
|
18999
19619
|
const expected = (spawnSync12("npm", ["view", "@seanyao/roll", "version"], { encoding: "utf8" }).stdout ?? "").trim();
|
|
19000
19620
|
const pkgRoot = (spawnSync12("npm", ["root", "-g"], { encoding: "utf8" }).stdout ?? "").trim();
|
|
19001
|
-
const installedTree =
|
|
19621
|
+
const installedTree = join45(pkgRoot, "@seanyao", "roll");
|
|
19002
19622
|
const installed = treeVersion(installedTree);
|
|
19003
19623
|
if (expected === "" || installed === "")
|
|
19004
19624
|
return;
|
|
@@ -19012,18 +19632,18 @@ function checkInstalledVersionOrRetry() {
|
|
|
19012
19632
|
}
|
|
19013
19633
|
}
|
|
19014
19634
|
function invalidateUpdateCache() {
|
|
19015
|
-
rmSync13(
|
|
19635
|
+
rmSync13(join45(rollHome(), ".update-check"), { force: true });
|
|
19016
19636
|
}
|
|
19017
19637
|
function showChangelog() {
|
|
19018
|
-
const changelog =
|
|
19019
|
-
if (!
|
|
19638
|
+
const changelog = join45(rollPkgDir(), "CHANGELOG.md");
|
|
19639
|
+
if (!existsSync48(changelog))
|
|
19020
19640
|
return;
|
|
19021
19641
|
const { BOLD: BOLD2, CYAN, NC } = pal6();
|
|
19022
19642
|
process.stdout.write(`${BOLD2}${m6("changelog.heading")}:${NC}
|
|
19023
19643
|
`);
|
|
19024
19644
|
let count = 0;
|
|
19025
19645
|
let inSection = false;
|
|
19026
|
-
for (const line of
|
|
19646
|
+
for (const line of readFileSync41(changelog, "utf8").split("\n")) {
|
|
19027
19647
|
if (/^## /.test(line)) {
|
|
19028
19648
|
count += 1;
|
|
19029
19649
|
if (count > 3)
|
|
@@ -19043,9 +19663,9 @@ function updateCommand(args) {
|
|
|
19043
19663
|
void args;
|
|
19044
19664
|
info6(m6("update.current_version", rollVersion()));
|
|
19045
19665
|
let installMethod = "npm";
|
|
19046
|
-
const methodFile =
|
|
19047
|
-
if (
|
|
19048
|
-
installMethod =
|
|
19666
|
+
const methodFile = join45(rollPkgDir(), ".install-method");
|
|
19667
|
+
if (existsSync48(methodFile)) {
|
|
19668
|
+
installMethod = readFileSync41(methodFile, "utf8").trim() || "npm";
|
|
19049
19669
|
}
|
|
19050
19670
|
if (installMethod === "curl") {
|
|
19051
19671
|
info6(m6("update.upgrading_via_curl"));
|
|
@@ -19092,6 +19712,12 @@ function registerAll() {
|
|
|
19092
19712
|
registerPorted("doctor", doctorCommand);
|
|
19093
19713
|
registerPorted("attest", attestCommand);
|
|
19094
19714
|
registerPorted("index", indexCommand);
|
|
19715
|
+
registerPorted("story", (args) => {
|
|
19716
|
+
if (args[0] === "new")
|
|
19717
|
+
return storyNewCommand(args.slice(1));
|
|
19718
|
+
process.stdout.write("Usage: roll story new <ID> --title <text> [--epic <epic>] [--note <text>]\n");
|
|
19719
|
+
return args[0] === void 0 || args[0] === "--help" || args[0] === "-h" ? 0 : 1;
|
|
19720
|
+
});
|
|
19095
19721
|
registerPorted("gc", gcCommand);
|
|
19096
19722
|
registerPorted("archive", (args) => {
|
|
19097
19723
|
if (args[0] === "migrate")
|