import { type RepointedHead } from '../git-diff-base.ts'; export interface ChangedFile { path: string; /** Rename/copy source — present only when `status` is renamed/copied. */ oldPath?: string; status: 'added' | 'modified' | 'deleted' | 'renamed' | 'copied'; adds: number; dels: number; binary: boolean; /** True when `path`'s extension is one the raw-bytes route (`/files?raw=1`) will serve as an * `` (#365) — lets the diff pane preview it inline instead of the "Binary file" note, * even for extensions (SVG) git itself doesn't flag `binary`. Present only when true. */ image?: boolean; patch: string; } export interface ChangesPayload { files: ChangedFile[]; stat: { adds: number; dels: number; files: number; }; /** Present when a run worktree was repointed away from the task branch (#591). In that case the * payload is intentionally limited to uncommitted changes instead of attributing the * checked-out branch's history to this task. */ repointedHead?: RepointedHead; } export type ChangesResult = { ok: true; changes: ChangesPayload; } | { ok: false; error: string; }; interface NumstatEntry { adds: number; dels: number; binary: boolean; path: string; oldPath?: string; } /** Parse `git diff --numstat -z -M`: `adds TAB dels TAB path NUL`, or for a * rename/copy `adds TAB dels TAB NUL old NUL new NUL`. Binary files report * `-` for both counters. Exported for tests. */ export declare function parseNumstatZ(out: string): NumstatEntry[]; interface NameStatusEntry { status: string; path: string; oldPath?: string; } /** Parse `git diff --name-status -z -M`: `X NUL path NUL`, renames/copies * `Rnnn NUL old NUL new NUL`. Exported for tests. */ export declare function parseNameStatusZ(out: string): NameStatusEntry[]; /** Split one `git diff --patch` blob into per-file sections, in git's file * order (the same order `--numstat`/`--name-status` use). Exported for tests. */ export declare function splitPatch(patch: string): string[]; /** Map each `git diff --patch` section to the file path it describes, so a file's * patch is looked up by path rather than by position. This is robust to the two * listings (`--name-status` and `--patch`) disagreeing on file count — the case * where positional matching used to blank *every* file's patch. Exported for tests. */ export declare function patchByPath(sections: string[]): Map; /** * Structured "what changed here" for a directory vs its base branch: * committed + uncommitted + untracked (via `add -N`), anchored by the shared * `resolveTaskDiffBase` rule (`src/git-diff-base.ts`) — the merge-base against * the freshest base ref, so the diff stays *this task's* changes even after the * base moves on, and the branch's state at `runStartedAt` when the agent * repointed the worktree onto another branch (#591, #751). * `worktreeShortstat` resolves through the same helper; the text-blob `/diff` * endpoint (`worktreeDiff`) deliberately does not — see its own note. */ export declare function collectChanges(dir: string, baseBranch: string, opts?: { patchCap?: number; intentToAdd?: boolean; taskBranch?: string; runStartedAt?: string; }): Promise; /** The three raw `git diff` listings (name-status, numstat, patch) → the `{files, stat}` * payload. Shared by the working-tree diff above and the commit diff below. Each file's * patch is matched by path (`patchByPath`), so a mismatch between the name-status and * patch file counts — a typechange, submodule, or any entry that emits no `diff --git` * block — drops at most that one file's patch instead of blanking every file's. Positional * matching remains the fallback for a section whose path can't be parsed, but only when the * counts agree (the same condition the old all-or-nothing guard required). Exported for tests. */ export declare function assemblePayload(nameStatusOut: string, numstatOut: string, patchOut: string, patchCap: number): ChangesPayload; export interface RunCommit { sha: string; subject: string; author: string; /** Relative time ("3 hours ago"), git's `%cr`. */ when: string; } export type RunCommitsResult = { ok: true; commits: RunCommit[]; } | { ok: false; error: string; }; /** * The commits reachable from the worktree's current HEAD after its base, newest first. A review * task may deliberately repoint HEAD to the reviewed branch, so this list retains that useful * history even though `collectChanges` narrows its payload to uncommitted work in that case. * Empty (not an error) when the branch has no commits past base. */ export declare function collectRunCommits(dir: string, baseBranch: string): Promise; /** `GET /api/repo/commit/:sha?structured=1` — one commit's metadata plus the same * structured `{files, stat}` shape the working-tree routes serve (R5 Step 1.7). The * legacy text-blob answer of that route is a protected surface and stays untouched. */ export interface CommitPayload { sha: string; subject: string; author: string; /** Relative time ("3 hours ago") — same `%cr` format the /api/repo log uses. */ when: string; files: ChangedFile[]; stat: { adds: number; dels: number; files: number; }; } export type CommitChangesResult = { ok: true; commit: CommitPayload; } | { ok: false; error: string; }; /** * Structured diff of ONE commit vs its first parent (`--root` covers the initial commit). * A merge commit honestly answers zero files — `git diff-tree` shows no diff for merges * without `-m`/`-c`, and inventing one side's diff would misattribute the changes. * Unknown/invalid shas degrade to `{ ok:false, error }` for the route's 409. */ export declare function collectCommitChanges(dir: string, sha: string, patchCap?: number): Promise; /** Max file content served to the Files tab — past this the GUI shows an * honest "too large" state instead of the bytes. */ export declare const FILE_CONTENT_CAP = 512000; export interface DirEntry { name: string; type: 'dir' | 'file'; size?: number; } export type FilesResult = { kind: 'dir'; path: string; entries: DirEntry[]; } | { kind: 'file'; path: string; size: number; binary: boolean; tooLarge: boolean; content?: string; } | { kind: 'invalid'; error: string; } | { kind: 'missing'; error: string; }; /** The image MIME type for a path, or null when it is not an image we serve raw. */ export declare function imageMimeType(path: string): string | null; /** True when `path` is an image safe to hand to the OS's default handler — see OS_OPENABLE_EXT. */ export declare function isOsOpenableImage(path: string): boolean; /** * Directory listing or file content from inside `root`, traversal-safe: * anything resolving outside the root (dot-segments, absolute paths, NUL) is * rejected — same "not a file we serve" stance as `isSafeAssetFilename` in * src/server/static-ui.ts. `.git` and symlinks are off-limits too. */ export declare function readWorktreePath(root: string, relPath: string, contentCap?: number): Promise; export type BranchResult = { ok: true; branch: string; created: boolean; } | { ok: false; error: string; }; /** * Repo-view branch action (`POST /api/repo/branch`): switch to `name` when it * already exists locally, otherwise create it from `from` (or HEAD) and switch. * Name validation is delegated to `git check-ref-format --branch` — git's own * rules, not a reimplementation — behind an explicit dash-guard (#431). * Predictable failures (invalid name, unknown `from`, dirty-tree checkout * conflict) come back as `{ ok:false, error }`. */ export declare function createOrSwitchBranch(dir: string, name: string, from?: string): Promise; export type CommitResult = { ok: true; sha: string; } | { ok: false; error: string; }; /** `git add -A && git commit -m ` in `dir`. A clean tree, a failing * hook or missing identity all come back as `{ ok:false, error }`. */ export declare function commitAll(dir: string, message: string): Promise; export type PushResult = { ok: true; branch: string; remote: string; upstreamSet: boolean; } | { ok: false; error: string; }; /** Push the current branch, setting upstream when it has none. No remote, * detached HEAD and rejected pushes all degrade to `{ ok:false, error }`. */ export declare function pushCurrentBranch(dir: string): Promise; export {};