create-quiver 0.15.0 → 0.15.2

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 (96) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +33 -0
  4. package/README_FOR_AI.md +13 -6
  5. package/ROADMAP.md +5 -0
  6. package/docs/AI_CONTEXT.md.template +4 -0
  7. package/docs/AI_ONBOARDING_PROMPT.md.template +8 -0
  8. package/docs/CLI_UX_GUIDE.md +31 -2
  9. package/docs/COMMANDS.md.template +16 -5
  10. package/docs/TROUBLESHOOTING.md +70 -0
  11. package/docs/TROUBLESHOOTING.md.template +20 -0
  12. package/docs/WORKFLOW.md.template +2 -2
  13. package/docs/getting-started/installation.md +86 -0
  14. package/docs/reference/commands.md +39 -5
  15. package/package.json +1 -1
  16. package/specs/quiver-v31-ai-model-catalog-agent-selection/EVIDENCE_REPORT.md +185 -0
  17. package/specs/quiver-v31-ai-model-catalog-agent-selection/EXECUTION_PLAN.md +85 -0
  18. package/specs/quiver-v31-ai-model-catalog-agent-selection/SPEC.md +337 -0
  19. package/specs/quiver-v31-ai-model-catalog-agent-selection/STATUS.md +30 -0
  20. package/specs/quiver-v31-ai-model-catalog-agent-selection/pr.md +98 -0
  21. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  22. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +51 -0
  23. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/slice.json +62 -0
  24. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/CLOSURE_BRIEF.md +32 -0
  25. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/EXECUTION_BRIEF.md +53 -0
  26. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/slice.json +72 -0
  27. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/CLOSURE_BRIEF.md +36 -0
  28. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/EXECUTION_BRIEF.md +56 -0
  29. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/slice.json +78 -0
  30. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/CLOSURE_BRIEF.md +36 -0
  31. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/EXECUTION_BRIEF.md +57 -0
  32. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/slice.json +77 -0
  33. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/CLOSURE_BRIEF.md +35 -0
  34. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/EXECUTION_BRIEF.md +55 -0
  35. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/slice.json +84 -0
  36. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/CLOSURE_BRIEF.md +32 -0
  37. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/EXECUTION_BRIEF.md +52 -0
  38. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/slice.json +72 -0
  39. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/CLOSURE_BRIEF.md +33 -0
  40. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/EXECUTION_BRIEF.md +58 -0
  41. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/slice.json +84 -0
  42. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/CLOSURE_BRIEF.md +32 -0
  43. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/EXECUTION_BRIEF.md +59 -0
  44. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/slice.json +94 -0
  45. package/specs/quiver-v32-npx-installation-guidance/EVIDENCE_REPORT.md +26 -0
  46. package/specs/quiver-v32-npx-installation-guidance/SPEC.md +55 -0
  47. package/specs/quiver-v32-npx-installation-guidance/STATUS.md +23 -0
  48. package/specs/quiver-v32-npx-installation-guidance/pr.md +72 -0
  49. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/CLOSURE_BRIEF.md +31 -0
  50. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/EXECUTION_BRIEF.md +56 -0
  51. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/slice.json +75 -0
  52. package/specs/quiver-v33-approval-ux-and-planner-progress/EVIDENCE_REPORT.md +93 -0
  53. package/specs/quiver-v33-approval-ux-and-planner-progress/EXECUTION_PLAN.md +83 -0
  54. package/specs/quiver-v33-approval-ux-and-planner-progress/SPEC.md +158 -0
  55. package/specs/quiver-v33-approval-ux-and-planner-progress/STATUS.md +31 -0
  56. package/specs/quiver-v33-approval-ux-and-planner-progress/pr.md +109 -0
  57. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/CLOSURE_BRIEF.md +30 -0
  58. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/EXECUTION_BRIEF.md +56 -0
  59. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-00-approval-ux-foundation/slice.json +65 -0
  60. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/CLOSURE_BRIEF.md +19 -0
  61. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/EXECUTION_BRIEF.md +51 -0
  62. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-01-approval-candidates-model/slice.json +74 -0
  63. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/CLOSURE_BRIEF.md +20 -0
  64. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/EXECUTION_BRIEF.md +53 -0
  65. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-02-approve-interactive-selection/slice.json +79 -0
  66. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/CLOSURE_BRIEF.md +19 -0
  67. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/EXECUTION_BRIEF.md +50 -0
  68. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-03-technical-plan-review-decision-data/slice.json +75 -0
  69. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/CLOSURE_BRIEF.md +19 -0
  70. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/EXECUTION_BRIEF.md +51 -0
  71. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-04-revise-input-guardrails/slice.json +73 -0
  72. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/CLOSURE_BRIEF.md +20 -0
  73. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/EXECUTION_BRIEF.md +51 -0
  74. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-05-provider-progress-alignment/slice.json +91 -0
  75. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/CLOSURE_BRIEF.md +20 -0
  76. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/EXECUTION_BRIEF.md +53 -0
  77. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-06-workflow-surface-integration/slice.json +87 -0
  78. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/CLOSURE_BRIEF.md +21 -0
  79. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/EXECUTION_BRIEF.md +55 -0
  80. package/specs/quiver-v33-approval-ux-and-planner-progress/slices/slice-07-docs-tests-release-readiness/slice.json +97 -0
  81. package/src/create-quiver/commands/ai.js +561 -46
  82. package/src/create-quiver/commands/flow.js +76 -14
  83. package/src/create-quiver/commands/spec.js +8 -1
  84. package/src/create-quiver/index.js +36 -12
  85. package/src/create-quiver/lib/agent-profiles.js +332 -4
  86. package/src/create-quiver/lib/ai/approval-candidates.js +86 -0
  87. package/src/create-quiver/lib/ai/execution-plan.js +7 -2
  88. package/src/create-quiver/lib/ai/executor.js +9 -2
  89. package/src/create-quiver/lib/ai/model-catalog.js +333 -0
  90. package/src/create-quiver/lib/ai/plan-review.js +77 -1
  91. package/src/create-quiver/lib/ai/providers.js +143 -4
  92. package/src/create-quiver/lib/ai/run-state.js +21 -4
  93. package/src/create-quiver/lib/approvals.js +103 -0
  94. package/src/create-quiver/lib/cli/selectors.js +53 -0
  95. package/src/create-quiver/lib/git.js +13 -4
  96. package/src/create-quiver/lib/paths.js +45 -3
