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.
- package/dist/adapters/claude-code.js +8 -2
- package/dist/adapters/codex-commit-check.d.ts +3 -2
- package/dist/adapters/codex-commit-check.js +37 -10
- package/dist/adapters/codex.js +10 -4
- package/dist/cli/commands/beat.d.ts +7 -2
- package/dist/cli/commands/beat.js +144 -77
- package/dist/cli/commands/fleet.js +66 -21
- package/dist/cli/commands/init.js +18 -7
- package/dist/cli/commands/plan.js +16 -18
- package/dist/cli/commands/verify.js +4 -2
- package/dist/cli/help.d.ts +1 -0
- package/dist/cli/help.js +14 -6
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/drivers/herdr.js +2 -2
- package/dist/drivers/orca.d.ts +12 -4
- package/dist/drivers/orca.js +200 -39
- package/dist/gates/baseline.d.ts +9 -0
- package/dist/gates/baseline.js +14 -3
- package/dist/gates/cache.js +1 -1
- package/dist/gates/evidence.js +3 -3
- package/dist/gates/review.d.ts +1 -1
- package/dist/gates/review.js +77 -13
- package/dist/gates/run-gates.d.ts +21 -6
- package/dist/gates/run-gates.js +439 -224
- package/dist/gates/test-manifest.d.ts +42 -0
- package/dist/gates/test-manifest.js +203 -118
- package/dist/run/beat-lifecycle.d.ts +239 -0
- package/dist/run/beat-lifecycle.js +1782 -0
- package/dist/run/daemon.d.ts +4 -0
- package/dist/run/daemon.js +129 -40
- package/dist/run/git-trust.d.ts +102 -0
- package/dist/run/git-trust.js +334 -0
- package/dist/run/git.d.ts +24 -3
- package/dist/run/git.js +169 -30
- package/dist/run/journal.js +15 -3
- package/dist/run/lease.d.ts +23 -0
- package/dist/run/lease.js +232 -38
- package/dist/run/merge.js +4 -4
- package/dist/run/stall.js +20 -1
- package/dist/run/supervision.d.ts +8 -0
- package/dist/run/supervision.js +24 -4
- package/dist/run/verification-job.d.ts +53 -0
- package/dist/run/verification-job.js +159 -0
- package/dist/tui/ink/fleet-app.d.ts +16 -2
- package/dist/tui/ink/fleet-app.js +265 -141
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +80 -27
- package/skills/tickmarkr-loop/SKILL.md +87 -27
- package/skills/tickmarkr-overseer/SKILL.md +125 -47
- 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 {};
|