@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.
- package/README.md +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1597 -0
- package/package.json +9 -1
- package/.eslintrc.json +0 -23
- package/.github/workflows/ci.yml +0 -84
- package/.idea/copilot.data.migration.ask2agent.xml +0 -6
- package/.idea/go.imports.xml +0 -11
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -7
- package/.idea/vexdo-cli.iml +0 -9
- package/.prettierrc +0 -5
- package/CLAUDE.md +0 -93
- package/CONTRIBUTING.md +0 -62
- package/src/commands/abort.ts +0 -66
- package/src/commands/fix.ts +0 -106
- package/src/commands/init.ts +0 -142
- package/src/commands/logs.ts +0 -74
- package/src/commands/review.ts +0 -107
- package/src/commands/start.ts +0 -197
- package/src/commands/status.ts +0 -52
- package/src/commands/submit.ts +0 -38
- package/src/index.ts +0 -42
- package/src/lib/claude.ts +0 -259
- package/src/lib/codex.ts +0 -96
- package/src/lib/config.ts +0 -157
- package/src/lib/gh.ts +0 -78
- package/src/lib/git.ts +0 -119
- package/src/lib/logger.ts +0 -147
- package/src/lib/requirements.ts +0 -18
- package/src/lib/review-loop.ts +0 -154
- package/src/lib/state.ts +0 -121
- package/src/lib/submit-task.ts +0 -43
- package/src/lib/tasks.ts +0 -94
- package/src/prompts/arbiter.ts +0 -21
- package/src/prompts/reviewer.ts +0 -20
- package/src/types/index.ts +0 -96
- package/test/config.test.ts +0 -124
- package/test/state.test.ts +0 -147
- package/test/unit/claude.test.ts +0 -117
- package/test/unit/codex.test.ts +0 -67
- package/test/unit/gh.test.ts +0 -49
- package/test/unit/git.test.ts +0 -120
- package/test/unit/review-loop.test.ts +0 -198
- package/tests/integration/review.test.ts +0 -137
- package/tests/integration/start.test.ts +0 -220
- package/tests/unit/init.test.ts +0 -91
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -8
- 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.
|
|
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
|
-
}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -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 }}
|
package/.idea/go.imports.xml
DELETED
|
@@ -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
package/.idea/vexdo-cli.iml
DELETED
|
@@ -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
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`.
|
package/src/commands/abort.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/fix.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -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
|
-
}
|