tickmarkr 2.6.1 → 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +14 -3
  2. package/dist/adapters/catalog-remote.js +89 -47
  3. package/dist/adapters/claude-code.js +9 -6
  4. package/dist/adapters/codex.js +7 -4
  5. package/dist/adapters/prompt.d.ts +1 -0
  6. package/dist/adapters/prompt.js +14 -6
  7. package/dist/adapters/registry.js +3 -3
  8. package/dist/adapters/types.d.ts +12 -4
  9. package/dist/adapters/types.js +6 -0
  10. package/dist/cli/commands/approve.d.ts +5 -1
  11. package/dist/cli/commands/approve.js +66 -23
  12. package/dist/cli/commands/compile.js +13 -3
  13. package/dist/cli/commands/doctor.d.ts +2 -0
  14. package/dist/cli/commands/doctor.js +11 -3
  15. package/dist/cli/commands/fleet.js +45 -7
  16. package/dist/cli/commands/report.js +18 -2
  17. package/dist/cli/commands/resume.js +4 -2
  18. package/dist/cli/commands/status.js +24 -19
  19. package/dist/cli/help.d.ts +2 -0
  20. package/dist/cli/help.js +9 -2
  21. package/dist/config/config.d.ts +20 -0
  22. package/dist/config/config.js +47 -8
  23. package/dist/config/fleet-overlay.d.ts +1 -0
  24. package/dist/config/fleet-overlay.js +56 -0
  25. package/dist/drivers/orca.d.ts +26 -1
  26. package/dist/drivers/orca.js +193 -60
  27. package/dist/eval/canary.d.ts +2 -1
  28. package/dist/eval/canary.js +2 -2
  29. package/dist/eval/dispatch.js +1 -0
  30. package/dist/gates/acceptance.d.ts +2 -1
  31. package/dist/gates/acceptance.js +7 -2
  32. package/dist/gates/baseline.d.ts +12 -1
  33. package/dist/gates/baseline.js +11 -4
  34. package/dist/gates/llm.d.ts +5 -4
  35. package/dist/gates/llm.js +13 -13
  36. package/dist/gates/review.d.ts +8 -0
  37. package/dist/gates/review.js +40 -4
  38. package/dist/gates/run-gates.d.ts +2 -1
  39. package/dist/gates/run-gates.js +27 -13
  40. package/dist/gates/test-manifest.d.ts +3 -1
  41. package/dist/gates/test-manifest.js +9 -2
  42. package/dist/graph/schema.d.ts +2 -0
  43. package/dist/graph/schema.js +2 -0
  44. package/dist/plan/scope.js +2 -2
  45. package/dist/route/preference.d.ts +20 -2
  46. package/dist/route/preference.js +48 -13
  47. package/dist/route/router.js +30 -15
  48. package/dist/run/consult.d.ts +13 -1
  49. package/dist/run/consult.js +14 -5
  50. package/dist/run/daemon.d.ts +37 -2
  51. package/dist/run/daemon.js +579 -141
  52. package/dist/run/git.d.ts +8 -0
  53. package/dist/run/git.js +14 -0
  54. package/dist/run/journal.d.ts +126 -3
  55. package/dist/run/journal.js +410 -37
  56. package/dist/run/merge.d.ts +3 -1
  57. package/dist/run/merge.js +3 -2
  58. package/dist/run/operator-summary.d.ts +3 -0
  59. package/dist/run/operator-summary.js +3 -1
  60. package/dist/run/protocol.d.ts +31 -1
  61. package/dist/run/protocol.js +3 -1
  62. package/dist/run/supervision.d.ts +7 -1
  63. package/dist/run/supervision.js +5 -2
  64. package/dist/tui/cockpit/board.js +3 -3
  65. package/dist/tui/cockpit/decision-actions.d.ts +8 -5
  66. package/dist/tui/cockpit/decision-actions.js +55 -32
  67. package/dist/tui/cockpit/derive.js +13 -2
  68. package/dist/tui/cockpit/live-runtime.d.ts +10 -0
  69. package/dist/tui/cockpit/live-runtime.js +50 -3
  70. package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
  71. package/dist/tui/cockpit/run-cockpit.js +26 -1
  72. package/dist/tui/cockpit/run-view.d.ts +9 -3
  73. package/dist/tui/cockpit/run-view.js +60 -7
  74. package/dist/tui/cockpit/setup-cockpit.d.ts +4 -0
  75. package/dist/tui/cockpit/setup-cockpit.js +6 -3
  76. package/dist/tui/ink/fleet-app.d.ts +15 -3
  77. package/dist/tui/ink/fleet-app.js +91 -22
  78. package/package.json +2 -1
  79. package/schema/config.schema.json +818 -0
  80. package/skills/tickmarkr-loop/SKILL.md +8 -2
  81. package/skills/tickmarkr-overseer/SKILL.md +42 -0
  82. package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +88 -0
  83. package/skills/tickmarkr-overseer/scripts/context-statusline.sh +81 -0
  84. package/skills/tickmarkr-overseer/scripts/grade-ci.sh +36 -34
  85. package/skills/tickmarkr-overseer/scripts/watch-journal.sh +6 -4
