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
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const { assertSupportedProvider } = require('./providers');
|
|
2
|
+
|
|
3
|
+
const MODEL_CATALOG_VERSION = 1;
|
|
4
|
+
const MODEL_CATALOG_LAST_UPDATED = '2026-05-27';
|
|
5
|
+
|
|
6
|
+
const MODEL_CATALOG = Object.freeze({
|
|
7
|
+
catalogVersion: MODEL_CATALOG_VERSION,
|
|
8
|
+
lastUpdated: MODEL_CATALOG_LAST_UPDATED,
|
|
9
|
+
providers: Object.freeze({
|
|
10
|
+
codex: Object.freeze({
|
|
11
|
+
id: 'codex',
|
|
12
|
+
displayName: 'Codex',
|
|
13
|
+
models: Object.freeze([
|
|
14
|
+
knownModel('gpt-5.5', 'GPT 5.5', {
|
|
15
|
+
aliases: ['GPT 5.5', 'gpt 5.5', 'Gpt-5.5'],
|
|
16
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
17
|
+
costTier: 'high',
|
|
18
|
+
qualityTier: 'premium',
|
|
19
|
+
stability: 'stable',
|
|
20
|
+
notes: 'Recommended for planning, specs, architecture, and high-risk reviews.',
|
|
21
|
+
}),
|
|
22
|
+
knownModel('gpt-5.4', 'GPT 5.4', {
|
|
23
|
+
aliases: ['GPT 5.4', 'gpt 5.4'],
|
|
24
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
25
|
+
costTier: 'medium',
|
|
26
|
+
qualityTier: 'high',
|
|
27
|
+
stability: 'stable',
|
|
28
|
+
notes: 'Balanced planner or reviewer model.',
|
|
29
|
+
}),
|
|
30
|
+
knownModel('gpt-5.4-mini', 'GPT 5.4 mini', {
|
|
31
|
+
aliases: ['GPT 5.4 mini', 'gpt 5.4 mini', 'gpt-5.4 mini'],
|
|
32
|
+
recommendedFor: ['executor', 'doctor'],
|
|
33
|
+
costTier: 'low',
|
|
34
|
+
qualityTier: 'standard',
|
|
35
|
+
stability: 'stable',
|
|
36
|
+
notes: 'Recommended for lower-cost execution, diagnostics, and sub-agent work.',
|
|
37
|
+
}),
|
|
38
|
+
knownModel('gpt-5.3-codex', 'GPT 5.3 Codex', {
|
|
39
|
+
aliases: ['GPT 5.3 Codex', 'gpt 5.3 codex'],
|
|
40
|
+
recommendedFor: ['executor'],
|
|
41
|
+
costTier: 'medium',
|
|
42
|
+
qualityTier: 'high',
|
|
43
|
+
stability: 'stable',
|
|
44
|
+
notes: 'Recommended for more complex coding slices.',
|
|
45
|
+
}),
|
|
46
|
+
knownModel('gpt-5.3-codex-spark', 'GPT 5.3 Codex Spark', {
|
|
47
|
+
aliases: ['GPT 5.3 Codex Spark', 'gpt 5.3 codex spark'],
|
|
48
|
+
recommendedFor: ['executor'],
|
|
49
|
+
costTier: 'medium',
|
|
50
|
+
qualityTier: 'high',
|
|
51
|
+
stability: 'preview',
|
|
52
|
+
notes: 'Preview or limited model for quick code iteration.',
|
|
53
|
+
}),
|
|
54
|
+
]),
|
|
55
|
+
}),
|
|
56
|
+
claude: Object.freeze({
|
|
57
|
+
id: 'claude',
|
|
58
|
+
displayName: 'Claude',
|
|
59
|
+
models: Object.freeze([
|
|
60
|
+
knownModel('opus', 'Claude Opus', {
|
|
61
|
+
aliases: ['Claude Opus', 'opus'],
|
|
62
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
63
|
+
costTier: 'high',
|
|
64
|
+
qualityTier: 'premium',
|
|
65
|
+
stability: 'stable',
|
|
66
|
+
notes: 'Recommended for complex planning and architectural decisions.',
|
|
67
|
+
}),
|
|
68
|
+
knownModel('sonnet', 'Claude Sonnet', {
|
|
69
|
+
aliases: ['Claude Sonnet', 'sonnet'],
|
|
70
|
+
recommendedFor: ['executor', 'reviewer'],
|
|
71
|
+
costTier: 'medium',
|
|
72
|
+
qualityTier: 'high',
|
|
73
|
+
stability: 'stable',
|
|
74
|
+
notes: 'Balanced execution and review model.',
|
|
75
|
+
}),
|
|
76
|
+
knownModel('haiku', 'Claude Haiku', {
|
|
77
|
+
aliases: ['Claude Haiku', 'haiku'],
|
|
78
|
+
recommendedFor: ['doctor'],
|
|
79
|
+
costTier: 'low',
|
|
80
|
+
qualityTier: 'standard',
|
|
81
|
+
stability: 'stable',
|
|
82
|
+
notes: 'Recommended for diagnostics and simple tasks.',
|
|
83
|
+
}),
|
|
84
|
+
knownModel('claude-opus-4-7', 'Claude Opus 4.7', {
|
|
85
|
+
aliases: ['OPUS 4.7', 'Claude Opus 4.7', 'opus 4.7'],
|
|
86
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
87
|
+
costTier: 'high',
|
|
88
|
+
qualityTier: 'premium',
|
|
89
|
+
stability: 'known',
|
|
90
|
+
notes: 'Version-pinned known catalog choice for planning.',
|
|
91
|
+
}),
|
|
92
|
+
knownModel('claude-sonnet-4-6', 'Claude Sonnet 4.6', {
|
|
93
|
+
aliases: ['Claude Sonnet 4.6', 'sonnet 4.6'],
|
|
94
|
+
recommendedFor: ['executor', 'reviewer'],
|
|
95
|
+
costTier: 'medium',
|
|
96
|
+
qualityTier: 'high',
|
|
97
|
+
stability: 'known',
|
|
98
|
+
notes: 'Version-pinned known catalog choice for execution and review.',
|
|
99
|
+
}),
|
|
100
|
+
knownModel('claude-haiku-4-5', 'Claude Haiku 4.5', {
|
|
101
|
+
aliases: ['Claude Haiku 4.5', 'haiku 4.5'],
|
|
102
|
+
recommendedFor: ['doctor'],
|
|
103
|
+
costTier: 'low',
|
|
104
|
+
qualityTier: 'standard',
|
|
105
|
+
stability: 'known',
|
|
106
|
+
notes: 'Version-pinned known catalog choice for low-cost diagnostics.',
|
|
107
|
+
}),
|
|
108
|
+
]),
|
|
109
|
+
}),
|
|
110
|
+
gemini: Object.freeze({
|
|
111
|
+
id: 'gemini',
|
|
112
|
+
displayName: 'Gemini',
|
|
113
|
+
models: Object.freeze([
|
|
114
|
+
knownModel('gemini-3.1-pro-preview', 'Gemini 3.1 Pro Preview', {
|
|
115
|
+
aliases: ['Gemini 3.1 Pro Preview', 'gemini 3.1 pro preview'],
|
|
116
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
117
|
+
costTier: 'high',
|
|
118
|
+
qualityTier: 'premium',
|
|
119
|
+
stability: 'preview',
|
|
120
|
+
notes: 'Known catalog choice for complex planning and review.',
|
|
121
|
+
}),
|
|
122
|
+
knownModel('gemini-3.5-flash', 'Gemini 3.5 Flash', {
|
|
123
|
+
aliases: ['Gemini 3.5 Flash', 'gemini 3.5 flash'],
|
|
124
|
+
recommendedFor: ['executor'],
|
|
125
|
+
costTier: 'medium',
|
|
126
|
+
qualityTier: 'high',
|
|
127
|
+
stability: 'stable',
|
|
128
|
+
notes: 'Known catalog choice for balanced execution.',
|
|
129
|
+
}),
|
|
130
|
+
knownModel('gemini-3.1-flash-lite', 'Gemini 3.1 Flash-Lite', {
|
|
131
|
+
aliases: ['Gemini 3.1 Flash Lite', 'Gemini 3.1 Flash-Lite', 'gemini 3.1 flash lite'],
|
|
132
|
+
recommendedFor: ['doctor'],
|
|
133
|
+
costTier: 'low',
|
|
134
|
+
qualityTier: 'standard',
|
|
135
|
+
stability: 'stable',
|
|
136
|
+
notes: 'Known catalog choice for low-cost diagnostics and mechanical tasks.',
|
|
137
|
+
}),
|
|
138
|
+
knownModel('gemini-3.1-pro-preview-customtools', 'Gemini 3.1 Pro Custom Tools', {
|
|
139
|
+
aliases: ['Gemini 3.1 Pro Custom Tools', 'gemini 3.1 pro preview custom tools'],
|
|
140
|
+
recommendedFor: ['planner', 'reviewer'],
|
|
141
|
+
costTier: 'high',
|
|
142
|
+
qualityTier: 'premium',
|
|
143
|
+
stability: 'preview',
|
|
144
|
+
notes: 'Known catalog choice for advanced tool-oriented workflows.',
|
|
145
|
+
}),
|
|
146
|
+
]),
|
|
147
|
+
}),
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
function knownModel(id, displayName, options = {}) {
|
|
152
|
+
return Object.freeze({
|
|
153
|
+
id,
|
|
154
|
+
displayName,
|
|
155
|
+
aliases: Object.freeze(Array.from(new Set([id, displayName].concat(options.aliases || [])))),
|
|
156
|
+
recommendedFor: Object.freeze((options.recommendedFor || []).slice()),
|
|
157
|
+
costTier: options.costTier || 'unknown',
|
|
158
|
+
qualityTier: options.qualityTier || 'unknown',
|
|
159
|
+
stability: options.stability || 'known',
|
|
160
|
+
notes: options.notes || '',
|
|
161
|
+
modelSource: 'catalog',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function customModel(providerId, modelId = '') {
|
|
166
|
+
const id = String(modelId || '').trim();
|
|
167
|
+
return {
|
|
168
|
+
id: id || 'custom',
|
|
169
|
+
displayName: id || 'Custom',
|
|
170
|
+
aliases: id ? [id] : ['custom'],
|
|
171
|
+
recommendedFor: [],
|
|
172
|
+
costTier: 'unknown',
|
|
173
|
+
qualityTier: 'unknown',
|
|
174
|
+
stability: 'custom',
|
|
175
|
+
notes: 'Custom model id. Quiver cannot guarantee account availability without a live validation.',
|
|
176
|
+
modelSource: 'custom',
|
|
177
|
+
custom: true,
|
|
178
|
+
provider: providerId ? assertSupportedProvider(providerId) : '',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function getModelCatalog() {
|
|
183
|
+
return MODEL_CATALOG;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function listCatalogProviders() {
|
|
187
|
+
return Object.values(MODEL_CATALOG.providers).map((provider) => ({
|
|
188
|
+
id: provider.id,
|
|
189
|
+
displayName: provider.displayName,
|
|
190
|
+
modelCount: provider.models.length,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getProviderModelCatalog(providerId) {
|
|
195
|
+
const provider = assertSupportedProvider(providerId);
|
|
196
|
+
return MODEL_CATALOG.providers[provider];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getKnownModelsForProvider(providerId, options = {}) {
|
|
200
|
+
const provider = getProviderModelCatalog(providerId);
|
|
201
|
+
const role = String(options.role || '').trim().toLowerCase();
|
|
202
|
+
const includeCustom = options.includeCustom !== false;
|
|
203
|
+
const models = provider.models.slice();
|
|
204
|
+
const sorted = role
|
|
205
|
+
? models.sort((left, right) => roleScore(right, role) - roleScore(left, role))
|
|
206
|
+
: models;
|
|
207
|
+
|
|
208
|
+
if (!includeCustom) {
|
|
209
|
+
return sorted;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return sorted.concat(customModel(provider.id));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function roleScore(model, role) {
|
|
216
|
+
const roles = Array.isArray(model.recommendedFor) ? model.recommendedFor : [];
|
|
217
|
+
const index = roles.indexOf(role);
|
|
218
|
+
if (index === -1) return 0;
|
|
219
|
+
return 100 - index;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function normalizeModelAliasKey(value) {
|
|
223
|
+
return String(value || '')
|
|
224
|
+
.trim()
|
|
225
|
+
.toLowerCase()
|
|
226
|
+
.replace(/[^a-z0-9]+/g, '');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function resolveModelFromEntries(providerId, modelValue, entries) {
|
|
230
|
+
const provider = assertSupportedProvider(providerId);
|
|
231
|
+
const input = String(modelValue || '').trim();
|
|
232
|
+
const inputKey = normalizeModelAliasKey(input);
|
|
233
|
+
if (!inputKey) {
|
|
234
|
+
return {
|
|
235
|
+
provider,
|
|
236
|
+
input,
|
|
237
|
+
matched: false,
|
|
238
|
+
ambiguous: false,
|
|
239
|
+
model: null,
|
|
240
|
+
matches: [],
|
|
241
|
+
reason: 'empty model',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const matches = [];
|
|
246
|
+
for (const model of entries || []) {
|
|
247
|
+
const aliases = Array.isArray(model.aliases) ? model.aliases : [model.id, model.displayName];
|
|
248
|
+
if (aliases.some((alias) => normalizeModelAliasKey(alias) === inputKey)) {
|
|
249
|
+
matches.push(model);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (matches.length === 1) {
|
|
254
|
+
const model = matches[0];
|
|
255
|
+
return {
|
|
256
|
+
provider,
|
|
257
|
+
input,
|
|
258
|
+
matched: true,
|
|
259
|
+
ambiguous: false,
|
|
260
|
+
model,
|
|
261
|
+
matches,
|
|
262
|
+
technicalModel: model.id,
|
|
263
|
+
displayName: model.displayName,
|
|
264
|
+
modelSource: model.modelSource || 'catalog',
|
|
265
|
+
reason: 'known model alias matched',
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (matches.length > 1) {
|
|
270
|
+
return {
|
|
271
|
+
provider,
|
|
272
|
+
input,
|
|
273
|
+
matched: false,
|
|
274
|
+
ambiguous: true,
|
|
275
|
+
model: null,
|
|
276
|
+
matches,
|
|
277
|
+
reason: 'ambiguous model alias',
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
provider,
|
|
283
|
+
input,
|
|
284
|
+
matched: false,
|
|
285
|
+
ambiguous: false,
|
|
286
|
+
model: customModel(provider, input),
|
|
287
|
+
matches: [],
|
|
288
|
+
technicalModel: input,
|
|
289
|
+
displayName: input,
|
|
290
|
+
modelSource: 'custom',
|
|
291
|
+
reason: 'custom model',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function resolveKnownModel(providerId, modelValue) {
|
|
296
|
+
const provider = getProviderModelCatalog(providerId);
|
|
297
|
+
return resolveModelFromEntries(provider.id, modelValue, provider.models);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function normalizeModelSelection(providerId, modelValue, options = {}) {
|
|
301
|
+
const resolution = resolveKnownModel(providerId, modelValue);
|
|
302
|
+
const fallbackDisplayName = String(options.displayName || '').trim();
|
|
303
|
+
|
|
304
|
+
if (resolution.ambiguous) {
|
|
305
|
+
return {
|
|
306
|
+
...resolution,
|
|
307
|
+
model: String(modelValue || '').trim(),
|
|
308
|
+
displayName: fallbackDisplayName || String(modelValue || '').trim(),
|
|
309
|
+
validationStatus: options.validationStatus || 'not-tested',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
...resolution,
|
|
315
|
+
model: resolution.technicalModel || String(modelValue || '').trim(),
|
|
316
|
+
displayName: fallbackDisplayName || resolution.displayName || String(modelValue || '').trim(),
|
|
317
|
+
validationStatus: options.validationStatus || 'not-tested',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
module.exports = {
|
|
322
|
+
MODEL_CATALOG_LAST_UPDATED,
|
|
323
|
+
MODEL_CATALOG_VERSION,
|
|
324
|
+
customModel,
|
|
325
|
+
getKnownModelsForProvider,
|
|
326
|
+
getModelCatalog,
|
|
327
|
+
getProviderModelCatalog,
|
|
328
|
+
listCatalogProviders,
|
|
329
|
+
normalizeModelAliasKey,
|
|
330
|
+
normalizeModelSelection,
|
|
331
|
+
resolveKnownModel,
|
|
332
|
+
resolveModelFromEntries,
|
|
333
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('node:fs');
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
|
|
4
|
-
const { readPhaseApproval, resolveApprovedPlannerInput } = require('../approvals');
|
|
4
|
+
const { buildPlannerApprovalCandidates, readPhaseApproval, resolveApprovedPlannerInput } = require('../approvals');
|
|
5
5
|
const { quiverInternalPaths } = require('../init-layout');
|
|
6
6
|
|
|
7
7
|
const PLAN_REVIEW_PROMPT_SOURCE = 'packaged production-readiness plan review template';
|
|
@@ -186,6 +186,81 @@ function reviewBlocksApproval(review) {
|
|
|
186
186
|
return result.blocking === true || result.approval_recommendation === 'revise';
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
function normalizeReviewDecision(review) {
|
|
190
|
+
const result = review?.meta?.review_result || null;
|
|
191
|
+
const requiredFixes = normalizeList(result?.required_fixes);
|
|
192
|
+
const optionalHardening = normalizeList(result?.optional_hardening);
|
|
193
|
+
const risks = normalizeList(result?.risks);
|
|
194
|
+
const blocking = reviewBlocksApproval(review);
|
|
195
|
+
const recommendation = result?.approval_recommendation || (review.status === 'missing' || review.status === 'stale' ? 'review-required' : 'approve-with-risk');
|
|
196
|
+
const nextCommand = result?.next_command || (review.status === 'unapproved' || review.status === 'reviewed'
|
|
197
|
+
? 'npx create-quiver ai approve --phase technical-plan --version <n>'
|
|
198
|
+
: 'npx create-quiver ai review-plan --dry-run');
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
status: review.status,
|
|
202
|
+
recommendation,
|
|
203
|
+
blocking: blocking || review.status === 'missing' || review.status === 'stale',
|
|
204
|
+
required_fixes: requiredFixes,
|
|
205
|
+
optional_hardening: optionalHardening,
|
|
206
|
+
risks,
|
|
207
|
+
required_fixes_count: requiredFixes.length,
|
|
208
|
+
optional_hardening_count: optionalHardening.length,
|
|
209
|
+
risks_count: risks.length,
|
|
210
|
+
source_file: review.meta?.source_file || '',
|
|
211
|
+
source_kind: review.meta?.source_kind || null,
|
|
212
|
+
source_version: review.meta?.source_version || null,
|
|
213
|
+
reviewed_at: review.meta?.reviewed_at || '',
|
|
214
|
+
next_command: nextCommand,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildTechnicalPlanApprovalCandidates(projectRoot) {
|
|
219
|
+
const base = buildPlannerApprovalCandidates(projectRoot, 'technical-plan');
|
|
220
|
+
const review = readPlanReview(projectRoot);
|
|
221
|
+
const reviewDecision = normalizeReviewDecision(review);
|
|
222
|
+
const reviewReady = (review.status === 'unapproved' || review.status === 'reviewed') && reviewDecision.blocking !== true;
|
|
223
|
+
const candidates = base.candidates.map((candidate) => {
|
|
224
|
+
const matchesReviewVersion = !reviewDecision.source_version
|
|
225
|
+
|| !candidate.version
|
|
226
|
+
|| Number(reviewDecision.source_version) === Number(candidate.version);
|
|
227
|
+
const approvable = candidate.approvable === true && reviewReady && matchesReviewVersion;
|
|
228
|
+
const reason = approvable
|
|
229
|
+
? `latest draft has plan-review recommendation ${reviewDecision.recommendation}`
|
|
230
|
+
: review.status === 'missing'
|
|
231
|
+
? 'technical-plan requires production review before approval'
|
|
232
|
+
: review.status === 'stale'
|
|
233
|
+
? 'technical-plan review is stale'
|
|
234
|
+
: reviewDecision.blocking
|
|
235
|
+
? `plan-review blocks approval with recommendation ${reviewDecision.recommendation}`
|
|
236
|
+
: matchesReviewVersion
|
|
237
|
+
? candidate.reason
|
|
238
|
+
: `plan-review targets v${reviewDecision.source_version}; candidate is v${candidate.version}`;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
...candidate,
|
|
242
|
+
approvable,
|
|
243
|
+
recommended: candidate.current === true && approvable,
|
|
244
|
+
blocked: !approvable,
|
|
245
|
+
status: approvable ? 'approvable' : candidate.current ? 'blocked' : 'history',
|
|
246
|
+
reason,
|
|
247
|
+
recommended_action: approvable ? 'approve' : reviewDecision.blocking || review.status === 'missing' || review.status === 'stale' ? 'review-or-revise' : 'inspect',
|
|
248
|
+
next_command: approvable ? candidate.next_command : reviewDecision.next_command,
|
|
249
|
+
review: reviewDecision,
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
...base,
|
|
255
|
+
candidates,
|
|
256
|
+
current: candidates.find((candidate) => candidate.current) || null,
|
|
257
|
+
recommended: candidates.find((candidate) => candidate.recommended) || null,
|
|
258
|
+
history: candidates.filter((candidate) => !candidate.current),
|
|
259
|
+
review: reviewDecision,
|
|
260
|
+
next_command: candidates.find((candidate) => candidate.recommended)?.next_command || reviewDecision.next_command,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
189
264
|
function latestTechnicalPlanDraft(approval) {
|
|
190
265
|
const version = Number(approval.meta?.draft?.version || 0);
|
|
191
266
|
if (!version) {
|
|
@@ -435,6 +510,7 @@ module.exports = {
|
|
|
435
510
|
PLAN_REVIEW_RECOMMENDATIONS,
|
|
436
511
|
assertPlanReviewed,
|
|
437
512
|
buildPlanReviewPrompt,
|
|
513
|
+
buildTechnicalPlanApprovalCandidates,
|
|
438
514
|
derivePlanReviewResult,
|
|
439
515
|
planReviewMetaPath,
|
|
440
516
|
planReviewPath,
|
|
@@ -72,9 +72,54 @@ function normalizeProviderModel(model) {
|
|
|
72
72
|
return value || '';
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function resolveProviderModelSelection(providerId, model, options = {}) {
|
|
76
|
+
const rawModel = normalizeProviderModel(model);
|
|
77
|
+
if (!rawModel) {
|
|
78
|
+
return {
|
|
79
|
+
input: '',
|
|
80
|
+
model: '',
|
|
81
|
+
displayName: '',
|
|
82
|
+
modelSource: '',
|
|
83
|
+
aliasNormalized: false,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { normalizeModelSelection } = require('./model-catalog');
|
|
88
|
+
const selection = normalizeModelSelection(providerId, rawModel, {
|
|
89
|
+
displayName: options.displayName,
|
|
90
|
+
});
|
|
91
|
+
const aliasNormalized = selection.modelSource === 'catalog' && selection.model !== rawModel;
|
|
92
|
+
if (aliasNormalized && options.blockModelAlias === true) {
|
|
93
|
+
throw new ProviderRunnerError(
|
|
94
|
+
'DISPLAY_MODEL_ALIAS',
|
|
95
|
+
`Model '${rawModel}' is a display alias for provider '${providerId}', not the technical id. Use '${selection.model}' or run 'npx create-quiver ai agent repair --dry-run'.`,
|
|
96
|
+
{
|
|
97
|
+
provider: providerId,
|
|
98
|
+
inputModel: rawModel,
|
|
99
|
+
suggestedModel: selection.model,
|
|
100
|
+
displayName: selection.displayName,
|
|
101
|
+
nextSteps: [
|
|
102
|
+
`Use --model ${selection.model}.`,
|
|
103
|
+
'Run npx create-quiver ai agent repair --dry-run to preview profile normalization.',
|
|
104
|
+
'Run npx create-quiver ai agent doctor to inspect profile issues.',
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
input: rawModel,
|
|
112
|
+
model: selection.model,
|
|
113
|
+
displayName: selection.displayName,
|
|
114
|
+
modelSource: selection.modelSource,
|
|
115
|
+
aliasNormalized,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
75
119
|
function buildProviderModelArgs(providerId, model, options = {}) {
|
|
76
120
|
const provider = getProviderDefinition(providerId);
|
|
77
|
-
const
|
|
121
|
+
const modelResolution = resolveProviderModelSelection(provider.id, model, options);
|
|
122
|
+
const normalizedModel = modelResolution.model;
|
|
78
123
|
const enforce = options.enforce === true;
|
|
79
124
|
|
|
80
125
|
if (!normalizedModel) {
|
|
@@ -114,13 +159,19 @@ function buildProviderModelArgs(providerId, model, options = {}) {
|
|
|
114
159
|
}
|
|
115
160
|
|
|
116
161
|
const args = provider.modelArgBuilder(normalizedModel);
|
|
117
|
-
|
|
162
|
+
const result = {
|
|
118
163
|
model: normalizedModel,
|
|
119
164
|
supported: true,
|
|
120
165
|
enforced: enforce,
|
|
121
166
|
args: Array.isArray(args) ? args.map((arg) => String(arg)) : [],
|
|
122
|
-
reason: 'model argument supported',
|
|
167
|
+
reason: modelResolution.aliasNormalized ? `model alias normalized from ${modelResolution.input}` : 'model argument supported',
|
|
123
168
|
};
|
|
169
|
+
if (modelResolution.aliasNormalized) {
|
|
170
|
+
result.input = modelResolution.input;
|
|
171
|
+
result.displayName = modelResolution.displayName;
|
|
172
|
+
result.modelSource = modelResolution.modelSource;
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
124
175
|
}
|
|
125
176
|
|
|
126
177
|
function buildProviderInvocation(providerId, options = {}) {
|
|
@@ -128,6 +179,8 @@ function buildProviderInvocation(providerId, options = {}) {
|
|
|
128
179
|
const extraArgs = Array.isArray(options.args) ? options.args.map((arg) => String(arg)) : [];
|
|
129
180
|
const modelSelection = buildProviderModelArgs(provider.id, options.model, {
|
|
130
181
|
enforce: options.enforceModelSelection === true,
|
|
182
|
+
blockModelAlias: options.blockModelAlias === true,
|
|
183
|
+
displayName: options.displayName,
|
|
131
184
|
});
|
|
132
185
|
const prompt = String(options.prompt ?? '');
|
|
133
186
|
const timeoutMs = Number.isFinite(options.timeoutMs) ? Number(options.timeoutMs) : provider.timeoutMs;
|
|
@@ -184,6 +237,85 @@ function serializeError(error, provider, invocation) {
|
|
|
184
237
|
};
|
|
185
238
|
}
|
|
186
239
|
|
|
240
|
+
function compactProviderText(value) {
|
|
241
|
+
return redactSecrets(String(value || ''))
|
|
242
|
+
.replace(/\bsk-[A-Za-z0-9_-]{16,}\b/g, '[REDACTED]')
|
|
243
|
+
.replace(/\bghp_[A-Za-z0-9_]{16,}\b/g, '[REDACTED]')
|
|
244
|
+
.replace(/\bgithub_pat_[A-Za-z0-9_]{16,}\b/g, '[REDACTED]')
|
|
245
|
+
.split(/\r?\n/)
|
|
246
|
+
.map((line) => line.trim())
|
|
247
|
+
.filter(Boolean)
|
|
248
|
+
.slice(0, 6)
|
|
249
|
+
.join(' ');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function extractProviderErrorCause(result = {}) {
|
|
253
|
+
const error = result.error || {};
|
|
254
|
+
const text = compactProviderText([
|
|
255
|
+
error.message,
|
|
256
|
+
result.stderr,
|
|
257
|
+
result.stdout,
|
|
258
|
+
].filter(Boolean).join('\n'));
|
|
259
|
+
const lower = text.toLowerCase();
|
|
260
|
+
|
|
261
|
+
if (error.code === 'MISSING_PROVIDER_CLI') {
|
|
262
|
+
return {
|
|
263
|
+
code: 'MISSING_PROVIDER_CLI',
|
|
264
|
+
message: error.message || `Provider CLI '${result.command || result.provider}' is not available.`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (error.code === 'PROVIDER_TIMEOUT') {
|
|
269
|
+
return {
|
|
270
|
+
code: 'PROVIDER_TIMEOUT',
|
|
271
|
+
message: error.message || `Provider '${result.provider}' timed out.`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const quotedUnsupportedModel = text.match(/the ['"`]([^'"`]+)['"`] model is not supported/i);
|
|
276
|
+
const genericInvalidModel = /invalid model|unknown model|model .*not supported|model .*not available|model .*does not exist|unsupported model/i.test(text);
|
|
277
|
+
if (quotedUnsupportedModel || genericInvalidModel) {
|
|
278
|
+
const model = quotedUnsupportedModel ? quotedUnsupportedModel[1] : result.modelSelection?.model;
|
|
279
|
+
return {
|
|
280
|
+
code: 'INVALID_PROVIDER_MODEL',
|
|
281
|
+
message: model
|
|
282
|
+
? `Provider '${result.provider}' rejected model '${model}'. ${text}`
|
|
283
|
+
: `Provider '${result.provider}' rejected the selected model. ${text}`,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (/unauthorized|forbidden|permission denied|not authenticated|authentication|api key|login required|log in/i.test(text)) {
|
|
288
|
+
return {
|
|
289
|
+
code: 'PROVIDER_AUTH_ERROR',
|
|
290
|
+
message: `Provider '${result.provider}' authentication failed. ${text}`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (text) {
|
|
295
|
+
return {
|
|
296
|
+
code: 'PROVIDER_RUN_FAILED',
|
|
297
|
+
message: text,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
code: 'PROVIDER_RUN_FAILED',
|
|
303
|
+
message: 'provider run failed',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function createProviderFailureError(result = {}) {
|
|
308
|
+
const cause = extractProviderErrorCause(result);
|
|
309
|
+
return new ProviderRunnerError(cause.code, cause.message, {
|
|
310
|
+
provider: result.provider,
|
|
311
|
+
command: result.command,
|
|
312
|
+
args: Array.isArray(result.args) ? result.args.slice() : [],
|
|
313
|
+
exitCode: result.exitCode,
|
|
314
|
+
signal: result.signal,
|
|
315
|
+
modelSelection: result.modelSelection,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
187
319
|
function runSpawn(command, args, options = {}) {
|
|
188
320
|
const spawnImpl = options.spawn || spawn;
|
|
189
321
|
|
|
@@ -352,7 +484,7 @@ async function runProvider(providerId, options = {}) {
|
|
|
352
484
|
timeoutMs: invocation.timeoutMs,
|
|
353
485
|
});
|
|
354
486
|
|
|
355
|
-
|
|
487
|
+
const providerResult = {
|
|
356
488
|
ok: execution.ok,
|
|
357
489
|
dryRun: false,
|
|
358
490
|
provider: invocation.provider,
|
|
@@ -369,6 +501,10 @@ async function runProvider(providerId, options = {}) {
|
|
|
369
501
|
error: execution.error,
|
|
370
502
|
preflight: preflightResult,
|
|
371
503
|
};
|
|
504
|
+
if (!providerResult.ok && !providerResult.error) {
|
|
505
|
+
providerResult.error = serializeError(createProviderFailureError(providerResult), invocation.provider, invocation);
|
|
506
|
+
}
|
|
507
|
+
return providerResult;
|
|
372
508
|
} finally {
|
|
373
509
|
finalizePromptTransport(transport);
|
|
374
510
|
}
|
|
@@ -380,7 +516,10 @@ module.exports = {
|
|
|
380
516
|
assertSupportedProvider,
|
|
381
517
|
buildProviderModelArgs,
|
|
382
518
|
buildProviderInvocation,
|
|
519
|
+
createProviderFailureError,
|
|
520
|
+
extractProviderErrorCause,
|
|
383
521
|
formatProviderList,
|
|
384
522
|
getProviderDefinition,
|
|
523
|
+
resolveProviderModelSelection,
|
|
385
524
|
runProvider,
|
|
386
525
|
};
|
|
@@ -2,6 +2,7 @@ const fs = require('node:fs');
|
|
|
2
2
|
const os = require('node:os');
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
|
|
5
|
+
const { buildApprovalCandidateReport } = require('./approval-candidates');
|
|
5
6
|
const { quiverInternalPaths } = require('../init-layout');
|
|
6
7
|
|
|
7
8
|
const AI_RUN_PHASES = Object.freeze([
|
|
@@ -104,8 +105,24 @@ function phaseRank(phase) {
|
|
|
104
105
|
return AI_RUN_PHASES.indexOf(phase);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
function nextCommandForPhase(phase) {
|
|
108
|
+
function nextCommandForPhase(phase, projectRoot = '') {
|
|
108
109
|
assertKnownPhase(phase);
|
|
110
|
+
if (projectRoot && phase === 'acceptance-draft') {
|
|
111
|
+
try {
|
|
112
|
+
const report = buildApprovalCandidateReport(projectRoot, 'acceptance');
|
|
113
|
+
return report.recommended?.next_command || report.current?.next_command || PHASE_NEXT_COMMAND[phase];
|
|
114
|
+
} catch {
|
|
115
|
+
return PHASE_NEXT_COMMAND[phase];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (projectRoot && phase === 'technical-plan-reviewed') {
|
|
119
|
+
try {
|
|
120
|
+
const report = buildApprovalCandidateReport(projectRoot, 'technical-plan');
|
|
121
|
+
return report.recommended?.next_command || report.current?.next_command || PHASE_NEXT_COMMAND[phase];
|
|
122
|
+
} catch {
|
|
123
|
+
return PHASE_NEXT_COMMAND[phase];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
109
126
|
return PHASE_NEXT_COMMAND[phase];
|
|
110
127
|
}
|
|
111
128
|
|
|
@@ -374,12 +391,12 @@ function formatAiRunStatus(projectRoot, run) {
|
|
|
374
391
|
if (otherOpenRuns.length > 0) {
|
|
375
392
|
lines.push('Other open runs:');
|
|
376
393
|
for (const item of otherOpenRuns) {
|
|
377
|
-
lines.push(`- ${item.run_id}: ${item.phase} (${item.status}) -> ${nextCommandForPhase(item.phase)}`);
|
|
394
|
+
lines.push(`- ${item.run_id}: ${item.phase} (${item.status}) -> ${nextCommandForPhase(item.phase, projectRoot)}`);
|
|
378
395
|
}
|
|
379
396
|
}
|
|
380
397
|
|
|
381
398
|
lines.push(
|
|
382
|
-
`Next safe command: ${nextCommandForPhase(run.phase)}`,
|
|
399
|
+
`Next safe command: ${nextCommandForPhase(run.phase, projectRoot)}`,
|
|
383
400
|
'',
|
|
384
401
|
);
|
|
385
402
|
|
|
@@ -400,7 +417,7 @@ function formatAiRunResume(projectRoot, run) {
|
|
|
400
417
|
'AI run resume',
|
|
401
418
|
`Run: ${run.run_id}`,
|
|
402
419
|
`Current phase: ${run.phase}`,
|
|
403
|
-
`Next safe command: ${nextCommandForPhase(run.phase)}`,
|
|
420
|
+
`Next safe command: ${nextCommandForPhase(run.phase, projectRoot)}`,
|
|
404
421
|
`State: ${toRelativePosix(projectRoot, runStatePath(projectRoot, run.run_id))}`,
|
|
405
422
|
'',
|
|
406
423
|
].join('\n');
|