@vexdo/cli 0.1.0 → 0.1.2

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 (51) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +1597 -0
  4. package/package.json +9 -1
  5. package/.eslintrc.json +0 -23
  6. package/.github/workflows/ci.yml +0 -84
  7. package/.idea/copilot.data.migration.ask2agent.xml +0 -6
  8. package/.idea/go.imports.xml +0 -11
  9. package/.idea/misc.xml +0 -6
  10. package/.idea/modules.xml +0 -8
  11. package/.idea/vcs.xml +0 -7
  12. package/.idea/vexdo-cli.iml +0 -9
  13. package/.prettierrc +0 -5
  14. package/CLAUDE.md +0 -93
  15. package/CONTRIBUTING.md +0 -62
  16. package/src/commands/abort.ts +0 -66
  17. package/src/commands/fix.ts +0 -106
  18. package/src/commands/init.ts +0 -142
  19. package/src/commands/logs.ts +0 -74
  20. package/src/commands/review.ts +0 -107
  21. package/src/commands/start.ts +0 -197
  22. package/src/commands/status.ts +0 -52
  23. package/src/commands/submit.ts +0 -38
  24. package/src/index.ts +0 -42
  25. package/src/lib/claude.ts +0 -259
  26. package/src/lib/codex.ts +0 -96
  27. package/src/lib/config.ts +0 -157
  28. package/src/lib/gh.ts +0 -78
  29. package/src/lib/git.ts +0 -119
  30. package/src/lib/logger.ts +0 -147
  31. package/src/lib/requirements.ts +0 -18
  32. package/src/lib/review-loop.ts +0 -154
  33. package/src/lib/state.ts +0 -121
  34. package/src/lib/submit-task.ts +0 -43
  35. package/src/lib/tasks.ts +0 -94
  36. package/src/prompts/arbiter.ts +0 -21
  37. package/src/prompts/reviewer.ts +0 -20
  38. package/src/types/index.ts +0 -96
  39. package/test/config.test.ts +0 -124
  40. package/test/state.test.ts +0 -147
  41. package/test/unit/claude.test.ts +0 -117
  42. package/test/unit/codex.test.ts +0 -67
  43. package/test/unit/gh.test.ts +0 -49
  44. package/test/unit/git.test.ts +0 -120
  45. package/test/unit/review-loop.test.ts +0 -198
  46. package/tests/integration/review.test.ts +0 -137
  47. package/tests/integration/start.test.ts +0 -220
  48. package/tests/unit/init.test.ts +0 -91
  49. package/tsconfig.json +0 -15
  50. package/tsup.config.ts +0 -8
  51. package/vitest.config.ts +0 -7
