mandrel 1.63.0 → 1.64.0

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.
@@ -195,13 +195,13 @@ function runGit(args, cwd) {
195
195
  * `GhExecError` — so a bare "gh exited with code 1" is actually diagnosable.
196
196
  */
197
197
  function logGhError(label, err) {
198
- Logger.error(`[bootstrap] ${label} failed: ${err.message}`);
198
+ Logger.error(`[Bootstrap] ${label} failed: ${err.message}`);
199
199
  if (err.stderr)
200
- Logger.error(`[bootstrap] gh stderr: ${String(err.stderr).trim()}`);
200
+ Logger.error(`[Bootstrap] gh stderr: ${String(err.stderr).trim()}`);
201
201
  if (err.stdout)
202
- Logger.error(`[bootstrap] gh stdout: ${String(err.stdout).trim()}`);
202
+ Logger.error(`[Bootstrap] gh stdout: ${String(err.stdout).trim()}`);
203
203
  if (Array.isArray(err.args)) {
204
- Logger.error(`[bootstrap] gh args: ${err.args.join(' ')}`);
204
+ Logger.error(`[Bootstrap] gh args: ${err.args.join(' ')}`);
205
205
  }
206
206
  }
207
207
 
@@ -249,7 +249,7 @@ function ensureGitInitialized(state) {
249
249
  initialized = true;
250
250
  state.gitInitialized = true;
251
251
  Logger.info(
252
- `[bootstrap] Initialized git repo (branch ${branch}) at ${cwd}.`,
252
+ `[Bootstrap] Initialized git repo (branch ${branch}) at ${cwd}.`,
253
253
  );
254
254
  }
255
255
 
@@ -281,7 +281,7 @@ function ensureGitInitialized(state) {
281
281
  return { ok: false, error: commit.stderr || 'git commit failed' };
282
282
  }
283
283
  committed = true;
284
- Logger.info('[bootstrap] Created initial commit.');
284
+ Logger.info('[Bootstrap] Created initial commit.');
285
285
  }
286
286
  return { ok: true, initialized, committed };
287
287
  }
@@ -303,21 +303,21 @@ async function ensureGitRemote(state, execImpl = exec) {
303
303
  if (runGit(['remote', 'get-url', 'origin'], cwd).ok) return;
304
304
  if (!(await repoExists(owner, repo, execImpl))) {
305
305
  Logger.warn(
306
- `[bootstrap] No 'origin' remote and ${owner}/${repo} does not exist on GitHub — skipping remote wiring.`,
306
+ `[Bootstrap] No 'origin' remote and ${owner}/${repo} does not exist on GitHub — skipping remote wiring.`,
307
307
  );
308
308
  return;
309
309
  }
310
310
  const url = `https://github.com/${owner}/${repo}.git`;
311
311
  const add = runGit(['remote', 'add', 'origin', url], cwd);
312
312
  if (!add.ok) {
313
- Logger.warn(`[bootstrap] Could not add 'origin' remote: ${add.stderr}`);
313
+ Logger.warn(`[Bootstrap] Could not add 'origin' remote: ${add.stderr}`);
314
314
  return;
315
315
  }
316
- Logger.info(`[bootstrap] Wired 'origin' → ${url}.`);
316
+ Logger.info(`[Bootstrap] Wired 'origin' → ${url}.`);
317
317
  const push = runGit(['push', '-u', 'origin', branch], cwd);
318
318
  if (!push.ok) {
319
319
  Logger.warn(
320
- `[bootstrap] 'origin' is set but push of '${branch}' failed (resolve manually, e.g. \`git pull --rebase origin ${branch}\`): ${push.stderr}`,
320
+ `[Bootstrap] 'origin' is set but push of '${branch}' failed (resolve manually, e.g. \`git pull --rebase origin ${branch}\`): ${push.stderr}`,
321
321
  );
322
322
  }
323
323
  }
@@ -359,11 +359,11 @@ async function ensureProjectLinked(state, execImpl = exec) {
359
359
  args: ['project', 'link', pn, '--owner', owner, '--repo', repo],
360
360
  });
361
361
  Logger.info(
362
- `[bootstrap] Linked repo ${owner}/${repo} to Project V2 #${pn}.`,
362
+ `[Bootstrap] Linked repo ${owner}/${repo} to Project V2 #${pn}.`,
363
363
  );
364
364
  } catch (err) {
365
365
  Logger.warn(
366
- `[bootstrap] Could not link repo ${owner}/${repo} to Project V2 #${pn} (continuing): ${err.message}`,
366
+ `[Bootstrap] Could not link repo ${owner}/${repo} to Project V2 #${pn} (continuing): ${err.message}`,
367
367
  );
368
368
  }
