@seanyao/roll 3.614.3 → 3.614.4

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/roll.mjs +1266 -842
  3. package/package.json +1 -1
package/dist/roll.mjs CHANGED
@@ -4978,6 +4978,532 @@ var init_bus = __esm({
4978
4978
  }
4979
4979
  });
4980
4980
 
4981
+ // packages/core/dist/loop/signals.js
4982
+ function signalKindForMarker(marker) {
4983
+ if (marker === "tcr")
4984
+ return "tcr";
4985
+ if (marker === "skill" || marker === "story")
4986
+ return "skill";
4987
+ if (marker.startsWith("ci:"))
4988
+ return "ci";
4989
+ if (marker === "peer:gate" || marker === "peer")
4990
+ return "peer";
4991
+ if (marker === "attest:gate" || marker === "evidence:frame-opened" || marker === "attest")
4992
+ return "attest";
4993
+ if (marker.startsWith("pr:"))
4994
+ return "pr";
4995
+ if (marker === "alert" || marker === "error")
4996
+ return "alert";
4997
+ return null;
4998
+ }
4999
+ var init_signals = __esm({
5000
+ "packages/core/dist/loop/signals.js"() {
5001
+ "use strict";
5002
+ }
5003
+ });
5004
+
5005
+ // packages/core/dist/loop/activity-signal.js
5006
+ function newNormalizerState() {
5007
+ return {
5008
+ cycleId: "",
5009
+ seg: "cycle",
5010
+ pendingCommit: false,
5011
+ pendingPr: false,
5012
+ pendingCi: false,
5013
+ pendingStory: false,
5014
+ lastBashCmd: "",
5015
+ tcrCount: 0,
5016
+ editStreakFile: null,
5017
+ lastActionTs: 0,
5018
+ lastSummary: "",
5019
+ lastHeartbeatTs: 0
5020
+ };
5021
+ }
5022
+ function clip(s, n = 60) {
5023
+ const flat = s.replace(/\s+/g, " ").trim();
5024
+ return flat.length > n ? `${flat.slice(0, n - 1)}\u2026` : flat;
5025
+ }
5026
+ function basename2(p) {
5027
+ const trimmed = p.replace(/\/+$/, "");
5028
+ const i = trimmed.lastIndexOf("/");
5029
+ return i >= 0 ? trimmed.slice(i + 1) : trimmed;
5030
+ }
5031
+ function emit(st, nowMs, fields) {
5032
+ let tier = fields.tier ?? KIND_TIER[fields.kind];
5033
+ if (fields.kind === "test" && fields.result === "fail")
5034
+ tier = "A";
5035
+ const sig = {
5036
+ ts: nowMs,
5037
+ cycleId: st.cycleId,
5038
+ seg: fields.seg ?? st.seg,
5039
+ kind: fields.kind,
5040
+ tier,
5041
+ summary: fields.summary
5042
+ };
5043
+ if (fields.result !== void 0)
5044
+ sig.result = fields.result;
5045
+ if (fields.ref !== void 0 && fields.ref !== "")
5046
+ sig.ref = fields.ref;
5047
+ if (fields.detail !== void 0 && fields.detail !== "")
5048
+ sig.detail = fields.detail;
5049
+ if (fields.marker !== void 0) {
5050
+ const sk = signalKindForMarker(fields.marker);
5051
+ if (sk !== null)
5052
+ sig.signalKind = sk;
5053
+ }
5054
+ if (fields.kind !== "heartbeat") {
5055
+ st.lastActionTs = nowMs;
5056
+ st.lastSummary = fields.summary;
5057
+ }
5058
+ return sig;
5059
+ }
5060
+ function clearPending(st) {
5061
+ st.pendingCommit = false;
5062
+ st.pendingPr = false;
5063
+ st.pendingCi = false;
5064
+ st.pendingStory = false;
5065
+ }
5066
+ function resetState(st) {
5067
+ clearPending(st);
5068
+ st.tcrCount = 0;
5069
+ st.editStreakFile = null;
5070
+ st.seg = "cycle";
5071
+ }
5072
+ function matchBanner(line) {
5073
+ const header = /^──\s*cycle\s+(.+?)\s*──\s*$/.exec(line);
5074
+ if (header) {
5075
+ const inner = header[1];
5076
+ const id = /([0-9]{6,8}-\d+)/.exec(inner);
5077
+ return { label: clip(inner, 80), cycleId: id ? id[1] : inner.split(/\s+/)[0] ?? "" };
5078
+ }
5079
+ const m7 = /\[loop\]\s+cycle\s+(\d+)/.exec(line);
5080
+ if (m7)
5081
+ return { label: `cycle #${m7[1]}`, cycleId: m7[1] };
5082
+ return null;
5083
+ }
5084
+ function fmtElapsed(ms) {
5085
+ const s = Math.round(ms / 1e3);
5086
+ if (s < 60)
5087
+ return `${s}s`;
5088
+ return `${Math.floor(s / 60)}m ${s % 60}s`;
5089
+ }
5090
+ function maybeHeartbeat(st, nowMs, gapMs = DEFAULT_HEARTBEAT_GAP_MS) {
5091
+ const baseline = st.lastActionTs || st.lastHeartbeatTs;
5092
+ if (baseline === 0)
5093
+ return [];
5094
+ const since = Math.max(st.lastActionTs, st.lastHeartbeatTs);
5095
+ if (nowMs - since < gapMs)
5096
+ return [];
5097
+ st.lastHeartbeatTs = nowMs;
5098
+ const elapsed = fmtElapsed(nowMs - st.lastActionTs);
5099
+ const last = st.lastSummary !== "" ? ` \xB7 last: ${st.lastSummary}` : "";
5100
+ return [
5101
+ {
5102
+ ts: nowMs,
5103
+ cycleId: st.cycleId,
5104
+ seg: st.seg,
5105
+ kind: "heartbeat",
5106
+ tier: "A",
5107
+ summary: `\u2026still in ${st.seg} \xB7 ${elapsed}${last}`
5108
+ }
5109
+ ];
5110
+ }
5111
+ function claudeText(text, st, nowMs) {
5112
+ const t2 = text.trim();
5113
+ if (t2 === "")
5114
+ return [];
5115
+ for (const v of PEER_VERDICTS) {
5116
+ if (t2.includes(v)) {
5117
+ const rm = /round\s+(\d+)[/\\](\d+)/i.exec(t2);
5118
+ const round = rm ? `round ${rm[1]}/${rm[2]}` : "round ?";
5119
+ const am = /(\w+)\s*→\s*(\w+)/.exec(t2);
5120
+ const agents = am ? `${am[1]} \u2192 ${am[2]}` : "peer";
5121
+ st.editStreakFile = null;
5122
+ st.seg = "peer";
5123
+ return [emit(st, nowMs, {
5124
+ seg: "peer",
5125
+ kind: "gate",
5126
+ summary: agents,
5127
+ detail: `${round} \xB7 ${v}`,
5128
+ ref: agents,
5129
+ marker: "peer"
5130
+ })];
5131
+ }
5132
+ }
5133
+ return [emit(st, nowMs, { kind: "say", summary: clip(t2, 80) })];
5134
+ }
5135
+ function claudeToolUse(blk, st, nowMs) {
5136
+ const name = typeof blk["name"] === "string" ? blk["name"] : "";
5137
+ const input = blk["input"] ?? {};
5138
+ if (SUPPRESS_TOOLS.has(name))
5139
+ return [];
5140
+ if (name === "Edit" || name === "Write") {
5141
+ const path = typeof input["file_path"] === "string" && input["file_path"] || typeof input["path"] === "string" && input["path"] || "";
5142
+ if (path === st.editStreakFile)
5143
+ return [];
5144
+ st.editStreakFile = path;
5145
+ st.seg = "build";
5146
+ return [emit(st, nowMs, {
5147
+ seg: "build",
5148
+ kind: "edit",
5149
+ summary: basename2(path),
5150
+ ref: path
5151
+ })];
5152
+ }
5153
+ st.editStreakFile = null;
5154
+ if (name === "Bash") {
5155
+ const cmd = typeof input["command"] === "string" ? input["command"] : "";
5156
+ const first = cmd.split("\n").map((l) => l.trim()).find((l) => l !== "") ?? cmd;
5157
+ st.lastBashCmd = first;
5158
+ if (/git\s+commit[\s\S]*tcr:/.test(cmd)) {
5159
+ st.pendingCommit = true;
5160
+ return [];
5161
+ }
5162
+ if (/gh\s+pr\s+(create|merge)/.test(cmd)) {
5163
+ st.pendingPr = true;
5164
+ st.seg = "pr";
5165
+ return [];
5166
+ }
5167
+ if (/(roll\s+ci|npm\s+run\s+ci|_ci_wait|ci:local)/.test(cmd)) {
5168
+ st.pendingCi = true;
5169
+ st.seg = "ci";
5170
+ return [];
5171
+ }
5172
+ return [emit(st, nowMs, { kind: "tool", summary: clip(first, 60), ref: "bash" })];
5173
+ }
5174
+ if (name === "Skill") {
5175
+ const skill = typeof input["skill"] === "string" ? input["skill"] : "";
5176
+ if (skill === "roll-build" || skill === "roll-fix") {
5177
+ const args = (typeof input["args"] === "string" ? input["args"] : "").trim();
5178
+ const usId = args.split(/\s+/)[0] || "?";
5179
+ st.pendingStory = true;
5180
+ st.seg = "story";
5181
+ return [emit(st, nowMs, {
5182
+ seg: "story",
5183
+ kind: "lifecycle",
5184
+ summary: usId,
5185
+ detail: clip(args, 60),
5186
+ ref: usId,
5187
+ marker: "skill"
5188
+ })];
5189
+ }
5190
+ return [];
5191
+ }
5192
+ return [];
5193
+ }
5194
+ function extractResultText(content) {
5195
+ if (typeof content === "string")
5196
+ return content;
5197
+ if (Array.isArray(content)) {
5198
+ return content.filter((c2) => typeof c2 === "object" && c2 !== null && c2["type"] === "text").map((c2) => typeof c2["text"] === "string" ? c2["text"] : "").join("\n");
5199
+ }
5200
+ return content == null ? "" : String(content);
5201
+ }
5202
+ function claudeAssistant(ev, st, nowMs) {
5203
+ const msg6 = ev["message"] ?? {};
5204
+ const content = Array.isArray(msg6["content"]) ? msg6["content"] : [];
5205
+ const out3 = [];
5206
+ for (const blkRaw of content) {
5207
+ if (typeof blkRaw !== "object" || blkRaw === null)
5208
+ continue;
5209
+ const blk = blkRaw;
5210
+ const bt = blk["type"];
5211
+ if (bt === "thinking")
5212
+ continue;
5213
+ if (bt === "text")
5214
+ out3.push(...claudeText(typeof blk["text"] === "string" ? blk["text"] : "", st, nowMs));
5215
+ else if (bt === "tool_use")
5216
+ out3.push(...claudeToolUse(blk, st, nowMs));
5217
+ }
5218
+ return out3;
5219
+ }
5220
+ function claudeUser(ev, st, nowMs) {
5221
+ const msg6 = ev["message"] ?? {};
5222
+ const content = Array.isArray(msg6["content"]) ? msg6["content"] : [];
5223
+ const out3 = [];
5224
+ for (const blkRaw of content) {
5225
+ if (typeof blkRaw !== "object" || blkRaw === null)
5226
+ continue;
5227
+ const blk = blkRaw;
5228
+ if (blk["type"] !== "tool_result")
5229
+ continue;
5230
+ const text = extractResultText(blk["content"]);
5231
+ if (blk["is_error"] === true) {
5232
+ st.editStreakFile = null;
5233
+ const lines2 = text.split("\n").filter((l) => l.trim() !== "").slice(0, 3);
5234
+ clearPending(st);
5235
+ out3.push(emit(st, nowMs, {
5236
+ kind: "alert",
5237
+ summary: "tool",
5238
+ detail: clip(lines2.join(" | "), 80),
5239
+ result: "fail",
5240
+ marker: "alert"
5241
+ }));
5242
+ continue;
5243
+ }
5244
+ if (st.pendingCommit) {
5245
+ st.pendingCommit = false;
5246
+ const m7 = /\[[\w/-]+ ([0-9a-f]{7,})\]\s*tcr:\s*(.+)/.exec(text);
5247
+ if (m7) {
5248
+ st.tcrCount += 1;
5249
+ out3.push(emit(st, nowMs, {
5250
+ kind: "tcr",
5251
+ summary: m7[1].slice(0, 7),
5252
+ detail: clip(m7[2].trim(), 60),
5253
+ ref: m7[1].slice(0, 7),
5254
+ result: "pass",
5255
+ marker: "tcr"
5256
+ }));
5257
+ }
5258
+ continue;
5259
+ }
5260
+ if (st.pendingStory) {
5261
+ st.pendingStory = false;
5262
+ continue;
5263
+ }
5264
+ if (st.pendingPr) {
5265
+ st.pendingPr = false;
5266
+ const m7 = /#(\d+)/.exec(text);
5267
+ if (m7) {
5268
+ const branch = /loop\/[\w-]+/.exec(st.lastBashCmd);
5269
+ out3.push(emit(st, nowMs, {
5270
+ seg: "pr",
5271
+ kind: "pr",
5272
+ summary: `#${m7[1]}`,
5273
+ ref: `#${m7[1]}`,
5274
+ detail: branch ? `merged \xB7 ${branch[0]}` : "merged",
5275
+ result: "pass",
5276
+ marker: "pr:merge"
5277
+ }));
5278
+ }
5279
+ continue;
5280
+ }
5281
+ if (st.pendingCi) {
5282
+ st.pendingCi = false;
5283
+ const green = /(green|pass|success|all tests)/i.test(text);
5284
+ const red = /(red|fail|error)/i.test(text);
5285
+ const dur = /(\d+(?:\.\d+)?)\s*s\b/.exec(text);
5286
+ const tests = /(\d+)\s+tests?/.exec(text);
5287
+ const detail = [dur ? `${dur[1]}s` : "", tests ? `${tests[1]} tests` : ""].filter(Boolean).join(" \xB7 ");
5288
+ const ok8 = green && !red;
5289
+ out3.push(emit(st, nowMs, {
5290
+ seg: "ci",
5291
+ kind: "gate",
5292
+ summary: ok8 ? "green" : "red",
5293
+ result: ok8 ? "pass" : "fail",
5294
+ detail,
5295
+ marker: ok8 ? "ci:pass" : "ci:fail"
5296
+ }));
5297
+ continue;
5298
+ }
5299
+ }
5300
+ return out3;
5301
+ }
5302
+ function claudeResult(ev, st, nowMs) {
5303
+ st.editStreakFile = null;
5304
+ st.seg = "end";
5305
+ const durMs = typeof ev["duration_ms"] === "number" ? ev["duration_ms"] : 0;
5306
+ const cost = typeof ev["total_cost_usd"] === "number" ? ev["total_cost_usd"] : 0;
5307
+ const durS = Math.round(durMs / 1e3);
5308
+ if (ev["subtype"] === "error_max_turns") {
5309
+ return [emit(st, nowMs, { seg: "end", kind: "alert", summary: "max-turns", detail: `${durS}s`, result: "fail", marker: "alert" })];
5310
+ }
5311
+ const parts = [
5312
+ st.tcrCount > 0 ? `${st.tcrCount} tcr` : "",
5313
+ `${durS}s`,
5314
+ cost > 0 ? `$${cost.toFixed(2)}` : ""
5315
+ ].filter(Boolean);
5316
+ return [emit(st, nowMs, { seg: "end", kind: "lifecycle", summary: "cycle done", detail: parts.join(" \xB7 ") })];
5317
+ }
5318
+ function codexJsonl(line) {
5319
+ if (!/^\s*\{/.test(line))
5320
+ return null;
5321
+ try {
5322
+ const o = JSON.parse(line);
5323
+ const text = typeof o["text"] === "string" && o["text"] || typeof o["message"] === "string" && o["message"] || typeof o["content"] === "string" && o["content"] || typeof o["delta"] === "string" && o["delta"] || "";
5324
+ const kind = typeof o["type"] === "string" ? o["type"] : typeof o["role"] === "string" ? o["role"] : void 0;
5325
+ const r = {};
5326
+ if (kind !== void 0)
5327
+ r.kind = kind;
5328
+ if (typeof text === "string" && text !== "")
5329
+ r.text = text;
5330
+ return r;
5331
+ } catch {
5332
+ return null;
5333
+ }
5334
+ }
5335
+ function normalizerFor(agent) {
5336
+ const a = (agent ?? "").trim().toLowerCase();
5337
+ if (a === "claude")
5338
+ return claudeNormalizer;
5339
+ if (a === "codex")
5340
+ return codexNormalizer;
5341
+ return genericNormalizer;
5342
+ }
5343
+ var KIND_TIER, DEFAULT_HEARTBEAT_GAP_MS, SUPPRESS_TOOLS, PEER_VERDICTS, claudeNormalizer, VITEST_FAIL_RE, VITEST_PASS_RE, TEST_FILE_RE, DIFF_FILE_RE, UNIFIED_DIFF_RE, CMD_RE, codexNormalizer, genericNormalizer;
5344
+ var init_activity_signal = __esm({
5345
+ "packages/core/dist/loop/activity-signal.js"() {
5346
+ "use strict";
5347
+ init_signals();
5348
+ KIND_TIER = {
5349
+ lifecycle: "A",
5350
+ tcr: "A",
5351
+ commit: "A",
5352
+ pr: "A",
5353
+ gate: "A",
5354
+ alert: "A",
5355
+ heartbeat: "A",
5356
+ test: "B",
5357
+ // pass → B; a FAIL is promoted to A at emit time
5358
+ edit: "B",
5359
+ tool: "B",
5360
+ say: "C"
5361
+ };
5362
+ DEFAULT_HEARTBEAT_GAP_MS = 45e3;
5363
+ SUPPRESS_TOOLS = /* @__PURE__ */ new Set([
5364
+ "Read",
5365
+ "Glob",
5366
+ "Grep",
5367
+ "ReadMcpResourceTool",
5368
+ "ListMcpResourcesTool",
5369
+ "WebFetch",
5370
+ "WebSearch",
5371
+ "TaskCreate",
5372
+ "TaskGet",
5373
+ "TaskList",
5374
+ "TaskUpdate",
5375
+ "TaskOutput",
5376
+ "TaskStop"
5377
+ ]);
5378
+ PEER_VERDICTS = ["AGREE", "REFINE", "OBJECT", "ESCALATE"];
5379
+ claudeNormalizer = {
5380
+ agent: "claude",
5381
+ reset: resetState,
5382
+ normalize(raw, st, nowMs) {
5383
+ const line = raw.replace(/\s+$/, "");
5384
+ if (line.trim() === "")
5385
+ return [];
5386
+ const banner = matchBanner(line);
5387
+ if (banner) {
5388
+ resetState(st);
5389
+ st.cycleId = banner.cycleId;
5390
+ return [emit(st, nowMs, { seg: "cycle", kind: "lifecycle", summary: banner.label })];
5391
+ }
5392
+ let ev;
5393
+ try {
5394
+ ev = JSON.parse(line);
5395
+ } catch {
5396
+ return [];
5397
+ }
5398
+ if (typeof ev !== "object" || ev === null)
5399
+ return [];
5400
+ const type = ev.type;
5401
+ if (type === "system")
5402
+ return [];
5403
+ if (type === "assistant")
5404
+ return claudeAssistant(ev, st, nowMs);
5405
+ if (type === "user")
5406
+ return claudeUser(ev, st, nowMs);
5407
+ if (type === "result")
5408
+ return claudeResult(ev, st, nowMs);
5409
+ return [];
5410
+ }
5411
+ };
5412
+ VITEST_FAIL_RE = /(\d+)\s+failed|FAIL\b|✗|✘|✖|tests?\s+failed|failed\s+\(\d+\)/i;
5413
+ VITEST_PASS_RE = /\b(\d+)\s+passed\b|✓\s+\d+|all tests pass|tests?\s+passed/i;
5414
+ TEST_FILE_RE = /([\w./-]+\.(?:test|spec)\.[jt]sx?)(?::(\d+))?/;
5415
+ DIFF_FILE_RE = /(?:^|\s)(?:✎|✏|edit(?:ing)?|writing|modified|patch(?:ing)?)\s+([\w./-]+)/i;
5416
+ UNIFIED_DIFF_RE = /^\s*(?:\+\+\+|---)\s+[ab]\/([\w./-]+)/;
5417
+ CMD_RE = /^\s*(?:\$|>|Running:|Exec:|Command:|\+\s)\s*(.+)$/;
5418
+ codexNormalizer = {
5419
+ agent: "codex",
5420
+ reset: resetState,
5421
+ normalize(raw, st, nowMs) {
5422
+ const line = raw.replace(/\s+$/, "");
5423
+ if (line.trim() === "")
5424
+ return [];
5425
+ const banner = matchBanner(line);
5426
+ if (banner) {
5427
+ resetState(st);
5428
+ st.cycleId = banner.cycleId;
5429
+ return [emit(st, nowMs, { seg: "cycle", kind: "lifecycle", summary: banner.label })];
5430
+ }
5431
+ let text = line;
5432
+ const j = codexJsonl(line);
5433
+ if (j !== null) {
5434
+ if (j.text === void 0)
5435
+ return [];
5436
+ text = j.text;
5437
+ }
5438
+ const t2 = text.trim();
5439
+ if (t2 === "")
5440
+ return [];
5441
+ const fileM = TEST_FILE_RE.exec(t2);
5442
+ if (VITEST_FAIL_RE.test(t2)) {
5443
+ st.seg = "ci";
5444
+ st.editStreakFile = null;
5445
+ const ref = fileM ? fileM[1] + (fileM[2] ? `:${fileM[2]}` : "") : "";
5446
+ return [emit(st, nowMs, { seg: "ci", kind: "test", summary: clip(t2, 60), result: "fail", ref, marker: "ci:fail" })];
5447
+ }
5448
+ if (VITEST_PASS_RE.test(t2)) {
5449
+ st.seg = "ci";
5450
+ st.editStreakFile = null;
5451
+ const ref = fileM ? fileM[1] + (fileM[2] ? `:${fileM[2]}` : "") : "";
5452
+ return [emit(st, nowMs, { seg: "ci", kind: "test", summary: clip(t2, 60), result: "pass", ref, marker: "ci:pass" })];
5453
+ }
5454
+ const tcrM = /\[[\w/-]+\s+([0-9a-f]{7,})\]\s*tcr:\s*(.+)/.exec(t2);
5455
+ if (tcrM) {
5456
+ st.tcrCount += 1;
5457
+ st.editStreakFile = null;
5458
+ return [emit(st, nowMs, { kind: "tcr", summary: tcrM[1].slice(0, 7), detail: clip(tcrM[2].trim(), 60), ref: tcrM[1].slice(0, 7), result: "pass", marker: "tcr" })];
5459
+ }
5460
+ const prM = /(?:Merged|merge[ds]?)\b[^#]*#(\d+)/.exec(t2);
5461
+ if (prM) {
5462
+ st.seg = "pr";
5463
+ st.editStreakFile = null;
5464
+ return [emit(st, nowMs, { seg: "pr", kind: "pr", summary: `#${prM[1]}`, ref: `#${prM[1]}`, detail: "merged", result: "pass", marker: "pr:merge" })];
5465
+ }
5466
+ const diffM = DIFF_FILE_RE.exec(t2) ?? UNIFIED_DIFF_RE.exec(t2);
5467
+ if (diffM) {
5468
+ const path = diffM[1];
5469
+ if (path === st.editStreakFile)
5470
+ return [];
5471
+ st.editStreakFile = path;
5472
+ st.seg = "build";
5473
+ return [emit(st, nowMs, { seg: "build", kind: "edit", summary: basename2(path), ref: path })];
5474
+ }
5475
+ const cmdM = CMD_RE.exec(t2);
5476
+ if (cmdM) {
5477
+ st.editStreakFile = null;
5478
+ const cmd = cmdM[1].trim();
5479
+ if (/git\s+commit/.test(cmd) || /gh\s+pr\s+(create|merge)/.test(cmd))
5480
+ st.seg = "pr";
5481
+ else if (/test|vitest|pnpm\s+-r/.test(cmd))
5482
+ st.seg = "ci";
5483
+ return [emit(st, nowMs, { kind: "tool", summary: clip(cmd, 60), ref: "cmd" })];
5484
+ }
5485
+ return [emit(st, nowMs, { kind: "say", summary: clip(t2, 80) })];
5486
+ }
5487
+ };
5488
+ genericNormalizer = {
5489
+ agent: "generic",
5490
+ reset: resetState,
5491
+ normalize(raw, st, nowMs) {
5492
+ const line = raw.replace(/\s+$/, "");
5493
+ if (line.trim() === "")
5494
+ return [];
5495
+ const banner = matchBanner(line);
5496
+ if (banner) {
5497
+ resetState(st);
5498
+ st.cycleId = banner.cycleId;
5499
+ return [emit(st, nowMs, { seg: "cycle", kind: "lifecycle", summary: banner.label })];
5500
+ }
5501
+ return [emit(st, nowMs, { kind: "say", summary: clip(line, 100) })];
5502
+ }
5503
+ };
5504
+ }
5505
+ });
5506
+
4981
5507
  // packages/core/dist/loop/alert-loop.js
4982
5508
  function alertFileName(slug) {
4983
5509
  return `ALERT-${slug}.md`;
@@ -5207,14 +5733,14 @@ function classifyAttribution(storyId, reasons) {
5207
5733
  }
5208
5734
  };
5209
5735
  }
5210
- function priorCorrectionCount(events, storyId, signal2) {
5736
+ function priorCorrectionCount(events, storyId, signal) {
5211
5737
  let n = 0;
5212
5738
  for (const ev of events) {
5213
5739
  if (ev.type !== "correction:action")
5214
5740
  continue;
5215
5741
  if (ev.storyId !== storyId)
5216
5742
  continue;
5217
- if (ev.signal !== signal2)
5743
+ if (ev.signal !== signal)
5218
5744
  continue;
5219
5745
  n += 1;
5220
5746
  }
@@ -5245,8 +5771,8 @@ var init_correction_actuator = __esm({
5245
5771
  });
5246
5772
 
5247
5773
  // packages/core/dist/loop/correction-safety.js
5248
- function normalizeSignal(signal2) {
5249
- return signal2.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 160);
5774
+ function normalizeSignal(signal) {
5775
+ return signal.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 160);
5250
5776
  }
5251
5777
  function cycleStoryMap(events) {
5252
5778
  const out3 = /* @__PURE__ */ new Map();
@@ -5261,26 +5787,26 @@ function correctionSignals(events) {
5261
5787
  const out3 = [];
5262
5788
  for (const ev of events) {
5263
5789
  if (ev.type === "correction:action") {
5264
- const signal2 = normalizeSignal(ev.signal);
5265
- if (signal2 === "")
5790
+ const signal = normalizeSignal(ev.signal);
5791
+ if (signal === "")
5266
5792
  continue;
5267
5793
  out3.push({
5268
5794
  storyId: ev.storyId,
5269
5795
  ...ev.cycleId !== void 0 ? { cycleId: ev.cycleId } : {},
5270
- signal: signal2,
5796
+ signal,
5271
5797
  action: ev.action,
5272
5798
  ts: ev.ts,
5273
5799
  source: "correction"
5274
5800
  });
5275
5801
  } else if (ev.type === "attest:gate" && ev.verdict === "skipped") {
5276
- const signal2 = normalizeSignal(ev.reasons[0] ?? "attest gate skipped");
5277
- if (signal2 === "")
5802
+ const signal = normalizeSignal(ev.reasons[0] ?? "attest gate skipped");
5803
+ if (signal === "")
5278
5804
  continue;
5279
5805
  const storyId = stories.get(ev.cycleId);
5280
5806
  out3.push({
5281
5807
  ...storyId !== void 0 ? { storyId } : {},
5282
5808
  cycleId: ev.cycleId,
5283
- signal: signal2,
5809
+ signal,
5284
5810
  action: "attest_skipped",
5285
5811
  ts: ev.ts,
5286
5812
  source: "attest"
@@ -5328,7 +5854,7 @@ function correctionSignalVerdict(events, safety, nowSec2) {
5328
5854
  for (const sig of inWindow) {
5329
5855
  bySignal.set(sig.signal, [...bySignal.get(sig.signal) ?? [], sig]);
5330
5856
  }
5331
- for (const [signal2, hits] of [...bySignal.entries()].sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]))) {
5857
+ for (const [signal, hits] of [...bySignal.entries()].sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]))) {
5332
5858
  if (hits.length < threshold)
5333
5859
  continue;
5334
5860
  const knownStories = new Set(hits.map((h) => h.storyId).filter((s) => s !== void 0 && s !== ""));
@@ -5338,10 +5864,10 @@ function correctionSignalVerdict(events, safety, nowSec2) {
5338
5864
  action: "pause_and_notify",
5339
5865
  kind: "signal_repeat",
5340
5866
  ...knownStories.size > 0 ? { storyId: [...knownStories].sort().join(",") } : {},
5341
- signal: signal2,
5867
+ signal,
5342
5868
  count: hits.length,
5343
5869
  threshold,
5344
- reason: `failure signal repeated: "${signal2}" ${hits.length} times in ${windowSec}s >= ${threshold}`
5870
+ reason: `failure signal repeated: "${signal}" ${hits.length} times in ${windowSec}s >= ${threshold}`
5345
5871
  };
5346
5872
  }
5347
5873
  return { action: "continue" };
@@ -5806,389 +6332,120 @@ function cycleStep(state, event) {
5806
6332
  }
5807
6333
  case "facts_captured": {
5808
6334
  const status2 = classifyCaptured(event.facts);
5809
- const next = { ...state, phase: "reconcile", captured: event.facts };
5810
- if (status2 !== "built") {
5811
- const extra = status2 === "idle" || status2 === "published" ? [{ kind: "cleanup_worktree", branch: state.ctx.branch }] : status2 === "failed" && (event.facts.mainAhead ?? 0) > 0 ? [
5812
- {
5813
- kind: "append_alert",
5814
- message: `cycle ${state.ctx.cycleId}: local main is ahead of origin/main by ${event.facts.mainAhead} commit(s) while cycle branch has ${event.facts.commitsAhead} commit(s); leaving state untouched for rescue (FIX-252)`
5815
- }
5816
- ] : status2 === "failed" && event.facts.commitsAhead > 0 ? [
5817
- { kind: "push_orphan", branch: state.ctx.branch },
5818
- {
5819
- kind: "append_alert",
5820
- message: `cycle ${state.ctx.cycleId}: gate-killed with ${event.facts.commitsAhead} commit(s) \u2014 branch ${state.ctx.branch} pushed for audit/rescue; next cycle starts fresh by design (FIX-247)`
5821
- }
5822
- ] : [];
5823
- return terminate(next, status2, extra);
5824
- }
5825
- return {
5826
- state: { ...next, phase: "publish" },
5827
- commands: [{ kind: "publish_pr", branch: state.ctx.branch, docOnly: false }]
5828
- };
5829
- }
5830
- case "published": {
5831
- const status2 = classifyPublish(event.result);
5832
- if (status2 === "published" || status2 === "done") {
5833
- return terminate({ ...state, phase: "cleanup" }, status2, [
5834
- { kind: "cleanup_worktree", branch: state.ctx.branch }
5835
- ]);
5836
- }
5837
- if (status2 === "orphan") {
5838
- return terminate({ ...state, phase: "cleanup" }, "orphan", [
5839
- { kind: "cleanup_worktree", branch: state.ctx.branch },
5840
- { kind: "append_alert", message: `cycle ${state.ctx.cycleId}: publish failed; orphan branch+tag pushed; worktree cleaned` }
5841
- ]);
5842
- }
5843
- return terminate({ ...state, phase: "publish" }, "failed", [
5844
- { kind: "append_alert", message: `cycle ${state.ctx.cycleId}: publish failed; worktree preserved at branch ${state.ctx.branch}` }
5845
- ]);
5846
- }
5847
- case "merge_polled": {
5848
- const action = nextWaitAction(event.state, event.elapsedSec);
5849
- if (action.kind === "wait") {
5850
- return {
5851
- state: { ...state, phase: "merge-wait" },
5852
- commands: [{ kind: "wait_merge", branch: state.ctx.branch, elapsedSec: event.elapsedSec + action.sleepSeconds }]
5853
- };
5854
- }
5855
- if (action.kind === "merged") {
5856
- return terminate({ ...state, phase: "reconcile" }, "done", [
5857
- { kind: "reconcile" },
5858
- { kind: "cleanup_worktree", branch: state.ctx.branch }
5859
- ]);
5860
- }
5861
- return terminate({ ...state, phase: "merge-wait" }, "orphan", [
5862
- { kind: "push_orphan", branch: state.ctx.branch }
5863
- ]);
5864
- }
5865
- default:
5866
- return { state, commands: [] };
5867
- }
5868
- }
5869
- function cycleStartEvent(ctx, ts = 0) {
5870
- return {
5871
- type: "cycle:start",
5872
- cycleId: ctx.cycleId,
5873
- storyId: ctx.storyId ?? "",
5874
- agent: ctx.agent ?? "",
5875
- model: ctx.model ?? "",
5876
- ts
5877
- };
5878
- }
5879
- function initialCycleState(ctx) {
5880
- return { phase: "pick", ctx, attempt: 0, done: false };
5881
- }
5882
- var CYCLE_TIMEOUT_SEC, WATCHDOG_KILL_GRACE_SEC, MAX_AGENT_ATTEMPTS, RETRY_BASE_BACKOFF_SEC, EVENT_VALID_PHASES;
5883
- var init_orchestrator = __esm({
5884
- "packages/core/dist/loop/orchestrator.js"() {
5885
- "use strict";
5886
- init_pr();
5887
- CYCLE_TIMEOUT_SEC = 2700;
5888
- WATCHDOG_KILL_GRACE_SEC = 5;
5889
- MAX_AGENT_ATTEMPTS = 3;
5890
- RETRY_BASE_BACKOFF_SEC = 30;
5891
- EVENT_VALID_PHASES = {
5892
- preflight_done: ["pick"],
5893
- worktree_created: ["worktree"],
5894
- worktree_failed: ["worktree"],
5895
- story_picked: ["pick"],
5896
- no_story: ["pick"],
5897
- route_resolved: ["route"],
5898
- budget_ok: ["route"],
5899
- budget_halt: ["route"],
5900
- agent_exited: ["execute"],
5901
- facts_captured: ["reconcile"],
5902
- published: ["publish"],
5903
- merge_polled: ["merge-wait", "publish"],
5904
- reconciled: ["reconcile"],
5905
- cleaned: ["cleanup"]
5906
- };
5907
- }
5908
- });
5909
-
5910
- // packages/core/dist/loop/recovery.js
5911
- function resolveKeepDays(envDays, yamlDays) {
5912
- if (envDays !== void 0 && /^\d+$/.test(envDays.trim()))
5913
- return Number(envDays.trim());
5914
- if (yamlDays !== void 0 && Number.isFinite(yamlDays) && yamlDays >= 0)
5915
- return Math.trunc(yamlDays);
5916
- return GC_DEFAULT_KEEP_DAYS;
5917
- }
5918
- var GC_DEFAULT_KEEP_DAYS;
5919
- var init_recovery = __esm({
5920
- "packages/core/dist/loop/recovery.js"() {
5921
- "use strict";
5922
- GC_DEFAULT_KEEP_DAYS = 30;
5923
- }
5924
- });
5925
-
5926
- // packages/core/dist/loop/signals.js
5927
- function signalKindForMarker(marker) {
5928
- if (marker === "tcr")
5929
- return "tcr";
5930
- if (marker === "skill" || marker === "story")
5931
- return "skill";
5932
- if (marker.startsWith("ci:"))
5933
- return "ci";
5934
- if (marker === "peer:gate" || marker === "peer")
5935
- return "peer";
5936
- if (marker === "attest:gate" || marker === "evidence:frame-opened" || marker === "attest")
5937
- return "attest";
5938
- if (marker.startsWith("pr:"))
5939
- return "pr";
5940
- if (marker === "alert" || marker === "error")
5941
- return "alert";
5942
- return null;
5943
- }
5944
- var init_signals = __esm({
5945
- "packages/core/dist/loop/signals.js"() {
5946
- "use strict";
5947
- }
5948
- });
5949
-
5950
- // packages/core/dist/loop/loop-fmt.js
5951
- function newFmtState() {
5952
- return {
5953
- pendingCommit: false,
5954
- pendingPr: false,
5955
- pendingCi: false,
5956
- pendingStory: false,
5957
- lastBashCmd: "",
5958
- tcrCount: 0,
5959
- editStreakFile: null
5960
- };
5961
- }
5962
- function clip(s, n = 60) {
5963
- const flat = s.replace(/\s+/g, " ").trim();
5964
- return flat.length > n ? `${flat.slice(0, n - 1)}\u2026` : flat;
5965
- }
5966
- function basename2(p) {
5967
- const trimmed = p.replace(/\/+$/, "");
5968
- const i = trimmed.lastIndexOf("/");
5969
- return i >= 0 ? trimmed.slice(i + 1) : trimmed;
5970
- }
5971
- function clearPending(st) {
5972
- st.pendingCommit = false;
5973
- st.pendingPr = false;
5974
- st.pendingCi = false;
5975
- st.pendingStory = false;
5976
- }
5977
- function signal(marker, category, label4, detail, ok8 = true) {
5978
- const kind = signalKindForMarker(marker);
5979
- const line = { tier: "signal", category, label: label4, ok: ok8 };
5980
- if (kind !== null)
5981
- line.kind = kind;
5982
- if (detail !== void 0 && detail !== "")
5983
- line.detail = detail;
5984
- return line;
5985
- }
5986
- function formatLine(raw, st) {
5987
- const line = raw.replace(/\s+$/, "");
5988
- if (line.trim() === "")
5989
- return [];
5990
- const header = /^──\s*cycle\s+(.+?)\s*──\s*$/.exec(line);
5991
- if (header) {
5992
- clearPending(st);
5993
- st.tcrCount = 0;
5994
- st.editStreakFile = null;
5995
- return [{ tier: "banner", category: "cycle", label: clip(header[1], 80) }];
5996
- }
5997
- let ev;
5998
- try {
5999
- ev = JSON.parse(line);
6000
- } catch {
6001
- const m7 = /\[loop\]\s+cycle\s+(\d+)/.exec(line);
6002
- if (m7) {
6003
- clearPending(st);
6004
- st.tcrCount = 0;
6005
- return [{ tier: "banner", category: "cycle", label: `cycle #${m7[1]}` }];
6006
- }
6007
- return [];
6008
- }
6009
- if (typeof ev !== "object" || ev === null)
6010
- return [];
6011
- const type = ev.type;
6012
- if (type === "system")
6013
- return [];
6014
- if (type === "assistant")
6015
- return handleAssistant(ev, st);
6016
- if (type === "user")
6017
- return handleUser(ev, st);
6018
- if (type === "result")
6019
- return handleResult(ev, st);
6020
- return [];
6021
- }
6022
- function handleAssistant(ev, st) {
6023
- const msg6 = ev["message"] ?? {};
6024
- const content = Array.isArray(msg6["content"]) ? msg6["content"] : [];
6025
- const out3 = [];
6026
- for (const blkRaw of content) {
6027
- if (typeof blkRaw !== "object" || blkRaw === null)
6028
- continue;
6029
- const blk = blkRaw;
6030
- const bt = blk["type"];
6031
- if (bt === "thinking")
6032
- continue;
6033
- if (bt === "text") {
6034
- out3.push(...handleText(typeof blk["text"] === "string" ? blk["text"] : "", st));
6035
- } else if (bt === "tool_use") {
6036
- out3.push(...handleToolUse(blk, st));
6037
- }
6038
- }
6039
- return out3;
6040
- }
6041
- function handleText(text, st) {
6042
- const t2 = text.trim();
6043
- if (t2 === "")
6044
- return [];
6045
- for (const v of PEER_VERDICTS) {
6046
- if (t2.includes(v)) {
6047
- const rm = /round\s+(\d+)[/\\](\d+)/i.exec(t2);
6048
- const round = rm ? `round ${rm[1]}/${rm[2]}` : "round ?";
6049
- const am = /(\w+)\s*→\s*(\w+)/.exec(t2);
6050
- const agents = am ? `${am[1]} \u2192 ${am[2]}` : "peer";
6051
- st.editStreakFile = null;
6052
- return [signal("peer", "peer", agents, `${round} \xB7 ${v}`)];
6053
- }
6054
- }
6055
- return [];
6056
- }
6057
- function handleToolUse(blk, st) {
6058
- const name = typeof blk["name"] === "string" ? blk["name"] : "";
6059
- const input = blk["input"] ?? {};
6060
- if (SUPPRESS_TOOLS.has(name))
6061
- return [];
6062
- if (name === "Edit" || name === "Write") {
6063
- const path = typeof input["file_path"] === "string" && input["file_path"] || typeof input["path"] === "string" && input["path"] || "";
6064
- if (path === st.editStreakFile)
6065
- return [];
6066
- st.editStreakFile = path;
6067
- return [{ tier: "muted", category: "\u270F", label: `\u270F ${basename2(path)}` }];
6068
- }
6069
- st.editStreakFile = null;
6070
- if (name === "Bash") {
6071
- const cmd = typeof input["command"] === "string" ? input["command"] : "";
6072
- const first = cmd.split("\n").map((l) => l.trim()).find((l) => l !== "") ?? cmd;
6073
- st.lastBashCmd = first;
6074
- if (/git\s+commit[\s\S]*tcr:/.test(cmd))
6075
- st.pendingCommit = true;
6076
- else if (/gh\s+pr\s+(create|merge)/.test(cmd))
6077
- st.pendingPr = true;
6078
- else if (/(roll\s+ci|npm\s+run\s+ci|_ci_wait|ci:local)/.test(cmd))
6079
- st.pendingCi = true;
6080
- return [];
6081
- }
6082
- if (name === "Skill") {
6083
- const skill = typeof input["skill"] === "string" ? input["skill"] : "";
6084
- if (skill === "roll-build" || skill === "roll-fix") {
6085
- const args = (typeof input["args"] === "string" ? input["args"] : "").trim();
6086
- const usId = args.split(/\s+/)[0] || "?";
6087
- st.pendingStory = true;
6088
- return [signal("skill", "story", usId, clip(args, 60))];
6089
- }
6090
- return [];
6091
- }
6092
- return [];
6093
- }
6094
- function extractResultText(content) {
6095
- if (typeof content === "string")
6096
- return content;
6097
- if (Array.isArray(content)) {
6098
- return content.filter((c2) => typeof c2 === "object" && c2 !== null && c2["type"] === "text").map((c2) => typeof c2["text"] === "string" ? c2["text"] : "").join("\n");
6099
- }
6100
- return content == null ? "" : String(content);
6101
- }
6102
- function handleUser(ev, st) {
6103
- const msg6 = ev["message"] ?? {};
6104
- const content = Array.isArray(msg6["content"]) ? msg6["content"] : [];
6105
- const out3 = [];
6106
- for (const blkRaw of content) {
6107
- if (typeof blkRaw !== "object" || blkRaw === null)
6108
- continue;
6109
- const blk = blkRaw;
6110
- if (blk["type"] !== "tool_result")
6111
- continue;
6112
- const text = extractResultText(blk["content"]);
6113
- if (blk["is_error"] === true) {
6114
- st.editStreakFile = null;
6115
- const lines2 = text.split("\n").filter((l) => l.trim() !== "").slice(0, 3);
6116
- clearPending(st);
6117
- out3.push(signal("error", "error", "tool", clip(lines2.join(" | "), 80), false));
6118
- continue;
6119
- }
6120
- if (st.pendingCommit) {
6121
- st.pendingCommit = false;
6122
- const m7 = /\[[\w/-]+ ([0-9a-f]{7,})\]\s*tcr:\s*(.+)/.exec(text);
6123
- if (m7) {
6124
- st.tcrCount += 1;
6125
- out3.push(signal("tcr", "tcr", m7[1].slice(0, 7), clip(m7[2].trim(), 60)));
6335
+ const next = { ...state, phase: "reconcile", captured: event.facts };
6336
+ if (status2 !== "built") {
6337
+ const extra = status2 === "idle" || status2 === "published" ? [{ kind: "cleanup_worktree", branch: state.ctx.branch }] : status2 === "failed" && (event.facts.mainAhead ?? 0) > 0 ? [
6338
+ {
6339
+ kind: "append_alert",
6340
+ message: `cycle ${state.ctx.cycleId}: local main is ahead of origin/main by ${event.facts.mainAhead} commit(s) while cycle branch has ${event.facts.commitsAhead} commit(s); leaving state untouched for rescue (FIX-252)`
6341
+ }
6342
+ ] : status2 === "failed" && event.facts.commitsAhead > 0 ? [
6343
+ { kind: "push_orphan", branch: state.ctx.branch },
6344
+ {
6345
+ kind: "append_alert",
6346
+ message: `cycle ${state.ctx.cycleId}: gate-killed with ${event.facts.commitsAhead} commit(s) \u2014 branch ${state.ctx.branch} pushed for audit/rescue; next cycle starts fresh by design (FIX-247)`
6347
+ }
6348
+ ] : [];
6349
+ return terminate(next, status2, extra);
6126
6350
  }
6127
- continue;
6128
- }
6129
- if (st.pendingStory) {
6130
- st.pendingStory = false;
6131
- continue;
6351
+ return {
6352
+ state: { ...next, phase: "publish" },
6353
+ commands: [{ kind: "publish_pr", branch: state.ctx.branch, docOnly: false }]
6354
+ };
6132
6355
  }
6133
- if (st.pendingPr) {
6134
- st.pendingPr = false;
6135
- const m7 = /#(\d+)/.exec(text);
6136
- if (m7) {
6137
- const branch = /loop\/[\w-]+/.exec(st.lastBashCmd);
6138
- out3.push(signal("pr:merge", "pr", `#${m7[1]}`, branch ? `merged \xB7 ${branch[0]}` : "merged"));
6356
+ case "published": {
6357
+ const status2 = classifyPublish(event.result);
6358
+ if (status2 === "published" || status2 === "done") {
6359
+ return terminate({ ...state, phase: "cleanup" }, status2, [
6360
+ { kind: "cleanup_worktree", branch: state.ctx.branch }
6361
+ ]);
6139
6362
  }
6140
- continue;
6363
+ if (status2 === "orphan") {
6364
+ return terminate({ ...state, phase: "cleanup" }, "orphan", [
6365
+ { kind: "cleanup_worktree", branch: state.ctx.branch },
6366
+ { kind: "append_alert", message: `cycle ${state.ctx.cycleId}: publish failed; orphan branch+tag pushed; worktree cleaned` }
6367
+ ]);
6368
+ }
6369
+ return terminate({ ...state, phase: "publish" }, "failed", [
6370
+ { kind: "append_alert", message: `cycle ${state.ctx.cycleId}: publish failed; worktree preserved at branch ${state.ctx.branch}` }
6371
+ ]);
6141
6372
  }
6142
- if (st.pendingCi) {
6143
- st.pendingCi = false;
6144
- const green = /(green|pass|success|all tests)/i.test(text);
6145
- const red = /(red|fail|error)/i.test(text);
6146
- const dur = /(\d+(?:\.\d+)?)\s*s\b/.exec(text);
6147
- const tests = /(\d+)\s+tests?/.exec(text);
6148
- const detail = [dur ? `${dur[1]}s` : "", tests ? `${tests[1]} tests` : ""].filter(Boolean).join(" \xB7 ");
6149
- const ok8 = green && !red;
6150
- out3.push(signal(ok8 ? "ci:pass" : "ci:fail", "ci", ok8 ? "green" : "red", detail, ok8));
6151
- continue;
6373
+ case "merge_polled": {
6374
+ const action = nextWaitAction(event.state, event.elapsedSec);
6375
+ if (action.kind === "wait") {
6376
+ return {
6377
+ state: { ...state, phase: "merge-wait" },
6378
+ commands: [{ kind: "wait_merge", branch: state.ctx.branch, elapsedSec: event.elapsedSec + action.sleepSeconds }]
6379
+ };
6380
+ }
6381
+ if (action.kind === "merged") {
6382
+ return terminate({ ...state, phase: "reconcile" }, "done", [
6383
+ { kind: "reconcile" },
6384
+ { kind: "cleanup_worktree", branch: state.ctx.branch }
6385
+ ]);
6386
+ }
6387
+ return terminate({ ...state, phase: "merge-wait" }, "orphan", [
6388
+ { kind: "push_orphan", branch: state.ctx.branch }
6389
+ ]);
6152
6390
  }
6391
+ default:
6392
+ return { state, commands: [] };
6153
6393
  }
6154
- return out3;
6155
6394
  }
6156
- function handleResult(ev, st) {
6157
- st.editStreakFile = null;
6158
- const durMs = typeof ev["duration_ms"] === "number" ? ev["duration_ms"] : 0;
6159
- const cost = typeof ev["total_cost_usd"] === "number" ? ev["total_cost_usd"] : 0;
6160
- const durS = Math.round(durMs / 1e3);
6161
- if (ev["subtype"] === "error_max_turns") {
6162
- return [signal("error", "error", "max-turns", `${durS}s`, false)];
6395
+ function cycleStartEvent(ctx, ts = 0) {
6396
+ return {
6397
+ type: "cycle:start",
6398
+ cycleId: ctx.cycleId,
6399
+ storyId: ctx.storyId ?? "",
6400
+ agent: ctx.agent ?? "",
6401
+ model: ctx.model ?? "",
6402
+ ts
6403
+ };
6404
+ }
6405
+ function initialCycleState(ctx) {
6406
+ return { phase: "pick", ctx, attempt: 0, done: false };
6407
+ }
6408
+ var CYCLE_TIMEOUT_SEC, WATCHDOG_KILL_GRACE_SEC, MAX_AGENT_ATTEMPTS, RETRY_BASE_BACKOFF_SEC, EVENT_VALID_PHASES;
6409
+ var init_orchestrator = __esm({
6410
+ "packages/core/dist/loop/orchestrator.js"() {
6411
+ "use strict";
6412
+ init_pr();
6413
+ CYCLE_TIMEOUT_SEC = 2700;
6414
+ WATCHDOG_KILL_GRACE_SEC = 5;
6415
+ MAX_AGENT_ATTEMPTS = 3;
6416
+ RETRY_BASE_BACKOFF_SEC = 30;
6417
+ EVENT_VALID_PHASES = {
6418
+ preflight_done: ["pick"],
6419
+ worktree_created: ["worktree"],
6420
+ worktree_failed: ["worktree"],
6421
+ story_picked: ["pick"],
6422
+ no_story: ["pick"],
6423
+ route_resolved: ["route"],
6424
+ budget_ok: ["route"],
6425
+ budget_halt: ["route"],
6426
+ agent_exited: ["execute"],
6427
+ facts_captured: ["reconcile"],
6428
+ published: ["publish"],
6429
+ merge_polled: ["merge-wait", "publish"],
6430
+ reconciled: ["reconcile"],
6431
+ cleaned: ["cleanup"]
6432
+ };
6163
6433
  }
6164
- const parts = [
6165
- st.tcrCount > 0 ? `${st.tcrCount} tcr` : "",
6166
- `${durS}s`,
6167
- cost > 0 ? `$${cost.toFixed(2)}` : ""
6168
- ].filter(Boolean);
6169
- return [{ tier: "banner", category: "cycle", label: "cycle done", detail: parts.join(" \xB7 ") }];
6434
+ });
6435
+
6436
+ // packages/core/dist/loop/recovery.js
6437
+ function resolveKeepDays(envDays, yamlDays) {
6438
+ if (envDays !== void 0 && /^\d+$/.test(envDays.trim()))
6439
+ return Number(envDays.trim());
6440
+ if (yamlDays !== void 0 && Number.isFinite(yamlDays) && yamlDays >= 0)
6441
+ return Math.trunc(yamlDays);
6442
+ return GC_DEFAULT_KEEP_DAYS;
6170
6443
  }
6171
- var SUPPRESS_TOOLS, PEER_VERDICTS;
6172
- var init_loop_fmt = __esm({
6173
- "packages/core/dist/loop/loop-fmt.js"() {
6444
+ var GC_DEFAULT_KEEP_DAYS;
6445
+ var init_recovery = __esm({
6446
+ "packages/core/dist/loop/recovery.js"() {
6174
6447
  "use strict";
6175
- init_signals();
6176
- SUPPRESS_TOOLS = /* @__PURE__ */ new Set([
6177
- "Read",
6178
- "Glob",
6179
- "Grep",
6180
- "ReadMcpResourceTool",
6181
- "ListMcpResourcesTool",
6182
- "WebFetch",
6183
- "WebSearch",
6184
- "TaskCreate",
6185
- "TaskGet",
6186
- "TaskList",
6187
- "TaskUpdate",
6188
- "TaskOutput",
6189
- "TaskStop"
6190
- ]);
6191
- PEER_VERDICTS = ["AGREE", "REFINE", "OBJECT", "ESCALATE"];
6448
+ GC_DEFAULT_KEEP_DAYS = 30;
6192
6449
  }
6193
6450
  });
6194
6451
 
@@ -6484,7 +6741,8 @@ function parseLoopSafety(lines2, start) {
6484
6741
  correctionSignalWindowSec: numOr(flat["correction_signal_window_sec"], DEFAULT_CORRECTION_SIGNAL_WINDOW_SEC),
6485
6742
  correctionActuator: flat["correction_actuator"] === "auto" ? "auto" : DEFAULT_CORRECTION_ACTUATOR,
6486
6743
  ...budget ? { budget } : {},
6487
- ...flat["attest_gate"] === "hard" || flat["attest_gate"] === "soft" ? { attestGate: flat["attest_gate"] } : {}
6744
+ ...flat["attest_gate"] === "hard" || flat["attest_gate"] === "soft" ? { attestGate: flat["attest_gate"] } : {},
6745
+ ...flat["peer_gate"] === "hard" || flat["peer_gate"] === "soft" ? { peerGate: flat["peer_gate"] } : {}
6488
6746
  };
6489
6747
  return [i, cfg];
6490
6748
  }
@@ -6731,47 +6989,47 @@ function routeProposals(input, minSamples) {
6731
6989
  }
6732
6990
  function rubricProposals(input, minSamples) {
6733
6991
  const out3 = [];
6734
- for (const signal2 of input.rubricSignals) {
6735
- if (signal2.samples < minSamples)
6992
+ for (const signal of input.rubricSignals) {
6993
+ if (signal.samples < minSamples)
6736
6994
  continue;
6737
- const current = input.current.rubricWeights?.[signal2.dimension] ?? DEFAULT_RUBRIC_WEIGHT;
6738
- if (signal2.reworkCorrelation >= 0.6 && signal2.noise <= 0.4) {
6995
+ const current = input.current.rubricWeights?.[signal.dimension] ?? DEFAULT_RUBRIC_WEIGHT;
6996
+ if (signal.reworkCorrelation >= 0.6 && signal.noise <= 0.4) {
6739
6997
  const next = round1(clamp(current + 0.2, 0.2, 3));
6740
6998
  if (next === current)
6741
6999
  continue;
6742
7000
  out3.push({
6743
7001
  kind: "rubric_weight",
6744
- target: `rubric.${signal2.dimension}.weight`,
7002
+ target: `rubric.${signal.dimension}.weight`,
6745
7003
  action: "raise_weight",
6746
7004
  from: current,
6747
7005
  to: next,
6748
7006
  rationale: "\u8BE5 rubric \u7EF4\u5EA6\u4E0E\u771F\u5B9E\u8FD4\u5DE5\u5F3A\u76F8\u5173\uFF0C\u6743\u91CD\u5C0F\u5E45\u4E0A\u8C03\u3002",
6749
7007
  evidence: [
6750
- `dimension=${signal2.dimension}`,
6751
- `samples=${signal2.samples}`,
6752
- `rework_correlation=${fmtRate(signal2.reworkCorrelation)}`,
6753
- `noise=${fmtRate(signal2.noise)}`
7008
+ `dimension=${signal.dimension}`,
7009
+ `samples=${signal.samples}`,
7010
+ `rework_correlation=${fmtRate(signal.reworkCorrelation)}`,
7011
+ `noise=${fmtRate(signal.noise)}`
6754
7012
  ],
6755
- rollback: reset(`rubric.${signal2.dimension}.weight`, DEFAULT_RUBRIC_WEIGHT)
7013
+ rollback: reset(`rubric.${signal.dimension}.weight`, DEFAULT_RUBRIC_WEIGHT)
6756
7014
  });
6757
- } else if (signal2.noise >= 0.6 && signal2.reworkCorrelation <= 0.3) {
7015
+ } else if (signal.noise >= 0.6 && signal.reworkCorrelation <= 0.3) {
6758
7016
  const next = round1(clamp(current - 0.2, 0.2, 3));
6759
7017
  if (next === current)
6760
7018
  continue;
6761
7019
  out3.push({
6762
7020
  kind: "rubric_weight",
6763
- target: `rubric.${signal2.dimension}.weight`,
7021
+ target: `rubric.${signal.dimension}.weight`,
6764
7022
  action: "lower_weight",
6765
7023
  from: current,
6766
7024
  to: next,
6767
7025
  rationale: "\u8BE5 rubric \u7EF4\u5EA6\u566A\u58F0\u9AD8\u4E14\u4E0E\u771F\u5B9E\u8FD4\u5DE5\u5F31\u76F8\u5173\uFF0C\u6743\u91CD\u5C0F\u5E45\u4E0B\u8C03\u3002",
6768
7026
  evidence: [
6769
- `dimension=${signal2.dimension}`,
6770
- `samples=${signal2.samples}`,
6771
- `rework_correlation=${fmtRate(signal2.reworkCorrelation)}`,
6772
- `noise=${fmtRate(signal2.noise)}`
7027
+ `dimension=${signal.dimension}`,
7028
+ `samples=${signal.samples}`,
7029
+ `rework_correlation=${fmtRate(signal.reworkCorrelation)}`,
7030
+ `noise=${fmtRate(signal.noise)}`
6773
7031
  ],
6774
- rollback: reset(`rubric.${signal2.dimension}.weight`, DEFAULT_RUBRIC_WEIGHT)
7032
+ rollback: reset(`rubric.${signal.dimension}.weight`, DEFAULT_RUBRIC_WEIGHT)
6775
7033
  });
6776
7034
  }
6777
7035
  }
@@ -8086,6 +8344,7 @@ var init_dist2 = __esm({
8086
8344
  init_score();
8087
8345
  init_bus();
8088
8346
  init_infra_default3();
8347
+ init_activity_signal();
8089
8348
  init_alert_loop();
8090
8349
  init_ci_loop();
8091
8350
  init_correction_actuator();
@@ -8095,7 +8354,6 @@ var init_dist2 = __esm({
8095
8354
  init_quality_gate();
8096
8355
  init_orchestrator();
8097
8356
  init_recovery();
8098
- init_loop_fmt();
8099
8357
  init_sentinel();
8100
8358
  init_signals();
8101
8359
  init_transcript();
@@ -8117,7 +8375,7 @@ var init_dist2 = __esm({
8117
8375
  });
8118
8376
 
8119
8377
  // packages/infra/dist/config.js
8120
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
8378
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
8121
8379
  import { homedir as homedir3 } from "node:os";
8122
8380
  import { dirname as dirname7, join as join12 } from "node:path";
8123
8381
  function expandLeadingTilde(val, home = homedir3()) {
@@ -8133,7 +8391,7 @@ function yamlReadNested(file, parent, child) {
8133
8391
  const parentRe = new RegExp(`^${parent}:`);
8134
8392
  const childRe = new RegExp(`^[ \\t]+${child}:`);
8135
8393
  let found = false;
8136
- const text = readFileSync13(file, "utf8");
8394
+ const text = readFileSync14(file, "utf8");
8137
8395
  const lines2 = text.length === 0 ? [] : text.replace(/\n$/, "").split("\n");
8138
8396
  for (const line of lines2) {
8139
8397
  if (!found) {
@@ -8153,7 +8411,7 @@ function yamlReadFlat(file, key) {
8153
8411
  if (!existsSync12(file))
8154
8412
  return "";
8155
8413
  const re = new RegExp(`^${escapeRegExp(key)}:`);
8156
- for (const line of readFileSync13(file, "utf8").split("\n")) {
8414
+ for (const line of readFileSync14(file, "utf8").split("\n")) {
8157
8415
  if (re.test(line)) {
8158
8416
  return line.replace(/^[^:]*:[ \t]*/, "").replace(/[ \t]*#.*$/, "").replace(/[ \t]*$/, "");
8159
8417
  }
@@ -8308,7 +8566,7 @@ function applyConfigSet(text, key, value) {
8308
8566
  }
8309
8567
  function configSet(key, value, file) {
8310
8568
  mkdirSync8(dirname7(file), { recursive: true });
8311
- const text = existsSync12(file) ? readFileSync13(file, "utf8") : "";
8569
+ const text = existsSync12(file) ? readFileSync14(file, "utf8") : "";
8312
8570
  writeFileSync9(file, applyConfigSet(text, key, value));
8313
8571
  }
8314
8572
  var CONFIG_KEYS;
@@ -8825,7 +9083,7 @@ var init_github = __esm({
8825
9083
  });
8826
9084
 
8827
9085
  // packages/infra/dist/process.js
8828
- import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync14, rmSync as rmSync4, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
9086
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync as rmSync4, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
8829
9087
  import { dirname as dirname9 } from "node:path";
8830
9088
  function parseLock(raw) {
8831
9089
  const firstLine = raw.split("\n", 1)[0] ?? "";
@@ -8853,7 +9111,7 @@ function acquireLock(lockPath2, pid = process.pid, opts = {}) {
8853
9111
  const pidAlive = opts.pidAlive ?? systemPidAlive;
8854
9112
  mkdirSync10(dirname9(lockPath2), { recursive: true });
8855
9113
  if (existsSync14(lockPath2)) {
8856
- const contents = parseLock(readFileSync14(lockPath2, "utf8"));
9114
+ const contents = parseLock(readFileSync15(lockPath2, "utf8"));
8857
9115
  if (isLockHeld(contents, now, staleSec, pidAlive)) {
8858
9116
  return { acquired: false, heldByPid: contents.pid };
8859
9117
  }
@@ -8873,7 +9131,7 @@ function writeHeartbeat(heartbeatPath, now = systemClock) {
8873
9131
  function heartbeatAge(heartbeatPath, now = systemClock) {
8874
9132
  let ts = 0;
8875
9133
  if (existsSync14(heartbeatPath)) {
8876
- const raw = readFileSync14(heartbeatPath, "utf8").trim();
9134
+ const raw = readFileSync15(heartbeatPath, "utf8").trim();
8877
9135
  ts = /^\d+$/.test(raw) ? Number(raw) : 0;
8878
9136
  }
8879
9137
  return now() - ts;
@@ -8902,10 +9160,10 @@ function installExitHooks(final, target = process) {
8902
9160
  const onExit = () => {
8903
9161
  runOnce();
8904
9162
  };
8905
- const onSignal = (signal2) => {
9163
+ const onSignal = (signal) => {
8906
9164
  runOnce();
8907
- target.removeListener(signal2, onSignal);
8908
- target.kill(target.pid, signal2);
9165
+ target.removeListener(signal, onSignal);
9166
+ target.kill(target.pid, signal);
8909
9167
  };
8910
9168
  target.on("exit", onExit);
8911
9169
  target.on("SIGTERM", onSignal);
@@ -10048,10 +10306,12 @@ var init_showcase = __esm({
10048
10306
  var showcase_exports = {};
10049
10307
  __export(showcase_exports, {
10050
10308
  SHOWCASE_USAGE: () => SHOWCASE_USAGE,
10309
+ probeMissingAgents: () => probeMissingAgents,
10310
+ resetSandbox: () => resetSandbox,
10051
10311
  showcaseCommand: () => showcaseCommand
10052
10312
  });
10053
10313
  import { spawnSync as spawnSync8 } from "node:child_process";
10054
- import { cpSync, existsSync as existsSync73, mkdirSync as mkdirSync38, mkdtempSync as mkdtempSync5, readdirSync as readdirSync32, readFileSync as readFileSync68, rmSync as rmSync15, statSync as statSync24, writeFileSync as writeFileSync40 } from "node:fs";
10314
+ import { cpSync, existsSync as existsSync73, mkdirSync as mkdirSync38, mkdtempSync as mkdtempSync5, readdirSync as readdirSync32, readFileSync as readFileSync69, rmSync as rmSync15, statSync as statSync24, writeFileSync as writeFileSync40 } from "node:fs";
10055
10315
  import { tmpdir as tmpdir7 } from "node:os";
10056
10316
  import { dirname as dirname31, join as join72 } from "node:path";
10057
10317
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -10106,14 +10366,17 @@ function packageRoot() {
10106
10366
  function rollBin2() {
10107
10367
  return join72(packageRoot(), "packages", "cli", "bin", "roll.js");
10108
10368
  }
10109
- function runRoll(sandbox, rollHome4, args, extraEnv = {}) {
10369
+ function runRoll(sandbox, rollHome4, args, opts = {}) {
10110
10370
  const env = {
10111
10371
  ...process.env,
10112
- ROLL_HOME: rollHome4,
10113
10372
  ROLL_LANG: process.env["ROLL_LANG"] ?? "en",
10114
10373
  GIT_TERMINAL_PROMPT: "0",
10115
- ...extraEnv
10374
+ ...opts.extraEnv
10116
10375
  };
10376
+ if (opts.realHome === true) {
10377
+ } else {
10378
+ env.ROLL_HOME = rollHome4;
10379
+ }
10117
10380
  const r = spawnSync8(process.execPath, [rollBin2(), ...args], {
10118
10381
  cwd: sandbox,
10119
10382
  env,
@@ -10176,9 +10439,13 @@ function findCardSpec(sandbox, card) {
10176
10439
  function resetSandbox(sandbox, card) {
10177
10440
  const notes = [];
10178
10441
  let reset2 = false;
10442
+ let ok8 = false;
10179
10443
  const backlogPath = join72(sandbox, ".roll", "backlog.md");
10180
10444
  if (existsSync73(backlogPath)) {
10181
- const before = readFileSync68(backlogPath, "utf8");
10445
+ const before = readFileSync69(backlogPath, "utf8");
10446
+ const cardRow = before.split("\n").find((line) => line.startsWith("|") && line.includes(card));
10447
+ const hadStatusToken = cardRow !== void 0 && /(✅ *Done|🚧 *WIP|🔄 *In Progress|⏳ *Hold|📋 *Todo|✔️ *Done)/.test(cardRow);
10448
+ const alreadyTodo = cardRow !== void 0 && /📋 *Todo/.test(cardRow);
10182
10449
  const lines2 = before.split("\n").map((line) => {
10183
10450
  if (!line.includes(card) || !line.startsWith("|"))
10184
10451
  return line;
@@ -10188,9 +10455,16 @@ function resetSandbox(sandbox, card) {
10188
10455
  if (after !== before) {
10189
10456
  writeFileSync40(backlogPath, after, "utf8");
10190
10457
  reset2 = true;
10458
+ ok8 = true;
10191
10459
  notes.push(`backlog: ${card} \u2192 Todo`);
10460
+ } else if (alreadyTodo) {
10461
+ ok8 = true;
10462
+ notes.push(`backlog: ${card} already Todo (no-op)`);
10463
+ } else if (!hadStatusToken) {
10464
+ notes.push(`backlog: ${card} has no status token`);
10192
10465
  } else {
10193
- notes.push(`backlog: ${card} already Todo (or no status token)`);
10466
+ ok8 = true;
10467
+ notes.push(`backlog: ${card} already Todo`);
10194
10468
  }
10195
10469
  } else {
10196
10470
  notes.push("backlog: .roll/backlog.md absent");
@@ -10200,7 +10474,7 @@ function resetSandbox(sandbox, card) {
10200
10474
  rmSync15(pulseCmd, { force: true });
10201
10475
  notes.push("removed prior pulse.ts surface");
10202
10476
  }
10203
- return { reset: reset2, notes };
10477
+ return { ok: ok8, reset: reset2, notes };
10204
10478
  }
10205
10479
  function writeCasting(sandbox, casting) {
10206
10480
  const agentsPath2 = join72(sandbox, ".roll", "agents.yaml");
@@ -10243,7 +10517,7 @@ function readBacklogStatus(sandbox, card) {
10243
10517
  const backlogPath = join72(sandbox, ".roll", "backlog.md");
10244
10518
  if (!existsSync73(backlogPath))
10245
10519
  return void 0;
10246
- for (const line of readFileSync68(backlogPath, "utf8").split("\n")) {
10520
+ for (const line of readFileSync69(backlogPath, "utf8").split("\n")) {
10247
10521
  if (line.startsWith("|") && line.includes(card)) {
10248
10522
  const m7 = /(✅ *Done|🚧 *WIP|🔄 *In Progress|⏳ *Hold|📋 *Todo|✔️ *Done)/.exec(line);
10249
10523
  if (m7 !== null && m7[1] !== void 0)
@@ -10257,7 +10531,7 @@ function readTruthLadder(sandbox, card) {
10257
10531
  if (!existsSync73(truthPath))
10258
10532
  return void 0;
10259
10533
  try {
10260
- const snap = JSON.parse(readFileSync68(truthPath, "utf8"));
10534
+ const snap = JSON.parse(readFileSync69(truthPath, "utf8"));
10261
10535
  const row2 = (snap.stories ?? []).find((s) => s.id === card);
10262
10536
  return row2?.ladder;
10263
10537
  } catch {
@@ -10270,7 +10544,7 @@ function readTcrCommits(sandbox) {
10270
10544
  return [];
10271
10545
  const out3 = [];
10272
10546
  try {
10273
- const lines2 = readFileSync68(runsPath3, "utf8").trim().split("\n").filter(Boolean);
10547
+ const lines2 = readFileSync69(runsPath3, "utf8").trim().split("\n").filter(Boolean);
10274
10548
  const last = lines2[lines2.length - 1];
10275
10549
  if (last === void 0)
10276
10550
  return [];
@@ -10315,7 +10589,7 @@ ${msg6}
10315
10589
  const sourceProject = process.cwd();
10316
10590
  const { sandbox, rollHome: rollHome4 } = makeSandbox(sourceProject);
10317
10591
  const steps = [];
10318
- const emit3 = (id, ok8, detail) => {
10592
+ const emit4 = (id, ok8, detail) => {
10319
10593
  steps.push({ id, ok: ok8, detail });
10320
10594
  if (!json)
10321
10595
  process.stdout.write(`${ok8 ? "\u2713" : "\u2717"} ${id.padEnd(18)} ${detail}
@@ -10324,28 +10598,28 @@ ${msg6}
10324
10598
  try {
10325
10599
  const spec = findCardSpec(sandbox, card);
10326
10600
  if (spec === void 0) {
10327
- emit3("sandbox", false, `target card ${card} not found in the sandbox \u2014 nothing to deliver`);
10601
+ emit4("sandbox", false, `target card ${card} not found in the sandbox \u2014 nothing to deliver`);
10328
10602
  throw new ShowcaseAbort(`card ${card} absent`);
10329
10603
  }
10330
10604
  const reset2 = resetSandbox(sandbox, card);
10331
- emit3("reset", reset2.reset, reset2.notes.join("; "));
10605
+ emit4("reset", reset2.ok, reset2.notes.join("; "));
10332
10606
  writeCasting(sandbox, casting);
10333
- emit3("casting", true, `builder=${casting.builder} reviewer=${casting.reviewer} scorer=${casting.scorer} (heterogeneous)`);
10607
+ emit4("casting", true, `builder=${casting.builder} reviewer=${casting.reviewer} scorer=${casting.scorer} (heterogeneous)`);
10334
10608
  const missing = probeMissingAgents(sandbox, rollHome4, casting);
10335
10609
  if (missing.length > 0) {
10336
- emit3("agents", false, `unavailable real agent(s): ${missing.join(", ")} \u2014 cannot run the real loop`);
10610
+ emit4("agents", false, `unavailable real agent(s): ${missing.join(", ")} \u2014 cannot run the real loop`);
10337
10611
  throw new ShowcaseAbort(`agents unavailable: ${missing.join(", ")}`);
10338
10612
  }
10339
- emit3("agents", true, `all cast agents available: ${casting.builder}, ${casting.reviewer}, ${casting.scorer}`);
10613
+ emit4("agents", true, `all cast agents available: ${casting.builder}, ${casting.reviewer}, ${casting.scorer}`);
10340
10614
  const go = runRoll(sandbox, rollHome4, ["loop", "go", "--cards", card, "--no-tmux", "--no-wait"]);
10341
- emit3("loop-go", go.code === 0, go.code === 0 ? "loop cycle completed" : `loop go exited ${go.code}: ${tail2(go.stderr || go.stdout)}`);
10615
+ emit4("loop-go", go.code === 0, go.code === 0 ? "loop cycle completed" : `loop go exited ${go.code}: ${tail2(go.stderr || go.stdout)}`);
10342
10616
  runRoll(sandbox, rollHome4, ["index", "--rebuild"]);
10343
10617
  const attest = runRoll(sandbox, rollHome4, ["attest", card]);
10344
10618
  const screenshots = await captureScreenshots(sandbox, card);
10345
10619
  const cliShot = screenshots.find((s) => s.surface === "cli");
10346
10620
  const webShot = screenshots.find((s) => s.surface === "web");
10347
- emit3("capture-cli", cliShot?.present === true, cliShot?.present === true ? cliShot.path : `skip: ${cliShot?.skipped ?? "none"}`);
10348
- emit3("capture-web", webShot?.present === true, webShot?.present === true ? webShot.path : `skip: ${webShot?.skipped ?? "none"}`);
10621
+ emit4("capture-cli", cliShot?.present === true, cliShot?.present === true ? cliShot.path : `skip: ${cliShot?.skipped ?? "none"}`);
10622
+ emit4("capture-web", webShot?.present === true, webShot?.present === true ? webShot.path : `skip: ${webShot?.skipped ?? "none"}`);
10349
10623
  const backlogStatus = readBacklogStatus(sandbox, card);
10350
10624
  const truthLadder = readTruthLadder(sandbox, card);
10351
10625
  const tcrCommits = readTcrCommits(sandbox);
@@ -10413,9 +10687,9 @@ ROLL_HOME: ${rollHome4}
10413
10687
  }
10414
10688
  }
10415
10689
  }
10416
- function probeMissingAgents(sandbox, rollHome4, casting) {
10690
+ function probeMissingAgents(sandbox, rollHome4, casting, runner = runRoll) {
10417
10691
  const wanted = [.../* @__PURE__ */ new Set([casting.builder, casting.reviewer, casting.scorer])];
10418
- const r = runRoll(sandbox, rollHome4, ["agent", "list"]);
10692
+ const r = runner(sandbox, rollHome4, ["agent", "list"], { realHome: true });
10419
10693
  if (r.code !== 0 && r.stdout.trim() === "")
10420
10694
  return wanted;
10421
10695
  const listed = r.stdout.toLowerCase();
@@ -11437,12 +11711,12 @@ function agentCommand(args, deps = {}) {
11437
11711
  init_dist2();
11438
11712
  init_dist();
11439
11713
  init_dist2();
11440
- import { appendFileSync as appendFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync13, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "node:fs";
11714
+ import { appendFileSync as appendFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync13, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "node:fs";
11441
11715
  import { dirname as dirname10, join as join16, relative as relative2 } from "node:path";
11442
11716
 
11443
11717
  // packages/cli/dist/runner/pairing-gate.js
11444
11718
  init_dist2();
11445
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
11719
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
11446
11720
  import { join as join9 } from "node:path";
11447
11721
 
11448
11722
  // packages/cli/dist/lib/self-score.js
@@ -12195,8 +12469,9 @@ function evaluateSelfScoreGate(projectPath3, storyId) {
12195
12469
  }
12196
12470
 
12197
12471
  // packages/cli/dist/runner/peer-gate.js
12472
+ init_dist2();
12198
12473
  import { execFile } from "node:child_process";
12199
- import { existsSync as existsSync9, readdirSync as readdirSync4 } from "node:fs";
12474
+ import { existsSync as existsSync9, readFileSync as readFileSync10, readdirSync as readdirSync4 } from "node:fs";
12200
12475
  import { join as join8 } from "node:path";
12201
12476
  import { promisify } from "node:util";
12202
12477
  var execFileAsync = promisify(execFile);
@@ -12236,21 +12511,32 @@ function peerEvidencePresent(runtimeDir6, cycleId) {
12236
12511
  return false;
12237
12512
  }
12238
12513
  }
12239
- async function runPeerGate(worktreeCwd, runtimeDir6, cycleId, sinks) {
12514
+ function readPeerGateMode(repoCwd) {
12515
+ try {
12516
+ const p = join8(repoCwd, ".roll", "policy.yaml");
12517
+ if (!existsSync9(p))
12518
+ return "hard";
12519
+ return parsePolicy(readFileSync10(p, "utf8")).loopSafety.peerGate === "soft" ? "soft" : "hard";
12520
+ } catch {
12521
+ return "hard";
12522
+ }
12523
+ }
12524
+ async function runPeerGate(worktreeCwd, runtimeDir6, cycleId, mode, sinks) {
12240
12525
  try {
12241
12526
  const files = await cycleChangedFiles(worktreeCwd);
12242
12527
  const cx = assessComplexity(files);
12243
12528
  if (!cx.high)
12244
- return { verdict: "not-required", reasons: [] };
12529
+ return { verdict: "not-required", mode, reasons: [], blocked: false };
12245
12530
  if (peerEvidencePresent(runtimeDir6, cycleId)) {
12246
12531
  sinks.event({ cycleId, verdict: "consulted", reasons: cx.reasons });
12247
- return { verdict: "consulted", reasons: cx.reasons };
12532
+ return { verdict: "consulted", mode, reasons: cx.reasons, blocked: false };
12248
12533
  }
12249
- sinks.alert(`peer gate: high-complexity delivery without peer evidence (${cx.reasons.join("; ")}) \u2014 cycle ${cycleId}`);
12534
+ const blocked = mode === "hard";
12535
+ sinks.alert(`peer gate (${mode}): high-complexity work without peer review (${cx.reasons.join("; ")}) \u2014 cycle ${cycleId}` + (blocked ? " \u2014 retrying the consult; story not marked Done unless peer evidence is produced" : ""));
12250
12536
  sinks.event({ cycleId, verdict: "skipped", reasons: cx.reasons });
12251
- return { verdict: "skipped", reasons: cx.reasons };
12537
+ return { verdict: "skipped", mode, reasons: cx.reasons, blocked };
12252
12538
  } catch {
12253
- return { verdict: "not-required", reasons: [] };
12539
+ return { verdict: "not-required", mode, reasons: [], blocked: false };
12254
12540
  }
12255
12541
  }
12256
12542
 
@@ -12260,7 +12546,7 @@ function enabledPairingStages(projectDir) {
12260
12546
  const cfgPath = join9(projectDir, ".roll", "pairing.yaml");
12261
12547
  if (!existsSync10(cfgPath))
12262
12548
  return [];
12263
- const cfg = parsePairingConfig(readFileSync10(cfgPath, "utf8"));
12549
+ const cfg = parsePairingConfig(readFileSync11(cfgPath, "utf8"));
12264
12550
  if (!cfg.enabled)
12265
12551
  return [];
12266
12552
  return cfg.stages.filter((s, i, arr2) => arr2.indexOf(s) === i && s !== "score");
@@ -12281,7 +12567,7 @@ async function runPairing(projectDir, worktreeCwd, runtimeDir6, cycleId, working
12281
12567
  const cfgPath = join9(projectDir, ".roll", "pairing.yaml");
12282
12568
  if (!existsSync10(cfgPath))
12283
12569
  return { status: "off" };
12284
- const cfg = parsePairingConfig(readFileSync10(cfgPath, "utf8"));
12570
+ const cfg = parsePairingConfig(readFileSync11(cfgPath, "utf8"));
12285
12571
  if (!cfg.enabled || !cfg.stages.includes(stage))
12286
12572
  return { status: "off" };
12287
12573
  const files = await deps.changedFiles(worktreeCwd);
@@ -12324,7 +12610,7 @@ async function runScorePairing(projectDir, runtimeDir6, cycleId, workingAgent, s
12324
12610
  const cfgPath = join9(projectDir, ".roll", "pairing.yaml");
12325
12611
  if (!existsSync10(cfgPath))
12326
12612
  return { status: "off" };
12327
- const cfg = parsePairingConfig(readFileSync10(cfgPath, "utf8"));
12613
+ const cfg = parsePairingConfig(readFileSync11(cfgPath, "utf8"));
12328
12614
  if (!cfg.enabled || !cfg.stages.includes("score"))
12329
12615
  return { status: "off" };
12330
12616
  const candidates = selectPairingCandidates({
@@ -12378,6 +12664,33 @@ function parsePairScoreOutput(stdout) {
12378
12664
  return null;
12379
12665
  return { score, verdict: vm[1].toLowerCase(), rationale: rm[1].trim() };
12380
12666
  }
12667
+ async function retryPeerConsult(worktreeCwd, runtimeDir6, cycleId, deps) {
12668
+ try {
12669
+ const working = canonicalAgentName(deps.workingAgent);
12670
+ const distinct = deps.installed.map(canonicalAgentName).filter((a, i, arr2) => arr2.indexOf(a) === i);
12671
+ const heterogeneous = distinct.filter((a) => a !== working && isHeterogeneous(a, working))[0];
12672
+ const peer = heterogeneous ?? working;
12673
+ const sameTypeFallback = heterogeneous === void 0;
12674
+ if (peer === "") {
12675
+ deps.event({ type: "pair:none-available", cycleId, stage: "code", reason: "peer-gate retry: no peer could be consulted (no agent to spawn a separate-session review)", ts: deps.now() });
12676
+ return { status: "none-available" };
12677
+ }
12678
+ const diff = await deps.diff(worktreeCwd);
12679
+ if (diff.trim() === "")
12680
+ return { status: "empty" };
12681
+ deps.event({ type: "pair:selected", cycleId, workingAgent: working, peer, stage: "code", ts: deps.now() });
12682
+ const review = await deps.reviewPeer(peer, diff, deps.timeoutMs ?? DEFAULT_TIMEOUT_MS);
12683
+ if (review === null)
12684
+ return { status: "timeout", peer, sameTypeFallback };
12685
+ const path = evidencePath(runtimeDir6, cycleId, "code");
12686
+ mkdirSync5(join9(runtimeDir6, "peer"), { recursive: true });
12687
+ writeFileSync6(path, JSON.stringify({ cycleId, workingAgent: working, peer, stage: "code", sameTypeFallback, ...review }, null, 2), "utf8");
12688
+ deps.event({ type: "pair:verdict", cycleId, peer, verdict: review.verdict, findings: review.findings.length, cost: review.cost, stage: "code", ts: deps.now() });
12689
+ return { status: "reviewed", peer, sameTypeFallback };
12690
+ } catch {
12691
+ return { status: "error" };
12692
+ }
12693
+ }
12381
12694
  function buildPairScorePrompt(summary) {
12382
12695
  return `You are a heterogeneous PAIRING scorer. A different agent delivered the cycle below; grade the delivery quality honestly (root-cause depth, test coverage, scope discipline, evidence). Reply with exactly one "SCORE: <integer 1..10>" line, one "VERDICT: good|ok|regression" line, and one "RATIONALE: <one sentence>" line.
12383
12696
 
@@ -12388,11 +12701,11 @@ DELIVERY:
12388
12701
  // packages/cli/dist/commands/peer.js
12389
12702
  init_dist2();
12390
12703
  import { spawn } from "node:child_process";
12391
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
12704
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
12392
12705
  import { dirname as dirname6, join as join11 } from "node:path";
12393
12706
 
12394
12707
  // packages/cli/dist/commands/setup-shared.js
12395
- import { copyFileSync, existsSync as existsSync11, lstatSync, mkdirSync as mkdirSync6, readFileSync as readFileSync11, readdirSync as readdirSync5, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync4, symlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
12708
+ import { copyFileSync, existsSync as existsSync11, lstatSync, mkdirSync as mkdirSync6, readFileSync as readFileSync12, readdirSync as readdirSync5, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync4, symlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
12396
12709
  import { homedir as homedir2 } from "node:os";
12397
12710
  import { basename as basename5, dirname as dirname5, join as join10 } from "node:path";
12398
12711
  function rollHome() {
@@ -12421,7 +12734,7 @@ function getAiTools() {
12421
12734
  if (!existsSync11(cfg))
12422
12735
  return [];
12423
12736
  const out3 = [];
12424
- for (const line of readFileSync11(cfg, "utf8").split("\n")) {
12737
+ for (const line of readFileSync12(cfg, "utf8").split("\n")) {
12425
12738
  if (/^ai_[a-z]+:/.test(line)) {
12426
12739
  let entry = line.replace(/^[^:]*:[ \t]*/, "");
12427
12740
  entry = entry.replace(/^~/, homedir2());
@@ -12554,7 +12867,7 @@ function safeCopy(src, dst, force) {
12554
12867
  }
12555
12868
  function sameFile(a, b) {
12556
12869
  try {
12557
- return readFileSync11(a).equals(readFileSync11(b));
12870
+ return readFileSync12(a).equals(readFileSync12(b));
12558
12871
  } catch {
12559
12872
  return false;
12560
12873
  }
@@ -12688,7 +13001,7 @@ function ensureConfigEntries() {
12688
13001
  const cfg = rollConfig();
12689
13002
  if (!existsSync11(cfg))
12690
13003
  return;
12691
- const original = readFileSync11(cfg, "utf8");
13004
+ const original = readFileSync12(cfg, "utf8");
12692
13005
  let lines2 = original.split("\n");
12693
13006
  let added = 0;
12694
13007
  for (const [key, val] of DEFAULT_AI_KEYS) {
@@ -12764,7 +13077,7 @@ function replacePrimaryAgent(newAgent) {
12764
13077
  const cfg = rollConfig();
12765
13078
  if (!existsSync11(cfg) || newAgent === "")
12766
13079
  return;
12767
- const lines2 = readFileSync11(cfg, "utf8").split("\n");
13080
+ const lines2 = readFileSync12(cfg, "utf8").split("\n");
12768
13081
  const out3 = lines2.map((l) => /^primary_agent:/.test(l) ? `primary_agent: ${newAgent}` : l);
12769
13082
  writeFileSync7(cfg, out3.join("\n"));
12770
13083
  }
@@ -12774,7 +13087,7 @@ function installLocal(force) {
12774
13087
  pullConventions(force);
12775
13088
  pullSkills();
12776
13089
  const cfg = rollConfig();
12777
- if (existsSync11(cfg) && !/^ai_[a-z]+:/m.test(readFileSync11(cfg, "utf8"))) {
13090
+ if (existsSync11(cfg) && !/^ai_[a-z]+:/m.test(readFileSync12(cfg, "utf8"))) {
12778
13091
  copyFileSync(cfg, `${cfg}.bak`);
12779
13092
  rmSync2(cfg, { force: true });
12780
13093
  }
@@ -12800,8 +13113,8 @@ function syncConventionForTool(src, mainDst, force) {
12800
13113
  copyFileSync(src, wkFile);
12801
13114
  if (!existsSync11(mainDst)) {
12802
13115
  writeFileSync7(mainDst, "@roll.md\n");
12803
- } else if (!readFileSync11(mainDst, "utf8").includes("@roll.md")) {
12804
- writeFileSync7(mainDst, readFileSync11(mainDst, "utf8") + "\n@roll.md\n");
13116
+ } else if (!readFileSync12(mainDst, "utf8").includes("@roll.md")) {
13117
+ writeFileSync7(mainDst, readFileSync12(mainDst, "utf8") + "\n@roll.md\n");
12805
13118
  }
12806
13119
  }
12807
13120
  function syncConventions(force) {
@@ -12940,7 +13253,7 @@ function textAgentArgv(agent, prompt) {
12940
13253
  case "gemini":
12941
13254
  case "agy":
12942
13255
  case "antigravity":
12943
- return { bin: "agy", args: ["-p", "--dangerously-skip-permissions", prompt] };
13256
+ return { bin: "agy", args: ["--dangerously-skip-permissions", "-p", prompt] };
12944
13257
  default:
12945
13258
  return null;
12946
13259
  }
@@ -12991,15 +13304,15 @@ function boundedAppend(current, chunk) {
12991
13304
  const next = current + chunk.toString("utf8");
12992
13305
  return next.length > 1e5 ? next.slice(-1e5) : next;
12993
13306
  }
12994
- function killChild(child, signal2) {
13307
+ function killChild(child, signal) {
12995
13308
  if (child.pid !== void 0) {
12996
13309
  try {
12997
- process.kill(-child.pid, signal2);
13310
+ process.kill(-child.pid, signal);
12998
13311
  return true;
12999
13312
  } catch {
13000
13313
  }
13001
13314
  }
13002
- return child.kill(signal2);
13315
+ return child.kill(signal);
13003
13316
  }
13004
13317
  function releaseChild(child) {
13005
13318
  child.stdout?.destroy();
@@ -13044,14 +13357,14 @@ function spawnPeerReviewAgent(input) {
13044
13357
  stderr = boundedAppend(stderr, chunk);
13045
13358
  });
13046
13359
  child.on("error", (error) => finish({ status: "error", reason: error.message, stdout }));
13047
- child.on("exit", (code, signal2) => {
13360
+ child.on("exit", (code, signal) => {
13048
13361
  setImmediate(() => {
13049
13362
  if (timedOut)
13050
13363
  finish({ status: "timeout", stdout });
13051
13364
  else if (code === 0)
13052
13365
  finish({ status: "ok", stdout });
13053
13366
  else
13054
- finish({ status: "error", reason: `exit_${code ?? signal2 ?? "signal"}:${stderr.trim().slice(0, 200)}`, stdout });
13367
+ finish({ status: "error", reason: `exit_${code ?? signal ?? "signal"}:${stderr.trim().slice(0, 200)}`, stdout });
13055
13368
  });
13056
13369
  });
13057
13370
  child.on("close", (code) => {
@@ -13234,7 +13547,7 @@ function readPrompt(opts) {
13234
13547
  if (opts.prompt !== void 0)
13235
13548
  return opts.prompt;
13236
13549
  if (opts.file !== void 0)
13237
- return readFileSync12(opts.file, "utf8");
13550
+ return readFileSync13(opts.file, "utf8");
13238
13551
  throw new Error("roll peer: --prompt or --file is required");
13239
13552
  }
13240
13553
  async function peerCommand(args, deps = realDeps()) {
@@ -13284,7 +13597,7 @@ duration: ${facts.durationMs}ms
13284
13597
  // packages/cli/dist/commands/dashboard.js
13285
13598
  import { execFileSync as execFileSync2 } from "node:child_process";
13286
13599
  import { createHash as createHash3 } from "node:crypto";
13287
- import { existsSync as existsSync17, readFileSync as readFileSync15, readdirSync as readdirSync7, realpathSync as realpathSync2, statSync as statSync8 } from "node:fs";
13600
+ import { existsSync as existsSync17, readFileSync as readFileSync16, readdirSync as readdirSync7, realpathSync as realpathSync2, statSync as statSync8 } from "node:fs";
13288
13601
  import { homedir as homedir4, platform } from "node:os";
13289
13602
  import { basename as basename6, join as join15 } from "node:path";
13290
13603
  init_dist3();
@@ -13396,7 +13709,7 @@ function resolveProjectPath(slug) {
13396
13709
  const plist = join15(homedir4(), "Library", "LaunchAgents", `com.roll.loop.${slug}.plist`);
13397
13710
  if (existsSync17(plist)) {
13398
13711
  try {
13399
- const text = readFileSync15(plist, "utf8");
13712
+ const text = readFileSync16(plist, "utf8");
13400
13713
  const m7 = /<key>WorkingDirectory<\/key>\s*<string>([^<]+)<\/string>/.exec(text);
13401
13714
  if (m7 && m7[1] !== void 0 && isDir(m7[1]))
13402
13715
  return m7[1];
@@ -13421,7 +13734,7 @@ function resolveProjectPath(slug) {
13421
13734
  const innerScript = join15(sharedRoot(), "loop", `run-${slug}-inner.sh`);
13422
13735
  if (existsSync17(innerScript)) {
13423
13736
  try {
13424
- const text = readFileSync15(innerScript, "utf8");
13737
+ const text = readFileSync16(innerScript, "utf8");
13425
13738
  const m7 = /export ROLL_MAIN_PROJECT="([^"]+)"/.exec(text);
13426
13739
  if (m7 && m7[1] !== void 0 && isDir(m7[1]))
13427
13740
  return m7[1];
@@ -13505,7 +13818,7 @@ function loadEvents(slug, days) {
13505
13818
  for (const p of existing) {
13506
13819
  let content;
13507
13820
  try {
13508
- content = readFileSync15(p, "utf8");
13821
+ content = readFileSync16(p, "utf8");
13509
13822
  } catch {
13510
13823
  continue;
13511
13824
  }
@@ -13543,7 +13856,7 @@ function loadCronLog(slug) {
13543
13856
  const out3 = [];
13544
13857
  let content;
13545
13858
  try {
13546
- content = readFileSync15(path, "utf8");
13859
+ content = readFileSync16(path, "utf8");
13547
13860
  } catch {
13548
13861
  return [];
13549
13862
  }
@@ -13573,7 +13886,7 @@ function loadState(slug) {
13573
13886
  const out3 = {};
13574
13887
  let content;
13575
13888
  try {
13576
- content = readFileSync15(path, "utf8");
13889
+ content = readFileSync16(path, "utf8");
13577
13890
  } catch {
13578
13891
  return {};
13579
13892
  }
@@ -13595,7 +13908,7 @@ function loadBacklog(projectRoot2) {
13595
13908
  const pat = /^\|\s*(?:\[)?([A-Z]+-\d+)(?:\]\([^)]+\))?\s*\|\s*([^|]+?)\s*\|/;
13596
13909
  let content;
13597
13910
  try {
13598
- content = readFileSync15(path, "utf8");
13911
+ content = readFileSync16(path, "utf8");
13599
13912
  } catch {
13600
13913
  return {};
13601
13914
  }
@@ -13746,7 +14059,7 @@ function detectLiveCycle(rtDir, cycles, now, pidAlive = systemPidAlive) {
13746
14059
  const nowSec2 = Math.floor(now.getTime() / 1e3);
13747
14060
  let lockRaw;
13748
14061
  try {
13749
- lockRaw = readFileSync15(lockPath2, "utf8");
14062
+ lockRaw = readFileSync16(lockPath2, "utf8");
13750
14063
  } catch {
13751
14064
  return dead;
13752
14065
  }
@@ -13779,7 +14092,7 @@ function loadRuns(slug) {
13779
14092
  const out3 = {};
13780
14093
  let content;
13781
14094
  try {
13782
- content = readFileSync15(path, "utf8");
14095
+ content = readFileSync16(path, "utf8");
13783
14096
  } catch {
13784
14097
  return {};
13785
14098
  }
@@ -13997,7 +14310,7 @@ function loadClaudeSessionUsage(label4, slug) {
13997
14310
  let durationMs = null;
13998
14311
  let content;
13999
14312
  try {
14000
- content = readFileSync15(path, "utf8");
14313
+ content = readFileSync16(path, "utf8");
14001
14314
  } catch {
14002
14315
  return null;
14003
14316
  }
@@ -14173,7 +14486,7 @@ function selfScoreSummaryLine(notesDir = ".roll/notes", windowN = 14, featuresDi
14173
14486
  let verdict = null;
14174
14487
  let content;
14175
14488
  try {
14176
- content = readFileSync15(f.path, "utf8");
14489
+ content = readFileSync16(f.path, "utf8");
14177
14490
  } catch {
14178
14491
  content = "";
14179
14492
  }
@@ -14403,7 +14716,7 @@ function readDailyPlistSchedule(svc) {
14403
14716
  return null;
14404
14717
  let text;
14405
14718
  try {
14406
- text = readFileSync15(plist, "utf8");
14719
+ text = readFileSync16(plist, "utf8");
14407
14720
  } catch {
14408
14721
  return null;
14409
14722
  }
@@ -14456,7 +14769,7 @@ function tickAgeLine(loopType, now) {
14456
14769
  return null;
14457
14770
  let lastLine;
14458
14771
  try {
14459
- const lines2 = readFileSync15(tickFile, "utf8").trim().split("\n");
14772
+ const lines2 = readFileSync16(tickFile, "utf8").trim().split("\n");
14460
14773
  lastLine = lines2[lines2.length - 1] ?? "";
14461
14774
  if (lastLine === "")
14462
14775
  return null;
@@ -14488,7 +14801,7 @@ function readLoopPlistSchedule() {
14488
14801
  return null;
14489
14802
  let text;
14490
14803
  try {
14491
- text = readFileSync15(plist, "utf8");
14804
+ text = readFileSync16(plist, "utf8");
14492
14805
  } catch {
14493
14806
  return null;
14494
14807
  }
@@ -14511,7 +14824,7 @@ function lastLoopFireEpochSec() {
14511
14824
  if (!existsSync17(cronLog))
14512
14825
  return null;
14513
14826
  try {
14514
- const lines2 = readFileSync15(cronLog, "utf8").trim().split("\n").reverse();
14827
+ const lines2 = readFileSync16(cronLog, "utf8").trim().split("\n").reverse();
14515
14828
  for (const line of lines2) {
14516
14829
  if (!line.includes("cycle start"))
14517
14830
  continue;
@@ -15317,7 +15630,7 @@ function pairStatus(rest) {
15317
15630
  const NC = noColor2 ? "" : "\x1B[0m";
15318
15631
  let view;
15319
15632
  try {
15320
- view = pairingPoolView(agentsInstalled(realAgentEnv()), parsePairingConfig(readFileSync16(path, "utf8")));
15633
+ view = pairingPoolView(agentsInstalled(realAgentEnv()), parsePairingConfig(readFileSync17(path, "utf8")));
15321
15634
  } catch (e) {
15322
15635
  process.stderr.write(`[roll] pairing.yaml invalid: ${e.message}
15323
15636
  `);
@@ -15359,7 +15672,7 @@ function pairingActivitySummary() {
15359
15672
  for (const p of pairingEventFiles()) {
15360
15673
  let content;
15361
15674
  try {
15362
- content = readFileSync16(p, "utf8");
15675
+ content = readFileSync17(p, "utf8");
15363
15676
  } catch {
15364
15677
  continue;
15365
15678
  }
@@ -15413,13 +15726,13 @@ function resolveSummary(storyId, summaryFlag, fileFlag) {
15413
15726
  return summaryFlag.trim();
15414
15727
  if (fileFlag !== void 0) {
15415
15728
  try {
15416
- return readFileSync16(fileFlag, "utf8").trim() || null;
15729
+ return readFileSync17(fileFlag, "utf8").trim() || null;
15417
15730
  } catch {
15418
15731
  return null;
15419
15732
  }
15420
15733
  }
15421
15734
  try {
15422
- const backlog = readFileSync16(join16(process.cwd(), ".roll", "backlog.md"), "utf8");
15735
+ const backlog = readFileSync17(join16(process.cwd(), ".roll", "backlog.md"), "utf8");
15423
15736
  const idRe = new RegExp(`${storyId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9])`);
15424
15737
  const row2 = backlog.split("\n").find((l) => idRe.test(l));
15425
15738
  return row2 !== void 0 ? `Story ${storyId} \u2014 backlog row:
@@ -15510,7 +15823,7 @@ ${HELP}`);
15510
15823
  // packages/cli/dist/commands/alert.js
15511
15824
  init_dist2();
15512
15825
  init_dist();
15513
- import { appendFileSync as appendFileSync4, existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync17, rmSync as rmSync6, statSync as statSync9 } from "node:fs";
15826
+ import { appendFileSync as appendFileSync4, existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync18, rmSync as rmSync6, statSync as statSync9 } from "node:fs";
15514
15827
  import { homedir as homedir5 } from "node:os";
15515
15828
  import { join as join17 } from "node:path";
15516
15829
  function palette() {
@@ -15573,7 +15886,7 @@ function renderLog(n, p, lang9) {
15573
15886
  let body = "";
15574
15887
  try {
15575
15888
  if (existsSync19(file) && statSync9(file).size > 0)
15576
- body = readFileSync17(file, "utf8");
15889
+ body = readFileSync18(file, "utf8");
15577
15890
  } catch {
15578
15891
  body = "";
15579
15892
  }
@@ -15604,7 +15917,7 @@ function alertCommand(args) {
15604
15917
  const rest = args.slice(1);
15605
15918
  const file = loopAlertPath();
15606
15919
  const fileExists = existsSync19(file);
15607
- const contents = fileExists ? readFileSync17(file, "utf8") : "";
15920
+ const contents = fileExists ? readFileSync18(file, "utf8") : "";
15608
15921
  const ts = nowStamp();
15609
15922
  const action = alertConsumeAction(subcmd, fileExists, contents, ts, rest[0]);
15610
15923
  switch (action.kind) {
@@ -15665,7 +15978,7 @@ function ackMessage(lang9, ts) {
15665
15978
  init_dist2();
15666
15979
  init_dist();
15667
15980
  init_dist3();
15668
- import { existsSync as existsSync36, mkdirSync as mkdirSync17, readFileSync as readFileSync33, readdirSync as readdirSync16, realpathSync as realpathSync5, rmSync as rmSync8, statSync as statSync14, symlinkSync as symlinkSync2, writeFileSync as writeFileSync16 } from "node:fs";
15981
+ import { existsSync as existsSync36, mkdirSync as mkdirSync17, readFileSync as readFileSync34, readdirSync as readdirSync16, realpathSync as realpathSync5, rmSync as rmSync8, statSync as statSync14, symlinkSync as symlinkSync2, writeFileSync as writeFileSync16 } from "node:fs";
15669
15982
  import { execFile as execFile8, execFileSync as execFileSync5 } from "node:child_process";
15670
15983
  import { basename as basename8, join as join33, relative as relative4 } from "node:path";
15671
15984
  import { promisify as promisify8 } from "node:util";
@@ -15675,7 +15988,7 @@ init_dist2();
15675
15988
  init_dist();
15676
15989
  init_dist2();
15677
15990
  init_dist();
15678
- import { existsSync as existsSync35, readFileSync as readFileSync32, readdirSync as readdirSync15, writeFileSync as writeFileSync15 } from "node:fs";
15991
+ import { existsSync as existsSync35, readFileSync as readFileSync33, readdirSync as readdirSync15, writeFileSync as writeFileSync15 } from "node:fs";
15679
15992
  import { join as join32 } from "node:path";
15680
15993
 
15681
15994
  // packages/cli/dist/lib/dossier-index.js
@@ -16159,7 +16472,7 @@ function copyChip(cmd) {
16159
16472
  }
16160
16473
  function cycleRow2(cy) {
16161
16474
  const color = VERDICT_COLORS[cy.verdict] ?? C.slate;
16162
- const n = cy.cycleId.slice(-6);
16475
+ const n = cycleHandle(cy.cycleId);
16163
16476
  return `<details class="cy-row" data-ts="${cy.tsSec}" data-verdict="${cy.verdict}" data-open-key="cy:${esc2(cy.cycleId)}" style="border-top:1px solid ${C.hair};"><summary style="display:grid;grid-template-columns:14px 70px 1fr auto;align-items:center;gap:14px;padding:12px 18px;cursor:pointer;list-style:none;"><span title="${cy.verdict}" style="width:10px;height:10px;border-radius:50%;background:${color};flex:none;"></span><span style="${MONO}font-size:13px;font-weight:600;color:${C.ink};">${esc2(n)}</span><div style="min-width:0;display:flex;align-items:center;gap:10px;"><span style="${MONO}font-size:10px;letter-spacing:.05em;text-transform:uppercase;font-weight:600;padding:2px 8px;border-radius:999px;border:1px solid ${color}44;color:${color};flex:none;">${bi(cy.verdict, VERDICT_ZH[cy.verdict] ?? cy.verdict)}</span><span style="${MONO}font-size:12px;color:${C.blue};font-weight:600;flex:none;">${esc2(cy.storyId || "\u2014")}</span></div><div style="display:flex;align-items:center;gap:14px;${MONO}font-size:11.5px;color:${C.dim};flex:none;"><span style="color:#5b6478;">${esc2(cy.model)}</span><span title="tokens in/out">${esc2(cy.tokens)}</span><span style="color:#5b6478;">${esc2(cy.cost)}</span><span>${esc2(cy.duration)}</span><span class="bl-caret" style="color:${C.faint};transition:transform .18s;font-size:10px;">\u25B6</span></div></summary><div style="padding:6px 18px 18px 60px;background:#fbfcfe;border-top:1px solid #f1f4f8;"><div style="display:flex;flex-wrap:nowrap;overflow-x:auto;gap:0;margin:12px 0 4px;padding-bottom:4px;">` + cy.tape.map((s, i) => tapeSegment(s, i === cy.tape.length - 1)).join("") + `</div><div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:14px;">` + cy.evidence.map((e) => `<a href="${esc2(e.href)}" style="${MONO}font-size:11px;padding:4px 10px;border-radius:6px;border:1px solid ${C.line};color:${C.blue};text-decoration:none;background:${C.card};">${esc2(e.label)}</a>`).join("") + // US-DOSSIER-018: the web teaches the CLI — this command really exists (US-CLI-013).
16164
16477
  copyChip(`roll cycle ${cycleHandle(cy.cycleId)}`) + `</div></div></details>`;
16165
16478
  }
@@ -16195,6 +16508,7 @@ function hooksPanel(input) {
16195
16508
  }
16196
16509
  function loopTab(input) {
16197
16510
  const ranges = [
16511
+ ["recent", "Recent", "\u8FD1\u671F"],
16198
16512
  ["1", "Today", "\u4ECA\u5929"],
16199
16513
  ["3", "3 days", "\u4E09\u5929"],
16200
16514
  ["7", "7 days", "\u4E03\u5929"],
@@ -16204,7 +16518,7 @@ function loopTab(input) {
16204
16518
  // project-scoped commit-hooks panel + the Cycle ledger. The inline agents
16205
16519
  // panel (now the machine Agents page) and the inline casting ladder (now its
16206
16520
  // own Casting tab) are NOT rendered here.
16207
- hooksPanel(input) + `<div style="display:flex;align-items:center;gap:12px;margin:24px 0 12px;flex-wrap:wrap;"><span style="${MONO}font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:${C.sub};font-weight:600;">${bi("Cycle ledger", "\u5468\u671F\u8D26\u672C")}</span><span style="${MONO}font-size:11.5px;color:${C.faint};">${bi("what it actually did while you were away", "\u4F60\u4E0D\u5728\u7684\u65F6\u5019\u5B83\u5230\u5E95\u5E72\u4E86\u4EC0\u4E48")}</span><span style="flex:1;height:1px;background:#dfe4ec;min-width:16px;"></span><div style="display:flex;border:1px solid #dfe4ec;border-radius:999px;overflow:hidden;background:${C.card};">` + ranges.map(([key, en, zh]) => `<button type="button" class="cy-range${key === "3" ? " on" : ""}" data-range="${key}" style="appearance:none;border:0;background:transparent;${MONO}font-size:11px;padding:6px 13px;cursor:pointer;color:${C.sub};">${bi(en, zh)}</button>`).join("") + `</div><span style="${MONO}font-size:11.5px;color:${C.dim};white-space:nowrap;"><span id="cy-count">\u2014</span> ${bi("cycles", "\u5468\u671F")} <span style="color:${C.faint};">\xB7</span> <b id="cy-failed" style="color:#d23b3b;font-weight:600;">\u2014</b> ${bi("failed", "\u5931\u8D25")}</span></div><section id="cy-ledger" style="border:1px solid ${C.line};border-radius:14px;background:${C.card};overflow:hidden;box-shadow:0 1px 2px rgba(17,26,69,.05);">` + (input.cycles.length > 0 ? input.cycles.map(cycleRow2).join("") : `<div style="padding:16px 18px;font-size:12.5px;color:${C.faint};font-style:italic;">${bi("no cycles recorded yet", "\u5C1A\u65E0\u5468\u671F\u8BB0\u5F55")}</div>`) + `</section>`;
16521
+ hooksPanel(input) + `<div style="display:flex;align-items:center;gap:12px;margin:24px 0 12px;flex-wrap:wrap;"><span style="${MONO}font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:${C.sub};font-weight:600;">${bi("Cycle ledger", "\u5468\u671F\u8D26\u672C")}</span><span style="${MONO}font-size:11.5px;color:${C.faint};">${bi("what it actually did while you were away", "\u4F60\u4E0D\u5728\u7684\u65F6\u5019\u5B83\u5230\u5E95\u5E72\u4E86\u4EC0\u4E48")}</span><span style="flex:1;height:1px;background:#dfe4ec;min-width:16px;"></span><div style="display:flex;border:1px solid #dfe4ec;border-radius:999px;overflow:hidden;background:${C.card};">` + ranges.map(([key, en, zh]) => `<button type="button" class="cy-range${key === "recent" ? " on" : ""}" data-range="${key}" style="appearance:none;border:0;background:transparent;${MONO}font-size:11px;padding:6px 13px;cursor:pointer;color:${C.sub};">${bi(en, zh)}</button>`).join("") + `</div><span style="${MONO}font-size:11.5px;color:${C.dim};white-space:nowrap;"><span id="cy-count">\u2014</span> ${bi("shown", "\u663E\u793A")} <span style="color:${C.faint};">\xB7</span> <b id="cy-failed" style="color:#d23b3b;font-weight:600;">\u2014</b> ${bi("failed (all)", "\u5931\u8D25\uFF08\u5168\u90E8\uFF09")}</span></div><section id="cy-ledger" style="border:1px solid ${C.line};border-radius:14px;background:${C.card};overflow:hidden;box-shadow:0 1px 2px rgba(17,26,69,.05);">` + (input.cycles.length > 0 ? input.cycles.map(cycleRow2).join("") : `<div style="padding:16px 18px;font-size:12.5px;color:${C.faint};font-style:italic;">${bi("no cycles recorded yet", "\u5C1A\u65E0\u5468\u671F\u8BB0\u5F55")}</div>`) + `</section>`;
16208
16522
  }
16209
16523
  var TYPE_COLORS = { US: C.blue, FIX: C.red, REFACTOR: C.purple, IDEA: C.amber };
16210
16524
  function typeBadge(type) {
@@ -16496,26 +16810,38 @@ var CONSOLE_SCRIPT = `<script>
16496
16810
  chips[c].classList.toggle("on", !!active[chips[c].getAttribute("data-filter")]);
16497
16811
  }
16498
16812
  }
16499
- // US-DOSSIER-013: cycle ledger range filter (verdicts recounted, failures never hidden).
16813
+ // US-DOSSIER-013 / FIX-297: cycle ledger range filter. Rows render newest-first.
16814
+ // The default "recent" window count-caps the view to the newest RECENT_CAP cycles
16815
+ // (the loop runs hundreds a week, so showing all history overwhelms the page).
16816
+ // Two invariants hold for EVERY range, including "recent":
16817
+ // 1. A failed/reverted/blocked cycle is NEVER hidden by the window \u2014 failures
16818
+ // are first-class, always in view however narrow the window.
16819
+ // 2. The "failed" tally counts the FULL ledger, not just what's visible, so it
16820
+ // stays accurate no matter which range is active.
16821
+ var RECENT_CAP = 50;
16500
16822
  function applyRange(range) {
16501
16823
  var rows = document.querySelectorAll(".cy-row");
16502
16824
  var nowSec = Math.floor(Date.now() / 1000);
16503
- var horizon = range === "all" ? Infinity : Number(range) * 86400;
16504
- var count = 0, failed = 0;
16825
+ var horizon = range === "all" || range === "recent" ? Infinity : Number(range) * 86400;
16826
+ var shown = 0, failedAll = 0, kept = 0;
16505
16827
  for (var i = 0; i < rows.length; i++) {
16828
+ var v = rows[i].getAttribute("data-verdict");
16829
+ var isFail = v === "failed" || v === "reverted" || v === "blocked";
16830
+ if (isFail) failedAll++; // full-ledger tally, independent of the window
16506
16831
  var ts = Number(rows[i].getAttribute("data-ts")) || 0;
16507
- var show = range === "all" || nowSec - ts <= horizon;
16832
+ var inWindow = range === "all" ? true
16833
+ : range === "recent" ? kept < RECENT_CAP
16834
+ : nowSec - ts <= horizon;
16835
+ // Failures are always shown; otherwise honor the window.
16836
+ var show = isFail || inWindow;
16508
16837
  rows[i].style.display = show ? "" : "none";
16509
- if (show) {
16510
- count++;
16511
- var v = rows[i].getAttribute("data-verdict");
16512
- if (v === "failed" || v === "reverted" || v === "blocked") failed++;
16513
- }
16838
+ if (show) shown++;
16839
+ if (inWindow) kept++; // the cap counts non-fail slots; failures are bonus
16514
16840
  }
16515
16841
  var c = document.getElementById("cy-count");
16516
16842
  var f = document.getElementById("cy-failed");
16517
- if (c) c.textContent = String(count);
16518
- if (f) f.textContent = String(failed);
16843
+ if (c) c.textContent = String(shown);
16844
+ if (f) f.textContent = String(failedAll);
16519
16845
  var btns = document.querySelectorAll(".cy-range");
16520
16846
  for (var b = 0; b < btns.length; b++) btns[b].classList.toggle("on", btns[b].getAttribute("data-range") === range);
16521
16847
  }
@@ -16617,7 +16943,7 @@ var CONSOLE_SCRIPT = `<script>
16617
16943
  for (var rb = 0; rb < rbs.length; rb++) {
16618
16944
  rbs[rb].addEventListener("click", function () { applyRange(this.getAttribute("data-range")); });
16619
16945
  }
16620
- applyRange("3");
16946
+ applyRange("recent");
16621
16947
  applyFreshness();
16622
16948
  tickCountdown();
16623
16949
  setInterval(tickCountdown, 30000);
@@ -16794,7 +17120,7 @@ ${CONSOLE_SCRIPT}
16794
17120
  }
16795
17121
 
16796
17122
  // packages/cli/dist/lib/page-charter.js
16797
- import { existsSync as existsSync20, readFileSync as readFileSync18, readdirSync as readdirSync8, statSync as statSync10 } from "node:fs";
17123
+ import { existsSync as existsSync20, readFileSync as readFileSync19, readdirSync as readdirSync8, statSync as statSync10 } from "node:fs";
16798
17124
  import { join as join18 } from "node:path";
16799
17125
  function docTitle(src, path) {
16800
17126
  for (const raw of src.split("\n")) {
@@ -16863,7 +17189,7 @@ function defaultCharterDeps(cwd, render2) {
16863
17189
  if (!existsSync20(abs))
16864
17190
  return void 0;
16865
17191
  try {
16866
- return readFileSync18(abs, "utf8");
17192
+ return readFileSync19(abs, "utf8");
16867
17193
  } catch {
16868
17194
  return void 0;
16869
17195
  }
@@ -17049,7 +17375,7 @@ function renderAboutPage(input) {
17049
17375
  // packages/cli/dist/lib/page-conventions.js
17050
17376
  init_dist3();
17051
17377
  init_dist2();
17052
- import { existsSync as existsSync22, readFileSync as readFileSync19 } from "node:fs";
17378
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "node:fs";
17053
17379
  import { join as join20 } from "node:path";
17054
17380
  var SYNC_KEYS = [
17055
17381
  { key: "sync_claude", agent: "claude" },
@@ -17087,7 +17413,7 @@ function defaultConventionsDeps(cwd, agents, render2) {
17087
17413
  if (!existsSync22(abs))
17088
17414
  return void 0;
17089
17415
  try {
17090
- return readFileSync19(abs, "utf8");
17416
+ return readFileSync20(abs, "utf8");
17091
17417
  } catch {
17092
17418
  return void 0;
17093
17419
  }
@@ -17132,7 +17458,7 @@ function renderConventionsPage(input) {
17132
17458
  }
17133
17459
 
17134
17460
  // packages/cli/dist/lib/projects-registry.js
17135
- import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync20, renameSync as renameSync4, realpathSync as realpathSync3, writeFileSync as writeFileSync13 } from "node:fs";
17461
+ import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync21, renameSync as renameSync4, realpathSync as realpathSync3, writeFileSync as writeFileSync13 } from "node:fs";
17136
17462
  import { homedir as homedir6, tmpdir } from "node:os";
17137
17463
  import { dirname as dirname11, join as join21, sep } from "node:path";
17138
17464
  function registryHome(home) {
@@ -17171,7 +17497,7 @@ function parseProjectsRegistry(text) {
17171
17497
  function collectProjectsRegistry(home) {
17172
17498
  let text;
17173
17499
  try {
17174
- text = readFileSync20(projectsRegistryPath(home), "utf8");
17500
+ text = readFileSync21(projectsRegistryPath(home), "utf8");
17175
17501
  } catch {
17176
17502
  return [];
17177
17503
  }
@@ -17222,7 +17548,7 @@ function writeProjectRow(entry, home) {
17222
17548
 
17223
17549
  // packages/cli/dist/lib/cycle-ledger.js
17224
17550
  init_dist();
17225
- import { existsSync as existsSync24, readFileSync as readFileSync21 } from "node:fs";
17551
+ import { existsSync as existsSync24, readFileSync as readFileSync22 } from "node:fs";
17226
17552
  import { join as join22 } from "node:path";
17227
17553
  function ledgerVerdict(status2, outcome) {
17228
17554
  if (status2 === "reverted")
@@ -17241,6 +17567,20 @@ function ledgerVerdict(status2, outcome) {
17241
17567
  function ledgerFailedCount(rows) {
17242
17568
  return rows.filter((r) => r.verdict === "failed" || r.verdict === "reverted" || r.verdict === "blocked").length;
17243
17569
  }
17570
+ function isIdleHeartbeat(row2, verdict) {
17571
+ if (verdict !== "idle")
17572
+ return false;
17573
+ const storyId = typeof row2["story_id"] === "string" ? row2["story_id"] : "";
17574
+ if (storyId !== "")
17575
+ return false;
17576
+ const tcr = typeof row2["tcr_count"] === "number" ? row2["tcr_count"] : 0;
17577
+ if (tcr > 0)
17578
+ return false;
17579
+ const built = row2["built"];
17580
+ if (Array.isArray(built) && built.length > 0)
17581
+ return false;
17582
+ return true;
17583
+ }
17244
17584
  function fmtTokens2(tin, tout, usageUnknown) {
17245
17585
  if (usageUnknown)
17246
17586
  return "?";
@@ -17267,7 +17607,7 @@ function readEventFacts(projectPath3) {
17267
17607
  return { byCycle, prMergedBy, prOpenBy };
17268
17608
  let content = "";
17269
17609
  try {
17270
- content = readFileSync21(path, "utf8");
17610
+ content = readFileSync22(path, "utf8");
17271
17611
  } catch {
17272
17612
  return { byCycle, prMergedBy, prOpenBy };
17273
17613
  }
@@ -17322,7 +17662,7 @@ function collectCycleLedger(projectPath3) {
17322
17662
  return [];
17323
17663
  let content = "";
17324
17664
  try {
17325
- content = readFileSync21(runsPath3, "utf8");
17665
+ content = readFileSync22(runsPath3, "utf8");
17326
17666
  } catch {
17327
17667
  return [];
17328
17668
  }
@@ -17343,6 +17683,8 @@ function collectCycleLedger(projectPath3) {
17343
17683
  const status2 = String(row2["status"] ?? "");
17344
17684
  const outcome = String(row2["outcome"] ?? "");
17345
17685
  const verdict = ledgerVerdict(status2, outcome);
17686
+ if (isIdleHeartbeat(row2, verdict))
17687
+ continue;
17346
17688
  const storyId = typeof row2["story_id"] === "string" ? row2["story_id"] : "";
17347
17689
  const rawTs = row2["ts"];
17348
17690
  const ts = typeof rawTs === "string" ? Date.parse(rawTs) : typeof rawTs === "number" ? rawTs > 1e10 ? rawTs : rawTs * 1e3 : Number.NaN;
@@ -17378,14 +17720,14 @@ function collectCycleLedger(projectPath3) {
17378
17720
 
17379
17721
  // packages/cli/dist/lib/agent-panel.js
17380
17722
  init_dist2();
17381
- import { existsSync as existsSync26, readFileSync as readFileSync23 } from "node:fs";
17723
+ import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
17382
17724
  import { join as join24 } from "node:path";
17383
17725
 
17384
17726
  // packages/cli/dist/commands/status.js
17385
17727
  init_dist();
17386
17728
  import { execFileSync as execFileSync3 } from "node:child_process";
17387
17729
  import { createHash as createHash4 } from "node:crypto";
17388
- import { existsSync as existsSync25, lstatSync as lstatSync2, readdirSync as readdirSync9, readFileSync as readFileSync22, realpathSync as realpathSync4, statSync as statSync11 } from "node:fs";
17730
+ import { existsSync as existsSync25, lstatSync as lstatSync2, readdirSync as readdirSync9, readFileSync as readFileSync23, realpathSync as realpathSync4, statSync as statSync11 } from "node:fs";
17389
17731
  import { homedir as homedir7 } from "node:os";
17390
17732
  import { basename as basename7, dirname as dirname12, join as join23 } from "node:path";
17391
17733
  function rollHome2() {
@@ -17419,7 +17761,7 @@ function parseAiEntries() {
17419
17761
  if (!existsSync25(cfg))
17420
17762
  return [];
17421
17763
  const entries = [];
17422
- for (const line of readFileSync22(cfg, "utf8").split("\n")) {
17764
+ for (const line of readFileSync23(cfg, "utf8").split("\n")) {
17423
17765
  const m7 = /^ai_[a-z]+:\s*(.+)/.exec(line);
17424
17766
  if (m7 === null)
17425
17767
  continue;
@@ -17447,13 +17789,13 @@ function aiSyncStatus(e) {
17447
17789
  if (!existsSync25(rollMd))
17448
17790
  return "out-of-sync";
17449
17791
  try {
17450
- if (existsSync25(src) && !readFileSync22(rollMd).equals(readFileSync22(src)))
17792
+ if (existsSync25(src) && !readFileSync23(rollMd).equals(readFileSync23(src)))
17451
17793
  return "out-of-sync";
17452
17794
  } catch {
17453
17795
  return "out-of-sync";
17454
17796
  }
17455
17797
  try {
17456
- if (!readFileSync22(cfgFile, "utf8").includes("@roll.md"))
17798
+ if (!readFileSync23(cfgFile, "utf8").includes("@roll.md"))
17457
17799
  return "out-of-sync";
17458
17800
  } catch {
17459
17801
  return "out-of-sync";
@@ -17862,7 +18204,7 @@ function spend72h(projectPath3, nowSec2) {
17862
18204
  return out3;
17863
18205
  let content = "";
17864
18206
  try {
17865
- content = readFileSync23(path, "utf8");
18207
+ content = readFileSync24(path, "utf8");
17866
18208
  } catch {
17867
18209
  return out3;
17868
18210
  }
@@ -17931,7 +18273,7 @@ function collectAgentPanel(projectPath3, deps = defaultAgentPanelDeps()) {
17931
18273
  // packages/cli/dist/lib/release-panel.js
17932
18274
  init_dist2();
17933
18275
  init_dist();
17934
- import { existsSync as existsSync27, readFileSync as readFileSync24, readdirSync as readdirSync10 } from "node:fs";
18276
+ import { existsSync as existsSync27, readFileSync as readFileSync25, readdirSync as readdirSync10 } from "node:fs";
17935
18277
  import { join as join25 } from "node:path";
17936
18278
  function collectReleasePanel(projectPath3) {
17937
18279
  const dimsEmpty = CONSISTENCY_DIMENSIONS.map((key) => ({
@@ -17943,7 +18285,7 @@ function collectReleasePanel(projectPath3) {
17943
18285
  try {
17944
18286
  const latest = readdirSync10(dir).filter((f) => f.endsWith(".json")).sort().at(-1);
17945
18287
  if (latest !== void 0) {
17946
- const obj = JSON.parse(readFileSync24(join25(dir, latest), "utf8"));
18288
+ const obj = JSON.parse(readFileSync25(join25(dir, latest), "utf8"));
17947
18289
  const findings = Array.isArray(obj.findings) ? obj.findings : [];
17948
18290
  const tallies = tallyByDimension(findings);
17949
18291
  out3.dims = CONSISTENCY_DIMENSIONS.map((key) => ({ key, tally: tallies[key] }));
@@ -17962,7 +18304,7 @@ function collectReleasePanel(projectPath3) {
17962
18304
  const path = join25(projectPath3, ".roll", "loop", "events.ndjson");
17963
18305
  if (existsSync27(path)) {
17964
18306
  const tags = [];
17965
- for (const line of readFileSync24(path, "utf8").split("\n")) {
18307
+ for (const line of readFileSync25(path, "utf8").split("\n")) {
17966
18308
  const e = parseEventLine(line);
17967
18309
  if (e !== null && e.type === "release:gate" && typeof e.tag === "string" && e.tag !== "" && tags.at(-1) !== e.tag) {
17968
18310
  tags.push(e.tag);
@@ -17978,7 +18320,7 @@ function collectReleasePanel(projectPath3) {
17978
18320
 
17979
18321
  // packages/cli/dist/lib/release-scope.js
17980
18322
  init_dist();
17981
- import { existsSync as existsSync28, readFileSync as readFileSync25 } from "node:fs";
18323
+ import { existsSync as existsSync28, readFileSync as readFileSync26 } from "node:fs";
17982
18324
  import { join as join26 } from "node:path";
17983
18325
  function groupByEpic(items) {
17984
18326
  const map = /* @__PURE__ */ new Map();
@@ -17995,7 +18337,7 @@ function prFromEvents(projectPath3) {
17995
18337
  if (!existsSync28(path))
17996
18338
  return out3;
17997
18339
  try {
17998
- for (const line of readFileSync25(path, "utf8").split("\n")) {
18340
+ for (const line of readFileSync26(path, "utf8").split("\n")) {
17999
18341
  const e = parseEventLine(line);
18000
18342
  if (e !== null && e.type === "pr:merge")
18001
18343
  out3.set(e.storyId, e.prNumber);
@@ -18032,7 +18374,7 @@ function collectHistory(projectPath3) {
18032
18374
  return [];
18033
18375
  let text = "";
18034
18376
  try {
18035
- text = readFileSync25(path, "utf8");
18377
+ text = readFileSync26(path, "utf8");
18036
18378
  } catch {
18037
18379
  return [];
18038
18380
  }
@@ -18040,7 +18382,7 @@ function collectHistory(projectPath3) {
18040
18382
  try {
18041
18383
  const ev = join26(projectPath3, ".roll", "loop", "events.ndjson");
18042
18384
  if (existsSync28(ev)) {
18043
- for (const line of readFileSync25(ev, "utf8").split("\n")) {
18385
+ for (const line of readFileSync26(ev, "utf8").split("\n")) {
18044
18386
  const e = parseEventLine(line);
18045
18387
  if (e !== null && e.type === "release:gate" && Array.isArray(e.waivedRules) && e.waivedRules.length > 0) {
18046
18388
  waivedTags.add(e.tag);
@@ -18073,11 +18415,11 @@ function collectHistory(projectPath3) {
18073
18415
  }
18074
18416
 
18075
18417
  // packages/cli/dist/lib/skills-panel.js
18076
- import { existsSync as existsSync30, readFileSync as readFileSync27, readdirSync as readdirSync12, statSync as statSync12 } from "node:fs";
18418
+ import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync12, statSync as statSync12 } from "node:fs";
18077
18419
  import { join as join28 } from "node:path";
18078
18420
 
18079
18421
  // packages/cli/dist/lib/skills-audit.js
18080
- import { existsSync as existsSync29, readFileSync as readFileSync26, readdirSync as readdirSync11 } from "node:fs";
18422
+ import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync11 } from "node:fs";
18081
18423
  import { join as join27, relative as relative3, sep as sep2 } from "node:path";
18082
18424
  function stripYamlQuotes(value) {
18083
18425
  const trimmed = value.trim();
@@ -18159,7 +18501,7 @@ function collectReferencedSpokes(body) {
18159
18501
  return [...refs].sort();
18160
18502
  }
18161
18503
  function parseSkillFile(file) {
18162
- const text = readFileSync26(file, "utf8");
18504
+ const text = readFileSync27(file, "utf8");
18163
18505
  const { fields, body, ok: ok8 } = parseFrontmatter(text);
18164
18506
  const skillDir = join27(file, "..");
18165
18507
  const description = fields["description"] ?? "";
@@ -18192,7 +18534,7 @@ function loadRouteCases(routeFile) {
18192
18534
  if (!existsSync29(routeFile))
18193
18535
  return { skills: {} };
18194
18536
  try {
18195
- return JSON.parse(readFileSync26(routeFile, "utf8"));
18537
+ return JSON.parse(readFileSync27(routeFile, "utf8"));
18196
18538
  } catch {
18197
18539
  return { skills: {} };
18198
18540
  }
@@ -18296,7 +18638,7 @@ function skillGroupOf(name) {
18296
18638
  }
18297
18639
  function lineCount(path) {
18298
18640
  try {
18299
- return readFileSync27(path, "utf8").split("\n").length;
18641
+ return readFileSync28(path, "utf8").split("\n").length;
18300
18642
  } catch {
18301
18643
  return 0;
18302
18644
  }
@@ -18360,7 +18702,7 @@ function collectSkillsPanel(projectPath3, deps = defaultSkillsPanelDeps(projectP
18360
18702
  usage: usage2[name] ?? 0,
18361
18703
  files: skillFiles(dir),
18362
18704
  dirPath: dir,
18363
- hubText: existsSync30(hubPath) ? readFileSync27(hubPath, "utf8") : ""
18705
+ hubText: existsSync30(hubPath) ? readFileSync28(hubPath, "utf8") : ""
18364
18706
  };
18365
18707
  });
18366
18708
  const hubLines = rows.reduce((acc, r) => acc + r.hubLines, 0);
@@ -18533,7 +18875,7 @@ function renderSkillsPage(input) {
18533
18875
  }
18534
18876
 
18535
18877
  // packages/cli/dist/lib/loop-heartbeat.js
18536
- import { existsSync as existsSync31, readFileSync as readFileSync28 } from "node:fs";
18878
+ import { existsSync as existsSync31, readFileSync as readFileSync29 } from "node:fs";
18537
18879
  import { join as join29 } from "node:path";
18538
18880
  var LANES = ["loop", "dream"];
18539
18881
  function defaultHeartbeatDeps(projectPath3, slug, launchAgentsDir3) {
@@ -18541,14 +18883,14 @@ function defaultHeartbeatDeps(projectPath3, slug, launchAgentsDir3) {
18541
18883
  plistText: (svc) => {
18542
18884
  const p = join29(launchAgentsDir3, `com.roll.${svc}.${slug}.plist`);
18543
18885
  try {
18544
- return existsSync31(p) ? readFileSync28(p, "utf8") : null;
18886
+ return existsSync31(p) ? readFileSync29(p, "utf8") : null;
18545
18887
  } catch {
18546
18888
  return null;
18547
18889
  }
18548
18890
  },
18549
18891
  lastRunAt: () => {
18550
18892
  try {
18551
- const lines2 = readFileSync28(join29(projectPath3, ".roll", "loop", "runs.jsonl"), "utf8").trim().split("\n");
18893
+ const lines2 = readFileSync29(join29(projectPath3, ".roll", "loop", "runs.jsonl"), "utf8").trim().split("\n");
18552
18894
  for (let i = lines2.length - 1; i >= 0; i--) {
18553
18895
  const line = lines2[i] ?? "";
18554
18896
  if (line.trim() === "")
@@ -18605,7 +18947,7 @@ function collectLoopHeartbeat(deps) {
18605
18947
 
18606
18948
  // packages/cli/dist/lib/casting.js
18607
18949
  init_dist2();
18608
- import { existsSync as existsSync32, readFileSync as readFileSync29 } from "node:fs";
18950
+ import { existsSync as existsSync32, readFileSync as readFileSync30 } from "node:fs";
18609
18951
  import { join as join30 } from "node:path";
18610
18952
  var EM_DASH = "\u2014";
18611
18953
  function slotRow(key, roleEn, roleZh, deps) {
@@ -18694,7 +19036,7 @@ function defaultCastingDeps(projectPath3) {
18694
19036
  const agentsPath2 = process.env["ROLL_AGENTS_CONFIG"] ?? join30(projectPath3, ".roll", "agents.yaml");
18695
19037
  try {
18696
19038
  if (existsSync32(agentsPath2))
18697
- agentsText = readFileSync29(agentsPath2, "utf8");
19039
+ agentsText = readFileSync30(agentsPath2, "utf8");
18698
19040
  } catch {
18699
19041
  agentsText = null;
18700
19042
  }
@@ -18702,7 +19044,7 @@ function defaultCastingDeps(projectPath3) {
18702
19044
  try {
18703
19045
  const evPath = join30(projectPath3, ".roll", "loop", "events.ndjson");
18704
19046
  if (existsSync32(evPath)) {
18705
- const content = readFileSync29(evPath, "utf8");
19047
+ const content = readFileSync30(evPath, "utf8");
18706
19048
  for (const line of content.split("\n")) {
18707
19049
  const t2 = line.trim();
18708
19050
  if (t2 === "" || !t2.includes("route:resolve"))
@@ -18741,7 +19083,7 @@ init_dist2();
18741
19083
  init_dist();
18742
19084
  import { createHash as createHash5 } from "node:crypto";
18743
19085
  import { spawn as spawn2, spawnSync } from "node:child_process";
18744
- import { existsSync as existsSync33, mkdirSync as mkdirSync16, readFileSync as readFileSync30, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync14, readdirSync as readdirSync13 } from "node:fs";
19086
+ import { existsSync as existsSync33, mkdirSync as mkdirSync16, readFileSync as readFileSync31, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync14, readdirSync as readdirSync13 } from "node:fs";
18745
19087
  import { homedir as homedir8 } from "node:os";
18746
19088
  import { dirname as dirname13, join as join31 } from "node:path";
18747
19089
  function realDeps2() {
@@ -19016,7 +19358,7 @@ function syncGoalPaused(projectPath3, reason) {
19016
19358
  if (!existsSync33(path))
19017
19359
  return;
19018
19360
  try {
19019
- const before = parseGoalYaml(readFileSync30(path, "utf8"));
19361
+ const before = parseGoalYaml(readFileSync31(path, "utf8"));
19020
19362
  if (before.status === "paused" || before.status === "complete")
19021
19363
  return;
19022
19364
  const at = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -19058,7 +19400,7 @@ async function loopOnCommand(_args, deps = realDeps2()) {
19058
19400
  const localYaml = join31(id.path, ".roll", "local.yaml");
19059
19401
  if (existsSync33(localYaml)) {
19060
19402
  try {
19061
- period = parseLoopPeriodMinutes(readFileSync30(localYaml, "utf8"));
19403
+ period = parseLoopPeriodMinutes(readFileSync31(localYaml, "utf8"));
19062
19404
  } catch {
19063
19405
  }
19064
19406
  }
@@ -19207,7 +19549,7 @@ async function loopResumeCommand(_args, deps = realDeps2()) {
19207
19549
  const stateFile2 = join31(rt, `state-${id.slug}.yaml`);
19208
19550
  if (existsSync33(stateFile2)) {
19209
19551
  try {
19210
- const body = readFileSync30(stateFile2, "utf8");
19552
+ const body = readFileSync31(stateFile2, "utf8");
19211
19553
  const lines2 = body.split("\n").filter((l) => /^(?!heal_count_head_)/.test(l));
19212
19554
  writeFileSync14(stateFile2, lines2.join("\n"), "utf8");
19213
19555
  } catch {
@@ -19250,7 +19592,7 @@ async function loopNowCommand(_args, deps = realDeps2()) {
19250
19592
  let legacy = false;
19251
19593
  if (existsSync33(runner)) {
19252
19594
  try {
19253
- legacy = isLegacyRunner(readFileSync30(runner, "utf8"));
19595
+ legacy = isLegacyRunner(readFileSync31(runner, "utf8"));
19254
19596
  } catch {
19255
19597
  legacy = true;
19256
19598
  }
@@ -19410,7 +19752,7 @@ ${CHROME_CONTROLS}
19410
19752
  init_dist2();
19411
19753
  init_dist();
19412
19754
  import { execFileSync as execFileSync4 } from "node:child_process";
19413
- import { existsSync as existsSync34, readdirSync as readdirSync14, readFileSync as readFileSync31, statSync as statSync13 } from "node:fs";
19755
+ import { existsSync as existsSync34, readdirSync as readdirSync14, readFileSync as readFileSync32, statSync as statSync13 } from "node:fs";
19414
19756
  import { join as joinPath2 } from "node:path";
19415
19757
  var esc6 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19416
19758
  function storyDeliveryState(d) {
@@ -20803,7 +21145,7 @@ function rebaseAcEvidenceToStoryRoot(ev) {
20803
21145
  return ev.map((e) => e.href !== void 0 ? { ...e, href: rebaseEvidenceHrefToStoryRoot(e.href) } : e);
20804
21146
  }
20805
21147
  function readFile(p) {
20806
- return readFileSync31(p, "utf8");
21148
+ return readFileSync32(p, "utf8");
20807
21149
  }
20808
21150
  function statDir(p) {
20809
21151
  return statSync13(p).isFile();
@@ -20828,7 +21170,7 @@ function renderNowSec() {
20828
21170
  function readJsonl(path) {
20829
21171
  let content;
20830
21172
  try {
20831
- content = readFileSync32(path, "utf8");
21173
+ content = readFileSync33(path, "utf8");
20832
21174
  } catch {
20833
21175
  return void 0;
20834
21176
  }
@@ -20871,7 +21213,7 @@ function latestConsistencyAudit(projectPath3) {
20871
21213
  if (latest === void 0)
20872
21214
  return void 0;
20873
21215
  try {
20874
- const obj = JSON.parse(readFileSync32(join32(dir, latest), "utf8"));
21216
+ const obj = JSON.parse(readFileSync33(join32(dir, latest), "utf8"));
20875
21217
  const summary = obj["summary"];
20876
21218
  if (typeof summary !== "object" || summary === null || Array.isArray(summary))
20877
21219
  return void 0;
@@ -20928,7 +21270,7 @@ function releaseTruthBoard(projectPath3, nowSec2) {
20928
21270
  const path = join32(projectPath3, ".roll", "loop", "events.ndjson");
20929
21271
  let content;
20930
21272
  try {
20931
- content = readFileSync32(path, "utf8");
21273
+ content = readFileSync33(path, "utf8");
20932
21274
  } catch {
20933
21275
  return void 0;
20934
21276
  }
@@ -20994,7 +21336,7 @@ function renderSpecHtml(storyDir, id) {
20994
21336
  return null;
20995
21337
  let md;
20996
21338
  try {
20997
- md = readFileSync32(specPath, "utf8");
21339
+ md = readFileSync33(specPath, "utf8");
20998
21340
  } catch {
20999
21341
  return null;
21000
21342
  }
@@ -21255,7 +21597,7 @@ function defaultProcessReaders(projectPath3, env) {
21255
21597
  if (!existsSync36(p))
21256
21598
  return null;
21257
21599
  try {
21258
- return readFileSync33(p, "utf8");
21600
+ return readFileSync34(p, "utf8");
21259
21601
  } catch {
21260
21602
  return null;
21261
21603
  }
@@ -21482,7 +21824,7 @@ function readAcMap(storyDir) {
21482
21824
  if (!existsSync36(p))
21483
21825
  return null;
21484
21826
  try {
21485
- const arr2 = JSON.parse(readFileSync33(p, "utf8"));
21827
+ const arr2 = JSON.parse(readFileSync34(p, "utf8"));
21486
21828
  if (!Array.isArray(arr2))
21487
21829
  return null;
21488
21830
  const m7 = /* @__PURE__ */ new Map();
@@ -21503,7 +21845,7 @@ function toRef(runDir, e) {
21503
21845
  if (!existsSync36(p))
21504
21846
  return null;
21505
21847
  try {
21506
- const { redacted, hits } = redactSecrets(readFileSync33(p, "utf8"));
21848
+ const { redacted, hits } = redactSecrets(readFileSync34(p, "utf8"));
21507
21849
  if (hits.length > 0)
21508
21850
  warn2(`redacted secret(s) in ${e.textFile}: ${hits.join(", ")}`);
21509
21851
  return { kind, label: label4, inlineHtml: ansiPre(redacted) };
@@ -21522,7 +21864,7 @@ function toRef(runDir, e) {
21522
21864
  return null;
21523
21865
  }
21524
21866
  if (kind === "cast") {
21525
- const { redacted, hits } = redactSecrets(readFileSync33(p, "utf8"));
21867
+ const { redacted, hits } = redactSecrets(readFileSync34(p, "utf8"));
21526
21868
  if (hits.length > 0)
21527
21869
  warn2(`redacted secret(s) in ${e.href}: ${hits.join(", ")}`);
21528
21870
  const bounded = boundTranscript(redacted);
@@ -21562,7 +21904,7 @@ function readBacklogRow(projectPath3, storyId) {
21562
21904
  return {};
21563
21905
  let text;
21564
21906
  try {
21565
- text = readFileSync33(p, "utf8");
21907
+ text = readFileSync34(p, "utf8");
21566
21908
  } catch {
21567
21909
  return {};
21568
21910
  }
@@ -21600,7 +21942,7 @@ function buildCardContext(projectPath3, featureFile, storyId, env) {
21600
21942
  const oneLiner = row2.description !== void 0 ? row2.description.replace(/\s*depends-on:\S+/gi, "").trim() : void 0;
21601
21943
  let summary;
21602
21944
  try {
21603
- summary = extractSummary(readFileSync33(featureFile, "utf8"));
21945
+ summary = extractSummary(readFileSync34(featureFile, "utf8"));
21604
21946
  } catch {
21605
21947
  }
21606
21948
  const epic = epicFromFeaturePath(featureFile);
@@ -21719,7 +22061,7 @@ function ensurePreEvidenceMarker(storyId, runDir) {
21719
22061
  function resolveStoryAcItems(projectPath3, storyId) {
21720
22062
  for (const cand of findFeatureFiles(projectPath3, storyId)) {
21721
22063
  try {
21722
- const items = acForStory(readFileSync33(cand, "utf8"), storyId, {
22064
+ const items = acForStory(readFileSync34(cand, "utf8"), storyId, {
21723
22065
  fileOwned: basename8(cand) === `${storyId}.md`
21724
22066
  });
21725
22067
  if (items.length > 0)
@@ -21760,7 +22102,7 @@ async function attestBackfillCommand(args, deps) {
21760
22102
  return 1;
21761
22103
  }
21762
22104
  const stats = { scanned: 0, backfilled: 0, skippedExisting: 0, skippedMissingCard: 0, failed: 0 };
21763
- const items = parseBacklog(readFileSync33(backlogPath, "utf8")).filter((item) => classifyStatus(item.status) === "done");
22105
+ const items = parseBacklog(readFileSync34(backlogPath, "utf8")).filter((item) => classifyStatus(item.status) === "done");
21764
22106
  for (const item of items) {
21765
22107
  stats.scanned += 1;
21766
22108
  const featureFile = findFeatureFile(projectPath3, item.id);
@@ -21949,7 +22291,7 @@ async function attestCommand(args, deps = {}) {
21949
22291
  const deliveryHtml = `<p><a href="${reportRel}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a></p>
21950
22292
  ` + dossierVisualsHtml(runRel, beforeAfter, afterOnly) + `<p class="muted">${bi("Delivered", "\u4EA4\u4ED8\u4E8E")} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}</p>
21951
22293
  `;
21952
- const idx = markPhaseDone(readFileSync33(indexPath, "utf8"), "delivery", deliveryHtml);
22294
+ const idx = markPhaseDone(readFileSync34(indexPath, "utf8"), "delivery", deliveryHtml);
21953
22295
  writeFileSync16(indexPath, idx, "utf8");
21954
22296
  } catch {
21955
22297
  }
@@ -21978,12 +22320,12 @@ async function attestCommand(args, deps = {}) {
21978
22320
 
21979
22321
  // packages/cli/dist/commands/backlog.js
21980
22322
  init_dist();
21981
- import { existsSync as existsSync37, readFileSync as readFileSync34 } from "node:fs";
22323
+ import { existsSync as existsSync37, readFileSync as readFileSync35 } from "node:fs";
21982
22324
  var ID_RE = /\[([^\]]+)\]\([^)]+\)/;
21983
22325
  var REASON_RE = /\[([^\]]+)\]/;
21984
22326
  function parseBacklog2(path) {
21985
22327
  const items = [];
21986
- for (const raw of readFileSync34(path, "utf8").split("\n")) {
22328
+ for (const raw of readFileSync35(path, "utf8").split("\n")) {
21987
22329
  const line = raw.replace(/\n$/, "");
21988
22330
  if (!line.startsWith("|"))
21989
22331
  continue;
@@ -22105,7 +22447,7 @@ function backlogCommand(args) {
22105
22447
  // packages/cli/dist/commands/backlog-mgmt.js
22106
22448
  init_dist2();
22107
22449
  init_dist();
22108
- import { appendFileSync as appendFileSync5, existsSync as existsSync38, mkdirSync as mkdirSync18, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
22450
+ import { appendFileSync as appendFileSync5, existsSync as existsSync38, mkdirSync as mkdirSync18, readFileSync as readFileSync36, writeFileSync as writeFileSync17 } from "node:fs";
22109
22451
  import { dirname as dirname14, join as join34 } from "node:path";
22110
22452
  var BACKLOG_PATH = ".roll/backlog.md";
22111
22453
  function lang() {
@@ -22226,8 +22568,8 @@ function backlogUnstickCommand(args, deps = realUnstickDeps()) {
22226
22568
  const slug = deps.slug();
22227
22569
  const loopDir = join34(deps.sharedRoot(), "loop");
22228
22570
  const eventsPath2 = join34(loopDir, `events-${slug}.ndjson`);
22229
- const events = existsSync38(eventsPath2) ? parseUnstickEvents(readFileSync35(eventsPath2, "utf8")) : [];
22230
- const content = readFileSync35(backlog, "utf8");
22571
+ const events = existsSync38(eventsPath2) ? parseUnstickEvents(readFileSync36(eventsPath2, "utf8")) : [];
22572
+ const content = readFileSync36(backlog, "utf8");
22231
22573
  const nowMs = deps.nowMs();
22232
22574
  const candidates = reconcileStuckBacklog(content, events, nowMs, ttlHours);
22233
22575
  if (candidates.length === 0)
@@ -22288,7 +22630,7 @@ function backlogLintCommand(args) {
22288
22630
  errLine(`[roll] backlog not found: ${backlog}`);
22289
22631
  return 1;
22290
22632
  }
22291
- const findings = lintBacklogContent(readFileSync35(backlog, "utf8"));
22633
+ const findings = lintBacklogContent(readFileSync36(backlog, "utf8"));
22292
22634
  for (const f of findings) {
22293
22635
  out(`${backlog}:${f.lineno}: ${f.sid} \u2014 ${f.issues}`);
22294
22636
  out(` ${f.desc}`);
@@ -22310,7 +22652,7 @@ function backlogLintCommand(args) {
22310
22652
  // packages/cli/dist/commands/backlog-sync.js
22311
22653
  init_dist2();
22312
22654
  import { execFileSync as execFileSync6 } from "node:child_process";
22313
- import { appendFileSync as appendFileSync6, existsSync as existsSync39, mkdirSync as mkdirSync19, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
22655
+ import { appendFileSync as appendFileSync6, existsSync as existsSync39, mkdirSync as mkdirSync19, readFileSync as readFileSync37, writeFileSync as writeFileSync18 } from "node:fs";
22314
22656
  import { dirname as dirname15, join as join35 } from "node:path";
22315
22657
  var API_ROOT = "https://api.github.com";
22316
22658
  var RATE_LIMIT_FLOOR = 5;
@@ -22405,7 +22747,7 @@ function realSyncDeps() {
22405
22747
  loadIssues: async (owner, repo) => {
22406
22748
  const fixture = (process.env["ROLL_SYNC_FIXTURE"] ?? "").trim();
22407
22749
  if (fixture)
22408
- return JSON.parse(readFileSync36(fixture, "utf8"));
22750
+ return JSON.parse(readFileSync37(fixture, "utf8"));
22409
22751
  return fetchIssues(owner, repo, { state: "open" });
22410
22752
  },
22411
22753
  nowIso: () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
@@ -22419,7 +22761,7 @@ async function backlogSyncCommand(args, deps = realSyncDeps()) {
22419
22761
  const backlog = flagValue(args, "--backlog") ?? ".roll/backlog.md";
22420
22762
  const featuresDir = flagValue(args, "--features") ?? ".roll/features";
22421
22763
  const localYaml = flagValue(args, "--local-yaml") ?? ".roll/local.yaml";
22422
- const cfg = existsSync39(localYaml) ? readSyncConfig(readFileSync36(localYaml, "utf8")) : {};
22764
+ const cfg = existsSync39(localYaml) ? readSyncConfig(readFileSync37(localYaml, "utf8")) : {};
22423
22765
  const repoArg = flagValue(args, "--repo") ?? cfg.repo ?? "";
22424
22766
  if (!repoArg) {
22425
22767
  process.stderr.write("usage: roll backlog sync --repo <owner/repo> [--backlog <path>] [--features <dir>] [--label <a,b>] [--dry-run]\n \u9996\u6B21 sync \u5FC5\u987B\u663E\u5F0F --repo\uFF08local.yaml \u4E2D\u5C1A\u65E0 backlog_sync.repo\uFF09\u3002\n");
@@ -22462,7 +22804,7 @@ async function backlogSyncCommand(args, deps = realSyncDeps()) {
22462
22804
  `);
22463
22805
  return 1;
22464
22806
  }
22465
- const content = readFileSync36(backlog, "utf8");
22807
+ const content = readFileSync37(backlog, "utf8");
22466
22808
  if (dryRun) {
22467
22809
  const preview = dryRunPreview(issues, content);
22468
22810
  for (const line of preview.lines)
@@ -22488,7 +22830,7 @@ async function backlogSyncCommand(args, deps = realSyncDeps()) {
22488
22830
  process.stdout.write(`added: ${result.added}, skipped: ${result.skipped}, total issues: ${result.total}
22489
22831
  `);
22490
22832
  const block = renderSyncBlock(repoArg, wanted, deps.nowIso());
22491
- const original = existsSync39(localYaml) ? readFileSync36(localYaml, "utf8") : "";
22833
+ const original = existsSync39(localYaml) ? readFileSync37(localYaml, "utf8") : "";
22492
22834
  mkdirSync19(dirname15(localYaml) || ".", { recursive: true });
22493
22835
  writeFileSync18(localYaml, original === "" ? block + "\n" : writeSyncBlock(original, block));
22494
22836
  return 0;
@@ -22499,7 +22841,7 @@ function writeFeatureStub(issue, featuresDir, epic = "backlog-lifecycle") {
22499
22841
  const path = join35(epicDir, `${ghId(issue)}.md`);
22500
22842
  const ac = renderAcSection(issue);
22501
22843
  if (existsSync39(path)) {
22502
- const existing = readFileSync36(path, "utf8");
22844
+ const existing = readFileSync37(path, "utf8");
22503
22845
  const block = ac ? ac + "\n" : "";
22504
22846
  const sep3 = existing.endsWith("\n") || existing === "" ? "" : "\n";
22505
22847
  if (block)
@@ -22515,7 +22857,7 @@ init_dist2();
22515
22857
  init_dist3();
22516
22858
  init_dist();
22517
22859
  import { execFileSync as execFileSync7, spawnSync as spawnSync2 } from "node:child_process";
22518
- import { existsSync as existsSync40, mkdirSync as mkdirSync20, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
22860
+ import { existsSync as existsSync40, mkdirSync as mkdirSync20, readFileSync as readFileSync38, writeFileSync as writeFileSync19 } from "node:fs";
22519
22861
  import { tmpdir as tmpdir2 } from "node:os";
22520
22862
  import { dirname as dirname16, join as join36 } from "node:path";
22521
22863
  function lang2() {
@@ -22615,7 +22957,7 @@ function loopEnforceTcrCommand(args, deps = realEnforceTcrDeps()) {
22615
22957
  return 0;
22616
22958
  const backlog = join36(".roll", "backlog.md");
22617
22959
  if (existsSync40(backlog)) {
22618
- writeFileSync19(backlog, revertStoryDone(readFileSync37(backlog, "utf8"), storyId));
22960
+ writeFileSync19(backlog, revertStoryDone(readFileSync38(backlog, "utf8"), storyId));
22619
22961
  }
22620
22962
  const alert = alertFile();
22621
22963
  mkdirSync20(dirname16(alert), { recursive: true });
@@ -22673,7 +23015,7 @@ function loopPrecheckCiCommand(args, deps = realPrecheckDeps()) {
22673
23015
  const sha8 = commit2.slice(0, 8);
22674
23016
  const healKey = `heal_count_head_${sha8}`;
22675
23017
  const state = stateFilePath();
22676
- const body = existsSync40(state) ? readFileSync37(state, "utf8") : "";
23018
+ const body = existsSync40(state) ? readFileSync38(state, "utf8") : "";
22677
23019
  const headHealCount = parseInt(stateGet(body, healKey) || "0", 10) || 0;
22678
23020
  const verdict = precheckCiVerdict({ ghAndCommitOk: true, runs, healMax, headHealCount });
22679
23021
  if (verdict.exit === 2) {
@@ -22785,7 +23127,7 @@ function loopUnknownSubcommand(sub) {
22785
23127
  // packages/cli/dist/commands/brief.js
22786
23128
  init_dist2();
22787
23129
  init_dist();
22788
- import { existsSync as existsSync41, readFileSync as readFileSync38 } from "node:fs";
23130
+ import { existsSync as existsSync41, readFileSync as readFileSync39 } from "node:fs";
22789
23131
  import { homedir as homedir9 } from "node:os";
22790
23132
  import { join as join37 } from "node:path";
22791
23133
  var BACKLOG_PATH2 = ".roll/backlog.md";
@@ -22877,7 +23219,7 @@ function briefCommand(args) {
22877
23219
  `);
22878
23220
  return 1;
22879
23221
  }
22880
- const items = parseBacklog(readFileSync38(BACKLOG_PATH2, "utf8"));
23222
+ const items = parseBacklog(readFileSync39(BACKLOG_PATH2, "utf8"));
22881
23223
  const model = composeBrief(items, activeAlerts());
22882
23224
  const dateStr = formatNow(/* @__PURE__ */ new Date());
22883
23225
  process.stdout.write(renderBrief(model, lang9, { full }, dateStr).join("\n") + "\n");
@@ -22950,7 +23292,7 @@ init_dist();
22950
23292
  // packages/cli/dist/commands/lang.js
22951
23293
  init_dist();
22952
23294
  import { execFileSync as execFileSync8 } from "node:child_process";
22953
- import { existsSync as existsSync42, mkdirSync as mkdirSync21, mkdtempSync, readFileSync as readFileSync39, renameSync as renameSync6, writeFileSync as writeFileSync20 } from "node:fs";
23295
+ import { existsSync as existsSync42, mkdirSync as mkdirSync21, mkdtempSync, readFileSync as readFileSync40, renameSync as renameSync6, writeFileSync as writeFileSync20 } from "node:fs";
22954
23296
  import { homedir as homedir10, tmpdir as tmpdir3 } from "node:os";
22955
23297
  import { dirname as dirname17, join as join38 } from "node:path";
22956
23298
  function rollConfigPath2() {
@@ -22961,7 +23303,7 @@ function configLang2() {
22961
23303
  const cfg = rollConfigPath2();
22962
23304
  if (!existsSync42(cfg))
22963
23305
  return void 0;
22964
- for (const line of readFileSync39(cfg, "utf8").split("\n")) {
23306
+ for (const line of readFileSync40(cfg, "utf8").split("\n")) {
22965
23307
  const m7 = /^lang:\s*(.*)$/.exec(line);
22966
23308
  if (m7 !== null) {
22967
23309
  const v = (m7[1] ?? "").replace(/\s*#.*$/, "").trim();
@@ -22975,7 +23317,7 @@ function configHasLangLine() {
22975
23317
  const cfg = rollConfigPath2();
22976
23318
  if (!existsSync42(cfg))
22977
23319
  return false;
22978
- return readFileSync39(cfg, "utf8").split("\n").some((l) => /^lang:/.test(l));
23320
+ return readFileSync40(cfg, "utf8").split("\n").some((l) => /^lang:/.test(l));
22979
23321
  }
22980
23322
  function appleLang2() {
22981
23323
  if (process.platform !== "darwin")
@@ -23018,7 +23360,7 @@ function resolveSource() {
23018
23360
  function writeLang(value) {
23019
23361
  const cfg = rollConfigPath2();
23020
23362
  mkdirSync21(dirname17(cfg), { recursive: true });
23021
- const existing = existsSync42(cfg) ? readFileSync39(cfg, "utf8") : "";
23363
+ const existing = existsSync42(cfg) ? readFileSync40(cfg, "utf8") : "";
23022
23364
  const kept = existing === "" ? [] : existing.split("\n").filter((l) => !/^lang:/.test(l));
23023
23365
  if (kept.length > 0 && kept[kept.length - 1] === "")
23024
23366
  kept.pop();
@@ -23032,7 +23374,7 @@ function clearLang() {
23032
23374
  const cfg = rollConfigPath2();
23033
23375
  if (!existsSync42(cfg))
23034
23376
  return;
23035
- const existing = readFileSync39(cfg, "utf8");
23377
+ const existing = readFileSync40(cfg, "utf8");
23036
23378
  const kept = existing.split("\n").filter((l) => !/^lang:/.test(l));
23037
23379
  if (kept.length > 0 && kept[kept.length - 1] === "")
23038
23380
  kept.pop();
@@ -23939,7 +24281,7 @@ ${CYCLE_USAGE}
23939
24281
 
23940
24282
  // packages/cli/dist/commands/ls.js
23941
24283
  init_dist();
23942
- import { existsSync as existsSync43, readFileSync as readFileSync40 } from "node:fs";
24284
+ import { existsSync as existsSync43, readFileSync as readFileSync41 } from "node:fs";
23943
24285
  var LS_USAGE = "Usage: roll ls [--json] [--stale-days <n>]\n List the cross-project registry (~/.roll/projects.json): name \xB7 tag \xB7 verdict \xB7 path.\n Missing paths and stale entries are flagged, never dropped. --json echoes the file verbatim.\n\u5217\u51FA\u8DE8\u9879\u76EE\u6CE8\u518C\u8868\uFF08~/.roll/projects.json\uFF09\uFF1A\u540D\u79F0 \xB7 \u7248\u672C \xB7 \u5224\u5B9A \xB7 \u8DEF\u5F84\u3002\n\u7F3A\u5931\u8DEF\u5F84\u4E0E\u8FC7\u671F\u6761\u76EE\u4F1A\u88AB\u6807\u6CE8\uFF0C\u7EDD\u4E0D\u4E22\u5F03\u3002--json \u9010\u5B57\u8F93\u51FA\u6587\u4EF6\u672C\u8EAB\u3002";
23944
24286
  var DEFAULT_STALE_DAYS = 14;
23945
24287
  function projectStatus(entry, nowMs, staleMs, pathExists) {
@@ -24033,7 +24375,7 @@ ${LS_USAGE}
24033
24375
  if (args.includes("--json")) {
24034
24376
  let text;
24035
24377
  try {
24036
- text = readFileSync40(projectsRegistryPath(), "utf8");
24378
+ text = readFileSync41(projectsRegistryPath(), "utf8");
24037
24379
  } catch {
24038
24380
  text = "[]\n";
24039
24381
  }
@@ -24048,7 +24390,7 @@ ${LS_USAGE}
24048
24390
 
24049
24391
  // packages/cli/dist/commands/loop-runs.js
24050
24392
  init_dist();
24051
- import { existsSync as existsSync44, readFileSync as readFileSync41, readdirSync as readdirSync17 } from "node:fs";
24393
+ import { existsSync as existsSync44, readFileSync as readFileSync42, readdirSync as readdirSync17 } from "node:fs";
24052
24394
  import { homedir as homedir12, platform as platform2 } from "node:os";
24053
24395
  import { basename as basename9, join as join40 } from "node:path";
24054
24396
  function lang3() {
@@ -24156,7 +24498,7 @@ function backlogDesc(backlogText, id) {
24156
24498
  }
24157
24499
  return "";
24158
24500
  }
24159
- function formatLine2(row2, showProject, backlogText) {
24501
+ function formatLine(row2, showProject, backlogText) {
24160
24502
  const ts = str3(row2["ts"]);
24161
24503
  const status2 = str3(row2["status"]);
24162
24504
  const project = str3(row2["project"]);
@@ -24216,7 +24558,7 @@ function aggregateAllFiles() {
24216
24558
  for (const pl of plists) {
24217
24559
  let content;
24218
24560
  try {
24219
- content = readFileSync41(join40(laDir, pl), "utf8");
24561
+ content = readFileSync42(join40(laDir, pl), "utf8");
24220
24562
  } catch {
24221
24563
  continue;
24222
24564
  }
@@ -24232,7 +24574,7 @@ function aggregateAllFiles() {
24232
24574
  if (!f || seen.has(f))
24233
24575
  continue;
24234
24576
  try {
24235
- if (!existsSync44(f) || readFileSync41(f, "utf8").trim() === "")
24577
+ if (!existsSync44(f) || readFileSync42(f, "utf8").trim() === "")
24236
24578
  continue;
24237
24579
  } catch {
24238
24580
  continue;
@@ -24246,7 +24588,7 @@ function aggregateAllRows() {
24246
24588
  const rows = [];
24247
24589
  for (const f of aggregateAllFiles()) {
24248
24590
  try {
24249
- rows.push(...parseRows(readFileSync41(f, "utf8")));
24591
+ rows.push(...parseRows(readFileSync42(f, "utf8")));
24250
24592
  } catch {
24251
24593
  }
24252
24594
  }
@@ -24268,7 +24610,7 @@ function runsDetail(cycleId) {
24268
24610
  }
24269
24611
  let rows;
24270
24612
  try {
24271
- rows = parseRows(readFileSync41(src, "utf8"));
24613
+ rows = parseRows(readFileSync42(src, "utf8"));
24272
24614
  } catch {
24273
24615
  rows = [];
24274
24616
  }
@@ -24325,12 +24667,12 @@ function loopRunsCommand(argv) {
24325
24667
  rows = aggregateAllRows();
24326
24668
  } else {
24327
24669
  const src = runsFile();
24328
- if (!existsSync44(src) || readFileSync41(src, "utf8").trim() === "") {
24670
+ if (!existsSync44(src) || readFileSync42(src, "utf8").trim() === "") {
24329
24671
  process.stdout.write(msg3("loop.no_loop_runs_yet") + "\n");
24330
24672
  return 0;
24331
24673
  }
24332
24674
  const slug = projectSlug2();
24333
- rows = parseRows(readFileSync41(src, "utf8")).filter((r) => str3(r["project"]) === slug);
24675
+ rows = parseRows(readFileSync42(src, "utf8")).filter((r) => str3(r["project"]) === slug);
24334
24676
  }
24335
24677
  if (rows.length === 0) {
24336
24678
  process.stdout.write(msg3("loop.no_loop_runs_for_current_project") + "\n");
@@ -24342,12 +24684,12 @@ function loopRunsCommand(argv) {
24342
24684
  const blPath = join40(proj, ".roll", "backlog.md");
24343
24685
  if (existsSync44(blPath)) {
24344
24686
  try {
24345
- backlogText = readFileSync41(blPath, "utf8");
24687
+ backlogText = readFileSync42(blPath, "utf8");
24346
24688
  } catch {
24347
24689
  backlogText = "";
24348
24690
  }
24349
24691
  }
24350
- const out3 = recent.map((r) => formatLine2(r, allFlag, backlogText));
24692
+ const out3 = recent.map((r) => formatLine(r, allFlag, backlogText));
24351
24693
  process.stdout.write(out3.join("\n") + "\n");
24352
24694
  return 0;
24353
24695
  }
@@ -24355,7 +24697,7 @@ function loopRunsCommand(argv) {
24355
24697
  // packages/cli/dist/commands/loop-signals.js
24356
24698
  init_dist2();
24357
24699
  init_dist();
24358
- import { appendFileSync as appendFileSync7, existsSync as existsSync45, mkdirSync as mkdirSync22, readFileSync as readFileSync42, writeFileSync as writeFileSync21 } from "node:fs";
24700
+ import { appendFileSync as appendFileSync7, existsSync as existsSync45, mkdirSync as mkdirSync22, readFileSync as readFileSync43, writeFileSync as writeFileSync21 } from "node:fs";
24359
24701
  import { dirname as dirname18, join as join41 } from "node:path";
24360
24702
  function lang4() {
24361
24703
  return resolveLang({ rollLang: process.env["ROLL_LANG"], lcAll: process.env["LC_ALL"], lang: process.env["LANG"] });
@@ -24388,14 +24730,14 @@ function loopSignalsCommand(argv) {
24388
24730
  process.stdout.write(s + "\n");
24389
24731
  };
24390
24732
  const runsSrc = runsFile();
24391
- if (!existsSync45(runsSrc) || readFileSync42(runsSrc, "utf8").trim() === "") {
24733
+ if (!existsSync45(runsSrc) || readFileSync43(runsSrc, "utf8").trim() === "") {
24392
24734
  say(t(v2Catalog, lang4(), "loop.no_loop_runs_yet"));
24393
24735
  return 0;
24394
24736
  }
24395
24737
  const projectPath3 = (process.env["ROLL_MAIN_PROJECT"] ?? "").trim() || process.cwd();
24396
24738
  const slug = projectSlug2();
24397
24739
  const records = [];
24398
- for (const line of readFileSync42(runsSrc, "utf8").split("\n")) {
24740
+ for (const line of readFileSync43(runsSrc, "utf8").split("\n")) {
24399
24741
  if (line.trim() === "")
24400
24742
  continue;
24401
24743
  try {
@@ -24416,13 +24758,13 @@ function loopSignalsCommand(argv) {
24416
24758
  writeFileSync21(seenFile, "");
24417
24759
  let lastId = 0;
24418
24760
  if (existsSync45(candFile)) {
24419
- for (const m7 of readFileSync42(candFile, "utf8").matchAll(/CAND-(\d+)/g)) {
24761
+ for (const m7 of readFileSync43(candFile, "utf8").matchAll(/CAND-(\d+)/g)) {
24420
24762
  const n = parseInt(m7[1] ?? "0", 10);
24421
24763
  if (n > lastId)
24422
24764
  lastId = n;
24423
24765
  }
24424
24766
  }
24425
- const seen = new Set(existsSync45(seenFile) ? readFileSync42(seenFile, "utf8").split("\n").filter((l) => l !== "") : []);
24767
+ const seen = new Set(existsSync45(seenFile) ? readFileSync43(seenFile, "utf8").split("\n").filter((l) => l !== "") : []);
24426
24768
  let newCount = 0;
24427
24769
  for (const sig of signals) {
24428
24770
  if (seen.has(sig.key))
@@ -24469,7 +24811,7 @@ Options:
24469
24811
 
24470
24812
  // packages/cli/dist/commands/loop-log.js
24471
24813
  init_dist();
24472
- import { existsSync as existsSync46, readFileSync as readFileSync43, readdirSync as readdirSync18 } from "node:fs";
24814
+ import { existsSync as existsSync46, readFileSync as readFileSync44, readdirSync as readdirSync18 } from "node:fs";
24473
24815
  import { basename as basename10, join as join42 } from "node:path";
24474
24816
  function lang5() {
24475
24817
  return resolveLang({ rollLang: process.env["ROLL_LANG"], lcAll: process.env["LC_ALL"], lang: process.env["LANG"] });
@@ -24486,7 +24828,7 @@ function showLog(file) {
24486
24828
  const ts = m7 ? `${m7[1]}-${m7[2]}-${m7[3]} ${m7[4]}:${m7[5]}` : id;
24487
24829
  let body = "";
24488
24830
  try {
24489
- body = readFileSync43(file, "utf8");
24831
+ body = readFileSync44(file, "utf8");
24490
24832
  } catch {
24491
24833
  body = "";
24492
24834
  }
@@ -24542,7 +24884,7 @@ function loopLogCommand(argv) {
24542
24884
 
24543
24885
  // packages/cli/dist/commands/loop-goal.js
24544
24886
  init_dist();
24545
- import { existsSync as existsSync47, readFileSync as readFileSync44 } from "node:fs";
24887
+ import { existsSync as existsSync47, readFileSync as readFileSync45 } from "node:fs";
24546
24888
  import { join as join43 } from "node:path";
24547
24889
  function realDeps3() {
24548
24890
  return { projectPath: () => process.cwd() };
@@ -24616,7 +24958,7 @@ async function loopGoalCommand(args, deps = realDeps3()) {
24616
24958
  return 0;
24617
24959
  }
24618
24960
  try {
24619
- process.stdout.write(renderGoal(parseGoalYaml(readFileSync44(path, "utf8"))));
24961
+ process.stdout.write(renderGoal(parseGoalYaml(readFileSync45(path, "utf8"))));
24620
24962
  return 0;
24621
24963
  } catch (e) {
24622
24964
  process.stderr.write(`[roll] goal.yaml invalid: ${e.message}
@@ -24630,7 +24972,7 @@ init_dist2();
24630
24972
  init_dist();
24631
24973
  init_dist3();
24632
24974
  import { spawn as spawn3, spawnSync as spawnSync4 } from "node:child_process";
24633
- import { appendFileSync as appendFileSync8, createReadStream, existsSync as existsSync48, mkdirSync as mkdirSync23, readFileSync as readFileSync45, renameSync as renameSync7, statSync as statSync15, writeFileSync as writeFileSync22 } from "node:fs";
24975
+ import { appendFileSync as appendFileSync8, createReadStream, existsSync as existsSync48, mkdirSync as mkdirSync23, readFileSync as readFileSync46, renameSync as renameSync7, statSync as statSync15, writeFileSync as writeFileSync22 } from "node:fs";
24634
24976
  import { dirname as dirname19, join as join44 } from "node:path";
24635
24977
  import { createInterface } from "node:readline";
24636
24978
 
@@ -25215,7 +25557,7 @@ function writeGoal(path, goal) {
25215
25557
  function readGoal(path) {
25216
25558
  if (!existsSync48(path))
25217
25559
  return void 0;
25218
- return parseGoalYaml(readFileSync45(path, "utf8"));
25560
+ return parseGoalYaml(readFileSync46(path, "utf8"));
25219
25561
  }
25220
25562
  function createGoal(opts, at) {
25221
25563
  return {
@@ -25249,7 +25591,7 @@ function rowSpentZeroNoExecution(row2) {
25249
25591
  function readRunSnapshot(path) {
25250
25592
  let text = "";
25251
25593
  try {
25252
- text = readFileSync45(path, "utf8");
25594
+ text = readFileSync46(path, "utf8");
25253
25595
  } catch {
25254
25596
  return { rows: [], summary: { cycles: 0, costUsd: 0, costUnknownRows: 0 } };
25255
25597
  }
@@ -25282,7 +25624,7 @@ function readRunSnapshot(path) {
25282
25624
  }
25283
25625
  function readBacklogRows(projectPath3) {
25284
25626
  try {
25285
- return parseBacklog(readFileSync45(join44(projectPath3, ".roll", "backlog.md"), "utf8")).map((row2) => ({
25627
+ return parseBacklog(readFileSync46(join44(projectPath3, ".roll", "backlog.md"), "utf8")).map((row2) => ({
25286
25628
  id: row2.id,
25287
25629
  status: row2.status
25288
25630
  }));
@@ -25292,7 +25634,7 @@ function readBacklogRows(projectPath3) {
25292
25634
  }
25293
25635
  function readStoryIndex(projectPath3) {
25294
25636
  try {
25295
- const obj = JSON.parse(readFileSync45(join44(projectPath3, ".roll", "index.json"), "utf8"));
25637
+ const obj = JSON.parse(readFileSync46(join44(projectPath3, ".roll", "index.json"), "utf8"));
25296
25638
  if (typeof obj.stories !== "object" || obj.stories === null || Array.isArray(obj.stories))
25297
25639
  return {};
25298
25640
  const out3 = {};
@@ -25629,7 +25971,7 @@ async function evaluateGoal(projectPath3, slug, goal, deps, session, workerAgent
25629
25971
  function hasSafetyPauseSince(path, since) {
25630
25972
  let text = "";
25631
25973
  try {
25632
- text = readFileSync45(path, "utf8");
25974
+ text = readFileSync46(path, "utf8");
25633
25975
  } catch {
25634
25976
  return false;
25635
25977
  }
@@ -25759,7 +26101,7 @@ function latestAlertSummary(projectPath3, slug, sinceSec) {
25759
26101
  try {
25760
26102
  if (!existsSync48(path) || statSync15(path).mtimeMs / 1e3 < sinceSec)
25761
26103
  return void 0;
25762
- const lines2 = readFileSync45(path, "utf8").split("\n").map((line2) => line2.trim()).filter((line2) => line2 !== "");
26104
+ const lines2 = readFileSync46(path, "utf8").split("\n").map((line2) => line2.trim()).filter((line2) => line2 !== "");
25763
26105
  const line = [...lines2].reverse().find((candidate) => /\b(ALERT|WARN|BLOCKED|refused|failed)\b/i.test(candidate));
25764
26106
  if (line === void 0)
25765
26107
  return void 0;
@@ -25777,7 +26119,7 @@ function innerLockHolder(projectPath3, nowSec2) {
25777
26119
  try {
25778
26120
  if (!existsSync48(path))
25779
26121
  return void 0;
25780
- const contents = parseLock(readFileSync45(path, "utf8"));
26122
+ const contents = parseLock(readFileSync46(path, "utf8"));
25781
26123
  return isLockHeld(contents, nowSec2, INNER_LOCK_STALE_SEC) ? contents.pid : void 0;
25782
26124
  } catch {
25783
26125
  return void 0;
@@ -25918,14 +26260,14 @@ function applyTimeboxGate(projectPath3, bus, session, goal, deps, deadlineSec) {
25918
26260
  }
25919
26261
  function installStopHandlers(onStop) {
25920
26262
  const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
25921
- const handlers = signals.map((signal2) => {
25922
- const handler = () => onStop(signal2);
25923
- process.on(signal2, handler);
25924
- return { signal: signal2, handler };
26263
+ const handlers = signals.map((signal) => {
26264
+ const handler = () => onStop(signal);
26265
+ process.on(signal, handler);
26266
+ return { signal, handler };
25925
26267
  });
25926
26268
  return () => {
25927
- for (const { signal: signal2, handler } of handlers)
25928
- process.off(signal2, handler);
26269
+ for (const { signal, handler } of handlers)
26270
+ process.off(signal, handler);
25929
26271
  };
25930
26272
  }
25931
26273
  function describeScope(scope) {
@@ -25998,9 +26340,9 @@ async function runGoWorker(id, opts, deps) {
25998
26340
  let stopReason;
25999
26341
  let stopRequested = false;
26000
26342
  let workerAgents = [];
26001
- const disposeSignals = installStopHandlers((signal2) => {
26343
+ const disposeSignals = installStopHandlers((signal) => {
26002
26344
  stopRequested = true;
26003
- stopReason = `signal_${signal2}`;
26345
+ stopReason = `signal_${signal}`;
26004
26346
  });
26005
26347
  try {
26006
26348
  const gPath = goalPath2(id.path);
@@ -26140,7 +26482,7 @@ async function runGoWorker(id, opts, deps) {
26140
26482
  }
26141
26483
 
26142
26484
  // packages/cli/dist/commands/loop-events.js
26143
- import { existsSync as existsSync49, readFileSync as readFileSync46 } from "node:fs";
26485
+ import { existsSync as existsSync49, readFileSync as readFileSync47 } from "node:fs";
26144
26486
  import { join as join45 } from "node:path";
26145
26487
  function str4(v) {
26146
26488
  return typeof v === "string" ? v : v === void 0 || v === null ? "" : String(v);
@@ -26156,7 +26498,7 @@ function loopEventsCommand(argv) {
26156
26498
  }
26157
26499
  let lines2;
26158
26500
  try {
26159
- lines2 = readFileSync46(evfile, "utf8").split("\n").filter((l) => l !== "");
26501
+ lines2 = readFileSync47(evfile, "utf8").split("\n").filter((l) => l !== "");
26160
26502
  } catch {
26161
26503
  lines2 = [];
26162
26504
  }
@@ -26194,14 +26536,14 @@ function loopBranchesRetired() {
26194
26536
  // packages/cli/dist/commands/doctor.js
26195
26537
  init_dist();
26196
26538
  import { execFileSync as execFileSync11 } from "node:child_process";
26197
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync51, mkdtempSync as mkdtempSync3, readFileSync as readFileSync48, readdirSync as readdirSync20, statSync as statSync17, writeFileSync as writeFileSync24 } from "node:fs";
26539
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync51, mkdtempSync as mkdtempSync3, readFileSync as readFileSync49, readdirSync as readdirSync20, statSync as statSync17, writeFileSync as writeFileSync24 } from "node:fs";
26198
26540
  import { homedir as homedir13, tmpdir as tmpdir5 } from "node:os";
26199
26541
  import { delimiter as delimiter2, join as join47 } from "node:path";
26200
26542
 
26201
26543
  // packages/cli/dist/commands/skills.js
26202
26544
  init_dist();
26203
26545
  import { execFileSync as execFileSync10 } from "node:child_process";
26204
- import { existsSync as existsSync50, mkdtempSync as mkdtempSync2, readFileSync as readFileSync47, readdirSync as readdirSync19, statSync as statSync16, writeFileSync as writeFileSync23 } from "node:fs";
26546
+ import { existsSync as existsSync50, mkdtempSync as mkdtempSync2, readFileSync as readFileSync48, readdirSync as readdirSync19, statSync as statSync16, writeFileSync as writeFileSync23 } from "node:fs";
26205
26547
  import { tmpdir as tmpdir4 } from "node:os";
26206
26548
  import { join as join46 } from "node:path";
26207
26549
  function pkgDir() {
@@ -26221,7 +26563,7 @@ function skillsDir() {
26221
26563
  return join46(pkgDir(), "skills");
26222
26564
  }
26223
26565
  function frontmatterField(file, field2) {
26224
- const lines2 = readFileSync47(file, "utf8").split("\n");
26566
+ const lines2 = readFileSync48(file, "utf8").split("\n");
26225
26567
  if (lines2.length === 0 || lines2[0] !== "---")
26226
26568
  return "";
26227
26569
  let collecting = false;
@@ -26466,7 +26808,7 @@ function bilingual(key) {
26466
26808
  return [t(v2Catalog, "en", key), t(v2Catalog, "zh", key)];
26467
26809
  }
26468
26810
  var out2 = { lines: [] };
26469
- function emit(line) {
26811
+ function emit2(line) {
26470
26812
  out2.lines.push(line);
26471
26813
  }
26472
26814
  function agentBinNames4(agent) {
@@ -26547,11 +26889,11 @@ function agentSection(p) {
26547
26889
  const cfg = rollConfigPath4();
26548
26890
  if (!existsSync51(cfg))
26549
26891
  return;
26550
- emit("");
26892
+ emit2("");
26551
26893
  for (const l of bilingual("doctor.agent_detection"))
26552
- emit(l);
26553
- emit("");
26554
- const text = readFileSync48(cfg, "utf8");
26894
+ emit2(l);
26895
+ emit2("");
26896
+ const text = readFileSync49(cfg, "utf8");
26555
26897
  let primary = "";
26556
26898
  for (const line of text.split("\n")) {
26557
26899
  const m7 = /^primary_agent:\s*(.*)$/.exec(line);
@@ -26577,7 +26919,7 @@ function agentSection(p) {
26577
26919
  const installed = agentInstalledByName4(name, dir) ? t(v2Catalog, msgLang5(), "doctor.agent_installed") : t(v2Catalog, msgLang5(), "doctor.agent_missing");
26578
26920
  const dirExists = safeIsDir(dir) ? t(v2Catalog, msgLang5(), "doctor.agent_dir_exists") : t(v2Catalog, msgLang5(), "doctor.agent_dir_missing");
26579
26921
  const tag = name === primary ? ` (${t(v2Catalog, msgLang5(), "doctor.agent_primary_label")})` : "";
26580
- emit(` ${padEnd(name, 10)} ${padEnd(installed, 14)} ${dirExists}${tag}`);
26922
+ emit2(` ${padEnd(name, 10)} ${padEnd(installed, 14)} ${dirExists}${tag}`);
26581
26923
  }
26582
26924
  }
26583
26925
  function ghAvailable3() {
@@ -26657,28 +26999,28 @@ function prEventHint(lang9) {
26657
26999
  function prSection(lang9) {
26658
27000
  if (!insideGitWorkTree())
26659
27001
  return;
26660
- emit("");
27002
+ emit2("");
26661
27003
  for (const l of bilingual("doctor.pr_review_extras"))
26662
- emit(l);
26663
- emit("");
27004
+ emit2(l);
27005
+ emit2("");
26664
27006
  const protection = branchProtectionState();
26665
27007
  if (protection === "enabled") {
26666
- emit(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_enabled")}`);
27008
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_enabled")}`);
26667
27009
  } else if (protection === "disabled") {
26668
- emit(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_disabled")}`);
27010
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_disabled")}`);
26669
27011
  for (const l of prPipelineHint())
26670
- emit(l);
27012
+ emit2(l);
26671
27013
  } else {
26672
- emit(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_unknown")}`);
27014
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_unknown")}`);
26673
27015
  for (const l of prPipelineHint())
26674
- emit(l);
27016
+ emit2(l);
26675
27017
  }
26676
27018
  if (eventWorkflowState() === "present") {
26677
- emit(` ${t(v2Catalog, lang9, "doctor.pr_event_enabled")}`);
27019
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_event_enabled")}`);
26678
27020
  } else {
26679
- emit(` ${t(v2Catalog, lang9, "doctor.pr_event_disabled")}`);
27021
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_event_disabled")}`);
26680
27022
  for (const l of prEventHint(lang9))
26681
- emit(l);
27023
+ emit2(l);
26682
27024
  }
26683
27025
  }
26684
27026
  function skillsCatalogSection(lang9) {
@@ -26686,18 +27028,18 @@ function skillsCatalogSection(lang9) {
26686
27028
  if (!safeIsDir(skillsDir2))
26687
27029
  return;
26688
27030
  const target = join47(pkgDir2(), "guide", "skills.md");
26689
- emit("");
27031
+ emit2("");
26690
27032
  for (const l of bilingual("skills.doctor_heading"))
26691
- emit(l);
27033
+ emit2(l);
26692
27034
  let drift;
26693
27035
  if (!existsSync51(target)) {
26694
27036
  drift = true;
26695
27037
  } else {
26696
27038
  const fresh = join47(mkdtempSync3(join47(tmpdir5(), "roll-doctor-")), "skills.md");
26697
27039
  writeFileSync24(fresh, generateCatalog());
26698
- drift = readFileSync48(target, "utf8") !== readFileSync48(fresh, "utf8");
27040
+ drift = readFileSync49(target, "utf8") !== readFileSync49(fresh, "utf8");
26699
27041
  }
26700
- emit(` ${t(v2Catalog, lang9, drift ? "skills.doctor_drift" : "skills.doctor_ok")}`);
27042
+ emit2(` ${t(v2Catalog, lang9, drift ? "skills.doctor_drift" : "skills.doctor_ok")}`);
26701
27043
  }
26702
27044
  function lanesSection(lang9, probe) {
26703
27045
  const lines2 = [];
@@ -26727,7 +27069,7 @@ function lanesSection(lang9, probe) {
26727
27069
  lines2.push(` \u2192 ${wd === "" ? "(no WorkingDirectory)" : wd}${stale ? lang9 === "zh" ? " [\u76EE\u5F55\u5DF2\u4E0D\u5B58\u5728\u2014\u2014\u9648\u65E7 lane]" : " [missing \u2014 STALE lane]" : ""}`);
26728
27070
  }
26729
27071
  for (const l of lines2)
26730
- emit(l);
27072
+ emit2(l);
26731
27073
  return lines2;
26732
27074
  }
26733
27075
  function launchdStaleSection(lang9) {
@@ -26751,15 +27093,15 @@ function launchdStaleSection(lang9) {
26751
27093
  if (safeIsDir(wd))
26752
27094
  continue;
26753
27095
  if (!found) {
26754
- emit("");
26755
- emit(t(v2Catalog, lang9, "doctor.stale_plists"));
26756
- emit("");
27096
+ emit2("");
27097
+ emit2(t(v2Catalog, lang9, "doctor.stale_plists"));
27098
+ emit2("");
26757
27099
  found = true;
26758
27100
  }
26759
27101
  const label4 = name.replace(/\.plist$/, "");
26760
- emit(` \u26A0 ${label4}`);
26761
- emit(` WorkingDirectory missing: ${wd}`);
26762
- emit(` ${t(v2Catalog, lang9, "doctor.stale_plists_cleanup")}: launchctl bootout gui/${process.getuid?.() ?? 0}/${label4}; rm '${plist}'`);
27102
+ emit2(` \u26A0 ${label4}`);
27103
+ emit2(` WorkingDirectory missing: ${wd}`);
27104
+ emit2(` ${t(v2Catalog, lang9, "doctor.stale_plists_cleanup")}: launchctl bootout gui/${process.getuid?.() ?? 0}/${label4}; rm '${plist}'`);
26763
27105
  }
26764
27106
  }
26765
27107
  function realLaneProbe() {
@@ -26848,23 +27190,23 @@ function launchdProxySection(lang9) {
26848
27190
  }
26849
27191
  if (stale.length === 0)
26850
27192
  return;
26851
- emit("");
26852
- emit(t(v3Catalog, "en", "doctor.proxy_env_warning"));
26853
- emit(t(v3Catalog, "zh", "doctor.proxy_env_warning"));
26854
- emit("");
27193
+ emit2("");
27194
+ emit2(t(v3Catalog, "en", "doctor.proxy_env_warning"));
27195
+ emit2(t(v3Catalog, "zh", "doctor.proxy_env_warning"));
27196
+ emit2("");
26855
27197
  for (const { name, target } of stale) {
26856
- emit(` \u26A0 ${name}=${target}`);
27198
+ emit2(` \u26A0 ${name}=${target}`);
26857
27199
  }
26858
- emit("");
26859
- emit(` ${t(v3Catalog, lang9, "doctor.proxy_env_hint")}`);
27200
+ emit2("");
27201
+ emit2(` ${t(v3Catalog, lang9, "doctor.proxy_env_hint")}`);
26860
27202
  for (const { name } of stale) {
26861
- emit(` launchctl unsetenv ${name}`);
27203
+ emit2(` launchctl unsetenv ${name}`);
26862
27204
  }
26863
27205
  }
26864
27206
  function readWorkingDirectory(plist) {
26865
27207
  let body;
26866
27208
  try {
26867
- body = readFileSync48(plist, "utf8");
27209
+ body = readFileSync49(plist, "utf8");
26868
27210
  } catch {
26869
27211
  return "";
26870
27212
  }
@@ -26900,11 +27242,11 @@ import { join as join51 } from "node:path";
26900
27242
  import { spawn as spawn4 } from "node:child_process";
26901
27243
  import { join as join48 } from "node:path";
26902
27244
  var liveAgents = /* @__PURE__ */ new Set();
26903
- function killLiveAgents(signal2 = "SIGKILL") {
27245
+ function killLiveAgents(signal = "SIGKILL") {
26904
27246
  let n = 0;
26905
27247
  for (const c2 of liveAgents) {
26906
27248
  try {
26907
- if (killHard(c2, signal2))
27249
+ if (killHard(c2, signal))
26908
27250
  n += 1;
26909
27251
  } catch {
26910
27252
  }
@@ -26912,15 +27254,15 @@ function killLiveAgents(signal2 = "SIGKILL") {
26912
27254
  liveAgents.clear();
26913
27255
  return n;
26914
27256
  }
26915
- function killHard(c2, signal2) {
27257
+ function killHard(c2, signal) {
26916
27258
  if (c2.pid !== void 0) {
26917
27259
  try {
26918
- process.kill(-c2.pid, signal2);
27260
+ process.kill(-c2.pid, signal);
26919
27261
  return true;
26920
27262
  } catch {
26921
27263
  }
26922
27264
  }
26923
- return c2.kill(signal2);
27265
+ return c2.kill(signal);
26924
27266
  }
26925
27267
  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: ";
26926
27268
  var AGENT_ARGV_TODO = {
@@ -26977,7 +27319,7 @@ function buildSpawnCommand(agent, opts) {
26977
27319
  return { bin: opts.bin ?? "qwen", args: [prompt] };
26978
27320
  }
26979
27321
  if (agent === "agy" || agent === "gemini" || agent === "antigravity") {
26980
- return { bin: opts.bin ?? "agy", args: ["-p", "--dangerously-skip-permissions", prompt] };
27322
+ return { bin: opts.bin ?? "agy", args: ["--dangerously-skip-permissions", "-p", prompt] };
26981
27323
  }
26982
27324
  const hint = AGENT_ARGV_TODO[agent] ?? "unknown agent";
26983
27325
  throw new Error(`runner: agent '${agent}' argv not yet ported. v2 shape: ${hint}`);
@@ -27043,8 +27385,8 @@ function spawnAndWait(bin, args, opts, pty = false) {
27043
27385
  settle({ stdout, stderr: `${stderr}${String(e)}
27044
27386
  `, exitCode: 127, timedOut });
27045
27387
  });
27046
- child.on("exit", (code, signal2) => {
27047
- setImmediate(() => settle({ stdout, stderr, exitCode: code ?? (signal2 !== null ? 137 : 1), timedOut }));
27388
+ child.on("exit", (code, signal) => {
27389
+ setImmediate(() => settle({ stdout, stderr, exitCode: code ?? (signal !== null ? 137 : 1), timedOut }));
27048
27390
  });
27049
27391
  child.on("close", (code) => {
27050
27392
  settle({ stdout, stderr, exitCode: code ?? 1, timedOut });
@@ -27057,7 +27399,7 @@ var realAgentSpawn = (agent, opts) => {
27057
27399
  };
27058
27400
 
27059
27401
  // packages/cli/dist/runner/skill-body.js
27060
- import { existsSync as existsSync52, readFileSync as readFileSync49 } from "node:fs";
27402
+ import { existsSync as existsSync52, readFileSync as readFileSync50 } from "node:fs";
27061
27403
  import { homedir as homedir14 } from "node:os";
27062
27404
  import { join as join49 } from "node:path";
27063
27405
  function readSkillBody(projectPath3, opts) {
@@ -27072,7 +27414,7 @@ function readSkillBody(projectPath3, opts) {
27072
27414
  continue;
27073
27415
  let raw = "";
27074
27416
  try {
27075
- raw = readFileSync49(p, "utf8");
27417
+ raw = readFileSync50(p, "utf8");
27076
27418
  } catch {
27077
27419
  continue;
27078
27420
  }
@@ -27474,7 +27816,7 @@ ${rowNote}`);
27474
27816
  init_dist2();
27475
27817
  init_dist();
27476
27818
  import { spawnSync as spawnSync5 } from "node:child_process";
27477
- import { copyFileSync as copyFileSync2, existsSync as existsSync56, mkdirSync as mkdirSync27, readSync, readFileSync as readFileSync50, readdirSync as readdirSync22, realpathSync as realpathSync6, statSync as statSync19, writeFileSync as writeFileSync27 } from "node:fs";
27819
+ import { copyFileSync as copyFileSync2, existsSync as existsSync56, mkdirSync as mkdirSync27, readSync, readFileSync as readFileSync51, readdirSync as readdirSync22, realpathSync as realpathSync6, statSync as statSync19, writeFileSync as writeFileSync27 } from "node:fs";
27478
27820
  import { homedir as homedir15 } from "node:os";
27479
27821
  import { dirname as dirname20, join as join54 } from "node:path";
27480
27822
  function registerProject(projectDir) {
@@ -27550,7 +27892,7 @@ function scanProjectType(dir) {
27550
27892
  const pkg = join54(dir, "package.json");
27551
27893
  const readPkg = () => {
27552
27894
  try {
27553
- return readFileSync50(pkg, "utf8");
27895
+ return readFileSync51(pkg, "utf8");
27554
27896
  } catch {
27555
27897
  return "";
27556
27898
  }
@@ -27695,7 +28037,7 @@ function discoverOnboardAgents() {
27695
28037
  if (!existsSync56(cfg))
27696
28038
  return { installed, missing };
27697
28039
  const env = realAgentEnv();
27698
- for (const line of readFileSync50(cfg, "utf8").split("\n")) {
28040
+ for (const line of readFileSync51(cfg, "utf8").split("\n")) {
27699
28041
  const match = /^(ai_[^:]+):\s*(.*)$/.exec(line);
27700
28042
  if (match === null)
27701
28043
  continue;
@@ -27766,7 +28108,7 @@ function readOnboardPrompt() {
27766
28108
  err8(`Skill file missing: ${skillFile}`);
27767
28109
  return null;
27768
28110
  }
27769
- const body = readFileSync50(skillFile, "utf8").replace(/^---\n[\s\S]*?\n---\n?/, "").trim();
28111
+ const body = readFileSync51(skillFile, "utf8").replace(/^---\n[\s\S]*?\n---\n?/, "").trim();
27770
28112
  return `Run the $roll-onboard skill below for this project. Follow it end-to-end and write .roll/onboard-plan.yaml when done.
27771
28113
 
27772
28114
  ${body}`;
@@ -27901,7 +28243,7 @@ function mergeGlobalToProject(projectDir, summary) {
27901
28243
  const projectType = scanProjectType(projectDir);
27902
28244
  const skipFrontend = ["cli", "backend-service", "unknown"].includes(projectType);
27903
28245
  const FRONTEND_HEAD = "## 7. Frontend Default Stack";
27904
- const srcText = readFileSync50(src, "utf8");
28246
+ const srcText = readFileSync51(src, "utf8");
27905
28247
  const srcLines = srcText.split("\n");
27906
28248
  const lines2 = srcText.endsWith("\n") ? srcLines.slice(0, -1) : srcLines;
27907
28249
  if (!existsSync56(dst)) {
@@ -27937,7 +28279,7 @@ ${fcB}`;
27937
28279
  summary.push("created|AGENTS.md");
27938
28280
  return;
27939
28281
  }
27940
- const dstText = readFileSync50(dst, "utf8");
28282
+ const dstText = readFileSync51(dst, "utf8");
27941
28283
  let added = 0;
27942
28284
  let curH = "";
27943
28285
  let curB = "";
@@ -27986,9 +28328,9 @@ function mergeClaudeToProject(projectDir, summary) {
27986
28328
  summary.push("created|.claude/CLAUDE.md");
27987
28329
  return;
27988
28330
  }
27989
- const tplText = readFileSync50(tplFile, "utf8");
28331
+ const tplText = readFileSync51(tplFile, "utf8");
27990
28332
  const lines2 = tplText.endsWith("\n") ? tplText.split("\n").slice(0, -1) : tplText.split("\n");
27991
- const outText = readFileSync50(outFile, "utf8");
28333
+ const outText = readFileSync51(outFile, "utf8");
27992
28334
  let added = 0;
27993
28335
  let curH = "";
27994
28336
  let curB = "";
@@ -28222,7 +28564,7 @@ function printMergeSummary(summary) {
28222
28564
  function seedBacklogRow(backlog, heading, row2, id) {
28223
28565
  if (!existsSync56(backlog))
28224
28566
  return false;
28225
- const text = readFileSync50(backlog, "utf8");
28567
+ const text = readFileSync51(backlog, "utf8");
28226
28568
  if (text.includes(`| ${id} |`))
28227
28569
  return false;
28228
28570
  const lines2 = text.split("\n");
@@ -28330,7 +28672,7 @@ function renderAndSeed(projectDir, plan, changeset) {
28330
28672
  }
28331
28673
  function addRollToGitignore(projectDir, changeset) {
28332
28674
  const gi = join54(projectDir, ".gitignore");
28333
- const current = existsSync56(gi) ? readFileSync50(gi, "utf8") : "";
28675
+ const current = existsSync56(gi) ? readFileSync51(gi, "utf8") : "";
28334
28676
  if (current.split("\n").includes(".roll/"))
28335
28677
  return;
28336
28678
  writeFileSync27(gi, current + (current === "" || current.endsWith("\n") ? "" : "\n") + ".roll/\n");
@@ -28586,28 +28928,52 @@ function initCommand(args) {
28586
28928
  init_dist2();
28587
28929
  import { createInterface as createInterface2 } from "node:readline";
28588
28930
  var KIND_COLOR = {
28931
+ lifecycle: "dim",
28932
+ edit: "muted",
28933
+ test: "green",
28934
+ tool: "muted",
28935
+ say: "dim",
28589
28936
  tcr: "green",
28590
- skill: "blue",
28591
- ci: "green",
28592
- peer: "purple",
28593
- attest: "blue",
28937
+ commit: "green",
28594
28938
  pr: "green",
28939
+ gate: "purple",
28940
+ heartbeat: "amber",
28595
28941
  alert: "red"
28596
28942
  };
28597
- function renderFmtLine(line, opts = {}) {
28943
+ var KIND_CAT = {
28944
+ lifecycle: "\xB7",
28945
+ edit: "\u270F",
28946
+ test: "test",
28947
+ tool: "\u203A",
28948
+ say: "\xB7",
28949
+ tcr: "tcr",
28950
+ commit: "commit",
28951
+ pr: "pr",
28952
+ gate: "gate",
28953
+ heartbeat: "\u2665",
28954
+ alert: "error"
28955
+ };
28956
+ function tierVisible(tier, verbose) {
28957
+ if (verbose)
28958
+ return true;
28959
+ return tier === "A" || tier === "B";
28960
+ }
28961
+ function renderSignal(sig, opts = {}) {
28598
28962
  const ts = opts.ts !== void 0 && opts.ts !== "" ? c("muted", opts.ts) + " " : "";
28599
- if (line.tier === "banner") {
28600
- const detail2 = line.detail !== void 0 && line.detail !== "" ? c("muted", ` \u2014 ${line.detail}`) : "";
28601
- return `${ts}${c("dim", line.label)}${detail2}`;
28602
- }
28603
- if (line.tier === "muted") {
28604
- return `${ts}${c("muted", line.label)}`;
28605
- }
28606
- const color = line.ok === false ? "red" : line.kind !== void 0 ? KIND_COLOR[line.kind] : "fg";
28607
- const arrow = c("faint", "\u2192");
28608
- const cat = c("blue", line.category.padEnd(6));
28609
- const label4 = c(color, line.label.padEnd(14), { bold: true });
28610
- const detail = line.detail !== void 0 && line.detail !== "" ? " " + c("muted", line.detail) : "";
28963
+ if (sig.kind === "lifecycle") {
28964
+ const detail2 = sig.detail !== void 0 && sig.detail !== "" ? c("muted", ` \u2014 ${sig.detail}`) : "";
28965
+ return `${ts}${c("dim", sig.summary)}${detail2}`;
28966
+ }
28967
+ if (sig.kind === "edit" || sig.kind === "tool" || sig.kind === "say") {
28968
+ const cat2 = KIND_CAT[sig.kind];
28969
+ return `${ts}${c("muted", `${cat2} ${sig.summary}`)}`;
28970
+ }
28971
+ const isFail = sig.result === "fail" || sig.kind === "alert";
28972
+ const color = isFail ? "red" : KIND_COLOR[sig.kind];
28973
+ const arrow = sig.kind === "heartbeat" ? c("amber", "\u2665") : c("faint", "\u2192");
28974
+ const cat = c("blue", KIND_CAT[sig.kind].padEnd(6));
28975
+ const label4 = c(color, sig.summary.padEnd(14), { bold: true });
28976
+ const detail = sig.detail !== void 0 && sig.detail !== "" ? " " + c("muted", sig.detail) : "";
28611
28977
  return `${ts}${arrow} ${cat} ${label4}${detail}`;
28612
28978
  }
28613
28979
  function hms() {
@@ -28615,14 +28981,24 @@ function hms() {
28615
28981
  const p = (n) => String(n).padStart(2, "0");
28616
28982
  return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
28617
28983
  }
28618
- var HELP3 = `Usage: roll loop fmt
28984
+ var HELP3 = `Usage: roll loop fmt [--verbose] [--no-color]
28619
28985
 
28620
- Reads the agent's raw stream-json on stdin and writes a three-tier formatted
28621
- transcript to stdout (the loop observation window). Suppresses noise, mutes
28622
- edits, and surfaces signals (tcr / story / ci / peer / pr / error).
28623
- \u8BFB\u53D6 agent \u7684 stream-json\uFF0C\u8F93\u51FA\u4E09\u5C42\u5173\u952E\u8282\u70B9\u8F6C\u5F55\uFF08\u89C2\u6D4B\u7A97\uFF09\u3002
28986
+ Reads the running agent's raw stream on stdin and writes a formatted, tiered
28987
+ transcript to stdout (the loop observation window). It selects a per-agent
28988
+ normalizer (claude stream-json \xB7 codex text/jsonl \xB7 generic passthrough),
28989
+ folds each line into a standard activity signal, and renders by tier:
28990
+ surfaces turning points (story / tcr / test / ci / pr / gate / error), folds
28991
+ edits + tools, and beats a heartbeat when the agent goes quiet so the window
28992
+ never looks frozen.
28993
+ \u8BFB\u53D6\u8FD0\u884C\u4E2D agent \u7684\u539F\u59CB\u6D41\uFF0C\u5F52\u4E00\u4E3A\u6807\u51C6\u6D3B\u52A8\u4FE1\u53F7\u540E\u5206\u5C42\u8F93\u51FA\uFF08\u89C2\u6D4B\u7A97\uFF09\u3002
28994
+ \u4EFB\u4F55 agent\uFF08claude/codex/kimi/pi\u2026\uFF09\u90FD\u663E\u793A\u771F\u5B9E\u6D3B\u52A8\uFF0C\u9759\u9ED8\u65F6\u5FC3\u8DF3\u4FDD\u6D3B\u3002
28624
28995
 
28625
- Intended to sit in the watch pipe: tail -F live.log | roll loop fmt
28996
+ --verbose, --raw also show tier-C lines (assistant prose / passthrough say)
28997
+ --no-color plain text (also auto-off when stdout is not a TTY)
28998
+
28999
+ The agent is read from $ROLL_LOOP_AGENT (default: claude \u2192 claude normalizer;
29000
+ unknown \u2192 generic). Intended to sit in the watch pipe:
29001
+ tail -F live.log | roll loop fmt
28626
29002
  `;
28627
29003
  async function loopFmtCommand(args) {
28628
29004
  if (args.includes("--help") || args.includes("-h")) {
@@ -28631,23 +29007,28 @@ async function loopFmtCommand(args) {
28631
29007
  }
28632
29008
  const noColor2 = args.includes("--no-color") || !process.stdout.isTTY || (process.env["NO_COLOR"] ?? "") !== "";
28633
29009
  renderState.useColor = !noColor2;
28634
- const agent = process.env["ROLL_LOOP_AGENT"] ?? "claude";
28635
- if (agent === "claude") {
28636
- const st = newFmtState();
28637
- const rl2 = createInterface2({ input: process.stdin, crlfDelay: Infinity });
28638
- for await (const raw of rl2) {
28639
- for (const fl of formatLine(raw, st)) {
28640
- process.stdout.write(renderFmtLine(fl, { ts: hms() }) + "\n");
29010
+ const verbose = args.includes("--verbose") || args.includes("--raw");
29011
+ const agent = (process.env["ROLL_LOOP_AGENT"] ?? "claude").trim() || "claude";
29012
+ const norm = normalizerFor(agent);
29013
+ const st = newNormalizerState();
29014
+ const rl = createInterface2({ input: process.stdin, crlfDelay: Infinity });
29015
+ const gapMs = DEFAULT_HEARTBEAT_GAP_MS;
29016
+ const beat = setInterval(() => {
29017
+ for (const sig of maybeHeartbeat(st, Date.now(), gapMs)) {
29018
+ if (tierVisible(sig.tier, verbose))
29019
+ process.stdout.write(renderSignal(sig, { ts: hms() }) + "\n");
29020
+ }
29021
+ }, Math.min(gapMs, 15e3));
29022
+ beat.unref?.();
29023
+ try {
29024
+ for await (const raw of rl) {
29025
+ for (const sig of norm.normalize(raw, st, Date.now())) {
29026
+ if (tierVisible(sig.tier, verbose))
29027
+ process.stdout.write(renderSignal(sig, { ts: hms() }) + "\n");
28641
29028
  }
28642
29029
  }
28643
- return 0;
28644
- }
28645
- const rl = createInterface2({ input: process.stdin, crlfDelay: Infinity });
28646
- for await (const raw of rl) {
28647
- if (raw.trim() === "")
28648
- continue;
28649
- process.stdout.write(`${c("muted", hms())} ${c("dim", raw)}
28650
- `);
29030
+ } finally {
29031
+ clearInterval(beat);
28651
29032
  }
28652
29033
  return 0;
28653
29034
  }
@@ -28656,7 +29037,7 @@ async function loopFmtCommand(args) {
28656
29037
  init_dist3();
28657
29038
  init_dist();
28658
29039
  import { spawnSync as spawnSync6 } from "node:child_process";
28659
- import { existsSync as existsSync57, mkdirSync as mkdirSync28, readFileSync as readFileSync51, readdirSync as readdirSync23, renameSync as renameSync8, rmSync as rmSync10, statSync as statSync20, writeFileSync as writeFileSync28 } from "node:fs";
29040
+ import { existsSync as existsSync57, mkdirSync as mkdirSync28, readFileSync as readFileSync52, readdirSync as readdirSync23, renameSync as renameSync8, rmSync as rmSync10, statSync as statSync20, writeFileSync as writeFileSync28 } from "node:fs";
28660
29041
  import { homedir as homedir16 } from "node:os";
28661
29042
  import { basename as basename11, dirname as dirname21, join as join55 } from "node:path";
28662
29043
  function palette3() {
@@ -28742,7 +29123,7 @@ function fileMtimeSec(f) {
28742
29123
  }
28743
29124
  function plistWorkingDir(plistPath) {
28744
29125
  try {
28745
- const m7 = /<key>WorkingDirectory<\/key>\s*<string>([^<]*)<\/string>/.exec(readFileSync51(plistPath, "utf8"));
29126
+ const m7 = /<key>WorkingDirectory<\/key>\s*<string>([^<]*)<\/string>/.exec(readFileSync52(plistPath, "utf8"));
28746
29127
  return m7?.[1] ?? "";
28747
29128
  } catch {
28748
29129
  return "";
@@ -28962,14 +29343,14 @@ function loopTestCommand(args = [], deps = realTestDeps()) {
28962
29343
  // packages/cli/dist/commands/loop-pr-inbox.js
28963
29344
  init_dist2();
28964
29345
  init_dist3();
28965
- import { appendFileSync as appendFileSync11, mkdirSync as mkdirSync30, readFileSync as readFileSync53, writeFileSync as writeFileSync30, existsSync as existsSync59, rmSync as rmSync12 } from "node:fs";
29346
+ import { appendFileSync as appendFileSync11, mkdirSync as mkdirSync30, readFileSync as readFileSync54, writeFileSync as writeFileSync30, existsSync as existsSync59, rmSync as rmSync12 } from "node:fs";
28966
29347
  import { dirname as dirname23, join as join57 } from "node:path";
28967
29348
 
28968
29349
  // packages/cli/dist/commands/loop-pr-heal.js
28969
29350
  init_dist2();
28970
29351
  init_dist3();
28971
29352
  import { execFileSync as execFileSync12, spawn as spawn5 } from "node:child_process";
28972
- import { appendFileSync as appendFileSync10, existsSync as existsSync58, mkdirSync as mkdirSync29, mkdtempSync as mkdtempSync4, readFileSync as readFileSync52, rmSync as rmSync11, writeFileSync as writeFileSync29 } from "node:fs";
29353
+ import { appendFileSync as appendFileSync10, existsSync as existsSync58, mkdirSync as mkdirSync29, mkdtempSync as mkdtempSync4, readFileSync as readFileSync53, rmSync as rmSync11, writeFileSync as writeFileSync29 } from "node:fs";
28973
29354
  import { tmpdir as tmpdir6 } from "node:os";
28974
29355
  import { dirname as dirname22, join as join56 } from "node:path";
28975
29356
  function runtimeDir3() {
@@ -28995,7 +29376,7 @@ function nowIso() {
28995
29376
  function realHealDeps() {
28996
29377
  const readState = () => {
28997
29378
  try {
28998
- return readFileSync52(statePath(), "utf8");
29379
+ return readFileSync53(statePath(), "utf8");
28999
29380
  } catch {
29000
29381
  return "";
29001
29382
  }
@@ -29013,7 +29394,7 @@ function realHealDeps() {
29013
29394
  return { lockPresent: false, lockPidAlive: void 0 };
29014
29395
  let alive;
29015
29396
  try {
29016
- const pid = parseInt(readFileSync52(p, "utf8").trim(), 10);
29397
+ const pid = parseInt(readFileSync53(p, "utf8").trim(), 10);
29017
29398
  alive = Number.isFinite(pid) ? (() => {
29018
29399
  try {
29019
29400
  process.kill(pid, 0);
@@ -29034,7 +29415,7 @@ function realHealDeps() {
29034
29415
  },
29035
29416
  alertHasKey: (key) => {
29036
29417
  try {
29037
- return readFileSync52(alertPath2(), "utf8").includes(key);
29418
+ return readFileSync53(alertPath2(), "utf8").includes(key);
29038
29419
  } catch {
29039
29420
  return false;
29040
29421
  }
@@ -29252,10 +29633,10 @@ function reducePrView(raw) {
29252
29633
  }
29253
29634
  async function runPrInbox(deps) {
29254
29635
  if (!await deps.ghAvailable())
29255
- return emit2(deps, prIdleTick("gh_unavailable"));
29636
+ return emit3(deps, prIdleTick("gh_unavailable"));
29256
29637
  const slug = await deps.resolveSlug();
29257
29638
  if (slug === void 0 || slug === "")
29258
- return emit2(deps, prIdleTick("gh_unavailable"));
29639
+ return emit3(deps, prIdleTick("gh_unavailable"));
29259
29640
  const list2 = await deps.listOpenPrs(slug);
29260
29641
  const stdout = (list2.stdout ?? "").trim();
29261
29642
  let openCount = 0;
@@ -29275,7 +29656,7 @@ async function runPrInbox(deps) {
29275
29656
  ...list2.stderr !== void 0 ? { listStderr: list2.stderr } : {}
29276
29657
  });
29277
29658
  if (gate !== void 0)
29278
- return emit2(deps, gate);
29659
+ return emit3(deps, gate);
29279
29660
  const prs = JSON.parse(stdout);
29280
29661
  for (const pr of prs) {
29281
29662
  const num4 = String(pr.number ?? "");
@@ -29311,9 +29692,9 @@ async function runPrInbox(deps) {
29311
29692
  break;
29312
29693
  }
29313
29694
  }
29314
- return emit2(deps, prActedTick());
29695
+ return emit3(deps, prActedTick());
29315
29696
  }
29316
- function emit2(deps, tick) {
29697
+ function emit3(deps, tick) {
29317
29698
  deps.writeTick(tick);
29318
29699
  return tick;
29319
29700
  }
@@ -29357,7 +29738,7 @@ function writeTickFile(tick) {
29357
29738
  appendFileSync11(file, `${JSON.stringify({ ts, ...tick })}
29358
29739
  `);
29359
29740
  try {
29360
- const lines2 = readFileSync53(file, "utf8").split("\n").filter((l) => l !== "");
29741
+ const lines2 = readFileSync54(file, "utf8").split("\n").filter((l) => l !== "");
29361
29742
  if (lines2.length > 500)
29362
29743
  writeFileSync30(file, `${lines2.slice(-500).join("\n")}
29363
29744
  `);
@@ -29374,7 +29755,7 @@ function deadTickMarkerPath() {
29374
29755
  function checkDeadTickStreak(file, alert, markerPath = deadTickMarkerPath()) {
29375
29756
  let rows = [];
29376
29757
  try {
29377
- rows = readFileSync53(file, "utf8").split("\n").filter((l) => l.trim() !== "").map((l) => JSON.parse(l));
29758
+ rows = readFileSync54(file, "utf8").split("\n").filter((l) => l.trim() !== "").map((l) => JSON.parse(l));
29378
29759
  } catch {
29379
29760
  return;
29380
29761
  }
@@ -29410,7 +29791,7 @@ function rebaseCircuitAllowed(num4) {
29410
29791
  const state = statePath2();
29411
29792
  let body = "";
29412
29793
  try {
29413
- body = readFileSync53(state, "utf8");
29794
+ body = readFileSync54(state, "utf8");
29414
29795
  } catch {
29415
29796
  }
29416
29797
  const verdict = rebaseCircuitVerdict(parseRebaseAttempts(body, num4), nowSec());
@@ -29476,7 +29857,7 @@ function writeRebaseAttempts(state, pr, timestamps) {
29476
29857
  mkdirSync30(dirname23(state), { recursive: true });
29477
29858
  let body = "";
29478
29859
  try {
29479
- body = readFileSync53(state, "utf8");
29860
+ body = readFileSync54(state, "utf8");
29480
29861
  } catch {
29481
29862
  }
29482
29863
  writeFileSync30(state, upsertRebaseAttempts(body, pr, renderRebaseAttempts(timestamps)));
@@ -29558,7 +29939,7 @@ async function loopPrInboxCommand(_args, deps = realDeps6()) {
29558
29939
  init_dist2();
29559
29940
  init_dist();
29560
29941
  init_dist3();
29561
- import { appendFileSync as appendFileSync16, existsSync as existsSync67, mkdirSync as mkdirSync35, readFileSync as readFileSync62, writeFileSync as writeFileSync36 } from "node:fs";
29942
+ import { appendFileSync as appendFileSync16, existsSync as existsSync67, mkdirSync as mkdirSync35, readFileSync as readFileSync63, writeFileSync as writeFileSync36 } from "node:fs";
29562
29943
  import { dirname as dirname29, join as join66 } from "node:path";
29563
29944
 
29564
29945
  // packages/cli/dist/runner/executor.js
@@ -29566,13 +29947,13 @@ init_dist2();
29566
29947
  init_dist();
29567
29948
  init_dist3();
29568
29949
  import { execFile as execFile9 } from "node:child_process";
29569
- import { appendFileSync as appendFileSync13, existsSync as existsSync64, lstatSync as lstatSync3, mkdirSync as mkdirSync32, readdirSync as readdirSync27, readFileSync as readFileSync58, realpathSync as realpathSync7, rmSync as rmSync13, symlinkSync as symlinkSync3, unlinkSync, writeFileSync as writeFileSync32 } from "node:fs";
29950
+ import { appendFileSync as appendFileSync13, existsSync as existsSync64, lstatSync as lstatSync3, mkdirSync as mkdirSync32, readdirSync as readdirSync27, readFileSync as readFileSync59, realpathSync as realpathSync7, rmSync as rmSync13, symlinkSync as symlinkSync3, unlinkSync, writeFileSync as writeFileSync32 } from "node:fs";
29570
29951
  import { dirname as dirname26, join as join63 } from "node:path";
29571
29952
  import { promisify as promisify9 } from "node:util";
29572
29953
 
29573
29954
  // packages/cli/dist/runner/attest-gate.js
29574
29955
  init_dist2();
29575
- import { existsSync as existsSync60, readFileSync as readFileSync54, readdirSync as readdirSync24, statSync as statSync21 } from "node:fs";
29956
+ import { existsSync as existsSync60, readFileSync as readFileSync55, readdirSync as readdirSync24, statSync as statSync21 } from "node:fs";
29576
29957
  import { dirname as dirname24, join as join58 } from "node:path";
29577
29958
  function reportCandidates(worktreeCwd, storyId) {
29578
29959
  return [join58(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId))];
@@ -29603,7 +29984,7 @@ function storyHasAcBlock(worktreeCwd, storyId) {
29603
29984
  if (spec === null)
29604
29985
  return null;
29605
29986
  try {
29606
- return acForStory(readFileSync54(spec, "utf8"), storyId, { fileOwned: true }).length > 0;
29987
+ return acForStory(readFileSync55(spec, "utf8"), storyId, { fileOwned: true }).length > 0;
29607
29988
  } catch {
29608
29989
  return null;
29609
29990
  }
@@ -29613,7 +29994,7 @@ function storyRequiresScreenshot(worktreeCwd, storyId) {
29613
29994
  if (spec === null)
29614
29995
  return false;
29615
29996
  try {
29616
- return /\b(CLI|web|UI|TUI)\b|界面|交互|截屏|截图|screenshot/i.test(readFileSync54(spec, "utf8"));
29997
+ return /\b(CLI|web|UI|TUI)\b|界面|交互|截屏|截图|screenshot/i.test(readFileSync55(spec, "utf8"));
29617
29998
  } catch {
29618
29999
  return false;
29619
30000
  }
@@ -29623,7 +30004,7 @@ function readAcMapEntries(worktreeCwd, storyId) {
29623
30004
  if (path === void 0 || !existsSync60(path))
29624
30005
  return null;
29625
30006
  try {
29626
- const parsed = JSON.parse(readFileSync54(path, "utf8"));
30007
+ const parsed = JSON.parse(readFileSync55(path, "utf8"));
29627
30008
  return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "object" && x !== null) : null;
29628
30009
  } catch {
29629
30010
  return null;
@@ -29637,7 +30018,7 @@ function evidenceManifest(worktreeCwd, storyId) {
29637
30018
  if (!existsSync60(path))
29638
30019
  return null;
29639
30020
  try {
29640
- const parsed = JSON.parse(readFileSync54(path, "utf8"));
30021
+ const parsed = JSON.parse(readFileSync55(path, "utf8"));
29641
30022
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
29642
30023
  } catch {
29643
30024
  return null;
@@ -29669,6 +30050,12 @@ function passAcVisualFloor(worktreeCwd, storyId) {
29669
30050
  const ids = missing.map((e) => e.ac ?? "?").join(", ");
29670
30051
  return { ok: false, reason: `pass AC(s) lack screenshot evidence or machine capture skip: ${ids}` };
29671
30052
  }
30053
+ function redAcFailures(worktreeCwd, storyId) {
30054
+ const entries = readAcMapEntries(worktreeCwd, storyId);
30055
+ if (entries === null)
30056
+ return [];
30057
+ return entries.filter((e) => e.status === "fail").map((e) => e.ac ?? "?");
30058
+ }
29672
30059
  function verificationReportPath(worktreeCwd, storyId) {
29673
30060
  return reportCandidates(worktreeCwd, storyId)[0];
29674
30061
  }
@@ -29704,7 +30091,7 @@ function verificationReportHasContent(worktreeCwd, storyId) {
29704
30091
  if (p === null)
29705
30092
  return false;
29706
30093
  try {
29707
- const html = readFileSync54(p, "utf8");
30094
+ const html = readFileSync55(p, "utf8");
29708
30095
  const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) => existsSync60(m7));
29709
30096
  if (!hasMap)
29710
30097
  return false;
@@ -29738,7 +30125,7 @@ function readAttestGateMode(repoCwd) {
29738
30125
  const p = join58(repoCwd, ".roll", "policy.yaml");
29739
30126
  if (!existsSync60(p))
29740
30127
  return "hard";
29741
- return parsePolicy(readFileSync54(p, "utf8")).loopSafety.attestGate === "hard" ? "hard" : "soft";
30128
+ return parsePolicy(readFileSync55(p, "utf8")).loopSafety.attestGate === "hard" ? "hard" : "soft";
29742
30129
  } catch {
29743
30130
  return "hard";
29744
30131
  }
@@ -29750,6 +30137,16 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks) {
29750
30137
  sinks.event({ cycleId, verdict: "produced", reasons: reasons2 });
29751
30138
  return { verdict: "produced", mode, reasons: reasons2, blocked: false };
29752
30139
  }
30140
+ const redAcs = redAcFailures(worktreeCwd, storyId);
30141
+ if (redAcs.length > 0) {
30142
+ const reasons2 = [
30143
+ `acceptance check failed for ${storyId}: ${redAcs.join(", ")} went red \u2014 a failing check is a regression, not an environment issue, so it cannot be waived`
30144
+ ];
30145
+ const blocked2 = mode === "hard";
30146
+ sinks.alert(`attest gate (${mode}): acceptance check failed (${storyId}) \u2014 ${redAcs.join(", ")} went red; a red check is a regression and is never waived as environmental \u2014 cycle ${cycleId}` + (blocked2 ? " \u2014 BLOCKED (hard mode); story not marked Done" : ""));
30147
+ sinks.event({ cycleId, verdict: "skipped", reasons: reasons2 });
30148
+ return { verdict: "skipped", mode, reasons: reasons2, blocked: blocked2 };
30149
+ }
29753
30150
  const fresh = verificationReportFresh(worktreeCwd, storyId, sinceSec);
29754
30151
  if (fresh && verificationReportHasContent(worktreeCwd, storyId)) {
29755
30152
  const score = evaluateSelfScoreGate(worktreeCwd, storyId);
@@ -29780,7 +30177,7 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks) {
29780
30177
 
29781
30178
  // packages/cli/dist/runner/usage-recovery.js
29782
30179
  init_dist2();
29783
- import { readFileSync as readFileSync55, readdirSync as readdirSync25, statSync as statSync22 } from "node:fs";
30180
+ import { readFileSync as readFileSync56, readdirSync as readdirSync25, statSync as statSync22 } from "node:fs";
29784
30181
  import { homedir as homedir17 } from "node:os";
29785
30182
  import { join as join59 } from "node:path";
29786
30183
  function defaultPiSessionsRoot() {
@@ -29803,7 +30200,7 @@ function recoverPiUsage(worktreeCwd, sinceSec, sessionsRoot = defaultPiSessionsR
29803
30200
  try {
29804
30201
  if (sinceSec !== void 0 && statSync22(p).mtimeMs / 1e3 < sinceSec)
29805
30202
  continue;
29806
- summaries.push(sumPiSession(readFileSync55(p, "utf8").split("\n")));
30203
+ summaries.push(sumPiSession(readFileSync56(p, "utf8").split("\n")));
29807
30204
  } catch {
29808
30205
  }
29809
30206
  }
@@ -29824,14 +30221,14 @@ function recoverPiUsage(worktreeCwd, sinceSec, sessionsRoot = defaultPiSessionsR
29824
30221
 
29825
30222
  // packages/cli/dist/runner/budget-check.js
29826
30223
  init_dist2();
29827
- import { existsSync as existsSync61, readFileSync as readFileSync56 } from "node:fs";
30224
+ import { existsSync as existsSync61, readFileSync as readFileSync57 } from "node:fs";
29828
30225
  import { join as join60 } from "node:path";
29829
30226
  function readBudgetPolicy(repoCwd) {
29830
30227
  try {
29831
30228
  const p = join60(repoCwd, ".roll", "policy.yaml");
29832
30229
  if (!existsSync61(p))
29833
30230
  return void 0;
29834
- return parsePolicy(readFileSync56(p, "utf8")).loopSafety.budget;
30231
+ return parsePolicy(readFileSync57(p, "utf8")).loopSafety.budget;
29835
30232
  } catch {
29836
30233
  return void 0;
29837
30234
  }
@@ -29840,7 +30237,7 @@ function ledgerFromRuns(runsPath3) {
29840
30237
  const ledger = new BudgetLedger();
29841
30238
  let body;
29842
30239
  try {
29843
- body = readFileSync56(runsPath3, "utf8");
30240
+ body = readFileSync57(runsPath3, "utf8");
29844
30241
  } catch {
29845
30242
  return ledger;
29846
30243
  }
@@ -29926,21 +30323,21 @@ function buildAcMapRemediationPrompt(worktreeCwd, storyId, runDir) {
29926
30323
  // packages/cli/dist/runner/correction-actuator.js
29927
30324
  init_dist2();
29928
30325
  init_dist();
29929
- import { appendFileSync as appendFileSync12, existsSync as existsSync63, mkdirSync as mkdirSync31, readFileSync as readFileSync57, readdirSync as readdirSync26, writeFileSync as writeFileSync31 } from "node:fs";
30326
+ import { appendFileSync as appendFileSync12, existsSync as existsSync63, mkdirSync as mkdirSync31, readFileSync as readFileSync58, readdirSync as readdirSync26, writeFileSync as writeFileSync31 } from "node:fs";
29930
30327
  import { dirname as dirname25, join as join62 } from "node:path";
29931
30328
  function readMode(projectPath3) {
29932
30329
  try {
29933
30330
  const path = join62(projectPath3, ".roll", "policy.yaml");
29934
30331
  if (!existsSync63(path))
29935
30332
  return "conservative";
29936
- return parsePolicy(readFileSync57(path, "utf8")).loopSafety.correctionActuator;
30333
+ return parsePolicy(readFileSync58(path, "utf8")).loopSafety.correctionActuator;
29937
30334
  } catch {
29938
30335
  return "conservative";
29939
30336
  }
29940
30337
  }
29941
30338
  function readEvents(eventsPath2) {
29942
30339
  try {
29943
- return readFileSync57(eventsPath2, "utf8").split("\n").map(parseEventLine).filter((ev) => ev !== null);
30340
+ return readFileSync58(eventsPath2, "utf8").split("\n").map(parseEventLine).filter((ev) => ev !== null);
29944
30341
  } catch {
29945
30342
  return [];
29946
30343
  }
@@ -29979,7 +30376,7 @@ function markStoryTodo(projectPath3, storyId) {
29979
30376
  }
29980
30377
  function storyEpic(projectPath3, storyId) {
29981
30378
  try {
29982
- const idx = JSON.parse(readFileSync57(join62(projectPath3, ".roll", "index.json"), "utf8"));
30379
+ const idx = JSON.parse(readFileSync58(join62(projectPath3, ".roll", "index.json"), "utf8"));
29983
30380
  const epic = idx[storyId];
29984
30381
  if (typeof epic === "string" && epic.trim() !== "")
29985
30382
  return epic;
@@ -29997,7 +30394,7 @@ function storyEpic(projectPath3, storyId) {
29997
30394
  }
29998
30395
  return "uncategorized";
29999
30396
  }
30000
- function existingFixId(projectPath3, storyId, signal2) {
30397
+ function existingFixId(projectPath3, storyId, signal) {
30001
30398
  try {
30002
30399
  const snap = new BacklogStore().readBacklog(join62(projectPath3, ".roll", "backlog.md"));
30003
30400
  return snap.items.find((it) => {
@@ -30008,7 +30405,7 @@ function existingFixId(projectPath3, storyId, signal2) {
30008
30405
  return false;
30009
30406
  if (!desc.includes(`fixes:${storyId.toLowerCase()}`))
30010
30407
  return false;
30011
- if (!desc.includes(`signal:${signal2.toLowerCase()}`))
30408
+ if (!desc.includes(`signal:${signal.toLowerCase()}`))
30012
30409
  return false;
30013
30410
  return classifyStatus(it.status) !== "done";
30014
30411
  })?.id;
@@ -30016,8 +30413,8 @@ function existingFixId(projectPath3, storyId, signal2) {
30016
30413
  return void 0;
30017
30414
  }
30018
30415
  }
30019
- function fixDescription(storyId, signal2) {
30020
- return `autofix [roll:manual-merge] fixes:${storyId} signal:${signal2} evidence correction`;
30416
+ function fixDescription(storyId, signal) {
30417
+ return `autofix [roll:manual-merge] fixes:${storyId} signal:${signal} evidence correction`;
30021
30418
  }
30022
30419
  function writeFixSpec(projectPath3, epic, fixId, decision) {
30023
30420
  try {
@@ -30344,7 +30741,45 @@ ${res.stderr}
30344
30741
  tcrCount = await ports.git.tcrCount(ports.paths.worktreePath);
30345
30742
  } catch {
30346
30743
  }
30347
- await runPeerGate(ports.paths.worktreePath, dirname26(ports.paths.eventsPath), ctx.cycleId ?? "", {
30744
+ const reviewPeer = async (peer, diff, timeoutMs) => {
30745
+ const prompt = `You are a heterogeneous PAIRING reviewer. A different agent wrote the diff below; give it a terse second-pair-of-eyes review (correctness, edge cases, quality). End with exactly one line "VERDICT: agree|refine|object" and one "FINDING: <issue>" line per concrete issue.
30746
+
30747
+ DIFF:
30748
+ ` + diff;
30749
+ let res;
30750
+ try {
30751
+ res = await Promise.race([
30752
+ ports.agentSpawn(peer, {
30753
+ cwd: ports.paths.worktreePath,
30754
+ skillBody: prompt,
30755
+ timeoutMs,
30756
+ ...ctx.evidenceRunDir !== void 0 ? { runDir: ctx.evidenceRunDir } : {}
30757
+ }),
30758
+ new Promise((resolve5) => setTimeout(() => resolve5(null), timeoutMs).unref())
30759
+ ]);
30760
+ } catch {
30761
+ return null;
30762
+ }
30763
+ if (res === null || res.timedOut || res.exitCode !== 0)
30764
+ return null;
30765
+ const vm = /VERDICT:\s*(agree|refine|object)/i.exec(res.stdout);
30766
+ const verdict = vm?.[1]?.toLowerCase() ?? "agree";
30767
+ const findings = [...res.stdout.matchAll(/^\s*FINDING:\s*(.+)$/gim)].map((m7) => (m7[1] ?? "").trim());
30768
+ const cost = peerReviewCost(peer, res.stdout);
30769
+ return { verdict, findings, cost };
30770
+ };
30771
+ const cycleDiff = async (cwd) => {
30772
+ try {
30773
+ const { stdout } = await execFileAsync9("git", ["diff", "origin/main...HEAD"], { cwd, encoding: "utf8" });
30774
+ return stdout.slice(0, 6e4);
30775
+ } catch {
30776
+ return "";
30777
+ }
30778
+ };
30779
+ const peerGateMode = readPeerGateMode(ports.repoCwd);
30780
+ const runtimeDir6 = dirname26(ports.paths.eventsPath);
30781
+ const cycleIdStr = ctx.cycleId ?? "";
30782
+ const peerGateSinks = {
30348
30783
  alert: (m7) => ports.events.appendAlert(ports.paths.alertsPath, m7),
30349
30784
  event: (p) => ports.events.appendEvent(ports.paths.eventsPath, {
30350
30785
  type: "peer:gate",
@@ -30353,58 +30788,44 @@ ${res.stderr}
30353
30788
  reasons: p.reasons,
30354
30789
  ts: ports.clock()
30355
30790
  })
30356
- });
30791
+ };
30792
+ let peerGate = await runPeerGate(ports.paths.worktreePath, runtimeDir6, cycleIdStr, peerGateMode, peerGateSinks);
30793
+ let peerBlocked = peerGate.blocked;
30794
+ if (peerGate.blocked) {
30795
+ const retry = await retryPeerConsult(ports.paths.worktreePath, runtimeDir6, cycleIdStr, {
30796
+ installed: ports.installedAgents?.() ?? agentsInstalled(realAgentEnv()),
30797
+ workingAgent: ctx.agent ?? "claude",
30798
+ reviewPeer,
30799
+ diff: cycleDiff,
30800
+ event: (e) => ports.events.appendEvent(ports.paths.eventsPath, e),
30801
+ now: () => ports.clock()
30802
+ });
30803
+ if (retry.status === "reviewed" && peerEvidencePresent(runtimeDir6, cycleIdStr)) {
30804
+ peerGate = await runPeerGate(ports.paths.worktreePath, runtimeDir6, cycleIdStr, peerGateMode, peerGateSinks);
30805
+ peerBlocked = peerGate.blocked;
30806
+ }
30807
+ if (peerBlocked) {
30808
+ const how = retry.sameTypeFallback === true ? "same-type separate-session review" : "peer review";
30809
+ ports.events.appendAlert(ports.paths.alertsPath, `peer gate (hard): high-complexity work still without peer evidence after one retry \u2014 the ${how} produced no evidence (${retry.status}) \u2014 cycle ${cycleIdStr} BLOCKED; story not marked Done`);
30810
+ }
30811
+ }
30357
30812
  {
30358
- const reviewPeer = async (peer, diff, timeoutMs) => {
30359
- const prompt = `You are a heterogeneous PAIRING reviewer. A different agent wrote the diff below; give it a terse second-pair-of-eyes review (correctness, edge cases, quality). End with exactly one line "VERDICT: agree|refine|object" and one "FINDING: <issue>" line per concrete issue.
30360
-
30361
- DIFF:
30362
- ` + diff;
30363
- let res;
30364
- try {
30365
- res = await Promise.race([
30366
- ports.agentSpawn(peer, {
30367
- cwd: ports.paths.worktreePath,
30368
- skillBody: prompt,
30369
- timeoutMs,
30370
- ...ctx.evidenceRunDir !== void 0 ? { runDir: ctx.evidenceRunDir } : {}
30371
- }),
30372
- new Promise((resolve5) => setTimeout(() => resolve5(null), timeoutMs).unref())
30373
- ]);
30374
- } catch {
30375
- return null;
30376
- }
30377
- if (res === null || res.timedOut || res.exitCode !== 0)
30378
- return null;
30379
- const vm = /VERDICT:\s*(agree|refine|object)/i.exec(res.stdout);
30380
- const verdict = vm?.[1]?.toLowerCase() ?? "agree";
30381
- const findings = [...res.stdout.matchAll(/^\s*FINDING:\s*(.+)$/gim)].map((m7) => (m7[1] ?? "").trim());
30382
- const cost = peerReviewCost(peer, res.stdout);
30383
- return { verdict, findings, cost };
30384
- };
30385
30813
  let pairHistory;
30386
30814
  try {
30387
30815
  if (existsSync64(ports.paths.eventsPath)) {
30388
- const events = readFileSync58(ports.paths.eventsPath, "utf8").split("\n").map(parseEventLine).filter((e) => e !== null);
30816
+ const events = readFileSync59(ports.paths.eventsPath, "utf8").split("\n").map(parseEventLine).filter((e) => e !== null);
30389
30817
  pairHistory = pairingHistory(events);
30390
30818
  }
30391
30819
  } catch {
30392
30820
  }
30393
30821
  const pairingDeps = {
30394
- installed: agentsInstalled(realAgentEnv()),
30822
+ installed: ports.installedAgents?.() ?? agentsInstalled(realAgentEnv()),
30395
30823
  isAvailable: () => true,
30396
30824
  // MVP: `installed` is the hard gate; a dead peer → reviewPeer null (non-blocking). Real probe is a refinement.
30397
30825
  reviewPeer,
30398
30826
  ...pairHistory !== void 0 ? { history: pairHistory } : {},
30399
30827
  changedFiles: cycleChangedFiles,
30400
- diff: async (cwd) => {
30401
- try {
30402
- const { stdout } = await execFileAsync9("git", ["diff", "origin/main...HEAD"], { cwd, encoding: "utf8" });
30403
- return stdout.slice(0, 6e4);
30404
- } catch {
30405
- return "";
30406
- }
30407
- },
30828
+ diff: cycleDiff,
30408
30829
  event: (e) => ports.events.appendEvent(ports.paths.eventsPath, e),
30409
30830
  now: () => ports.clock()
30410
30831
  };
@@ -30517,10 +30938,13 @@ ${diffStat}`;
30517
30938
  }
30518
30939
  const facts = {
30519
30940
  usedWorktree: true,
30520
- // accept-path reaches capture at exit 0; a HARD attest block fails the
30521
- // capture (classifyCaptured: exit ≠ 0 → failed) so Done is withheld
30522
- // unless the PR-state probe reclassifies it "published" (FIX-244).
30523
- agentExit: attestBlocked ? 1 : 0,
30941
+ // accept-path reaches capture at exit 0; a HARD attest OR peer block fails
30942
+ // the capture (classifyCaptured: exit ≠ 0 → failed) so Done is withheld.
30943
+ // FIX-293: a high-complexity delivery with no peer review (even after the
30944
+ // one retry) is peerBlocked → it MUST NOT self-score / flip Done. The
30945
+ // FIX-244 PR-state "published" reclassification stays scoped to the attest
30946
+ // block (a peer-blocked cycle still owes peer review; Done≡merged anyway).
30947
+ agentExit: attestBlocked || peerBlocked ? 1 : 0,
30524
30948
  timedOut: false,
30525
30949
  commitsAhead,
30526
30950
  ...mainAhead > 0 ? { mainAhead } : {},
@@ -30732,7 +31156,7 @@ function readRunsRows(runsPath3) {
30732
31156
  try {
30733
31157
  if (!existsSync64(runsPath3))
30734
31158
  return [];
30735
- return readFileSync58(runsPath3, "utf8").split("\n").filter((l) => l.trim() !== "").map((l) => {
31159
+ return readFileSync59(runsPath3, "utf8").split("\n").filter((l) => l.trim() !== "").map((l) => {
30736
31160
  try {
30737
31161
  return JSON.parse(l);
30738
31162
  } catch {
@@ -30755,14 +31179,14 @@ function storyRequiresManualMerge(repoCwd, storyId) {
30755
31179
  return needles.some((n) => lower.includes(n));
30756
31180
  };
30757
31181
  try {
30758
- const backlog = readFileSync58(join63(repoCwd, ".roll", "backlog.md"), "utf8");
31182
+ const backlog = readFileSync59(join63(repoCwd, ".roll", "backlog.md"), "utf8");
30759
31183
  const row2 = parseBacklog(backlog).find((it) => it.id === storyId);
30760
31184
  if (row2 !== void 0 && containsMarker(row2.desc))
30761
31185
  return true;
30762
31186
  } catch {
30763
31187
  }
30764
31188
  try {
30765
- return containsMarker(readFileSync58(join63(cardArchiveDir(repoCwd, storyId), "spec.md"), "utf8"));
31189
+ return containsMarker(readFileSync59(join63(cardArchiveDir(repoCwd, storyId), "spec.md"), "utf8"));
30766
31190
  } catch {
30767
31191
  return false;
30768
31192
  }
@@ -30801,7 +31225,7 @@ function persistWorktreeAlerts(worktreePath, alertsPath, events) {
30801
31225
  const path = join63(worktreePath, name);
30802
31226
  if (!lstatSync3(path).isFile())
30803
31227
  continue;
30804
- const body = readFileSync58(path, "utf8").trim();
31228
+ const body = readFileSync59(path, "utf8").trim();
30805
31229
  if (body === "")
30806
31230
  continue;
30807
31231
  events.appendAlert(alertsPath, `# worktree alert persisted: ${name}
@@ -30831,7 +31255,7 @@ async function linkRollIntoWorktree(repoCwd, worktreePath) {
30831
31255
  if (common === "")
30832
31256
  return;
30833
31257
  const exclude = join63(common, "info", "exclude");
30834
- const cur = existsSync64(exclude) ? readFileSync58(exclude, "utf8") : "";
31258
+ const cur = existsSync64(exclude) ? readFileSync59(exclude, "utf8") : "";
30835
31259
  if (!/^\.roll$/m.test(cur)) {
30836
31260
  mkdirSync32(dirname26(exclude), { recursive: true });
30837
31261
  appendFileSync13(exclude, `${cur === "" || cur.endsWith("\n") ? "" : "\n"}.roll
@@ -30955,7 +31379,7 @@ function nodePorts(opts) {
30955
31379
  const p = join63(projectCwd, ".roll", "backlog.md");
30956
31380
  if (!existsSync64(p))
30957
31381
  return [];
30958
- return parseBacklog(readFileSync58(p, "utf8"));
31382
+ return parseBacklog(readFileSync59(p, "utf8"));
30959
31383
  },
30960
31384
  // FIX-198: the production binding was MISSING entirely (the optional
30961
31385
  // chain made every In-Progress claim a silent no-op). ID-anchored mark
@@ -31188,21 +31612,21 @@ function describeCommand(cmd) {
31188
31612
  // packages/cli/dist/runner/correction-circuit.js
31189
31613
  init_dist2();
31190
31614
  init_dist();
31191
- import { appendFileSync as appendFileSync14, existsSync as existsSync65, mkdirSync as mkdirSync33, readFileSync as readFileSync59, writeFileSync as writeFileSync33 } from "node:fs";
31615
+ import { appendFileSync as appendFileSync14, existsSync as existsSync65, mkdirSync as mkdirSync33, readFileSync as readFileSync60, writeFileSync as writeFileSync33 } from "node:fs";
31192
31616
  import { dirname as dirname27, join as join64 } from "node:path";
31193
31617
  function readLoopSafety(projectPath3) {
31194
31618
  try {
31195
31619
  const path = join64(projectPath3, ".roll", "policy.yaml");
31196
31620
  if (!existsSync65(path))
31197
31621
  return parsePolicy("").loopSafety;
31198
- return parsePolicy(readFileSync59(path, "utf8")).loopSafety;
31622
+ return parsePolicy(readFileSync60(path, "utf8")).loopSafety;
31199
31623
  } catch {
31200
31624
  return parsePolicy("").loopSafety;
31201
31625
  }
31202
31626
  }
31203
31627
  function readEvents2(eventsPath2) {
31204
31628
  try {
31205
- return readFileSync59(eventsPath2, "utf8").split("\n").map(parseEventLine).filter((ev) => ev !== null);
31629
+ return readFileSync60(eventsPath2, "utf8").split("\n").map(parseEventLine).filter((ev) => ev !== null);
31206
31630
  } catch {
31207
31631
  return [];
31208
31632
  }
@@ -31259,7 +31683,7 @@ function applyCorrectionCircuitBreaker(projectPath3, slug, eventsPath2, alertsPa
31259
31683
  // packages/cli/dist/lib/morning-report.js
31260
31684
  init_dist2();
31261
31685
  init_dist();
31262
- import { appendFileSync as appendFileSync15, existsSync as existsSync66, mkdirSync as mkdirSync34, readFileSync as readFileSync60, writeFileSync as writeFileSync34 } from "node:fs";
31686
+ import { appendFileSync as appendFileSync15, existsSync as existsSync66, mkdirSync as mkdirSync34, readFileSync as readFileSync61, writeFileSync as writeFileSync34 } from "node:fs";
31263
31687
  import { dirname as dirname28, join as join65 } from "node:path";
31264
31688
  var esc7 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
31265
31689
  function iso2(sec) {
@@ -31267,14 +31691,14 @@ function iso2(sec) {
31267
31691
  }
31268
31692
  function readEvents3(path) {
31269
31693
  try {
31270
- return readFileSync60(path, "utf8").split("\n").map(parseEventLine).filter((ev) => ev !== null);
31694
+ return readFileSync61(path, "utf8").split("\n").map(parseEventLine).filter((ev) => ev !== null);
31271
31695
  } catch {
31272
31696
  return [];
31273
31697
  }
31274
31698
  }
31275
31699
  function readRuns(path) {
31276
31700
  try {
31277
- return readFileSync60(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line !== "").map((line) => JSON.parse(line));
31701
+ return readFileSync61(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line !== "").map((line) => JSON.parse(line));
31278
31702
  } catch {
31279
31703
  return [];
31280
31704
  }
@@ -31346,7 +31770,7 @@ function writeLatestMorningReport(projectPath3, eventsPath2, runsPath3, nowSec2
31346
31770
  // packages/cli/dist/lib/runs-backfill.js
31347
31771
  init_dist2();
31348
31772
  init_dist3();
31349
- import { readFileSync as readFileSync61, writeFileSync as writeFileSync35 } from "node:fs";
31773
+ import { readFileSync as readFileSync62, writeFileSync as writeFileSync35 } from "node:fs";
31350
31774
  var BACKFILL_PROBE_CAP = 20;
31351
31775
  function parseLine(raw) {
31352
31776
  if (raw.trim() === "")
@@ -31368,7 +31792,7 @@ function isCandidate(row2) {
31368
31792
  async function backfillMergedRuns(projectPath3, runsPath3, deps = {}) {
31369
31793
  let body;
31370
31794
  try {
31371
- body = readFileSync61(runsPath3, "utf8");
31795
+ body = readFileSync62(runsPath3, "utf8");
31372
31796
  } catch {
31373
31797
  return [];
31374
31798
  }
@@ -31456,7 +31880,7 @@ function cycleSignalTeardown(paths, cycleId, branch, sig, deps = {}) {
31456
31880
  }
31457
31881
  let owned = false;
31458
31882
  try {
31459
- owned = existsSync67(paths.lockPath) && parseLock(readFileSync62(paths.lockPath, "utf8")).pid === pid;
31883
+ owned = existsSync67(paths.lockPath) && parseLock(readFileSync63(paths.lockPath, "utf8")).pid === pid;
31460
31884
  } catch {
31461
31885
  owned = false;
31462
31886
  }
@@ -31541,7 +31965,7 @@ function incrementConsecutiveFails(projectPath3, slug, alertsPath, eventsPath2,
31541
31965
  let count = 0;
31542
31966
  try {
31543
31967
  if (existsSync67(counterFile)) {
31544
- count = parseInt(readFileSync62(counterFile, "utf8").trim(), 10) || 0;
31968
+ count = parseInt(readFileSync63(counterFile, "utf8").trim(), 10) || 0;
31545
31969
  }
31546
31970
  } catch {
31547
31971
  }
@@ -31593,7 +32017,7 @@ function readFailurePauseThreshold(projectPath3) {
31593
32017
  const policy = join66(projectPath3, ".roll", "policy.yaml");
31594
32018
  if (!existsSync67(policy))
31595
32019
  return PAUSE_THRESHOLD;
31596
- return parsePolicy(readFileSync62(policy, "utf8")).loopSafety.maxConsecutiveFailures;
32020
+ return parsePolicy(readFileSync63(policy, "utf8")).loopSafety.maxConsecutiveFailures;
31597
32021
  } catch {
31598
32022
  return PAUSE_THRESHOLD;
31599
32023
  }
@@ -31618,7 +32042,7 @@ function buildLoopRouteDeps(projectPath3) {
31618
32042
  function readSlot(slot) {
31619
32043
  const agentsYaml = join66(projectPath3, ".roll", "agents.yaml");
31620
32044
  try {
31621
- return readSlotFromText(readFileSync62(agentsYaml, "utf8"), slot);
32045
+ return readSlotFromText(readFileSync63(agentsYaml, "utf8"), slot);
31622
32046
  } catch {
31623
32047
  return void 0;
31624
32048
  }
@@ -31633,7 +32057,7 @@ function buildLoopRouteDeps(projectPath3) {
31633
32057
  }
31634
32058
  function readField(path, re) {
31635
32059
  try {
31636
- const text = readFileSync62(path, "utf8");
32060
+ const text = readFileSync63(path, "utf8");
31637
32061
  for (const line of text.split("\n")) {
31638
32062
  const m7 = line.match(re);
31639
32063
  if (m7 !== null) {
@@ -31862,7 +32286,7 @@ async function egressBlocked(resolve5 = (h) => lookup(h), tcpProbe2 = () => tcpC
31862
32286
  // packages/cli/dist/commands/offboard.js
31863
32287
  init_dist();
31864
32288
  import { spawnSync as spawnSync7 } from "node:child_process";
31865
- import { existsSync as existsSync68, readFileSync as readFileSync63, realpathSync as realpathSync8, rmSync as rmSync14, writeFileSync as writeFileSync37 } from "node:fs";
32289
+ import { existsSync as existsSync68, readFileSync as readFileSync64, realpathSync as realpathSync8, rmSync as rmSync14, writeFileSync as writeFileSync37 } from "node:fs";
31866
32290
  import { homedir as homedir18 } from "node:os";
31867
32291
  import { join as join67, resolve as resolve3 } from "node:path";
31868
32292
  function pal5() {
@@ -31990,7 +32414,7 @@ function offboardCommand(args) {
31990
32414
  `);
31991
32415
  return 1;
31992
32416
  }
31993
- const parsed = parseChangeset(readFileSync63(changeset, "utf8"));
32417
+ const parsed = parseChangeset(readFileSync64(changeset, "utf8"));
31994
32418
  if (!parsed.ok) {
31995
32419
  err10(m4("offboard.failed_to_parse_changeset"));
31996
32420
  return 1;
@@ -32084,7 +32508,7 @@ function offboardCommand(args) {
32084
32508
  for (const item of giEntries) {
32085
32509
  const gi = join67(projectDir, ".gitignore");
32086
32510
  if (existsSync68(gi)) {
32087
- const lines2 = readFileSync63(gi, "utf8").split("\n");
32511
+ const lines2 = readFileSync64(gi, "utf8").split("\n");
32088
32512
  if (lines2.includes(item)) {
32089
32513
  const kept = lines2.filter((l) => l !== item);
32090
32514
  writeFileSync37(gi, kept.join("\n"));
@@ -32108,12 +32532,12 @@ function offboardCommand(args) {
32108
32532
 
32109
32533
  // packages/cli/dist/commands/prices.js
32110
32534
  init_dist();
32111
- import { existsSync as existsSync70, readdirSync as readdirSync29, readFileSync as readFileSync65 } from "node:fs";
32535
+ import { existsSync as existsSync70, readdirSync as readdirSync29, readFileSync as readFileSync66 } from "node:fs";
32112
32536
  import { join as join69 } from "node:path";
32113
32537
 
32114
32538
  // packages/cli/dist/commands/prices-refresh.js
32115
32539
  init_dist();
32116
- import { existsSync as existsSync69, mkdirSync as mkdirSync36, readdirSync as readdirSync28, readFileSync as readFileSync64, writeFileSync as writeFileSync38 } from "node:fs";
32540
+ import { existsSync as existsSync69, mkdirSync as mkdirSync36, readdirSync as readdirSync28, readFileSync as readFileSync65, writeFileSync as writeFileSync38 } from "node:fs";
32117
32541
  import { join as join68 } from "node:path";
32118
32542
  var FetchError = class extends Error {
32119
32543
  constructor(message) {
@@ -32341,7 +32765,7 @@ function latestSnapshotPath(snapshotDir, vendor) {
32341
32765
  return snaps[snaps.length - 1] ?? null;
32342
32766
  }
32343
32767
  function readPrices(path) {
32344
- const data = JSON.parse(readFileSync64(path, "utf8"));
32768
+ const data = JSON.parse(readFileSync65(path, "utf8"));
32345
32769
  return data.prices ?? {};
32346
32770
  }
32347
32771
  function diffPrices(oldPrices, newPrices) {
@@ -32549,7 +32973,7 @@ function loadSnapshots() {
32549
32973
  }
32550
32974
  const files = readdirSync29(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
32551
32975
  return files.map((name) => {
32552
- const data = JSON.parse(readFileSync65(join69(dir, name), "utf8"));
32976
+ const data = JSON.parse(readFileSync66(join69(dir, name), "utf8"));
32553
32977
  for (const key of ["version", "effective_at", "source_url", "prices"]) {
32554
32978
  if (data[key] === void 0)
32555
32979
  throw new Error(`snapshot ${name} missing required key ${key}`);
@@ -32645,13 +33069,13 @@ function pricesCommand(args, deps = {}) {
32645
33069
  init_dist2();
32646
33070
  init_dist();
32647
33071
  import { execFileSync as execFileSync14 } from "node:child_process";
32648
- import { readFileSync as readFileSync69, writeFileSync as writeFileSync41, existsSync as existsSync74 } from "node:fs";
33072
+ import { readFileSync as readFileSync70, writeFileSync as writeFileSync41, existsSync as existsSync74 } from "node:fs";
32649
33073
  import { join as join73 } from "node:path";
32650
33074
 
32651
33075
  // packages/cli/dist/lib/release-consistency.js
32652
33076
  init_dist2();
32653
33077
  init_dist();
32654
- import { existsSync as existsSync72, readFileSync as readFileSync67, readdirSync as readdirSync31, statSync as statSync23 } from "node:fs";
33078
+ import { existsSync as existsSync72, readFileSync as readFileSync68, readdirSync as readdirSync31, statSync as statSync23 } from "node:fs";
32655
33079
  import { join as join71 } from "node:path";
32656
33080
 
32657
33081
  // packages/cli/dist/lib/consistency-audit.js
@@ -32659,7 +33083,7 @@ init_dist2();
32659
33083
  init_dist3();
32660
33084
  init_dist();
32661
33085
  import { execFile as execFile10 } from "node:child_process";
32662
- import { existsSync as existsSync71, mkdirSync as mkdirSync37, readFileSync as readFileSync66, readdirSync as readdirSync30, writeFileSync as writeFileSync39 } from "node:fs";
33086
+ import { existsSync as existsSync71, mkdirSync as mkdirSync37, readFileSync as readFileSync67, readdirSync as readdirSync30, writeFileSync as writeFileSync39 } from "node:fs";
32663
33087
  import { dirname as dirname30, join as join70 } from "node:path";
32664
33088
  import { promisify as promisify10 } from "node:util";
32665
33089
  init_dist();
@@ -32671,7 +33095,7 @@ function readJsonl2(path) {
32671
33095
  if (!existsSync71(path))
32672
33096
  return [];
32673
33097
  const out3 = [];
32674
- for (const line of readFileSync66(path, "utf8").split("\n")) {
33098
+ for (const line of readFileSync67(path, "utf8").split("\n")) {
32675
33099
  if (line.trim() === "")
32676
33100
  continue;
32677
33101
  try {
@@ -32687,7 +33111,7 @@ function readJson(path) {
32687
33111
  if (!existsSync71(path))
32688
33112
  return null;
32689
33113
  try {
32690
- const parsed = JSON.parse(readFileSync66(path, "utf8"));
33114
+ const parsed = JSON.parse(readFileSync67(path, "utf8"));
32691
33115
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
32692
33116
  } catch {
32693
33117
  return null;
@@ -32750,7 +33174,7 @@ async function gatherAuditSnapshot(projectPath3, runtimeDir6, deps = {}) {
32750
33174
  const skipped = [];
32751
33175
  const backlogPath = join70(projectPath3, ".roll", "backlog.md");
32752
33176
  if (existsSync71(backlogPath)) {
32753
- snapshot.backlog = parseBacklog(readFileSync66(backlogPath, "utf8")).map((r) => ({ id: r.id, status: r.status }));
33177
+ snapshot.backlog = parseBacklog(readFileSync67(backlogPath, "utf8")).map((r) => ({ id: r.id, status: r.status }));
32754
33178
  }
32755
33179
  snapshot.index = readIndex(projectPath3);
32756
33180
  snapshot.localMainAhead = deps.localMainAhead !== void 0 ? await deps.localMainAhead() : await gitLocalMainAhead(projectPath3);
@@ -32759,7 +33183,7 @@ async function gatherAuditSnapshot(projectPath3, runtimeDir6, deps = {}) {
32759
33183
  const terminal = [];
32760
33184
  const eventsPath2 = join70(runtimeDir6, "events.ndjson");
32761
33185
  if (existsSync71(eventsPath2)) {
32762
- for (const line of readFileSync66(eventsPath2, "utf8").split("\n")) {
33186
+ for (const line of readFileSync67(eventsPath2, "utf8").split("\n")) {
32763
33187
  const e = parseEventLine(line);
32764
33188
  if (e === null)
32765
33189
  continue;
@@ -32924,7 +33348,7 @@ function escapeRegExp3(s) {
32924
33348
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32925
33349
  }
32926
33350
  function readText(p) {
32927
- return readFileSync67(p, "utf8");
33351
+ return readFileSync68(p, "utf8");
32928
33352
  }
32929
33353
  var COMMAND_SURFACE_REPLACEMENTS = /* @__PURE__ */ new Map([
32930
33354
  ["migrate", "npx @seanyao/roll@2 migrate"],
@@ -33659,7 +34083,7 @@ function realReleaseDeps() {
33659
34083
  return {
33660
34084
  version: (cwd) => {
33661
34085
  try {
33662
- const pkg = JSON.parse(readFileSync69(join73(cwd, "package.json"), "utf8"));
34086
+ const pkg = JSON.parse(readFileSync70(join73(cwd, "package.json"), "utf8"));
33663
34087
  return typeof pkg.version === "string" ? pkg.version : "";
33664
34088
  } catch {
33665
34089
  return "";
@@ -33694,11 +34118,11 @@ function realReleaseDeps() {
33694
34118
  return true;
33695
34119
  }
33696
34120
  },
33697
- readChangelog: (cwd) => readFileSync69(join73(cwd, "CHANGELOG.md"), "utf8"),
34121
+ readChangelog: (cwd) => readFileSync70(join73(cwd, "CHANGELOG.md"), "utf8"),
33698
34122
  writeChangelog: (cwd, text) => writeFileSync41(join73(cwd, "CHANGELOG.md"), text, "utf8"),
33699
34123
  bumpVersion: (cwd, version) => {
33700
34124
  const path = join73(cwd, "package.json");
33701
- const pkg = JSON.parse(readFileSync69(path, "utf8"));
34125
+ const pkg = JSON.parse(readFileSync70(path, "utf8"));
33702
34126
  pkg["version"] = version;
33703
34127
  writeFileSync41(path, `${JSON.stringify(pkg, null, 2)}
33704
34128
  `, "utf8");
@@ -34006,7 +34430,7 @@ async function selfScoreCommand(args) {
34006
34430
  // packages/cli/dist/commands/setup.js
34007
34431
  init_dist();
34008
34432
  import { spawnSync as spawnSync9 } from "node:child_process";
34009
- import { existsSync as existsSync75, lstatSync as lstatSync4, mkdirSync as mkdirSync39, readFileSync as readFileSync70, readdirSync as readdirSync33, readlinkSync as readlinkSync2, statSync as statSync25 } from "node:fs";
34433
+ import { existsSync as existsSync75, lstatSync as lstatSync4, mkdirSync as mkdirSync39, readFileSync as readFileSync71, readdirSync as readdirSync33, readlinkSync as readlinkSync2, statSync as statSync25 } from "node:fs";
34010
34434
  import { join as join74 } from "node:path";
34011
34435
  function err13(line) {
34012
34436
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
@@ -34080,7 +34504,7 @@ function walk(dir, lines2) {
34080
34504
  }
34081
34505
  function fileFingerprint(p) {
34082
34506
  try {
34083
- const buf = readFileSync70(p);
34507
+ const buf = readFileSync71(p);
34084
34508
  let sum = 0;
34085
34509
  for (let i = 0; i < buf.length; i++)
34086
34510
  sum = sum * 31 + (buf[i] ?? 0) >>> 0;
@@ -34274,7 +34698,7 @@ init_showcase2();
34274
34698
 
34275
34699
  // packages/cli/dist/commands/test.js
34276
34700
  import { spawnSync as spawnSync10 } from "node:child_process";
34277
- import { appendFileSync as appendFileSync17, existsSync as existsSync76, mkdirSync as mkdirSync40, readFileSync as readFileSync71, rmSync as rmSync16, writeFileSync as writeFileSync42 } from "node:fs";
34701
+ import { appendFileSync as appendFileSync17, existsSync as existsSync76, mkdirSync as mkdirSync40, readFileSync as readFileSync72, rmSync as rmSync16, writeFileSync as writeFileSync42 } from "node:fs";
34278
34702
  import { dirname as dirname32, join as join75, resolve as resolve4 } from "node:path";
34279
34703
  function pal7() {
34280
34704
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
@@ -34357,7 +34781,7 @@ function isolationGetType() {
34357
34781
  return "none";
34358
34782
  let text;
34359
34783
  try {
34360
- text = readFileSync71(file, "utf8");
34784
+ text = readFileSync72(file, "utf8");
34361
34785
  } catch {
34362
34786
  return "none";
34363
34787
  }
@@ -34548,7 +34972,7 @@ function testCommand(args) {
34548
34972
 
34549
34973
  // packages/cli/dist/commands/tune.js
34550
34974
  init_dist2();
34551
- import { existsSync as existsSync77, readFileSync as readFileSync72 } from "node:fs";
34975
+ import { existsSync as existsSync77, readFileSync as readFileSync73 } from "node:fs";
34552
34976
  import { join as join76 } from "node:path";
34553
34977
  function projectPath2() {
34554
34978
  return (process.env["ROLL_MAIN_PROJECT"] ?? "").trim() || process.cwd();
@@ -34561,7 +34985,7 @@ function parseJsonl(path) {
34561
34985
  return [];
34562
34986
  let text;
34563
34987
  try {
34564
- text = readFileSync72(path, "utf8");
34988
+ text = readFileSync73(path, "utf8");
34565
34989
  } catch {
34566
34990
  return [];
34567
34991
  }
@@ -34733,7 +35157,7 @@ function tuneCommand(argv) {
34733
35157
  // packages/cli/dist/commands/update.js
34734
35158
  init_dist();
34735
35159
  import { spawnSync as spawnSync11 } from "node:child_process";
34736
- import { existsSync as existsSync78, mkdtempSync as mkdtempSync6, readFileSync as readFileSync73, rmSync as rmSync17 } from "node:fs";
35160
+ import { existsSync as existsSync78, mkdtempSync as mkdtempSync6, readFileSync as readFileSync74, rmSync as rmSync17 } from "node:fs";
34737
35161
  import { tmpdir as tmpdir8 } from "node:os";
34738
35162
  import { join as join77 } from "node:path";
34739
35163
  function pal8() {
@@ -34854,7 +35278,7 @@ function showChangelog() {
34854
35278
  `);
34855
35279
  let count = 0;
34856
35280
  let inSection = false;
34857
- for (const line of readFileSync73(changelog, "utf8").split("\n")) {
35281
+ for (const line of readFileSync74(changelog, "utf8").split("\n")) {
34858
35282
  if (/^## /.test(line)) {
34859
35283
  count += 1;
34860
35284
  if (count > 3)
@@ -34879,7 +35303,7 @@ function updateCommand(args) {
34879
35303
  let installMethod = "npm";
34880
35304
  const methodFile = join77(rollPkgDir(), ".install-method");
34881
35305
  if (existsSync78(methodFile)) {
34882
- installMethod = readFileSync73(methodFile, "utf8").trim() || "npm";
35306
+ installMethod = readFileSync74(methodFile, "utf8").trim() || "npm";
34883
35307
  }
34884
35308
  if (installMethod === "curl") {
34885
35309
  info5(m6("update.upgrading_via_curl"));