@seanyao/roll 3.607.1 → 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 +39 -0
- package/README.md +4 -1
- package/dist/roll.mjs +1424 -604
- 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
|
@@ -3028,6 +3028,21 @@ function agentBinNames(agent) {
|
|
|
3028
3028
|
return null;
|
|
3029
3029
|
}
|
|
3030
3030
|
}
|
|
3031
|
+
function realAgentEnv() {
|
|
3032
|
+
return {
|
|
3033
|
+
home: homedir(),
|
|
3034
|
+
commandOnPath,
|
|
3035
|
+
dirExists: (p) => existsSync2(p),
|
|
3036
|
+
fileExecutable: (p) => {
|
|
3037
|
+
try {
|
|
3038
|
+
accessSync(p, constants.X_OK);
|
|
3039
|
+
return statSync(p).isFile();
|
|
3040
|
+
} catch {
|
|
3041
|
+
return false;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3031
3046
|
function commandOnPath(bin) {
|
|
3032
3047
|
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
3033
3048
|
if (dir === "")
|
|
@@ -3130,6 +3145,115 @@ var nodeFileStore = {
|
|
|
3130
3145
|
}
|
|
3131
3146
|
};
|
|
3132
3147
|
|
|
3148
|
+
// packages/core/dist/agent/registry.js
|
|
3149
|
+
function agentBinNames2(agent) {
|
|
3150
|
+
switch (agent) {
|
|
3151
|
+
case "claude":
|
|
3152
|
+
return ["claude"];
|
|
3153
|
+
case "codex":
|
|
3154
|
+
case "openai":
|
|
3155
|
+
return ["codex"];
|
|
3156
|
+
case "agy":
|
|
3157
|
+
case "gemini":
|
|
3158
|
+
return ["agy", "gemini"];
|
|
3159
|
+
case "kimi":
|
|
3160
|
+
return ["kimi-code", "kimi-cli", "kimi"];
|
|
3161
|
+
case "deepseek":
|
|
3162
|
+
return ["deepseek"];
|
|
3163
|
+
case "qwen":
|
|
3164
|
+
return ["qwen"];
|
|
3165
|
+
case "pi":
|
|
3166
|
+
return ["pi"];
|
|
3167
|
+
default:
|
|
3168
|
+
return null;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
function joinPath(...parts) {
|
|
3172
|
+
return parts.join("/");
|
|
3173
|
+
}
|
|
3174
|
+
function agentInstalledByName2(env, agent, dir) {
|
|
3175
|
+
const home = env.home;
|
|
3176
|
+
switch (agent) {
|
|
3177
|
+
case "trae":
|
|
3178
|
+
return env.dirExists(joinPath(home, "Library", "Application Support", "Trae")) || env.dirExists(joinPath(home, ".config", "Trae"));
|
|
3179
|
+
case "opencode":
|
|
3180
|
+
return env.fileExecutable(joinPath(home, ".opencode", "bin", "opencode"));
|
|
3181
|
+
case "cursor":
|
|
3182
|
+
return env.commandOnPath("cursor") || env.dirExists(joinPath(home, ".cursor"));
|
|
3183
|
+
case "openclaw":
|
|
3184
|
+
return env.dirExists(joinPath(home, ".openclaw", "workspace"));
|
|
3185
|
+
}
|
|
3186
|
+
const bins = agentBinNames2(agent);
|
|
3187
|
+
if (bins !== null)
|
|
3188
|
+
return bins.some((b) => env.commandOnPath(b));
|
|
3189
|
+
return dir !== void 0 && dir !== "" && env.dirExists(dir);
|
|
3190
|
+
}
|
|
3191
|
+
var FIRST_INSTALLED_ORDER = [
|
|
3192
|
+
"claude",
|
|
3193
|
+
"codex",
|
|
3194
|
+
"kimi",
|
|
3195
|
+
"deepseek",
|
|
3196
|
+
"qwen",
|
|
3197
|
+
"agy",
|
|
3198
|
+
"pi",
|
|
3199
|
+
"cursor",
|
|
3200
|
+
"opencode",
|
|
3201
|
+
"trae",
|
|
3202
|
+
"openclaw"
|
|
3203
|
+
];
|
|
3204
|
+
function firstInstalledAgent(env) {
|
|
3205
|
+
return FIRST_INSTALLED_ORDER.find((a) => agentInstalledByName2(env, a));
|
|
3206
|
+
}
|
|
3207
|
+
function lineAgentValue(line) {
|
|
3208
|
+
const s = line.replace(/\t/g, " ");
|
|
3209
|
+
if (s.startsWith("agent:"))
|
|
3210
|
+
return s.slice("agent:".length);
|
|
3211
|
+
const m7 = /[ ,{]agent:/.exec(s);
|
|
3212
|
+
if (m7 === null)
|
|
3213
|
+
return void 0;
|
|
3214
|
+
return s.slice(m7.index + m7[0].length);
|
|
3215
|
+
}
|
|
3216
|
+
function cleanAgentValue(raw) {
|
|
3217
|
+
let v = raw;
|
|
3218
|
+
const hash = v.indexOf("#");
|
|
3219
|
+
if (hash >= 0)
|
|
3220
|
+
v = v.slice(0, hash);
|
|
3221
|
+
v = v.replace(/[{}",']/g, "").replace(/,/g, "");
|
|
3222
|
+
return v.trim();
|
|
3223
|
+
}
|
|
3224
|
+
function readSlotFromText(text, slot) {
|
|
3225
|
+
let inBlock = false;
|
|
3226
|
+
let agent = "";
|
|
3227
|
+
let found = false;
|
|
3228
|
+
const slotHeader = `${slot}:`;
|
|
3229
|
+
for (const raw of text.split("\n")) {
|
|
3230
|
+
const line = raw.replace(/\r$/, "");
|
|
3231
|
+
if (line === slotHeader || line.startsWith(`${slot}: `) || line.startsWith(`${slot}:{`)) {
|
|
3232
|
+
inBlock = true;
|
|
3233
|
+
const v = lineAgentValue(line);
|
|
3234
|
+
if (v !== void 0) {
|
|
3235
|
+
agent = v;
|
|
3236
|
+
found = true;
|
|
3237
|
+
}
|
|
3238
|
+
} else if (line.length > 0 && line[0] !== " ") {
|
|
3239
|
+
if (inBlock)
|
|
3240
|
+
break;
|
|
3241
|
+
} else if (inBlock && !found) {
|
|
3242
|
+
const v = lineAgentValue(line);
|
|
3243
|
+
if (v !== void 0) {
|
|
3244
|
+
agent = v;
|
|
3245
|
+
found = true;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
if (found)
|
|
3249
|
+
break;
|
|
3250
|
+
}
|
|
3251
|
+
if (!inBlock)
|
|
3252
|
+
return void 0;
|
|
3253
|
+
const cleaned = cleanAgentValue(agent);
|
|
3254
|
+
return cleaned === "" ? void 0 : cleaned;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3133
3257
|
// packages/core/dist/agent/router.js
|
|
3134
3258
|
var EASY_MAX_MIN = 8;
|
|
3135
3259
|
var HARD_MIN_MIN = 20;
|
|
@@ -5988,11 +6112,11 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
5988
6112
|
import { dirname as dirname5, join as join7 } from "node:path";
|
|
5989
6113
|
|
|
5990
6114
|
// packages/cli/dist/lib/archive-migrate.js
|
|
5991
|
-
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";
|
|
5992
6116
|
import { join as join6 } from "node:path";
|
|
5993
6117
|
|
|
5994
6118
|
// packages/cli/dist/lib/archive.js
|
|
5995
|
-
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";
|
|
5996
6120
|
import { basename as basename3, dirname as dirname4, join as join5 } from "node:path";
|
|
5997
6121
|
var UNCATEGORIZED = "uncategorized";
|
|
5998
6122
|
function findFeatureFile(projectPath, storyId) {
|
|
@@ -6006,7 +6130,8 @@ function findFeatureFile(projectPath, storyId) {
|
|
|
6006
6130
|
if (e.isDirectory())
|
|
6007
6131
|
walk2(p);
|
|
6008
6132
|
else if (e.isFile() && e.name.endsWith(".md")) {
|
|
6009
|
-
|
|
6133
|
+
const idOwned = e.name === `${storyId}.md` || e.name === "spec.md" && basename3(dir) === storyId;
|
|
6134
|
+
if (idOwned)
|
|
6010
6135
|
hits.unshift(p);
|
|
6011
6136
|
else {
|
|
6012
6137
|
try {
|
|
@@ -6028,14 +6153,22 @@ function findFeatureFile(projectPath, storyId) {
|
|
|
6028
6153
|
function reportFileName(storyId) {
|
|
6029
6154
|
return `${storyId}-report.html`;
|
|
6030
6155
|
}
|
|
6156
|
+
function isEpicName(name) {
|
|
6157
|
+
return name !== "" && name !== "." && name !== "features";
|
|
6158
|
+
}
|
|
6159
|
+
function epicFromFeaturePath(featureFile) {
|
|
6160
|
+
const storyDir = dirname4(featureFile);
|
|
6161
|
+
const epic = basename3(dirname4(storyDir));
|
|
6162
|
+
if (isEpicName(epic))
|
|
6163
|
+
return epic;
|
|
6164
|
+
const legacy = basename3(storyDir);
|
|
6165
|
+
return isEpicName(legacy) ? legacy : null;
|
|
6166
|
+
}
|
|
6031
6167
|
function liveEpicOf(projectPath, storyId) {
|
|
6032
6168
|
const file = findFeatureFile(projectPath, storyId);
|
|
6033
6169
|
if (file === null)
|
|
6034
6170
|
return null;
|
|
6035
|
-
|
|
6036
|
-
if (epic === "" || epic === "." || epic === "features")
|
|
6037
|
-
return null;
|
|
6038
|
-
return epic;
|
|
6171
|
+
return epicFromFeaturePath(file);
|
|
6039
6172
|
}
|
|
6040
6173
|
function readIndex(projectPath) {
|
|
6041
6174
|
const p = join5(projectPath, ".roll", "index.json");
|
|
@@ -6078,9 +6211,6 @@ function cardArchiveDir(projectPath, storyId) {
|
|
|
6078
6211
|
const epic = epicForStory(projectPath, storyId) ?? UNCATEGORIZED;
|
|
6079
6212
|
return join5(projectPath, ".roll", "features", epic, storyId);
|
|
6080
6213
|
}
|
|
6081
|
-
function legacyArchiveDir(projectPath, storyId) {
|
|
6082
|
-
return join5(projectPath, ".roll", "verification", storyId);
|
|
6083
|
-
}
|
|
6084
6214
|
function buildStoryIndex(ids, epicOf) {
|
|
6085
6215
|
const out2 = {};
|
|
6086
6216
|
for (const id of ids) {
|
|
@@ -6096,6 +6226,69 @@ function serializeIndex(stories) {
|
|
|
6096
6226
|
sorted[k] = stories[k];
|
|
6097
6227
|
return JSON.stringify({ stories: sorted }, null, 2) + "\n";
|
|
6098
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
|
+
}
|
|
6099
6292
|
|
|
6100
6293
|
// packages/cli/dist/lib/archive-migrate.js
|
|
6101
6294
|
var RUN_ID_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/;
|
|
@@ -6111,7 +6304,7 @@ function runsInDir(absDir) {
|
|
|
6111
6304
|
if (!e.isDirectory() || !RUN_ID_RE.test(e.name))
|
|
6112
6305
|
continue;
|
|
6113
6306
|
try {
|
|
6114
|
-
out2.push({ runId: e.name, mtimeSec: Math.floor(
|
|
6307
|
+
out2.push({ runId: e.name, mtimeSec: Math.floor(statSync5(join6(absDir, e.name)).mtimeMs / 1e3) });
|
|
6115
6308
|
} catch {
|
|
6116
6309
|
}
|
|
6117
6310
|
}
|
|
@@ -6818,7 +7011,7 @@ async function runPublishPlan(plan, opts = {}) {
|
|
|
6818
7011
|
}
|
|
6819
7012
|
|
|
6820
7013
|
// packages/infra/dist/process.js
|
|
6821
|
-
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";
|
|
6822
7015
|
import { dirname as dirname8 } from "node:path";
|
|
6823
7016
|
var OUTER_LOCK_STALE_SEC = 900;
|
|
6824
7017
|
var INNER_LOCK_STALE_SEC = 14400;
|
|
@@ -6994,7 +7187,7 @@ import { promisify as promisify4 } from "node:util";
|
|
|
6994
7187
|
var execFileAsync4 = promisify4(execFile4);
|
|
6995
7188
|
|
|
6996
7189
|
// packages/infra/dist/evidence.js
|
|
6997
|
-
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";
|
|
6998
7191
|
import { join as join9 } from "node:path";
|
|
6999
7192
|
import { execFile as execFile5 } from "node:child_process";
|
|
7000
7193
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -7051,13 +7244,13 @@ async function collectEvidence(opts) {
|
|
|
7051
7244
|
const proof = join9(opts.projectPath, ".roll", "last-test-pass");
|
|
7052
7245
|
if (existsSync12(proof)) {
|
|
7053
7246
|
try {
|
|
7054
|
-
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));
|
|
7055
7248
|
testPass = { present: true, age_seconds: age };
|
|
7056
7249
|
} catch {
|
|
7057
7250
|
testPass = { present: true, age_seconds: -1 };
|
|
7058
7251
|
}
|
|
7059
7252
|
}
|
|
7060
|
-
const
|
|
7253
|
+
const listDir2 = (sub, ext) => {
|
|
7061
7254
|
const dir = join9(opts.runDir, sub);
|
|
7062
7255
|
if (!existsSync12(dir))
|
|
7063
7256
|
return [];
|
|
@@ -7074,8 +7267,8 @@ async function collectEvidence(opts) {
|
|
|
7074
7267
|
ci,
|
|
7075
7268
|
deploy,
|
|
7076
7269
|
test_pass: testPass,
|
|
7077
|
-
screenshots:
|
|
7078
|
-
texts:
|
|
7270
|
+
screenshots: listDir2("screenshots", /\.png$/i),
|
|
7271
|
+
texts: listDir2("evidence", /\.(txt|log)$/i)
|
|
7079
7272
|
};
|
|
7080
7273
|
}
|
|
7081
7274
|
function writeEvidenceJson(manifest, runDir) {
|
|
@@ -7086,7 +7279,7 @@ function writeEvidenceJson(manifest, runDir) {
|
|
|
7086
7279
|
}
|
|
7087
7280
|
|
|
7088
7281
|
// packages/infra/dist/screenshot.js
|
|
7089
|
-
import { existsSync as existsSync13, statSync as
|
|
7282
|
+
import { existsSync as existsSync13, statSync as statSync8 } from "node:fs";
|
|
7090
7283
|
import { execFile as execFile6 } from "node:child_process";
|
|
7091
7284
|
import { promisify as promisify6 } from "node:util";
|
|
7092
7285
|
|
|
@@ -7148,7 +7341,7 @@ var defaultRun2 = async (cmd, argv) => {
|
|
|
7148
7341
|
};
|
|
7149
7342
|
function fileNonEmpty(p) {
|
|
7150
7343
|
try {
|
|
7151
|
-
return existsSync13(p) &&
|
|
7344
|
+
return existsSync13(p) && statSync8(p).size > 0;
|
|
7152
7345
|
} catch {
|
|
7153
7346
|
return false;
|
|
7154
7347
|
}
|
|
@@ -7164,11 +7357,11 @@ function parseRegion(region) {
|
|
|
7164
7357
|
return { x, y, w, h };
|
|
7165
7358
|
}
|
|
7166
7359
|
function terminalOpenScript(line, r) {
|
|
7167
|
-
const
|
|
7360
|
+
const esc5 = line.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
7168
7361
|
return [
|
|
7169
7362
|
'tell application "Terminal"',
|
|
7170
7363
|
" activate",
|
|
7171
|
-
` do script "${
|
|
7364
|
+
` do script "${esc5}"`,
|
|
7172
7365
|
" delay 1.5",
|
|
7173
7366
|
` set bounds of front window to {${r.x}, ${r.y}, ${r.x + r.w}, ${r.y + r.h}}`,
|
|
7174
7367
|
"end tell"
|
|
@@ -7243,7 +7436,7 @@ function screenshotEvidenceRef(result, href) {
|
|
|
7243
7436
|
|
|
7244
7437
|
// packages/cli/dist/commands/attest.js
|
|
7245
7438
|
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync9, readdirSync as readdirSync6, rmSync as rmSync5, symlinkSync as symlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
7246
|
-
import { basename as basename4,
|
|
7439
|
+
import { basename as basename4, join as join10, relative } from "node:path";
|
|
7247
7440
|
|
|
7248
7441
|
// packages/cli/dist/lib/story-page.js
|
|
7249
7442
|
var STORY_ID_RE = /^(FIX|US|IDEA|REFACTOR)-/;
|
|
@@ -7537,11 +7730,11 @@ function buildCardContext(projectPath, featureFile, storyId, env) {
|
|
|
7537
7730
|
summary = extractSummary(readFileSync9(featureFile, "utf8"));
|
|
7538
7731
|
} catch {
|
|
7539
7732
|
}
|
|
7540
|
-
const epic =
|
|
7733
|
+
const epic = epicFromFeaturePath(featureFile);
|
|
7541
7734
|
const cycleId = env.LOOP_CYCLE_ID;
|
|
7542
7735
|
const ctx = {
|
|
7543
7736
|
...oneLiner !== void 0 && oneLiner !== "" ? { oneLiner } : {},
|
|
7544
|
-
...epic !==
|
|
7737
|
+
...epic !== null ? { epic } : {},
|
|
7545
7738
|
...summary !== void 0 ? { summary } : {},
|
|
7546
7739
|
...row2.status !== void 0 ? { backlogStatus: row2.status } : {},
|
|
7547
7740
|
...cycleId !== void 0 && cycleId !== "" ? { delivery: { cycleId } } : {}
|
|
@@ -7709,6 +7902,10 @@ async function attestCommand(args, deps = {}) {
|
|
|
7709
7902
|
\u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210
|
|
7710
7903
|
${relative(projectPath, reportPath)}
|
|
7711
7904
|
`);
|
|
7905
|
+
try {
|
|
7906
|
+
generateIndex(projectPath);
|
|
7907
|
+
} catch {
|
|
7908
|
+
}
|
|
7712
7909
|
if (!smoke.ok) {
|
|
7713
7910
|
for (const p of smoke.problems)
|
|
7714
7911
|
warn2(`render smoke: ${p}`);
|
|
@@ -8441,9 +8638,9 @@ function writeUnreleased(draft, cl) {
|
|
|
8441
8638
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
8442
8639
|
|
|
8443
8640
|
// packages/cli/dist/commands/setup-shared.js
|
|
8444
|
-
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";
|
|
8445
8642
|
import { homedir as homedir5 } from "node:os";
|
|
8446
|
-
import { basename as basename5, dirname as
|
|
8643
|
+
import { basename as basename5, dirname as dirname9, join as join12 } from "node:path";
|
|
8447
8644
|
function rollHome() {
|
|
8448
8645
|
return process.env["ROLL_HOME"] ?? join12(homedir5(), ".roll");
|
|
8449
8646
|
}
|
|
@@ -8484,14 +8681,14 @@ function aiField(entry, idx) {
|
|
|
8484
8681
|
}
|
|
8485
8682
|
function canonicalDir(path) {
|
|
8486
8683
|
try {
|
|
8487
|
-
if (!
|
|
8684
|
+
if (!statSync9(path).isDirectory())
|
|
8488
8685
|
return null;
|
|
8489
8686
|
return realpathSync2(path);
|
|
8490
8687
|
} catch {
|
|
8491
8688
|
return null;
|
|
8492
8689
|
}
|
|
8493
8690
|
}
|
|
8494
|
-
function
|
|
8691
|
+
function agentBinNames3(agent) {
|
|
8495
8692
|
switch (agent) {
|
|
8496
8693
|
case "claude":
|
|
8497
8694
|
return ["claude"];
|
|
@@ -8520,7 +8717,7 @@ function onPath(bin) {
|
|
|
8520
8717
|
continue;
|
|
8521
8718
|
const p = join12(dir, bin);
|
|
8522
8719
|
try {
|
|
8523
|
-
const st =
|
|
8720
|
+
const st = statSync9(p);
|
|
8524
8721
|
if (st.isFile() && (st.mode & 73) !== 0)
|
|
8525
8722
|
return true;
|
|
8526
8723
|
} catch {
|
|
@@ -8528,7 +8725,7 @@ function onPath(bin) {
|
|
|
8528
8725
|
}
|
|
8529
8726
|
return false;
|
|
8530
8727
|
}
|
|
8531
|
-
function
|
|
8728
|
+
function agentInstalledByName3(agent, dir = "") {
|
|
8532
8729
|
const home = homedir5();
|
|
8533
8730
|
switch (agent) {
|
|
8534
8731
|
case "trae":
|
|
@@ -8536,7 +8733,7 @@ function agentInstalledByName2(agent, dir = "") {
|
|
|
8536
8733
|
case "opencode": {
|
|
8537
8734
|
const p = join12(home, ".opencode", "bin", "opencode");
|
|
8538
8735
|
try {
|
|
8539
|
-
return existsSync18(p) && (
|
|
8736
|
+
return existsSync18(p) && (statSync9(p).mode & 73) !== 0;
|
|
8540
8737
|
} catch {
|
|
8541
8738
|
return false;
|
|
8542
8739
|
}
|
|
@@ -8548,7 +8745,7 @@ function agentInstalledByName2(agent, dir = "") {
|
|
|
8548
8745
|
default:
|
|
8549
8746
|
break;
|
|
8550
8747
|
}
|
|
8551
|
-
const bins =
|
|
8748
|
+
const bins = agentBinNames3(agent);
|
|
8552
8749
|
if (bins !== null) {
|
|
8553
8750
|
return bins.some((b) => onPath(b));
|
|
8554
8751
|
}
|
|
@@ -8557,13 +8754,13 @@ function agentInstalledByName2(agent, dir = "") {
|
|
|
8557
8754
|
function isAiInstalled(aiDir) {
|
|
8558
8755
|
let bn = basename5(aiDir).replace(/^\./, "");
|
|
8559
8756
|
if (bn === "agent" || bn === "workspace") {
|
|
8560
|
-
bn = basename5(
|
|
8757
|
+
bn = basename5(dirname9(aiDir)).replace(/^\./, "");
|
|
8561
8758
|
}
|
|
8562
8759
|
if (bn === "gemini")
|
|
8563
8760
|
bn = "agy";
|
|
8564
8761
|
if (bn === "kimi-code")
|
|
8565
8762
|
bn = "kimi";
|
|
8566
|
-
return
|
|
8763
|
+
return agentInstalledByName3(bn, aiDir);
|
|
8567
8764
|
}
|
|
8568
8765
|
function listDirEntries(dir) {
|
|
8569
8766
|
try {
|
|
@@ -8581,7 +8778,7 @@ function pruneDir(installedDir, sourceDir) {
|
|
|
8581
8778
|
const installedF = join12(installedDir, name);
|
|
8582
8779
|
let isFile4 = false;
|
|
8583
8780
|
try {
|
|
8584
|
-
isFile4 =
|
|
8781
|
+
isFile4 = statSync9(installedF).isFile();
|
|
8585
8782
|
} catch {
|
|
8586
8783
|
isFile4 = false;
|
|
8587
8784
|
}
|
|
@@ -8594,7 +8791,7 @@ function pruneDir(installedDir, sourceDir) {
|
|
|
8594
8791
|
function safeCopy(src, dst, force) {
|
|
8595
8792
|
if (!existsSync18(src))
|
|
8596
8793
|
return;
|
|
8597
|
-
mkdirSync10(
|
|
8794
|
+
mkdirSync10(dirname9(dst), { recursive: true });
|
|
8598
8795
|
if (existsSync18(dst) && !force) {
|
|
8599
8796
|
if (sameFile(src, dst))
|
|
8600
8797
|
return;
|
|
@@ -8619,7 +8816,7 @@ function listFiles(dir, includeDot) {
|
|
|
8619
8816
|
continue;
|
|
8620
8817
|
const p = join12(dir, name);
|
|
8621
8818
|
try {
|
|
8622
|
-
if (
|
|
8819
|
+
if (statSync9(p).isFile())
|
|
8623
8820
|
out2.push(name);
|
|
8624
8821
|
} catch {
|
|
8625
8822
|
}
|
|
@@ -8646,7 +8843,7 @@ function pullConventions(force) {
|
|
|
8646
8843
|
for (const tplName of listDirEntries(tplSrcRoot)) {
|
|
8647
8844
|
const tplDir = join12(tplSrcRoot, tplName);
|
|
8648
8845
|
try {
|
|
8649
|
-
if (!
|
|
8846
|
+
if (!statSync9(tplDir).isDirectory())
|
|
8650
8847
|
continue;
|
|
8651
8848
|
} catch {
|
|
8652
8849
|
continue;
|
|
@@ -8670,7 +8867,7 @@ function pullSkills() {
|
|
|
8670
8867
|
for (const skillName of listDirEntries(pkgSkills)) {
|
|
8671
8868
|
const skillDir = join12(pkgSkills, skillName);
|
|
8672
8869
|
try {
|
|
8673
|
-
if (!
|
|
8870
|
+
if (!statSync9(skillDir).isDirectory())
|
|
8674
8871
|
continue;
|
|
8675
8872
|
} catch {
|
|
8676
8873
|
continue;
|
|
@@ -8680,7 +8877,7 @@ function pullSkills() {
|
|
|
8680
8877
|
for (const name of listDirEntries(skillDir)) {
|
|
8681
8878
|
const f = join12(skillDir, name);
|
|
8682
8879
|
try {
|
|
8683
|
-
if (
|
|
8880
|
+
if (statSync9(f).isFile())
|
|
8684
8881
|
copyFileSync(f, join12(destDir, name));
|
|
8685
8882
|
} catch {
|
|
8686
8883
|
}
|
|
@@ -8690,7 +8887,7 @@ function pullSkills() {
|
|
|
8690
8887
|
for (const installedName of listDirEntries(homeSkills)) {
|
|
8691
8888
|
const installedDir = join12(homeSkills, installedName);
|
|
8692
8889
|
try {
|
|
8693
|
-
if (!
|
|
8890
|
+
if (!statSync9(installedDir).isDirectory())
|
|
8694
8891
|
continue;
|
|
8695
8892
|
} catch {
|
|
8696
8893
|
continue;
|
|
@@ -8771,7 +8968,7 @@ loop_dream_hour: 3
|
|
|
8771
8968
|
# loop_dream_minute: 10 # omit to auto-derive
|
|
8772
8969
|
primary_agent: claude
|
|
8773
8970
|
`;
|
|
8774
|
-
function
|
|
8971
|
+
function firstInstalledAgent2() {
|
|
8775
8972
|
for (const agent of [
|
|
8776
8973
|
"claude",
|
|
8777
8974
|
"codex",
|
|
@@ -8785,7 +8982,7 @@ function firstInstalledAgent() {
|
|
|
8785
8982
|
"trae",
|
|
8786
8983
|
"openclaw"
|
|
8787
8984
|
]) {
|
|
8788
|
-
if (
|
|
8985
|
+
if (agentInstalledByName3(agent))
|
|
8789
8986
|
return agent;
|
|
8790
8987
|
}
|
|
8791
8988
|
return null;
|
|
@@ -8810,7 +9007,7 @@ function installLocal(force) {
|
|
|
8810
9007
|
}
|
|
8811
9008
|
if (!existsSync18(cfg)) {
|
|
8812
9009
|
writeFileSync9(cfg, DEFAULT_CONFIG);
|
|
8813
|
-
const detected =
|
|
9010
|
+
const detected = firstInstalledAgent2();
|
|
8814
9011
|
if (detected !== null && detected !== "claude")
|
|
8815
9012
|
replacePrimaryAgent(detected);
|
|
8816
9013
|
}
|
|
@@ -8820,7 +9017,7 @@ function installLocal(force) {
|
|
|
8820
9017
|
function syncConventionForTool(src, mainDst, force) {
|
|
8821
9018
|
if (!existsSync18(src))
|
|
8822
9019
|
return;
|
|
8823
|
-
const dstDir =
|
|
9020
|
+
const dstDir = dirname9(mainDst);
|
|
8824
9021
|
if (dstDir !== join12(homedir5(), ".claude") && !isAiInstalled(dstDir) && !existsSync18(dstDir)) {
|
|
8825
9022
|
return;
|
|
8826
9023
|
}
|
|
@@ -8917,7 +9114,7 @@ function linkSkills() {
|
|
|
8917
9114
|
for (const skillName of listDirEntries(homeSkills)) {
|
|
8918
9115
|
const skillDir = `${join12(homeSkills, skillName)}/`;
|
|
8919
9116
|
try {
|
|
8920
|
-
if (!
|
|
9117
|
+
if (!statSync9(join12(homeSkills, skillName)).isDirectory())
|
|
8921
9118
|
continue;
|
|
8922
9119
|
} catch {
|
|
8923
9120
|
continue;
|
|
@@ -9335,9 +9532,9 @@ function configCommand(args) {
|
|
|
9335
9532
|
}
|
|
9336
9533
|
|
|
9337
9534
|
// packages/cli/dist/commands/consistency.js
|
|
9338
|
-
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";
|
|
9339
9536
|
import { join as join14 } from "node:path";
|
|
9340
|
-
var DIMENSIONS2 = ["code", "docs", "i18n", "tests", "site"];
|
|
9537
|
+
var DIMENSIONS2 = ["code", "cards", "docs", "i18n", "tests", "site"];
|
|
9341
9538
|
function err6(line) {
|
|
9342
9539
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
9343
9540
|
const RED = noColor2 ? "" : "\x1B[0;31m";
|
|
@@ -9398,6 +9595,64 @@ function checkFeaturesCatalog(projectDir) {
|
|
|
9398
9595
|
}
|
|
9399
9596
|
return { status: gaps.length === 0 ? "pass" : "fail", gaps };
|
|
9400
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
|
+
}
|
|
9401
9656
|
var SITE_INTERNAL_FEATURES = /* @__PURE__ */ new Set([
|
|
9402
9657
|
"cycle-meta-sync",
|
|
9403
9658
|
"loop-log-locality",
|
|
@@ -9496,7 +9751,7 @@ function listFiles2(dir) {
|
|
|
9496
9751
|
try {
|
|
9497
9752
|
return readdirSync8(dir).filter((n) => {
|
|
9498
9753
|
try {
|
|
9499
|
-
return
|
|
9754
|
+
return statSync10(join14(dir, n)).isFile();
|
|
9500
9755
|
} catch {
|
|
9501
9756
|
return false;
|
|
9502
9757
|
}
|
|
@@ -9573,7 +9828,7 @@ function rglobBats(dir) {
|
|
|
9573
9828
|
const full = join14(d, e);
|
|
9574
9829
|
let st;
|
|
9575
9830
|
try {
|
|
9576
|
-
st =
|
|
9831
|
+
st = statSync10(full);
|
|
9577
9832
|
} catch {
|
|
9578
9833
|
continue;
|
|
9579
9834
|
}
|
|
@@ -9650,6 +9905,8 @@ function runAll(projectDir) {
|
|
|
9650
9905
|
let result;
|
|
9651
9906
|
if (dim === "code")
|
|
9652
9907
|
result = checkFeaturesCatalog(projectDir);
|
|
9908
|
+
else if (dim === "cards")
|
|
9909
|
+
result = checkCards(projectDir);
|
|
9653
9910
|
else if (dim === "i18n")
|
|
9654
9911
|
result = checkI18n(projectDir);
|
|
9655
9912
|
else if (dim === "tests")
|
|
@@ -9764,7 +10021,7 @@ function consistencyCommand(args) {
|
|
|
9764
10021
|
// packages/cli/dist/commands/dashboard.js
|
|
9765
10022
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
9766
10023
|
import { createHash as createHash3 } from "node:crypto";
|
|
9767
|
-
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";
|
|
9768
10025
|
import { homedir as homedir7, platform } from "node:os";
|
|
9769
10026
|
import { basename as basename6, join as join15 } from "node:path";
|
|
9770
10027
|
var TZ_OFFSET_MS2 = 8 * 3600 * 1e3;
|
|
@@ -9857,7 +10114,7 @@ function sharedRoot() {
|
|
|
9857
10114
|
}
|
|
9858
10115
|
function isDir(p) {
|
|
9859
10116
|
try {
|
|
9860
|
-
return
|
|
10117
|
+
return statSync11(p).isDirectory();
|
|
9861
10118
|
} catch {
|
|
9862
10119
|
return false;
|
|
9863
10120
|
}
|
|
@@ -10432,8 +10689,8 @@ function loadClaudeSessionUsage(label4, slug) {
|
|
|
10432
10689
|
if (entries.length === 0)
|
|
10433
10690
|
return null;
|
|
10434
10691
|
entries.sort((a, b) => {
|
|
10435
|
-
const sa =
|
|
10436
|
-
const sb =
|
|
10692
|
+
const sa = statSync11(join15(projDir, a)).size;
|
|
10693
|
+
const sb = statSync11(join15(projDir, b)).size;
|
|
10437
10694
|
return sb - sa;
|
|
10438
10695
|
});
|
|
10439
10696
|
const path = join15(projDir, entries[0] ?? "");
|
|
@@ -10585,17 +10842,34 @@ function rollupForDay(dayCycles) {
|
|
|
10585
10842
|
}
|
|
10586
10843
|
return r;
|
|
10587
10844
|
}
|
|
10588
|
-
function selfScoreSummaryLine(notesDir = ".roll/notes", windowN = 14) {
|
|
10589
|
-
|
|
10590
|
-
return "";
|
|
10591
|
-
let files;
|
|
10845
|
+
function selfScoreSummaryLine(notesDir = ".roll/notes", windowN = 14, featuresDir = ".roll/features") {
|
|
10846
|
+
const entries = [];
|
|
10592
10847
|
try {
|
|
10593
|
-
|
|
10848
|
+
for (const n of readdirSync9(notesDir))
|
|
10849
|
+
if (n.endsWith(".md"))
|
|
10850
|
+
entries.push({ name: n, path: join15(notesDir, n) });
|
|
10594
10851
|
} catch {
|
|
10595
|
-
return "";
|
|
10596
10852
|
}
|
|
10597
|
-
|
|
10598
|
-
|
|
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
|
+
}
|
|
10869
|
+
} catch {
|
|
10870
|
+
}
|
|
10871
|
+
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
10872
|
+
const files = entries.slice(-windowN);
|
|
10599
10873
|
if (files.length === 0)
|
|
10600
10874
|
return "";
|
|
10601
10875
|
let total = 0;
|
|
@@ -10607,7 +10881,7 @@ function selfScoreSummaryLine(notesDir = ".roll/notes", windowN = 14) {
|
|
|
10607
10881
|
let verdict = null;
|
|
10608
10882
|
let content;
|
|
10609
10883
|
try {
|
|
10610
|
-
content = readFileSync15(
|
|
10884
|
+
content = readFileSync15(f.path, "utf8");
|
|
10611
10885
|
} catch {
|
|
10612
10886
|
content = "";
|
|
10613
10887
|
}
|
|
@@ -10959,8 +11233,17 @@ function nextCronHint(zh) {
|
|
|
10959
11233
|
const nxtSh = toShanghai(new Date(nxtEpoch));
|
|
10960
11234
|
return `${pad22(nxtSh.getUTCHours())}:${pad22(nxtSh.getUTCMinutes())} \xB7 in ${mins}m ${pad22(secs)}s`;
|
|
10961
11235
|
}
|
|
11236
|
+
function renderNow() {
|
|
11237
|
+
const v = process.env["ROLL_RENDER_NOW"] ?? "";
|
|
11238
|
+
if (v !== "") {
|
|
11239
|
+
const ms = /^\d+$/.test(v) ? Number(v) : Date.parse(v);
|
|
11240
|
+
if (!Number.isNaN(ms))
|
|
11241
|
+
return new Date(ms);
|
|
11242
|
+
}
|
|
11243
|
+
return /* @__PURE__ */ new Date();
|
|
11244
|
+
}
|
|
10962
11245
|
function fixtureData() {
|
|
10963
|
-
const now =
|
|
11246
|
+
const now = renderNow();
|
|
10964
11247
|
const events = [];
|
|
10965
11248
|
const cron = [];
|
|
10966
11249
|
let cycleId = 0;
|
|
@@ -11163,7 +11446,7 @@ function render(out2, events, cron, state, backlog, args) {
|
|
|
11163
11446
|
out2.push(" " + c("dim", agentLine));
|
|
11164
11447
|
let skillLine = "";
|
|
11165
11448
|
try {
|
|
11166
|
-
skillLine = selfScoreSummaryLine();
|
|
11449
|
+
skillLine = selfScoreSummaryLine(process.env["ROLL_NOTES_DIR"], 14, process.env["ROLL_FEATURES_DIR"]);
|
|
11167
11450
|
} catch {
|
|
11168
11451
|
skillLine = "";
|
|
11169
11452
|
}
|
|
@@ -11554,7 +11837,7 @@ roll-loop-status.py: error: unrecognized arguments: ${args.unknown.join(" ")}
|
|
|
11554
11837
|
}
|
|
11555
11838
|
renderState.useColor = !args.noColor && (process.env["NO_COLOR"] ?? "") === "" && (process.stdout.isTTY === true || (process.env["FORCE_COLOR"] ?? "") !== "");
|
|
11556
11839
|
const lang4 = args.en ? "en" : args.zh ? "zh" : "both";
|
|
11557
|
-
const now =
|
|
11840
|
+
const now = renderNow();
|
|
11558
11841
|
let events;
|
|
11559
11842
|
let cron;
|
|
11560
11843
|
let state;
|
|
@@ -11901,7 +12184,7 @@ function loopRunsCommand(argv) {
|
|
|
11901
12184
|
|
|
11902
12185
|
// packages/cli/dist/commands/loop-signals.js
|
|
11903
12186
|
import { appendFileSync as appendFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
|
|
11904
|
-
import { dirname as
|
|
12187
|
+
import { dirname as dirname10, join as join17 } from "node:path";
|
|
11905
12188
|
function lang2() {
|
|
11906
12189
|
return resolveLang({ rollLang: process.env["ROLL_LANG"], lcAll: process.env["LC_ALL"], lang: process.env["LANG"] });
|
|
11907
12190
|
}
|
|
@@ -11956,7 +12239,7 @@ function loopSignalsCommand(argv) {
|
|
|
11956
12239
|
const seenFile = join17(rtDir, `signals-seen-${slug}`);
|
|
11957
12240
|
const candFile = join17(projectPath, ".roll", "signals", "candidates.md");
|
|
11958
12241
|
mkdirSync11(rtDir, { recursive: true });
|
|
11959
|
-
mkdirSync11(
|
|
12242
|
+
mkdirSync11(dirname10(candFile), { recursive: true });
|
|
11960
12243
|
if (!existsSync22(seenFile))
|
|
11961
12244
|
writeFileSync10(seenFile, "");
|
|
11962
12245
|
let lastId = 0;
|
|
@@ -12027,13 +12310,13 @@ function loopAttachRetired() {
|
|
|
12027
12310
|
|
|
12028
12311
|
// packages/cli/dist/commands/doctor.js
|
|
12029
12312
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
12030
|
-
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";
|
|
12031
12314
|
import { homedir as homedir9, tmpdir as tmpdir2 } from "node:os";
|
|
12032
12315
|
import { delimiter as delimiter2, join as join19 } from "node:path";
|
|
12033
12316
|
|
|
12034
12317
|
// packages/cli/dist/commands/skills.js
|
|
12035
12318
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
12036
|
-
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";
|
|
12037
12320
|
import { tmpdir } from "node:os";
|
|
12038
12321
|
import { join as join18 } from "node:path";
|
|
12039
12322
|
function pkgDir() {
|
|
@@ -12108,7 +12391,7 @@ function generateCatalog() {
|
|
|
12108
12391
|
for (const name of entries) {
|
|
12109
12392
|
const skillDir = join18(dir, name);
|
|
12110
12393
|
try {
|
|
12111
|
-
if (!
|
|
12394
|
+
if (!statSync12(skillDir).isDirectory())
|
|
12112
12395
|
continue;
|
|
12113
12396
|
} catch {
|
|
12114
12397
|
continue;
|
|
@@ -12214,7 +12497,7 @@ var out = { lines: [] };
|
|
|
12214
12497
|
function emit(line) {
|
|
12215
12498
|
out.lines.push(line);
|
|
12216
12499
|
}
|
|
12217
|
-
function
|
|
12500
|
+
function agentBinNames4(agent) {
|
|
12218
12501
|
switch (agent) {
|
|
12219
12502
|
case "claude":
|
|
12220
12503
|
return ["claude"];
|
|
@@ -12241,7 +12524,7 @@ function commandOnPath2(bin) {
|
|
|
12241
12524
|
if (dir === "")
|
|
12242
12525
|
continue;
|
|
12243
12526
|
try {
|
|
12244
|
-
const st =
|
|
12527
|
+
const st = statSync13(join19(dir, bin));
|
|
12245
12528
|
if (!st.isFile())
|
|
12246
12529
|
continue;
|
|
12247
12530
|
accessSync2(join19(dir, bin), constants2.X_OK);
|
|
@@ -12251,7 +12534,7 @@ function commandOnPath2(bin) {
|
|
|
12251
12534
|
}
|
|
12252
12535
|
return false;
|
|
12253
12536
|
}
|
|
12254
|
-
function
|
|
12537
|
+
function agentInstalledByName4(agent, dir) {
|
|
12255
12538
|
const home = homedir9();
|
|
12256
12539
|
switch (agent) {
|
|
12257
12540
|
case "trae":
|
|
@@ -12260,7 +12543,7 @@ function agentInstalledByName3(agent, dir) {
|
|
|
12260
12543
|
const p = join19(home, ".opencode", "bin", "opencode");
|
|
12261
12544
|
try {
|
|
12262
12545
|
accessSync2(p, constants2.X_OK);
|
|
12263
|
-
return
|
|
12546
|
+
return statSync13(p).isFile();
|
|
12264
12547
|
} catch {
|
|
12265
12548
|
return false;
|
|
12266
12549
|
}
|
|
@@ -12270,7 +12553,7 @@ function agentInstalledByName3(agent, dir) {
|
|
|
12270
12553
|
case "openclaw":
|
|
12271
12554
|
return existsSync24(join19(home, ".openclaw", "workspace"));
|
|
12272
12555
|
default: {
|
|
12273
|
-
const bins =
|
|
12556
|
+
const bins = agentBinNames4(agent);
|
|
12274
12557
|
if (bins !== null)
|
|
12275
12558
|
return bins.some(commandOnPath2);
|
|
12276
12559
|
return dir !== "" && existsSync24(dir) && safeIsDir(dir);
|
|
@@ -12279,7 +12562,7 @@ function agentInstalledByName3(agent, dir) {
|
|
|
12279
12562
|
}
|
|
12280
12563
|
function safeIsDir(p) {
|
|
12281
12564
|
try {
|
|
12282
|
-
return
|
|
12565
|
+
return statSync13(p).isDirectory();
|
|
12283
12566
|
} catch {
|
|
12284
12567
|
return false;
|
|
12285
12568
|
}
|
|
@@ -12319,7 +12602,7 @@ function agentSection(p) {
|
|
|
12319
12602
|
dir = dir.replace(/^ /, "");
|
|
12320
12603
|
if (dir.startsWith("~"))
|
|
12321
12604
|
dir = home + dir.slice(1);
|
|
12322
|
-
const installed =
|
|
12605
|
+
const installed = agentInstalledByName4(name, dir) ? t(v2Catalog, msgLang6(), "doctor.agent_installed") : t(v2Catalog, msgLang6(), "doctor.agent_missing");
|
|
12323
12606
|
const dirExists = safeIsDir(dir) ? t(v2Catalog, msgLang6(), "doctor.agent_dir_exists") : t(v2Catalog, msgLang6(), "doctor.agent_dir_missing");
|
|
12324
12607
|
const tag = name === primary ? ` (${t(v2Catalog, msgLang6(), "doctor.agent_primary_label")})` : "";
|
|
12325
12608
|
emit(` ${padEnd(name, 10)} ${padEnd(installed, 14)} ${dirExists}${tag}`);
|
|
@@ -12515,7 +12798,7 @@ function killLiveAgents(signal2 = "SIGKILL") {
|
|
|
12515
12798
|
let n = 0;
|
|
12516
12799
|
for (const c2 of liveAgents) {
|
|
12517
12800
|
try {
|
|
12518
|
-
if (c2
|
|
12801
|
+
if (killHard(c2, signal2))
|
|
12519
12802
|
n += 1;
|
|
12520
12803
|
} catch {
|
|
12521
12804
|
}
|
|
@@ -12523,18 +12806,20 @@ function killLiveAgents(signal2 = "SIGKILL") {
|
|
|
12523
12806
|
liveAgents.clear();
|
|
12524
12807
|
return n;
|
|
12525
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
|
+
}
|
|
12526
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: ";
|
|
12527
12820
|
var AGENT_ARGV_TODO = {
|
|
12528
|
-
kimi: "kimi-code|kimi-cli|kimi -p <prompt> (FIX-126/133; no stream-json splice)",
|
|
12529
|
-
pi: "pi -p <prompt> (text mode; no usage on stdout \u2014 cost via session file)",
|
|
12530
|
-
deepseek: "deepseek <prompt> (positional)",
|
|
12531
|
-
codex: "codex exec <prompt>",
|
|
12532
12821
|
openai: "codex exec <prompt>",
|
|
12533
|
-
opencode: "opencode run <prompt>"
|
|
12534
|
-
gemini: "agy -p --dangerously-skip-permissions <prompt> (FIX-153)",
|
|
12535
|
-
agy: "agy -p --dangerously-skip-permissions <prompt> (FIX-153)",
|
|
12536
|
-
antigravity: "agy -p --dangerously-skip-permissions <prompt> (FIX-153)",
|
|
12537
|
-
qwen: "qwen <prompt> (positional)"
|
|
12822
|
+
opencode: "opencode run <prompt>"
|
|
12538
12823
|
};
|
|
12539
12824
|
function storyPinDirective(storyId) {
|
|
12540
12825
|
return `[\u672C\u5468\u671F\u6307\u5B9A\u6545\u4E8B] \u8C03\u5EA6\u5668\u5DF2\u9501\u5B9A ${storyId} \u5E76\u5728 backlog \u6807\u8BB0 \u{1F528} In Progress\u2014\u2014\u53EA\u6267\u884C\u8FD9\u4E00\u4E2A\u6545\u4E8B,\u4E25\u7981\u91CD\u65B0\u6311\u9009\u6216\u987A\u624B\u505A\u522B\u7684;\u82E5\u5B83\u786E\u5B9E\u4E0D\u53EF\u6267\u884C,\u5199 ALERT \u8BF4\u660E\u539F\u56E0\u540E\u5E72\u51C0\u9000\u51FA\u3002
|
|
@@ -12545,7 +12830,7 @@ function buildClaudeArgv(input) {
|
|
|
12545
12830
|
const bin = input.bin ?? "claude";
|
|
12546
12831
|
const pin = input.storyId !== void 0 && input.storyId !== "" ? storyPinDirective(input.storyId) : "";
|
|
12547
12832
|
const prompt = `${AUTORUN_DIRECTIVE}${pin}${input.skillBody}`;
|
|
12548
|
-
const args = [
|
|
12833
|
+
const args = input.interactive ? ["-p", prompt, "--dangerously-skip-permissions", "--add-dir", input.worktree] : [
|
|
12549
12834
|
"-p",
|
|
12550
12835
|
prompt,
|
|
12551
12836
|
"--verbose",
|
|
@@ -12557,24 +12842,54 @@ function buildClaudeArgv(input) {
|
|
|
12557
12842
|
];
|
|
12558
12843
|
return { bin, args };
|
|
12559
12844
|
}
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12845
|
+
function buildSpawnCommand(agent, opts) {
|
|
12846
|
+
const prompt = `${AUTORUN_DIRECTIVE}${opts.storyId !== void 0 && opts.storyId !== "" ? storyPinDirective(opts.storyId) : ""}${opts.skillBody}`;
|
|
12847
|
+
if (agent === "claude") {
|
|
12848
|
+
return buildClaudeArgv({
|
|
12849
|
+
worktree: opts.cwd,
|
|
12850
|
+
skillBody: opts.skillBody,
|
|
12851
|
+
...opts.storyId !== void 0 ? { storyId: opts.storyId } : {},
|
|
12852
|
+
bin: opts.bin,
|
|
12853
|
+
interactive: opts.interactive
|
|
12854
|
+
});
|
|
12564
12855
|
}
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
bin: opts.bin
|
|
12570
|
-
}
|
|
12856
|
+
if (agent === "pi") {
|
|
12857
|
+
return { bin: opts.bin ?? "pi", args: ["-p", prompt] };
|
|
12858
|
+
}
|
|
12859
|
+
if (agent === "kimi") {
|
|
12860
|
+
return { bin: opts.bin ?? "kimi", args: ["-p", prompt] };
|
|
12861
|
+
}
|
|
12862
|
+
if (agent === "codex") {
|
|
12863
|
+
return { bin: opts.bin ?? "codex", args: ["exec", prompt] };
|
|
12864
|
+
}
|
|
12865
|
+
if (agent === "deepseek") {
|
|
12866
|
+
return { bin: opts.bin ?? "deepseek", args: [prompt] };
|
|
12867
|
+
}
|
|
12868
|
+
if (agent === "qwen") {
|
|
12869
|
+
return { bin: opts.bin ?? "qwen", args: [prompt] };
|
|
12870
|
+
}
|
|
12871
|
+
if (agent === "agy" || agent === "gemini" || agent === "antigravity") {
|
|
12872
|
+
return { bin: opts.bin ?? "agy", args: ["-p", "--dangerously-skip-permissions", prompt] };
|
|
12873
|
+
}
|
|
12874
|
+
const hint = AGENT_ARGV_TODO[agent] ?? "unknown agent";
|
|
12875
|
+
throw new Error(`runner: agent '${agent}' argv not yet ported. v2 shape: ${hint}`);
|
|
12876
|
+
}
|
|
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) {
|
|
12571
12883
|
process.stderr.write(`[runner] spawn ${bin} argv=${JSON.stringify(args.map((a) => a.length > 80 ? `${a.slice(0, 77)}...` : a))}
|
|
12572
12884
|
`);
|
|
12573
|
-
return new Promise((resolve3
|
|
12885
|
+
return new Promise((resolve3) => {
|
|
12574
12886
|
const child = spawn(bin, args, {
|
|
12575
12887
|
cwd: opts.cwd,
|
|
12576
12888
|
env: opts.env ?? process.env,
|
|
12577
|
-
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
|
|
12578
12893
|
});
|
|
12579
12894
|
let stdout = "";
|
|
12580
12895
|
let stderr = "";
|
|
@@ -12583,7 +12898,7 @@ var realAgentSpawn = (agent, opts) => {
|
|
|
12583
12898
|
if (opts.timeoutMs !== void 0 && opts.timeoutMs > 0) {
|
|
12584
12899
|
timer = setTimeout(() => {
|
|
12585
12900
|
timedOut = true;
|
|
12586
|
-
child
|
|
12901
|
+
killHard(child, "SIGKILL");
|
|
12587
12902
|
}, opts.timeoutMs);
|
|
12588
12903
|
}
|
|
12589
12904
|
child.stdout?.on("data", (d) => {
|
|
@@ -12616,16 +12931,22 @@ var realAgentSpawn = (agent, opts) => {
|
|
|
12616
12931
|
settle({ stdout, stderr, exitCode: code ?? 1, timedOut });
|
|
12617
12932
|
});
|
|
12618
12933
|
});
|
|
12934
|
+
}
|
|
12935
|
+
var realAgentSpawn = (agent, opts) => {
|
|
12936
|
+
const { bin, args, pty } = withPtyWrap(buildSpawnCommand(agent, opts), agent);
|
|
12937
|
+
return spawnAndWait(bin, args, opts, pty);
|
|
12619
12938
|
};
|
|
12620
12939
|
|
|
12621
12940
|
// packages/cli/dist/runner/skill-body.js
|
|
12622
12941
|
import { existsSync as existsSync25, readFileSync as readFileSync20 } from "node:fs";
|
|
12942
|
+
import { homedir as homedir10 } from "node:os";
|
|
12623
12943
|
import { join as join20 } from "node:path";
|
|
12624
12944
|
function readSkillBody(projectPath, opts) {
|
|
12625
12945
|
const candidates = [
|
|
12626
12946
|
opts.envOverride ?? "",
|
|
12627
12947
|
join20(projectPath, ".roll", "skills", opts.skillName, "SKILL.md"),
|
|
12628
|
-
join20(projectPath, "skills", opts.skillName, "SKILL.md")
|
|
12948
|
+
join20(projectPath, "skills", opts.skillName, "SKILL.md"),
|
|
12949
|
+
join20(homedir10(), ".roll", "skills", opts.skillName, "SKILL.md")
|
|
12629
12950
|
].filter((p) => p !== "");
|
|
12630
12951
|
for (const p of candidates) {
|
|
12631
12952
|
if (!existsSync25(p))
|
|
@@ -12701,7 +13022,7 @@ dream run-once: \u627E\u4E0D\u5230 roll-.dream SKILL.md \u2014 \u62D2\u7EDD\u76F
|
|
|
12701
13022
|
// packages/cli/dist/commands/feedback.js
|
|
12702
13023
|
import { execFileSync as execFileSync6, spawnSync as spawnSync4 } from "node:child_process";
|
|
12703
13024
|
import { existsSync as existsSync26, readFileSync as readFileSync22 } from "node:fs";
|
|
12704
|
-
import { homedir as
|
|
13025
|
+
import { homedir as homedir11 } from "node:os";
|
|
12705
13026
|
import { basename as basename8, join as join23 } from "node:path";
|
|
12706
13027
|
|
|
12707
13028
|
// packages/cli/dist/commands/version.js
|
|
@@ -12745,7 +13066,7 @@ function err8(line) {
|
|
|
12745
13066
|
`);
|
|
12746
13067
|
}
|
|
12747
13068
|
function projectAgent2() {
|
|
12748
|
-
const
|
|
13069
|
+
const readField2 = (file, key) => {
|
|
12749
13070
|
if (!existsSync26(file))
|
|
12750
13071
|
return void 0;
|
|
12751
13072
|
let text;
|
|
@@ -12764,14 +13085,14 @@ function projectAgent2() {
|
|
|
12764
13085
|
};
|
|
12765
13086
|
const local = ".roll/local.yaml";
|
|
12766
13087
|
if (existsSync26(local) && /^agent:/m.test(safeRead(local))) {
|
|
12767
|
-
return
|
|
13088
|
+
return readField2(local, /^agent:/) ?? "claude";
|
|
12768
13089
|
}
|
|
12769
13090
|
if (existsSync26(".roll.yaml") && /^agent:/m.test(safeRead(".roll.yaml"))) {
|
|
12770
|
-
return
|
|
13091
|
+
return readField2(".roll.yaml", /^agent:/) ?? "claude";
|
|
12771
13092
|
}
|
|
12772
13093
|
const cfg = rollConfig2();
|
|
12773
13094
|
if (existsSync26(cfg) && /primary_agent:/.test(safeRead(cfg))) {
|
|
12774
|
-
return
|
|
13095
|
+
return readField2(cfg, /primary_agent:/) ?? "claude";
|
|
12775
13096
|
}
|
|
12776
13097
|
return "claude";
|
|
12777
13098
|
}
|
|
@@ -12783,7 +13104,7 @@ function safeRead(p) {
|
|
|
12783
13104
|
}
|
|
12784
13105
|
}
|
|
12785
13106
|
function rollConfig2() {
|
|
12786
|
-
const rollHome4 = process.env["ROLL_HOME"] ?? join23(
|
|
13107
|
+
const rollHome4 = process.env["ROLL_HOME"] ?? join23(homedir11(), ".roll");
|
|
12787
13108
|
return join23(rollHome4, "config.yaml");
|
|
12788
13109
|
}
|
|
12789
13110
|
function feedbackYamlField(file, field) {
|
|
@@ -12830,7 +13151,7 @@ function feedbackDefaultRepo() {
|
|
|
12830
13151
|
let v = feedbackYamlField(".roll/local.yaml", "feedback_repo");
|
|
12831
13152
|
if (v)
|
|
12832
13153
|
return v;
|
|
12833
|
-
v = feedbackYamlField(join23(
|
|
13154
|
+
v = feedbackYamlField(join23(homedir11(), ".roll", "config.yaml"), "feedback_repo");
|
|
12834
13155
|
if (v)
|
|
12835
13156
|
return v;
|
|
12836
13157
|
return feedbackOriginRepo();
|
|
@@ -12990,7 +13311,7 @@ function feedbackCommand(args) {
|
|
|
12990
13311
|
}
|
|
12991
13312
|
|
|
12992
13313
|
// packages/cli/dist/commands/gc.js
|
|
12993
|
-
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";
|
|
12994
13315
|
import { join as join24 } from "node:path";
|
|
12995
13316
|
var RUN_ID_RE2 = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/;
|
|
12996
13317
|
function runsInDir2(dir) {
|
|
@@ -13005,7 +13326,7 @@ function runsInDir2(dir) {
|
|
|
13005
13326
|
if (!e.isDirectory() || !RUN_ID_RE2.test(e.name))
|
|
13006
13327
|
continue;
|
|
13007
13328
|
try {
|
|
13008
|
-
out2.push({ runId: e.name, mtimeSec: Math.floor(
|
|
13329
|
+
out2.push({ runId: e.name, mtimeSec: Math.floor(statSync14(join24(dir, e.name)).mtimeMs / 1e3) });
|
|
13009
13330
|
} catch {
|
|
13010
13331
|
}
|
|
13011
13332
|
}
|
|
@@ -13152,14 +13473,14 @@ ${label2(lang4, "ideav3.usage")}
|
|
|
13152
13473
|
throw e;
|
|
13153
13474
|
}
|
|
13154
13475
|
const kindLabel = label2(lang4, plan.kind === "bug" ? "ideav3.kind_bug" : "ideav3.kind_idea");
|
|
13155
|
-
const
|
|
13476
|
+
const section2 = IDEA_SECTIONS[plan.kind].replace(/^#+\s*/, "");
|
|
13156
13477
|
process.stdout.write(`
|
|
13157
13478
|
${c("green", "\u{1F4DD} " + label2(lang4, "ideav3.recorded", plan.id))}
|
|
13158
13479
|
|
|
13159
13480
|
`);
|
|
13160
13481
|
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.type") + ":")} ${kindLabel}
|
|
13161
13482
|
`);
|
|
13162
|
-
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.section") + ":")} ${
|
|
13483
|
+
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.section") + ":")} ${section2}
|
|
13163
13484
|
`);
|
|
13164
13485
|
process.stdout.write(` ${c("dim", label2(lang4, "ideav3.text") + ":")} ${text}
|
|
13165
13486
|
|
|
@@ -13182,109 +13503,546 @@ ${c("green", "\u{1F4DD} " + label2(lang4, "ideav3.recorded", plan.id))}
|
|
|
13182
13503
|
}
|
|
13183
13504
|
|
|
13184
13505
|
// packages/cli/dist/commands/index-gen.js
|
|
13185
|
-
import { existsSync as existsSync29,
|
|
13506
|
+
import { existsSync as existsSync29, writeFileSync as writeFileSync14 } from "node:fs";
|
|
13186
13507
|
import { join as join26 } from "node:path";
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
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);
|
|
13221
13625
|
}
|
|
13222
|
-
|
|
13626
|
+
document.documentElement.setAttribute("data-filtered", needle || ship ? "1" : "0");
|
|
13223
13627
|
}
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
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>
|
|
13245
13672
|
<html lang="zh-CN">
|
|
13246
13673
|
<head>
|
|
13247
13674
|
<meta charset="UTF-8">
|
|
13248
13675
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
13249
|
-
<title>Roll
|
|
13676
|
+
<title>Roll \xB7 Delivery Dossier</title>
|
|
13250
13677
|
<style>
|
|
13251
|
-
${CHROME_CSS}body { max-width:1000px; }
|
|
13252
|
-
table { width:100%; border-collapse:collapse; position:relative; z-index:2; }
|
|
13253
|
-
th, td { padding:8px 12px; text-align:left; border-bottom:1px solid var(--line); vertical-align:top; }
|
|
13254
|
-
th { font-family:var(--serif); letter-spacing:.04em; color:var(--muted); }
|
|
13255
|
-
tr:hover td { background:var(--bg-raise); }
|
|
13256
|
-
td code, td strong a { font-family:var(--mono); }
|
|
13678
|
+
${CHROME_CSS}${DOSSIER_CSS}body { max-width:1000px; }
|
|
13257
13679
|
</style>
|
|
13258
13680
|
${CHROME_SCRIPT}
|
|
13681
|
+
${DOSSIER_FILTER_SCRIPT}
|
|
13259
13682
|
</head>
|
|
13260
13683
|
<body>
|
|
13261
13684
|
${CHROME_CONTROLS}
|
|
13685
|
+
<div class="masthead">
|
|
13262
13686
|
<p class="kicker">Roll \xB7 ${bi("Delivery Dossier", "\u4EA4\u4ED8\u6863\u6848")}</p>
|
|
13263
13687
|
<h1>${bi("Features Index", "\u529F\u80FD\u6863\u6848")}</h1>
|
|
13264
|
-
<p class="
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
</tbody></table>
|
|
13269
|
-
<footer>${bi("Generated by", "\u751F\u6210\u81EA")} <code>roll index</code></footer>
|
|
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>
|
|
13270
13692
|
</body>
|
|
13271
13693
|
</html>
|
|
13272
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>
|
|
13759
|
+
</body>
|
|
13760
|
+
</html>
|
|
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 = [];
|
|
13273
13892
|
try {
|
|
13274
|
-
|
|
13275
|
-
|
|
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)
|
|
13276
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;
|
|
13277
13956
|
} catch {
|
|
13278
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;
|
|
13279
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 {
|
|
14033
|
+
}
|
|
14034
|
+
process.stdout.write(`card minted
|
|
14035
|
+
\u5361\u5DF2\u5EFA\u6863
|
|
14036
|
+
.roll/features/${epic}/${id}/spec.md
|
|
14037
|
+
`);
|
|
13280
14038
|
return 0;
|
|
13281
14039
|
}
|
|
13282
14040
|
|
|
13283
14041
|
// packages/cli/dist/commands/init.js
|
|
13284
14042
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
13285
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
13286
|
-
import { homedir as
|
|
13287
|
-
import { dirname 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";
|
|
14044
|
+
import { homedir as homedir12 } from "node:os";
|
|
14045
|
+
import { dirname as dirname11, join as join28 } from "node:path";
|
|
13288
14046
|
function err9(line) {
|
|
13289
14047
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
13290
14048
|
const RED = noColor2 ? "" : "\x1B[0;31m";
|
|
@@ -13293,43 +14051,43 @@ function err9(line) {
|
|
|
13293
14051
|
`);
|
|
13294
14052
|
}
|
|
13295
14053
|
function rollHome2() {
|
|
13296
|
-
return process.env["ROLL_HOME"] ??
|
|
14054
|
+
return process.env["ROLL_HOME"] ?? join28(homedir12(), ".roll");
|
|
13297
14055
|
}
|
|
13298
14056
|
function rollGlobal2() {
|
|
13299
|
-
return
|
|
14057
|
+
return join28(rollHome2(), "conventions", "global");
|
|
13300
14058
|
}
|
|
13301
14059
|
function rollTemplates2() {
|
|
13302
|
-
return
|
|
14060
|
+
return join28(rollHome2(), "conventions", "templates");
|
|
13303
14061
|
}
|
|
13304
14062
|
function scanProjectType(dir) {
|
|
13305
14063
|
let hasFrontend = false;
|
|
13306
14064
|
let hasBackend = false;
|
|
13307
14065
|
let hasCli = false;
|
|
13308
|
-
const pkg =
|
|
14066
|
+
const pkg = join28(dir, "package.json");
|
|
13309
14067
|
const readPkg = () => {
|
|
13310
14068
|
try {
|
|
13311
|
-
return
|
|
14069
|
+
return readFileSync24(pkg, "utf8");
|
|
13312
14070
|
} catch {
|
|
13313
14071
|
return "";
|
|
13314
14072
|
}
|
|
13315
14073
|
};
|
|
13316
|
-
if (
|
|
14074
|
+
if (existsSync31(pkg)) {
|
|
13317
14075
|
if (/"react"|"vue"|"next"|"nuxt"|"vite"|"svelte"/i.test(readPkg()))
|
|
13318
14076
|
hasFrontend = true;
|
|
13319
14077
|
}
|
|
13320
|
-
if (["src", "app", "pages", "components"].some((d) =>
|
|
14078
|
+
if (["src", "app", "pages", "components"].some((d) => existsSync31(join28(dir, d))))
|
|
13321
14079
|
hasFrontend = true;
|
|
13322
|
-
if (["server", "api", "backend"].some((d) =>
|
|
14080
|
+
if (["server", "api", "backend"].some((d) => existsSync31(join28(dir, d))))
|
|
13323
14081
|
hasBackend = true;
|
|
13324
|
-
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))))
|
|
13325
14083
|
hasBackend = true;
|
|
13326
|
-
if (
|
|
14084
|
+
if (existsSync31(pkg)) {
|
|
13327
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()))
|
|
13328
14086
|
hasBackend = true;
|
|
13329
14087
|
}
|
|
13330
|
-
if (
|
|
14088
|
+
if (existsSync31(join28(dir, "prisma", "schema.prisma")))
|
|
13331
14089
|
hasBackend = true;
|
|
13332
|
-
if (
|
|
14090
|
+
if (existsSync31(join28(dir, "bin")) || existsSync31(join28(dir, "cmd")))
|
|
13333
14091
|
hasCli = true;
|
|
13334
14092
|
if (hasFrontend && hasBackend)
|
|
13335
14093
|
return "fullstack";
|
|
@@ -13349,8 +14107,8 @@ function countNonEmptyFiles(dir) {
|
|
|
13349
14107
|
}
|
|
13350
14108
|
function isLegacyProject(projectDir) {
|
|
13351
14109
|
for (const dir of ["src", "app", "lib", "pkg", "cmd"]) {
|
|
13352
|
-
const p =
|
|
13353
|
-
if (
|
|
14110
|
+
const p = join28(projectDir, dir);
|
|
14111
|
+
if (existsSync31(p) && statSync16(p).isDirectory()) {
|
|
13354
14112
|
if (countNonEmptyFiles(p) >= 10)
|
|
13355
14113
|
return true;
|
|
13356
14114
|
}
|
|
@@ -13380,12 +14138,12 @@ function isLegacyProject(projectDir) {
|
|
|
13380
14138
|
"deno.jsonc"
|
|
13381
14139
|
];
|
|
13382
14140
|
for (const man of manifests)
|
|
13383
|
-
if (
|
|
14141
|
+
if (existsSync31(join28(projectDir, man)))
|
|
13384
14142
|
return true;
|
|
13385
14143
|
const tf = spawnSync5("bash", ["-c", `compgen -G '${projectDir.replace(/'/g, "'\\''")}/*.tf' >/dev/null 2>&1`]);
|
|
13386
14144
|
if (tf.status === 0)
|
|
13387
14145
|
return true;
|
|
13388
|
-
if (
|
|
14146
|
+
if (existsSync31(join28(projectDir, ".git"))) {
|
|
13389
14147
|
const g = spawnSync5("git", ["rev-parse", "--verify", "HEAD"], { cwd: projectDir, stdio: "ignore" });
|
|
13390
14148
|
if (g.status === 0)
|
|
13391
14149
|
return true;
|
|
@@ -13393,18 +14151,18 @@ function isLegacyProject(projectDir) {
|
|
|
13393
14151
|
return false;
|
|
13394
14152
|
}
|
|
13395
14153
|
function mergeGlobalToProject(projectDir, summary) {
|
|
13396
|
-
const src =
|
|
13397
|
-
const dst =
|
|
13398
|
-
if (!
|
|
14154
|
+
const src = join28(rollGlobal2(), "AGENTS.md");
|
|
14155
|
+
const dst = join28(projectDir, "AGENTS.md");
|
|
14156
|
+
if (!existsSync31(src)) {
|
|
13399
14157
|
return;
|
|
13400
14158
|
}
|
|
13401
14159
|
const projectType = scanProjectType(projectDir);
|
|
13402
14160
|
const skipFrontend = ["cli", "backend-service", "unknown"].includes(projectType);
|
|
13403
14161
|
const FRONTEND_HEAD = "## 7. Frontend Default Stack";
|
|
13404
|
-
const srcText =
|
|
14162
|
+
const srcText = readFileSync24(src, "utf8");
|
|
13405
14163
|
const srcLines = srcText.split("\n");
|
|
13406
14164
|
const lines = srcText.endsWith("\n") ? srcLines.slice(0, -1) : srcLines;
|
|
13407
|
-
if (!
|
|
14165
|
+
if (!existsSync31(dst)) {
|
|
13408
14166
|
let out2 = "";
|
|
13409
14167
|
let fcH = "";
|
|
13410
14168
|
let fcB = "";
|
|
@@ -13433,11 +14191,11 @@ ${fcB}`;
|
|
|
13433
14191
|
}
|
|
13434
14192
|
}
|
|
13435
14193
|
flush();
|
|
13436
|
-
|
|
14194
|
+
writeFileSync16(dst, out2);
|
|
13437
14195
|
summary.push("created|AGENTS.md");
|
|
13438
14196
|
return;
|
|
13439
14197
|
}
|
|
13440
|
-
const dstText =
|
|
14198
|
+
const dstText = readFileSync24(dst, "utf8");
|
|
13441
14199
|
let added = 0;
|
|
13442
14200
|
let curH = "";
|
|
13443
14201
|
let curB = "";
|
|
@@ -13467,7 +14225,7 @@ ${curB}`;
|
|
|
13467
14225
|
}
|
|
13468
14226
|
tryAppend();
|
|
13469
14227
|
if (appendBuffer !== "")
|
|
13470
|
-
|
|
14228
|
+
writeFileSync16(dst, dstText + appendBuffer);
|
|
13471
14229
|
if (added > 0)
|
|
13472
14230
|
summary.push("merged|AGENTS.md");
|
|
13473
14231
|
else
|
|
@@ -13475,20 +14233,20 @@ ${curB}`;
|
|
|
13475
14233
|
}
|
|
13476
14234
|
function mergeClaudeToProject(projectDir, summary) {
|
|
13477
14235
|
const projectType = scanProjectType(projectDir);
|
|
13478
|
-
const tplFile =
|
|
13479
|
-
if (!
|
|
14236
|
+
const tplFile = join28(rollTemplates2(), projectType, "CLAUDE.md");
|
|
14237
|
+
if (!existsSync31(tplFile))
|
|
13480
14238
|
return;
|
|
13481
|
-
const claudeDir =
|
|
13482
|
-
const outFile =
|
|
13483
|
-
|
|
13484
|
-
if (!
|
|
14239
|
+
const claudeDir = join28(projectDir, ".claude");
|
|
14240
|
+
const outFile = join28(claudeDir, "CLAUDE.md");
|
|
14241
|
+
mkdirSync15(claudeDir, { recursive: true });
|
|
14242
|
+
if (!existsSync31(outFile)) {
|
|
13485
14243
|
copyFileSync2(tplFile, outFile);
|
|
13486
14244
|
summary.push("created|.claude/CLAUDE.md");
|
|
13487
14245
|
return;
|
|
13488
14246
|
}
|
|
13489
|
-
const tplText =
|
|
14247
|
+
const tplText = readFileSync24(tplFile, "utf8");
|
|
13490
14248
|
const lines = tplText.endsWith("\n") ? tplText.split("\n").slice(0, -1) : tplText.split("\n");
|
|
13491
|
-
const outText =
|
|
14249
|
+
const outText = readFileSync24(outFile, "utf8");
|
|
13492
14250
|
let added = 0;
|
|
13493
14251
|
let curH = "";
|
|
13494
14252
|
let curB = "";
|
|
@@ -13513,7 +14271,7 @@ ${curB}`;
|
|
|
13513
14271
|
}
|
|
13514
14272
|
tryAppend();
|
|
13515
14273
|
if (appendBuffer !== "")
|
|
13516
|
-
|
|
14274
|
+
writeFileSync16(outFile, outText + appendBuffer);
|
|
13517
14275
|
if (added > 0)
|
|
13518
14276
|
summary.push("merged|.claude/CLAUDE.md");
|
|
13519
14277
|
else
|
|
@@ -13530,20 +14288,20 @@ var BACKLOG_TEMPLATE = `# Project Backlog
|
|
|
13530
14288
|
|----|---------|--------|
|
|
13531
14289
|
`;
|
|
13532
14290
|
function writeBacklog(path, summary) {
|
|
13533
|
-
if (
|
|
14291
|
+
if (existsSync31(path)) {
|
|
13534
14292
|
summary.push("unchanged|.roll/backlog.md");
|
|
13535
14293
|
return;
|
|
13536
14294
|
}
|
|
13537
|
-
|
|
13538
|
-
|
|
14295
|
+
mkdirSync15(dirname11(path), { recursive: true });
|
|
14296
|
+
writeFileSync16(path, BACKLOG_TEMPLATE);
|
|
13539
14297
|
summary.push("created|.roll/backlog.md");
|
|
13540
14298
|
}
|
|
13541
14299
|
function ensureFeaturesDir(path, summary) {
|
|
13542
|
-
if (
|
|
14300
|
+
if (existsSync31(path) && statSync16(path).isDirectory()) {
|
|
13543
14301
|
summary.push("unchanged|.roll/features/");
|
|
13544
14302
|
return;
|
|
13545
14303
|
}
|
|
13546
|
-
|
|
14304
|
+
mkdirSync15(path, { recursive: true });
|
|
13547
14305
|
summary.push("created|.roll/features/");
|
|
13548
14306
|
}
|
|
13549
14307
|
var FEATURES_TEMPLATE = `# Features
|
|
@@ -13557,38 +14315,38 @@ var FEATURES_TEMPLATE = `# Features
|
|
|
13557
14315
|
<!-- Add feature entries here as epics are completed -->
|
|
13558
14316
|
`;
|
|
13559
14317
|
function writeFeaturesMd(path, summary) {
|
|
13560
|
-
if (
|
|
14318
|
+
if (existsSync31(path)) {
|
|
13561
14319
|
summary.push("unchanged|.roll/features.md");
|
|
13562
14320
|
return;
|
|
13563
14321
|
}
|
|
13564
|
-
|
|
13565
|
-
|
|
14322
|
+
mkdirSync15(dirname11(path), { recursive: true });
|
|
14323
|
+
writeFileSync16(path, FEATURES_TEMPLATE);
|
|
13566
14324
|
summary.push("created|.roll/features.md");
|
|
13567
14325
|
}
|
|
13568
14326
|
function initSeedAgentRoutes(templateName, projectDir, summary) {
|
|
13569
|
-
const dest =
|
|
13570
|
-
if (
|
|
14327
|
+
const dest = join28(projectDir, ".roll", "agent-routes.yaml");
|
|
14328
|
+
if (existsSync31(dest)) {
|
|
13571
14329
|
summary.push("unchanged|.roll/agent-routes.yaml");
|
|
13572
14330
|
return 0;
|
|
13573
14331
|
}
|
|
13574
|
-
const src =
|
|
13575
|
-
if (!
|
|
14332
|
+
const src = join28(rollTemplates2(), "agent-routes", `${templateName}.yaml`);
|
|
14333
|
+
if (!existsSync31(src)) {
|
|
13576
14334
|
return 1;
|
|
13577
14335
|
}
|
|
13578
|
-
|
|
14336
|
+
mkdirSync15(dirname11(dest), { recursive: true });
|
|
13579
14337
|
copyFileSync2(src, dest);
|
|
13580
14338
|
summary.push("created|.roll/agent-routes.yaml");
|
|
13581
14339
|
return 0;
|
|
13582
14340
|
}
|
|
13583
14341
|
function writeVersionStamp(projectDir, summary) {
|
|
13584
|
-
const stampPath =
|
|
13585
|
-
if (
|
|
14342
|
+
const stampPath = join28(projectDir, ".roll", ".version");
|
|
14343
|
+
if (existsSync31(stampPath)) {
|
|
13586
14344
|
summary.push("unchanged|.roll/.version");
|
|
13587
14345
|
return;
|
|
13588
14346
|
}
|
|
13589
|
-
|
|
14347
|
+
mkdirSync15(join28(projectDir, ".roll"), { recursive: true });
|
|
13590
14348
|
const installedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13591
|
-
|
|
14349
|
+
writeFileSync16(stampPath, `# Roll project version stamp \u2014 written by \`roll init\` (US-ONBOARD-019).
|
|
13592
14350
|
# Used by \`_check_structure\` to recognise a previously-onboarded Roll project
|
|
13593
14351
|
# without depending on directory-name heuristics.
|
|
13594
14352
|
roll_version: "${rollVersion() || "unknown"}"
|
|
@@ -13726,7 +14484,7 @@ function initCommand(args) {
|
|
|
13726
14484
|
return null;
|
|
13727
14485
|
if (args[0] !== void 0 && args[0].startsWith("-"))
|
|
13728
14486
|
return null;
|
|
13729
|
-
if (!
|
|
14487
|
+
if (!existsSync31(rollTemplates2()))
|
|
13730
14488
|
return null;
|
|
13731
14489
|
let projectDir;
|
|
13732
14490
|
try {
|
|
@@ -13736,16 +14494,16 @@ function initCommand(args) {
|
|
|
13736
14494
|
}
|
|
13737
14495
|
let hasAgents = false;
|
|
13738
14496
|
const summary = [];
|
|
13739
|
-
if (
|
|
14497
|
+
if (existsSync31(join28(projectDir, "AGENTS.md"))) {
|
|
13740
14498
|
hasAgents = true;
|
|
13741
14499
|
} else if (isLegacyProject(projectDir)) {
|
|
13742
14500
|
return null;
|
|
13743
14501
|
}
|
|
13744
14502
|
mergeGlobalToProject(projectDir, summary);
|
|
13745
14503
|
mergeClaudeToProject(projectDir, summary);
|
|
13746
|
-
writeBacklog(
|
|
13747
|
-
ensureFeaturesDir(
|
|
13748
|
-
writeFeaturesMd(
|
|
14504
|
+
writeBacklog(join28(projectDir, ".roll", "backlog.md"), summary);
|
|
14505
|
+
ensureFeaturesDir(join28(projectDir, ".roll", "features"), summary);
|
|
14506
|
+
writeFeaturesMd(join28(projectDir, ".roll", "features.md"), summary);
|
|
13749
14507
|
const routesTemplate = process.env["ROLL_AGENT_ROUTES_TEMPLATE"] ?? "default";
|
|
13750
14508
|
initSeedAgentRoutes(routesTemplate, projectDir, summary);
|
|
13751
14509
|
writeVersionStamp(projectDir, summary);
|
|
@@ -13758,19 +14516,19 @@ function initCommand(args) {
|
|
|
13758
14516
|
}
|
|
13759
14517
|
|
|
13760
14518
|
// packages/cli/dist/commands/lang.js
|
|
13761
|
-
import { execFileSync as
|
|
13762
|
-
import { existsSync as
|
|
13763
|
-
import { homedir as
|
|
13764
|
-
import { dirname 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";
|
|
14521
|
+
import { homedir as homedir13, tmpdir as tmpdir3 } from "node:os";
|
|
14522
|
+
import { dirname as dirname12, join as join29 } from "node:path";
|
|
13765
14523
|
function rollConfigPath4() {
|
|
13766
|
-
const rollHome4 = process.env["ROLL_HOME"] ??
|
|
13767
|
-
return
|
|
14524
|
+
const rollHome4 = process.env["ROLL_HOME"] ?? join29(homedir13(), ".roll");
|
|
14525
|
+
return join29(rollHome4, "config.yaml");
|
|
13768
14526
|
}
|
|
13769
14527
|
function configLang2() {
|
|
13770
14528
|
const cfg = rollConfigPath4();
|
|
13771
|
-
if (!
|
|
14529
|
+
if (!existsSync32(cfg))
|
|
13772
14530
|
return void 0;
|
|
13773
|
-
for (const line of
|
|
14531
|
+
for (const line of readFileSync25(cfg, "utf8").split("\n")) {
|
|
13774
14532
|
const m7 = /^lang:\s*(.*)$/.exec(line);
|
|
13775
14533
|
if (m7 !== null) {
|
|
13776
14534
|
const v = (m7[1] ?? "").replace(/\s*#.*$/, "").trim();
|
|
@@ -13782,15 +14540,15 @@ function configLang2() {
|
|
|
13782
14540
|
}
|
|
13783
14541
|
function configHasLangLine() {
|
|
13784
14542
|
const cfg = rollConfigPath4();
|
|
13785
|
-
if (!
|
|
14543
|
+
if (!existsSync32(cfg))
|
|
13786
14544
|
return false;
|
|
13787
|
-
return
|
|
14545
|
+
return readFileSync25(cfg, "utf8").split("\n").some((l) => /^lang:/.test(l));
|
|
13788
14546
|
}
|
|
13789
14547
|
function appleLang2() {
|
|
13790
14548
|
if (process.platform !== "darwin")
|
|
13791
14549
|
return void 0;
|
|
13792
14550
|
try {
|
|
13793
|
-
const out2 =
|
|
14551
|
+
const out2 = execFileSync8("defaults", ["read", "-g", "AppleLanguages"], {
|
|
13794
14552
|
encoding: "utf8",
|
|
13795
14553
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13796
14554
|
});
|
|
@@ -13818,7 +14576,7 @@ function resolveSource() {
|
|
|
13818
14576
|
const env = process.env;
|
|
13819
14577
|
if ((env["ROLL_LANG"] ?? "") !== "")
|
|
13820
14578
|
return "ROLL_LANG env";
|
|
13821
|
-
if (
|
|
14579
|
+
if (existsSync32(rollConfigPath4()) && configHasLangLine())
|
|
13822
14580
|
return `config (${rollConfigPath4()})`;
|
|
13823
14581
|
if ((env["LC_ALL"] ?? "") !== "" || (env["LANG"] ?? "") !== "")
|
|
13824
14582
|
return "LC_ALL/LANG";
|
|
@@ -13843,28 +14601,28 @@ function err10(line) {
|
|
|
13843
14601
|
}
|
|
13844
14602
|
function writeLang(value) {
|
|
13845
14603
|
const cfg = rollConfigPath4();
|
|
13846
|
-
|
|
13847
|
-
const existing =
|
|
14604
|
+
mkdirSync16(dirname12(cfg), { recursive: true });
|
|
14605
|
+
const existing = existsSync32(cfg) ? readFileSync25(cfg, "utf8") : "";
|
|
13848
14606
|
const kept = existing === "" ? [] : existing.split("\n").filter((l) => !/^lang:/.test(l));
|
|
13849
14607
|
if (kept.length > 0 && kept[kept.length - 1] === "")
|
|
13850
14608
|
kept.pop();
|
|
13851
14609
|
const body = kept.length > 0 ? kept.join("\n") + "\n" : "";
|
|
13852
|
-
const tmp =
|
|
13853
|
-
|
|
14610
|
+
const tmp = join29(mkdtempSync3(join29(tmpdir3(), "roll-lang-")), "config.yaml");
|
|
14611
|
+
writeFileSync17(tmp, `${body}lang: ${value}
|
|
13854
14612
|
`);
|
|
13855
14613
|
renameSync2(tmp, cfg);
|
|
13856
14614
|
}
|
|
13857
14615
|
function clearLang() {
|
|
13858
14616
|
const cfg = rollConfigPath4();
|
|
13859
|
-
if (!
|
|
14617
|
+
if (!existsSync32(cfg))
|
|
13860
14618
|
return;
|
|
13861
|
-
const existing =
|
|
14619
|
+
const existing = readFileSync25(cfg, "utf8");
|
|
13862
14620
|
const kept = existing.split("\n").filter((l) => !/^lang:/.test(l));
|
|
13863
14621
|
if (kept.length > 0 && kept[kept.length - 1] === "")
|
|
13864
14622
|
kept.pop();
|
|
13865
14623
|
const body = kept.length > 0 ? kept.join("\n") + "\n" : "";
|
|
13866
|
-
const tmp =
|
|
13867
|
-
|
|
14624
|
+
const tmp = join29(mkdtempSync3(join29(tmpdir3(), "roll-lang-")), "config.yaml");
|
|
14625
|
+
writeFileSync17(tmp, body);
|
|
13868
14626
|
renameSync2(tmp, cfg);
|
|
13869
14627
|
}
|
|
13870
14628
|
function langCommand(args) {
|
|
@@ -13964,9 +14722,9 @@ async function loopFmtCommand(args) {
|
|
|
13964
14722
|
|
|
13965
14723
|
// packages/cli/dist/commands/loop-pr-inbox.js
|
|
13966
14724
|
import { spawn as spawn2 } from "node:child_process";
|
|
13967
|
-
import { appendFileSync as appendFileSync5, existsSync as
|
|
13968
|
-
import { homedir as
|
|
13969
|
-
import { dirname as
|
|
14725
|
+
import { appendFileSync as appendFileSync5, existsSync as existsSync33, mkdirSync as mkdirSync17, readFileSync as readFileSync26, writeFileSync as writeFileSync18 } from "node:fs";
|
|
14726
|
+
import { homedir as homedir14 } from "node:os";
|
|
14727
|
+
import { dirname as dirname13, join as join30 } from "node:path";
|
|
13970
14728
|
function reducePrView(raw) {
|
|
13971
14729
|
const reviews = raw.reviews ?? [];
|
|
13972
14730
|
const botReviews = reviews.filter((r) => r.authorAssociation === "BOT" || r.authorAssociation === "APP");
|
|
@@ -14049,7 +14807,7 @@ function runtimeDir() {
|
|
|
14049
14807
|
const override = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
|
|
14050
14808
|
if (override !== "")
|
|
14051
14809
|
return override;
|
|
14052
|
-
return
|
|
14810
|
+
return join30(process.cwd(), ".roll", "loop");
|
|
14053
14811
|
}
|
|
14054
14812
|
function projSlug() {
|
|
14055
14813
|
const override = (process.env["ROLL_MAIN_SLUG"] ?? "").trim();
|
|
@@ -14058,26 +14816,26 @@ function projSlug() {
|
|
|
14058
14816
|
return process.cwd().split("/").filter(Boolean).pop() ?? "default";
|
|
14059
14817
|
}
|
|
14060
14818
|
function alertPath() {
|
|
14061
|
-
return
|
|
14819
|
+
return join30(runtimeDir(), `ALERT-${projSlug()}.md`);
|
|
14062
14820
|
}
|
|
14063
14821
|
function statePath() {
|
|
14064
|
-
return
|
|
14822
|
+
return join30(runtimeDir(), `state-${projSlug()}.yaml`);
|
|
14065
14823
|
}
|
|
14066
14824
|
function tickPath() {
|
|
14067
|
-
return
|
|
14825
|
+
return join30(runtimeDir(), "pr-tick.jsonl");
|
|
14068
14826
|
}
|
|
14069
14827
|
function engineBin() {
|
|
14070
|
-
let dir =
|
|
14828
|
+
let dir = dirname13(new URL(import.meta.url).pathname);
|
|
14071
14829
|
for (let i = 0; i < 10; i++) {
|
|
14072
|
-
const candidate =
|
|
14073
|
-
if (
|
|
14830
|
+
const candidate = join30(dir, "bin", "roll");
|
|
14831
|
+
if (existsSync33(candidate))
|
|
14074
14832
|
return candidate;
|
|
14075
|
-
const parent =
|
|
14833
|
+
const parent = dirname13(dir);
|
|
14076
14834
|
if (parent === dir)
|
|
14077
14835
|
break;
|
|
14078
14836
|
dir = parent;
|
|
14079
14837
|
}
|
|
14080
|
-
return
|
|
14838
|
+
return join30(homedir14(), ".local", "lib", "roll", "bin", "roll");
|
|
14081
14839
|
}
|
|
14082
14840
|
function pal2() {
|
|
14083
14841
|
return (process.env["NO_COLOR"] ?? "") !== "" ? { yellow: "", nc: "" } : { yellow: "\x1B[0;33m", nc: "\x1B[0m" };
|
|
@@ -14087,21 +14845,21 @@ function nowSec() {
|
|
|
14087
14845
|
}
|
|
14088
14846
|
function writeTickFile(tick) {
|
|
14089
14847
|
const file = tickPath();
|
|
14090
|
-
|
|
14848
|
+
mkdirSync17(dirname13(file), { recursive: true });
|
|
14091
14849
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
14092
14850
|
appendFileSync5(file, `${JSON.stringify({ ts, ...tick })}
|
|
14093
14851
|
`);
|
|
14094
14852
|
try {
|
|
14095
|
-
const lines =
|
|
14853
|
+
const lines = readFileSync26(file, "utf8").split("\n").filter((l) => l !== "");
|
|
14096
14854
|
if (lines.length > 500)
|
|
14097
|
-
|
|
14855
|
+
writeFileSync18(file, `${lines.slice(-500).join("\n")}
|
|
14098
14856
|
`);
|
|
14099
14857
|
} catch {
|
|
14100
14858
|
}
|
|
14101
14859
|
}
|
|
14102
14860
|
function appendAlert(line) {
|
|
14103
14861
|
const file = alertPath();
|
|
14104
|
-
|
|
14862
|
+
mkdirSync17(dirname13(file), { recursive: true });
|
|
14105
14863
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
14106
14864
|
appendFileSync5(file, `[${ts}] ${line}
|
|
14107
14865
|
`);
|
|
@@ -14110,16 +14868,16 @@ function rebaseCircuitAllowed(num2) {
|
|
|
14110
14868
|
const state = statePath();
|
|
14111
14869
|
let body = "";
|
|
14112
14870
|
try {
|
|
14113
|
-
body =
|
|
14871
|
+
body = readFileSync26(state, "utf8");
|
|
14114
14872
|
} catch {
|
|
14115
14873
|
}
|
|
14116
14874
|
const verdict = rebaseCircuitVerdict(parseRebaseAttempts(body, num2), nowSec());
|
|
14117
14875
|
writeRebaseAttempts(state, num2, verdict.freshTimestamps);
|
|
14118
14876
|
if (!verdict.allowed) {
|
|
14119
14877
|
const file = alertPath();
|
|
14120
|
-
|
|
14878
|
+
mkdirSync17(dirname13(file), { recursive: true });
|
|
14121
14879
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
|
|
14122
|
-
|
|
14880
|
+
writeFileSync18(file, [
|
|
14123
14881
|
`# ALERT \u2014 PR rebase circuit breaker tripped`,
|
|
14124
14882
|
``,
|
|
14125
14883
|
`**Time**: ${stamp}`,
|
|
@@ -14173,13 +14931,13 @@ function upsertRebaseAttempts(stateBody, pr, value) {
|
|
|
14173
14931
|
`;
|
|
14174
14932
|
}
|
|
14175
14933
|
function writeRebaseAttempts(state, pr, timestamps) {
|
|
14176
|
-
|
|
14934
|
+
mkdirSync17(dirname13(state), { recursive: true });
|
|
14177
14935
|
let body = "";
|
|
14178
14936
|
try {
|
|
14179
|
-
body =
|
|
14937
|
+
body = readFileSync26(state, "utf8");
|
|
14180
14938
|
} catch {
|
|
14181
14939
|
}
|
|
14182
|
-
|
|
14940
|
+
writeFileSync18(state, upsertRebaseAttempts(body, pr, renderRebaseAttempts(timestamps)));
|
|
14183
14941
|
}
|
|
14184
14942
|
function bridgeBash(args) {
|
|
14185
14943
|
return new Promise((resolve3) => {
|
|
@@ -14266,19 +15024,19 @@ async function loopPrInboxCommand(_args, deps = realDeps2()) {
|
|
|
14266
15024
|
}
|
|
14267
15025
|
|
|
14268
15026
|
// packages/cli/dist/commands/loop-run-once.js
|
|
14269
|
-
import { appendFileSync as appendFileSync7, existsSync as
|
|
14270
|
-
import { dirname 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";
|
|
14271
15029
|
|
|
14272
15030
|
// packages/cli/dist/runner/executor.js
|
|
14273
15031
|
import { execFile as execFile8 } from "node:child_process";
|
|
14274
|
-
import { appendFileSync as appendFileSync6, existsSync as
|
|
14275
|
-
import { dirname 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";
|
|
14276
15034
|
import { promisify as promisify8 } from "node:util";
|
|
14277
15035
|
|
|
14278
15036
|
// packages/cli/dist/runner/peer-gate.js
|
|
14279
15037
|
import { execFile as execFile7 } from "node:child_process";
|
|
14280
|
-
import { existsSync as
|
|
14281
|
-
import { join as
|
|
15038
|
+
import { existsSync as existsSync34, readdirSync as readdirSync15 } from "node:fs";
|
|
15039
|
+
import { join as join31 } from "node:path";
|
|
14282
15040
|
import { promisify as promisify7 } from "node:util";
|
|
14283
15041
|
var execFileAsync7 = promisify7(execFile7);
|
|
14284
15042
|
var HIGH_RISK = [/^\.github\/workflows\//, /^packages\/infra\/src\/(git|github|process)\.ts$/];
|
|
@@ -14308,8 +15066,8 @@ async function cycleChangedFiles(worktreeCwd) {
|
|
|
14308
15066
|
}
|
|
14309
15067
|
}
|
|
14310
15068
|
function peerEvidencePresent(runtimeDir3, cycleId) {
|
|
14311
|
-
const dir =
|
|
14312
|
-
if (!
|
|
15069
|
+
const dir = join31(runtimeDir3, "peer");
|
|
15070
|
+
if (!existsSync34(dir))
|
|
14313
15071
|
return false;
|
|
14314
15072
|
try {
|
|
14315
15073
|
return readdirSync15(dir).some((f) => f.startsWith(`cycle-${cycleId}.`));
|
|
@@ -14336,24 +15094,18 @@ async function runPeerGate(worktreeCwd, runtimeDir3, cycleId, sinks) {
|
|
|
14336
15094
|
}
|
|
14337
15095
|
|
|
14338
15096
|
// packages/cli/dist/runner/attest-gate.js
|
|
14339
|
-
import { existsSync as
|
|
14340
|
-
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";
|
|
14341
15099
|
function reportCandidates(worktreeCwd, storyId) {
|
|
14342
|
-
return [
|
|
14343
|
-
join31(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId)),
|
|
14344
|
-
join31(legacyArchiveDir(worktreeCwd, storyId), "latest", "report.html")
|
|
14345
|
-
];
|
|
15100
|
+
return [join32(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId))];
|
|
14346
15101
|
}
|
|
14347
15102
|
function acMapCandidates(worktreeCwd, storyId) {
|
|
14348
|
-
return [
|
|
14349
|
-
join31(cardArchiveDir(worktreeCwd, storyId), "ac-map.json"),
|
|
14350
|
-
join31(legacyArchiveDir(worktreeCwd, storyId), "ac-map.json")
|
|
14351
|
-
];
|
|
15103
|
+
return [join32(cardArchiveDir(worktreeCwd, storyId), "ac-map.json")];
|
|
14352
15104
|
}
|
|
14353
15105
|
function existingReport(worktreeCwd, storyId) {
|
|
14354
15106
|
for (const p of reportCandidates(worktreeCwd, storyId)) {
|
|
14355
15107
|
try {
|
|
14356
|
-
if (
|
|
15108
|
+
if (statSync17(p).isFile())
|
|
14357
15109
|
return p;
|
|
14358
15110
|
} catch {
|
|
14359
15111
|
}
|
|
@@ -14367,7 +15119,7 @@ function verificationReportFresh(worktreeCwd, storyId, sinceSec) {
|
|
|
14367
15119
|
if (p === null)
|
|
14368
15120
|
return false;
|
|
14369
15121
|
try {
|
|
14370
|
-
const st =
|
|
15122
|
+
const st = statSync17(p);
|
|
14371
15123
|
if (sinceSec === void 0)
|
|
14372
15124
|
return true;
|
|
14373
15125
|
return st.mtimeMs / 1e3 >= sinceSec;
|
|
@@ -14382,9 +15134,9 @@ function verificationReportHasContent(worktreeCwd, storyId) {
|
|
|
14382
15134
|
if (p === null)
|
|
14383
15135
|
return false;
|
|
14384
15136
|
try {
|
|
14385
|
-
const html =
|
|
15137
|
+
const html = readFileSync27(p, "utf8");
|
|
14386
15138
|
const hasAc = /<section class="ac[\s">]/.test(html);
|
|
14387
|
-
const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) =>
|
|
15139
|
+
const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) => existsSync35(m7));
|
|
14388
15140
|
return hasAc && hasMap;
|
|
14389
15141
|
} catch {
|
|
14390
15142
|
return false;
|
|
@@ -14392,10 +15144,10 @@ function verificationReportHasContent(worktreeCwd, storyId) {
|
|
|
14392
15144
|
}
|
|
14393
15145
|
function readAttestGateMode(repoCwd) {
|
|
14394
15146
|
try {
|
|
14395
|
-
const p =
|
|
14396
|
-
if (!
|
|
15147
|
+
const p = join32(repoCwd, ".roll", "policy.yaml");
|
|
15148
|
+
if (!existsSync35(p))
|
|
14397
15149
|
return "soft";
|
|
14398
|
-
return parsePolicy(
|
|
15150
|
+
return parsePolicy(readFileSync27(p, "utf8")).loopSafety.attestGate === "hard" ? "hard" : "soft";
|
|
14399
15151
|
} catch {
|
|
14400
15152
|
return "soft";
|
|
14401
15153
|
}
|
|
@@ -14503,9 +15255,9 @@ async function executeCommand(cmd, ports, ctx) {
|
|
|
14503
15255
|
// execute: spawn the agent (TCR commits happen inside the worktree). The
|
|
14504
15256
|
// exit code + timeout feed back as agent_exited; usage is captured for cost.
|
|
14505
15257
|
case "spawn_agent": {
|
|
14506
|
-
const livePath =
|
|
15258
|
+
const livePath = join33(dirname14(ports.paths.eventsPath), "live.log");
|
|
14507
15259
|
try {
|
|
14508
|
-
|
|
15260
|
+
writeFileSync19(livePath, `\u2500\u2500 cycle ${ctx.cycleId ?? "?"} \xB7 ${ctx.storyId ?? "?"} \xB7 agent ${cmd.agent} \u2500\u2500
|
|
14509
15261
|
`);
|
|
14510
15262
|
} catch {
|
|
14511
15263
|
}
|
|
@@ -14523,9 +15275,9 @@ async function executeCommand(cmd, ports, ctx) {
|
|
|
14523
15275
|
}
|
|
14524
15276
|
});
|
|
14525
15277
|
try {
|
|
14526
|
-
const logDir =
|
|
14527
|
-
|
|
14528
|
-
|
|
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}
|
|
14529
15281
|
--- stdout ---
|
|
14530
15282
|
${res.stdout}
|
|
14531
15283
|
--- stderr ---
|
|
@@ -14572,7 +15324,7 @@ ${res.stderr}
|
|
|
14572
15324
|
tcrCount = await ports.git.tcrCount(ports.paths.worktreePath);
|
|
14573
15325
|
} catch {
|
|
14574
15326
|
}
|
|
14575
|
-
await runPeerGate(ports.paths.worktreePath,
|
|
15327
|
+
await runPeerGate(ports.paths.worktreePath, dirname14(ports.paths.eventsPath), ctx.cycleId ?? "", {
|
|
14576
15328
|
alert: (m7) => ports.events.appendAlert(ports.paths.alertsPath, m7),
|
|
14577
15329
|
event: (p) => ports.events.appendEvent(ports.paths.eventsPath, {
|
|
14578
15330
|
type: "peer:gate",
|
|
@@ -14648,7 +15400,7 @@ ${res.stderr}
|
|
|
14648
15400
|
// _worktree_cleanup (tolerant). Side effect; no feedback (terminal path).
|
|
14649
15401
|
case "cleanup_worktree":
|
|
14650
15402
|
try {
|
|
14651
|
-
const dst =
|
|
15403
|
+
const dst = join33(ports.paths.worktreePath, ".roll");
|
|
14652
15404
|
if (lstatSync2(dst, { throwIfNoEntry: false })?.isSymbolicLink() === true)
|
|
14653
15405
|
unlinkSync(dst);
|
|
14654
15406
|
} catch {
|
|
@@ -14720,9 +15472,9 @@ function buildRunRow(cmd, ctx, nowSec2) {
|
|
|
14720
15472
|
}
|
|
14721
15473
|
function readRunsRows(runsPath) {
|
|
14722
15474
|
try {
|
|
14723
|
-
if (!
|
|
15475
|
+
if (!existsSync36(runsPath))
|
|
14724
15476
|
return [];
|
|
14725
|
-
return
|
|
15477
|
+
return readFileSync28(runsPath, "utf8").split("\n").filter((l) => l.trim() !== "").map((l) => {
|
|
14726
15478
|
try {
|
|
14727
15479
|
return JSON.parse(l);
|
|
14728
15480
|
} catch {
|
|
@@ -14745,15 +15497,15 @@ function sleep(ms) {
|
|
|
14745
15497
|
}
|
|
14746
15498
|
async function linkRollIntoWorktree(repoCwd, worktreePath) {
|
|
14747
15499
|
try {
|
|
14748
|
-
const src =
|
|
14749
|
-
const dst =
|
|
14750
|
-
if (!
|
|
15500
|
+
const src = join33(repoCwd, ".roll");
|
|
15501
|
+
const dst = join33(worktreePath, ".roll");
|
|
15502
|
+
if (!existsSync36(src))
|
|
14751
15503
|
return;
|
|
14752
15504
|
const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
|
|
14753
15505
|
if (dstStat) {
|
|
14754
15506
|
if (dstStat.isSymbolicLink())
|
|
14755
15507
|
return;
|
|
14756
|
-
const incompleteFossil =
|
|
15508
|
+
const incompleteFossil = existsSync36(join33(src, "backlog.md")) && !existsSync36(join33(dst, "backlog.md"));
|
|
14757
15509
|
if (!incompleteFossil)
|
|
14758
15510
|
return;
|
|
14759
15511
|
rmSync8(dst, { recursive: true, force: true });
|
|
@@ -14762,10 +15514,10 @@ async function linkRollIntoWorktree(repoCwd, worktreePath) {
|
|
|
14762
15514
|
const common = (await execFileAsync8("git", ["-C", repoCwd, "rev-parse", "--path-format=absolute", "--git-common-dir"])).stdout.trim();
|
|
14763
15515
|
if (common === "")
|
|
14764
15516
|
return;
|
|
14765
|
-
const exclude =
|
|
14766
|
-
const cur =
|
|
15517
|
+
const exclude = join33(common, "info", "exclude");
|
|
15518
|
+
const cur = existsSync36(exclude) ? readFileSync28(exclude, "utf8") : "";
|
|
14767
15519
|
if (!/^\.roll$/m.test(cur)) {
|
|
14768
|
-
|
|
15520
|
+
mkdirSync18(dirname14(exclude), { recursive: true });
|
|
14769
15521
|
appendFileSync6(exclude, `${cur === "" || cur.endsWith("\n") ? "" : "\n"}.roll
|
|
14770
15522
|
`, "utf8");
|
|
14771
15523
|
}
|
|
@@ -14854,10 +15606,10 @@ function nodePorts(opts) {
|
|
|
14854
15606
|
},
|
|
14855
15607
|
backlog: {
|
|
14856
15608
|
read(projectCwd) {
|
|
14857
|
-
const p =
|
|
14858
|
-
if (!
|
|
15609
|
+
const p = join33(projectCwd, ".roll", "backlog.md");
|
|
15610
|
+
if (!existsSync36(p))
|
|
14859
15611
|
return [];
|
|
14860
|
-
return parseBacklog(
|
|
15612
|
+
return parseBacklog(readFileSync28(p, "utf8"));
|
|
14861
15613
|
},
|
|
14862
15614
|
// FIX-198: the production binding was MISSING entirely (the optional
|
|
14863
15615
|
// chain made every In-Progress claim a silent no-op). ID-anchored mark
|
|
@@ -14865,8 +15617,8 @@ function nodePorts(opts) {
|
|
|
14865
15617
|
// never kill the cycle, the reconcile pass is the safety net.
|
|
14866
15618
|
markStatus(projectCwd, id, status2) {
|
|
14867
15619
|
try {
|
|
14868
|
-
const p =
|
|
14869
|
-
if (!
|
|
15620
|
+
const p = join33(projectCwd, ".roll", "backlog.md");
|
|
15621
|
+
if (!existsSync36(p))
|
|
14870
15622
|
return;
|
|
14871
15623
|
const store = new BacklogStore();
|
|
14872
15624
|
const snap = store.readBacklog(p);
|
|
@@ -14886,7 +15638,7 @@ function nodePorts(opts) {
|
|
|
14886
15638
|
};
|
|
14887
15639
|
}
|
|
14888
15640
|
function appendAlertLine(alertsPath, message) {
|
|
14889
|
-
|
|
15641
|
+
mkdirSync18(dirname14(alertsPath), { recursive: true });
|
|
14890
15642
|
appendFileSync6(alertsPath, `${message}
|
|
14891
15643
|
`, "utf8");
|
|
14892
15644
|
}
|
|
@@ -15063,6 +15815,7 @@ function describeCommand(cmd) {
|
|
|
15063
15815
|
|
|
15064
15816
|
// packages/cli/dist/commands/loop-run-once.js
|
|
15065
15817
|
import { spawn as spawn3 } from "node:child_process";
|
|
15818
|
+
import { lookup } from "node:dns/promises";
|
|
15066
15819
|
function announceReport(projectPath, slug, storyId, opener = (p) => {
|
|
15067
15820
|
try {
|
|
15068
15821
|
spawn3("open", [p], { stdio: "ignore", detached: true }).unref();
|
|
@@ -15071,13 +15824,13 @@ function announceReport(projectPath, slug, storyId, opener = (p) => {
|
|
|
15071
15824
|
}) {
|
|
15072
15825
|
if (storyId === "")
|
|
15073
15826
|
return null;
|
|
15074
|
-
const report =
|
|
15075
|
-
if (!
|
|
15827
|
+
const report = join34(cardArchiveDir(projectPath, storyId), "latest", reportFileName(storyId));
|
|
15828
|
+
if (!existsSync37(report))
|
|
15076
15829
|
return null;
|
|
15077
15830
|
process.stdout.write(`evidence: ${report}
|
|
15078
15831
|
\u9A8C\u6536\u62A5\u544A: ${report}
|
|
15079
15832
|
`);
|
|
15080
|
-
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}`));
|
|
15081
15834
|
if (!muted)
|
|
15082
15835
|
opener(report);
|
|
15083
15836
|
return report;
|
|
@@ -15094,7 +15847,7 @@ function cycleSignalTeardown(paths, cycleId, branch, sig, deps = {}) {
|
|
|
15094
15847
|
}
|
|
15095
15848
|
let owned = false;
|
|
15096
15849
|
try {
|
|
15097
|
-
owned =
|
|
15850
|
+
owned = existsSync37(paths.lockPath) && parseLock(readFileSync29(paths.lockPath, "utf8")).pid === pid;
|
|
15098
15851
|
} catch {
|
|
15099
15852
|
owned = false;
|
|
15100
15853
|
}
|
|
@@ -15139,27 +15892,27 @@ function makeCycleId(now = /* @__PURE__ */ new Date(), pid = process.pid) {
|
|
|
15139
15892
|
}
|
|
15140
15893
|
function runtimeDir2(projectPath) {
|
|
15141
15894
|
const env = (process.env["ROLL_PROJECT_RUNTIME_DIR"] ?? "").trim();
|
|
15142
|
-
return env !== "" ? env :
|
|
15895
|
+
return env !== "" ? env : join34(projectPath, ".roll", "loop");
|
|
15143
15896
|
}
|
|
15144
15897
|
var PAUSE_THRESHOLD = 3;
|
|
15145
15898
|
function incrementConsecutiveFails(projectPath, slug, alertsPath, cycleId, storyId, terminal) {
|
|
15146
15899
|
const rt = runtimeDir2(projectPath);
|
|
15147
|
-
const counterFile =
|
|
15900
|
+
const counterFile = join34(rt, "consecutive-fails");
|
|
15148
15901
|
let count = 0;
|
|
15149
15902
|
try {
|
|
15150
|
-
if (
|
|
15151
|
-
count = parseInt(
|
|
15903
|
+
if (existsSync37(counterFile)) {
|
|
15904
|
+
count = parseInt(readFileSync29(counterFile, "utf8").trim(), 10) || 0;
|
|
15152
15905
|
}
|
|
15153
15906
|
} catch {
|
|
15154
15907
|
}
|
|
15155
15908
|
count += 1;
|
|
15156
15909
|
try {
|
|
15157
|
-
|
|
15910
|
+
writeFileSync20(counterFile, String(count), "utf8");
|
|
15158
15911
|
} catch {
|
|
15159
15912
|
}
|
|
15160
15913
|
if (count < PAUSE_THRESHOLD)
|
|
15161
15914
|
return;
|
|
15162
|
-
const pauseMarker =
|
|
15915
|
+
const pauseMarker = join34(projectPath, ".roll", "loop", `PAUSE-${slug}`);
|
|
15163
15916
|
const alertMsg = `# ALERT \u2014 loop auto-paused after ${count} consecutive failures
|
|
15164
15917
|
|
|
15165
15918
|
**Cycle**: ${cycleId}
|
|
@@ -15169,7 +15922,7 @@ function incrementConsecutiveFails(projectPath, slug, alertsPath, cycleId, story
|
|
|
15169
15922
|
Resolve the root cause, then: \`roll loop resume\`
|
|
15170
15923
|
`;
|
|
15171
15924
|
try {
|
|
15172
|
-
|
|
15925
|
+
writeFileSync20(pauseMarker, alertMsg, "utf8");
|
|
15173
15926
|
appendFileSync7(alertsPath, `${alertMsg}
|
|
15174
15927
|
`, "utf8");
|
|
15175
15928
|
} catch {
|
|
@@ -15181,7 +15934,7 @@ loop run-once: \u8FDE\u7EED ${count} \u6B21\u5931\u8D25\u540E\u81EA\u52A8\u6682\
|
|
|
15181
15934
|
function resetConsecutiveFails(projectPath) {
|
|
15182
15935
|
const rt = runtimeDir2(projectPath);
|
|
15183
15936
|
try {
|
|
15184
|
-
|
|
15937
|
+
writeFileSync20(join34(rt, "consecutive-fails"), "0", "utf8");
|
|
15185
15938
|
} catch {
|
|
15186
15939
|
}
|
|
15187
15940
|
}
|
|
@@ -15191,6 +15944,38 @@ function readSkillBody2(projectPath) {
|
|
|
15191
15944
|
envOverride: process.env["ROLL_LOOP_SKILL"]
|
|
15192
15945
|
});
|
|
15193
15946
|
}
|
|
15947
|
+
function buildLoopRouteDeps(projectPath) {
|
|
15948
|
+
function readSlot(slot) {
|
|
15949
|
+
const agentsYaml = join34(projectPath, ".roll", "agents.yaml");
|
|
15950
|
+
try {
|
|
15951
|
+
return readSlotFromText(readFileSync29(agentsYaml, "utf8"), slot);
|
|
15952
|
+
} catch {
|
|
15953
|
+
return void 0;
|
|
15954
|
+
}
|
|
15955
|
+
}
|
|
15956
|
+
function firstInstalled() {
|
|
15957
|
+
const fromLocal = readField(join34(projectPath, ".roll", "local.yaml"), /^agent:/);
|
|
15958
|
+
if (fromLocal !== void 0)
|
|
15959
|
+
return fromLocal;
|
|
15960
|
+
return firstInstalledAgent(realAgentEnv());
|
|
15961
|
+
}
|
|
15962
|
+
return { readSlot, firstInstalled };
|
|
15963
|
+
}
|
|
15964
|
+
function readField(path, re) {
|
|
15965
|
+
try {
|
|
15966
|
+
const text = readFileSync29(path, "utf8");
|
|
15967
|
+
for (const line of text.split("\n")) {
|
|
15968
|
+
const m7 = line.match(re);
|
|
15969
|
+
if (m7 !== null) {
|
|
15970
|
+
const v = line.slice((m7.index ?? 0) + m7[0].length).trim();
|
|
15971
|
+
if (v !== "")
|
|
15972
|
+
return v.replace(/^["']|["']$/g, "");
|
|
15973
|
+
}
|
|
15974
|
+
}
|
|
15975
|
+
} catch {
|
|
15976
|
+
}
|
|
15977
|
+
return void 0;
|
|
15978
|
+
}
|
|
15194
15979
|
async function loopRunOnceCommand(args) {
|
|
15195
15980
|
const dryRun = args.includes("--dry-run");
|
|
15196
15981
|
const id = await projectIdentity();
|
|
@@ -15214,15 +15999,15 @@ async function loopRunOnceCommand(args) {
|
|
|
15214
15999
|
return 0;
|
|
15215
16000
|
}
|
|
15216
16001
|
const rt = runtimeDir2(id.path);
|
|
15217
|
-
const alertsPath =
|
|
15218
|
-
|
|
16002
|
+
const alertsPath = join34(rt, `ALERT-${id.slug}.md`);
|
|
16003
|
+
mkdirSync19(dirname15(alertsPath), { recursive: true });
|
|
15219
16004
|
const paths = {
|
|
15220
|
-
eventsPath:
|
|
15221
|
-
runsPath:
|
|
16005
|
+
eventsPath: join34(rt, "events.ndjson"),
|
|
16006
|
+
runsPath: join34(rt, "runs.jsonl"),
|
|
15222
16007
|
alertsPath,
|
|
15223
|
-
lockPath:
|
|
15224
|
-
heartbeatPath:
|
|
15225
|
-
worktreePath:
|
|
16008
|
+
lockPath: join34(rt, "inner.lock"),
|
|
16009
|
+
heartbeatPath: join34(rt, "heartbeat"),
|
|
16010
|
+
worktreePath: join34(rt, "worktrees", `cycle-${cycleId}`)
|
|
15226
16011
|
};
|
|
15227
16012
|
const skillBody = readSkillBody2(id.path);
|
|
15228
16013
|
if (skillBody === null) {
|
|
@@ -15238,15 +16023,16 @@ loop run-once: \u627E\u4E0D\u5230 roll-loop SKILL.md \u2014 \u62D2\u7EDD\u76F2\u
|
|
|
15238
16023
|
incrementConsecutiveFails(id.path, id.slug, alertsPath, cycleId, "", "skill_missing");
|
|
15239
16024
|
return 1;
|
|
15240
16025
|
}
|
|
15241
|
-
const routeDeps =
|
|
15242
|
-
|
|
15243
|
-
firstInstalled: () => process.env["ROLL_LOOP_AGENT"] ?? "claude"
|
|
15244
|
-
};
|
|
16026
|
+
const routeDeps = buildLoopRouteDeps(id.path);
|
|
16027
|
+
const isInteractive = (process.env["ROLL_LOOP_FORCE"] ?? "").trim() !== "";
|
|
15245
16028
|
const ports = nodePorts({
|
|
15246
16029
|
repoCwd: id.path,
|
|
15247
16030
|
paths,
|
|
15248
16031
|
skillBody,
|
|
15249
|
-
routeDeps
|
|
16032
|
+
routeDeps,
|
|
16033
|
+
...isInteractive ? {
|
|
16034
|
+
agentSpawn: (agent, opts) => realAgentSpawn(agent, { ...opts, interactive: true })
|
|
16035
|
+
} : {}
|
|
15250
16036
|
});
|
|
15251
16037
|
const disposeSignals = installCycleSignalTeardown(paths, cycleId, branch);
|
|
15252
16038
|
let result;
|
|
@@ -15269,24 +16055,43 @@ loop run-once: \u627E\u4E0D\u5230 roll-loop SKILL.md \u2014 \u62D2\u7EDD\u76F2\u
|
|
|
15269
16055
|
}
|
|
15270
16056
|
const isFail = result.terminal === "failed" || result.terminal === "blocked";
|
|
15271
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
|
+
}
|
|
15272
16062
|
const storyId = (result.state?.ctx?.storyId ?? "").trim();
|
|
15273
16063
|
incrementConsecutiveFails(id.path, id.slug, alertsPath, cycleId, storyId, result.terminal ?? "unknown");
|
|
15274
16064
|
}
|
|
15275
16065
|
return isFail ? 1 : 0;
|
|
15276
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
|
+
}
|
|
15277
16082
|
|
|
15278
16083
|
// packages/cli/dist/commands/loop-sched.js
|
|
15279
16084
|
import { createHash as createHash4 } from "node:crypto";
|
|
15280
16085
|
import { spawn as spawn4, spawnSync as spawnSync6 } from "node:child_process";
|
|
15281
|
-
import { existsSync as
|
|
15282
|
-
import { homedir as
|
|
15283
|
-
import { dirname as
|
|
16086
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync20, readFileSync as readFileSync30, rmSync as rmSync9, writeFileSync as writeFileSync21 } from "node:fs";
|
|
16087
|
+
import { homedir as homedir15 } from "node:os";
|
|
16088
|
+
import { dirname as dirname16, join as join35 } from "node:path";
|
|
15284
16089
|
function realDeps3() {
|
|
15285
16090
|
return {
|
|
15286
16091
|
identity: () => projectIdentity(),
|
|
15287
16092
|
uid: () => process.getuid?.() ?? 501,
|
|
15288
|
-
sharedRoot: () => process.env["ROLL_SHARED_ROOT"] ||
|
|
15289
|
-
launchdDir: () =>
|
|
16093
|
+
sharedRoot: () => process.env["ROLL_SHARED_ROOT"] || join35(homedir15(), ".shared", "roll"),
|
|
16094
|
+
launchdDir: () => join35(homedir15(), "Library", "LaunchAgents"),
|
|
15290
16095
|
launchd: { reinstall, uninstall, isLoaded },
|
|
15291
16096
|
execRunner: (runner) => new Promise((resolve3) => {
|
|
15292
16097
|
const child = spawn4("bash", [runner], {
|
|
@@ -15306,8 +16111,8 @@ function realDeps3() {
|
|
|
15306
16111
|
// The `loop now` inline observation: tail live.log while the cycle holds
|
|
15307
16112
|
// the inner lock; Ctrl-C stops the TAIL only (the cycle lives in tmux).
|
|
15308
16113
|
observe: (rt) => new Promise((resolve3) => {
|
|
15309
|
-
const lock =
|
|
15310
|
-
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" });
|
|
15311
16116
|
let sawLock = false;
|
|
15312
16117
|
const t0 = Date.now();
|
|
15313
16118
|
const finish = () => {
|
|
@@ -15319,9 +16124,9 @@ function realDeps3() {
|
|
|
15319
16124
|
resolve3();
|
|
15320
16125
|
};
|
|
15321
16126
|
const timer = setInterval(() => {
|
|
15322
|
-
if (
|
|
16127
|
+
if (existsSync38(lock))
|
|
15323
16128
|
sawLock = true;
|
|
15324
|
-
const done = sawLock ? !
|
|
16129
|
+
const done = sawLock ? !existsSync38(lock) : Date.now() - t0 > 3e4;
|
|
15325
16130
|
if (done) {
|
|
15326
16131
|
clearInterval(timer);
|
|
15327
16132
|
finish();
|
|
@@ -15478,7 +16283,7 @@ function parseLoopPeriodMinutes(text) {
|
|
|
15478
16283
|
return 30;
|
|
15479
16284
|
}
|
|
15480
16285
|
function pathValue() {
|
|
15481
|
-
const home =
|
|
16286
|
+
const home = homedir15();
|
|
15482
16287
|
return [
|
|
15483
16288
|
"/opt/homebrew/bin",
|
|
15484
16289
|
`${home}/.local/bin`,
|
|
@@ -15490,11 +16295,11 @@ function pathValue() {
|
|
|
15490
16295
|
].join(":");
|
|
15491
16296
|
}
|
|
15492
16297
|
function writeExecutable(path, content) {
|
|
15493
|
-
|
|
15494
|
-
|
|
16298
|
+
mkdirSync20(dirname16(path), { recursive: true });
|
|
16299
|
+
writeFileSync21(path, content, { mode: 493 });
|
|
15495
16300
|
}
|
|
15496
16301
|
function pauseMarkerPath(projectPath, slug) {
|
|
15497
|
-
return
|
|
16302
|
+
return join35(projectPath, ".roll", "loop", `PAUSE-${slug}`);
|
|
15498
16303
|
}
|
|
15499
16304
|
var LOOP_SERVICES = ["loop", "dream", "pr"];
|
|
15500
16305
|
async function mountService(deps, uid, label4, plist) {
|
|
@@ -15513,16 +16318,16 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15513
16318
|
const shared = deps.sharedRoot();
|
|
15514
16319
|
const ld = deps.launchdDir();
|
|
15515
16320
|
const uid = deps.uid();
|
|
15516
|
-
|
|
16321
|
+
mkdirSync20(ld, { recursive: true });
|
|
15517
16322
|
let period = 30;
|
|
15518
|
-
const localYaml =
|
|
15519
|
-
if (
|
|
16323
|
+
const localYaml = join35(id.path, ".roll", "local.yaml");
|
|
16324
|
+
if (existsSync38(localYaml)) {
|
|
15520
16325
|
try {
|
|
15521
|
-
period = parseLoopPeriodMinutes(
|
|
16326
|
+
period = parseLoopPeriodMinutes(readFileSync30(localYaml, "utf8"));
|
|
15522
16327
|
} catch {
|
|
15523
16328
|
}
|
|
15524
16329
|
}
|
|
15525
|
-
const loopRunner =
|
|
16330
|
+
const loopRunner = join35(shared, "loop", `run-${id.slug}.sh`);
|
|
15526
16331
|
const rollBinOverride = (process.env["ROLL_RUNNER_ROLL_BIN"] ?? "").trim();
|
|
15527
16332
|
writeExecutable(loopRunner, buildLoopRunnerScript({
|
|
15528
16333
|
projectPath: id.path,
|
|
@@ -15533,7 +16338,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15533
16338
|
}));
|
|
15534
16339
|
const loopLabel = launchdLabel("loop", id.slug);
|
|
15535
16340
|
const loopPlist = launchdPlistPath("loop", id.slug, ld);
|
|
15536
|
-
|
|
16341
|
+
writeFileSync21(loopPlist, plistContent({
|
|
15537
16342
|
label: loopLabel,
|
|
15538
16343
|
runnerScript: loopRunner,
|
|
15539
16344
|
projectPath: id.path,
|
|
@@ -15541,14 +16346,14 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15541
16346
|
schedule: { kind: "interval", periodMinutes: period }
|
|
15542
16347
|
}));
|
|
15543
16348
|
const loopMount = await mountService(deps, uid, loopLabel, loopPlist);
|
|
15544
|
-
const prRunner =
|
|
16349
|
+
const prRunner = join35(shared, "pr", `run-${id.slug}.sh`);
|
|
15545
16350
|
writeExecutable(prRunner, buildPrRunnerScript({
|
|
15546
16351
|
projectPath: id.path,
|
|
15547
16352
|
...rollBinOverride !== "" ? { rollBin: rollBinOverride } : {}
|
|
15548
16353
|
}));
|
|
15549
16354
|
const prLabel = launchdLabel("pr", id.slug);
|
|
15550
16355
|
const prPlist = launchdPlistPath("pr", id.slug, ld);
|
|
15551
|
-
|
|
16356
|
+
writeFileSync21(prPlist, plistContent({
|
|
15552
16357
|
label: prLabel,
|
|
15553
16358
|
runnerScript: prRunner,
|
|
15554
16359
|
projectPath: id.path,
|
|
@@ -15557,7 +16362,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15557
16362
|
}));
|
|
15558
16363
|
const prMount = await mountService(deps, uid, prLabel, prPlist);
|
|
15559
16364
|
const dream = dreamScheduleFor(id.path);
|
|
15560
|
-
const dreamRunner =
|
|
16365
|
+
const dreamRunner = join35(shared, "dream", `run-${id.slug}.sh`);
|
|
15561
16366
|
writeExecutable(dreamRunner, buildDreamRunnerScript({
|
|
15562
16367
|
projectPath: id.path,
|
|
15563
16368
|
slug: id.slug,
|
|
@@ -15565,7 +16370,7 @@ async function loopOnCommand(_args, deps = realDeps3()) {
|
|
|
15565
16370
|
}));
|
|
15566
16371
|
const dreamLabel = launchdLabel("dream", id.slug);
|
|
15567
16372
|
const dreamPlist = launchdPlistPath("dream", id.slug, ld);
|
|
15568
|
-
|
|
16373
|
+
writeFileSync21(dreamPlist, plistContent({
|
|
15569
16374
|
label: dreamLabel,
|
|
15570
16375
|
runnerScript: dreamRunner,
|
|
15571
16376
|
projectPath: id.path,
|
|
@@ -15617,10 +16422,10 @@ Loop \u5DF2\u505C\u7528(loop/dream/pr \u5747\u5DF2\u5378\u8F7D)
|
|
|
15617
16422
|
async function loopPauseCommand(_args, deps = realDeps3()) {
|
|
15618
16423
|
const id = await deps.identity();
|
|
15619
16424
|
const marker = pauseMarkerPath(id.path, id.slug);
|
|
15620
|
-
|
|
15621
|
-
const already =
|
|
16425
|
+
mkdirSync20(dirname16(marker), { recursive: true });
|
|
16426
|
+
const already = existsSync38(marker);
|
|
15622
16427
|
if (!already)
|
|
15623
|
-
|
|
16428
|
+
writeFileSync21(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
15624
16429
|
`);
|
|
15625
16430
|
process.stdout.write(already ? `Loop already paused
|
|
15626
16431
|
Loop \u5DF2\u5904\u4E8E\u6682\u505C
|
|
@@ -15632,7 +16437,7 @@ Loop \u5DF2\u6682\u505C \u2014 \u540E\u7EED\u6392\u7A0B\u5468\u671F\u5C06\u8DF3\
|
|
|
15632
16437
|
async function loopResumeCommand(_args, deps = realDeps3()) {
|
|
15633
16438
|
const id = await deps.identity();
|
|
15634
16439
|
const marker = pauseMarkerPath(id.path, id.slug);
|
|
15635
|
-
const existed =
|
|
16440
|
+
const existed = existsSync38(marker);
|
|
15636
16441
|
rmSync9(marker, { force: true });
|
|
15637
16442
|
process.stdout.write(existed ? `Loop resumed \u2014 scheduling active again
|
|
15638
16443
|
Loop \u5DF2\u6062\u590D \u2014 \u6392\u7A0B\u91CD\u65B0\u751F\u6548
|
|
@@ -15650,16 +16455,16 @@ function isLegacyRunner(text) {
|
|
|
15650
16455
|
}
|
|
15651
16456
|
async function loopNowCommand(_args, deps = realDeps3()) {
|
|
15652
16457
|
const id = await deps.identity();
|
|
15653
|
-
const runner =
|
|
16458
|
+
const runner = join35(deps.sharedRoot(), "loop", `run-${id.slug}.sh`);
|
|
15654
16459
|
let legacy = false;
|
|
15655
|
-
if (
|
|
16460
|
+
if (existsSync38(runner)) {
|
|
15656
16461
|
try {
|
|
15657
|
-
legacy = isLegacyRunner(
|
|
16462
|
+
legacy = isLegacyRunner(readFileSync30(runner, "utf8"));
|
|
15658
16463
|
} catch {
|
|
15659
16464
|
legacy = true;
|
|
15660
16465
|
}
|
|
15661
16466
|
}
|
|
15662
|
-
if (!
|
|
16467
|
+
if (!existsSync38(runner) || legacy) {
|
|
15663
16468
|
process.stdout.write(legacy ? `Legacy v2 runner detected \u2014 regenerating templates (FIX-197)
|
|
15664
16469
|
\u68C0\u6D4B\u5230 v2 \u65E7\u7248 runner \u2014 \u6B63\u5728\u518D\u751F\u6210\u6A21\u677F\uFF08FIX-197\uFF09
|
|
15665
16470
|
` : `No runner yet \u2014 generating templates
|
|
@@ -15687,7 +16492,7 @@ live transcript below \u2014 Ctrl-C stops watching, never the cycle
|
|
|
15687
16492
|
}
|
|
15688
16493
|
const rc = await exec(runner);
|
|
15689
16494
|
if (rc === 0 && useTmux && deps.observe !== void 0) {
|
|
15690
|
-
await deps.observe(
|
|
16495
|
+
await deps.observe(join35(id.path, ".roll", "loop"));
|
|
15691
16496
|
process.stdout.write(`
|
|
15692
16497
|
cycle finished \u2014 logs: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
15693
16498
|
\u5468\u671F\u7ED3\u675F \u2014 \u65E5\u5FD7: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
@@ -15698,8 +16503,8 @@ cycle finished \u2014 logs: .roll/loop/cron.log \xB7 .roll/loop/cycle-logs/
|
|
|
15698
16503
|
|
|
15699
16504
|
// packages/cli/dist/commands/migrate.js
|
|
15700
16505
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
15701
|
-
import { existsSync as
|
|
15702
|
-
import { dirname as
|
|
16506
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync21 } from "node:fs";
|
|
16507
|
+
import { dirname as dirname17 } from "node:path";
|
|
15703
16508
|
function colors() {
|
|
15704
16509
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
15705
16510
|
return noColor2 ? { RED: "", GREEN: "", YELLOW: "", CYAN: "", NC: "" } : {
|
|
@@ -15818,9 +16623,9 @@ function migrateExecute(activeMoves) {
|
|
|
15818
16623
|
for (const move of activeMoves) {
|
|
15819
16624
|
const src = srcOf(move);
|
|
15820
16625
|
const tgt = tgtOf(move);
|
|
15821
|
-
const targetDir =
|
|
15822
|
-
if (!
|
|
15823
|
-
|
|
16626
|
+
const targetDir = dirname17(tgt);
|
|
16627
|
+
if (!existsSync39(targetDir))
|
|
16628
|
+
mkdirSync21(targetDir, { recursive: true });
|
|
15824
16629
|
const r = git3(["mv", src, tgt]);
|
|
15825
16630
|
if (r.status !== 0) {
|
|
15826
16631
|
err11(`git mv failed: ${src} \u2192 ${tgt}`);
|
|
@@ -15829,7 +16634,7 @@ function migrateExecute(activeMoves) {
|
|
|
15829
16634
|
}
|
|
15830
16635
|
moved += 1;
|
|
15831
16636
|
}
|
|
15832
|
-
if (
|
|
16637
|
+
if (existsSync39("docs")) {
|
|
15833
16638
|
spawnSync7("find", ["docs", "-type", "d", "-empty", "-delete"], { stdio: "ignore" });
|
|
15834
16639
|
}
|
|
15835
16640
|
git3([
|
|
@@ -15870,10 +16675,10 @@ function migrateCommand(args) {
|
|
|
15870
16675
|
const moves = buildMoves();
|
|
15871
16676
|
let hasNew = false;
|
|
15872
16677
|
let hasOld = false;
|
|
15873
|
-
if (
|
|
16678
|
+
if (existsSync39(".roll"))
|
|
15874
16679
|
hasNew = true;
|
|
15875
16680
|
for (const move of moves) {
|
|
15876
|
-
if (
|
|
16681
|
+
if (existsSync39(srcOf(move))) {
|
|
15877
16682
|
hasOld = true;
|
|
15878
16683
|
break;
|
|
15879
16684
|
}
|
|
@@ -15885,7 +16690,7 @@ function migrateCommand(args) {
|
|
|
15885
16690
|
for (const move of moves) {
|
|
15886
16691
|
const src = srcOf(move);
|
|
15887
16692
|
const tgt = tgtOf(move);
|
|
15888
|
-
if (
|
|
16693
|
+
if (existsSync39(src) && existsSync39(tgt)) {
|
|
15889
16694
|
process.stderr.write(` - ${src} AND ${tgt} both exist
|
|
15890
16695
|
`);
|
|
15891
16696
|
}
|
|
@@ -15902,7 +16707,7 @@ function migrateCommand(args) {
|
|
|
15902
16707
|
info3(m2("migrate.no_old_structure_detected_nothing_to"));
|
|
15903
16708
|
return 0;
|
|
15904
16709
|
}
|
|
15905
|
-
const activeMoves = moves.filter((move) =>
|
|
16710
|
+
const activeMoves = moves.filter((move) => existsSync39(srcOf(move)));
|
|
15906
16711
|
if (activeMoves.length === 0) {
|
|
15907
16712
|
warn4(m2("migrate.old_structure_markers_found_but_no"));
|
|
15908
16713
|
return 0;
|
|
@@ -15919,11 +16724,11 @@ function migrateCommand(args) {
|
|
|
15919
16724
|
}
|
|
15920
16725
|
|
|
15921
16726
|
// packages/cli/dist/commands/migrate-features.js
|
|
15922
|
-
import { existsSync as
|
|
15923
|
-
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";
|
|
15924
16729
|
function specFrontmatter(specFile, storyId) {
|
|
15925
16730
|
try {
|
|
15926
|
-
const lines =
|
|
16731
|
+
const lines = readFileSync31(specFile, "utf8").split("\n").slice(0, 12);
|
|
15927
16732
|
const pick = (key) => {
|
|
15928
16733
|
const row2 = lines.find((l) => l.startsWith(`${key}: `));
|
|
15929
16734
|
return row2?.slice(key.length + 2).trim();
|
|
@@ -15943,24 +16748,24 @@ function specFrontmatter(specFile, storyId) {
|
|
|
15943
16748
|
function migrateFeaturesCommand(args) {
|
|
15944
16749
|
const refresh = args.includes("--refresh-html");
|
|
15945
16750
|
const cwd = process.cwd();
|
|
15946
|
-
if (!
|
|
16751
|
+
if (!existsSync40(join36(cwd, ".roll", "index.json"))) {
|
|
15947
16752
|
process.stderr.write("migrate-features: .roll/index.json not found\n");
|
|
15948
16753
|
return 1;
|
|
15949
16754
|
}
|
|
15950
16755
|
const stories = readIndex(cwd);
|
|
15951
|
-
const root =
|
|
16756
|
+
const root = join36(cwd, ".roll", "features");
|
|
15952
16757
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
15953
16758
|
let specCreated = 0;
|
|
15954
16759
|
let htmlWritten = 0;
|
|
15955
16760
|
let skipped = 0;
|
|
15956
16761
|
for (const [storyId, epic] of Object.entries(stories)) {
|
|
15957
|
-
const storyDir =
|
|
15958
|
-
const specFile =
|
|
15959
|
-
if (!
|
|
15960
|
-
const mdFile =
|
|
15961
|
-
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)) {
|
|
15962
16767
|
try {
|
|
15963
|
-
|
|
16768
|
+
mkdirSync22(storyDir, { recursive: true });
|
|
15964
16769
|
} catch {
|
|
15965
16770
|
skipped++;
|
|
15966
16771
|
continue;
|
|
@@ -15970,8 +16775,8 @@ function migrateFeaturesCommand(args) {
|
|
|
15970
16775
|
continue;
|
|
15971
16776
|
}
|
|
15972
16777
|
}
|
|
15973
|
-
if (!
|
|
15974
|
-
|
|
16778
|
+
if (!existsSync40(specFile)) {
|
|
16779
|
+
writeFileSync22(specFile, renderSpecMd({
|
|
15975
16780
|
id: storyId,
|
|
15976
16781
|
epic,
|
|
15977
16782
|
created: today,
|
|
@@ -15979,25 +16784,25 @@ function migrateFeaturesCommand(args) {
|
|
|
15979
16784
|
}), "utf8");
|
|
15980
16785
|
specCreated++;
|
|
15981
16786
|
}
|
|
15982
|
-
const htmlFile =
|
|
15983
|
-
if (!
|
|
16787
|
+
const htmlFile = join36(storyDir, "index.html");
|
|
16788
|
+
if (!existsSync40(htmlFile) || refresh) {
|
|
15984
16789
|
const fm = specFrontmatter(specFile, storyId);
|
|
15985
16790
|
const card = { id: storyId, epic, created: fm.created ?? today, ...fm.title !== void 0 ? { title: fm.title } : {} };
|
|
15986
16791
|
let html = renderStoryPage(card);
|
|
15987
|
-
const latest =
|
|
15988
|
-
const report =
|
|
15989
|
-
if (
|
|
16792
|
+
const latest = join36(storyDir, "latest");
|
|
16793
|
+
const report = join36(latest, reportFileName(storyId));
|
|
16794
|
+
if (existsSync40(report)) {
|
|
15990
16795
|
let runRel = "latest";
|
|
15991
16796
|
try {
|
|
15992
16797
|
runRel = readlinkSync3(latest);
|
|
15993
16798
|
} catch {
|
|
15994
16799
|
}
|
|
15995
|
-
const deliveredOn =
|
|
16800
|
+
const deliveredOn = statSync18(report).mtime.toISOString().slice(0, 10);
|
|
15996
16801
|
html = markPhaseDone(html, "delivery", `<p><a href="${runRel}/${reportFileName(storyId)}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a></p>
|
|
15997
16802
|
<p class="muted">${bi("Delivered", "\u4EA4\u4ED8\u4E8E")} ${deliveredOn}</p>
|
|
15998
16803
|
`);
|
|
15999
16804
|
}
|
|
16000
|
-
|
|
16805
|
+
writeFileSync22(htmlFile, html, "utf8");
|
|
16001
16806
|
htmlWritten++;
|
|
16002
16807
|
}
|
|
16003
16808
|
}
|
|
@@ -16008,9 +16813,9 @@ function migrateFeaturesCommand(args) {
|
|
|
16008
16813
|
|
|
16009
16814
|
// packages/cli/dist/commands/offboard.js
|
|
16010
16815
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
16011
|
-
import { existsSync as
|
|
16012
|
-
import { homedir as
|
|
16013
|
-
import { join as
|
|
16816
|
+
import { existsSync as existsSync41, readFileSync as readFileSync32, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync23 } from "node:fs";
|
|
16817
|
+
import { homedir as homedir16 } from "node:os";
|
|
16818
|
+
import { join as join37, resolve as resolve2 } from "node:path";
|
|
16014
16819
|
function pal3() {
|
|
16015
16820
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
16016
16821
|
return noColor2 ? { RED: "", GREEN: "", YELLOW: "", CYAN: "", BOLD: "", NC: "" } : {
|
|
@@ -16048,7 +16853,7 @@ function m3(key, ...args) {
|
|
|
16048
16853
|
return t(v2Catalog, msgLang9(), key, ...args);
|
|
16049
16854
|
}
|
|
16050
16855
|
function changesetPath(projectDir) {
|
|
16051
|
-
return
|
|
16856
|
+
return join37(projectDir, ".roll", "onboard-changeset.yaml");
|
|
16052
16857
|
}
|
|
16053
16858
|
function loopInCycle() {
|
|
16054
16859
|
return (process.env["ROLL_LOOP_AGENT"] ?? "") !== "" || (process.env["ROLL_CYCLE_LOG_RAW"] ?? "") !== "";
|
|
@@ -16058,7 +16863,7 @@ function runLaunchctl(args) {
|
|
|
16058
16863
|
if (args[0] !== void 0 && readOnly.has(args[0])) {
|
|
16059
16864
|
return spawnSync8("launchctl", args, { stdio: "inherit" }).status ?? 1;
|
|
16060
16865
|
}
|
|
16061
|
-
const canonical =
|
|
16866
|
+
const canonical = join37(homedir16(), "Library", "LaunchAgents");
|
|
16062
16867
|
const launchdDir = process.env["_LAUNCHD_DIR"] ?? canonical;
|
|
16063
16868
|
if (launchdDir !== canonical)
|
|
16064
16869
|
return 0;
|
|
@@ -16122,7 +16927,7 @@ function offboardCommand(args) {
|
|
|
16122
16927
|
projectDir = process.cwd();
|
|
16123
16928
|
}
|
|
16124
16929
|
const changeset = changesetPath(projectDir);
|
|
16125
|
-
if (!
|
|
16930
|
+
if (!existsSync41(changeset)) {
|
|
16126
16931
|
err12(m3("offboard.no_changeset_en"));
|
|
16127
16932
|
err12(m3("offboard.no_changeset_zh"));
|
|
16128
16933
|
process.stderr.write("\n");
|
|
@@ -16136,7 +16941,7 @@ function offboardCommand(args) {
|
|
|
16136
16941
|
`);
|
|
16137
16942
|
return 1;
|
|
16138
16943
|
}
|
|
16139
|
-
const parsed = parseChangeset(
|
|
16944
|
+
const parsed = parseChangeset(readFileSync32(changeset, "utf8"));
|
|
16140
16945
|
if (!parsed.ok) {
|
|
16141
16946
|
err12(m3("offboard.failed_to_parse_changeset"));
|
|
16142
16947
|
return 1;
|
|
@@ -16212,8 +17017,8 @@ function offboardCommand(args) {
|
|
|
16212
17017
|
process.stdout.write(m3("offboard.applying_offboard") + "\n");
|
|
16213
17018
|
for (const item of files) {
|
|
16214
17019
|
try {
|
|
16215
|
-
rmSync10(
|
|
16216
|
-
if (!
|
|
17020
|
+
rmSync10(join37(projectDir, item), { force: true });
|
|
17021
|
+
if (!existsSync41(join37(projectDir, item)))
|
|
16217
17022
|
process.stdout.write(` removed file ${item}
|
|
16218
17023
|
`);
|
|
16219
17024
|
} catch {
|
|
@@ -16221,26 +17026,26 @@ function offboardCommand(args) {
|
|
|
16221
17026
|
}
|
|
16222
17027
|
for (const item of dirs) {
|
|
16223
17028
|
try {
|
|
16224
|
-
rmSync10(
|
|
17029
|
+
rmSync10(join37(projectDir, item), { recursive: true, force: true });
|
|
16225
17030
|
process.stdout.write(` removed dir ${item}
|
|
16226
17031
|
`);
|
|
16227
17032
|
} catch {
|
|
16228
17033
|
}
|
|
16229
17034
|
}
|
|
16230
17035
|
for (const item of giEntries) {
|
|
16231
|
-
const gi =
|
|
16232
|
-
if (
|
|
16233
|
-
const lines =
|
|
17036
|
+
const gi = join37(projectDir, ".gitignore");
|
|
17037
|
+
if (existsSync41(gi)) {
|
|
17038
|
+
const lines = readFileSync32(gi, "utf8").split("\n");
|
|
16234
17039
|
if (lines.includes(item)) {
|
|
16235
17040
|
const kept = lines.filter((l) => l !== item);
|
|
16236
|
-
|
|
17041
|
+
writeFileSync23(gi, kept.join("\n"));
|
|
16237
17042
|
process.stdout.write(` .gitignore - ${item}
|
|
16238
17043
|
`);
|
|
16239
17044
|
}
|
|
16240
17045
|
}
|
|
16241
17046
|
}
|
|
16242
17047
|
for (const item of plists) {
|
|
16243
|
-
const plistPath =
|
|
17048
|
+
const plistPath = join37(homedir16(), "Library", "LaunchAgents", item);
|
|
16244
17049
|
const r = runLaunchctl(["unload", "-w", plistPath]);
|
|
16245
17050
|
if (r === 0)
|
|
16246
17051
|
process.stdout.write(` unloaded ${item}
|
|
@@ -16253,16 +17058,16 @@ function offboardCommand(args) {
|
|
|
16253
17058
|
}
|
|
16254
17059
|
|
|
16255
17060
|
// packages/cli/dist/commands/prices.js
|
|
16256
|
-
import { existsSync as
|
|
16257
|
-
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";
|
|
16258
17063
|
function loadSnapshots() {
|
|
16259
|
-
const dir =
|
|
16260
|
-
if (!
|
|
17064
|
+
const dir = join38(repoRoot(), "lib", "prices");
|
|
17065
|
+
if (!existsSync42(dir)) {
|
|
16261
17066
|
throw new Error(`no price snapshots found in ${dir}; run \`roll prices refresh\``);
|
|
16262
17067
|
}
|
|
16263
17068
|
const files = readdirSync16(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
|
|
16264
17069
|
return files.map((name) => {
|
|
16265
|
-
const data = JSON.parse(
|
|
17070
|
+
const data = JSON.parse(readFileSync33(join38(dir, name), "utf8"));
|
|
16266
17071
|
for (const key of ["version", "effective_at", "source_url", "prices"]) {
|
|
16267
17072
|
if (data[key] === void 0)
|
|
16268
17073
|
throw new Error(`snapshot ${name} missing required key ${key}`);
|
|
@@ -16355,8 +17160,8 @@ function pricesCommand(args) {
|
|
|
16355
17160
|
}
|
|
16356
17161
|
|
|
16357
17162
|
// packages/cli/dist/commands/release.js
|
|
16358
|
-
import { existsSync as
|
|
16359
|
-
import { join as
|
|
17163
|
+
import { existsSync as existsSync43, readFileSync as readFileSync34 } from "node:fs";
|
|
17164
|
+
import { join as join39 } from "node:path";
|
|
16360
17165
|
function label3(lang4, key, ...args) {
|
|
16361
17166
|
if (v3Catalog[key] !== void 0)
|
|
16362
17167
|
return t(v3Catalog, lang4, key, ...args);
|
|
@@ -16367,25 +17172,34 @@ function dateOf(d) {
|
|
|
16367
17172
|
}
|
|
16368
17173
|
function currentVersion(cwd) {
|
|
16369
17174
|
try {
|
|
16370
|
-
const pkg = JSON.parse(
|
|
17175
|
+
const pkg = JSON.parse(readFileSync34(join39(cwd, "package.json"), "utf8"));
|
|
16371
17176
|
return typeof pkg.version === "string" ? pkg.version : "";
|
|
16372
17177
|
} catch {
|
|
16373
17178
|
return "";
|
|
16374
17179
|
}
|
|
16375
17180
|
}
|
|
16376
|
-
function changelogReady(cwd) {
|
|
16377
|
-
const path =
|
|
16378
|
-
if (!
|
|
17181
|
+
function changelogReady(cwd, current) {
|
|
17182
|
+
const path = join39(cwd, "CHANGELOG.md");
|
|
17183
|
+
if (!existsSync43(path))
|
|
16379
17184
|
return false;
|
|
16380
|
-
const text =
|
|
16381
|
-
const
|
|
16382
|
-
|
|
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)
|
|
17199
|
+
return false;
|
|
17200
|
+
if (m7[1] === current)
|
|
16383
17201
|
return false;
|
|
16384
|
-
|
|
16385
|
-
const nextHeading = section.search(/\n## /);
|
|
16386
|
-
if (nextHeading !== -1)
|
|
16387
|
-
section = section.slice(0, nextHeading);
|
|
16388
|
-
return /^\s*-\s+\S/m.test(section);
|
|
17202
|
+
return hasBullet(sectionAfter(m7.index, m7[0].length));
|
|
16389
17203
|
}
|
|
16390
17204
|
function releaseCommand(args, now) {
|
|
16391
17205
|
const noColor2 = args.includes("--no-color") || !process.stdout.isTTY || (process.env["NO_COLOR"] ?? "") !== "";
|
|
@@ -16407,7 +17221,7 @@ function releaseCommand(args, now) {
|
|
|
16407
17221
|
`);
|
|
16408
17222
|
return 1;
|
|
16409
17223
|
}
|
|
16410
|
-
const ready = changelogReady(cwd);
|
|
17224
|
+
const ready = changelogReady(cwd, cur);
|
|
16411
17225
|
const plan = planRelease({
|
|
16412
17226
|
currentVersion: cur,
|
|
16413
17227
|
date: now ?? dateOf(/* @__PURE__ */ new Date()),
|
|
@@ -16444,8 +17258,8 @@ function releaseCommand(args, now) {
|
|
|
16444
17258
|
|
|
16445
17259
|
// packages/cli/dist/commands/setup.js
|
|
16446
17260
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
16447
|
-
import { existsSync as
|
|
16448
|
-
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";
|
|
16449
17263
|
function err13(line) {
|
|
16450
17264
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
16451
17265
|
const RED = noColor2 ? "" : "\x1B[0;31m";
|
|
@@ -16470,7 +17284,7 @@ function setupSnapshot(watch) {
|
|
|
16470
17284
|
continue;
|
|
16471
17285
|
let isDir4 = false;
|
|
16472
17286
|
try {
|
|
16473
|
-
isDir4 =
|
|
17287
|
+
isDir4 = statSync19(d).isDirectory();
|
|
16474
17288
|
} catch {
|
|
16475
17289
|
isDir4 = false;
|
|
16476
17290
|
}
|
|
@@ -16488,7 +17302,7 @@ function walk(dir, lines) {
|
|
|
16488
17302
|
return;
|
|
16489
17303
|
}
|
|
16490
17304
|
for (const name of names) {
|
|
16491
|
-
const p =
|
|
17305
|
+
const p = join40(dir, name);
|
|
16492
17306
|
let isLink = false;
|
|
16493
17307
|
try {
|
|
16494
17308
|
isLink = lstatSync3(p).isSymbolicLink();
|
|
@@ -16504,7 +17318,7 @@ function walk(dir, lines) {
|
|
|
16504
17318
|
}
|
|
16505
17319
|
let st;
|
|
16506
17320
|
try {
|
|
16507
|
-
st =
|
|
17321
|
+
st = statSync19(p);
|
|
16508
17322
|
} catch {
|
|
16509
17323
|
continue;
|
|
16510
17324
|
}
|
|
@@ -16518,7 +17332,7 @@ function walk(dir, lines) {
|
|
|
16518
17332
|
}
|
|
16519
17333
|
function fileFingerprint(p) {
|
|
16520
17334
|
try {
|
|
16521
|
-
const buf =
|
|
17335
|
+
const buf = readFileSync35(p);
|
|
16522
17336
|
let sum = 0;
|
|
16523
17337
|
for (let i = 0; i < buf.length; i++)
|
|
16524
17338
|
sum = sum * 31 + (buf[i] ?? 0) >>> 0;
|
|
@@ -16548,18 +17362,18 @@ function ensureHooksPath(repoPath) {
|
|
|
16548
17362
|
}
|
|
16549
17363
|
}
|
|
16550
17364
|
function peerEnsureStateDir() {
|
|
16551
|
-
const base =
|
|
16552
|
-
|
|
16553
|
-
|
|
17365
|
+
const base = join40(rollHome(), ".peer-state");
|
|
17366
|
+
mkdirSync23(base, { recursive: true });
|
|
17367
|
+
mkdirSync23(join40(base, "logs"), { recursive: true });
|
|
16554
17368
|
}
|
|
16555
17369
|
function tmuxPresent() {
|
|
16556
17370
|
return onPath("tmux");
|
|
16557
17371
|
}
|
|
16558
17372
|
function submoduleGuard() {
|
|
16559
17373
|
const pkg = rollPkgDir();
|
|
16560
|
-
const skills =
|
|
16561
|
-
const hasGit =
|
|
16562
|
-
const hasMods =
|
|
17374
|
+
const skills = join40(pkg, "skills");
|
|
17375
|
+
const hasGit = existsSync44(join40(pkg, ".git"));
|
|
17376
|
+
const hasMods = existsSync44(join40(pkg, ".gitmodules"));
|
|
16563
17377
|
let empty = true;
|
|
16564
17378
|
try {
|
|
16565
17379
|
empty = readdirSync17(skills).length === 0;
|
|
@@ -16652,7 +17466,7 @@ function setupCommand(args) {
|
|
|
16652
17466
|
return 1;
|
|
16653
17467
|
}
|
|
16654
17468
|
}
|
|
16655
|
-
if (!
|
|
17469
|
+
if (!existsSync44(rollPkgConventions()))
|
|
16656
17470
|
return null;
|
|
16657
17471
|
submoduleGuard();
|
|
16658
17472
|
const home = rollHome();
|
|
@@ -16671,7 +17485,7 @@ function setupCommand(args) {
|
|
|
16671
17485
|
".qwen"
|
|
16672
17486
|
];
|
|
16673
17487
|
const homeDir = process.env["HOME"] ?? "";
|
|
16674
|
-
const aiDirs = aiDirsList.map((d) =>
|
|
17488
|
+
const aiDirs = aiDirsList.map((d) => join40(homeDir, d)).join(":");
|
|
16675
17489
|
const steps = [];
|
|
16676
17490
|
const s1 = runSetupStep(home, () => {
|
|
16677
17491
|
installLocal(force);
|
|
@@ -16685,7 +17499,7 @@ function setupCommand(args) {
|
|
|
16685
17499
|
syncSkills(force);
|
|
16686
17500
|
});
|
|
16687
17501
|
steps.push({ num: 3, label: "Install skills to ~/.claude", status: stateToMarker(s3, force) });
|
|
16688
|
-
const s4 = runSetupStep(
|
|
17502
|
+
const s4 = runSetupStep(join40(home, ".peer-state"), () => {
|
|
16689
17503
|
peerEnsureStateDir();
|
|
16690
17504
|
});
|
|
16691
17505
|
steps.push({ num: 4, label: "Initialize peer-review state directory", status: stateToMarker(s4, force) });
|
|
@@ -16706,12 +17520,12 @@ function setupCommand(args) {
|
|
|
16706
17520
|
|
|
16707
17521
|
// packages/cli/dist/commands/slides/index.js
|
|
16708
17522
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
16709
|
-
import { mkdirSync as
|
|
16710
|
-
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";
|
|
16711
17525
|
|
|
16712
17526
|
// packages/cli/dist/commands/slides/render.js
|
|
16713
|
-
import { readFileSync as
|
|
16714
|
-
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";
|
|
16715
17529
|
function coerceScalar(v) {
|
|
16716
17530
|
if (v.length >= 2 && v[0] === v[v.length - 1] && (v[0] === "'" || v[0] === '"')) {
|
|
16717
17531
|
return v.slice(1, -1);
|
|
@@ -17027,7 +17841,7 @@ function pyTruthy(v) {
|
|
|
17027
17841
|
return v.length > 0;
|
|
17028
17842
|
return Object.keys(v).length > 0;
|
|
17029
17843
|
}
|
|
17030
|
-
function
|
|
17844
|
+
function lookup2(ctxStack, key) {
|
|
17031
17845
|
for (let i = ctxStack.length - 1; i >= 0; i--) {
|
|
17032
17846
|
const ctx = ctxStack[i];
|
|
17033
17847
|
if (isPlainObject(ctx) && Object.prototype.hasOwnProperty.call(ctx, key)) {
|
|
@@ -17086,19 +17900,19 @@ function mustache(template, context) {
|
|
|
17086
17900
|
const tagKey = m7[3];
|
|
17087
17901
|
const matchEnd = m7.index + m7[0].length;
|
|
17088
17902
|
if (rawKey) {
|
|
17089
|
-
buf.push(pyStr(
|
|
17903
|
+
buf.push(pyStr(lookup2(ctxStack, rawKey)));
|
|
17090
17904
|
i = matchEnd;
|
|
17091
17905
|
continue;
|
|
17092
17906
|
}
|
|
17093
17907
|
if (sigil === "") {
|
|
17094
|
-
buf.push(escapeHtml2(pyStr(
|
|
17908
|
+
buf.push(escapeHtml2(pyStr(lookup2(ctxStack, tagKey))));
|
|
17095
17909
|
i = matchEnd;
|
|
17096
17910
|
continue;
|
|
17097
17911
|
}
|
|
17098
17912
|
if (sigil === "#" || sigil === "^") {
|
|
17099
17913
|
const closeIdx = findClose(chunk, matchEnd, tagKey);
|
|
17100
17914
|
const inner = chunk.slice(matchEnd, closeIdx);
|
|
17101
|
-
const val =
|
|
17915
|
+
const val = lookup2(ctxStack, tagKey);
|
|
17102
17916
|
if (sigil === "#") {
|
|
17103
17917
|
if (Array.isArray(val)) {
|
|
17104
17918
|
for (const item of val) {
|
|
@@ -17196,7 +18010,7 @@ var DEFAULT_LAYOUT = "plain";
|
|
|
17196
18010
|
var LayoutResolver = class {
|
|
17197
18011
|
componentsDir;
|
|
17198
18012
|
constructor(componentsDir) {
|
|
17199
|
-
this.componentsDir = componentsDir ??
|
|
18013
|
+
this.componentsDir = componentsDir ?? join41(libDirFor(import.meta.url), "slides", "components");
|
|
17200
18014
|
}
|
|
17201
18015
|
available() {
|
|
17202
18016
|
if (!isDir2(this.componentsDir))
|
|
@@ -17210,7 +18024,7 @@ var LayoutResolver = class {
|
|
|
17210
18024
|
if (!/^[a-z0-9-]+$/.test(layout)) {
|
|
17211
18025
|
throw new ValueError(`Unknown layout: ${layout}; available: ${this.available().join(", ")}`);
|
|
17212
18026
|
}
|
|
17213
|
-
const path =
|
|
18027
|
+
const path = join41(this.componentsDir, `${layout}.html`);
|
|
17214
18028
|
if (!isFile(path)) {
|
|
17215
18029
|
throw new ValueError(`Unknown layout: ${layout}; available: ${this.available().join(", ")}`);
|
|
17216
18030
|
}
|
|
@@ -17220,7 +18034,7 @@ var LayoutResolver = class {
|
|
|
17220
18034
|
function renderSlideInner(slide, resolver) {
|
|
17221
18035
|
const layout = typeof slide["layout"] === "string" && slide["layout"] || DEFAULT_LAYOUT;
|
|
17222
18036
|
const partialPath = resolver.resolve(String(layout));
|
|
17223
|
-
let partial =
|
|
18037
|
+
let partial = readFileSync36(partialPath, "utf8");
|
|
17224
18038
|
partial = partial.replace(/^\s*<!--[\s\S]*?-->(?:\s*\n)?/, "");
|
|
17225
18039
|
const rendered = mustache(partial, slide);
|
|
17226
18040
|
return stripNewlines(rendered);
|
|
@@ -17268,30 +18082,30 @@ function pyRepr(s) {
|
|
|
17268
18082
|
}
|
|
17269
18083
|
function isDir2(p) {
|
|
17270
18084
|
try {
|
|
17271
|
-
return
|
|
18085
|
+
return statSync20(p).isDirectory();
|
|
17272
18086
|
} catch {
|
|
17273
18087
|
return false;
|
|
17274
18088
|
}
|
|
17275
18089
|
}
|
|
17276
18090
|
function isFile(p) {
|
|
17277
18091
|
try {
|
|
17278
|
-
return
|
|
18092
|
+
return statSync20(p).isFile();
|
|
17279
18093
|
} catch {
|
|
17280
18094
|
return false;
|
|
17281
18095
|
}
|
|
17282
18096
|
}
|
|
17283
18097
|
function libDirFor(_metaUrl) {
|
|
17284
18098
|
void _metaUrl;
|
|
17285
|
-
void
|
|
17286
|
-
void
|
|
17287
|
-
return
|
|
18099
|
+
void existsSync45;
|
|
18100
|
+
void dirname18;
|
|
18101
|
+
return join41(repoLibFallback(), "lib");
|
|
17288
18102
|
}
|
|
17289
18103
|
function repoLibFallback() {
|
|
17290
18104
|
let dir = process.cwd();
|
|
17291
18105
|
for (let i = 0; i < 12; i++) {
|
|
17292
|
-
if (
|
|
18106
|
+
if (existsSync45(join41(dir, "bin", "roll")))
|
|
17293
18107
|
return dir;
|
|
17294
|
-
const parent =
|
|
18108
|
+
const parent = dirname18(dir);
|
|
17295
18109
|
if (parent === dir)
|
|
17296
18110
|
break;
|
|
17297
18111
|
dir = parent;
|
|
@@ -17300,7 +18114,7 @@ function repoLibFallback() {
|
|
|
17300
18114
|
}
|
|
17301
18115
|
|
|
17302
18116
|
// packages/cli/dist/commands/slides/validate.js
|
|
17303
|
-
import { readFileSync as
|
|
18117
|
+
import { readFileSync as readFileSync37, statSync as statSync21 } from "node:fs";
|
|
17304
18118
|
var REQUIRED_FRONTMATTER = [
|
|
17305
18119
|
"template",
|
|
17306
18120
|
"slug",
|
|
@@ -17504,7 +18318,7 @@ function validateDeckFile(path, errSink, componentsDir) {
|
|
|
17504
18318
|
let fm;
|
|
17505
18319
|
let slides;
|
|
17506
18320
|
try {
|
|
17507
|
-
src =
|
|
18321
|
+
src = readFileSync37(path, "utf8");
|
|
17508
18322
|
[fm] = parseFrontmatter(src);
|
|
17509
18323
|
const [, body] = parseFrontmatter(src);
|
|
17510
18324
|
slides = parseSlides(body);
|
|
@@ -17538,7 +18352,7 @@ function validateDeckFile(path, errSink, componentsDir) {
|
|
|
17538
18352
|
}
|
|
17539
18353
|
function isFile2(p) {
|
|
17540
18354
|
try {
|
|
17541
|
-
return
|
|
18355
|
+
return statSync21(p).isFile();
|
|
17542
18356
|
} catch {
|
|
17543
18357
|
return false;
|
|
17544
18358
|
}
|
|
@@ -17617,13 +18431,13 @@ function slidesHelp(out2) {
|
|
|
17617
18431
|
out2(SLIDES_HELP);
|
|
17618
18432
|
}
|
|
17619
18433
|
function slidesLib() {
|
|
17620
|
-
return
|
|
18434
|
+
return join42(rollPkgDir(), "lib");
|
|
17621
18435
|
}
|
|
17622
18436
|
function slidesTemplatePath(name) {
|
|
17623
|
-
const projTpl =
|
|
18437
|
+
const projTpl = join42(".roll", "slides", "templates", `${name}.html`);
|
|
17624
18438
|
if (isFile3(projTpl))
|
|
17625
18439
|
return projTpl;
|
|
17626
|
-
const tpl =
|
|
18440
|
+
const tpl = join42(rollPkgDir(), "lib", "slides", "templates", `${name}.html`);
|
|
17627
18441
|
if (isFile3(tpl))
|
|
17628
18442
|
return tpl;
|
|
17629
18443
|
return null;
|
|
@@ -17631,7 +18445,7 @@ function slidesTemplatePath(name) {
|
|
|
17631
18445
|
function slidesTemplateForDeck(deckPath) {
|
|
17632
18446
|
let tpl = "";
|
|
17633
18447
|
try {
|
|
17634
|
-
const src =
|
|
18448
|
+
const src = readFileSync38(deckPath, "utf8");
|
|
17635
18449
|
let d = 0;
|
|
17636
18450
|
for (const line of src.split("\n")) {
|
|
17637
18451
|
if (/^---[ \t]*$/.test(line)) {
|
|
@@ -17657,21 +18471,21 @@ function slidesTemplateForDeck(deckPath) {
|
|
|
17657
18471
|
return tpl;
|
|
17658
18472
|
}
|
|
17659
18473
|
function slidesEnsureGitignore() {
|
|
17660
|
-
const gi =
|
|
17661
|
-
|
|
18474
|
+
const gi = join42(".roll", ".gitignore");
|
|
18475
|
+
mkdirSync24(".roll", { recursive: true });
|
|
17662
18476
|
if (isFile3(gi)) {
|
|
17663
|
-
const content =
|
|
18477
|
+
const content = readFileSync38(gi, "utf8");
|
|
17664
18478
|
if (content.split("\n").some((l) => /^slides\/\*\.html$/.test(l)))
|
|
17665
18479
|
return;
|
|
17666
18480
|
if (content.length > 0 && !content.endsWith("\n")) {
|
|
17667
|
-
|
|
18481
|
+
writeFileSync24(gi, content + "\n");
|
|
17668
18482
|
}
|
|
17669
18483
|
}
|
|
17670
18484
|
appendFile(gi, "slides/*.html\n");
|
|
17671
18485
|
}
|
|
17672
18486
|
function appendFile(path, data) {
|
|
17673
|
-
const prev = isFile3(path) ?
|
|
17674
|
-
|
|
18487
|
+
const prev = isFile3(path) ? readFileSync38(path, "utf8") : "";
|
|
18488
|
+
writeFileSync24(path, prev + data);
|
|
17675
18489
|
}
|
|
17676
18490
|
function slidesOpenCmd() {
|
|
17677
18491
|
const sys = spawnSync10("uname", ["-s"], { encoding: "utf8" });
|
|
@@ -17730,7 +18544,7 @@ function cmdBuild(args) {
|
|
|
17730
18544
|
process.stderr.write(m5("slides_build.usage_roll_slides_build_slug_no") + "\n");
|
|
17731
18545
|
return 1;
|
|
17732
18546
|
}
|
|
17733
|
-
const deck =
|
|
18547
|
+
const deck = join42(".roll", "slides", slug, "deck.md");
|
|
17734
18548
|
if (!isFile3(deck)) {
|
|
17735
18549
|
err15(`Deck not found: ${deck}`);
|
|
17736
18550
|
process.stderr.write(m5("slides_build.en_deck", deck) + "\n");
|
|
@@ -17740,14 +18554,14 @@ function cmdBuild(args) {
|
|
|
17740
18554
|
return 1;
|
|
17741
18555
|
}
|
|
17742
18556
|
const libDir = slidesLib();
|
|
17743
|
-
const validator =
|
|
17744
|
-
const renderer =
|
|
18557
|
+
const validator = join42(libDir, "slides-validate.py");
|
|
18558
|
+
const renderer = join42(libDir, "slides-render.py");
|
|
17745
18559
|
if (!isFile3(validator) || !isFile3(renderer)) {
|
|
17746
18560
|
err15(m5("slides_build.slides_toolchain_missing_re_run_roll"));
|
|
17747
18561
|
return 1;
|
|
17748
18562
|
}
|
|
17749
|
-
const errFile =
|
|
17750
|
-
const componentsDir =
|
|
18563
|
+
const errFile = join42(".roll", "slides", slug, ".last-build.err");
|
|
18564
|
+
const componentsDir = join42(libDir, "slides", "components");
|
|
17751
18565
|
const valLines = [];
|
|
17752
18566
|
const valExit = validateDeckFile(deck, (l) => valLines.push(l), componentsDir);
|
|
17753
18567
|
const valOut = valLines.join("\n");
|
|
@@ -17756,8 +18570,8 @@ function cmdBuild(args) {
|
|
|
17756
18570
|
`);
|
|
17757
18571
|
} else if (valExit !== 0) {
|
|
17758
18572
|
const ts = utcTs();
|
|
17759
|
-
|
|
17760
|
-
|
|
18573
|
+
mkdirSync24(join42(".roll", "slides", slug), { recursive: true });
|
|
18574
|
+
writeFileSync24(errFile, `[${ts}] stage=validate
|
|
17761
18575
|
${valOut}
|
|
17762
18576
|
`);
|
|
17763
18577
|
if (valOut !== "")
|
|
@@ -17774,15 +18588,15 @@ ${valOut}
|
|
|
17774
18588
|
const tplPath = slidesTemplatePath(tplName);
|
|
17775
18589
|
if (tplPath === null) {
|
|
17776
18590
|
const ts = utcTs();
|
|
17777
|
-
|
|
17778
|
-
|
|
18591
|
+
mkdirSync24(join42(".roll", "slides", slug), { recursive: true });
|
|
18592
|
+
writeFileSync24(errFile, `[${ts}] stage=template
|
|
17779
18593
|
template not found: ${tplName}
|
|
17780
18594
|
`);
|
|
17781
18595
|
const { RED, NC } = pal4();
|
|
17782
18596
|
process.stderr.write(`${RED}[FAIL]${NC} ${m5("slides_build.template_not_found", tplName)}
|
|
17783
18597
|
`);
|
|
17784
18598
|
process.stderr.write(" " + m5("slides_build.available_templates") + "\n");
|
|
17785
|
-
const builtinDir =
|
|
18599
|
+
const builtinDir = join42(rollPkgDir(), "lib", "slides", "templates");
|
|
17786
18600
|
if (isDir3(builtinDir)) {
|
|
17787
18601
|
for (const t2 of listHtml(builtinDir)) {
|
|
17788
18602
|
const n = basenameNoHtml(t2);
|
|
@@ -17790,7 +18604,7 @@ template not found: ${tplName}
|
|
|
17790
18604
|
`);
|
|
17791
18605
|
}
|
|
17792
18606
|
}
|
|
17793
|
-
const projDir =
|
|
18607
|
+
const projDir = join42(".roll", "slides", "templates");
|
|
17794
18608
|
if (isDir3(projDir)) {
|
|
17795
18609
|
for (const t2 of listHtml(projDir)) {
|
|
17796
18610
|
const n = basenameNoHtml(t2);
|
|
@@ -17801,18 +18615,18 @@ template not found: ${tplName}
|
|
|
17801
18615
|
process.stderr.write(" " + m5("slides_build.templates_list_hint") + "\n");
|
|
17802
18616
|
return 1;
|
|
17803
18617
|
}
|
|
17804
|
-
const out2 =
|
|
17805
|
-
|
|
18618
|
+
const out2 = join42(".roll", "slides", `${slug}.html`);
|
|
18619
|
+
mkdirSync24(join42(".roll", "slides"), { recursive: true });
|
|
17806
18620
|
let htmlOut;
|
|
17807
18621
|
try {
|
|
17808
|
-
const src =
|
|
17809
|
-
const template =
|
|
18622
|
+
const src = readFileSync38(deck, "utf8");
|
|
18623
|
+
const template = readFileSync38(tplPath, "utf8");
|
|
17810
18624
|
htmlOut = renderDeck(src, template, { componentsDir });
|
|
17811
18625
|
} catch (e) {
|
|
17812
18626
|
const renderOut = renderErrorBlock(e);
|
|
17813
18627
|
const ts = utcTs();
|
|
17814
|
-
|
|
17815
|
-
|
|
18628
|
+
mkdirSync24(join42(".roll", "slides", slug), { recursive: true });
|
|
18629
|
+
writeFileSync24(errFile, `[${ts}] stage=render
|
|
17816
18630
|
${renderOut}
|
|
17817
18631
|
`);
|
|
17818
18632
|
const { RED, NC } = pal4();
|
|
@@ -17826,7 +18640,7 @@ ${renderOut}
|
|
|
17826
18640
|
}
|
|
17827
18641
|
return 1;
|
|
17828
18642
|
}
|
|
17829
|
-
|
|
18643
|
+
writeFileSync24(out2, htmlOut);
|
|
17830
18644
|
try {
|
|
17831
18645
|
rmSync11(errFile, { force: true });
|
|
17832
18646
|
} catch {
|
|
@@ -17851,7 +18665,7 @@ function tailLines(s, n) {
|
|
|
17851
18665
|
}
|
|
17852
18666
|
function frontmatterField2(deckPath, field) {
|
|
17853
18667
|
try {
|
|
17854
|
-
const src =
|
|
18668
|
+
const src = readFileSync38(deckPath, "utf8");
|
|
17855
18669
|
let d = 0;
|
|
17856
18670
|
const pat = new RegExp("^" + field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "[ ]*:[ ]*");
|
|
17857
18671
|
for (const line of src.split("\n")) {
|
|
@@ -17894,7 +18708,7 @@ function cmdList(args) {
|
|
|
17894
18708
|
err15(m5("slides_list.unexpected_argument_1"));
|
|
17895
18709
|
return 1;
|
|
17896
18710
|
}
|
|
17897
|
-
const slidesDir =
|
|
18711
|
+
const slidesDir = join42(".roll", "slides");
|
|
17898
18712
|
if (!isDir3(slidesDir)) {
|
|
17899
18713
|
info5(m5("slides_list.no_decks_found_under_roll_slides"));
|
|
17900
18714
|
process.stdout.write(` Hint: run 'roll slides new "<topic>"' to create one.
|
|
@@ -17904,8 +18718,8 @@ function cmdList(args) {
|
|
|
17904
18718
|
}
|
|
17905
18719
|
const slugs = [];
|
|
17906
18720
|
for (const entry of readdirSync19(slidesDir)) {
|
|
17907
|
-
const d =
|
|
17908
|
-
if (isDir3(d) && isFile3(
|
|
18721
|
+
const d = join42(slidesDir, entry);
|
|
18722
|
+
if (isDir3(d) && isFile3(join42(d, "deck.md")))
|
|
17909
18723
|
slugs.push(entry);
|
|
17910
18724
|
}
|
|
17911
18725
|
if (slugs.length === 0) {
|
|
@@ -17919,9 +18733,9 @@ function cmdList(args) {
|
|
|
17919
18733
|
process.stdout.write(rowSix("slug", "template", "total_slides", "created", "built", "size") + "\n");
|
|
17920
18734
|
process.stdout.write(rowSix("----", "--------", "------------", "-------", "------", "----") + "\n");
|
|
17921
18735
|
for (const s of sorted) {
|
|
17922
|
-
const deck =
|
|
17923
|
-
const html =
|
|
17924
|
-
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");
|
|
17925
18739
|
let template = frontmatterField2(deck, "template");
|
|
17926
18740
|
if (template === "")
|
|
17927
18741
|
template = "-";
|
|
@@ -17944,7 +18758,7 @@ function cmdList(args) {
|
|
|
17944
18758
|
built = "\u2713 built";
|
|
17945
18759
|
let bytes = 0;
|
|
17946
18760
|
try {
|
|
17947
|
-
bytes =
|
|
18761
|
+
bytes = statSync22(html).size;
|
|
17948
18762
|
} catch {
|
|
17949
18763
|
bytes = 0;
|
|
17950
18764
|
}
|
|
@@ -17960,7 +18774,7 @@ function cmdList(args) {
|
|
|
17960
18774
|
}
|
|
17961
18775
|
function isNewer(a, b) {
|
|
17962
18776
|
try {
|
|
17963
|
-
return
|
|
18777
|
+
return statSync22(a).mtimeMs > statSync22(b).mtimeMs;
|
|
17964
18778
|
} catch {
|
|
17965
18779
|
return false;
|
|
17966
18780
|
}
|
|
@@ -17995,7 +18809,7 @@ function cmdPreview(args) {
|
|
|
17995
18809
|
process.stderr.write(m5("slides_preview.usage_roll_slides_preview_slug_no") + "\n");
|
|
17996
18810
|
return 1;
|
|
17997
18811
|
}
|
|
17998
|
-
const html =
|
|
18812
|
+
const html = join42(".roll", "slides", `${slug}.html`);
|
|
17999
18813
|
if (!isFile3(html)) {
|
|
18000
18814
|
err15(`Rendered HTML not found: ${html}`);
|
|
18001
18815
|
process.stderr.write(m5("slides_preview.en_html", html) + "\n");
|
|
@@ -18036,9 +18850,9 @@ function cmdLogs(args) {
|
|
|
18036
18850
|
process.stderr.write(m5("slides_logs.usage_roll_slides_logs_slug") + "\n");
|
|
18037
18851
|
return 1;
|
|
18038
18852
|
}
|
|
18039
|
-
const deckDir =
|
|
18040
|
-
const errFile =
|
|
18041
|
-
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"))) {
|
|
18042
18856
|
err15(m5("slides_logs.deck_not_found", slug));
|
|
18043
18857
|
return 1;
|
|
18044
18858
|
}
|
|
@@ -18046,7 +18860,7 @@ function cmdLogs(args) {
|
|
|
18046
18860
|
info5(m5("slides_logs.no_failure_records_for", slug));
|
|
18047
18861
|
return 0;
|
|
18048
18862
|
}
|
|
18049
|
-
process.stdout.write(
|
|
18863
|
+
process.stdout.write(readFileSync38(errFile, "utf8"));
|
|
18050
18864
|
return 0;
|
|
18051
18865
|
}
|
|
18052
18866
|
function cmdDelete(args) {
|
|
@@ -18079,9 +18893,9 @@ function cmdDelete(args) {
|
|
|
18079
18893
|
process.stderr.write(m5("slides_delete.usage_roll_slides_delete_slug_force") + "\n");
|
|
18080
18894
|
return 1;
|
|
18081
18895
|
}
|
|
18082
|
-
const deckDir =
|
|
18083
|
-
const html =
|
|
18084
|
-
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"))) {
|
|
18085
18899
|
err15(m5("slides_delete.deck_not_found", slug));
|
|
18086
18900
|
return 1;
|
|
18087
18901
|
}
|
|
@@ -18120,7 +18934,7 @@ function cmdTemplates(args) {
|
|
|
18120
18934
|
let found = false;
|
|
18121
18935
|
process.stdout.write(rowThree("name", "source", "path") + "\n");
|
|
18122
18936
|
process.stdout.write(rowThree("----", "------", "----") + "\n");
|
|
18123
|
-
const builtinDir =
|
|
18937
|
+
const builtinDir = join42(rollPkgDir(), "lib", "slides", "templates");
|
|
18124
18938
|
if (isDir3(builtinDir)) {
|
|
18125
18939
|
for (const tpl of listHtml(builtinDir)) {
|
|
18126
18940
|
const name = basenameNoHtml(tpl);
|
|
@@ -18128,11 +18942,11 @@ function cmdTemplates(args) {
|
|
|
18128
18942
|
found = true;
|
|
18129
18943
|
}
|
|
18130
18944
|
}
|
|
18131
|
-
const projDir =
|
|
18945
|
+
const projDir = join42(".roll", "slides", "templates");
|
|
18132
18946
|
if (isDir3(projDir)) {
|
|
18133
18947
|
for (const tpl of listHtml(projDir)) {
|
|
18134
18948
|
const name = basenameNoHtml(tpl);
|
|
18135
|
-
const source = isFile3(
|
|
18949
|
+
const source = isFile3(join42(builtinDir, `${name}.html`)) ? "project (override)" : "project";
|
|
18136
18950
|
process.stdout.write(rowThree(name, source, tpl) + "\n");
|
|
18137
18951
|
found = true;
|
|
18138
18952
|
}
|
|
@@ -18180,20 +18994,20 @@ function slidesCommand(args) {
|
|
|
18180
18994
|
}
|
|
18181
18995
|
function isFile3(p) {
|
|
18182
18996
|
try {
|
|
18183
|
-
return
|
|
18997
|
+
return statSync22(p).isFile();
|
|
18184
18998
|
} catch {
|
|
18185
18999
|
return false;
|
|
18186
19000
|
}
|
|
18187
19001
|
}
|
|
18188
19002
|
function isDir3(p) {
|
|
18189
19003
|
try {
|
|
18190
|
-
return
|
|
19004
|
+
return statSync22(p).isDirectory();
|
|
18191
19005
|
} catch {
|
|
18192
19006
|
return false;
|
|
18193
19007
|
}
|
|
18194
19008
|
}
|
|
18195
19009
|
function listHtml(dir) {
|
|
18196
|
-
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));
|
|
18197
19011
|
}
|
|
18198
19012
|
function basenameNoHtml(path) {
|
|
18199
19013
|
const b = path.slice(path.lastIndexOf("/") + 1);
|
|
@@ -18211,21 +19025,21 @@ function rowThree(a, b, c2) {
|
|
|
18211
19025
|
}
|
|
18212
19026
|
|
|
18213
19027
|
// packages/cli/dist/commands/status.js
|
|
18214
|
-
import { execFileSync as
|
|
19028
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
18215
19029
|
import { createHash as createHash5 } from "node:crypto";
|
|
18216
|
-
import { existsSync as
|
|
18217
|
-
import { homedir as
|
|
18218
|
-
import { basename as basename10, dirname as
|
|
19030
|
+
import { existsSync as existsSync46, lstatSync as lstatSync4, readdirSync as readdirSync20, readFileSync as readFileSync39, realpathSync as realpathSync6, statSync as statSync23 } from "node:fs";
|
|
19031
|
+
import { homedir as homedir17 } from "node:os";
|
|
19032
|
+
import { basename as basename10, dirname as dirname19, join as join43 } from "node:path";
|
|
18219
19033
|
function rollHome3() {
|
|
18220
|
-
return process.env["ROLL_HOME"] ??
|
|
19034
|
+
return process.env["ROLL_HOME"] ?? join43(homedir17(), ".roll");
|
|
18221
19035
|
}
|
|
18222
|
-
var globalDir = () =>
|
|
18223
|
-
var templatesDir = () =>
|
|
18224
|
-
var configPath = () =>
|
|
19036
|
+
var globalDir = () => join43(rollHome3(), "conventions", "global");
|
|
19037
|
+
var templatesDir = () => join43(rollHome3(), "conventions", "templates");
|
|
19038
|
+
var configPath = () => join43(rollHome3(), "config.yaml");
|
|
18225
19039
|
function projectSlugPy() {
|
|
18226
19040
|
let path = realpathSync6(process.cwd());
|
|
18227
19041
|
try {
|
|
18228
|
-
const common =
|
|
19042
|
+
const common = execFileSync9("git", ["-C", path, "rev-parse", "--git-common-dir"], {
|
|
18229
19043
|
encoding: "utf8",
|
|
18230
19044
|
stdio: ["ignore", "pipe", "ignore"]
|
|
18231
19045
|
}).trim();
|
|
@@ -18240,18 +19054,18 @@ function projectSlugPy() {
|
|
|
18240
19054
|
var CONVENTION_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md", ".cursor-rules", "project_rules.md"];
|
|
18241
19055
|
var TEMPLATES = ["fullstack", "frontend-only", "backend-service", "cli"];
|
|
18242
19056
|
function globalConventions() {
|
|
18243
|
-
return CONVENTION_FILES.map((f) => [f,
|
|
19057
|
+
return CONVENTION_FILES.map((f) => [f, existsSync46(join43(globalDir(), f))]);
|
|
18244
19058
|
}
|
|
18245
19059
|
function parseAiEntries() {
|
|
18246
19060
|
const cfg = configPath();
|
|
18247
|
-
if (!
|
|
19061
|
+
if (!existsSync46(cfg))
|
|
18248
19062
|
return [];
|
|
18249
19063
|
const entries = [];
|
|
18250
|
-
for (const line of
|
|
19064
|
+
for (const line of readFileSync39(cfg, "utf8").split("\n")) {
|
|
18251
19065
|
const m7 = /^ai_[a-z]+:\s*(.+)/.exec(line);
|
|
18252
19066
|
if (m7 === null)
|
|
18253
19067
|
continue;
|
|
18254
|
-
const val = (m7[1] ?? "").trim().replaceAll("~",
|
|
19068
|
+
const val = (m7[1] ?? "").trim().replaceAll("~", homedir17());
|
|
18255
19069
|
const parts = val.split("|");
|
|
18256
19070
|
if (parts.length < 3)
|
|
18257
19071
|
continue;
|
|
@@ -18260,28 +19074,28 @@ function parseAiEntries() {
|
|
|
18260
19074
|
const srcFile = (parts[2] ?? "").trim();
|
|
18261
19075
|
let name = basename10(aiDir).replace(/^\.+/, "");
|
|
18262
19076
|
if (name === "workspace" || name === "agent") {
|
|
18263
|
-
name = basename10(
|
|
19077
|
+
name = basename10(dirname19(aiDir)).replace(/^\.+/, "");
|
|
18264
19078
|
}
|
|
18265
19079
|
entries.push({ name, ai_dir: aiDir, cfg_file: cfgFile, src_file: srcFile });
|
|
18266
19080
|
}
|
|
18267
19081
|
return entries;
|
|
18268
19082
|
}
|
|
18269
19083
|
function aiSyncStatus(e) {
|
|
18270
|
-
const cfgFile =
|
|
18271
|
-
const rollMd =
|
|
18272
|
-
const src =
|
|
18273
|
-
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))
|
|
18274
19088
|
return "missing";
|
|
18275
|
-
if (!
|
|
19089
|
+
if (!existsSync46(rollMd))
|
|
18276
19090
|
return "out-of-sync";
|
|
18277
19091
|
try {
|
|
18278
|
-
if (
|
|
19092
|
+
if (existsSync46(src) && !readFileSync39(rollMd).equals(readFileSync39(src)))
|
|
18279
19093
|
return "out-of-sync";
|
|
18280
19094
|
} catch {
|
|
18281
19095
|
return "out-of-sync";
|
|
18282
19096
|
}
|
|
18283
19097
|
try {
|
|
18284
|
-
if (!
|
|
19098
|
+
if (!readFileSync39(cfgFile, "utf8").includes("@roll.md"))
|
|
18285
19099
|
return "out-of-sync";
|
|
18286
19100
|
} catch {
|
|
18287
19101
|
return "out-of-sync";
|
|
@@ -18289,15 +19103,15 @@ function aiSyncStatus(e) {
|
|
|
18289
19103
|
return "sync";
|
|
18290
19104
|
}
|
|
18291
19105
|
function aiSkillCount(e) {
|
|
18292
|
-
const skillsDir2 =
|
|
18293
|
-
if (!
|
|
19106
|
+
const skillsDir2 = join43(e.ai_dir, "skills");
|
|
19107
|
+
if (!existsSync46(skillsDir2))
|
|
18294
19108
|
return 0;
|
|
18295
19109
|
try {
|
|
18296
19110
|
let n = 0;
|
|
18297
19111
|
for (const name of readdirSync20(skillsDir2)) {
|
|
18298
19112
|
if (!name.startsWith("roll-"))
|
|
18299
19113
|
continue;
|
|
18300
|
-
const p =
|
|
19114
|
+
const p = join43(skillsDir2, name);
|
|
18301
19115
|
const st = lstatSync4(p);
|
|
18302
19116
|
if (st.isSymbolicLink() || st.isDirectory())
|
|
18303
19117
|
n++;
|
|
@@ -18310,8 +19124,8 @@ function aiSkillCount(e) {
|
|
|
18310
19124
|
function countFilesRecursive(dir) {
|
|
18311
19125
|
let n = 0;
|
|
18312
19126
|
for (const name of readdirSync20(dir)) {
|
|
18313
|
-
const p =
|
|
18314
|
-
const st =
|
|
19127
|
+
const p = join43(dir, name);
|
|
19128
|
+
const st = statSync23(p);
|
|
18315
19129
|
if (st.isDirectory())
|
|
18316
19130
|
n += countFilesRecursive(p);
|
|
18317
19131
|
else if (st.isFile())
|
|
@@ -18320,8 +19134,8 @@ function countFilesRecursive(dir) {
|
|
|
18320
19134
|
return n;
|
|
18321
19135
|
}
|
|
18322
19136
|
function templateCount(tpl) {
|
|
18323
|
-
const d =
|
|
18324
|
-
if (!
|
|
19137
|
+
const d = join43(templatesDir(), tpl);
|
|
19138
|
+
if (!existsSync46(d))
|
|
18325
19139
|
return 0;
|
|
18326
19140
|
try {
|
|
18327
19141
|
return countFilesRecursive(d);
|
|
@@ -18330,22 +19144,22 @@ function templateCount(tpl) {
|
|
|
18330
19144
|
}
|
|
18331
19145
|
}
|
|
18332
19146
|
function skillsInstalled() {
|
|
18333
|
-
const sd =
|
|
18334
|
-
if (!
|
|
19147
|
+
const sd = join43(rollHome3(), "skills");
|
|
19148
|
+
if (!existsSync46(sd))
|
|
18335
19149
|
return 0;
|
|
18336
19150
|
try {
|
|
18337
|
-
return readdirSync20(sd).filter((n) =>
|
|
19151
|
+
return readdirSync20(sd).filter((n) => statSync23(join43(sd, n)).isDirectory()).length;
|
|
18338
19152
|
} catch {
|
|
18339
19153
|
return 0;
|
|
18340
19154
|
}
|
|
18341
19155
|
}
|
|
18342
19156
|
function launchdState(service, slug) {
|
|
18343
19157
|
const label4 = `com.roll.${service}.${slug}`;
|
|
18344
|
-
const plist =
|
|
18345
|
-
if (!
|
|
19158
|
+
const plist = join43(homedir17(), "Library", "LaunchAgents", `${label4}.plist`);
|
|
19159
|
+
if (!existsSync46(plist))
|
|
18346
19160
|
return "not-installed";
|
|
18347
19161
|
try {
|
|
18348
|
-
const out2 =
|
|
19162
|
+
const out2 = execFileSync9("launchctl", ["list", label4], {
|
|
18349
19163
|
encoding: "utf8",
|
|
18350
19164
|
stdio: ["ignore", "pipe", "ignore"]
|
|
18351
19165
|
});
|
|
@@ -18476,17 +19290,17 @@ function renderThisProject(out2, d) {
|
|
|
18476
19290
|
}
|
|
18477
19291
|
function liveData() {
|
|
18478
19292
|
const slug = projectSlugPy();
|
|
18479
|
-
const home =
|
|
19293
|
+
const home = homedir17();
|
|
18480
19294
|
const aiClients = parseAiEntries().map((e) => ({
|
|
18481
19295
|
name: e.name,
|
|
18482
19296
|
cfg_file: e.cfg_file,
|
|
18483
|
-
path:
|
|
19297
|
+
path: join43(e.ai_dir, e.cfg_file).replaceAll(home, "~"),
|
|
18484
19298
|
sync: aiSyncStatus(e),
|
|
18485
19299
|
skills: aiSkillCount(e)
|
|
18486
19300
|
}));
|
|
18487
19301
|
const featDir = ".roll/features";
|
|
18488
19302
|
let featCount = 0;
|
|
18489
|
-
if (
|
|
19303
|
+
if (existsSync46(featDir)) {
|
|
18490
19304
|
featCount = readdirSync20(featDir).filter((n) => n.endsWith(".md")).length;
|
|
18491
19305
|
}
|
|
18492
19306
|
return {
|
|
@@ -18494,8 +19308,8 @@ function liveData() {
|
|
|
18494
19308
|
ai_clients: aiClients,
|
|
18495
19309
|
templates: TEMPLATES.map((t2) => [t2, templateCount(t2)]),
|
|
18496
19310
|
skills_installed: skillsInstalled(),
|
|
18497
|
-
project_has_agents:
|
|
18498
|
-
project_has_backlog:
|
|
19311
|
+
project_has_agents: existsSync46("AGENTS.md"),
|
|
19312
|
+
project_has_backlog: existsSync46(".roll/backlog.md"),
|
|
18499
19313
|
project_features_count: featCount,
|
|
18500
19314
|
loop_state: launchdState("loop", slug),
|
|
18501
19315
|
dream_state: launchdState("dream", slug)
|
|
@@ -18519,8 +19333,8 @@ function statusCommand(args) {
|
|
|
18519
19333
|
|
|
18520
19334
|
// packages/cli/dist/commands/test.js
|
|
18521
19335
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
18522
|
-
import { existsSync as
|
|
18523
|
-
import { dirname 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";
|
|
18524
19338
|
function pal5() {
|
|
18525
19339
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
18526
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" };
|
|
@@ -18537,12 +19351,12 @@ function err16(line) {
|
|
|
18537
19351
|
}
|
|
18538
19352
|
var SUPPORTED_TYPES = ["none"];
|
|
18539
19353
|
function isolationGetType() {
|
|
18540
|
-
const file =
|
|
18541
|
-
if (!
|
|
19354
|
+
const file = join44(process.cwd(), ".roll", "local.yaml");
|
|
19355
|
+
if (!existsSync47(file))
|
|
18542
19356
|
return "none";
|
|
18543
19357
|
let text;
|
|
18544
19358
|
try {
|
|
18545
|
-
text =
|
|
19359
|
+
text = readFileSync40(file, "utf8");
|
|
18546
19360
|
} catch {
|
|
18547
19361
|
return "none";
|
|
18548
19362
|
}
|
|
@@ -18590,14 +19404,14 @@ function resetLockPath() {
|
|
|
18590
19404
|
return ".roll/.iso-reset.lock";
|
|
18591
19405
|
}
|
|
18592
19406
|
function resetLockHeld() {
|
|
18593
|
-
return
|
|
19407
|
+
return existsSync47(resetLockPath());
|
|
18594
19408
|
}
|
|
18595
19409
|
function resetAcquireLock() {
|
|
18596
19410
|
const lock = resetLockPath();
|
|
18597
|
-
if (
|
|
19411
|
+
if (existsSync47(lock))
|
|
18598
19412
|
return false;
|
|
18599
|
-
|
|
18600
|
-
|
|
19413
|
+
mkdirSync25(dirname20(lock), { recursive: true });
|
|
19414
|
+
writeFileSync25(lock, `${process.pid}
|
|
18601
19415
|
`);
|
|
18602
19416
|
return true;
|
|
18603
19417
|
}
|
|
@@ -18614,7 +19428,7 @@ function runForward(cmd, argv) {
|
|
|
18614
19428
|
}
|
|
18615
19429
|
function isolationDispatch(method, args) {
|
|
18616
19430
|
const type = isolationGetType();
|
|
18617
|
-
if (type === "none" && !
|
|
19431
|
+
if (type === "none" && !existsSync47(join44(process.cwd(), ".roll", "local.yaml"))) {
|
|
18618
19432
|
infoErr("isolation: no test_isolation config, falling back to type=none (host)");
|
|
18619
19433
|
}
|
|
18620
19434
|
if (!SUPPORTED_TYPES.includes(type)) {
|
|
@@ -18708,9 +19522,9 @@ function testCommand(args) {
|
|
|
18708
19522
|
|
|
18709
19523
|
// packages/cli/dist/commands/update.js
|
|
18710
19524
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18711
|
-
import { existsSync as
|
|
19525
|
+
import { existsSync as existsSync48, mkdtempSync as mkdtempSync4, readFileSync as readFileSync41, rmSync as rmSync13 } from "node:fs";
|
|
18712
19526
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
18713
|
-
import { join as
|
|
19527
|
+
import { join as join45 } from "node:path";
|
|
18714
19528
|
function pal6() {
|
|
18715
19529
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
18716
19530
|
return noColor2 ? { CYAN: "", GREEN: "", YELLOW: "", RED: "", BOLD: "", NC: "" } : {
|
|
@@ -18774,20 +19588,20 @@ function resolveRemoteVersion() {
|
|
|
18774
19588
|
}
|
|
18775
19589
|
function downloadAndInstallCurl(tag) {
|
|
18776
19590
|
const url = `https://github.com/seanyao/roll/archive/refs/tags/${tag}.tar.gz`;
|
|
18777
|
-
const tmpDir = mkdtempSync4(
|
|
19591
|
+
const tmpDir = mkdtempSync4(join45(tmpdir4(), "roll-update-"));
|
|
18778
19592
|
try {
|
|
18779
19593
|
info6(`[roll] Downloading roll ${tag} ...`);
|
|
18780
|
-
const dl = runForward2("curl", ["-fsSL", url, "-o",
|
|
19594
|
+
const dl = runForward2("curl", ["-fsSL", url, "-o", join45(tmpDir, "roll.tar.gz")]);
|
|
18781
19595
|
if (dl !== 0) {
|
|
18782
19596
|
err17(m6("update.curl_download_failed"));
|
|
18783
19597
|
return { ok: false };
|
|
18784
19598
|
}
|
|
18785
19599
|
info6("[roll] Extracting ...");
|
|
18786
|
-
const extractDir =
|
|
19600
|
+
const extractDir = join45(tmpDir, "extract");
|
|
18787
19601
|
spawnSync12("mkdir", ["-p", extractDir], { stdio: "ignore" });
|
|
18788
19602
|
const ex = runForward2("tar", [
|
|
18789
19603
|
"-xzf",
|
|
18790
|
-
|
|
19604
|
+
join45(tmpDir, "roll.tar.gz"),
|
|
18791
19605
|
"--strip-components=1",
|
|
18792
19606
|
"-C",
|
|
18793
19607
|
extractDir
|
|
@@ -18804,7 +19618,7 @@ function downloadAndInstallCurl(tag) {
|
|
|
18804
19618
|
function checkInstalledVersionOrRetry() {
|
|
18805
19619
|
const expected = (spawnSync12("npm", ["view", "@seanyao/roll", "version"], { encoding: "utf8" }).stdout ?? "").trim();
|
|
18806
19620
|
const pkgRoot = (spawnSync12("npm", ["root", "-g"], { encoding: "utf8" }).stdout ?? "").trim();
|
|
18807
|
-
const installedTree =
|
|
19621
|
+
const installedTree = join45(pkgRoot, "@seanyao", "roll");
|
|
18808
19622
|
const installed = treeVersion(installedTree);
|
|
18809
19623
|
if (expected === "" || installed === "")
|
|
18810
19624
|
return;
|
|
@@ -18818,18 +19632,18 @@ function checkInstalledVersionOrRetry() {
|
|
|
18818
19632
|
}
|
|
18819
19633
|
}
|
|
18820
19634
|
function invalidateUpdateCache() {
|
|
18821
|
-
rmSync13(
|
|
19635
|
+
rmSync13(join45(rollHome(), ".update-check"), { force: true });
|
|
18822
19636
|
}
|
|
18823
19637
|
function showChangelog() {
|
|
18824
|
-
const changelog =
|
|
18825
|
-
if (!
|
|
19638
|
+
const changelog = join45(rollPkgDir(), "CHANGELOG.md");
|
|
19639
|
+
if (!existsSync48(changelog))
|
|
18826
19640
|
return;
|
|
18827
19641
|
const { BOLD: BOLD2, CYAN, NC } = pal6();
|
|
18828
19642
|
process.stdout.write(`${BOLD2}${m6("changelog.heading")}:${NC}
|
|
18829
19643
|
`);
|
|
18830
19644
|
let count = 0;
|
|
18831
19645
|
let inSection = false;
|
|
18832
|
-
for (const line of
|
|
19646
|
+
for (const line of readFileSync41(changelog, "utf8").split("\n")) {
|
|
18833
19647
|
if (/^## /.test(line)) {
|
|
18834
19648
|
count += 1;
|
|
18835
19649
|
if (count > 3)
|
|
@@ -18849,9 +19663,9 @@ function updateCommand(args) {
|
|
|
18849
19663
|
void args;
|
|
18850
19664
|
info6(m6("update.current_version", rollVersion()));
|
|
18851
19665
|
let installMethod = "npm";
|
|
18852
|
-
const methodFile =
|
|
18853
|
-
if (
|
|
18854
|
-
installMethod =
|
|
19666
|
+
const methodFile = join45(rollPkgDir(), ".install-method");
|
|
19667
|
+
if (existsSync48(methodFile)) {
|
|
19668
|
+
installMethod = readFileSync41(methodFile, "utf8").trim() || "npm";
|
|
18855
19669
|
}
|
|
18856
19670
|
if (installMethod === "curl") {
|
|
18857
19671
|
info6(m6("update.upgrading_via_curl"));
|
|
@@ -18898,6 +19712,12 @@ function registerAll() {
|
|
|
18898
19712
|
registerPorted("doctor", doctorCommand);
|
|
18899
19713
|
registerPorted("attest", attestCommand);
|
|
18900
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
|
+
});
|
|
18901
19721
|
registerPorted("gc", gcCommand);
|
|
18902
19722
|
registerPorted("archive", (args) => {
|
|
18903
19723
|
if (args[0] === "migrate")
|