@ttfw/envoi 1.0.8 → 1.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 +6 -5
- package/dist/commands/onboard.d.ts +4 -0
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +355 -13
- package/dist/commands/onboard.js.map +1 -1
- package/dist/lib/blocked.d.ts.map +1 -1
- package/dist/lib/blocked.js +3 -2
- package/dist/lib/blocked.js.map +1 -1
- package/dist/lib/doctor.d.ts +25 -2
- package/dist/lib/doctor.d.ts.map +1 -1
- package/dist/lib/doctor.js +109 -4
- package/dist/lib/doctor.js.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/runner/orchestrator.d.ts.map +1 -1
- package/dist/runner/orchestrator.js +10 -0
- package/dist/runner/orchestrator.js.map +1 -1
- package/package.json +1 -1
- package/relais/prompts/orchestrator.user.txt +1 -0
package/README.md
CHANGED
|
@@ -89,13 +89,14 @@ envoi start
|
|
|
89
89
|
6. configures reviewer (`codex|none`, default recommended: `codex`),
|
|
90
90
|
7. auto-initializes git (`git init`) when no repository exists,
|
|
91
91
|
8. runs role connectivity checks,
|
|
92
|
-
9. writes `ROADMAP.json` from the planner snapshot,
|
|
93
|
-
10. asks whether to start execution now
|
|
92
|
+
9. writes `envoi/ROADMAP.json` from the planner snapshot,
|
|
93
|
+
10. asks whether to start execution now,
|
|
94
|
+
11. if starting immediately in a brand-new repo, auto-commits scaffold files before first tick.
|
|
94
95
|
|
|
95
96
|
Non-TTY (agent-run) onboarding:
|
|
96
97
|
- Envoi emits exactly one next question per run (`envoi.onboarding.next_question.v1`) and returns exit code `0` by default (to avoid noisy error wrappers in agent CLIs).
|
|
97
98
|
- Reply with one answer envelope at a time (`envoi.onboarding.answer.v1`), then rerun to get the next question.
|
|
98
|
-
- After required fields are collected, Envoi asks for explicit review/confirm (`confirm`, `confirm_and_start`, or `edit:<field>`).
|
|
99
|
+
- After required fields are collected, Envoi asks for explicit review/confirm (`confirm`, `confirm_and_start`, or `edit:<field>`), including full PRD content plus preview/path.
|
|
99
100
|
- Output defaults to JSON payloads; switch with:
|
|
100
101
|
- `--json` (single-line payload; explicit for automation)
|
|
101
102
|
- `--verbose-onboarding` (human + pretty JSON)
|
|
@@ -131,7 +132,7 @@ envoi idea "Need staging validation faster" --testability soon --target-by 2026-
|
|
|
131
132
|
|
|
132
133
|
Ideas are persisted in state (`idea_inbox`) and fed into planner decisions.
|
|
133
134
|
Planner should choose sequencing, scope, and timing; user should not be forced into low-level technical choices.
|
|
134
|
-
Planner snapshots are written to ROADMAP.json and refreshed by onboarding when needed.
|
|
135
|
+
Planner snapshots are written to `envoi/ROADMAP.json` and refreshed by onboarding when needed.
|
|
135
136
|
|
|
136
137
|
## Optional Reviewer Gate
|
|
137
138
|
When configured, reviewer can:
|
|
@@ -237,6 +238,7 @@ Default runtime files live under `envoi/`:
|
|
|
237
238
|
- `REPORT.json`
|
|
238
239
|
- `REPORT.md` (optional)
|
|
239
240
|
- `BLOCKED.json`
|
|
241
|
+
- `ROADMAP.json`
|
|
240
242
|
- `history/<run_id>/...`
|
|
241
243
|
|
|
242
244
|
## PR Attribution
|
|
@@ -255,5 +257,4 @@ pnpm typecheck
|
|
|
255
257
|
## Additional Docs
|
|
256
258
|
- `docs/DOGFOOD.md`
|
|
257
259
|
- `docs/NEW-PLAN.md`
|
|
258
|
-
- `ROADMAP.json`
|
|
259
260
|
- `pilot/` (legacy workflow docs)
|
|
@@ -46,6 +46,8 @@ interface OnboardingReviewPayload {
|
|
|
46
46
|
reviewer?: string;
|
|
47
47
|
reviewer_model?: string;
|
|
48
48
|
prd_preview?: string;
|
|
49
|
+
prd_full?: string;
|
|
50
|
+
prd_path?: string;
|
|
49
51
|
};
|
|
50
52
|
options: Array<{
|
|
51
53
|
value: 'confirm' | 'confirm_and_start' | 'edit';
|
|
@@ -100,6 +102,8 @@ export declare function resolveOnboardingNeedsInputPolicy(options: {
|
|
|
100
102
|
export declare function buildNonInteractiveQuestions(input: OnboardingQuestionInputs): OnboardingQuestion[];
|
|
101
103
|
export declare function parseInlineAnswer(line: string): OnboardingAnswerPayload;
|
|
102
104
|
export declare function normalizeQuestionAnswer(question: OnboardingQuestion, raw: unknown): string | undefined;
|
|
105
|
+
export declare function parseGitPorcelainPaths(stdout: string): string[];
|
|
106
|
+
export declare function isScaffoldOnlyInitialDirtyFiles(files: string[], workspaceDir: string, configFileName: string): boolean;
|
|
103
107
|
export declare function buildReviewPayload(sessionId: string, state: InlineAnswersState): OnboardingReviewPayload;
|
|
104
108
|
export declare function isMeaningfulPrdText(value: string): boolean;
|
|
105
109
|
export interface OnboardCommandResult {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAqBH,KAAK,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC;AACpD,KAAK,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC;AAC9C,KAAK,eAAe,GAAG,aAAa,GAAG,SAAS,CAAC;AACjD,KAAK,cAAc,GAAG,MAAM,GAAG,OAAO,CAAC;AA2BvC,QAAA,MAAM,8BAA8B,+BAA+B,CAAC;AAOpE,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAElE,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,oBAAoB,CAAC;IACjC,UAAU,EAAE,OAAO,CAAC;CACrB;AAsBD,KAAK,sBAAsB,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,sBAAsB,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAoCD,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,8BAA8B,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/onboard.js
CHANGED
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
* - Optionally start execution immediately
|
|
11
11
|
*/
|
|
12
12
|
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
|
13
|
-
import {
|
|
13
|
+
import { resolve, join, relative } from 'node:path';
|
|
14
14
|
import { spawnSync } from 'node:child_process';
|
|
15
15
|
import { randomUUID } from 'node:crypto';
|
|
16
16
|
import readline from 'node:readline/promises';
|
|
17
17
|
import { stdin as input, stdout as output } from 'node:process';
|
|
18
18
|
import { atomicReadJson, atomicWriteJson } from '../lib/fs.js';
|
|
19
19
|
import { CONFIG_FILE_NAME, findConfigFile, loadConfig, validateConfig, ConfigError } from '../lib/config.js';
|
|
20
|
-
import { checkCursorAgent, checkCodexCli } from '../lib/doctor.js';
|
|
20
|
+
import { checkCursorAgent, checkCodexCli, checkClaudeCodeCli } from '../lib/doctor.js';
|
|
21
21
|
import { getGitTopLevel, isGitRepo, getHeadCommit } from '../lib/git.js';
|
|
22
22
|
import { CLI_NAME, PRODUCT_NAME, PACKAGE_NAME } from '../lib/branding.js';
|
|
23
23
|
import { createInitialState } from '../lib/state.js';
|
|
@@ -422,6 +422,89 @@ function summarizePrdPreview(text) {
|
|
|
422
422
|
return compact;
|
|
423
423
|
return `${compact.slice(0, 177)}...`;
|
|
424
424
|
}
|
|
425
|
+
function normalizeGitStatusPath(pathText) {
|
|
426
|
+
const trimmed = pathText.trim();
|
|
427
|
+
if (!trimmed.startsWith('"') || !trimmed.endsWith('"'))
|
|
428
|
+
return trimmed;
|
|
429
|
+
const body = trimmed.slice(1, -1);
|
|
430
|
+
return body
|
|
431
|
+
.replace(/\\n/g, '\n')
|
|
432
|
+
.replace(/\\t/g, '\t')
|
|
433
|
+
.replace(/\\"/g, '"')
|
|
434
|
+
.replace(/\\\\/g, '\\');
|
|
435
|
+
}
|
|
436
|
+
export function parseGitPorcelainPaths(stdout) {
|
|
437
|
+
return stdout
|
|
438
|
+
.split(/\r?\n/)
|
|
439
|
+
.map((line) => line.trimEnd())
|
|
440
|
+
.filter((line) => line.length >= 4)
|
|
441
|
+
.map((line) => {
|
|
442
|
+
const pathPart = line.slice(3).trim();
|
|
443
|
+
const renamedIndex = pathPart.indexOf(' -> ');
|
|
444
|
+
const candidate = renamedIndex >= 0 ? pathPart.slice(renamedIndex + 4) : pathPart;
|
|
445
|
+
return normalizeGitStatusPath(candidate);
|
|
446
|
+
})
|
|
447
|
+
.filter((path) => path.length > 0);
|
|
448
|
+
}
|
|
449
|
+
export function isScaffoldOnlyInitialDirtyFiles(files, workspaceDir, configFileName) {
|
|
450
|
+
const normalizedWorkspacePrefix = `${workspaceDir.replace(/^\.\/+/, '').replace(/\/+$/, '')}/`;
|
|
451
|
+
return files.every((file) => {
|
|
452
|
+
const normalized = file.replace(/^\.\/+/, '');
|
|
453
|
+
return (normalized === '.gitignore' ||
|
|
454
|
+
normalized === configFileName ||
|
|
455
|
+
normalized.startsWith(normalizedWorkspacePrefix));
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
async function autoCommitInitialScaffoldIfSafe(config, configFilePath) {
|
|
459
|
+
if (!isGitRepo())
|
|
460
|
+
return;
|
|
461
|
+
const hasHeadCommit = spawnSync('git', ['rev-parse', '--verify', 'HEAD'], { stdio: 'pipe' }).status === 0;
|
|
462
|
+
if (hasHeadCommit)
|
|
463
|
+
return;
|
|
464
|
+
const statusResult = spawnSync('git', ['status', '--porcelain'], { stdio: 'pipe' });
|
|
465
|
+
if (statusResult.status !== 0)
|
|
466
|
+
return;
|
|
467
|
+
const dirtyFiles = parseGitPorcelainPaths(statusResult.stdout?.toString('utf-8') ?? '');
|
|
468
|
+
if (dirtyFiles.length === 0) {
|
|
469
|
+
console.log('Skipping auto-start: repository has no initial commit yet.');
|
|
470
|
+
console.log('Create an initial commit, then run envoi start again.');
|
|
471
|
+
throw new Error('Initial repository commit required before auto-start.');
|
|
472
|
+
}
|
|
473
|
+
const normalizedConfigPath = (() => {
|
|
474
|
+
if (!configFilePath)
|
|
475
|
+
return CONFIG_FILE_NAME;
|
|
476
|
+
const relPath = relative(process.cwd(), resolve(configFilePath)).replace(/\\/g, '/');
|
|
477
|
+
return relPath.startsWith('../') ? CONFIG_FILE_NAME : relPath;
|
|
478
|
+
})();
|
|
479
|
+
if (!isScaffoldOnlyInitialDirtyFiles(dirtyFiles, config.workspace_dir, normalizedConfigPath)) {
|
|
480
|
+
console.log('Skipping auto-start: initial repo has non-Envoi uncommitted files.');
|
|
481
|
+
console.log('Commit your existing files first, then run envoi start again.');
|
|
482
|
+
throw new Error('Initial repository contains non-scaffold changes; not auto-committing.');
|
|
483
|
+
}
|
|
484
|
+
const addResult = spawnSync('git', ['add', '--', '.gitignore', normalizedConfigPath, config.workspace_dir], { stdio: 'pipe' });
|
|
485
|
+
if (addResult.status !== 0) {
|
|
486
|
+
const stderr = addResult.stderr?.toString('utf-8').trim();
|
|
487
|
+
throw new Error(`Failed to stage Envoi scaffold for initial commit: ${stderr || 'git add failed'}`);
|
|
488
|
+
}
|
|
489
|
+
const commitResult = spawnSync('git', [
|
|
490
|
+
'-c',
|
|
491
|
+
'user.name=Envoi',
|
|
492
|
+
'-c',
|
|
493
|
+
'user.email=envoi@local',
|
|
494
|
+
'commit',
|
|
495
|
+
'-m',
|
|
496
|
+
'Initialize Envoi workspace',
|
|
497
|
+
], { stdio: 'pipe' });
|
|
498
|
+
if (commitResult.status !== 0) {
|
|
499
|
+
const stderr = commitResult.stderr?.toString('utf-8').trim();
|
|
500
|
+
const stdout = commitResult.stdout?.toString('utf-8').trim();
|
|
501
|
+
const combined = `${stderr}\n${stdout}`.toLowerCase();
|
|
502
|
+
if (combined.includes('nothing to commit'))
|
|
503
|
+
return;
|
|
504
|
+
throw new Error(`Failed to create initial scaffold commit: ${stderr || stdout || 'git commit failed'}`);
|
|
505
|
+
}
|
|
506
|
+
console.log('Created initial git commit for Envoi scaffold.');
|
|
507
|
+
}
|
|
425
508
|
function clearInlineField(state, field) {
|
|
426
509
|
switch (field) {
|
|
427
510
|
case 'mode':
|
|
@@ -495,10 +578,11 @@ function applyQuestionValue(state, questionId, value) {
|
|
|
495
578
|
}
|
|
496
579
|
}
|
|
497
580
|
export function buildReviewPayload(sessionId, state) {
|
|
581
|
+
const prdFull = (state.prdText ?? '').trim();
|
|
498
582
|
return {
|
|
499
583
|
type: ONBOARDING_REVIEW_PAYLOAD_TYPE,
|
|
500
584
|
session_id: sessionId,
|
|
501
|
-
message: 'Review onboarding choices before saving.
|
|
585
|
+
message: 'Review onboarding choices before saving. Full PRD is included below and saved to envoi/PRD.md.',
|
|
502
586
|
summary: {
|
|
503
587
|
mode: state.mode,
|
|
504
588
|
builder: state.builder,
|
|
@@ -507,7 +591,9 @@ export function buildReviewPayload(sessionId, state) {
|
|
|
507
591
|
builder_model: state.builderModel,
|
|
508
592
|
reviewer: state.reviewer,
|
|
509
593
|
reviewer_model: state.reviewerModel,
|
|
510
|
-
prd_preview: summarizePrdPreview(
|
|
594
|
+
prd_preview: summarizePrdPreview(prdFull),
|
|
595
|
+
prd_full: prdFull,
|
|
596
|
+
prd_path: 'envoi/PRD.md',
|
|
511
597
|
},
|
|
512
598
|
options: [
|
|
513
599
|
{ value: 'confirm', label: 'confirm', desc: 'Save and continue onboarding.' },
|
|
@@ -776,6 +862,8 @@ function buildReviewQuestion(state) {
|
|
|
776
862
|
`reviewer=${summary.reviewer ?? 'n/a'}`,
|
|
777
863
|
`reviewer_model=${summary.reviewer_model ?? 'n/a'}`,
|
|
778
864
|
`prd_preview=${summary.prd_preview ?? '(empty)'}`,
|
|
865
|
+
`prd_path=${summary.prd_path ?? 'envoi/PRD.md'}`,
|
|
866
|
+
`prd_full=${summary.prd_full ?? '(empty)'}`,
|
|
779
867
|
].join('\n');
|
|
780
868
|
return {
|
|
781
869
|
id: 'review',
|
|
@@ -933,6 +1021,211 @@ export function isMeaningfulPrdText(value) {
|
|
|
933
1021
|
.trim();
|
|
934
1022
|
return compact.length >= 30;
|
|
935
1023
|
}
|
|
1024
|
+
function roleLabel(role) {
|
|
1025
|
+
if (role === 'planner')
|
|
1026
|
+
return 'planner';
|
|
1027
|
+
if (role === 'builder')
|
|
1028
|
+
return 'builder';
|
|
1029
|
+
return 'reviewer';
|
|
1030
|
+
}
|
|
1031
|
+
function isAuthenticatedStatus(status) {
|
|
1032
|
+
return status === 'authenticated' || status === 'api_key_present';
|
|
1033
|
+
}
|
|
1034
|
+
function mergeAuthIssue(issues, issue) {
|
|
1035
|
+
const existing = issues.get(issue.key);
|
|
1036
|
+
if (!existing) {
|
|
1037
|
+
issues.set(issue.key, issue);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
const mergedRoles = new Set([...existing.roles, ...issue.roles]);
|
|
1041
|
+
existing.roles = Array.from(mergedRoles);
|
|
1042
|
+
}
|
|
1043
|
+
function formatAuthIssue(issue) {
|
|
1044
|
+
const roles = issue.roles.map((role) => roleLabel(role)).join('/');
|
|
1045
|
+
const details = issue.details ? ` (${issue.details})` : '';
|
|
1046
|
+
const remediation = issue.remediation.map((step) => `- ${step}`).join('\n');
|
|
1047
|
+
return `${roles}: ${issue.reason}${details}\n${remediation}`;
|
|
1048
|
+
}
|
|
1049
|
+
function buildAuthQuestion(issues) {
|
|
1050
|
+
const helpLines = [
|
|
1051
|
+
'Authenticate the required CLI(s), then reply with: done',
|
|
1052
|
+
...issues.flatMap((issue) => formatAuthIssue(issue).split('\n')),
|
|
1053
|
+
];
|
|
1054
|
+
return {
|
|
1055
|
+
id: 'auth',
|
|
1056
|
+
label: 'Authentication required before continuing onboarding',
|
|
1057
|
+
type: 'text',
|
|
1058
|
+
required: true,
|
|
1059
|
+
default: 'done',
|
|
1060
|
+
help: helpLines.join('\n'),
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
function printAuthIssues(issues) {
|
|
1064
|
+
console.log('\nAuthentication required before continuing onboarding:');
|
|
1065
|
+
for (const issue of issues) {
|
|
1066
|
+
console.log(`- ${formatAuthIssue(issue).replace(/\n/g, '\n ')}`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
async function collectOnboardingAuthIssues(params) {
|
|
1070
|
+
const issues = new Map();
|
|
1071
|
+
const claudeCommand = params.config.claude_code_cli.command ?? 'claude';
|
|
1072
|
+
let claudeCheckPromise;
|
|
1073
|
+
let codexCheckPromise;
|
|
1074
|
+
let cursorCheckPromise;
|
|
1075
|
+
const checkClaude = async () => {
|
|
1076
|
+
if (!claudeCheckPromise)
|
|
1077
|
+
claudeCheckPromise = checkClaudeCodeCli(claudeCommand);
|
|
1078
|
+
return await claudeCheckPromise;
|
|
1079
|
+
};
|
|
1080
|
+
const checkCodex = async () => {
|
|
1081
|
+
if (!codexCheckPromise)
|
|
1082
|
+
codexCheckPromise = checkCodexCli(params.config);
|
|
1083
|
+
return await codexCheckPromise;
|
|
1084
|
+
};
|
|
1085
|
+
const checkCursor = async () => {
|
|
1086
|
+
if (!cursorCheckPromise)
|
|
1087
|
+
cursorCheckPromise = checkCursorAgent(params.config);
|
|
1088
|
+
return await cursorCheckPromise;
|
|
1089
|
+
};
|
|
1090
|
+
if (params.plannerProvider === 'claude_code') {
|
|
1091
|
+
const claude = await checkClaude();
|
|
1092
|
+
if (!claude.cli_available) {
|
|
1093
|
+
mergeAuthIssue(issues, {
|
|
1094
|
+
key: 'claude-missing-cli',
|
|
1095
|
+
roles: ['planner'],
|
|
1096
|
+
reason: `CLI '${claude.command}' is not available`,
|
|
1097
|
+
remediation: [
|
|
1098
|
+
`Install Claude Code and ensure '${claude.command}' is on your PATH.`,
|
|
1099
|
+
],
|
|
1100
|
+
details: claude.details,
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
else if (!isAuthenticatedStatus(claude.auth_status)) {
|
|
1104
|
+
mergeAuthIssue(issues, {
|
|
1105
|
+
key: 'claude-auth-required',
|
|
1106
|
+
roles: ['planner'],
|
|
1107
|
+
reason: `CLI '${claude.command}' is not authenticated`,
|
|
1108
|
+
remediation: [
|
|
1109
|
+
`Run '${claude.command}' and complete login/authentication in the CLI.`,
|
|
1110
|
+
"If you use API auth, set ANTHROPIC_API_KEY in your environment.",
|
|
1111
|
+
],
|
|
1112
|
+
details: claude.details,
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
const codex = await checkCodex();
|
|
1118
|
+
if (!codex.cli_available) {
|
|
1119
|
+
mergeAuthIssue(issues, {
|
|
1120
|
+
key: 'codex-missing-cli',
|
|
1121
|
+
roles: ['planner'],
|
|
1122
|
+
reason: "CLI 'codex' is not available",
|
|
1123
|
+
remediation: [
|
|
1124
|
+
"Install Codex CLI and ensure 'codex' is on your PATH.",
|
|
1125
|
+
],
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
else if (!isAuthenticatedStatus(codex.auth_status)) {
|
|
1129
|
+
mergeAuthIssue(issues, {
|
|
1130
|
+
key: 'codex-auth-required',
|
|
1131
|
+
roles: ['planner'],
|
|
1132
|
+
reason: "CLI 'codex' is not authenticated",
|
|
1133
|
+
remediation: [
|
|
1134
|
+
"Run 'codex login' in this shell.",
|
|
1135
|
+
'Or set CODEX_API_KEY in your environment.',
|
|
1136
|
+
],
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (params.builderChoice === 'cursor') {
|
|
1141
|
+
const cursor = await checkCursor();
|
|
1142
|
+
if (!cursor.cli_available) {
|
|
1143
|
+
mergeAuthIssue(issues, {
|
|
1144
|
+
key: 'cursor-missing-cli',
|
|
1145
|
+
roles: ['builder'],
|
|
1146
|
+
reason: `CLI '${cursor.command}' is not available`,
|
|
1147
|
+
remediation: [
|
|
1148
|
+
`Install Cursor CLI and ensure '${cursor.command}' is on your PATH.`,
|
|
1149
|
+
],
|
|
1150
|
+
details: cursor.details,
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
else if (!cursor.agent_available) {
|
|
1154
|
+
mergeAuthIssue(issues, {
|
|
1155
|
+
key: 'cursor-agent-missing',
|
|
1156
|
+
roles: ['builder'],
|
|
1157
|
+
reason: `'${cursor.command} agent' is unavailable`,
|
|
1158
|
+
remediation: [
|
|
1159
|
+
`Update Cursor CLI so '${cursor.command} agent' is supported.`,
|
|
1160
|
+
],
|
|
1161
|
+
details: cursor.details,
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
else if (!isAuthenticatedStatus(cursor.auth_status)) {
|
|
1165
|
+
mergeAuthIssue(issues, {
|
|
1166
|
+
key: 'cursor-auth-required',
|
|
1167
|
+
roles: ['builder'],
|
|
1168
|
+
reason: `'${cursor.command} agent' is not authenticated`,
|
|
1169
|
+
remediation: [
|
|
1170
|
+
`Run '${cursor.command} agent login' in this shell.`,
|
|
1171
|
+
'Or set CURSOR_API_KEY in your environment.',
|
|
1172
|
+
],
|
|
1173
|
+
details: cursor.details,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
const claude = await checkClaude();
|
|
1179
|
+
if (!claude.cli_available) {
|
|
1180
|
+
mergeAuthIssue(issues, {
|
|
1181
|
+
key: 'claude-missing-cli',
|
|
1182
|
+
roles: ['builder'],
|
|
1183
|
+
reason: `CLI '${claude.command}' is not available`,
|
|
1184
|
+
remediation: [
|
|
1185
|
+
`Install Claude Code and ensure '${claude.command}' is on your PATH.`,
|
|
1186
|
+
],
|
|
1187
|
+
details: claude.details,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
else if (!isAuthenticatedStatus(claude.auth_status)) {
|
|
1191
|
+
mergeAuthIssue(issues, {
|
|
1192
|
+
key: 'claude-auth-required',
|
|
1193
|
+
roles: ['builder'],
|
|
1194
|
+
reason: `CLI '${claude.command}' is not authenticated`,
|
|
1195
|
+
remediation: [
|
|
1196
|
+
`Run '${claude.command}' and complete login/authentication in the CLI.`,
|
|
1197
|
+
"If you use API auth, set ANTHROPIC_API_KEY in your environment.",
|
|
1198
|
+
],
|
|
1199
|
+
details: claude.details,
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (params.reviewerChoice === 'codex') {
|
|
1204
|
+
const codex = await checkCodex();
|
|
1205
|
+
if (!codex.cli_available) {
|
|
1206
|
+
mergeAuthIssue(issues, {
|
|
1207
|
+
key: 'codex-missing-cli',
|
|
1208
|
+
roles: ['reviewer'],
|
|
1209
|
+
reason: "CLI 'codex' is not available",
|
|
1210
|
+
remediation: [
|
|
1211
|
+
"Install Codex CLI and ensure 'codex' is on your PATH.",
|
|
1212
|
+
],
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
else if (!isAuthenticatedStatus(codex.auth_status)) {
|
|
1216
|
+
mergeAuthIssue(issues, {
|
|
1217
|
+
key: 'codex-auth-required',
|
|
1218
|
+
roles: ['reviewer'],
|
|
1219
|
+
reason: "CLI 'codex' is not authenticated",
|
|
1220
|
+
remediation: [
|
|
1221
|
+
"Run 'codex login' in this shell.",
|
|
1222
|
+
'Or set CODEX_API_KEY in your environment.',
|
|
1223
|
+
],
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return Array.from(issues.values());
|
|
1228
|
+
}
|
|
936
1229
|
function normalizeReviewerChoice(value) {
|
|
937
1230
|
if (!value || value.trim() === '')
|
|
938
1231
|
return 'none';
|
|
@@ -1354,8 +1647,8 @@ async function generateRoadmapDraft(config, mode) {
|
|
|
1354
1647
|
},
|
|
1355
1648
|
};
|
|
1356
1649
|
}
|
|
1357
|
-
function printRoadmapPreview(roadmap) {
|
|
1358
|
-
console.log(
|
|
1650
|
+
function printRoadmapPreview(roadmap, roadmapPath) {
|
|
1651
|
+
console.log(`\nPlanner snapshot (${roadmapPath}):`);
|
|
1359
1652
|
console.log(`- Source: ${roadmap.source}`);
|
|
1360
1653
|
console.log(`- Task: ${roadmap.task_id} (${roadmap.milestone_id})`);
|
|
1361
1654
|
if (roadmap.milestones.length > 0) {
|
|
@@ -1424,8 +1717,11 @@ async function runConnectivityChecks(config) {
|
|
|
1424
1717
|
if (!codex.cli_available) {
|
|
1425
1718
|
console.log('- checker: BLOCKED (codex CLI not available)');
|
|
1426
1719
|
}
|
|
1427
|
-
else if (codex.auth_status === 'api_key_present') {
|
|
1428
|
-
console.log('- checker: CLEARED (codex CLI
|
|
1720
|
+
else if (codex.auth_status === 'api_key_present' || codex.auth_status === 'authenticated') {
|
|
1721
|
+
console.log('- checker: CLEARED (codex CLI authenticated)');
|
|
1722
|
+
}
|
|
1723
|
+
else if (codex.auth_status === 'unauthenticated') {
|
|
1724
|
+
console.log('- checker: STANDBY (codex CLI available; login required)');
|
|
1429
1725
|
}
|
|
1430
1726
|
else {
|
|
1431
1727
|
console.log('- checker: STANDBY (codex CLI available; auth status unknown)');
|
|
@@ -1445,13 +1741,14 @@ async function runConnectivityChecks(config) {
|
|
|
1445
1741
|
console.log('- checker: STANDBY (optional reviewer disabled)');
|
|
1446
1742
|
}
|
|
1447
1743
|
}
|
|
1448
|
-
async function maybeStartRun(config, mode, autoRun) {
|
|
1744
|
+
async function maybeStartRun(config, mode, autoRun, configFilePath) {
|
|
1449
1745
|
if (!autoRun) {
|
|
1450
1746
|
console.log('\nSetup complete. Waiting for you.');
|
|
1451
1747
|
console.log(`- Run '${CLI_NAME} tick' for one bounded cycle`);
|
|
1452
1748
|
console.log(`- Or run '${CLI_NAME} loop --mode ${mode}'`);
|
|
1453
1749
|
return;
|
|
1454
1750
|
}
|
|
1751
|
+
await autoCommitInitialScaffoldIfSafe(config, configFilePath);
|
|
1455
1752
|
console.log(`\nStarting now using mode '${mode}'...`);
|
|
1456
1753
|
if (mode === 'task') {
|
|
1457
1754
|
const { runTick } = await import('../runner/tick.js');
|
|
@@ -1528,7 +1825,7 @@ export async function onboardCommand(options) {
|
|
|
1528
1825
|
console.log(`${PRODUCT_NAME} onboarding already complete.`);
|
|
1529
1826
|
const mode = raw.runner.default_loop_mode ?? defaults.mode;
|
|
1530
1827
|
const autoRunRequested = options.autoRun ?? false;
|
|
1531
|
-
await maybeStartRun(raw, mode, autoRunRequested);
|
|
1828
|
+
await maybeStartRun(raw, mode, autoRunRequested, configPath);
|
|
1532
1829
|
return { needsInput: false };
|
|
1533
1830
|
}
|
|
1534
1831
|
const sessionId = asNonEmptyString(answerBag.__session_id) ??
|
|
@@ -1604,6 +1901,28 @@ export async function onboardCommand(options) {
|
|
|
1604
1901
|
});
|
|
1605
1902
|
return { needsInput: true };
|
|
1606
1903
|
}
|
|
1904
|
+
const effectivePlannerProvider = plannerProviderSeed ?? defaults.plannerProvider;
|
|
1905
|
+
const effectiveBuilderChoice = builderSeed ?? defaults.builder;
|
|
1906
|
+
const effectiveReviewerChoice = reviewerSeed ?? defaults.reviewer;
|
|
1907
|
+
const authIssues = await collectOnboardingAuthIssues({
|
|
1908
|
+
config: raw,
|
|
1909
|
+
plannerProvider: effectivePlannerProvider,
|
|
1910
|
+
builderChoice: effectiveBuilderChoice,
|
|
1911
|
+
reviewerChoice: effectiveReviewerChoice,
|
|
1912
|
+
});
|
|
1913
|
+
if (authIssues.length > 0) {
|
|
1914
|
+
await emitNextQuestion({
|
|
1915
|
+
workspaceDir: config.workspace_dir,
|
|
1916
|
+
sessionId,
|
|
1917
|
+
question: buildAuthQuestion(authIssues),
|
|
1918
|
+
pendingQuestionIds: ['auth'],
|
|
1919
|
+
answers: answerBag,
|
|
1920
|
+
awaitingReview: false,
|
|
1921
|
+
policy: needsInputPolicy,
|
|
1922
|
+
});
|
|
1923
|
+
return { needsInput: true };
|
|
1924
|
+
}
|
|
1925
|
+
delete answerBag.auth;
|
|
1607
1926
|
const parsedReviewAction = parseReviewAction(asNonEmptyString(answerBag.__review_action));
|
|
1608
1927
|
if (!parsedReviewAction) {
|
|
1609
1928
|
const reviewQuestion = buildReviewQuestion({
|
|
@@ -1784,6 +2103,29 @@ export async function onboardCommand(options) {
|
|
|
1784
2103
|
else if (raw.reviewer) {
|
|
1785
2104
|
raw.reviewer = { ...raw.reviewer, enabled: false };
|
|
1786
2105
|
}
|
|
2106
|
+
const authIssues = await collectOnboardingAuthIssues({
|
|
2107
|
+
config: raw,
|
|
2108
|
+
plannerProvider,
|
|
2109
|
+
builderChoice,
|
|
2110
|
+
reviewerChoice,
|
|
2111
|
+
});
|
|
2112
|
+
if (authIssues.length > 0) {
|
|
2113
|
+
if (input.isTTY) {
|
|
2114
|
+
printAuthIssues(authIssues);
|
|
2115
|
+
throw new Error('Authentication required. Complete the steps above, then rerun onboarding.');
|
|
2116
|
+
}
|
|
2117
|
+
await emitNextQuestion({
|
|
2118
|
+
workspaceDir: config.workspace_dir,
|
|
2119
|
+
sessionId,
|
|
2120
|
+
question: buildAuthQuestion(authIssues),
|
|
2121
|
+
pendingQuestionIds: ['auth'],
|
|
2122
|
+
answers: answerBag,
|
|
2123
|
+
awaitingReview: false,
|
|
2124
|
+
policy: needsInputPolicy,
|
|
2125
|
+
});
|
|
2126
|
+
return { needsInput: true };
|
|
2127
|
+
}
|
|
2128
|
+
delete answerBag.auth;
|
|
1787
2129
|
// Ensure PRD is runner-owned (protects from builder edits)
|
|
1788
2130
|
if (Array.isArray(raw.runner.runner_owned_globs) && !raw.runner.runner_owned_globs.includes(`${config.workspace_dir}/PRD.md`)) {
|
|
1789
2131
|
raw.runner.runner_owned_globs = [...raw.runner.runner_owned_globs, `${config.workspace_dir}/PRD.md`];
|
|
@@ -1813,7 +2155,7 @@ export async function onboardCommand(options) {
|
|
|
1813
2155
|
console.log(`- Planner: ${plannerProvider} (${orchestratorModel})`);
|
|
1814
2156
|
console.log(`- Reviewer: ${raw.reviewer?.enabled ? 'enabled' : 'disabled'}`);
|
|
1815
2157
|
await runConnectivityChecks(raw);
|
|
1816
|
-
const roadmapPath = join(
|
|
2158
|
+
const roadmapPath = join(config.workspace_dir, 'ROADMAP.json');
|
|
1817
2159
|
let existingPrdContent = '';
|
|
1818
2160
|
try {
|
|
1819
2161
|
existingPrdContent = (await readFile(prdPath, 'utf-8')).trim();
|
|
@@ -1826,7 +2168,7 @@ export async function onboardCommand(options) {
|
|
|
1826
2168
|
const draftResult = await generateRoadmapDraft(raw, mode);
|
|
1827
2169
|
if (draftResult.draft) {
|
|
1828
2170
|
await writeFile(roadmapPath, `${JSON.stringify(draftResult.draft, null, 2)}\n`, 'utf-8');
|
|
1829
|
-
printRoadmapPreview(draftResult.draft);
|
|
2171
|
+
printRoadmapPreview(draftResult.draft, roadmapPath);
|
|
1830
2172
|
console.log(`- Saved: ${roadmapPath}`);
|
|
1831
2173
|
}
|
|
1832
2174
|
else {
|
|
@@ -1841,7 +2183,7 @@ export async function onboardCommand(options) {
|
|
|
1841
2183
|
const shouldRunNow = input.isTTY
|
|
1842
2184
|
? await promptYesNo(`Start now with mode '${mode}'?`, autoRunRequested)
|
|
1843
2185
|
: nonInteractiveStartNow;
|
|
1844
|
-
await maybeStartRun(raw, mode, shouldRunNow);
|
|
2186
|
+
await maybeStartRun(raw, mode, shouldRunNow, configPath);
|
|
1845
2187
|
return { needsInput: false };
|
|
1846
2188
|
}
|
|
1847
2189
|
//# sourceMappingURL=onboard.js.map
|