@@ -5,7 +5,7 @@ import { GLYPHS, PLAIN_COMPACT_LOCKUP, } from "../../brand.js";
5
5
  import { allocateBandColumns, BandLines, BodyText, CockpitGrid, composeBandLine, JournalRowPanel, keyRosterLines, Panel, PANEL_CHROME_ROWS, ProgressMeter, Sparkline, StatTile, StatusStrip, } from "./components.js";
6
6
  import { authorsNote } from "../../run/operator-state.js";
7
7
  import { fieldReading } from "./derive.js";
8
- import { STALL_MARKER } from "./run-view.js";
8
+ import { MISSING_EVIDENCE, STALL_MARKER } from "./run-view.js";
9
9
  import { initialRunInteractionState, projectRunKeyEntries, reconcileRunInteraction, RUN_SIDE_RAIL_COLUMN_FLOOR, runPanelFocusOrder, runSideRailVisible, selectableRunViewRowIds, } from "./keys.js";
10
10
  import { DECISIONS_TAB_TITLE, RUN_VIEWS, runViewById, setupSectionAt, SETUP_SECTIONS, } from "./views.js";
11
11
  import { FRAME_HEADER_GAP, FRAME_NESTED_ROW_BANDS, FULL_JOURNAL_ROWS, planFrame, RUN_PANEL_HORIZONTAL_CHROME, SIDEBAR_COLUMN_FLOOR, } from "./layout.js";
@@ -309,8 +309,13 @@ function promotedViewRows(data, viewId) {
309
309
  * task row — identity, phase, build evidence, blocker, next action — each unrecorded field saying so,
310
310
  * each recorded one citing the journal line it was read from, and the OBS-1048 stall marker when the
311
311
  * newest attempt's harvest is suspect. No attempt label is printed here: the ruler is the row's own.
312
+ * A task with no recorded dispatch folds identity, phase, and build into one missing-evidence clause
313
+ * (OBS-1104). A recorded blocker or next action — a pre-dispatch park, a dependency wait — stays,
314
+ * cited to the journal row that produced it.
312
315
  */
313
316
  export function taskProjectionText(row, journal = []) {
317
+ if ((row.attempts ?? 0) === 0)
318
+ return undispatchedProjectionText(row, journal);
314
319
  const identity = row.identities?.at(-1);
315
320
  const blocker = row.summary?.blocker;
316
321
  const harvest = row.harvests?.at(-1);
@@ -331,6 +336,26 @@ export function taskProjectionText(row, journal = []) {
331
336
  ...(harvest?.suspectedStalledHarvest ? [`${STALL_MARKER} · launch ${at(evidence.launch)} · ${harvest.nudgeFailures} nudge failed ${at(evidence.nudge)} · ${harvest.pageCount} paged ${at(evidence.page)} · no worker-result`] : []),
332
337
  ].reduce((text, part) => `${text} · ${part}`);
333
338
  }
339
+ /**
340
+ * Identity, phase, and build are unrecorded until a dispatch. Append the blocker and next action
341
+ * only when the summary recorded one — an unknown placeholder with no journal row is the missing clause.
342
+ */
343
+ function undispatchedProjectionText(row, journal) {
344
+ const blocker = row.summary?.blocker;
345
+ if (blocker == null)
346
+ return MISSING_EVIDENCE;
347
+ const evidence = projectionEvidence(row, journal);
348
+ const at = (line) => line === undefined ? "(no journal row)" : `#L${line}`;
349
+ const showBlocker = blocker.kind !== "unknown" || evidence.blocker !== undefined;
350
+ const showNext = blocker.nextAction != null;
351
+ if (!showBlocker && !showNext)
352
+ return MISSING_EVIDENCE;
353
+ return [
354
+ MISSING_EVIDENCE,
355
+ ...(showBlocker ? [`blocker ${blocker.kind} ${at(evidence.blocker)}`] : []),
356
+ ...(showNext ? [`next ${blocker.nextAction} ${at(evidence.nextAction)}`] : []),
357
+ ].reduce((text, part) => `${text} · ${part}`);
358
+ }
334
359
  /**
335
360
  * Correlate each already-derived field with the row that can have produced that value. This is
336
361
  * intentionally not a "newest row with a plausible name" lookup: launch identity is paired by
@@ -1,7 +1,7 @@
1
1
  import { type ReactElement } from "react";
2
2
  import type { ApprovalRunOwner } from "../../cli/commands/approve.js";
3
3
  import { type GateName, type RunGraph } from "../../graph/schema.js";
4
- import type { JournalEvent } from "../../run/journal.js";
4
+ import { type JournalEvent } from "../../run/journal.js";
5
5
  import { type GateOutcome } from "../../run/outcome.js";
6
6
  import type { OperatorGateState, OperatorSnapshot, OperatorTask } from "../../run/operator-state.js";
7
7
  import { type DecisionCommand, type DecisionPreview, type DecisionSession, type RunDecision } from "./decision-actions.js";
@@ -93,8 +93,9 @@ export interface TaskProjection {
93
93
  readonly build: ProjectionField;
94
94
  readonly blocker: ProjectionField;
95
95
  readonly nextAction: ProjectionField;
96
- /** OBS-1048: present only when the newest launched attempt has failed nudges, pages and no worker-result. */
96
+ /** OBS-1048: present only when the newest launched attempt has failed nudges, pages and no worker-result. OBS-1104: neverDispatched collapses that task's line. */
97
97
  readonly stalled?: ProjectionField;
98
+ readonly neverDispatched?: true;
98
99
  }