@@ -13,10 +13,17 @@ const {
13
13
  const { buildContextPackMetadata, normalizeRole } = require('../lib/ai/context-packs');
14
14
  const { parseContextProposalOutput } = require('../lib/ai/context-proposal');
15
15
  const { openEditor } = require('../lib/cli/editor');
16
+ const { selectOption, promptText } = require('../lib/cli/selectors');
16
17
  const { createUx } = require('../lib/cli/ux');
17
18
  const { runExecuteSlice, runPromptSlice } = require('../lib/ai/executor');
18
19
  const { runExecutePlan } = require('../lib/ai/execution-plan');
19
20
  const { buildPrCreatePlan, formatPreflightReport, formatPrCreateReport, preflightGitHubPr, runGhPrCreate } = require('../lib/ai/github');
21
+ const {
22
+ MODEL_CATALOG_LAST_UPDATED,
23
+ MODEL_CATALOG_VERSION,
24
+ getKnownModelsForProvider,
25
+ listCatalogProviders,
26
+ } = require('../lib/ai/model-catalog');
20
27
  const { buildContextPreparationDrafts, buildPlannerOnboardingPrompt } = require('../lib/ai/onboarding-template');
21
28
  const {
22
29
  collectLifecycleExport,
@@ -43,6 +50,7 @@ const {
43
50
  validateTechnicalPlanSpecContract,
44
51
  } = require('../lib/ai/spec-generator');
45
52
  const { buildProviderInvocation, runProvider } = require('../lib/ai/providers');
53
+ const { preflightProvider } = require('../lib/ai/preflight');
46
54
  const {
47
55
  createAiRun,
48
56
  ensureAiRun,
@@ -55,9 +63,12 @@ const {
55
63
  } = require('../lib/ai/run-state');
56
64
  const {
57
65
  agentProfilesPath,
66
+ buildAgentProfileDoctorReport,
67
+ buildAgentProfileRepairPlan,
58
68
  buildAgentProfileState,
59
69
  getAgentProfile,
60
70
  getAgentProfileById,
71
+ getAgentProfilesForRole,
61
72
  listAgentProfiles,
62
73
  normalizeAgentProfileRole,
63
74
  resolveAgentProfileDisplayName,
@@ -74,6 +85,10 @@ const {
74
85
  savePlannerDraft,
75
86
  summarizePlannerApproval,
76
87
  } = require('../lib/approvals');
88
+ const {
89
+ buildApprovalCandidateReport,
90
+ formatApprovalDecisionLines,
91
+ } = require('../lib/ai/approval-candidates');
77
92
  const { assertPlannerPhaseReady, getPlannerPhaseDetails, normalizePlannerPhase, PlannerPhaseError } = require('../lib/ai/phase-gates');
78
93
  const { collectActiveSliceState, resolveProjectState } = require('../lib/project-state-resolver');
79
94
 
@@ -175,6 +190,13 @@ function resolveRuntimeAgentProfile(repoRoot, role, options = {}, fallbackProvid
175
190
  };
176
191
  }
177
192
 
193
+ function runtimeModelExecutionOptions(runtimeProfile, options = {}) {
194
+ return {
195
+ model: runtimeProfile.model,
196
+ blockModelAlias: Boolean(runtimeProfile.profile && !String(options.model || '').trim()),
197
+ };
198
+ }
199
+
178
200
  function createCommandUx(options = {}) {
179
201
  if (options.ux) {
180
202
  return options.ux;
@@ -1206,7 +1228,11 @@ function formatApprovalStatusReport(repoRoot) {
1206
1228
  for (const phase of PLANNER_APPROVAL_PHASES) {
1207
1229
  const summary = summarizePlannerApproval(repoRoot, phase).trimEnd();
1208
1230
  const relation = classifyGlobalApprovalRelation(readPhaseApproval(repoRoot, phase), runApprovalRows);
1209
- sections.push(`${summary}\nRun relation: ${relation}`);
1231
+ const candidates = buildApprovalCandidateReport(repoRoot, phase);
1232
+ const decisionLines = formatApprovalDecisionLines(candidates)
1233
+ .map((line) => `- ${line}`)
1234
+ .join('\n');
1235
+ sections.push(`${summary}\nRun relation: ${relation}${decisionLines ? `\nApproval candidates:\n${decisionLines}` : ''}`);
1210
1236
  }
1211
1237
  sections.push(summarizePlanReview(repoRoot).trimEnd());
1212
1238
  return `${sections.join('\n\n')}\n`;
@@ -1247,7 +1273,7 @@ async function runOnboard(repoRoot, options = {}) {
1247
1273
  prompt,
1248
1274
  cwd: repoRoot,
1249
1275
  timeoutMs,
1250
- model: runtimeProfile.model,
1276
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1251
1277
  enforceModelSelection: false,
1252
1278
  });
1253
1279
  } catch (error) {
@@ -1311,7 +1337,7 @@ async function runOnboard(repoRoot, options = {}) {
1311
1337
  tempRoot: options.tempRoot,
1312
1338
  tempFileName: options.tempFileName,
1313
1339
  tempFilePrefix: options.tempFilePrefix,
1314
- model: runtimeProfile.model,
1340
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1315
1341
  enforceModelSelection: Boolean(runtimeProfile.model),
1316
1342
  }),
1317
1343
  });
@@ -1427,7 +1453,7 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1427
1453
  prompt,
1428
1454
  cwd: repoRoot,
1429
1455
  timeoutMs,
1430
- model: runtimeProfile.model,
1456
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1431
1457
  enforceModelSelection: false,
1432
1458
  });
