create-quiver 0.14.1 → 0.15.1

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 (94) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +35 -0
  3. package/README_FOR_AI.md +15 -4
  4. package/ROADMAP.md +13 -2
  5. package/docs/AI_CONTEXT.md.template +4 -0
  6. package/docs/AI_ONBOARDING_PROMPT.md.template +8 -0
  7. package/docs/CLI_UX_GUIDE.md +79 -4
  8. package/docs/COMMANDS.md.template +21 -5
  9. package/docs/TROUBLESHOOTING.md +70 -0
  10. package/docs/TROUBLESHOOTING.md.template +20 -0
  11. package/docs/getting-started/installation.md +86 -0
  12. package/docs/reference/commands.md +33 -1
  13. package/package.json +1 -1
  14. package/specs/quiver-v30-interactive-cli-ux-agent-selection/EVIDENCE_REPORT.md +213 -0
  15. package/specs/quiver-v30-interactive-cli-ux-agent-selection/EXECUTION_PLAN.md +85 -0
  16. package/specs/quiver-v30-interactive-cli-ux-agent-selection/SPEC.md +213 -0
  17. package/specs/quiver-v30-interactive-cli-ux-agent-selection/STATUS.md +31 -0
  18. package/specs/quiver-v30-interactive-cli-ux-agent-selection/pr.md +103 -0
  19. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +33 -0
  20. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +56 -0
  21. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/slice.json +71 -0
  22. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/CLOSURE_BRIEF.md +31 -0
  23. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/EXECUTION_BRIEF.md +54 -0
  24. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/slice.json +69 -0
  25. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/CLOSURE_BRIEF.md +33 -0
  26. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/EXECUTION_BRIEF.md +56 -0
  27. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/slice.json +81 -0
  28. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/CLOSURE_BRIEF.md +32 -0
  29. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/EXECUTION_BRIEF.md +54 -0
  30. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/slice.json +75 -0
  31. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/CLOSURE_BRIEF.md +32 -0
  32. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/EXECUTION_BRIEF.md +57 -0
  33. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/slice.json +85 -0
  34. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/CLOSURE_BRIEF.md +33 -0
  35. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/EXECUTION_BRIEF.md +57 -0
  36. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/slice.json +85 -0
  37. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/CLOSURE_BRIEF.md +35 -0
  38. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/EXECUTION_BRIEF.md +55 -0
  39. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/slice.json +81 -0
  40. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/CLOSURE_BRIEF.md +34 -0
  41. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/EXECUTION_BRIEF.md +55 -0
  42. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/slice.json +85 -0
  43. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/CLOSURE_BRIEF.md +34 -0
  44. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/EXECUTION_BRIEF.md +59 -0
  45. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/slice.json +95 -0
  46. package/specs/quiver-v31-ai-model-catalog-agent-selection/EVIDENCE_REPORT.md +185 -0
  47. package/specs/quiver-v31-ai-model-catalog-agent-selection/EXECUTION_PLAN.md +85 -0
  48. package/specs/quiver-v31-ai-model-catalog-agent-selection/SPEC.md +337 -0
  49. package/specs/quiver-v31-ai-model-catalog-agent-selection/STATUS.md +30 -0
  50. package/specs/quiver-v31-ai-model-catalog-agent-selection/pr.md +98 -0
  51. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  52. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +51 -0
  53. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/slice.json +62 -0
  54. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/CLOSURE_BRIEF.md +32 -0
  55. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/EXECUTION_BRIEF.md +53 -0
  56. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/slice.json +72 -0
  57. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/CLOSURE_BRIEF.md +36 -0
  58. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/EXECUTION_BRIEF.md +56 -0
  59. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/slice.json +78 -0
  60. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/CLOSURE_BRIEF.md +36 -0
  61. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/EXECUTION_BRIEF.md +57 -0
  62. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/slice.json +77 -0
  63. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/CLOSURE_BRIEF.md +35 -0
  64. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/EXECUTION_BRIEF.md +55 -0
  65. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/slice.json +84 -0
  66. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/CLOSURE_BRIEF.md +32 -0
  67. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/EXECUTION_BRIEF.md +52 -0
  68. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/slice.json +72 -0
  69. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/CLOSURE_BRIEF.md +33 -0
  70. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/EXECUTION_BRIEF.md +58 -0
  71. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/slice.json +84 -0
  72. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/CLOSURE_BRIEF.md +32 -0
  73. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/EXECUTION_BRIEF.md +59 -0
  74. package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/slice.json +94 -0
  75. package/specs/quiver-v32-npx-installation-guidance/EVIDENCE_REPORT.md +26 -0
  76. package/specs/quiver-v32-npx-installation-guidance/SPEC.md +55 -0
  77. package/specs/quiver-v32-npx-installation-guidance/STATUS.md +23 -0
  78. package/specs/quiver-v32-npx-installation-guidance/pr.md +72 -0
  79. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/CLOSURE_BRIEF.md +31 -0
  80. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/EXECUTION_BRIEF.md +56 -0
  81. package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/slice.json +75 -0
  82. package/src/create-quiver/commands/ai.js +797 -99
  83. package/src/create-quiver/commands/flow.js +2 -2
  84. package/src/create-quiver/commands/spec.js +106 -6
  85. package/src/create-quiver/index.js +557 -78
  86. package/src/create-quiver/lib/agent-profiles.js +440 -11
  87. package/src/create-quiver/lib/ai/execution-plan.js +111 -8
  88. package/src/create-quiver/lib/ai/executor.js +291 -28
  89. package/src/create-quiver/lib/ai/model-catalog.js +333 -0
  90. package/src/create-quiver/lib/ai/providers.js +211 -2
  91. package/src/create-quiver/lib/cli/selectors.js +160 -0
  92. package/src/create-quiver/lib/cli/theme.js +5 -2
  93. package/src/create-quiver/lib/cli/ux-flags.js +18 -0
  94. package/src/create-quiver/lib/cli/ux.js +100 -5
