/** * Private git worktrees for co-threads (task 143, phase 4). * * A co-thread inherits its master's `projectDir` through task 098 prefix * fallback, which means every co-thread of a master lands in the SAME checkout. * Two turn loops editing one working tree lose each other's writes silently — * the hazard task 120 wrote a system-prompt rule about, reproduced by the very * feature meant to parallelise work. * * The fix is one private worktree per co-thread, on its own branch, derived * rather than configured. * * WHY DERIVED AND NOT WRITTEN TO CONFIG * A co-thread has no config file of its own, and that absence IS the feature: * it is what makes the master's model, effort, contextLimit, persona and * projectDir apply for free. Writing `projectDir` for the co-thread would * create an exact config and, in the same stroke, cut it off from every other * inherited field. Phase 2 hit this same wall with `sharedContentStore` and * took the same answer — derive, don't write. Nothing new is persisted about a * co-thread anywhere; the master's `coThreads` list remains the single record. * * This module is a LEAF: it imports only `resolveCoThreadMaster`, so both cwd * sites (a thread's own turns, and `run_job`) can call one resolver. Task 113 * is the recorded case of a field reaching one spawn path of several, and here * a miss fails OPEN — nothing errors, the co-thread just shares the checkout * and overwrites its siblings silently. */ /** Override the worktree root (tests; mirrors `setConfigThreadsDir`). */ export declare function setWorktreeRoot(dir: string): void; /** The worktree root currently in effect. */ export declare function getWorktreeRoot(): string; /** * Where a co-thread's private checkout lives. * * Deliberately OUTSIDE the master's checkout. A worktree nested inside it would * appear in the master's own `git status`, be walked by its builds, test globs * and linters, and be a permanent source of "why is this directory here". * `git worktree list` from the master still shows them, which is the visibility * that is actually wanted. */ export declare function worktreePathFor(coThreadName: string): string; /** * The branch a co-thread works on. * * Git refuses to check out one branch in two worktrees, so "one branch per * co-thread" is enforced by git itself rather than by our bookkeeping. */ export declare function coThreadBranchFor(coThreadName: string): string; /** Is `dir` the working tree of a git repository? */ export declare function isGitWorkTree(dir: string): Promise; export type WorktreeSkipReason = 'not-a-co-thread' | 'no-project-dir' | 'not-a-git-repo' | 'no-commits' | 'git-failed'; export interface WorktreeResolution { /** The directory the co-thread should actually work in. */ cwd: string; /** Set when `cwd` is a private worktree rather than the shared checkout. */ worktree?: { master: string; branch: string; created: boolean; }; /** Set when the private worktree could NOT be used, and why. */ fellBackTo?: { reason: WorktreeSkipReason; detail?: string; }; } /** * Ensure the private worktree for a co-thread exists, returning the directory * it should work in. * * Idempotent, and called at USE as well as at creation. One path rather than * two, which buys three things: co-threads created before this shipped get * worktrees with no migration step, a worktree deleted by hand repairs itself, * and a creation-time failure is not permanent. * * Degradation is honest rather than fatal: a project that is not a git repo * (or a git that fails) falls back to the shared checkout — today's behaviour — * and reports why, instead of refusing to run the turn. */ export declare function ensureCoThreadWorktree(coThreadName: string, masterDir: string | undefined): Promise; /** * Resolve the working directory for any thread. * * Called at BOTH cwd sites. For a non-co-thread this is the identity on * `configuredDir`, so every existing thread on the box is unaffected. */ export declare function resolveThreadCwd(threadName: string, configuredDir: string | undefined): Promise; /** * Remove a co-thread's worktree and its branch. * * `--force` because the worktree is private scratch space that we created: a * co-thread being deleted has no other owner whose uncommitted work could be at * stake. The branch is only deleted if it merges cleanly into nothing — `-D` is * used because an unmerged co-thread branch is exactly the case where the * thread is being discarded on purpose. */ export declare function removeCoThreadWorktree(coThreadName: string, masterDir: string | undefined): Promise<{ removed: boolean; detail?: string; }>; /** * Follow a co-thread rename (task 186): move its worktree directory and rename * its branch, so `worktreePathFor(to)` / `coThreadBranchFor(to)` — which are * DERIVED from the name and nothing else — keep pointing at the same checkout. * * Without this, the renamed co-thread's next turn would `worktree add` a fresh * checkout on a fresh branch and the old one would sit unmerged under a name * no thread has any more. `git worktree move` rewrites the `.git` back-pointer * so the master's `worktree list` stays correct; `branch -m` carries the * upstream-less branch with its commits. * * Best-effort like `removeCoThreadWorktree`: no worktree means nothing to move * (a non-git project, or a co-thread that has never taken a turn). */ export declare function moveCoThreadWorktree(from: string, to: string, masterDir: string | undefined): Promise<{ moved: boolean; detail?: string; }>; export interface CoThreadStatus { thread: string; branch: string; worktree: string; exists: boolean; /** Uncommitted paths in the co-thread's own tree. */ dirtyFiles: string[]; /** Commits on the co-thread branch not yet in the master's current branch. */ aheadBy: number; /** * Every path this co-thread will bring to the merge: uncommitted edits PLUS * paths changed by commits the master does not have yet. * * `dirtyFiles` alone is the wrong conflict signal — a co-thread that has * committed its work has no dirty files and will still collide. This is the * input `coThreadOverlaps` intersects. */ pendingFiles: string[]; } /** One path that more than one co-thread is bringing to the merge. */ export interface CoThreadOverlap { path: string; threads: string[]; } /** * Paths claimed by two or more co-threads (task 143 phase 5). * * Phase 4 removed the silent lost write by giving each co-thread its own tree; * it did not remove the CONFLICT, it deferred it to merge time. This surfaces * the collision while both threads are still working, when redirecting one of * them is cheap. Pure over data the listing already computes. */ export declare function coThreadOverlaps(statuses: CoThreadStatus[]): CoThreadOverlap[]; /** * How many co-threads the activity ticker names before eliding, and how many * filenames it shows per co-thread. Bounded for the same reason task 102 bounds * its files-touched annotation: this text is appended to EVERY turn of a master * thread, so an unbounded list is a per-turn dilution charge that grows with the * project (the 088-092 lesson). */ export declare const ACTIVITY_MAX_THREADS = 6; export declare const ACTIVITY_MAX_FILES = 3; /** * The master-facing activity ticker (task 153). * * Pure over exactly the data `list_co_threads` already computes, so the prompt * and the tool can never disagree about what a co-thread is doing. * * The header is load-bearing, not decoration. This is git state — files a * co-thread currently has open — and it is NOT a report of what that co-thread * concluded. Task 114 is the recorded incident of a confidently false prompt * claim, and the specific false claim available here is "the worker found X" * inferred from "the worker has X.ts open". Naming the provenance is the same * discipline task 093 applied to retrieved context. * * Returns '' when there is nothing to say, so the caller's append is identity * and a master whose co-threads are all idle pays no tokens. */ export declare function formatCoThreadActivity(statuses: CoThreadStatus[], overlaps: CoThreadOverlap[]): string; /** What a co-thread has done in its worktree, from the master's point of view. */ export declare function coThreadStatus(coThreadName: string, masterDir: string | undefined): Promise; export interface MergeResult { merged: boolean; /** Nothing on the branch that the master does not already have. */ upToDate?: boolean; /** Paths git could not merge. The master's tree is restored when this is set. */ conflicts?: string[]; /** Uncommitted work found in the worktree and committed before merging. */ committed?: string[]; error?: string; } /** * Merge a co-thread's branch into the master's current branch. * * Three rules, each of which exists because its absence loses work: * * 1. **Uncommitted work in the worktree is committed first.** Merging the * branch while the co-thread's actual edits sit uncommitted beside it is * precisely the silent loss this phase exists to prevent. * 2. **A dirty master refuses.** Asymmetric with (1) on purpose: the worktree is * private scratch space we created, while the master's checkout may hold * Karl's own uncommitted work, and no automation should sweep that into a * commit it did not ask for. * 3. **Conflicts abort and report.** `git merge --abort` restores the master's * tree and the conflicted paths come back to the MASTER THREAD as work to * direct. Leaving a half-merged tree behind would hand Karl exactly the git * chore this phase promised to keep away from him. */ export declare function mergeCoThread(coThreadName: string, masterDir: string | undefined): Promise; //# sourceMappingURL=worktree.d.ts.map