@seanyao/roll 3.614.2 → 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.
package/dist/roll.mjs CHANGED
@@ -2324,6 +2324,7 @@ function buildTerminalEvent(input) {
2324
2324
  cycleId: input.cycleId,
2325
2325
  storyId: input.storyId,
2326
2326
  agent: input.agent,
2327
+ model: input.model ?? "",
2327
2328
  startedAt: input.startedAt,
2328
2329
  endedAt: input.endedAt,
2329
2330
  outcome: input.outcome,
@@ -2412,6 +2413,10 @@ var init_truth_registry = __esm({
2412
2413
  { field: "cycleId", surface: "event:cycle:terminal", anchor: "cycle_outcome", writer: "buildTerminalEvent", kind: "authoritative" },
2413
2414
  { field: "storyId", surface: "event:cycle:terminal", anchor: "cycle_outcome", writer: "buildTerminalEvent", kind: "authoritative" },
2414
2415
  { field: "agent", surface: "event:cycle:terminal", anchor: "cycle_outcome", writer: "buildTerminalEvent", kind: "authoritative" },
2416
+ // FIX-294: routed model is a dispatch-time fact (like agent) — authoritative,
2417
+ // ALWAYS present even when usage couldn't be parsed; the `usage` fact below
2418
+ // still owns the present-or-reasoned token/cost truth.
2419
+ { field: "model", surface: "event:cycle:terminal", anchor: "cycle_outcome", writer: "buildTerminalEvent", kind: "authoritative" },
2415
2420
  { field: "startedAt", surface: "event:cycle:terminal", anchor: "cycle_outcome", writer: "buildTerminalEvent", kind: "authoritative" },
2416
2421
  { field: "endedAt", surface: "event:cycle:terminal", anchor: "cycle_outcome", writer: "buildTerminalEvent", kind: "authoritative" },
2417
2422
  { field: "outcome", surface: "event:cycle:terminal", anchor: "cycle_outcome", writer: "buildTerminalEvent", kind: "authoritative" },
@@ -4973,6 +4978,532 @@ var init_bus = __esm({
4973
4978
  }
4974
4979
  });
4975
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
+
4976
5507
  // packages/core/dist/loop/alert-loop.js
4977
5508
  function alertFileName(slug) {
4978
5509
  return `ALERT-${slug}.md`;
@@ -5202,14 +5733,14 @@ function classifyAttribution(storyId, reasons) {
5202
5733
  }
5203
5734
  };
5204
5735
  }
5205
- function priorCorrectionCount(events, storyId, signal2) {
5736
+ function priorCorrectionCount(events, storyId, signal) {
5206
5737
  let n = 0;
5207
5738
  for (const ev of events) {
5208
5739
  if (ev.type !== "correction:action")
5209
5740
  continue;
5210
5741
  if (ev.storyId !== storyId)
5211
5742
  continue;
5212
- if (ev.signal !== signal2)
5743
+ if (ev.signal !== signal)
5213
5744
  continue;
5214
5745
  n += 1;
5215
5746
  }
@@ -5240,8 +5771,8 @@ var init_correction_actuator = __esm({
5240
5771
  });
5241
5772
 
5242
5773
  // packages/core/dist/loop/correction-safety.js
5243
- function normalizeSignal(signal2) {
5244
- 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);
5245
5776
  }
5246
5777
  function cycleStoryMap(events) {
5247
5778
  const out3 = /* @__PURE__ */ new Map();
@@ -5256,26 +5787,26 @@ function correctionSignals(events) {
5256
5787
  const out3 = [];
5257
5788
  for (const ev of events) {
5258
5789
  if (ev.type === "correction:action") {
5259
- const signal2 = normalizeSignal(ev.signal);
5260
- if (signal2 === "")
5790
+ const signal = normalizeSignal(ev.signal);
5791
+ if (signal === "")
5261
5792
  continue;
5262
5793
  out3.push({
5263
5794
  storyId: ev.storyId,
5264
5795
  ...ev.cycleId !== void 0 ? { cycleId: ev.cycleId } : {},
5265
- signal: signal2,
5796
+ signal,
5266
5797
  action: ev.action,
5267
5798
  ts: ev.ts,
5268
5799
  source: "correction"
5269
5800
  });
5270
5801
  } else if (ev.type === "attest:gate" && ev.verdict === "skipped") {
5271
- const signal2 = normalizeSignal(ev.reasons[0] ?? "attest gate skipped");
5272
- if (signal2 === "")
5802
+ const signal = normalizeSignal(ev.reasons[0] ?? "attest gate skipped");
5803
+ if (signal === "")
5273
5804
  continue;
5274
5805
  const storyId = stories.get(ev.cycleId);
5275
5806
  out3.push({
5276
5807
  ...storyId !== void 0 ? { storyId } : {},
5277
5808
  cycleId: ev.cycleId,
5278
- signal: signal2,
5809
+ signal,
5279
5810
  action: "attest_skipped",
5280
5811
  ts: ev.ts,
5281
5812
  source: "attest"
@@ -5323,7 +5854,7 @@ function correctionSignalVerdict(events, safety, nowSec2) {
5323
5854
  for (const sig of inWindow) {
5324
5855
  bySignal.set(sig.signal, [...bySignal.get(sig.signal) ?? [], sig]);
5325
5856
  }
5326
- 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]))) {
5327
5858
  if (hits.length < threshold)
5328
5859
  continue;
5329
5860
  const knownStories = new Set(hits.map((h) => h.storyId).filter((s) => s !== void 0 && s !== ""));
@@ -5333,10 +5864,10 @@ function correctionSignalVerdict(events, safety, nowSec2) {
5333
5864
  action: "pause_and_notify",
5334
5865
  kind: "signal_repeat",
5335
5866
  ...knownStories.size > 0 ? { storyId: [...knownStories].sort().join(",") } : {},
5336
- signal: signal2,
5867
+ signal,
5337
5868
  count: hits.length,
5338
5869
  threshold,
5339
- 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}`
5340
5871
  };
5341
5872
  }
5342
5873
  return { action: "continue" };
@@ -5802,388 +6333,119 @@ function cycleStep(state, event) {
5802
6333
  case "facts_captured": {
5803
6334
  const status2 = classifyCaptured(event.facts);
5804
6335
  const next = { ...state, phase: "reconcile", captured: event.facts };
5805
- if (status2 !== "built") {
5806
- const extra = status2 === "idle" || status2 === "published" ? [{ kind: "cleanup_worktree", branch: state.ctx.branch }] : status2 === "failed" && (event.facts.mainAhead ?? 0) > 0 ? [
5807
- {
5808
- kind: "append_alert",
5809
- 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)`
5810
- }
5811
- ] : status2 === "failed" && event.facts.commitsAhead > 0 ? [
5812
- { kind: "push_orphan", branch: state.ctx.branch },
5813
- {
5814
- kind: "append_alert",
5815
- 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)`
5816
- }
5817
- ] : [];
5818
- return terminate(next, status2, extra);
5819
- }
5820
- return {
5821
- state: { ...next, phase: "publish" },
5822
- commands: [{ kind: "publish_pr", branch: state.ctx.branch, docOnly: false }]
5823
- };
5824
- }
5825
- case "published": {
5826
- const status2 = classifyPublish(event.result);
5827
- if (status2 === "published" || status2 === "done") {
5828
- return terminate({ ...state, phase: "cleanup" }, status2, [
5829
- { kind: "cleanup_worktree", branch: state.ctx.branch }
5830
- ]);
5831
- }
5832
- if (status2 === "orphan") {
5833
- return terminate({ ...state, phase: "cleanup" }, "orphan", [
5834
- { kind: "cleanup_worktree", branch: state.ctx.branch },
5835
- { kind: "append_alert", message: `cycle ${state.ctx.cycleId}: publish failed; orphan branch+tag pushed; worktree cleaned` }
5836
- ]);
5837
- }
5838
- return terminate({ ...state, phase: "publish" }, "failed", [
5839
- { kind: "append_alert", message: `cycle ${state.ctx.cycleId}: publish failed; worktree preserved at branch ${state.ctx.branch}` }
5840
- ]);
5841
- }
5842
- case "merge_polled": {
5843
- const action = nextWaitAction(event.state, event.elapsedSec);
5844
- if (action.kind === "wait") {
5845
- return {
5846
- state: { ...state, phase: "merge-wait" },
5847
- commands: [{ kind: "wait_merge", branch: state.ctx.branch, elapsedSec: event.elapsedSec + action.sleepSeconds }]
5848
- };
5849
- }
5850
- if (action.kind === "merged") {
5851
- return terminate({ ...state, phase: "reconcile" }, "done", [
5852
- { kind: "reconcile" },
5853
- { kind: "cleanup_worktree", branch: state.ctx.branch }
5854
- ]);
5855
- }
5856
- return terminate({ ...state, phase: "merge-wait" }, "orphan", [
5857
- { kind: "push_orphan", branch: state.ctx.branch }
5858
- ]);
5859
- }
5860
- default:
5861
- return { state, commands: [] };
5862
- }
5863
- }
5864
- function cycleStartEvent(ctx, ts = 0) {
5865
- return {
5866
- type: "cycle:start",
5867
- cycleId: ctx.cycleId,
5868
- storyId: ctx.storyId ?? "",
5869
- agent: ctx.agent ?? "",
5870
- model: ctx.model ?? "",
5871
- ts
5872
- };
5873
- }
5874
- function initialCycleState(ctx) {
5875
- return { phase: "pick", ctx, attempt: 0, done: false };
5876
- }
5877
- var CYCLE_TIMEOUT_SEC, WATCHDOG_KILL_GRACE_SEC, MAX_AGENT_ATTEMPTS, RETRY_BASE_BACKOFF_SEC, EVENT_VALID_PHASES;
5878
- var init_orchestrator = __esm({
5879
- "packages/core/dist/loop/orchestrator.js"() {
5880
- "use strict";
5881
- init_pr();
5882
- CYCLE_TIMEOUT_SEC = 2700;
5883
- WATCHDOG_KILL_GRACE_SEC = 5;
5884
- MAX_AGENT_ATTEMPTS = 3;
5885
- RETRY_BASE_BACKOFF_SEC = 30;
5886
- EVENT_VALID_PHASES = {
5887
- preflight_done: ["pick"],
5888
- worktree_created: ["worktree"],
5889
- worktree_failed: ["worktree"],
5890
- story_picked: ["pick"],
5891
- no_story: ["pick"],
5892
- route_resolved: ["route"],
5893
- budget_ok: ["route"],
5894
- budget_halt: ["route"],
5895
- agent_exited: ["execute"],
5896
- facts_captured: ["reconcile"],
5897
- published: ["publish"],
5898
- merge_polled: ["merge-wait", "publish"],
5899
- reconciled: ["reconcile"],
5900
- cleaned: ["cleanup"]
5901
- };
5902
- }
5903
- });
5904
-
5905
- // packages/core/dist/loop/recovery.js
5906
- function resolveKeepDays(envDays, yamlDays) {
5907
- if (envDays !== void 0 && /^\d+$/.test(envDays.trim()))
5908
- return Number(envDays.trim());
5909
- if (yamlDays !== void 0 && Number.isFinite(yamlDays) && yamlDays >= 0)
5910
- return Math.trunc(yamlDays);
5911
- return GC_DEFAULT_KEEP_DAYS;
5912
- }
5913
- var GC_DEFAULT_KEEP_DAYS;
5914
- var init_recovery = __esm({
5915
- "packages/core/dist/loop/recovery.js"() {
5916
- "use strict";
5917
- GC_DEFAULT_KEEP_DAYS = 30;
5918
- }
5919
- });
5920
-
5921
- // packages/core/dist/loop/signals.js
5922
- function signalKindForMarker(marker) {
5923
- if (marker === "tcr")
5924
- return "tcr";
5925
- if (marker === "skill" || marker === "story")
5926
- return "skill";
5927
- if (marker.startsWith("ci:"))
5928
- return "ci";
5929
- if (marker === "peer:gate" || marker === "peer")
5930
- return "peer";
5931
- if (marker === "attest:gate" || marker === "evidence:frame-opened" || marker === "attest")
5932
- return "attest";
5933
- if (marker.startsWith("pr:"))
5934
- return "pr";
5935
- if (marker === "alert" || marker === "error")
5936
- return "alert";
5937
- return null;
5938
- }
5939
- var init_signals = __esm({
5940
- "packages/core/dist/loop/signals.js"() {
5941
- "use strict";
5942
- }
5943
- });
5944
-
5945
- // packages/core/dist/loop/loop-fmt.js
5946
- function newFmtState() {
5947
- return {
5948
- pendingCommit: false,
5949
- pendingPr: false,
5950
- pendingCi: false,
5951
- pendingStory: false,
5952
- lastBashCmd: "",
5953
- tcrCount: 0,
5954
- editStreakFile: null
5955
- };
5956
- }
5957
- function clip(s, n = 60) {
5958
- const flat = s.replace(/\s+/g, " ").trim();
5959
- return flat.length > n ? `${flat.slice(0, n - 1)}\u2026` : flat;
5960
- }
5961
- function basename2(p) {
5962
- const trimmed = p.replace(/\/+$/, "");
5963
- const i = trimmed.lastIndexOf("/");
5964
- return i >= 0 ? trimmed.slice(i + 1) : trimmed;
5965
- }
5966
- function clearPending(st) {
5967
- st.pendingCommit = false;
5968
- st.pendingPr = false;
5969
- st.pendingCi = false;
5970
- st.pendingStory = false;
5971
- }
5972
- function signal(marker, category, label4, detail, ok8 = true) {
5973
- const kind = signalKindForMarker(marker);
5974
- const line = { tier: "signal", category, label: label4, ok: ok8 };
5975
- if (kind !== null)
5976
- line.kind = kind;
5977
- if (detail !== void 0 && detail !== "")
5978
- line.detail = detail;
5979
- return line;
5980
- }
5981
- function formatLine(raw, st) {
5982
- const line = raw.replace(/\s+$/, "");
5983
- if (line.trim() === "")
5984
- return [];
5985
- const header = /^──\s*cycle\s+(.+?)\s*──\s*$/.exec(line);
5986
- if (header) {
5987
- clearPending(st);
5988
- st.tcrCount = 0;
5989
- st.editStreakFile = null;
5990
- return [{ tier: "banner", category: "cycle", label: clip(header[1], 80) }];
5991
- }
5992
- let ev;
5993
- try {
5994
- ev = JSON.parse(line);
5995
- } catch {
5996
- const m7 = /\[loop\]\s+cycle\s+(\d+)/.exec(line);
5997
- if (m7) {
5998
- clearPending(st);
5999
- st.tcrCount = 0;
6000
- return [{ tier: "banner", category: "cycle", label: `cycle #${m7[1]}` }];
6001
- }
6002
- return [];
6003
- }
6004
- if (typeof ev !== "object" || ev === null)
6005
- return [];
6006
- const type = ev.type;
6007
- if (type === "system")
6008
- return [];
6009
- if (type === "assistant")
6010
- return handleAssistant(ev, st);
6011
- if (type === "user")
6012
- return handleUser(ev, st);
6013
- if (type === "result")
6014
- return handleResult(ev, st);
6015
- return [];
6016
- }
6017
- function handleAssistant(ev, st) {
6018
- const msg6 = ev["message"] ?? {};
6019
- const content = Array.isArray(msg6["content"]) ? msg6["content"] : [];
6020
- const out3 = [];
6021
- for (const blkRaw of content) {
6022
- if (typeof blkRaw !== "object" || blkRaw === null)
6023
- continue;
6024
- const blk = blkRaw;
6025
- const bt = blk["type"];
6026
- if (bt === "thinking")
6027
- continue;
6028
- if (bt === "text") {
6029
- out3.push(...handleText(typeof blk["text"] === "string" ? blk["text"] : "", st));
6030
- } else if (bt === "tool_use") {
6031
- out3.push(...handleToolUse(blk, st));
6032
- }
6033
- }
6034
- return out3;
6035
- }
6036
- function handleText(text, st) {
6037
- const t2 = text.trim();
6038
- if (t2 === "")
6039
- return [];
6040
- for (const v of PEER_VERDICTS) {
6041
- if (t2.includes(v)) {
6042
- const rm = /round\s+(\d+)[/\\](\d+)/i.exec(t2);
6043
- const round = rm ? `round ${rm[1]}/${rm[2]}` : "round ?";
6044
- const am = /(\w+)\s*→\s*(\w+)/.exec(t2);
6045
- const agents = am ? `${am[1]} \u2192 ${am[2]}` : "peer";
6046
- st.editStreakFile = null;
6047
- return [signal("peer", "peer", agents, `${round} \xB7 ${v}`)];
6048
- }
6049
- }
6050
- return [];
6051
- }
6052
- function handleToolUse(blk, st) {
6053
- const name = typeof blk["name"] === "string" ? blk["name"] : "";
6054
- const input = blk["input"] ?? {};
6055
- if (SUPPRESS_TOOLS.has(name))
6056
- return [];
6057
- if (name === "Edit" || name === "Write") {
6058
- const path = typeof input["file_path"] === "string" && input["file_path"] || typeof input["path"] === "string" && input["path"] || "";
6059
- if (path === st.editStreakFile)
6060
- return [];
6061
- st.editStreakFile = path;
6062
- return [{ tier: "muted", category: "\u270F", label: `\u270F ${basename2(path)}` }];
6063
- }
6064
- st.editStreakFile = null;
6065
- if (name === "Bash") {
6066
- const cmd = typeof input["command"] === "string" ? input["command"] : "";
6067
- const first = cmd.split("\n").map((l) => l.trim()).find((l) => l !== "") ?? cmd;
6068
- st.lastBashCmd = first;
6069
- if (/git\s+commit[\s\S]*tcr:/.test(cmd))
6070
- st.pendingCommit = true;
6071
- else if (/gh\s+pr\s+(create|merge)/.test(cmd))
6072
- st.pendingPr = true;
6073
- else if (/(roll\s+ci|npm\s+run\s+ci|_ci_wait|ci:local)/.test(cmd))
6074
- st.pendingCi = true;
6075
- return [];
6076
- }
6077
- if (name === "Skill") {
6078
- const skill = typeof input["skill"] === "string" ? input["skill"] : "";
6079
- if (skill === "roll-build" || skill === "roll-fix") {
6080
- const args = (typeof input["args"] === "string" ? input["args"] : "").trim();
6081
- const usId = args.split(/\s+/)[0] || "?";
6082
- st.pendingStory = true;
6083
- return [signal("skill", "story", usId, clip(args, 60))];
6084
- }
6085
- return [];
6086
- }
6087
- return [];
6088
- }
6089
- function extractResultText(content) {
6090
- if (typeof content === "string")
6091
- return content;
6092
- if (Array.isArray(content)) {
6093
- return content.filter((c2) => typeof c2 === "object" && c2 !== null && c2["type"] === "text").map((c2) => typeof c2["text"] === "string" ? c2["text"] : "").join("\n");
6094
- }
6095
- return content == null ? "" : String(content);
6096
- }
6097
- function handleUser(ev, st) {
6098
- const msg6 = ev["message"] ?? {};
6099
- const content = Array.isArray(msg6["content"]) ? msg6["content"] : [];
6100
- const out3 = [];
6101
- for (const blkRaw of content) {
6102
- if (typeof blkRaw !== "object" || blkRaw === null)
6103
- continue;
6104
- const blk = blkRaw;
6105
- if (blk["type"] !== "tool_result")
6106
- continue;
6107
- const text = extractResultText(blk["content"]);
6108
- if (blk["is_error"] === true) {
6109
- st.editStreakFile = null;
6110
- const lines2 = text.split("\n").filter((l) => l.trim() !== "").slice(0, 3);
6111
- clearPending(st);
6112
- out3.push(signal("error", "error", "tool", clip(lines2.join(" | "), 80), false));
6113
- continue;
6114
- }
6115
- if (st.pendingCommit) {
6116
- st.pendingCommit = false;
6117
- const m7 = /\[[\w/-]+ ([0-9a-f]{7,})\]\s*tcr:\s*(.+)/.exec(text);
6118
- if (m7) {
6119
- st.tcrCount += 1;
6120
- out3.push(signal("tcr", "tcr", m7[1].slice(0, 7), clip(m7[2].trim(), 60)));
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);
6121
6350
  }
6122
- continue;
6123
- }
6124
- if (st.pendingStory) {
6125
- st.pendingStory = false;
6126
- continue;
6351
+ return {
6352
+ state: { ...next, phase: "publish" },
6353
+ commands: [{ kind: "publish_pr", branch: state.ctx.branch, docOnly: false }]
6354
+ };
6127
6355
  }
6128
- if (st.pendingPr) {
6129
- st.pendingPr = false;
6130
- const m7 = /#(\d+)/.exec(text);
6131
- if (m7) {
6132
- const branch = /loop\/[\w-]+/.exec(st.lastBashCmd);
6133
- 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
+ ]);
6134
6362
  }
6135
- 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
+ ]);
6136
6372
  }
6137
- if (st.pendingCi) {
6138
- st.pendingCi = false;
6139
- const green = /(green|pass|success|all tests)/i.test(text);
6140
- const red = /(red|fail|error)/i.test(text);
6141
- const dur = /(\d+(?:\.\d+)?)\s*s\b/.exec(text);
6142
- const tests = /(\d+)\s+tests?/.exec(text);
6143
- const detail = [dur ? `${dur[1]}s` : "", tests ? `${tests[1]} tests` : ""].filter(Boolean).join(" \xB7 ");
6144
- const ok8 = green && !red;
6145
- out3.push(signal(ok8 ? "ci:pass" : "ci:fail", "ci", ok8 ? "green" : "red", detail, ok8));
6146
- 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
+ ]);
6147
6390
  }
6391
+ default:
6392
+ return { state, commands: [] };
6148
6393
  }
6149
- return out3;
6150
6394
  }
6151
- function handleResult(ev, st) {
6152
- st.editStreakFile = null;
6153
- const durMs = typeof ev["duration_ms"] === "number" ? ev["duration_ms"] : 0;
6154
- const cost = typeof ev["total_cost_usd"] === "number" ? ev["total_cost_usd"] : 0;
6155
- const durS = Math.round(durMs / 1e3);
6156
- if (ev["subtype"] === "error_max_turns") {
6157
- 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
+ };
6158
6433
  }
6159
- const parts = [
6160
- st.tcrCount > 0 ? `${st.tcrCount} tcr` : "",
6161
- `${durS}s`,
6162
- cost > 0 ? `$${cost.toFixed(2)}` : ""
6163
- ].filter(Boolean);
6164
- 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;
6165
6443
  }
6166
- var SUPPRESS_TOOLS, PEER_VERDICTS;
6167
- var init_loop_fmt = __esm({
6168
- "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"() {
6169
6447
  "use strict";
6170
- init_signals();
6171
- SUPPRESS_TOOLS = /* @__PURE__ */ new Set([
6172
- "Read",
6173
- "Glob",
6174
- "Grep",
6175
- "ReadMcpResourceTool",
6176
- "ListMcpResourcesTool",
6177
- "WebFetch",
6178
- "WebSearch",
6179
- "TaskCreate",
6180
- "TaskGet",
6181
- "TaskList",
6182
- "TaskUpdate",
6183
- "TaskOutput",
6184
- "TaskStop"
6185
- ]);
6186
- PEER_VERDICTS = ["AGREE", "REFINE", "OBJECT", "ESCALATE"];
6448
+ GC_DEFAULT_KEEP_DAYS = 30;
6187
6449
  }
6188
6450
  });
