@wardby/cli 0.4.1 → 0.5.1
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/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 +486 -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.js +61 -12
- 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 +127 -5
- 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 +56 -20
- package/docs/getting-started-gke.md +24 -0
- package/docs/getting-started.md +196 -10
- 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 +30 -4
- 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 +28 -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 +3 -2
- 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
|
@@ -35,6 +35,7 @@ import { parseWorkerDiagnosticLine } from "./docker.js";
|
|
|
35
35
|
import { KubernetesAlreadyExistsError, KubernetesConflictError } from "./kubernetes-api.js";
|
|
36
36
|
import { ENFORCEMENT_PROBE_DENY_REACHABLE, ENFORCEMENT_PROBE_PROVEN, ENFORCEMENT_PROBE_DENY_REFUSED, ENFORCEMENT_PROBE_PROXY_UNREACHABLE, KEEPER_CONTAINER, KEEPER_SEEDED_MARKER, KUBERNETES_ISOLATION_ERROR, STORAGE_ROOT, TOOL_RUNNER_CONTAINER, WORKER_CONTAINER, assertRunNetworkPolicyMatches, assertRunPodMatches, buildCapabilitySecret, buildRunNetworkPolicy, buildRunPod, enforcementProbeScript, kubernetesRunNames, kubernetesRunNamesForToken, runLabels, serviceContainerName, validateKubernetesSpec, } from "./kubernetes-isolation.js";
|
|
37
37
|
import { claudeToolSetup } from "./claude-tool-setup.js";
|
|
38
|
+
import { podQuotaUsage, quotaShortfall } from "./kubernetes-quota.js";
|
|
38
39
|
import { readProxyWitness } from "./kubernetes-witness.js";
|
|
39
40
|
import { safeExtract } from "./safe-extract.js";
|
|
40
41
|
import { reportServiceState, } from "./service-state.js";
|
|
@@ -185,6 +186,8 @@ const TOKEN = /^[a-f0-9]{20}$/;
|
|
|
185
186
|
const WORKSPACE_STORAGE = `${STORAGE_ROOT}/workspace`;
|
|
186
187
|
const INPUT_STORAGE = `${STORAGE_ROOT}/input`;
|
|
187
188
|
const RESULT_PATH = `${STORAGE_ROOT}/output/result.json`;
|
|
189
|
+
/** Stands in for the proxy ClusterIP when building a pod only to count its resources. */
|
|
190
|
+
const QUOTA_PROBE_PROXY_IP = "10.0.0.1";
|
|
188
191
|
const kubernetesLog = logger.child({ module: "kubernetes-jobs" });
|
|
189
192
|
const PHASES = new Set(["provisioning", "active", "succeeded", "failed", "stopped", "lost", "removed"]);
|
|
190
193
|
const TERMINAL_PHASES = new Set(["succeeded", "failed", "stopped", "lost", "removed"]);
|
|
@@ -427,6 +430,53 @@ export class KubernetesJobLauncher {
|
|
|
427
430
|
return undefined;
|
|
428
431
|
}
|
|
429
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Whether this spec's pod fits the free room in the namespace's ResourceQuota
|
|
435
|
+
* (KubernetesJobConfig.resourceQuota), counted from the same pod `launch` would create. Any
|
|
436
|
+
* doubt (no quota configured, unreadable, unparseable, a spec the pod builder rejects) answers
|
|
437
|
+
* yes, so the launch and the API server stay the authority.
|
|
438
|
+
*/
|
|
439
|
+
async hasCapacityFor(spec) {
|
|
440
|
+
const quotaName = this.options.config.resourceQuota;
|
|
441
|
+
if (!quotaName)
|
|
442
|
+
return true;
|
|
443
|
+
let quota;
|
|
444
|
+
try {
|
|
445
|
+
quota = await this.api.readResourceQuota(this.namespace, quotaName);
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
this.warnOnce("kubernetes_resource_quota_unreadable: capacity is not checked before launch");
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
if (!quota) {
|
|
452
|
+
this.warnOnce(`kubernetes_resource_quota_missing: ${quotaName}; capacity is not checked before launch`);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
let usage;
|
|
456
|
+
try {
|
|
457
|
+
const { runtimeClassName, priorityClassName, platform } = this.options.config;
|
|
458
|
+
// The proxy address only fills a hosts entry; it doesn't change what the pod requests.
|
|
459
|
+
const pod = buildRunPod(spec, {
|
|
460
|
+
namespace: this.namespace,
|
|
461
|
+
proxyIp: QUOTA_PROBE_PROXY_IP,
|
|
462
|
+
runtimeClassName,
|
|
463
|
+
priorityClassName,
|
|
464
|
+
platform,
|
|
465
|
+
});
|
|
466
|
+
usage = podQuotaUsage(pod);
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
return quotaShortfall(quota, usage) === null;
|
|
472
|
+
}
|
|
473
|
+
warnedOnce = new Set();
|
|
474
|
+
warnOnce(message) {
|
|
475
|
+
if (this.warnedOnce.has(message))
|
|
476
|
+
return;
|
|
477
|
+
this.warnedOnce.add(message);
|
|
478
|
+
this.warn(message);
|
|
479
|
+
}
|
|
430
480
|
async launch(spec) {
|
|
431
481
|
try {
|
|
432
482
|
validateKubernetesSpec(spec);
|
|
@@ -81,6 +81,13 @@ export interface JobLauncher {
|
|
|
81
81
|
* cannot know the handle in advance omit this, and callers persist only after `launch` returns.
|
|
82
82
|
*/
|
|
83
83
|
plannedHandle?: (spec: JobSpec) => JobHandle | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the cluster has room for this spec's job right now (Kubernetes: the namespace
|
|
86
|
+
* ResourceQuota). Asked before a run claims a slot; false keeps the run queued so it is retried
|
|
87
|
+
* later instead of failing when the job is refused. Absent, or any doubt, means yes: the launch
|
|
88
|
+
* itself stays the authority.
|
|
89
|
+
*/
|
|
90
|
+
hasCapacityFor?: (spec: JobSpec) => Promise<boolean>;
|
|
84
91
|
launch: (spec: JobSpec) => Promise<JobHandle>;
|
|
85
92
|
status: (handle: JobHandle) => Promise<JobStatus>;
|
|
86
93
|
collect: (handle: JobHandle) => Promise<JobResult>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Host-neutral CI summary for repo_pr_read. */
|
|
2
|
+
import type { CiCheckView, CiState } from "./types.js";
|
|
3
|
+
/** Most CI results listed on one head. */
|
|
4
|
+
export declare const MAX_CI_CHECKS = 50;
|
|
5
|
+
export declare const FAILING_CONCLUSIONS: ReadonlySet<string>;
|
|
6
|
+
export declare const PASSING_CONCLUSIONS: ReadonlySet<string>;
|
|
7
|
+
export declare function summarizeCi(checks: readonly CiCheckView[]): Exclude<CiState, "unavailable">;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Most CI results listed on one head. */
|
|
2
|
+
export const MAX_CI_CHECKS = 50;
|
|
3
|
+
export const FAILING_CONCLUSIONS = new Set([
|
|
4
|
+
"failure",
|
|
5
|
+
"timed_out",
|
|
6
|
+
"action_required",
|
|
7
|
+
"startup_failure",
|
|
8
|
+
"error",
|
|
9
|
+
]);
|
|
10
|
+
export const PASSING_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
|
|
11
|
+
export function summarizeCi(checks) {
|
|
12
|
+
if (checks.length === 0)
|
|
13
|
+
return "none";
|
|
14
|
+
if (checks.some((c) => c.status === "completed" && FAILING_CONCLUSIONS.has(c.conclusion ?? "")))
|
|
15
|
+
return "failing";
|
|
16
|
+
if (checks.some((c) => c.status !== "completed"))
|
|
17
|
+
return "pending";
|
|
18
|
+
const allPassing = checks.every((c) => PASSING_CONCLUSIONS.has(c.conclusion ?? ""));
|
|
19
|
+
return allPassing && checks.some((c) => c.conclusion === "success") ? "passing" : "inconclusive";
|
|
20
|
+
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { HostEvent } from "./types.js";
|
|
2
2
|
export declare function verifyGitHubSignature(rawBody: string, header: string | undefined, secret: string): boolean;
|
|
3
|
+
type Json = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* The run id in a PR description's marker. Only a PR the App itself authored
|
|
6
|
+
* counts: anyone can write the marker into their own PR's description.
|
|
7
|
+
*/
|
|
8
|
+
export declare function appMarkerRunId(pr: Json | null, slug: string): string | undefined;
|
|
3
9
|
export declare function normalizeGitHubEvent(eventName: string, payload: unknown, app: {
|
|
4
10
|
id: number;
|
|
5
11
|
slug: string;
|
|
6
12
|
}): HostEvent | null;
|
|
13
|
+
export {};
|
|
@@ -11,6 +11,8 @@ const SAFE_SHA = /^[0-9a-f]{40}$/;
|
|
|
11
11
|
const PUSH_MAX_COMMITS = 2048;
|
|
12
12
|
const SAFE_BRANCH = /^[A-Za-z0-9._/-]{1,100}$/;
|
|
13
13
|
const PUSH_MAX_PATHS = 1000;
|
|
14
|
+
/** Pull requests one CI completion may re-review; a commit is rarely the head of more. */
|
|
15
|
+
const MAX_CI_PRS = 10;
|
|
14
16
|
/** Mirrors the runId shape validated in providers/vcs (github.ts, git.ts). */
|
|
15
17
|
const SAFE_RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
16
18
|
/** The hidden marker a coding run writes at the top of every PR it opens (providers/vcs/github.ts); legacy prefix too. */
|
|
@@ -44,14 +46,10 @@ function subjectOf(item) {
|
|
|
44
46
|
return typeof item?.title === "string" ? { title: item.title, body: text(item.body) } : undefined;
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
|
-
* The run
|
|
48
|
-
* anyone can write the marker into their own PR's description.
|
|
49
|
-
* PR counts too: a merged or closed PR's branch is stale (a squash merge leaves
|
|
50
|
-
* its commits behind), so a follow-up there starts from the default branch.
|
|
49
|
+
* The run id in a PR description's marker. Only a PR the App itself authored
|
|
50
|
+
* counts: anyone can write the marker into their own PR's description.
|
|
51
51
|
*/
|
|
52
|
-
function
|
|
53
|
-
if (pr?.state !== "open")
|
|
54
|
-
return undefined;
|
|
52
|
+
export function appMarkerRunId(pr, slug) {
|
|
55
53
|
const author = obj(pr?.user);
|
|
56
54
|
if (author?.type !== "Bot" || typeof author.login !== "string")
|
|
57
55
|
return undefined;
|
|
@@ -60,6 +58,16 @@ function priorRunIdOf(pr, slug) {
|
|
|
60
58
|
const id = RUN_MARKER.exec(text(pr?.body))?.[1];
|
|
61
59
|
return id && SAFE_RUN_ID.test(id) ? id : undefined;
|
|
62
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* The run that opened this PR, for a follow-up. Only an open PR counts: a
|
|
63
|
+
* merged or closed PR's branch is stale (a squash merge leaves its commits
|
|
64
|
+
* behind), so a follow-up there starts from the default branch.
|
|
65
|
+
*/
|
|
66
|
+
function priorRunIdOf(pr, slug) {
|
|
67
|
+
if (pr?.state !== "open")
|
|
68
|
+
return undefined;
|
|
69
|
+
return appMarkerRunId(pr, slug);
|
|
70
|
+
}
|
|
63
71
|
/**
|
|
64
72
|
* A cheap pre-filter only: author_association is not a permission (MEMBER is
|
|
65
73
|
* any org member, COLLABORATOR includes read and triage). The real gate is the
|
|
@@ -134,6 +142,22 @@ export function normalizeGitHubEvent(eventName, payload, app) {
|
|
|
134
142
|
const isFork = typeof headRepo !== "string" || headRepo.toLowerCase() !== repository;
|
|
135
143
|
return { kind: "pr_updated", provider: "github", repository, prNumber, headSha, isFork };
|
|
136
144
|
}
|
|
145
|
+
if (eventName === "check_suite") {
|
|
146
|
+
if (p.action !== "completed")
|
|
147
|
+
return null;
|
|
148
|
+
const suite = obj(p.check_suite);
|
|
149
|
+
// wardby's own review check finishing is not CI.
|
|
150
|
+
if (int(obj(suite?.app)?.id) === app.id)
|
|
151
|
+
return null;
|
|
152
|
+
const headSha = suite?.head_sha;
|
|
153
|
+
if (typeof headSha !== "string" || !SAFE_SHA.test(headSha))
|
|
154
|
+
return null;
|
|
155
|
+
const prs = Array.isArray(suite?.pull_requests) ? suite.pull_requests : [];
|
|
156
|
+
const prNumbers = [...new Set(prs.map((pr) => int(obj(pr)?.number)).filter((n) => n !== null))];
|
|
157
|
+
if (prNumbers.length === 0)
|
|
158
|
+
return null;
|
|
159
|
+
return { kind: "ci_completed", provider: "github", repository, headSha, prNumbers: prNumbers.slice(0, MAX_CI_PRS) };
|
|
160
|
+
}
|
|
137
161
|
if (eventName === "check_run") {
|
|
138
162
|
if (p.action !== "rerequested")
|
|
139
163
|
return null;
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* the narrowest permission set that call needs (GitHubAppClient.withScopedToken),
|
|
5
5
|
* and revokes it afterwards. See docs/private/2026-09-25-code-review-host-design.md §5.1.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
8
|
-
import { ReviewHostError, type CodeReviewHost, type CommentInput, type CommentRef, type CompleteCheckInput, type EditCommentInput, type FileListView, type FileReadResult, type HostPermission, type HostUser, type PublishReviewInput, type PublishReviewResult, type PullRequestHead, type PullRequestView, type StartCheckInput } from "./types.js";
|
|
7
|
+
import { type GitHubAppClient } from "../vcs/github.js";
|
|
8
|
+
import { ReviewHostError, type CiView, type CodeReviewHost, type CommentInput, type CommentRef, type CompleteCheckInput, type EditCommentInput, type FileListView, type FileReadResult, type HostPermission, type HostUser, type PublishReviewInput, type PublishReviewResult, type PullRequestHead, type PullRequestOrigin, type PullRequestView, type StartCheckInput } from "./types.js";
|
|
9
|
+
/** listFiles leaves out vendored trees and lockfiles; every host applies the same filter. */
|
|
10
|
+
export declare function skippedInListing(path: string): boolean;
|
|
9
11
|
type Json = Record<string, unknown>;
|
|
10
12
|
/** Maps GitHubAppClient's fixed error messages onto host-neutral codes; keeps the body-free message. */
|
|
11
13
|
export declare function toReviewHostError(err: unknown): ReviewHostError;
|
|
@@ -21,7 +23,22 @@ export declare class GitHubReviewHost implements CodeReviewHost {
|
|
|
21
23
|
login: string;
|
|
22
24
|
}>;
|
|
23
25
|
pullRequestHead(repository: string, prNumber: number): Promise<PullRequestHead>;
|
|
26
|
+
pullRequestOrigin(repository: string, prNumber: number): Promise<PullRequestOrigin>;
|
|
27
|
+
addLabel(repository: string, prNumber: number, label: string): Promise<void>;
|
|
28
|
+
replaceRelatedSection(repository: string, prNumber: number, input: {
|
|
29
|
+
expectedMarkerRunId: string;
|
|
30
|
+
block: string;
|
|
31
|
+
}): Promise<"updated" | "unchanged" | "skipped">;
|
|
24
32
|
private head;
|
|
33
|
+
/**
|
|
34
|
+
* CI on one commit: check runs (Checks: read) minus wardby's own, then
|
|
35
|
+
* commit statuses (Commit statuses: read) best effort. Never throws: a
|
|
36
|
+
* failure reading check runs is state "unavailable", a failure reading
|
|
37
|
+
* statuses only sets statusesUnavailable. Both reads are attempted
|
|
38
|
+
* independently, so a failed checks read still mints (and attempts) the
|
|
39
|
+
* statuses token.
|
|
40
|
+
*/
|
|
41
|
+
readCi(repository: string, headSha: string): Promise<CiView>;
|
|
25
42
|
readPullRequest(repository: string, prNumber: number, opts: {
|
|
26
43
|
sinceSha?: string;
|
|
27
44
|
maxPatchChars: number;
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub implementation of CodeReviewHost, acting as the wardby GitHub App.
|
|
3
|
+
* Each method mints its own installation token scoped to one repository and
|
|
4
|
+
* the narrowest permission set that call needs (GitHubAppClient.withScopedToken),
|
|
5
|
+
* and revokes it afterwards. See docs/private/2026-09-25-code-review-host-design.md §5.1.
|
|
6
|
+
*/
|
|
7
|
+
import { MAX_PULL_REQUEST_BODY_CHARS, upsertRelatedSection } from "../vcs/github.js";
|
|
1
8
|
import { normalizeGitHubRepository } from "../../coding/protocol.js";
|
|
9
|
+
import { MAX_CI_CHECKS, summarizeCi } from "./ci.js";
|
|
2
10
|
import { partitionComments } from "./diff-lines.js";
|
|
11
|
+
import { appMarkerRunId } from "./github-events.js";
|
|
3
12
|
import { findingMarker, hasFindingMarker, hasReviewMarker, parseReviewMarker, renderInlineComment, renderSummaryComment, verdictConclusion, } from "./review-format.js";
|
|
4
13
|
import { ReviewHostError, } from "./types.js";
|
|
5
14
|
const READ = { contents: "read", pull_requests: "read" };
|
|
@@ -8,11 +17,22 @@ const MAX_FILE_PAGES = 3;
|
|
|
8
17
|
const MAX_COMMENT_PAGES = 3;
|
|
9
18
|
const MAX_LISTED_FILES = 1000;
|
|
10
19
|
const VENDORED = /(^|\/)(node_modules|dist|\.venv)\//;
|
|
20
|
+
/** listFiles leaves out vendored trees and lockfiles; every host applies the same filter. */
|
|
21
|
+
export function skippedInListing(path) {
|
|
22
|
+
return VENDORED.test(path) || path.endsWith("package-lock.json");
|
|
23
|
+
}
|
|
11
24
|
const CHECK_TEXT_LIMIT = 65_535;
|
|
12
25
|
const COMMENT_WRITE = { issues: "write", pull_requests: "write" };
|
|
13
26
|
const REVIEW_WRITE = { pull_requests: "write", checks: "write" };
|
|
14
27
|
/** Always implicitly granted to an installation token; enough for the collaborator-permission endpoint. */
|
|
15
28
|
const METADATA_READ = { metadata: "read" };
|
|
29
|
+
/** Editing a pull request's description: nothing else. */
|
|
30
|
+
const PR_WRITE = { pull_requests: "write" };
|
|
31
|
+
/** CI reads, each on its own token: Checks is already granted; Commit statuses may not be. */
|
|
32
|
+
const CHECKS_READ = { checks: "read" };
|
|
33
|
+
const STATUSES_READ = { statuses: "read" };
|
|
34
|
+
const CI_NAME_CHARS = 100;
|
|
35
|
+
const APP_SLUG = /^[a-z0-9-]{1,64}$/;
|
|
16
36
|
const GITHUB_USER_ID = /^[1-9]\d{0,19}$/;
|
|
17
37
|
const GITHUB_LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;
|
|
18
38
|
/** Highest first: user.permissions booleans, which (unlike the legacy field) keep maintain and triage apart. */
|
|
@@ -103,6 +123,31 @@ function fileView(raw) {
|
|
|
103
123
|
changes: typeof raw.changes === "number" ? raw.changes : num(raw.additions) + num(raw.deletions),
|
|
104
124
|
};
|
|
105
125
|
}
|
|
126
|
+
function checkRunView(raw) {
|
|
127
|
+
const status = raw.status === "completed" ? "completed" : raw.status === "in_progress" ? "in_progress" : "queued";
|
|
128
|
+
const app = raw.app && typeof raw.app === "object" ? raw.app : null;
|
|
129
|
+
return {
|
|
130
|
+
name: str(raw.name).slice(0, CI_NAME_CHARS),
|
|
131
|
+
kind: "check_run",
|
|
132
|
+
status,
|
|
133
|
+
conclusion: status === "completed" && typeof raw.conclusion === "string" ? raw.conclusion : null,
|
|
134
|
+
app: app && typeof app.slug === "string" && APP_SLUG.test(app.slug) ? app.slug : null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function statusView(raw) {
|
|
138
|
+
const state = str(raw.state);
|
|
139
|
+
return {
|
|
140
|
+
name: str(raw.context).slice(0, CI_NAME_CHARS),
|
|
141
|
+
kind: "status",
|
|
142
|
+
status: state === "pending" ? "in_progress" : "completed",
|
|
143
|
+
conclusion: state === "pending" ? null : state,
|
|
144
|
+
app: null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** True for a check run wardby's own App created (its review checks, wardby/continuation). */
|
|
148
|
+
function fromApp(raw, appId) {
|
|
149
|
+
return !!raw.app && typeof raw.app === "object" && raw.app.id === appId;
|
|
150
|
+
}
|
|
106
151
|
const THREADS_QUERY = `query($owner: String!, $name: String!, $number: Int!) {
|
|
107
152
|
repository(owner: $owner, name: $name) {
|
|
108
153
|
pullRequest(number: $number) {
|
|
@@ -205,6 +250,48 @@ export class GitHubReviewHost {
|
|
|
205
250
|
return this.head(repository, pr);
|
|
206
251
|
});
|
|
207
252
|
}
|
|
253
|
+
async pullRequestOrigin(repository, prNumber) {
|
|
254
|
+
const { slug } = await this.client.appIdentity();
|
|
255
|
+
return this.withToken(repository, READ, async (get) => {
|
|
256
|
+
const pr = record(await (await get(`${repoPath(repository)}/pulls/${prNumber}`)).json());
|
|
257
|
+
const labels = Array.isArray(pr.labels)
|
|
258
|
+
? pr.labels
|
|
259
|
+
.map((l) => (l && typeof l === "object" ? str(l.name) : ""))
|
|
260
|
+
.filter((name) => name !== "")
|
|
261
|
+
: [];
|
|
262
|
+
const markerRunId = appMarkerRunId(pr, slug);
|
|
263
|
+
return {
|
|
264
|
+
...this.head(repository, pr),
|
|
265
|
+
labels,
|
|
266
|
+
merged: pr.merged === true,
|
|
267
|
+
draft: pr.draft === true,
|
|
268
|
+
...(markerRunId ? { markerRunId } : {}),
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
async addLabel(repository, prNumber, label) {
|
|
273
|
+
await this.withToken(repository, COMMENT_WRITE, async (get) => {
|
|
274
|
+
await get(`${repoPath(repository)}/issues/${prNumber}/labels`, { method: "POST", body: JSON.stringify({ labels: [label] }) }, [200]);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
async replaceRelatedSection(repository, prNumber, input) {
|
|
278
|
+
const { slug } = await this.client.appIdentity();
|
|
279
|
+
const base = repoPath(repository);
|
|
280
|
+
return this.withToken(repository, PR_WRITE, async (get) => {
|
|
281
|
+
const pr = record(await (await get(`${base}/pulls/${prNumber}`)).json());
|
|
282
|
+
// Re-checked on the body actually being edited: only the App's own PR for this run.
|
|
283
|
+
if (pr.state !== "open" || appMarkerRunId(pr, slug) !== input.expectedMarkerRunId)
|
|
284
|
+
return "skipped";
|
|
285
|
+
const body = typeof pr.body === "string" ? pr.body : "";
|
|
286
|
+
const next = upsertRelatedSection(body, input.block);
|
|
287
|
+
if (next === null || next.length > MAX_PULL_REQUEST_BODY_CHARS)
|
|
288
|
+
return "skipped";
|
|
289
|
+
if (next === body)
|
|
290
|
+
return "unchanged";
|
|
291
|
+
await get(`${base}/pulls/${prNumber}`, { method: "PATCH", body: JSON.stringify({ body: next }) }, [200]);
|
|
292
|
+
return "updated";
|
|
293
|
+
});
|
|
294
|
+
}
|
|
208
295
|
head(repository, pr) {
|
|
209
296
|
const head = record(pr.head);
|
|
210
297
|
const headRepo = head.repo && typeof head.repo === "object" ? record(head.repo) : null;
|
|
@@ -216,9 +303,64 @@ export class GitHubReviewHost {
|
|
|
216
303
|
headRepo.full_name.toLowerCase() !== normalizeGitHubRepository(repository);
|
|
217
304
|
return { headSha, isFork, state: str(pr.state) };
|
|
218
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* CI on one commit: check runs (Checks: read) minus wardby's own, then
|
|
308
|
+
* commit statuses (Commit statuses: read) best effort. Never throws: a
|
|
309
|
+
* failure reading check runs is state "unavailable", a failure reading
|
|
310
|
+
* statuses only sets statusesUnavailable. Both reads are attempted
|
|
311
|
+
* independently, so a failed checks read still mints (and attempts) the
|
|
312
|
+
* statuses token.
|
|
313
|
+
*/
|
|
314
|
+
async readCi(repository, headSha) {
|
|
315
|
+
const base = repoPath(repository);
|
|
316
|
+
let checks = [];
|
|
317
|
+
let truncated = false;
|
|
318
|
+
let unavailableReason;
|
|
319
|
+
try {
|
|
320
|
+
const { id: appId } = await this.client.appIdentity();
|
|
321
|
+
({ checks, truncated } = await this.withToken(repository, CHECKS_READ, async (get) => {
|
|
322
|
+
const payload = record(await (await get(`${base}/commits/${headSha}/check-runs?filter=latest&per_page=100`)).json());
|
|
323
|
+
const runs = list(payload.check_runs ?? []);
|
|
324
|
+
const total = typeof payload.total_count === "number" ? payload.total_count : runs.length;
|
|
325
|
+
return { checks: runs.filter((r) => !fromApp(r, appId)).map(checkRunView), truncated: total > runs.length };
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
unavailableReason = toReviewHostError(err).code;
|
|
330
|
+
}
|
|
331
|
+
let statusesUnavailable = false;
|
|
332
|
+
try {
|
|
333
|
+
const statuses = await this.withToken(repository, STATUSES_READ, async (get) => {
|
|
334
|
+
const payload = record(await (await get(`${base}/commits/${headSha}/status?per_page=100`)).json());
|
|
335
|
+
return list(payload.statuses ?? []).map(statusView);
|
|
336
|
+
});
|
|
337
|
+
checks = [...checks, ...statuses];
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
statusesUnavailable = true;
|
|
341
|
+
}
|
|
342
|
+
if (unavailableReason !== undefined) {
|
|
343
|
+
return {
|
|
344
|
+
headSha,
|
|
345
|
+
state: "unavailable",
|
|
346
|
+
checks: [],
|
|
347
|
+
truncated: false,
|
|
348
|
+
statusesUnavailable: true,
|
|
349
|
+
unavailableReason,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const shown = checks.slice(0, MAX_CI_CHECKS);
|
|
353
|
+
return {
|
|
354
|
+
headSha,
|
|
355
|
+
state: summarizeCi(checks),
|
|
356
|
+
checks: shown,
|
|
357
|
+
truncated: truncated || shown.length < checks.length,
|
|
358
|
+
statusesUnavailable,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
219
361
|
async readPullRequest(repository, prNumber, opts) {
|
|
220
362
|
const base = repoPath(repository);
|
|
221
|
-
|
|
363
|
+
const view = await this.withToken(repository, READ, async (get, graphql) => {
|
|
222
364
|
const pr = record(await (await get(`${base}/pulls/${prNumber}`)).json());
|
|
223
365
|
const { headSha, isFork, state } = this.head(repository, pr);
|
|
224
366
|
const prFiles = async () => {
|
|
@@ -298,6 +440,7 @@ export class GitHubReviewHost {
|
|
|
298
440
|
openThreads,
|
|
299
441
|
};
|
|
300
442
|
});
|
|
443
|
+
return { ...view, ci: await this.readCi(repository, view.headSha) };
|
|
301
444
|
}
|
|
302
445
|
async readFile(repository, path, ref, window) {
|
|
303
446
|
assertRelativePath(path);
|
|
@@ -346,7 +489,7 @@ export class GitHubReviewHost {
|
|
|
346
489
|
const tree = record(await (await get(`${base}/git/trees/${encodeURIComponent(resolvedRef)}?recursive=1`)).json());
|
|
347
490
|
const entries = list(tree.tree)
|
|
348
491
|
.filter((e) => e.type === "blob" && typeof e.path === "string" && e.path.startsWith(pathPrefix))
|
|
349
|
-
.filter((e) => !
|
|
492
|
+
.filter((e) => !skippedInListing(str(e.path)));
|
|
350
493
|
return {
|
|
351
494
|
ref: resolvedRef,
|
|
352
495
|
count: entries.length,
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { type LocalReviewHostOptions } from "./local.js";
|
|
1
2
|
import type { HostUserAuthorizerRegistry, ReviewHostRegistry } from "./types.js";
|
|
2
3
|
export * from "./types.js";
|
|
3
4
|
export { GitHubReviewHost } from "./github.js";
|
|
4
5
|
export { GitHubUserAuthorizer } from "./github-user-auth.js";
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
export { LocalReviewHost } from "./local.js";
|
|
7
|
+
/**
|
|
8
|
+
* One host per configured provider: github when a GitHub App is configured,
|
|
9
|
+
* local always (the roots are re-read on every local call, so a link with no
|
|
10
|
+
* roots configured is refused with local_repo_not_allowed at call time).
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildReviewHosts(env: NodeJS.ProcessEnv, db: LocalReviewHostOptions["db"]): ReviewHostRegistry;
|
|
7
13
|
/**
|
|
8
14
|
* One identity-linking flow per provider whose App has OAuth client
|
|
9
15
|
* credentials (GITHUB_APP_CLIENT_ID/SECRET). Empty = link_host_account is
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { loadGitHubUserAuthConfig, loadGitHubVcsConfig } from "../../config/providers.js";
|
|
2
|
+
import { loadLocalRepoRoots } from "../../coding/local-repo.js";
|
|
2
3
|
import { GitHubAppClient } from "../vcs/github.js";
|
|
3
4
|
import { GitHubReviewHost } from "./github.js";
|
|
4
5
|
import { GitHubUserAuthorizer } from "./github-user-auth.js";
|
|
6
|
+
import { LocalReviewHost } from "./local.js";
|
|
5
7
|
export * from "./types.js";
|
|
6
8
|
export { GitHubReviewHost } from "./github.js";
|
|
7
9
|
export { GitHubUserAuthorizer } from "./github-user-auth.js";
|
|
10
|
+
export { LocalReviewHost } from "./local.js";
|
|
8
11
|
function githubClient(env) {
|
|
9
12
|
const github = loadGitHubVcsConfig(env);
|
|
10
13
|
if (!github.appId || !github.privateKey)
|
|
11
14
|
return null;
|
|
12
15
|
return new GitHubAppClient({ appId: github.appId, privateKey: github.privateKey, apiVersion: github.apiVersion });
|
|
13
16
|
}
|
|
14
|
-
/**
|
|
15
|
-
|
|
17
|
+
/**
|
|
18
|
+
* One host per configured provider: github when a GitHub App is configured,
|
|
19
|
+
* local always (the roots are re-read on every local call, so a link with no
|
|
20
|
+
* roots configured is refused with local_repo_not_allowed at call time).
|
|
21
|
+
*/
|
|
22
|
+
export function buildReviewHosts(env, db) {
|
|
23
|
+
const hosts = {};
|
|
16
24
|
const client = githubClient(env);
|
|
17
|
-
|
|
25
|
+
if (client)
|
|
26
|
+
hosts.github = new GitHubReviewHost(client);
|
|
27
|
+
hosts.local = new LocalReviewHost({ db, roots: () => loadLocalRepoRoots(env).roots });
|
|
28
|
+
return hosts;
|
|
18
29
|
}
|
|
19
30
|
/**
|
|
20
31
|
* One identity-linking flow per provider whose App has OAuth client
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeReviewHost over local git repositories (`local:/abs/path`). A "pull
|
|
3
|
+
* request" is a LocalPullRequest row: a branch compared against a base in the
|
|
4
|
+
* repository, keyed by a durable number. Reviews and comments are stored as
|
|
5
|
+
* LocalReview rows; checks, reactions and threads are no-ops.
|
|
6
|
+
*
|
|
7
|
+
* Every read goes through git objects (`cat-file`, `ls-tree`, `diff A B`),
|
|
8
|
+
* never the working tree, in the canonical repository path that
|
|
9
|
+
* resolveLocalRepository returns at call time (the trusted roots can change).
|
|
10
|
+
* git runs with GIT_* stripped, hooks and fsmonitor off, no external diff or
|
|
11
|
+
* textconv drivers, and literal pathspecs. Every ref from the model or a row
|
|
12
|
+
* is checked before it reaches git (a 40-hex sha, or a branch name that
|
|
13
|
+
* `git check-ref-format --branch` accepts and that cannot read as an option),
|
|
14
|
+
* and revisions follow `--end-of-options`.
|
|
15
|
+
*/
|
|
16
|
+
import type { PrismaClient } from "#prisma";
|
|
17
|
+
import { type CodeReviewHost, type CommentInput, type CommentRef, type CompleteCheckInput, type EditCommentInput, type FileListView, type FileReadResult, type HostPermission, type HostUser, type PublishReviewInput, type PublishReviewResult, type PullRequestHead, type PullRequestView, type StartCheckInput } from "./types.js";
|
|
18
|
+
type Db = Pick<PrismaClient, "localPullRequest" | "localReview">;
|
|
19
|
+
export interface LocalReviewHostOptions {
|
|
20
|
+
db: Db;
|
|
21
|
+
/** Trusted roots, read at each call (default: LOCAL_REPO_ROOTS). */
|
|
22
|
+
roots?: () => readonly string[];
|
|
23
|
+
}
|
|
24
|
+
export declare class LocalReviewHost implements CodeReviewHost {
|
|
25
|
+
readonly provider: "local";
|
|
26
|
+
private readonly db;
|
|
27
|
+
private readonly roots;
|
|
28
|
+
constructor(options: LocalReviewHostOptions);
|
|
29
|
+
/** The canonical repository and its realpath, checked against the current roots. */
|
|
30
|
+
private repo;
|
|
31
|
+
private pullRequest;
|
|
32
|
+
/** Rejects anything but a plain branch/tag name before it can reach git as a revision or an option. */
|
|
33
|
+
private assertRefName;
|
|
34
|
+
/** The commit a sha or branch name points at. */
|
|
35
|
+
private commitOf;
|
|
36
|
+
/** The repository's checked-out HEAD commit (its "default branch"), and a label for it. */
|
|
37
|
+
private defaultRef;
|
|
38
|
+
private resolveRef;
|
|
39
|
+
private isAncestor;
|
|
40
|
+
/** Files changed between two commits (both 40-hex), with rename detection, in git's order. */
|
|
41
|
+
private changedFiles;
|
|
42
|
+
/** Attaches patches, sharing one character budget across files and flagging truncation like GitHub. */
|
|
43
|
+
private withPatches;
|
|
44
|
+
repositoryPermission(_repository: string, _user: HostUser): Promise<{
|
|
45
|
+
level: HostPermission;
|
|
46
|
+
login: string;
|
|
47
|
+
}>;
|
|
48
|
+
readPullRequest(repository: string, prNumber: number, opts: {
|
|
49
|
+
sinceSha?: string;
|
|
50
|
+
maxPatchChars: number;
|
|
51
|
+
agentMarker: string;
|
|
52
|
+
}): Promise<PullRequestView>;
|
|
53
|
+
pullRequestHead(repository: string, prNumber: number): Promise<PullRequestHead>;
|
|
54
|
+
readFile(repository: string, path: string, ref: string | undefined, window: {
|
|
55
|
+
startLine: number;
|
|
56
|
+
maxLines: number;
|
|
57
|
+
}): Promise<FileReadResult>;
|
|
58
|
+
listFiles(repository: string, ref: string | undefined, pathPrefix?: string): Promise<FileListView>;
|
|
59
|
+
publishReview(repository: string, input: PublishReviewInput): Promise<PublishReviewResult>;
|
|
60
|
+
/** Stored as a COMMENT review on the branch's current head. The host is not told who comments: agentId is "". */
|
|
61
|
+
comment(repository: string, input: CommentInput): Promise<{
|
|
62
|
+
url: string;
|
|
63
|
+
id: string;
|
|
64
|
+
}>;
|
|
65
|
+
editComment(_repository: string, _input: EditCommentInput): Promise<void>;
|
|
66
|
+
acknowledge(_repository: string, _target: CommentRef): Promise<void>;
|
|
67
|
+
startCheck(_repository: string, _input: StartCheckInput): Promise<{
|
|
68
|
+
checkId: string;
|
|
69
|
+
}>;
|
|
70
|
+
completeCheck(_repository: string, _input: CompleteCheckInput): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
export {};
|