create-quiver 0.14.1 → 0.15.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +4 -0
  3. package/README_FOR_AI.md +8 -2
  4. package/ROADMAP.md +8 -2
  5. package/docs/CLI_UX_GUIDE.md +64 -4
  6. package/docs/COMMANDS.md.template +7 -1
  7. package/docs/reference/commands.md +4 -0
  8. package/package.json +1 -1
  9. package/specs/quiver-v30-interactive-cli-ux-agent-selection/EVIDENCE_REPORT.md +213 -0
  10. package/specs/quiver-v30-interactive-cli-ux-agent-selection/EXECUTION_PLAN.md +85 -0
  11. package/specs/quiver-v30-interactive-cli-ux-agent-selection/SPEC.md +213 -0
  12. package/specs/quiver-v30-interactive-cli-ux-agent-selection/STATUS.md +31 -0
  13. package/specs/quiver-v30-interactive-cli-ux-agent-selection/pr.md +103 -0
  14. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +33 -0
  15. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +56 -0
  16. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/slice.json +71 -0
  17. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/CLOSURE_BRIEF.md +31 -0
  18. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/EXECUTION_BRIEF.md +54 -0
  19. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/slice.json +69 -0
  20. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/CLOSURE_BRIEF.md +33 -0
  21. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/EXECUTION_BRIEF.md +56 -0
  22. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/slice.json +81 -0
  23. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/CLOSURE_BRIEF.md +32 -0
  24. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/EXECUTION_BRIEF.md +54 -0
  25. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/slice.json +75 -0
  26. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/CLOSURE_BRIEF.md +32 -0
  27. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/EXECUTION_BRIEF.md +57 -0
  28. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/slice.json +85 -0
  29. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/CLOSURE_BRIEF.md +33 -0
  30. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/EXECUTION_BRIEF.md +57 -0
  31. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/slice.json +85 -0
  32. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/CLOSURE_BRIEF.md +35 -0
  33. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/EXECUTION_BRIEF.md +55 -0
  34. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/slice.json +81 -0
  35. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/CLOSURE_BRIEF.md +34 -0
  36. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/EXECUTION_BRIEF.md +55 -0
  37. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/slice.json +85 -0
  38. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/CLOSURE_BRIEF.md +34 -0
  39. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/EXECUTION_BRIEF.md +59 -0
  40. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/slice.json +95 -0
  41. package/src/create-quiver/commands/ai.js +364 -81
  42. package/src/create-quiver/commands/spec.js +106 -6
  43. package/src/create-quiver/index.js +533 -70
  44. package/src/create-quiver/lib/agent-profiles.js +111 -10
  45. package/src/create-quiver/lib/ai/execution-plan.js +106 -8
  46. package/src/create-quiver/lib/ai/executor.js +284 -28
  47. package/src/create-quiver/lib/ai/providers.js +71 -1
  48. package/src/create-quiver/lib/cli/selectors.js +107 -0
  49. package/src/create-quiver/lib/cli/theme.js +5 -2
  50. package/src/create-quiver/lib/cli/ux-flags.js +18 -0
  51. package/src/create-quiver/lib/cli/ux.js +100 -5
@@ -57,7 +57,10 @@ const {
57
57
  agentProfilesPath,
58
58
  buildAgentProfileState,
59
59
  getAgentProfile,
60
+ getAgentProfileById,
60
61
  listAgentProfiles,
62
+ normalizeAgentProfileRole,
63
+ resolveAgentProfileDisplayName,
61
64
  resolveProfileProvider,
62
65
  setAgentProfile,
63
66
  } = require('../lib/agent-profiles');
@@ -122,11 +125,119 @@ function normalizeTimeout(timeoutMs) {
122
125
  return parsed;
123
126
  }
124
127
 
