@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
package/dist/quickstart/index.js
CHANGED
|
@@ -6,9 +6,12 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
8
8
|
import { composeProjectName, defaultModel, ensureWardbyIgnored, generatedSecret, providerKeyName, quickstartPaths, readQuickstartEnv, readQuickstartState, resolveProjectDir, writeQuickstartEnv, writeQuickstartState, } from "./config.js";
|
|
9
|
+
import { codingStep, quickstartComposeFiles } from "./coding.js";
|
|
10
|
+
import { prismaCatalogImages, prismaLocalAgents, prismaSeedCodingAgents } from "./coding-db.js";
|
|
11
|
+
import { codingDoctorLines } from "./coding-doctor.js";
|
|
12
|
+
import { parseStarterChoice } from "./starter-services.js";
|
|
9
13
|
import { FETCH_NOTICE, migrateFailureReason, prismaMigrateInvocation, REGISTRY_FAILURE_MESSAGE, runPrismaMigrateInvocation, } from "./migrate.js";
|
|
10
14
|
const packageRoot = fileURLToPath(new URL("../../", import.meta.url));
|
|
11
|
-
const composeFile = join(packageRoot, "deploy/local/docker-compose.yml");
|
|
12
15
|
const wardbyBin = join(packageRoot, "bin/wardby.js");
|
|
13
16
|
const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
14
17
|
const DEMO_AGENT = "hello-wardby";
|
|
@@ -33,17 +36,10 @@ function commandFailure(label, result) {
|
|
|
33
36
|
function runtimeEnv(paths, values = readQuickstartEnv(paths)) {
|
|
34
37
|
return { ...process.env, ...values, WARDBY_PROJECT_DIR: paths.projectDir };
|
|
35
38
|
}
|
|
39
|
+
/** The coding proxy's compose file joins once the coding step configured it, so ps/logs/down cover it too. */
|
|
36
40
|
function composeArgs(paths, state, args) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"--project-name",
|
|
40
|
-
state.composeProject,
|
|
41
|
-
"--env-file",
|
|
42
|
-
paths.envFile,
|
|
43
|
-
"--file",
|
|
44
|
-
composeFile,
|
|
45
|
-
...args,
|
|
46
|
-
];
|
|
41
|
+
const files = quickstartComposeFiles(packageRoot, readQuickstartEnv(paths)).flatMap((file) => ["--file", file]);
|
|
42
|
+
return ["compose", "--project-name", state.composeProject, "--env-file", paths.envFile, ...files, ...args];
|
|
47
43
|
}
|
|
48
44
|
function runCompose(paths, state, args, options = {}) {
|
|
49
45
|
return runCommand("docker", composeArgs(paths, state, args), {
|
|
@@ -85,7 +81,7 @@ async function selectPostgresPort(firstPort = 55432) {
|
|
|
85
81
|
}
|
|
86
82
|
async function startPostgres(paths, state, config) {
|
|
87
83
|
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
88
|
-
const up = runCompose(paths, state, ["up", "--detach", "--wait", "--wait-timeout", "90"]);
|
|
84
|
+
const up = runCompose(paths, state, ["up", "--detach", "--wait", "--wait-timeout", "90", "postgres"]);
|
|
89
85
|
if (up.status === 0)
|
|
90
86
|
return;
|
|
91
87
|
const output = `${up.stderr}\n${up.stdout}`;
|
|
@@ -170,6 +166,39 @@ function parseProvider(value) {
|
|
|
170
166
|
}
|
|
171
167
|
return value;
|
|
172
168
|
}
|
|
169
|
+
function parseCodingProvider(value) {
|
|
170
|
+
if (value === undefined)
|
|
171
|
+
return undefined;
|
|
172
|
+
if (value !== "codex" && value !== "claude-code") {
|
|
173
|
+
throw new Error(`--coding-provider must be codex or claude-code; got "${value}".`);
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
function codingDeps(paths) {
|
|
178
|
+
return {
|
|
179
|
+
run: runCommand,
|
|
180
|
+
prompts: { line: promptLine, yesNo: promptYesNo, secret: promptSecret },
|
|
181
|
+
log: (line) => console.log(line),
|
|
182
|
+
cwd: process.cwd(),
|
|
183
|
+
env: process.env,
|
|
184
|
+
packageRoot,
|
|
185
|
+
seed: (input) => prismaSeedCodingAgents(paths, input),
|
|
186
|
+
catalogImages: (services) => prismaCatalogImages(paths, services),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function codingSection(paths) {
|
|
190
|
+
return await codingDoctorLines(readQuickstartEnv(paths), {
|
|
191
|
+
run: runCommand,
|
|
192
|
+
listLocalAgents: async () => {
|
|
193
|
+
try {
|
|
194
|
+
return await prismaLocalAgents(paths);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
173
202
|
function parseClient(value) {
|
|
174
203
|
if (value === undefined)
|
|
175
204
|
return undefined;
|
|
@@ -316,7 +345,12 @@ async function chooseClient(nonInteractive) {
|
|
|
316
345
|
export async function quickstartCommand(args) {
|
|
317
346
|
const { values } = parseArgs({
|
|
318
347
|
args,
|
|
348
|
+
allowNegative: true,
|
|
319
349
|
options: {
|
|
350
|
+
coding: { type: "boolean" },
|
|
351
|
+
trust: { type: "string", multiple: true },
|
|
352
|
+
"coding-provider": { type: "string" },
|
|
353
|
+
"starter-services": { type: "string" },
|
|
320
354
|
provider: { type: "string" },
|
|
321
355
|
model: { type: "string" },
|
|
322
356
|
budget: { type: "string" },
|
|
@@ -327,6 +361,13 @@ export async function quickstartCommand(args) {
|
|
|
327
361
|
},
|
|
328
362
|
});
|
|
329
363
|
const nonInteractive = values["non-interactive"] ?? false;
|
|
364
|
+
const codingOptions = {
|
|
365
|
+
nonInteractive,
|
|
366
|
+
coding: values.coding,
|
|
367
|
+
trust: values.trust ?? [],
|
|
368
|
+
provider: parseCodingProvider(values["coding-provider"]),
|
|
369
|
+
starterServices: values["starter-services"] === undefined ? undefined : parseStarterChoice(values["starter-services"]),
|
|
370
|
+
};
|
|
330
371
|
const skipDemo = values["skip-demo"] ?? false;
|
|
331
372
|
const budget = values.budget === undefined ? 1 : Number(values.budget);
|
|
332
373
|
if (!Number.isFinite(budget) || budget <= 0)
|
|
@@ -398,6 +439,7 @@ export async function quickstartCommand(args) {
|
|
|
398
439
|
else
|
|
399
440
|
console.log(`Skipped the billed run. Start it later with: npx @wardby/cli@latest run ${DEMO_AGENT}`);
|
|
400
441
|
}
|
|
442
|
+
await codingStep(paths, state, codingOptions, codingDeps(paths));
|
|
401
443
|
const client = parseClient(values.client) ?? (await chooseClient(nonInteractive));
|
|
402
444
|
const mcpConfigured = client !== "none" && configureMcpClients(client, paths);
|
|
403
445
|
console.log("\nWardby is ready.");
|
|
@@ -481,6 +523,10 @@ export async function doctorCommand(args) {
|
|
|
481
523
|
failed ||= !ok;
|
|
482
524
|
console.log(`${ok ? "✓" : "✗"} ${name}${detail ? ` (${detail})` : ""}`);
|
|
483
525
|
}
|
|
526
|
+
for (const line of await codingSection(paths)) {
|
|
527
|
+
failed ||= line.startsWith("✗");
|
|
528
|
+
console.log(line);
|
|
529
|
+
}
|
|
484
530
|
if (failed)
|
|
485
531
|
process.exitCode = 1;
|
|
486
532
|
}
|
|
@@ -500,6 +546,9 @@ export async function statusCommand(args) {
|
|
|
500
546
|
process.stdout.write(ps.stdout);
|
|
501
547
|
const healthy = await databaseHealthy(paths);
|
|
502
548
|
console.log(`database: ${healthy ? "healthy" : "unavailable"}`);
|
|
549
|
+
const coding = healthy ? await codingSection(paths) : [];
|
|
550
|
+
if (coding.length > 0)
|
|
551
|
+
console.log(["coding:", ...coding.map((line) => ` ${line}`)].join("\n"));
|
|
503
552
|
if (!healthy)
|
|
504
553
|
process.exitCode = 1;
|
|
505
554
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type DeclaredService } from "../coding/services/declaration.js";
|
|
2
|
+
export declare const STARTER_SERVICES_BRANCH = "wardby/quickstart-services";
|
|
3
|
+
export type StarterService = "postgres" | "redis";
|
|
4
|
+
export declare function starterServicesYaml(choices: readonly StarterService[]): string;
|
|
5
|
+
/** `--starter-services postgres,redis|none`. */
|
|
6
|
+
export declare function parseStarterChoice(value: string): StarterService[];
|
|
7
|
+
/** The services a starter choice declares, as parseServiceDeclaration would read them back. */
|
|
8
|
+
export declare function starterDeclaredServices(choices: readonly StarterService[]): DeclaredService[];
|
|
9
|
+
/** The checked-out branch and its commit; null when HEAD is detached or the branch has no commits. */
|
|
10
|
+
export declare function repoDefaultBranch(dir: string): Promise<{
|
|
11
|
+
branch: string;
|
|
12
|
+
sha: string;
|
|
13
|
+
} | null>;
|
|
14
|
+
export type DeclaredServicesStatus = {
|
|
15
|
+
kind: "absent";
|
|
16
|
+
} | {
|
|
17
|
+
kind: "valid";
|
|
18
|
+
services: DeclaredService[];
|
|
19
|
+
} | {
|
|
20
|
+
kind: "invalid";
|
|
21
|
+
reason: string;
|
|
22
|
+
};
|
|
23
|
+
/** The committed `.wardby/services.yaml` at `ref`, validated; `dir` must sit inside `roots`. */
|
|
24
|
+
export declare function inspectDeclaredServices(dir: string, roots: readonly string[], ref: string): Promise<DeclaredServicesStatus>;
|
|
25
|
+
export type StarterCommitResult = {
|
|
26
|
+
status: "created" | "replaced";
|
|
27
|
+
commit: string;
|
|
28
|
+
} | {
|
|
29
|
+
status: "kept";
|
|
30
|
+
commit: string;
|
|
31
|
+
} | {
|
|
32
|
+
status: "refused";
|
|
33
|
+
reason: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Commits `content` as `.wardby/services.yaml` on top of `baseSha` and points
|
|
37
|
+
* the starter branch at it. An existing branch is replaced only when
|
|
38
|
+
* `confirmReplace` says so, and only if it has not moved since it was read.
|
|
39
|
+
*/
|
|
40
|
+
export declare function commitStarterServices(input: {
|
|
41
|
+
dir: string;
|
|
42
|
+
baseSha: string;
|
|
43
|
+
content: string;
|
|
44
|
+
confirmReplace: (existingSha: string) => Promise<boolean>;
|
|
45
|
+
}): Promise<StarterCommitResult>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The quickstart's `.wardby/services.yaml` handling for a local repository:
|
|
3
|
+
* read the committed declaration on the default branch, or write a starter one
|
|
4
|
+
* onto its own branch with git plumbing.
|
|
5
|
+
*
|
|
6
|
+
* The write never touches the user's working tree, index, or HEAD: it builds
|
|
7
|
+
* the tree in a scratch index (GIT_INDEX_FILE in a temp dir), creates the
|
|
8
|
+
* commit with commit-tree, and moves only refs/heads/wardby/quickstart-services
|
|
9
|
+
* with a compare-and-swap update-ref. git runs through the hardened runner
|
|
10
|
+
* (GIT_* stripped, hooks/fsmonitor off, no signing), with an explicit identity
|
|
11
|
+
* so no user configuration is needed.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { localGit, localGitWrite } from "../coding/local-git.js";
|
|
17
|
+
import { LOCAL_REPO_PREFIX } from "../coding/local-repo.js";
|
|
18
|
+
import { MAX_SERVICE_DECLARATION_BYTES, SERVICE_DECLARATION_PATH, ServiceDeclarationError, parseServiceDeclaration, } from "../coding/services/declaration.js";
|
|
19
|
+
import { LocalRemote } from "../providers/vcs/local-remote.js";
|
|
20
|
+
export const STARTER_SERVICES_BRANCH = "wardby/quickstart-services";
|
|
21
|
+
const STARTER_REF = `refs/heads/${STARTER_SERVICES_BRANCH}`;
|
|
22
|
+
const COMMIT_MESSAGE = "Add starter .wardby/services.yaml (wardby quickstart)";
|
|
23
|
+
const IDENTITY = {
|
|
24
|
+
GIT_AUTHOR_NAME: "wardby quickstart",
|
|
25
|
+
GIT_AUTHOR_EMAIL: "quickstart@wardby.invalid",
|
|
26
|
+
GIT_COMMITTER_NAME: "wardby quickstart",
|
|
27
|
+
GIT_COMMITTER_EMAIL: "quickstart@wardby.invalid",
|
|
28
|
+
};
|
|
29
|
+
const OBJECT_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
30
|
+
/** Catalog versions (src/coding/services/builtins.ts); the catalog pins each to an image digest. */
|
|
31
|
+
const STARTER_VERSIONS = { postgres: "16", redis: "7" };
|
|
32
|
+
export function starterServicesYaml(choices) {
|
|
33
|
+
const lines = [
|
|
34
|
+
"# Services wardby starts next to each coding run. Each name and version must",
|
|
35
|
+
"# exist in the wardby service catalog, and the agent must allow the name",
|
|
36
|
+
"# (codingProfile.services). See docs/coding-services.md.",
|
|
37
|
+
"services:",
|
|
38
|
+
...[...new Set(choices)].sort().map((name) => ` ${name}: "${STARTER_VERSIONS[name]}"`),
|
|
39
|
+
];
|
|
40
|
+
return `${lines.join("\n")}\n`;
|
|
41
|
+
}
|
|
42
|
+
/** `--starter-services postgres,redis|none`. */
|
|
43
|
+
export function parseStarterChoice(value) {
|
|
44
|
+
const names = value
|
|
45
|
+
.split(",")
|
|
46
|
+
.map((part) => part.trim().toLowerCase())
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
if (names.length === 1 && names[0] === "none")
|
|
49
|
+
return [];
|
|
50
|
+
const chosen = new Set();
|
|
51
|
+
for (const name of names) {
|
|
52
|
+
if (name !== "postgres" && name !== "redis") {
|
|
53
|
+
throw new Error(`--starter-services must list postgres, redis, or none; got "${value}".`);
|
|
54
|
+
}
|
|
55
|
+
chosen.add(name);
|
|
56
|
+
}
|
|
57
|
+
return [...chosen].sort();
|
|
58
|
+
}
|
|
59
|
+
/** The services a starter choice declares, as parseServiceDeclaration would read them back. */
|
|
60
|
+
export function starterDeclaredServices(choices) {
|
|
61
|
+
return parseServiceDeclaration(starterServicesYaml(choices));
|
|
62
|
+
}
|
|
63
|
+
/** The checked-out branch and its commit; null when HEAD is detached or the branch has no commits. */
|
|
64
|
+
export async function repoDefaultBranch(dir) {
|
|
65
|
+
let branch;
|
|
66
|
+
try {
|
|
67
|
+
branch = (await localGit(dir, ["symbolic-ref", "--quiet", "--short", "HEAD"], 64 * 1024)).trim();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (!branch)
|
|
73
|
+
return null;
|
|
74
|
+
try {
|
|
75
|
+
const sha = (await localGit(dir, ["rev-parse", "--verify", "--quiet", "--end-of-options", `refs/heads/${branch}^{commit}`])).trim();
|
|
76
|
+
return OBJECT_ID.test(sha) ? { branch, sha } : null;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** The committed `.wardby/services.yaml` at `ref`, validated; `dir` must sit inside `roots`. */
|
|
83
|
+
export async function inspectDeclaredServices(dir, roots, ref) {
|
|
84
|
+
let text;
|
|
85
|
+
try {
|
|
86
|
+
text = await new LocalRemote({ roots: () => roots }).readRepositoryFile({
|
|
87
|
+
repository: `${LOCAL_REPO_PREFIX}${dir}`,
|
|
88
|
+
ref,
|
|
89
|
+
path: SERVICE_DECLARATION_PATH,
|
|
90
|
+
maxBytes: MAX_SERVICE_DECLARATION_BYTES,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return { kind: "invalid", reason: error instanceof Error ? error.message : String(error) };
|
|
95
|
+
}
|
|
96
|
+
if (text === null)
|
|
97
|
+
return { kind: "absent" };
|
|
98
|
+
try {
|
|
99
|
+
return { kind: "valid", services: parseServiceDeclaration(text) };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof ServiceDeclarationError)
|
|
103
|
+
return { kind: "invalid", reason: error.reason };
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function currentRef(dir) {
|
|
108
|
+
try {
|
|
109
|
+
const sha = (await localGit(dir, ["rev-parse", "--verify", "--quiet", "--end-of-options", STARTER_REF])).trim();
|
|
110
|
+
return OBJECT_ID.test(sha) ? sha : null;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** True when any worktree of the repository (this one included) has the starter branch checked out. */
|
|
117
|
+
async function branchCheckedOut(dir) {
|
|
118
|
+
const listing = await localGit(dir, ["worktree", "list", "--porcelain"]);
|
|
119
|
+
return listing.split("\n").some((line) => line.trim() === `branch ${STARTER_REF}`);
|
|
120
|
+
}
|
|
121
|
+
/** The starter commit on top of `baseSha`, built in a scratch index; writes objects only, never a ref. */
|
|
122
|
+
async function buildStarterCommit(dir, baseSha, content) {
|
|
123
|
+
const scratch = await mkdtemp(join(tmpdir(), "wardby-quickstart-index-"));
|
|
124
|
+
try {
|
|
125
|
+
const env = { ...IDENTITY, GIT_INDEX_FILE: join(scratch, "index") };
|
|
126
|
+
await localGitWrite(dir, ["read-tree", "--end-of-options", baseSha], { env });
|
|
127
|
+
const blob = (await localGitWrite(dir, ["hash-object", "-w", "--no-filters", "--stdin"], { env, input: content })).trim();
|
|
128
|
+
if (!OBJECT_ID.test(blob))
|
|
129
|
+
throw new Error("git hash-object returned no object id");
|
|
130
|
+
await localGitWrite(dir, ["update-index", "--add", "--cacheinfo", `100644,${blob},${SERVICE_DECLARATION_PATH}`], {
|
|
131
|
+
env,
|
|
132
|
+
});
|
|
133
|
+
const tree = (await localGitWrite(dir, ["write-tree"], { env })).trim();
|
|
134
|
+
if (!OBJECT_ID.test(tree))
|
|
135
|
+
throw new Error("git write-tree returned no tree id");
|
|
136
|
+
const commit = (await localGitWrite(dir, ["commit-tree", "--no-gpg-sign", "-p", baseSha, "-m", COMMIT_MESSAGE, tree], { env })).trim();
|
|
137
|
+
if (!OBJECT_ID.test(commit))
|
|
138
|
+
throw new Error("git commit-tree returned no commit id");
|
|
139
|
+
return commit;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
await rm(scratch, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Commits `content` as `.wardby/services.yaml` on top of `baseSha` and points
|
|
147
|
+
* the starter branch at it. An existing branch is replaced only when
|
|
148
|
+
* `confirmReplace` says so, and only if it has not moved since it was read.
|
|
149
|
+
*/
|
|
150
|
+
export async function commitStarterServices(input) {
|
|
151
|
+
const { dir, baseSha, content } = input;
|
|
152
|
+
if (!OBJECT_ID.test(baseSha))
|
|
153
|
+
return { status: "refused", reason: "the base commit id is not valid" };
|
|
154
|
+
// Moving a checked-out branch would change that worktree's HEAD commit under it.
|
|
155
|
+
if (await branchCheckedOut(dir)) {
|
|
156
|
+
return { status: "refused", reason: `${STARTER_SERVICES_BRANCH} is checked out; switch away from it first` };
|
|
157
|
+
}
|
|
158
|
+
const existing = await currentRef(dir);
|
|
159
|
+
if (existing && !(await input.confirmReplace(existing)))
|
|
160
|
+
return { status: "kept", commit: existing };
|
|
161
|
+
let commit;
|
|
162
|
+
try {
|
|
163
|
+
commit = await buildStarterCommit(dir, baseSha, content);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const detail = (error.stderr || error.message).trim().split("\n")[0];
|
|
167
|
+
return { status: "refused", reason: `git could not build the starter commit: ${detail}` };
|
|
168
|
+
}
|
|
169
|
+
// Re-checked right before the move: a checkout since the first check would otherwise be updated under it.
|
|
170
|
+
if (await branchCheckedOut(dir)) {
|
|
171
|
+
return { status: "refused", reason: `${STARTER_SERVICES_BRANCH} is checked out; switch away from it first` };
|
|
172
|
+
}
|
|
173
|
+
const expected = existing ?? "0".repeat(baseSha.length);
|
|
174
|
+
try {
|
|
175
|
+
await localGitWrite(dir, ["update-ref", "--no-deref", "-m", "wardby quickstart", STARTER_REF, commit, expected], {
|
|
176
|
+
env: IDENTITY,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return {
|
|
181
|
+
status: "refused",
|
|
182
|
+
reason: `${STARTER_SERVICES_BRANCH} changed while quickstart was writing it (or a conflicting ref exists); left as is`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return { status: existing ? "replaced" : "created", commit };
|
|
186
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"runtime":"ghcr.io/wardby/wardby/wardby-runtime@sha256:1b6646eccf13dbe534257f641b757cd11a127ef54741cf7b7a0a56fc53de60da","worker":"ghcr.io/wardby/wardby/wardby-coding-worker@sha256:0a9d4a8f737b4b2badde4dfad5646db3cb26ead4f3783ee0c13819daf0f44b1a","claudeWorker":"ghcr.io/wardby/wardby/wardby-claude-coding-worker@sha256:36fdf3b28d60a332b170ec5bd52d7158d11cab70562d7cadffe2fa286ccff974","claudeToolRunner":"ghcr.io/wardby/wardby/wardby-claude-tool-runner@sha256:68c276dce5341bf5e8dd3723ae8ad45ae01c81065a99ee2c5ca9847ce1afe0d7"}
|
package/dist/serve.js
CHANGED
|
@@ -20,6 +20,7 @@ import { loadCodingConcurrencyConfig, loadMcpConfig } from "./config/providers.j
|
|
|
20
20
|
import { drainCodingQueue } from "./core/coding-queue.js";
|
|
21
21
|
import { prisma } from "./core/db.js";
|
|
22
22
|
import { startReconciler } from "./core/reconciler.js";
|
|
23
|
+
import { createRepoAccessGate } from "./core/repo-access.js";
|
|
23
24
|
import { startScheduler } from "./core/scheduler.js";
|
|
24
25
|
import { buildMcpProviders, startMcp } from "./mcp/index.js";
|
|
25
26
|
export async function startServe(options = {}) {
|
|
@@ -33,10 +34,22 @@ export async function startServe(options = {}) {
|
|
|
33
34
|
const concurrency = loadCodingConcurrencyConfig();
|
|
34
35
|
const providers = options.providers ?? buildMcpProviders().providers;
|
|
35
36
|
const mcp = await startMcp({ providers, schedulerAttached: true });
|
|
37
|
+
const reviewHosts = providers.reviewHosts;
|
|
36
38
|
const reconciler = startReconciler({
|
|
37
39
|
executor: providers.executor,
|
|
38
|
-
reviewHosts
|
|
40
|
+
reviewHosts,
|
|
39
41
|
issueTrackers: providers.issueTrackers,
|
|
42
|
+
...(reviewHosts
|
|
43
|
+
? {
|
|
44
|
+
deferredReviews: {
|
|
45
|
+
db: prisma,
|
|
46
|
+
executor: providers.executor,
|
|
47
|
+
hosts: reviewHosts,
|
|
48
|
+
repoAccess: providers.repoAccess ?? createRepoAccessGate({ db: prisma, hosts: reviewHosts }),
|
|
49
|
+
...(providers.issueTrackers ? { issueTrackers: providers.issueTrackers } : {}),
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
: {}),
|
|
40
53
|
});
|
|
41
54
|
const selfDefects = { db: prisma, issueTrackers: providers.issueTrackers };
|
|
42
55
|
const scheduler = startScheduler({
|