99
100
  export declare const STALL_MARKER = "\u26A0 stalled harvest suspected";
100
101
  /**
@@ -103,7 +104,12 @@ export declare const STALL_MARKER = "\u26A0 stalled harvest suspected";
103
104
  * Nothing is inferred from a title, a provider or a task id: an unrecorded field says so.
104
105
  */
105
106
  export declare function projectRunTasks(snapshot: OperatorSnapshot, rows: readonly RunEvidenceRow[], graph: RunGraph | undefined, decisions: readonly RunDecision[], runId: string): readonly TaskProjection[];
106
- /** One wrapped line per task: every field with its line, and the stall marker when the harvest is suspect. */
107
+ /**
108
+ * OBS-1104: a never-dispatched task has no identity, phase, or build row. Those fold into one
109
+ * missing-evidence clause. A substantive or journal-cited blocker and next action stay — a
110
+ * pre-dispatch park and a dependency wait are recorded facts, and dropping them hides the park token.
111
+ */
112
+ export declare const MISSING_EVIDENCE = "missing evidence";
107
113
  export declare const projectionLine: (p: TaskProjection) => string;
108
114
  export interface PaneLocator {
109
115
  readonly status: "recorded" | "unavailable";
@@ -5,6 +5,7 @@ import { GLYPHS } from "../../brand.js";
5
5
  import { formatOwnedName, parseOwnedName } from "../../drivers/types.js";
6
6
  import { GATE_NAMES } from "../../graph/schema.js";
7
7
  import { projectActivity } from "../../run/activity.js";
8
+ import { effectiveDecisions, withPhysicalLine } from "../../run/journal.js";
8
9
  import { projectOperatorSummary } from "../../run/operator-summary.js";
9
10
  import { trackJournalRows } from "../../run/protocol.js";
10
11
  import { normalizeGateOutcome } from "../../run/outcome.js";
@@ -122,7 +123,7 @@ export function runGateCells(task, evidence, rows = []) {
122
123
  const e = later.event;
123
124
  if (e.event === "gate-reused" && e.data.gate === gate)
124
125
  labels.push(`inherited from ${str(e.data.commit) ?? "unknown commit"} · #L${later.line}`);
125
- if (e.event === "task-approved" && e.data.release === "gate-satisfied" && e.data.gate === gate)
126
+ if (e.event === "task-approved" && e.data.release === "gate-satisfied" && e.data.gate === gate && !undecidedLines(evidence.later(0)).has(later.line))
126
127
  labels.push(`satisfied by approval #L${later.line}`);
127
128
  }
128
129
  const verdict = row?.event?.event === "gate-result" && typeof data?.details === "string" ? data.details.split("\n") : [];
@@ -191,7 +192,7 @@ const readLabel = (f) => `${f.label} ${f.line === undefined ? `(${NO_ROW})` : `#
191
192
  * Nothing is inferred from a title, a provider or a task id: an unrecorded field says so.
192
193
  */
193
194
  export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
194
- const ordered = [...rows].filter((r) => r.event !== undefined).sort((a, b) => a.line - b.line);
195
+ const ordered = decidedRows(rows);
195
196
  const graphTask = (id) => graph?.tasks.find((t) => t.id === id);
196
197
  const activityTasks = snapshot.tasks.map((t) => ({ id: t.id, gates: graphTask(t.id)?.gates ?? [...GATE_NAMES], deps: graphTask(t.id)?.deps ?? [], status: t.state }));
197
198
  const tracked = trackJournalRows(runId, ordered.map((r) => ({ raw: r.event, sourceIndex: r.line })));
@@ -208,7 +209,16 @@ export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
208
209
  ...(own.length === 0 ? {} : { phase: activity.get(t.id)?.state, lastEvidenceAt: own.at(-1).event.ts }),
209
210
  ...(responsibility ? { responsible: { role: str(responsibility.event.data.role), agent: str(responsibility.event.data.agent) } } : {}),
210
211
  };