125
- function resolveProviderForProfile(repoRoot, role, provider, providerExplicit, fallbackProvider) {
126
- if (providerExplicit === true || (provider && providerExplicit !== false)) {
127
- return String(provider || fallbackProvider).trim().toLowerCase();
128
+ function profileOptionForRole(options, role) {
129
+ const normalized = normalizeAgentProfileRole(role);
130
+ if (normalized === 'planner') {
131
+ return options.plannerProfile || options.profileId || '';
132
+ }
133
+ if (normalized === 'executor') {
134
+ return options.executorProfile || options.profileId || '';
135
+ }
136
+ if (normalized === 'reviewer') {
137
+ return options.reviewerProfile || options.profileId || '';
138
+ }
139
+ if (normalized === 'doctor') {
140
+ return options.doctorProfile || options.profileId || '';
141
+ }
142
+ return options.profileId || '';
143
+ }
144
+
145
+ function resolveRuntimeAgentProfile(repoRoot, role, options = {}, fallbackProvider = DEFAULT_PLAN_PROVIDER) {
146
+ const normalizedRole = normalizeAgentProfileRole(role);
147
+ const explicitProvider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false);
148
+ const explicitModel = String(options.model || '').trim();
149
+
150
+ if (explicitProvider) {
151
+ const provider = String(options.provider || fallbackProvider).trim().toLowerCase();
152
+ return {
153
+ role: normalizedRole,
154
+ profile: null,
155
+ profileId: '',
156
+ displayName: provider,
157
+ provider,
158
+ model: explicitModel,
159
+ };
160
+ }
161
+
162
+ const profileId = profileOptionForRole(options, normalizedRole);
163
+ const profile = profileId
164
+ ? getAgentProfileById(repoRoot, normalizedRole, profileId)
165
+ : getAgentProfile(repoRoot, normalizedRole);
166
+ const provider = profile?.provider || resolveProfileProvider(repoRoot, normalizedRole, fallbackProvider);
167
+
168
+ return {
169
+ role: normalizedRole,
170
+ profile,
171
+ profileId: profile?.id || profileId || '',
172
+ displayName: profile ? resolveAgentProfileDisplayName(profile) : provider,
173
+ provider,
174
+ model: explicitModel || profile?.model || '',
175
+ };
176
+ }
177
+
178
+ function createCommandUx(options = {}) {
179
+ if (options.ux) {
180
+ return options.ux;
181
+ }
182
+
183
+ return createUx({
184
+ env: options.env || process.env,
185
+ input: options.inputStream,
186
+ output: options.outputStream,
187
+ error: options.errorStream,
188
+ interactive: options.interactive,
189
+ json: options.json,
190
+ noColor: options.noColor,
191
+ prompts: options.prompts,
192
+ spinner: options.spinner,
193
+ stdinIsTTY: options.stdinIsTTY,
194
+ stdoutIsTTY: options.stdoutIsTTY,
195
+ stderrIsTTY: options.stderrIsTTY,
196
+ write: options.write,
197
+ });
198
+ }
199
+
200
+ function shouldShowHumanProgress(ux, options = {}) {
201
+ return options.progress !== false
202
+ && options.dryRun !== true
203
+ && options.printPrompt !== true
204
+ && ux?.mode?.decoration === true;
205
+ }
206
+
207
+ function plannerProgressTitle(action, runtimeProfile) {
208
+ return `${action} con ${runtimeProfile.displayName || runtimeProfile.model || runtimeProfile.provider}`;
209
+ }
210
+
211
+ function writeProgressChecks(ux, enabled, title, checks = []) {
212
+ if (!enabled) {
213
+ return;
214
+ }
215
+ ux.heading(title);
216
+ for (const check of checks) {
217
+ ux.check(check);
128
218
  }
129
- return resolveProfileProvider(repoRoot, role, fallbackProvider);
219
+ }
220
+
221
+ async function runProviderWithProgress({ ux, enabled, message = 'Ejecutando agente...', successMessage = 'Agente finalizado', failureMessage = 'Fallo ejecutando agente', run }) {
222
+ async function runAndFailOnProviderResult() {
223
+ const result = await run();
224
+ if (result && result.ok === false) {
225
+ const error = new Error(result.error?.message || 'provider run failed');
226
+ error.code = result.error?.code || 'AI_PROVIDER_RUN_FAILED';
227
+ error.providerResult = result;
228
+ throw error;
229
+ }
230
+ return result;
231
+ }
232
+
233
+ if (!enabled) {
234
+ return run();
235
+ }
236
+
237
+ return ux.withSpinner(message, runAndFailOnProviderResult, {
238
+ successMessage,
239
+ failureMessage,
240
+ });
130
241
  }
131
242
 
132
243
  function buildPlanContext({ role, context, phase, inputText, inputPath, repoRoot, revise = false }) {
@@ -210,6 +321,10 @@ function formatDryRunReport({ task, provider, role, contextPack, phase, invocati
210
321
  lines.push(`Timeout: ${invocation.timeoutMs}ms`);
211
322
  lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
212
323
  lines.push(`Prompt length: ${invocation.promptLength} bytes`);
324
+ if (invocation.modelSelection && invocation.modelSelection.model) {
325
+ lines.push(`Model: ${invocation.modelSelection.model}`);
326
+ lines.push(`Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`);
327
+ }
213
328
 
214
329
  if (onboardingPlan) {
215
330
  lines.push(`Prompt source: ${onboardingPlan.promptSource}`);
@@ -236,6 +351,10 @@ function formatPromptOnlyReport({ task, provider, role, contextPack, phase, invo
236
351
  lines.push(`Timeout: ${invocation.timeoutMs}ms`);
237
352
  lines.push(`Prompt transport: ${invocation.promptTransport.mode}`);
238
353
  lines.push(`Prompt length: ${invocation.promptLength} bytes`);
354
+ if (invocation.modelSelection && invocation.modelSelection.model) {
355
+ lines.push(`Model: ${invocation.modelSelection.model}`);
356
+ lines.push(`Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`);
357
+ }
239
358
 
240
359
  if (onboardingPlan) {
241
360
  lines.push(`Prompt source: ${onboardingPlan.promptSource}`);
@@ -407,6 +526,12 @@ function formatPrepareContextPlannerDryRunReport({ provider, role, context, invo
407
526
  `Context pack: ${context}`,
408
527
  `Command: ${invocation.command} ${invocation.args.join(' ')}`.trim(),
409
528
  `Prompt bytes: ${invocation.promptLength}`,
529
+ invocation.modelSelection && invocation.modelSelection.model
530
+ ? `Model: ${invocation.modelSelection.model}`
531
+ : '',
532
+ invocation.modelSelection && invocation.modelSelection.model
533
+ ? `Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`
534
+ : '',
410
535
  `Prompt source: ${promptInfo.promptSource}`,
411
536
  `Review requested: ${review ? 'yes' : 'no'}`,
412
537
  `Interactive requested: ${interactive ? 'yes' : 'no'}`,
@@ -424,7 +549,7 @@ function formatPrepareContextPlannerDryRunReport({ provider, role, context, invo
424
549
  '- npx create-quiver ai prepare-context --with-planner',
425
550
  ];
426
551
 
427
- return `${lines.join('\n')}\n`;
552
+ return `${lines.filter(Boolean).join('\n')}\n`;
428
553
  }
429
554
 
430
555
  function serializeProposalForReview(proposal) {
@@ -1108,7 +1233,8 @@ function annotateGitHubError(error, scope) {
1108
1233
 
1109
1234
  async function runOnboard(repoRoot, options = {}) {
1110
1235
  const role = normalizeRole(options.role || DEFAULT_ONBOARD_ROLE);
1111
- const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_ONBOARD_PROVIDER);
1236
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_ONBOARD_PROVIDER);
1237
+ const provider = runtimeProfile.provider;
1112
1238
  const context = options.context || DEFAULT_ONBOARD_CONTEXT;
1113
1239
  const timeoutMs = normalizeTimeout(options.timeout);
1114
1240
  const inputText = readTextFile(options.input, repoRoot);
@@ -1121,9 +1247,15 @@ async function runOnboard(repoRoot, options = {}) {
1121
1247
  prompt,
1122
1248
  cwd: repoRoot,
1123
1249
  timeoutMs,
1250
+ model: runtimeProfile.model,
1251
+ enforceModelSelection: false,
1124
1252
  });
1125
1253
  } catch (error) {
1126
- throw annotateProviderError(error, 'onboard');
1254
+ if (error.providerResult) {
1255
+ result = error.providerResult;
1256
+ } else {
1257
+ throw annotateProviderError(error, 'onboard');
1258
+ }
1127
1259
  }
1128
1260
 
1129
1261
  if (options.dryRun) {
@@ -1134,6 +1266,7 @@ async function runOnboard(repoRoot, options = {}) {
1134
1266
  contextPack: context,
1135
1267
  invocation,
1136
1268
  onboardingPlan: contextInfo.plan,
1269
+ profile: runtimeProfile,
1137
1270
  };
1138
1271
  process.stdout.write(formatDryRunReport(report));
1139
1272
  return report;
@@ -1148,23 +1281,39 @@ async function runOnboard(repoRoot, options = {}) {
1148
1281
  invocation,
1149
1282
  onboardingPlan: contextInfo.plan,
1150
1283
  prompt,
1284
+ profile: runtimeProfile,
1151
1285
  };
1152
1286
  process.stdout.write(formatPromptOnlyReport(report));
1153
1287
  return report;
1154
1288
  }
1155
1289
 
1290
+ const ux = createCommandUx(options);
1291
+ const showProgress = shouldShowHumanProgress(ux, options);
1292
+ writeProgressChecks(
1293
+ ux,
1294
+ showProgress,
1295
+ plannerProgressTitle('Ejecutando onboarding', runtimeProfile),
1296
+ ['Leyendo docs base', 'Detectando estructura', 'Preparando prompt'],
1297
+ );
1298
+
1156
1299
  let result;
1157
1300
  try {
1158
- result = await (options.runProviderFn || runProvider)(provider, {
1159
- prompt,
1160
- cwd: repoRoot,
1161
- timeoutMs,
1162
- dryRun: false,
1163
- probe: options.probe,
1164
- spawn: options.spawn,
1165
- tempRoot: options.tempRoot,
1166
- tempFileName: options.tempFileName,
1167
- tempFilePrefix: options.tempFilePrefix,
1301
+ result = await runProviderWithProgress({
1302
+ ux,
1303
+ enabled: showProgress,
1304
+ run: () => (options.runProviderFn || runProvider)(provider, {
1305
+ prompt,
1306
+ cwd: repoRoot,
1307
+ timeoutMs,
1308
+ dryRun: false,
1309
+ probe: options.probe,
1310
+ spawn: options.spawn,
1311
+ tempRoot: options.tempRoot,
1312
+ tempFileName: options.tempFileName,
1313
+ tempFilePrefix: options.tempFilePrefix,
1314
+ model: runtimeProfile.model,
1315
+ enforceModelSelection: Boolean(runtimeProfile.model),
1316
+ }),
1168
1317
  });
1169
1318
  } catch (error) {
1170
1319
  throw annotateProviderError(error, 'onboard');
@@ -1185,6 +1334,7 @@ async function runOnboard(repoRoot, options = {}) {
1185
1334
  contextPack: context,
1186
1335
  invocation,
1187
1336
  onboardingPlan: contextInfo.plan,
1337
+ profile: runtimeProfile,
1188
1338
  result,
1189
1339
  };
1190
1340
  }
@@ -1257,7 +1407,8 @@ async function runPrepareContext(repoRoot, options = {}) {
1257
1407
 
1258
1408
  async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1259
1409
  const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
1260
- const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
1410
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
1411
+ const provider = runtimeProfile.provider;
1261
1412
  const context = options.context || DEFAULT_PLAN_CONTEXT;
1262
1413
  const timeoutMs = normalizeTimeout(options.timeout);
1263
1414
  const draftPack = buildContextPreparationDrafts(repoRoot);
@@ -1276,9 +1427,15 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1276
1427
  prompt,
1277
1428
  cwd: repoRoot,
1278
1429
  timeoutMs,
1430
+ model: runtimeProfile.model,
1431
+ enforceModelSelection: false,
1279
1432
  });
1280
1433
  } catch (error) {
1281
- throw annotateProviderError(error, 'prepare-context');
1434
+ if (error.providerResult) {
1435
+ result = error.providerResult;
1436
+ } else {
1437
+ throw annotateProviderError(error, 'prepare-context');
1438
+ }
1282
1439
  }
1283
1440
 
1284
1441
  if (options.dryRun) {
@@ -1292,6 +1449,7 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1292
1449
  invocation,
1293
1450
  candidateDocs: promptInfo.allowedPaths,
1294
1451
  plan: draftPack.plan,
1452
+ profile: runtimeProfile,
1295
1453
  };
1296
1454
  process.stdout.write(formatPrepareContextPlannerDryRunReport({
1297
1455
  provider,
@@ -1314,23 +1472,39 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1314
1472
  invocation,
1315
1473
  prompt,
1316
1474
  promptSource: promptInfo.promptSource,
1475
+ profile: runtimeProfile,
1317
1476
  };
1318
1477
  process.stdout.write(formatPromptOnlyReport(report));
1319
1478
  return report;
1320
1479
  }
1321
1480
 
1481
+ const ux = createCommandUx(options);
1482
+ const showProgress = shouldShowHumanProgress(ux, options);
1483
+ writeProgressChecks(
1484
+ ux,
1485
+ showProgress,
1486
+ plannerProgressTitle('Ejecutando onboarding', runtimeProfile),
1487
+ ['Leyendo docs base', 'Detectando estructura', 'Preparando prompt'],
1488
+ );
1489
+
1322
1490
  let result;
1323
1491
  try {
1324
- result = await (options.runProviderFn || runProvider)(provider, {
1325
- prompt,
1326
- cwd: repoRoot,
1327
- timeoutMs,
1328
- dryRun: false,
1329
- probe: options.probe,
1330
- spawn: options.spawn,
1331
- tempRoot: options.tempRoot,
1332
- tempFileName: options.tempFileName,
1333
- tempFilePrefix: options.tempFilePrefix,
1492
+ result = await runProviderWithProgress({
1493
+ ux,
1494
+ enabled: showProgress,
1495
+ run: () => (options.runProviderFn || runProvider)(provider, {
1496
+ prompt,
1497
+ cwd: repoRoot,
1498
+ timeoutMs,
1499
+ dryRun: false,
1500
+ probe: options.probe,
1501
+ spawn: options.spawn,
1502
+ tempRoot: options.tempRoot,
1503
+ tempFileName: options.tempFileName,
1504
+ tempFilePrefix: options.tempFilePrefix,
1505
+ model: runtimeProfile.model,
1506
+ enforceModelSelection: Boolean(runtimeProfile.model),
1507
+ }),
1334
1508
  });
1335
1509
  } catch (error) {
1336
1510
  throw annotateProviderError(error, 'prepare-context');
@@ -1404,7 +1578,8 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1404
1578
  async function runPlan(repoRoot, options = {}) {
1405
1579
  const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
1406
1580
  const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
1407
- const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
1581
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
1582
+ const provider = runtimeProfile.provider;
1408
1583
  const context = options.context || DEFAULT_PLAN_CONTEXT;
1409
1584
  const timeoutMs = normalizeTimeout(options.timeout);
1410
1585
  let inputPath = options.input || '';
@@ -1506,9 +1681,15 @@ async function runPlan(repoRoot, options = {}) {
1506
1681
  prompt,
1507
1682
  cwd: repoRoot,
1508
1683
  timeoutMs,
1684
+ model: runtimeProfile.model,
1685
+ enforceModelSelection: false,
1509
1686
  });
1510
1687
  } catch (error) {
1511
- throw annotateProviderError(error, 'plan', phase);
1688
+ if (error.providerResult) {
1689
+ result = error.providerResult;
1690
+ } else {
1691
+ throw annotateProviderError(error, 'plan', phase);
1692
+ }
1512
1693
  }
1513
1694
 
1514
1695
  if (options.dryRun) {
@@ -1519,6 +1700,7 @@ async function runPlan(repoRoot, options = {}) {
1519
1700
  contextPack: contextInfo.pack.packName,
1520
1701
  phase,
1521
1702
  invocation,
1703
+ profile: runtimeProfile,
1522
1704
  };
1523
1705
  process.stdout.write(formatDryRunReport(report));
1524
1706
  if (options.withPlanner === true) {
@@ -1542,23 +1724,39 @@ async function runPlan(repoRoot, options = {}) {
1542
1724
  phase,
1543
1725
  invocation,
1544
1726
  prompt,
1727
+ profile: runtimeProfile,
1545
1728
  };
1546
1729
  process.stdout.write(formatPromptOnlyReport(report));
1547
1730
  return report;
1548
1731
  }
1549
1732
 
1733
+ const ux = createCommandUx(options);
1734
+ const showProgress = shouldShowHumanProgress(ux, options);
1735
+ writeProgressChecks(
1736
+ ux,
1737
+ showProgress,
1738
+ plannerProgressTitle(`Ejecutando plan ${phase}`, runtimeProfile),
1739
+ ['Leyendo entrada', 'Preparando contexto', 'Preparando prompt'],
1740
+ );
1741
+
1550
1742
  let result;
1551
1743
  try {
1552
- result = await (options.runProviderFn || runProvider)(provider, {
1553
- prompt,
1554
- cwd: repoRoot,
1555
- timeoutMs,
1556
- dryRun: false,
1557
- probe: options.probe,
1558
- spawn: options.spawn,
1559
- tempRoot: options.tempRoot,
1560
- tempFileName: options.tempFileName,
1561
- tempFilePrefix: options.tempFilePrefix,
1744
+ result = await runProviderWithProgress({
1745
+ ux,
1746
+ enabled: showProgress,
1747
+ run: () => (options.runProviderFn || runProvider)(provider, {
1748
+ prompt,
1749
+ cwd: repoRoot,
1750
+ timeoutMs,
1751
+ dryRun: false,
1752
+ probe: options.probe,
1753
+ spawn: options.spawn,
1754
+ tempRoot: options.tempRoot,
1755
+ tempFileName: options.tempFileName,
1756
+ tempFilePrefix: options.tempFilePrefix,
1757
+ model: runtimeProfile.model,
1758
+ enforceModelSelection: Boolean(runtimeProfile.model),
1759
+ }),
1562
1760
  });
1563
1761
  } catch (error) {
1564
1762
  throw annotateProviderError(error, 'plan', phase);
@@ -1625,7 +1823,8 @@ async function runPlan(repoRoot, options = {}) {
1625
1823
 
1626
1824
  async function runReviewPlan(repoRoot, options = {}) {
1627
1825
  const role = 'planner';
1628
- const provider = resolveProviderForProfile(repoRoot, 'reviewer', options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
1826
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, 'reviewer', options, DEFAULT_PLAN_PROVIDER);
1827
+ const provider = runtimeProfile.provider;
1629
1828
  const context = options.context || DEFAULT_PLAN_CONTEXT;
1630
1829
  const timeoutMs = normalizeTimeout(options.timeout);
1631
1830
  const resolved = resolveTechnicalPlanReviewInput(repoRoot, options.input || undefined);
@@ -1649,9 +1848,15 @@ async function runReviewPlan(repoRoot, options = {}) {
1649
1848
  prompt: built.prompt,
1650
1849
  cwd: repoRoot,
1651
1850
  timeoutMs,
1851
+ model: runtimeProfile.model,
1852
+ enforceModelSelection: false,
1652
1853
  });
1653
1854
  } catch (error) {
1654
- throw annotateProviderError(error, 'review-plan');
1855
+ if (error.providerResult) {
1856
+ result = error.providerResult;
1857
+ } else {
1858
+ throw annotateProviderError(error, 'review-plan');
1859
+ }
1655
1860
  }
1656
1861
 
1657
1862
  if (options.dryRun) {
@@ -1665,6 +1870,7 @@ async function runReviewPlan(repoRoot, options = {}) {
1665
1870
  inputPath,
1666
1871
  inputKind: resolved.kind,
1667
1872
  inputVersion: resolved.version,
1873
+ profile: runtimeProfile,
1668
1874
  };
1669
1875
  process.stdout.write(formatDryRunReport({
1670
1876
  task: 'review-plan',
@@ -1696,23 +1902,39 @@ async function runReviewPlan(repoRoot, options = {}) {
1696
1902
  inputPath,
1697
1903
  inputKind: resolved.kind,
1698
1904
  inputVersion: resolved.version,
1905
+ profile: runtimeProfile,
1699
1906
  };
1700
1907
  process.stdout.write(formatPromptOnlyReport(report));
1701
1908
  return report;
1702
1909
  }
1703
1910
 
1911
+ const ux = createCommandUx(options);
1912
+ const showProgress = shouldShowHumanProgress(ux, options);
1913
+ writeProgressChecks(
1914
+ ux,
1915
+ showProgress,
1916
+ plannerProgressTitle('Ejecutando revisión del plan', runtimeProfile),
1917
+ ['Leyendo plan técnico', 'Preparando contexto', 'Preparando prompt'],
1918
+ );
1919
+
1704
1920
  let result;
1705
1921
  try {
1706
- result = await (options.runProviderFn || runProvider)(provider, {
1707
- prompt: built.prompt,
1708
- cwd: repoRoot,
1709
- timeoutMs,
1710
- dryRun: false,
1711
- probe: options.probe,
1712
- spawn: options.spawn,
1713
- tempRoot: options.tempRoot,
1714
- tempFileName: options.tempFileName,
1715
- tempFilePrefix: options.tempFilePrefix,
1922
+ result = await runProviderWithProgress({
1923
+ ux,
1924
+ enabled: showProgress,
1925
+ run: () => (options.runProviderFn || runProvider)(provider, {
1926
+ prompt: built.prompt,
1927
+ cwd: repoRoot,
1928
+ timeoutMs,
1929
+ dryRun: false,
1930
+ probe: options.probe,
1931
+ spawn: options.spawn,
1932
+ tempRoot: options.tempRoot,
1933
+ tempFileName: options.tempFileName,
1934
+ tempFilePrefix: options.tempFilePrefix,
1935
+ model: runtimeProfile.model,
1936
+ enforceModelSelection: Boolean(runtimeProfile.model),
1937
+ }),
1716
1938
  });
1717
1939
  } catch (error) {
1718
1940
  throw annotateProviderError(error, 'review-plan');
@@ -1770,7 +1992,8 @@ async function runReviewPlan(repoRoot, options = {}) {
1770
1992
 
1771
1993
  async function runRepairPlan(repoRoot, options = {}) {
1772
1994
  const role = normalizeRole(options.role || DEFAULT_PLAN_ROLE);
1773
- const provider = resolveProviderForProfile(repoRoot, role, options.provider, options.providerExplicit, DEFAULT_PLAN_PROVIDER);
1995
+ const runtimeProfile = resolveRuntimeAgentProfile(repoRoot, role, options, DEFAULT_PLAN_PROVIDER);
1996
+ const provider = runtimeProfile.provider;
1774
1997
  const context = options.context || DEFAULT_PLAN_CONTEXT;
1775
1998
  const timeoutMs = normalizeTimeout(options.timeout);
1776
1999
  const source = resolveApprovedTechnicalPlanForRepair(repoRoot, options.input || '');
@@ -1790,9 +2013,15 @@ async function runRepairPlan(repoRoot, options = {}) {
1790
2013
  prompt: built.prompt,
1791
2014
  cwd: repoRoot,
1792
2015
  timeoutMs,
2016
+ model: runtimeProfile.model,
2017
+ enforceModelSelection: false,
1793
2018
  });
1794
2019
  } catch (error) {
1795
- throw annotateProviderError(error, 'repair-plan');
2020
+ if (error.providerResult) {
2021
+ providerResult = error.providerResult;
2022
+ } else {
2023
+ throw annotateProviderError(error, 'repair-plan');
2024
+ }
1796
2025
  }
1797
2026
 
1798
2027
  if (options.dryRun) {
@@ -1803,6 +2032,7 @@ async function runRepairPlan(repoRoot, options = {}) {
1803
2032
  contextPack: built.pack.packName,
1804
2033
  phase: 'technical-plan',
1805
2034
  invocation,
2035
+ profile: runtimeProfile,
1806
2036
  };
1807
2037
  process.stdout.write(formatDryRunReport(report));
1808
2038
  process.stdout.write(`Source approved artifact: ${source.path}\n`);
@@ -1822,23 +2052,39 @@ async function runRepairPlan(repoRoot, options = {}) {
1822
2052
  inputPath: source.path,
1823
2053
  inputKind: 'approved',
1824
2054
  inputVersion: source.approval.meta?.approved?.version || null,
2055
+ profile: runtimeProfile,
1825
2056
  };
1826
2057
  process.stdout.write(formatPromptOnlyReport(report));
1827
2058
  return report;
1828
2059
  }
1829
2060
 
2061
+ const ux = createCommandUx(options);
2062
+ const showProgress = shouldShowHumanProgress(ux, options);
2063
+ writeProgressChecks(
2064
+ ux,
2065
+ showProgress,
2066
+ plannerProgressTitle('Ejecutando reparación del plan', runtimeProfile),
2067
+ ['Leyendo plan aprobado', 'Preparando contexto', 'Preparando prompt'],
2068
+ );
2069
+
1830
2070
  let providerResult;
1831
2071
  try {
1832
- providerResult = await (options.runProviderFn || runProvider)(provider, {
1833
- prompt: built.prompt,
1834
- cwd: repoRoot,
1835
- timeoutMs,
1836
- dryRun: false,
1837
- probe: options.probe,
1838
- spawn: options.spawn,
1839
- tempRoot: options.tempRoot,
1840
- tempFileName: options.tempFileName,
1841
- tempFilePrefix: options.tempFilePrefix,
2072
+ providerResult = await runProviderWithProgress({
2073
+ ux,
2074
+ enabled: showProgress,
2075
+ run: () => (options.runProviderFn || runProvider)(provider, {
2076
+ prompt: built.prompt,
2077
+ cwd: repoRoot,
2078
+ timeoutMs,
2079
+ dryRun: false,
2080
+ probe: options.probe,
2081
+ spawn: options.spawn,
2082
+ tempRoot: options.tempRoot,
2083
+ tempFileName: options.tempFileName,
2084
+ tempFilePrefix: options.tempFilePrefix,
2085
+ model: runtimeProfile.model,
2086
+ enforceModelSelection: Boolean(runtimeProfile.model),
2087
+ }),
1842
2088
  });
1843
2089
  } catch (error) {
1844
2090
  throw annotateProviderError(error, 'repair-plan');
@@ -2174,10 +2420,13 @@ function runLifecycleRun(repoRoot, options = {}) {
2174
2420
 
2175
2421
  function formatAgentProfile(profile) {
2176
2422
  const lines = [
2423
+ `ID: ${profile.id || 'default'}`,
2177
2424
  `Role: ${profile.role}`,
2178
2425
  `Provider: ${profile.provider}`,
2179
2426
  `Model: ${profile.model || '(not set)'}`,
2180
2427
  `Label: ${profile.label || '(not set)'}`,
2428
+ `Display name: ${resolveAgentProfileDisplayName(profile) || '(not set)'}`,
2429
+ `Default: ${profile.default === true ? 'yes' : 'no'}`,
2181
2430
  `Context: ${profile.context || '(not set)'}`,
2182
2431
  `Updated: ${profile.updated_at}`,
2183
2432
  ];
@@ -2193,7 +2442,9 @@ function formatAgentProfileList(profiles) {
2193
2442
  }
2194
2443
  const model = item.profile.model ? ` model=${item.profile.model}` : '';
2195
2444
  const label = item.profile.label ? ` label=${item.profile.label}` : '';
2196
- lines.push(`- ${item.role}: provider=${item.profile.provider}${model}${label}`);
2445
+ const displayName = resolveAgentProfileDisplayName(item.profile);
2446
+ const alternatives = item.profiles.length > 1 ? ` options=${item.profiles.length}` : '';
2447
+ lines.push(`- ${item.role}: provider=${item.profile.provider}${model}${label} displayName=${displayName}${alternatives}`);
2197
2448
  }
2198
2449
  return `${lines.join('\n')}\n`;
2199
2450
  }
@@ -2226,6 +2477,9 @@ function runAgent(repoRoot, options = {}) {
2226
2477
  if (options.dryRun) {
2227
2478
  const preview = buildAgentProfileState(repoRoot, options.role, {
2228
2479
  context: options.context,
2480
+ default: options.defaultProfile,
2481
+ displayName: options.displayName,
2482
+ id: options.id,
2229
2483
  label: options.label,
2230
2484
  model: options.model,
2231
2485
  provider: options.provider,
@@ -2241,6 +2495,9 @@ function runAgent(repoRoot, options = {}) {
2241
2495
  }
2242
2496
  const result = setAgentProfile(repoRoot, options.role, {
2243
2497
  context: options.context,
2498
+ default: options.defaultProfile,
2499
+ displayName: options.displayName,
2500
+ id: options.id,
2244
2501
  label: options.label,
2245
2502
  model: options.model,
2246
2503
  provider: options.provider,
@@ -2260,10 +2517,14 @@ function runAgent(repoRoot, options = {}) {
2260
2517
  if (!options.role) {
2261
2518
  throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent show <planner|executor|reviewer|doctor>'));
2262
2519
  }
2263
- const profile = getAgentProfile(repoRoot, options.role);
2520
+ const profile = options.id
2521
+ ? getAgentProfileById(repoRoot, options.role, options.id)
2522
+ : getAgentProfile(repoRoot, options.role);
2264
2523
  if (!profile) {
2265
2524
  throw new Error(formatActionableError({
2266
- failure: `agent profile '${options.role}' is not configured.`,
2525
+ failure: options.id
2526
+ ? `agent profile '${options.role}/${options.id}' is not configured.`
2527
+ : `agent profile '${options.role}' is not configured.`,
2267
2528
  impact: 'Quiver will fall back to default provider behavior and may use the wrong model/cost profile.',
2268
2529
  fix: `Configure the ${options.role} profile with a supported provider and optional model label.`,
2269
2530
  nextCommand: `npx create-quiver ai agent set ${options.role} --provider <provider> --model <label>`,
@@ -2324,20 +2585,32 @@ async function runGitHubTask(repoRoot, options = {}, mode = 'pr') {
2324
2585
  async function runPr(repoRoot, options = {}) {
2325
2586
  const dryRun = options.dryRun === true;
2326
2587
  const create = options.create === true;
2588
+ const ux = createCommandUx(options);
2589
+ const showProgress = create && !dryRun && shouldShowHumanProgress(ux, options);
2590
+ if (showProgress) {
2591
+ ux.heading('Creando PR con gh');
2592
+ }
2327
2593
  let preflight;
2328
2594
 
2329
2595
  try {
2330
- preflight = await (options.preflightFn || preflightGitHubPr)(repoRoot, {
2331
- remote: options.remote,
2332
- sshHostAlias: options.sshHostAlias,
2333
- identityFile: options.identityFile,
2334
- gitFlowGuidePath: options.gitFlowGuidePath,
2335
- ghCommand: options.ghCommand,
2336
- ghProbe: options.ghProbe,
2337
- ghAuthProbe: options.ghAuthProbe,
2338
- ghProbeArgs: options.ghProbeArgs,
2339
- ghAuthArgs: options.ghAuthArgs,
2340
- blockedBranches: options.blockedBranches,
2596
+ preflight = await runProviderWithProgress({
2597
+ ux,
2598
+ enabled: showProgress,
2599
+ message: 'Ejecutando preflight de GitHub...',
2600
+ successMessage: 'Preflight de GitHub listo',
2601
+ failureMessage: 'Falló preflight de GitHub',
2602
+ run: () => (options.preflightFn || preflightGitHubPr)(repoRoot, {
2603
+ remote: options.remote,
2604
+ sshHostAlias: options.sshHostAlias,
2605
+ identityFile: options.identityFile,
2606
+ gitFlowGuidePath: options.gitFlowGuidePath,
2607
+ ghCommand: options.ghCommand,
2608
+ ghProbe: options.ghProbe,
2609
+ ghAuthProbe: options.ghAuthProbe,
2610
+ ghProbeArgs: options.ghProbeArgs,
2611
+ ghAuthArgs: options.ghAuthArgs,
2612
+ blockedBranches: options.blockedBranches,
2613
+ }),
2341
2614
  });
2342
2615
  } catch (error) {
2343
2616
  throw annotateGitHubError(error, 'pr');
@@ -2352,6 +2625,9 @@ async function runPr(repoRoot, options = {}) {
2352
2625
  prBodyPath: options.prBodyPath,
2353
2626
  title: options.title,
2354
2627
  });
2628
+ if (showProgress) {
2629
+ ux.check('Cuerpo del PR preparado');
2630
+ }
2355
2631
  } catch (error) {
2356
2632
  throw annotateGitHubError(error, 'pr');
2357
2633
  }
@@ -2396,8 +2672,15 @@ async function runPr(repoRoot, options = {}) {
2396
2672
 
2397
2673
  let result;
2398
2674
  try {
2399
- result = runGhPrCreate(plan, {
2400
- ghCreateRunner: options.ghCreateRunner,
2675
+ result = await runProviderWithProgress({
2676
+ ux,
2677
+ enabled: showProgress,
2678
+ message: 'Creando PR en GitHub...',
2679
+ successMessage: 'PR creado',
2680
+ failureMessage: 'Falló creación del PR',
2681
+ run: () => runGhPrCreate(plan, {
2682
+ ghCreateRunner: options.ghCreateRunner,
2683
+ }),
2401
2684
  });
2402
2685
  } catch (error) {
2403
2686
  throw annotateGitHubError(error, 'pr');