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.
- package/CHANGELOG.md +22 -0
- package/README.md +35 -0
- package/README_FOR_AI.md +15 -4
- package/ROADMAP.md +13 -2
- package/docs/AI_CONTEXT.md.template +4 -0
- package/docs/AI_ONBOARDING_PROMPT.md.template +8 -0
- package/docs/CLI_UX_GUIDE.md +79 -4
- package/docs/COMMANDS.md.template +21 -5
- package/docs/TROUBLESHOOTING.md +70 -0
- package/docs/TROUBLESHOOTING.md.template +20 -0
- package/docs/getting-started/installation.md +86 -0
- package/docs/reference/commands.md +33 -1
- package/package.json +1 -1
- 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/specs/quiver-v31-ai-model-catalog-agent-selection/EVIDENCE_REPORT.md +185 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/EXECUTION_PLAN.md +85 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/SPEC.md +337 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/STATUS.md +30 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/pr.md +98 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/slice.json +62 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/slice.json +72 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/slice.json +78 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/slice.json +77 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/slice.json +84 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/slice.json +72 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/slice.json +84 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/slice.json +94 -0
- package/specs/quiver-v32-npx-installation-guidance/EVIDENCE_REPORT.md +26 -0
- package/specs/quiver-v32-npx-installation-guidance/SPEC.md +55 -0
- package/specs/quiver-v32-npx-installation-guidance/STATUS.md +23 -0
- package/specs/quiver-v32-npx-installation-guidance/pr.md +72 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/slice.json +75 -0
- package/src/create-quiver/commands/ai.js +797 -99
- package/src/create-quiver/commands/flow.js +2 -2
- package/src/create-quiver/commands/spec.js +106 -6
- package/src/create-quiver/index.js +557 -78
- package/src/create-quiver/lib/agent-profiles.js +440 -11
- package/src/create-quiver/lib/ai/execution-plan.js +111 -8
- package/src/create-quiver/lib/ai/executor.js +291 -28
- package/src/create-quiver/lib/ai/model-catalog.js +333 -0
- package/src/create-quiver/lib/ai/providers.js +211 -2
- package/src/create-quiver/lib/cli/selectors.js +160 -0
- package/src/create-quiver/lib/cli/theme.js +5 -2
- package/src/create-quiver/lib/cli/ux-flags.js +18 -0
- 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 {
|
|
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
|
|
561
|
-
|
|
562
|
-
|
|
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 =
|
|
594
|
-
|
|
595
|
-
:
|
|
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 {
|
|
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
|
|
715
|
-
|
|
716
|
-
|
|
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:
|
|
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 (
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
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 =
|
|
844
|
-
|
|
845
|
-
executorContext.validationCommands,
|
|
846
|
-
|
|
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 =
|
|
876
|
-
|
|
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,
|