@wardby/cli 0.4.0 → 0.5.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/.env.example +8 -1
- package/README.md +3 -1
- package/deploy/local/docker-compose.quickstart-coding.yml +27 -0
- package/dist/claude-coding-worker/driver.d.ts +2 -0
- package/dist/claude-coding-worker/driver.js +19 -3
- package/dist/claude-coding-worker/sdk.js +1 -1
- package/dist/cli.js +41 -22
- package/dist/coding/continuation-wording.d.ts +10 -0
- package/dist/coding/continuation-wording.js +13 -0
- package/dist/coding/docker-preflight.d.ts +12 -0
- package/dist/coding/docker-preflight.js +40 -0
- package/dist/coding/local-git.d.ts +22 -0
- package/dist/coding/local-git.js +102 -0
- package/dist/coding/local-repo-status.d.ts +7 -0
- package/dist/coding/local-repo-status.js +56 -0
- package/dist/coding/local-repo.d.ts +17 -0
- package/dist/coding/local-repo.js +65 -0
- package/dist/coding/observability.d.ts +1 -1
- package/dist/coding/observability.js +1 -0
- package/dist/coding/profile.d.ts +6 -0
- package/dist/coding/profile.js +13 -4
- package/dist/coding/protocol.d.ts +32 -7
- package/dist/coding/protocol.js +66 -4
- package/dist/coding-worker/artifact.d.ts +1 -0
- package/dist/coding-worker/errors.js +2 -0
- package/dist/config/providers.d.ts +13 -0
- package/dist/config/providers.js +21 -9
- package/dist/core/attribution.d.ts +13 -0
- package/dist/core/attribution.js +30 -0
- package/dist/core/budget-groups.d.ts +3 -1
- package/dist/core/budget-groups.js +53 -5
- package/dist/core/ci-context.d.ts +8 -0
- package/dist/core/ci-context.js +59 -0
- package/dist/core/delegation-siblings.d.ts +22 -0
- package/dist/core/delegation-siblings.js +40 -0
- package/dist/core/dispatch.d.ts +28 -4
- package/dist/core/dispatch.js +57 -15
- package/dist/core/engine-native.d.ts +16 -0
- package/dist/core/engine-native.js +49 -2
- package/dist/core/host-events.d.ts +25 -2
- package/dist/core/host-events.js +361 -19
- package/dist/core/host-status.d.ts +8 -3
- package/dist/core/host-status.js +38 -7
- package/dist/core/issue-events.d.ts +2 -6
- package/dist/core/issue-events.js +13 -19
- package/dist/core/pull-request-state-sync.d.ts +17 -0
- package/dist/core/pull-request-state-sync.js +53 -0
- package/dist/core/reconciler.d.ts +10 -1
- package/dist/core/reconciler.js +15 -2
- package/dist/core/related-pull-requests.d.ts +95 -0
- package/dist/core/related-pull-requests.js +338 -0
- package/dist/core/repo-access.d.ts +5 -1
- package/dist/core/repo-access.js +17 -1
- package/dist/core/review-fix-ledger.d.ts +20 -0
- package/dist/core/review-fix-ledger.js +41 -0
- package/dist/core/review-fix.d.ts +60 -0
- package/dist/core/review-fix.js +155 -0
- package/dist/core/review-host-tools.d.ts +13 -2
- package/dist/core/review-host-tools.js +72 -11
- package/dist/core/runner.d.ts +2 -2
- package/dist/core/runner.js +379 -184
- package/dist/core/serial-gate.d.ts +11 -0
- package/dist/core/serial-gate.js +19 -0
- package/dist/core/timing.d.ts +7 -0
- package/dist/core/timing.js +7 -0
- package/dist/env.js +6 -1
- package/dist/generated/prisma/browser.d.ts +23 -0
- package/dist/generated/prisma/client.d.ts +23 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +22 -0
- package/dist/generated/prisma/internal/class.d.ts +33 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +274 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +47 -2
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +48 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +47 -2
- package/dist/generated/prisma/models/Agent.d.ts +422 -1
- package/dist/generated/prisma/models/AgentRepository.d.ts +120 -2
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +42 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
- package/dist/generated/prisma/models/DeferredReview.d.ts +1336 -0
- package/dist/generated/prisma/models/DeferredReview.js +1 -0
- package/dist/generated/prisma/models/LocalPullRequest.d.ts +1384 -0
- package/dist/generated/prisma/models/LocalPullRequest.js +1 -0
- package/dist/generated/prisma/models/LocalReview.d.ts +1315 -0
- package/dist/generated/prisma/models/LocalReview.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +223 -0
- package/dist/generated/prisma/models/RunHostCheck.d.ts +148 -1
- package/dist/generated/prisma/models.d.ts +3 -0
- package/dist/help-index.json +476 -37
- package/dist/import/neutral-schema.d.ts +2 -2
- package/dist/mcp/auth/access.d.ts +3 -1
- package/dist/mcp/auth/host-account-cli.js +2 -2
- package/dist/mcp/auth/repo-authorization.d.ts +6 -7
- package/dist/mcp/auth/repo-authorization.js +21 -0
- package/dist/mcp/index.js +4 -1
- package/dist/mcp/tools/agents.js +46 -4
- package/dist/mcp/tools/host-accounts.js +2 -2
- package/dist/mcp/tools/repositories.js +71 -12
- package/dist/mcp/tools/runs.js +23 -0
- package/dist/mcp/tools/trigger.js +153 -24
- package/dist/providers/engine/types.d.ts +11 -0
- package/dist/providers/executor/composition.js +12 -7
- package/dist/providers/executor/container.d.ts +40 -3
- package/dist/providers/executor/container.js +140 -9
- package/dist/providers/executor/types.d.ts +1 -1
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +3 -2
- package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +3 -1
- package/dist/providers/jobs/kubernetes-client.d.ts +2 -1
- package/dist/providers/jobs/kubernetes-client.js +3 -0
- package/dist/providers/jobs/kubernetes-isolation.d.ts +7 -0
- package/dist/providers/jobs/kubernetes-isolation.js +8 -3
- package/dist/providers/jobs/kubernetes-preflight.d.ts +8 -0
- package/dist/providers/jobs/kubernetes-preflight.js +7 -0
- package/dist/providers/jobs/kubernetes-quota.d.ts +31 -0
- package/dist/providers/jobs/kubernetes-quota.js +110 -0
- package/dist/providers/jobs/kubernetes.d.ts +9 -0
- package/dist/providers/jobs/kubernetes.js +50 -0
- package/dist/providers/jobs/types.d.ts +7 -0
- package/dist/providers/review-host/ci.d.ts +7 -0
- package/dist/providers/review-host/ci.js +20 -0
- package/dist/providers/review-host/github-events.d.ts +7 -0
- package/dist/providers/review-host/github-events.js +31 -7
- package/dist/providers/review-host/github.d.ts +19 -2
- package/dist/providers/review-host/github.js +145 -2
- package/dist/providers/review-host/index.d.ts +8 -2
- package/dist/providers/review-host/index.js +14 -3
- package/dist/providers/review-host/local.d.ts +72 -0
- package/dist/providers/review-host/local.js +434 -0
- package/dist/providers/review-host/types.d.ts +65 -3
- package/dist/providers/review-host/types.js +5 -3
- package/dist/providers/vcs/git.d.ts +10 -20
- package/dist/providers/vcs/git.js +71 -127
- package/dist/providers/vcs/github-remote.d.ts +67 -0
- package/dist/providers/vcs/github-remote.js +201 -0
- package/dist/providers/vcs/github.d.ts +81 -0
- package/dist/providers/vcs/github.js +203 -6
- package/dist/providers/vcs/index.d.ts +16 -1
- package/dist/providers/vcs/index.js +43 -15
- package/dist/providers/vcs/local-remote.d.ts +44 -0
- package/dist/providers/vcs/local-remote.js +108 -0
- package/dist/providers/vcs/remote.d.ts +58 -0
- package/dist/providers/vcs/remote.js +1 -0
- package/dist/providers/vcs/routing.d.ts +33 -0
- package/dist/providers/vcs/routing.js +52 -0
- package/dist/providers/vcs/types.d.ts +12 -1
- package/dist/quickstart/coding-db.d.ts +11 -0
- package/dist/quickstart/coding-db.js +75 -0
- package/dist/quickstart/coding-doctor.d.ts +13 -0
- package/dist/quickstart/coding-doctor.js +88 -0
- package/dist/quickstart/coding-images.d.ts +15 -0
- package/dist/quickstart/coding-images.js +79 -0
- package/dist/quickstart/coding-seed.d.ts +63 -0
- package/dist/quickstart/coding-seed.js +85 -0
- package/dist/quickstart/coding.d.ts +66 -0
- package/dist/quickstart/coding.js +430 -0
- package/dist/quickstart/images.d.ts +19 -0
- package/dist/quickstart/images.js +63 -0
- package/dist/quickstart/index.d.ts +6 -0
- package/dist/quickstart/index.js +72 -40
- package/dist/quickstart/starter-services.d.ts +45 -0
- package/dist/quickstart/starter-services.js +186 -0
- package/dist/quickstart-images.json +1 -0
- package/dist/serve.js +14 -1
- package/dist/viewer/api-schema.d.ts +176 -0
- package/dist/viewer/api-schema.js +27 -2
- package/dist/viewer/graph.d.ts +7 -2
- package/dist/viewer/graph.js +25 -11
- package/dist/viewer/http.d.ts +7 -1
- package/dist/viewer/http.js +8 -3
- package/dist/viewer/infra.d.ts +2 -0
- package/dist/viewer/infra.js +23 -0
- package/dist/viewer/run-detail.d.ts +2 -1
- package/dist/viewer/run-detail.js +2 -2
- package/docs/agent-recipes.md +123 -2
- package/docs/code-review-agents.md +293 -38
- package/docs/coding-agent-setup.md +141 -2
- package/docs/coding-packages.md +21 -0
- package/docs/coding-services.md +3 -0
- package/docs/coding-worker-isolation.md +87 -24
- package/docs/getting-started-gke.md +24 -0
- package/docs/getting-started.md +82 -8
- package/docs/jira-agents.md +26 -5
- package/docs/security-deployment.md +9 -0
- package/docs/viewer-api.md +21 -2
- package/help/admin-viewer.md +11 -1
- package/help/agent-recipes.md +25 -1
- package/help/builder-agent.md +4 -0
- package/help/code-review-agents.md +82 -2
- package/help/coding-packages.md +12 -1
- package/help/coding-services.md +4 -0
- package/help/cost-attribution.md +3 -1
- package/help/creating-agents.md +3 -0
- package/help/deploy-gke.md +2 -1
- package/help/errors/coding-provider-not-configured.md +60 -0
- package/help/errors/coding-turn-limit.md +25 -0
- package/help/errors/continuation-closed.md +75 -0
- package/help/errors/local-branch-conflict.md +37 -0
- package/help/errors/local-path-invalid.md +30 -0
- package/help/errors/local-ref-invalid.md +36 -0
- package/help/errors/local-ref-not-found.md +41 -0
- package/help/errors/local-repo-not-allowed.md +46 -0
- package/help/errors/local-repo-not-found.md +39 -0
- package/help/errors/model-unavailable.md +7 -6
- package/help/errors/vcs-github-not-configured.md +34 -0
- package/help/getting-started.md +4 -0
- package/help/github.md +22 -5
- package/help/jira.md +9 -2
- package/help/local-repositories.md +182 -0
- package/help/related-pull-requests.md +89 -0
- package/help/review-fix-rounds.md +69 -0
- package/help/troubleshooting/budgets.md +11 -0
- package/help/troubleshooting/coding-workers.md +22 -0
- package/help/troubleshooting/repository-access.md +5 -0
- package/package.json +5 -3
- package/prisma/migrations/20261004100000_delegations_and_coding_turns/migration.sql +12 -0
- package/prisma/migrations/20261005000000_review_fix_rounds/migration.sql +5 -0
- package/prisma/migrations/20261006000000_parallel_delegations/migration.sql +6 -0
- package/prisma/migrations/20261007000000_ci_rereview/migration.sql +4 -0
- package/prisma/migrations/20261008000000_review_after_ci/migration.sql +33 -0
- package/prisma/migrations/20261009000001_coding_run_local_branch/migration.sql +7 -0
- package/prisma/migrations/20261009000002_local_review/migration.sql +50 -0
- package/prisma/schema.prisma +98 -1
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { LocalRepoError, loadLocalRepoRoots, normalizeLocalRepository, resolveLocalRepository, } from "../../coding/local-repo.js";
|
|
2
|
+
import { SHA, isSafeRefName, isSafeRepoPath, localGit as git } from "../../coding/local-git.js";
|
|
3
|
+
import { skippedInListing } from "./github.js";
|
|
4
|
+
import { verdictConclusion } from "./review-format.js";
|
|
5
|
+
import { ReviewHostError, } from "./types.js";
|
|
6
|
+
/** GitHub lists at most 3 pages of 100 files; match it. */
|
|
7
|
+
const MAX_PR_FILES = 300;
|
|
8
|
+
const MAX_LISTED_FILES = 1000;
|
|
9
|
+
const MAX_BODY_CHARS = 8000;
|
|
10
|
+
const MAX_LOGGED_COMMITS = 100;
|
|
11
|
+
const MAX_PG_INT = 2_147_483_647;
|
|
12
|
+
const STATUS = {
|
|
13
|
+
A: "added",
|
|
14
|
+
M: "modified",
|
|
15
|
+
D: "removed",
|
|
16
|
+
R: "renamed",
|
|
17
|
+
C: "copied",
|
|
18
|
+
T: "changed",
|
|
19
|
+
};
|
|
20
|
+
function fail(message) {
|
|
21
|
+
throw new ReviewHostError("host_api_error", message);
|
|
22
|
+
}
|
|
23
|
+
function assertRepoPath(path) {
|
|
24
|
+
if (!isSafeRepoPath(path))
|
|
25
|
+
fail("path_invalid");
|
|
26
|
+
}
|
|
27
|
+
function prUrl(row) {
|
|
28
|
+
return row.runId ? `wardby://runs/${row.runId}/review` : `wardby://local-pulls/${row.number}`;
|
|
29
|
+
}
|
|
30
|
+
/** Hunks only, as GitHub's `patch` field: the `diff --git`/index/---/+++ header is dropped, no trailing newline. */
|
|
31
|
+
function hunks(diff) {
|
|
32
|
+
const start = diff.startsWith("@@") ? 0 : diff.indexOf("\n@@") + 1;
|
|
33
|
+
// indexOf -1 + 1 = 0 without a leading "@@": no hunks (binary, mode or pure rename).
|
|
34
|
+
if (start === 0 && !diff.startsWith("@@"))
|
|
35
|
+
return "";
|
|
36
|
+
return diff.slice(start).replace(/\n$/, "");
|
|
37
|
+
}
|
|
38
|
+
function tooLarge(err) {
|
|
39
|
+
return err?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
40
|
+
}
|
|
41
|
+
export class LocalReviewHost {
|
|
42
|
+
provider = "local";
|
|
43
|
+
db;
|
|
44
|
+
roots;
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.db = options.db;
|
|
47
|
+
this.roots = options.roots ?? (() => loadLocalRepoRoots(process.env).roots);
|
|
48
|
+
}
|
|
49
|
+
/** The canonical repository and its realpath, checked against the current roots. */
|
|
50
|
+
async repo(repository) {
|
|
51
|
+
try {
|
|
52
|
+
return await resolveLocalRepository(repository, this.roots());
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
fail(err instanceof LocalRepoError ? err.code : "local_repo_invalid");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async pullRequest(canonical, requested, number) {
|
|
59
|
+
const row = Number.isSafeInteger(number) && number > 0 && number <= MAX_PG_INT
|
|
60
|
+
? await this.db.localPullRequest.findUnique({ where: { number } })
|
|
61
|
+
: null;
|
|
62
|
+
if (!row || (row.repository !== canonical && row.repository !== normalizeLocalRepository(requested))) {
|
|
63
|
+
fail("no such local pull request");
|
|
64
|
+
}
|
|
65
|
+
return row;
|
|
66
|
+
}
|
|
67
|
+
/** Rejects anything but a plain branch/tag name before it can reach git as a revision or an option. */
|
|
68
|
+
async assertRefName(dir, name) {
|
|
69
|
+
if (!(await isSafeRefName(dir, name)))
|
|
70
|
+
fail("ref_invalid");
|
|
71
|
+
}
|
|
72
|
+
/** The commit a sha or branch name points at. */
|
|
73
|
+
async commitOf(dir, ref) {
|
|
74
|
+
if (!SHA.test(ref))
|
|
75
|
+
await this.assertRefName(dir, ref);
|
|
76
|
+
let out;
|
|
77
|
+
try {
|
|
78
|
+
out = await git(dir, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${ref}^{commit}`], 64 * 1024);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
fail("local_ref_not_found");
|
|
82
|
+
}
|
|
83
|
+
const sha = out.trim();
|
|
84
|
+
if (!SHA.test(sha))
|
|
85
|
+
fail("local_ref_not_found");
|
|
86
|
+
return sha;
|
|
87
|
+
}
|
|
88
|
+
/** The repository's checked-out HEAD commit (its "default branch"), and a label for it. */
|
|
89
|
+
async defaultRef(dir) {
|
|
90
|
+
let label = "HEAD";
|
|
91
|
+
try {
|
|
92
|
+
label = (await git(dir, ["symbolic-ref", "--short", "-q", "HEAD"], 64 * 1024)).trim() || "HEAD";
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// detached HEAD
|
|
96
|
+
}
|
|
97
|
+
let sha;
|
|
98
|
+
try {
|
|
99
|
+
sha = (await git(dir, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], 64 * 1024)).trim();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
fail("local_ref_not_found");
|
|
103
|
+
}
|
|
104
|
+
if (!SHA.test(sha))
|
|
105
|
+
fail("local_ref_not_found");
|
|
106
|
+
return { sha, label };
|
|
107
|
+
}
|
|
108
|
+
async resolveRef(dir, ref) {
|
|
109
|
+
return ref === undefined ? this.defaultRef(dir) : { sha: await this.commitOf(dir, ref), label: ref };
|
|
110
|
+
}
|
|
111
|
+
async isAncestor(dir, ancestor, descendant) {
|
|
112
|
+
try {
|
|
113
|
+
await git(dir, ["merge-base", "--is-ancestor", "--end-of-options", ancestor, descendant], 64 * 1024);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Files changed between two commits (both 40-hex), with rename detection, in git's order. */
|
|
121
|
+
async changedFiles(dir, from, to) {
|
|
122
|
+
const diff = ["diff", "--no-ext-diff", "--no-textconv", "--no-color", "-M", "-z"];
|
|
123
|
+
let nameStatus;
|
|
124
|
+
let numstat;
|
|
125
|
+
try {
|
|
126
|
+
nameStatus = await git(dir, [...diff, "--name-status", "--end-of-options", from, to]);
|
|
127
|
+
numstat = await git(dir, [...diff, "--numstat", "--end-of-options", from, to]);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
fail("local_git_failed");
|
|
131
|
+
}
|
|
132
|
+
const counts = new Map();
|
|
133
|
+
const n = numstat.split("\0");
|
|
134
|
+
for (let i = 0; i < n.length && n[i] !== "";) {
|
|
135
|
+
const [add, del, ...rest] = n[i].split("\t");
|
|
136
|
+
let path = rest.join("\t");
|
|
137
|
+
if (path === "") {
|
|
138
|
+
path = n[i + 2] ?? "";
|
|
139
|
+
i += 3;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
i += 1;
|
|
143
|
+
}
|
|
144
|
+
// Binary files show "-": GitHub reports 0 for them.
|
|
145
|
+
counts.set(path, { additions: Number(add) || 0, deletions: Number(del) || 0 });
|
|
146
|
+
}
|
|
147
|
+
const files = [];
|
|
148
|
+
const s = nameStatus.split("\0");
|
|
149
|
+
for (let i = 0; i < s.length && s[i] !== "";) {
|
|
150
|
+
const code = s[i].charAt(0);
|
|
151
|
+
let filename;
|
|
152
|
+
let previousFilename;
|
|
153
|
+
if (code === "R" || code === "C") {
|
|
154
|
+
previousFilename = s[i + 1];
|
|
155
|
+
filename = s[i + 2] ?? "";
|
|
156
|
+
i += 3;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
filename = s[i + 1] ?? "";
|
|
160
|
+
i += 2;
|
|
161
|
+
}
|
|
162
|
+
files.push({
|
|
163
|
+
filename,
|
|
164
|
+
...(previousFilename !== undefined ? { previousFilename } : {}),
|
|
165
|
+
status: STATUS[code] ?? "changed",
|
|
166
|
+
...(counts.get(filename) ?? { additions: 0, deletions: 0 }),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return files.slice(0, MAX_PR_FILES);
|
|
170
|
+
}
|
|
171
|
+
/** Attaches patches, sharing one character budget across files and flagging truncation like GitHub. */
|
|
172
|
+
async withPatches(dir, from, to, files, maxPatchChars) {
|
|
173
|
+
let budget = maxPatchChars;
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
const changes = file.additions + file.deletions;
|
|
177
|
+
if (budget <= 0) {
|
|
178
|
+
out.push({ ...file, patch: "", patchTruncated: changes > 0 });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
let patch;
|
|
182
|
+
let complete = true;
|
|
183
|
+
try {
|
|
184
|
+
const paths = file.previousFilename ? [file.previousFilename, file.filename] : [file.filename];
|
|
185
|
+
patch = hunks(await git(dir, [
|
|
186
|
+
"diff",
|
|
187
|
+
"--no-ext-diff",
|
|
188
|
+
"--no-textconv",
|
|
189
|
+
"--no-color",
|
|
190
|
+
"-M",
|
|
191
|
+
"--end-of-options",
|
|
192
|
+
from,
|
|
193
|
+
to,
|
|
194
|
+
"--",
|
|
195
|
+
...paths,
|
|
196
|
+
]));
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Too large for the buffer (or unreadable): no patch, flagged.
|
|
200
|
+
patch = "";
|
|
201
|
+
complete = changes === 0;
|
|
202
|
+
}
|
|
203
|
+
const kept = patch.length <= budget ? patch : patch.slice(0, Math.max(0, budget));
|
|
204
|
+
budget -= kept.length;
|
|
205
|
+
out.push({ ...file, patch: kept, patchTruncated: kept.length < patch.length || !complete });
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
async repositoryPermission(_repository, _user) {
|
|
210
|
+
return { level: "admin", login: "local" };
|
|
211
|
+
}
|
|
212
|
+
async readPullRequest(repository, prNumber, opts) {
|
|
213
|
+
const { repository: canonical, path: dir } = await this.repo(repository);
|
|
214
|
+
const row = await this.pullRequest(canonical, repository, prNumber);
|
|
215
|
+
const headSha = await this.commitOf(dir, row.branch);
|
|
216
|
+
const baseTip = await this.commitOf(dir, row.base);
|
|
217
|
+
let baseSha;
|
|
218
|
+
try {
|
|
219
|
+
baseSha = (await git(dir, ["merge-base", "--end-of-options", baseTip, headSha], 64 * 1024)).trim();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
fail("no common ancestor between the branch and its base");
|
|
223
|
+
}
|
|
224
|
+
if (!SHA.test(baseSha))
|
|
225
|
+
fail("no common ancestor between the branch and its base");
|
|
226
|
+
let from = baseSha;
|
|
227
|
+
let files = null;
|
|
228
|
+
let comparedFrom = null;
|
|
229
|
+
let baseMergedSince = false;
|
|
230
|
+
const since = opts.sinceSha;
|
|
231
|
+
if (since && SHA.test(since) && since !== headSha && (await this.isAncestor(dir, since, headSha))) {
|
|
232
|
+
comparedFrom = since;
|
|
233
|
+
files = await this.changedFiles(dir, since, headSha);
|
|
234
|
+
from = since;
|
|
235
|
+
let merges = "0";
|
|
236
|
+
try {
|
|
237
|
+
merges = await git(dir, ["rev-list", "--count", "--min-parents=2", "--end-of-options", `${since}..${headSha}`]);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
fail("local_git_failed");
|
|
241
|
+
}
|
|
242
|
+
baseMergedSince = Number(merges.trim()) > 0;
|
|
243
|
+
if (baseMergedSince) {
|
|
244
|
+
// As on GitHub: the PR's own diff against its base, limited to files that changed since.
|
|
245
|
+
const changedSince = new Set(files.map((f) => f.filename));
|
|
246
|
+
files = (await this.changedFiles(dir, baseSha, headSha)).filter((f) => changedSince.has(f.filename));
|
|
247
|
+
from = baseSha;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
files ??= await this.changedFiles(dir, baseSha, headSha);
|
|
251
|
+
let log = "";
|
|
252
|
+
try {
|
|
253
|
+
log = await git(dir, [
|
|
254
|
+
"log",
|
|
255
|
+
"--no-color",
|
|
256
|
+
`--max-count=${MAX_LOGGED_COMMITS}`,
|
|
257
|
+
"--format=- %h %s",
|
|
258
|
+
"--end-of-options",
|
|
259
|
+
`${baseSha}..${headSha}`,
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// The commit list is a courtesy; the diff is the review.
|
|
264
|
+
}
|
|
265
|
+
const last = await this.db.localReview.findFirst({
|
|
266
|
+
where: { pullRequestId: row.id, agentId: opts.agentMarker },
|
|
267
|
+
orderBy: { createdAt: "desc" },
|
|
268
|
+
select: { headSha: true },
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
number: row.number,
|
|
272
|
+
title: `${row.branch} into ${row.base}`,
|
|
273
|
+
body: (log.trim() ? `Commits on ${row.branch} since ${row.base}:\n${log.trimEnd()}` : "").slice(0, MAX_BODY_CHARS),
|
|
274
|
+
author: null,
|
|
275
|
+
state: "open",
|
|
276
|
+
merged: false,
|
|
277
|
+
draft: false,
|
|
278
|
+
baseRef: row.base,
|
|
279
|
+
headRef: row.branch,
|
|
280
|
+
headSha,
|
|
281
|
+
isFork: false,
|
|
282
|
+
htmlUrl: prUrl(row),
|
|
283
|
+
lastReviewedSha: last?.headSha ?? null,
|
|
284
|
+
comparedFrom,
|
|
285
|
+
baseMergedSince,
|
|
286
|
+
files: await this.withPatches(dir, from, headSha, files, opts.maxPatchChars),
|
|
287
|
+
openThreads: [],
|
|
288
|
+
ci: { headSha, state: "none", checks: [], truncated: false, statusesUnavailable: false },
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
async pullRequestHead(repository, prNumber) {
|
|
292
|
+
const { repository: canonical, path: dir } = await this.repo(repository);
|
|
293
|
+
const row = await this.pullRequest(canonical, repository, prNumber);
|
|
294
|
+
return { headSha: await this.commitOf(dir, row.branch), isFork: false, state: "open" };
|
|
295
|
+
}
|
|
296
|
+
async readFile(repository, path, ref, window) {
|
|
297
|
+
assertRepoPath(path);
|
|
298
|
+
const { path: dir } = await this.repo(repository);
|
|
299
|
+
const { sha, label } = await this.resolveRef(dir, ref);
|
|
300
|
+
const object = `${sha}:${path}`;
|
|
301
|
+
let type;
|
|
302
|
+
try {
|
|
303
|
+
type = (await git(dir, ["cat-file", "-t", "--end-of-options", object], 64 * 1024)).trim();
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return { kind: "not_found", path };
|
|
307
|
+
}
|
|
308
|
+
if (type === "tree") {
|
|
309
|
+
let listing;
|
|
310
|
+
try {
|
|
311
|
+
listing = await git(dir, ["ls-tree", "-z", "--end-of-options", object]);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
fail("local_git_failed");
|
|
315
|
+
}
|
|
316
|
+
const entries = listing
|
|
317
|
+
.split("\0")
|
|
318
|
+
.filter((entry) => entry !== "")
|
|
319
|
+
.map((entry) => {
|
|
320
|
+
const tab = entry.indexOf("\t");
|
|
321
|
+
const kind = entry.slice(0, tab).split(" ")[1];
|
|
322
|
+
return `${kind === "tree" ? "dir " : "file"} ${path}/${entry.slice(tab + 1)}`;
|
|
323
|
+
});
|
|
324
|
+
return { kind: "directory", path, entries };
|
|
325
|
+
}
|
|
326
|
+
if (type !== "blob")
|
|
327
|
+
return { kind: "not_found", path };
|
|
328
|
+
let text;
|
|
329
|
+
try {
|
|
330
|
+
text = await git(dir, ["cat-file", "blob", "--end-of-options", object]);
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
fail(tooLarge(err) ? "file_too_large" : "local_git_failed");
|
|
334
|
+
}
|
|
335
|
+
const lines = text.split("\n");
|
|
336
|
+
const slice = lines.slice(window.startLine - 1, window.startLine - 1 + window.maxLines);
|
|
337
|
+
return {
|
|
338
|
+
kind: "file",
|
|
339
|
+
path,
|
|
340
|
+
ref: label,
|
|
341
|
+
totalLines: lines.length,
|
|
342
|
+
startLine: window.startLine,
|
|
343
|
+
endLine: window.startLine + slice.length - 1,
|
|
344
|
+
truncated: window.startLine - 1 + slice.length < lines.length,
|
|
345
|
+
content: slice.map((line, i) => `${window.startLine + i}: ${line}`).join("\n"),
|
|
346
|
+
text: slice.join("\n"),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
async listFiles(repository, ref, pathPrefix = "") {
|
|
350
|
+
const { path: dir } = await this.repo(repository);
|
|
351
|
+
const { sha, label } = await this.resolveRef(dir, ref);
|
|
352
|
+
let listing;
|
|
353
|
+
try {
|
|
354
|
+
listing = await git(dir, ["ls-tree", "-r", "-l", "-z", "--full-tree", "--end-of-options", sha]);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
fail("local_git_failed");
|
|
358
|
+
}
|
|
359
|
+
const entries = listing
|
|
360
|
+
.split("\0")
|
|
361
|
+
.filter((entry) => entry !== "")
|
|
362
|
+
.map((entry) => {
|
|
363
|
+
const tab = entry.indexOf("\t");
|
|
364
|
+
const [, kind, , size] = entry.slice(0, tab).split(/ +/);
|
|
365
|
+
return { kind, size: Number(size) || 0, path: entry.slice(tab + 1) };
|
|
366
|
+
})
|
|
367
|
+
.filter((e) => e.kind === "blob" && e.path.startsWith(pathPrefix) && !skippedInListing(e.path));
|
|
368
|
+
const shown = entries.slice(0, MAX_LISTED_FILES);
|
|
369
|
+
return {
|
|
370
|
+
ref: label,
|
|
371
|
+
count: entries.length,
|
|
372
|
+
truncated: entries.length > MAX_LISTED_FILES,
|
|
373
|
+
files: shown.map((e) => `${e.path} (${e.size} bytes)`),
|
|
374
|
+
paths: shown.map((e) => e.path),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
async publishReview(repository, input) {
|
|
378
|
+
const { repository: canonical, path: dir } = await this.repo(repository);
|
|
379
|
+
const row = await this.pullRequest(canonical, repository, input.prNumber);
|
|
380
|
+
const currentHeadSha = await this.commitOf(dir, row.branch);
|
|
381
|
+
if (input.headSha !== currentHeadSha)
|
|
382
|
+
return { published: false, reason: "stale_head", currentHeadSha };
|
|
383
|
+
const review = await this.db.localReview.create({
|
|
384
|
+
data: {
|
|
385
|
+
pullRequestId: row.id,
|
|
386
|
+
agentId: input.agentMarker,
|
|
387
|
+
headSha: currentHeadSha,
|
|
388
|
+
verdict: input.verdict,
|
|
389
|
+
summary: input.summary,
|
|
390
|
+
body: input.body,
|
|
391
|
+
comments: input.comments.map(({ path, line, side, severity, body }) => ({ path, line, side, severity, body })),
|
|
392
|
+
},
|
|
393
|
+
select: { id: true },
|
|
394
|
+
});
|
|
395
|
+
const url = `${prUrl(row)}#${review.id}`;
|
|
396
|
+
return {
|
|
397
|
+
published: true,
|
|
398
|
+
reviewUrl: url,
|
|
399
|
+
summaryCommentUrl: url,
|
|
400
|
+
checkId: input.checkId ?? null,
|
|
401
|
+
checkConclusion: verdictConclusion(input.verdict),
|
|
402
|
+
inlineCount: input.comments.length,
|
|
403
|
+
outsideDiffCount: 0,
|
|
404
|
+
resolvedThreadIds: [],
|
|
405
|
+
// A local pull request has no threads to resolve.
|
|
406
|
+
skippedThreadIds: [...new Set(input.resolveThreadIds ?? [])],
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/** Stored as a COMMENT review on the branch's current head. The host is not told who comments: agentId is "". */
|
|
410
|
+
async comment(repository, input) {
|
|
411
|
+
const { repository: canonical, path: dir } = await this.repo(repository);
|
|
412
|
+
const row = await this.pullRequest(canonical, repository, input.number);
|
|
413
|
+
const headSha = await this.commitOf(dir, row.branch);
|
|
414
|
+
const review = await this.db.localReview.create({
|
|
415
|
+
data: {
|
|
416
|
+
pullRequestId: row.id,
|
|
417
|
+
agentId: "",
|
|
418
|
+
headSha,
|
|
419
|
+
verdict: "COMMENT",
|
|
420
|
+
summary: "",
|
|
421
|
+
body: input.body,
|
|
422
|
+
comments: [],
|
|
423
|
+
},
|
|
424
|
+
select: { id: true },
|
|
425
|
+
});
|
|
426
|
+
return { url: `${prUrl(row)}#${review.id}`, id: review.id };
|
|
427
|
+
}
|
|
428
|
+
async editComment(_repository, _input) { }
|
|
429
|
+
async acknowledge(_repository, _target) { }
|
|
430
|
+
async startCheck(_repository, _input) {
|
|
431
|
+
return { checkId: "local" };
|
|
432
|
+
}
|
|
433
|
+
async completeCheck(_repository, _input) { }
|
|
434
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host-neutral code-review surface for native agents. GitHub
|
|
3
|
-
*
|
|
2
|
+
* Host-neutral code-review surface for native agents. GitHub (./github.ts) and
|
|
3
|
+
* local git repositories (./local.ts) implement it; Bitbucket Cloud is anticipated. Every method
|
|
4
4
|
* takes the repository and mints its own least-permission credential — no
|
|
5
5
|
* method accepts a token or a permission. See
|
|
6
6
|
* docs/private/2026-09-25-code-review-host-design.md §5.
|
|
7
7
|
*/
|
|
8
|
-
export type ReviewHostProvider = "github";
|
|
8
|
+
export type ReviewHostProvider = "github" | "local";
|
|
9
9
|
export declare const REVIEW_HOST_PROVIDERS: readonly ReviewHostProvider[];
|
|
10
|
+
/** Providers with host user accounts to link (link_host_account); a local repository has none. */
|
|
11
|
+
export declare const HOST_ACCOUNT_PROVIDERS: readonly ReviewHostProvider[];
|
|
10
12
|
export type ReviewVerdict = "APPROVE" | "CHANGES_REQUESTED" | "COMMENT";
|
|
11
13
|
export type CheckConclusion = "success" | "failure" | "neutral";
|
|
12
14
|
export type CommentSide = "LEFT" | "RIGHT";
|
|
@@ -30,6 +32,30 @@ export interface ReviewThreadView {
|
|
|
30
32
|
/** The finding as posted, without its marker, capped at 1,000 characters. */
|
|
31
33
|
body: string;
|
|
32
34
|
}
|
|
35
|
+
export type CiState = "passing" | "failing" | "pending" | "inconclusive" | "none" | "unavailable";
|
|
36
|
+
/** One CI result on a commit: a check run or a commit status. */
|
|
37
|
+
export interface CiCheckView {
|
|
38
|
+
/** Check run name or status context (repository-controlled text), capped at 100 characters. */
|
|
39
|
+
name: string;
|
|
40
|
+
kind: "check_run" | "status";
|
|
41
|
+
status: "queued" | "in_progress" | "completed";
|
|
42
|
+
/** Set when completed: success, failure, neutral, cancelled, skipped, timed_out, action_required, stale, startup_failure, error. */
|
|
43
|
+
conclusion: string | null;
|
|
44
|
+
/** The app that reported a check run (e.g. "github-actions"); null for statuses. */
|
|
45
|
+
app: string | null;
|
|
46
|
+
}
|
|
47
|
+
/** CI on the PR's head commit, excluding everything wardby's own App reported. */
|
|
48
|
+
export interface CiView {
|
|
49
|
+
headSha: string;
|
|
50
|
+
state: CiState;
|
|
51
|
+
checks: CiCheckView[];
|
|
52
|
+
/** More results exist than are listed. */
|
|
53
|
+
truncated: boolean;
|
|
54
|
+
/** Commit statuses could not be read (permission not granted); only check runs are listed. */
|
|
55
|
+
statusesUnavailable: boolean;
|
|
56
|
+
/** Set when state is "unavailable": a host error code. */
|
|
57
|
+
unavailableReason?: string;
|
|
58
|
+
}
|
|
33
59
|
export interface PullRequestView {
|
|
34
60
|
number: number;
|
|
35
61
|
title: string;
|
|
@@ -56,12 +82,24 @@ export interface PullRequestView {
|
|
|
56
82
|
files: PullRequestFileView[];
|
|
57
83
|
/** This agent's own unresolved review threads on the PR (the first 100 threads are searched). */
|
|
58
84
|
openThreads: ReviewThreadView[];
|
|
85
|
+
/** CI on headSha (check runs and commit statuses, minus wardby's own); absent on hosts that can't read CI. */
|
|
86
|
+
ci?: CiView;
|
|
59
87
|
}
|
|
60
88
|
export interface PullRequestHead {
|
|
61
89
|
headSha: string;
|
|
62
90
|
isFork: boolean;
|
|
63
91
|
state: string;
|
|
64
92
|
}
|
|
93
|
+
export interface PullRequestOrigin extends PullRequestHead {
|
|
94
|
+
/** The run id in the description's marker; only on a PR the App itself authored. */
|
|
95
|
+
markerRunId?: string;
|
|
96
|
+
/** The PR's label names; empty on a host without labels. */
|
|
97
|
+
labels: string[];
|
|
98
|
+
/** True once merged; absent on hosts that can't say. */
|
|
99
|
+
merged?: boolean;
|
|
100
|
+
/** True while a draft; absent on hosts that can't say. */
|
|
101
|
+
draft?: boolean;
|
|
102
|
+
}
|
|
65
103
|
export type FileReadResult = {
|
|
66
104
|
kind: "file";
|
|
67
105
|
path: string;
|
|
@@ -200,6 +238,23 @@ export interface CodeReviewHost {
|
|
|
200
238
|
agentMarker: string;
|
|
201
239
|
}): Promise<PullRequestView>;
|
|
202
240
|
pullRequestHead(repository: string, prNumber: number): Promise<PullRequestHead>;
|
|
241
|
+
/** CI on one commit, excluding wardby's own checks. Never throws. Hosts that can't say leave it out. */
|
|
242
|
+
readCi?(repository: string, headSha: string): Promise<CiView>;
|
|
243
|
+
/** Head, labels, and (on an App-authored PR) the run marker. Hosts that can't say leave it out. */
|
|
244
|
+
pullRequestOrigin?(repository: string, prNumber: number): Promise<PullRequestOrigin>;
|
|
245
|
+
/** Adds a label to a pull request (created if missing). Absent on hosts without labels. */
|
|
246
|
+
addLabel?(repository: string, prNumber: number, label: string): Promise<void>;
|
|
247
|
+
/**
|
|
248
|
+
* Replaces (or inserts) wardby's marked Related pull requests section in an
|
|
249
|
+
* open pull request's description — only on a PR the App itself authored
|
|
250
|
+
* whose run marker is `expectedMarkerRunId`. "skipped" covers every refusal
|
|
251
|
+
* (not the App's, another run's, closed, malformed markers, too long).
|
|
252
|
+
* Absent on hosts that can't edit descriptions.
|
|
253
|
+
*/
|
|
254
|
+
replaceRelatedSection?(repository: string, prNumber: number, input: {
|
|
255
|
+
expectedMarkerRunId: string;
|
|
256
|
+
block: string;
|
|
257
|
+
}): Promise<"updated" | "unchanged" | "skipped">;
|
|
203
258
|
readFile(repository: string, path: string, ref: string | undefined, window: {
|
|
204
259
|
startLine: number;
|
|
205
260
|
maxLines: number;
|
|
@@ -278,6 +333,13 @@ export type HostEvent = {
|
|
|
278
333
|
repository: string;
|
|
279
334
|
prNumber: number;
|
|
280
335
|
merged: boolean;
|
|
336
|
+
} | {
|
|
337
|
+
/** A CI check suite (not wardby's own) finished on a commit some open pull requests point at. */
|
|
338
|
+
kind: "ci_completed";
|
|
339
|
+
provider: ReviewHostProvider;
|
|
340
|
+
repository: string;
|
|
341
|
+
headSha: string;
|
|
342
|
+
prNumbers: number[];
|
|
281
343
|
} | {
|
|
282
344
|
kind: "check_rerun";
|
|
283
345
|
provider: ReviewHostProvider;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host-neutral code-review surface for native agents. GitHub
|
|
3
|
-
*
|
|
2
|
+
* Host-neutral code-review surface for native agents. GitHub (./github.ts) and
|
|
3
|
+
* local git repositories (./local.ts) implement it; Bitbucket Cloud is anticipated. Every method
|
|
4
4
|
* takes the repository and mints its own least-permission credential — no
|
|
5
5
|
* method accepts a token or a permission. See
|
|
6
6
|
* docs/private/2026-09-25-code-review-host-design.md §5.
|
|
7
7
|
*/
|
|
8
|
-
export const REVIEW_HOST_PROVIDERS = ["github"];
|
|
8
|
+
export const REVIEW_HOST_PROVIDERS = ["github", "local"];
|
|
9
|
+
/** Providers with host user accounts to link (link_host_account); a local repository has none. */
|
|
10
|
+
export const HOST_ACCOUNT_PROVIDERS = ["github"];
|
|
9
11
|
export const HOST_PERMISSION_RANK = {
|
|
10
12
|
none: 0,
|
|
11
13
|
read: 1,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type RepositoryFileInput } from "./github.js";
|
|
2
|
+
import type { GitRemote } from "./remote.js";
|
|
2
3
|
import type { ContinuationFinishedDetails, ContinuationOutcome, FinalizeChangesDetails, FinalizeChangesResult, PreparedWorkspace, VcsPrepareInput, VcsProvider } from "./types.js";
|
|
3
4
|
export declare const DEFAULT_MAX_CHANGED_FILES = 1000;
|
|
4
5
|
export declare const DEFAULT_MAX_DIFF_BYTES: number;
|
|
@@ -36,12 +37,11 @@ export declare class NodeGitCommandRunner implements GitCommandRunner {
|
|
|
36
37
|
}
|
|
37
38
|
export interface GitVcsProviderOptions {
|
|
38
39
|
rootDir: string;
|
|
39
|
-
|
|
40
|
+
/** Where repositories are cloned from and pushed to (GitHubRemote in production). */
|
|
41
|
+
remote: GitRemote;
|
|
40
42
|
git?: GitCommandRunner;
|
|
41
43
|
maxChangedFiles?: number;
|
|
42
44
|
maxDiffBytes?: number;
|
|
43
|
-
/** Test-only transport override; production composition always uses github.com. */
|
|
44
|
-
cloneUrlForRepository?: (repository: string) => string;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Whether a changed path is protected, in this order: the WARDBY_PROTECTED_PATHS
|
|
@@ -55,37 +55,27 @@ export interface GitVcsProviderOptions {
|
|
|
55
55
|
* in-flight run gets it.
|
|
56
56
|
*/
|
|
57
57
|
export declare function protectedPathMatcher(patterns: readonly string[]): (path: string) => boolean;
|
|
58
|
+
export declare function validateChangedPath(path: string): string;
|
|
58
59
|
export declare class GitVcsProvider implements VcsProvider {
|
|
59
|
-
private readonly options;
|
|
60
60
|
private readonly rootDir;
|
|
61
61
|
private readonly git;
|
|
62
62
|
private readonly maxChangedFiles;
|
|
63
63
|
private readonly maxDiffBytes;
|
|
64
|
-
private readonly
|
|
64
|
+
private readonly remote;
|
|
65
65
|
constructor(options: GitVcsProviderOptions);
|
|
66
66
|
prepareWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace>;
|
|
67
67
|
recoverWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace | null>;
|
|
68
68
|
finalizeChanges(workspace: PreparedWorkspace, details?: FinalizeChangesDetails): Promise<FinalizeChangesResult>;
|
|
69
69
|
cleanup(workspace: PreparedWorkspace): Promise<void>;
|
|
70
70
|
/**
|
|
71
|
-
* Best-effort "wardby is working on this PR" signal (see VcsProvider)
|
|
72
|
-
*
|
|
73
|
-
* real coding run
|
|
74
|
-
* ("diagnostics are optional and must never affect terminal cleanup").
|
|
75
|
-
* No-op for a fresh (non-continuation) workspace: there's no PR to
|
|
76
|
-
* attach anything to until its one commit lands.
|
|
71
|
+
* Best-effort "wardby is working on this PR" signal (see VcsProvider),
|
|
72
|
+
* delegated to the remote; a remote without one is a no-op. Wrapped so
|
|
73
|
+
* this can NEVER throw or otherwise affect the real coding run.
|
|
77
74
|
*/
|
|
78
75
|
notifyContinuationStarted(workspace: PreparedWorkspace, details?: {
|
|
79
76
|
agentName?: string;
|
|
80
77
|
}): Promise<void>;
|
|
81
|
-
/**
|
|
82
|
-
* Companion to notifyContinuationStarted -- finds and updates whatever
|
|
83
|
-
* that call created, never creates fresh state itself (see
|
|
84
|
-
* upsertContinuationStatusComment vs updateContinuationStatusComment,
|
|
85
|
-
* and createContinuationCheckRun vs completeContinuationCheckRun in
|
|
86
|
-
* github.ts). Safe to call more than once for the same run. Same
|
|
87
|
-
* never-throw contract as notifyContinuationStarted.
|
|
88
|
-
*/
|
|
78
|
+
/** Companion to notifyContinuationStarted, delegated to the remote under the same never-throw contract. */
|
|
89
79
|
notifyContinuationFinished(workspace: PreparedWorkspace, outcome: ContinuationOutcome, details?: ContinuationFinishedDetails): Promise<void>;
|
|
90
80
|
readRepositoryFile(input: RepositoryFileInput): Promise<string | null>;
|
|
91
81
|
private validateInput;
|