369
369
  }
@@ -413,7 +413,7 @@ async function createGithubRepo(state, execImpl = exec) {
413
413
  ],
414
414
  });
415
415
  Logger.info(
416
- `[bootstrap] Created GitHub repo ${slug} (${visibility}) and pushed.`,
416
+ `[Bootstrap] Created GitHub repo ${slug} (${visibility}) and pushed.`,
417
417
  );
418
418
  }
419
419
 
@@ -480,7 +480,7 @@ async function createGithubProject(state, execImpl = exec) {
480
480
  if (Number.isInteger(existing)) {
481
481
  state.answers.projectNumber = String(existing);
482
482
  Logger.info(
483
- `[bootstrap] Reusing existing GitHub Project V2 "${title}" (#${existing}) — no duplicate created.`,
483
+ `[Bootstrap] Reusing existing GitHub Project V2 "${title}" (#${existing}) — no duplicate created.`,
484
484
  );
485
485
  return existing;
486
486
  }
@@ -510,7 +510,7 @@ async function createGithubProject(state, execImpl = exec) {
510
510
  );
511
511
  }
512
512
  state.answers.projectNumber = String(number);
513
- Logger.info(`[bootstrap] Created GitHub Project V2 "${title}" (#${number}).`);
513
+ Logger.info(`[Bootstrap] Created GitHub Project V2 "${title}" (#${number}).`);
514
514
  return number;
515
515
  }
516
516
 
@@ -539,7 +539,7 @@ export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
539
539
  key: 'owner',
540
540
  flag: 'owner',
541
541
  env: 'GH_OWNER',
542
- message: 'Github repo owner',
542
+ message: '\n\nGitHub repo owner',
543
543
  default: defaults.owner,
544
544
  required: true,
545
545
  validate: (v) =>
@@ -549,7 +549,8 @@ export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
549
549
  key: 'operatorHandle',
550
550
  flag: 'operator-handle',
551
551
  env: 'GH_OPERATOR_HANDLE',
552
- message: 'Github username/handle (without the @)',
552
+ message:
553
+ 'GitHub username/handle without preceding@ (default: same as owner)',
553
554
  // Default tracks the repo owner; resolved post-collect if left blank.
554
555
  default: defaults.owner,
555
556
  required: false,
@@ -562,8 +563,9 @@ export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
562
563
  key: 'repo',
563
564
  flag: 'repo',
564
565
  env: 'GH_REPO',
565
- message:
566
- 'Github repo name - Select from the list or enter a new name to create one',
566
+ message: 'New GitHub repo name',
567
+ pickerMessage:
568
+ 'GitHub repo name - Select existing or press ENTER to create',
567
569
  default: defaults.repo,
568
570
  required: true,
