gsd-opencode 1.20.1 → 1.20.3

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.
Files changed (31) hide show
  1. package/commands/gsd/gsd-check-profile.md +30 -0
  2. package/commands/gsd/gsd-research-phase.md +2 -2
  3. package/get-shit-done/bin/gsd-oc-commands/check-oc-config-json.cjs +169 -0
  4. package/get-shit-done/bin/gsd-oc-commands/check-opencode-json.cjs +86 -0
  5. package/get-shit-done/bin/gsd-oc-commands/get-profile.cjs +117 -0
  6. package/get-shit-done/bin/gsd-oc-commands/set-profile.cjs +357 -0
  7. package/get-shit-done/bin/gsd-oc-commands/update-opencode-json.cjs +199 -0
  8. package/get-shit-done/bin/gsd-oc-commands/validate-models.cjs +75 -0
  9. package/get-shit-done/bin/gsd-oc-lib/oc-config.cjs +205 -0
  10. package/get-shit-done/bin/gsd-oc-lib/oc-core.cjs +113 -0
  11. package/get-shit-done/bin/gsd-oc-lib/oc-models.cjs +133 -0
  12. package/get-shit-done/bin/gsd-oc-lib/oc-profile-config.cjs +409 -0
  13. package/get-shit-done/bin/gsd-oc-tools.cjs +130 -0
  14. package/get-shit-done/bin/lib/oc-config.cjs +200 -0
  15. package/get-shit-done/bin/lib/oc-core.cjs +114 -0
  16. package/get-shit-done/bin/lib/oc-models.cjs +133 -0
  17. package/get-shit-done/bin/test/fixtures/oc-config-invalid.json +14 -0
  18. package/get-shit-done/bin/test/fixtures/oc-config-valid.json +22 -0
  19. package/get-shit-done/bin/test/get-profile.test.cjs +447 -0
  20. package/get-shit-done/bin/test/oc-profile-config.test.cjs +377 -0
  21. package/get-shit-done/bin/test/pivot-profile.test.cjs +276 -0
  22. package/get-shit-done/bin/test/set-profile.test.cjs +301 -0
  23. package/get-shit-done/workflows/diagnose-issues.md +1 -1
  24. package/get-shit-done/workflows/discuss-phase.md +1 -1
  25. package/get-shit-done/workflows/new-project.md +4 -4
  26. package/get-shit-done/workflows/oc-check-profile.md +181 -0
  27. package/get-shit-done/workflows/oc-set-profile.md +83 -243
  28. package/get-shit-done/workflows/plan-phase.md +4 -4
  29. package/get-shit-done/workflows/quick.md +1 -1
  30. package/get-shit-done/workflows/settings.md +4 -3
  31. package/package.json +2 -2
