tickmarkr 2.5.6 → 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.
- package/dist/adapters/prompt.js +21 -1
- package/dist/cli/commands/approve.js +5 -5
- package/dist/cli/commands/status.js +95 -34
- package/dist/cli/commands/verify.js +6 -4
- package/dist/gates/baseline.d.ts +8 -3
- package/dist/gates/baseline.js +6 -3
- package/dist/gates/review.js +12 -1
- package/dist/gates/run-gates.d.ts +4 -1
- package/dist/gates/run-gates.js +65 -9
- package/dist/gates/test-manifest.d.ts +3 -0
- package/dist/gates/test-manifest.js +20 -2
- package/dist/gates/test-reporter.js +6 -1
- package/dist/graph/graph.d.ts +2 -0
- package/dist/graph/graph.js +5 -0
- package/dist/run/activity.d.ts +28 -0
- package/dist/run/activity.js +194 -0
- package/dist/run/daemon.d.ts +9 -0
- package/dist/run/daemon.js +200 -46
- package/dist/run/git.d.ts +6 -1
- package/dist/run/git.js +63 -11
- package/dist/run/journal.d.ts +20 -4
- package/dist/run/journal.js +60 -9
- package/dist/run/operator-page-summary.d.ts +56 -0
- package/dist/run/operator-page-summary.js +68 -0
- package/dist/run/operator-summary.d.ts +69 -0
- package/dist/run/operator-summary.js +77 -0
- package/dist/run/protocol.d.ts +71 -0
- package/dist/run/protocol.js +32 -0
- package/dist/tui/cockpit/board.d.ts +9 -0
- package/dist/tui/cockpit/board.js +10 -0
- package/dist/tui/cockpit/derive.d.ts +35 -0
- package/dist/tui/cockpit/derive.js +152 -10
- package/dist/tui/cockpit/evidence-view.d.ts +2 -0
- package/dist/tui/cockpit/evidence-view.js +42 -12
- package/dist/tui/cockpit/run-cockpit.d.ts +8 -1
- package/dist/tui/cockpit/run-cockpit.js +95 -1
- package/dist/tui/cockpit/run-view.d.ts +37 -0
- package/dist/tui/cockpit/run-view.js +189 -2
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +55 -3
|
@@ -50,7 +50,12 @@ export default class TickmarkrReporter {
|
|
|
50
50
|
}
|
|
51
51
|
onTestRunEnd(modules, errors, reason) {
|
|
52
52
|
const failed = reason === 'failed' || errors.length > 0 || Object.values(this.report.completed).some(c => c.status === 'failed');
|
|
53
|
-
|
|
53
|
+
// Collection failures can reach run end without a module start/end event. Keep their
|
|
54
|
+
// identity as diagnostics, without inventing lifecycle records or changing the verdict.
|
|
55
|
+
const loadErrors = modules.flatMap(module => module.errors().map(e => this.file(module) + ': ' + e.message));
|
|
56
|
+
this.report.certificate = { at: Date.now(), exitCode: failed ? 1 : 0,
|
|
57
|
+
errors: errors.length + loadErrors.length,
|
|
58
|
+
diagnostics: [...loadErrors, ...errors.map(e => [e.testPath, e.name, e.message].filter(Boolean).join(': '))] };
|
|
54
59
|
this.save();
|
|
55
60
|
}
|
|
56
61
|
}
|
package/dist/graph/graph.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface OnDiskSpecHash {
|
|
|
21
21
|
* evidence for failed recompiles, while moved/deleted source files need not strand a compiled graph.
|
|
22
22
|
*/
|
|
23
23
|
export declare function onDiskSpecHash(_repoRoot: string, graph: RunGraph): OnDiskSpecHash | undefined;
|
|
24
|
+
/** A task's definition minus its files[] (and run state): what a scope amendment must NOT move (OBS-1073). */
|
|
25
|
+
export declare function taskDefinitionFingerprint(task: Task): string;
|
|
24
26
|
export declare function graphDefinitionHash(g: RunGraph): string;
|
|
25
27
|
export declare function taskContentDigest(task: Pick<Task, "goal" | "files" | "acceptance">): string;
|
|
26
28
|
export declare function tickmarkrDir(repoRoot: string): string;
|
package/dist/graph/graph.js
CHANGED
|
@@ -80,6 +80,11 @@ export function onDiskSpecHash(_repoRoot, graph) {
|
|
|
80
80
|
// single comparator in journal.ts (engagementComparable) so the journal↔graph join is decided once.
|
|
81
81
|
// ponytail: sha256 truncated to 16 hex — stable, grep-friendly; promote to full digest only if a
|
|
82
82
|
// collision ever bites (engagement ids are not a trust boundary, collisions just force a re-run).
|
|
83
|
+
/** A task's definition minus its files[] (and run state): what a scope amendment must NOT move (OBS-1073). */
|
|
84
|
+
export function taskDefinitionFingerprint(task) {
|
|
85
|
+
const { status: _status, evidence: _evidence, files: _files, ...def } = task;
|
|
86
|
+
return createHash("sha256").update(JSON.stringify(def)).digest("hex").slice(0, 16);
|
|
87
|
+
}
|
|
83
88
|
export function graphDefinitionHash(g) {
|
|
84
89
|
const definitions = g.tasks.map(({ status: _status, evidence: _evidence, ...def }) => def);
|
|
85
90
|
return createHash("sha256").update(JSON.stringify({ version: g.version, spec: g.spec, tasks: definitions })).digest("hex").slice(0, 16);
|
package/dist/run/activity.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type JournalEvent } from "./journal.js";
|
|
2
|
+
import { type CommandReceipt, type TrackedJournalRow } from "./protocol.js";
|
|
2
3
|
export interface ActivityTask {
|
|
3
4
|
id: string;
|
|
4
5
|
gates: readonly string[];
|
|
@@ -13,3 +14,30 @@ export interface ActivitySnapshot {
|
|
|
13
14
|
cells: Map<string, string>;
|
|
14
15
|
}
|
|
15
16
|
export declare function foldActivity(events: JournalEvent[], tasks: readonly ActivityTask[]): ActivitySnapshot;
|
|
17
|
+
/** Recorded evidence, not a probe of whether a subprocess is still alive. */
|
|
18
|
+
export type BuildActivity = {
|
|
19
|
+
state: "start-unrecorded" | "awaiting-command";
|
|
20
|
+
} | {
|
|
21
|
+
state: CommandReceipt["outcome"] | "unresolved";
|
|
22
|
+
receipt: CommandReceipt;
|
|
23
|
+
};
|
|
24
|
+
export interface TaskActivityProjection {
|
|
25
|
+
taskId: string;
|
|
26
|
+
/** Journal attempt label (zero based), absent until an attempt is recorded. */
|
|
27
|
+
attempt?: number;
|
|
28
|
+
state: "unconfirmed" | "preparing" | "implementing" | "returned-for-verification" | "validating" | "reviewing" | "merging" | "terminal";
|
|
29
|
+
/** Concurrent gates retain separate entries; state alone is only a summary. */
|
|
30
|
+
phases: {
|
|
31
|
+
gate: string;
|
|
32
|
+
state: "validating" | "reviewing";
|
|
33
|
+
}[];
|
|
34
|
+
build: BuildActivity;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Pure, evidence-only successor to foldActivity. Feed Journal.readTracked() and the owning run ID;
|
|
38
|
+
* sourceIndex supplies journal order, never wall time. Unattributed legacy rows belong to their
|
|
39
|
+
* tracked run and current attempt/round. They cannot prove identities absent from the journal.
|
|
40
|
+
* A new gates phase opens a round; completed gates cannot reopen within that round. No declared
|
|
41
|
+
* gate order, graph status, or successful verdict predicts a later phase.
|
|
42
|
+
*/
|
|
43
|
+
export declare function projectActivity(runId: string, rows: readonly TrackedJournalRow[], tasks: readonly ActivityTask[]): Map<string, TaskActivityProjection>;
|
package/dist/run/activity.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/run/daemon.d.ts
CHANGED
|
@@ -6,6 +6,15 @@ 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">;
|
|
9
18
|
export declare function setAttemptHardTimeoutMsForTests(ms: number): void;
|
|
10
19
|
export declare function resetAttemptHardTimeoutMsForTests(): void;
|
|
11
20
|
export { harvestCpuFlatWindowMs, resetHarvestCpuFlatMsForTests, setHarvestCpuFlatMsForTests, workerTreeCpuMs, } from "./stall.js";
|