package/package.json CHANGED
@@ -1,12 +1,20 @@
1
1
  {
2
2
  "name": "@vexdo/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
+ "main": "./dist/index.js",
5
6
  "bin": {
6
7
  "vexdo": "./bin/vexdo.js"
7
8
  },
9
+ "files": [
10
+ "bin",
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
8
15
  "scripts": {
9
16
  "build": "tsup",
17
+ "prepack": "npm run build",
10
18
  "typecheck": "tsc --noEmit",
11
19
  "lint": "eslint .",
12
20
  "test": "vitest run",
package/.eslintrc.json DELETED
@@ -1,23 +0,0 @@
1
- {
2
- "root": true,
3
- "env": {
4
- "node": true,
5
- "es2022": true
6
- },
7
- "parser": "@typescript-eslint/parser",
8
- "parserOptions": {
9
- "project": "./tsconfig.json",
10
- "sourceType": "module"
11
- },
12
- "plugins": ["@typescript-eslint"],
13
- "extends": [
14
- "eslint:recommended",
15
- "plugin:@typescript-eslint/strict-type-checked",
16
- "plugin:@typescript-eslint/stylistic-type-checked",
17
- "prettier"
18
- ],
19
- "rules": {
20
- "@typescript-eslint/consistent-type-imports": "error"
21
- },
22
- "ignorePatterns": ["dist", "bin/*.js"]
23
- }
@@ -1,84 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches:
6
- - '**'
7
- tags:
8
- - 'v*'
9
- pull_request:
10
-
11
- jobs:
12
- lint-typecheck:
13
- runs-on: ubuntu-latest
14
- steps:
15
- - uses: actions/checkout@v4
16
- - uses: actions/setup-node@v4
17
- with:
18
- node-version: 18
19
- - uses: actions/cache@v4
20
- with:
21
- path: ~/.npm
22
- key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
23
- restore-keys: |
24
- npm-${{ runner.os }}-
25
- - run: npm ci
26
- - run: npm run typecheck
27
- - run: npm run lint
28
-
29
- test:
30
- runs-on: ubuntu-latest
31
- needs: lint-typecheck
32
- steps:
33
- - uses: actions/checkout@v4
34
- - uses: actions/setup-node@v4
35
- with:
36
- node-version: 18
37
- - uses: actions/cache@v4
38
- with:
39
- path: ~/.npm
40
- key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
41
- restore-keys: |
42
- npm-${{ runner.os }}-
43
- - run: npm ci
44
- - run: npm run test:coverage
45
-
46
- build:
47
- runs-on: ubuntu-latest
48
- needs: test
49
- steps:
50
- - uses: actions/checkout@v4
51
- - uses: actions/setup-node@v4
52
- with:
53
- node-version: 18
54
- - uses: actions/cache@v4
55
- with:
56
- path: ~/.npm
57
- key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
58
- restore-keys: |
59
- npm-${{ runner.os }}-
60
- - run: npm ci
61
- - run: npm run build
62
- - run: test -n "$(find dist -mindepth 1 -maxdepth 1 -print -quit)"
63
-
64
- release:
65
- if: startsWith(github.ref, 'refs/tags/v')
66
- runs-on: ubuntu-latest
67
- needs: [lint-typecheck, test, build]
68
- steps:
69
- - uses: actions/checkout@v4
70
- - uses: actions/setup-node@v4
71
- with:
72
- node-version: 18
73
- registry-url: https://registry.npmjs.org
74
- - uses: actions/cache@v4
75
- with:
76
- path: ~/.npm
77
- key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
78
- restore-keys: |
79
- npm-${{ runner.os }}-
80
- - run: npm ci
81
- - run: npm run build
82
- - run: npm publish
83
- env:
84
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="Ask2AgentMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
@@ -1,11 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="GoImports">
4
- <option name="excludedPackages">
5
- <array>
6
- <option value="github.com/pkg/errors" />
7
- <option value="golang.org/x/net/context" />
8
- </array>
9
- </option>
10
- </component>
11
- </project>
package/.idea/misc.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectRootManager" version="2" languageLevel="JDK_24" default="true" project-jdk-name="openjdk-24" project-jdk-type="JavaSDK">
4
- <output url="file://$PROJECT_DIR$/out" />
5
- </component>
6
- </project>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/vexdo-cli.iml" filepath="$PROJECT_DIR$/.idea/vexdo-cli.iml" />
6
- </modules>
7
- </component>
8
- </project>
package/.idea/vcs.xml DELETED
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="" vcs="Git" />
5
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
6
- </component>
7
- </project>
@@ -1,9 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="JAVA_MODULE" version="4">
3
- <component name="NewModuleRootManager" inherit-compiler-output="true">
4
- <exclude-output />
5
- <content url="file://$MODULE_DIR$" />
6
- <orderEntry type="inheritedJdk" />
7
- <orderEntry type="sourceFolder" forTests="false" />
8
- </component>
9
- </module>
package/.prettierrc DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "singleQuote": true,
3
- "trailingComma": "all",
4
- "printWidth": 100
5
- }
package/CLAUDE.md DELETED
@@ -1,93 +0,0 @@
1
- # CLAUDE.md
2
-
3
- ## 1) Project overview
4
-
5
- `vexdo` is a task orchestrator CLI for multi-service repositories. The core flow is: load task + config, run Codex for each step, run Claude reviewer + arbiter loop, then submit PRs or escalate.
6
-
7
- ## 2) Architecture
8
-
9
- Dependency direction should stay:
10
-
11
- ```text
12
- types ← lib ← commands ← index.ts
13
- ```
14
-
15
- Key invariants:
16
- - `commands/` may exit process; `lib/` should generally return errors.
17
- - `lib/` is reusable orchestration/integration logic.
18
- - `types/` stays free of runtime dependencies.
19
- - Reviewer and arbiter must run with isolated contexts/data.
20
-
21
- ## 3) Key files and roles
22
-
23
- ```text
24
- src/
25
- index.ts # commander bootstrap, global flags, command wiring
26
- commands/
27
- init.ts # interactive bootstrap (`vexdo init`)
28
- start.ts # primary task execution flow
29
- review.ts # rerun review loop on active step
30
- fix.ts # feed corrective prompt to Codex then review
31
- submit.ts # PR creation and state completion
32
- abort.ts # cancel task, preserve branches
33
- status.ts # print active state
34
- logs.ts # print iteration logs
35
- lib/
36
- config.ts # .vexdo.yml discovery + validation
37
- tasks.ts # task loading/validation and lane moves
38
- state.ts # active state persistence
39
- review-loop.ts # reviewer/arbiter loop control
40
- claude.ts # Anthropic SDK wrapper
41
- codex.ts # codex CLI wrapper
42
- gh.ts # gh CLI wrapper for PRs
43
- git.ts # git operations
44
- logger.ts # output formatting
45
- requirements.ts # env/runtime checks
46
- prompts/
47
- reviewer.ts # reviewer prompt construction
48
- arbiter.ts # arbiter prompt construction
49
- types/index.ts # shared type contracts
50
- ```
51
-
52
- ## 4) Common tasks
53
-
54
- ### Add a command
55
- 1. Add `src/commands/<name>.ts` with `register<Name>Command`.
56
- 2. Keep parsing and CLI concerns in command file.
57
- 3. Put reusable logic in `lib/`.
58
- 4. Register in `src/index.ts`.
59
- 5. Add tests and README docs.
60
-
61
- ### Add a lib function
62
- 1. Add function to the nearest `lib/*` module.
63
- 2. Keep signature typed and avoid `any`.
64
- 3. Return errors; avoid process termination.
65
- 4. Add focused unit tests.
66
-
67
- ### Add a test
68
- 1. Unit tests in `test/unit` or `tests/unit` for isolated behavior.
69
- 2. Integration tests in `test/integration` or `tests/integration` for flow.
70
- 3. Use `vi.mock` for `child_process`, SDKs, and external CLIs.
71
-
72
- ## 5) Constraints to always enforce
73
-
74
- - No `any` in new code.
75
- - Keep ESM `.js` import specifiers in TS source.
76
- - No `process.exit` inside `lib/` modules.
77
- - Do not add `chalk`; use `picocolors` for output styling.
78
- - Reviewer and arbiter contexts must remain isolated.
79
-
80
- ## 6) Test conventions
81
-
82
- - Prefer mocking at module boundary.
83
- - `child_process` calls should be mocked with `vi.mock('node:child_process', ...)`.
84
- - Anthropic SDK behavior should be mocked through `lib/claude.ts` dependencies.
85
- - Use temp directories for filesystem side effects.
86
-
87
- ## 7) What not to do
88
-
89
- - Do not embed orchestration logic directly in `index.ts`.
90
- - Do not tightly couple command handlers to specific prompt text.
91
- - Do not bypass task/config validation.
92
- - Do not mutate state without persisting through `state.ts` helpers.
93
- - Do not mix reviewer and arbiter outputs in a single decision context.
package/CONTRIBUTING.md DELETED
@@ -1,62 +0,0 @@
1
- # Contributing to vexdo
2
-
3
- ## 1) Development setup
4
-
5
- ```bash
6
- git clone <repo-url>
7
- cd vexdo-cli
8
- npm install
9
- npm link
10
- npm test
11
- ```
12
-
13
- Useful checks:
14
- - `npm run typecheck`
15
- - `npm run lint`
16
- - `npm run build`
17
-
18
- ## 2) Project structure
19
-
20
- ```text
21
- src/
22
- index.ts # CLI entrypoint and command registration
23
- commands/ # User-facing command handlers
24
- lib/ # Core orchestration, integrations, and helpers
25
- prompts/ # Claude reviewer/arbiter prompt templates
26
- types/ # Shared TypeScript types
27
- test/ + tests/ # Unit and integration tests
28
- ```
29
-
30
- ## 3) Adding a new command
31
-
32
- 1. Create `src/commands/<name>.ts`.
33
- 2. Export `register<Name>Command(program: Command)`.
34
- 3. Implement `run<Name>` function and keep side effects inside command layer.
35
- 4. Add registration in `src/index.ts`.
36
- 5. Add unit tests for parsing/behavior and integration tests if command touches git/fs/process.
37
- 6. Update README command docs.
38
-
39
- ## 4) Testing conventions
40
-
41
- - **Unit tests**: isolated logic, heavy mocking (`child_process`, SDK clients, filesystem helpers).
42
- - **Integration tests**: flow-level behavior and state transitions.
43
- - Prefer deterministic fixtures and temporary directories.
44
- - External dependencies (Anthropic, gh, codex) must be mocked in unit tests.
45
-
46
- ## 5) Commit conventions
47
-
48
- - Follow Conventional Commits, e.g.:
49
- - `feat: add init command`
50
- - `fix: prevent duplicate gitignore entry`
51
- - `docs: expand README`
52
- - PRs should include:
53
- - clear summary
54
- - tests run
55
- - any breaking changes
56
-
57
- ## 6) Release process
58
-
59
- 1. Bump version and update changelog/release notes.
60
- 2. Push tag `vX.Y.Z`.
61
- 3. GitHub Actions CI runs lint/typecheck/test/build.
62
- 4. Release job publishes to npm using `NPM_TOKEN`.
@@ -1,66 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import readline from 'node:readline/promises';
4
-
5
- import type { Command } from 'commander';
6
-
7
- import { findProjectRoot } from '../lib/config.js';
8
- import * as logger from '../lib/logger.js';
9
- import { clearState, loadState } from '../lib/state.js';
10
- import { ensureTaskDirectory, moveTaskFileAtomically } from '../lib/tasks.js';
11
-
12
- export interface AbortOptions { force?: boolean }
13
-
14
- function fatalAndExit(message: string): never {
15
- logger.fatal(message);
16
- process.exit(1);
17
- }
18
-
19
- async function promptConfirmation(taskId: string): Promise<boolean> {
20
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
21
- try {
22
- const answer = await rl.question(`Abort task ${taskId}? Branches will be kept. [y/N] `);
23
- return answer.trim().toLowerCase() === 'y';
24
- } finally {
25
- rl.close();
26
- }
27
- }
28
-
29
- export async function runAbort(options: AbortOptions): Promise<void> {
30
- const projectRoot = findProjectRoot();
31
- if (!projectRoot) {
32
- fatalAndExit('Not inside a vexdo project.');
33
- }
34
-
35
- const state = loadState(projectRoot);
36
- if (!state) {
37
- fatalAndExit('No active task.');
38
- }
39
-
40
- if (!options.force) {
41
- const confirmed = await promptConfirmation(state.taskId);
42
- if (!confirmed) {
43
- logger.info('Abort cancelled.');
44
- return;
45
- }
46
- }
47
-
48
- const inProgressDir = path.join(projectRoot, 'tasks', 'in_progress');
49
- if (state.taskPath.startsWith(inProgressDir) && fs.existsSync(state.taskPath)) {
50
- const backlogDir = ensureTaskDirectory(projectRoot, 'backlog');
51
- moveTaskFileAtomically(state.taskPath, backlogDir);
52
- }
53
-
54
- clearState(projectRoot);
55
- logger.info('Task aborted. Branches preserved for manual review.');
56
- }
57
-
58
- export function registerAbortCommand(program: Command): void {
59
- program
60
- .command('abort')
61
- .description('Abort active task')
62
- .option('--force', 'Skip confirmation prompt')
63
- .action(async (options: AbortOptions) => {
64
- await runAbort(options);
65
- });
66
- }
@@ -1,106 +0,0 @@
1
- import path from 'node:path';
2
-
3
- import type { Command } from 'commander';
4
-
5
- import { ClaudeClient } from '../lib/claude.js';
6
- import * as codex from '../lib/codex.js';
7
- import { findProjectRoot, loadConfig } from '../lib/config.js';
8
- import * as logger from '../lib/logger.js';
9
- import { requireAnthropicApiKey } from '../lib/requirements.js';
10
- import { runReviewLoop } from '../lib/review-loop.js';
11
- import { loadState, saveState } from '../lib/state.js';
12
- import { ensureTaskDirectory, loadAndValidateTask, moveTaskFileAtomically } from '../lib/tasks.js';
13
-
14
- interface FixOptions { dryRun?: boolean; verbose?: boolean }
15
-
16
- function fatalAndExit(message: string): never {
17
- logger.fatal(message);
18
- process.exit(1);
19
- }
20
-
21
- export async function runFix(feedback: string, options: FixOptions): Promise<void> {
22
- try {
23
- const projectRoot = findProjectRoot();
24
- if (!projectRoot) {
25
- fatalAndExit('Not inside a vexdo project.');
26
- }
27
-
28
- const config = loadConfig(projectRoot);
29
- const state = loadState(projectRoot);
30
- if (!state) {
31
- fatalAndExit('No active task.');
32
- }
33
-
34
- if (!options.dryRun) {
35
- requireAnthropicApiKey();
36
- await codex.checkCodexAvailable();
37
- }
38
-
39
- const currentStep = state.steps.find((step) => step.status === 'in_progress' || step.status === 'pending');
40
- if (!currentStep) {
41
- fatalAndExit('No in-progress step found in active task.');
42
- }
43
-
44
- const task = loadAndValidateTask(state.taskPath, config);
45
- const step = task.steps.find((item) => item.service === currentStep.service);
46
- if (!step) {
47
- fatalAndExit(`Could not locate task step for service '${currentStep.service}'.`);
48
- }
49
-
50
- if (!options.dryRun) {
51
- const serviceConfig = config.services.find((service) => service.name === currentStep.service);
52
- if (!serviceConfig) {
53
- fatalAndExit(`Unknown service in step: ${currentStep.service}`);
54
- }
55
-
56
- await codex.exec({
57
- spec: feedback,
58
- model: config.codex.model,
59
- cwd: path.resolve(projectRoot, serviceConfig.path),
60
- verbose: options.verbose,
61
- });
62
- }
63
-
64
- const result = await runReviewLoop({
65
- taskId: task.id,
66
- task,
67
- step,
68
- stepState: currentStep,
69
- projectRoot,
70
- config,
71
- claude: new ClaudeClient(process.env.ANTHROPIC_API_KEY ?? ''),
72
- dryRun: options.dryRun,
73
- verbose: options.verbose,
74
- });
75
-
76
- if (result.decision === 'escalate') {
77
- currentStep.status = 'escalated';
78
- state.status = 'escalated';
79
- if (!options.dryRun) {
80
- saveState(projectRoot, state);
81
- const blockedDir = ensureTaskDirectory(projectRoot, 'blocked');
82
- state.taskPath = moveTaskFileAtomically(state.taskPath, blockedDir);
83
- saveState(projectRoot, state);
84
- }
85
- process.exit(1);
86
- }
87
-
88
- currentStep.status = 'done';
89
- if (!options.dryRun) {
90
- saveState(projectRoot, state);
91
- }
92
- } catch (error: unknown) {
93
- fatalAndExit(error instanceof Error ? error.message : String(error));
94
- }
95
- }
96
-
97
- export function registerFixCommand(program: Command): void {
98
- program
99
- .command('fix')
100
- .description('Provide feedback to codex and rerun review')
101
- .argument('<feedback>')
102
- .action(async (feedback: string, options: FixOptions, command: Command) => {
103
- const merged = command.optsWithGlobals();
104
- await runFix(feedback, { ...options, ...merged });
105
- });
106
- }
@@ -1,142 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { createInterface } from 'node:readline/promises';
4
- import { stdin as input, stdout as output } from 'node:process';
5
-
6
- import type { Command } from 'commander';
7
- import { stringify } from 'yaml';
8
-
9
- import * as logger from '../lib/logger.js';
10
- import type { VexdoConfig } from '../types/index.js';
11
-
12
- const DEFAULT_REVIEW_MODEL = 'claude-haiku-4-5-20251001';
13
- const DEFAULT_MAX_ITERATIONS = 3;
14
- const DEFAULT_CODEX_MODEL = 'gpt-4o';
15
-
16
- const TASK_DIRS = ['backlog', 'in_progress', 'review', 'done', 'blocked'] as const;
17
-
18
- export type PromptFn = (question: string) => Promise<string>;
19
-
20
- async function defaultPrompt(question: string): Promise<string> {
21
- const rl = createInterface({ input, output });
22
- try {
23
- return await rl.question(question);
24
- } finally {
25
- rl.close();
26
- }
27
- }
28
-
29
- function parseServices(value: string): string[] {
30
- const parsed = value
31
- .split(',')
32
- .map((item) => item.trim())
33
- .filter((item) => item.length > 0);
34
-
35
- return Array.from(new Set(parsed));
36
- }
37
-
38
- function parseBoolean(value: string): boolean {
39
- const normalized = value.trim().toLowerCase();
40
- return normalized === 'y' || normalized === 'yes';
41
- }
42
-
43
- function parseMaxIterations(value: string): number {
44
- const parsed = Number.parseInt(value, 10);
45
- return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_ITERATIONS;
46
- }
47
-
48
- function ensureGitignoreEntry(gitignorePath: string, entry: string): boolean {
49
- if (!fs.existsSync(gitignorePath)) {
50
- fs.writeFileSync(gitignorePath, `${entry}\n`, 'utf8');
51
- return true;
52
- }
53
-
54
- const content = fs.readFileSync(gitignorePath, 'utf8');
55
- const lines = content.split(/\r?\n/).map((line) => line.trim());
56
-
57
- if (lines.includes(entry)) {
58
- return false;
59
- }
60
-
61
- const suffix = content.endsWith('\n') || content.length === 0 ? '' : '\n';
62
- fs.appendFileSync(gitignorePath, `${suffix}${entry}\n`, 'utf8');
63
- return true;
64
- }
65
-
66
- export async function runInit(projectRoot: string, prompt: PromptFn = defaultPrompt): Promise<void> {
67
- const configPath = path.join(projectRoot, '.vexdo.yml');
68
-
69
- if (fs.existsSync(configPath)) {
70
- logger.warn('Found existing .vexdo.yml.');
71
- const overwriteAnswer = await prompt('Overwrite existing .vexdo.yml? (y/N): ');
72
- if (!parseBoolean(overwriteAnswer)) {
73
- logger.info('Initialization cancelled.');
74
- return;
75
- }
76
- }
77
-
78
- let services = parseServices(await prompt('Project services (comma-separated names, e.g. api,web): '));
79
- if (services.length === 0) {
80
- services = ['api'];
81
- }
82
-
83
- const serviceConfigs: VexdoConfig['services'] = [];
84
- for (const name of services) {
85
- const answer = await prompt(`Path for ${name} (default: ./${name}): `);
86
- serviceConfigs.push({
87
- name,
88
- path: answer.trim().length > 0 ? answer.trim() : `./${name}`,
89
- });
90
- }
91
-
92
- const reviewModelRaw = await prompt(`Review model (default: ${DEFAULT_REVIEW_MODEL}): `);
93
- const maxIterationsRaw = await prompt(`Max review iterations (default: ${String(DEFAULT_MAX_ITERATIONS)}): `);
94
- const autoSubmitRaw = await prompt('Auto-submit PRs? (y/N): ');
95
- const codexModelRaw = await prompt(`Codex model (default: ${DEFAULT_CODEX_MODEL}): `);
96
-
97
- const config: VexdoConfig = {
98
- version: 1,
99
- services: serviceConfigs,
100
- review: {
101
- model: reviewModelRaw.trim() || DEFAULT_REVIEW_MODEL,
102
- max_iterations: maxIterationsRaw.trim() ? parseMaxIterations(maxIterationsRaw.trim()) : DEFAULT_MAX_ITERATIONS,
103
- auto_submit: parseBoolean(autoSubmitRaw),
104
- },
105
- codex: {
106
- model: codexModelRaw.trim() || DEFAULT_CODEX_MODEL,
107
- },
108
- };
109
-
110
- fs.writeFileSync(configPath, stringify(config), 'utf8');
111
-
112
- const createdDirs: string[] = [];
113
- for (const taskDir of TASK_DIRS) {
114
- const directory = path.join(projectRoot, 'tasks', taskDir);
115
- fs.mkdirSync(directory, { recursive: true });
116
- createdDirs.push(path.relative(projectRoot, directory));
117
- }
118
-
119
- const logDir = path.join(projectRoot, '.vexdo', 'logs');
120
- fs.mkdirSync(logDir, { recursive: true });
121
- createdDirs.push(path.relative(projectRoot, logDir));
122
-
123
- const gitignorePath = path.join(projectRoot, '.gitignore');
124
- const gitignoreUpdated = ensureGitignoreEntry(gitignorePath, '.vexdo/');
125
-
126
- logger.success('Initialized vexdo project.');
127
- logger.info(`Created: ${path.relative(projectRoot, configPath)}`);
128
- logger.info(`Created directories: ${createdDirs.join(', ')}`);
129
- if (gitignoreUpdated) {
130
- logger.info('Updated .gitignore with .vexdo/');
131
- }
132
- logger.info("Next: create a task file in tasks/backlog/ and run 'vexdo start tasks/backlog/my-task.yml'");
133
- }
134
-
135
- export function registerInitCommand(program: Command): void {
136
- program
137
- .command('init')
138
- .description('Initialize vexdo in the current project')
139
- .action(async () => {
140
- await runInit(process.cwd());
141
- });
142
- }