569
571
  picker: {
@@ -590,8 +592,9 @@ export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
590
592
  key: 'projectNumber',
591
593
  flag: 'project-number',
592
594
  env: 'GH_PROJECT_NUMBER',
593
- message:
594
- 'Github Project V2 name - Select from the list or enter a new name to create one',
595
+ message: 'New GitHub Project V2 name',
596
+ pickerMessage:
597
+ 'GitHub Project V2 name - Select existing or press ENTER to create',
595
598
  // Prefer the already-stored numeric project number (an
596
599
  // already-provisioned project on a re-run) so `--assume-yes` resolves a
597
600
  // numeric answer that `detectCreation` treats as existing — never a
@@ -750,7 +753,7 @@ export function parseAndValidate(argv, opts = {}) {
750
753
  // `--assume-yes` path did.)
751
754
  if (!interactive && !assumeYes && !approveGithubAdmin) {
752
755
  Logger.error(
753
- '[bootstrap] non-TTY run requires --assume-yes or --approve-github-admin ' +
756
+ '[Bootstrap] non-TTY run requires --assume-yes or --approve-github-admin ' +
754
757
  '(no operator is present to confirm the GitHub-admin mutations).',
755
758
  );
756
759
  return { ok: false, exit: 1 };
@@ -760,7 +763,7 @@ export function parseAndValidate(argv, opts = {}) {
760
763
  const githubAdminApproved = interactive || assumeYes || approveGithubAdmin;
761
764
  if (resolveRepoVisibility(flags) === null) {
762
765
  Logger.error(
763
- `[bootstrap] invalid --visibility "${flags.visibility}". ` +
766
+ `[Bootstrap] invalid --visibility "${flags.visibility}". ` +
764
767
  `Expected one of: ${REPO_VISIBILITIES.join(', ')}.`,
765
768
  );
766
769
  return { ok: false, exit: 1 };
@@ -783,11 +786,12 @@ export function prepareContext(state, opts = {}) {
783
786
  const defaults = inferDefaults(projectRoot);
784
787
  const silentAccept = resolveSilentAccept(defaults, state.flags);
785
788
 
786
- Logger.info('[bootstrap] Detected from local git:');
787
- Logger.info(` owner ${defaults.owner ?? '(none)'}`);
788
- Logger.info(` repo ${defaults.repo ?? '(none)'}`);
789
- Logger.info(` base branch ${defaults.baseBranch ?? '(none)'}`);
790
- Logger.info(` username ${defaults.operatorHandle ?? '(none)'}`);
789
+ Logger.info('[\n');
790
+ Logger.info('[Bootstrap] Checking existing GitHub values:');
791
+ Logger.info(` GitHub Repo Owner ${defaults.owner ?? '(unknown)'}`);
792
+ Logger.info(` GitHub Repo Name ${defaults.repo ?? '(unknown)'}`);
793
+ Logger.info(` Base Branch ${defaults.baseBranch ?? '(unknown)'}`);
794
+ Logger.info(` GitHub Username ${defaults.operatorHandle ?? '(unknown)'}`);
791
795
 
792
796
  return {
793
797
  ok: true,
@@ -811,24 +815,34 @@ export async function runPreflightPhase(state, opts = {}) {
811
815
 
812
816
  for (const check of result.checks) {
813
817
  if (check.ok) {
818
+ // A non-fatal informational check (it carries `gitInitialized`) shows a
819
+ // glyph reflecting the real state rather than its always-true gate pass:
820
+ // ✓ when the git repo exists, ✗ when it does not (bootstrap initialises
821
+ // it in a later phase regardless — the ✗ never aborts the run).
822
+ const glyph =
823
+ typeof check.gitInitialized === 'boolean' && !check.gitInitialized
824
+ ? '✗'
825
+ : '✓';
814
826
  Logger.info(
815
- `[bootstrap] ${check.name}${check.detail ? ` — ${check.detail}` : ''}`,
827
+ `[Bootstrap] ${glyph} ${check.name}${check.detail ? ` — ${check.detail}` : ''}`,
816
828
  );
817
829
  } else {
818
- Logger.error(`[bootstrap] ✗ ${check.name}: ${check.remedy}`);
830
+ Logger.error(`[Bootstrap] ✗ ${check.name}: ${check.remedy}`);
819
831
  }
820
832
  }
821
833
 
822
834
  if (!result.ok) {
823
835
  Logger.error(
824
- '[bootstrap] Preflight failed. Resolve the issues above and re-run.',
836
+ '[Bootstrap] Preflight failed. Resolve the issues above and re-run.',
825
837
  );
826
838
  return { ok: false, exit: 1 };
827
839
  }
828
840
 
829
- Logger.info(
830
- `[bootstrap] git initialized: ${result.gitInitialized ? 'yes' : 'no'}`,
831
- );
841
+ // The git-repo state is already reported by the (non-fatal) "Local git
842
+ // initialized" check above both derive from the same
843
+ // `git rev-parse --is-inside-work-tree` probe — so there is no separate
844
+ // "git initialized" line here (it would duplicate, and contradict, that
845
+ // check). The boolean is still threaded through the payload for later phases.
832
846
  return {
833
847
  ok: true,
834
848
  payload: { preflight: result, gitInitialized: result.gitInitialized },
@@ -844,15 +858,15 @@ function renderAnswerSummary(
844
858
  visibility,
845
859
  ) {
846
860
  const newRepoNote = creation.newRepo
847
- ? ` (NEW — will be created, ${visibility})`
861
+ ? ` will be created as ${visibility}`
848
862
  : '';
849
863
  const lines = [
850
- '\n=== Review your answers ===',
864
+ '=== Review choices ===',
851
865
  ` Repo owner ${answers.owner}`,
852
866
  ` Username/handle ${answers.operatorHandle || '(none)'}`,
853
867
  ` Repo name ${answers.repo}${newRepoNote}`,
854
868
  ` Base branch ${answers.baseBranch}`,
855
- ` Project V2 name ${project.name}${creation.newProject ? ' (NEW — will be created)' : ''}`,
869
+ ` Project V2 name ${project.name}${creation.newProject ? ' will be created' : ''}`,
856
870
  ` Project V2 # ${project.number}`,
857
871
  ` Local git ${gitInitialized ? 'initialized' : 'will be initialized'}`,
858
872
  ];
@@ -943,7 +957,7 @@ export async function collectAndConfirm(state) {
943
957
  });
944
958
  if (missing.length > 0) {
945
959
  Logger.error(
946
- `[bootstrap] missing required answers: ${missing.join(', ')}`,
960
+ `[Bootstrap] missing required answers: ${missing.join(', ')}`,
947
961
  );
948
962
  return { ok: false, exit: 1 };
949
963
  }
@@ -968,7 +982,7 @@ export async function collectAndConfirm(state) {
968
982
  );
969
983
  const correct = await confirmYesNo('Is this correct?', state.interactive);
970
984
  if (!correct) {
971
- Logger.info('[bootstrap] Okay — let’s try again.');
985
+ Logger.info('[Bootstrap] Okay — let’s try again.');
972
986
  // Re-prompt everything on the next pass (drop silent-accept).
973
987
  silentAccept = [];
974
988
  continue;
@@ -982,7 +996,7 @@ export async function collectAndConfirm(state) {
982
996
  );
983
997
  if (!approved) {
984
998
  Logger.error(
985
- '[bootstrap] Creation declined — cannot continue without the repo/project. Exiting.',
999
+ '[Bootstrap] Creation declined — cannot continue without the repo/project. Exiting.',
986
1000
  );
987
1001
  return { ok: false, exit: 1 };
988
1002
  }
@@ -1025,7 +1039,7 @@ function renderDryRunPlan(state) {
1025
1039
  export function dryRunPlan(state) {
1026
1040
  if (!state.flags['dry-run']) return { ok: true, payload: {} };
1027
1041
  Logger.info(
1028
- '[bootstrap] --dry-run: no files, GitHub settings, or labels will be changed.',
1042
+ '[Bootstrap] --dry-run: no files, GitHub settings, or labels will be changed.',
1029
1043
  );
1030
1044
  Logger.info(renderDryRunPlan(state));
1031
1045
  return { ok: false, exit: 0 };
@@ -1062,18 +1076,18 @@ export async function provisionResources(state, deps = {}) {
1062
1076
  // 1. Local git — initialize + first commit when missing (idempotent).
1063
1077
  const git = ensureGitInitialized(state);
1064
1078
  if (!git.ok) {
1065
- Logger.error(`[bootstrap] git initialization failed: ${git.error}`);
1079
+ Logger.error(`[Bootstrap] git initialization failed: ${git.error}`);
1066
1080
  return { ok: false, exit: 1 };
1067
1081
  }
1068
1082
  if (!git.initialized && !git.committed) {
1069
- Logger.info('[bootstrap] git already initialized — leaving as-is.');
1083
+ Logger.info('[Bootstrap] git already initialized — leaving as-is.');
1070
1084
  }
1071
1085
 
1072
1086
  const { newRepo, newProject } = state.creation;
1073
1087
  if (skipGithub) {
1074
1088
  if (newRepo || newProject) {
1075
1089
  Logger.info(
1076
- '[bootstrap] --skip-github set; not creating the GitHub repo/project.',
1090
+ '[Bootstrap] --skip-github set; not creating the GitHub repo/project.',
1077
1091
  );
1078
1092
  }
1079
1093
  return { ok: true, payload: {} };
@@ -1108,7 +1122,7 @@ export async function provisionResources(state, deps = {}) {
1108
1122
  }
1109
1123
 
1110
1124
  if (!newRepo && !newProject) {
1111
- Logger.info('[bootstrap] No new GitHub resources needed.');
1125
+ Logger.info('[Bootstrap] No new GitHub resources needed.');
1112
1126
  }
1113
1127
 
1114
1128
  // 4. Link the repo to the project board so issues/PRs surface on it
@@ -1125,7 +1139,7 @@ export async function provisionResources(state, deps = {}) {
1125
1139
  */
1126
1140
  export async function executeBootstrap(state) {
1127
1141
  Logger.info(
1128
- `[bootstrap] Starting project bootstrap at ${state.projectRoot} (owner=${state.answers.owner} repo=${state.answers.repo} base=${state.answers.baseBranch})`,
1142
+ `[Bootstrap] Starting project bootstrap at ${state.projectRoot} (owner=${state.answers.owner} repo=${state.answers.repo} base=${state.answers.baseBranch})`,
1129
1143
  );
1130
1144
  const approvedGroups = new Set(Object.values(PHASE_GROUPS));
1131
1145
  const report = await applyProjectBootstrap({
@@ -1158,7 +1172,7 @@ export function persistProjectNumber(state) {
1158
1172
  config = JSON.parse(fs.readFileSync(target, 'utf8'));
1159
1173
  } catch (err) {
1160
1174
  Logger.error(
1161
- `[bootstrap] Could not read ${target} to store projectNumber: ${err.message}`,
1175
+ `[Bootstrap] Could not read ${target} to store projectNumber: ${err.message}`,
1162
1176
  );
1163
1177
  return { ok: true, payload: {} };
1164
1178
  }
@@ -1172,14 +1186,14 @@ export function persistProjectNumber(state) {
1172
1186
  }
1173
1187
  config.github.projectNumber = Number(pn);
1174
1188
  fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
1175
- Logger.info(`[bootstrap] Stored github.projectNumber=${pn} in .agentrc.json`);
1189
+ Logger.info(`[Bootstrap] Stored github.projectNumber=${pn} in .agentrc.json`);
1176
1190
  return { ok: true, payload: {} };
1177
1191
  }
1178
1192
 
1179
1193
  /** Step 6b — GitHub-side bootstrap. Honours `--skip-github`. */
1180
1194
  export async function executeGithubBootstrap(state) {
1181
1195
  if (state.flags['skip-github']) {
1182
- Logger.info('[bootstrap] --skip-github set; skipping GitHub bootstrap.');
1196
+ Logger.info('[Bootstrap] --skip-github set; skipping GitHub bootstrap.');
1183
1197
  return { ok: true, payload: {} };
1184
1198
  }
1185
1199
  try {
@@ -1274,7 +1288,7 @@ export async function offerCommitPush(state, deps = {}) {
1274
1288
  // Non-interactive (--assume-yes / no TTY): never push unprompted. Print the
1275
1289
  // exact commands and leave the working tree untouched.
1276
1290
  if (!state.interactive) {
1277
- Logger.info(`\n[bootstrap] ${instructions}`);
1291
+ Logger.info(`\n[Bootstrap] ${instructions}`);
1278
1292
  return { ok: true, payload: { commitPush: { action: 'instructed' } } };
1279
1293
  }
1280
1294
 
@@ -1283,14 +1297,14 @@ export async function offerCommitPush(state, deps = {}) {
1283
1297
  state.interactive,
1284
1298
  );
1285
1299
  if (!accepted) {
1286
- Logger.info(`\n[bootstrap] ${instructions}`);
1300
+ Logger.info(`\n[Bootstrap] ${instructions}`);
1287
1301
  return { ok: true, payload: { commitPush: { action: 'declined' } } };
1288
1302
  }
1289
1303
 
1290
1304
  const staged = stageBootstrapFiles({ projectRoot: cwd, runGit: runGitImpl });
1291
1305
  if (!staged.ok) {
1292
- Logger.warn(`[bootstrap] Could not stage the wiring: ${staged.error}`);
1293
- Logger.info(`\n[bootstrap] ${instructions}`);
1306
+ Logger.warn(`[Bootstrap] Could not stage the wiring: ${staged.error}`);
1307
+ Logger.info(`\n[Bootstrap] ${instructions}`);
1294
1308
  return { ok: true, payload: { commitPush: { action: 'stage-failed' } } };
1295
1309
  }
1296
1310
  const commit = runGitImpl(
@@ -1300,20 +1314,20 @@ export async function offerCommitPush(state, deps = {}) {
1300
1314
  if (!commit.ok) {
1301
1315
  // A "nothing to commit" exit is benign — the wiring is already committed.
1302
1316
  Logger.warn(
1303
- `[bootstrap] git commit did not create a commit (already committed?): ${commit.stderr || commit.stdout}`,
1317
+ `[Bootstrap] git commit did not create a commit (already committed?): ${commit.stderr || commit.stdout}`,
1304
1318
  );
1305
- Logger.info(`\n[bootstrap] ${instructions}`);
1319
+ Logger.info(`\n[Bootstrap] ${instructions}`);
1306
1320
  return { ok: true, payload: { commitPush: { action: 'commit-skipped' } } };
1307
1321
  }
1308
- Logger.info('[bootstrap] Committed the Mandrel wiring.');
1322
+ Logger.info('[Bootstrap] Committed the Mandrel wiring.');
1309
1323
  const push = runGitImpl(['push', '-u', 'origin', branch], cwd);
1310
1324
  if (!push.ok) {
1311
1325
  Logger.warn(
1312
- `[bootstrap] Commit landed but push of '${branch}' failed (push it manually with \`git push -u origin ${branch}\`): ${push.stderr}`,
1326
+ `[Bootstrap] Commit landed but push of '${branch}' failed (push it manually with \`git push -u origin ${branch}\`): ${push.stderr}`,
1313
1327
  );
1314
1328
  return { ok: true, payload: { commitPush: { action: 'push-failed' } } };
1315
1329
  }
1316
- Logger.info(`[bootstrap] Pushed '${branch}' to origin.`);
1330
+ Logger.info(`[Bootstrap] Pushed '${branch}' to origin.`);
1317
1331
  return { ok: true, payload: { commitPush: { action: 'committed-pushed' } } };
1318
1332
  }
1319
1333
 
@@ -1357,7 +1371,7 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
1357
1371
  const githubError = result.state?.report?.github?.error;
1358
1372
  if (githubError) {
1359
1373
  Logger.error(
1360
- `\n[bootstrap] GitHub bootstrap failed: ${githubError}. ` +
1374
+ `\n[Bootstrap] GitHub bootstrap failed: ${githubError}. ` +
1361
1375
  'Project-side setup (labels are GitHub-side; the local .agentrc.json / ' +
1362
1376
  'quality-gate / workflow files that were applied are recorded in the ' +
1363
1377
  'install ledger) completed, but the GitHub label/board/views/protection ' +
@@ -1368,7 +1382,7 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
1368
1382
  return 1;
1369
1383
  }
1370
1384
 
1371
- Logger.info('\n[bootstrap] Done.');
1385
+ Logger.info('\n[Bootstrap] Done.');
1372
1386
  return 0;
1373
1387
  }
1374
1388
 
@@ -126,7 +126,7 @@ export async function applyBranchProtection({
126
126
 
127
127
  if (enforce === false) {
128
128
  log(
129
- `[bootstrap] Branch protection on '${baseBranch}': skipped (github.branchProtection.enforce=false).`,
129
+ `[Bootstrap] Branch protection on '${baseBranch}': skipped (github.branchProtection.enforce=false).`,
130
130
  );
131
131
  return { status: 'skipped', reason: 'opt-out' };
132
132
  }
@@ -136,7 +136,7 @@ export async function applyBranchProtection({
136
136
  .filter((n) => typeof n === 'string' && n.length > 0);
137
137
  if (checkNames.length === 0) {
138
138
  log(
139
- `[bootstrap] Branch protection on '${baseBranch}': skipped (no github.branchProtection.requiredChecks configured).`,
139
+ `[Bootstrap] Branch protection on '${baseBranch}': skipped (no github.branchProtection.requiredChecks configured).`,
140
140
  );
141
141
  return { status: 'skipped', reason: 'no-checks' };
142
142
  }
@@ -152,12 +152,12 @@ export async function applyBranchProtection({
152
152
  exists = await provider.branchExists(baseBranch);
153
153
  } catch (err) {
154
154
  log(
155
- `[bootstrap] Branch protection on '${baseBranch}': existence probe failed — ${err.message}. Proceeding with the write attempt.`,
155
+ `[Bootstrap] Branch protection on '${baseBranch}': existence probe failed — ${err.message}. Proceeding with the write attempt.`,
156
156
  );
157
157
  }
158
158
  if (!exists) {
159
159
  log(
160
- `[bootstrap] Branch protection on '${baseBranch}': skipped (base branch does not exist on the remote — push an initial commit first).`,
160
+ `[Bootstrap] Branch protection on '${baseBranch}': skipped (base branch does not exist on the remote — push an initial commit first).`,
161
161
  );
162
162
  return { status: 'skipped', reason: 'no-base-branch' };
163
163
  }
@@ -169,7 +169,7 @@ export async function applyBranchProtection({
169
169
  current = probe?.enabled ? (probe.raw ?? null) : null;
170
170
  } catch (err) {
171
171
  log(
172
- `[bootstrap] Branch protection on '${baseBranch}': read failed — ${err.message}. Proceeding as if no rule exists.`,
172
+ `[Bootstrap] Branch protection on '${baseBranch}': read failed — ${err.message}. Proceeding as if no rule exists.`,
173
173
  );
174
174
  }
175
175
 
@@ -193,7 +193,7 @@ export async function applyBranchProtection({
193
193
  : false;
194
194
  if (!approved) {
195
195
  log(
196
- `[bootstrap] Branch protection on '${baseBranch}': diverges from framework stance; HITL declined / non-TTY — leaving the operator's rule untouched.`,
196
+ `[Bootstrap] Branch protection on '${baseBranch}': diverges from framework stance; HITL declined / non-TTY — leaving the operator's rule untouched.`,
197
197
  );
198
198
  return { status: 'skipped', reason: 'hitl-declined', diff };
199
199
  }
@@ -210,12 +210,12 @@ export async function applyBranchProtection({
210
210
  ? ` (added: ${result.added.join(', ')})`
211
211
  : ' (all required checks already present)';
212
212
  log(
213
- `[bootstrap] Branch protection on '${baseBranch}': ${verb} rule${addedSuffix}.`,
213
+ `[Bootstrap] Branch protection on '${baseBranch}': ${verb} rule${addedSuffix}.`,
214
214
  );
215
215
  return { status: result.created ? 'created' : 'merged', ...result };
216
216
  } catch (err) {
217
217
  log(
218
- `[bootstrap] Branch protection on '${baseBranch}': failed — ${err.message}. Proceeding without it.`,
218
+ `[Bootstrap] Branch protection on '${baseBranch}': failed — ${err.message}. Proceeding without it.`,
219
219
  );
220
220
  return { status: 'failed', reason: err.message };
221
221
  }
@@ -253,16 +253,16 @@ export async function checkProjectScopes(opts = {}) {
253
253
  function classifyProjectScopes(scopeLine) {
254
254
  if (!scopeLine) {
255
255
  return {
256
- name: 'gh-project-scope',
256
+ name: 'GitHub Projects V2 access',
257
257
  ok: true,
258
258
  detail: GH_SCOPES_UNREADABLE_NOTE,
259
259
  };
260
260
  }
261
261
  if (/\bproject\b/i.test(scopeLine[1])) {
262
- return { name: 'gh-project-scope', ok: true };
262
+ return { name: 'GitHub Projects V2 access', ok: true };
263
263
  }
264
264
  return {
265
- name: 'gh-project-scope',
265
+ name: 'GitHub Projects V2 access',
266
266
  ok: true,
267
267
  detail: GH_PROJECT_SCOPE_NOTE,
268
268
  };
@@ -26,7 +26,7 @@
26
26
  import { createInterface } from 'node:readline';
27
27
 
28
28
  const ABORT_MESSAGE =
29
- '[bootstrap] aborting: no TTY available for HITL confirm (opt in with --approve-github-admin for GitHub-admin mutations, or --assume-yes to accept every phase group)';
29
+ '[Bootstrap] aborting: no TTY available for HITL confirm (opt in with --approve-github-admin for GitHub-admin mutations, or --assume-yes to accept every phase group)';
30
30
 
31
31
  /**
32
32
  * @param {object} args
@@ -61,7 +61,7 @@ export async function confirm({ summary, current, proposed }, opts = {}) {
61
61
  // Render the diff. Single-line summary, then a JSON block so the
62
62
  // operator can pipe the prompt to a logger and still recover the
63
63
  // structured shape.
64
- stdout.write(`\n[bootstrap] HITL confirm: ${summary}\n`);
64
+ stdout.write(`\nHITL confirm: ${summary}\n`);
65
65
  stdout.write(
66
66
  ` current: ${JSON.stringify(current ?? null, null, 2)
67
67
  .split('\n')
@@ -74,13 +74,13 @@ export async function applyMergeMethods({
74
74
  try {
75
75
  current = (await provider.getMergeMethods()) ?? {};
76
76
  } catch (err) {
77
- log(`[bootstrap] Merge methods: read failed — ${err.message}.`);
77
+ log(`[Bootstrap] Merge methods: read failed — ${err.message}.`);
78
78
  return { status: 'failed', reason: err.message };
79
79
  }
80
80
 
81
81
  const diff = diffMergeMethods(current, target);
82
82
  if (!diff) {
83
- log('[bootstrap] Merge methods: already at target stance (no-op).');
83
+ log('[Bootstrap] Merge methods: already at target stance (no-op).');
84
84
  return { status: 'unchanged' };
85
85
  }
86
86
 
@@ -94,8 +94,8 @@ export async function applyMergeMethods({
94
94
  });
95
95
  if (!approved) {
96
96
  log(
97
- '[bootstrap] Merge methods: HITL declined — leaving operator settings ' +
98
- 'untouched. Note: auto-merge will remain disabled until the merge-method ' +
97
+ '[Bootstrap] Merge methods: HITL declined — leaving operator settings untouched\n\n' +
98
+ 'Note: auto-merge will remain disabled until the merge-method ' +
99
99
  'settings match the framework stance (allow_squash_merge: true, ' +
100
100
  'allow_auto_merge: true, delete_branch_on_merge: true).',
101
101
  );
@@ -105,7 +105,7 @@ export async function applyMergeMethods({
105
105
  // Non-TTY: no operator present to confirm. Default-apply the framework
106
106
  // stance and log explicitly so the consequence is never silent.
107
107
  log(
108
- '[bootstrap] Merge methods: non-TTY — applying framework stance automatically ' +
108
+ '[Bootstrap] Merge methods: non-TTY — applying framework stance automatically ' +
109
109
  '(allow_squash_merge, allow_auto_merge, delete_branch_on_merge). ' +
110
110
  'To opt out, pass a hitlConfirm gate or set github.mergeMethods overrides in .agentrc.json.',
111
111
  );
@@ -114,10 +114,10 @@ export async function applyMergeMethods({
114
114
 
115
115
  try {
116
116
  const result = await provider.setMergeMethods(target);
117
- log(`[bootstrap] Merge methods: patched (${result.patched.join(', ')}).`);
117
+ log(`[Bootstrap] Merge methods: patched (${result.patched.join(', ')}).`);
118
118
  return { status: 'patched', ...result, diff };
119
119
  } catch (err) {
120
- log(`[bootstrap] Merge methods: PATCH failed — ${err.message}.`);
120
+ log(`[Bootstrap] Merge methods: PATCH failed — ${err.message}.`);
121
121
  return { status: 'failed', reason: err.message };
122
122
  }
123
123
  }
@@ -59,8 +59,8 @@ function defaultGitRunner(args) {
59
59
  */
60
60
  function checkNode(nodeCheck) {
61
61
  const result = nodeCheck();
62
- if (result.ok) return { name: 'node', ok: true };
63
- return { name: 'node', ok: false, remedy: NODE_REMEDY(result) };
62
+ if (result.ok) return { name: 'Node version', ok: true };
63
+ return { name: 'Node version', ok: false, remedy: NODE_REMEDY(result) };
64
64
  }
65
65
 
66
66
  /**
@@ -73,19 +73,19 @@ function checkNode(nodeCheck) {
73
73
  function checkGitAvailable(gitRunner) {
74
74
  const result = gitRunner(['--version']);
75
75
  if (result.error?.code === 'ENOENT') {
76
- return { name: 'git', ok: false, remedy: GIT_INSTALL_HINT };
76
+ return { name: 'Git installed', ok: false, remedy: GIT_INSTALL_HINT };
77
77
  }
78
78
  if (result.status !== 0) {
79
79
  const snippet = (result.stderr || '').trim().slice(0, 200);
80
80
  return {
81
- name: 'git',
81
+ name: 'Git installed',
82
82
  ok: false,
83
83
  remedy: `git --version failed (exit ${result.status})${
84
84
  snippet ? `: ${snippet}` : ''
85
85
  }. ${GIT_INSTALL_HINT}`,
86
86
  };
87
87
  }
88
- return { name: 'git', ok: true };
88
+ return { name: 'Git installed', ok: true };
89
89
  }
90
90
 
91
91
  /**
@@ -99,9 +99,13 @@ function checkGitAvailable(gitRunner) {
99
99
  function checkInsideWorkTree(gitRunner) {
100
100
  const result = gitRunner(['rev-parse', '--is-inside-work-tree']);
101
101
  if (result.status === 0 && result.stdout.trim() === 'true') {
102
- return { name: 'git-work-tree', ok: true };
102
+ return { name: 'Local git initialized', ok: true };
103
103
  }
104
- return { name: 'git-work-tree', ok: false, remedy: GIT_WORKTREE_HINT };
104
+ return {
105
+ name: 'Local git initialized',
106
+ ok: false,
107
+ remedy: GIT_WORKTREE_HINT,
108
+ };
105
109
  }
106
110
 
107
111
  /**
@@ -115,9 +119,9 @@ function checkInsideWorkTree(gitRunner) {
115
119
  async function checkGh(gh) {
116
120
  try {
117
121
  await gh();
118
- return { name: 'gh', ok: true };
122
+ return { name: 'GitHub CLI', ok: true };
119
123
  } catch (err) {
120
- return { name: 'gh', ok: false, remedy: err.message };
124
+ return { name: 'GitHub CLI', ok: false, remedy: err.message };
121
125
  }
122
126
  }
123
127
 
@@ -170,15 +174,14 @@ export async function runPreflight(opts = {}) {
170
174
  checks.push(workTree);
171
175
  } else {
172
176
  // Non-fatal detection: record whether we are inside a git repo but
173
- // never fail the gate on it — `bootstrap-new.js` can run pre-init and
174
- // collect the remote/branch/owner in later steps.
177
+ // never fail the gate on it — bootstrap initialises git in a later
178
+ // phase. `ok` stays true so the gate passes; the rendered glyph is
179
+ // driven off `gitInitialized` (✓ when a repo exists, ✗ when not) so the
180
+ // operator sees the real state without the run aborting.
175
181
  checks.push({
176
- name: 'git-work-tree',
182
+ name: 'Local git initialized',
177
183
  ok: true,
178
184
  gitInitialized,
179
- detail: gitInitialized
180
- ? 'inside a git work tree'
181
- : 'not a git repo yet — will be collected in later steps',
182
185
  });
183
186
  }
184
187
  }