211
- }), decisions.map((d) => ({ taskId: d.taskId, park: { kind: d.park.kind, tombstone: d.park.tombstone, ...(d.park.reason === undefined ? {} : { reason: d.park.reason }) }, verbs: d.verbs, ...(d.diagnostic === undefined ? {} : { diagnostic: d.diagnostic }) }))).map((s) => [s.taskId, s]));
212
+ }), decisions.map((d) => ({
213
+ taskId: d.taskId,
214
+ park: {
215
+ kind: d.park.kind, tombstone: d.park.tombstone, line: d.park.line,
216
+ ...(d.park.ts === undefined ? {} : { ts: d.park.ts }),
217
+ ...(d.park.reason === undefined ? {} : { reason: d.park.reason }),
218
+ },
219
+ verbs: d.verbs,
220
+ ...(d.diagnostic === undefined ? {} : { diagnostic: d.diagnostic }),
221
+ }))).map((s) => [s.taskId, s]));
212
222
  return snapshot.tasks.map((t) => {
213
223
  const own = byTask(t.id);
214
224
  const act = activity.get(t.id);
@@ -272,7 +282,7 @@ export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
272
282
  const stalled = launched !== undefined && !returned && nudges.length > 0 && pages.length > 0
273
283
  ? { label: `${STALL_MARKER} · launch #L${launched} · nudge failed ${nudges.map((l) => `#L${l}`).join(",")} · paged ${pages.map((l) => `#L${l}`).join(",")} · no worker-result`, line: launched }
274
284
  : undefined;
275
- return { taskId: t.id, identity, phase, build, blocker, nextAction, ...(stalled ? { stalled } : {}) };
285
+ return { taskId: t.id, identity, phase, build, blocker, nextAction, ...(stalled ? { stalled } : {}), ...(t.dispatches === 0 ? { neverDispatched: true } : {}) };
276
286
  });
277
287
  }
278
288
  /** The line at which each projected value last changed, folding the task's rows (and run-level resets) prefix by prefix. */
@@ -299,8 +309,16 @@ function acceptedChanges(runId, tracked, taskId, task) {
299
309
  }
300
310
  return out;
301
311
  }
302
- /** One wrapped line per task: every field with its line, and the stall marker when the harvest is suspect. */
303
- export const projectionLine = (p) => [p.taskId, readLabel(p.identity), readLabel(p.phase), readLabel(p.build), readLabel(p.blocker), readLabel(p.nextAction), ...(p.stalled ? [p.stalled.label] : [])].join(" · ");
312
+ /**
313
+ * OBS-1104: a never-dispatched task has no identity, phase, or build row. Those fold into one
314
+ * missing-evidence clause. A substantive or journal-cited blocker and next action stay — a
315
+ * pre-dispatch park and a dependency wait are recorded facts, and dropping them hides the park token.
316
+ */
317
+ export const MISSING_EVIDENCE = "missing evidence";
318
+ const UNRECORDED_BLOCKER = new Set(["blocker none", "blocker unknown"]);
319
+ const UNRECORDED_NEXT = new Set(["next none"]);
320
+ const keptWhenUndispatched = (field, unrecorded) => field.line !== undefined || !unrecorded.has(field.label);
321
+ export const projectionLine = (p) => p.neverDispatched === true ? undispatchedProjectionLine(p) : recordedProjectionLine(p);
304
322
  /**
305
323
  * Where the current attempt's pane is recorded — and only that. The reading is exact-name: the
306
324
  * launch row's slot name must be the owned name of THIS task, attempt and run. No terminal is ever
@@ -358,6 +376,29 @@ export function paneLocator(task, rows, runId) {
358
376
  }
359
377
  /** The board's footer keys merged with the cockpit's own — every one of them handled by this view or the shell. */
360
378
  export const RUN_VIEW_KEYS = [...BOARD_KEYS.slice(0, 2), "←→ verdict gate", "PgUp/PgDn page", "x outcome", "a actions", "o pane", "Tab focus", "? keys", BOARD_KEYS[2]];
379
+ function recordedProjectionLine(p) {
380
+ return [p.taskId, readLabel(p.identity), readLabel(p.phase), readLabel(p.build), readLabel(p.blocker), readLabel(p.nextAction), ...(p.stalled ? [p.stalled.label] : [])].join(" · ");
381
+ }
382
+ function undispatchedProjectionLine(p) {
383
+ return [
384
+ p.taskId,
385
+ MISSING_EVIDENCE,
386
+ ...(keptWhenUndispatched(p.blocker, UNRECORDED_BLOCKER) ? [readLabel(p.blocker)] : []),
387
+ ...(keptWhenUndispatched(p.nextAction, UNRECORDED_NEXT) ? [readLabel(p.nextAction)] : []),
388
+ ...(p.stalled ? [p.stalled.label] : []),
389
+ ].join(" · ");
390
+ }
391
+ /**
392
+ * Rows the projection panel paints. A never-dispatched clause is shorter than the field line it
393
+ * replaces; the shell's content counter is the panel's wrapped height, and the pinned run frames
394
+ * record that counter. Blank rows keep the block as tall as the field lines were.
395
+ */
396
+ function projectionBlockRows(projections, wrap) {
397
+ const shown = projections.flatMap((p) => wrap(projectionLine(p)).map((line, i) => ({ key: `${p.taskId}:${i}`, line, strong: p.stalled !== undefined && i === 0 })));
398
+ const prior = projections.flatMap((p) => wrap(recordedProjectionLine(p)));
399
+ const pad = Math.max(0, prior.length - shown.length);
400
+ return [...shown, ...Array.from({ length: pad }, (_, i) => ({ key: `projection-pad:${i}`, line: " ", strong: false }))];
401
+ }
361
402
  /** The Run body: the approved board (BD-1) over the fold, then the selected task's detail panels. */
