framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
75 lines • 4.69 kB
TypeScript
import { type GitRunner } from './project.js';
import type { SpikeAssignment } from './auto-pm.js';
/** The first line of a placeholder sibling: the claim, naming the agent that holds it. */
export declare const SPIKE_LOCK_PREFIX = "PENDING:";
/**
* How old a PENDING lock must be before it is presumed dead and released. Generous against the
* slowest live path — spiking and planning can take hours, on top of a cloud queue — because a
* lock released under a live agent re-opens the double-work window the lock exists to close,
* while a dead agent's ticket only waits one interval longer. This is a bug-recovery mechanism
* that ideally never fires (#1364 review), so erring long costs nearly nothing.
*/
export declare const SPIKE_LOCK_STALE_MS: number;
/** Whether a sibling's content is a PENDING placeholder rather than a real spike or plan. */
export declare function isSpikeLock(md: string): boolean;
/** What a lock file holds: the claim line and nothing else, so the check above stays trivial. */
export declare function spikeLockContent(agentId: string): string;
/** The commit a batch of locks lands as. Names the count so the history reads as what happened. */
export declare function lockMessage(count: number): string;
/** The commit a release lands as. */
export declare function releaseMessage(count: number): string;
/** Injectable seams so both operations are unit-testable off disk, git, and GitHub. */
export interface SpikeLockDeps {
git?: GitRunner;
/** Write one lock file (default `fs.writeFile`). */
write?: (path: string, content: string) => Promise<void>;
/** Read one sibling (default `fs.readFile`); a rejection reads as "not a lock". */
read?: (path: string) => Promise<string>;
/** Delete one lock file (default `fs.rm`). */
remove?: (path: string) => Promise<void>;
/** List `tickets/` (default `fs.readdir`); a rejection reads as an empty directory. */
list?: (dir: string) => Promise<string[]>;
/**
* Whether any open PR touches `file` (#1313's read, reused): a finished agent's PR carries the
* real sibling, so an open one keeps the lock however old it is. `undefined` means the answer
* could not be read, which also keeps the lock — releasing on a `gh` hiccup would re-open the
* double-work window over a network blip.
*/
prTouches?: (cwd: string, file: string) => Promise<boolean | undefined>;
/** Clock, injectable for tests. */
now?: () => number;
/** Override {@link SPIKE_LOCK_STALE_MS}. */
staleMs?: number;
/** Progress line. */
log?: (message: string) => void;
}
/**
* Claim `assignments`' tickets for their agents: write both placeholder siblings per ticket,
* commit the whole batch in one pathspec-scoped commit, and push it to origin's default branch
* ({@link pushLockCommit}). Resolves the subset actually locked — a ticket whose sibling appeared since the candidates
* were enumerated is skipped, not overwritten: an existing file is someone's claim or someone's
* work, and either outranks this batch.
*
* A batch whose commit failed is rolled back (the written files removed) and resolves `[]`:
* uncommitted placeholders in the user's checkout would be noise git blames on nobody. A batch
* whose *push* failed is kept and resolved as locked — the commit still guards every run forked
* from this checkout, which is the common case, and the sweep should not stand a healthy local
* fan-out down over a network blip. The push is what closes the cross-machine window (#1320), so
* its failure is logged rather than swallowed.
*
* Never throws: this runs on a background tick with nothing to catch it.
*/
export declare function acquireSpikeLocks(cwd: string, assignments: readonly SpikeAssignment[], deps?: SpikeLockDeps): Promise<SpikeAssignment[]>;
/**
* Release the PENDING locks whose agents are presumed dead, so a crashed spike does not brick its
* ticket forever. A lock is stale exactly when all three hold (#1327's rule): its content is still
* the placeholder, no open PR touches it (a finished agent's PR carries the real sibling — the
* #1313 claim takes over from here), and its last commit is older than {@link SPIKE_LOCK_STALE_MS}.
* An *uncommitted* placeholder is left alone: it is a batch being acquired right now, or a commit
* failure {@link acquireSpikeLocks} already rolled back.
*
* Deletions land as one pathspec-scoped commit, pushed best-effort like the acquisition.
* Resolves the released tickets' filenames. Never throws.
*/
export declare function releaseStaleSpikeLocks(cwd: string, deps?: SpikeLockDeps): Promise<string[]>;
//# sourceMappingURL=spike-locks.d.ts.map