6189
6451
 
@@ -6479,7 +6741,8 @@ function parseLoopSafety(lines2, start) {
6479
6741
  correctionSignalWindowSec: numOr(flat["correction_signal_window_sec"], DEFAULT_CORRECTION_SIGNAL_WINDOW_SEC),
6480
6742
  correctionActuator: flat["correction_actuator"] === "auto" ? "auto" : DEFAULT_CORRECTION_ACTUATOR,
6481
6743
  ...budget ? { budget } : {},
6482
- ...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"] } : {}
6483
6746
  };
6484
6747
  return [i, cfg];
6485
6748
  }
@@ -6726,47 +6989,47 @@ function routeProposals(input, minSamples) {
6726
6989
  }
6727
6990
  function rubricProposals(input, minSamples) {
6728
6991
  const out3 = [];
6729
- for (const signal2 of input.rubricSignals) {
6730
- if (signal2.samples < minSamples)
6992
+ for (const signal of input.rubricSignals) {
6993
+ if (signal.samples < minSamples)
6731
6994
  continue;
6732
- const current = input.current.rubricWeights?.[signal2.dimension] ?? DEFAULT_RUBRIC_WEIGHT;
6733
- 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) {
6734
6997
  const next = round1(clamp(current + 0.2, 0.2, 3));
6735
6998
  if (next === current)
6736
6999
  continue;
6737
7000
  out3.push({
6738
7001
  kind: "rubric_weight",
6739
- target: `rubric.${signal2.dimension}.weight`,
7002
+ target: `rubric.${signal.dimension}.weight`,
6740
7003
  action: "raise_weight",
6741
7004
  from: current,
6742
7005
  to: next,
6743
7006
  rationale: "\u8BE5 rubric \u7EF4\u5EA6\u4E0E\u771F\u5B9E\u8FD4\u5DE5\u5F3A\u76F8\u5173\uFF0C\u6743\u91CD\u5C0F\u5E45\u4E0A\u8C03\u3002",
6744
7007
  evidence: [
6745
- `dimension=${signal2.dimension}`,
6746
- `samples=${signal2.samples}`,
6747
- `rework_correlation=${fmtRate(signal2.reworkCorrelation)}`,
6748
- `noise=${fmtRate(signal2.noise)}`
7008
+ `dimension=${signal.dimension}`,
7009
+ `samples=${signal.samples}`,
7010
+ `rework_correlation=${fmtRate(signal.reworkCorrelation)}`,
7011
+ `noise=${fmtRate(signal.noise)}`
6749
7012
  ],
6750
- rollback: reset(`rubric.${signal2.dimension}.weight`, DEFAULT_RUBRIC_WEIGHT)
7013
+ rollback: reset(`rubric.${signal.dimension}.weight`, DEFAULT_RUBRIC_WEIGHT)
6751
7014
  });
6752
- } else if (signal2.noise >= 0.6 && signal2.reworkCorrelation <= 0.3) {
7015
+ } else if (signal.noise >= 0.6 && signal.reworkCorrelation <= 0.3) {
6753
7016
  const next = round1(clamp(current - 0.2, 0.2, 3));
6754
7017
  if (next === current)
6755
7018
  continue;
6756
7019
  out3.push({
6757
7020
  kind: "rubric_weight",
6758
- target: `rubric.${signal2.dimension}.weight`,
7021
+ target: `rubric.${signal.dimension}.weight`,
6759
7022
  action: "lower_weight",
6760
7023
  from: current,
6761
7024
  to: next,
6762
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",
6763
7026
  evidence: [
6764
- `dimension=${signal2.dimension}`,
6765
- `samples=${signal2.samples}`,
6766
- `rework_correlation=${fmtRate(signal2.reworkCorrelation)}`,
6767
- `noise=${fmtRate(signal2.noise)}`
7027
+ `dimension=${signal.dimension}`,
7028
+ `samples=${signal.samples}`,
7029
+ `rework_correlation=${fmtRate(signal.reworkCorrelation)}`,
7030
+ `noise=${fmtRate(signal.noise)}`
6768
7031
  ],
6769
- rollback: reset(`rubric.${signal2.dimension}.weight`, DEFAULT_RUBRIC_WEIGHT)
7032
+ rollback: reset(`rubric.${signal.dimension}.weight`, DEFAULT_RUBRIC_WEIGHT)
6770
7033
  });
6771
7034
  }
6772
7035
  }
@@ -8081,6 +8344,7 @@ var init_dist2 = __esm({
8081
8344
  init_score();
8082
8345
  init_bus();
8083
8346
  init_infra_default3();
8347
+ init_activity_signal();
8084
8348
  init_alert_loop();
8085
8349
  init_ci_loop();
8086
8350
  init_correction_actuator();
@@ -8090,7 +8354,6 @@ var init_dist2 = __esm({
8090
8354
  init_quality_gate();
8091
8355
  init_orchestrator();
8092
8356
  init_recovery();
8093
- init_loop_fmt();
8094
8357
  init_sentinel();
8095
8358
  init_signals();
8096
8359
  init_transcript();
@@ -8112,7 +8375,7 @@ var init_dist2 = __esm({
8112
8375
  });
8113
8376
 
8114
8377
  // packages/infra/dist/config.js
8115
- 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";
8116
8379
  import { homedir as homedir3 } from "node:os";
8117
8380
  import { dirname as dirname7, join as join12 } from "node:path";
8118
8381
  function expandLeadingTilde(val, home = homedir3()) {
@@ -8128,7 +8391,7 @@ function yamlReadNested(file, parent, child) {
8128
8391
  const parentRe = new RegExp(`^${parent}:`);
8129
8392
  const childRe = new RegExp(`^[ \\t]+${child}:`);
8130
8393
  let found = false;
8131
- const text = readFileSync13(file, "utf8");
8394
+ const text = readFileSync14(file, "utf8");
8132
8395
  const lines2 = text.length === 0 ? [] : text.replace(/\n$/, "").split("\n");
8133
8396
  for (const line of lines2) {
8134
8397
  if (!found) {
@@ -8148,7 +8411,7 @@ function yamlReadFlat(file, key) {
8148
8411
  if (!existsSync12(file))
8149
8412
  return "";
8150
8413
  const re = new RegExp(`^${escapeRegExp(key)}:`);
8151
- for (const line of readFileSync13(file, "utf8").split("\n")) {
8414
+ for (const line of readFileSync14(file, "utf8").split("\n")) {
8152
8415
  if (re.test(line)) {
8153
8416
  return line.replace(/^[^:]*:[ \t]*/, "").replace(/[ \t]*#.*$/, "").replace(/[ \t]*$/, "");
8154
8417
  }
@@ -8303,7 +8566,7 @@ function applyConfigSet(text, key, value) {
8303
8566
  }
8304
8567
  function configSet(key, value, file) {
8305
8568
  mkdirSync8(dirname7(file), { recursive: true });
8306
- const text = existsSync12(file) ? readFileSync13(file, "utf8") : "";
8569
+ const text = existsSync12(file) ? readFileSync14(file, "utf8") : "";
8307
8570
  writeFileSync9(file, applyConfigSet(text, key, value));
8308
8571
  }
8309
8572
  var CONFIG_KEYS;
@@ -8820,7 +9083,7 @@ var init_github = __esm({
8820
9083
  });
8821
9084
 
8822
9085
  // packages/infra/dist/process.js
8823
- 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";
8824
9087
  import { dirname as dirname9 } from "node:path";
8825
9088
  function parseLock(raw) {
8826
9089
  const firstLine = raw.split("\n", 1)[0] ?? "";
@@ -8848,7 +9111,7 @@ function acquireLock(lockPath2, pid = process.pid, opts = {}) {
8848
9111
  const pidAlive = opts.pidAlive ?? systemPidAlive;
8849
9112
  mkdirSync10(dirname9(lockPath2), { recursive: true });
8850
9113
  if (existsSync14(lockPath2)) {
8851
- const contents = parseLock(readFileSync14(lockPath2, "utf8"));
9114
+ const contents = parseLock(readFileSync15(lockPath2, "utf8"));
8852
9115
  if (isLockHeld(contents, now, staleSec, pidAlive)) {
8853
9116
  return { acquired: false, heldByPid: contents.pid };
8854
9117
  }
@@ -8868,7 +9131,7 @@ function writeHeartbeat(heartbeatPath, now = systemClock) {
8868
9131
  function heartbeatAge(heartbeatPath, now = systemClock) {
8869
9132
  let ts = 0;
8870
9133
  if (existsSync14(heartbeatPath)) {
8871
- const raw = readFileSync14(heartbeatPath, "utf8").trim();
9134
+ const raw = readFileSync15(heartbeatPath, "utf8").trim();
8872
9135
  ts = /^\d+$/.test(raw) ? Number(raw) : 0;
8873
9136
  }
8874
9137
  return now() - ts;
@@ -8897,10 +9160,10 @@ function installExitHooks(final, target = process) {
8897
9160
  const onExit = () => {
8898
9161
  runOnce();
8899
9162
  };
8900
- const onSignal = (signal2) => {
9163
+ const onSignal = (signal) => {
8901
9164
  runOnce();
8902
- target.removeListener(signal2, onSignal);
8903
- target.kill(target.pid, signal2);
9165
+ target.removeListener(signal, onSignal);
9166
+ target.kill(target.pid, signal);
8904
9167
  };
8905
9168
  target.on("exit", onExit);
8906
9169
  target.on("SIGTERM", onSignal);
@@ -10043,10 +10306,12 @@ var init_showcase = __esm({
10043
10306
  var showcase_exports = {};
10044
10307
  __export(showcase_exports, {
10045
10308
  SHOWCASE_USAGE: () => SHOWCASE_USAGE,
10309
+ probeMissingAgents: () => probeMissingAgents,
10310
+ resetSandbox: () => resetSandbox,
10046
10311
  showcaseCommand: () => showcaseCommand
10047
10312
  });
10048
10313
  import { spawnSync as spawnSync8 } from "node:child_process";
10049
- 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";
10050
10315
  import { tmpdir as tmpdir7 } from "node:os";
10051
10316
  import { dirname as dirname31, join as join72 } from "node:path";
10052
10317
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -10101,14 +10366,17 @@ function packageRoot() {
10101
10366
  function rollBin2() {
10102
10367
  return join72(packageRoot(), "packages", "cli", "bin", "roll.js");
10103
10368
  }
10104
- function runRoll(sandbox, rollHome4, args, extraEnv = {}) {
10369
+ function runRoll(sandbox, rollHome4, args, opts = {}) {
10105
10370
  const env = {
10106
10371
  ...process.env,
10107
- ROLL_HOME: rollHome4,
10108
10372
  ROLL_LANG: process.env["ROLL_LANG"] ?? "en",
10109
10373
  GIT_TERMINAL_PROMPT: "0",
10110
- ...extraEnv
10374
+ ...opts.extraEnv
10111
10375
  };
10376
+ if (opts.realHome === true) {
10377
+ } else {
10378
+ env.ROLL_HOME = rollHome4;
10379
+ }
10112
10380
  const r = spawnSync8(process.execPath, [rollBin2(), ...args], {
10113
10381
  cwd: sandbox,
10114
10382
  env,
@@ -10171,9 +10439,13 @@ function findCardSpec(sandbox, card) {
10171
10439
  function resetSandbox(sandbox, card) {
10172
10440
  const notes = [];
10173
10441
  let reset2 = false;
10442
+ let ok8 = false;
10174
10443
  const backlogPath = join72(sandbox, ".roll", "backlog.md");
10175
10444
  if (existsSync73(backlogPath)) {
10176
- 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);
10177
10449
  const lines2 = before.split("\n").map((line) => {
10178
10450
  if (!line.includes(card) || !line.startsWith("|"))
10179
10451
  return line;
@@ -10183,9 +10455,16 @@ function resetSandbox(sandbox, card) {
10183
10455
  if (after !== before) {
10184
10456
  writeFileSync40(backlogPath, after, "utf8");
10185
10457
  reset2 = true;
10458
+ ok8 = true;
10186
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`);
10187
10465
  } else {
10188
- notes.push(`backlog: ${card} already Todo (or no status token)`);
10466
+ ok8 = true;
10467
+ notes.push(`backlog: ${card} already Todo`);
10189
10468
  }
10190
10469
  } else {
10191
10470
  notes.push("backlog: .roll/backlog.md absent");
@@ -10195,7 +10474,7 @@ function resetSandbox(sandbox, card) {
10195
10474
  rmSync15(pulseCmd, { force: true });
10196
10475
  notes.push("removed prior pulse.ts surface");
10197
10476
  }
10198
- return { reset: reset2, notes };
10477
+ return { ok: ok8, reset: reset2, notes };
10199
10478
  }
10200
10479
  function writeCasting(sandbox, casting) {
10201
10480
  const agentsPath2 = join72(sandbox, ".roll", "agents.yaml");
@@ -10238,7 +10517,7 @@ function readBacklogStatus(sandbox, card) {
10238
10517
  const backlogPath = join72(sandbox, ".roll", "backlog.md");
10239
10518
  if (!existsSync73(backlogPath))
10240
10519
  return void 0;
10241
- for (const line of readFileSync68(backlogPath, "utf8").split("\n")) {
10520
+ for (const line of readFileSync69(backlogPath, "utf8").split("\n")) {
10242
10521
  if (line.startsWith("|") && line.includes(card)) {
10243
10522
  const m7 = /(✅ *Done|🚧 *WIP|🔄 *In Progress|⏳ *Hold|📋 *Todo|✔️ *Done)/.exec(line);
10244
10523
  if (m7 !== null && m7[1] !== void 0)
@@ -10252,7 +10531,7 @@ function readTruthLadder(sandbox, card) {
10252
10531
  if (!existsSync73(truthPath))
10253
10532
  return void 0;
10254
10533
  try {
10255
- const snap = JSON.parse(readFileSync68(truthPath, "utf8"));
10534
+ const snap = JSON.parse(readFileSync69(truthPath, "utf8"));
10256
10535
  const row2 = (snap.stories ?? []).find((s) => s.id === card);
10257
10536
  return row2?.ladder;
10258
10537
  } catch {
@@ -10265,7 +10544,7 @@ function readTcrCommits(sandbox) {
10265
10544
  return [];
10266
10545
  const out3 = [];
10267
10546
  try {
10268
- const lines2 = readFileSync68(runsPath3, "utf8").trim().split("\n").filter(Boolean);
10547
+ const lines2 = readFileSync69(runsPath3, "utf8").trim().split("\n").filter(Boolean);
10269
10548
  const last = lines2[lines2.length - 1];
10270
10549
  if (last === void 0)
10271
10550
  return [];
@@ -10310,7 +10589,7 @@ ${msg6}
10310
10589
  const sourceProject = process.cwd();
10311
10590
  const { sandbox, rollHome: rollHome4 } = makeSandbox(sourceProject);
10312
10591
  const steps = [];
10313
- const emit3 = (id, ok8, detail) => {
10592
+ const emit4 = (id, ok8, detail) => {
10314
10593
  steps.push({ id, ok: ok8, detail });
10315
10594
  if (!json)
10316
10595
  process.stdout.write(`${ok8 ? "\u2713" : "\u2717"} ${id.padEnd(18)} ${detail}
@@ -10319,28 +10598,28 @@ ${msg6}
10319
10598
  try {
10320
10599
  const spec = findCardSpec(sandbox, card);
10321
10600
  if (spec === void 0) {
10322
- 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`);
10323
10602
  throw new ShowcaseAbort(`card ${card} absent`);
10324
10603
  }
10325
10604
  const reset2 = resetSandbox(sandbox, card);
10326
- emit3("reset", reset2.reset, reset2.notes.join("; "));
10605
+ emit4("reset", reset2.ok, reset2.notes.join("; "));
10327
10606
  writeCasting(sandbox, casting);
10328
- 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)`);
10329
10608
  const missing = probeMissingAgents(sandbox, rollHome4, casting);
10330
10609
  if (missing.length > 0) {
10331
- 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`);
10332
10611
  throw new ShowcaseAbort(`agents unavailable: ${missing.join(", ")}`);
10333
10612
  }
10334
- 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}`);
10335
10614
  const go = runRoll(sandbox, rollHome4, ["loop", "go", "--cards", card, "--no-tmux", "--no-wait"]);
10336
- 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)}`);
10337
10616
  runRoll(sandbox, rollHome4, ["index", "--rebuild"]);
10338
10617
  const attest = runRoll(sandbox, rollHome4, ["attest", card]);
10339
10618
  const screenshots = await captureScreenshots(sandbox, card);
10340
10619
  const cliShot = screenshots.find((s) => s.surface === "cli");
10341
10620
  const webShot = screenshots.find((s) => s.surface === "web");
10342
- emit3("capture-cli", cliShot?.present === true, cliShot?.present === true ? cliShot.path : `skip: ${cliShot?.skipped ?? "none"}`);
10343
- 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"}`);
10344
10623
  const backlogStatus = readBacklogStatus(sandbox, card);
10345
10624
  const truthLadder = readTruthLadder(sandbox, card);
10346
10625
  const tcrCommits = readTcrCommits(sandbox);
@@ -10408,9 +10687,9 @@ ROLL_HOME: ${rollHome4}
10408
10687
  }
10409
10688
  }
10410
10689
  }
10411
- function probeMissingAgents(sandbox, rollHome4, casting) {
10690
+ function probeMissingAgents(sandbox, rollHome4, casting, runner = runRoll) {
10412
10691
  const wanted = [.../* @__PURE__ */ new Set([casting.builder, casting.reviewer, casting.scorer])];
10413
- const r = runRoll(sandbox, rollHome4, ["agent", "list"]);
10692
+ const r = runner(sandbox, rollHome4, ["agent", "list"], { realHome: true });
10414
10693
  if (r.code !== 0 && r.stdout.trim() === "")
10415
10694
  return wanted;
10416
10695
  const listed = r.stdout.toLowerCase();
@@ -11432,12 +11711,12 @@ function agentCommand(args, deps = {}) {
11432
11711
  init_dist2();
11433
11712
  init_dist();
11434
11713
  init_dist2();
11435
- 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";
11436
11715
  import { dirname as dirname10, join as join16, relative as relative2 } from "node:path";
11437
11716
 
11438
11717
  // packages/cli/dist/runner/pairing-gate.js
11439
11718
  init_dist2();
11440
- 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";
11441
11720
  import { join as join9 } from "node:path";
11442
11721
 
11443
11722
  // packages/cli/dist/lib/self-score.js
@@ -12190,8 +12469,9 @@ function evaluateSelfScoreGate(projectPath3, storyId) {
12190
12469
  }
12191
12470
 
12192
12471
  // packages/cli/dist/runner/peer-gate.js
12472
+ init_dist2();
12193
12473
  import { execFile } from "node:child_process";
12194
- import { existsSync as existsSync9, readdirSync as readdirSync4 } from "node:fs";
12474
+ import { existsSync as existsSync9, readFileSync as readFileSync10, readdirSync as readdirSync4 } from "node:fs";
12195
12475
  import { join as join8 } from "node:path";
12196
12476
  import { promisify } from "node:util";
12197
12477
  var execFileAsync = promisify(execFile);
@@ -12231,21 +12511,32 @@ function peerEvidencePresent(runtimeDir6, cycleId) {
12231
12511
  return false;
12232
12512
  }
12233
12513
  }
12234
- 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) {
12235
12525
  try {
12236
12526
  const files = await cycleChangedFiles(worktreeCwd);
12237
12527
  const cx = assessComplexity(files);
12238
12528
  if (!cx.high)
12239
- return { verdict: "not-required", reasons: [] };
12529
+ return { verdict: "not-required", mode, reasons: [], blocked: false };
12240
12530
  if (peerEvidencePresent(runtimeDir6, cycleId)) {
12241
12531
  sinks.event({ cycleId, verdict: "consulted", reasons: cx.reasons });
12242
- return { verdict: "consulted", reasons: cx.reasons };
12532
+ return { verdict: "consulted", mode, reasons: cx.reasons, blocked: false };
12243
12533
  }
12244
- 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" : ""));
12245
12536
  sinks.event({ cycleId, verdict: "skipped", reasons: cx.reasons });
12246
- return { verdict: "skipped", reasons: cx.reasons };
12537
+ return { verdict: "skipped", mode, reasons: cx.reasons, blocked };
12247
12538
  } catch {
12248
- return { verdict: "not-required", reasons: [] };
12539
+ return { verdict: "not-required", mode, reasons: [], blocked: false };
12249
12540
  }
12250
12541
  }
12251
12542
 
@@ -12255,7 +12546,7 @@ function enabledPairingStages(projectDir) {
12255
12546
  const cfgPath = join9(projectDir, ".roll", "pairing.yaml");
12256
12547
  if (!existsSync10(cfgPath))
12257
12548
  return [];
12258
- const cfg = parsePairingConfig(readFileSync10(cfgPath, "utf8"));
12549
+ const cfg = parsePairingConfig(readFileSync11(cfgPath, "utf8"));
12259
12550
  if (!cfg.enabled)
12260
12551
  return [];
12261
12552
  return cfg.stages.filter((s, i, arr2) => arr2.indexOf(s) === i && s !== "score");
@@ -12276,7 +12567,7 @@ async function runPairing(projectDir, worktreeCwd, runtimeDir6, cycleId, working
12276
12567
  const cfgPath = join9(projectDir, ".roll", "pairing.yaml");
12277
12568
  if (!existsSync10(cfgPath))
12278
12569
  return { status: "off" };
12279
- const cfg = parsePairingConfig(readFileSync10(cfgPath, "utf8"));
12570
+ const cfg = parsePairingConfig(readFileSync11(cfgPath, "utf8"));
12280
12571
  if (!cfg.enabled || !cfg.stages.includes(stage))
12281
12572
  return { status: "off" };
12282
12573
  const files = await deps.changedFiles(worktreeCwd);
@@ -12319,7 +12610,7 @@ async function runScorePairing(projectDir, runtimeDir6, cycleId, workingAgent, s
12319
12610
  const cfgPath = join9(projectDir, ".roll", "pairing.yaml");
12320
12611
  if (!existsSync10(cfgPath))
12321
12612
  return { status: "off" };
12322
- const cfg = parsePairingConfig(readFileSync10(cfgPath, "utf8"));
12613
+ const cfg = parsePairingConfig(readFileSync11(cfgPath, "utf8"));
12323
12614
  if (!cfg.enabled || !cfg.stages.includes("score"))
12324
12615
  return { status: "off" };
12325
12616
  const candidates = selectPairingCandidates({
@@ -12373,6 +12664,33 @@ function parsePairScoreOutput(stdout) {
12373
12664
  return null;
12374
12665
  return { score, verdict: vm[1].toLowerCase(), rationale: rm[1].trim() };
12375
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
+ }
12376
12694
  function buildPairScorePrompt(summary) {
12377
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.
12378
12696
 
@@ -12383,11 +12701,11 @@ DELIVERY:
12383
12701
  // packages/cli/dist/commands/peer.js
12384
12702
  init_dist2();
12385
12703
  import { spawn } from "node:child_process";
12386
- 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";
12387
12705
  import { dirname as dirname6, join as join11 } from "node:path";
12388
12706
 
12389
12707
  // packages/cli/dist/commands/setup-shared.js
12390
- 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";
12391
12709
  import { homedir as homedir2 } from "node:os";
12392
12710
  import { basename as basename5, dirname as dirname5, join as join10 } from "node:path";
12393
12711
  function rollHome() {
@@ -12416,7 +12734,7 @@ function getAiTools() {
12416
12734
  if (!existsSync11(cfg))
12417
12735
  return [];
12418
12736
  const out3 = [];
12419
- for (const line of readFileSync11(cfg, "utf8").split("\n")) {
12737
+ for (const line of readFileSync12(cfg, "utf8").split("\n")) {
12420
12738
  if (/^ai_[a-z]+:/.test(line)) {
12421
12739
  let entry = line.replace(/^[^:]*:[ \t]*/, "");
12422
12740
  entry = entry.replace(/^~/, homedir2());
@@ -12549,7 +12867,7 @@ function safeCopy(src, dst, force) {
12549
12867
  }
12550
12868
  function sameFile(a, b) {
12551
12869
  try {
12552
- return readFileSync11(a).equals(readFileSync11(b));
12870
+ return readFileSync12(a).equals(readFileSync12(b));
12553
12871
  } catch {
12554
12872
  return false;
12555
12873
  }
@@ -12683,7 +13001,7 @@ function ensureConfigEntries() {
12683
13001
  const cfg = rollConfig();
12684
13002
  if (!existsSync11(cfg))
12685
13003
  return;
12686
- const original = readFileSync11(cfg, "utf8");
13004
+ const original = readFileSync12(cfg, "utf8");
12687
13005
  let lines2 = original.split("\n");
12688
13006
  let added = 0;
12689
13007
  for (const [key, val] of DEFAULT_AI_KEYS) {
@@ -12759,7 +13077,7 @@ function replacePrimaryAgent(newAgent) {
12759
13077
  const cfg = rollConfig();
12760
13078
  if (!existsSync11(cfg) || newAgent === "")
12761
13079
  return;
12762
- const lines2 = readFileSync11(cfg, "utf8").split("\n");
13080
+ const lines2 = readFileSync12(cfg, "utf8").split("\n");
12763
13081
  const out3 = lines2.map((l) => /^primary_agent:/.test(l) ? `primary_agent: ${newAgent}` : l);
12764
13082
  writeFileSync7(cfg, out3.join("\n"));
12765
13083
  }
@@ -12769,7 +13087,7 @@ function installLocal(force) {
12769
13087
  pullConventions(force);
12770
13088
  pullSkills();
12771
13089
  const cfg = rollConfig();
12772
- if (existsSync11(cfg) && !/^ai_[a-z]+:/m.test(readFileSync11(cfg, "utf8"))) {
13090
+ if (existsSync11(cfg) && !/^ai_[a-z]+:/m.test(readFileSync12(cfg, "utf8"))) {
12773
13091
  copyFileSync(cfg, `${cfg}.bak`);
12774
13092
  rmSync2(cfg, { force: true });
12775
13093
  }
@@ -12795,8 +13113,8 @@ function syncConventionForTool(src, mainDst, force) {
12795
13113
  copyFileSync(src, wkFile);
12796
13114
  if (!existsSync11(mainDst)) {
12797
13115
  writeFileSync7(mainDst, "@roll.md\n");
12798
- } else if (!readFileSync11(mainDst, "utf8").includes("@roll.md")) {
12799
- 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");
12800
13118
  }
12801
13119
  }
12802
13120
  function syncConventions(force) {
@@ -12935,7 +13253,7 @@ function textAgentArgv(agent, prompt) {
12935
13253
  case "gemini":
12936
13254
  case "agy":
12937
13255
  case "antigravity":
12938
- return { bin: "agy", args: ["-p", "--dangerously-skip-permissions", prompt] };
13256
+ return { bin: "agy", args: ["--dangerously-skip-permissions", "-p", prompt] };
12939
13257
  default:
12940
13258
  return null;
12941
13259
  }
@@ -12986,15 +13304,15 @@ function boundedAppend(current, chunk) {
12986
13304
  const next = current + chunk.toString("utf8");
12987
13305
  return next.length > 1e5 ? next.slice(-1e5) : next;
12988
13306
  }
12989
- function killChild(child, signal2) {
13307
+ function killChild(child, signal) {
12990
13308
  if (child.pid !== void 0) {
12991
13309
  try {
12992
- process.kill(-child.pid, signal2);
13310
+ process.kill(-child.pid, signal);
12993
13311
  return true;
12994
13312
  } catch {
12995
13313
  }
12996
13314
  }
12997
- return child.kill(signal2);
13315
+ return child.kill(signal);
12998
13316
  }
12999
13317
  function releaseChild(child) {
13000
13318
  child.stdout?.destroy();
@@ -13039,14 +13357,14 @@ function spawnPeerReviewAgent(input) {
13039
13357
  stderr = boundedAppend(stderr, chunk);
13040
13358
  });
13041
13359
  child.on("error", (error) => finish({ status: "error", reason: error.message, stdout }));
13042
- child.on("exit", (code, signal2) => {
13360
+ child.on("exit", (code, signal) => {
13043
13361
  setImmediate(() => {
13044
13362
  if (timedOut)
13045
13363
  finish({ status: "timeout", stdout });
13046
13364
  else if (code === 0)
13047
13365
  finish({ status: "ok", stdout });
13048
13366
  else
13049
- 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 });
13050
13368
  });
13051
13369
  });
13052
13370
  child.on("close", (code) => {
@@ -13229,7 +13547,7 @@ function readPrompt(opts) {
13229
13547
  if (opts.prompt !== void 0)
13230
13548
  return opts.prompt;
13231
13549
  if (opts.file !== void 0)
13232
- return readFileSync12(opts.file, "utf8");
13550
+ return readFileSync13(opts.file, "utf8");
13233
13551
  throw new Error("roll peer: --prompt or --file is required");
13234
13552
  }
13235
13553
  async function peerCommand(args, deps = realDeps()) {
@@ -13279,7 +13597,7 @@ duration: ${facts.durationMs}ms
13279
13597
  // packages/cli/dist/commands/dashboard.js
13280
13598
  import { execFileSync as execFileSync2 } from "node:child_process";
13281
13599
  import { createHash as createHash3 } from "node:crypto";
13282
- 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";
13283
13601
  import { homedir as homedir4, platform } from "node:os";
13284
13602
  import { basename as basename6, join as join15 } from "node:path";
13285
13603
  init_dist3();
@@ -13391,7 +13709,7 @@ function resolveProjectPath(slug) {
13391
13709
  const plist = join15(homedir4(), "Library", "LaunchAgents", `com.roll.loop.${slug}.plist`);
13392
13710
  if (existsSync17(plist)) {
13393
13711
  try {
13394
- const text = readFileSync15(plist, "utf8");
13712
+ const text = readFileSync16(plist, "utf8");
13395
13713
  const m7 = /<key>WorkingDirectory<\/key>\s*<string>([^<]+)<\/string>/.exec(text);
13396
13714
  if (m7 && m7[1] !== void 0 && isDir(m7[1]))
13397
13715
  return m7[1];
@@ -13416,7 +13734,7 @@ function resolveProjectPath(slug) {
13416
13734
  const innerScript = join15(sharedRoot(), "loop", `run-${slug}-inner.sh`);
13417
13735
  if (existsSync17(innerScript)) {
13418
13736
  try {
13419
- const text = readFileSync15(innerScript, "utf8");
13737
+ const text = readFileSync16(innerScript, "utf8");
13420
13738
  const m7 = /export ROLL_MAIN_PROJECT="([^"]+)"/.exec(text);
13421
13739
  if (m7 && m7[1] !== void 0 && isDir(m7[1]))
13422
13740
  return m7[1];
@@ -13500,7 +13818,7 @@ function loadEvents(slug, days) {
13500
13818
  for (const p of existing) {
13501
13819
  let content;
13502
13820
  try {
13503
- content = readFileSync15(p, "utf8");
13821
+ content = readFileSync16(p, "utf8");
13504
13822
  } catch {
13505
13823
  continue;
13506
13824
  }
@@ -13538,7 +13856,7 @@ function loadCronLog(slug) {
13538
13856
  const out3 = [];
13539
13857
  let content;
13540
13858
  try {
13541
- content = readFileSync15(path, "utf8");
13859
+ content = readFileSync16(path, "utf8");
13542
13860
  } catch {
13543
13861
  return [];
13544
13862
  }
@@ -13568,7 +13886,7 @@ function loadState(slug) {
13568
13886
  const out3 = {};
13569
13887
  let content;
13570
13888
  try {
13571
- content = readFileSync15(path, "utf8");
13889
+ content = readFileSync16(path, "utf8");
13572
13890
  } catch {
13573
13891
  return {};
13574
13892
  }
@@ -13590,7 +13908,7 @@ function loadBacklog(projectRoot2) {
13590
13908
  const pat = /^\|\s*(?:\[)?([A-Z]+-\d+)(?:\]\([^)]+\))?\s*\|\s*([^|]+?)\s*\|/;
13591
13909
  let content;
13592
13910
  try {
13593
- content = readFileSync15(path, "utf8");
13911
+ content = readFileSync16(path, "utf8");
13594
13912
  } catch {
13595
13913
  return {};
13596
13914
  }
@@ -13741,7 +14059,7 @@ function detectLiveCycle(rtDir, cycles, now, pidAlive = systemPidAlive) {
13741
14059
  const nowSec2 = Math.floor(now.getTime() / 1e3);
13742
14060
  let lockRaw;
13743
14061
  try {
13744
- lockRaw = readFileSync15(lockPath2, "utf8");
14062
+ lockRaw = readFileSync16(lockPath2, "utf8");
13745
14063
  } catch {
13746
14064
  return dead;
13747
14065
  }
@@ -13774,7 +14092,7 @@ function loadRuns(slug) {
13774
14092
  const out3 = {};
13775
14093
  let content;
13776
14094
  try {
13777
- content = readFileSync15(path, "utf8");
14095
+ content = readFileSync16(path, "utf8");
13778
14096
  } catch {
13779
14097
  return {};
13780
14098
  }
@@ -13992,7 +14310,7 @@ function loadClaudeSessionUsage(label4, slug) {
13992
14310
  let durationMs = null;
13993
14311
  let content;
13994
14312
  try {
13995
- content = readFileSync15(path, "utf8");
14313
+ content = readFileSync16(path, "utf8");
13996
14314
  } catch {
13997
14315
  return null;
13998
14316
  }
@@ -14168,7 +14486,7 @@ function selfScoreSummaryLine(notesDir = ".roll/notes", windowN = 14, featuresDi
14168
14486
  let verdict = null;
14169
14487
  let content;
14170
14488
  try {
14171
- content = readFileSync15(f.path, "utf8");
14489
+ content = readFileSync16(f.path, "utf8");
14172
14490
  } catch {
14173
14491
  content = "";
14174
14492
  }
@@ -14398,7 +14716,7 @@ function readDailyPlistSchedule(svc) {
14398
14716
  return null;
14399
14717
  let text;
14400
14718
  try {
14401
- text = readFileSync15(plist, "utf8");
14719
+ text = readFileSync16(plist, "utf8");
14402
14720
  } catch {
14403
14721
  return null;
14404
14722
  }
@@ -14451,7 +14769,7 @@ function tickAgeLine(loopType, now) {
14451
14769
  return null;
14452
14770
  let lastLine;
14453
14771
  try {
14454
- const lines2 = readFileSync15(tickFile, "utf8").trim().split("\n");
14772
+ const lines2 = readFileSync16(tickFile, "utf8").trim().split("\n");
14455
14773
  lastLine = lines2[lines2.length - 1] ?? "";
14456
14774
  if (lastLine === "")
14457
14775
  return null;
@@ -14483,7 +14801,7 @@ function readLoopPlistSchedule() {
14483
14801
  return null;
14484
14802
  let text;
14485
14803
  try {
14486
- text = readFileSync15(plist, "utf8");
14804
+ text = readFileSync16(plist, "utf8");
14487
14805
  } catch {
14488
14806
  return null;
14489
14807
  }
@@ -14506,7 +14824,7 @@ function lastLoopFireEpochSec() {
14506
14824
  if (!existsSync17(cronLog))
14507
14825
  return null;
14508
14826
  try {
14509
- const lines2 = readFileSync15(cronLog, "utf8").trim().split("\n").reverse();
14827
+ const lines2 = readFileSync16(cronLog, "utf8").trim().split("\n").reverse();
14510
14828
  for (const line of lines2) {
14511
14829
  if (!line.includes("cycle start"))
14512
14830
  continue;
@@ -15312,7 +15630,7 @@ function pairStatus(rest) {
15312
15630
  const NC = noColor2 ? "" : "\x1B[0m";
15313
15631
  let view;
15314
15632
  try {
15315
- view = pairingPoolView(agentsInstalled(realAgentEnv()), parsePairingConfig(readFileSync16(path, "utf8")));
15633
+ view = pairingPoolView(agentsInstalled(realAgentEnv()), parsePairingConfig(readFileSync17(path, "utf8")));
15316
15634
  } catch (e) {
15317
15635
  process.stderr.write(`[roll] pairing.yaml invalid: ${e.message}
15318
15636
  `);
@@ -15354,7 +15672,7 @@ function pairingActivitySummary() {
15354
15672
  for (const p of pairingEventFiles()) {
15355
15673
  let content;
15356
15674
  try {
15357
- content = readFileSync16(p, "utf8");
15675
+ content = readFileSync17(p, "utf8");
15358
15676
  } catch {
15359
15677
  continue;
15360
15678
  }
@@ -15408,13 +15726,13 @@ function resolveSummary(storyId, summaryFlag, fileFlag) {
15408
15726
  return summaryFlag.trim();
15409
15727
  if (fileFlag !== void 0) {
15410
15728
  try {
15411
- return readFileSync16(fileFlag, "utf8").trim() || null;
15729
+ return readFileSync17(fileFlag, "utf8").trim() || null;
15412
15730
  } catch {
15413
15731
  return null;
15414
15732
  }
15415
15733
  }
15416
15734
  try {
15417
- const backlog = readFileSync16(join16(process.cwd(), ".roll", "backlog.md"), "utf8");
15735
+ const backlog = readFileSync17(join16(process.cwd(), ".roll", "backlog.md"), "utf8");
15418
15736
  const idRe = new RegExp(`${storyId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9])`);
15419
15737
  const row2 = backlog.split("\n").find((l) => idRe.test(l));
15420
15738
  return row2 !== void 0 ? `Story ${storyId} \u2014 backlog row:
@@ -15505,7 +15823,7 @@ ${HELP}`);
15505
15823
  // packages/cli/dist/commands/alert.js
15506
15824
  init_dist2();
15507
15825
  init_dist();
15508
- 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";
15509
15827
  import { homedir as homedir5 } from "node:os";
15510
15828
  import { join as join17 } from "node:path";
15511
15829
  function palette() {
@@ -15568,7 +15886,7 @@ function renderLog(n, p, lang9) {
15568
15886
  let body = "";
15569
15887
  try {
15570
15888
  if (existsSync19(file) && statSync9(file).size > 0)
15571
- body = readFileSync17(file, "utf8");
15889
+ body = readFileSync18(file, "utf8");
15572
15890
  } catch {
15573
15891
  body = "";
15574
15892
  }
@@ -15599,7 +15917,7 @@ function alertCommand(args) {
15599
15917
  const rest = args.slice(1);
15600
15918
  const file = loopAlertPath();
15601
15919
  const fileExists = existsSync19(file);
15602
- const contents = fileExists ? readFileSync17(file, "utf8") : "";
15920
+ const contents = fileExists ? readFileSync18(file, "utf8") : "";
15603
15921
  const ts = nowStamp();
15604
15922
  const action = alertConsumeAction(subcmd, fileExists, contents, ts, rest[0]);
15605
15923
  switch (action.kind) {
@@ -15660,7 +15978,7 @@ function ackMessage(lang9, ts) {
15660
15978
  init_dist2();
15661
15979
  init_dist();
15662
15980
  init_dist3();
15663
- 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";
15664
15982
  import { execFile as execFile8, execFileSync as execFileSync5 } from "node:child_process";
15665
15983
  import { basename as basename8, join as join33, relative as relative4 } from "node:path";
15666
15984
  import { promisify as promisify8 } from "node:util";
@@ -15670,7 +15988,7 @@ init_dist2();
15670
15988
  init_dist();
15671
15989
  init_dist2();
15672
15990
  init_dist();
15673
- 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";
15674
15992
  import { join as join32 } from "node:path";
15675
15993
 
15676
15994
  // packages/cli/dist/lib/dossier-index.js
@@ -16154,7 +16472,7 @@ function copyChip(cmd) {
16154
16472
  }
16155
16473
  function cycleRow2(cy) {
16156
16474
  const color = VERDICT_COLORS[cy.verdict] ?? C.slate;
16157
- const n = cy.cycleId.slice(-6);
16475
+ const n = cycleHandle(cy.cycleId);
16158
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).
16159
16477
  copyChip(`roll cycle ${cycleHandle(cy.cycleId)}`) + `</div></div></details>`;
16160
16478
  }
@@ -16190,6 +16508,7 @@ function hooksPanel(input) {
16190
16508
  }
16191
16509
  function loopTab(input) {
16192
16510
  const ranges = [
16511
+ ["recent", "Recent", "\u8FD1\u671F"],
16193
16512
  ["1", "Today", "\u4ECA\u5929"],
16194
16513
  ["3", "3 days", "\u4E09\u5929"],
16195
16514
  ["7", "7 days", "\u4E03\u5929"],
@@ -16199,7 +16518,7 @@ function loopTab(input) {
16199
16518
  // project-scoped commit-hooks panel + the Cycle ledger. The inline agents
16200
16519
  // panel (now the machine Agents page) and the inline casting ladder (now its
16201
16520
  // own Casting tab) are NOT rendered here.
16202
- 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>`;
16203
16522
  }
16204
16523
  var TYPE_COLORS = { US: C.blue, FIX: C.red, REFACTOR: C.purple, IDEA: C.amber };
16205
16524
  function typeBadge(type) {
@@ -16491,26 +16810,38 @@ var CONSOLE_SCRIPT = `<script>
16491
16810
  chips[c].classList.toggle("on", !!active[chips[c].getAttribute("data-filter")]);
16492
16811
  }
16493
16812
  }
16494
- // 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;
16495
16822
  function applyRange(range) {
16496
16823
  var rows = document.querySelectorAll(".cy-row");
16497
16824
  var nowSec = Math.floor(Date.now() / 1000);
16498
- var horizon = range === "all" ? Infinity : Number(range) * 86400;
16499
- var count = 0, failed = 0;
16825
+ var horizon = range === "all" || range === "recent" ? Infinity : Number(range) * 86400;
16826
+ var shown = 0, failedAll = 0, kept = 0;
16500
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
16501
16831
  var ts = Number(rows[i].getAttribute("data-ts")) || 0;
16502
- 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;
16503
16837
  rows[i].style.display = show ? "" : "none";
16504
- if (show) {
16505
- count++;
16506
- var v = rows[i].getAttribute("data-verdict");
16507
- if (v === "failed" || v === "reverted" || v === "blocked") failed++;
16508
- }
16838
+ if (show) shown++;
16839
+ if (inWindow) kept++; // the cap counts non-fail slots; failures are bonus
16509
16840
  }
16510
16841
  var c = document.getElementById("cy-count");
16511
16842
  var f = document.getElementById("cy-failed");
16512
- if (c) c.textContent = String(count);
16513
- if (f) f.textContent = String(failed);
16843
+ if (c) c.textContent = String(shown);
16844
+ if (f) f.textContent = String(failedAll);
16514
16845
  var btns = document.querySelectorAll(".cy-range");
16515
16846
  for (var b = 0; b < btns.length; b++) btns[b].classList.toggle("on", btns[b].getAttribute("data-range") === range);
16516
16847
  }
@@ -16612,7 +16943,7 @@ var CONSOLE_SCRIPT = `<script>
16612
16943
  for (var rb = 0; rb < rbs.length; rb++) {
16613
16944
  rbs[rb].addEventListener("click", function () { applyRange(this.getAttribute("data-range")); });
16614
16945
  }
16615
- applyRange("3");
16946
+ applyRange("recent");
16616
16947
  applyFreshness();
16617
16948
  tickCountdown();
16618
16949
  setInterval(tickCountdown, 30000);
@@ -16789,7 +17120,7 @@ ${CONSOLE_SCRIPT}
16789
17120
  }
16790
17121
 
16791
17122
  // packages/cli/dist/lib/page-charter.js
16792
- 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";
16793
17124
  import { join as join18 } from "node:path";
16794
17125
  function docTitle(src, path) {
16795
17126
  for (const raw of src.split("\n")) {
@@ -16858,7 +17189,7 @@ function defaultCharterDeps(cwd, render2) {
16858
17189
  if (!existsSync20(abs))
16859
17190
  return void 0;
16860
17191
  try {
16861
- return readFileSync18(abs, "utf8");
17192
+ return readFileSync19(abs, "utf8");
16862
17193
  } catch {
16863
17194
  return void 0;
16864
17195
  }
@@ -17044,7 +17375,7 @@ function renderAboutPage(input) {
17044
17375
  // packages/cli/dist/lib/page-conventions.js
17045
17376
  init_dist3();
17046
17377
  init_dist2();
17047
- import { existsSync as existsSync22, readFileSync as readFileSync19 } from "node:fs";
17378
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "node:fs";
17048
17379
  import { join as join20 } from "node:path";
17049
17380
  var SYNC_KEYS = [
17050
17381
  { key: "sync_claude", agent: "claude" },
@@ -17082,7 +17413,7 @@ function defaultConventionsDeps(cwd, agents, render2) {
17082
17413
  if (!existsSync22(abs))
17083
17414
  return void 0;
17084
17415
  try {
17085
- return readFileSync19(abs, "utf8");
17416
+ return readFileSync20(abs, "utf8");
17086
17417
  } catch {
17087
17418
  return void 0;
17088
17419
  }
@@ -17127,7 +17458,7 @@ function renderConventionsPage(input) {
17127
17458
  }
17128
17459
 
17129
17460
  // packages/cli/dist/lib/projects-registry.js
17130
- 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";
17131
17462
  import { homedir as homedir6, tmpdir } from "node:os";
17132
17463
  import { dirname as dirname11, join as join21, sep } from "node:path";
17133
17464
  function registryHome(home) {
@@ -17166,7 +17497,7 @@ function parseProjectsRegistry(text) {
17166
17497
  function collectProjectsRegistry(home) {
17167
17498
  let text;
17168
17499
  try {
17169
- text = readFileSync20(projectsRegistryPath(home), "utf8");
17500
+ text = readFileSync21(projectsRegistryPath(home), "utf8");
17170
17501
  } catch {
17171
17502
  return [];
17172
17503
  }
@@ -17217,7 +17548,7 @@ function writeProjectRow(entry, home) {
17217
17548
 
17218
17549
  // packages/cli/dist/lib/cycle-ledger.js
17219
17550
  init_dist();
17220
- import { existsSync as existsSync24, readFileSync as readFileSync21 } from "node:fs";
17551
+ import { existsSync as existsSync24, readFileSync as readFileSync22 } from "node:fs";
17221
17552
  import { join as join22 } from "node:path";
17222
17553
  function ledgerVerdict(status2, outcome) {
17223
17554
  if (status2 === "reverted")
@@ -17236,6 +17567,20 @@ function ledgerVerdict(status2, outcome) {
17236
17567
  function ledgerFailedCount(rows) {
17237
17568
  return rows.filter((r) => r.verdict === "failed" || r.verdict === "reverted" || r.verdict === "blocked").length;
17238
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
+ }
17239
17584
  function fmtTokens2(tin, tout, usageUnknown) {
17240
17585
  if (usageUnknown)
17241
17586
  return "?";
@@ -17262,7 +17607,7 @@ function readEventFacts(projectPath3) {
17262
17607
  return { byCycle, prMergedBy, prOpenBy };
17263
17608
  let content = "";
17264
17609
  try {
17265
- content = readFileSync21(path, "utf8");
17610
+ content = readFileSync22(path, "utf8");
17266
17611
  } catch {
17267
17612
  return { byCycle, prMergedBy, prOpenBy };
17268
17613
  }
@@ -17317,7 +17662,7 @@ function collectCycleLedger(projectPath3) {
17317
17662
  return [];
17318
17663
  let content = "";
17319
17664
  try {
17320
- content = readFileSync21(runsPath3, "utf8");
17665
+ content = readFileSync22(runsPath3, "utf8");
17321
17666
  } catch {
17322
17667
  return [];
17323
17668
  }
@@ -17338,6 +17683,8 @@ function collectCycleLedger(projectPath3) {
17338
17683
  const status2 = String(row2["status"] ?? "");
17339
17684
  const outcome = String(row2["outcome"] ?? "");
17340
17685
  const verdict = ledgerVerdict(status2, outcome);
17686
+ if (isIdleHeartbeat(row2, verdict))
17687
+ continue;
17341
17688
  const storyId = typeof row2["story_id"] === "string" ? row2["story_id"] : "";
17342
17689
  const rawTs = row2["ts"];
17343
17690
  const ts = typeof rawTs === "string" ? Date.parse(rawTs) : typeof rawTs === "number" ? rawTs > 1e10 ? rawTs : rawTs * 1e3 : Number.NaN;
@@ -17373,14 +17720,14 @@ function collectCycleLedger(projectPath3) {
17373
17720
 
17374
17721
  // packages/cli/dist/lib/agent-panel.js
17375
17722
  init_dist2();
17376
- import { existsSync as existsSync26, readFileSync as readFileSync23 } from "node:fs";
17723
+ import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
17377
17724
  import { join as join24 } from "node:path";
17378
17725
 
17379
17726
  // packages/cli/dist/commands/status.js
17380
17727
  init_dist();
17381
17728
  import { execFileSync as execFileSync3 } from "node:child_process";
17382
17729
  import { createHash as createHash4 } from "node:crypto";
17383
- 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";
17384
17731
  import { homedir as homedir7 } from "node:os";
17385
17732
  import { basename as basename7, dirname as dirname12, join as join23 } from "node:path";
17386
17733
  function rollHome2() {
@@ -17414,7 +17761,7 @@ function parseAiEntries() {
17414
17761
  if (!existsSync25(cfg))
17415
17762
  return [];
17416
17763
  const entries = [];
17417
- for (const line of readFileSync22(cfg, "utf8").split("\n")) {
17764
+ for (const line of readFileSync23(cfg, "utf8").split("\n")) {
17418
17765
  const m7 = /^ai_[a-z]+:\s*(.+)/.exec(line);
17419
17766
  if (m7 === null)
17420
17767
  continue;
@@ -17442,13 +17789,13 @@ function aiSyncStatus(e) {
17442
17789
  if (!existsSync25(rollMd))
17443
17790
  return "out-of-sync";
17444
17791
  try {
17445
- if (existsSync25(src) && !readFileSync22(rollMd).equals(readFileSync22(src)))
17792
+ if (existsSync25(src) && !readFileSync23(rollMd).equals(readFileSync23(src)))
17446
17793
  return "out-of-sync";
17447
17794
  } catch {
17448
17795
  return "out-of-sync";
17449
17796
  }
17450
17797
  try {
17451
- if (!readFileSync22(cfgFile, "utf8").includes("@roll.md"))
17798
+ if (!readFileSync23(cfgFile, "utf8").includes("@roll.md"))
17452
17799
  return "out-of-sync";
17453
17800
  } catch {
17454
17801
  return "out-of-sync";
@@ -17857,7 +18204,7 @@ function spend72h(projectPath3, nowSec2) {
17857
18204
  return out3;
17858
18205
  let content = "";
17859
18206
  try {
17860
- content = readFileSync23(path, "utf8");
18207
+ content = readFileSync24(path, "utf8");
17861
18208
  } catch {
17862
18209
  return out3;
17863
18210
  }
@@ -17926,7 +18273,7 @@ function collectAgentPanel(projectPath3, deps = defaultAgentPanelDeps()) {
17926
18273
  // packages/cli/dist/lib/release-panel.js
17927
18274
  init_dist2();
17928
18275
  init_dist();
17929
- 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";
17930
18277
  import { join as join25 } from "node:path";
17931
18278
  function collectReleasePanel(projectPath3) {
17932
18279
  const dimsEmpty = CONSISTENCY_DIMENSIONS.map((key) => ({
@@ -17938,7 +18285,7 @@ function collectReleasePanel(projectPath3) {
17938
18285
  try {
17939
18286
  const latest = readdirSync10(dir).filter((f) => f.endsWith(".json")).sort().at(-1);
17940
18287
  if (latest !== void 0) {
17941
- const obj = JSON.parse(readFileSync24(join25(dir, latest), "utf8"));
18288
+ const obj = JSON.parse(readFileSync25(join25(dir, latest), "utf8"));
17942
18289
  const findings = Array.isArray(obj.findings) ? obj.findings : [];
17943
18290
  const tallies = tallyByDimension(findings);
17944
18291
  out3.dims = CONSISTENCY_DIMENSIONS.map((key) => ({ key, tally: tallies[key] }));
@@ -17957,7 +18304,7 @@ function collectReleasePanel(projectPath3) {
17957
18304
  const path = join25(projectPath3, ".roll", "loop", "events.ndjson");
17958
18305
  if (existsSync27(path)) {
17959
18306
  const tags = [];
17960
- for (const line of readFileSync24(path, "utf8").split("\n")) {
18307
+ for (const line of readFileSync25(path, "utf8").split("\n")) {
17961
18308
  const e = parseEventLine(line);
17962
18309
  if (e !== null && e.type === "release:gate" && typeof e.tag === "string" && e.tag !== "" && tags.at(-1) !== e.tag) {
17963
18310
  tags.push(e.tag);
@@ -17973,7 +18320,7 @@ function collectReleasePanel(projectPath3) {
17973
18320
 
17974
18321
  // packages/cli/dist/lib/release-scope.js
17975
18322
  init_dist();
17976
- import { existsSync as existsSync28, readFileSync as readFileSync25 } from "node:fs";
18323
+ import { existsSync as existsSync28, readFileSync as readFileSync26 } from "node:fs";
17977
18324
  import { join as join26 } from "node:path";
17978
18325
  function groupByEpic(items) {
17979
18326
  const map = /* @__PURE__ */ new Map();
@@ -17990,7 +18337,7 @@ function prFromEvents(projectPath3) {
17990
18337
  if (!existsSync28(path))
17991
18338
  return out3;
17992
18339
  try {
17993
- for (const line of readFileSync25(path, "utf8").split("\n")) {
18340
+ for (const line of readFileSync26(path, "utf8").split("\n")) {
17994
18341
  const e = parseEventLine(line);
17995
18342
  if (e !== null && e.type === "pr:merge")
17996
18343
  out3.set(e.storyId, e.prNumber);
@@ -18027,7 +18374,7 @@ function collectHistory(projectPath3) {
18027
18374
  return [];
18028
18375
  let text = "";
18029
18376
  try {
18030
- text = readFileSync25(path, "utf8");
18377
+ text = readFileSync26(path, "utf8");
18031
18378
  } catch {
18032
18379
  return [];
18033
18380
  }
@@ -18035,7 +18382,7 @@ function collectHistory(projectPath3) {
18035
18382
  try {
18036
18383
  const ev = join26(projectPath3, ".roll", "loop", "events.ndjson");
18037
18384
  if (existsSync28(ev)) {
18038
- for (const line of readFileSync25(ev, "utf8").split("\n")) {
18385
+ for (const line of readFileSync26(ev, "utf8").split("\n")) {
18039
18386
  const e = parseEventLine(line);
18040
18387
  if (e !== null && e.type === "release:gate" && Array.isArray(e.waivedRules) && e.waivedRules.length > 0) {
18041
18388
  waivedTags.add(e.tag);
@@ -18068,11 +18415,11 @@ function collectHistory(projectPath3) {
18068
18415
  }
18069
18416
 
18070
18417
  // packages/cli/dist/lib/skills-panel.js
18071
- 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";
18072
18419
  import { join as join28 } from "node:path";
18073
18420
 
18074
18421
  // packages/cli/dist/lib/skills-audit.js
18075
- 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";
18076
18423
  import { join as join27, relative as relative3, sep as sep2 } from "node:path";
18077
18424
  function stripYamlQuotes(value) {
18078
18425
  const trimmed = value.trim();
@@ -18154,7 +18501,7 @@ function collectReferencedSpokes(body) {
18154
18501
  return [...refs].sort();
18155
18502
  }
18156
18503
  function parseSkillFile(file) {
18157
- const text = readFileSync26(file, "utf8");
18504
+ const text = readFileSync27(file, "utf8");
18158
18505
  const { fields, body, ok: ok8 } = parseFrontmatter(text);
18159
18506
  const skillDir = join27(file, "..");
18160
18507
  const description = fields["description"] ?? "";
@@ -18187,7 +18534,7 @@ function loadRouteCases(routeFile) {
18187
18534
  if (!existsSync29(routeFile))
18188
18535
  return { skills: {} };
18189
18536
  try {
18190
- return JSON.parse(readFileSync26(routeFile, "utf8"));
18537
+ return JSON.parse(readFileSync27(routeFile, "utf8"));
18191
18538
  } catch {
18192
18539
  return { skills: {} };
18193
18540
  }
@@ -18291,7 +18638,7 @@ function skillGroupOf(name) {
18291
18638
  }
18292
18639
  function lineCount(path) {
18293
18640
  try {
18294
- return readFileSync27(path, "utf8").split("\n").length;
18641
+ return readFileSync28(path, "utf8").split("\n").length;
18295
18642
  } catch {
18296
18643
  return 0;
18297
18644
  }
@@ -18355,7 +18702,7 @@ function collectSkillsPanel(projectPath3, deps = defaultSkillsPanelDeps(projectP
18355
18702
  usage: usage2[name] ?? 0,
18356
18703
  files: skillFiles(dir),
18357
18704
  dirPath: dir,
18358
- hubText: existsSync30(hubPath) ? readFileSync27(hubPath, "utf8") : ""
18705
+ hubText: existsSync30(hubPath) ? readFileSync28(hubPath, "utf8") : ""
18359
18706
  };
18360
18707
  });
18361
18708
  const hubLines = rows.reduce((acc, r) => acc + r.hubLines, 0);
@@ -18528,7 +18875,7 @@ function renderSkillsPage(input) {
18528
18875
  }
18529
18876
 
18530
18877
  // packages/cli/dist/lib/loop-heartbeat.js
18531
- import { existsSync as existsSync31, readFileSync as readFileSync28 } from "node:fs";
18878
+ import { existsSync as existsSync31, readFileSync as readFileSync29 } from "node:fs";
18532
18879
  import { join as join29 } from "node:path";
18533
18880
  var LANES = ["loop", "dream"];
18534
18881
  function defaultHeartbeatDeps(projectPath3, slug, launchAgentsDir3) {
@@ -18536,14 +18883,14 @@ function defaultHeartbeatDeps(projectPath3, slug, launchAgentsDir3) {
18536
18883
  plistText: (svc) => {
18537
18884
  const p = join29(launchAgentsDir3, `com.roll.${svc}.${slug}.plist`);
18538
18885
  try {
18539
- return existsSync31(p) ? readFileSync28(p, "utf8") : null;
18886
+ return existsSync31(p) ? readFileSync29(p, "utf8") : null;
18540
18887
  } catch {
18541
18888
  return null;
18542
18889
  }
18543
18890
  },
18544
18891
  lastRunAt: () => {
18545
18892
  try {
18546
- 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");
18547
18894
  for (let i = lines2.length - 1; i >= 0; i--) {
18548
18895
  const line = lines2[i] ?? "";
18549
18896
  if (line.trim() === "")
@@ -18600,7 +18947,7 @@ function collectLoopHeartbeat(deps) {
18600
18947
 
18601
18948
  // packages/cli/dist/lib/casting.js
18602
18949
  init_dist2();
18603
- import { existsSync as existsSync32, readFileSync as readFileSync29 } from "node:fs";
18950
+ import { existsSync as existsSync32, readFileSync as readFileSync30 } from "node:fs";
18604
18951
  import { join as join30 } from "node:path";
18605
18952
  var EM_DASH = "\u2014";
18606
18953
  function slotRow(key, roleEn, roleZh, deps) {
@@ -18689,7 +19036,7 @@ function defaultCastingDeps(projectPath3) {
18689
19036
  const agentsPath2 = process.env["ROLL_AGENTS_CONFIG"] ?? join30(projectPath3, ".roll", "agents.yaml");
18690
19037
  try {
18691
19038
  if (existsSync32(agentsPath2))
18692
- agentsText = readFileSync29(agentsPath2, "utf8");
19039
+ agentsText = readFileSync30(agentsPath2, "utf8");
18693
19040
  } catch {
18694
19041
  agentsText = null;
18695
19042
  }
@@ -18697,7 +19044,7 @@ function defaultCastingDeps(projectPath3) {
18697
19044
  try {
18698
19045
  const evPath = join30(projectPath3, ".roll", "loop", "events.ndjson");
18699
19046
  if (existsSync32(evPath)) {
18700
- const content = readFileSync29(evPath, "utf8");
19047
+ const content = readFileSync30(evPath, "utf8");
18701
19048
  for (const line of content.split("\n")) {
18702
19049
  const t2 = line.trim();
18703
19050
  if (t2 === "" || !t2.includes("route:resolve"))
@@ -18736,7 +19083,7 @@ init_dist2();
18736
19083
  init_dist();
18737
19084
  import { createHash as createHash5 } from "node:crypto";
18738
19085
  import { spawn as spawn2, spawnSync } from "node:child_process";
18739
- 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";
18740
19087
  import { homedir as homedir8 } from "node:os";
18741
19088
  import { dirname as dirname13, join as join31 } from "node:path";
18742
19089
  function realDeps2() {
@@ -19011,7 +19358,7 @@ function syncGoalPaused(projectPath3, reason) {
19011
19358
  if (!existsSync33(path))
19012
19359
  return;
19013
19360
  try {
19014
- const before = parseGoalYaml(readFileSync30(path, "utf8"));
19361
+ const before = parseGoalYaml(readFileSync31(path, "utf8"));
19015
19362
  if (before.status === "paused" || before.status === "complete")
19016
19363
  return;
19017
19364
  const at = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -19053,7 +19400,7 @@ async function loopOnCommand(_args, deps = realDeps2()) {
19053
19400
  const localYaml = join31(id.path, ".roll", "local.yaml");
19054
19401
  if (existsSync33(localYaml)) {
19055
19402
  try {
19056
- period = parseLoopPeriodMinutes(readFileSync30(localYaml, "utf8"));
19403
+ period = parseLoopPeriodMinutes(readFileSync31(localYaml, "utf8"));
19057
19404
  } catch {
19058
19405
  }
19059
19406
  }
@@ -19202,7 +19549,7 @@ async function loopResumeCommand(_args, deps = realDeps2()) {
19202
19549
  const stateFile2 = join31(rt, `state-${id.slug}.yaml`);
19203
19550
  if (existsSync33(stateFile2)) {
19204
19551
  try {
19205
- const body = readFileSync30(stateFile2, "utf8");
19552
+ const body = readFileSync31(stateFile2, "utf8");
19206
19553
  const lines2 = body.split("\n").filter((l) => /^(?!heal_count_head_)/.test(l));
19207
19554
  writeFileSync14(stateFile2, lines2.join("\n"), "utf8");
19208
19555
  } catch {
@@ -19245,7 +19592,7 @@ async function loopNowCommand(_args, deps = realDeps2()) {
19245
19592
  let legacy = false;
19246
19593
  if (existsSync33(runner)) {
19247
19594
  try {
19248
- legacy = isLegacyRunner(readFileSync30(runner, "utf8"));
19595
+ legacy = isLegacyRunner(readFileSync31(runner, "utf8"));
19249
19596
  } catch {
19250
19597
  legacy = true;
19251
19598
  }
@@ -19405,7 +19752,7 @@ ${CHROME_CONTROLS}
19405
19752
  init_dist2();
19406
19753
  init_dist();
19407
19754
  import { execFileSync as execFileSync4 } from "node:child_process";
19408
- 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";
19409
19756
  import { join as joinPath2 } from "node:path";
19410
19757
  var esc6 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19411
19758
  function storyDeliveryState(d) {
@@ -20798,7 +21145,7 @@ function rebaseAcEvidenceToStoryRoot(ev) {
20798
21145
  return ev.map((e) => e.href !== void 0 ? { ...e, href: rebaseEvidenceHrefToStoryRoot(e.href) } : e);
20799
21146
  }
20800
21147
  function readFile(p) {
20801
- return readFileSync31(p, "utf8");
21148
+ return readFileSync32(p, "utf8");
20802
21149
  }
20803
21150
  function statDir(p) {
20804
21151
  return statSync13(p).isFile();
@@ -20823,7 +21170,7 @@ function renderNowSec() {
20823
21170
  function readJsonl(path) {
20824
21171
  let content;
20825
21172
  try {
20826
- content = readFileSync32(path, "utf8");
21173
+ content = readFileSync33(path, "utf8");
20827
21174
  } catch {
20828
21175
  return void 0;
20829
21176
  }
@@ -20866,7 +21213,7 @@ function latestConsistencyAudit(projectPath3) {
20866
21213
  if (latest === void 0)
20867
21214
  return void 0;
20868
21215
  try {
20869
- const obj = JSON.parse(readFileSync32(join32(dir, latest), "utf8"));
21216
+ const obj = JSON.parse(readFileSync33(join32(dir, latest), "utf8"));
20870
21217
  const summary = obj["summary"];
20871
21218
  if (typeof summary !== "object" || summary === null || Array.isArray(summary))
20872
21219
  return void 0;
@@ -20923,7 +21270,7 @@ function releaseTruthBoard(projectPath3, nowSec2) {
20923
21270
  const path = join32(projectPath3, ".roll", "loop", "events.ndjson");
20924
21271
  let content;
20925
21272
  try {
20926
- content = readFileSync32(path, "utf8");
21273
+ content = readFileSync33(path, "utf8");
20927
21274
  } catch {
20928
21275
  return void 0;
20929
21276
  }
@@ -20989,7 +21336,7 @@ function renderSpecHtml(storyDir, id) {
20989
21336
  return null;
20990
21337
  let md;
20991
21338
  try {
20992
- md = readFileSync32(specPath, "utf8");
21339
+ md = readFileSync33(specPath, "utf8");
20993
21340
  } catch {
20994
21341
  return null;
20995
21342
  }
@@ -21250,7 +21597,7 @@ function defaultProcessReaders(projectPath3, env) {
21250
21597
  if (!existsSync36(p))
21251
21598
  return null;
21252
21599
  try {
21253
- return readFileSync33(p, "utf8");
21600
+ return readFileSync34(p, "utf8");
21254
21601
  } catch {
21255
21602
  return null;
21256
21603
  }
@@ -21477,7 +21824,7 @@ function readAcMap(storyDir) {
21477
21824
  if (!existsSync36(p))
21478
21825
  return null;
21479
21826
  try {
21480
- const arr2 = JSON.parse(readFileSync33(p, "utf8"));
21827
+ const arr2 = JSON.parse(readFileSync34(p, "utf8"));
21481
21828
  if (!Array.isArray(arr2))
21482
21829
  return null;
21483
21830
  const m7 = /* @__PURE__ */ new Map();
@@ -21498,7 +21845,7 @@ function toRef(runDir, e) {
21498
21845
  if (!existsSync36(p))
21499
21846
  return null;
21500
21847
  try {
21501
- const { redacted, hits } = redactSecrets(readFileSync33(p, "utf8"));
21848
+ const { redacted, hits } = redactSecrets(readFileSync34(p, "utf8"));
21502
21849
  if (hits.length > 0)
21503
21850
  warn2(`redacted secret(s) in ${e.textFile}: ${hits.join(", ")}`);
21504
21851
  return { kind, label: label4, inlineHtml: ansiPre(redacted) };
@@ -21517,7 +21864,7 @@ function toRef(runDir, e) {
21517
21864
  return null;
21518
21865
  }
21519
21866
  if (kind === "cast") {
21520
- const { redacted, hits } = redactSecrets(readFileSync33(p, "utf8"));
21867
+ const { redacted, hits } = redactSecrets(readFileSync34(p, "utf8"));
21521
21868
  if (hits.length > 0)
21522
21869
  warn2(`redacted secret(s) in ${e.href}: ${hits.join(", ")}`);
21523
21870
  const bounded = boundTranscript(redacted);
@@ -21557,7 +21904,7 @@ function readBacklogRow(projectPath3, storyId) {
21557
21904
  return {};
21558
21905
  let text;
21559
21906
  try {
21560
- text = readFileSync33(p, "utf8");
21907
+ text = readFileSync34(p, "utf8");
21561
21908
  } catch {
21562
21909
  return {};
21563
21910
  }
@@ -21595,7 +21942,7 @@ function buildCardContext(projectPath3, featureFile, storyId, env) {
21595
21942
  const oneLiner = row2.description !== void 0 ? row2.description.replace(/\s*depends-on:\S+/gi, "").trim() : void 0;
21596
21943
  let summary;
21597
21944
  try {
21598
- summary = extractSummary(readFileSync33(featureFile, "utf8"));
21945
+ summary = extractSummary(readFileSync34(featureFile, "utf8"));
21599
21946
  } catch {
21600
21947
  }
21601
21948
  const epic = epicFromFeaturePath(featureFile);
@@ -21714,7 +22061,7 @@ function ensurePreEvidenceMarker(storyId, runDir) {
21714
22061
  function resolveStoryAcItems(projectPath3, storyId) {
21715
22062
  for (const cand of findFeatureFiles(projectPath3, storyId)) {
21716
22063
  try {
21717
- const items = acForStory(readFileSync33(cand, "utf8"), storyId, {
22064
+ const items = acForStory(readFileSync34(cand, "utf8"), storyId, {
21718
22065
  fileOwned: basename8(cand) === `${storyId}.md`
21719
22066
  });
21720
22067
  if (items.length > 0)
@@ -21755,7 +22102,7 @@ async function attestBackfillCommand(args, deps) {
21755
22102
  return 1;
21756
22103
  }
21757
22104
  const stats = { scanned: 0, backfilled: 0, skippedExisting: 0, skippedMissingCard: 0, failed: 0 };
21758
- 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");
21759
22106
  for (const item of items) {
21760
22107
  stats.scanned += 1;
21761
22108
  const featureFile = findFeatureFile(projectPath3, item.id);
@@ -21944,7 +22291,7 @@ async function attestCommand(args, deps = {}) {
21944
22291
  const deliveryHtml = `<p><a href="${reportRel}">${bi("Attestation report", "\u9A8C\u6536\u62A5\u544A")}</a></p>
21945
22292
  ` + dossierVisualsHtml(runRel, beforeAfter, afterOnly) + `<p class="muted">${bi("Delivered", "\u4EA4\u4ED8\u4E8E")} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}</p>
21946
22293
  `;
21947
- const idx = markPhaseDone(readFileSync33(indexPath, "utf8"), "delivery", deliveryHtml);
22294
+ const idx = markPhaseDone(readFileSync34(indexPath, "utf8"), "delivery", deliveryHtml);
21948
22295
  writeFileSync16(indexPath, idx, "utf8");
21949
22296
  } catch {
21950
22297
  }
@@ -21973,12 +22320,12 @@ async function attestCommand(args, deps = {}) {
21973
22320
 
21974
22321
  // packages/cli/dist/commands/backlog.js
21975
22322
  init_dist();
21976
- import { existsSync as existsSync37, readFileSync as readFileSync34 } from "node:fs";
22323
+ import { existsSync as existsSync37, readFileSync as readFileSync35 } from "node:fs";
21977
22324
  var ID_RE = /\[([^\]]+)\]\([^)]+\)/;
21978
22325
  var REASON_RE = /\[([^\]]+)\]/;
21979
22326
  function parseBacklog2(path) {
21980
22327
  const items = [];
21981
- for (const raw of readFileSync34(path, "utf8").split("\n")) {
22328
+ for (const raw of readFileSync35(path, "utf8").split("\n")) {
21982
22329
  const line = raw.replace(/\n$/, "");
21983
22330
  if (!line.startsWith("|"))
21984
22331
  continue;
@@ -22100,7 +22447,7 @@ function backlogCommand(args) {
22100
22447
  // packages/cli/dist/commands/backlog-mgmt.js
22101
22448
  init_dist2();
22102
22449
  init_dist();
22103
- 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";
22104
22451
  import { dirname as dirname14, join as join34 } from "node:path";
22105
22452
  var BACKLOG_PATH = ".roll/backlog.md";
22106
22453
  function lang() {
@@ -22221,8 +22568,8 @@ function backlogUnstickCommand(args, deps = realUnstickDeps()) {
22221
22568
  const slug = deps.slug();
22222
22569
  const loopDir = join34(deps.sharedRoot(), "loop");
22223
22570
  const eventsPath2 = join34(loopDir, `events-${slug}.ndjson`);
22224
- const events = existsSync38(eventsPath2) ? parseUnstickEvents(readFileSync35(eventsPath2, "utf8")) : [];
22225
- const content = readFileSync35(backlog, "utf8");
22571
+ const events = existsSync38(eventsPath2) ? parseUnstickEvents(readFileSync36(eventsPath2, "utf8")) : [];
22572
+ const content = readFileSync36(backlog, "utf8");
22226
22573
  const nowMs = deps.nowMs();
22227
22574
  const candidates = reconcileStuckBacklog(content, events, nowMs, ttlHours);
22228
22575
  if (candidates.length === 0)
@@ -22283,7 +22630,7 @@ function backlogLintCommand(args) {
22283
22630
  errLine(`[roll] backlog not found: ${backlog}`);
22284
22631
  return 1;
22285
22632
  }
22286
- const findings = lintBacklogContent(readFileSync35(backlog, "utf8"));
22633
+ const findings = lintBacklogContent(readFileSync36(backlog, "utf8"));
22287
22634
  for (const f of findings) {
22288
22635
  out(`${backlog}:${f.lineno}: ${f.sid} \u2014 ${f.issues}`);
22289
22636
  out(` ${f.desc}`);
@@ -22305,7 +22652,7 @@ function backlogLintCommand(args) {
22305
22652
  // packages/cli/dist/commands/backlog-sync.js
22306
22653
  init_dist2();
22307
22654
  import { execFileSync as execFileSync6 } from "node:child_process";
22308
- 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";
22309
22656
  import { dirname as dirname15, join as join35 } from "node:path";
22310
22657
  var API_ROOT = "https://api.github.com";
22311
22658
  var RATE_LIMIT_FLOOR = 5;
@@ -22400,7 +22747,7 @@ function realSyncDeps() {
22400
22747
  loadIssues: async (owner, repo) => {
22401
22748
  const fixture = (process.env["ROLL_SYNC_FIXTURE"] ?? "").trim();
22402
22749
  if (fixture)
22403
- return JSON.parse(readFileSync36(fixture, "utf8"));
22750
+ return JSON.parse(readFileSync37(fixture, "utf8"));
22404
22751
  return fetchIssues(owner, repo, { state: "open" });
22405
22752
  },
22406
22753
  nowIso: () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
@@ -22414,7 +22761,7 @@ async function backlogSyncCommand(args, deps = realSyncDeps()) {
22414
22761
  const backlog = flagValue(args, "--backlog") ?? ".roll/backlog.md";
22415
22762
  const featuresDir = flagValue(args, "--features") ?? ".roll/features";
22416
22763
  const localYaml = flagValue(args, "--local-yaml") ?? ".roll/local.yaml";
22417
- const cfg = existsSync39(localYaml) ? readSyncConfig(readFileSync36(localYaml, "utf8")) : {};
22764
+ const cfg = existsSync39(localYaml) ? readSyncConfig(readFileSync37(localYaml, "utf8")) : {};
22418
22765
  const repoArg = flagValue(args, "--repo") ?? cfg.repo ?? "";
22419
22766
  if (!repoArg) {
22420
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");
@@ -22457,7 +22804,7 @@ async function backlogSyncCommand(args, deps = realSyncDeps()) {
22457
22804
  `);
22458
22805
  return 1;
22459
22806
  }
22460
- const content = readFileSync36(backlog, "utf8");
22807
+ const content = readFileSync37(backlog, "utf8");
22461
22808
  if (dryRun) {
22462
22809
  const preview = dryRunPreview(issues, content);
22463
22810
  for (const line of preview.lines)
@@ -22483,7 +22830,7 @@ async function backlogSyncCommand(args, deps = realSyncDeps()) {
22483
22830
  process.stdout.write(`added: ${result.added}, skipped: ${result.skipped}, total issues: ${result.total}
22484
22831
  `);
22485
22832
  const block = renderSyncBlock(repoArg, wanted, deps.nowIso());
22486
- const original = existsSync39(localYaml) ? readFileSync36(localYaml, "utf8") : "";
22833
+ const original = existsSync39(localYaml) ? readFileSync37(localYaml, "utf8") : "";
22487
22834
  mkdirSync19(dirname15(localYaml) || ".", { recursive: true });
22488
22835
  writeFileSync18(localYaml, original === "" ? block + "\n" : writeSyncBlock(original, block));
22489
22836
  return 0;
@@ -22494,7 +22841,7 @@ function writeFeatureStub(issue, featuresDir, epic = "backlog-lifecycle") {
22494
22841
  const path = join35(epicDir, `${ghId(issue)}.md`);
22495
22842
  const ac = renderAcSection(issue);
22496
22843
  if (existsSync39(path)) {
22497
- const existing = readFileSync36(path, "utf8");
22844
+ const existing = readFileSync37(path, "utf8");
22498
22845
  const block = ac ? ac + "\n" : "";
22499
22846
  const sep3 = existing.endsWith("\n") || existing === "" ? "" : "\n";
22500
22847
  if (block)
@@ -22510,7 +22857,7 @@ init_dist2();
22510
22857
  init_dist3();
22511
22858
  init_dist();
22512
22859
  import { execFileSync as execFileSync7, spawnSync as spawnSync2 } from "node:child_process";
22513
- 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";
22514
22861
  import { tmpdir as tmpdir2 } from "node:os";
22515
22862
  import { dirname as dirname16, join as join36 } from "node:path";
22516
22863
  function lang2() {
@@ -22610,7 +22957,7 @@ function loopEnforceTcrCommand(args, deps = realEnforceTcrDeps()) {
22610
22957
  return 0;
22611
22958
  const backlog = join36(".roll", "backlog.md");
22612
22959
  if (existsSync40(backlog)) {
22613
- writeFileSync19(backlog, revertStoryDone(readFileSync37(backlog, "utf8"), storyId));
22960
+ writeFileSync19(backlog, revertStoryDone(readFileSync38(backlog, "utf8"), storyId));
22614
22961
  }
22615
22962
  const alert = alertFile();
22616
22963
  mkdirSync20(dirname16(alert), { recursive: true });
@@ -22668,7 +23015,7 @@ function loopPrecheckCiCommand(args, deps = realPrecheckDeps()) {
22668
23015
  const sha8 = commit2.slice(0, 8);
22669
23016
  const healKey = `heal_count_head_${sha8}`;
22670
23017
  const state = stateFilePath();
22671
- const body = existsSync40(state) ? readFileSync37(state, "utf8") : "";
23018
+ const body = existsSync40(state) ? readFileSync38(state, "utf8") : "";
22672
23019
  const headHealCount = parseInt(stateGet(body, healKey) || "0", 10) || 0;
22673
23020
  const verdict = precheckCiVerdict({ ghAndCommitOk: true, runs, healMax, headHealCount });
22674
23021
  if (verdict.exit === 2) {
@@ -22780,7 +23127,7 @@ function loopUnknownSubcommand(sub) {
22780
23127
  // packages/cli/dist/commands/brief.js
22781
23128
  init_dist2();
22782
23129
  init_dist();
22783
- import { existsSync as existsSync41, readFileSync as readFileSync38 } from "node:fs";
23130
+ import { existsSync as existsSync41, readFileSync as readFileSync39 } from "node:fs";
22784
23131
  import { homedir as homedir9 } from "node:os";
22785
23132
  import { join as join37 } from "node:path";
22786
23133
  var BACKLOG_PATH2 = ".roll/backlog.md";
@@ -22872,7 +23219,7 @@ function briefCommand(args) {
22872
23219
  `);
22873
23220
  return 1;
22874
23221
  }
22875
- const items = parseBacklog(readFileSync38(BACKLOG_PATH2, "utf8"));
23222
+ const items = parseBacklog(readFileSync39(BACKLOG_PATH2, "utf8"));
22876
23223
  const model = composeBrief(items, activeAlerts());
22877
23224
  const dateStr = formatNow(/* @__PURE__ */ new Date());
22878
23225
  process.stdout.write(renderBrief(model, lang9, { full }, dateStr).join("\n") + "\n");
@@ -22945,7 +23292,7 @@ init_dist();
22945
23292
  // packages/cli/dist/commands/lang.js
22946
23293
  init_dist();
22947
23294
  import { execFileSync as execFileSync8 } from "node:child_process";
22948
- 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";
22949
23296
  import { homedir as homedir10, tmpdir as tmpdir3 } from "node:os";
22950
23297
  import { dirname as dirname17, join as join38 } from "node:path";
22951
23298
  function rollConfigPath2() {
@@ -22956,7 +23303,7 @@ function configLang2() {
22956
23303
  const cfg = rollConfigPath2();
22957
23304
  if (!existsSync42(cfg))
22958
23305
  return void 0;
22959
- for (const line of readFileSync39(cfg, "utf8").split("\n")) {
23306
+ for (const line of readFileSync40(cfg, "utf8").split("\n")) {
22960
23307
  const m7 = /^lang:\s*(.*)$/.exec(line);
22961
23308
  if (m7 !== null) {
22962
23309
  const v = (m7[1] ?? "").replace(/\s*#.*$/, "").trim();
@@ -22970,7 +23317,7 @@ function configHasLangLine() {
22970
23317
  const cfg = rollConfigPath2();
22971
23318
  if (!existsSync42(cfg))
22972
23319
  return false;
22973
- return readFileSync39(cfg, "utf8").split("\n").some((l) => /^lang:/.test(l));
23320
+ return readFileSync40(cfg, "utf8").split("\n").some((l) => /^lang:/.test(l));
22974
23321
  }
22975
23322
  function appleLang2() {
22976
23323
  if (process.platform !== "darwin")
@@ -23013,7 +23360,7 @@ function resolveSource() {
23013
23360
  function writeLang(value) {
23014
23361
  const cfg = rollConfigPath2();
23015
23362
  mkdirSync21(dirname17(cfg), { recursive: true });
23016
- const existing = existsSync42(cfg) ? readFileSync39(cfg, "utf8") : "";
23363
+ const existing = existsSync42(cfg) ? readFileSync40(cfg, "utf8") : "";
23017
23364
  const kept = existing === "" ? [] : existing.split("\n").filter((l) => !/^lang:/.test(l));
23018
23365
  if (kept.length > 0 && kept[kept.length - 1] === "")
23019
23366
  kept.pop();
@@ -23027,7 +23374,7 @@ function clearLang() {
23027
23374
  const cfg = rollConfigPath2();
23028
23375
  if (!existsSync42(cfg))
23029
23376
  return;
23030
- const existing = readFileSync39(cfg, "utf8");
23377
+ const existing = readFileSync40(cfg, "utf8");
23031
23378
  const kept = existing.split("\n").filter((l) => !/^lang:/.test(l));
23032
23379
  if (kept.length > 0 && kept[kept.length - 1] === "")
23033
23380
  kept.pop();
@@ -23934,7 +24281,7 @@ ${CYCLE_USAGE}
23934
24281
 
23935
24282
  // packages/cli/dist/commands/ls.js
23936
24283
  init_dist();
23937
- import { existsSync as existsSync43, readFileSync as readFileSync40 } from "node:fs";
24284
+ import { existsSync as existsSync43, readFileSync as readFileSync41 } from "node:fs";
23938
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";
23939
24286
  var DEFAULT_STALE_DAYS = 14;
23940
24287
  function projectStatus(entry, nowMs, staleMs, pathExists) {
@@ -24028,7 +24375,7 @@ ${LS_USAGE}
24028
24375
  if (args.includes("--json")) {
24029
24376
  let text;
24030
24377
  try {
24031
- text = readFileSync40(projectsRegistryPath(), "utf8");
24378
+ text = readFileSync41(projectsRegistryPath(), "utf8");
24032
24379
  } catch {
24033
24380
  text = "[]\n";
24034
24381
  }
@@ -24043,7 +24390,7 @@ ${LS_USAGE}
24043
24390
 
24044
24391
  // packages/cli/dist/commands/loop-runs.js
24045
24392
  init_dist();
24046
- 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";
24047
24394
  import { homedir as homedir12, platform as platform2 } from "node:os";
24048
24395
  import { basename as basename9, join as join40 } from "node:path";
24049
24396
  function lang3() {
@@ -24151,7 +24498,7 @@ function backlogDesc(backlogText, id) {
24151
24498
  }
24152
24499
  return "";
24153
24500
  }
24154
- function formatLine2(row2, showProject, backlogText) {
24501
+ function formatLine(row2, showProject, backlogText) {
24155
24502
  const ts = str3(row2["ts"]);
24156
24503
  const status2 = str3(row2["status"]);
24157
24504
  const project = str3(row2["project"]);
@@ -24211,7 +24558,7 @@ function aggregateAllFiles() {
24211
24558
  for (const pl of plists) {
24212
24559
  let content;
24213
24560
  try {
24214
- content = readFileSync41(join40(laDir, pl), "utf8");
24561
+ content = readFileSync42(join40(laDir, pl), "utf8");
24215
24562
  } catch {
24216
24563
  continue;
24217
24564
  }
@@ -24227,7 +24574,7 @@ function aggregateAllFiles() {
24227
24574
  if (!f || seen.has(f))
24228
24575
  continue;
24229
24576
  try {
24230
- if (!existsSync44(f) || readFileSync41(f, "utf8").trim() === "")
24577
+ if (!existsSync44(f) || readFileSync42(f, "utf8").trim() === "")
24231
24578
  continue;
24232
24579
  } catch {
24233
24580
  continue;
@@ -24241,7 +24588,7 @@ function aggregateAllRows() {
24241
24588
  const rows = [];
24242
24589
  for (const f of aggregateAllFiles()) {
24243
24590
  try {
24244
- rows.push(...parseRows(readFileSync41(f, "utf8")));
24591
+ rows.push(...parseRows(readFileSync42(f, "utf8")));
24245
24592
  } catch {
24246
24593
  }
24247
24594
  }
@@ -24263,7 +24610,7 @@ function runsDetail(cycleId) {
24263
24610
  }
24264
24611
  let rows;
24265
24612
  try {
24266
- rows = parseRows(readFileSync41(src, "utf8"));
24613
+ rows = parseRows(readFileSync42(src, "utf8"));
24267
24614
  } catch {
24268
24615
  rows = [];
24269
24616
  }
@@ -24320,12 +24667,12 @@ function loopRunsCommand(argv) {
24320
24667
  rows = aggregateAllRows();
24321
24668
  } else {
24322
24669
  const src = runsFile();
24323
- if (!existsSync44(src) || readFileSync41(src, "utf8").trim() === "") {
24670
+ if (!existsSync44(src) || readFileSync42(src, "utf8").trim() === "") {
24324
24671
  process.stdout.write(msg3("loop.no_loop_runs_yet") + "\n");
24325
24672
  return 0;
24326
24673
  }
24327
24674
  const slug = projectSlug2();
24328
- rows = parseRows(readFileSync41(src, "utf8")).filter((r) => str3(r["project"]) === slug);
24675
+ rows = parseRows(readFileSync42(src, "utf8")).filter((r) => str3(r["project"]) === slug);
24329
24676
  }
24330
24677
  if (rows.length === 0) {
24331
24678
  process.stdout.write(msg3("loop.no_loop_runs_for_current_project") + "\n");
@@ -24337,12 +24684,12 @@ function loopRunsCommand(argv) {
24337
24684
  const blPath = join40(proj, ".roll", "backlog.md");
24338
24685
  if (existsSync44(blPath)) {
24339
24686
  try {
24340
- backlogText = readFileSync41(blPath, "utf8");
24687
+ backlogText = readFileSync42(blPath, "utf8");
24341
24688
  } catch {
24342
24689
  backlogText = "";
24343
24690
  }
24344
24691
  }
24345
- const out3 = recent.map((r) => formatLine2(r, allFlag, backlogText));
24692
+ const out3 = recent.map((r) => formatLine(r, allFlag, backlogText));
24346
24693
  process.stdout.write(out3.join("\n") + "\n");
24347
24694
  return 0;
24348
24695
  }
@@ -24350,7 +24697,7 @@ function loopRunsCommand(argv) {
24350
24697
  // packages/cli/dist/commands/loop-signals.js
24351
24698
  init_dist2();
24352
24699
  init_dist();
24353
- 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";
24354
24701
  import { dirname as dirname18, join as join41 } from "node:path";
24355
24702
  function lang4() {
24356
24703
  return resolveLang({ rollLang: process.env["ROLL_LANG"], lcAll: process.env["LC_ALL"], lang: process.env["LANG"] });
@@ -24383,14 +24730,14 @@ function loopSignalsCommand(argv) {
24383
24730
  process.stdout.write(s + "\n");
24384
24731
  };
24385
24732
  const runsSrc = runsFile();
24386
- if (!existsSync45(runsSrc) || readFileSync42(runsSrc, "utf8").trim() === "") {
24733
+ if (!existsSync45(runsSrc) || readFileSync43(runsSrc, "utf8").trim() === "") {
24387
24734
  say(t(v2Catalog, lang4(), "loop.no_loop_runs_yet"));
24388
24735
  return 0;
24389
24736
  }
24390
24737
  const projectPath3 = (process.env["ROLL_MAIN_PROJECT"] ?? "").trim() || process.cwd();
24391
24738
  const slug = projectSlug2();
24392
24739
  const records = [];
24393
- for (const line of readFileSync42(runsSrc, "utf8").split("\n")) {
24740
+ for (const line of readFileSync43(runsSrc, "utf8").split("\n")) {
24394
24741
  if (line.trim() === "")
24395
24742
  continue;
24396
24743
  try {
@@ -24411,13 +24758,13 @@ function loopSignalsCommand(argv) {
24411
24758
  writeFileSync21(seenFile, "");
24412
24759
  let lastId = 0;
24413
24760
  if (existsSync45(candFile)) {
24414
- for (const m7 of readFileSync42(candFile, "utf8").matchAll(/CAND-(\d+)/g)) {
24761
+ for (const m7 of readFileSync43(candFile, "utf8").matchAll(/CAND-(\d+)/g)) {
24415
24762
  const n = parseInt(m7[1] ?? "0", 10);
24416
24763
  if (n > lastId)
24417
24764
  lastId = n;
24418
24765
  }
24419
24766
  }
24420
- 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 !== "") : []);
24421
24768
  let newCount = 0;
24422
24769
  for (const sig of signals) {
24423
24770
  if (seen.has(sig.key))
@@ -24464,7 +24811,7 @@ Options:
24464
24811
 
24465
24812
  // packages/cli/dist/commands/loop-log.js
24466
24813
  init_dist();
24467
- 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";
24468
24815
  import { basename as basename10, join as join42 } from "node:path";
24469
24816
  function lang5() {
24470
24817
  return resolveLang({ rollLang: process.env["ROLL_LANG"], lcAll: process.env["LC_ALL"], lang: process.env["LANG"] });
@@ -24481,7 +24828,7 @@ function showLog(file) {
24481
24828
  const ts = m7 ? `${m7[1]}-${m7[2]}-${m7[3]} ${m7[4]}:${m7[5]}` : id;
24482
24829
  let body = "";
24483
24830
  try {
24484
- body = readFileSync43(file, "utf8");
24831
+ body = readFileSync44(file, "utf8");
24485
24832
  } catch {
24486
24833
  body = "";
24487
24834
  }
@@ -24537,7 +24884,7 @@ function loopLogCommand(argv) {
24537
24884
 
24538
24885
  // packages/cli/dist/commands/loop-goal.js
24539
24886
  init_dist();
24540
- import { existsSync as existsSync47, readFileSync as readFileSync44 } from "node:fs";
24887
+ import { existsSync as existsSync47, readFileSync as readFileSync45 } from "node:fs";
24541
24888
  import { join as join43 } from "node:path";
24542
24889
  function realDeps3() {
24543
24890
  return { projectPath: () => process.cwd() };
@@ -24611,7 +24958,7 @@ async function loopGoalCommand(args, deps = realDeps3()) {
24611
24958
  return 0;
24612
24959
  }
24613
24960
  try {
24614
- process.stdout.write(renderGoal(parseGoalYaml(readFileSync44(path, "utf8"))));
24961
+ process.stdout.write(renderGoal(parseGoalYaml(readFileSync45(path, "utf8"))));
24615
24962
  return 0;
24616
24963
  } catch (e) {
24617
24964
  process.stderr.write(`[roll] goal.yaml invalid: ${e.message}
@@ -24625,7 +24972,7 @@ init_dist2();
24625
24972
  init_dist();
24626
24973
  init_dist3();
24627
24974
  import { spawn as spawn3, spawnSync as spawnSync4 } from "node:child_process";
24628
- 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";
24629
24976
  import { dirname as dirname19, join as join44 } from "node:path";
24630
24977
  import { createInterface } from "node:readline";
24631
24978
 
@@ -25210,7 +25557,7 @@ function writeGoal(path, goal) {
25210
25557
  function readGoal(path) {
25211
25558
  if (!existsSync48(path))
25212
25559
  return void 0;
25213
- return parseGoalYaml(readFileSync45(path, "utf8"));
25560
+ return parseGoalYaml(readFileSync46(path, "utf8"));
25214
25561
  }
25215
25562
  function createGoal(opts, at) {
25216
25563
  return {
@@ -25244,7 +25591,7 @@ function rowSpentZeroNoExecution(row2) {
25244
25591
  function readRunSnapshot(path) {
25245
25592
  let text = "";
25246
25593
  try {
25247
- text = readFileSync45(path, "utf8");
25594
+ text = readFileSync46(path, "utf8");
25248
25595
  } catch {
25249
25596
  return { rows: [], summary: { cycles: 0, costUsd: 0, costUnknownRows: 0 } };
25250
25597
  }
@@ -25277,7 +25624,7 @@ function readRunSnapshot(path) {
25277
25624
  }
25278
25625
  function readBacklogRows(projectPath3) {
25279
25626
  try {
25280
- return parseBacklog(readFileSync45(join44(projectPath3, ".roll", "backlog.md"), "utf8")).map((row2) => ({
25627
+ return parseBacklog(readFileSync46(join44(projectPath3, ".roll", "backlog.md"), "utf8")).map((row2) => ({
25281
25628
  id: row2.id,
25282
25629
  status: row2.status
25283
25630
  }));
@@ -25287,7 +25634,7 @@ function readBacklogRows(projectPath3) {
25287
25634
  }
25288
25635
  function readStoryIndex(projectPath3) {
25289
25636
  try {
25290
- const obj = JSON.parse(readFileSync45(join44(projectPath3, ".roll", "index.json"), "utf8"));
25637
+ const obj = JSON.parse(readFileSync46(join44(projectPath3, ".roll", "index.json"), "utf8"));
25291
25638
  if (typeof obj.stories !== "object" || obj.stories === null || Array.isArray(obj.stories))
25292
25639
  return {};
25293
25640
  const out3 = {};
@@ -25624,7 +25971,7 @@ async function evaluateGoal(projectPath3, slug, goal, deps, session, workerAgent
25624
25971
  function hasSafetyPauseSince(path, since) {
25625
25972
  let text = "";
25626
25973
  try {
25627
- text = readFileSync45(path, "utf8");
25974
+ text = readFileSync46(path, "utf8");
25628
25975
  } catch {
25629
25976
  return false;
25630
25977
  }
@@ -25754,7 +26101,7 @@ function latestAlertSummary(projectPath3, slug, sinceSec) {
25754
26101
  try {
25755
26102
  if (!existsSync48(path) || statSync15(path).mtimeMs / 1e3 < sinceSec)
25756
26103
  return void 0;
25757
- 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 !== "");
25758
26105
  const line = [...lines2].reverse().find((candidate) => /\b(ALERT|WARN|BLOCKED|refused|failed)\b/i.test(candidate));
25759
26106
  if (line === void 0)
25760
26107
  return void 0;
@@ -25772,7 +26119,7 @@ function innerLockHolder(projectPath3, nowSec2) {
25772
26119
  try {
25773
26120
  if (!existsSync48(path))
25774
26121
  return void 0;
25775
- const contents = parseLock(readFileSync45(path, "utf8"));
26122
+ const contents = parseLock(readFileSync46(path, "utf8"));
25776
26123
  return isLockHeld(contents, nowSec2, INNER_LOCK_STALE_SEC) ? contents.pid : void 0;
25777
26124
  } catch {
25778
26125
  return void 0;
@@ -25913,14 +26260,14 @@ function applyTimeboxGate(projectPath3, bus, session, goal, deps, deadlineSec) {
25913
26260
  }
25914
26261
  function installStopHandlers(onStop) {
25915
26262
  const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
25916
- const handlers = signals.map((signal2) => {
25917
- const handler = () => onStop(signal2);
25918
- process.on(signal2, handler);
25919
- return { signal: signal2, handler };
26263
+ const handlers = signals.map((signal) => {
26264
+ const handler = () => onStop(signal);
26265
+ process.on(signal, handler);
26266
+ return { signal, handler };
25920
26267
  });
25921
26268
  return () => {
25922
- for (const { signal: signal2, handler } of handlers)
25923
- process.off(signal2, handler);
26269
+ for (const { signal, handler } of handlers)
26270
+ process.off(signal, handler);
25924
26271
  };
25925
26272
  }
25926
26273
  function describeScope(scope) {
@@ -25993,9 +26340,9 @@ async function runGoWorker(id, opts, deps) {
25993
26340
  let stopReason;
25994
26341
  let stopRequested = false;
25995
26342
  let workerAgents = [];
25996
- const disposeSignals = installStopHandlers((signal2) => {
26343
+ const disposeSignals = installStopHandlers((signal) => {
25997
26344
  stopRequested = true;
25998
- stopReason = `signal_${signal2}`;
26345
+ stopReason = `signal_${signal}`;
25999
26346
  });
26000
26347
  try {
26001
26348
  const gPath = goalPath2(id.path);
@@ -26135,7 +26482,7 @@ async function runGoWorker(id, opts, deps) {
26135
26482
  }
26136
26483
 
26137
26484
  // packages/cli/dist/commands/loop-events.js
26138
- import { existsSync as existsSync49, readFileSync as readFileSync46 } from "node:fs";
26485
+ import { existsSync as existsSync49, readFileSync as readFileSync47 } from "node:fs";
26139
26486
  import { join as join45 } from "node:path";
26140
26487
  function str4(v) {
26141
26488
  return typeof v === "string" ? v : v === void 0 || v === null ? "" : String(v);
@@ -26151,7 +26498,7 @@ function loopEventsCommand(argv) {
26151
26498
  }
26152
26499
  let lines2;
26153
26500
  try {
26154
- lines2 = readFileSync46(evfile, "utf8").split("\n").filter((l) => l !== "");
26501
+ lines2 = readFileSync47(evfile, "utf8").split("\n").filter((l) => l !== "");
26155
26502
  } catch {
26156
26503
  lines2 = [];
26157
26504
  }
@@ -26189,14 +26536,14 @@ function loopBranchesRetired() {
26189
26536
  // packages/cli/dist/commands/doctor.js
26190
26537
  init_dist();
26191
26538
  import { execFileSync as execFileSync11 } from "node:child_process";
26192
- 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";
26193
26540
  import { homedir as homedir13, tmpdir as tmpdir5 } from "node:os";
26194
26541
  import { delimiter as delimiter2, join as join47 } from "node:path";
26195
26542
 
26196
26543
  // packages/cli/dist/commands/skills.js
26197
26544
  init_dist();
26198
26545
  import { execFileSync as execFileSync10 } from "node:child_process";
26199
- 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";
26200
26547
  import { tmpdir as tmpdir4 } from "node:os";
26201
26548
  import { join as join46 } from "node:path";
26202
26549
  function pkgDir() {
@@ -26216,7 +26563,7 @@ function skillsDir() {
26216
26563
  return join46(pkgDir(), "skills");
26217
26564
  }
26218
26565
  function frontmatterField(file, field2) {
26219
- const lines2 = readFileSync47(file, "utf8").split("\n");
26566
+ const lines2 = readFileSync48(file, "utf8").split("\n");
26220
26567
  if (lines2.length === 0 || lines2[0] !== "---")
26221
26568
  return "";
26222
26569
  let collecting = false;
@@ -26461,7 +26808,7 @@ function bilingual(key) {
26461
26808
  return [t(v2Catalog, "en", key), t(v2Catalog, "zh", key)];
26462
26809
  }
26463
26810
  var out2 = { lines: [] };
26464
- function emit(line) {
26811
+ function emit2(line) {
26465
26812
  out2.lines.push(line);
26466
26813
  }
26467
26814
  function agentBinNames4(agent) {
@@ -26542,11 +26889,11 @@ function agentSection(p) {
26542
26889
  const cfg = rollConfigPath4();
26543
26890
  if (!existsSync51(cfg))
26544
26891
  return;
26545
- emit("");
26892
+ emit2("");
26546
26893
  for (const l of bilingual("doctor.agent_detection"))
26547
- emit(l);
26548
- emit("");
26549
- const text = readFileSync48(cfg, "utf8");
26894
+ emit2(l);
26895
+ emit2("");
26896
+ const text = readFileSync49(cfg, "utf8");
26550
26897
  let primary = "";
26551
26898
  for (const line of text.split("\n")) {
26552
26899
  const m7 = /^primary_agent:\s*(.*)$/.exec(line);
@@ -26572,7 +26919,7 @@ function agentSection(p) {
26572
26919
  const installed = agentInstalledByName4(name, dir) ? t(v2Catalog, msgLang5(), "doctor.agent_installed") : t(v2Catalog, msgLang5(), "doctor.agent_missing");
26573
26920
  const dirExists = safeIsDir(dir) ? t(v2Catalog, msgLang5(), "doctor.agent_dir_exists") : t(v2Catalog, msgLang5(), "doctor.agent_dir_missing");
26574
26921
  const tag = name === primary ? ` (${t(v2Catalog, msgLang5(), "doctor.agent_primary_label")})` : "";
26575
- emit(` ${padEnd(name, 10)} ${padEnd(installed, 14)} ${dirExists}${tag}`);
26922
+ emit2(` ${padEnd(name, 10)} ${padEnd(installed, 14)} ${dirExists}${tag}`);
26576
26923
  }
26577
26924
  }
26578
26925
  function ghAvailable3() {
@@ -26652,28 +26999,28 @@ function prEventHint(lang9) {
26652
26999
  function prSection(lang9) {
26653
27000
  if (!insideGitWorkTree())
26654
27001
  return;
26655
- emit("");
27002
+ emit2("");
26656
27003
  for (const l of bilingual("doctor.pr_review_extras"))
26657
- emit(l);
26658
- emit("");
27004
+ emit2(l);
27005
+ emit2("");
26659
27006
  const protection = branchProtectionState();
26660
27007
  if (protection === "enabled") {
26661
- emit(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_enabled")}`);
27008
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_enabled")}`);
26662
27009
  } else if (protection === "disabled") {
26663
- emit(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_disabled")}`);
27010
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_disabled")}`);
26664
27011
  for (const l of prPipelineHint())
26665
- emit(l);
27012
+ emit2(l);
26666
27013
  } else {
26667
- emit(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_unknown")}`);
27014
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_double_gate_unknown")}`);
26668
27015
  for (const l of prPipelineHint())
26669
- emit(l);
27016
+ emit2(l);
26670
27017
  }
26671
27018
  if (eventWorkflowState() === "present") {
26672
- emit(` ${t(v2Catalog, lang9, "doctor.pr_event_enabled")}`);
27019
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_event_enabled")}`);
26673
27020
  } else {
26674
- emit(` ${t(v2Catalog, lang9, "doctor.pr_event_disabled")}`);
27021
+ emit2(` ${t(v2Catalog, lang9, "doctor.pr_event_disabled")}`);
26675
27022
  for (const l of prEventHint(lang9))
26676
- emit(l);
27023
+ emit2(l);
26677
27024
  }
26678
27025
  }
26679
27026
  function skillsCatalogSection(lang9) {
@@ -26681,18 +27028,18 @@ function skillsCatalogSection(lang9) {
26681
27028
  if (!safeIsDir(skillsDir2))
26682
27029
  return;
26683
27030
  const target = join47(pkgDir2(), "guide", "skills.md");
26684
- emit("");
27031
+ emit2("");
26685
27032
  for (const l of bilingual("skills.doctor_heading"))
26686
- emit(l);
27033
+ emit2(l);
26687
27034
  let drift;
26688
27035
  if (!existsSync51(target)) {
26689
27036
  drift = true;
26690
27037
  } else {
26691
27038
  const fresh = join47(mkdtempSync3(join47(tmpdir5(), "roll-doctor-")), "skills.md");
26692
27039
  writeFileSync24(fresh, generateCatalog());
26693
- drift = readFileSync48(target, "utf8") !== readFileSync48(fresh, "utf8");
27040
+ drift = readFileSync49(target, "utf8") !== readFileSync49(fresh, "utf8");
26694
27041
  }
26695
- emit(` ${t(v2Catalog, lang9, drift ? "skills.doctor_drift" : "skills.doctor_ok")}`);
27042
+ emit2(` ${t(v2Catalog, lang9, drift ? "skills.doctor_drift" : "skills.doctor_ok")}`);
26696
27043
  }
26697
27044
  function lanesSection(lang9, probe) {
26698
27045
  const lines2 = [];
@@ -26722,7 +27069,7 @@ function lanesSection(lang9, probe) {
26722
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]" : ""}`);
26723
27070
  }
26724
27071
  for (const l of lines2)
26725
- emit(l);
27072
+ emit2(l);
26726
27073
  return lines2;
26727
27074
  }
26728
27075
  function launchdStaleSection(lang9) {
@@ -26746,15 +27093,15 @@ function launchdStaleSection(lang9) {
26746
27093
  if (safeIsDir(wd))
26747
27094
  continue;
26748
27095
  if (!found) {
26749
- emit("");
26750
- emit(t(v2Catalog, lang9, "doctor.stale_plists"));
26751
- emit("");
27096
+ emit2("");
27097
+ emit2(t(v2Catalog, lang9, "doctor.stale_plists"));
27098
+ emit2("");
26752
27099
  found = true;
26753
27100
  }
26754
27101
  const label4 = name.replace(/\.plist$/, "");
26755
- emit(` \u26A0 ${label4}`);
26756
- emit(` WorkingDirectory missing: ${wd}`);
26757
- 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}'`);
26758
27105
  }
26759
27106
  }
26760
27107
  function realLaneProbe() {
@@ -26843,23 +27190,23 @@ function launchdProxySection(lang9) {
26843
27190
  }
26844
27191
  if (stale.length === 0)
26845
27192
  return;
26846
- emit("");
26847
- emit(t(v3Catalog, "en", "doctor.proxy_env_warning"));
26848
- emit(t(v3Catalog, "zh", "doctor.proxy_env_warning"));
26849
- emit("");
27193
+ emit2("");
27194
+ emit2(t(v3Catalog, "en", "doctor.proxy_env_warning"));
27195
+ emit2(t(v3Catalog, "zh", "doctor.proxy_env_warning"));
27196
+ emit2("");
26850
27197
  for (const { name, target } of stale) {
26851
- emit(` \u26A0 ${name}=${target}`);
27198
+ emit2(` \u26A0 ${name}=${target}`);
26852
27199
  }
26853
- emit("");
26854
- emit(` ${t(v3Catalog, lang9, "doctor.proxy_env_hint")}`);
27200
+ emit2("");
27201
+ emit2(` ${t(v3Catalog, lang9, "doctor.proxy_env_hint")}`);
26855
27202
  for (const { name } of stale) {
26856
- emit(` launchctl unsetenv ${name}`);
27203
+ emit2(` launchctl unsetenv ${name}`);
26857
27204
  }
26858
27205
  }
26859
27206
  function readWorkingDirectory(plist) {
26860
27207
  let body;
26861
27208
  try {
26862
- body = readFileSync48(plist, "utf8");
27209
+ body = readFileSync49(plist, "utf8");
26863
27210
  } catch {
26864
27211
  return "";
26865
27212
  }
@@ -26895,11 +27242,11 @@ import { join as join51 } from "node:path";
26895
27242
  import { spawn as spawn4 } from "node:child_process";
26896
27243
  import { join as join48 } from "node:path";
26897
27244
  var liveAgents = /* @__PURE__ */ new Set();
26898
- function killLiveAgents(signal2 = "SIGKILL") {
27245
+ function killLiveAgents(signal = "SIGKILL") {
26899
27246
  let n = 0;
26900
27247
  for (const c2 of liveAgents) {
26901
27248
  try {
26902
- if (killHard(c2, signal2))
27249
+ if (killHard(c2, signal))
26903
27250
  n += 1;
26904
27251
  } catch {
26905
27252
  }
@@ -26907,15 +27254,15 @@ function killLiveAgents(signal2 = "SIGKILL") {
26907
27254
  liveAgents.clear();
26908
27255
  return n;
26909
27256
  }
26910
- function killHard(c2, signal2) {
27257
+ function killHard(c2, signal) {
26911
27258
  if (c2.pid !== void 0) {
26912
27259
  try {
26913
- process.kill(-c2.pid, signal2);
27260
+ process.kill(-c2.pid, signal);
26914
27261
  return true;
26915
27262
  } catch {
26916
27263
  }
26917
27264
  }
26918
- return c2.kill(signal2);
27265
+ return c2.kill(signal);
26919
27266
  }
26920
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: ";
26921
27268
  var AGENT_ARGV_TODO = {
@@ -26972,7 +27319,7 @@ function buildSpawnCommand(agent, opts) {
26972
27319
  return { bin: opts.bin ?? "qwen", args: [prompt] };
26973
27320
  }
26974
27321
  if (agent === "agy" || agent === "gemini" || agent === "antigravity") {
26975
- return { bin: opts.bin ?? "agy", args: ["-p", "--dangerously-skip-permissions", prompt] };
27322
+ return { bin: opts.bin ?? "agy", args: ["--dangerously-skip-permissions", "-p", prompt] };
26976
27323
  }
26977
27324
  const hint = AGENT_ARGV_TODO[agent] ?? "unknown agent";
26978
27325
  throw new Error(`runner: agent '${agent}' argv not yet ported. v2 shape: ${hint}`);
@@ -27038,8 +27385,8 @@ function spawnAndWait(bin, args, opts, pty = false) {
27038
27385
  settle({ stdout, stderr: `${stderr}${String(e)}
27039
27386
  `, exitCode: 127, timedOut });
27040
27387
  });
27041
- child.on("exit", (code, signal2) => {
27042
- 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 }));
27043
27390
  });
27044
27391
  child.on("close", (code) => {
27045
27392
  settle({ stdout, stderr, exitCode: code ?? 1, timedOut });
@@ -27052,7 +27399,7 @@ var realAgentSpawn = (agent, opts) => {
27052
27399
  };
27053
27400
 
27054
27401
  // packages/cli/dist/runner/skill-body.js
27055
- import { existsSync as existsSync52, readFileSync as readFileSync49 } from "node:fs";
27402
+ import { existsSync as existsSync52, readFileSync as readFileSync50 } from "node:fs";
27056
27403
  import { homedir as homedir14 } from "node:os";
27057
27404
  import { join as join49 } from "node:path";
27058
27405
  function readSkillBody(projectPath3, opts) {
@@ -27067,7 +27414,7 @@ function readSkillBody(projectPath3, opts) {
27067
27414
  continue;
27068
27415
  let raw = "";
27069
27416
  try {
27070
- raw = readFileSync49(p, "utf8");
27417
+ raw = readFileSync50(p, "utf8");
27071
27418
  } catch {
27072
27419
  continue;
27073
27420
  }
@@ -27469,7 +27816,7 @@ ${rowNote}`);
27469
27816
  init_dist2();
27470
27817
  init_dist();
27471
27818
  import { spawnSync as spawnSync5 } from "node:child_process";
27472
- 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";
27473
27820
  import { homedir as homedir15 } from "node:os";
27474
27821
  import { dirname as dirname20, join as join54 } from "node:path";
27475
27822
  function registerProject(projectDir) {
@@ -27545,7 +27892,7 @@ function scanProjectType(dir) {
27545
27892
  const pkg = join54(dir, "package.json");
27546
27893
  const readPkg = () => {
27547
27894
  try {
27548
- return readFileSync50(pkg, "utf8");
27895
+ return readFileSync51(pkg, "utf8");
27549
27896
  } catch {
27550
27897
  return "";
27551
27898
  }
@@ -27690,7 +28037,7 @@ function discoverOnboardAgents() {
27690
28037
  if (!existsSync56(cfg))
27691
28038
  return { installed, missing };
27692
28039
  const env = realAgentEnv();
27693
- for (const line of readFileSync50(cfg, "utf8").split("\n")) {
28040
+ for (const line of readFileSync51(cfg, "utf8").split("\n")) {
27694
28041
  const match = /^(ai_[^:]+):\s*(.*)$/.exec(line);
27695
28042
  if (match === null)
27696
28043
  continue;
@@ -27761,7 +28108,7 @@ function readOnboardPrompt() {
27761
28108
  err8(`Skill file missing: ${skillFile}`);
27762
28109
  return null;
27763
28110
  }
27764
- 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();
27765
28112
  return `Run the $roll-onboard skill below for this project. Follow it end-to-end and write .roll/onboard-plan.yaml when done.
27766
28113
 
27767
28114
  ${body}`;
@@ -27896,7 +28243,7 @@ function mergeGlobalToProject(projectDir, summary) {
27896
28243
  const projectType = scanProjectType(projectDir);
27897
28244
  const skipFrontend = ["cli", "backend-service", "unknown"].includes(projectType);
27898
28245
  const FRONTEND_HEAD = "## 7. Frontend Default Stack";
27899
- const srcText = readFileSync50(src, "utf8");
28246
+ const srcText = readFileSync51(src, "utf8");
27900
28247
  const srcLines = srcText.split("\n");
27901
28248
  const lines2 = srcText.endsWith("\n") ? srcLines.slice(0, -1) : srcLines;
27902
28249
  if (!existsSync56(dst)) {
@@ -27932,7 +28279,7 @@ ${fcB}`;
27932
28279
  summary.push("created|AGENTS.md");
27933
28280
  return;
27934
28281
  }
27935
- const dstText = readFileSync50(dst, "utf8");
28282
+ const dstText = readFileSync51(dst, "utf8");
27936
28283
  let added = 0;
27937
28284
  let curH = "";
27938
28285
  let curB = "";
@@ -27981,9 +28328,9 @@ function mergeClaudeToProject(projectDir, summary) {
27981
28328
  summary.push("created|.claude/CLAUDE.md");
27982
28329
  return;
27983
28330
  }
27984
- const tplText = readFileSync50(tplFile, "utf8");
28331
+ const tplText = readFileSync51(tplFile, "utf8");
27985
28332
  const lines2 = tplText.endsWith("\n") ? tplText.split("\n").slice(0, -1) : tplText.split("\n");
27986
- const outText = readFileSync50(outFile, "utf8");
28333
+ const outText = readFileSync51(outFile, "utf8");
27987
28334
  let added = 0;
27988
28335
  let curH = "";
27989
28336
  let curB = "";
@@ -28217,7 +28564,7 @@ function printMergeSummary(summary) {
28217
28564
  function seedBacklogRow(backlog, heading, row2, id) {
28218
28565
  if (!existsSync56(backlog))
28219
28566
  return false;
28220
- const text = readFileSync50(backlog, "utf8");
28567
+ const text = readFileSync51(backlog, "utf8");
28221
28568
  if (text.includes(`| ${id} |`))
28222
28569
  return false;
28223
28570
  const lines2 = text.split("\n");
@@ -28325,7 +28672,7 @@ function renderAndSeed(projectDir, plan, changeset) {
28325
28672
  }
28326
28673
  function addRollToGitignore(projectDir, changeset) {
28327
28674
  const gi = join54(projectDir, ".gitignore");
28328
- const current = existsSync56(gi) ? readFileSync50(gi, "utf8") : "";
28675
+ const current = existsSync56(gi) ? readFileSync51(gi, "utf8") : "";
28329
28676
  if (current.split("\n").includes(".roll/"))
28330
28677
  return;
28331
28678
  writeFileSync27(gi, current + (current === "" || current.endsWith("\n") ? "" : "\n") + ".roll/\n");
@@ -28581,28 +28928,52 @@ function initCommand(args) {
28581
28928
  init_dist2();
28582
28929
  import { createInterface as createInterface2 } from "node:readline";
28583
28930
  var KIND_COLOR = {
28931
+ lifecycle: "dim",
28932
+ edit: "muted",
28933
+ test: "green",
28934
+ tool: "muted",
28935
+ say: "dim",
28584
28936
  tcr: "green",
28585
- skill: "blue",
28586
- ci: "green",
28587
- peer: "purple",
28588
- attest: "blue",
28937
+ commit: "green",
28589
28938
  pr: "green",
28939
+ gate: "purple",
28940
+ heartbeat: "amber",
28590
28941
  alert: "red"
28591
28942
  };
28592
- 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 = {}) {
28593
28962
  const ts = opts.ts !== void 0 && opts.ts !== "" ? c("muted", opts.ts) + " " : "";
28594
- if (line.tier === "banner") {
28595
- const detail2 = line.detail !== void 0 && line.detail !== "" ? c("muted", ` \u2014 ${line.detail}`) : "";
28596
- return `${ts}${c("dim", line.label)}${detail2}`;
28597
- }
28598
- if (line.tier === "muted") {
28599
- return `${ts}${c("muted", line.label)}`;
28600
- }
28601
- const color = line.ok === false ? "red" : line.kind !== void 0 ? KIND_COLOR[line.kind] : "fg";
28602
- const arrow = c("faint", "\u2192");
28603
- const cat = c("blue", line.category.padEnd(6));
28604
- const label4 = c(color, line.label.padEnd(14), { bold: true });
28605
- 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) : "";
28606
28977
  return `${ts}${arrow} ${cat} ${label4}${detail}`;
28607
28978
  }
28608
28979
  function hms() {
@@ -28610,14 +28981,24 @@ function hms() {
28610
28981
  const p = (n) => String(n).padStart(2, "0");
28611
28982
  return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
28612
28983
  }
28613
- var HELP3 = `Usage: roll loop fmt
28984
+ var HELP3 = `Usage: roll loop fmt [--verbose] [--no-color]
28985
+
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
28614
28995
 
28615
- Reads the agent's raw stream-json on stdin and writes a three-tier formatted
28616
- transcript to stdout (the loop observation window). Suppresses noise, mutes
28617
- edits, and surfaces signals (tcr / story / ci / peer / pr / error).
28618
- \u8BFB\u53D6 agent \u7684 stream-json\uFF0C\u8F93\u51FA\u4E09\u5C42\u5173\u952E\u8282\u70B9\u8F6C\u5F55\uFF08\u89C2\u6D4B\u7A97\uFF09\u3002
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)
28619
28998
 
28620
- Intended to sit in the watch pipe: tail -F live.log | roll loop fmt
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
28621
29002
  `;
28622
29003
  async function loopFmtCommand(args) {
28623
29004
  if (args.includes("--help") || args.includes("-h")) {
@@ -28626,23 +29007,28 @@ async function loopFmtCommand(args) {
28626
29007
  }
28627
29008
  const noColor2 = args.includes("--no-color") || !process.stdout.isTTY || (process.env["NO_COLOR"] ?? "") !== "";
28628
29009
  renderState.useColor = !noColor2;
28629
- const agent = process.env["ROLL_LOOP_AGENT"] ?? "claude";
28630
- if (agent === "claude") {
28631
- const st = newFmtState();
28632
- const rl2 = createInterface2({ input: process.stdin, crlfDelay: Infinity });
28633
- for await (const raw of rl2) {
28634
- for (const fl of formatLine(raw, st)) {
28635
- 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");
28636
29028
  }
28637
29029
  }
28638
- return 0;
28639
- }
28640
- const rl = createInterface2({ input: process.stdin, crlfDelay: Infinity });
28641
- for await (const raw of rl) {
28642
- if (raw.trim() === "")
28643
- continue;
28644
- process.stdout.write(`${c("muted", hms())} ${c("dim", raw)}
28645
- `);
29030
+ } finally {
29031
+ clearInterval(beat);
28646
29032
  }
28647
29033
  return 0;
28648
29034
  }
@@ -28651,7 +29037,7 @@ async function loopFmtCommand(args) {
28651
29037
  init_dist3();
28652
29038
  init_dist();
28653
29039
  import { spawnSync as spawnSync6 } from "node:child_process";
28654
- 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";
28655
29041
  import { homedir as homedir16 } from "node:os";
28656
29042
  import { basename as basename11, dirname as dirname21, join as join55 } from "node:path";
28657
29043
  function palette3() {
@@ -28737,7 +29123,7 @@ function fileMtimeSec(f) {
28737
29123
  }
28738
29124
  function plistWorkingDir(plistPath) {
28739
29125
  try {
28740
- 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"));
28741
29127
  return m7?.[1] ?? "";
28742
29128
  } catch {
28743
29129
  return "";
@@ -28957,14 +29343,14 @@ function loopTestCommand(args = [], deps = realTestDeps()) {
28957
29343
  // packages/cli/dist/commands/loop-pr-inbox.js
28958
29344
  init_dist2();
28959
29345
  init_dist3();
28960
- 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";
28961
29347
  import { dirname as dirname23, join as join57 } from "node:path";
28962
29348
 
28963
29349
  // packages/cli/dist/commands/loop-pr-heal.js
28964
29350
  init_dist2();
28965
29351
  init_dist3();
28966
29352
  import { execFileSync as execFileSync12, spawn as spawn5 } from "node:child_process";
28967
- 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";
28968
29354
  import { tmpdir as tmpdir6 } from "node:os";
28969
29355
  import { dirname as dirname22, join as join56 } from "node:path";
28970
29356
  function runtimeDir3() {
@@ -28990,7 +29376,7 @@ function nowIso() {
28990
29376
  function realHealDeps() {
28991
29377
  const readState = () => {
28992
29378
  try {
28993
- return readFileSync52(statePath(), "utf8");
29379
+ return readFileSync53(statePath(), "utf8");
28994
29380
  } catch {
28995
29381
  return "";
28996
29382
  }
@@ -29008,7 +29394,7 @@ function realHealDeps() {
29008
29394
  return { lockPresent: false, lockPidAlive: void 0 };
29009
29395
  let alive;
29010
29396
  try {
29011
- const pid = parseInt(readFileSync52(p, "utf8").trim(), 10);
29397
+ const pid = parseInt(readFileSync53(p, "utf8").trim(), 10);
29012
29398
  alive = Number.isFinite(pid) ? (() => {
29013
29399
  try {
29014
29400
  process.kill(pid, 0);
@@ -29029,7 +29415,7 @@ function realHealDeps() {
29029
29415
  },
29030
29416
  alertHasKey: (key) => {
29031
29417
  try {
29032
- return readFileSync52(alertPath2(), "utf8").includes(key);
29418
+ return readFileSync53(alertPath2(), "utf8").includes(key);
29033
29419
  } catch {
29034
29420
  return false;
29035
29421
  }
@@ -29247,10 +29633,10 @@ function reducePrView(raw) {
29247
29633
  }
29248
29634
  async function runPrInbox(deps) {
29249
29635
  if (!await deps.ghAvailable())
29250
- return emit2(deps, prIdleTick("gh_unavailable"));
29636
+ return emit3(deps, prIdleTick("gh_unavailable"));
29251
29637
  const slug = await deps.resolveSlug();
29252
29638
  if (slug === void 0 || slug === "")
29253
- return emit2(deps, prIdleTick("gh_unavailable"));
29639
+ return emit3(deps, prIdleTick("gh_unavailable"));
29254
29640
  const list2 = await deps.listOpenPrs(slug);
29255
29641
  const stdout = (list2.stdout ?? "").trim();
29256
29642
  let openCount = 0;
@@ -29270,7 +29656,7 @@ async function runPrInbox(deps) {
29270
29656
  ...list2.stderr !== void 0 ? { listStderr: list2.stderr } : {}
29271
29657
  });
29272
29658
  if (gate !== void 0)
29273
- return emit2(deps, gate);
29659
+ return emit3(deps, gate);
29274
29660
  const prs = JSON.parse(stdout);
29275
29661
  for (const pr of prs) {
29276
29662
  const num4 = String(pr.number ?? "");
@@ -29306,9 +29692,9 @@ async function runPrInbox(deps) {
29306
29692
  break;
29307
29693
  }
29308
29694
  }
29309
- return emit2(deps, prActedTick());
29695
+ return emit3(deps, prActedTick());
29310
29696
  }
29311
- function emit2(deps, tick) {
29697
+ function emit3(deps, tick) {
29312
29698
  deps.writeTick(tick);
29313
29699
  return tick;
29314
29700
  }
@@ -29352,7 +29738,7 @@ function writeTickFile(tick) {
29352
29738
  appendFileSync11(file, `${JSON.stringify({ ts, ...tick })}
29353
29739
  `);
29354
29740
  try {
29355
- const lines2 = readFileSync53(file, "utf8").split("\n").filter((l) => l !== "");
29741
+ const lines2 = readFileSync54(file, "utf8").split("\n").filter((l) => l !== "");
29356
29742
  if (lines2.length > 500)
29357
29743
  writeFileSync30(file, `${lines2.slice(-500).join("\n")}
29358
29744
  `);
@@ -29369,7 +29755,7 @@ function deadTickMarkerPath() {
29369
29755
  function checkDeadTickStreak(file, alert, markerPath = deadTickMarkerPath()) {
29370
29756
  let rows = [];
29371
29757
  try {
29372
- 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));
29373
29759
  } catch {
29374
29760
  return;
29375
29761
  }
@@ -29405,7 +29791,7 @@ function rebaseCircuitAllowed(num4) {
29405
29791
  const state = statePath2();
29406
29792
  let body = "";
29407
29793
  try {
29408
- body = readFileSync53(state, "utf8");
29794
+ body = readFileSync54(state, "utf8");
29409
29795
  } catch {
29410
29796
  }
29411
29797
  const verdict = rebaseCircuitVerdict(parseRebaseAttempts(body, num4), nowSec());
@@ -29471,7 +29857,7 @@ function writeRebaseAttempts(state, pr, timestamps) {
29471
29857
  mkdirSync30(dirname23(state), { recursive: true });
29472
29858
  let body = "";
29473
29859
  try {
29474
- body = readFileSync53(state, "utf8");
29860
+ body = readFileSync54(state, "utf8");
29475
29861
  } catch {
29476
29862
  }
29477
29863
  writeFileSync30(state, upsertRebaseAttempts(body, pr, renderRebaseAttempts(timestamps)));
@@ -29553,7 +29939,7 @@ async function loopPrInboxCommand(_args, deps = realDeps6()) {
29553
29939
  init_dist2();
29554
29940
  init_dist();
29555
29941
  init_dist3();
29556
- 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";
29557
29943
  import { dirname as dirname29, join as join66 } from "node:path";
29558
29944
 
29559
29945
  // packages/cli/dist/runner/executor.js
@@ -29561,13 +29947,13 @@ init_dist2();
29561
29947
  init_dist();
29562
29948
  init_dist3();
29563
29949
  import { execFile as execFile9 } from "node:child_process";
29564
- 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";
29565
29951
  import { dirname as dirname26, join as join63 } from "node:path";
29566
29952
  import { promisify as promisify9 } from "node:util";
29567
29953
 
29568
29954
  // packages/cli/dist/runner/attest-gate.js
29569
29955
  init_dist2();
29570
- 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";
29571
29957
  import { dirname as dirname24, join as join58 } from "node:path";
29572
29958
  function reportCandidates(worktreeCwd, storyId) {
29573
29959
  return [join58(cardArchiveDir(worktreeCwd, storyId), "latest", reportFileName(storyId))];
@@ -29598,7 +29984,7 @@ function storyHasAcBlock(worktreeCwd, storyId) {
29598
29984
  if (spec === null)
29599
29985
  return null;
29600
29986
  try {
29601
- return acForStory(readFileSync54(spec, "utf8"), storyId, { fileOwned: true }).length > 0;
29987
+ return acForStory(readFileSync55(spec, "utf8"), storyId, { fileOwned: true }).length > 0;
29602
29988
  } catch {
29603
29989
  return null;
29604
29990
  }
@@ -29608,7 +29994,7 @@ function storyRequiresScreenshot(worktreeCwd, storyId) {
29608
29994
  if (spec === null)
29609
29995
  return false;
29610
29996
  try {
29611
- 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"));
29612
29998
  } catch {
29613
29999
  return false;
29614
30000
  }
@@ -29618,7 +30004,7 @@ function readAcMapEntries(worktreeCwd, storyId) {
29618
30004
  if (path === void 0 || !existsSync60(path))
29619
30005
  return null;
29620
30006
  try {
29621
- const parsed = JSON.parse(readFileSync54(path, "utf8"));
30007
+ const parsed = JSON.parse(readFileSync55(path, "utf8"));
29622
30008
  return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "object" && x !== null) : null;
29623
30009
  } catch {
29624
30010
  return null;
@@ -29632,7 +30018,7 @@ function evidenceManifest(worktreeCwd, storyId) {
29632
30018
  if (!existsSync60(path))
29633
30019
  return null;
29634
30020
  try {
29635
- const parsed = JSON.parse(readFileSync54(path, "utf8"));
30021
+ const parsed = JSON.parse(readFileSync55(path, "utf8"));
29636
30022
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
29637
30023
  } catch {
29638
30024
  return null;
@@ -29664,6 +30050,12 @@ function passAcVisualFloor(worktreeCwd, storyId) {
29664
30050
  const ids = missing.map((e) => e.ac ?? "?").join(", ");
29665
30051
  return { ok: false, reason: `pass AC(s) lack screenshot evidence or machine capture skip: ${ids}` };
29666
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
+ }
29667
30059
  function verificationReportPath(worktreeCwd, storyId) {
29668
30060
  return reportCandidates(worktreeCwd, storyId)[0];
29669
30061
  }
@@ -29699,7 +30091,7 @@ function verificationReportHasContent(worktreeCwd, storyId) {
29699
30091
  if (p === null)
29700
30092
  return false;
29701
30093
  try {
29702
- const html = readFileSync54(p, "utf8");
30094
+ const html = readFileSync55(p, "utf8");
29703
30095
  const hasMap = acMapCandidates(worktreeCwd, storyId).some((m7) => existsSync60(m7));
29704
30096
  if (!hasMap)
29705
30097
  return false;
@@ -29733,7 +30125,7 @@ function readAttestGateMode(repoCwd) {
29733
30125
  const p = join58(repoCwd, ".roll", "policy.yaml");
29734
30126
  if (!existsSync60(p))
29735
30127
  return "hard";
29736
- return parsePolicy(readFileSync54(p, "utf8")).loopSafety.attestGate === "hard" ? "hard" : "soft";
30128
+ return parsePolicy(readFileSync55(p, "utf8")).loopSafety.attestGate === "hard" ? "hard" : "soft";
29737
30129
  } catch {
29738
30130
  return "hard";
29739
30131
  }
@@ -29745,6 +30137,16 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks) {
29745
30137
  sinks.event({ cycleId, verdict: "produced", reasons: reasons2 });
29746
30138
  return { verdict: "produced", mode, reasons: reasons2, blocked: false };
29747
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
+ }
29748
30150
  const fresh = verificationReportFresh(worktreeCwd, storyId, sinceSec);
29749
30151
  if (fresh && verificationReportHasContent(worktreeCwd, storyId)) {
29750
30152
  const score = evaluateSelfScoreGate(worktreeCwd, storyId);
@@ -29775,7 +30177,7 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks) {
29775
30177
 
29776
30178
  // packages/cli/dist/runner/usage-recovery.js
29777
30179
  init_dist2();
29778
- 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";
29779
30181
  import { homedir as homedir17 } from "node:os";
29780
30182
  import { join as join59 } from "node:path";
29781
30183
  function defaultPiSessionsRoot() {
@@ -29798,7 +30200,7 @@ function recoverPiUsage(worktreeCwd, sinceSec, sessionsRoot = defaultPiSessionsR
29798
30200
  try {
29799
30201
  if (sinceSec !== void 0 && statSync22(p).mtimeMs / 1e3 < sinceSec)
29800
30202
  continue;
29801
- summaries.push(sumPiSession(readFileSync55(p, "utf8").split("\n")));
30203
+ summaries.push(sumPiSession(readFileSync56(p, "utf8").split("\n")));
29802
30204
  } catch {
29803
30205
  }
29804
30206
  }
@@ -29819,14 +30221,14 @@ function recoverPiUsage(worktreeCwd, sinceSec, sessionsRoot = defaultPiSessionsR
29819
30221
 
29820
30222
  // packages/cli/dist/runner/budget-check.js
29821
30223
  init_dist2();
29822
- import { existsSync as existsSync61, readFileSync as readFileSync56 } from "node:fs";
30224
+ import { existsSync as existsSync61, readFileSync as readFileSync57 } from "node:fs";
29823
30225
  import { join as join60 } from "node:path";
29824
30226
  function readBudgetPolicy(repoCwd) {
29825
30227
  try {
29826
30228
  const p = join60(repoCwd, ".roll", "policy.yaml");
29827
30229
  if (!existsSync61(p))
29828
30230
  return void 0;
29829
- return parsePolicy(readFileSync56(p, "utf8")).loopSafety.budget;
30231
+ return parsePolicy(readFileSync57(p, "utf8")).loopSafety.budget;
29830
30232
  } catch {
29831
30233
  return void 0;
29832
30234
  }
@@ -29835,7 +30237,7 @@ function ledgerFromRuns(runsPath3) {
29835
30237
  const ledger = new BudgetLedger();
29836
30238
  let body;
29837
30239
  try {
29838
- body = readFileSync56(runsPath3, "utf8");
30240
+ body = readFileSync57(runsPath3, "utf8");
29839
30241
  } catch {
29840
30242
  return ledger;
29841
30243
  }
@@ -29921,21 +30323,21 @@ function buildAcMapRemediationPrompt(worktreeCwd, storyId, runDir) {
29921
30323
  // packages/cli/dist/runner/correction-actuator.js
29922
30324
  init_dist2();
29923
30325
  init_dist();
29924
- 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";
29925
30327
  import { dirname as dirname25, join as join62 } from "node:path";
29926
30328
  function readMode(projectPath3) {
29927
30329
  try {
29928
30330
  const path = join62(projectPath3, ".roll", "policy.yaml");
29929
30331
  if (!existsSync63(path))
29930
30332
  return "conservative";
29931
- return parsePolicy(readFileSync57(path, "utf8")).loopSafety.correctionActuator;
30333
+ return parsePolicy(readFileSync58(path, "utf8")).loopSafety.correctionActuator;
29932
30334
  } catch {
29933
30335
  return "conservative";
29934
30336
  }
29935
30337
  }
29936
30338
  function readEvents(eventsPath2) {
29937
30339
  try {
29938
- 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);
29939
30341
  } catch {
29940
30342
  return [];
29941
30343
  }
@@ -29974,7 +30376,7 @@ function markStoryTodo(projectPath3, storyId) {
29974
30376
  }
29975
30377
  function storyEpic(projectPath3, storyId) {
29976
30378
  try {
29977
- const idx = JSON.parse(readFileSync57(join62(projectPath3, ".roll", "index.json"), "utf8"));
30379
+ const idx = JSON.parse(readFileSync58(join62(projectPath3, ".roll", "index.json"), "utf8"));
29978
30380
  const epic = idx[storyId];
29979
30381
  if (typeof epic === "string" && epic.trim() !== "")
29980
30382
  return epic;
@@ -29992,7 +30394,7 @@ function storyEpic(projectPath3, storyId) {
29992
30394
  }
29993
30395
  return "uncategorized";
29994
30396
  }
29995
- function existingFixId(projectPath3, storyId, signal2) {
30397
+ function existingFixId(projectPath3, storyId, signal) {
29996
30398
  try {
29997
30399
  const snap = new BacklogStore().readBacklog(join62(projectPath3, ".roll", "backlog.md"));
29998
30400
  return snap.items.find((it) => {
@@ -30003,7 +30405,7 @@ function existingFixId(projectPath3, storyId, signal2) {
30003
30405
  return false;
30004
30406
  if (!desc.includes(`fixes:${storyId.toLowerCase()}`))
30005
30407
  return false;
30006
- if (!desc.includes(`signal:${signal2.toLowerCase()}`))
30408
+ if (!desc.includes(`signal:${signal.toLowerCase()}`))
30007
30409
  return false;
30008
30410
  return classifyStatus(it.status) !== "done";
30009
30411
  })?.id;
@@ -30011,8 +30413,8 @@ function existingFixId(projectPath3, storyId, signal2) {
30011
30413
  return void 0;
30012
30414
  }
30013
30415
  }
30014
- function fixDescription(storyId, signal2) {
30015
- 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`;
30016
30418
  }
30017
30419
  function writeFixSpec(projectPath3, epic, fixId, decision) {
30018
30420
  try {
@@ -30339,7 +30741,45 @@ ${res.stderr}
30339
30741
  tcrCount = await ports.git.tcrCount(ports.paths.worktreePath);
30340
30742
  } catch {
30341
30743
  }
30342
- 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 = {
30343
30783
  alert: (m7) => ports.events.appendAlert(ports.paths.alertsPath, m7),
30344
30784
  event: (p) => ports.events.appendEvent(ports.paths.eventsPath, {
30345
30785
  type: "peer:gate",
@@ -30348,58 +30788,44 @@ ${res.stderr}
30348
30788
  reasons: p.reasons,
30349
30789
  ts: ports.clock()
30350
30790
  })
30351
- });
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
+ }
30352
30812
  {
30353
- const reviewPeer = async (peer, diff, timeoutMs) => {
30354
- 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.
30355
-
30356
- DIFF:
30357
- ` + diff;
30358
- let res;
30359
- try {
30360
- res = await Promise.race([
30361
- ports.agentSpawn(peer, {
30362
- cwd: ports.paths.worktreePath,
30363
- skillBody: prompt,
30364
- timeoutMs,
30365
- ...ctx.evidenceRunDir !== void 0 ? { runDir: ctx.evidenceRunDir } : {}
30366
- }),
30367
- new Promise((resolve5) => setTimeout(() => resolve5(null), timeoutMs).unref())
30368
- ]);
30369
- } catch {
30370
- return null;
30371
- }
30372
- if (res === null || res.timedOut || res.exitCode !== 0)
30373
- return null;
30374
- const vm = /VERDICT:\s*(agree|refine|object)/i.exec(res.stdout);
30375
- const verdict = vm?.[1]?.toLowerCase() ?? "agree";
30376
- const findings = [...res.stdout.matchAll(/^\s*FINDING:\s*(.+)$/gim)].map((m7) => (m7[1] ?? "").trim());
30377
- const cost = peerReviewCost(peer, res.stdout);
30378
- return { verdict, findings, cost };
30379
- };
30380
30813
  let pairHistory;
30381
30814
  try {
30382
30815
  if (existsSync64(ports.paths.eventsPath)) {
30383
- 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);
30384
30817
  pairHistory = pairingHistory(events);
30385
30818
  }
30386
30819
  } catch {
30387
30820
  }
30388
30821
  const pairingDeps = {
30389
- installed: agentsInstalled(realAgentEnv()),
30822
+ installed: ports.installedAgents?.() ?? agentsInstalled(realAgentEnv()),
30390
30823
  isAvailable: () => true,
30391
30824
  // MVP: `installed` is the hard gate; a dead peer → reviewPeer null (non-blocking). Real probe is a refinement.
30392
30825
  reviewPeer,
30393
30826
  ...pairHistory !== void 0 ? { history: pairHistory } : {},
30394
30827
  changedFiles: cycleChangedFiles,
30395
- diff: async (cwd) => {
30396
- try {
30397
- const { stdout } = await execFileAsync9("git", ["diff", "origin/main...HEAD"], { cwd, encoding: "utf8" });
30398
- return stdout.slice(0, 6e4);
30399
- } catch {
30400
- return "";
30401
- }
30402
- },
30828
+ diff: cycleDiff,
30403
30829
  event: (e) => ports.events.appendEvent(ports.paths.eventsPath, e),
30404
30830
  now: () => ports.clock()
30405
30831
  };
@@ -30512,10 +30938,13 @@ ${diffStat}`;
30512
30938
  }
30513
30939
  const facts = {
30514
30940
  usedWorktree: true,
30515
- // accept-path reaches capture at exit 0; a HARD attest block fails the
30516
- // capture (classifyCaptured: exit ≠ 0 → failed) so Done is withheld
30517
- // unless the PR-state probe reclassifies it "published" (FIX-244).
30518
- 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,
30519
30948
  timedOut: false,
30520
30949
  commitsAhead,
30521
30950
  ...mainAhead > 0 ? { mainAhead } : {},
@@ -30703,10 +31132,13 @@ function buildTerminalRecord(cmd, ctx, worktreePath, nowSec2) {
30703
31132
  } else {
30704
31133
  usage2 = absent("no_parseable_usage");
30705
31134
  }
31135
+ const routedModel = (ctx.model ?? "").trim() !== "" ? ctx.model : ctx.agent ?? "";
31136
+ const model = ctx.cost !== void 0 && ctx.cost.model !== "" ? ctx.cost.model : routedModel;
30706
31137
  return buildTerminalEvent({
30707
31138
  cycleId: cmd.cycleId,
30708
31139
  storyId,
30709
31140
  agent: ctx.agent ?? "",
31141
+ model,
30710
31142
  startedAt: ctx.startSec ?? nowSec2,
30711
31143
  endedAt: nowSec2,
30712
31144
  outcome: OUTCOME[cmd.status] ?? "unknown",
@@ -30724,7 +31156,7 @@ function readRunsRows(runsPath3) {
30724
31156
  try {
30725
31157
  if (!existsSync64(runsPath3))
30726
31158
  return [];
30727
- 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) => {
30728
31160
  try {
30729
31161
  return JSON.parse(l);
30730
31162
  } catch {
@@ -30747,14 +31179,14 @@ function storyRequiresManualMerge(repoCwd, storyId) {
30747
31179
  return needles.some((n) => lower.includes(n));
30748
31180
  };
30749
31181
  try {
30750
- const backlog = readFileSync58(join63(repoCwd, ".roll", "backlog.md"), "utf8");
31182
+ const backlog = readFileSync59(join63(repoCwd, ".roll", "backlog.md"), "utf8");
30751
31183
  const row2 = parseBacklog(backlog).find((it) => it.id === storyId);
30752
31184
  if (row2 !== void 0 && containsMarker(row2.desc))
30753
31185
  return true;
30754
31186
  } catch {
30755
31187
  }
30756
31188
  try {
30757
- return containsMarker(readFileSync58(join63(cardArchiveDir(repoCwd, storyId), "spec.md"), "utf8"));
31189
+ return containsMarker(readFileSync59(join63(cardArchiveDir(repoCwd, storyId), "spec.md"), "utf8"));
30758
31190
  } catch {
30759
31191
  return false;
30760
31192
  }
@@ -30793,7 +31225,7 @@ function persistWorktreeAlerts(worktreePath, alertsPath, events) {
30793
31225
  const path = join63(worktreePath, name);
30794
31226
  if (!lstatSync3(path).isFile())
30795
31227
  continue;
30796
- const body = readFileSync58(path, "utf8").trim();
31228
+ const body = readFileSync59(path, "utf8").trim();
30797
31229
  if (body === "")
30798
31230
  continue;
30799
31231
  events.appendAlert(alertsPath, `# worktree alert persisted: ${name}
@@ -30823,7 +31255,7 @@ async function linkRollIntoWorktree(repoCwd, worktreePath) {
30823
31255
  if (common === "")
30824
31256
  return;
30825
31257
  const exclude = join63(common, "info", "exclude");
30826
- const cur = existsSync64(exclude) ? readFileSync58(exclude, "utf8") : "";
31258
+ const cur = existsSync64(exclude) ? readFileSync59(exclude, "utf8") : "";
30827
31259
  if (!/^\.roll$/m.test(cur)) {
30828
31260
  mkdirSync32(dirname26(exclude), { recursive: true });
30829
31261
  appendFileSync13(exclude, `${cur === "" || cur.endsWith("\n") ? "" : "\n"}.roll
@@ -30947,7 +31379,7 @@ function nodePorts(opts) {
30947
31379
  const p = join63(projectCwd, ".roll", "backlog.md");
30948
31380
  if (!existsSync64(p))
30949
31381
  return [];
30950
- return parseBacklog(readFileSync58(p, "utf8"));
31382
+ return parseBacklog(readFileSync59(p, "utf8"));
30951
31383
  },
30952
31384
  // FIX-198: the production binding was MISSING entirely (the optional
30953
31385
  // chain made every In-Progress claim a silent no-op). ID-anchored mark
@@ -31180,21 +31612,21 @@ function describeCommand(cmd) {
31180
31612
  // packages/cli/dist/runner/correction-circuit.js
31181
31613
  init_dist2();
31182
31614
  init_dist();
31183
- 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";
31184
31616
  import { dirname as dirname27, join as join64 } from "node:path";
31185
31617
  function readLoopSafety(projectPath3) {
31186
31618
  try {
31187
31619
  const path = join64(projectPath3, ".roll", "policy.yaml");
31188
31620
  if (!existsSync65(path))
31189
31621
  return parsePolicy("").loopSafety;
31190
- return parsePolicy(readFileSync59(path, "utf8")).loopSafety;
31622
+ return parsePolicy(readFileSync60(path, "utf8")).loopSafety;
31191
31623
  } catch {
31192
31624
  return parsePolicy("").loopSafety;
31193
31625
  }
31194
31626
  }
31195
31627
  function readEvents2(eventsPath2) {
31196
31628
  try {
31197
- 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);
31198
31630
  } catch {
31199
31631
  return [];
31200
31632
  }
@@ -31251,7 +31683,7 @@ function applyCorrectionCircuitBreaker(projectPath3, slug, eventsPath2, alertsPa
31251
31683
  // packages/cli/dist/lib/morning-report.js
31252
31684
  init_dist2();
31253
31685
  init_dist();
31254
- 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";
31255
31687
  import { dirname as dirname28, join as join65 } from "node:path";
31256
31688
  var esc7 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
31257
31689
  function iso2(sec) {
@@ -31259,14 +31691,14 @@ function iso2(sec) {
31259
31691
  }
31260
31692
  function readEvents3(path) {
31261
31693
  try {
31262
- 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);
31263
31695
  } catch {
31264
31696
  return [];
31265
31697
  }
31266
31698
  }
31267
31699
  function readRuns(path) {
31268
31700
  try {
31269
- 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));
31270
31702
  } catch {
31271
31703
  return [];
31272
31704
  }
@@ -31338,7 +31770,7 @@ function writeLatestMorningReport(projectPath3, eventsPath2, runsPath3, nowSec2
31338
31770
  // packages/cli/dist/lib/runs-backfill.js
31339
31771
  init_dist2();
31340
31772
  init_dist3();
31341
- import { readFileSync as readFileSync61, writeFileSync as writeFileSync35 } from "node:fs";
31773
+ import { readFileSync as readFileSync62, writeFileSync as writeFileSync35 } from "node:fs";
31342
31774
  var BACKFILL_PROBE_CAP = 20;
31343
31775
  function parseLine(raw) {
31344
31776
  if (raw.trim() === "")
@@ -31360,7 +31792,7 @@ function isCandidate(row2) {
31360
31792
  async function backfillMergedRuns(projectPath3, runsPath3, deps = {}) {
31361
31793
  let body;
31362
31794
  try {
31363
- body = readFileSync61(runsPath3, "utf8");
31795
+ body = readFileSync62(runsPath3, "utf8");
31364
31796
  } catch {
31365
31797
  return [];
31366
31798
  }
@@ -31448,7 +31880,7 @@ function cycleSignalTeardown(paths, cycleId, branch, sig, deps = {}) {
31448
31880
  }
31449
31881
  let owned = false;
31450
31882
  try {
31451
- owned = existsSync67(paths.lockPath) && parseLock(readFileSync62(paths.lockPath, "utf8")).pid === pid;
31883
+ owned = existsSync67(paths.lockPath) && parseLock(readFileSync63(paths.lockPath, "utf8")).pid === pid;
31452
31884
  } catch {
31453
31885
  owned = false;
31454
31886
  }
@@ -31533,7 +31965,7 @@ function incrementConsecutiveFails(projectPath3, slug, alertsPath, eventsPath2,
31533
31965
  let count = 0;
31534
31966
  try {
31535
31967
  if (existsSync67(counterFile)) {
31536
- count = parseInt(readFileSync62(counterFile, "utf8").trim(), 10) || 0;
31968
+ count = parseInt(readFileSync63(counterFile, "utf8").trim(), 10) || 0;
31537
31969
  }
31538
31970
  } catch {
31539
31971
  }
@@ -31585,7 +32017,7 @@ function readFailurePauseThreshold(projectPath3) {
31585
32017
  const policy = join66(projectPath3, ".roll", "policy.yaml");
31586
32018
  if (!existsSync67(policy))
31587
32019
  return PAUSE_THRESHOLD;
31588
- return parsePolicy(readFileSync62(policy, "utf8")).loopSafety.maxConsecutiveFailures;
32020
+ return parsePolicy(readFileSync63(policy, "utf8")).loopSafety.maxConsecutiveFailures;
31589
32021
  } catch {
31590
32022
  return PAUSE_THRESHOLD;
31591
32023
  }
@@ -31610,7 +32042,7 @@ function buildLoopRouteDeps(projectPath3) {
31610
32042
  function readSlot(slot) {
31611
32043
  const agentsYaml = join66(projectPath3, ".roll", "agents.yaml");
31612
32044
  try {
31613
- return readSlotFromText(readFileSync62(agentsYaml, "utf8"), slot);
32045
+ return readSlotFromText(readFileSync63(agentsYaml, "utf8"), slot);
31614
32046
  } catch {
31615
32047
  return void 0;
31616
32048
  }
@@ -31625,7 +32057,7 @@ function buildLoopRouteDeps(projectPath3) {
31625
32057
  }
31626
32058
  function readField(path, re) {
31627
32059
  try {
31628
- const text = readFileSync62(path, "utf8");
32060
+ const text = readFileSync63(path, "utf8");
31629
32061
  for (const line of text.split("\n")) {
31630
32062
  const m7 = line.match(re);
31631
32063
  if (m7 !== null) {
@@ -31854,7 +32286,7 @@ async function egressBlocked(resolve5 = (h) => lookup(h), tcpProbe2 = () => tcpC
31854
32286
  // packages/cli/dist/commands/offboard.js
31855
32287
  init_dist();
31856
32288
  import { spawnSync as spawnSync7 } from "node:child_process";
31857
- 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";
31858
32290
  import { homedir as homedir18 } from "node:os";
31859
32291
  import { join as join67, resolve as resolve3 } from "node:path";
31860
32292
  function pal5() {
@@ -31982,7 +32414,7 @@ function offboardCommand(args) {
31982
32414
  `);
31983
32415
  return 1;
31984
32416
  }
31985
- const parsed = parseChangeset(readFileSync63(changeset, "utf8"));
32417
+ const parsed = parseChangeset(readFileSync64(changeset, "utf8"));
31986
32418
  if (!parsed.ok) {
31987
32419
  err10(m4("offboard.failed_to_parse_changeset"));
31988
32420
  return 1;
@@ -32076,7 +32508,7 @@ function offboardCommand(args) {
32076
32508
  for (const item of giEntries) {
32077
32509
  const gi = join67(projectDir, ".gitignore");
32078
32510
  if (existsSync68(gi)) {
32079
- const lines2 = readFileSync63(gi, "utf8").split("\n");
32511
+ const lines2 = readFileSync64(gi, "utf8").split("\n");
32080
32512
  if (lines2.includes(item)) {
32081
32513
  const kept = lines2.filter((l) => l !== item);
32082
32514
  writeFileSync37(gi, kept.join("\n"));
@@ -32100,12 +32532,12 @@ function offboardCommand(args) {
32100
32532
 
32101
32533
  // packages/cli/dist/commands/prices.js
32102
32534
  init_dist();
32103
- 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";
32104
32536
  import { join as join69 } from "node:path";
32105
32537
 
32106
32538
  // packages/cli/dist/commands/prices-refresh.js
32107
32539
  init_dist();
32108
- 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";
32109
32541
  import { join as join68 } from "node:path";
32110
32542
  var FetchError = class extends Error {
32111
32543
  constructor(message) {
@@ -32333,7 +32765,7 @@ function latestSnapshotPath(snapshotDir, vendor) {
32333
32765
  return snaps[snaps.length - 1] ?? null;
32334
32766
  }
32335
32767
  function readPrices(path) {
32336
- const data = JSON.parse(readFileSync64(path, "utf8"));
32768
+ const data = JSON.parse(readFileSync65(path, "utf8"));
32337
32769
  return data.prices ?? {};
32338
32770
  }
32339
32771
  function diffPrices(oldPrices, newPrices) {
@@ -32541,7 +32973,7 @@ function loadSnapshots() {
32541
32973
  }
32542
32974
  const files = readdirSync29(dir).filter((n) => n.startsWith("snapshot-") && n.endsWith(".json")).sort();
32543
32975
  return files.map((name) => {
32544
- const data = JSON.parse(readFileSync65(join69(dir, name), "utf8"));
32976
+ const data = JSON.parse(readFileSync66(join69(dir, name), "utf8"));
32545
32977
  for (const key of ["version", "effective_at", "source_url", "prices"]) {
32546
32978
  if (data[key] === void 0)
32547
32979
  throw new Error(`snapshot ${name} missing required key ${key}`);
@@ -32637,13 +33069,13 @@ function pricesCommand(args, deps = {}) {
32637
33069
  init_dist2();
32638
33070
  init_dist();
32639
33071
  import { execFileSync as execFileSync14 } from "node:child_process";
32640
- 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";
32641
33073
  import { join as join73 } from "node:path";
32642
33074
 
32643
33075
  // packages/cli/dist/lib/release-consistency.js
32644
33076
  init_dist2();
32645
33077
  init_dist();
32646
- 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";
32647
33079
  import { join as join71 } from "node:path";
32648
33080
 
32649
33081
  // packages/cli/dist/lib/consistency-audit.js
@@ -32651,7 +33083,7 @@ init_dist2();
32651
33083
  init_dist3();
32652
33084
  init_dist();
32653
33085
  import { execFile as execFile10 } from "node:child_process";
32654
- 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";
32655
33087
  import { dirname as dirname30, join as join70 } from "node:path";
32656
33088
  import { promisify as promisify10 } from "node:util";
32657
33089
  init_dist();
@@ -32663,7 +33095,7 @@ function readJsonl2(path) {
32663
33095
  if (!existsSync71(path))
32664
33096
  return [];
32665
33097
  const out3 = [];
32666
- for (const line of readFileSync66(path, "utf8").split("\n")) {
33098
+ for (const line of readFileSync67(path, "utf8").split("\n")) {
32667
33099
  if (line.trim() === "")
32668
33100
  continue;
32669
33101
  try {
@@ -32679,7 +33111,7 @@ function readJson(path) {
32679
33111
  if (!existsSync71(path))
32680
33112
  return null;
32681
33113
  try {
32682
- const parsed = JSON.parse(readFileSync66(path, "utf8"));
33114
+ const parsed = JSON.parse(readFileSync67(path, "utf8"));
32683
33115
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
32684
33116
  } catch {
32685
33117
  return null;
@@ -32742,7 +33174,7 @@ async function gatherAuditSnapshot(projectPath3, runtimeDir6, deps = {}) {
32742
33174
  const skipped = [];
32743
33175
  const backlogPath = join70(projectPath3, ".roll", "backlog.md");
32744
33176
  if (existsSync71(backlogPath)) {
32745
- 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 }));
32746
33178
  }
32747
33179
  snapshot.index = readIndex(projectPath3);
32748
33180
  snapshot.localMainAhead = deps.localMainAhead !== void 0 ? await deps.localMainAhead() : await gitLocalMainAhead(projectPath3);
@@ -32751,7 +33183,7 @@ async function gatherAuditSnapshot(projectPath3, runtimeDir6, deps = {}) {
32751
33183
  const terminal = [];
32752
33184
  const eventsPath2 = join70(runtimeDir6, "events.ndjson");
32753
33185
  if (existsSync71(eventsPath2)) {
32754
- for (const line of readFileSync66(eventsPath2, "utf8").split("\n")) {
33186
+ for (const line of readFileSync67(eventsPath2, "utf8").split("\n")) {
32755
33187
  const e = parseEventLine(line);
32756
33188
  if (e === null)
32757
33189
  continue;
@@ -32916,7 +33348,7 @@ function escapeRegExp3(s) {
32916
33348
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32917
33349
  }
32918
33350
  function readText(p) {
32919
- return readFileSync67(p, "utf8");
33351
+ return readFileSync68(p, "utf8");
32920
33352
  }
32921
33353
  var COMMAND_SURFACE_REPLACEMENTS = /* @__PURE__ */ new Map([
32922
33354
  ["migrate", "npx @seanyao/roll@2 migrate"],
@@ -33651,7 +34083,7 @@ function realReleaseDeps() {
33651
34083
  return {
33652
34084
  version: (cwd) => {
33653
34085
  try {
33654
- const pkg = JSON.parse(readFileSync69(join73(cwd, "package.json"), "utf8"));
34086
+ const pkg = JSON.parse(readFileSync70(join73(cwd, "package.json"), "utf8"));
33655
34087
  return typeof pkg.version === "string" ? pkg.version : "";
33656
34088
  } catch {
33657
34089
  return "";
@@ -33686,11 +34118,11 @@ function realReleaseDeps() {
33686
34118
  return true;
33687
34119
  }
33688
34120
  },
33689
- readChangelog: (cwd) => readFileSync69(join73(cwd, "CHANGELOG.md"), "utf8"),
34121
+ readChangelog: (cwd) => readFileSync70(join73(cwd, "CHANGELOG.md"), "utf8"),
33690
34122
  writeChangelog: (cwd, text) => writeFileSync41(join73(cwd, "CHANGELOG.md"), text, "utf8"),
33691
34123
  bumpVersion: (cwd, version) => {
33692
34124
  const path = join73(cwd, "package.json");
33693
- const pkg = JSON.parse(readFileSync69(path, "utf8"));
34125
+ const pkg = JSON.parse(readFileSync70(path, "utf8"));
33694
34126
  pkg["version"] = version;
33695
34127
  writeFileSync41(path, `${JSON.stringify(pkg, null, 2)}
33696
34128
  `, "utf8");
@@ -33998,7 +34430,7 @@ async function selfScoreCommand(args) {
33998
34430
  // packages/cli/dist/commands/setup.js
33999
34431
  init_dist();
34000
34432
  import { spawnSync as spawnSync9 } from "node:child_process";
34001
- 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";
34002
34434
  import { join as join74 } from "node:path";
34003
34435
  function err13(line) {
34004
34436
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
@@ -34072,7 +34504,7 @@ function walk(dir, lines2) {
34072
34504
  }
34073
34505
  function fileFingerprint(p) {
34074
34506
  try {
34075
- const buf = readFileSync70(p);
34507
+ const buf = readFileSync71(p);
34076
34508
  let sum = 0;
34077
34509
  for (let i = 0; i < buf.length; i++)
34078
34510
  sum = sum * 31 + (buf[i] ?? 0) >>> 0;
@@ -34266,7 +34698,7 @@ init_showcase2();
34266
34698
 
34267
34699
  // packages/cli/dist/commands/test.js
34268
34700
  import { spawnSync as spawnSync10 } from "node:child_process";
34269
- 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";
34270
34702
  import { dirname as dirname32, join as join75, resolve as resolve4 } from "node:path";
34271
34703
  function pal7() {
34272
34704
  const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
@@ -34349,7 +34781,7 @@ function isolationGetType() {
34349
34781
  return "none";
34350
34782
  let text;
34351
34783
  try {
34352
- text = readFileSync71(file, "utf8");
34784
+ text = readFileSync72(file, "utf8");
34353
34785
  } catch {
34354
34786
  return "none";
34355
34787
  }
@@ -34540,7 +34972,7 @@ function testCommand(args) {
34540
34972
 
34541
34973
  // packages/cli/dist/commands/tune.js
34542
34974
  init_dist2();
34543
- import { existsSync as existsSync77, readFileSync as readFileSync72 } from "node:fs";
34975
+ import { existsSync as existsSync77, readFileSync as readFileSync73 } from "node:fs";
34544
34976
  import { join as join76 } from "node:path";
34545
34977
  function projectPath2() {
34546
34978
  return (process.env["ROLL_MAIN_PROJECT"] ?? "").trim() || process.cwd();
@@ -34553,7 +34985,7 @@ function parseJsonl(path) {
34553
34985
  return [];
34554
34986
  let text;
34555
34987
  try {
34556
- text = readFileSync72(path, "utf8");
34988
+ text = readFileSync73(path, "utf8");
34557
34989
  } catch {
34558
34990
  return [];
34559
34991
  }
@@ -34725,7 +35157,7 @@ function tuneCommand(argv) {
34725
35157
  // packages/cli/dist/commands/update.js
34726
35158
  init_dist();
34727
35159
  import { spawnSync as spawnSync11 } from "node:child_process";
34728
- 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";
34729
35161
  import { tmpdir as tmpdir8 } from "node:os";
34730
35162
  import { join as join77 } from "node:path";
34731
35163
  function pal8() {
@@ -34846,7 +35278,7 @@ function showChangelog() {
34846
35278
  `);
34847
35279
  let count = 0;
34848
35280
  let inSection = false;
34849
- for (const line of readFileSync73(changelog, "utf8").split("\n")) {
35281
+ for (const line of readFileSync74(changelog, "utf8").split("\n")) {
34850
35282
  if (/^## /.test(line)) {
34851
35283
  count += 1;
34852
35284
  if (count > 3)
@@ -34871,7 +35303,7 @@ function updateCommand(args) {
34871
35303
  let installMethod = "npm";
34872
35304
  const methodFile = join77(rollPkgDir(), ".install-method");
34873
35305
  if (existsSync78(methodFile)) {
34874
- installMethod = readFileSync73(methodFile, "utf8").trim() || "npm";
35306
+ installMethod = readFileSync74(methodFile, "utf8").trim() || "npm";
34875
35307
  }
34876
35308
  if (installMethod === "curl") {
34877
35309
  info5(m6("update.upgrading_via_curl"));