@wardby/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) 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/env.js +6 -1
  67. package/dist/generated/prisma/browser.d.ts +23 -0
  68. package/dist/generated/prisma/client.d.ts +23 -0
  69. package/dist/generated/prisma/commonInputTypes.d.ts +22 -0
  70. package/dist/generated/prisma/internal/class.d.ts +33 -0
  71. package/dist/generated/prisma/internal/class.js +4 -4
  72. package/dist/generated/prisma/internal/prismaNamespace.d.ts +274 -1
  73. package/dist/generated/prisma/internal/prismaNamespace.js +47 -2
  74. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +48 -0
  75. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +47 -2
  76. package/dist/generated/prisma/models/Agent.d.ts +422 -1
  77. package/dist/generated/prisma/models/AgentRepository.d.ts +120 -2
  78. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +42 -1
  79. package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
  80. package/dist/generated/prisma/models/DeferredReview.d.ts +1336 -0
  81. package/dist/generated/prisma/models/DeferredReview.js +1 -0
  82. package/dist/generated/prisma/models/LocalPullRequest.d.ts +1384 -0
  83. package/dist/generated/prisma/models/LocalPullRequest.js +1 -0
  84. package/dist/generated/prisma/models/LocalReview.d.ts +1315 -0
  85. package/dist/generated/prisma/models/LocalReview.js +1 -0
  86. package/dist/generated/prisma/models/Run.d.ts +223 -0
  87. package/dist/generated/prisma/models/RunHostCheck.d.ts +148 -1
  88. package/dist/generated/prisma/models.d.ts +3 -0
  89. package/dist/help-index.json +476 -37
  90. package/dist/import/neutral-schema.d.ts +2 -2
  91. package/dist/mcp/auth/access.d.ts +3 -1
  92. package/dist/mcp/auth/host-account-cli.js +2 -2
  93. package/dist/mcp/auth/repo-authorization.d.ts +6 -7
  94. package/dist/mcp/auth/repo-authorization.js +21 -0
  95. package/dist/mcp/index.js +4 -1
  96. package/dist/mcp/tools/agents.js +46 -4
  97. package/dist/mcp/tools/host-accounts.js +2 -2
  98. package/dist/mcp/tools/repositories.js +71 -12
  99. package/dist/mcp/tools/runs.js +23 -0
  100. package/dist/mcp/tools/trigger.js +153 -24
  101. package/dist/providers/engine/types.d.ts +11 -0
  102. package/dist/providers/executor/composition.js +12 -7
  103. package/dist/providers/executor/container.d.ts +40 -3
  104. package/dist/providers/executor/container.js +140 -9
  105. package/dist/providers/executor/types.d.ts +1 -1
  106. package/dist/providers/jobs/fake-kubernetes-api.d.ts +3 -2
  107. package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
  108. package/dist/providers/jobs/kubernetes-api.d.ts +3 -1
  109. package/dist/providers/jobs/kubernetes-client.d.ts +2 -1
  110. package/dist/providers/jobs/kubernetes-client.js +3 -0
  111. package/dist/providers/jobs/kubernetes-isolation.d.ts +7 -0
  112. package/dist/providers/jobs/kubernetes-isolation.js +8 -3
  113. package/dist/providers/jobs/kubernetes-preflight.d.ts +8 -0
  114. package/dist/providers/jobs/kubernetes-preflight.js +7 -0
  115. package/dist/providers/jobs/kubernetes-quota.d.ts +31 -0
  116. package/dist/providers/jobs/kubernetes-quota.js +110 -0
  117. package/dist/providers/jobs/kubernetes.d.ts +9 -0
  118. package/dist/providers/jobs/kubernetes.js +50 -0
  119. package/dist/providers/jobs/types.d.ts +7 -0
  120. package/dist/providers/review-host/ci.d.ts +7 -0
  121. package/dist/providers/review-host/ci.js +20 -0
  122. package/dist/providers/review-host/github-events.d.ts +7 -0
  123. package/dist/providers/review-host/github-events.js +31 -7
  124. package/dist/providers/review-host/github.d.ts +19 -2
  125. package/dist/providers/review-host/github.js +145 -2
  126. package/dist/providers/review-host/index.d.ts +8 -2
  127. package/dist/providers/review-host/index.js +14 -3
  128. package/dist/providers/review-host/local.d.ts +72 -0
  129. package/dist/providers/review-host/local.js +434 -0
  130. package/dist/providers/review-host/types.d.ts +65 -3
  131. package/dist/providers/review-host/types.js +5 -3
  132. package/dist/providers/vcs/git.d.ts +10 -20
  133. package/dist/providers/vcs/git.js +71 -127
  134. package/dist/providers/vcs/github-remote.d.ts +67 -0
  135. package/dist/providers/vcs/github-remote.js +201 -0
  136. package/dist/providers/vcs/github.d.ts +81 -0
  137. package/dist/providers/vcs/github.js +203 -6
  138. package/dist/providers/vcs/index.d.ts +16 -1
  139. package/dist/providers/vcs/index.js +43 -15
  140. package/dist/providers/vcs/local-remote.d.ts +44 -0
  141. package/dist/providers/vcs/local-remote.js +108 -0
  142. package/dist/providers/vcs/remote.d.ts +58 -0
  143. package/dist/providers/vcs/remote.js +1 -0
  144. package/dist/providers/vcs/routing.d.ts +33 -0
  145. package/dist/providers/vcs/routing.js +52 -0
  146. package/dist/providers/vcs/types.d.ts +12 -1
  147. package/dist/quickstart/coding-db.d.ts +11 -0
  148. package/dist/quickstart/coding-db.js +75 -0
  149. package/dist/quickstart/coding-doctor.d.ts +13 -0
  150. package/dist/quickstart/coding-doctor.js +88 -0
  151. package/dist/quickstart/coding-images.d.ts +15 -0
  152. package/dist/quickstart/coding-images.js +79 -0
  153. package/dist/quickstart/coding-seed.d.ts +63 -0
  154. package/dist/quickstart/coding-seed.js +85 -0
  155. package/dist/quickstart/coding.d.ts +66 -0
  156. package/dist/quickstart/coding.js +430 -0
  157. package/dist/quickstart/images.d.ts +19 -0
  158. package/dist/quickstart/images.js +63 -0
  159. package/dist/quickstart/index.d.ts +6 -0
  160. package/dist/quickstart/index.js +72 -40
  161. package/dist/quickstart/starter-services.d.ts +45 -0
  162. package/dist/quickstart/starter-services.js +186 -0
  163. package/dist/quickstart-images.json +1 -0
  164. package/dist/serve.js +14 -1
  165. package/dist/viewer/api-schema.d.ts +176 -0
  166. package/dist/viewer/api-schema.js +27 -2
  167. package/dist/viewer/graph.d.ts +7 -2
  168. package/dist/viewer/graph.js +25 -11
  169. package/dist/viewer/http.d.ts +7 -1
  170. package/dist/viewer/http.js +8 -3
  171. package/dist/viewer/infra.d.ts +2 -0
  172. package/dist/viewer/infra.js +23 -0
  173. package/dist/viewer/run-detail.d.ts +2 -1
  174. package/dist/viewer/run-detail.js +2 -2
  175. package/docs/agent-recipes.md +123 -2
  176. package/docs/code-review-agents.md +293 -38
  177. package/docs/coding-agent-setup.md +141 -2
  178. package/docs/coding-packages.md +21 -0
  179. package/docs/coding-services.md +3 -0
  180. package/docs/coding-worker-isolation.md +87 -24
  181. package/docs/getting-started-gke.md +24 -0
  182. package/docs/getting-started.md +82 -8
  183. package/docs/jira-agents.md +26 -5
  184. package/docs/security-deployment.md +9 -0
  185. package/docs/viewer-api.md +21 -2
  186. package/help/admin-viewer.md +11 -1
  187. package/help/agent-recipes.md +25 -1
  188. package/help/builder-agent.md +4 -0
  189. package/help/code-review-agents.md +82 -2
  190. package/help/coding-packages.md +12 -1
  191. package/help/coding-services.md +4 -0
  192. package/help/cost-attribution.md +3 -1
  193. package/help/creating-agents.md +3 -0
  194. package/help/deploy-gke.md +2 -1
  195. package/help/errors/coding-provider-not-configured.md +60 -0
  196. package/help/errors/coding-turn-limit.md +25 -0
  197. package/help/errors/continuation-closed.md +75 -0
  198. package/help/errors/local-branch-conflict.md +37 -0
  199. package/help/errors/local-path-invalid.md +30 -0
  200. package/help/errors/local-ref-invalid.md +36 -0
  201. package/help/errors/local-ref-not-found.md +41 -0
  202. package/help/errors/local-repo-not-allowed.md +46 -0
  203. package/help/errors/local-repo-not-found.md +39 -0
  204. package/help/errors/model-unavailable.md +7 -6
  205. package/help/errors/vcs-github-not-configured.md +34 -0
  206. package/help/getting-started.md +4 -0
  207. package/help/github.md +22 -5
  208. package/help/jira.md +9 -2
  209. package/help/local-repositories.md +182 -0
  210. package/help/related-pull-requests.md +89 -0
  211. package/help/review-fix-rounds.md +69 -0
  212. package/help/troubleshooting/budgets.md +11 -0
  213. package/help/troubleshooting/coding-workers.md +22 -0
  214. package/help/troubleshooting/repository-access.md +5 -0
  215. package/package.json +5 -3
  216. package/prisma/migrations/20261004100000_delegations_and_coding_turns/migration.sql +12 -0
  217. package/prisma/migrations/20261005000000_review_fix_rounds/migration.sql +5 -0
  218. package/prisma/migrations/20261006000000_parallel_delegations/migration.sql +6 -0
  219. package/prisma/migrations/20261007000000_ci_rereview/migration.sql +4 -0
  220. package/prisma/migrations/20261008000000_review_after_ci/migration.sql +33 -0
  221. package/prisma/migrations/20261009000001_coding_run_local_branch/migration.sql +7 -0
  222. package/prisma/migrations/20261009000002_local_review/migration.sql +50 -0
  223. 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;
