tickmarkr 2.5.5 → 2.5.7

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 (69) hide show
  1. package/README.md +3 -1
  2. package/dist/adapters/prompt.js +21 -1
  3. package/dist/cli/commands/approve.js +69 -8
  4. package/dist/cli/commands/doctor.d.ts +10 -0
  5. package/dist/cli/commands/fleet.js +4 -0
  6. package/dist/cli/commands/plan.js +20 -4
  7. package/dist/cli/commands/status.js +95 -34
  8. package/dist/cli/commands/verify.js +108 -85
  9. package/dist/config/config.d.ts +11 -0
  10. package/dist/config/config.js +21 -12
  11. package/dist/config/fleet-overlay.js +55 -17
  12. package/dist/drivers/index.js +2 -1
  13. package/dist/drivers/orca.d.ts +21 -1
  14. package/dist/drivers/orca.js +209 -27
  15. package/dist/gates/baseline.d.ts +21 -5
  16. package/dist/gates/baseline.js +67 -17
  17. package/dist/gates/cache.d.ts +14 -13
  18. package/dist/gates/cache.js +17 -5
  19. package/dist/gates/llm.d.ts +3 -0
  20. package/dist/gates/llm.js +11 -0
  21. package/dist/gates/review.d.ts +28 -3
  22. package/dist/gates/review.js +118 -15
  23. package/dist/gates/run-gates.d.ts +10 -2
  24. package/dist/gates/run-gates.js +362 -108
  25. package/dist/gates/test-manifest.d.ts +33 -1
  26. package/dist/gates/test-manifest.js +132 -40
  27. package/dist/gates/test-reporter.js +20 -7
  28. package/dist/graph/graph.d.ts +4 -0
  29. package/dist/graph/graph.js +50 -1
  30. package/dist/run/activity.d.ts +28 -0
  31. package/dist/run/activity.js +194 -0
  32. package/dist/run/consult.js +5 -4
  33. package/dist/run/daemon.d.ts +11 -0
  34. package/dist/run/daemon.js +663 -128
  35. package/dist/run/execution-budget.d.ts +25 -0
  36. package/dist/run/execution-budget.js +142 -0
  37. package/dist/run/git.d.ts +46 -1
  38. package/dist/run/git.js +149 -12
  39. package/dist/run/journal.d.ts +23 -5
  40. package/dist/run/journal.js +98 -25
  41. package/dist/run/lease.d.ts +44 -0
  42. package/dist/run/lease.js +226 -3
  43. package/dist/run/operator-page-summary.d.ts +56 -0
  44. package/dist/run/operator-page-summary.js +68 -0
  45. package/dist/run/operator-summary.d.ts +69 -0
  46. package/dist/run/operator-summary.js +77 -0
  47. package/dist/run/protocol.d.ts +71 -0
  48. package/dist/run/protocol.js +32 -0
  49. package/dist/run/recovery.d.ts +8 -0
  50. package/dist/run/recovery.js +25 -0
  51. package/dist/run/repair-selection.d.ts +12 -0
  52. package/dist/run/repair-selection.js +56 -0
  53. package/dist/run/stall.d.ts +6 -1
  54. package/dist/run/stall.js +60 -3
  55. package/dist/tui/cockpit/board.d.ts +9 -0
  56. package/dist/tui/cockpit/board.js +10 -0
  57. package/dist/tui/cockpit/derive.d.ts +35 -0
  58. package/dist/tui/cockpit/derive.js +152 -10
  59. package/dist/tui/cockpit/evidence-view.d.ts +2 -0
  60. package/dist/tui/cockpit/evidence-view.js +42 -12
  61. package/dist/tui/cockpit/run-cockpit.d.ts +8 -1
  62. package/dist/tui/cockpit/run-cockpit.js +95 -1
  63. package/dist/tui/cockpit/run-view.d.ts +37 -0
  64. package/dist/tui/cockpit/run-view.js +189 -2
  65. package/dist/tui/ink/fleet-app.d.ts +10 -2
  66. package/dist/tui/ink/fleet-app.js +33 -15
  67. package/package.json +2 -2
  68. package/skills/tickmarkr-overseer/SKILL.md +55 -3
  69. package/skills/tickmarkr-overseer/scripts/grade-ci.sh +29 -2
