@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 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":"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"}
1
+ {"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"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"}
@@ -10,14 +10,14 @@
10
10
  * - Optionally start execution immediately
11
11
  */
12
12
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
13
- import { dirname, resolve, join } from 'node:path';
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. Reply with confirm, confirm_and_start, or edit.',
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(state.prdText ?? ''),
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('\nPlanner snapshot (ROADMAP.json):');
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 + API key detected)');
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(dirname(configPath), 'ROADMAP.json');
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