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.
168 lines • 9.62 kB
TypeScript
import { type GitRunner } from '../project.js';
/**
* Git-worktree lifecycle for concurrent agents (#453/#735): give each agent its own
* checkout so N runs on one repo never fight over the working tree. Pure plumbing
* over the existing {@link GitRunner} seam; no daemon wiring, no concurrency, no
* dashboard changes (those are the sibling #453 slices). This module only knows
* how to add, list, remove, and prune worktrees.
*/
/** The path an agent's worktree gets (#1580): `<repo>/.the-framework/branches/<run branch name>`. */
export declare function worktreePath(repo: string, agentId: string): string;
export { AGENT_BRANCH_PREFIX, LEGACY_AGENT_BRANCH_PREFIX, agentBranchName, legacyAgentBranchName, } from '../branch-names.js';
/** One entry parsed from `git worktree list --porcelain`. */
export interface WorktreeInfo {
/** Absolute worktree path (the main checkout included). */
path: string;
/** The checked-out commit. */
head: string;
/** The checked-out branch (short name), or absent when detached. */
branch?: string;
}
/** Inputs to {@link addWorktree}. The caller owns branch naming (#736). */
export interface AddWorktreeOptions {
agentId: string;
/** The branch to create for the agent. */
branch: string;
/** Base ref to branch from; defaults to the repo's current HEAD. */
base?: string;
}
/** The worktree {@link addWorktree} created. */
export interface AddedWorktree {
path: string;
branch: string;
}
/**
* Create a worktree for an agent on a fresh branch: `git worktree add -b <branch>
* <path> [base]`. Git makes the leaf dir (and any missing parents) itself. The
* `agentId` is validated as path-safe first so a caller can never traverse out of
* `.the-framework/branches/`. Rejects on any git failure (a caller that wants a
* run needs its checkout, so failure must surface, not be swallowed).
*/
export declare function addWorktree(repo: string, opts: AddWorktreeOptions, agent?: GitRunner): Promise<AddedWorktree>;
/**
* Check an *existing* branch out into an agent's worktree (#762): `git worktree add <path> <branch>`,
* no `-b`. Continuing an agent puts it back on the branch its work is already on, rather than
* branching again from HEAD and stranding what it did last time.
*
* Rejects on git failure, like {@link addWorktree}: a continued agent needs its checkout.
*/
export declare function attachWorktree(repo: string, opts: {
agentId: string;
branch: string;
}, agent?: GitRunner): Promise<AddedWorktree>;
/**
* Every worktree registered for the repo (the main checkout included). Forgiving:
* a non-repo / git failure yields `[]` so a reconcile scan never throws.
*/
export declare function listWorktrees(repo: string, agent?: GitRunner): Promise<WorktreeInfo[]>;
/**
* Parse `git worktree list --porcelain`: blank-line-separated records, each with
* a `worktree <path>` line, a `HEAD <sha>` line, and either `branch refs/heads/...`
* or `detached`. Extra attributes (bare/locked/prunable) are ignored. Exported so
* the parsing is unit-testable without a real repo.
*/
export declare function parseWorktreeList(porcelain: string): WorktreeInfo[];
/**
* Commit whatever the agent left behind, on the agent's own branch (#786).
*
* An agent that edits and stops without committing is behaving as instructed: the
* system prompt has it commit *pre-existing* changes before it starts, never its own
* work at the end. Removing that checkout would destroy the diff (the work was never
* staged, so it is not recoverable from git afterwards), so teardown commits it first
* and the branch outlives the worktree.
*
* Returns whether the checkout is safe to remove: true when it was already clean or
* the work is now committed, false when the commit failed (no git identity, a hook
* refusing it). False means keep the checkout, which is the safe direction.
*
* Retries before giving up (#1376): the daemon's conversation committer works in the same
* checkout and is busiest exactly when this runs (session end), so a first attempt can lose
* an `index.lock` race. That transient loss is how a session's real work got judged
* "committed nothing" by the handoff while the teardown's identical commit, seconds later,
* succeeded. A short wait outlasts the committer's hold; a persistent failure (identity,
* hooks) still comes back false.
*/
export declare function commitPendingWork(path: string, agent?: GitRunner, retry?: {
attempts?: number;
delayMs?: number;
}): Promise<boolean>;
/**
* Remove an agent's worktree. Tolerant of an already-gone / never-registered path so
* teardown stays idempotent (the agent child is detached; the daemon only holds its pid).
*
* Plain removal first: it refuses a checkout git considers unclean, which after
* {@link commitPendingWork} means a state we did not anticipate. Falling back to
* `--force` keeps teardown working (an ignored build artifact must not strand a
* worktree forever), but it says so, because forcing past unknown state is exactly
* how uncommitted work got deleted in the first place.
*/
export declare function removeWorktree(repo: string, path: string, agent?: GitRunner): Promise<void>;
/**
* The branch checked out at `path`, or `undefined` when detached / not a repo.
* Forgiving, like {@link listWorktrees}: callers use it to decide, not to fail.
*/
export declare function currentBranch(path: string, agent?: GitRunner): Promise<string | undefined>;
/**
* Rename an agent's branch once the agent names the session (#736): the worktree is
* created on `tf-agent-<agentId>` before a name exists, and this puts the
* readable `tf-<sessionName>` on it.
*
* Only renames when `path` is still on `from`. The #326 system prompt currently
* tells the agent to create and check out its own `tf-<name>` branch,
* and until that step is dropped there (the prompt ships verbatim from the issue,
* so it is not ours to edit) the agent may already have moved off `from` — in
* which case it named the branch itself and there is nothing to rename. Returns
* whether it renamed, and never throws: an agent must not die over a branch name.
*/
export declare function renameAgentBranch(path: string, from: string, to: string, agent?: GitRunner): Promise<boolean>;
/**
* `git worktree prune`: drop administrative entries for worktree dirs a crash left
* behind. Never removes a live worktree, so it is always safe. Forgiving.
*/
export declare function pruneWorktrees(repo: string, agent?: GitRunner): Promise<void>;
/** Runs `du`, resolving its stdout. Injectable so the size read can be tested without a real tree. */
export type SizeRunner = (path: string) => Promise<string>;
/** A {@link SizeRunner} over `du -sk`: one process, and it does not follow the symlinked deps (#736). */
export declare function nodeSizeRunner(): SizeRunner;
/**
* A worktree's size on disk in bytes, or undefined when it cannot be read (#798). Best-effort by
* design: this only ever labels a "remove this" button, so a missing number costs nothing while a
* throw or a hang would cost the panel it sits in. `du` is absent on Windows, which reads as
* unknown like any other failure.
*/
export declare function worktreeSize(path: string, agent?: SizeRunner): Promise<number | undefined>;
/**
* Whether a branch is on the remote, with the local tip already there (E5).
*
* The one predicate the whole retention story is built on: nothing local is ever the last copy of
* work, so anything the remote has may be deleted and anything it does not have stays. It replaced
* three interacting rules — a clean finish removes the checkout, a failure keeps it, a merged
* branch reclaims it later — each of which asked *what state did this session end in* rather than
* *is this recoverable*.
*
* `git rev-parse` of the remote-tracking ref, then a merge-base check: the ref existing is not
* enough, because a branch pushed and then committed to again has a tip the remote has never seen.
* Reads only local refs (no fetch), so it is cheap enough to ask on every teardown — the remote ref
* is written by the push this is checking for, which is what makes that sound.
*
* Anything unreadable answers `false`. A repo with no remote configured therefore keeps every
* checkout, which is the honest outcome: there is nowhere for the work to be recoverable from.
*/
export declare function branchPushed(repo: string, branch: string, agent?: GitRunner): Promise<boolean>;
/**
* Whether the checkout has nothing uncommitted — the read half of {@link commitPendingWork}, for
* a decision that must not commit on the way to its answer: removing a publish-nothing session's
* checkout requires a clean tree, and grabbing someone's half-typed edits as a commit to find
* that out would be the intrusion the question exists to avoid. Throws when git cannot answer,
* so the caller keeps the checkout rather than guessing.
*/
export declare function worktreeClean(path: string, agent?: GitRunner): Promise<boolean>;
/**
* Whether the repo has any remote configured at all. What the sweep asks once per project: with
* no remote, {@link branchPushed} is false for every checkout and the push cannot land, so the
* whole per-checkout probe-and-push cycle is doomed before it starts — and that answer cannot
* change between two rows of the same sweep. Anything unreadable answers `false`, like
* {@link branchPushed}: keeping a checkout is the safe direction.
*/
export declare function repoHasRemote(repo: string, agent?: GitRunner): Promise<boolean>;
//# sourceMappingURL=worktree.d.ts.map