@@ -1,4 +1,5 @@
1
1
  import { formatJournalNarration } from "./journal.js";
2
+ import { readCommandReceipt } from "./protocol.js";
2
3
  const channelOf = (assignment) => {
3
4
  const a = assignment;
4
5
  return typeof a?.adapter === "string" && typeof a.model === "string" ? `${a.adapter}:${a.model}` : "unknown channel";
@@ -94,3 +95,196 @@ export function foldActivity(events, tasks) {
94
95
  const last = events.at(-1);
95
96
  return { ...(last ? { now: formatJournalNarration(last) } : {}), cells };
96
97
  }
98
+ const activityRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
99
+ const activityOrdinal = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
100
+ /**
101
+ * Pure, evidence-only successor to foldActivity. Feed Journal.readTracked() and the owning run ID;
102
+ * sourceIndex supplies journal order, never wall time. Unattributed legacy rows belong to their
103
+ * tracked run and current attempt/round. They cannot prove identities absent from the journal.
104
+ * A new gates phase opens a round; completed gates cannot reopen within that round. No declared
105
+ * gate order, graph status, or successful verdict predicts a later phase.
106
+ */
107
+ export function projectActivity(runId, rows, tasks) {
108
+ const states = new Map(tasks.map((task) => [task.id, {
109
+ projection: { taskId: task.id, state: "unconfirmed", phases: [], build: { state: "start-unrecorded" } },
110
+ round: 0,
111
+ closed: new Set(),
112
+ invocations: new Set(),
113
+ workerReturned: false,
114
+ suspended: false,
115
+ redispatch: false,
116
+ dispatchedSinceRound: false,
117
+ adoptRoundAttempt: false,
118
+ }]));
119
+ for (const row of [...rows].sort((a, b) => a.sourceIndex - b.sourceIndex)) {
120
+ if (row.runId !== runId || row.kind === "protocol-issue")
121
+ continue;
122
+ // Compatibility readers normalize missing legacy attempts to zero. Retain the physical
123
+ // payload here so that absence still means the currently recorded attempt.
124
+ const raw = row.raw;
125
+ if (!activityRecord(raw) || typeof raw.event !== "string" || !activityRecord(raw.data))
126
+ continue;
127
+ const data = raw.data;
128
+ if (typeof data.runId === "string" && data.runId !== runId)
129
+ continue;
130
+ if (raw.event === "run-resume" || raw.event === "run-end") {
131
+ for (const st of states.values()) {
132
+ const p = st.projection;
133
+ if (p.build.state === "started" && "receipt" in p.build) {
134
+ p.build = { state: "unresolved", receipt: p.build.receipt };
135
+ }
136
+ for (const phase of p.phases)
137
+ st.closed.add(phase.gate);
138
+ p.phases = [];
139
+ if (p.state !== "terminal")
140
+ p.state = "unconfirmed";
141
+ st.suspended = true;
142
+ st.redispatch = raw.event === "run-resume";
143
+ st.dispatchedSinceRound = false;
144
+ st.adoptRoundAttempt = false;
145
+ }
146
+ continue;
147
+ }
148
+ if (typeof raw.taskId !== "string")
149
+ continue;
150
+ const st = states.get(raw.taskId);
151
+ if (!st)
152
+ continue;
153
+ const p = st.projection;
154
+ const attempt = activityOrdinal(data.attempt);
155
+ if (attempt !== undefined && p.attempt !== undefined && attempt < p.attempt)
156
+ continue;
157
+ if (raw.event === "task-dispatch") {
158
+ const nextAttempt = attempt ?? 0;
159
+ if (p.attempt === nextAttempt && !st.redispatch)
160
+ continue;
161
+ p.attempt = nextAttempt;
162
+ p.state = "preparing";
163
+ p.phases = [];
164
+ p.build = { state: "start-unrecorded" };
165
+ st.closed.clear();
166
+ st.workerReturned = false;
167
+ st.suspended = false;
168
+ st.redispatch = false;
169
+ st.dispatchedSinceRound = true;
170
+ st.adoptRoundAttempt = false;
171
+ continue;
172
+ }
173
+ const round = activityOrdinal(data.gateRound);
174
+ if (round !== undefined && round !== st.round)
175
+ continue;
176
+ const roundGate = (raw.event === "gate-result" || raw.event === "gate-phase-start"
177
+ || raw.event === "phase-start") && typeof data.gate === "string"
178
+ && !st.suspended && !st.closed.has(data.gate);
179
+ if (attempt !== undefined && p.attempt !== undefined && attempt !== p.attempt
180
+ && !(st.adoptRoundAttempt && roundGate))
181
+ continue;
182
+ if (st.adoptRoundAttempt && roundGate && attempt !== undefined) {
183
+ p.attempt = attempt;
184
+ st.adoptRoundAttempt = false;
185
+ }
186
+ if (p.attempt === undefined && attempt !== undefined)
187
+ p.attempt = attempt;
188
+ if (["task-done", "task-failed", "task-human", "task-approved", "merge"].includes(raw.event)) {
189
+ p.state = "terminal";
190
+ p.phases = [];
191
+ st.suspended = true;
192
+ continue;
193
+ }
194
+ // Only an explicit new battery may re-enter verification after a resume or completed task.
195
+ if (raw.event === "phase-start" && data.phase === "gates") {
196
+ st.round++;
197
+ // Resume verification has no dispatch and labels the round with the dispatch count,
198
+ // rather than the last worker's attempt. Its first attributed gate/receipt owns the label.
199
+ st.adoptRoundAttempt = !st.dispatchedSinceRound;
200
+ st.dispatchedSinceRound = false;
201
+ st.closed.clear();
202
+ p.phases = [];
203
+ p.build = { state: "start-unrecorded" };
204
+ if (p.state === "terminal")
205
+ p.state = "unconfirmed";
206
+ st.suspended = false;
207
+ continue;
208
+ }
209
+ if (raw.event === "build-receipt" || raw.event === "build-result") {
210
+ // The emitter adds gate/reason metadata beside the strict command receipt.
211
+ const { gate: _gate, reason: _reason, freshBuildRan: _fresh, ...payload } = data;
212
+ const parsed = readCommandReceipt(payload);
213
+ if (parsed.kind !== "receipt")
214
+ continue;
215
+ const receipt = parsed.receipt;
216
+ if (receipt.outcome === "started" && !receipt.confirmedStart)
217
+ continue;
218
+ const identity = receipt.attribution;
219
+ if (identity.runId !== runId || identity.taskId !== p.taskId
220
+ || identity.gateRound !== st.round)
221
+ continue;
222
+ if (identity.attempt < (p.attempt ?? 0)
223
+ || (!st.adoptRoundAttempt && identity.attempt !== (p.attempt ?? 0)))
224
+ continue;
225
+ const previous = "receipt" in p.build ? p.build.receipt : undefined;
226
+ const same = previous?.attribution.invocation === identity.invocation;
227
+ if (same) {
228
+ if (p.build.state !== "started" && p.build.state !== "unresolved")
229
+ continue;
230
+ if (receipt.outcome === "started")
231
+ continue;
232
+ }
233
+ else {
234
+ if (st.suspended || st.closed.has("build") || st.invocations.has(identity.invocation))
235
+ continue;
236
+ // A late terminal cannot displace a newer invocation. No-start outcomes are themselves
237
+ // complete receipts and need no preceding start; a first terminal remains useful evidence.
238
+ if (previous && receipt.confirmedStart && receipt.outcome !== "started")
239
+ continue;
240
+ }
241
+ st.invocations.add(identity.invocation);
242
+ if (st.adoptRoundAttempt) {
243
+ p.attempt = identity.attempt;
244
+ st.adoptRoundAttempt = false;
245
+ }
246
+ p.build = { state: receipt.outcome, receipt };
247
+ continue;
248
+ }
249
+ if (st.suspended)
250
+ continue;
251
+ if (raw.event === "worker-launch" && !st.workerReturned && p.phases.length === 0
252
+ && (p.state === "preparing" || p.state === "unconfirmed"))
253
+ p.state = "implementing";
254
+ if (raw.event === "worker-result" && !st.workerReturned && p.phases.length === 0) {
255
+ st.workerReturned = true;
256
+ p.state = "returned-for-verification";
257
+ }
258
+ if (raw.event === "phase-start" && data.phase === "merge") {
259
+ for (const phase of p.phases)
260
+ st.closed.add(phase.gate);
261
+ p.phases = [];
262
+ p.state = "merging";
263
+ st.workerReturned = true;
264
+ continue;
265
+ }
266
+ if (raw.event === "gate-result" && typeof data.gate === "string") {
267
+ st.workerReturned = true;
268
+ // Infrastructure skips have no verdict; a retry may start again in this round.
269
+ if (typeof data.pass === "boolean" || (data.skipped !== true && data.infra !== true))
270
+ st.closed.add(data.gate);
271
+ if (data.gate === "build" && p.build.state === "awaiting-command")
272
+ p.build = { state: "start-unrecorded" };
273
+ p.phases = p.phases.filter((phase) => phase.gate !== data.gate);
274
+ if (p.state !== "merging")
275
+ p.state = p.phases[0]?.state ?? "unconfirmed";
276
+ }
277
+ if ((raw.event === "phase-start" || raw.event === "gate-phase-start")
278
+ && typeof data.gate === "string" && !st.closed.has(data.gate) && p.state !== "merging") {
279
+ const gate = data.gate;
280
+ if (!p.phases.some((phase) => phase.gate === gate)) {
281
+ p.phases.push({ gate, state: gate === "review" ? "reviewing" : "validating" });
282
+ }
283
+ st.workerReturned = true;
284
+ p.state = p.phases[0].state;
285
+ if (gate === "build" && p.build.state === "start-unrecorded")
286
+ p.build = { state: "awaiting-command" };
287
+ }
288
+ }
289
+ return new Map([...states].map(([id, st]) => [id, st.projection]));
290
+ }
@@ -164,10 +164,11 @@ opts = {}) {
164
164
  out = r.stdout + r.stderr;
165
165
  }