362
403
  export function RunView({ snapshot, rows, page, graph, decisions, session, columns, run, now = Date.now }) {
363
404
  const inner = Math.max(1, Math.floor(columns) - 4);
@@ -383,9 +424,21 @@ export function RunView({ snapshot, rows, page, graph, decisions, session, colum
383
424
  const { menu, confirming, receipt, notice } = session.decisions;
384
425
  const blocked = task === undefined ? [] : graph?.tasks.filter((g) => g.deps.includes(task.id)).map((g) => g.id) ?? decision?.blocks ?? [];
385
426
  const board = renderBoardLines({ runId: run.runId, snapshot, graph, now: now(), live: run.live, selection: task?.id, keys: false, colour }, width);
386
- return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(BodyText, { emphasis: "dim", children: fitCells(`RUN / ${snapshot.lifecycle} · ${runSummaryLine(snapshot)}`, width) }), board.map((line, i) => _jsx(BodyText, { children: line || " " }, `${i}:${line}`)), tasks.length === 0 && _jsx(BodyText, { children: fitCells("no tasks recorded — no plan", width) }), task !== undefined && (_jsxs(Panel, { title: `SELECTED / ${task.id}${task.title ? ` · ${task.title}` : ""}`, children: [wrap(`state ${parkOf(task)} · ${attemptLabel(task)} · dispatches ${task.dispatches} · path ${task.path ?? "unknown"} · pane ${task.pane ?? "unknown"} · alarm ${task.alarmMs === undefined ? "unknown" : `${task.alarmMs}ms`}${task.mergeEvidence ? ` · merged #L${task.mergeEvidence.line}` : ""} · locator ${(() => { const l = paneLocator(task, allRows, run.runId); return l.status === "recorded" ? `recorded ${l.reason}` : `unavailable — ${l.reason}`; })()}`).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)), _jsx(BodyText, { emphasis: "dim", children: fit(`outcome filter ${session.outcomeFilter} (x cycles) · ${shown.length} of ${cells.length} gates shown`) }), shown.map((c) => (_jsx(BodyText, { children: fit(`${c.letter} ${c.gate.padEnd(10)} ${c.labels.join(" · ")}${c.line === undefined ? "" : ` · #L${c.line}`}`) }, c.gate)))] })), projections.length > 0 && (_jsx(Panel, { title: "PROJECTION / every task \u00B7 identity \u00B7 phase \u00B7 build \u00B7 blocker \u00B7 next action, each with its journal line", children: projections.flatMap((p) => wrap(projectionLine(p)).map((line, i) => _jsx(BodyText, { emphasis: p.stalled && i === 0 ? "strong" : "normal", children: line }, `${p.taskId}:${i}`))) })), verdictCell !== undefined && (_jsxs(Panel, { title: `VERDICT / ${verdictCell.gate}${verdictCell.line === undefined ? "" : ` #L${verdictCell.line}`} · ${verdict.length === 0 ? "no verdict text" : `lines ${from + 1}–${from + window.length} of ${verdict.length}`}`, children: [window.map((line, i) => _jsx(BodyText, { children: fit(`${String(from + i + 1).padStart(4)} ${line}`) }, `${from + i}:${line}`)), _jsx(BodyText, { emphasis: "dim", children: fit("←→ gate · PageUp/PageDown page") })] })), task?.state === "human" && (_jsx(Panel, { title: `PARK / ${task.id}`, children: decision === undefined
427
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(BodyText, { emphasis: "dim", children: fitCells(`RUN / ${snapshot.lifecycle} · ${runSummaryLine(snapshot)}`, width) }), board.map((line, i) => _jsx(BodyText, { children: line || " " }, `${i}:${line}`)), tasks.length === 0 && _jsx(BodyText, { children: fitCells("no tasks recorded — no plan", width) }), task !== undefined && (_jsxs(Panel, { title: `SELECTED / ${task.id}${task.title ? ` · ${task.title}` : ""}`, children: [wrap(`state ${parkOf(task)} · ${attemptLabel(task)} · dispatches ${task.dispatches} · path ${task.path ?? "unknown"} · pane ${task.pane ?? "unknown"} · alarm ${task.alarmMs === undefined ? "unknown" : `${task.alarmMs}ms`}${task.mergeEvidence ? ` · merged #L${task.mergeEvidence.line}` : ""} · locator ${(() => { const l = paneLocator(task, allRows, run.runId); return l.status === "recorded" ? `recorded ${l.reason}` : `unavailable — ${l.reason}`; })()}`).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)), _jsx(BodyText, { emphasis: "dim", children: fit(`outcome filter ${session.outcomeFilter} (x cycles) · ${shown.length} of ${cells.length} gates shown`) }), shown.map((c) => (_jsx(BodyText, { children: fit(`${c.letter} ${c.gate.padEnd(10)} ${c.labels.join(" · ")}${c.line === undefined ? "" : ` · #L${c.line}`}`) }, c.gate)))] })), projections.length > 0 && (_jsx(Panel, { title: "PROJECTION / every task \u00B7 identity \u00B7 phase \u00B7 build \u00B7 blocker \u00B7 next action, each with its journal line", children: projectionBlockRows(projections, wrap).map((row) => _jsx(BodyText, { emphasis: row.strong ? "strong" : "normal", children: row.line }, row.key)) })), verdictCell !== undefined && (_jsxs(Panel, { title: `VERDICT / ${verdictCell.gate}${verdictCell.line === undefined ? "" : ` #L${verdictCell.line}`} · ${verdict.length === 0 ? "no verdict text" : `lines ${from + 1}–${from + window.length} of ${verdict.length}`}`, children: [window.map((line, i) => _jsx(BodyText, { children: fit(`${String(from + i + 1).padStart(4)} ${line}`) }, `${from + i}:${line}`)), _jsx(BodyText, { emphasis: "dim", children: fit("←→ gate · PageUp/PageDown page") })] })), task?.state === "human" && (_jsx(Panel, { title: `PARK / ${task.id}`, children: decision === undefined
387
428
  ? _jsx(BodyText, { children: fit("park evidence unavailable — refresh before deciding") })
388
429
  : (_jsxs(_Fragment, { children: [wrap(`#L${decision.park.line} ${decision.park.kind ?? "unknown kind"}${decision.park.failedGate ? ` · failed gate ${decision.park.failedGate}` : ""} · ${decision.park.reason ?? "no reason recorded"}`).map((line, i) => _jsx(BodyText, { children: line }, `park:${i}`)), _jsx(BodyText, { children: fit(`blocks ${blocked.length === 0 ? "nothing" : blocked.join(", ")} · attempts ${decision.attempts}`) }), wrap(decision.verbs.length === 0 ? `diagnostic: ${decision.diagnostic ?? "no verb"}` : `decisions: ${decision.verbs.join(" · ")} (a Actions)`).map((line, i) => _jsx(BodyText, { children: line }, `verbs:${i}`)), _jsx(BodyText, { emphasis: "dim", children: fit(run.live ? "matching live daemon enacts a release at its next task boundary" : run.blockingRunId ? `other live run ${run.blockingRunId} holds the lock; resume after it ends` : "no live owner: a release records permission; resume dispatches") })] })) })), menu !== null && (_jsx(Panel, { title: `ACTIONS / ${menu.taskId}`, focused: true, children: menu.verbs.length === 0
389
430
  ? _jsx(BodyText, { children: fit(`no decision — ${decision?.diagnostic ?? "no verb for this park"}`) })
390
431
  : menu.verbs.map((verb, i) => _jsx(BodyText, { emphasis: i === menu.selection ? "strong" : "normal", children: fit(`${i === menu.selection ? `${GLYPHS.pointer} ` : " "}${verb}`) }, verb)) })), confirming !== null && (_jsx(Panel, { title: `CONFIRM ${confirming.command.verb.toUpperCase()}`, focused: true, children: decisionLines(decisionConfirmLines(confirming)).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)) })), receipt !== null && (_jsx(Panel, { title: receipt.ok ? "RECEIPT · appended" : "RECEIPT · refused", children: decisionLines(decisionReceiptLines(receipt)).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)) })), notice !== null && _jsx(BodyText, { children: notice }), _jsx(BodyText, { emphasis: "dim", children: clipBoard(boardFooter([...RUN_VIEW_KEYS, decisionKeybar(session.decisions, decision)].filter(Boolean), colour), width) })] }));
391
432
  }
433
+ // OBS-1178: a refused or unsound decision released and satisfied nothing, so neither the projections nor
434
+ // a gate cell's satisfied label read it; the one decision fold judges every row by its physical line.
435
+ function undecidedLines(rows) {
436
+ const ordered = rows.filter((r) => r.event !== undefined).sort((a, b) => a.line - b.line);
437
+ const effective = effectiveDecisions(ordered.map((r) => withPhysicalLine(r.event, r.line)));
438
+ return new Set(ordered.filter((r) => r.event.event === "task-approved" && !effective.has(r.line)).map((r) => r.line));
439
+ }
440
+ /** Readable rows in physical-line order, minus the decisions that are not effective. */
441
+ function decidedRows(rows) {
442
+ const undecided = undecidedLines(rows);
443
+ return rows.filter((r) => r.event !== undefined && !undecided.has(r.line)).sort((a, b) => a.line - b.line);
444
+ }
@@ -71,6 +71,8 @@ export type ParkedDecision = {
71
71
  * so it is drawn read-only and neither verb is offered on it.
72
72
  */
73
73
  readonly tombstone: boolean;
74
+ /** OBS-1178: the park's `<line>@<ts>` token — the confirmed write binds to it via `--park`. */
75
+ readonly park?: string;
74
76
  };
75
77
  /** The parks a verb can release — the rows the surface draws live verbs on. */
76
78
  export declare function actionableDecisions(decisions: readonly ParkedDecision[]): readonly ParkedDecision[];
@@ -80,6 +82,8 @@ export type SetupDecisionVerb = "approve" | "waive" | "uphold" | "recheck";
80
82
  export type SetupDecisionCommand = {
81
83
  readonly verb: SetupDecisionVerb;
82
84
  readonly taskId: string;
85
+ /** OBS-1178: the park token the confirm inset displayed — approve refuses it once a newer park opens. */
86
+ readonly park?: string;
83
87
  };
84
88
  /** What the journal file gained this session — one entry per confirmed write. */
85
89
  export type SetupDecisionWrite = {
@@ -5,7 +5,7 @@ import { join } from "node:path";
5
5
  import { cloneElement, createElement, useState, } from "react";
6
6
  import { parse } from "yaml";
7
7
  import { approvalDispositionForRelease, approvalEnactment, approvalRunOwner, approve, } from "../../cli/commands/approve.js";
8
- import { ATTEMPT_CAP_RELEASE, GATE_SATISFIED_RELEASE, Journal, RECHECK_RELEASE, REVIEW_UPHELD_RELEASE, } from "../../run/journal.js";
8
+ import { ATTEMPT_CAP_RELEASE, bindingToken, GATE_SATISFIED_RELEASE, Journal, RECHECK_RELEASE, REVIEW_UPHELD_RELEASE, } from "../../run/journal.js";
9
9
  import { BANNER, GLYPHS, PLAIN_BANNER, } from "../../brand.js";
10
10
  import { DEFAULT_CONFIG, INTEGRITY_FLOOR_SHAPES, TIER_RANK, TickmarkrConfigSchema, unifiedYamlDiff, } from "../../config/config.js";
11
11
  import { GATE_NAMES, SHAPES } from "../../graph/schema.js";
@@ -576,7 +576,7 @@ function failedGateForNewestPark(events, taskId, parkedIndex) {
576
576
  }
577
577
  export function deriveParkedDecisions(journal) {
578
578
  const statuses = journal.replayStatuses();
579
- const events = journal.read();
579
+ const { events, lines } = journal.readSourced();
580
580
  const decisions = [];
581
581
  for (const [taskId, status] of statuses) {
582
582
  if (status !== "human")
@@ -606,6 +606,7 @@ export function deriveParkedDecisions(journal) {
606
606
  attempts,
607
607
  failedGate,
608
608
  tombstone: isTombstonePark(kind, reason),
609
+ ...(typeof parked?.ts === "string" ? { park: bindingToken({ line: lines[parkedIndex], ts: parked.ts }) } : {}),
609
610
  });
610
611
  }
611
612
  return decisions;
@@ -677,7 +678,7 @@ export function applySetupDecisionsKey(session, event, parked) {
677
678
  session: {
678
679
  ...session,
679
680
  selection,
680
- confirming: { verb, taskId: decision.taskId },
681
+ confirming: { verb, taskId: decision.taskId, ...(decision.park === undefined ? {} : { park: decision.park }) },
681
682
  },
682
683
  };
683
684
  }
@@ -704,6 +705,7 @@ export async function executeSetupDecision(command, { cwd, runId, by }) {
704
705
  runId,
705
706
  command.taskId,
706
707
  ...decisionFlag(command.verb),
708
+ ...(command.park === undefined ? [] : ["--park", command.park]),
707
709
  "--by",
708
710
  by,
709
711
  ], cwd);
