tickmarkr 2.6.6 → 2.6.8

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 (51) hide show
  1. package/dist/adapters/claude-code.js +8 -2
  2. package/dist/adapters/codex-commit-check.d.ts +3 -2
  3. package/dist/adapters/codex-commit-check.js +37 -10
  4. package/dist/adapters/codex.js +10 -4
  5. package/dist/cli/commands/beat.d.ts +7 -2
  6. package/dist/cli/commands/beat.js +144 -77
  7. package/dist/cli/commands/fleet.js +66 -21
  8. package/dist/cli/commands/init.js +18 -7
  9. package/dist/cli/commands/plan.js +16 -18
  10. package/dist/cli/commands/verify.js +4 -2
  11. package/dist/cli/help.d.ts +1 -0
  12. package/dist/cli/help.js +14 -6
  13. package/dist/cli/index.d.ts +1 -1
  14. package/dist/cli/index.js +1 -1
  15. package/dist/drivers/herdr.js +2 -2
  16. package/dist/drivers/orca.d.ts +12 -4
  17. package/dist/drivers/orca.js +200 -39
  18. package/dist/gates/baseline.d.ts +9 -0
  19. package/dist/gates/baseline.js +14 -3
  20. package/dist/gates/cache.js +1 -1
  21. package/dist/gates/evidence.js +3 -3
  22. package/dist/gates/review.d.ts +1 -1
  23. package/dist/gates/review.js +77 -13
  24. package/dist/gates/run-gates.d.ts +21 -6
  25. package/dist/gates/run-gates.js +439 -224
  26. package/dist/gates/test-manifest.d.ts +42 -0
  27. package/dist/gates/test-manifest.js +203 -118
  28. package/dist/run/beat-lifecycle.d.ts +239 -0
  29. package/dist/run/beat-lifecycle.js +1782 -0
  30. package/dist/run/daemon.d.ts +4 -0
  31. package/dist/run/daemon.js +129 -40
  32. package/dist/run/git-trust.d.ts +102 -0
  33. package/dist/run/git-trust.js +334 -0
  34. package/dist/run/git.d.ts +24 -3
  35. package/dist/run/git.js +169 -30
  36. package/dist/run/journal.js +15 -3
  37. package/dist/run/lease.d.ts +23 -0
  38. package/dist/run/lease.js +232 -38
  39. package/dist/run/merge.js +4 -4
  40. package/dist/run/stall.js +20 -1
  41. package/dist/run/supervision.d.ts +8 -0
  42. package/dist/run/supervision.js +24 -4
  43. package/dist/run/verification-job.d.ts +53 -0
  44. package/dist/run/verification-job.js +159 -0
  45. package/dist/tui/ink/fleet-app.d.ts +16 -2
  46. package/dist/tui/ink/fleet-app.js +265 -141
  47. package/package.json +1 -1
  48. package/skills/tickmarkr-auto/SKILL.md +80 -27
  49. package/skills/tickmarkr-loop/SKILL.md +87 -27
  50. package/skills/tickmarkr-overseer/SKILL.md +125 -47
  51. package/skills/tickmarkr-overseer/scripts/watch-journal.sh +9 -7