166
166
  else {
167
- // Preserve the adapter contract for CLIs that cannot seed their TUI: supported adapters use
168
- // the interactive form, while a declared null keeps the existing visible print fallback.
169
- const command = adapter.interactiveCommand(promptFile, seatModel)
170
- ?? adapter.headlessCommand(promptFile, seatModel);
167
+ // OBS-1009: the pane runs the HEADLESS command. A seeded interactive session answers on screen
168
+ // but never exits, so the exit trailer after it never runs, the wait runs to the stall cap and
169
+ // the 300-line read is a TUI frame. The headless command exits, the trailer prints, and the
170
+ // pane is harvested through dewrap exactly like a judge or review seat.
171
+ const command = adapter.headlessCommand(promptFile, seatModel);
171
172
  // T8: role-first pane name for fleet visibility (consult · T2); consultSeq stays on the dossier artifact only
172
173
  const slot = await driver.slot(cwd, gatePaneName("consult", d.taskId), {
173
174
  label: `CONSULT ${d.taskId}`,
@@ -6,6 +6,17 @@ import { type Baseline } from "../gates/baseline.js";
6
6
  import type { GateResult } from "../gates/types.js";
7
7
  import { Journal, type JournalEvent } from "./journal.js";
8
8
  export declare function closeLiveSlot(liveSlots: Set<Slot>, driver: Pick<ExecutorDriver, "close">, slot: Slot): Promise<void>;
9
+ /** Optional authoritative transport receipt. Screen text can prove execution, never nonacceptance. */
10
+ export interface DispatchObservation {
11
+ slotId: string;
12
+ command: string;
13
+ dispatchId: string;
14
+ outcome: "accepted" | "not-accepted";
15
+ authoritative: true;
16
+ }
17
+ export declare function heldWorkerTransport(driver: ExecutorDriver, slot: Slot, dispatchId: string, held: (data: Record<string, unknown>) => void, sleep?: (ms: number) => Promise<void>): Pick<ExecutorDriver, "run" | "read" | "status" | "waitOutput" | "waitAgentStatus">;
18
+ export declare function setAttemptHardTimeoutMsForTests(ms: number): void;
19
+ export declare function resetAttemptHardTimeoutMsForTests(): void;
9
20
  export { harvestCpuFlatWindowMs, resetHarvestCpuFlatMsForTests, setHarvestCpuFlatMsForTests, workerTreeCpuMs, } from "./stall.js";
10
21
  export interface RunOptions {
11
22
  runId?: string;