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
|
@@ -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,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const { buildPlannerApprovalCandidates } = require('../approvals');
|
|
2
|
+
const { buildTechnicalPlanApprovalCandidates } = require('./plan-review');
|
|
3
|
+
|
|
4
|
+
function buildApprovalCandidateReport(projectRoot, phase) {
|
|
5
|
+
return phase === 'technical-plan'
|
|
6
|
+
? buildTechnicalPlanApprovalCandidates(projectRoot)
|
|
7
|
+
: buildPlannerApprovalCandidates(projectRoot, phase);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatReviewSummary(review) {
|
|
11
|
+
if (!review || !review.recommendation) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const parts = [`review=${review.recommendation}`];
|
|
16
|
+
if (review.blocking) {
|
|
17
|
+
parts.push('blocking');
|
|
18
|
+
}
|
|
19
|
+
if (review.required_fixes_count) {
|
|
20
|
+
parts.push(`required fixes=${review.required_fixes_count}`);
|
|
21
|
+
}
|
|
22
|
+
if (review.optional_hardening_count) {
|
|
23
|
+
parts.push(`optional=${review.optional_hardening_count}`);
|
|
24
|
+
}
|
|
25
|
+
if (review.risks_count) {
|
|
26
|
+
parts.push(`risks=${review.risks_count}`);
|
|
27
|
+
}
|
|
28
|
+
return parts.join(', ');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatCandidateSummary(candidate) {
|
|
32
|
+
if (!candidate) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parts = [
|
|
37
|
+
candidate.label,
|
|
38
|
+
candidate.current ? 'current' : 'history',
|
|
39
|
+
candidate.approvable ? 'approvable' : 'blocked',
|
|
40
|
+
candidate.reason,
|
|
41
|
+
formatReviewSummary(candidate.review),
|
|
42
|
+
].filter(Boolean);
|
|
43
|
+
return parts.join(', ');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function approvalCandidateCommand(report, fallback = 'npx create-quiver ai approvals') {
|
|
47
|
+
if (report?.recommended?.next_command) {
|
|
48
|
+
return report.recommended.next_command;
|
|
49
|
+
}
|
|
50
|
+
if (report?.current?.next_command) {
|
|
51
|
+
return report.current.next_command;
|
|
52
|
+
}
|
|
53
|
+
return report?.next_command || fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatApprovalDecisionLines(report) {
|
|
57
|
+
const lines = [];
|
|
58
|
+
if (!report || !Array.isArray(report.candidates)) {
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
lines.push(`Candidates: ${report.candidates.length}`);
|
|
63
|
+
if (report.latest_version) {
|
|
64
|
+
lines.push(`Latest draft: v${report.latest_version}`);
|
|
65
|
+
}
|
|
66
|
+
if (report.current) {
|
|
67
|
+
lines.push(`Current candidate: ${formatCandidateSummary(report.current)}`);
|
|
68
|
+
}
|
|
69
|
+
if (report.recommended) {
|
|
70
|
+
lines.push(`Recommended approval: ${report.recommended.next_command}`);
|
|
71
|
+
} else if (report.next_command) {
|
|
72
|
+
lines.push(`Recommended next command: ${report.next_command}`);
|
|
73
|
+
}
|
|
74
|
+
if (report.review?.status) {
|
|
75
|
+
lines.push(`Review status: ${report.review.status}`);
|
|
76
|
+
}
|
|
77
|
+
return lines;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
approvalCandidateCommand,
|
|
82
|
+
buildApprovalCandidateReport,
|
|
83
|
+
formatApprovalDecisionLines,
|
|
84
|
+
formatCandidateSummary,
|
|
85
|
+
formatReviewSummary,
|
|
86
|
+
};
|
|
@@ -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
|
});
|