@@ -0,0 +1,239 @@
1
+ import { type ChildProcess, type SpawnOptions } from "node:child_process";
2
+ import { type SupervisionArm, type SupervisionTier } from "./supervision.js";
3
+ /** start's whole budget — launch, read-back and any overrun — before it must refuse and roll back. */
4
+ export declare const BEAT_STARTUP_CEILING_MS = 60000;
5
+ /** The generation a started child carries; it lets the child act under its launcher's claim only. */
6
+ export declare const BEAT_GENERATION_ENV = "TICKMARKR_BEAT_GENERATION";
7
+ export interface ProcessIdentity {
8
+ birth: string;
9
+ command: string;
10
+ cwd: string;
11
+ pgid: number;
12
+ }
13
+ /** `UNKNOWN — <why>` is UNKNOWN that names its cause (Darwin's lsof missing or unusable): never life, never death. */
14
+ export type IdentityRead = ProcessIdentity | "DEAD" | "UNKNOWN" | `UNKNOWN — ${string}`;
15
+ /** The host the production reader inspects through; Darwin (and every non-Linux host) reads cwd only via lsof. */
16
+ export interface IdentityHost {
17
+ platform?: NodeJS.Platform;
18
+ lsof?: string;
19
+ }
20
+ /** Every boundary the lifecycle crosses, injectable so failures are exercised where they really occur. */
21
+ export interface BeatLifecycleDeps {
22
+ /** Command prefix that re-executes this same CLI (node, its flags, the entry the launcher ran). */
23
+ cli: readonly string[];
24
+ spawn: (command: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
25
+ kill: (pid: number, signal: NodeJS.Signals) => void;
26
+ identity: (pid: number) => IdentityRead;
27
+ openLog: (path: string) => number;
28
+ now: () => number;
29
+ sleep: (ms: number) => Promise<void>;
30
+ env: NodeJS.ProcessEnv;
31
+ }
32
+ export declare function defaultBeatDeps(host?: IdentityHost): BeatLifecycleDeps;
33
+ /** A named, nonzero refusal: dispatch renders the message and exits 1. `notice` is a reclaim it must not lose. */
34
+ export declare class BeatRefusal extends Error {
35
+ readonly notice?: string | undefined;
36
+ constructor(message: string, notice?: string | undefined);
37
+ }
38
+ /**
39
+ * The live process behind a pid: birth (lstart), full argv, cwd and process group. A missing process is
40
+ * DEAD only when the kernel says so (ESRCH or a zombie) — also when it exits between ps and the cwd read;
41
+ * anything unreadable is UNKNOWN, never DEAD. Linux reads cwd from /proc and never needs lsof; every other
42
+ * host reads it through lsof, so a missing or unusable lsof beside a LIVE pid is UNKNOWN that names lsof.
43
+ */
44
+ export declare function readProcessIdentity(pid: number, host?: IdentityHost): IdentityRead;
45
+ export declare const beatClaimPath: (repoRoot: string, tier: SupervisionTier) => string;
46
+ export declare const beatOwnerPath: (repoRoot: string, tier: SupervisionTier) => string;
47
+ export declare const beatLogPath: (repoRoot: string, tier: SupervisionTier) => string;
48
+ /**
49
+ * The exact generation start launched. pid/birth/command/pgid are absent only while it is launching;
50
+ * priorArmId/priorArmEpoch name the (already fenced) arm on disk at launch, which the child's own arm then
51
+ * replaces; armEpoch is bound from the exact child's checked beat, and ownership is the COMPLETE durable arm
52
+ * generation (armId AND armEpoch) — a same-id arm with another epoch is a replacement.
53
+ */
54
+ export interface BeatOwner {
55
+ tier: SupervisionTier;
56
+ seat: string;
57
+ generation: string;
58
+ armId: string;
59
+ cwd: string;
60
+ startedAt: string;
61
+ armEpoch?: string;
62
+ priorArmId?: string;
63
+ priorArmEpoch?: string;
64
+ pid?: number;
65
+ birth?: string;
66
+ command?: string;
67
+ pgid?: number;
68
+ }
69
+ type Read<T> = T | "NONE" | "UNREADABLE";
70
+ export declare function readBeatOwner(repoRoot: string, tier: SupervisionTier): Read<BeatOwner>;
71
+ /** Runs `mutate` as ONE claimed act (claimedAct); `check` is the act's own barrier (owner generation, records). */
72
+ export type ClaimGuard = <T>(mutate: () => T, check?: () => void) => T;
73
+ /** Outside any claim (a direct call): the barrier, then the mutation. */
74
+ export declare const unclaimed: ClaimGuard;
75
+ /**
76
+ * Atomic first-writer-wins claim. A held claim is BUSY unless `reclaim` (every claimant passes its boundaries) confirms
77
+ * DEAD every pid parsed from the claim's own bytes — its holder and any launched writer publishing under it
78
+ * (reclaimDeadClaim): then exactly those bytes are removed (takeClaim) and the claim is taken; the returned notice says
79
+ * so (on a refusal, `BeatRefusal.notice` does). A live, reused or unreadable holder or publisher is never taken over.
80
+ * A linked claim is SETTLED (returned) only when no claim removal is under way and it is still the canonical claim: a
81
+ * removal's put-back may replace a claim linked into its gap, so until that removal ends the claim holds nothing — the
82
+ * claimant withdraws exactly its own linked bytes (takeClaim) and refuses BUSY, so no caller, a long-lived bridge
83
+ * included, is left holding a live claim nobody acts under; its next attempt links afresh. When another live taker
84
+ * holds or has moved aside those very bytes (a mistaken death proof included), the withdrawal cannot land then; once
85
+ * this attempt refuses, its token is no longer in use, so the bytes are this process's STRAND wherever they are
86
+ * (strandOf), proven from the bytes alone: every later attempt in this process withdraws them from the claim path
87
+ * first (withdrawStrand) and discards them from a killed taker's lock instead of restoring them (recoverLock) — so
88
+ * recovery never waits for the caller to exit.
89
+ */
90
+ export declare function acquireBeatClaim(repoRoot: string, tier: SupervisionTier, token: string, reclaim?: BeatLifecycleDeps): string | undefined;
91
+ /** The ONE finite contention policy shared by every waiting claimant: loop ticks, legacy one-shots and stop. */
92
+ export declare const BEAT_CLAIM_WAIT: {
93
+ readonly tries: 20;
94
+ readonly intervalMs: 250;
95
+ };
96
+ export interface ClaimWait {
97
+ tries?: number;
98
+ intervalMs?: number;
99
+ sleep: (ms: number) => Promise<void>;
100
+ }
101
+ /**
102
+ * Release removes exactly the bytes that name this token (takeClaim), after any claimed act under them — a launched
103
+ * writer's included — has finished: a replacement found at any instant is left in place. However it ends, the token is
104
+ * then no longer in use: bytes under it that a live taker kept or moved aside are this process's strand (strandOf),
105
+ * withdrawn or discarded by its next attempt. `told` gets the notice of a dead taker's lock recovered on the way.
106
+ */
107
+ export declare function releaseBeatClaim(repoRoot: string, tier: SupervisionTier, token: string, told?: (notice: string) => void): void;
108
+ /**
109
+ * Run ONE supervision.ts writer (stand-down, arm or beat) under a claim without letting it touch the tier: it writes
110
+ * into a STAGED copy of the tier's records (arm, clear-duty latch, beat, stand-down marker), then every record it
111
+ * changed is committed by one rename — a discharged latch by one removal — as ONE claimed act each (`guard`), in the
112
+ * writers' own order (arm, raised latch, beat, marker, discharged latch). Compare-and-commit: inside each act, after
113
+ * the caller's own barrier (its owner generation) and before the final claim check, every record must still be
114
+ * exactly what this write was staged from (or what it committed itself) — a record replaced meanwhile, the owner by
115
+ * the caller's barrier, refuses with nothing further committed. A claim lost, or a record or owner replaced, before
116
+ * the first commit therefore lands nothing; one lost between two commits lands nothing further; either refuses. What a
117
+ * failing writer published before its failure is committed the same way, then the failure is rethrown.
118
+ * ponytail: staging stands in for a commit guard supervision.ts's atomic writers do not take; a writer killed mid-write
119
+ * leaves an inert `<tier>.stage.<pid>.<uuid>` directory (no reader lists it), which the next start, stop or legacy
120
+ * write removes once that pid is confirmed dead (acquireBeatClaim).
121
+ */
122
+ export declare function supervised<T>(repoRoot: string, tier: SupervisionTier, guard: ClaimGuard, write: (root: string) => T): T;
123
+ /**
124
+ * A legacy writer: the launch generation it carries (if any), its seat, the arm a running loop holds, the
125
+ * boundaries its checks read through, and where the notice of a dead claim it reclaimed goes.
126
+ */
127
+ export interface LegacyWriter {
128
+ generation?: string;
129
+ seat: string;
130
+ settled?: boolean;
131
+ arm?: SupervisionArm;
132
+ deps: BeatLifecycleDeps;
133
+ noticed?: (notice: string) => void;
134
+ }
135
+ /**
136
+ * The recheck a legacy write calls immediately before it mutates; `arm` is the arm the writer itself holds, and
137
+ * `created` says this writer published that arm under this claim a moment ago (an owned re-arm).
138
+ */
139
+ export type LegacyRecheck = (arm?: SupervisionArm, created?: boolean) => void;
140
+ /**
141
+ * Run one legacy write (one-shot tick, new arm, EVERY loop tick, stand-down) under the tier claim. A launched
142
+ * child acts under its launcher's claim (underLaunchClaim) while that claim is still held for its generation, names
143
+ * this child as its publisher and its launcher is not confirmed dead; afterwards it takes the tier claim itself like
144
+ * every other writer. `act` receives the recheck it must call before it writes (the tier's ownership still admits
145
+ * this writer, then the claim still names this holder) and the guard every supervision write it makes commits under
146
+ * (supervised): each commit is one claimed act whose barrier re-runs that same ownership check. A claim held by
147
+ * anyone else is waited out under BEAT_CLAIM_WAIT (20 attempts 250 ms apart, absent `waits` fields default to it)
148
+ * and then refuses BUSY with nothing written; invalid tries/interval refuse before any claim or write. Like start
149
+ * and stop, the writer reclaims a claim whose recorded pids are all confirmed dead (`noticed` receives the notice): a
150
+ * legacy --loop writer killed inside its claimed tick never strands the --stand-down migration step, and a live
151
+ * writer's claim is never taken. Under its own claim, the release that follows `act` is the claim's last check: a claim
152
+ * lost by then refuses, never reports success. Under the launcher's claim no release follows and nothing is checked
153
+ * after the last act — each act verified the claim last — so the launcher's release after its read-back never ends
154
+ * the loop it launched. A dead or abandoned generation recorded for this writer's seat is retired first (retireDead).
155
+ */
156
+ export declare function withLegacyClaim<T>(repoRoot: string, tier: SupervisionTier, writer: LegacyWriter, act: (recheck: LegacyRecheck, held: ClaimGuard) => T | Promise<T>, waits?: ClaimWait): Promise<T>;
157
+ /** The ownership a legacy writer needs before each mutation: its own COMPLETE launched generation, or no owner at all. */
158
+ export declare function assertLegacyOwnership(repoRoot: string, tier: SupervisionTier, writer: LegacyWriter, arm?: SupervisionArm, created?: boolean, held?: BeatOwner, claimed?: ClaimGuard): BeatOwner | undefined;
159
+ /**
160
+ * ONE RULE: every legacy write is preceded by the shared complete owned-generation check of the CURRENT owner
161
+ * record and durable arm against the EXACT generation this writer holds — token, seat, pid (once settled), armId
162
+ * and armEpoch. `held` is the owner this claim's previous check validated and `arm` the arm the writer holds (a
163
+ * loop's own arm; before the launcher binds the epoch that arm is the evidence). The first check of a claim by a
164
+ * writer holding neither takes the generation on record; from then on the held generation changes in exactly
165
+ * two verified ways, and any other change — also one between this check's own reads — refuses with no write:
166
+ * (i) the writer's OWN re-arm (`created`: it published `arm` under this claim after the complete check): the
167
+ * arm on disk must be exactly that arm and the owner exactly the owner checked before the arm write; only
168
+ * then is the owner rebound to the arm (same token, seat, process), and the complete check runs AGAIN as
169
+ * the new generation before the beat. After any mismatch the owner is never rewritten.
170
+ * (ii) the launcher completing an UNBOUND owner for this writer (launcherBound) — never adopted from disk.
171
+ * Once `settled` (past its first beat) the record must name its pid: a launch that refused without proving it
172
+ * could signal its child leaves no pid behind, and that child retires itself.
173
+ */
174
+ export declare function assertOwnedGeneration(repoRoot: string, tier: SupervisionTier, writer: LegacyWriter & {
175
+ generation: string;
176
+ }, arm?: SupervisionArm | undefined, created?: boolean, held?: BeatOwner, claimed?: ClaimGuard, pid?: number): BeatOwner;
177
+ export type CheckedBeat = {
178
+ kind: "EMPTY";
179
+ } | {
180
+ kind: "DISARMED";
181
+ seat?: string;
182
+ } | {
183
+ kind: "OWN-LIVE";
184
+ owner: BeatOwner & {
185
+ pid: number;
186
+ };
187
+ advancing: boolean;
188
+ } | {
189
+ kind: "OWN-DEAD";
190
+ owner: BeatOwner & {
191
+ pid: number;
192
+ };
193
+ } | {
194
+ kind: "ABANDONED";
195
+ owner: BeatOwner;
196
+ reason: string;
197
+ } | {
198
+ kind: "FOREIGN";
199
+ reason: string;
200
+ migrate?: string;
201
+ } | {
202
+ kind: "PID-REUSED";
203
+ owner: BeatOwner & {
204
+ pid: number;
205
+ };
206
+ } | {
207
+ kind: "UNKNOWN";
208
+ reason: string;
209
+ };
210
+ /**
211
+ * The checked state of one tier, read-only: no claim, no write, no signal. Every authoritative record —
212
+ * owner, marker, beat and durable arm — is validated before any outcome is selected, and ownership binds to
213
+ * this repository's canonical root, the recorded seat and the recorded arm before the process is consulted.
214
+ * An UNBOUND owner (no pid, or no arm epoch) is a launch in progress only while a claim is held: a launch publishes and
215
+ * binds only under its own claim. So when the caller holds the tier claim (`held`), or no claim exists before and after
216
+ * the owner is read (status), an unbound owner with no live recorded process is ABANDONED — its launcher died — and
217
+ * the next start, stop or legacy write retires it (retireDead); a live recorded process keeps it UNKNOWN.
218
+ */
219
+ export declare function inspectBeat(repoRoot: string, tier: SupervisionTier, seat: string | undefined, deps: BeatLifecycleDeps, held?: boolean): CheckedBeat;
220
+ /**
221
+ * Status: the recorded liveness, rendered without writing; healthy states exit 0, the rest nonzero. It is observation
222
+ * only: a claim whose recorded pids are all dead, what a killed taker or writer left, or — read in the process that
223
+ * stranded it (a bridge) — that process's own strand (strandOf), is REPORTED (nonzero) with the pids and the exact
224
+ * command that recovers it — never recovered here, and no file is touched.
225
+ */
226
+ export declare function beatStatus(repoRoot: string, tier: SupervisionTier, seat: string | undefined, deps: BeatLifecycleDeps): {
227
+ out: string;
228
+ code: number;
229
+ };
230
+ /** start: one claim (a single attempt), one child; exit zero only after this exact child's own advancing beat reads back. */
231
+ export declare function startBeat(repoRoot: string, tier: SupervisionTier, seat: string, deps: BeatLifecycleDeps): Promise<string>;
232
+ /**
233
+ * stop: retire only the recorded generation, signalling its exact pid, then read back DISARMED. A loop tick
234
+ * holding the claim is waited out under BEAT_CLAIM_WAIT; a claim whose recorded pids are all confirmed dead is
235
+ * reclaimed. Every stand-down commit (supervised), signal and removal is one claimed act under stop's claim (`held`)
236
+ * with the owner generation it acts on re-verified inside it.
237
+ */
238
+ export declare function stopBeat(repoRoot: string, tier: SupervisionTier, seat: string, deps: BeatLifecycleDeps): Promise<string>;
239
+ export {};