|
1 | 1 | import { type Command } from 'commander' |
| 2 | +import { existsSync, mkdirSync, writeFileSync } from 'node:fs' |
| 3 | +import { basename, dirname, extname } from 'node:path' |
2 | 4 | import { ApiClient, ApiError } from '../lib/api' |
3 | 5 | import { alsoKnownAs, apiRoutes } from '../lib/help' |
4 | 6 | import { createWipCommit, currentBranch, pushReviewBranch, repoFromCwd } from '../lib/laptop' |
@@ -31,6 +33,9 @@ import type { |
31 | 33 | // How long to wait between REST polls when the stream isn't available. |
32 | 34 | const FALLBACK_POLL_INTERVAL_SECONDS = 3 |
33 | 35 |
|
| 36 | +// The conventional path for a pipeline file. Convention only: `kind:` decides. |
| 37 | +const DEFAULT_PIPELINE_PATH = 'agents/code_review.yaml' |
| 38 | + |
34 | 39 | export function registerReview(program: Command): void { |
35 | 40 | const review = alsoKnownAs( |
36 | 41 | program |
@@ -176,9 +181,97 @@ export function registerReview(program: Command): void { |
176 | 181 | }) |
177 | 182 | }) |
178 | 183 |
|
| 184 | + registerReviewInit(review) |
179 | 185 | registerReviewDefaults(review) |
180 | 186 | } |
181 | 187 |
|
| 188 | +// `agent review init`: the code review twin of `agent config init`. Scaffolds a |
| 189 | +// starter pipeline YAML locally; you commit it and Ellipsis syncs it from |
| 190 | +// GitHub. No API call and no pull request, because `agent config create` posts |
| 191 | +// an agent config and a pipeline is a different kind of file. |
| 192 | +function registerReviewInit(review: Command): void { |
| 193 | + review |
| 194 | + .command('init [path]') |
| 195 | + .description( |
| 196 | + `Scaffold a starter code review pipeline YAML locally (default: ${DEFAULT_PIPELINE_PATH})`, |
| 197 | + ) |
| 198 | + // No `-f` short: CLI-wide, `-f` means an input file. |
| 199 | + .option('--force', 'overwrite the file if it already exists') |
| 200 | + .action((path: string | undefined, opts: { force?: boolean }) => { |
| 201 | + const target = path ?? DEFAULT_PIPELINE_PATH |
| 202 | + if (existsSync(target) && !opts.force) { |
| 203 | + console.error(`error: ${target} already exists (use --force to overwrite)`) |
| 204 | + process.exitCode = 1 |
| 205 | + return |
| 206 | + } |
| 207 | + const name = basename(target, extname(target)) |
| 208 | + mkdirSync(dirname(target), { recursive: true }) |
| 209 | + writeFileSync(target, starterPipeline(name, repoNameFromCwd())) |
| 210 | + console.log(`✓ wrote ${target}`) |
| 211 | + console.log( |
| 212 | + 'Commit it to your default branch. Ellipsis syncs code review pipelines from GitHub.', |
| 213 | + ) |
| 214 | + }) |
| 215 | +} |
| 216 | + |
| 217 | +// The repository this pipeline should watch, as the bare name the schema takes |
| 218 | +// (`repositories:` is scoped to your account, so it never carries the owner). |
| 219 | +function repoNameFromCwd(): string | undefined { |
| 220 | + const repo = repoFromCwd(process.cwd()) |
| 221 | + return repo ? repo.split('/')[1] : undefined |
| 222 | +} |
| 223 | + |
| 224 | +// A minimal valid pipeline. `ellipsis.kind` is the only field the schema |
| 225 | +// requires; every stage left unset runs the platform's default reviewers. |
| 226 | +// Exported for tests. |
| 227 | +export function starterPipeline(name: string, repository: string | undefined): string { |
| 228 | + return `# Ellipsis code review pipeline. Commit this to your default branch; Ellipsis |
| 229 | +# syncs it from GitHub. Valid locations: agents/, .agents/, ellipsis/, .ellipsis/ |
| 230 | +# (any depth). The kind line is what makes this a review pipeline, not an agent. |
| 231 | +ellipsis: |
| 232 | + version: v1 |
| 233 | + kind: code_review |
| 234 | + name: ${name} |
| 235 | + description: What this pipeline reviews. |
| 236 | +
|
| 237 | +# Which pull requests this pipeline watches. Only one enabled pipeline may watch |
| 238 | +# a given pull request, so name your repositories here. Leaving this out watches |
| 239 | +# every repository in the account. |
| 240 | +pull_requests: |
| 241 | + repositories: |
| 242 | + - ${repository ?? 'my-repo'} |
| 243 | + # base: [main] |
| 244 | + # draft: false |
| 245 | + # paths: ["src/**"] |
| 246 | + # for: { bots: false } |
| 247 | +
|
| 248 | +# Every stage is optional. With all of them unset, reviews run the platform |
| 249 | +# default pipeline: three reviewer lenses (correctness, security, regression) |
| 250 | +# and a gatekeeper that judges what they found. |
| 251 | +# |
| 252 | +# review: |
| 253 | +# - name: migration-safety |
| 254 | +# claude: |
| 255 | +# system: | |
| 256 | +# Review SQL migrations for locks that block writes on a large table. |
| 257 | +# pull_requests: |
| 258 | +# paths: ["sql/migrations/**"] |
| 259 | +# |
| 260 | +# include_default_reviewers: true # run the built-in lenses as well |
| 261 | +# |
| 262 | +# filter: |
| 263 | +# name: gatekeeper |
| 264 | +# claude: |
| 265 | +# system: | |
| 266 | +# Drop any finding that is not worth the author's time. |
| 267 | +
|
| 268 | +budget: |
| 269 | + run: 2.00 |
| 270 | + day: 20.00 |
| 271 | + week: 75.00 |
| 272 | +` |
| 273 | +} |
| 274 | + |
182 | 275 | // `agent review default`: which code review pipeline runs when an explicit |
183 | 276 | // review names none — a two-rung ladder (account default + per-repo defaults, |
184 | 277 | // repo wins) mirroring `agent config default`, with the same --repo |
|
0 commit comments