@@ -1,11 +1,18 @@
1
1
  const fs = require('node:fs');
2
2
  const path = require('node:path');
3
3
 
4
- const { resolveProfileProvider } = require('../agent-profiles');
4
+ const {
5
+ getAgentProfile,
6
+ getAgentProfileById,
7
+ resolveAgentProfileDisplayName,
8
+ resolveProfileProvider,
9
+ } = require('../agent-profiles');
10
+ const { createUx } = require('../cli/ux');
5
11
  const { branchDelete, runGit, statusPorcelain, worktreeAdd, worktreePrune, worktreeRemove } = require('../git');
6
12
  const { withLock } = require('../locks');
7
13
  const { safeBranchName, worktreesRootForRepo } = require('../slice');
8
14
  const { buildGraph, computeLevels, detectFileConflicts, isFoundationSliceId, readAllSlices, topoSort, SliceGraphError } = require('../slice-graph');
15
+ const { resolveProviderModelSelection } = require('./providers');
9
16
  const { runExecuteSlice } = require('./executor');
10
17
 
11
18
  const EXCLUDED_STATUSES = new Set(['completed', 'skipped', 'cancelled']);
@@ -19,6 +26,71 @@ function toRelativePath(repoRoot, filePath) {
19
26
  return path.relative(repoRoot, filePath).split(path.sep).join('/');
20
27
  }
21
28
 
