create-quiver 0.15.0 → 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 +6 -0
- package/README.md +31 -0
- package/README_FOR_AI.md +8 -3
- 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 +16 -1
- package/docs/COMMANDS.md.template +14 -4
- 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 +29 -1
- 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/src/create-quiver/commands/ai.js +449 -34
- package/src/create-quiver/commands/flow.js +2 -2
- package/src/create-quiver/index.js +26 -10
- package/src/create-quiver/lib/agent-profiles.js +332 -4
- 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/providers.js +143 -4
- package/src/create-quiver/lib/cli/selectors.js +53 -0
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
runLifecycleResume: runAiLifecycleResume,
|
|
23
23
|
runLifecycleRun: runAiLifecycleRun,
|
|
24
24
|
runLifecycleStatus: runAiLifecycleStatus,
|
|
25
|
+
runModelsList: runAiModelsList,
|
|
25
26
|
runOnboard,
|
|
26
27
|
runPlan: runAiPlan,
|
|
27
28
|
runPrepareContext: runAiPrepareContext,
|
|
@@ -172,7 +173,8 @@ const COMMAND_HELP_GROUPS = [
|
|
|
172
173
|
['ai resume', 'Resume guidance from the last valid lifecycle phase without chat memory.'],
|
|
173
174
|
['ai onboard', 'Run or print the planner onboarding prompt with a token-aware context pack.'],
|
|
174
175
|
['ai prepare-context', 'Preview or write docs-only AI context updates with assumptions and risks.'],
|
|
175
|
-
['ai agent set|list|show', 'Manage planner, executor, reviewer, and doctor provider profiles without secrets
|
|
176
|
+
['ai agent set|list|show|doctor|repair', 'Manage, diagnose, and dry-run repair planner, executor, reviewer, and doctor provider profiles without secrets.'],
|
|
177
|
+
['ai models list', 'List provider/model ids known by Quiver without claiming account availability.'],
|
|
176
178
|
['ai plan', 'Generate versioned planner drafts for acceptance criteria, technical plan, or spec phase.'],
|
|
177
179
|
['ai revise', 'Create a new planner draft from human feedback without approving it.'],
|
|
178
180
|
['ai repair-plan', 'Repair an approved technical plan into a new structured draft without mutating the approved artifact.'],
|
|
@@ -261,7 +263,8 @@ function printUsage() {
|
|
|
261
263
|
npx create-quiver ai specs list [--json]
|
|
262
264
|
npx create-quiver ai slices list [--json]
|
|
263
265
|
npx create-quiver ai trace report [options]
|
|
264
|
-
npx create-quiver ai agent <set|list|show> [role] [options]
|
|
266
|
+
npx create-quiver ai agent <set|list|show|doctor|repair> [role] [options]
|
|
267
|
+
npx create-quiver ai models list [--provider codex|claude|gemini] [--json]
|
|
265
268
|
npx create-quiver ai prepare-context [options]
|
|
266
269
|
npx create-quiver ai revise [options]
|
|
267
270
|
npx create-quiver ai repair-plan [options]
|
|
@@ -321,7 +324,7 @@ Options:
|
|
|
321
324
|
--allow-dirty For ai execute-slice, allow pre-existing dirty files and ignore them for scope diff
|
|
322
325
|
--mode <name> Execution mode for ai execute-plan (auto, manual, delegated)
|
|
323
326
|
--provider <name> Provider CLI to preflight for prepare or AI commands
|
|
324
|
-
--model <
|
|
327
|
+
--model <model-id> Technical model id for AI agent profiles or provider-backed AI commands
|
|
325
328
|
--version <n> Draft version to approve for AI planner phases
|
|
326
329
|
--run <id> AI lifecycle run id
|
|
327
330
|
--ssh-host-alias <name> SSH host alias to validate for prepare or AI commands
|
|
@@ -359,7 +362,10 @@ Examples:
|
|
|
359
362
|
cd ./my-project && npx create-quiver ai trace report
|
|
360
363
|
cd ./my-project && npx create-quiver ai agent set planner --provider codex --model gpt-5.5 --dry-run
|
|
361
364
|
cd ./my-project && npx create-quiver ai agent set planner --provider codex --model gpt-5.5
|
|
365
|
+
cd ./my-project && npx create-quiver ai agent doctor
|
|
366
|
+
cd ./my-project && npx create-quiver ai agent repair --dry-run
|
|
362
367
|
cd ./my-project && npx create-quiver ai agent list
|
|
368
|
+
cd ./my-project && npx create-quiver ai models list --provider codex
|
|
363
369
|
cd ./my-project && npx create-quiver ai plan --phase acceptance --input requirements.md --dry-run
|
|
364
370
|
cd ./my-project && npx create-quiver ai revise --phase acceptance --input feedback.md --dry-run
|
|
365
371
|
cd ./my-project && npx create-quiver ai approve --phase acceptance --version 1
|
|
@@ -1050,10 +1056,10 @@ function parseArgs(argv) {
|
|
|
1050
1056
|
if (result.aiCommand === 'run' && !result.aiRunCommand && positional.length > 0) {
|
|
1051
1057
|
result.aiRunCommand = positional.shift();
|
|
1052
1058
|
}
|
|
1053
|
-
if ((result.aiCommand === 'specs' || result.aiCommand === 'slices' || result.aiCommand === 'trace' || result.aiCommand === 'active-slice') && !result.aiSecondaryCommand && positional.length > 0) {
|
|
1059
|
+
if ((result.aiCommand === 'specs' || result.aiCommand === 'slices' || result.aiCommand === 'models' || result.aiCommand === 'trace' || result.aiCommand === 'active-slice') && !result.aiSecondaryCommand && positional.length > 0) {
|
|
1054
1060
|
result.aiSecondaryCommand = positional.shift();
|
|
1055
1061
|
}
|
|
1056
|
-
if ((result.aiCommand === 'specs' || result.aiCommand === 'slices') && result.aiSecondaryCommand && result.aiSecondaryCommand !== 'list') {
|
|
1062
|
+
if ((result.aiCommand === 'specs' || result.aiCommand === 'slices' || result.aiCommand === 'models') && result.aiSecondaryCommand && result.aiSecondaryCommand !== 'list') {
|
|
1057
1063
|
throw new Error(formatError(`unsupported ai ${result.aiCommand} subcommand: ${result.aiSecondaryCommand}. Supported tasks: list`));
|
|
1058
1064
|
}
|
|
1059
1065
|
if (result.aiCommand === 'trace' && result.aiSecondaryCommand && result.aiSecondaryCommand !== 'report') {
|
|
@@ -2704,8 +2710,8 @@ async function resolveInteractiveInitOptions(args, targetDir, projectName, optio
|
|
|
2704
2710
|
|
|
2705
2711
|
if (agentGuidance.value === 'show') {
|
|
2706
2712
|
ux.nextSteps([
|
|
2707
|
-
'npx create-quiver ai agent set planner --provider codex --model
|
|
2708
|
-
'npx create-quiver ai agent set executor --provider claude --model
|
|
2713
|
+
'npx create-quiver ai agent set planner --provider codex --model gpt-5.5 --dry-run',
|
|
2714
|
+
'npx create-quiver ai agent set executor --provider claude --model claude-sonnet-4-6 --dry-run',
|
|
2709
2715
|
], { title: 'Despues de inicializar' });
|
|
2710
2716
|
}
|
|
2711
2717
|
|
|
@@ -2836,7 +2842,7 @@ async function run(argv) {
|
|
|
2836
2842
|
|
|
2837
2843
|
if (args.mode === 'ai') {
|
|
2838
2844
|
if (!args.aiCommand) {
|
|
2839
|
-
throw new Error(formatError('missing ai subcommand. Use: npx create-quiver ai onboard | prepare-context | run | active-slice | status | resume | inspect | export | specs | slices | trace | plan | revise | repair-plan | review-plan | approve | approvals | agent | prompt-slice | execute-slice | execute-plan | doctor | pr'));
|
|
2845
|
+
throw new Error(formatError('missing ai subcommand. Use: npx create-quiver ai onboard | prepare-context | run | active-slice | status | resume | inspect | export | specs | slices | models | trace | plan | revise | repair-plan | review-plan | approve | approvals | agent | prompt-slice | execute-slice | execute-plan | doctor | pr'));
|
|
2840
2846
|
}
|
|
2841
2847
|
|
|
2842
2848
|
if (args.aiCommand === 'run') {
|
|
@@ -2909,13 +2915,23 @@ async function run(argv) {
|
|
|
2909
2915
|
return;
|
|
2910
2916
|
}
|
|
2911
2917
|
|
|
2918
|
+
if (args.aiCommand === 'models') {
|
|
2919
|
+
runAiModelsList({
|
|
2920
|
+
json: args.json,
|
|
2921
|
+
provider: args.aiProviderExplicit ? args.aiProvider : undefined,
|
|
2922
|
+
});
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2912
2926
|
if (args.aiCommand === 'agent') {
|
|
2913
|
-
runAiAgent(process.cwd(), {
|
|
2927
|
+
await runAiAgent(process.cwd(), {
|
|
2914
2928
|
command: args.aiAgentCommand,
|
|
2915
2929
|
context: args.aiContext || undefined,
|
|
2916
2930
|
defaultProfile: args.aiDefaultProfile,
|
|
2917
2931
|
displayName: args.aiDisplayName || undefined,
|
|
2918
2932
|
id: args.aiProfileId || undefined,
|
|
2933
|
+
interactive: args.interactive,
|
|
2934
|
+
json: args.json,
|
|
2919
2935
|
label: args.aiLabel || undefined,
|
|
2920
2936
|
model: args.aiModel || undefined,
|
|
2921
2937
|
provider: args.aiProviderExplicit ? args.aiProvider : undefined,
|
|
@@ -3120,7 +3136,7 @@ async function run(argv) {
|
|
|
3120
3136
|
return;
|
|
3121
3137
|
}
|
|
3122
3138
|
|
|
3123
|
-
throw new Error(formatError(`unsupported ai subcommand: ${args.aiCommand}. Supported tasks: onboard, prepare-context, run, active-slice, status, resume, inspect, export, specs, slices, trace, plan, revise, repair-plan, review-plan, approve, approvals, agent, prompt-slice, execute-slice, execute-plan, doctor, pr`));
|
|
3139
|
+
throw new Error(formatError(`unsupported ai subcommand: ${args.aiCommand}. Supported tasks: onboard, prepare-context, run, active-slice, status, resume, inspect, export, specs, slices, models, trace, plan, revise, repair-plan, review-plan, approve, approvals, agent, prompt-slice, execute-slice, execute-plan, doctor, pr`));
|
|
3124
3140
|
}
|
|
3125
3141
|
|
|
3126
3142
|
if (args.mode === 'graph') {
|
|
@@ -2,6 +2,8 @@ const fs = require('node:fs');
|
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
|
|
4
4
|
const { assertSupportedProvider, formatProviderList } = require('./ai/providers');
|
|
5
|
+
const { normalizeModelSelection } = require('./ai/model-catalog');
|
|
6
|
+
const { preflightProvider } = require('./ai/preflight');
|
|
5
7
|
const { quiverInternalPaths } = require('./init-layout');
|
|
6
8
|
|
|
7
9
|
const AGENT_PROFILE_ROLES = Object.freeze(['planner', 'executor', 'reviewer', 'doctor']);
|
|
@@ -87,6 +89,11 @@ function normalizeStoredProfile(profile, role, fallbackId = 'default') {
|
|
|
87
89
|
label: normalizeOptionalText(profile?.label, 'label'),
|
|
88
90
|
displayName: normalizeOptionalText(profile?.displayName || profile?.display_name, 'displayName'),
|
|
89
91
|
context: normalizeOptionalText(profile?.context, 'context'),
|
|
92
|
+
modelSource: normalizeOptionalText(profile?.modelSource || profile?.model_source, 'modelSource'),
|
|
93
|
+
modelAlias: normalizeOptionalText(profile?.modelAlias || profile?.model_alias, 'modelAlias'),
|
|
94
|
+
validation_status: normalizeOptionalText(profile?.validation_status || profile?.validationStatus, 'validation_status'),
|
|
95
|
+
validated_at: normalizeOptionalText(profile?.validated_at || profile?.validatedAt, 'validated_at'),
|
|
96
|
+
validation_error: normalizeOptionalText(profile?.validation_error || profile?.validationError, 'validation_error'),
|
|
90
97
|
default: profile?.default === true,
|
|
91
98
|
updated_at: profile?.updated_at || '',
|
|
92
99
|
};
|
|
@@ -181,25 +188,44 @@ function setAgentProfile(projectRoot, role, options = {}) {
|
|
|
181
188
|
function buildAgentProfileState(projectRoot, role, options = {}) {
|
|
182
189
|
const normalizedRole = normalizeAgentProfileRole(role);
|
|
183
190
|
const provider = assertSupportedProvider(options.provider);
|
|
184
|
-
const
|
|
191
|
+
const rawModel = normalizeOptionalText(options.model, 'model');
|
|
185
192
|
const label = normalizeOptionalText(options.label, 'label');
|
|
186
|
-
const
|
|
193
|
+
const rawDisplayName = normalizeOptionalText(options.displayName || options.display_name, 'displayName');
|
|
187
194
|
const context = normalizeOptionalText(options.context, 'context');
|
|
188
195
|
const state = readAgentProfiles(projectRoot);
|
|
189
196
|
const currentProfiles = getAgentProfilesForRole(projectRoot, normalizedRole);
|
|
190
197
|
const currentDefault = currentProfiles.find((profile) => profile.default) || currentProfiles[0] || {};
|
|
191
|
-
const
|
|
198
|
+
const modelSelection = rawModel
|
|
199
|
+
? normalizeModelSelection(provider, rawModel, { displayName: rawDisplayName })
|
|
200
|
+
: null;
|
|
201
|
+
const id = normalizeProfileId(options.id || currentDefault.id || label || modelSelection?.model || rawModel || provider);
|
|
192
202
|
const current = currentProfiles.find((profile) => profile.id === id) || {};
|
|
203
|
+
const model = modelSelection?.model || current.model || '';
|
|
204
|
+
const displayName = rawDisplayName
|
|
205
|
+
|| modelSelection?.displayName
|
|
206
|
+
|| current.displayName
|
|
207
|
+
|| model
|
|
208
|
+
|| label
|
|
209
|
+
|| provider;
|
|
193
210
|
const now = options.now instanceof Date ? options.now.toISOString() : new Date().toISOString();
|
|
194
211
|
const shouldBeDefault = options.default === true || currentProfiles.length === 0 || (options.default !== false && current.default === true);
|
|
212
|
+
const modelSource = modelSelection?.modelSource || current.modelSource || (model ? 'custom' : '');
|
|
213
|
+
const modelAlias = modelSelection && modelSelection.input && modelSelection.input !== model
|
|
214
|
+
? modelSelection.input
|
|
215
|
+
: (current.modelAlias || '');
|
|
195
216
|
const profile = {
|
|
196
217
|
id,
|
|
197
218
|
role: normalizedRole,
|
|
198
219
|
provider,
|
|
199
220
|
model: model || current.model || '',
|
|
200
221
|
label: label || current.label || '',
|
|
201
|
-
displayName
|
|
222
|
+
displayName,
|
|
202
223
|
context: context || current.context || '',
|
|
224
|
+
modelSource,
|
|
225
|
+
modelAlias,
|
|
226
|
+
validation_status: current.validation_status || (model ? 'not-tested' : ''),
|
|
227
|
+
validated_at: current.validated_at || '',
|
|
228
|
+
validation_error: current.validation_error || '',
|
|
203
229
|
default: shouldBeDefault,
|
|
204
230
|
updated_at: now,
|
|
205
231
|
};
|
|
@@ -243,10 +269,312 @@ function resolveProfileProvider(projectRoot, role, fallbackProvider) {
|
|
|
243
269
|
return assertSupportedProvider(fallbackProvider);
|
|
244
270
|
}
|
|
245
271
|
|
|
272
|
+
function cloneJson(value) {
|
|
273
|
+
return JSON.parse(JSON.stringify(value));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function rawText(value) {
|
|
277
|
+
return String(value || '').trim();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function rawDisplayName(profile, role) {
|
|
281
|
+
return rawText(profile.displayName || profile.display_name || profile.model || profile.provider || role);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function collectRawAgentProfileEntries(state) {
|
|
285
|
+
const entries = [];
|
|
286
|
+
for (const role of AGENT_PROFILE_ROLES) {
|
|
287
|
+
const setKey = profileSetKey(role);
|
|
288
|
+
const setProfiles = Array.isArray(state.profile_sets?.[setKey]) ? state.profile_sets[setKey] : [];
|
|
289
|
+
if (setProfiles.length > 0) {
|
|
290
|
+
setProfiles.forEach((profile, index) => {
|
|
291
|
+
entries.push({
|
|
292
|
+
role,
|
|
293
|
+
profile,
|
|
294
|
+
id: normalizeProfileId(profile?.id || profile?.label, index === 0 ? 'default' : `${role}-${index + 1}`),
|
|
295
|
+
source: 'profile_sets',
|
|
296
|
+
setKey,
|
|
297
|
+
index,
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (state.profiles?.[role]) {
|
|
304
|
+
entries.push({
|
|
305
|
+
role,
|
|
306
|
+
profile: state.profiles[role],
|
|
307
|
+
id: normalizeProfileId(state.profiles[role]?.id || state.profiles[role]?.label, 'default'),
|
|
308
|
+
source: 'profiles',
|
|
309
|
+
index: null,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return entries;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function createAgentProfileFinding(severity, code, message, entry, extra = {}) {
|
|
317
|
+
return {
|
|
318
|
+
severity,
|
|
319
|
+
code,
|
|
320
|
+
message,
|
|
321
|
+
role: entry.role,
|
|
322
|
+
profileId: entry.id,
|
|
323
|
+
fix: extra.fix || '',
|
|
324
|
+
before: extra.before,
|
|
325
|
+
after: extra.after,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function classifyAgentProfileEntry(projectRoot, entry, options = {}) {
|
|
330
|
+
const findings = [];
|
|
331
|
+
const profile = entry.profile || {};
|
|
332
|
+
const providerInput = rawText(profile.provider).toLowerCase();
|
|
333
|
+
const modelInput = rawText(profile.model);
|
|
334
|
+
const displayName = rawDisplayName(profile, entry.role);
|
|
335
|
+
let provider = providerInput;
|
|
336
|
+
let status = 'ok';
|
|
337
|
+
|
|
338
|
+
if (!providerInput) {
|
|
339
|
+
findings.push(createAgentProfileFinding(
|
|
340
|
+
'error',
|
|
341
|
+
'missing-provider',
|
|
342
|
+
`Profile ${entry.role}/${entry.id} is missing provider.`,
|
|
343
|
+
entry,
|
|
344
|
+
{ fix: `npx create-quiver ai agent set ${entry.role} --provider <provider> --model <model>` },
|
|
345
|
+
));
|
|
346
|
+
} else {
|
|
347
|
+
try {
|
|
348
|
+
provider = assertSupportedProvider(providerInput);
|
|
349
|
+
const runPreflight = options.checkProviderCli !== false;
|
|
350
|
+
if (runPreflight) {
|
|
351
|
+
const preflight = options.preflightProvider || preflightProvider;
|
|
352
|
+
try {
|
|
353
|
+
preflight(provider, {
|
|
354
|
+
cwd: projectRoot,
|
|
355
|
+
probe: options.providerProbe,
|
|
356
|
+
probeArgs: options.providerProbeArgs,
|
|
357
|
+
});
|
|
358
|
+
} catch (error) {
|
|
359
|
+
findings.push(createAgentProfileFinding(
|
|
360
|
+
'warning',
|
|
361
|
+
error && error.code === 'MISSING_PROVIDER_CLI' ? 'provider-cli-missing' : 'provider-cli-unverified',
|
|
362
|
+
`Provider CLI for '${provider}' could not be verified.`,
|
|
363
|
+
entry,
|
|
364
|
+
{ fix: `Install and authenticate the ${provider} CLI, then rerun npx create-quiver ai agent doctor.` },
|
|
365
|
+
));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} catch (error) {
|
|
369
|
+
findings.push(createAgentProfileFinding(
|
|
370
|
+
'error',
|
|
371
|
+
'unsupported-provider',
|
|
372
|
+
`Profile ${entry.role}/${entry.id} uses unsupported provider '${profile.provider}'.`,
|
|
373
|
+
entry,
|
|
374
|
+
{ fix: `Use one of: ${formatProviderList()}.` },
|
|
375
|
+
));
|
|
376
|
+
provider = providerInput;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!modelInput) {
|
|
381
|
+
findings.push(createAgentProfileFinding(
|
|
382
|
+
'warning',
|
|
383
|
+
'missing-model',
|
|
384
|
+
`Profile ${entry.role}/${entry.id} has no model selected.`,
|
|
385
|
+
entry,
|
|
386
|
+
{ fix: `npx create-quiver ai agent set ${entry.role} --provider ${provider || '<provider>'} --model <model>` },
|
|
387
|
+
));
|
|
388
|
+
} else if (providerInput && findings.every((finding) => finding.code !== 'unsupported-provider')) {
|
|
389
|
+
const modelSelection = normalizeModelSelection(provider, modelInput, {
|
|
390
|
+
displayName: rawText(profile.displayName || profile.display_name),
|
|
391
|
+
});
|
|
392
|
+
if (modelSelection.modelSource === 'catalog' && modelSelection.model !== modelInput) {
|
|
393
|
+
findings.push(createAgentProfileFinding(
|
|
394
|
+
'warning',
|
|
395
|
+
'display-model-alias',
|
|
396
|
+
`Profile ${entry.role}/${entry.id} stores display alias '${modelInput}' as the technical model id.`,
|
|
397
|
+
entry,
|
|
398
|
+
{
|
|
399
|
+
fix: 'Run npx create-quiver ai agent repair --dry-run to preview the normalized profile.',
|
|
400
|
+
before: { model: modelInput, displayName: rawText(profile.displayName || profile.display_name) },
|
|
401
|
+
after: { model: modelSelection.model, displayName: modelSelection.displayName },
|
|
402
|
+
},
|
|
403
|
+
));
|
|
404
|
+
}
|
|
405
|
+
if (modelSelection.modelSource === 'custom') {
|
|
406
|
+
const validationStatus = rawText(profile.validation_status || profile.validationStatus);
|
|
407
|
+
if (!['validated', 'ok', 'passed'].includes(validationStatus.toLowerCase())) {
|
|
408
|
+
findings.push(createAgentProfileFinding(
|
|
409
|
+
'warning',
|
|
410
|
+
'custom-model-unvalidated',
|
|
411
|
+
`Profile ${entry.role}/${entry.id} uses custom model '${modelInput}' without live validation evidence.`,
|
|
412
|
+
entry,
|
|
413
|
+
{ fix: `Run a dry-run or provider smoke with ${provider} before using this profile in automation.` },
|
|
414
|
+
));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (profile.default === true) {
|
|
420
|
+
findings.push(createAgentProfileFinding(
|
|
421
|
+
'info',
|
|
422
|
+
'default-profile',
|
|
423
|
+
`Profile ${entry.role}/${entry.id} is the default ${entry.role} profile.`,
|
|
424
|
+
entry,
|
|
425
|
+
));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (findings.some((finding) => finding.severity === 'error')) status = 'error';
|
|
429
|
+
else if (findings.some((finding) => finding.severity === 'warning')) status = 'warning';
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
role: entry.role,
|
|
433
|
+
id: entry.id,
|
|
434
|
+
source: entry.source,
|
|
435
|
+
provider,
|
|
436
|
+
model: modelInput,
|
|
437
|
+
displayName,
|
|
438
|
+
default: profile.default === true,
|
|
439
|
+
status,
|
|
440
|
+
findings,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function addDuplicateDisplayNameFindings(checks) {
|
|
445
|
+
const groups = new Map();
|
|
446
|
+
for (const check of checks) {
|
|
447
|
+
const key = `${check.role}:${rawText(check.displayName).toLowerCase()}`;
|
|
448
|
+
if (!check.displayName) continue;
|
|
449
|
+
const group = groups.get(key) || [];
|
|
450
|
+
group.push(check);
|
|
451
|
+
groups.set(key, group);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
for (const group of groups.values()) {
|
|
455
|
+
if (group.length < 2) continue;
|
|
456
|
+
const ids = group.map((item) => `${item.role}/${item.id}`).join(', ');
|
|
457
|
+
for (const check of group) {
|
|
458
|
+
check.findings.push({
|
|
459
|
+
severity: 'warning',
|
|
460
|
+
code: 'duplicate-display-name',
|
|
461
|
+
message: `Display name '${check.displayName}' is shared by ${ids}.`,
|
|
462
|
+
role: check.role,
|
|
463
|
+
profileId: check.id,
|
|
464
|
+
fix: 'Use --display-name or --id to make selector entries easier to distinguish.',
|
|
465
|
+
});
|
|
466
|
+
if (check.status !== 'error') check.status = 'warning';
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function buildAgentProfileDoctorReport(projectRoot, options = {}) {
|
|
472
|
+
const state = readAgentProfiles(projectRoot);
|
|
473
|
+
const entries = collectRawAgentProfileEntries(state);
|
|
474
|
+
const checks = entries.map((entry) => classifyAgentProfileEntry(projectRoot, entry, options));
|
|
475
|
+
addDuplicateDisplayNameFindings(checks);
|
|
476
|
+
const findings = checks.flatMap((check) => check.findings);
|
|
477
|
+
const errors = findings.filter((finding) => finding.severity === 'error').length;
|
|
478
|
+
const warnings = findings.filter((finding) => finding.severity === 'warning').length;
|
|
479
|
+
const info = findings.filter((finding) => finding.severity === 'info').length;
|
|
480
|
+
const suggestedFixes = Array.from(new Set(findings
|
|
481
|
+
.map((finding) => finding.fix)
|
|
482
|
+
.filter(Boolean)));
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
ok: errors === 0,
|
|
486
|
+
summary: {
|
|
487
|
+
profiles: checks.length,
|
|
488
|
+
defaults: checks.filter((check) => check.default).length,
|
|
489
|
+
errors,
|
|
490
|
+
warnings,
|
|
491
|
+
info,
|
|
492
|
+
},
|
|
493
|
+
checks,
|
|
494
|
+
findings,
|
|
495
|
+
suggestedFixes,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function repairProfileObject(profile, provider, modelSelection) {
|
|
500
|
+
const before = {
|
|
501
|
+
model: rawText(profile.model),
|
|
502
|
+
displayName: rawText(profile.displayName || profile.display_name),
|
|
503
|
+
modelSource: rawText(profile.modelSource || profile.model_source),
|
|
504
|
+
modelAlias: rawText(profile.modelAlias || profile.model_alias),
|
|
505
|
+
validation_status: rawText(profile.validation_status || profile.validationStatus),
|
|
506
|
+
};
|
|
507
|
+
const after = {
|
|
508
|
+
...before,
|
|
509
|
+
model: modelSelection.model,
|
|
510
|
+
displayName: before.displayName || modelSelection.displayName,
|
|
511
|
+
modelSource: 'catalog',
|
|
512
|
+
modelAlias: before.model,
|
|
513
|
+
validation_status: before.validation_status || 'not-tested',
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
profile.model = after.model;
|
|
517
|
+
profile.displayName = after.displayName;
|
|
518
|
+
profile.modelSource = after.modelSource;
|
|
519
|
+
profile.modelAlias = after.modelAlias;
|
|
520
|
+
profile.validation_status = after.validation_status;
|
|
521
|
+
return { before, after, provider };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function buildAgentProfileRepairPlan(projectRoot, options = {}) {
|
|
525
|
+
const state = readAgentProfiles(projectRoot);
|
|
526
|
+
const nextState = cloneJson(state);
|
|
527
|
+
const entries = collectRawAgentProfileEntries(state);
|
|
528
|
+
const changes = [];
|
|
529
|
+
|
|
530
|
+
for (const entry of entries) {
|
|
531
|
+
const profile = entry.profile || {};
|
|
532
|
+
const providerInput = rawText(profile.provider).toLowerCase();
|
|
533
|
+
const modelInput = rawText(profile.model);
|
|
534
|
+
if (!providerInput || !modelInput) continue;
|
|
535
|
+
|
|
536
|
+
let provider;
|
|
537
|
+
try {
|
|
538
|
+
provider = assertSupportedProvider(providerInput);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const modelSelection = normalizeModelSelection(provider, modelInput, {
|
|
544
|
+
displayName: rawText(profile.displayName || profile.display_name),
|
|
545
|
+
});
|
|
546
|
+
if (modelSelection.modelSource !== 'catalog' || modelSelection.model === modelInput) {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const target = entry.source === 'profile_sets'
|
|
551
|
+
? nextState.profile_sets[entry.setKey][entry.index]
|
|
552
|
+
: nextState.profiles[entry.role];
|
|
553
|
+
const repair = repairProfileObject(target, provider, modelSelection);
|
|
554
|
+
changes.push({
|
|
555
|
+
role: entry.role,
|
|
556
|
+
profileId: entry.id,
|
|
557
|
+
provider,
|
|
558
|
+
reason: 'display-model-alias',
|
|
559
|
+
before: repair.before,
|
|
560
|
+
after: repair.after,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
dryRun: true,
|
|
566
|
+
wouldWrite: false,
|
|
567
|
+
changes,
|
|
568
|
+
nextState: options.includeState === true ? nextState : undefined,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
246
572
|
module.exports = {
|
|
247
573
|
AGENT_PROFILE_ROLES,
|
|
248
574
|
PROFILE_STATE_VERSION,
|
|
249
575
|
agentProfilesPath,
|
|
576
|
+
buildAgentProfileDoctorReport,
|
|
577
|
+
buildAgentProfileRepairPlan,
|
|
250
578
|
getAgentProfileById,
|
|
251
579
|
getAgentProfilesForRole,
|
|
252
580
|
resolveAgentProfileDisplayName,
|
|
@@ -12,6 +12,7 @@ const { branchDelete, runGit, statusPorcelain, worktreeAdd, worktreePrune, workt
|
|
|
12
12
|
const { withLock } = require('../locks');
|
|
13
13
|
const { safeBranchName, worktreesRootForRepo } = require('../slice');
|
|
14
14
|
const { buildGraph, computeLevels, detectFileConflicts, isFoundationSliceId, readAllSlices, topoSort, SliceGraphError } = require('../slice-graph');
|
|
15
|
+
const { resolveProviderModelSelection } = require('./providers');
|
|
15
16
|
const { runExecuteSlice } = require('./executor');
|
|
16
17
|
|
|
17
18
|
const EXCLUDED_STATUSES = new Set(['completed', 'skipped', 'cancelled']);
|
|
@@ -641,13 +642,17 @@ async function runExecutePlan(repoRoot, options = {}) {
|
|
|
641
642
|
const executionMode = normalizeExecutionMode(options.mode || options.executionMode);
|
|
642
643
|
const runtimeProfile = resolveExecutorRuntimeProfile(repoRoot, options);
|
|
643
644
|
const provider = runtimeProfile.provider;
|
|
645
|
+
const explicitModel = String(options.model || '').trim();
|
|
646
|
+
const resolvedModel = runtimeProfile.model
|
|
647
|
+
? resolveProviderModelSelection(provider, runtimeProfile.model).model
|
|
648
|
+
: '';
|
|
644
649
|
const resolvedOptions = {
|
|
645
650
|
...options,
|
|
646
651
|
mode: executionMode,
|
|
647
|
-
model:
|
|
652
|
+
model: explicitModel ? resolvedModel : undefined,
|
|
648
653
|
provider,
|
|
649
654
|
profile: runtimeProfile,
|
|
650
|
-
resolvedModel
|
|
655
|
+
resolvedModel,
|
|
651
656
|
resolvedProvider: provider,
|
|
652
657
|
};
|
|
653
658
|
|
|
@@ -86,6 +86,13 @@ function resolveExecutorRuntimeProfile(repoRoot, role, options = {}) {
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function runtimeModelExecutionOptions(runtimeProfile, options = {}) {
|
|
90
|
+
return {
|
|
91
|
+
model: runtimeProfile.model,
|
|
92
|
+
blockModelAlias: Boolean(runtimeProfile.profile && !String(options.model || '').trim()),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
89
96
|
function createCommandUx(options = {}) {
|
|
90
97
|
if (options.ux) {
|
|
91
98
|
return options.ux;
|
|
@@ -955,7 +962,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
955
962
|
prompt,
|
|
956
963
|
cwd: canonicalRepoRoot,
|
|
957
964
|
timeoutMs,
|
|
958
|
-
|
|
965
|
+
...runtimeModelExecutionOptions(runtimeProfile, options),
|
|
959
966
|
enforceModelSelection: false,
|
|
960
967
|
});
|
|
961
968
|
} catch (error) {
|
|
@@ -1028,7 +1035,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
1028
1035
|
tempRoot: options.tempRoot,
|
|
1029
1036
|
tempFileName: options.tempFileName,
|
|
1030
1037
|
tempFilePrefix: options.tempFilePrefix,
|
|
1031
|
-
|
|
1038
|
+
...runtimeModelExecutionOptions(runtimeProfile, options),
|
|
1032
1039
|
enforceModelSelection: Boolean(runtimeProfile.model),
|
|
1033
1040
|
}),
|
|
1034
1041
|
});
|