1433
1459
  } catch (error) {
@@ -1502,7 +1528,7 @@ async function runPrepareContextWithPlanner(repoRoot, options = {}) {
1502
1528
  tempRoot: options.tempRoot,
1503
1529
  tempFileName: options.tempFileName,
1504
1530
  tempFilePrefix: options.tempFilePrefix,
1505
- model: runtimeProfile.model,
1531
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1506
1532
  enforceModelSelection: Boolean(runtimeProfile.model),
1507
1533
  }),
1508
1534
  });
@@ -1639,7 +1665,7 @@ async function runPlan(repoRoot, options = {}) {
1639
1665
 
1640
1666
  if (options.revise === true) {
1641
1667
  if (!inputPath) {
1642
- throw new Error(formatError(`missing feedback input file for ai revise phase '${phase}'`));
1668
+ throw new Error(formatError(`missing feedback input file for ai revise phase '${phase}'. Use: npx create-quiver ai revise --phase ${phase} --input <feedback.md> --dry-run`));
1643
1669
  }
1644
1670
  const feedbackText = readTextFile(inputPath, repoRoot);
1645
1671
  const revisionInput = buildRevisionInput({
@@ -1681,7 +1707,7 @@ async function runPlan(repoRoot, options = {}) {
1681
1707
  prompt,
1682
1708
  cwd: repoRoot,
1683
1709
  timeoutMs,
1684
- model: runtimeProfile.model,
1710
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1685
1711
  enforceModelSelection: false,
1686
1712
  });
1687
1713
  } catch (error) {
@@ -1754,7 +1780,7 @@ async function runPlan(repoRoot, options = {}) {
1754
1780
  tempRoot: options.tempRoot,
1755
1781
  tempFileName: options.tempFileName,
1756
1782
  tempFilePrefix: options.tempFilePrefix,
1757
- model: runtimeProfile.model,
1783
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1758
1784
  enforceModelSelection: Boolean(runtimeProfile.model),
1759
1785
  }),
1760
1786
  });
@@ -1848,7 +1874,7 @@ async function runReviewPlan(repoRoot, options = {}) {
1848
1874
  prompt: built.prompt,
1849
1875
  cwd: repoRoot,
1850
1876
  timeoutMs,
1851
- model: runtimeProfile.model,
1877
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1852
1878
  enforceModelSelection: false,
1853
1879
  });
1854
1880
  } catch (error) {
@@ -1932,7 +1958,7 @@ async function runReviewPlan(repoRoot, options = {}) {
1932
1958
  tempRoot: options.tempRoot,
1933
1959
  tempFileName: options.tempFileName,
1934
1960
  tempFilePrefix: options.tempFilePrefix,
1935
- model: runtimeProfile.model,
1961
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1936
1962
  enforceModelSelection: Boolean(runtimeProfile.model),
1937
1963
  }),
1938
1964
  });
@@ -2013,7 +2039,7 @@ async function runRepairPlan(repoRoot, options = {}) {
2013
2039
  prompt: built.prompt,
2014
2040
  cwd: repoRoot,
2015
2041
  timeoutMs,
2016
- model: runtimeProfile.model,
2042
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
2017
2043
  enforceModelSelection: false,
2018
2044
  });
2019
2045
  } catch (error) {
@@ -2082,7 +2108,7 @@ async function runRepairPlan(repoRoot, options = {}) {
2082
2108
  tempRoot: options.tempRoot,
2083
2109
  tempFileName: options.tempFileName,
2084
2110
  tempFilePrefix: options.tempFilePrefix,
2085
- model: runtimeProfile.model,
2111
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
2086
2112
  enforceModelSelection: Boolean(runtimeProfile.model),
2087
2113
  }),
2088
2114
  });
@@ -2172,20 +2198,112 @@ async function runRevise(repoRoot, options = {}) {
2172
2198
  });
2173
2199
  }
2174
2200
 
