@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.
Files changed (221) hide show
  1. package/.env.example +8 -1
  2. package/README.md +3 -1
  3. package/deploy/local/docker-compose.quickstart-coding.yml +27 -0
  4. package/dist/claude-coding-worker/driver.d.ts +2 -0
  5. package/dist/claude-coding-worker/driver.js +19 -3
  6. package/dist/claude-coding-worker/sdk.js +1 -1
  7. package/dist/cli.js +41 -22
  8. package/dist/coding/continuation-wording.d.ts +10 -0
  9. package/dist/coding/continuation-wording.js +13 -0
  10. package/dist/coding/docker-preflight.d.ts +12 -0
  11. package/dist/coding/docker-preflight.js +40 -0
  12. package/dist/coding/local-git.d.ts +22 -0
  13. package/dist/coding/local-git.js +102 -0
  14. package/dist/coding/local-repo-status.d.ts +7 -0
  15. package/dist/coding/local-repo-status.js +56 -0
  16. package/dist/coding/local-repo.d.ts +17 -0
  17. package/dist/coding/local-repo.js +65 -0
  18. package/dist/coding/observability.d.ts +1 -1
  19. package/dist/coding/observability.js +1 -0
  20. package/dist/coding/profile.d.ts +6 -0
  21. package/dist/coding/profile.js +13 -4
  22. package/dist/coding/protocol.d.ts +32 -7
  23. package/dist/coding/protocol.js +66 -4
  24. package/dist/coding-worker/artifact.d.ts +1 -0
  25. package/dist/coding-worker/errors.js +2 -0
  26. package/dist/config/providers.d.ts +13 -0
  27. package/dist/config/providers.js +21 -9
  28. package/dist/core/attribution.d.ts +13 -0
  29. package/dist/core/attribution.js +30 -0
  30. package/dist/core/budget-groups.d.ts +3 -1
  31. package/dist/core/budget-groups.js +53 -5
  32. package/dist/core/ci-context.d.ts +8 -0
  33. package/dist/core/ci-context.js +59 -0
  34. package/dist/core/delegation-siblings.d.ts +22 -0
  35. package/dist/core/delegation-siblings.js +40 -0
  36. package/dist/core/dispatch.d.ts +28 -4
  37. package/dist/core/dispatch.js +57 -15
  38. package/dist/core/engine-native.d.ts +16 -0
  39. package/dist/core/engine-native.js +49 -2
  40. package/dist/core/host-events.d.ts +25 -2
  41. package/dist/core/host-events.js +361 -19
  42. package/dist/core/host-status.d.ts +8 -3
  43. package/dist/core/host-status.js +38 -7
  44. package/dist/core/issue-events.d.ts +2 -6
  45. package/dist/core/issue-events.js +13 -19
  46. package/dist/core/pull-request-state-sync.d.ts +17 -0
  47. package/dist/core/pull-request-state-sync.js +53 -0
  48. package/dist/core/reconciler.d.ts +10 -1
  49. package/dist/core/reconciler.js +15 -2
  50. package/dist/core/related-pull-requests.d.ts +95 -0
  51. package/dist/core/related-pull-requests.js +338 -0
  52. package/dist/core/repo-access.d.ts +5 -1
  53. package/dist/core/repo-access.js +17 -1
  54. package/dist/core/review-fix-ledger.d.ts +20 -0
  55. package/dist/core/review-fix-ledger.js +41 -0
  56. package/dist/core/review-fix.d.ts +60 -0
  57. package/dist/core/review-fix.js +155 -0
  58. package/dist/core/review-host-tools.d.ts +13 -2
  59. package/dist/core/review-host-tools.js +72 -11
  60. package/dist/core/runner.d.ts +2 -2
  61. package/dist/core/runner.js +379 -184
  62. package/dist/core/serial-gate.d.ts +11 -0
  63. package/dist/core/serial-gate.js +19 -0
  64. package/dist/core/timing.d.ts +7 -0
  65. package/dist/core/timing.js +7 -0
  66. package/dist/generated/prisma/browser.d.ts +23 -0
  67. package/dist/generated/prisma/client.d.ts +23 -0
  68. package/dist/generated/prisma/commonInputTypes.d.ts +22 -0
  69. package/dist/generated/prisma/internal/class.d.ts +33 -0
  70. package/dist/generated/prisma/internal/class.js +4 -4
  71. package/dist/generated/prisma/internal/prismaNamespace.d.ts +274 -1
  72. package/dist/generated/prisma/internal/prismaNamespace.js +47 -2
  73. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +48 -0
  74. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +47 -2
  75. package/dist/generated/prisma/models/Agent.d.ts +422 -1
  76. package/dist/generated/prisma/models/AgentRepository.d.ts +120 -2
  77. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +42 -1
  78. package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
  79. package/dist/generated/prisma/models/DeferredReview.d.ts +1336 -0
  80. package/dist/generated/prisma/models/DeferredReview.js +1 -0
  81. package/dist/generated/prisma/models/LocalPullRequest.d.ts +1384 -0
  82. package/dist/generated/prisma/models/LocalPullRequest.js +1 -0
  83. package/dist/generated/prisma/models/LocalReview.d.ts +1315 -0
  84. package/dist/generated/prisma/models/LocalReview.js +1 -0
  85. package/dist/generated/prisma/models/Run.d.ts +223 -0
  86. package/dist/generated/prisma/models/RunHostCheck.d.ts +148 -1
  87. package/dist/generated/prisma/models.d.ts +3 -0
  88. package/dist/help-index.json +486 -37
  89. package/dist/import/neutral-schema.d.ts +2 -2
  90. package/dist/mcp/auth/access.d.ts +3 -1
  91. package/dist/mcp/auth/host-account-cli.js +2 -2
  92. package/dist/mcp/auth/repo-authorization.d.ts +6 -7
  93. package/dist/mcp/auth/repo-authorization.js +21 -0
  94. package/dist/mcp/index.js +4 -1
  95. package/dist/mcp/tools/agents.js +46 -4
  96. package/dist/mcp/tools/host-accounts.js +2 -2
  97. package/dist/mcp/tools/repositories.js +71 -12
  98. package/dist/mcp/tools/runs.js +23 -0
  99. package/dist/mcp/tools/trigger.js +153 -24
  100. package/dist/providers/engine/types.d.ts +11 -0
  101. package/dist/providers/executor/composition.js +12 -7
  102. package/dist/providers/executor/container.d.ts +40 -3
  103. package/dist/providers/executor/container.js +140 -9
  104. package/dist/providers/executor/types.d.ts +1 -1
  105. package/dist/providers/jobs/fake-kubernetes-api.d.ts +3 -2
  106. package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
  107. package/dist/providers/jobs/kubernetes-api.d.ts +3 -1
  108. package/dist/providers/jobs/kubernetes-client.d.ts +2 -1
  109. package/dist/providers/jobs/kubernetes-client.js +3 -0
  110. package/dist/providers/jobs/kubernetes-isolation.d.ts +7 -0
  111. package/dist/providers/jobs/kubernetes-isolation.js +8 -3
  112. package/dist/providers/jobs/kubernetes-preflight.d.ts +8 -0
  113. package/dist/providers/jobs/kubernetes-preflight.js +7 -0
  114. package/dist/providers/jobs/kubernetes-quota.d.ts +31 -0
  115. package/dist/providers/jobs/kubernetes-quota.js +110 -0
  116. package/dist/providers/jobs/kubernetes.d.ts +9 -0
  117. package/dist/providers/jobs/kubernetes.js +50 -0
  118. package/dist/providers/jobs/types.d.ts +7 -0
  119. package/dist/providers/review-host/ci.d.ts +7 -0
  120. package/dist/providers/review-host/ci.js +20 -0
  121. package/dist/providers/review-host/github-events.d.ts +7 -0
  122. package/dist/providers/review-host/github-events.js +31 -7
  123. package/dist/providers/review-host/github.d.ts +19 -2
  124. package/dist/providers/review-host/github.js +145 -2
  125. package/dist/providers/review-host/index.d.ts +8 -2
  126. package/dist/providers/review-host/index.js +14 -3
  127. package/dist/providers/review-host/local.d.ts +72 -0
  128. package/dist/providers/review-host/local.js +434 -0
  129. package/dist/providers/review-host/types.d.ts +65 -3
  130. package/dist/providers/review-host/types.js +5 -3
  131. package/dist/providers/vcs/git.d.ts +10 -20
  132. package/dist/providers/vcs/git.js +71 -127
  133. package/dist/providers/vcs/github-remote.d.ts +67 -0
  134. package/dist/providers/vcs/github-remote.js +201 -0
  135. package/dist/providers/vcs/github.d.ts +81 -0
  136. package/dist/providers/vcs/github.js +203 -6
  137. package/dist/providers/vcs/index.d.ts +16 -1
  138. package/dist/providers/vcs/index.js +43 -15
  139. package/dist/providers/vcs/local-remote.d.ts +44 -0
  140. package/dist/providers/vcs/local-remote.js +108 -0
  141. package/dist/providers/vcs/remote.d.ts +58 -0
  142. package/dist/providers/vcs/remote.js +1 -0
  143. package/dist/providers/vcs/routing.d.ts +33 -0
  144. package/dist/providers/vcs/routing.js +52 -0
  145. package/dist/providers/vcs/types.d.ts +12 -1
  146. package/dist/quickstart/coding-db.d.ts +11 -0
  147. package/dist/quickstart/coding-db.js +75 -0
  148. package/dist/quickstart/coding-doctor.d.ts +13 -0
  149. package/dist/quickstart/coding-doctor.js +88 -0
  150. package/dist/quickstart/coding-images.d.ts +15 -0
  151. package/dist/quickstart/coding-images.js +79 -0
  152. package/dist/quickstart/coding-seed.d.ts +63 -0
  153. package/dist/quickstart/coding-seed.js +85 -0
  154. package/dist/quickstart/coding.d.ts +66 -0
  155. package/dist/quickstart/coding.js +430 -0
  156. package/dist/quickstart/images.d.ts +19 -0
  157. package/dist/quickstart/images.js +63 -0
  158. package/dist/quickstart/index.js +61 -12
  159. package/dist/quickstart/starter-services.d.ts +45 -0
  160. package/dist/quickstart/starter-services.js +186 -0
  161. package/dist/quickstart-images.json +1 -0
  162. package/dist/serve.js +14 -1
  163. package/dist/viewer/api-schema.d.ts +176 -0
  164. package/dist/viewer/api-schema.js +27 -2
  165. package/dist/viewer/graph.d.ts +7 -2
  166. package/dist/viewer/graph.js +25 -11
  167. package/dist/viewer/http.d.ts +7 -1
  168. package/dist/viewer/http.js +8 -3
  169. package/dist/viewer/infra.d.ts +2 -0
  170. package/dist/viewer/infra.js +23 -0
  171. package/dist/viewer/run-detail.d.ts +2 -1
  172. package/dist/viewer/run-detail.js +2 -2
  173. package/docs/agent-recipes.md +127 -5
  174. package/docs/code-review-agents.md +293 -38
  175. package/docs/coding-agent-setup.md +141 -2
  176. package/docs/coding-packages.md +21 -0
  177. package/docs/coding-services.md +3 -0
  178. package/docs/coding-worker-isolation.md +56 -20
  179. package/docs/getting-started-gke.md +24 -0
  180. package/docs/getting-started.md +196 -10
  181. package/docs/jira-agents.md +26 -5
  182. package/docs/security-deployment.md +9 -0
  183. package/docs/viewer-api.md +21 -2
  184. package/help/admin-viewer.md +11 -1
  185. package/help/agent-recipes.md +30 -4
  186. package/help/builder-agent.md +4 -0
  187. package/help/code-review-agents.md +82 -2
  188. package/help/coding-packages.md +12 -1
  189. package/help/coding-services.md +4 -0
  190. package/help/cost-attribution.md +3 -1
  191. package/help/creating-agents.md +3 -0
  192. package/help/deploy-gke.md +2 -1
  193. package/help/errors/coding-provider-not-configured.md +60 -0
  194. package/help/errors/coding-turn-limit.md +25 -0
  195. package/help/errors/continuation-closed.md +75 -0
  196. package/help/errors/local-branch-conflict.md +37 -0
  197. package/help/errors/local-path-invalid.md +30 -0
  198. package/help/errors/local-ref-invalid.md +36 -0
  199. package/help/errors/local-ref-not-found.md +41 -0
  200. package/help/errors/local-repo-not-allowed.md +46 -0
  201. package/help/errors/local-repo-not-found.md +39 -0
  202. package/help/errors/model-unavailable.md +7 -6
  203. package/help/errors/vcs-github-not-configured.md +34 -0
  204. package/help/getting-started.md +28 -0
  205. package/help/github.md +22 -5
  206. package/help/jira.md +9 -2
  207. package/help/local-repositories.md +182 -0
  208. package/help/related-pull-requests.md +89 -0
  209. package/help/review-fix-rounds.md +69 -0
  210. package/help/troubleshooting/budgets.md +11 -0
  211. package/help/troubleshooting/coding-workers.md +22 -0
  212. package/help/troubleshooting/repository-access.md +5 -0
  213. package/package.json +3 -2
  214. package/prisma/migrations/20261004100000_delegations_and_coding_turns/migration.sql +12 -0
  215. package/prisma/migrations/20261005000000_review_fix_rounds/migration.sql +5 -0
  216. package/prisma/migrations/20261006000000_parallel_delegations/migration.sql +6 -0
  217. package/prisma/migrations/20261007000000_ci_rereview/migration.sql +4 -0
  218. package/prisma/migrations/20261008000000_review_after_ci/migration.sql +33 -0
  219. package/prisma/migrations/20261009000001_coding_run_local_branch/migration.sql +7 -0
  220. package/prisma/migrations/20261009000002_local_review/migration.sql +50 -0
  221. package/prisma/schema.prisma +98 -1
@@ -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
- return [
38
- "compose",
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: providers.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({