@@ -0,0 +1,357 @@
1
+ /**
2
+ * set-profile.cjs — Switch profile in oc_config.json with three operation modes
3
+ *
4
+ * Command module for managing OpenCode profiles using .planning/oc_config.json:
5
+ * 1. Mode 1 (no profile name): Validate and apply current profile
6
+ * 2. Mode 2 (profile name): Switch to specified profile
7
+ * 3. Mode 3 (inline JSON): Create new profile from definition
8
+ *
9
+ * Features:
10
+ * - Pre-flight validation BEFORE any file modifications
11
+ * - Atomic transaction with rollback on failure
12
+ * - Dry-run mode for previewing changes
13
+ * - Structured JSON output
14
+ *
15
+ * Usage:
16
+ * node set-profile.cjs # Mode 1: validate current
17
+ * node set-profile.cjs genius # Mode 2: switch to profile
18
+ * node set-profile.cjs 'custom:{...}' # Mode 3: create profile
19
+ * node set-profile.cjs --dry-run genius # Preview changes
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const { output, error, createBackup } = require('../gsd-oc-lib/oc-core.cjs');
25
+ const { applyProfileWithValidation } = require('../gsd-oc-lib/oc-profile-config.cjs');
26
+ const { getModelCatalog } = require('../gsd-oc-lib/oc-models.cjs');
27
+ const { applyProfileToOpencode } = require('../gsd-oc-lib/oc-config.cjs');
28
+
29
+ /**
30
+ * Error codes for set-profile operations
31
+ */
32
+ const ERROR_CODES = {
33
+ CONFIG_NOT_FOUND: 'CONFIG_NOT_FOUND',
34
+ INVALID_JSON: 'INVALID_JSON',
35
+ INVALID_SYNTAX: 'INVALID_SYNTAX',
36
+ PROFILE_NOT_FOUND: 'PROFILE_NOT_FOUND',
37
+ PROFILE_EXISTS: 'PROFILE_EXISTS',
38
+ INVALID_MODELS: 'INVALID_MODELS',
39
+ INCOMPLETE_PROFILE: 'INCOMPLETE_PROFILE',
40
+ WRITE_FAILED: 'WRITE_FAILED',
41
+ APPLY_FAILED: 'APPLY_FAILED',
42
+ ROLLBACK_FAILED: 'ROLLBACK_FAILED',
43
+ MISSING_CURRENT_PROFILE: 'MISSING_CURRENT_PROFILE',
44
+ INVALID_ARGS: 'INVALID_ARGS'
45
+ };
46
+
47
+ /**
48
+ * Parse inline profile definition from argument
49
+ * Expected format: profileName:{"planning":"...", "execution":"...", "verification":"..."}
50
+ *
51
+ * @param {string} arg - Argument string
52
+ * @returns {Object|null} {name, profile} or null if invalid
53
+ */
54
+ function parseInlineProfile(arg) {
55
+ const match = arg.match(/^([^:]+):(.+)$/);
56
+ if (!match) {
57
+ return null;
58
+ }
59
+
60
+ const [, profileName, profileJson] = match;
61
+
62
+ try {
63
+ const profile = JSON.parse(profileJson);
64
+ return { name: profileName, profile };
65
+ } catch (err) {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Validate inline profile definition has all required keys
72
+ *
73
+ * @param {Object} profile - Profile object to validate
74
+ * @returns {Object} {valid: boolean, missingKeys: string[]}
75
+ */
76
+ function validateInlineProfile(profile) {
77
+ const requiredKeys = ['planning', 'execution', 'verification'];
78
+ const missingKeys = requiredKeys.filter(key => !profile[key]);
79
+
80
+ return {
81
+ valid: missingKeys.length === 0,
82
+ missingKeys
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Validate models against whitelist
88
+ *
89
+ * @param {Object} profile - Profile with planning/execution/verification
90
+ * @param {string[]} validModels - Array of valid model IDs
91
+ * @returns {Object} {valid: boolean, invalidModels: string[]}
92
+ */
93
+ function validateProfileModels(profile, validModels) {
94
+ const modelsToCheck = [profile.planning, profile.execution, profile.verification].filter(Boolean);
95
+ const invalidModels = modelsToCheck.filter(model => !validModels.includes(model));
96
+
97
+ return {
98
+ valid: invalidModels.length === 0,
99
+ invalidModels
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Main command function
105
+ *
106
+ * @param {string} cwd - Current working directory
107
+ * @param {string[]} args - Command line arguments
108
+ */
109
+ function setProfilePhase16(cwd, args) {
110
+ const verbose = args.includes('--verbose');
111
+ const dryRun = args.includes('--dry-run');
112
+ const raw = args.includes('--raw');
113
+
114
+ const log = verbose ? (...args) => console.error('[set-profile]', ...args) : () => {};
115
+ const configPath = path.join(cwd, '.planning', 'oc_config.json');
116
+ const opencodePath = path.join(cwd, 'opencode.json');
117
+ const backupsDir = path.join(cwd, '.planning', 'backups');
118
+
119
+ log('Starting set-profile command');
120
+
121
+ // Filter flags to get profile argument
122
+ const profileArgs = args.filter(arg => !arg.startsWith('--'));
123
+
124
+ // Check for too many arguments
125
+ if (profileArgs.length > 1) {
126
+ error('Too many arguments. Usage: set-profile [profile-name | profileName:JSON] [--dry-run]', 'INVALID_ARGS');
127
+ }
128
+
129
+ const profileArg = profileArgs.length > 0 ? profileArgs[0] : null;
130
+
131
+ // ========== MODE 3: Inline profile definition ==========
132
+ if (profileArg && profileArg.includes(':')) {
133
+ const parsed = parseInlineProfile(profileArg);
134
+
135
+ if (!parsed) {
136
+ error(
137
+ 'Invalid profile syntax. Use: profileName:{"planning":"...", "execution":"...", "verification":"..."}',
138
+ 'INVALID_SYNTAX'
139
+ );
140
+ }
141
+
142
+ const { name: profileName, profile } = parsed;
143
+ log(`Mode 3: Creating inline profile "${profileName}"`);
144
+
145
+ // Validate complete profile definition
146
+ const validation = validateInlineProfile(profile);
147
+ if (!validation.valid) {
148
+ error(
149
+ `Profile definition missing required keys: ${validation.missingKeys.join(', ')}`,
150
+ 'INCOMPLETE_PROFILE'
151
+ );
152
+ }
153
+
154
+ // Get model catalog for validation
155
+ const catalogResult = getModelCatalog();
156
+ if (!catalogResult.success) {
157
+ error(catalogResult.error.message, catalogResult.error.code);
158
+ }
159
+
160
+ // Validate models against whitelist
161
+ const modelValidation = validateProfileModels(profile, catalogResult.models);
162
+ if (!modelValidation.valid) {
163
+ error(
164
+ `Invalid models: ${modelValidation.invalidModels.join(', ')}`,
165
+ 'INVALID_MODELS'
166
+ );
167
+ }
168
+
169
+ log('Inline profile validation passed');
170
+
171
+ // Dry-run mode
172
+ if (dryRun) {
173
+ output({
174
+ success: true,
175
+ data: {
176
+ dryRun: true,
177
+ action: 'create_profile',
178
+ profile: profileName,
179
+ models: profile
180
+ }
181
+ });
182
+ process.exit(0);
183
+ }
184
+
185
+ // Load or create oc_config.json
186
+ let config = {};
187
+ if (fs.existsSync(configPath)) {
188
+ try {
189
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
190
+ } catch (err) {
191
+ error(`Failed to parse oc_config.json: ${err.message}`, 'INVALID_JSON');
192
+ }
193
+ }
194
+
195
+ // Create backup
196
+ if (!fs.existsSync(backupsDir)) {
197
+ fs.mkdirSync(backupsDir, { recursive: true });
198
+ }
199
+
200
+ const backupPath = createBackup(configPath, backupsDir);
201
+
202
+ // Initialize structure if needed
203
+ if (!config.profiles) config.profiles = {};
204
+ if (!config.profiles.presets) config.profiles.presets = {};
205
+
206
+ // Add profile and set as current
207
+ config.profiles.presets[profileName] = profile;
208
+ config.current_oc_profile = profileName;
209
+
210
+ // write oc_config.json
211
+ try {
212
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
213
+ log('Updated oc_config.json');
214
+ } catch (err) {
215
+ error(`Failed to write oc_config.json: ${err.message}`, 'WRITE_FAILED');
216
+ }
217
+
218
+ // Apply to opencode.json
219
+ const applyResult = applyProfileToOpencode(opencodePath, configPath, profileName);
220
+ if (!applyResult.success) {
221
+ // Rollback
222
+ try {
223
+ if (backupPath) {
224
+ fs.copyFileSync(backupPath, configPath);
225
+ }
226
+ } catch (rollbackErr) {
227
+ error(
228
+ `Failed to apply profile AND failed to rollback: ${rollbackErr.message}`,
229
+ 'ROLLBACK_FAILED'
230
+ );
231
+ }
232
+ error(`Failed to apply profile to opencode.json: ${applyResult.error.message}`, 'APPLY_FAILED');
233
+ }
234
+
235
+ output({
236
+ success: true,
237
+ data: {
238
+ profile: profileName,
239
+ models: profile,
240
+ backup: backupPath,
241
+ configPath
242
+ }
243
+ });
244
+ process.exit(0);
245
+ }
246
+
247
+ // ========== MODE 1 & 2: Use applyProfileWithValidation ==========
248
+ // Load oc_config.json first to determine mode
249
+ let config;
250
+ if (fs.existsSync(configPath)) {
251
+ try {
252
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
253
+ } catch (err) {
254
+ error(`Failed to parse oc_config.json: ${err.message}`, 'INVALID_JSON');
255
+ }
256
+ } else {
257
+ error('.planning/oc_config.json not found. Create it with an inline profile definition first.', 'CONFIG_NOT_FOUND');
258
+ }
259
+
260
+ const presets = config.profiles?.presets || {};
261
+ const currentProfile = config.current_oc_profile;
262
+
263
+ // ========== MODE 2: Profile name provided ==========
264
+ if (profileArg) {
265
+ log(`Mode 2: Switching to profile "${profileArg}"`);
266
+
267
+ // Check profile exists
268
+ if (!presets[profileArg]) {
269
+ const available = Object.keys(presets).join(', ') || 'none';
270
+ error(`Profile "${profileArg}" not found. Available profiles: ${available}`, 'PROFILE_NOT_FOUND');
271
+ }
272
+
273
+ // Use applyProfileWithValidation for Mode 2
274
+ const result = applyProfileWithValidation(cwd, profileArg, { dryRun, verbose });
275
+
276
+ if (!result.success) {
277
+ error(result.error.message, result.error.code || 'UNKNOWN_ERROR');
278
+ }
279
+
280
+ if (result.dryRun) {
281
+ output({
282
+ success: true,
283
+ data: {
284
+ dryRun: true,
285
+ action: 'switch_profile',
286
+ profile: profileArg,
287
+ models: result.preview.models,
288
+ changes: result.preview.changes
289
+ }
290
+ });
291
+ } else {
292
+ output({
293
+ success: true,
294
+ data: {
295
+ profile: profileArg,
296
+ models: result.data.models,
297
+ backup: result.data.backup,
298
+ updated: result.data.updated,
299
+ configPath: result.data.configPath
300
+ }
301
+ });
302
+ }
303
+ process.exit(0);
304
+ }
305
+
306
+ // ========== MODE 1: No profile name - validate current profile ==========
307
+ log('Mode 1: Validating current profile');
308
+
309
+ if (!currentProfile) {
310
+ const available = Object.keys(presets).join(', ') || 'none';
311
+ error(
312
+ `current_oc_profile not set. Available profiles: ${available}`,
313
+ 'MISSING_CURRENT_PROFILE'
314
+ );
315
+ }
316
+
317
+ if (!presets[currentProfile]) {
318
+ error(
319
+ `Current profile "${currentProfile}" not found in profiles.presets`,
320
+ 'PROFILE_NOT_FOUND'
321
+ );
322
+ }
323
+
324
+ // Use applyProfileWithValidation for Mode 1
325
+ const result = applyProfileWithValidation(cwd, currentProfile, { dryRun, verbose });
326
+
327
+ if (!result.success) {
328
+ error(result.error.message, result.error.code || 'UNKNOWN_ERROR');
329
+ }
330
+
331
+ if (result.dryRun) {
332
+ output({
333
+ success: true,
334
+ data: {
335
+ dryRun: true,
336
+ action: 'validate_current',
337
+ profile: currentProfile,
338
+ models: result.preview.models,
339
+ changes: result.preview.changes
340
+ }
341
+ });
342
+ } else {
343
+ output({
344
+ success: true,
345
+ data: {
346
+ profile: currentProfile,
347
+ models: result.data.models,
348
+ backup: result.data.backup,
349
+ updated: result.data.updated,
350
+ configPath: result.data.configPath
351
+ }
352
+ });
353
+ }
354
+ process.exit(0);
355
+ }
356
+
357
+ module.exports = setProfilePhase16;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * update-opencode-json.cjs — Update opencode.json agent models from profile config
3
+ *
4
+ * Command module that updates opencode.json model assignments based on oc_config.json structure.
5
+ * Creates timestamped backup before modifications.
6
+ * Outputs JSON envelope format with update results.
7
+ *
8
+ * Usage: node update-opencode-json.cjs [cwd] [--dry-run] [--verbose]
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { output, error, createBackup } = require('../gsd-oc-lib/oc-core.cjs');
14
+ const { applyProfileToOpencode } = require('../gsd-oc-lib/oc-config.cjs');
15
+
16
+ /**
17
+ * Main command function
18
+ *
19
+ * @param {string} cwd - Current working directory
20
+ * @param {string[]} args - Command line arguments
21
+ */
22
+ function updateOpencodeJson(cwd, args) {
23
+ const verbose = args.includes('--verbose');
24
+ const dryRun = args.includes('--dry-run');
25
+
26
+ const opencodePath = path.join(cwd, 'opencode.json');
27
+ const configPath = path.join(cwd, '.planning', 'oc_config.json');
28
+
29
+ // Check if opencode.json exists
30
+ if (!fs.existsSync(opencodePath)) {
31
+ error('opencode.json not found in current directory', 'CONFIG_NOT_FOUND');
32
+ }
33
+
34
+ // Check if .planning/oc_config.json exists
35
+ if (!fs.existsSync(configPath)) {
36
+ error('.planning/oc_config.json not found', 'CONFIG_NOT_FOUND');
37
+ }
38
+
39
+ if (verbose) {
40
+ console.error(`[verbose] opencode.json: ${opencodePath}`);
41
+ console.error(`[verbose] oc_config.json: ${configPath}`);
42
+ console.error(`[verbose] dry-run: ${dryRun}`);
43
+ }
44
+
45
+ // Load and validate profile config
46
+ let config;
47
+ try {
48
+ const content = fs.readFileSync(configPath, 'utf8');
49
+ config = JSON.parse(content);
50
+ } catch (err) {
51
+ error('Failed to parse .planning/oc_config.json', 'INVALID_JSON');
52
+ }
53
+
54
+ // Validate current_oc_profile
55
+ const profileName = config.current_oc_profile;
56
+ if (!profileName) {
57
+ error('current_oc_profile not found in oc_config.json', 'PROFILE_NOT_FOUND');
58
+ }
59
+
60
+ // Validate profile exists in profiles.presets
61
+ const presets = config.profiles?.presets;
62
+ if (!presets || !presets[profileName]) {
63
+ const availableProfiles = presets ? Object.keys(presets).join(', ') : 'none';
64
+ error(`Profile "${profileName}" not found in profiles.presets. Available profiles: ${availableProfiles}`, 'PROFILE_NOT_FOUND');
65
+ }
66
+
67
+ if (verbose) {
68
+ console.error(`[verbose] Profile name: ${profileName}`);
69
+ }
70
+
71
+ // Dry-run mode: preview changes without modifying
72
+ if (dryRun) {
73
+ if (verbose) {
74
+ console.error('[verbose] Dry-run mode - no changes will be made');
75
+ }
76
+
77
+ // Simulate what would be updated
78
+ try {
79
+ const opencodeContent = fs.readFileSync(opencodePath, 'utf8');
80
+ const opencodeData = JSON.parse(opencodeContent);
81
+
82
+ const profileModels = presets[profileName];
83
+
84
+ if (!profileModels.planning && !profileModels.execution && !profileModels.verification) {
85
+ error(`No model assignments found for profile "${profileName}"`, 'PROFILE_NOT_FOUND');
86
+ }
87
+
88
+ // Determine which agents would be updated
89
+ const wouldUpdate = [];
90
+
91
+ const PROFILE_AGENT_MAPPING = {
92
+ planning: [
93
+ 'gsd-planner', 'gsd-plan-checker', 'gsd-phase-researcher',
94
+ 'gsd-roadmapper', 'gsd-project-researcher', 'gsd-research-synthesizer',
95
+ 'gsd-codebase-mapper'
96
+ ],
97
+ execution: ['gsd-executor', 'gsd-debugger'],
98
+ verification: ['gsd-verifier', 'gsd-integration-checker']
99
+ };
100
+
101
+ for (const [category, agentNames] of Object.entries(PROFILE_AGENT_MAPPING)) {
102
+ const modelId = profileModels[category];
103
+ if (modelId) {
104
+ for (const agentName of agentNames) {
105
+ const currentModel = typeof opencodeData.agent[agentName] === 'string'
106
+ ? opencodeData.agent[agentName]
107
+ : opencodeData.agent[agentName]?.model;
108
+
109
+ if (currentModel !== modelId) {
110
+ wouldUpdate.push({
111
+ agent: agentName,
112
+ from: currentModel || '(not set)',
113
+ to: modelId,
114
+ modelId: modelId
115
+ });
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ const result = {
122
+ success: true,
123
+ data: {
124
+ backup: null,
125
+ updated: wouldUpdate.map(u => u.agent),
126
+ dryRun: true,
127
+ changes: wouldUpdate
128
+ }
129
+ };
130
+
131
+ if (verbose) {
132
+ console.error(`[verbose] Would update ${wouldUpdate.length} agent(s)`);
133
+ }
134
+
135
+ output(result);
136
+ process.exit(0);
137
+ } catch (err) {
138
+ error(`Failed to preview changes: ${err.message}`, 'PREVIEW_FAILED');
139
+ }
140
+ }
141
+
142
+ // Actual update mode
143
+ if (verbose) {
144
+ console.error('[verbose] Creating backup...');
145
+ }
146
+
147
+ // Create timestamped backup
148
+ const backupPath = createBackup(opencodePath);
149
+ if (!backupPath) {
150
+ error('Failed to create backup of opencode.json', 'BACKUP_FAILED');
151
+ }
152
+
153
+ if (verbose) {
154
+ console.error(`[verbose] Backup created: ${backupPath}`);
155
+ }
156
+
157
+ // Apply profile to opencode.json using the existing function which already supports oc_config.json
158
+ if (verbose) {
159
+ console.error('[verbose] Applying profile to opencode.json...');
160
+ }
161
+
162
+ const result = applyProfileToOpencode(opencodePath, configPath, profileName);
163
+
164
+ if (!result.success) {
165
+ // Restore backup on failure
166
+ if (verbose) {
167
+ console.error('[verbose] Update failed, restoring backup...');
168
+ }
169
+ try {
170
+ fs.copyFileSync(backupPath, opencodePath);
171
+ } catch (err) {
172
+ // Best effort restore
173
+ }
174
+ error(result.error.message, result.error.code);
175
+ }
176
+
177
+ if (verbose) {
178
+ console.error(`[verbose] Updated ${result.updated.length} agent(s)`);
179
+ for (const { agent } of result.updated) {
180
+ console.error(`[verbose] - ${agent}`);
181
+ }
182
+ }
183
+
184
+ const outputResult = {
185
+ success: true,
186
+ data: {
187
+ backup: backupPath,
188
+ updated: result.updated.map(u => u.agent),
189
+ dryRun: false,
190
+ details: result.updated
191
+ }
192
+ };
193
+
194
+ output(outputResult);
195
+ process.exit(0);
196
+ }
197
+
198
+ // Export for use by main router
199
+ module.exports = updateOpencodeJson;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * validate-models.cjs — Validate model IDs against opencode models catalog
3
+ *
4
+ * Command module that validates one or more model IDs exist in the opencode catalog.
5
+ * Outputs JSON envelope format with validation results.
6
+ *
7
+ * Usage: node validate-models.cjs <model1> [model2...] [--raw]
8
+ */
9
+
10
+ const { output, error } = require('../gsd-oc-lib/oc-core.cjs');
11
+ const { getModelCatalog } = require('../gsd-oc-lib/oc-models.cjs');
12
+
13
+ /**
14
+ * Main command function
15
+ *
16
+ * @param {string} cwd - Current working directory
17
+ * @param {string[]} args - Command line arguments (model IDs)
18
+ */
19
+ function validateModels(cwd, args) {
20
+ const raw = args.includes('--raw');
21
+ const modelIds = args.filter(arg => !arg.startsWith('--'));
22
+
23
+ if (modelIds.length === 0) {
24
+ error('No model IDs provided. Usage: validate-models <model1> [model2...]', 'INVALID_USAGE');
25
+ }
26
+
27
+ // Fetch model catalog
28
+ const catalogResult = getModelCatalog();
29
+ if (!catalogResult.success) {
30
+ error(catalogResult.error.message, catalogResult.error.code);
31
+ }
32
+
33
+ const validModels = catalogResult.models;
34
+ const results = [];
35
+
36
+ for (const modelId of modelIds) {
37
+ const isValid = validModels.includes(modelId);
38
+ results.push({
39
+ model: modelId,
40
+ valid: isValid,
41
+ reason: isValid ? 'Model found in catalog' : 'Model not found in catalog'
42
+ });
43
+ }
44
+
45
+ const allValid = results.every(r => r.valid);
46
+ const validCount = results.filter(r => r.valid).length;
47
+ const invalidCount = results.filter(r => !r.valid).length;
48
+
49
+ const result = {
50
+ success: allValid,
51
+ data: {
52
+ total: modelIds.length,
53
+ valid: validCount,
54
+ invalid: invalidCount,
55
+ models: results
56
+ }
57
+ };
58
+
59
+ if (!allValid) {
60
+ result.error = {
61
+ code: 'INVALID_MODELS',
62
+ message: `${invalidCount} model(s) not found in catalog`
63
+ };
64
+ }
65
+
66
+ if (raw) {
67
+ output(result, true, allValid ? 'valid' : 'invalid');
68
+ } else {
69
+ output(result);
70
+ }
71
+
72
+ process.exit(allValid ? 0 : 1);
73
+ }
74
+
75
+ module.exports = validateModels;