2201
+ function formatApprovalCandidateHint(candidate) {
2202
+ const parts = [];
2203
+ if (candidate.current) {
2204
+ parts.push('current');
2205
+ }
2206
+ if (candidate.created_at) {
2207
+ parts.push(candidate.created_at);
2208
+ }
2209
+ if (candidate.review?.recommendation) {
2210
+ parts.push(`review=${candidate.review.recommendation}`);
2211
+ }
2212
+ if (candidate.review?.required_fixes_count) {
2213
+ parts.push(`required fixes=${candidate.review.required_fixes_count}`);
2214
+ }
2215
+ if (candidate.review?.optional_hardening_count) {
2216
+ parts.push(`optional=${candidate.review.optional_hardening_count}`);
2217
+ }
2218
+ if (candidate.review?.risks_count) {
2219
+ parts.push(`risks=${candidate.review.risks_count}`);
2220
+ }
2221
+ parts.push(candidate.reason);
2222
+ return parts.filter(Boolean).join(', ');
2223
+ }
2224
+
2225
+ function approvalSelectionOptions(report) {
2226
+ return report.candidates.map((candidate) => ({
2227
+ label: `${candidate.label}${candidate.recommended ? ' (recommended)' : candidate.current ? ' (current)' : ' (history)'}`,
2228
+ value: String(candidate.version || ''),
2229
+ hint: formatApprovalCandidateHint(candidate),
2230
+ default: candidate.recommended === true,
2231
+ raw: candidate,
2232
+ }));
2233
+ }
2234
+
2235
+ async function resolveApprovalVersion(repoRoot, phase, options = {}) {
2236
+ if (options.version) {
2237
+ return options.version;
2238
+ }
2239
+
2240
+ const canPrompt = isInteractiveAgentPromptAvailable(options);
2241
+ const shouldPrompt = options.interactive === true || canPrompt;
2242
+ const report = buildApprovalCandidateReport(repoRoot, phase);
2243
+
2244
+ if (!shouldPrompt || !canPrompt) {
2245
+ const recommended = report.recommended?.version || report.latest_version || '<n>';
2246
+ throw new Error(formatActionableError({
2247
+ failure: `ai approve --phase ${phase} requires --version <n> when prompts are not available.`,
2248
+ impact: 'Quiver cannot safely guess which saved planner draft the human approved.',
2249
+ fix: 'Review drafts with `npx create-quiver ai approvals`, then pass the version explicitly.',
2250
+ nextCommand: `npx create-quiver ai approve --phase ${phase} --version ${recommended}`,
2251
+ }));
2252
+ }
2253
+
2254
+ if (report.candidates.length === 0) {
2255
+ throw new Error(formatActionableError({
2256
+ failure: `ai approve --phase ${phase} has no saved drafts to approve.`,
2257
+ impact: 'There is no planner artifact that can pass the approval gate.',
2258
+ fix: `Generate a ${phase} draft first.`,
2259
+ nextCommand: `npx create-quiver ai plan --phase ${phase}${phase === 'acceptance' ? ' --input <requirements.md>' : ''} --dry-run`,
2260
+ }));
2261
+ }
2262
+
2263
+ const selected = await selectOption(`¿Qué ${phase} draft querés aprobar?`, approvalSelectionOptions(report), {
2264
+ env: options.env,
2265
+ error: options.error,
2266
+ input: options.input,
2267
+ interactive: true,
2268
+ noColor: options.noColor,
2269
+ output: options.output,
2270
+ prompts: options.prompts,
2271
+ promptSelect: options.promptSelect,
2272
+ stdinIsTTY: options.stdinIsTTY,
2273
+ stdoutIsTTY: options.stdoutIsTTY,
2274
+ stderrIsTTY: options.stderrIsTTY,
2275
+ defaultValue: report.recommended?.version ? String(report.recommended.version) : undefined,
2276
+ flag: '--version',
2277
+ name: `${phase} approval version`,
2278
+ });
2279
+
2280
+ const candidate = selected.raw;
2281
+ if (!candidate?.approvable) {
2282
+ throw new Error(formatActionableError({
2283
+ failure: `${phase} draft ${selected.label} is not approvable.`,
2284
+ impact: candidate?.review?.blocking
2285
+ ? 'The current review gate blocks technical-plan approval.'
2286
+ : 'Quiver only approves the current eligible draft version.',
2287
+ fix: candidate?.reason || 'Inspect planner approvals before approving.',
2288
+ nextCommand: candidate?.next_command || `npx create-quiver ai approvals`,
2289
+ }));
2290
+ }
2291
+
2292
+ return selected.value;
2293
+ }
2294
+
2175
2295
  async function runApprove(repoRoot, options = {}) {
2176
2296
  const phase = normalizePlannerPhase(options.phase || DEFAULT_PLAN_PHASE);
2177
2297
  if (phase === 'spec') {
2178
2298
  throw new Error(formatError(`ai approve does not support phase '${phase}'`));
2179
2299
  }
2180
2300
 
2181
- if (!options.version) {
2182
- throw new Error(formatError(`ai approve --phase ${phase} requires --version <n>. Review drafts with \`npx create-quiver ai approvals\`.`));
2183
- }
2184
-
2185
2301
  if (options.input) {
2186
2302
  throw new Error(formatError(`ai approve --phase ${phase} approves saved draft versions only. Use \`npx create-quiver ai revise --phase ${phase} --input ${options.input}\` to create a new draft first.`));
2187
2303
  }
2188
2304
 
2305
+ const version = await resolveApprovalVersion(repoRoot, phase, options);
2306
+
2189
2307
  if (phase === 'technical-plan') {
2190
2308
  const review = readPlanReview(repoRoot);
2191
2309
  if (review.status !== 'unapproved' && review.status !== 'reviewed') {
@@ -2196,24 +2314,24 @@ async function runApprove(repoRoot, options = {}) {
2196
2314
  const requiredFixes = Array.isArray(result.required_fixes) ? result.required_fixes.length : 0;
2197
2315
  throw new Error(formatError(`ai approve --phase technical-plan is blocked by plan review; approval recommendation is ${result.approval_recommendation}. Required fixes: ${requiredFixes}. Next command: ${result.next_command}`));
2198
2316
  }
2199
- assertTechnicalPlanDraftHasSpecContract(repoRoot, options.version);
2317
+ assertTechnicalPlanDraftHasSpecContract(repoRoot, version);
2200
2318
  }
2201
2319
 
2202
2320
  const inputText = '';
2203
2321
 
2204
2322
  if (options.dryRun) {
2205
- process.stdout.write(formatApprovalDryRunResult({ phase, input: options.input, version: options.version }));
2323
+ process.stdout.write(formatApprovalDryRunResult({ phase, input: options.input, version }));
2206
2324
  return {
2207
2325
  task: 'approve',
2208
2326
  phase,
2209
2327
  input: options.input,
2210
- version: options.version || null,
2328
+ version: version || null,
2211
2329
  dryRun: true,
2212
2330
  };
2213
2331
  }
2214
2332
 
2215
2333
  const result = approvePlannerPhase(repoRoot, phase, options.input || '', inputText, {
2216
- version: options.version || undefined,
2334
+ version: version || undefined,
2217
2335
  });
2218
2336
  const lifecycleRun = ensureAiRun(repoRoot, {
2219
2337
  command: `ai approve --phase ${phase}`,
@@ -2223,7 +2341,7 @@ async function runApprove(repoRoot, options = {}) {
2223
2341
  recordAiRunApproval(repoRoot, lifecycleRun.run_id, {
2224
2342
  artifact: path.relative(repoRoot, result.filePath).split(path.sep).join('/'),
2225
2343
  phase,
2226
- source_file: options.input || `draft version ${options.version}`,
2344
+ source_file: options.input || `draft version ${version}`,
2227
2345
  version: result.version || null,
2228
2346
  });
2229
2347
  updateAiRunPhase(repoRoot, lifecycleRun.run_id, phase === 'acceptance' ? 'acceptance-approved' : 'technical-plan-approved', {
@@ -2232,7 +2350,7 @@ async function runApprove(repoRoot, options = {}) {
2232
2350
  });
2233
2351
  process.stdout.write(formatApprovalResult({
2234
2352
  ...result,
2235
- sourceFile: options.input || `draft version ${options.version}`,
2353
+ sourceFile: options.input || `draft version ${version}`,
2236
2354
  }, repoRoot));
2237
2355
 
2238
2356
  return {
@@ -2418,6 +2536,212 @@ function runLifecycleRun(repoRoot, options = {}) {
2418
2536
  };
2419
2537
  }
2420
2538
 
2539
+ function isInteractiveAgentPromptAvailable(options = {}) {
2540
+ const stdinIsTTY = options.stdinIsTTY ?? Boolean((options.input || process.stdin).isTTY);
2541
+ const stdoutIsTTY = options.stdoutIsTTY ?? Boolean((options.output || process.stdout).isTTY);
2542
+ const ci = String((options.env || process.env).CI || '').trim().toLowerCase();
2543
+ return stdinIsTTY && stdoutIsTTY && ci !== '1' && ci !== 'true';
2544
+ }
2545
+
2546
+ function providerInstallHint(repoRoot, providerId, options = {}) {
2547
+ const probe = options.preflightProvider || preflightProvider;
2548
+ try {
2549
+ probe(providerId, {
2550
+ cwd: repoRoot,
2551
+ probe: options.providerProbe,
2552
+ probeArgs: options.providerProbeArgs,
2553
+ });
2554
+ return 'installed';
2555
+ } catch (error) {
2556
+ if (error && error.code === 'MISSING_PROVIDER_CLI') {
2557
+ return 'not installed';
2558
+ }
2559
+ return 'not verified';
2560
+ }
2561
+ }
2562
+
2563
+ function buildAgentProviderChoices(repoRoot, options = {}) {
2564
+ return listCatalogProviders().map((provider) => {
2565
+ const status = providerInstallHint(repoRoot, provider.id, options);
2566
+ return {
2567
+ label: provider.displayName,
2568
+ value: provider.id,
2569
+ hint: `${provider.modelCount} models, ${status}`,
2570
+ raw: {
2571
+ ...provider,
2572
+ installStatus: status,
2573
+ },
2574
+ };
2575
+ });
2576
+ }
2577
+
2578
+ function buildAgentModelChoices(provider, role) {
2579
+ return getKnownModelsForProvider(provider, { role }).map((model) => {
2580
+ const recommended = model.recommendedFor.includes(role) ? 'recommended' : 'available';
2581
+ const tier = [model.costTier, model.qualityTier, model.stability].filter(Boolean).join('/');
2582
+ return {
2583
+ label: model.displayName,
2584
+ value: model.id,
2585
+ hint: [recommended, tier].filter(Boolean).join(', '),
2586
+ default: model.recommendedFor.includes(role),
2587
+ raw: model,
2588
+ };
2589
+ });
2590
+ }
2591
+
2592
+ async function resolveInteractiveAgentSetOptions(repoRoot, options = {}) {
2593
+ const role = normalizeAgentProfileRole(options.role);
2594
+ const hasProvider = Boolean(String(options.provider || '').trim());
2595
+ const hasModel = Boolean(String(options.model || '').trim());
2596
+ if (hasProvider && hasModel) {
2597
+ return {
2598
+ ...options,
2599
+ role,
2600
+ };
2601
+ }
2602
+
2603
+ const canPrompt = isInteractiveAgentPromptAvailable(options);
2604
+ const shouldPrompt = options.interactive === true || canPrompt;
2605
+ if (!shouldPrompt || !canPrompt) {
2606
+ throw new Error(formatActionableError({
2607
+ failure: `ai agent set ${role} requires --provider and --model when prompts are not available.`,
2608
+ impact: 'Quiver cannot safely guess the provider or technical model id for an agent profile.',
2609
+ fix: 'Pass both flags explicitly, or rerun from an interactive terminal with --interactive.',
2610
+ nextCommand: `npx create-quiver ai agent set ${role} --provider codex --model gpt-5.5`,
2611
+ }));
2612
+ }
2613
+
2614
+ const promptOptions = {
2615
+ env: options.env,
2616
+ error: options.error,
2617
+ input: options.input,
2618
+ interactive: true,
2619
+ noColor: options.noColor,
2620
+ output: options.output,
2621
+ prompts: options.prompts,
2622
+ promptSelect: options.promptSelect,
2623
+ promptText: options.promptText,
2624
+ stdinIsTTY: options.stdinIsTTY,
2625
+ stdoutIsTTY: options.stdoutIsTTY,
2626
+ stderrIsTTY: options.stderrIsTTY,
2627
+ write: options.write,
2628
+ };
2629
+
2630
+ const existingProfiles = getAgentProfilesForRole(repoRoot, role);
2631
+ let next = { ...options, role };
2632
+ if (existingProfiles.length > 0 && !options.id && !options.defaultProfile) {
2633
+ const ux = createCommandUx(promptOptions);
2634
+ ux.summary(existingProfiles.map((profile) => ({
2635
+ label: profile.id,
2636
+ value: `${profile.provider} ${profile.model || '(no model)'}${profile.default ? ' default' : ''}`,
2637
+ })), {
2638
+ title: `Existing ${role} profiles`,
2639
+ });
2640
+
2641
+ const action = await selectOption(`Ya existe al menos un perfil ${role}. ¿Qué querés hacer?`, [
2642
+ { label: 'Actualizar perfil actual', value: 'update-current', hint: 'Reutiliza el perfil default', default: true },
2643
+ { label: 'Crear perfil nuevo', value: 'create-new', hint: 'Permite guardar otro modelo o provider' },
2644
+ { label: 'Cambiar default', value: 'change-default', hint: 'Marca un perfil existente como default' },
2645
+ { label: 'Cancelar', value: 'cancel', hint: 'No escribe archivos' },
2646
+ ], {
2647
+ ...promptOptions,
2648
+ name: 'agent profile action',
2649
+ flag: '--id',
2650
+ });
2651
+
2652
+ if (action.value === 'cancel') {
2653
+ throw new Error(formatError('ai agent set canceled. No files were written.'));
2654
+ }
2655
+
2656
+ if (action.value === 'change-default') {
2657
+ const profile = await selectOption(`Elegí el perfil ${role} default`, existingProfiles.map((item) => ({
2658
+ label: resolveAgentProfileDisplayName(item),
2659
+ value: item.id,
2660
+ hint: `${item.provider} ${item.model || '(no model)'}`,
2661
+ default: item.default === true,
2662
+ raw: item,
2663
+ })), {
2664
+ ...promptOptions,
2665
+ name: 'agent profile default',
2666
+ flag: '--id',
2667
+ });
2668
+ return {
2669
+ ...next,
2670
+ id: profile.raw.id,
2671
+ provider: profile.raw.provider,
2672
+ model: profile.raw.model,
2673
+ displayName: profile.raw.displayName,
2674
+ label: profile.raw.label,
2675
+ context: profile.raw.context,
2676
+ defaultProfile: true,
2677
+ };
2678
+ }
2679
+
2680
+ if (action.value === 'update-current') {
2681
+ const current = existingProfiles.find((profile) => profile.default) || existingProfiles[0];
2682
+ next = {
2683
+ ...next,
2684
+ id: current.id,
2685
+ context: next.context || current.context,
2686
+ label: next.label || current.label,
2687
+ };
2688
+ }
2689
+
2690
+ if (action.value === 'create-new') {
2691
+ const id = await promptText(`ID para el nuevo perfil ${role}`, {
2692
+ ...promptOptions,
2693
+ name: 'agent profile id',
2694
+ flag: '--id',
2695
+ placeholder: `${role}-gpt-55`,
2696
+ });
2697
+ next.id = id;
2698
+ next.defaultProfile = options.defaultProfile === true;
2699
+ }
2700
+ }
2701
+
2702
+ if (!next.provider) {
2703
+ const selectedProvider = await selectOption(`Elegí un provider para ${role}`, buildAgentProviderChoices(repoRoot, options), {
2704
+ ...promptOptions,
2705
+ name: 'agent provider',
2706
+ flag: '--provider',
2707
+ });
2708
+ next.provider = selectedProvider.value;
2709
+ }
2710
+
2711
+ if (!next.model) {
2712
+ const selectedModel = await selectOption(`Elegí un modelo para ${role}`, buildAgentModelChoices(next.provider, role), {
2713
+ ...promptOptions,
2714
+ name: 'agent model',
2715
+ flag: '--model',
2716
+ });
2717
+ if (selectedModel.value === 'custom') {
2718
+ const model = await promptText(`Modelo custom para ${next.provider}`, {
2719
+ ...promptOptions,
2720
+ name: 'agent model',
2721
+ flag: '--model',
2722
+ placeholder: `${next.provider}-model-id`,
2723
+ });
2724
+ const displayName = await promptText('Nombre visible del modelo custom', {
2725
+ ...promptOptions,
2726
+ name: 'agent model display name',
2727
+ flag: '--display-name',
2728
+ initialValue: model,
2729
+ required: false,
2730
+ });
2731
+ next.model = model;
2732
+ next.displayName = next.displayName || displayName || model;
2733
+ } else {
2734
+ next.model = selectedModel.value;
2735
+ next.displayName = next.displayName || selectedModel.raw.displayName;
2736
+ }
2737
+ }
2738
+
2739
+ return {
2740
+ ...next,
2741
+ interactiveResolved: true,
2742
+ };
2743
+ }
2744
+
2421
2745
  function formatAgentProfile(profile) {
2422
2746
  const lines = [
2423
2747
  `ID: ${profile.id || 'default'}`,
@@ -2464,25 +2788,156 @@ function formatAgentProfileDryRun(repoRoot, result) {
2464
2788
  ].join('\n');
2465
2789
  }
2466
2790
 
2467
- function runAgent(repoRoot, options = {}) {
2791
+ function agentDoctorSymbol(status) {
2792
+ if (status === 'error') return 'x';
2793
+ if (status === 'warning') return '!';
2794
+ return 'OK';
2795
+ }
2796
+
2797
+ function formatAgentDoctorReport(report) {
2798
+ const lines = [
2799
+ 'Quiver Agent Doctor',
2800
+ '',
2801
+ 'Checks',
2802
+ ];
2803
+
2804
+ if (report.checks.length === 0) {
2805
+ lines.push(' ! No agent profiles configured');
2806
+ }
2807
+
2808
+ for (const check of report.checks) {
2809
+ const target = `${check.role}/${check.id}`;
2810
+ const model = check.model || '(no model)';
2811
+ const provider = check.provider || '(no provider)';
2812
+ const defaultText = check.default ? ' default' : '';
2813
+ lines.push(` ${agentDoctorSymbol(check.status)} ${target}: provider=${provider} model=${model}${defaultText}`);
2814
+ for (const finding of check.findings.filter((item) => item.severity !== 'info')) {
2815
+ lines.push(` - ${finding.severity}: ${finding.message}`);
2816
+ }
2817
+ }
2818
+
2819
+ lines.push('', 'Suggested fixes');
2820
+ if (report.suggestedFixes.length === 0) {
2821
+ lines.push(' OK No fixes suggested.');
2822
+ } else {
2823
+ for (const fix of report.suggestedFixes) {
2824
+ lines.push(` - ${fix}`);
2825
+ }
2826
+ }
2827
+ lines.push('');
2828
+ lines.push(`Summary: profiles=${report.summary.profiles} errors=${report.summary.errors} warnings=${report.summary.warnings} info=${report.summary.info}`);
2829
+ lines.push('');
2830
+ return `${lines.join('\n')}\n`;
2831
+ }
2832
+
2833
+ function formatAgentRepairPlan(plan) {
2834
+ const lines = [
2835
+ 'AI agent profile repair dry-run',
2836
+ '- Writes: none',
2837
+ '',
2838
+ 'Proposed changes',
2839
+ ];
2840
+
2841
+ if (plan.changes.length === 0) {
2842
+ lines.push(' OK No safe repairs found.');
2843
+ }
2844
+
2845
+ for (const change of plan.changes) {
2846
+ lines.push(` - ${change.role}/${change.profileId}: ${change.reason}`);
2847
+ lines.push(` Before: model=${change.before.model || '(not set)'} displayName=${change.before.displayName || '(not set)'}`);
2848
+ lines.push(` After: model=${change.after.model || '(not set)'} displayName=${change.after.displayName || '(not set)'}`);
2849
+ }
2850
+
2851
+ lines.push('');
2852
+ return `${lines.join('\n')}\n`;
2853
+ }
2854
+
2855
+ function buildModelsListReport(options = {}) {
2856
+ const providerFilter = String(options.provider || '').trim().toLowerCase();
2857
+ const providers = providerFilter
2858
+ ? [listCatalogProviders().find((provider) => provider.id === providerFilter)].filter(Boolean)
2859
+ : listCatalogProviders();
2860
+
2861
+ if (providerFilter && providers.length === 0) {
2862
+ throw new Error(formatError(`unsupported provider filter '${options.provider}'. Supported providers: ${listCatalogProviders().map((provider) => provider.id).join(', ')}.`));
2863
+ }
2864
+
2865
+ return {
2866
+ catalogVersion: MODEL_CATALOG_VERSION,
2867
+ lastUpdated: MODEL_CATALOG_LAST_UPDATED,
2868
+ note: 'Models are known by Quiver. This does not guarantee provider account access.',
2869
+ providers: providers.map((provider) => ({
2870
+ id: provider.id,
2871
+ displayName: provider.displayName,
2872
+ models: getKnownModelsForProvider(provider.id).map((model) => ({
2873
+ id: model.id,
2874
+ displayName: model.displayName,
2875
+ recommendedFor: model.recommendedFor,
2876
+ costTier: model.costTier,
2877
+ qualityTier: model.qualityTier,
2878
+ stability: model.stability,
2879
+ custom: model.custom === true,
2880
+ })),
2881
+ })),
2882
+ };
2883
+ }
2884
+
2885
+ function formatModelsListReport(report) {
2886
+ const lines = [
2887
+ 'AI models known by Quiver',
2888
+ `Catalog version: ${report.catalogVersion}`,
2889
+ `Last updated: ${report.lastUpdated}`,
2890
+ 'Note: Models are known by Quiver; provider account access is not guaranteed.',
2891
+ '',
2892
+ ];
2893
+
2894
+ for (const provider of report.providers) {
2895
+ lines.push(`${provider.displayName} (${provider.id})`);
2896
+ for (const model of provider.models) {
2897
+ const roles = model.recommendedFor.length > 0 ? model.recommendedFor.join(', ') : 'custom/manual';
2898
+ lines.push(` - ${model.id} (${model.displayName})`);
2899
+ lines.push(` roles: ${roles}`);
2900
+ lines.push(` cost=${model.costTier} quality=${model.qualityTier} stability=${model.stability}`);
2901
+ }
2902
+ lines.push('');
2903
+ }
2904
+
2905
+ return `${lines.join('\n')}\n`;
2906
+ }
2907
+
2908
+ function runModelsList(options = {}) {
2909
+ const report = buildModelsListReport({
2910
+ provider: options.provider,
2911
+ });
2912
+ if (options.json) {
2913
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
2914
+ } else {
2915
+ process.stdout.write(formatModelsListReport(report));
2916
+ }
2917
+ return {
2918
+ task: 'models',
2919
+ command: 'list',
2920
+ report,
2921
+ };
2922
+ }
2923
+
2924
+ async function runAgent(repoRoot, options = {}) {
2468
2925
  const command = String(options.command || '').trim().toLowerCase();
2469
2926
 
2470
2927
  if (command === 'set') {
2471
2928
  if (!options.role) {
2472
- throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|doctor> --provider <provider>'));
2473
- }
2474
- if (!options.provider) {
2475
- throw new Error(formatError('ai agent set requires --provider. Supported providers: codex, claude, gemini.'));
2929
+ throw new Error(formatError('missing agent role. Use: npx create-quiver ai agent set <planner|executor|reviewer|doctor> --provider <provider> --model <model>'));
2476
2930
  }
2931
+ const profileOptions = await resolveInteractiveAgentSetOptions(repoRoot, options);
2477
2932
  if (options.dryRun) {
2478
- const preview = buildAgentProfileState(repoRoot, options.role, {
2479
- context: options.context,
2480
- default: options.defaultProfile,
2481
- displayName: options.displayName,
2482
- id: options.id,
2483
- label: options.label,
2484
- model: options.model,
2485
- provider: options.provider,
2933
+ const preview = buildAgentProfileState(repoRoot, profileOptions.role, {
2934
+ context: profileOptions.context,
2935
+ default: profileOptions.defaultProfile,
2936
+ displayName: profileOptions.displayName,
2937
+ id: profileOptions.id,
2938
+ label: profileOptions.label,
2939
+ model: profileOptions.model,
2940
+ provider: profileOptions.provider,
2486
2941
  });
2487
2942
  process.stdout.write(formatAgentProfileDryRun(repoRoot, preview));
2488
2943
  return {
@@ -2493,14 +2948,35 @@ function runAgent(repoRoot, options = {}) {
2493
2948
  filePath: path.relative(repoRoot, preview.filePath).split(path.sep).join('/'),
2494
2949
  };
2495
2950
  }
2496
- const result = setAgentProfile(repoRoot, options.role, {
2497
- context: options.context,
2498
- default: options.defaultProfile,
2499
- displayName: options.displayName,
2500
- id: options.id,
2501
- label: options.label,
2502
- model: options.model,
2503
- provider: options.provider,
2951
+ if (profileOptions.interactiveResolved === true) {
2952
+ const preview = buildAgentProfileState(repoRoot, profileOptions.role, {
2953
+ context: profileOptions.context,
2954
+ default: profileOptions.defaultProfile,
2955
+ displayName: profileOptions.displayName,
2956
+ id: profileOptions.id,
2957
+ label: profileOptions.label,
2958
+ model: profileOptions.model,
2959
+ provider: profileOptions.provider,
2960
+ });
2961
+ const ux = createCommandUx(profileOptions);
2962
+ ux.summary([
2963
+ { label: 'Role', value: preview.profile.role },
2964
+ { label: 'Provider', value: preview.profile.provider },
2965
+ { label: 'Model', value: preview.profile.model || '(not set)' },
2966
+ { label: 'Display name', value: resolveAgentProfileDisplayName(preview.profile) || '(not set)' },
2967
+ { label: 'Default', value: preview.profile.default === true ? 'yes' : 'no' },
2968
+ ], {
2969
+ title: 'Profile to save',
2970
+ });
2971
+ }
2972
+ const result = setAgentProfile(repoRoot, profileOptions.role, {
2973
+ context: profileOptions.context,
2974
+ default: profileOptions.defaultProfile,
2975
+ displayName: profileOptions.displayName,
2976
+ id: profileOptions.id,
2977
+ label: profileOptions.label,
2978
+ model: profileOptions.model,
2979
+ provider: profileOptions.provider,
2504
2980
  });
2505
2981
  process.stdout.write('AI agent profile saved\n');
2506
2982
  process.stdout.write(formatAgentProfile(result.profile));
@@ -2526,8 +3002,8 @@ function runAgent(repoRoot, options = {}) {
2526
3002
  ? `agent profile '${options.role}/${options.id}' is not configured.`
2527
3003
  : `agent profile '${options.role}' is not configured.`,
2528
3004
  impact: 'Quiver will fall back to default provider behavior and may use the wrong model/cost profile.',
2529
- fix: `Configure the ${options.role} profile with a supported provider and optional model label.`,
2530
- nextCommand: `npx create-quiver ai agent set ${options.role} --provider <provider> --model <label>`,
3005
+ fix: `Configure the ${options.role} profile with a supported provider and technical model id.`,
3006
+ nextCommand: `npx create-quiver ai agent set ${options.role} --provider <provider> --model <model-id>`,
2531
3007
  }));
2532
3008
  }
2533
3009
  process.stdout.write(formatAgentProfile(profile));
@@ -2549,7 +3025,44 @@ function runAgent(repoRoot, options = {}) {
2549
3025
  };
2550
3026
  }
2551
3027
 
2552
- throw new Error(formatError(`unsupported ai agent subcommand: ${command}. Supported tasks: set, list, show`));
3028
+ if (command === 'doctor') {
3029
+ const report = buildAgentProfileDoctorReport(repoRoot);
3030
+ if (options.json) {
3031
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
3032
+ } else {
3033
+ process.stdout.write(formatAgentDoctorReport(report));
3034
+ }
3035
+ if (report.summary.errors > 0) {
3036
+ process.exitCode = 1;
3037
+ }
3038
+ return {
3039
+ task: 'agent',
3040
+ command,
3041
+ report,
3042
+ };
3043
+ }
3044
+
3045
+ if (command === 'repair') {
3046
+ if (options.dryRun !== true) {
3047
+ throw new Error(formatError('ai agent repair only supports --dry-run for now. No files were written.'));
3048
+ }
3049
+ const plan = buildAgentProfileRepairPlan(repoRoot, {
3050
+ includeState: options.json === true,
3051
+ });
3052
+ if (options.json) {
3053
+ process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
3054
+ } else {
3055
+ process.stdout.write(formatAgentRepairPlan(plan));
3056
+ }
3057
+ return {
3058
+ task: 'agent',
3059
+ command,
3060
+ dryRun: true,
3061
+ plan,
3062
+ };
3063
+ }
3064
+
3065
+ throw new Error(formatError(`unsupported ai agent subcommand: ${command}. Supported tasks: set, list, show, doctor, repair`));
2553
3066
  }
2554
3067
 
2555
3068
  async function runGitHubTask(repoRoot, options = {}, mode = 'pr') {
@@ -2718,6 +3231,7 @@ module.exports = {
2718
3231
  formatSpecDryRunReport,
2719
3232
  normalizeTimeout,
2720
3233
  readTextFile,
3234
+ resolveInteractiveAgentSetOptions,
2721
3235
  runAgent,
2722
3236
  runActiveSlice,
2723
3237
  runDoctor,
@@ -2726,6 +3240,7 @@ module.exports = {
2726
3240
  runLifecycleResume,
2727
3241
  runLifecycleRun,
2728
3242
  runLifecycleStatus,
3243
+ runModelsList,
2729
3244
  runExport,
2730
3245
  runInspect,
2731
3246
  runPromptSlice,