@cat-factory/executor-harness 1.78.0 → 1.82.0

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.
Files changed (55) hide show
  1. package/README.md +1 -0
  2. package/dist/agent-capabilities.d.ts +130 -0
  3. package/dist/agent-runner.d.ts +114 -0
  4. package/dist/agent-runner.js +15 -1
  5. package/dist/agent-shared.d.ts +18 -0
  6. package/dist/agent.d.ts +66 -0
  7. package/dist/bootstrap-mode.d.ts +20 -0
  8. package/dist/captured-command.d.ts +58 -0
  9. package/dist/claude-call-aggregator.d.ts +164 -0
  10. package/dist/claude-call-aggregator.js +123 -17
  11. package/dist/claude-stream.d.ts +56 -0
  12. package/dist/claude-stream.js +23 -0
  13. package/dist/coding-agent.d.ts +263 -0
  14. package/dist/dependency-install.d.ts +111 -0
  15. package/dist/effort.d.ts +19 -0
  16. package/dist/embed.d.ts +4 -0
  17. package/dist/failure.d.ts +42 -0
  18. package/dist/follow-ups.d.ts +28 -0
  19. package/dist/frontend-infra.d.ts +25 -0
  20. package/dist/fs-utils.d.ts +2 -0
  21. package/dist/git.d.ts +394 -0
  22. package/dist/host-markdown.d.ts +28 -0
  23. package/dist/inline.d.ts +10 -0
  24. package/dist/job.d.ts +666 -0
  25. package/dist/logger.d.ts +16 -0
  26. package/dist/onboarding-preseed.d.ts +24 -0
  27. package/dist/package-registries.d.ts +32 -0
  28. package/dist/pi-workspace.d.ts +194 -0
  29. package/dist/pi-workspace.js +4 -0
  30. package/dist/pi.d.ts +475 -0
  31. package/dist/pr-description.d.ts +85 -0
  32. package/dist/pr-template.d.ts +101 -0
  33. package/dist/process-exit.d.ts +7 -0
  34. package/dist/process.d.ts +19 -0
  35. package/dist/progress-guard.d.ts +88 -0
  36. package/dist/progress.d.ts +87 -0
  37. package/dist/redact.d.ts +31 -0
  38. package/dist/reproduction-proof.d.ts +224 -0
  39. package/dist/runner.d.ts +282 -0
  40. package/dist/runner.js +3 -0
  41. package/dist/server.d.ts +3 -0
  42. package/dist/structured-output.d.ts +75 -0
  43. package/dist/subagents.d.ts +88 -0
  44. package/dist/subagents.js +74 -4
  45. package/dist/transcript-retention.d.ts +21 -0
  46. package/dist/validation-checks.d.ts +159 -0
  47. package/dist/vcs-api.d.ts +73 -0
  48. package/dist/version.d.ts +2 -0
  49. package/package.json +9 -5
  50. package/src/agent-runner.ts +21 -2
  51. package/src/claude-call-aggregator.ts +181 -32
  52. package/src/claude-stream.ts +21 -0
  53. package/src/pi-workspace.ts +4 -0
  54. package/src/runner.ts +24 -0
  55. package/src/subagents.ts +57 -3
