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.
- package/CHANGELOG.md +27 -0
- package/README.md +187 -518
- package/README_FOR_AI.md +39 -27
- package/ROADMAP.md +11 -0
- package/assets/quiver-wordmark.svg +22 -0
- package/docs/AI_CONTEXT.md.template +2 -0
- package/docs/AI_ONBOARDING_PROMPT.md.template +9 -1
- package/docs/CLI_UX_GUIDE.md +185 -0
- package/docs/COMMANDS.md.template +22 -3
- package/docs/GITFLOW_PR_GUIDE.md +70 -0
- package/docs/getting-started/linux.md +84 -0
- package/docs/getting-started/macos.md +85 -0
- package/docs/getting-started/windows-git-bash-wsl.md +78 -0
- package/docs/getting-started/windows-powershell.md +96 -0
- package/docs/reference/commands.md +98 -0
- package/docs/workflows/existing-project.md +131 -0
- package/docs/workflows/full-ai-spec-to-pr.md +311 -0
- package/docs/workflows/legacy-quiver-project.md +102 -0
- package/docs/workflows/new-project.md +76 -0
- package/package.json +5 -1
- package/specs/quiver-v29-planner-prepare-context-cli-ux/EVIDENCE_REPORT.md +163 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/EXECUTION_PLAN.md +72 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/SPEC.md +173 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/STATUS.md +34 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/pr.md +95 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/EXECUTION_BRIEF.md +45 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-00-cli-ux-spec-foundation/slice.json +58 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/EXECUTION_BRIEF.md +49 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-01-cli-ux-primitives-theme/slice.json +75 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/EXECUTION_BRIEF.md +47 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-02-planner-context-proposal-contract/slice.json +71 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-03-prepare-context-planner-review-flow/slice.json +82 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/EXECUTION_BRIEF.md +46 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-04-ux-flag-matrix-compatibility/slice.json +73 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/EXECUTION_BRIEF.md +46 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-05-progressive-command-adoption/slice.json +83 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/EXECUTION_BRIEF.md +50 -0
- package/specs/quiver-v29-planner-prepare-context-cli-ux/slices/slice-06-docs-tests-smoke-readiness/slice.json +95 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/EVIDENCE_REPORT.md +213 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/EXECUTION_PLAN.md +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/SPEC.md +213 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/STATUS.md +31 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/pr.md +103 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/slice.json +71 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/slice.json +69 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/slice.json +81 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/slice.json +75 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/slice.json +81 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/slice.json +95 -0
- package/src/create-quiver/commands/ai.js +809 -71
- package/src/create-quiver/commands/spec.js +167 -5
- package/src/create-quiver/index.js +582 -71
- package/src/create-quiver/lib/agent-profiles.js +111 -10
- package/src/create-quiver/lib/ai/context-proposal.js +389 -0
- package/src/create-quiver/lib/ai/context-proposal.schema.js +31 -0
- package/src/create-quiver/lib/ai/execution-plan.js +106 -8
- package/src/create-quiver/lib/ai/executor.js +284 -28
- package/src/create-quiver/lib/ai/providers.js +71 -1
- package/src/create-quiver/lib/cli/editor.js +118 -0
- package/src/create-quiver/lib/cli/selectors.js +107 -0
- package/src/create-quiver/lib/cli/theme.js +103 -0
- package/src/create-quiver/lib/cli/ux-flags.js +169 -0
- 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 {
|
|
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
|
|
561
|
-
|
|
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 =
|
|
594
|
-
|
|
595
|
-
:
|
|
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 {
|
|
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
|
|
715
|
-
|
|
716
|
-
|
|
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:
|
|
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 (
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
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 =
|
|
844
|
-
|
|
845
|
-
executorContext.validationCommands,
|
|
846
|
-
|
|
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 =
|
|
876
|
-
|
|
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,
|