@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
@@ -0,0 +1,58 @@
1
+ import type { FinalizeChangesDetails, PreparedWorkspace, VcsProvider } from "./types.js";
2
+ /** What a remote needs to publish a pushed commit (GitHub: open or find the draft PR). */
3
+ export interface PublishInput {
4
+ /** The validated workspace whose headRef was just pushed. */
5
+ workspace: PreparedWorkspace;
6
+ details?: FinalizeChangesDetails;
7
+ /** Every path the commit changes relative to baseCommit (already validated). */
8
+ changedPaths: readonly string[];
9
+ }
10
+ export type PublishResult = {
11
+ kind: "pull_request";
12
+ number: number;
13
+ url: string;
14
+ } | {
15
+ kind: "branch";
16
+ branch: string;
17
+ };
18
+ /**
19
+ * The remote-specific half of GitVcsProvider: where a repository is cloned
20
+ * from and pushed to, how git authenticates, and what happens after a push.
21
+ * Everything else (workspace layout, hardened git config, finalize checks,
22
+ * fetch-compare-push) stays in GitVcsProvider and applies to every remote.
23
+ */
24
+ export interface GitRemote {
25
+ /** Extra `-c` pairs appended after HARDENED_GIT_CONFIG for clone/push/ls-remote (GitHub: none). */
26
+ readonly gitConfig: readonly string[];
27
+ /** Syntactic normalization of a repository id for this remote (GitHub: owner/name; local: local:/abs/path). */
28
+ normalizeRepository(repository: string): string;
29
+ cloneUrl(repository: string): string;
30
+ /** Runs fn with an auth token for git (GitHub: installation token; local: undefined). */
31
+ withAccess<T>(repository: string, fn: (token: string | undefined) => Promise<T>): Promise<T>;
32
+ /** Continuation precondition (GitHub: the PR is still open). */
33
+ assertContinuationOpen(input: {
34
+ repository: string;
35
+ baseRef: string;
36
+ headRef: string;
37
+ continuation?: {
38
+ rootRunId: string;
39
+ };
40
+ }): Promise<void>;
41
+ /** After a successful push: GitHub opens or finds the draft PR; local returns the branch. */
42
+ publish(input: PublishInput): Promise<PublishResult>;
43
+ /** The error pushOnce throws when the remote branch moved since the clone. */
44
+ conflictError(): Error;
45
+ /**
46
+ * When set, prepareWorkspace runs `ls-remote --heads` for the branch it is
47
+ * about to clone and throws this error if the branch is absent, instead of
48
+ * leaving a missing ref to surface as an opaque clone failure (GitHub: unset).
49
+ */
50
+ refNotFoundError?(ref: string): Error;
51
+ /** When set, called with headRef before any push; throws to refuse writing that ref (GitHub: unset). */
52
+ assertPushRef?(headRef: string): void;
53
+ /** When set, called inside withAccess right before the push; throws to refuse it (GitHub: unset). */
54
+ beforePush?(repository: string, headRef: string): Promise<void>;
55
+ notifyContinuationStarted?: VcsProvider["notifyContinuationStarted"];
56
+ notifyContinuationFinished?: VcsProvider["notifyContinuationFinished"];
57
+ readRepositoryFile?: VcsProvider["readRepositoryFile"];
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { RepositoryFileInput } from "./github.js";
2
+ import type { ContinuationFinishedDetails, ContinuationOutcome, FinalizeChangesDetails, FinalizeChangesResult, PreparedWorkspace, VcsPrepareInput, VcsProvider } from "./types.js";
3
+ /** Keeps the `vcs_github_not_configured` code prefix that error matching and categorization key on. */
4
+ export declare const VCS_GITHUB_NOT_CONFIGURED_ERROR = "vcs_github_not_configured: set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY to run coding agents on GitHub repositories";
5
+ export interface RoutingVcsOptions {
6
+ /** Null when no GitHub App is configured. */
7
+ github: VcsProvider | null;
8
+ /** Null when no local repository roots are configured. */
9
+ local: VcsProvider | null;
10
+ }
11
+ /**
12
+ * One VcsProvider over both repository kinds: `local:/abs/path` goes to the
13
+ * local provider, everything else to GitHub. A kind that is not configured
14
+ * fails only when a repository of that kind is actually used, so a server with
15
+ * only one of the two still starts. The repository name is not re-resolved
16
+ * here: the local provider verifies it against the trusted roots.
17
+ */
18
+ export declare class RoutingVcsProvider implements VcsProvider {
19
+ private readonly options;
20
+ constructor(options: RoutingVcsOptions);
21
+ private target;
22
+ /** The target when configured, else null (best-effort calls that must never throw). */
23
+ private optionalTarget;
24
+ prepareWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace>;
25
+ recoverWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace | null>;
26
+ finalizeChanges(workspace: PreparedWorkspace, details?: FinalizeChangesDetails): Promise<FinalizeChangesResult>;
27
+ cleanup(workspace: PreparedWorkspace): Promise<void>;
28
+ notifyContinuationStarted(workspace: PreparedWorkspace, details?: {
29
+ agentName?: string;
30
+ }): Promise<void>;
31
+ notifyContinuationFinished(workspace: PreparedWorkspace, outcome: ContinuationOutcome, details?: ContinuationFinishedDetails): Promise<void>;
32
+ readRepositoryFile(input: RepositoryFileInput): Promise<string | null>;
33
+ }
@@ -0,0 +1,52 @@
1
+ import { LocalRepoError, isLocalRepository } from "../../coding/local-repo.js";
2
+ /** Keeps the `vcs_github_not_configured` code prefix that error matching and categorization key on. */
3
+ export const VCS_GITHUB_NOT_CONFIGURED_ERROR = "vcs_github_not_configured: set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY to run coding agents on GitHub repositories";
4
+ /**
5
+ * One VcsProvider over both repository kinds: `local:/abs/path` goes to the
6
+ * local provider, everything else to GitHub. A kind that is not configured
7
+ * fails only when a repository of that kind is actually used, so a server with
8
+ * only one of the two still starts. The repository name is not re-resolved
9
+ * here: the local provider verifies it against the trusted roots.
10
+ */
11
+ export class RoutingVcsProvider {
12
+ options;
13
+ constructor(options) {
14
+ this.options = options;
15
+ }
16
+ target(repository) {
17
+ if (isLocalRepository(repository)) {
18
+ if (!this.options.local) {
19
+ throw new LocalRepoError("local_repo_not_allowed", "no local repository roots are configured (LOCAL_REPO_ROOTS)");
20
+ }
21
+ return this.options.local;
22
+ }
23
+ if (!this.options.github)
24
+ throw new Error(VCS_GITHUB_NOT_CONFIGURED_ERROR);
25
+ return this.options.github;
26
+ }
27
+ /** The target when configured, else null (best-effort calls that must never throw). */
28
+ optionalTarget(repository) {
29
+ return (isLocalRepository(repository) ? this.options.local : this.options.github) ?? null;
30
+ }
31
+ async prepareWorkspace(input) {
32
+ return this.target(input.repository).prepareWorkspace(input);
33
+ }
34
+ async recoverWorkspace(input) {
35
+ return this.target(input.repository).recoverWorkspace(input);
36
+ }
37
+ async finalizeChanges(workspace, details) {
38
+ return this.target(workspace.repository).finalizeChanges(workspace, details);
39
+ }
40
+ async cleanup(workspace) {
41
+ return this.target(workspace.repository).cleanup(workspace);
42
+ }
43
+ async notifyContinuationStarted(workspace, details) {
44
+ await this.optionalTarget(workspace.repository)?.notifyContinuationStarted?.(workspace, details);
45
+ }
46
+ async notifyContinuationFinished(workspace, outcome, details) {
47
+ await this.optionalTarget(workspace.repository)?.notifyContinuationFinished?.(workspace, outcome, details);
48
+ }
49
+ async readRepositoryFile(input) {
50
+ return (await this.optionalTarget(input.repository)?.readRepositoryFile?.(input)) ?? null;
51
+ }
52
+ }
@@ -1,4 +1,4 @@
1
- import type { RepositoryFileInput } from "./github.js";
1
+ import type { RelatedPullRequestsInput, RepositoryFileInput } from "./github.js";
2
2
  export interface VcsPrepareInput {
3
3
  runId: string;
4
4
  repository: string;
@@ -71,6 +71,8 @@ export interface FinalizeChangesDetails {
71
71
  url?: string;
72
72
  trackerName?: string;
73
73
  };
74
+ /** The request's other pull requests (control-plane rows): the PR body's Related pull requests section. */
75
+ related?: RelatedPullRequestsInput;
74
76
  }
75
77
  export type FinalizeChangesResult = {
76
78
  outcome: "no_changes";
@@ -97,6 +99,15 @@ export type FinalizeChangesResult = {
97
99
  commitSha: string;
98
100
  pullRequestNumber: number;
99
101
  pullRequestUrl: string;
102
+ } | {
103
+ /** Pushed the run's branch to a remote with no pull requests (a local
104
+ * repository): headRef now points at commitSha there. */
105
+ outcome: "branch_pushed";
106
+ repository: string;
107
+ baseRef: string;
108
+ baseCommit: string;
109
+ headRef: string;
110
+ commitSha: string;
100
111
  };
101
112
  export interface VcsProvider {
102
113
  prepareWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace>;
@@ -0,0 +1,11 @@
1
+ import type { DeclaredService } from "../coding/services/declaration.js";
2
+ import type { LocalAgent } from "./coding-doctor.js";
3
+ import { type CodingSeedInput, type CodingSeedResult } from "./coding-seed.js";
4
+ import { type QuickstartPaths } from "./config.js";
5
+ export declare function prismaSeedCodingAgents(paths: QuickstartPaths, input: Omit<CodingSeedInput, "ownerId">): Promise<CodingSeedResult>;
6
+ export declare function prismaCatalogImages(paths: QuickstartPaths, services: DeclaredService[]): Promise<Array<{
7
+ service: DeclaredService;
8
+ image?: string;
9
+ }>>;
10
+ /** Coding agents with a local repository, and native agents with a local link. */
11
+ export declare function prismaLocalAgents(paths: QuickstartPaths): Promise<LocalAgent[]>;
@@ -0,0 +1,75 @@
1
+ import { LOCAL_REPO_PREFIX } from "../coding/local-repo.js";
2
+ import { seedCodingAgents } from "./coding-seed.js";
3
+ import { readQuickstartEnv } from "./config.js";
4
+ async function withDb(paths, use) {
5
+ const { createPrismaClient } = await import("../core/db.js");
6
+ const env = readQuickstartEnv(paths);
7
+ const db = createPrismaClient(env.DATABASE_URL);
8
+ try {
9
+ return await use(db, env);
10
+ }
11
+ finally {
12
+ await db.$disconnect();
13
+ }
14
+ }
15
+ function seedDb(db) {
16
+ return {
17
+ findAgent: (name) => db.agent.findUnique({ where: { name }, select: { id: true, kind: true, systemPrompt: true } }),
18
+ createAgent: (data) => db.agent.create({ data, select: { id: true } }),
19
+ updateAgent: async (id, data) => {
20
+ await db.agent.update({ where: { id }, data });
21
+ },
22
+ upsertCodingProfile: async (agentId, profile) => {
23
+ const fields = profile;
24
+ await db.codingAgentProfile.upsert({ where: { agentId }, create: { agentId, ...fields }, update: fields });
25
+ },
26
+ upsertLocalLink: async (agentId, link) => {
27
+ const fields = { access: link.access, triggers: [], checkName: null, ...link.stamp };
28
+ await db.agentRepository.upsert({
29
+ where: { agentId_provider_repository: { agentId, provider: "local", repository: link.repository } },
30
+ create: { agentId, provider: "local", repository: link.repository, ...fields },
31
+ update: fields,
32
+ });
33
+ },
34
+ };
35
+ }
36
+ export async function prismaSeedCodingAgents(paths, input) {
37
+ const { resolvePrincipal } = await import("../mcp/auth/principal.js");
38
+ return await withDb(paths, async (db, env) => {
39
+ const owner = await resolvePrincipal(env.LOCAL_PRINCIPAL || "local", db);
40
+ return await seedCodingAgents(seedDb(db), { ...input, ownerId: owner.id });
41
+ });
42
+ }
43
+ export async function prismaCatalogImages(paths, services) {
44
+ if (services.length === 0)
45
+ return [];
46
+ return await withDb(paths, async (db) => {
47
+ const rows = await db.codingService.findMany({
48
+ where: { OR: services.map(({ name, version }) => ({ name, version })) },
49
+ select: { name: true, version: true, image: true },
50
+ });
51
+ return services.map((service) => ({
52
+ service,
53
+ image: rows.find((row) => row.name === service.name && row.version === service.version)?.image,
54
+ }));
55
+ });
56
+ }
57
+ /** Coding agents with a local repository, and native agents with a local link. */
58
+ export async function prismaLocalAgents(paths) {
59
+ return await withDb(paths, async (db) => {
60
+ const [coding, links] = await Promise.all([
61
+ db.codingAgentProfile.findMany({
62
+ where: { repository: { startsWith: LOCAL_REPO_PREFIX } },
63
+ select: { repository: true, baseRef: true, agent: { select: { name: true } } },
64
+ }),
65
+ db.agentRepository.findMany({
66
+ where: { provider: "local" },
67
+ select: { repository: true, agent: { select: { name: true } } },
68
+ }),
69
+ ]);
70
+ return [
71
+ ...coding.map((row) => ({ name: row.agent.name, repository: row.repository, ref: row.baseRef })),
72
+ ...links.map((row) => ({ name: row.agent.name, repository: row.repository })),
73
+ ].sort((a, b) => a.name.localeCompare(b.name));
74
+ });
75
+ }
@@ -0,0 +1,13 @@
1
+ import { type RunCommand } from "./coding.js";
2
+ export interface LocalAgent {
3
+ name: string;
4
+ /** `local:/abs/path`. */
5
+ repository: string;
6
+ /** The branch whose services.yaml applies (a coding agent's baseRef); default: the checked-out branch. */
7
+ ref?: string;
8
+ }
9
+ export interface CodingDoctorDeps {
10
+ run: RunCommand;
11
+ listLocalAgents: () => Promise<LocalAgent[]>;
12
+ }
13
+ export declare function codingDoctorLines(env: Record<string, string | undefined>, deps: CodingDoctorDeps): Promise<string[]>;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The coding section of `wardby doctor` / `wardby status` for a quickstart
3
+ * project: trusted folders, the worker image, the coding proxy, and for each
4
+ * agent with a local repository whether it is inside the trusted folders and
5
+ * what its committed `.wardby/services.yaml` declares. Lines start with ✓ or ✗
6
+ * like the rest of doctor's output; an empty list means the coding step was
7
+ * never configured.
8
+ */
9
+ import { LOCAL_REPO_PREFIX, LocalRepoError, loadLocalRepoRoots, resolveLocalRepository } from "../coding/local-repo.js";
10
+ import { imageVariable } from "../config/providers.js";
11
+ import { isImmutableDockerImage } from "../providers/jobs/docker-isolation.js";
12
+ import { CODING_PROXY_CONTAINER } from "./coding.js";
13
+ import { inspectDeclaredServices, repoDefaultBranch } from "./starter-services.js";
14
+ const ok = (name, detail) => `✓ ${name}${detail ? ` (${detail})` : ""}`;
15
+ const bad = (name, detail) => `✗ ${name}${detail ? ` (${detail})` : ""}`;
16
+ async function agentLines(agent, roots) {
17
+ let path;
18
+ try {
19
+ path = (await resolveLocalRepository(agent.repository, roots)).path;
20
+ }
21
+ catch (error) {
22
+ const message = error instanceof LocalRepoError ? error.message : String(error);
23
+ return [`✗ ${agent.name}: ${message}`];
24
+ }
25
+ const lines = [`✓ ${agent.name}: ${path} is inside the trusted folders`];
26
+ const ref = agent.ref ?? (await repoDefaultBranch(path))?.branch;
27
+ if (!ref)
28
+ return [...lines, `✗ ${agent.name}: ${path} has no checked-out branch with commits`];
29
+ const declared = await inspectDeclaredServices(path, roots, ref);
30
+ if (declared.kind === "absent")
31
+ lines.push(`✓ ${agent.name}: no .wardby/services.yaml on ${ref} (no services)`);
32
+ else if (declared.kind === "invalid") {
33
+ lines.push(`✗ ${agent.name}: .wardby/services.yaml on ${ref} is invalid: ${declared.reason}`);
34
+ }
35
+ else {
36
+ const list = declared.services.map((service) => `${service.name} ${service.version}`).join(", ");
37
+ lines.push(`✓ ${agent.name}: .wardby/services.yaml on ${ref} declares ${list || "no services"}`);
38
+ }
39
+ return lines;
40
+ }
41
+ export async function codingDoctorLines(env, deps) {
42
+ if (!env.LOCAL_REPO_ROOTS && env.JOB_LAUNCHER !== "docker")
43
+ return [];
44
+ const lines = [];
45
+ const { roots, missing } = loadLocalRepoRoots({ LOCAL_REPO_ROOTS: env.LOCAL_REPO_ROOTS });
46
+ for (const root of roots)
47
+ lines.push(ok(`Trusted folder ${root}`));
48
+ for (const root of missing)
49
+ lines.push(bad(`Trusted folder ${root}`, "does not exist"));
50
+ if (roots.length === 0 && missing.length === 0)
51
+ lines.push(bad("Trusted folders", "LOCAL_REPO_ROOTS is empty"));
52
+ // Each provider's images are optional (Codex: CODING_WORKER_IMAGE; Claude Code: its pair); check what is set.
53
+ // Blank is unset, as the server reads it (imageVariable).
54
+ const codexSet = Boolean(imageVariable(env.CODING_WORKER_IMAGE));
55
+ const claudeSet = Boolean(imageVariable(env.CODING_CLAUDE_WORKER_IMAGE) || imageVariable(env.CODING_CLAUDE_TOOL_RUNNER_IMAGE));
56
+ const checkImage = (label, name) => {
57
+ const image = imageVariable(env[name]);
58
+ if (!image)
59
+ lines.push(bad(label, `${name} is not set`));
60
+ else if (!isImmutableDockerImage(image))
61
+ lines.push(bad(label, `${name} must be an immutable digest or image id`));
62
+ else if (deps.run("docker", ["image", "inspect", image]).status !== 0) {
63
+ lines.push(bad(label, "Docker cannot inspect it; re-run quickstart"));
64
+ }
65
+ else
66
+ lines.push(ok(label));
67
+ };
68
+ if (codexSet)
69
+ checkImage("Coding worker image", "CODING_WORKER_IMAGE");
70
+ if (claudeSet) {
71
+ checkImage("Claude Code worker image", "CODING_CLAUDE_WORKER_IMAGE");
72
+ checkImage("Claude Code tool runner image", "CODING_CLAUDE_TOOL_RUNNER_IMAGE");
73
+ }
74
+ if (!codexSet && !claudeSet) {
75
+ lines.push(bad("Coding worker image", "set CODING_WORKER_IMAGE for Codex, or CODING_CLAUDE_WORKER_IMAGE and CODING_CLAUDE_TOOL_RUNNER_IMAGE for Claude Code"));
76
+ }
77
+ const proxy = deps.run("docker", ["inspect", "--format", "{{.State.Running}}", CODING_PROXY_CONTAINER]);
78
+ const running = proxy.status === 0 && proxy.stdout.trim() === "true";
79
+ lines.push(running
80
+ ? ok(`Coding proxy container (${CODING_PROXY_CONTAINER})`)
81
+ : bad(`Coding proxy container (${CODING_PROXY_CONTAINER})`, "not running"));
82
+ for (const agent of await deps.listLocalAgents()) {
83
+ if (!agent.repository.startsWith(LOCAL_REPO_PREFIX))
84
+ continue;
85
+ lines.push(...(await agentLines(agent, roots)));
86
+ }
87
+ return lines;
88
+ }
@@ -0,0 +1,15 @@
1
+ import type { CodingProvider } from "../coding/provider.js";
2
+ import type { CodingDeps } from "./coding.js";
3
+ /** Stops the optional coding step with a message; the rest of quickstart carries on. */
4
+ export declare class CodingSkip extends Error {
5
+ }
6
+ export interface PreparedImages {
7
+ runtime: string;
8
+ /** The Codex worker; only for codex. */
9
+ worker?: string;
10
+ /** Claude Code's worker and tool runner; only for claude-code. */
11
+ claudeWorker?: string;
12
+ claudeToolRunner?: string;
13
+ }
14
+ /** Only the chosen provider's images: a Claude-only setup never builds or pulls the Codex worker. */
15
+ export declare function prepareImages(provider: CodingProvider, config: Record<string, string>, deps: CodingDeps): PreparedImages;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The coding step's images: the wardby runtime (coding proxy) plus the chosen
3
+ * provider's own images -- the Codex worker, or Claude Code's worker and tool
4
+ * runner. Pulled by digest from a release, or built from a source checkout
5
+ * and pinned by local image id (the Docker launcher refuses mutable tags).
6
+ */
7
+ import { join } from "node:path";
8
+ import { isImmutableDockerImage } from "../providers/jobs/docker-isolation.js";
9
+ import { resolveQuickstartImages } from "./images.js";
10
+ /** Stops the optional coding step with a message; the rest of quickstart carries on. */
11
+ export class CodingSkip extends Error {
12
+ }
13
+ function imageId(deps, tag) {
14
+ const inspected = deps.run("docker", ["image", "inspect", "--format", "{{.Id}}", tag]);
15
+ const id = inspected.stdout.trim();
16
+ if (inspected.status !== 0 || !isImmutableDockerImage(id))
17
+ throw new CodingSkip(`could not read the image id of ${tag}`);
18
+ return id;
19
+ }
20
+ function build(deps, dockerfile, tag, target) {
21
+ deps.log(`Building ${tag} (this can take a few minutes the first time)...`);
22
+ const args = ["build", "--file", join(deps.packageRoot, dockerfile), ...(target ? ["--target", target] : [])];
23
+ const result = deps.run("docker", [...args, "--tag", tag, deps.packageRoot], { inherit: true });
24
+ if (result.status !== 0)
25
+ throw new CodingSkip(`docker build of ${tag} failed`);
26
+ }
27
+ /** Pulls `image`; an image that cannot be pulled is fine if Docker already has it (e.g. a local image id). */
28
+ function pullOrPresent(deps, image) {
29
+ if (deps.run("docker", ["pull", image], { inherit: true }).status === 0)
30
+ return;
31
+ if (deps.run("docker", ["image", "inspect", image]).status === 0)
32
+ return;
33
+ throw new CodingSkip(`could not pull ${image}`);
34
+ }
35
+ /** Only the chosen provider's images: a Claude-only setup never builds or pulls the Codex worker. */
36
+ export function prepareImages(provider, config, deps) {
37
+ const resolved = resolveQuickstartImages({ env: deps.env, packageRoot: deps.packageRoot });
38
+ if ("unavailable" in resolved)
39
+ throw new CodingSkip(resolved.unavailable);
40
+ if (resolved.source === "build") {
41
+ build(deps, "deploy/Dockerfile", resolved.runtime, "runtime");
42
+ if (provider !== "claude-code") {
43
+ build(deps, "src/coding-worker/Dockerfile", resolved.worker);
44
+ return { runtime: resolved.runtime, worker: imageId(deps, resolved.worker) };
45
+ }
46
+ const claudeWorker = resolved.claudeWorker ?? "wardby-claude-coding-worker:local";
47
+ const claudeToolRunner = resolved.claudeToolRunner ?? "wardby-claude-tool-runner:local";
48
+ build(deps, "src/claude-coding-worker/Dockerfile", claudeWorker);
49
+ build(deps, "src/claude-tool-runner/Dockerfile", claudeToolRunner);
50
+ return {
51
+ runtime: resolved.runtime,
52
+ claudeWorker: imageId(deps, claudeWorker),
53
+ claudeToolRunner: imageId(deps, claudeToolRunner),
54
+ };
55
+ }
56
+ let images;
57
+ if (provider !== "claude-code") {
58
+ if (!isImmutableDockerImage(resolved.worker)) {
59
+ throw new CodingSkip("CODING_WORKER_IMAGE must be an immutable digest (repo@sha256:...) or local image id");
60
+ }
61
+ images = { runtime: resolved.runtime, worker: resolved.worker };
62
+ }
63
+ else {
64
+ const claudeWorker = resolved.claudeWorker || config.CODING_CLAUDE_WORKER_IMAGE;
65
+ const claudeToolRunner = resolved.claudeToolRunner || config.CODING_CLAUDE_TOOL_RUNNER_IMAGE;
66
+ if (!claudeWorker || !claudeToolRunner) {
67
+ throw new CodingSkip("Claude Code images aren't available: set both CODING_CLAUDE_WORKER_IMAGE and CODING_CLAUDE_TOOL_RUNNER_IMAGE, upgrade to a release that publishes them, or run from a wardby source checkout");
68
+ }
69
+ if (!isImmutableDockerImage(claudeWorker) || !isImmutableDockerImage(claudeToolRunner)) {
70
+ throw new CodingSkip("CODING_CLAUDE_WORKER_IMAGE and CODING_CLAUDE_TOOL_RUNNER_IMAGE must be immutable digests (repo@sha256:...) or local image ids");
71
+ }
72
+ images = { runtime: resolved.runtime, claudeWorker, claudeToolRunner };
73
+ }
74
+ for (const image of [images.runtime, images.worker, images.claudeWorker, images.claudeToolRunner]) {
75
+ if (image)
76
+ pullOrPresent(deps, image);
77
+ }
78
+ return images;
79
+ }
@@ -0,0 +1,63 @@
1
+ import { type CodingProvider } from "../coding/provider.js";
2
+ export declare const BUILDER_AGENT = "local-builder";
3
+ export declare const REVIEWER_AGENT = "local-reviewer";
4
+ export declare const BUILDER_PROMPT = "You are the Wardby quickstart coding agent, working in a local git repository. Make the smallest change that completes the task, follow the repository's existing style, add or update tests when behavior changes, and run the tests before you finish. End with a short summary of what you changed and how you verified it.";
5
+ export declare const REVIEWER_PROMPT: string;
6
+ /** The database operations seeding needs; the default implementation is Prisma (quickstart/coding.ts). */
7
+ export interface SeedDb {
8
+ findAgent(name: string): Promise<{
9
+ id: string;
10
+ kind: string;
11
+ systemPrompt: string;
12
+ } | null>;
13
+ createAgent(data: AgentData): Promise<{
14
+ id: string;
15
+ }>;
16
+ updateAgent(id: string, data: Partial<AgentData>): Promise<void>;
17
+ upsertCodingProfile(agentId: string, profile: Record<string, unknown>): Promise<void>;
18
+ upsertLocalLink(agentId: string, link: {
19
+ repository: string;
20
+ access: "write";
21
+ stamp: Stamp;
22
+ }): Promise<void>;
23
+ }
24
+ export interface AgentData {
25
+ name: string;
26
+ kind: "native" | "coding";
27
+ model: string;
28
+ budgetUsd: number;
29
+ systemPrompt: string;
30
+ maxTurns: number;
31
+ ownerId: string;
32
+ }
33
+ interface Stamp {
34
+ authorizedVia: "local_root";
35
+ authorizedById: string;
36
+ authorizedAt: Date;
37
+ }
38
+ export interface CodingSeedInput {
39
+ ownerId: string;
40
+ provider: CodingProvider;
41
+ /** The coding agent's model; must be one the coding provider supports. */
42
+ builderModel: string;
43
+ /** The native reviewer's model (the quickstart's own provider). */
44
+ reviewerModel: string;
45
+ /** Canonical `local:/realpath`, already checked against the trusted roots. */
46
+ repository: string;
47
+ baseRef: string;
48
+ /** Catalog service names the builder's runs may start. */
49
+ services: string[];
50
+ }
51
+ export type SeedOutcome = {
52
+ id: string;
53
+ status: "created" | "updated";
54
+ } | {
55
+ status: "skipped";
56
+ reason: string;
57
+ };
58
+ export interface CodingSeedResult {
59
+ builder: SeedOutcome;
60
+ reviewer: SeedOutcome;
61
+ }
62
+ export declare function seedCodingAgents(db: SeedDb, input: CodingSeedInput, now?: Date): Promise<CodingSeedResult>;
63
+ export {};
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Seeds the quickstart's two local-repository agents, idempotently:
3
+ * `local-builder` (a coding agent whose repository is a trusted local folder)
4
+ * and `local-reviewer` (a native agent with a write `local` link, so it can
5
+ * publish reviews of the builder's branches). Re-running updates them in
6
+ * place; an agent of the same name that quickstart did not create is left
7
+ * alone. Writes go straight to the database, like seedDemo, with the same
8
+ * authorization stamp the MCP tools give a local repository ("local_root").
9
+ */
10
+ import { CodingProfileSchema } from "../coding/profile.js";
11
+ import { codingProviderSupportsModel } from "../coding/provider.js";
12
+ export const BUILDER_AGENT = "local-builder";
13
+ export const REVIEWER_AGENT = "local-reviewer";
14
+ const BUILDER_BUDGET_USD = 2;
15
+ const REVIEWER_BUDGET_USD = 1;
16
+ const REVIEWER_MAX_TURNS = 12;
17
+ export const BUILDER_PROMPT = "You are the Wardby quickstart coding agent, working in a local git repository. Make the smallest change that completes the task, follow the repository's existing style, add or update tests when behavior changes, and run the tests before you finish. End with a short summary of what you changed and how you verified it.";
18
+ export const REVIEWER_PROMPT = [
19
+ "You are the Wardby quickstart review agent. Your task names one pull request: its number, its repository (local:/path), and its head commit.",
20
+ "1. Call repo_pr_read with that repository and prNumber to get the description and the diff.",
21
+ "2. When a change needs more context, read the surrounding code with repo_read_file at the head commit (repo_list_files lists what exists).",
22
+ "3. Look for correctness bugs, missing or weak tests, security problems, and confusing code. Skip style a formatter would fix.",
23
+ "4. Call repo_publish_review exactly once with the head commit, a verdict (APPROVE when nothing needs to change, CHANGES_REQUESTED for real defects, COMMENT otherwise), a one-line summary, a markdown body, and inline comments on changed lines, each with a severity (CRITICAL, MAJOR, MINOR, NIT).",
24
+ "If repo_publish_review returns stale_head, stop: the branch moved on.",
25
+ ].join("\n");
26
+ async function upsertAgent(db, data) {
27
+ const existing = await db.findAgent(data.name);
28
+ if (existing && (existing.systemPrompt !== data.systemPrompt || existing.kind !== data.kind)) {
29
+ return {
30
+ status: "skipped",
31
+ reason: `an agent named "${data.name}" already exists and was not created by quickstart; it was left unchanged`,
32
+ };
33
+ }
34
+ if (existing) {
35
+ const { name: _name, systemPrompt: _prompt, kind: _kind, ownerId: _owner, ...changes } = data;
36
+ await db.updateAgent(existing.id, changes);
37
+ return { id: existing.id, status: "updated" };
38
+ }
39
+ const created = await db.createAgent(data);
40
+ return { id: created.id, status: "created" };
41
+ }
42
+ export async function seedCodingAgents(db, input, now = new Date()) {
43
+ if (!codingProviderSupportsModel(input.provider, input.builderModel)) {
44
+ throw new Error(`Model "${input.builderModel}" is not supported by coding provider "${input.provider}".`);
45
+ }
46
+ const stamp = { authorizedVia: "local_root", authorizedById: input.ownerId, authorizedAt: now };
47
+ // Parsed for its defaults and validation, exactly as create_agent would store it.
48
+ const profile = CodingProfileSchema.parse({
49
+ provider: input.provider,
50
+ repository: input.repository,
51
+ baseRef: input.baseRef,
52
+ services: input.services,
53
+ });
54
+ const builder = await upsertAgent(db, {
55
+ name: BUILDER_AGENT,
56
+ kind: "coding",
57
+ model: input.builderModel,
58
+ budgetUsd: BUILDER_BUDGET_USD,
59
+ systemPrompt: BUILDER_PROMPT,
60
+ maxTurns: 10,
61
+ ownerId: input.ownerId,
62
+ });
63
+ if (builder.status !== "skipped") {
64
+ await db.upsertCodingProfile(builder.id, {
65
+ ...profile,
66
+ repositoryAuthorizedVia: stamp.authorizedVia,
67
+ repositoryAuthorizedById: stamp.authorizedById,
68
+ repositoryAuthorizedAt: stamp.authorizedAt,
69
+ });
70
+ }
71
+ const reviewer = await upsertAgent(db, {
72
+ name: REVIEWER_AGENT,
73
+ kind: "native",
74
+ model: input.reviewerModel,
75
+ budgetUsd: REVIEWER_BUDGET_USD,
76
+ systemPrompt: REVIEWER_PROMPT,
77
+ maxTurns: REVIEWER_MAX_TURNS,
78
+ ownerId: input.ownerId,
79
+ });
80
+ if (reviewer.status !== "skipped") {
81
+ // Write access: repo_publish_review is a write tool on the link.
82
+ await db.upsertLocalLink(reviewer.id, { repository: profile.repository, access: "write", stamp });
83
+ }
84
+ return { builder, reviewer };
85
+ }