@@ -0,0 +1,19 @@
1
+ /** The sentinel file the agent writes its effort self-assessment to (relative to its cwd). */
2
+ export declare const EFFORT_REPORT_FILE = ".cat-effort.json";
3
+ /** A container agent's self-assessment of the work it just did. */
4
+ export interface EffortReport {
5
+ /** How hard the work was: 1 (trivial) .. 10 (extremely hard). */
6
+ difficulty: number;
7
+ /** One or two sentences on how hard/easy the work was and why. */
8
+ summary?: string;
9
+ /** What reduced the agent's effectiveness. */
10
+ reducedEffectiveness?: string;
11
+ /** The key obstacles the agent hit. */
12
+ obstacles?: string[];
13
+ }
14
+ /**
15
+ * Read + parse + REMOVE the agent's effort sentinel file from `cwd`. Lenient: returns undefined
16
+ * when the file is absent (the agent wrote none), unreadable, not JSON, or carries nothing
17
+ * meaningful. Never throws — a malformed self-report must never fail an otherwise-good run.
18
+ */
19
+ export declare function readEffortReport(cwd: string): Promise<EffortReport | undefined>;
@@ -0,0 +1,4 @@
1
+ export { PI_MAX_OUTPUT_TOKENS, writePiModelsConfig, writeAgentsContext, runPi, summarizePiRun, parsePiOutput, parseTodoProgress, terminalRunError, type PiRunOutcome, type PiRunStats, type TodoItem, type TodoProgress, } from './pi.js';
2
+ export { DEFAULT_PROGRESS_GUARD_LIMITS, progressGuardLimitsFromEnv, type ProgressGuardLimits, } from './progress-guard.js';
3
+ export { cloneRepo, createBranch, changedPathsFromPorcelain, hasAgentChanges, redactSecrets, } from './git.js';
4
+ export type { RepoSpec } from './job.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The structured reason a harness job failed, surfaced on the job view's `failureCause`.
3
+ * Covers only HARNESS-owned failures — container eviction is detected by the runtime facade
4
+ * (a vanished container → `(container evicted or crashed)`), never set here.
5
+ *
6
+ * - `inactivity-timeout` — the inactivity watchdog fired (no agent output for the window).
7
+ * - `max-duration` — the overall wall-clock cap fired.
8
+ * - `agent` — the agent ran but produced an unusable/failed result, or threw.
9
+ * - `git` — a git operation failed (clone/push/merge/PR).
10
+ * - `api` — an upstream API call failed (e.g. the GitHub/GitLab PR/MR REST call).
11
+ * - `llm-upstream` — the model provider rejected every call (auth/quota/rate-limit) and Pi
12
+ * exhausted its retries, so the run never produced a result.
13
+ * - `no-usable-output` — the agent finished but returned no usable report / structured output.
14
+ * - `no-changes` — a coding agent finished without producing any change to push.
15
+ */
16
+ export type FailureCause = 'inactivity-timeout' | 'max-duration' | 'agent' | 'git' | 'api' | 'llm-upstream' | 'no-usable-output' | 'no-changes';
17
+ /**
18
+ * A thrown failure that carries a structured {@link FailureCause}, so a `git` / `api`
19
+ * operation that fails deep in a helper surfaces its real cause instead of being flattened
20
+ * to the generic `agent` in the registry's catch. The watchdog kills set their cause from
21
+ * `killReason` and never throw this; anything else thrown without a cause stays `agent`.
22
+ */
23
+ export declare class HarnessFailure extends Error {
24
+ readonly failureCause: FailureCause;
25
+ constructor(failureCause: FailureCause, message: string);
26
+ }
27
+ /** The structured cause a thrown error carries, or undefined for a plain/agent error. */
28
+ export declare function failureCauseOf(err: unknown): FailureCause | undefined;
29
+ /**
30
+ * The inactivity-watchdog abort message PREFIX. Human-readable only now — the backend reads the
31
+ * structured `inactivity-timeout` {@link FailureCause}, not this phrase (the string fallback was
32
+ * deleted in error-message coverage I5), so it is free to change. The caller appends a `(likely
33
+ * hung ...)` diagnostic clause (phase + last tool) after this, so the prefix deliberately stops
34
+ * before the parenthetical (see `runner.ts` drive catch).
35
+ */
36
+ export declare function inactivityAbortMessage(inactivityMs: number): string;
37
+ /**
38
+ * The max-duration-watchdog abort message. Human-readable only now — the backend reads the
39
+ * structured `max-duration` {@link FailureCause}, not this phrase (the string fallback was deleted
40
+ * in error-message coverage I5), so it is free to change.
41
+ */
42
+ export declare function maxDurationAbortMessage(maxDurationMs: number): string;
@@ -0,0 +1,28 @@
1
+ import { type Logger } from './logger.js';
2
+ /** The sentinel file the Coder appends items to, relative to its working directory. */
3
+ export declare const FOLLOW_UPS_FILENAME = ".cat-follow-ups.jsonl";
4
+ /** One streamed item the Coder surfaced. Mirrors the backend's `streamedFollowUpSchema`. */
5
+ export interface FollowUpLine {
6
+ kind: 'follow_up' | 'question';
7
+ title: string;
8
+ detail: string;
9
+ suggestedAction?: string;
10
+ }
11
+ /**
12
+ * Tails an append-only JSONL sentinel file, yielding only the NEW complete lines on each
13
+ * {@link poll}. Tracks how many characters have been consumed so a partially-written
14
+ * trailing line (no newline yet) is held back until it completes. Tolerant: a malformed
15
+ * line is skipped, a missing file yields nothing — surfacing follow-ups must never
16
+ * disturb the coding run.
17
+ */
18
+ export declare class FollowUpTailer {
19
+ private readonly filePath;
20
+ private readonly onItems;
21
+ private readonly logger;
22
+ private consumed;
23
+ /** Running count of complete-but-unparsable lines, so silent drops become visible. */
24
+ private skipped;
25
+ constructor(filePath: string, onItems: (items: FollowUpLine[]) => void, logger?: Logger);
26
+ /** Read any new complete lines and emit the coerced items. Best-effort; never throws. */
27
+ poll(): Promise<void>;
28
+ }
@@ -0,0 +1,25 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import type { FrontendInfraSpec, InfraSetupRecord } from './job.js';
3
+ import type { RunOptions } from './runner.js';
4
+ import { type Logger } from './logger.js';
5
+ export interface FrontendStandUp {
6
+ /** The processes to terminate on teardown (WireMock + the served app). */
7
+ processes: ChildProcess[];
8
+ /** The URL the built app is served at, when it came up. Folded into the agent prompt. */
9
+ serveUrl?: string;
10
+ /** A problem note folded into the agent prompt (a failed build / server that never bound). */
11
+ note?: string;
12
+ /** The captured (redacted, bounded) stand-up record surfaced on the Tester step. */
13
+ record: InfraSetupRecord;
14
+ }
15
+ /** The install command for a package manager (an explicit `install` overrides this). */
16
+ export declare function installCommand(spec: FrontendInfraSpec): string[];
17
+ /**
18
+ * Build the frontend, start WireMock, serve the built app and health-check both. Best-effort,
19
+ * like the docker-compose stand-up: a failed build / server that never binds is surfaced to
20
+ * the agent as a prompt note (and captured on the record) rather than failing the job — the
21
+ * agent then reports the gap as a concern. Every path returns the processes to tear down.
22
+ */
23
+ export declare function standUpFrontend(dir: string, infra: FrontendInfraSpec, run: Pick<RunOptions, 'signal' | 'onActivity' | 'agentEnv'>, logger?: Logger): Promise<FrontendStandUp>;
24
+ /** Terminate the frontend stand-up's processes (WireMock + the served app). Best-effort. */
25
+ export declare function tearDownFrontend(processes: ChildProcess[], logger?: Logger): Promise<void>;
@@ -0,0 +1,2 @@
1
+ /** Whether `path` exists (a file or directory), swallowing ENOENT (and any stat error). */
2
+ export declare function pathExists(path: string): Promise<boolean>;
package/dist/git.d.ts ADDED
@@ -0,0 +1,394 @@
1
+ import type { BootstrapTargetSpec, RepoSpec } from './job.js';
2
+ export { redactSecrets } from './redact.js';
3
+ export declare const NON_INTERACTIVE_CREDENTIAL_ARGS: string[];
4
+ /**
5
+ * Whether `err` is a per-command TIMEOUT kill (the child exceeded `execFile`'s `timeout`, so
6
+ * Node killed it with `killSignal` and set `killed=true`) — as opposed to a normal non-zero
7
+ * exit or a watchdog/caller abort. `aborted` is the caller signal's state: an abort ALSO
8
+ * kills the child, but it's the outer watchdog's story (recorded via `killReason` upstream),
9
+ * so it must NOT be reported here as a git timeout. Pure, so the classification is unit-tested.
10
+ */
11
+ export declare function isGitTimeoutKill(err: unknown, aborted: boolean): boolean;
12
+ /**
13
+ * Classify the common shapes of git's own stderr into an actionable remedy, else undefined
14
+ * (an unrecognized failure keeps just its raw stderr). This is the FIRST-WRAP-POINT for
15
+ * unavoidable third-party text (per the error-message initiative's I6): git's stderr is the
16
+ * only signal we get for a clone/push auth or access fault, so we match it ONCE here and
17
+ * APPEND a cause + fix, never rewrite the raw line. Host-neutral — the same remedy serves a
18
+ * GitHub-App installation token and a GitLab/GitHub PAT (local mode). Pure, so it is
19
+ * unit-tested over a fixed set of stderr strings.
20
+ */
21
+ export declare function describeGitFailure(stderr: string): string | undefined;
22
+ /**
23
+ * Build the remote URL git uses. Only the username (`x-access-token`) is embedded
24
+ * — never the token — so the token never appears in argv. The token is supplied
25
+ * separately via {@link authEnv} and read by the GIT_ASKPASS helper.
26
+ *
27
+ * The `x-access-token` username is host-neutral: GitHub keys auth off the token (password)
28
+ * and ignores the username, and GitLab likewise accepts ANY non-blank username with a PAT as
29
+ * the password — so the same embedded username authenticates github.com and gitlab.com alike.
30
+ */
31
+ export declare function authenticatedCloneUrl(cloneUrl: string): string;
32
+ /** Clone `repo`'s base branch (shallow by default) into `dir` and set commit identity. */
33
+ export declare function cloneRepo(opts: {
34
+ repo: RepoSpec;
35
+ ghToken: string;
36
+ dir: string;
37
+ signal?: AbortSignal;
38
+ /**
39
+ * Full history + all remote-tracking branches. A shallow single-branch clone is
40
+ * enough to implement on one branch, but merging ANOTHER branch in (the
41
+ * conflict-resolver) needs the merge base in history and `origin/<other>` present
42
+ * — so `full` drops both `--depth 1` (which implies `--single-branch`).
43
+ */
44
+ full?: boolean;
45
+ }): Promise<void>;
46
+ /** Create and switch to the work branch. */
47
+ export declare function createBranch(dir: string, branch: string, signal?: AbortSignal): Promise<void>;
48
+ /**
49
+ * Whether `branch` already exists on the remote — i.e. an earlier (possibly
50
+ * evicted) run of this task already pushed work to it, so a re-dispatch should
51
+ * RESUME on it (clone it, continue on its commits) rather than branch off base and
52
+ * start over. Uses `git ls-remote` (no checkout); the token is supplied out of band.
53
+ */
54
+ export declare function remoteBranchExists(cloneUrl: string, branch: string, ghToken: string, signal?: AbortSignal): Promise<boolean>;
55
+ /**
56
+ * Clone an EXISTING work branch (full history) into `dir` and check it out — used
57
+ * to resume a task whose earlier run already pushed commits to this branch, so the
58
+ * agent continues on top of that work instead of redoing it.
59
+ */
60
+ export declare function cloneExistingBranch(opts: {
61
+ cloneUrl: string;
62
+ branch: string;
63
+ ghToken: string;
64
+ dir: string;
65
+ signal?: AbortSignal;
66
+ }): Promise<void>;
67
+ /**
68
+ * The directory-name globs the clean sweep PRESERVES — dependency caches that are
69
+ * expensive to rebuild (node_modules, language toolchain caches). Keeping them is the
70
+ * whole point of reusing a checkout: a `git clean -ffdx` would otherwise wipe them and
71
+ * force a reinstall every run. Configurable via `HARNESS_CLEAN_KEEP` (comma-separated).
72
+ */
73
+ export declare function cleanKeepPatterns(env?: NodeJS.ProcessEnv): string[];
74
+ /**
75
+ * Reset a REUSED checkout to a pristine state before the next job runs in it: hard-reset
76
+ * tracked files and remove every untracked/ignored file EXCEPT the preserved dependency
77
+ * caches (see {@link cleanKeepPatterns}). This is what guarantees a prior run's garbage —
78
+ * stray scratch files, half-written edits, stale build output — never contaminates the
79
+ * next run that reuses the same persistent checkout. A fresh clone never needs it.
80
+ *
81
+ * Submodules: when `.gitmodules` is present we use a single `-f` (which makes `git clean`
82
+ * skip nested git repositories, i.e. the submodule worktrees) and reset/refresh the
83
+ * submodules explicitly; otherwise `-ff` also nukes any stray nested repo the agent left.
84
+ */
85
+ export declare function cleanSweep(dir: string, ghToken: string, signal?: AbortSignal, env?: NodeJS.ProcessEnv): Promise<void>;
86
+ /**
87
+ * The `origin` remote URL (without credentials) of the checkout at `dir`, or undefined
88
+ * when it isn't a git repo / has no origin. Used to detect a persistent checkout dir that
89
+ * somehow holds a DIFFERENT repo than the one we're about to prepare (it never should —
90
+ * the dir is keyed per repo — but a stale dir from a prior layout would be a silent
91
+ * cross-repo bleed, so we re-clone rather than reuse).
92
+ */
93
+ export declare function checkoutRemoteUrl(dir: string, signal?: AbortSignal): Promise<string | undefined>;
94
+ /**
95
+ * Prepare a REUSED (persistent) checkout at `dir` so the agent runs against a clean tree
96
+ * on the right branch — the persistent-checkout analogue of {@link cloneRepo} +
97
+ * {@link cloneExistingBranch}. On the FIRST use of a per-repo dir there's no `.git` yet, so
98
+ * it clones once (full history, so a later merger/conflict step reusing the dir can diff
99
+ * against the base); afterwards it reuses the dir in place: clean sweep → re-point origin →
100
+ * fetch → check out `branch`. When `existing` is true `branch` is fetched and checked out
101
+ * directly (resume / base branch); otherwise `branch` is (re)created off `baseBranch`'s tip
102
+ * (a fresh work branch). Only the local transport sets `persistentCheckout`, so every other
103
+ * runtime keeps the fresh-clone path untouched.
104
+ */
105
+ export declare function prepareExistingCheckout(opts: {
106
+ dir: string;
107
+ repo: RepoSpec;
108
+ ghToken: string;
109
+ /** The branch to end up checked out on. */
110
+ branch: string;
111
+ /** Base branch to (re)create `branch` off when `existing` is false; also fetched for history. */
112
+ baseBranch: string;
113
+ /** Whether `branch` already exists on the remote (resume / base) — checkout it directly. */
114
+ existing: boolean;
115
+ signal?: AbortSignal;
116
+ }): Promise<void>;
117
+ /**
118
+ * Commit edits the agent left UNCOMMITTED — but only to files git already tracks
119
+ * (`git add -u`), never new untracked files. The agent owns commit selection (it
120
+ * alone knows which new files are part of the solution vs scratch scripts/artifacts
121
+ * it created while exploring), so this is just a safety net that captures forgotten
122
+ * edits to existing files without ever sweeping in junk a blanket `git add -A`
123
+ * would. Returns false when there was nothing tracked to commit.
124
+ */
125
+ export declare function commitTrackedEdits(dir: string, message: string, signal?: AbortSignal): Promise<boolean>;
126
+ /**
127
+ * The untracked, non-ignored files left in the working tree (`git ls-files --others
128
+ * --exclude-standard`). The harness deliberately never blanket-stages new files (the
129
+ * agent owns commit selection), so this is exactly what {@link commitTrackedEdits}
130
+ * does NOT capture — a NEW file the agent created but forgot to commit. The caller
131
+ * surfaces it as a warning so that silent loss is at least observable in the logs.
132
+ */
133
+ export declare function listUntrackedFiles(dir: string, signal?: AbortSignal): Promise<string[]>;
134
+ /**
135
+ * The untracked, non-ignored paths in the working tree with whole untracked DIRECTORIES
136
+ * collapsed to a single `dir/` entry (`--directory`), rather than every file beneath them.
137
+ *
138
+ * The sibling {@link listUntrackedFiles} answers "what did the agent forget to commit", where
139
+ * every individual file is the point. This one answers "what appeared in the tree", where it is
140
+ * emphatically not: a dependency install leaves tens of thousands of files under one directory,
141
+ * and enumerating them would cost a multi-megabyte listing to learn a single name.
142
+ */
143
+ export declare function listUntrackedPaths(dir: string, signal?: AbortSignal): Promise<string[]>;
144
+ /**
145
+ * Locally exclude `pattern` from this checkout via `.git/info/exclude` — a per-clone
146
+ * ignore that never lands in the repo (unlike a `.gitignore`). Used for the harness's
147
+ * follow-up sentinel file so the agent's own `git add` can never stage it and it never
148
+ * surfaces as an untracked-leftover warning or in the PR. Best-effort: a failure here
149
+ * just means the sentinel might show as untracked (logged, not pushed), never fatal.
150
+ */
151
+ export declare function excludeFromGit(dir: string, pattern: string, signal?: AbortSignal): Promise<void>;
152
+ /**
153
+ * Locally exclude LITERAL paths — never patterns — from this checkout, in ONE write.
154
+ *
155
+ * The sibling {@link excludeFromGit} takes an author-written pattern for a known sentinel. These
156
+ * paths instead come from the FILESYSTEM (what a dependency install left behind), so two things
157
+ * differ. Each is escaped, because a directory named `pkg[1]` read as a gitignore character class
158
+ * excludes something else entirely and, being a no-op on the real path, fails silently. And they
159
+ * are appended together, because a per-path append would cost one file write per entry to build
160
+ * a list that is already known in full.
161
+ *
162
+ * Anchored: `ls-files` reports repo-root-relative paths and a gitignore pattern containing a
163
+ * slash is root-anchored, which is what makes `packages/api/node_modules/` exclude that service's
164
+ * tree and not a same-named directory elsewhere. Best-effort, exactly like its sibling.
165
+ */
166
+ export declare function excludePathsFromGit(dir: string, paths: readonly string[], signal?: AbortSignal): Promise<void>;
167
+ /** Whether the branch advanced past `baseSha` via commits (the agent's own + any safety-net commit). */
168
+ export declare function branchHasCommitsSince(dir: string, baseSha: string, signal?: AbortSignal): Promise<boolean>;
169
+ /**
170
+ * Whether the checked-out branch carries at least one commit the PR base does NOT — i.e.
171
+ * `git rev-list --count <base>..HEAD > 0`. A resume clone is single-branch, so it has no
172
+ * `origin/<base>` tracking ref; this fetches the base into a dedicated local ref first and
173
+ * diffs HEAD against it.
174
+ *
175
+ * Tri-state on purpose:
176
+ * - `true` — confirmed ≥1 commit ahead (there is something to open a PR for).
177
+ * - `false` — confirmed 0 commits ahead (the branch is reachable from base, e.g. its earlier
178
+ * PR was merged with a merge commit and the best-effort branch delete was skipped).
179
+ * - `undefined` — could not determine (fetch / rev-list error); the caller keeps its prior
180
+ * behaviour rather than wrongly dropping a resumed branch that has real work.
181
+ *
182
+ * Used by the resume path to avoid declaring a merged/empty branch as work and then failing
183
+ * the run with GitHub's opaque 422 "No commits between <base> and <branch>".
184
+ */
185
+ export declare function branchAheadOfBase(dir: string, baseBranch: string, ghToken: string, signal?: AbortSignal): Promise<boolean | undefined>;
186
+ /**
187
+ * The files `commitish` changes relative to its merge base with the PR base branch — i.e.
188
+ * everything the work branch has added on top of base, `git diff --name-only <base>...<commitish>`.
189
+ *
190
+ * The BUGFIX REPRODUCTION PROOF uses this to answer the one question that decides whether a GREEN
191
+ * pre-fix tree means anything: does that tree ALREADY carry non-test work committed on this
192
+ * branch? A resumed run's `baseSha` is whatever the branch tip was when this pass started, which
193
+ * in the designed flow is the reproduction step's test commit — but after an eviction it is this
194
+ * same coder step's own interrupted work, fix included. Reporting "the check passed before your
195
+ * change, so it does not demonstrate the defect" in that case is simply false.
196
+ *
197
+ * `undefined` means "could not determine" (a shallow clone with no reachable merge base, a fetch
198
+ * failure, an unknown ref), never an empty list: the caller must degrade to its prior behaviour
199
+ * rather than read a failed probe as "the tree is clean".
200
+ *
201
+ * NUL-delimited so a path containing a newline (legal in git) cannot split into two entries.
202
+ */
203
+ export declare function changedFilesSinceBase(dir: string, baseBranch: string, ghToken: string, commitish: string, signal?: AbortSignal): Promise<string[] | undefined>;
204
+ /**
205
+ * Whether the checked-out branch has a real, examinable diff against
206
+ * `origin/<baseBranch>` — i.e. the base branch's remote-tracking ref exists (so the
207
+ * merge base resolves) AND there are changes between that merge base and HEAD. The
208
+ * merger uses this to refuse to score a PR it could not actually inspect (a missing
209
+ * base ref or an empty diff) instead of emitting bogus low scores that would
210
+ * auto-merge. Returns false on ANY git error (e.g. an unknown ref). Requires a
211
+ * {@link cloneRepo} with `full: true` so `origin/<baseBranch>` and the merge base exist.
212
+ */
213
+ export declare function hasDiffAgainstBase(dir: string, baseBranch: string, signal?: AbortSignal): Promise<boolean>;
214
+ /**
215
+ * Parse the paths out of `git status --porcelain` (v1) output. Each line is
216
+ * `XY <path>`, or `XY <old> -> <new>` for a rename/copy (we keep the new path);
217
+ * git quotes paths with special characters, which we unquote. Blank lines are
218
+ * skipped. Pure so the no-op detection can be tested without spawning git.
219
+ */
220
+ export declare function changedPathsFromPorcelain(status: string): string[];
221
+ /**
222
+ * Whether the agent changed anything in a cloned checkout. Stages the working
223
+ * tree and inspects the porcelain status: an empty result means the bootstrapper
224
+ * made no adaptation — a no-op we must not pass off as a successful push. (The
225
+ * harness writes its prompt context to Pi's global `~/.pi/agent/AGENTS.md`, never
226
+ * into the checkout, so every change reported here is a genuine agent edit.)
227
+ */
228
+ export declare function hasAgentChanges(dir: string, signal?: AbortSignal): Promise<boolean>;
229
+ /** The commit SHA at `dir`'s HEAD — captured right after clone as the base tip. */
230
+ export declare function headCommit(dir: string, signal?: AbortSignal): Promise<string>;
231
+ /**
232
+ * Add a DETACHED worktree of `commitish` at `worktreePath`, sharing `dir`'s object database.
233
+ *
234
+ * The bugfix reproduction proof runs the declared check against two trees of the SAME clone (the
235
+ * pre-fix tree and the final tree), so a worktree is the only mechanism that gets both without a
236
+ * second clone, a second fetch, or disturbing the agent's own checkout — which must stay exactly
237
+ * as the agent left it, since the push and the PR come off it.
238
+ *
239
+ * `--detach` (rather than a branch) is deliberate: a worktree that claimed a branch would collide
240
+ * with the work branch checked out in `dir`, and nothing here ever commits.
241
+ *
242
+ * `worktreePath` is expected to live OUTSIDE the checkout (a per-job temp root), so the worktree's
243
+ * `.git` pointer file can never be swept into the agent's commit by a broad `git add -A`.
244
+ */
245
+ export declare function addWorktree(dir: string, worktreePath: string, commitish: string, signal?: AbortSignal): Promise<void>;
246
+ /**
247
+ * Remove a worktree previously added by {@link addWorktree} and prune the stale administrative
248
+ * entry, never throwing: teardown is bookkeeping, and a run whose PROOF succeeded must not fail
249
+ * because a temp directory could not be cleaned up. The caller still deletes the temp root, so a
250
+ * failure here leaks only a `.git/worktrees/<name>` record inside a container that is about to be
251
+ * destroyed anyway.
252
+ */
253
+ export declare function removeWorktree(dir: string, worktreePath: string, signal?: AbortSignal): Promise<void>;
254
+ /**
255
+ * Which of `paths` actually exist in `commitish`'s tree. Used by the reproduction proof to tell a
256
+ * DECLARED test file that was committed from one that only ever existed as an untracked working-
257
+ * tree file: the proof runs against committed trees, so an unadded test is invisible to it — and
258
+ * equally invisible to the push, which is the point worth telling the agent about rather than
259
+ * reporting a verdict computed without the reproduction in it.
260
+ *
261
+ * Returns the input order/spelling of the paths that matched, so the caller can diff against its
262
+ * declared list to name the missing ones verbatim.
263
+ */
264
+ export declare function pathsPresentAtCommit(dir: string, commitish: string, paths: readonly string[], signal?: AbortSignal): Promise<string[]>;
265
+ /**
266
+ * Check `paths` out of `commitish` into `dir`'s working tree (and index), leaving every other file
267
+ * untouched.
268
+ *
269
+ * This is how the reproduction's declared TEST files are placed onto the pre-fix worktree, and the
270
+ * narrowness is the whole safety property: a whole-tree checkout would drag the FIX across too and
271
+ * green the base, manufacturing a "the test does not capture the defect" verdict out of a
272
+ * perfectly good reproduction. Only the paths the caller has already sanitized are passed, and
273
+ * `--` stops any of them being read as a revision.
274
+ */
275
+ export declare function checkoutPathsFrom(dir: string, commitish: string, paths: readonly string[], signal?: AbortSignal): Promise<void>;
276
+ /** Stage everything and commit; returns false when there was nothing to commit. */
277
+ export declare function commitAll(dir: string, message: string, signal?: AbortSignal): Promise<boolean>;
278
+ /** Paths git still reports as unmerged (conflict stage entries) in the working tree. */
279
+ export declare function unmergedPaths(dir: string, signal?: AbortSignal): Promise<string[]>;
280
+ /**
281
+ * The conflict hunks for the given unmerged `paths`: `git diff` over exactly those
282
+ * files, which for an unmerged entry renders the combined diff carrying the
283
+ * `<<<<<<<` / `=======` / `>>>>>>>` markers each side contributed. Handed to the
284
+ * conflict-resolver agent so it sees the actual conflicts instead of having to
285
+ * rediscover them. Capped to `maxChars` total (a note is appended on truncation) so a
286
+ * huge conflict can't blow up the prompt. Returns '' when there are no paths.
287
+ */
288
+ export declare function conflictDiff(dir: string, paths: string[], signal?: AbortSignal, maxChars?: number): Promise<string>;
289
+ /**
290
+ * Merge `origin/<baseBranch>` into the current branch (no fast-forward squash, no
291
+ * editor). Returns `true` for a clean merge (or an already-up-to-date no-op) and
292
+ * `false` when the merge left conflicts in the working tree — the expected case the
293
+ * conflict-resolver agent then fixes, NOT an error. Any other git failure (e.g. an
294
+ * unknown ref) is re-thrown. Requires a {@link cloneRepo} with `full: true` so the
295
+ * merge base and `origin/<baseBranch>` are present.
296
+ */
297
+ export declare function mergeBranch(dir: string, baseBranch: string, signal?: AbortSignal): Promise<boolean>;
298
+ /**
299
+ * Bring a RESUMED work branch up to the latest `baseBranch` when (and only when) the
300
+ * two merge cleanly. A resumed branch was cut from an older base, so without this the
301
+ * agent continues against a stale base and the eventual PR can carry avoidable
302
+ * conflicts. Fetches the base (the single-branch resume clone doesn't have it),
303
+ * attempts `git merge --no-edit`, and on a conflict ABORTS — leaving the branch
304
+ * exactly as it was so the run proceeds on the stale base (the CI/merge gate handles
305
+ * a genuinely conflicting PR downstream, as before). Returns whether base was merged
306
+ * in. Best-effort: callers treat a thrown/false result as "continue without refresh".
307
+ */
308
+ export declare function refreshFromBaseIfClean(dir: string, baseBranch: string, ghToken: string, signal?: AbortSignal): Promise<boolean>;
309
+ /**
310
+ * The directory the reference-branches prompt section suggests for a `git worktree add` checkout of
311
+ * a reference branch alongside the agent's own work. Excluded from the checkout (below) so the
312
+ * embedded worktree can never be staged into the agent's PR — mirrors the `.cat-context/` treatment
313
+ * in {@link file://./pi.ts}. Kept in step with the same literal in the backend's
314
+ * `renderReferenceBranchesSection` (a separate package, so a shared constant isn't feasible).
315
+ */
316
+ export declare const REFERENCE_WORKTREE_DIR = ".cat-reference";
317
+ /**
318
+ * Fetch pre-existing REFERENCE branches into their `origin/<b>` tracking refs, so the agent can
319
+ * inspect them read-only (`git log origin/<b>`, two-dot `git diff origin/<b>`,
320
+ * `git show origin/<b>:<path>`) without any git network credentials of its own. The primary
321
+ * checkout is a shallow single-branch clone, so these refs aren't present until fetched — and the
322
+ * harness (which holds the per-job token) is the only place that can reach the remote. Uses an
323
+ * explicit destination refspec (`+refs/heads/<b>:refs/remotes/origin/<b>`) and `--no-tags` so a
324
+ * reference branch's tags don't pollute the checkout. Best-effort PER branch: a fetch failure (a
325
+ * branch deleted since dispatch, a transient network error) is reported via `onSkip` and skipped,
326
+ * never fatal — a reference branch is context, not the run's starting point (contrast the WORKING
327
+ * branch, whose absence fails the dispatch loudly). Returns the branch names that fetched cleanly.
328
+ *
329
+ * On any successful fetch it locally excludes {@link REFERENCE_WORKTREE_DIR} from this checkout, so
330
+ * if the agent follows the prompt's suggested `git worktree add .cat-reference/<b>` a broad
331
+ * `git add -A` can never embed that worktree as a stray gitlink in the run's PR.
332
+ */
333
+ export declare function fetchReferenceBranches(opts: {
334
+ dir: string;
335
+ branches: string[];
336
+ ghToken: string;
337
+ signal?: AbortSignal;
338
+ /** Called once per branch that failed to fetch, so the caller (which owns a logger) can warn. */
339
+ onSkip?: (branch: string, reason: string) => void;
340
+ }): Promise<string[]>;
341
+ /** The local tracking ref a fetched PR/MR head lands on, so the reviewer reads `origin/pr-head`. */
342
+ export declare const PR_HEAD_REF = "refs/remotes/origin/pr-head";
343
+ /**
344
+ * The `git fetch` refspec that maps a PR/MR's server-side HEAD ref onto {@link PR_HEAD_REF}. A
345
+ * PR head is a synthetic ref the host maintains, NOT part of a normal clone: GitHub exposes it at
346
+ * `refs/pull/<n>/head`, GitLab at `refs/merge-requests/<n>/head`. Pure so the provider branch is
347
+ * unit-tested without a network. The leading `+` forces the update (the ref is read-only here).
348
+ */
349
+ export declare function pullHeadRefspec(number: number, provider: 'github' | 'gitlab'): string;
350
+ /**
351
+ * Fetch the reviewed PR/MR's HEAD into {@link PR_HEAD_REF} so a read-only reviewer can inspect the
352
+ * PROPOSED code — files the PR adds (absent from the base checkout) and the head version of every
353
+ * modified file — with `git diff origin/<base>...origin/pr-head`, `git show origin/pr-head:<path>`.
354
+ * The base clone never includes the pull ref, and the container agent holds no git credential of
355
+ * its own (the token lives with the harness), so the agent's own `git fetch pull/<n>/head` fails
356
+ * on a private repo — this harness-side fetch (which carries the token out of band via GIT_ASKPASS,
357
+ * exactly like {@link fetchReferenceBranches}) is what actually makes the head reachable.
358
+ *
359
+ * Best-effort: a fetch failure (a closed/deleted PR, a host without the pull ref, a transient
360
+ * network error) is reported via `onSkip` and swallowed — the review then proceeds on the base
361
+ * checkout + the injected diff, never fails. Returns whether the head was fetched.
362
+ */
363
+ export declare function fetchPullRequestHead(opts: {
364
+ dir: string;
365
+ number: number;
366
+ provider: 'github' | 'gitlab';
367
+ ghToken: string;
368
+ signal?: AbortSignal;
369
+ /** Called when the fetch failed, so the caller (which owns a logger) can warn. */
370
+ onSkip?: (reason: string) => void;
371
+ }): Promise<boolean>;
372
+ /**
373
+ * Push the work branch to origin. The remote URL carries only the username, so
374
+ * the token is supplied here via the askpass env (never in argv).
375
+ */
376
+ export declare function pushBranch(dir: string, branch: string, ghToken: string, signal?: AbortSignal): Promise<void>;
377
+ /**
378
+ * Reset the working tree's git history to a single bootstrap commit and push it
379
+ * to the target repository's default branch. Wiping `.git` before re-initialising
380
+ * means the new repo starts clean — it inherits the bootstrapped *contents* of the
381
+ * reference architecture, not its commit history.
382
+ *
383
+ * The push is forced: the fresh single-commit history shares no ancestor with
384
+ * whatever GitHub prepopulated when the user created the repo (a README,
385
+ * .gitignore and/or license picked on the new-repo page), so a fast-forward is
386
+ * impossible. The Worker pre-flights that the target is empty or holds only that
387
+ * boilerplate, so overwriting it is safe and intended.
388
+ */
389
+ export declare function reinitAndPush(opts: {
390
+ dir: string;
391
+ target: BootstrapTargetSpec;
392
+ ghToken: string;
393
+ message: string;
394
+ }): Promise<void>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Walk `lines`, tracking fenced-code state, and hand each line to `visit` together with
3
+ * whether it sits INSIDE a fenced block. Returns the fence still open at the end, if any.
4
+ *
5
+ * One shared walker so the three things that care about fences — leaving code untouched,
6
+ * closing what the text left open, and finding the briefing's title heading — can never
7
+ * disagree about where a block starts and ends.
8
+ */
9
+ export declare function walkFences(lines: readonly string[], visit: (line: string, insideFence: boolean) => void): {
10
+ char: string;
11
+ length: number;
12
+ } | null;
13
+ /**
14
+ * Render untrusted multi-line markdown safe to send to a host: auto-link triggers defused
15
+ * outside fenced code, and any fence the text leaves open closed again.
16
+ *
17
+ * Unlike kernel's `hostMarkdown.prose` this does NOT cap the length — the caller
18
+ * ({@link import('./pr-description.js')}) applies its own budget with its own visible note
19
+ * BEFORE calling here, so an escape entity can never be sliced in half. With that one
20
+ * difference the output is identical, which the conformity test pins.
21
+ */
22
+ export declare function inertMarkdown(text: string): string;
23
+ /**
24
+ * Render untrusted text INLINE (a pull-request title): newlines folded to spaces because the
25
+ * surrounding line has its own meaning, and auto-link triggers defused. The caller caps the
26
+ * length first, for the same reason as {@link inertMarkdown}.
27
+ */
28
+ export declare function inertInline(text: string): string;
@@ -0,0 +1,10 @@
1
+ import type { InlineJob, InlineResult } from './job.js';
2
+ import type { RunOptions } from './runner.js';
3
+ /**
4
+ * Run one inline completion in a throwaway temp cwd and return the reply text + lifted
5
+ * usage/telemetry. The CLI clones/pushes nothing — the empty cwd only gives it a working
6
+ * directory. The job's watchdog (inactivity + max-duration, see {@link JobRegistry}) bounds
7
+ * it through `opts.signal`; `opts.onActivity` keeps the inactivity timer alive while the CLI
8
+ * streams. The temp cwd is always removed.
9
+ */
10
+ export declare function handleInline(job: InlineJob, opts: RunOptions): Promise<InlineResult>;