create-quiver 0.14.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +35 -0
- package/README_FOR_AI.md +15 -4
- package/ROADMAP.md +13 -2
- package/docs/AI_CONTEXT.md.template +4 -0
- package/docs/AI_ONBOARDING_PROMPT.md.template +8 -0
- package/docs/CLI_UX_GUIDE.md +79 -4
- package/docs/COMMANDS.md.template +21 -5
- package/docs/TROUBLESHOOTING.md +70 -0
- package/docs/TROUBLESHOOTING.md.template +20 -0
- package/docs/getting-started/installation.md +86 -0
- package/docs/reference/commands.md +33 -1
- package/package.json +1 -1
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/EVIDENCE_REPORT.md +213 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/EXECUTION_PLAN.md +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/SPEC.md +213 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/STATUS.md +31 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/pr.md +103 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-00-spec-foundation/slice.json +71 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-01-cli-ux-runtime-progress-engine/slice.json +69 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-02-agent-profile-selection-selectors/slice.json +81 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-03-provider-model-selection-contract/slice.json +75 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-04-planner-ia-progress-flows/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-05-executor-pr-progress-flows/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-06-doctor-visual-json-contract/slice.json +81 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-07-interactive-init-spec-create/slice.json +85 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v30-interactive-cli-ux-agent-selection/slices/slice-08-tests-docs-cross-platform-release/slice.json +95 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/EVIDENCE_REPORT.md +185 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/EXECUTION_PLAN.md +85 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/SPEC.md +337 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/STATUS.md +30 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/pr.md +98 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-00-spec-foundation/slice.json +62 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-01-model-catalog-alias-normalization/slice.json +72 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-02-interactive-agent-set-selectors/slice.json +78 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-03-agent-doctor-repair/slice.json +77 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-04-shared-preflight-provider-errors/slice.json +84 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-05-ai-models-list/slice.json +72 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/EXECUTION_BRIEF.md +58 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-06-docs-templates-alignment/slice.json +84 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v31-ai-model-catalog-agent-selection/slices/slice-07-tests-smokes-release-readiness/slice.json +94 -0
- package/specs/quiver-v32-npx-installation-guidance/EVIDENCE_REPORT.md +26 -0
- package/specs/quiver-v32-npx-installation-guidance/SPEC.md +55 -0
- package/specs/quiver-v32-npx-installation-guidance/STATUS.md +23 -0
- package/specs/quiver-v32-npx-installation-guidance/pr.md +72 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v32-npx-installation-guidance/slices/slice-00-installation-docs/slice.json +75 -0
- package/src/create-quiver/commands/ai.js +797 -99
- package/src/create-quiver/commands/flow.js +2 -2
- package/src/create-quiver/commands/spec.js +106 -6
- package/src/create-quiver/index.js +557 -78
- package/src/create-quiver/lib/agent-profiles.js +440 -11
- package/src/create-quiver/lib/ai/execution-plan.js +111 -8
- package/src/create-quiver/lib/ai/executor.js +291 -28
- package/src/create-quiver/lib/ai/model-catalog.js +333 -0
- package/src/create-quiver/lib/ai/providers.js +211 -2
- package/src/create-quiver/lib/cli/selectors.js +160 -0
- package/src/create-quiver/lib/cli/theme.js +5 -2
- package/src/create-quiver/lib/cli/ux-flags.js +18 -0
- package/src/create-quiver/lib/cli/ux.js +100 -5
|
@@ -2,10 +2,12 @@ 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']);
|
|
8
|
-
const PROFILE_STATE_VERSION =
|
|
10
|
+
const PROFILE_STATE_VERSION = 2;
|
|
9
11
|
|
|
10
12
|
function formatError(message) {
|
|
11
13
|
return `create-quiver: ${message}`;
|
|
@@ -56,9 +58,51 @@ function emptyProfilesState() {
|
|
|
56
58
|
return {
|
|
57
59
|
version: PROFILE_STATE_VERSION,
|
|
58
60
|
profiles: {},
|
|
61
|
+
profile_sets: {},
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
function profileSetKey(role) {
|
|
66
|
+
return `${normalizeAgentProfileRole(role)}s`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeProfileId(value, fallback = 'default') {
|
|
70
|
+
const source = String(value || fallback || 'default').trim().toLowerCase();
|
|
71
|
+
const normalized = source
|
|
72
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
73
|
+
.replace(/^-+|-+$/g, '');
|
|
74
|
+
return normalized || 'default';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveAgentProfileDisplayName(profile = {}) {
|
|
78
|
+
return profile.displayName || profile.model || profile.provider || profile.role || '';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeStoredProfile(profile, role, fallbackId = 'default') {
|
|
82
|
+
const normalizedRole = normalizeAgentProfileRole(role || profile?.role);
|
|
83
|
+
const provider = profile?.provider ? assertSupportedProvider(profile.provider) : '';
|
|
84
|
+
const normalized = {
|
|
85
|
+
id: normalizeProfileId(profile?.id || profile?.label, fallbackId),
|
|
86
|
+
role: normalizedRole,
|
|
87
|
+
provider,
|
|
88
|
+
model: normalizeOptionalText(profile?.model, 'model'),
|
|
89
|
+
label: normalizeOptionalText(profile?.label, 'label'),
|
|
90
|
+
displayName: normalizeOptionalText(profile?.displayName || profile?.display_name, 'displayName'),
|
|
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'),
|
|
97
|
+
default: profile?.default === true,
|
|
98
|
+
updated_at: profile?.updated_at || '',
|
|
99
|
+
};
|
|
100
|
+
if (!normalized.displayName) {
|
|
101
|
+
normalized.displayName = resolveAgentProfileDisplayName(normalized);
|
|
102
|
+
}
|
|
103
|
+
return normalized;
|
|
104
|
+
}
|
|
105
|
+
|
|
62
106
|
function readAgentProfiles(projectRoot) {
|
|
63
107
|
const filePath = agentProfilesPath(projectRoot);
|
|
64
108
|
if (!fs.existsSync(filePath)) {
|
|
@@ -69,6 +113,8 @@ function readAgentProfiles(projectRoot) {
|
|
|
69
113
|
return {
|
|
70
114
|
version: state.version || PROFILE_STATE_VERSION,
|
|
71
115
|
profiles: state.profiles && typeof state.profiles === 'object' ? state.profiles : {},
|
|
116
|
+
profile_sets: state.profile_sets && typeof state.profile_sets === 'object' ? state.profile_sets : {},
|
|
117
|
+
updated_at: state.updated_at || undefined,
|
|
72
118
|
};
|
|
73
119
|
}
|
|
74
120
|
|
|
@@ -80,18 +126,53 @@ function writeAgentProfiles(projectRoot, state) {
|
|
|
80
126
|
}
|
|
81
127
|
|
|
82
128
|
function getAgentProfile(projectRoot, role) {
|
|
129
|
+
const normalizedRole = normalizeAgentProfileRole(role);
|
|
130
|
+
const profiles = getAgentProfilesForRole(projectRoot, normalizedRole);
|
|
131
|
+
if (profiles.length === 0) return null;
|
|
132
|
+
return profiles.find((profile) => profile.default) || profiles[0];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getAgentProfileById(projectRoot, role, profileId) {
|
|
136
|
+
const normalizedRole = normalizeAgentProfileRole(role);
|
|
137
|
+
const normalizedId = normalizeProfileId(profileId);
|
|
138
|
+
return getAgentProfilesForRole(projectRoot, normalizedRole)
|
|
139
|
+
.find((profile) => profile.id === normalizedId) || null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getAgentProfilesForRole(projectRoot, role) {
|
|
83
143
|
const normalizedRole = normalizeAgentProfileRole(role);
|
|
84
144
|
const state = readAgentProfiles(projectRoot);
|
|
85
|
-
|
|
145
|
+
const setKey = profileSetKey(normalizedRole);
|
|
146
|
+
const fromSet = Array.isArray(state.profile_sets[setKey])
|
|
147
|
+
? state.profile_sets[setKey].map((profile, index) => normalizeStoredProfile(profile, normalizedRole, index === 0 ? 'default' : `${normalizedRole}-${index + 1}`))
|
|
148
|
+
: [];
|
|
149
|
+
const legacy = state.profiles[normalizedRole]
|
|
150
|
+
? normalizeStoredProfile({ ...state.profiles[normalizedRole], default: true }, normalizedRole, 'default')
|
|
151
|
+
: null;
|
|
152
|
+
const merged = new Map();
|
|
153
|
+
|
|
154
|
+
if (legacy) merged.set(legacy.id, legacy);
|
|
155
|
+
for (const profile of fromSet) {
|
|
156
|
+
merged.set(profile.id, profile);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const profiles = Array.from(merged.values()).filter((profile) => profile.provider);
|
|
160
|
+
if (profiles.length > 0 && !profiles.some((profile) => profile.default)) {
|
|
161
|
+
profiles[0].default = true;
|
|
162
|
+
}
|
|
163
|
+
return profiles;
|
|
86
164
|
}
|
|
87
165
|
|
|
88
166
|
function listAgentProfiles(projectRoot) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
167
|
+
return AGENT_PROFILE_ROLES.map((role) => {
|
|
168
|
+
const profiles = getAgentProfilesForRole(projectRoot, role);
|
|
169
|
+
return {
|
|
170
|
+
role,
|
|
171
|
+
configured: profiles.length > 0,
|
|
172
|
+
profile: profiles.find((profile) => profile.default) || profiles[0] || null,
|
|
173
|
+
profiles,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
95
176
|
}
|
|
96
177
|
|
|
97
178
|
function setAgentProfile(projectRoot, role, options = {}) {
|
|
@@ -107,25 +188,68 @@ function setAgentProfile(projectRoot, role, options = {}) {
|
|
|
107
188
|
function buildAgentProfileState(projectRoot, role, options = {}) {
|
|
108
189
|
const normalizedRole = normalizeAgentProfileRole(role);
|
|
109
190
|
const provider = assertSupportedProvider(options.provider);
|
|
110
|
-
const
|
|
191
|
+
const rawModel = normalizeOptionalText(options.model, 'model');
|
|
111
192
|
const label = normalizeOptionalText(options.label, 'label');
|
|
193
|
+
const rawDisplayName = normalizeOptionalText(options.displayName || options.display_name, 'displayName');
|
|
112
194
|
const context = normalizeOptionalText(options.context, 'context');
|
|
113
195
|
const state = readAgentProfiles(projectRoot);
|
|
114
|
-
const
|
|
196
|
+
const currentProfiles = getAgentProfilesForRole(projectRoot, normalizedRole);
|
|
197
|
+
const currentDefault = currentProfiles.find((profile) => profile.default) || currentProfiles[0] || {};
|
|
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);
|
|
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;
|
|
115
210
|
const now = options.now instanceof Date ? options.now.toISOString() : new Date().toISOString();
|
|
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 || '');
|
|
116
216
|
const profile = {
|
|
217
|
+
id,
|
|
117
218
|
role: normalizedRole,
|
|
118
219
|
provider,
|
|
119
220
|
model: model || current.model || '',
|
|
120
221
|
label: label || current.label || '',
|
|
222
|
+
displayName,
|
|
121
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 || '',
|
|
229
|
+
default: shouldBeDefault,
|
|
122
230
|
updated_at: now,
|
|
123
231
|
};
|
|
124
232
|
|
|
125
233
|
state.version = PROFILE_STATE_VERSION;
|
|
234
|
+
const setKey = profileSetKey(normalizedRole);
|
|
235
|
+
const nextProfiles = currentProfiles
|
|
236
|
+
.filter((item) => item.id !== id)
|
|
237
|
+
.map((item) => ({
|
|
238
|
+
...item,
|
|
239
|
+
default: shouldBeDefault ? false : item.default === true,
|
|
240
|
+
}))
|
|
241
|
+
.concat(profile);
|
|
242
|
+
if (!nextProfiles.some((item) => item.default)) {
|
|
243
|
+
nextProfiles[0].default = true;
|
|
244
|
+
}
|
|
245
|
+
state.profile_sets = {
|
|
246
|
+
...state.profile_sets,
|
|
247
|
+
[setKey]: nextProfiles,
|
|
248
|
+
};
|
|
249
|
+
const defaultProfile = nextProfiles.find((item) => item.default) || profile;
|
|
126
250
|
state.profiles = {
|
|
127
251
|
...state.profiles,
|
|
128
|
-
[normalizedRole]:
|
|
252
|
+
[normalizedRole]: defaultProfile,
|
|
129
253
|
};
|
|
130
254
|
state.updated_at = now;
|
|
131
255
|
|
|
@@ -145,10 +269,315 @@ function resolveProfileProvider(projectRoot, role, fallbackProvider) {
|
|
|
145
269
|
return assertSupportedProvider(fallbackProvider);
|
|
146
270
|
}
|
|
147
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
|
+
|
|
148
572
|
module.exports = {
|
|
149
573
|
AGENT_PROFILE_ROLES,
|
|
150
574
|
PROFILE_STATE_VERSION,
|
|
151
575
|
agentProfilesPath,
|
|
576
|
+
buildAgentProfileDoctorReport,
|
|
577
|
+
buildAgentProfileRepairPlan,
|
|
578
|
+
getAgentProfileById,
|
|
579
|
+
getAgentProfilesForRole,
|
|
580
|
+
resolveAgentProfileDisplayName,
|
|
152
581
|
formatProviderList,
|
|
153
582
|
buildAgentProfileState,
|
|
154
583
|
getAgentProfile,
|