29
+ function resolveExecutorRuntimeProfile(repoRoot, options = {}) {
30
+ const explicitProvider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false);
31
+ const explicitModel = String(options.model || '').trim();
32
+
33
+ if (explicitProvider) {
34
+ const provider = String(options.provider || 'codex').trim().toLowerCase();
35
+ return {
36
+ provider,
37
+ model: explicitModel,
38
+ displayName: explicitModel || provider,
39
+ profile: null,
40
+ };
41
+ }
42
+
43
+ const profile = options.executorProfile
44
+ ? getAgentProfileById(repoRoot, 'executor', options.executorProfile)
45
+ : getAgentProfile(repoRoot, 'executor');
46
+ const provider = profile?.provider || resolveProfileProvider(repoRoot, options.role || 'executor', 'codex');
47
+ const model = explicitModel || profile?.model || '';
48
+
49
+ return {
50
+ provider,
51
+ model,
52
+ displayName: profile ? resolveAgentProfileDisplayName(profile) : (model || provider),
53
+ profile,
54
+ };
55
+ }
56
+
57
+ function createCommandUx(options = {}) {
58
+ if (options.ux) {
59
+ return options.ux;
60
+ }
61
+
62
+ return createUx({
63
+ env: options.env || process.env,
64
+ interactive: options.interactive,
65
+ json: options.json,
66
+ noColor: options.noColor,
67
+ prompts: options.prompts,
68
+ spinner: options.spinner,
69
+ stdinIsTTY: options.stdinIsTTY,
70
+ stdoutIsTTY: options.stdoutIsTTY,
71
+ stderrIsTTY: options.stderrIsTTY,
72
+ write: options.write,
73
+ });
74
+ }
75
+
76
+ function shouldShowHumanProgress(ux, options = {}) {
77
+ return options.progress !== false
78
+ && options.dryRun !== true
79
+ && options.json !== true
80
+ && ux?.mode?.decoration === true;
81
+ }
82
+
83
+ async function runWithProgress({ ux, enabled, message, successMessage, failureMessage, run }) {
84
+ if (!enabled) {
85
+ return run();
86
+ }
87
+
88
+ return ux.withSpinner(message, run, {
89
+ successMessage,
90
+ failureMessage,
91
+ });
92
+ }
93
+
22
94
  function normalizeExecutionMode(mode) {
23
95
  const value = String(mode || 'auto').trim().toLowerCase() || 'auto';
24
96
  if (!EXECUTION_MODES.has(value)) {
@@ -316,12 +388,14 @@ function formatHumanExecutionPlan(report) {
316
388
 
317
389
  function formatExecutePlanDryRun(report, options = {}) {
318
390
  const provider = options.resolvedProvider || options.provider || 'codex';
391
+ const model = options.resolvedModel || options.model || '';
319
392
  const commitEnabled = options.commit === true;
320
393
  const executionMode = normalizeExecutionMode(options.mode || options.executionMode);
321
394
  const lines = [
322
395
  'AI execute-plan dry-run',
323
396
  `Execution mode: ${executionMode}`,
324
397
  `Provider: ${provider}`,
398
+ model ? `Model: ${model}` : '',
325
399
  `Commit after each slice: ${commitEnabled ? 'enabled' : 'disabled'}`,
326
400
  `Total slices: ${report.summary.total_slices}`,
327
401
  '',
@@ -334,6 +408,9 @@ function formatExecutePlanDryRun(report, options = {}) {
334
408
  `--slice ${JSON.stringify(slice.slice_path)}`,
335
409
  `--provider ${provider}`,
336
410
  ];
411
+ if (model) {
412
+ parts.push(`--model ${JSON.stringify(model)}`);
413
+ }
337
414
  if (commitEnabled) {
338
415
  parts.push('--commit');
339
416
  }
@@ -363,7 +440,7 @@ function formatExecutePlanDryRun(report, options = {}) {
363
440
  }
364
441
  }
365
442
 
366
- return `${lines.join('\n')}\n`;
443
+ return `${lines.filter(Boolean).join('\n')}\n`;
367
444
  }
368
445
 
369
446
  function buildRecoveryGuidance(ref, workspaces = []) {
@@ -455,8 +532,11 @@ async function runSequentialGroup(repoRoot, level, group, options = {}) {
455
532
  commit: true,
456
533
  context: options.context,
457
534
  dryRun: false,
535
+ executorProfile: options.executorProfile,
458
536
  provider: options.provider,
459
537
  providerExplicit: options.providerExplicit,
538
+ progress: false,
539
+ model: options.model,
460
540
  role: options.role,
461
541
  slice: slice.slice_path,
462
542
  skipWorktreeBranchCheck: true,
@@ -507,8 +587,11 @@ async function runParallelGroupInWorktrees(repoRoot, level, group, options = {})
507
587
  commit: true,
508
588
  context: options.context,
509
589
  dryRun: false,
590
+ executorProfile: options.executorProfile,
510
591
  provider: options.provider,
511
592
  providerExplicit: options.providerExplicit,
593
+ progress: false,
594
+ model: options.model,
512
595
  role: options.role,
513
596
  slice: workspace.slice.slice_path,
514
597
  skipWorktreeBranchCheck: true,
@@ -557,13 +640,19 @@ async function runExecutePlan(repoRoot, options = {}) {
557
640
  const report = collectExecutionPlan(repoRoot, options);
558
641
  const execute = options.execute === true;
559
642
  const executionMode = normalizeExecutionMode(options.mode || options.executionMode);
560
- const provider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false)
561
- ? options.provider
562
- : resolveProfileProvider(repoRoot, options.role || 'executor', 'codex');
643
+ const runtimeProfile = resolveExecutorRuntimeProfile(repoRoot, options);
644
+ const provider = runtimeProfile.provider;
645
+ const explicitModel = String(options.model || '').trim();
646
+ const resolvedModel = runtimeProfile.model
647
+ ? resolveProviderModelSelection(provider, runtimeProfile.model).model
648
+ : '';
563
649
  const resolvedOptions = {
564
650
  ...options,
565
651
  mode: executionMode,
652
+ model: explicitModel ? resolvedModel : undefined,
566
653
  provider,
654
+ profile: runtimeProfile,
655
+ resolvedModel,
567
656
  resolvedProvider: provider,
568
657
  };
569
658
 
@@ -586,13 +675,27 @@ async function runExecutePlan(repoRoot, options = {}) {
586
675
  }
587
676
 
588
677
  const results = [];
678
+ const ux = createCommandUx(options);
679
+ const showProgress = shouldShowHumanProgress(ux, options);
680
+ if (showProgress) {
681
+ ux.heading(`Ejecutando plan de slices con ${runtimeProfile.displayName}`);
682
+ ux.check(`Plan cargado: ${report.summary.total_slices} slice${report.summary.total_slices === 1 ? '' : 's'}`);
683
+ ux.check(`Modo: ${executionMode}`);
684
+ }
589
685
 
590
686
  for (const level of report.ready_levels) {
591
687
  for (const group of level.execution_groups) {
592
688
  try {
593
- const groupResults = executionMode === 'delegated' && group.mode === 'parallel' && group.slice_refs.length > 1
594
- ? await runParallelGroupInWorktrees(repoRoot, level, group, resolvedOptions)
595
- : await runSequentialGroup(repoRoot, level, group, resolvedOptions);
689
+ const groupResults = await runWithProgress({
690
+ ux,
691
+ enabled: showProgress,
692
+ message: `Ejecutando wave ${level.index}: ${group.slice_refs.join(', ')}`,
693
+ successMessage: `Wave ${level.index} completada`,
694
+ failureMessage: `Falló wave ${level.index}`,
695
+ run: () => (executionMode === 'delegated' && group.mode === 'parallel' && group.slice_refs.length > 1
696
+ ? runParallelGroupInWorktrees(repoRoot, level, group, resolvedOptions)
697
+ : runSequentialGroup(repoRoot, level, group, resolvedOptions)),
698
+ });
596
699
  results.push(...groupResults);
597
700
  } catch (error) {
598
701
  const wrapped = new Error(formatError(`ai execute-plan stopped at wave ${level.index} group ${group.slice_refs.join(', ')}: ${error.message || error}`));
@@ -4,11 +4,20 @@ const cp = require('node:child_process');
4
4
 
5
5
  const { buildContextPackMetadata, normalizeRole } = require('./context-packs');
6
6
  const { buildProviderInvocation, runProvider } = require('./providers');
7
- const { resolveProfileProvider } = require('../agent-profiles');
7
+ const {
8
+ getAgentProfile,
9
+ getAgentProfileById,
10
+ getAgentProfilesForRole,
11
+ resolveAgentProfileDisplayName,
12
+ resolveProfileProvider,
13
+ } = require('../agent-profiles');
14
+ const { selectOption } = require('../cli/selectors');
15
+ const { createUx } = require('../cli/ux');
8
16
  const { currentBranch, runGit } = require('../git');
9
17
  const { redactSecrets, truncateText } = require('../evidence');
10
18
  const { captureWorktreeSnapshot, validateScopeSnapshot } = require('../scope');
11
19
  const { resolveSliceContext } = require('../slice');
20
+ const { buildGraph, readAllSlices, topoSort } = require('../slice-graph');
12
21
  const { validateProjectRelativePaths } = require('../paths');
13
22
 
14
23
  const DEFAULT_EXECUTE_PROVIDER = 'codex';
@@ -49,6 +58,215 @@ function normalizeTimeout(timeoutMs) {
49
58
  return parsed;
50
59
  }
51
60
 
61
+ function resolveExecutorRuntimeProfile(repoRoot, role, options = {}) {
62
+ const explicitProvider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false);
63
+ const explicitModel = String(options.model || '').trim();
64
+
65
+ if (explicitProvider) {
66
+ const provider = String(options.provider || DEFAULT_EXECUTE_PROVIDER).trim().toLowerCase();
67
+ return {
68
+ provider,
69
+ model: explicitModel,
70
+ displayName: explicitModel || provider,
71
+ profile: null,
72
+ };
73
+ }
74
+
75
+ const profile = options.executorProfile
76
+ ? getAgentProfileById(repoRoot, role, options.executorProfile)
77
+ : getAgentProfile(repoRoot, role);
78
+ const provider = profile?.provider || resolveProfileProvider(repoRoot, role, DEFAULT_EXECUTE_PROVIDER);
79
+ const model = explicitModel || profile?.model || '';
80
+
81
+ return {
82
+ provider,
83
+ model,
84
+ displayName: profile ? resolveAgentProfileDisplayName(profile) : (model || provider),
85
+ profile,
86
+ };
87
+ }
88
+
89
+ function runtimeModelExecutionOptions(runtimeProfile, options = {}) {
90
+ return {
91
+ model: runtimeProfile.model,
92
+ blockModelAlias: Boolean(runtimeProfile.profile && !String(options.model || '').trim()),
93
+ };
94
+ }
95
+
96
+ function createCommandUx(options = {}) {
97
+ if (options.ux) {
98
+ return options.ux;
99
+ }
100
+
101
+ return createUx({
102
+ env: options.env || process.env,
103
+ interactive: options.interactive,
104
+ json: options.json,
105
+ noColor: options.noColor,
106
+ prompts: options.prompts,
107
+ spinner: options.spinner,
108
+ stdinIsTTY: options.stdinIsTTY,
109
+ stdoutIsTTY: options.stdoutIsTTY,
110
+ stderrIsTTY: options.stderrIsTTY,
111
+ write: options.write,
112
+ });
113
+ }
114
+
115
+ function selectorOptions(options = {}) {
116
+ return {
117
+ defaultValue: options.defaultValue,
118
+ env: options.env || process.env,
119
+ flag: options.flag,
120
+ interactive: options.interactive,
121
+ promptSelect: options.promptSelect,
122
+ prompts: options.prompts,
123
+ stdinIsTTY: options.stdinIsTTY,
124
+ stdoutIsTTY: options.stdoutIsTTY,
125
+ stderrIsTTY: options.stderrIsTTY,
126
+ };
127
+ }
128
+
129
+ async function resolveInteractiveExecutorProfile(repoRoot, role, options = {}) {
130
+ if (options.providerExplicit === true || options.provider || options.executorProfile || options.interactive !== true) {
131
+ return options.executorProfile || '';
132
+ }
133
+
134
+ const profiles = getAgentProfilesForRole(repoRoot, role);
135
+ if (profiles.length <= 1) {
136
+ return options.executorProfile || '';
137
+ }
138
+
139
+ const selected = await selectOption(
140
+ '¿Qué Executor querés usar?',
141
+ profiles.map((profile) => ({
142
+ label: resolveAgentProfileDisplayName(profile),
143
+ value: profile.id,
144
+ hint: `${profile.provider}${profile.model ? ` / ${profile.model}` : ''}`,
145
+ })),
146
+ selectorOptions({
147
+ ...options,
148
+ defaultValue: profiles.find((profile) => profile.default)?.id || profiles[0].id,
149
+ flag: '--executor',
150
+ }),
151
+ );
152
+
153
+ return selected.value;
154
+ }
155
+
156
+ function isCompletedStatus(status) {
157
+ return ['completed', 'done', 'skipped', 'cancelled'].includes(String(status || '').trim().toLowerCase());
158
+ }
159
+
160
+ function readySliceOptions(repoRoot) {
161
+ const allSlices = readAllSlices(repoRoot);
162
+ const graph = buildGraph(allSlices);
163
+ topoSort(graph);
164
+ const pending = graph.nodes.filter((node) => !isCompletedStatus(node.status));
165
+ const pendingRefs = new Set(pending.map((node) => node.ref));
166
+ const completedRefs = new Set(graph.nodes.filter((node) => isCompletedStatus(node.status)).map((node) => node.ref));
167
+ const pendingFoundationBySpec = new Set(pending
168
+ .filter((node) => node.sliceId === 'slice-00' || String(node.sliceId || '').startsWith('slice-00-'))
169
+ .map((node) => node.specSlug));
170
+ const incomingByRef = new Map();
171
+
172
+ for (const edge of graph.edges) {
173
+ if (!incomingByRef.has(edge.to)) {
174
+ incomingByRef.set(edge.to, []);
175
+ }
176
+ incomingByRef.get(edge.to).push(edge.from);
177
+ }
178
+
179
+ return pending
180
+ .filter((node) => {
181
+ if (pendingFoundationBySpec.has(node.specSlug) && !(node.sliceId === 'slice-00' || String(node.sliceId || '').startsWith('slice-00-'))) {
182
+ return false;
183
+ }
184
+ const incoming = incomingByRef.get(node.ref) || [];
185
+ return incoming.every((depRef) => completedRefs.has(depRef) || !pendingRefs.has(depRef));
186
+ })
187
+ .sort((left, right) => String(left.ref).localeCompare(String(right.ref)))
188
+ .map((node) => ({
189
+ label: `${node.ref} [${node.status || 'draft'}]`,
190
+ value: toRelativePath(repoRoot, node.slicePath),
191
+ hint: node.title || node.sliceId,
192
+ }));
193
+ }
194
+
195
+ async function resolveInteractiveSliceInput(repoRoot, options = {}) {
196
+ if (options.slice) {
197
+ return options.slice;
198
+ }
199
+
200
+ if (options.interactive !== true) {
201
+ throw new Error(formatError('missing required --slice path for ai execute-slice'));
202
+ }
203
+
204
+ const optionsForSlices = readySliceOptions(repoRoot);
205
+ if (optionsForSlices.length === 0) {
206
+ throw new Error(formatError('no ready slices found for interactive execution. Run `npx create-quiver ai execute-plan --dry-run` to inspect dependencies and blockers.'));
207
+ }
208
+
209
+ const selected = await selectOption(
210
+ '¿Qué slice querés ejecutar?',
211
+ optionsForSlices,
212
+ selectorOptions({
213
+ ...options,
214
+ defaultValue: optionsForSlices.length === 1 ? optionsForSlices[0].value : undefined,
215
+ flag: '--slice',
216
+ }),
217
+ );
218
+ return selected.value;
219
+ }
220
+
221
+ function shouldShowHumanProgress(ux, options = {}) {
222
+ return options.progress !== false
223
+ && options.dryRun !== true
224
+ && ux?.mode?.decoration === true;
225
+ }
226
+
227
+ function writeProgressChecks(ux, enabled, title, checks = []) {
228
+ if (!enabled) {
229
+ return;
230
+ }
231
+ ux.heading(title);
232
+ for (const check of checks) {
233
+ ux.check(check);
234
+ }
235
+ }
236
+
237
+ async function runWithProgress({ ux, enabled, message, successMessage, failureMessage, run }) {
238
+ if (!enabled) {
239
+ return run();
240
+ }
241
+
242
+ return ux.withSpinner(message, run, {
243
+ successMessage,
244
+ failureMessage,
245
+ });
246
+ }
247
+
248
+ async function runProviderWithProgress({ ux, enabled, run }) {
249
+ async function runAndFailOnProviderResult() {
250
+ const result = await run();
251
+ if (result && result.ok === false) {
252
+ const error = new Error(result.error?.message || 'provider run failed');
253
+ error.code = result.error?.code || 'AI_PROVIDER_RUN_FAILED';
254
+ error.providerResult = result;
255
+ throw error;
256
+ }
257
+ return result;
258
+ }
259
+
260
+ return runWithProgress({
261
+ ux,
262
+ enabled,
263
+ message: 'Ejecutando agente...',
264
+ successMessage: 'Agente finalizado',
265
+ failureMessage: 'Fallo ejecutando agente',
266
+ run: runAndFailOnProviderResult,
267
+ });
268
+ }
269
+
52
270
  function toRelativePath(repoRoot, absolutePath) {
53
271
  return path.relative(repoRoot, absolutePath).split(path.sep).join('/');
54
272
  }
@@ -378,11 +596,17 @@ function formatExecuteSliceDryRunReport({ provider, role, contextPack, slice, br
378
596
  `Prompt transport: ${invocation.promptTransport.mode}`,
379
597
  `Prompt length: ${invocation.promptLength} bytes`,
380
598
  `Commit after validation: ${commitEnabled ? 'enabled' : 'disabled'}`,
599
+ ];
600
+ if (invocation.modelSelection && invocation.modelSelection.model) {
601
+ lines.push(`Model: ${invocation.modelSelection.model}`);
602
+ lines.push(`Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`);
603
+ }
604
+ lines.push(
381
605
  'Allowed files:',
382
606
  ...formatList(allowedFiles),
383
607
  'Validation commands:',
384
608
  ...formatList(validationCommands),
385
- ];
609
+ );
386
610
 
387
611
  return `${lines.join('\n')}\n`;
388
612
  }
@@ -711,19 +935,21 @@ function writeExecutionArtifacts(repoRoot, executorContext, details) {
711
935
  async function runExecuteSlice(repoRoot, options = {}) {
712
936
  const canonicalRepoRoot = canonicalizeRepoRoot(repoRoot);
713
937
  const role = normalizeRole(options.role || DEFAULT_EXECUTE_ROLE);
714
- const provider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false)
715
- ? String(options.provider || DEFAULT_EXECUTE_PROVIDER).trim().toLowerCase()
716
- : resolveProfileProvider(canonicalRepoRoot, role, DEFAULT_EXECUTE_PROVIDER);
938
+ const selectedSlice = await resolveInteractiveSliceInput(canonicalRepoRoot, options);
939
+ const selectedExecutorProfile = await resolveInteractiveExecutorProfile(canonicalRepoRoot, role, options);
940
+ const resolvedOptions = {
941
+ ...options,
942
+ executorProfile: selectedExecutorProfile || options.executorProfile,
943
+ slice: selectedSlice,
944
+ };
945
+ const runtimeProfile = resolveExecutorRuntimeProfile(canonicalRepoRoot, role, resolvedOptions);
946
+ const provider = runtimeProfile.provider;
717
947
  const context = options.context || DEFAULT_EXECUTE_CONTEXT;
718
948
  const timeoutMs = normalizeTimeout(options.timeout);
719
949
 
720
- if (!options.slice) {
721
- throw new Error(formatError('missing required --slice path for ai execute-slice'));
722
- }
723
-
724
950
  const executorContext = buildExecuteSliceContext({
725
951
  repoRoot: canonicalRepoRoot,
726
- slicePath: options.slice,
952
+ slicePath: resolvedOptions.slice,
727
953
  role,
728
954
  context,
729
955
  });
@@ -736,6 +962,8 @@ async function runExecuteSlice(repoRoot, options = {}) {
736
962
  prompt,
737
963
  cwd: canonicalRepoRoot,
738
964
  timeoutMs,
965
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
966
+ enforceModelSelection: false,
739
967
  });
740
968
  } catch (error) {
741
969
  throw annotateProviderError(error, 'execute-slice');
@@ -753,6 +981,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
753
981
  allowedFiles: executorContext.allowedFiles,
754
982
  validationCommands: executorContext.validationCommands,
755
983
  commitEnabled: options.commit === true,
984
+ profile: runtimeProfile,
756
985
  };
757
986
  process.stdout.write(formatExecuteSliceDryRunReport({
758
987
  provider,
@@ -782,21 +1011,40 @@ async function runExecuteSlice(repoRoot, options = {}) {
782
1011
  throw appendRecovery(new Error(formatError(`ai execute-slice requires a clean worktree before running. Commit or stash first: ${beforeSnapshot.files.join(', ')}`)), executorContext.slice);
783
1012
  }
784
1013
 
1014
+ const ux = createCommandUx(options);
1015
+ const showProgress = shouldShowHumanProgress(ux, options);
1016
+ writeProgressChecks(
1017
+ ux,
1018
+ showProgress,
1019
+ `Ejecutando slice con ${runtimeProfile.displayName}`,
1020
+ ['Leyendo slice', 'Validando worktree', 'Preparando prompt'],
1021
+ );
1022
+
785
1023
  let result;
786
1024
  try {
787
- result = await (options.runProviderFn || runProvider)(provider, {
788
- prompt,
789
- cwd: canonicalRepoRoot,
790
- timeoutMs,
791
- dryRun: false,
792
- probe: options.probe,
793
- spawn: options.spawn,
794
- tempRoot: options.tempRoot,
795
- tempFileName: options.tempFileName,
796
- tempFilePrefix: options.tempFilePrefix,
1025
+ result = await runProviderWithProgress({
1026
+ ux,
1027
+ enabled: showProgress,
1028
+ run: () => (options.runProviderFn || runProvider)(provider, {
1029
+ prompt,
1030
+ cwd: canonicalRepoRoot,
1031
+ timeoutMs,
1032
+ dryRun: false,
1033
+ probe: options.probe,
1034
+ spawn: options.spawn,
1035
+ tempRoot: options.tempRoot,
1036
+ tempFileName: options.tempFileName,
1037
+ tempFilePrefix: options.tempFilePrefix,
1038
+ ...runtimeModelExecutionOptions(runtimeProfile, options),
1039
+ enforceModelSelection: Boolean(runtimeProfile.model),
1040
+ }),
797
1041
  });
798
1042
  } catch (error) {
799
- throw appendRecovery(annotateProviderError(error, 'execute-slice'), executorContext.slice);
1043
+ if (error.providerResult) {
1044
+ result = error.providerResult;
1045
+ } else {
1046
+ throw appendRecovery(annotateProviderError(error, 'execute-slice'), executorContext.slice);
1047
+ }
800
1048
  }
801
1049
 
802
1050
  if (result.stdout) {
@@ -840,11 +1088,18 @@ async function runExecuteSlice(repoRoot, options = {}) {
840
1088
 
841
1089
  let validationResults = [];
842
1090
  try {
843
- validationResults = runValidationCommands(
844
- canonicalRepoRoot,
845
- executorContext.validationCommands,
846
- options.runValidationCommandFn,
847
- );
1091
+ validationResults = await runWithProgress({
1092
+ ux,
1093
+ enabled: showProgress && executorContext.validationCommands.length > 0,
1094
+ message: 'Ejecutando validaciones...',
1095
+ successMessage: 'Validaciones completadas',
1096
+ failureMessage: 'Fallaron las validaciones',
1097
+ run: () => runValidationCommands(
1098
+ canonicalRepoRoot,
1099
+ executorContext.validationCommands,
1100
+ options.runValidationCommandFn,
1101
+ ),
1102
+ });
848
1103
  } catch (error) {
849
1104
  throw appendRecovery(error, executorContext.slice);
850
1105
  }
@@ -872,8 +1127,15 @@ async function runExecuteSlice(repoRoot, options = {}) {
872
1127
  let commitResult = null;
873
1128
  if (options.commit === true) {
874
1129
  try {
875
- commitResult = commitSliceChanges(canonicalRepoRoot, executorContext.slice, finalScopeResult.changedFiles, {
876
- message: options.commitMessage,
1130
+ commitResult = await runWithProgress({
1131
+ ux,
1132
+ enabled: showProgress,
1133
+ message: 'Creando commit del slice...',
1134
+ successMessage: 'Commit del slice creado',
1135
+ failureMessage: 'Fallo creando commit del slice',
1136
+ run: () => commitSliceChanges(canonicalRepoRoot, executorContext.slice, finalScopeResult.changedFiles, {
1137
+ message: options.commitMessage,
1138
+ }),
877
1139
  });
878
1140
  } catch (error) {
879
1141
  throw appendRecovery(error, executorContext.slice);
@@ -894,6 +1156,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
894
1156
  provider,
895
1157
  role,
896
1158
  contextPack: executorContext.context.packName,
1159
+ profile: runtimeProfile,
897
1160
  slice: executorContext.slice.sliceId,
898
1161
  specSlug: executorContext.slice.specSlug,
899
1162
  invocation,