writethevision 7.0.7 → 7.0.9
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 +27 -2
- package/package.json +1 -1
- package/src/cli.js +196 -63
- package/src/engine.js +109 -0
package/README.md
CHANGED
|
@@ -296,9 +296,34 @@ The Masterbuilder reads your vision document and coordinates the artisans.
|
|
|
296
296
|
|
|
297
297
|
## Vision Runner (Ralphy-Style)
|
|
298
298
|
|
|
299
|
-
`wtv run`
|
|
299
|
+
`wtv run` (with no arguments) launches the **Vision Runner**, an autonomous loop that executes a project vision until completion.
|
|
300
300
|
|
|
301
|
-
It
|
|
301
|
+
It is designed for "Ralphy-style" execution: give it a vision, and it runs until the vision is a reality.
|
|
302
|
+
|
|
303
|
+
### The Workflow
|
|
304
|
+
|
|
305
|
+
1. **Pick a Vision**: WTV finds `VISION.md` (root) or files in `vision/*.md` and asks you to select one.
|
|
306
|
+
2. **Generate Plan**: It prompts the engine (OpenCode, Codex, or Claude) to generate a detailed `PRD.md` with a task checklist.
|
|
307
|
+
3. **Execute Loop**:
|
|
308
|
+
- Reads `PRD.md` to find the next unchecked task.
|
|
309
|
+
- Creates a focused prompt for the engine.
|
|
310
|
+
- Runs the engine to implement the task.
|
|
311
|
+
- Verifies the task was marked completed in `PRD.md`.
|
|
312
|
+
- Appends a checkpoint to `progress.txt`.
|
|
313
|
+
- Repeats until all tasks are done.
|
|
314
|
+
4. **Finalize**: Creates a single git commit ("feat: complete <vision>") and pushes to remote (unless disabled).
|
|
315
|
+
|
|
316
|
+
### File Contracts
|
|
317
|
+
|
|
318
|
+
The Vision Runner relies on strict file conventions:
|
|
319
|
+
|
|
320
|
+
- **Input**:
|
|
321
|
+
- `VISION.md` (or `vision/*.md`): The source of truth for *what* to build.
|
|
322
|
+
- **Artifacts**:
|
|
323
|
+
- `PRD.md`: The execution checklist. Must use `- [ ]` for incomplete and `- [x]` for complete tasks. The runner stops when no `- [ ]` remain.
|
|
324
|
+
- `progress.txt`: An append-only log of every completed task with timestamps.
|
|
325
|
+
|
|
326
|
+
### Usage
|
|
302
327
|
|
|
303
328
|
```bash
|
|
304
329
|
wtv run
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "writethevision",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.9",
|
|
4
4
|
"description": "Write The Vision (WTV): vision-driven development with the Habakkuk workflow. 10 agents + 21 skills for Claude Code, Codex CLI, and OpenCode.",
|
|
5
5
|
"author": "Christopher Hogg",
|
|
6
6
|
"license": "MIT",
|
package/src/cli.js
CHANGED
|
@@ -1256,15 +1256,23 @@ async function checkForUpdates() {
|
|
|
1256
1256
|
|
|
1257
1257
|
try {
|
|
1258
1258
|
const packageName = getPackageName();
|
|
1259
|
+
const checkEveryRun = process.env.WTV_UPDATE_CHECK_EVERY_RUN === '1';
|
|
1259
1260
|
const notifier = updateNotifier({
|
|
1260
1261
|
pkg: {
|
|
1261
1262
|
name: packageName,
|
|
1262
1263
|
version: getVersion(),
|
|
1263
1264
|
},
|
|
1264
|
-
updateCheckInterval: 1000 * 60 * 60 * 24 * 7,
|
|
1265
|
+
updateCheckInterval: checkEveryRun ? Number.POSITIVE_INFINITY : 1000 * 60 * 60 * 24 * 7,
|
|
1265
1266
|
shouldNotifyInNpmScript: false,
|
|
1266
1267
|
});
|
|
1267
1268
|
|
|
1269
|
+
if (checkEveryRun) {
|
|
1270
|
+
try {
|
|
1271
|
+
notifier.update = await notifier.fetchInfo();
|
|
1272
|
+
} catch {
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1268
1276
|
const update = notifier.update;
|
|
1269
1277
|
if (!update) return;
|
|
1270
1278
|
|
|
@@ -1290,7 +1298,7 @@ Or run latest once:
|
|
|
1290
1298
|
},
|
|
1291
1299
|
});
|
|
1292
1300
|
|
|
1293
|
-
const shouldPrompt = process.stdin.isTTY && process.env.WTV_NO_AUTO_UPDATE !== '1' && !process.env.CI;
|
|
1301
|
+
const shouldPrompt = process.stdin.isTTY && process.env.WTV_NO_AUTO_UPDATE !== '1' && !process.env.CI && !checkEveryRun;
|
|
1294
1302
|
if (!shouldPrompt) return;
|
|
1295
1303
|
|
|
1296
1304
|
const isDevCheckout = existsSync(join(PACKAGE_ROOT, '.git'));
|
|
@@ -3281,13 +3289,12 @@ async function runEngine(engine, promptText) {
|
|
|
3281
3289
|
throw new Error(`Unsupported engine: ${engine}`);
|
|
3282
3290
|
}
|
|
3283
3291
|
|
|
3284
|
-
function ensureProgressHeader(progressPath, { visionPath, engine, startSha }) {
|
|
3292
|
+
export function ensureProgressHeader(progressPath, { visionPath, engine, startSha }) {
|
|
3285
3293
|
if (!existsSync(progressPath)) {
|
|
3286
3294
|
writeFileSync(progressPath, '');
|
|
3287
3295
|
}
|
|
3288
3296
|
|
|
3289
3297
|
const existing = readFileSync(progressPath, 'utf8');
|
|
3290
|
-
if (existing.trim().length > 0) return;
|
|
3291
3298
|
|
|
3292
3299
|
const startedAt = new Date().toISOString();
|
|
3293
3300
|
const header = [
|
|
@@ -3296,16 +3303,19 @@ function ensureProgressHeader(progressPath, { visionPath, engine, startSha }) {
|
|
|
3296
3303
|
`Vision: ${visionPath}`,
|
|
3297
3304
|
`Engine: ${engine}`,
|
|
3298
3305
|
startSha ? `Start SHA: ${startSha}` : `Start SHA: (not a git repo)`,
|
|
3306
|
+
startSha ? `Rollback hint: git reset --hard ${startSha}` : '',
|
|
3299
3307
|
'',
|
|
3300
3308
|
].join('\n');
|
|
3301
3309
|
|
|
3302
|
-
|
|
3310
|
+
if (existing.trim().length > 0) {
|
|
3311
|
+
writeFileSync(progressPath, existing.trimEnd() + '\n\n' + header);
|
|
3312
|
+
} else {
|
|
3313
|
+
writeFileSync(progressPath, header);
|
|
3314
|
+
}
|
|
3303
3315
|
}
|
|
3304
3316
|
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
const promptText = `You are generating a PRD for a software project.
|
|
3317
|
+
export function generatePrdPrompt(visionContent) {
|
|
3318
|
+
return `You are generating a PRD for a software project.
|
|
3309
3319
|
|
|
3310
3320
|
Input vision document:
|
|
3311
3321
|
---
|
|
@@ -3322,6 +3332,12 @@ Requirements:
|
|
|
3322
3332
|
- Do NOT implement any code yet.
|
|
3323
3333
|
- Do NOT run git commit or git push.
|
|
3324
3334
|
- Output is the updated files on disk (PRD.md).`;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
async function generatePrdFromVision({ engine, visionPath, prdPath }) {
|
|
3338
|
+
const visionContent = readFileSync(visionPath, 'utf8');
|
|
3339
|
+
|
|
3340
|
+
const promptText = generatePrdPrompt(visionContent);
|
|
3325
3341
|
|
|
3326
3342
|
const code = await runEngine(engine, promptText);
|
|
3327
3343
|
if (code !== 0) {
|
|
@@ -3338,7 +3354,7 @@ Requirements:
|
|
|
3338
3354
|
}
|
|
3339
3355
|
}
|
|
3340
3356
|
|
|
3341
|
-
async function runVisionTaskIteration({ engine, prdPath, progressPath, taskText, noTests, noLint }) {
|
|
3357
|
+
export async function runVisionTaskIteration({ engine, prdPath, progressPath, taskText, noTests, noLint, _runEngine }) {
|
|
3342
3358
|
const promptText = `You are an autonomous coding agent executing a PRD.
|
|
3343
3359
|
|
|
3344
3360
|
Files:
|
|
@@ -3350,7 +3366,7 @@ Task to implement (ONLY this task):
|
|
|
3350
3366
|
|
|
3351
3367
|
Rules:
|
|
3352
3368
|
- Implement ONLY the task above.
|
|
3353
|
-
- Update PRD.md: change that exact task from '- [ ]' to '- [x]'.
|
|
3369
|
+
- Update PRD.md: change that exact task from '- [ ]' to '- [x]' (lowercase x).
|
|
3354
3370
|
- Append a short checkpoint entry to progress.txt.
|
|
3355
3371
|
- Do NOT run git commit.
|
|
3356
3372
|
- Do NOT run git push.
|
|
@@ -3366,7 +3382,25 @@ Stop after completing this one task.`;
|
|
|
3366
3382
|
|
|
3367
3383
|
const progressBefore = existsSync(progressPath) ? readFileSync(progressPath, 'utf8') : '';
|
|
3368
3384
|
|
|
3369
|
-
const
|
|
3385
|
+
const workDir = dirname(prdPath);
|
|
3386
|
+
const gitProbe = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
3387
|
+
cwd: workDir,
|
|
3388
|
+
encoding: 'utf8',
|
|
3389
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
3390
|
+
});
|
|
3391
|
+
const hasGit = gitProbe.status === 0 && String(gitProbe.stdout || '').trim() === 'true';
|
|
3392
|
+
const statusBefore = hasGit
|
|
3393
|
+
? String(
|
|
3394
|
+
spawnSync('git', ['status', '--porcelain'], {
|
|
3395
|
+
cwd: workDir,
|
|
3396
|
+
encoding: 'utf8',
|
|
3397
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
3398
|
+
}).stdout || '',
|
|
3399
|
+
)
|
|
3400
|
+
: '';
|
|
3401
|
+
|
|
3402
|
+
const runFn = _runEngine || runEngine;
|
|
3403
|
+
const code = await runFn(engine, promptText);
|
|
3370
3404
|
if (code !== 0) {
|
|
3371
3405
|
throw new Error(`Engine exited with code ${code}`);
|
|
3372
3406
|
}
|
|
@@ -3374,10 +3408,61 @@ Stop after completing this one task.`;
|
|
|
3374
3408
|
const afterPrd = readFileSync(prdPath, 'utf8');
|
|
3375
3409
|
const afterUnchecked = countUncheckedPrdTasks(afterPrd);
|
|
3376
3410
|
|
|
3377
|
-
const
|
|
3378
|
-
const
|
|
3411
|
+
const escaped = taskText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3412
|
+
const uncheckedRe = new RegExp(`^\\s*- \\[ \\]\\s+${escaped}\\s*$`, 'm');
|
|
3413
|
+
const checkedRe = new RegExp(`^\\s*- \\[[xX]\\]\\s+${escaped}\\s*$`, 'm');
|
|
3414
|
+
|
|
3415
|
+
let stillUnchecked = uncheckedRe.test(afterPrd);
|
|
3416
|
+
let nowChecked = checkedRe.test(afterPrd);
|
|
3379
3417
|
|
|
3380
3418
|
if (stillUnchecked || (!nowChecked && afterUnchecked >= beforeUnchecked)) {
|
|
3419
|
+
const statusAfter = hasGit
|
|
3420
|
+
? String(
|
|
3421
|
+
spawnSync('git', ['status', '--porcelain'], {
|
|
3422
|
+
cwd: workDir,
|
|
3423
|
+
encoding: 'utf8',
|
|
3424
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
3425
|
+
}).stdout || '',
|
|
3426
|
+
)
|
|
3427
|
+
: '';
|
|
3428
|
+
|
|
3429
|
+
// Best-effort repair: if the engine changed files but forgot PRD.md, run a
|
|
3430
|
+
// follow-up pass that ONLY updates PRD.md and progress.txt.
|
|
3431
|
+
const hasAnyChanges = hasGit ? statusAfter !== statusBefore : false;
|
|
3432
|
+
if (hasAnyChanges) {
|
|
3433
|
+
const repairPrompt = `You previously completed this PRD task but did NOT update the checklist.
|
|
3434
|
+
|
|
3435
|
+
Fix it now by editing ONLY these files:
|
|
3436
|
+
- PRD.md
|
|
3437
|
+
- progress.txt
|
|
3438
|
+
|
|
3439
|
+
Rules:
|
|
3440
|
+
- Do NOT change any other files.
|
|
3441
|
+
- In PRD.md, find the exact checkbox line for this task and change '- [ ]' to '- [x]':
|
|
3442
|
+
${taskText}
|
|
3443
|
+
- Append a single new line to progress.txt: "[ISO_TIMESTAMP] Completed: ${taskText}"
|
|
3444
|
+
- Then stop.`;
|
|
3445
|
+
|
|
3446
|
+
const repairCode = await runFn(engine, repairPrompt);
|
|
3447
|
+
if (repairCode !== 0) {
|
|
3448
|
+
throw new Error(`Engine exited with code ${repairCode}`);
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
const repairedPrd = readFileSync(prdPath, 'utf8');
|
|
3452
|
+
const repairedUnchecked = countUncheckedPrdTasks(repairedPrd);
|
|
3453
|
+
stillUnchecked = uncheckedRe.test(repairedPrd);
|
|
3454
|
+
nowChecked = checkedRe.test(repairedPrd);
|
|
3455
|
+
|
|
3456
|
+
if (!stillUnchecked && (nowChecked || repairedUnchecked < beforeUnchecked)) {
|
|
3457
|
+
const repairedProgress = existsSync(progressPath) ? readFileSync(progressPath, 'utf8') : '';
|
|
3458
|
+
if (repairedProgress === progressBefore) {
|
|
3459
|
+
const stamp = new Date().toISOString();
|
|
3460
|
+
writeFileSync(progressPath, repairedProgress + `\n[${stamp}] Completed: ${taskText}\n`);
|
|
3461
|
+
}
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3381
3466
|
throw new Error('PRD.md was not updated to mark the task complete');
|
|
3382
3467
|
}
|
|
3383
3468
|
|
|
@@ -3388,6 +3473,71 @@ Stop after completing this one task.`;
|
|
|
3388
3473
|
}
|
|
3389
3474
|
}
|
|
3390
3475
|
|
|
3476
|
+
export function finalizeVisionRun({
|
|
3477
|
+
prdPath,
|
|
3478
|
+
visionPath,
|
|
3479
|
+
hasGit,
|
|
3480
|
+
startSha,
|
|
3481
|
+
noPush,
|
|
3482
|
+
_runGit,
|
|
3483
|
+
_readFileSync,
|
|
3484
|
+
_countUncheckedPrdTasks,
|
|
3485
|
+
_getCurrentBranch
|
|
3486
|
+
}) {
|
|
3487
|
+
const runGitFn = _runGit || runGit;
|
|
3488
|
+
const readFileSyncFn = _readFileSync || readFileSync;
|
|
3489
|
+
const countUncheckedFn = _countUncheckedPrdTasks || countUncheckedPrdTasks;
|
|
3490
|
+
const getCurrentBranchFn = _getCurrentBranch || getCurrentBranch;
|
|
3491
|
+
|
|
3492
|
+
if (!hasGit) {
|
|
3493
|
+
console.log(`\n ${c.green}${sym.check}${c.reset} PRD complete (no git repo detected).\n`);
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
const prdFinal = readFileSyncFn(prdPath, 'utf8');
|
|
3498
|
+
if (countUncheckedFn(prdFinal) > 0) {
|
|
3499
|
+
console.log(`\n ${c.yellow}${sym.warn}${c.reset} PRD still has remaining tasks; skipping commit/push.\n`);
|
|
3500
|
+
return;
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
const visionName = basename(visionPath).replace(/\.md$/, '');
|
|
3504
|
+
const commitMessage = `feat: run vision (${visionName})`;
|
|
3505
|
+
|
|
3506
|
+
const addRes = runGitFn(['add', '-A']);
|
|
3507
|
+
if (addRes.status !== 0) {
|
|
3508
|
+
console.log(`\n ${c.red}${sym.cross}${c.reset} git add failed.\n`);
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
const statusRes = runGitFn(['status', '--porcelain']);
|
|
3513
|
+
if (statusRes.status === 0 && String(statusRes.stdout || '').trim().length === 0) {
|
|
3514
|
+
console.log(`\n ${c.green}${sym.check}${c.reset} Nothing to commit.\n`);
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
const commitRes = runGitFn(['commit', '-m', commitMessage]);
|
|
3519
|
+
if (commitRes.status !== 0) {
|
|
3520
|
+
console.log(`\n ${c.red}${sym.cross}${c.reset} git commit failed.\n`);
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
if (!noPush) {
|
|
3525
|
+
const pushRes = runGitFn(['push']);
|
|
3526
|
+
if (pushRes.status !== 0) {
|
|
3527
|
+
const branch = getCurrentBranchFn();
|
|
3528
|
+
if (branch) {
|
|
3529
|
+
runGitFn(['push', '-u', 'origin', branch]);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
console.log(`\n ${c.green}${sym.check}${c.reset} Vision complete.`);
|
|
3535
|
+
if (startSha) {
|
|
3536
|
+
console.log(` Started from: ${c.dim}${startSha}${c.reset}`);
|
|
3537
|
+
}
|
|
3538
|
+
console.log('');
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3391
3541
|
async function visionRunner(opts) {
|
|
3392
3542
|
const config = loadConfig();
|
|
3393
3543
|
const interactive = process.stdout.isTTY && process.stdin.isTTY;
|
|
@@ -3458,6 +3608,12 @@ async function visionRunner(opts) {
|
|
|
3458
3608
|
const hasGit = isGitRepo();
|
|
3459
3609
|
const startSha = hasGit ? getGitHeadSha() : null;
|
|
3460
3610
|
|
|
3611
|
+
if (!hasGit) {
|
|
3612
|
+
console.log(`\n ${c.yellow}${sym.warn} Warning:${c.reset} Not a git repository. Version control features disabled.\n`);
|
|
3613
|
+
} else if (startSha) {
|
|
3614
|
+
console.log(`\n ${c.blue}${sym.info} Rollback hint:${c.reset} git reset --hard ${startSha}\n`);
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3461
3617
|
if (hasGit && !isWorkingTreeClean() && !opts.dryRun) {
|
|
3462
3618
|
if (!interactive) {
|
|
3463
3619
|
console.log(`\n ${c.red}Error:${c.reset} Working tree is not clean (non-interactive mode).\n`);
|
|
@@ -3554,53 +3710,13 @@ async function visionRunner(opts) {
|
|
|
3554
3710
|
}
|
|
3555
3711
|
}
|
|
3556
3712
|
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
console.log(`\n ${c.yellow}${sym.warn}${c.reset} PRD still has remaining tasks; skipping commit/push.\n`);
|
|
3565
|
-
return;
|
|
3566
|
-
}
|
|
3567
|
-
|
|
3568
|
-
const visionName = basename(visionPath).replace(/\.md$/, '');
|
|
3569
|
-
const commitMessage = `feat: run vision (${visionName})`;
|
|
3570
|
-
|
|
3571
|
-
const addRes = runGit(['add', '-A']);
|
|
3572
|
-
if (addRes.status !== 0) {
|
|
3573
|
-
console.log(`\n ${c.red}${sym.cross}${c.reset} git add failed.\n`);
|
|
3574
|
-
return;
|
|
3575
|
-
}
|
|
3576
|
-
|
|
3577
|
-
const statusRes = runGit(['status', '--porcelain']);
|
|
3578
|
-
if (statusRes.status === 0 && String(statusRes.stdout || '').trim().length === 0) {
|
|
3579
|
-
console.log(`\n ${c.green}${sym.check}${c.reset} Nothing to commit.\n`);
|
|
3580
|
-
return;
|
|
3581
|
-
}
|
|
3582
|
-
|
|
3583
|
-
const commitRes = runGit(['commit', '-m', commitMessage]);
|
|
3584
|
-
if (commitRes.status !== 0) {
|
|
3585
|
-
console.log(`\n ${c.red}${sym.cross}${c.reset} git commit failed.\n`);
|
|
3586
|
-
return;
|
|
3587
|
-
}
|
|
3588
|
-
|
|
3589
|
-
if (!opts.noPush) {
|
|
3590
|
-
const pushRes = runGit(['push']);
|
|
3591
|
-
if (pushRes.status !== 0) {
|
|
3592
|
-
const branch = getCurrentBranch();
|
|
3593
|
-
if (branch) {
|
|
3594
|
-
runGit(['push', '-u', 'origin', branch]);
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
|
|
3599
|
-
console.log(`\n ${c.green}${sym.check}${c.reset} Vision complete.`);
|
|
3600
|
-
if (startSha) {
|
|
3601
|
-
console.log(` Started from: ${c.dim}${startSha}${c.reset}`);
|
|
3602
|
-
}
|
|
3603
|
-
console.log('');
|
|
3713
|
+
finalizeVisionRun({
|
|
3714
|
+
prdPath,
|
|
3715
|
+
visionPath,
|
|
3716
|
+
hasGit,
|
|
3717
|
+
startSha,
|
|
3718
|
+
noPush: opts.noPush
|
|
3719
|
+
});
|
|
3604
3720
|
}
|
|
3605
3721
|
|
|
3606
3722
|
// ============================================================================
|
|
@@ -4570,7 +4686,7 @@ function showHelp() {
|
|
|
4570
4686
|
console.log(`${pad} ${c.cyan}vision${c.reset} Show VISION.md status`);
|
|
4571
4687
|
console.log(`${pad} ${c.cyan}log${c.reset} Show task logs`);
|
|
4572
4688
|
console.log(`${pad} ${c.cyan}meet${c.reset} Meet Paul and the Artisans`);
|
|
4573
|
-
console.log(`${pad} ${c.cyan}run${c.reset} Run the vision (
|
|
4689
|
+
console.log(`${pad} ${c.cyan}run${c.reset} Run the vision (Vision Runner)`);
|
|
4574
4690
|
console.log(`${pad} ${c.cyan}help${c.reset} Show this help\n`);
|
|
4575
4691
|
|
|
4576
4692
|
console.log(`${pad}${c.bold}Agent Commands${c.reset}`);
|
|
@@ -4581,12 +4697,17 @@ function showHelp() {
|
|
|
4581
4697
|
console.log(`${pad} ${c.cyan}agents fav${c.reset} <name> Toggle favorite`);
|
|
4582
4698
|
console.log(`${pad} ${c.cyan}agents rm${c.reset} <name> Remove agent\n`);
|
|
4583
4699
|
|
|
4700
|
+
console.log(`${pad}${c.bold}Vision Runner${c.reset} ${c.dim}"That he may run that readeth it"${c.reset}`);
|
|
4701
|
+
console.log(`${pad} ${c.cyan}run${c.reset} Launch Vision Runner (interactive)`);
|
|
4702
|
+
console.log(`${pad} ${c.cyan}run${c.reset} --vision <f> Execute specific vision file`);
|
|
4703
|
+
console.log(`${pad} ${c.cyan}run${c.reset} --resume Resume existing PRD.md\n`);
|
|
4704
|
+
|
|
4584
4705
|
console.log(`${pad}${c.bold}Habakkuk Workflow${c.reset} ${c.dim}"Write the vision, make it plain"${c.reset}`);
|
|
4585
4706
|
console.log(`${pad} ${c.cyan}board${c.reset} [--all] Show kanban board`);
|
|
4586
4707
|
console.log(`${pad} ${c.cyan}cry${c.reset} "desc" Enter a problem or need`);
|
|
4587
4708
|
console.log(`${pad} ${c.cyan}wait${c.reset} <id> Move to waiting (seeking)`);
|
|
4588
4709
|
console.log(`${pad} ${c.cyan}vision${c.reset} <id> Move to vision (answer received)`);
|
|
4589
|
-
console.log(`${pad} ${c.cyan}run${c.reset}
|
|
4710
|
+
console.log(`${pad} ${c.cyan}run${c.reset} <id> Move to run (execution started)`);
|
|
4590
4711
|
console.log(`${pad} ${c.cyan}worship${c.reset} <id> Move to worship (retrospective)`);
|
|
4591
4712
|
console.log(`${pad} ${c.cyan}note${c.reset} <id> "text" Add note to item`);
|
|
4592
4713
|
console.log(`${pad} ${c.cyan}item${c.reset} <id> Show item details`);
|
|
@@ -4604,6 +4725,18 @@ function showHelp() {
|
|
|
4604
4725
|
console.log(`${pad} ${c.dim}--gemini${c.reset} Target Gemini CLI`);
|
|
4605
4726
|
console.log(`${pad} ${c.dim}--antigravity${c.reset} Target Antigravity\n`);
|
|
4606
4727
|
|
|
4728
|
+
console.log(`${pad}${c.bold}Vision Runner Options${c.reset}`);
|
|
4729
|
+
console.log(`${pad} ${c.dim}--vision <file>${c.reset} Input vision file (default: discover)`);
|
|
4730
|
+
console.log(`${pad} ${c.dim}--engine <name>${c.reset} Execution engine (opencode|codex|claude)`);
|
|
4731
|
+
console.log(`${pad} ${c.dim}--resume${c.reset} Resume existing PRD.md`);
|
|
4732
|
+
console.log(`${pad} ${c.dim}--regenerate-prd${c.reset} Force regeneration of PRD.md from vision`);
|
|
4733
|
+
console.log(`${pad} ${c.dim}--max-iterations <n>${c.reset} Stop after N tasks`);
|
|
4734
|
+
console.log(`${pad} ${c.dim}--dry-run${c.reset} Show what would happen without running`);
|
|
4735
|
+
console.log(`${pad} ${c.dim}--fast${c.reset} Skip tests and linting`);
|
|
4736
|
+
console.log(`${pad} ${c.dim}--no-tests${c.reset} Skip tests`);
|
|
4737
|
+
console.log(`${pad} ${c.dim}--no-lint${c.reset} Skip linting`);
|
|
4738
|
+
console.log(`${pad} ${c.dim}--no-push${c.reset} Skip git push at end\n`);
|
|
4739
|
+
|
|
4607
4740
|
console.log(`${pad}${c.bold}Examples${c.reset}`);
|
|
4608
4741
|
console.log(`${pad} ${c.green}wtv${c.reset} ${c.dim}# Dashboard${c.reset}`);
|
|
4609
4742
|
console.log(`${pad} ${c.green}wtv agents${c.reset} ${c.dim}# List agents${c.reset}`);
|
package/src/engine.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base class for AI CLI engine adapters.
|
|
5
|
+
*/
|
|
6
|
+
class EngineAdapter {
|
|
7
|
+
constructor(cwd = process.cwd()) {
|
|
8
|
+
this.cwd = cwd;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the command and arguments for the engine.
|
|
13
|
+
* @param {string} prompt - The prompt to execute.
|
|
14
|
+
* @returns {{ command: string, args: string[] }}
|
|
15
|
+
*/
|
|
16
|
+
getCommand(prompt) {
|
|
17
|
+
throw new Error('getCommand must be implemented by subclass');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run the engine with the given prompt.
|
|
22
|
+
* @param {string} prompt - The prompt to execute.
|
|
23
|
+
* @param {object} options - Options for spawn.
|
|
24
|
+
* @returns {Promise<number>} - Exit code.
|
|
25
|
+
*/
|
|
26
|
+
async run(prompt, options = {}) {
|
|
27
|
+
const { command, args } = this.getCommand(prompt);
|
|
28
|
+
|
|
29
|
+
console.log(`[Engine] Running: ${command} ${args.join(' ')}`);
|
|
30
|
+
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const child = spawn(command, args, {
|
|
33
|
+
cwd: this.cwd,
|
|
34
|
+
stdio: 'inherit', // Stream output to parent stdout/stderr
|
|
35
|
+
shell: true, // Use shell to resolve command paths
|
|
36
|
+
...options
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
child.on('error', (err) => {
|
|
40
|
+
reject(err);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
child.on('close', (code) => {
|
|
44
|
+
resolve(code);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adapter for OpenCode CLI.
|
|
52
|
+
*/
|
|
53
|
+
class OpenCodeEngine extends EngineAdapter {
|
|
54
|
+
getCommand(prompt) {
|
|
55
|
+
// Assumption: opencode takes the prompt as a single argument string
|
|
56
|
+
return {
|
|
57
|
+
command: 'opencode',
|
|
58
|
+
args: [prompt]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Adapter for Codex CLI.
|
|
65
|
+
*/
|
|
66
|
+
class CodexEngine extends EngineAdapter {
|
|
67
|
+
getCommand(prompt) {
|
|
68
|
+
// Assumption: codex takes the prompt as a single argument string
|
|
69
|
+
return {
|
|
70
|
+
command: 'codex',
|
|
71
|
+
args: [prompt]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Adapter for Claude Code CLI.
|
|
78
|
+
*/
|
|
79
|
+
class ClaudeEngine extends EngineAdapter {
|
|
80
|
+
getCommand(prompt) {
|
|
81
|
+
// Assumption: claude takes the prompt as a single argument string
|
|
82
|
+
// Note: Check if it needs -p or similar flag
|
|
83
|
+
return {
|
|
84
|
+
command: 'claude',
|
|
85
|
+
args: [prompt]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Factory to create the appropriate engine adapter.
|
|
92
|
+
* @param {string} name - The name of the engine ('opencode', 'codex', 'claude').
|
|
93
|
+
* @param {string} cwd - Current working directory.
|
|
94
|
+
* @returns {EngineAdapter}
|
|
95
|
+
*/
|
|
96
|
+
export function createEngine(name, cwd) {
|
|
97
|
+
switch (name.toLowerCase()) {
|
|
98
|
+
case 'opencode':
|
|
99
|
+
return new OpenCodeEngine(cwd);
|
|
100
|
+
case 'codex':
|
|
101
|
+
return new CodexEngine(cwd);
|
|
102
|
+
case 'claude':
|
|
103
|
+
return new ClaudeEngine(cwd);
|
|
104
|
+
default:
|
|
105
|
+
throw new Error(`Unsupported engine: ${name}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { EngineAdapter, OpenCodeEngine, CodexEngine, ClaudeEngine };
|