create-quiver 0.14.0 → 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 (92) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +187 -518
  3. package/README_FOR_AI.md +39 -27
  4. package/ROADMAP.md +11 -0
  5. package/assets/quiver-wordmark.svg +22 -0
  6. package/docs/AI_CONTEXT.md.template +2 -0
  7. package/docs/AI_ONBOARDING_PROMPT.md.template +9 -1
  8. package/docs/CLI_UX_GUIDE.md +185 -0
  9. package/docs/COMMANDS.md.template +22 -3
  10. package/docs/GITFLOW_PR_GUIDE.md +70 -0
  11. package/docs/getting-started/linux.md +84 -0
  12. package/docs/getting-started/macos.md +85 -0
  13. package/docs/getting-started/windows-git-bash-wsl.md +78 -0
  14. package/docs/getting-started/windows-powershell.md +96 -0
  15. package/docs/reference/commands.md +98 -0
  16. package/docs/workflows/existing-project.md +131 -0
  17. package/docs/workflows/full-ai-spec-to-pr.md +311 -0
  18. package/docs/workflows/legacy-quiver-project.md +102 -0
  19. package/docs/workflows/new-project.md +76 -0
  20. package/package.json +5 -1
  21. package/specs/quiver-v29-planner-prepare-context-cli-ux/EVIDENCE_REPORT.md +163 -0
  22. package/specs/quiver-v29-planner-prepare-context-cli-ux/EXECUTION_PLAN.md +72 -0
  23. package/specs/quiver-v29-planner-prepare-context-cli-ux/SPEC.md +173 -0
  24. package/specs/quiver-v29-planner-prepare-context-cli-ux/STATUS.md +34 -0
  25. package/specs/quiver-v29-planner-prepare-context-cli-ux/pr.md +95 -0
  26. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/CLOSURE_BRIEF.md +32 -0
  27. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/EXECUTION_BRIEF.md +45 -0
  28. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/slice.json +58 -0
  29. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/CLOSURE_BRIEF.md +33 -0
  30. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/EXECUTION_BRIEF.md +49 -0
  31. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/slice.json +75 -0
  32. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/CLOSURE_BRIEF.md +32 -0
  33. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/EXECUTION_BRIEF.md +47 -0
  34. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/slice.json +71 -0
  35. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/CLOSURE_BRIEF.md +33 -0
  36. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/EXECUTION_BRIEF.md +52 -0
  37. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/slice.json +82 -0
  38. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/CLOSURE_BRIEF.md +34 -0
  39. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/EXECUTION_BRIEF.md +46 -0
  40. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/slice.json +73 -0
  41. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/CLOSURE_BRIEF.md +34 -0
  42. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/EXECUTION_BRIEF.md +46 -0
  43. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/slice.json +83 -0
  44. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/CLOSURE_BRIEF.md +31 -0
  45. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/EXECUTION_BRIEF.md +50 -0
  46. package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/slice.json +95 -0
  47. package/specs/quiver-v30-interactive-cli-ux-agent-selection/EVIDENCE_REPORT.md +213 -0
  48. package/specs/quiver-v30-interactive-cli-ux-agent-selection/EXECUTION_PLAN.md +85 -0
  49. package/specs/quiver-v30-interactive-cli-ux-agent-selection/SPEC.md +213 -0
  50. package/specs/quiver-v30-interactive-cli-ux-agent-selection/STATUS.md +31 -0
  51. package/specs/quiver-v30-interactive-cli-ux-agent-selection/pr.md +103 -0
  52. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +33 -0
  53. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +56 -0
  54. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/slice.json +71 -0
  55. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/CLOSURE_BRIEF.md +31 -0
  56. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/EXECUTION_BRIEF.md +54 -0
  57. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/slice.json +69 -0
  58. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/CLOSURE_BRIEF.md +33 -0
  59. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/EXECUTION_BRIEF.md +56 -0
  60. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/slice.json +81 -0
  61. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/CLOSURE_BRIEF.md +32 -0
  62. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/EXECUTION_BRIEF.md +54 -0
  63. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/slice.json +75 -0
  64. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/CLOSURE_BRIEF.md +32 -0
  65. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/EXECUTION_BRIEF.md +57 -0
  66. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/slice.json +85 -0
  67. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/CLOSURE_BRIEF.md +33 -0
  68. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/EXECUTION_BRIEF.md +57 -0
  69. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/slice.json +85 -0
  70. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/CLOSURE_BRIEF.md +35 -0
  71. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/EXECUTION_BRIEF.md +55 -0
  72. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/slice.json +81 -0
  73. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/CLOSURE_BRIEF.md +34 -0
  74. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/EXECUTION_BRIEF.md +55 -0
  75. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/slice.json +85 -0
  76. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/CLOSURE_BRIEF.md +34 -0
  77. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/EXECUTION_BRIEF.md +59 -0
  78. package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/slice.json +95 -0
  79. package/src/create-quiver/commands/ai.js +809 -71
  80. package/src/create-quiver/commands/spec.js +167 -5
  81. package/src/create-quiver/index.js +582 -71
  82. package/src/create-quiver/lib/agent-profiles.js +111 -10
  83. package/src/create-quiver/lib/ai/context-proposal.js +389 -0
  84. package/src/create-quiver/lib/ai/context-proposal.schema.js +31 -0
  85. package/src/create-quiver/lib/ai/execution-plan.js +106 -8
  86. package/src/create-quiver/lib/ai/executor.js +284 -28
  87. package/src/create-quiver/lib/ai/providers.js +71 -1
  88. package/src/create-quiver/lib/cli/editor.js +118 -0
  89. package/src/create-quiver/lib/cli/selectors.js +107 -0
  90. package/src/create-quiver/lib/cli/theme.js +103 -0
  91. package/src/create-quiver/lib/cli/ux-flags.js +169 -0
  92. package/src/create-quiver/lib/cli/ux.js +225 -0