@@ -800,6 +802,7 @@ function decisionEffectLines(command, decision, run) {
800
802
  export function setupDecisionConfirmLines(command, decision, actor, journalFile, run) {
801
803
  return [
802
804
  `task ${command.taskId}${decision === undefined ? "" : ` · ${decision.kind} · attempt ${decision.attempts}`}`,
805
+ ...(command.park === undefined ? [] : [`park ${command.park} · bound; refused if a newer park opens`]),
803
806
  `actor ${actor}`,
804
807
  ...decisionEffectLines(command, decision, run),
805
808
  `file ${journalFile} · append only`,
@@ -1,11 +1,13 @@
1
1
  import { type AuthHealth, type WorkerAdapter } from "../../adapters/types.js";
2
2
  import { type DenyScopeKey, type InitialDenyProps, type MapEntry, type RoutingMode, type StagedDenyKey, type Tier, type TickmarkrConfig } from "../../config/config.js";
3
- import { type Shape } from "../../graph/schema.js";
3
+ import { type Effort, type Shape } from "../../graph/schema.js";
4
4
  export type FleetSteeringKey = "review" | "consult";
5
5
  export type FleetEditorState = Record<DenyScopeKey, string[]> & {
6
6
  /** OBS-1046: the staged routing.allow complement, distinct from the authored deny lists above */
7
7
  allowOut?: string[];
8
8
  classifications: FleetClassification[];
9
+ /** OBS-1182: staged launch effort per adapter → model; absent = the CLI's own default */
10
+ efforts?: Record<string, Record<string, Effort>>;
9
11
  selectedMode: RoutingMode;
10
12
  map: Record<string, MapEntry>;
11
13
  judgeSeat?: {
@@ -22,6 +24,8 @@ export type FleetOverlayReview = {
22
24
  after: string;
23
25
  diff: string;
24
26
  path: string;
27
+ /** OBS-1144: dead pool entries the written config would carry, each with its reason. */
28
+ notes?: string[];
25
29
  };
26
30
  export type FleetEditorResult = {
27
31
  kind: "write";
@@ -121,11 +125,13 @@ export type FleetStagedRouting = {
121
125
  ok: false;
122
126
  error: string;
123
127
  };
124
- export declare function FleetApp({ ageMs, agents, initialAllowOut, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, viewRows, frameColumns, frameRows, ...initialDenyProps }: InitialDenyProps & {
128
+ export declare function FleetApp({ ageMs, agents, initialAllowOut, initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, viewRows, frameColumns, frameRows, ...initialDenyProps }: InitialDenyProps & {
125
129
  ageMs: number | null;
126
130
  agents: AgentCli[];
127
131
  /** OBS-1046: adapter ids and adapter:model keys routing.allow leaves out of the fleet */
128
132
  initialAllowOut?: string[];
133
+ /** OBS-1182: configured launch effort per adapter → model */
134
+ initialEfforts?: Record<string, Record<string, Effort>>;
129
135
  modelGroups: FleetModelGroup[];
130
136
  initialMode: RoutingMode;
131
137
  modeOptions: FleetModeOption[];
@@ -140,6 +146,8 @@ export declare function FleetApp({ ageMs, agents, initialAllowOut, modelGroups,
140
146
  preferOptionsForShape: (shape: Shape, current: string[]) => string[];
141
147
  initialSteering: Record<FleetSteeringKey, string[] | undefined>;
142
148
  steeringOptionsFor: (which: FleetSteeringKey, current: string[]) => string[];
149
+ /** OBS-1052(3): advisory review no-verdict history lines — display only, never a routing input */
150
+ reviewAdvisories?: string[];
143
151
  reviewOverlay: (state: FleetEditorState) => FleetOverlayReview;
144
152
  reloadGuard: (bytes: string) => string | null;
145
153
  /** the routing policy the staged deny sets load as (fleet: the config loader over the candidate
@@ -157,12 +165,14 @@ export declare function FleetApp({ ageMs, agents, initialAllowOut, modelGroups,
157
165
  /** terminal height — set only on a real TTY so the footer sticks to the bottom */
158
166
  frameRows?: number;
159
167
  }): import("react").JSX.Element;
160
- export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, initialInput, input, output, debug, ...initialDenyProps }: InitialDenyProps & {
168
+ export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut, initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, initialInput, input, output, debug, ...initialDenyProps }: InitialDenyProps & {
161
169
  ageMs: number | null;
162
170
  adapters: WorkerAdapter[];
163
171
  health: Record<string, AuthHealth>;
164
172
  /** OBS-1046: adapter ids and adapter:model keys routing.allow leaves out of the fleet */
165
173
  initialAllowOut?: string[];
174
+ /** OBS-1182: configured launch effort per adapter → model */
175
+ initialEfforts?: Record<string, Record<string, Effort>>;
166
176
  modelGroups: FleetModelGroup[];
167
177
  initialMode: RoutingMode;
168
178
  modeOptions: FleetModeOption[];
@@ -177,6 +187,8 @@ export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllo
177
187
  preferOptionsForShape: (shape: Shape, current: string[]) => string[];
178
188
  initialSteering: Record<FleetSteeringKey, string[] | undefined>;
179
189
  steeringOptionsFor: (which: FleetSteeringKey, current: string[]) => string[];
190
+ /** OBS-1052(3): advisory review no-verdict history lines — display only, never a routing input */
191
+ reviewAdvisories?: string[];
180
192
  reviewOverlay: (state: FleetEditorState) => FleetOverlayReview;
181
193
  reloadGuard: (bytes: string) => string | null;
182
194
  /** the routing policy the staged deny sets load as (fleet: the config loader over the candidate