@@ -263,6 +292,16 @@ function runDemo(paths) {
263
292
  function clientInstalled(command) {
264
293
  return runCommand(command, ["--version"]).status === 0;
265
294
  }
295
+ /**
296
+ * Arguments for `<client> mcp add`. The server name comes right after `add`:
297
+ * Claude Code's `-e/--env <env...>` is variadic and would otherwise read the
298
+ * name as another KEY=value ("Invalid environment variable format: wardby").
299
+ */
300
+ export function mcpAddArgs(client, projectDir, packageSpec) {
301
+ const env = `WARDBY_PROJECT_DIR=${projectDir}`;
302
+ const options = client === "codex" ? ["--env", env] : ["--scope", "local", "-e", env];
303
+ return ["mcp", "add", "wardby", ...options, "--", "npx", "--yes", packageSpec, "mcp"];
304
+ }
266
305
  function configureOneMcpClient(client, paths) {
267
306
  if (!clientInstalled(client)) {
268
307
  console.warn(`! ${client} is not installed; skipped its MCP configuration.`);
@@ -273,34 +312,7 @@ function configureOneMcpClient(client, paths) {
273
312
  console.log(`✓ ${client} already has an MCP server named wardby.`);
274
313
  return true;
275
314
  }
276
- const packageSpec = `@wardby/cli@${packageJson.version}`;
277
- const args = client === "codex"
278
- ? [
279
- "mcp",
280
- "add",
281
- "--env",
282
- `WARDBY_PROJECT_DIR=${paths.projectDir}`,
283
- "wardby",
284
- "--",
285
- "npx",
286
- "--yes",
287
- packageSpec,
288
- "mcp",
289
- ]
290
- : [
291
- "mcp",
292
- "add",
293
- "--scope",
294
- "local",
295
- "-e",
296
- `WARDBY_PROJECT_DIR=${paths.projectDir}`,
297
- "wardby",
298
- "--",
299
- "npx",
300
- "--yes",
301
- packageSpec,
302
- "mcp",
303
- ];
315
+ const args = mcpAddArgs(client, paths.projectDir, `@wardby/cli@${packageJson.version}`);
304
316
  const result = runCommand(client, args, { cwd: paths.projectDir });
305
317
  if (result.status !== 0)
306
318
  commandFailure(`${client} MCP configuration`, result);
@@ -333,7 +345,12 @@ async function chooseClient(nonInteractive) {
333
345
  export async function quickstartCommand(args) {
334
346
  const { values } = parseArgs({
335
347
  args,
348
+ allowNegative: true,
336
349
  options: {
350
+ coding: { type: "boolean" },
351
+ trust: { type: "string", multiple: true },
352
+ "coding-provider": { type: "string" },
353
+ "starter-services": { type: "string" },
337
354
  provider: { type: "string" },
338
355
  model: { type: "string" },
339
356
  budget: { type: "string" },
@@ -344,6 +361,13 @@ export async function quickstartCommand(args) {
344
361
  },
345
362
  });
346
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
+ };
347
371
  const skipDemo = values["skip-demo"] ?? false;
348
372
  const budget = values.budget === undefined ? 1 : Number(values.budget);
349
373
  if (!Number.isFinite(budget) || budget <= 0)
@@ -415,6 +439,7 @@ export async function quickstartCommand(args) {
415
439
  else
416
440
  console.log(`Skipped the billed run. Start it later with: npx @wardby/cli@latest run ${DEMO_AGENT}`);
417
441
  }
442
+ await codingStep(paths, state, codingOptions, codingDeps(paths));
418
443
  const client = parseClient(values.client) ?? (await chooseClient(nonInteractive));
419
444
  const mcpConfigured = client !== "none" && configureMcpClients(client, paths);
420
445
  console.log("\nWardby is ready.");
@@ -498,6 +523,10 @@ export async function doctorCommand(args) {
498
523
  failed ||= !ok;
499
524
  console.log(`${ok ? "✓" : "✗"} ${name}${detail ? ` (${detail})` : ""}`);
500
525
  }
526
+ for (const line of await codingSection(paths)) {
527
+ failed ||= line.startsWith("✗");
528
+ console.log(line);
529
+ }
501
530
  if (failed)
502
531
  process.exitCode = 1;
503
532
  }
@@ -517,6 +546,9 @@ export async function statusCommand(args) {
517
546
  process.stdout.write(ps.stdout);
518
547
  const healthy = await databaseHealthy(paths);
519
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"));
520
552
  if (!healthy)
521
553
  process.exitCode = 1;
522
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:b6d6403338725abc9fc109c5a026e654c88e72ae8db43788db0cfa86f18c1635","worker":"ghcr.io/wardby/wardby/wardby-coding-worker@sha256:45acf6f2ac4b43b46dd3b9e1a89cd3e3369fd4898f0ce177cf063806f98f65d1","claudeWorker":"ghcr.io/wardby/wardby/wardby-claude-coding-worker@sha256:701abc30b689aed61b8d2028523d53847cbc33dea23ce16ddd22b302a5d964e2","claudeToolRunner":"ghcr.io/wardby/wardby/wardby-claude-tool-runner@sha256:7c4574fffc1adba27d3c86949b62d8f8a95f8198fbbca0710ed68e812ad2f357"}
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({