@@ -1,7 +1,13 @@
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');
@@ -19,6 +25,71 @@ function toRelativePath(repoRoot, filePath) {
19
25
  return path.relative(repoRoot, filePath).split(path.sep).join('/');
20
26
  }
21
27
 
28
+ function resolveExecutorRuntimeProfile(repoRoot, options = {}) {
29
+ const explicitProvider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false);
30
+ const explicitModel = String(options.model || '').trim();
31
+
32
+ if (explicitProvider) {
33
+ const provider = String(options.provider || 'codex').trim().toLowerCase();
34
+ return {
35
+ provider,
36
+ model: explicitModel,
37
+ displayName: explicitModel || provider,
38
+ profile: null,
39
+ };
40
+ }
41
+
42
+ const profile = options.executorProfile
43
+ ? getAgentProfileById(repoRoot, 'executor', options.executorProfile)
44
+ : getAgentProfile(repoRoot, 'executor');
45
+ const provider = profile?.provider || resolveProfileProvider(repoRoot, options.role || 'executor', 'codex');
46
+ const model = explicitModel || profile?.model || '';
47
+
48
+ return {
49
+ provider,
50
+ model,
51
+ displayName: profile ? resolveAgentProfileDisplayName(profile) : (model || provider),
52
+ profile,
53
+ };
54
+ }
55
+
56
+ function createCommandUx(options = {}) {
57
+ if (options.ux) {
58
+ return options.ux;
59
+ }
60
+
61
+ return createUx({
62
+ env: options.env || process.env,
63
+ interactive: options.interactive,
64
+ json: options.json,
65
+ noColor: options.noColor,
66
+ prompts: options.prompts,
67
+ spinner: options.spinner,
68
+ stdinIsTTY: options.stdinIsTTY,
69
+ stdoutIsTTY: options.stdoutIsTTY,
70
+ stderrIsTTY: options.stderrIsTTY,
71
+ write: options.write,
72
+ });
73
+ }
74
+
75
+ function shouldShowHumanProgress(ux, options = {}) {
76
+ return options.progress !== false
77
+ && options.dryRun !== true
78
+ && options.json !== true
79
+ && ux?.mode?.decoration === true;
80
+ }
81
+
82
+ async function runWithProgress({ ux, enabled, message, successMessage, failureMessage, run }) {
83
+ if (!enabled) {
84
+ return run();
85
+ }
86
+
87
+ return ux.withSpinner(message, run, {
88
+ successMessage,
89
+ failureMessage,
90
+ });
91
+ }
92
+
22
93
  function normalizeExecutionMode(mode) {
23
94
  const value = String(mode || 'auto').trim().toLowerCase() || 'auto';
24
95
  if (!EXECUTION_MODES.has(value)) {
@@ -316,12 +387,14 @@ function formatHumanExecutionPlan(report) {
316
387
 
317
388
  function formatExecutePlanDryRun(report, options = {}) {
318
389
  const provider = options.resolvedProvider || options.provider || 'codex';
390
+ const model = options.resolvedModel || options.model || '';
319
391
  const commitEnabled = options.commit === true;
320
392
  const executionMode = normalizeExecutionMode(options.mode || options.executionMode);
321
393
  const lines = [
322
394
  'AI execute-plan dry-run',
323
395
  `Execution mode: ${executionMode}`,
324
396
  `Provider: ${provider}`,
397
+ model ? `Model: ${model}` : '',
325
398
  `Commit after each slice: ${commitEnabled ? 'enabled' : 'disabled'}`,
326
399
  `Total slices: ${report.summary.total_slices}`,
327
400
  '',
@@ -334,6 +407,9 @@ function formatExecutePlanDryRun(report, options = {}) {
334
407
  `--slice ${JSON.stringify(slice.slice_path)}`,
335
408
  `--provider ${provider}`,
336
409
  ];
410
+ if (model) {
411
+ parts.push(`--model ${JSON.stringify(model)}`);
412
+ }
337
413
  if (commitEnabled) {
338
414
  parts.push('--commit');
339
415
  }
@@ -363,7 +439,7 @@ function formatExecutePlanDryRun(report, options = {}) {
363
439
  }
364
440
  }
365
441
 
366
- return `${lines.join('\n')}\n`;
442
+ return `${lines.filter(Boolean).join('\n')}\n`;
367
443
  }
368
444
 
369
445
  function buildRecoveryGuidance(ref, workspaces = []) {
@@ -455,8 +531,11 @@ async function runSequentialGroup(repoRoot, level, group, options = {}) {
455
531
  commit: true,
456
532
  context: options.context,
457
533
  dryRun: false,
534
+ executorProfile: options.executorProfile,
458
535
  provider: options.provider,
459
536
  providerExplicit: options.providerExplicit,
537
+ progress: false,
538
+ model: options.model,
460
539
  role: options.role,
461
540
  slice: slice.slice_path,
462
541
  skipWorktreeBranchCheck: true,
@@ -507,8 +586,11 @@ async function runParallelGroupInWorktrees(repoRoot, level, group, options = {})
507
586
  commit: true,
508
587
  context: options.context,
509
588
  dryRun: false,
589
+ executorProfile: options.executorProfile,
510
590
  provider: options.provider,
511
591
  providerExplicit: options.providerExplicit,
592
+ progress: false,
593
+ model: options.model,
512
594
  role: options.role,
513
595
  slice: workspace.slice.slice_path,
514
596
  skipWorktreeBranchCheck: true,
@@ -557,13 +639,15 @@ async function runExecutePlan(repoRoot, options = {}) {
557
639
  const report = collectExecutionPlan(repoRoot, options);
558
640
  const execute = options.execute === true;
559
641
  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');
642
+ const runtimeProfile = resolveExecutorRuntimeProfile(repoRoot, options);
643
+ const provider = runtimeProfile.provider;
563
644
  const resolvedOptions = {
564
645
  ...options,
565
646
  mode: executionMode,
647
+ model: runtimeProfile.model,
566
648
  provider,
649
+ profile: runtimeProfile,
650
+ resolvedModel: runtimeProfile.model,
567
651
  resolvedProvider: provider,
568
652
  };
569
653
 
@@ -586,13 +670,27 @@ async function runExecutePlan(repoRoot, options = {}) {
586
670
  }
587
671
 
588
672
  const results = [];
673
+ const ux = createCommandUx(options);
674
+ const showProgress = shouldShowHumanProgress(ux, options);
675
+ if (showProgress) {
676
+ ux.heading(`Ejecutando plan de slices con ${runtimeProfile.displayName}`);
677
+ ux.check(`Plan cargado: ${report.summary.total_slices} slice${report.summary.total_slices === 1 ? '' : 's'}`);
678
+ ux.check(`Modo: ${executionMode}`);
679
+ }
589
680
 
590
681
  for (const level of report.ready_levels) {
591
682
  for (const group of level.execution_groups) {
592
683
  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);
684
+ const groupResults = await runWithProgress({
685
+ ux,
686
+ enabled: showProgress,
687
+ message: `Ejecutando wave ${level.index}: ${group.slice_refs.join(', ')}`,
688
+ successMessage: `Wave ${level.index} completada`,
689
+ failureMessage: `Falló wave ${level.index}`,
690
+ run: () => (executionMode === 'delegated' && group.mode === 'parallel' && group.slice_refs.length > 1
691
+ ? runParallelGroupInWorktrees(repoRoot, level, group, resolvedOptions)
692
+ : runSequentialGroup(repoRoot, level, group, resolvedOptions)),
693
+ });
596
694
  results.push(...groupResults);
597
695
  } catch (error) {
598
696
  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,208 @@ 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 createCommandUx(options = {}) {
90
+ if (options.ux) {
91
+ return options.ux;
92
+ }
93
+
94
+ return createUx({
95
+ env: options.env || process.env,
96
+ interactive: options.interactive,
97
+ json: options.json,
98
+ noColor: options.noColor,
99
+ prompts: options.prompts,
100
+ spinner: options.spinner,
101
+ stdinIsTTY: options.stdinIsTTY,
102
+ stdoutIsTTY: options.stdoutIsTTY,
103
+ stderrIsTTY: options.stderrIsTTY,
104
+ write: options.write,
105
+ });
106
+ }
107
+
108
+ function selectorOptions(options = {}) {
109
+ return {
110
+ defaultValue: options.defaultValue,
111
+ env: options.env || process.env,
112
+ flag: options.flag,
113
+ interactive: options.interactive,
114
+ promptSelect: options.promptSelect,
115
+ prompts: options.prompts,
116
+ stdinIsTTY: options.stdinIsTTY,
117
+ stdoutIsTTY: options.stdoutIsTTY,
118
+ stderrIsTTY: options.stderrIsTTY,
119
+ };
120
+ }
121
+
122
+ async function resolveInteractiveExecutorProfile(repoRoot, role, options = {}) {
123
+ if (options.providerExplicit === true || options.provider || options.executorProfile || options.interactive !== true) {
124
+ return options.executorProfile || '';
125
+ }
126
+
127
+ const profiles = getAgentProfilesForRole(repoRoot, role);
128
+ if (profiles.length <= 1) {
129
+ return options.executorProfile || '';
130
+ }
131
+
132
+ const selected = await selectOption(
133
+ '¿Qué Executor querés usar?',
134
+ profiles.map((profile) => ({
135
+ label: resolveAgentProfileDisplayName(profile),
136
+ value: profile.id,
137
+ hint: `${profile.provider}${profile.model ? ` / ${profile.model}` : ''}`,
138
+ })),
139
+ selectorOptions({
140
+ ...options,
141
+ defaultValue: profiles.find((profile) => profile.default)?.id || profiles[0].id,
142
+ flag: '--executor',
143
+ }),
144
+ );
145
+
146
+ return selected.value;
147
+ }
148
+
149
+ function isCompletedStatus(status) {
150
+ return ['completed', 'done', 'skipped', 'cancelled'].includes(String(status || '').trim().toLowerCase());
151
+ }
152
+
153
+ function readySliceOptions(repoRoot) {
154
+ const allSlices = readAllSlices(repoRoot);
155
+ const graph = buildGraph(allSlices);
156
+ topoSort(graph);
157
+ const pending = graph.nodes.filter((node) => !isCompletedStatus(node.status));
158
+ const pendingRefs = new Set(pending.map((node) => node.ref));
159
+ const completedRefs = new Set(graph.nodes.filter((node) => isCompletedStatus(node.status)).map((node) => node.ref));
160
+ const pendingFoundationBySpec = new Set(pending
161
+ .filter((node) => node.sliceId === 'slice-00' || String(node.sliceId || '').startsWith('slice-00-'))
162
+ .map((node) => node.specSlug));
163
+ const incomingByRef = new Map();
164
+
165
+ for (const edge of graph.edges) {
166
+ if (!incomingByRef.has(edge.to)) {
167
+ incomingByRef.set(edge.to, []);
168
+ }
169
+ incomingByRef.get(edge.to).push(edge.from);
170
+ }
171
+
172
+ return pending
173
+ .filter((node) => {
174
+ if (pendingFoundationBySpec.has(node.specSlug) && !(node.sliceId === 'slice-00' || String(node.sliceId || '').startsWith('slice-00-'))) {
175
+ return false;
176
+ }
177
+ const incoming = incomingByRef.get(node.ref) || [];
178
+ return incoming.every((depRef) => completedRefs.has(depRef) || !pendingRefs.has(depRef));
179
+ })
180
+ .sort((left, right) => String(left.ref).localeCompare(String(right.ref)))
181
+ .map((node) => ({
182
+ label: `${node.ref} [${node.status || 'draft'}]`,
183
+ value: toRelativePath(repoRoot, node.slicePath),
184
+ hint: node.title || node.sliceId,
185
+ }));
186
+ }
187
+
188
+ async function resolveInteractiveSliceInput(repoRoot, options = {}) {
189
+ if (options.slice) {
190
+ return options.slice;
191
+ }
192
+
193
+ if (options.interactive !== true) {
194
+ throw new Error(formatError('missing required --slice path for ai execute-slice'));
195
+ }
196
+
197
+ const optionsForSlices = readySliceOptions(repoRoot);
198
+ if (optionsForSlices.length === 0) {
199
+ 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.'));
200
+ }
201
+
202
+ const selected = await selectOption(
203
+ '¿Qué slice querés ejecutar?',
204
+ optionsForSlices,
205
+ selectorOptions({
206
+ ...options,
207
+ defaultValue: optionsForSlices.length === 1 ? optionsForSlices[0].value : undefined,
208
+ flag: '--slice',
209
+ }),
210
+ );
211
+ return selected.value;
212
+ }
213
+
214
+ function shouldShowHumanProgress(ux, options = {}) {
215
+ return options.progress !== false
216
+ && options.dryRun !== true
217
+ && ux?.mode?.decoration === true;
218
+ }
219
+
220
+ function writeProgressChecks(ux, enabled, title, checks = []) {
221
+ if (!enabled) {
222
+ return;
223
+ }
224
+ ux.heading(title);
225
+ for (const check of checks) {
226
+ ux.check(check);
227
+ }
228
+ }
229
+
230
+ async function runWithProgress({ ux, enabled, message, successMessage, failureMessage, run }) {
231
+ if (!enabled) {
232
+ return run();
233
+ }
234
+
235
+ return ux.withSpinner(message, run, {
236
+ successMessage,
237
+ failureMessage,
238
+ });
239
+ }
240
+
241
+ async function runProviderWithProgress({ ux, enabled, run }) {
242
+ async function runAndFailOnProviderResult() {
243
+ const result = await run();
244
+ if (result && result.ok === false) {
245
+ const error = new Error(result.error?.message || 'provider run failed');
246
+ error.code = result.error?.code || 'AI_PROVIDER_RUN_FAILED';
247
+ error.providerResult = result;
248
+ throw error;
249
+ }
250
+ return result;
251
+ }
252
+
253
+ return runWithProgress({
254
+ ux,
255
+ enabled,
256
+ message: 'Ejecutando agente...',
257
+ successMessage: 'Agente finalizado',
258
+ failureMessage: 'Fallo ejecutando agente',
259
+ run: runAndFailOnProviderResult,
260
+ });
261
+ }
262
+
52
263
  function toRelativePath(repoRoot, absolutePath) {
53
264
  return path.relative(repoRoot, absolutePath).split(path.sep).join('/');
54
265
  }
@@ -378,11 +589,17 @@ function formatExecuteSliceDryRunReport({ provider, role, contextPack, slice, br
378
589
  `Prompt transport: ${invocation.promptTransport.mode}`,
379
590
  `Prompt length: ${invocation.promptLength} bytes`,
380
591
  `Commit after validation: ${commitEnabled ? 'enabled' : 'disabled'}`,
592
+ ];
593
+ if (invocation.modelSelection && invocation.modelSelection.model) {
594
+ lines.push(`Model: ${invocation.modelSelection.model}`);
595
+ lines.push(`Model support: ${invocation.modelSelection.supported ? 'supported' : 'unsupported'} (${invocation.modelSelection.reason})`);
596
+ }
597
+ lines.push(
381
598
  'Allowed files:',
382
599
  ...formatList(allowedFiles),
383
600
  'Validation commands:',
384
601
  ...formatList(validationCommands),
385
- ];
602
+ );
386
603
 
387
604
  return `${lines.join('\n')}\n`;
388
605
  }
@@ -711,19 +928,21 @@ function writeExecutionArtifacts(repoRoot, executorContext, details) {
711
928
  async function runExecuteSlice(repoRoot, options = {}) {
712
929
  const canonicalRepoRoot = canonicalizeRepoRoot(repoRoot);
713
930
  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);
931
+ const selectedSlice = await resolveInteractiveSliceInput(canonicalRepoRoot, options);
932
+ const selectedExecutorProfile = await resolveInteractiveExecutorProfile(canonicalRepoRoot, role, options);
933
+ const resolvedOptions = {
934
+ ...options,
935
+ executorProfile: selectedExecutorProfile || options.executorProfile,
936
+ slice: selectedSlice,
937
+ };
938
+ const runtimeProfile = resolveExecutorRuntimeProfile(canonicalRepoRoot, role, resolvedOptions);
939
+ const provider = runtimeProfile.provider;
717
940
  const context = options.context || DEFAULT_EXECUTE_CONTEXT;
718
941
  const timeoutMs = normalizeTimeout(options.timeout);
719
942
 
720
- if (!options.slice) {
721
- throw new Error(formatError('missing required --slice path for ai execute-slice'));
722
- }
723
-
724
943
  const executorContext = buildExecuteSliceContext({
725
944
  repoRoot: canonicalRepoRoot,
726
- slicePath: options.slice,
945
+ slicePath: resolvedOptions.slice,
727
946
  role,
728
947
  context,
729
948
  });
@@ -736,6 +955,8 @@ async function runExecuteSlice(repoRoot, options = {}) {
736
955
  prompt,
737
956
  cwd: canonicalRepoRoot,
738
957
  timeoutMs,
958
+ model: runtimeProfile.model,
959
+ enforceModelSelection: false,
739
960
  });
740
961
  } catch (error) {
741
962
  throw annotateProviderError(error, 'execute-slice');
@@ -753,6 +974,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
753
974
  allowedFiles: executorContext.allowedFiles,
754
975
  validationCommands: executorContext.validationCommands,
755
976
  commitEnabled: options.commit === true,
977
+ profile: runtimeProfile,
756
978
  };
757
979
  process.stdout.write(formatExecuteSliceDryRunReport({
758
980
  provider,
@@ -782,21 +1004,40 @@ async function runExecuteSlice(repoRoot, options = {}) {
782
1004
  throw appendRecovery(new Error(formatError(`ai execute-slice requires a clean worktree before running. Commit or stash first: ${beforeSnapshot.files.join(', ')}`)), executorContext.slice);
783
1005
  }
784
1006
 
1007
+ const ux = createCommandUx(options);
1008
+ const showProgress = shouldShowHumanProgress(ux, options);
1009
+ writeProgressChecks(
1010
+ ux,
1011
+ showProgress,
1012
+ `Ejecutando slice con ${runtimeProfile.displayName}`,
1013
+ ['Leyendo slice', 'Validando worktree', 'Preparando prompt'],
1014
+ );
1015
+
785
1016
  let result;
786
1017
  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,
1018
+ result = await runProviderWithProgress({
1019
+ ux,
1020
+ enabled: showProgress,
1021
+ run: () => (options.runProviderFn || runProvider)(provider, {
1022
+ prompt,
1023
+ cwd: canonicalRepoRoot,
1024
+ timeoutMs,
1025
+ dryRun: false,
1026
+ probe: options.probe,
1027
+ spawn: options.spawn,
1028
+ tempRoot: options.tempRoot,
1029
+ tempFileName: options.tempFileName,
1030
+ tempFilePrefix: options.tempFilePrefix,
1031
+ model: runtimeProfile.model,
1032
+ enforceModelSelection: Boolean(runtimeProfile.model),
1033
+ }),
797
1034
  });
798
1035
  } catch (error) {
799
- throw appendRecovery(annotateProviderError(error, 'execute-slice'), executorContext.slice);
1036
+ if (error.providerResult) {
1037
+ result = error.providerResult;
1038
+ } else {
1039
+ throw appendRecovery(annotateProviderError(error, 'execute-slice'), executorContext.slice);
1040
+ }
800
1041
  }
801
1042
 
802
1043
  if (result.stdout) {
@@ -840,11 +1081,18 @@ async function runExecuteSlice(repoRoot, options = {}) {
840
1081
 
841
1082
  let validationResults = [];
842
1083
  try {
843
- validationResults = runValidationCommands(
844
- canonicalRepoRoot,
845
- executorContext.validationCommands,
846
- options.runValidationCommandFn,
847
- );
1084
+ validationResults = await runWithProgress({
1085
+ ux,
1086
+ enabled: showProgress && executorContext.validationCommands.length > 0,
1087
+ message: 'Ejecutando validaciones...',
1088
+ successMessage: 'Validaciones completadas',
1089
+ failureMessage: 'Fallaron las validaciones',
1090
+ run: () => runValidationCommands(
1091
+ canonicalRepoRoot,
1092
+ executorContext.validationCommands,
1093
+ options.runValidationCommandFn,
1094
+ ),
1095
+ });
848
1096
  } catch (error) {
849
1097
  throw appendRecovery(error, executorContext.slice);
850
1098
  }
@@ -872,8 +1120,15 @@ async function runExecuteSlice(repoRoot, options = {}) {
872
1120
  let commitResult = null;
873
1121
  if (options.commit === true) {
874
1122
  try {
875
- commitResult = commitSliceChanges(canonicalRepoRoot, executorContext.slice, finalScopeResult.changedFiles, {
876
- message: options.commitMessage,
1123
+ commitResult = await runWithProgress({
1124
+ ux,
1125
+ enabled: showProgress,
1126
+ message: 'Creando commit del slice...',
1127
+ successMessage: 'Commit del slice creado',
1128
+ failureMessage: 'Fallo creando commit del slice',
1129
+ run: () => commitSliceChanges(canonicalRepoRoot, executorContext.slice, finalScopeResult.changedFiles, {
1130
+ message: options.commitMessage,
1131
+ }),
877
1132
  });
878
1133
  } catch (error) {
879
1134
  throw appendRecovery(error, executorContext.slice);
@@ -894,6 +1149,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
894
1149
  provider,
895
1150
  role,
896
1151
  contextPack: executorContext.context.packName,
1152
+ profile: runtimeProfile,
897
1153
  slice: executorContext.slice.sliceId,
898
1154
  specSlug: executorContext.slice.specSlug,
899
1155
  invocation,