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