@vexdo/cli 0.1.0 → 0.1.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.
- package/README.md +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1540 -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/src/commands/logs.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import type { Command } from 'commander';
|
|
5
|
-
|
|
6
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
7
|
-
import * as logger from '../lib/logger.js';
|
|
8
|
-
import { getLogsDir, getStateDir, loadState } from '../lib/state.js';
|
|
9
|
-
|
|
10
|
-
interface LogsOptions { full?: boolean }
|
|
11
|
-
|
|
12
|
-
function fatalAndExit(message: string): never {
|
|
13
|
-
logger.fatal(message);
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function runLogs(taskIdArg?: string, options?: LogsOptions): void {
|
|
18
|
-
const projectRoot = findProjectRoot();
|
|
19
|
-
if (!projectRoot) {
|
|
20
|
-
fatalAndExit('Not inside a vexdo project.');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const state = loadState(projectRoot);
|
|
24
|
-
const taskId = taskIdArg ?? state?.taskId;
|
|
25
|
-
|
|
26
|
-
if (!taskId) {
|
|
27
|
-
const base = path.join(getStateDir(projectRoot), 'logs');
|
|
28
|
-
if (!fs.existsSync(base)) {
|
|
29
|
-
logger.info('No logs available.');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const tasks = fs.readdirSync(base, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
34
|
-
for (const dir of tasks) {
|
|
35
|
-
logger.info(dir.name);
|
|
36
|
-
}
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const logsDir = getLogsDir(projectRoot, taskId);
|
|
41
|
-
if (!fs.existsSync(logsDir)) {
|
|
42
|
-
fatalAndExit(`No logs found for task '${taskId}'.`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const files = fs.readdirSync(logsDir).filter((name) => name.endsWith('-arbiter.json'));
|
|
46
|
-
for (const arbiterFile of files) {
|
|
47
|
-
const base = arbiterFile.replace(/-arbiter\.json$/, '');
|
|
48
|
-
const arbiterPath = path.join(logsDir, `${base}-arbiter.json`);
|
|
49
|
-
const reviewPath = path.join(logsDir, `${base}-review.json`);
|
|
50
|
-
const diffPath = path.join(logsDir, `${base}-diff.txt`);
|
|
51
|
-
|
|
52
|
-
const arbiter = JSON.parse(fs.readFileSync(arbiterPath, 'utf8')) as { decision: string; summary: string };
|
|
53
|
-
const review = JSON.parse(fs.readFileSync(reviewPath, 'utf8')) as { comments?: unknown[] };
|
|
54
|
-
|
|
55
|
-
logger.info(`${base}: decision=${arbiter.decision}, comments=${String(review.comments?.length ?? 0)}, summary=${arbiter.summary}`);
|
|
56
|
-
|
|
57
|
-
if (options?.full) {
|
|
58
|
-
console.log(fs.readFileSync(diffPath, 'utf8'));
|
|
59
|
-
console.log(JSON.stringify(review, null, 2));
|
|
60
|
-
console.log(JSON.stringify(arbiter, null, 2));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function registerLogsCommand(program: Command): void {
|
|
66
|
-
program
|
|
67
|
-
.command('logs')
|
|
68
|
-
.description('Show iteration logs')
|
|
69
|
-
.argument('[task-id]')
|
|
70
|
-
.option('--full', 'Print full diff and comments')
|
|
71
|
-
.action((taskId?: string, options?: LogsOptions) => {
|
|
72
|
-
runLogs(taskId, options);
|
|
73
|
-
});
|
|
74
|
-
}
|
package/src/commands/review.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
|
|
3
|
-
import type { Command } from 'commander';
|
|
4
|
-
|
|
5
|
-
import { ClaudeClient } from '../lib/claude.js';
|
|
6
|
-
import { findProjectRoot, loadConfig } from '../lib/config.js';
|
|
7
|
-
import * as logger from '../lib/logger.js';
|
|
8
|
-
import { requireAnthropicApiKey } from '../lib/requirements.js';
|
|
9
|
-
import { runReviewLoop } from '../lib/review-loop.js';
|
|
10
|
-
import { loadState, saveState } from '../lib/state.js';
|
|
11
|
-
import { ensureTaskDirectory, loadAndValidateTask, moveTaskFileAtomically } from '../lib/tasks.js';
|
|
12
|
-
|
|
13
|
-
interface ReviewOptions { dryRun?: boolean; verbose?: boolean }
|
|
14
|
-
|
|
15
|
-
function fatalAndExit(message: string): never {
|
|
16
|
-
logger.fatal(message);
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function runReview(options: ReviewOptions): Promise<void> {
|
|
21
|
-
try {
|
|
22
|
-
const projectRoot = findProjectRoot();
|
|
23
|
-
if (!projectRoot) {
|
|
24
|
-
fatalAndExit('Not inside a vexdo project.');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const config = loadConfig(projectRoot);
|
|
28
|
-
const state = loadState(projectRoot);
|
|
29
|
-
if (!state) {
|
|
30
|
-
fatalAndExit('No active task.');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!options.dryRun) {
|
|
34
|
-
requireAnthropicApiKey();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const currentStep = state.steps.find((step) => step.status === 'in_progress' || step.status === 'pending');
|
|
38
|
-
if (!currentStep) {
|
|
39
|
-
fatalAndExit('No in-progress step found in active task.');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!fs.existsSync(state.taskPath)) {
|
|
43
|
-
fatalAndExit(`Task file not found: ${state.taskPath}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const task = loadAndValidateTask(state.taskPath, config);
|
|
47
|
-
const step = task.steps.find((item) => item.service === currentStep.service);
|
|
48
|
-
if (!step) {
|
|
49
|
-
fatalAndExit(`Could not locate task step for service '${currentStep.service}'.`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const result = await runReviewLoop({
|
|
53
|
-
taskId: task.id,
|
|
54
|
-
task,
|
|
55
|
-
step,
|
|
56
|
-
stepState: currentStep,
|
|
57
|
-
projectRoot,
|
|
58
|
-
config,
|
|
59
|
-
claude: new ClaudeClient(process.env.ANTHROPIC_API_KEY ?? ''),
|
|
60
|
-
dryRun: options.dryRun,
|
|
61
|
-
verbose: options.verbose,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (result.decision === 'escalate') {
|
|
65
|
-
logger.escalation({
|
|
66
|
-
taskId: task.id,
|
|
67
|
-
service: step.service,
|
|
68
|
-
iteration: result.finalIteration,
|
|
69
|
-
spec: step.spec,
|
|
70
|
-
diff: '',
|
|
71
|
-
reviewComments: result.lastReviewComments,
|
|
72
|
-
arbiterReasoning: result.lastArbiterResult.reasoning,
|
|
73
|
-
summary: result.lastArbiterResult.summary,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
currentStep.status = 'escalated';
|
|
77
|
-
state.status = 'escalated';
|
|
78
|
-
|
|
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
|
-
|
|
86
|
-
process.exit(1);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
currentStep.status = 'done';
|
|
90
|
-
if (!options.dryRun) {
|
|
91
|
-
saveState(projectRoot, state);
|
|
92
|
-
}
|
|
93
|
-
} catch (error: unknown) {
|
|
94
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
-
fatalAndExit(message);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function registerReviewCommand(program: Command): void {
|
|
100
|
-
program
|
|
101
|
-
.command('review')
|
|
102
|
-
.description('Run review loop for the current step')
|
|
103
|
-
.action(async (options: ReviewOptions, command: Command) => {
|
|
104
|
-
const merged = command.optsWithGlobals();
|
|
105
|
-
await runReview({ ...options, ...merged });
|
|
106
|
-
});
|
|
107
|
-
}
|
package/src/commands/start.ts
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import type { Command } from 'commander';
|
|
5
|
-
|
|
6
|
-
import { ClaudeClient } from '../lib/claude.js';
|
|
7
|
-
import * as codex from '../lib/codex.js';
|
|
8
|
-
import { findProjectRoot, loadConfig } from '../lib/config.js';
|
|
9
|
-
import * as git from '../lib/git.js';
|
|
10
|
-
import * as logger from '../lib/logger.js';
|
|
11
|
-
import { requireAnthropicApiKey } from '../lib/requirements.js';
|
|
12
|
-
import { runReviewLoop } from '../lib/review-loop.js';
|
|
13
|
-
import { createState, hasActiveTask, loadState, saveState } from '../lib/state.js';
|
|
14
|
-
import { submitActiveTask } from '../lib/submit-task.js';
|
|
15
|
-
import { buildInitialStepState, ensureTaskDirectory, loadAndValidateTask, moveTaskFileAtomically } from '../lib/tasks.js';
|
|
16
|
-
|
|
17
|
-
export interface StartCommandOptions {
|
|
18
|
-
dryRun?: boolean;
|
|
19
|
-
verbose?: boolean;
|
|
20
|
-
resume?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function fatalAndExit(message: string, hint?: string): never {
|
|
24
|
-
logger.fatal(message, hint);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function runStart(taskFile: string, options: StartCommandOptions): Promise<void> {
|
|
29
|
-
try {
|
|
30
|
-
const projectRoot = findProjectRoot();
|
|
31
|
-
if (!projectRoot) {
|
|
32
|
-
fatalAndExit('Not inside a vexdo project. Could not find .vexdo.yml.');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const config = loadConfig(projectRoot);
|
|
36
|
-
const taskPath = path.resolve(taskFile);
|
|
37
|
-
const task = loadAndValidateTask(taskPath, config);
|
|
38
|
-
|
|
39
|
-
if (hasActiveTask(projectRoot) && !options.resume) {
|
|
40
|
-
fatalAndExit('An active task already exists.', "Use --resume to continue or 'vexdo abort' to cancel.");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!options.dryRun) {
|
|
44
|
-
requireAnthropicApiKey();
|
|
45
|
-
await codex.checkCodexAvailable();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let state = loadState(projectRoot);
|
|
49
|
-
|
|
50
|
-
if (!options.resume) {
|
|
51
|
-
let taskPathInProgress = taskPath;
|
|
52
|
-
if (!options.dryRun) {
|
|
53
|
-
const inProgressDir = ensureTaskDirectory(projectRoot, 'in_progress');
|
|
54
|
-
taskPathInProgress = moveTaskFileAtomically(taskPath, inProgressDir);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
state = createState(task.id, task.title, taskPathInProgress, buildInitialStepState(task));
|
|
58
|
-
if (!options.dryRun) {
|
|
59
|
-
saveState(projectRoot, state);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!state) {
|
|
64
|
-
fatalAndExit('No resumable task state found.');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const claude = new ClaudeClient(process.env.ANTHROPIC_API_KEY ?? '');
|
|
68
|
-
const total = task.steps.length;
|
|
69
|
-
|
|
70
|
-
for (let i = 0; i < task.steps.length; i += 1) {
|
|
71
|
-
const step = task.steps[i];
|
|
72
|
-
const stepState = state.steps[i];
|
|
73
|
-
if (!step || !stepState) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (stepState.status === 'done') {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (step.depends_on && step.depends_on.length > 0) {
|
|
82
|
-
for (const depService of step.depends_on) {
|
|
83
|
-
const depState = state.steps.find((item) => item.service === depService);
|
|
84
|
-
if (depState?.status !== 'done') {
|
|
85
|
-
fatalAndExit(`Step dependency '${depService}' for service '${step.service}' is not done.`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
logger.step(i + 1, total, `${step.service}: ${task.title}`);
|
|
91
|
-
|
|
92
|
-
const serviceCfg = config.services.find((service) => service.name === step.service);
|
|
93
|
-
if (!serviceCfg) {
|
|
94
|
-
fatalAndExit(`Unknown service in step: ${step.service}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const serviceRoot = path.resolve(projectRoot, serviceCfg.path);
|
|
98
|
-
const branch = git.getBranchName(task.id, step.service);
|
|
99
|
-
|
|
100
|
-
if (!options.dryRun) {
|
|
101
|
-
if (options.resume) {
|
|
102
|
-
await git.checkoutBranch(stepState.branch ?? branch, serviceRoot);
|
|
103
|
-
} else {
|
|
104
|
-
await git.createBranch(branch, serviceRoot);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
stepState.status = 'in_progress';
|
|
109
|
-
stepState.branch = branch;
|
|
110
|
-
if (!options.dryRun) {
|
|
111
|
-
saveState(projectRoot, state);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!options.resume && !options.dryRun) {
|
|
115
|
-
await codex.exec({
|
|
116
|
-
spec: step.spec,
|
|
117
|
-
model: config.codex.model,
|
|
118
|
-
cwd: serviceRoot,
|
|
119
|
-
verbose: options.verbose,
|
|
120
|
-
});
|
|
121
|
-
} else if (options.dryRun) {
|
|
122
|
-
logger.info(`[dry-run] Would run codex for service ${step.service}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const result = await runReviewLoop({
|
|
126
|
-
taskId: task.id,
|
|
127
|
-
task,
|
|
128
|
-
step,
|
|
129
|
-
stepState,
|
|
130
|
-
projectRoot,
|
|
131
|
-
config,
|
|
132
|
-
claude,
|
|
133
|
-
dryRun: options.dryRun,
|
|
134
|
-
verbose: options.verbose,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
if (result.decision === 'escalate') {
|
|
138
|
-
logger.escalation({
|
|
139
|
-
taskId: task.id,
|
|
140
|
-
service: step.service,
|
|
141
|
-
iteration: result.finalIteration,
|
|
142
|
-
spec: step.spec,
|
|
143
|
-
diff: '',
|
|
144
|
-
reviewComments: result.lastReviewComments,
|
|
145
|
-
arbiterReasoning: result.lastArbiterResult.reasoning,
|
|
146
|
-
summary: result.lastArbiterResult.summary,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
stepState.status = 'escalated';
|
|
150
|
-
state.status = 'escalated';
|
|
151
|
-
|
|
152
|
-
if (!options.dryRun) {
|
|
153
|
-
saveState(projectRoot, state);
|
|
154
|
-
const blockedDir = ensureTaskDirectory(projectRoot, 'blocked');
|
|
155
|
-
state.taskPath = moveTaskFileAtomically(state.taskPath, blockedDir);
|
|
156
|
-
saveState(projectRoot, state);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
stepState.status = 'done';
|
|
163
|
-
if (!options.dryRun) {
|
|
164
|
-
saveState(projectRoot, state);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
state.status = 'review';
|
|
169
|
-
if (!options.dryRun) {
|
|
170
|
-
const reviewDir = ensureTaskDirectory(projectRoot, 'review');
|
|
171
|
-
state.taskPath = moveTaskFileAtomically(state.taskPath, reviewDir);
|
|
172
|
-
saveState(projectRoot, state);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (config.review.auto_submit && !options.dryRun) {
|
|
176
|
-
await submitActiveTask(projectRoot, config, state);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
logger.success("Task ready for PR. Run 'vexdo submit' to create PR.");
|
|
181
|
-
} catch (error: unknown) {
|
|
182
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
-
fatalAndExit(message);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function registerStartCommand(program: Command): void {
|
|
188
|
-
program
|
|
189
|
-
.command('start')
|
|
190
|
-
.description('Start a task from a YAML file')
|
|
191
|
-
.argument('<task-file>')
|
|
192
|
-
.option('--resume', 'Resume an existing active task')
|
|
193
|
-
.action(async (taskFile: string, options: StartCommandOptions, command: Command) => {
|
|
194
|
-
const merged = command.optsWithGlobals();
|
|
195
|
-
await runStart(taskFile, { ...options, ...merged });
|
|
196
|
-
});
|
|
197
|
-
}
|
package/src/commands/status.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
|
|
3
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
4
|
-
import * as logger from '../lib/logger.js';
|
|
5
|
-
import { loadState } from '../lib/state.js';
|
|
6
|
-
|
|
7
|
-
function fatalAndExit(message: string): never {
|
|
8
|
-
logger.fatal(message);
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function formatElapsed(startedAt: string): string {
|
|
13
|
-
const elapsedMs = Date.now() - new Date(startedAt).getTime();
|
|
14
|
-
const minutes = Math.floor(elapsedMs / 1000 / 60);
|
|
15
|
-
const hours = Math.floor(minutes / 60);
|
|
16
|
-
if (hours > 0) {
|
|
17
|
-
return `${String(hours)}h ${String(minutes % 60)}m`;
|
|
18
|
-
}
|
|
19
|
-
return `${String(minutes)}m`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function runStatus(): void {
|
|
23
|
-
const projectRoot = findProjectRoot();
|
|
24
|
-
if (!projectRoot) {
|
|
25
|
-
fatalAndExit('Not inside a vexdo project.');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const state = loadState(projectRoot);
|
|
29
|
-
if (!state) {
|
|
30
|
-
fatalAndExit('No active task.');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
logger.info(`Task: ${state.taskId} — ${state.taskTitle}`);
|
|
34
|
-
logger.info(`Status: ${state.status}`);
|
|
35
|
-
console.log('service | status | iteration | branch');
|
|
36
|
-
for (const step of state.steps) {
|
|
37
|
-
console.log(`${step.service} | ${step.status} | ${String(step.iteration)} | ${step.branch ?? '-'}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const inProgress = state.steps.find((step) => step.status === 'in_progress');
|
|
41
|
-
if (inProgress?.lastArbiterResult?.summary) {
|
|
42
|
-
logger.info(`Last arbiter summary: ${inProgress.lastArbiterResult.summary}`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
logger.info(`Elapsed: ${formatElapsed(state.startedAt)}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function registerStatusCommand(program: Command): void {
|
|
49
|
-
program.command('status').description('Print active task status').action(() => {
|
|
50
|
-
runStatus();
|
|
51
|
-
});
|
|
52
|
-
}
|
package/src/commands/submit.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
|
|
3
|
-
import { findProjectRoot, loadConfig } from '../lib/config.js';
|
|
4
|
-
import * as logger from '../lib/logger.js';
|
|
5
|
-
import { requireGhAvailable } from '../lib/requirements.js';
|
|
6
|
-
import { loadState } from '../lib/state.js';
|
|
7
|
-
import { submitActiveTask } from '../lib/submit-task.js';
|
|
8
|
-
|
|
9
|
-
function fatalAndExit(message: string): never {
|
|
10
|
-
logger.fatal(message);
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function runSubmit(): Promise<void> {
|
|
15
|
-
try {
|
|
16
|
-
const projectRoot = findProjectRoot();
|
|
17
|
-
if (!projectRoot) {
|
|
18
|
-
fatalAndExit('Not inside a vexdo project.');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const config = loadConfig(projectRoot);
|
|
22
|
-
const state = loadState(projectRoot);
|
|
23
|
-
if (!state) {
|
|
24
|
-
fatalAndExit('No active task.');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
await requireGhAvailable();
|
|
28
|
-
await submitActiveTask(projectRoot, config, state);
|
|
29
|
-
} catch (error: unknown) {
|
|
30
|
-
fatalAndExit(error instanceof Error ? error.message : String(error));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function registerSubmitCommand(program: Command): void {
|
|
35
|
-
program.command('submit').description('Create PRs for active task').action(async () => {
|
|
36
|
-
await runSubmit();
|
|
37
|
-
});
|
|
38
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { Command } from 'commander';
|
|
5
|
-
|
|
6
|
-
import { registerAbortCommand } from './commands/abort.js';
|
|
7
|
-
import { registerFixCommand } from './commands/fix.js';
|
|
8
|
-
import { registerInitCommand } from './commands/init.js';
|
|
9
|
-
import { registerLogsCommand } from './commands/logs.js';
|
|
10
|
-
import { registerReviewCommand } from './commands/review.js';
|
|
11
|
-
import { registerStartCommand } from './commands/start.js';
|
|
12
|
-
import { registerStatusCommand } from './commands/status.js';
|
|
13
|
-
import { registerSubmitCommand } from './commands/submit.js';
|
|
14
|
-
import * as logger from './lib/logger.js';
|
|
15
|
-
|
|
16
|
-
const packageJsonPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'package.json');
|
|
17
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { version: string };
|
|
18
|
-
|
|
19
|
-
const program = new Command();
|
|
20
|
-
|
|
21
|
-
program
|
|
22
|
-
.name('vexdo')
|
|
23
|
-
.description('Vexdo CLI')
|
|
24
|
-
.version(packageJson.version)
|
|
25
|
-
.option('--verbose', 'Enable verbose logs')
|
|
26
|
-
.option('--dry-run', 'Print plan without making changes');
|
|
27
|
-
|
|
28
|
-
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
29
|
-
const globalOpts = actionCommand.optsWithGlobals();
|
|
30
|
-
logger.setVerbose(Boolean(globalOpts.verbose));
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
registerInitCommand(program);
|
|
34
|
-
registerStartCommand(program);
|
|
35
|
-
registerReviewCommand(program);
|
|
36
|
-
registerFixCommand(program);
|
|
37
|
-
registerSubmitCommand(program);
|
|
38
|
-
registerStatusCommand(program);
|
|
39
|
-
registerAbortCommand(program);
|
|
40
|
-
registerLogsCommand(program);
|
|
41
|
-
|
|
42
|
-
program.parse(process.argv);
|