@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.
- package/README.md +1 -0
- package/dist/agent-capabilities.d.ts +130 -0
- package/dist/agent-runner.d.ts +114 -0
- package/dist/agent-runner.js +15 -1
- package/dist/agent-shared.d.ts +18 -0
- package/dist/agent.d.ts +66 -0
- package/dist/bootstrap-mode.d.ts +20 -0
- package/dist/captured-command.d.ts +58 -0
- package/dist/claude-call-aggregator.d.ts +164 -0
- package/dist/claude-call-aggregator.js +123 -17
- package/dist/claude-stream.d.ts +56 -0
- package/dist/claude-stream.js +23 -0
- package/dist/coding-agent.d.ts +263 -0
- package/dist/dependency-install.d.ts +111 -0
- package/dist/effort.d.ts +19 -0
- package/dist/embed.d.ts +4 -0
- package/dist/failure.d.ts +42 -0
- package/dist/follow-ups.d.ts +28 -0
- package/dist/frontend-infra.d.ts +25 -0
- package/dist/fs-utils.d.ts +2 -0
- package/dist/git.d.ts +394 -0
- package/dist/host-markdown.d.ts +28 -0
- package/dist/inline.d.ts +10 -0
- package/dist/job.d.ts +666 -0
- package/dist/logger.d.ts +16 -0
- package/dist/onboarding-preseed.d.ts +24 -0
- package/dist/package-registries.d.ts +32 -0
- package/dist/pi-workspace.d.ts +194 -0
- package/dist/pi-workspace.js +4 -0
- package/dist/pi.d.ts +475 -0
- package/dist/pr-description.d.ts +85 -0
- package/dist/pr-template.d.ts +101 -0
- package/dist/process-exit.d.ts +7 -0
- package/dist/process.d.ts +19 -0
- package/dist/progress-guard.d.ts +88 -0
- package/dist/progress.d.ts +87 -0
- package/dist/redact.d.ts +31 -0
- package/dist/reproduction-proof.d.ts +224 -0
- package/dist/runner.d.ts +282 -0
- package/dist/runner.js +3 -0
- package/dist/server.d.ts +3 -0
- package/dist/structured-output.d.ts +75 -0
- package/dist/subagents.d.ts +88 -0
- package/dist/subagents.js +74 -4
- package/dist/transcript-retention.d.ts +21 -0
- package/dist/validation-checks.d.ts +159 -0
- package/dist/vcs-api.d.ts +73 -0
- package/dist/version.d.ts +2 -0
- package/package.json +9 -5
- package/src/agent-runner.ts +21 -2
- package/src/claude-call-aggregator.ts +181 -32
- package/src/claude-stream.ts +21 -0
- package/src/pi-workspace.ts +4 -0
- package/src/runner.ts +24 -0
- package/src/subagents.ts +57 -3
package/dist/effort.d.ts
ADDED
|
@@ -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>;
|
package/dist/embed.d.ts
ADDED
|
@@ -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>;
|
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;
|
package/dist/inline.d.ts
ADDED
|
@@ -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>;
|