clavix 5.6.2 → 5.6.4

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/README.md CHANGED
@@ -42,12 +42,56 @@ The AI agent reads the template and optimizes your prompt.
42
42
 
43
43
  ### 3. Choose Your Workflow
44
44
 
45
- | Command | When to Use |
46
- |---------|-------------|
47
- | `/clavix:improve` | Optimize a single prompt |
48
- | `/clavix:prd` | Plan something new with guided questions |
49
- | `/clavix:plan` | Generate tasks from a PRD |
50
- | `/clavix:implement` | Execute tasks with progress tracking |
45
+ ```mermaid
46
+ graph TD
47
+ A["/clavix:improve"] --> B["/clavix:implement"]
48
+ C["/clavix:prd"] --> D["/clavix:plan"]
49
+ D --> E["/clavix:implement"]
50
+ E --> F["/clavix:verify"]
51
+ F --> G["/clavix:archive"]
52
+ H["/clavix:start"] --> I["/clavix:summarize"]
53
+ I --> D
54
+ J["/clavix:refine"] --> D
55
+ J --> B
56
+ ```
57
+
58
+ #### Quick Path (Simple Tasks)
59
+ ```
60
+ /clavix:improve → /clavix:implement
61
+ ```
62
+ Optimize a prompt and implement it directly.
63
+
64
+ #### Full Planning (Complex Features)
65
+ ```
66
+ /clavix:prd → /clavix:plan → /clavix:implement → /clavix:verify → /clavix:archive
67
+ ```
68
+ Structured planning with PRD, task breakdown, implementation, verification, and archival.
69
+
70
+ #### Exploratory (Discovery Mode)
71
+ ```
72
+ /clavix:start → [conversation] → /clavix:summarize → /clavix:plan
73
+ ```
74
+ Have a conversation to explore requirements, then extract and plan.
75
+
76
+ #### Refinement (Iteration)
77
+ ```
78
+ /clavix:refine → (updated PRD or prompt) → continue workflow
79
+ ```
80
+ Refine existing PRDs or prompts based on feedback.
81
+
82
+ ### All 9 Slash Commands
83
+
84
+ | Command | Purpose |
85
+ |---------|---------|
86
+ | `/clavix:improve` | Optimize prompts (auto-selects depth) |
87
+ | `/clavix:prd` | Generate PRD through guided questions |
88
+ | `/clavix:plan` | Create task breakdown from PRD |
89
+ | `/clavix:implement` | Execute tasks or prompts |
90
+ | `/clavix:start` | Begin conversational session |
91
+ | `/clavix:summarize` | Extract requirements from conversation |
92
+ | `/clavix:refine` | Refine existing PRD or prompt |
93
+ | `/clavix:verify` | Verify implementation against requirements |
94
+ | `/clavix:archive` | Archive completed projects |
51
95
 
52
96
  See [Getting Started](docs/getting-started.md) for the full guide.
53
97
 
@@ -19,11 +19,12 @@ import { QwenAdapter } from '../../core/adapters/qwen-adapter.js';
19
19
  import { loadCommandTemplates } from '../../utils/template-loader.js';
20
20
  import { collectLegacyCommandFiles } from '../../utils/legacy-command-cleanup.js';
21
21
  import { CLAVIX_BLOCK_START, CLAVIX_BLOCK_END } from '../../constants.js';
22
+ import { validateUserConfig } from '../../utils/schemas.js';
22
23
  export default class Init extends Command {
23
24
  static description = 'Initialize Clavix in the current project';
24
25
  static examples = ['<%= config.bin %> <%= command.id %>'];
25
26
  async run() {
26
- console.log(chalk.bold.cyan('\n🚀 Clavix Initialization\n'));
27
+ this.log(chalk.bold.cyan('\n🚀 Clavix Initialization\n'));
27
28
  try {
28
29
  const agentManager = new AgentManager();
29
30
  let existingIntegrations = [];
@@ -31,26 +32,48 @@ export default class Init extends Command {
31
32
  if (await FileSystem.exists('.clavix/config.json')) {
32
33
  try {
33
34
  const configContent = await FileSystem.readFile('.clavix/config.json');
34
- const config = JSON.parse(configContent);
35
- existingIntegrations = config.integrations || config.providers || [];
35
+ const rawConfig = JSON.parse(configContent);
36
+ // Validate config structure with Zod
37
+ const validationResult = validateUserConfig(rawConfig);
38
+ if (validationResult.success && validationResult.data) {
39
+ existingIntegrations =
40
+ validationResult.data.integrations || validationResult.data.providers || [];
41
+ // Log warnings (non-blocking)
42
+ if (validationResult.warnings) {
43
+ for (const warning of validationResult.warnings) {
44
+ this.log(chalk.yellow(` ⚠ ${warning}`));
45
+ }
46
+ }
47
+ }
48
+ else {
49
+ this.log(chalk.yellow('⚠ Warning: Config file has invalid structure') +
50
+ '\n' +
51
+ chalk.gray(' Continuing with fresh configuration...\n'));
52
+ }
36
53
  }
37
- catch {
38
- // Ignore parse errors, will use empty array
54
+ catch (error) {
55
+ const { getErrorMessage } = await import('../../utils/error-utils.js');
56
+ this.log(chalk.yellow('⚠ Warning: Could not parse existing config.json') +
57
+ '\n' +
58
+ chalk.gray(` Error: ${getErrorMessage(error)}`) +
59
+ '\n' +
60
+ chalk.gray(' Continuing with fresh configuration...\n'));
61
+ // Continue with empty array - will prompt for new configuration
39
62
  }
40
63
  }
41
64
  // Check if already initialized
42
65
  if (await FileSystem.exists('.clavix')) {
43
66
  // Show current state
44
- console.log(chalk.cyan('You have existing Clavix configuration:'));
67
+ this.log(chalk.cyan('You have existing Clavix configuration:'));
45
68
  if (existingIntegrations.length > 0) {
46
69
  const displayNames = existingIntegrations.map((name) => {
47
70
  const adapter = agentManager.getAdapter(name);
48
71
  return adapter?.displayName || name;
49
72
  });
50
- console.log(chalk.gray(` Integrations: ${displayNames.join(', ')}\n`));
73
+ this.log(chalk.gray(` Integrations: ${displayNames.join(', ')}\n`));
51
74
  }
52
75
  else {
53
- console.log(chalk.gray(' Integrations: (none configured)\n'));
76
+ this.log(chalk.gray(' Integrations: (none configured)\n'));
54
77
  }
55
78
  const { action } = await inquirer.prompt([
56
79
  {
@@ -65,39 +88,39 @@ export default class Init extends Command {
65
88
  },
66
89
  ]);
67
90
  if (action === 'cancel') {
68
- console.log(chalk.yellow('\n✓ Initialization cancelled\n'));
91
+ this.log(chalk.yellow('\n✓ Initialization cancelled\n'));
69
92
  return;
70
93
  }
71
94
  if (action === 'update') {
72
95
  // Just regenerate commands for existing integrations
73
- console.log(chalk.cyan('\n📝 Regenerating commands...\n'));
96
+ this.log(chalk.cyan('\n📝 Regenerating commands...\n'));
74
97
  await this.regenerateCommands(agentManager, existingIntegrations);
75
- console.log(chalk.green('\n✅ Commands updated successfully!\n'));
98
+ this.log(chalk.green('\n✅ Commands updated successfully!\n'));
76
99
  return;
77
100
  }
78
101
  // Continue with reconfiguration flow below
79
102
  }
80
103
  // Select integrations using shared utility
81
- console.log(chalk.gray('Select AI development tools to support:\n'));
82
- console.log(chalk.gray('(Space to select, Enter to confirm)\n'));
83
- console.log(chalk.cyan('ℹ'), chalk.gray('AGENTS.md is always enabled to provide universal agent guidance.\n'));
104
+ this.log(chalk.gray('Select AI development tools to support:\n'));
105
+ this.log(chalk.gray('(Space to select, Enter to confirm)\n'));
106
+ this.log(chalk.cyan('ℹ'), chalk.gray('AGENTS.md is always enabled to provide universal agent guidance.\n'));
84
107
  const { selectIntegrations, ensureMandatoryIntegrations } = await import('../../utils/integration-selector.js');
85
108
  const userSelectedIntegrations = await selectIntegrations(agentManager, existingIntegrations);
86
109
  // Always include AGENTS.md
87
110
  const selectedIntegrations = ensureMandatoryIntegrations(userSelectedIntegrations);
88
111
  if (!selectedIntegrations || selectedIntegrations.length === 0) {
89
- console.log(chalk.red('\n✗ No integrations selected\n'));
112
+ this.log(chalk.red('\n✗ No integrations selected\n'));
90
113
  return;
91
114
  }
92
115
  // Handle deselected integrations (cleanup prompt)
93
116
  const deselectedIntegrations = existingIntegrations.filter((p) => !selectedIntegrations.includes(p));
94
117
  if (deselectedIntegrations.length > 0) {
95
- console.log(chalk.yellow('\n⚠ Previously configured but not selected:'));
118
+ this.log(chalk.yellow('\n⚠ Previously configured but not selected:'));
96
119
  for (const integrationName of deselectedIntegrations) {
97
120
  const adapter = agentManager.getAdapter(integrationName);
98
121
  const displayName = adapter?.displayName || integrationName;
99
122
  const directory = adapter?.directory || 'unknown';
100
- console.log(chalk.gray(` • ${displayName} (${directory})`));
123
+ this.log(chalk.gray(` • ${displayName} (${directory})`));
101
124
  }
102
125
  const { cleanupAction } = await inquirer.prompt([
103
126
  {
@@ -112,85 +135,85 @@ export default class Init extends Command {
112
135
  },
113
136
  ]);
114
137
  if (cleanupAction === 'cleanup') {
115
- console.log(chalk.gray('\n🗑️ Cleaning up deselected integrations...'));
138
+ this.log(chalk.gray('\n🗑️ Cleaning up deselected integrations...'));
116
139
  for (const integrationName of deselectedIntegrations) {
117
140
  // Handle doc generators (AGENTS.md, OCTO.md, WARP.md, copilot-instructions)
118
141
  if (integrationName === 'agents-md') {
119
142
  await DocInjector.removeBlock('AGENTS.md');
120
- console.log(chalk.gray(' ✓ Cleaned AGENTS.md Clavix block'));
143
+ this.log(chalk.gray(' ✓ Cleaned AGENTS.md Clavix block'));
121
144
  continue;
122
145
  }
123
146
  if (integrationName === 'octo-md') {
124
147
  await DocInjector.removeBlock('OCTO.md');
125
- console.log(chalk.gray(' ✓ Cleaned OCTO.md Clavix block'));
148
+ this.log(chalk.gray(' ✓ Cleaned OCTO.md Clavix block'));
126
149
  continue;
127
150
  }
128
151
  if (integrationName === 'warp-md') {
129
152
  await DocInjector.removeBlock('WARP.md');
130
- console.log(chalk.gray(' ✓ Cleaned WARP.md Clavix block'));
153
+ this.log(chalk.gray(' ✓ Cleaned WARP.md Clavix block'));
131
154
  continue;
132
155
  }
133
156
  if (integrationName === 'copilot-instructions') {
134
157
  await DocInjector.removeBlock('.github/copilot-instructions.md');
135
- console.log(chalk.gray(' ✓ Cleaned copilot-instructions.md Clavix block'));
158
+ this.log(chalk.gray(' ✓ Cleaned copilot-instructions.md Clavix block'));
136
159
  continue;
137
160
  }
138
161
  // Handle Claude Code (has CLAUDE.md doc injection)
139
162
  if (integrationName === 'claude-code') {
140
163
  await DocInjector.removeBlock('CLAUDE.md');
141
- console.log(chalk.gray(' ✓ Cleaned CLAUDE.md Clavix block'));
164
+ this.log(chalk.gray(' ✓ Cleaned CLAUDE.md Clavix block'));
142
165
  }
143
166
  const adapter = agentManager.getAdapter(integrationName);
144
167
  if (adapter) {
145
168
  const removed = await adapter.removeAllCommands();
146
- console.log(chalk.gray(` ✓ Removed ${removed} command(s) from ${adapter.displayName}`));
169
+ this.log(chalk.gray(` ✓ Removed ${removed} command(s) from ${adapter.displayName}`));
147
170
  }
148
171
  }
149
172
  }
150
173
  else if (cleanupAction === 'update') {
151
174
  // Add them back to selection
152
175
  selectedIntegrations.push(...deselectedIntegrations);
153
- console.log(chalk.gray('\n✓ Keeping all integrations\n'));
176
+ this.log(chalk.gray('\n✓ Keeping all integrations\n'));
154
177
  }
155
178
  // If 'skip': do nothing
156
179
  }
157
180
  // Create .clavix directory structure
158
- console.log(chalk.cyan('\n📁 Creating directory structure...'));
181
+ this.log(chalk.cyan('\n📁 Creating directory structure...'));
159
182
  await this.createDirectoryStructure();
160
183
  // Generate config
161
- console.log(chalk.cyan('⚙️ Generating configuration...'));
184
+ this.log(chalk.cyan('⚙️ Generating configuration...'));
162
185
  await this.generateConfig(selectedIntegrations);
163
186
  // Generate INSTRUCTIONS.md and QUICKSTART.md
164
187
  await this.generateInstructions();
165
188
  await this.generateQuickstart();
166
189
  // Generate commands for each selected integration
167
- console.log(chalk.cyan(`\n📝 Generating commands for ${selectedIntegrations.length} integration(s)...\n`));
190
+ this.log(chalk.cyan(`\n📝 Generating commands for ${selectedIntegrations.length} integration(s)...\n`));
168
191
  for (const integrationName of selectedIntegrations) {
169
192
  // Handle agents-md separately (it's not an adapter)
170
193
  if (integrationName === 'agents-md') {
171
- console.log(chalk.gray(' ✓ Generating AGENTS.md...'));
194
+ this.log(chalk.gray(' ✓ Generating AGENTS.md...'));
172
195
  await AgentsMdGenerator.generate();
173
196
  continue;
174
197
  }
175
198
  // Handle copilot-instructions separately (it's not an adapter)
176
199
  if (integrationName === 'copilot-instructions') {
177
- console.log(chalk.gray(' ✓ Generating .github/copilot-instructions.md...'));
200
+ this.log(chalk.gray(' ✓ Generating .github/copilot-instructions.md...'));
178
201
  await CopilotInstructionsGenerator.generate();
179
202
  continue;
180
203
  }
181
204
  // Handle octo-md separately (it's not an adapter)
182
205
  if (integrationName === 'octo-md') {
183
- console.log(chalk.gray(' ✓ Generating OCTO.md...'));
206
+ this.log(chalk.gray(' ✓ Generating OCTO.md...'));
184
207
  await OctoMdGenerator.generate();
185
208
  continue;
186
209
  }
187
210
  if (integrationName === 'warp-md') {
188
- console.log(chalk.gray(' ✓ Generating WARP.md...'));
211
+ this.log(chalk.gray(' ✓ Generating WARP.md...'));
189
212
  await WarpMdGenerator.generate();
190
213
  continue;
191
214
  }
192
215
  let adapter = agentManager.requireAdapter(integrationName);
193
- console.log(chalk.gray(` ✓ Generating ${adapter.displayName} commands...`));
216
+ this.log(chalk.gray(` ✓ Generating ${adapter.displayName} commands...`));
194
217
  if (adapter.name === 'codex') {
195
218
  const codexPath = adapter.getCommandPath();
196
219
  const { confirmCodex } = await inquirer.prompt([
@@ -202,7 +225,7 @@ export default class Init extends Command {
202
225
  },
203
226
  ]);
204
227
  if (!confirmCodex) {
205
- console.log(chalk.yellow(' ⊗ Skipped Codex CLI'));
228
+ this.log(chalk.yellow(' ⊗ Skipped Codex CLI'));
206
229
  continue;
207
230
  }
208
231
  }
@@ -221,15 +244,15 @@ export default class Init extends Command {
221
244
  adapter.name === 'gemini'
222
245
  ? new GeminiAdapter({ useNamespace: false })
223
246
  : new QwenAdapter({ useNamespace: false });
224
- console.log(chalk.gray(` → Using ${adapter.getCommandPath()} (no namespacing)`));
247
+ this.log(chalk.gray(` → Using ${adapter.getCommandPath()} (no namespacing)`));
225
248
  }
226
249
  }
227
250
  // Validate before generating
228
251
  if (adapter.validate) {
229
252
  const validation = await adapter.validate();
230
253
  if (!validation.valid) {
231
- console.log(chalk.yellow(` ⚠ Validation warnings for ${adapter.displayName}:`));
232
- validation.errors?.forEach((err) => console.log(chalk.yellow(` - ${err}`)));
254
+ this.log(chalk.yellow(` ⚠ Validation warnings for ${adapter.displayName}:`));
255
+ validation.errors?.forEach((err) => this.log(chalk.yellow(` - ${err}`)));
233
256
  const { continueAnyway } = await inquirer.prompt([
234
257
  {
235
258
  type: 'confirm',
@@ -239,7 +262,7 @@ export default class Init extends Command {
239
262
  },
240
263
  ]);
241
264
  if (!continueAnyway) {
242
- console.log(chalk.yellow(` ⊗ Skipped ${adapter.displayName}`));
265
+ this.log(chalk.yellow(` ⊗ Skipped ${adapter.displayName}`));
243
266
  continue;
244
267
  }
245
268
  }
@@ -247,7 +270,7 @@ export default class Init extends Command {
247
270
  // Remove all existing commands before regenerating (ensures clean state)
248
271
  const removed = await adapter.removeAllCommands();
249
272
  if (removed > 0) {
250
- console.log(chalk.gray(` Removed ${removed} existing command(s)`));
273
+ this.log(chalk.gray(` Removed ${removed} existing command(s)`));
251
274
  }
252
275
  // Generate slash commands
253
276
  const generatedTemplates = await this.generateSlashCommands(adapter);
@@ -264,24 +287,24 @@ export default class Init extends Command {
264
287
  const slashName = filename.slice(0, -adapter.fileExtension.length);
265
288
  return `/${slashName}`;
266
289
  });
267
- console.log(chalk.green(` → Registered ${commandNames.join(', ')}`));
268
- console.log(chalk.gray(` Commands saved to ${commandPath}`));
269
- console.log(chalk.gray(' Tip: reopen the CLI or run /help to refresh the command list.'));
290
+ this.log(chalk.green(` → Registered ${commandNames.join(', ')}`));
291
+ this.log(chalk.gray(` Commands saved to ${commandPath}`));
292
+ this.log(chalk.gray(' Tip: reopen the CLI or run /help to refresh the command list.'));
270
293
  }
271
294
  // Inject documentation blocks (Claude Code only)
272
295
  if (integrationName === 'claude-code') {
273
- console.log(chalk.gray(' ✓ Injecting CLAUDE.md documentation...'));
296
+ this.log(chalk.gray(' ✓ Injecting CLAUDE.md documentation...'));
274
297
  await this.injectDocumentation(adapter);
275
298
  }
276
299
  }
277
300
  // Generate .clavix/instructions/ folder for generic integrations
278
301
  if (InstructionsGenerator.needsGeneration(selectedIntegrations)) {
279
- console.log(chalk.gray('\n📁 Generating .clavix/instructions/ reference folder...'));
302
+ this.log(chalk.gray('\n📁 Generating .clavix/instructions/ reference folder...'));
280
303
  await InstructionsGenerator.generate();
281
- console.log(chalk.gray(' ✓ Created detailed workflow guides for generic integrations'));
304
+ this.log(chalk.gray(' ✓ Created detailed workflow guides for generic integrations'));
282
305
  }
283
306
  // Success message with prominent command format display
284
- console.log(chalk.bold.green('\n✅ Clavix initialized successfully!\n'));
307
+ this.log(chalk.bold.green('\n✅ Clavix initialized successfully!\n'));
285
308
  // Determine the primary command format based on selected integrations
286
309
  const colonTools = ['claude-code', 'gemini', 'qwen', 'crush', 'llxprt', 'augment'];
287
310
  const usesColon = selectedIntegrations.some((i) => colonTools.includes(i));
@@ -289,30 +312,30 @@ export default class Init extends Command {
289
312
  const separator = usesColon && !usesHyphen ? ':' : usesHyphen && !usesColon ? '-' : ':';
290
313
  const altSeparator = separator === ':' ? '-' : ':';
291
314
  // Show command format prominently at the TOP
292
- console.log(chalk.bold('📋 Your command format:'), chalk.bold.cyan(`/clavix${separator}improve`));
315
+ this.log(chalk.bold('📋 Your command format:'), chalk.bold.cyan(`/clavix${separator}improve`));
293
316
  if (usesColon && usesHyphen) {
294
- console.log(chalk.gray(' (Some integrations use'), chalk.cyan(`/clavix${altSeparator}improve`), chalk.gray('instead)'));
317
+ this.log(chalk.gray(' (Some integrations use'), chalk.cyan(`/clavix${altSeparator}improve`), chalk.gray('instead)'));
295
318
  }
296
- console.log();
319
+ this.log();
297
320
  // Available commands
298
- console.log(chalk.gray('Available slash commands:'));
299
- console.log(chalk.gray(' •'), chalk.cyan(`/clavix${separator}improve`), chalk.gray('- Smart prompt optimization'));
300
- console.log(chalk.gray(' •'), chalk.cyan(`/clavix${separator}prd`), chalk.gray('- Generate PRD through guided questions'));
301
- console.log(chalk.gray(' •'), chalk.cyan(`/clavix${separator}plan`), chalk.gray('- Create task breakdown from PRD'));
302
- console.log(chalk.gray(' •'), chalk.cyan(`/clavix${separator}implement`), chalk.gray('- Execute tasks or prompts'));
303
- console.log(chalk.gray('\nNext steps:'));
304
- console.log(chalk.gray(' • Slash commands are now available in your AI agent'));
305
- console.log(chalk.gray(' • Run'), chalk.cyan('clavix diagnose'), chalk.gray('to verify installation'));
306
- console.log();
321
+ this.log(chalk.gray('Available slash commands:'));
322
+ this.log(chalk.gray(' •'), chalk.cyan(`/clavix${separator}improve`), chalk.gray('- Smart prompt optimization'));
323
+ this.log(chalk.gray(' •'), chalk.cyan(`/clavix${separator}prd`), chalk.gray('- Generate PRD through guided questions'));
324
+ this.log(chalk.gray(' •'), chalk.cyan(`/clavix${separator}plan`), chalk.gray('- Create task breakdown from PRD'));
325
+ this.log(chalk.gray(' •'), chalk.cyan(`/clavix${separator}implement`), chalk.gray('- Execute tasks or prompts'));
326
+ this.log(chalk.gray('\nNext steps:'));
327
+ this.log(chalk.gray(' • Slash commands are now available in your AI agent'));
328
+ this.log(chalk.gray(' • Run'), chalk.cyan('clavix diagnose'), chalk.gray('to verify installation'));
329
+ this.log();
307
330
  }
308
331
  catch (error) {
309
332
  const { getErrorMessage, toError } = await import('../../utils/error-utils.js');
310
- console.error(chalk.red('\n✗ Initialization failed:'), getErrorMessage(error));
333
+ this.log(chalk.red('\n✗ Initialization failed:'), getErrorMessage(error));
311
334
  if (error &&
312
335
  typeof error === 'object' &&
313
336
  'hint' in error &&
314
337
  typeof error.hint === 'string') {
315
- console.error(chalk.yellow(' Hint:'), error.hint);
338
+ this.log(chalk.yellow(' Hint:'), error.hint);
316
339
  }
317
340
  throw toError(error);
318
341
  }
@@ -324,36 +347,36 @@ export default class Init extends Command {
324
347
  for (const integrationName of integrations) {
325
348
  // Handle doc generators (not adapters)
326
349
  if (integrationName === 'agents-md') {
327
- console.log(chalk.gray(' ✓ Regenerating AGENTS.md...'));
350
+ this.log(chalk.gray(' ✓ Regenerating AGENTS.md...'));
328
351
  await AgentsMdGenerator.generate();
329
352
  continue;
330
353
  }
331
354
  if (integrationName === 'copilot-instructions') {
332
- console.log(chalk.gray(' ✓ Regenerating .github/copilot-instructions.md...'));
355
+ this.log(chalk.gray(' ✓ Regenerating .github/copilot-instructions.md...'));
333
356
  await CopilotInstructionsGenerator.generate();
334
357
  continue;
335
358
  }
336
359
  if (integrationName === 'octo-md') {
337
- console.log(chalk.gray(' ✓ Regenerating OCTO.md...'));
360
+ this.log(chalk.gray(' ✓ Regenerating OCTO.md...'));
338
361
  await OctoMdGenerator.generate();
339
362
  continue;
340
363
  }
341
364
  if (integrationName === 'warp-md') {
342
- console.log(chalk.gray(' ✓ Regenerating WARP.md...'));
365
+ this.log(chalk.gray(' ✓ Regenerating WARP.md...'));
343
366
  await WarpMdGenerator.generate();
344
367
  continue;
345
368
  }
346
369
  // Handle regular adapters
347
370
  const adapter = agentManager.getAdapter(integrationName);
348
371
  if (!adapter) {
349
- console.log(chalk.yellow(` ⚠ Unknown integration: ${integrationName}`));
372
+ this.log(chalk.yellow(` ⚠ Unknown integration: ${integrationName}`));
350
373
  continue;
351
374
  }
352
- console.log(chalk.gray(` ✓ Regenerating ${adapter.displayName} commands...`));
375
+ this.log(chalk.gray(` ✓ Regenerating ${adapter.displayName} commands...`));
353
376
  // Remove existing commands before regenerating
354
377
  const removed = await adapter.removeAllCommands();
355
378
  if (removed > 0) {
356
- console.log(chalk.gray(` Removed ${removed} existing command(s)`));
379
+ this.log(chalk.gray(` Removed ${removed} existing command(s)`));
357
380
  }
358
381
  // Generate slash commands
359
382
  const templates = await this.generateSlashCommands(adapter);
@@ -364,17 +387,17 @@ export default class Init extends Command {
364
387
  for (const file of legacyFiles) {
365
388
  await FileSystem.remove(file);
366
389
  }
367
- console.log(chalk.gray(` Cleaned ${legacyFiles.length} legacy file(s)`));
390
+ this.log(chalk.gray(` Cleaned ${legacyFiles.length} legacy file(s)`));
368
391
  }
369
392
  // Re-inject documentation blocks (Claude Code only)
370
393
  if (integrationName === 'claude-code') {
371
- console.log(chalk.gray(' ✓ Updating CLAUDE.md documentation...'));
394
+ this.log(chalk.gray(' ✓ Updating CLAUDE.md documentation...'));
372
395
  await this.injectDocumentation(adapter);
373
396
  }
374
397
  }
375
398
  // Regenerate instructions folder if needed
376
399
  if (InstructionsGenerator.needsGeneration(integrations)) {
377
- console.log(chalk.gray('\n📁 Updating .clavix/instructions/ reference folder...'));
400
+ this.log(chalk.gray('\n📁 Updating .clavix/instructions/ reference folder...'));
378
401
  await InstructionsGenerator.generate();
379
402
  }
380
403
  }
@@ -529,9 +552,9 @@ To reconfigure integrations, run \`clavix init\` again.
529
552
  const relativePaths = legacyFiles
530
553
  .map((file) => path.relative(process.cwd(), file))
531
554
  .sort((a, b) => a.localeCompare(b));
532
- console.log(chalk.gray(` ⚠ Found ${relativePaths.length} deprecated command file(s):`));
555
+ this.log(chalk.gray(` ⚠ Found ${relativePaths.length} deprecated command file(s):`));
533
556
  for (const file of relativePaths) {
534
- console.log(chalk.gray(` • ${file}`));
557
+ this.log(chalk.gray(` • ${file}`));
535
558
  }
536
559
  const { removeLegacy } = await inquirer.prompt([
537
560
  {
@@ -542,12 +565,12 @@ To reconfigure integrations, run \`clavix init\` again.
542
565
  },
543
566
  ]);
544
567
  if (!removeLegacy) {
545
- console.log(chalk.gray(' ⊗ Kept legacy files (deprecated naming retained)'));
568
+ this.log(chalk.gray(' ⊗ Kept legacy files (deprecated naming retained)'));
546
569
  return;
547
570
  }
548
571
  for (const file of legacyFiles) {
549
572
  await FileSystem.remove(file);
550
- console.log(chalk.gray(` ✓ Removed ${path.relative(process.cwd(), file)}`));
573
+ this.log(chalk.gray(` ✓ Removed ${path.relative(process.cwd(), file)}`));
551
574
  }
552
575
  }
553
576
  async injectDocumentation(adapter) {
@@ -11,6 +11,7 @@ import { WarpMdGenerator } from '../../core/adapters/warp-md-generator.js';
11
11
  import { InstructionsGenerator } from '../../core/adapters/instructions-generator.js';
12
12
  import { collectLegacyCommandFiles } from '../../utils/legacy-command-cleanup.js';
13
13
  import { CLAVIX_BLOCK_START, CLAVIX_BLOCK_END } from '../../constants.js';
14
+ import { validateUserConfig, formatZodErrors } from '../../utils/schemas.js';
14
15
  export default class Update extends Command {
15
16
  static description = 'Update managed blocks and slash commands';
16
17
  static examples = [
@@ -46,7 +47,39 @@ export default class Update extends Command {
46
47
  }
47
48
  this.log(chalk.bold.cyan('🔄 Updating Clavix integration...\n'));
48
49
  // Load config to determine integrations
49
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
50
+ let config;
51
+ try {
52
+ const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
53
+ // Validate config structure with Zod
54
+ const validationResult = validateUserConfig(rawConfig);
55
+ if (!validationResult.success && validationResult.errors) {
56
+ const errorMessages = formatZodErrors(validationResult.errors);
57
+ this.error(chalk.red(`Invalid config file structure: ${configPath}`) +
58
+ '\n' +
59
+ errorMessages.map((e) => chalk.gray(` - ${e}`)).join('\n') +
60
+ '\n' +
61
+ chalk.yellow(' Hint: Delete .clavix/config.json and run ') +
62
+ chalk.cyan('clavix init') +
63
+ chalk.yellow(' to regenerate.'));
64
+ }
65
+ // Log warnings (non-blocking)
66
+ if (validationResult.warnings) {
67
+ for (const warning of validationResult.warnings) {
68
+ this.log(chalk.yellow(` ⚠ ${warning}`));
69
+ }
70
+ }
71
+ config = validationResult.data;
72
+ }
73
+ catch (error) {
74
+ const { getErrorMessage } = await import('../../utils/error-utils.js');
75
+ this.error(chalk.red(`Invalid JSON in config file: ${configPath}`) +
76
+ '\n' +
77
+ chalk.gray(` Error: ${getErrorMessage(error)}`) +
78
+ '\n' +
79
+ chalk.yellow(' Hint: Check for syntax errors or delete .clavix/config.json and run ') +
80
+ chalk.cyan('clavix init') +
81
+ chalk.yellow(' to regenerate.'));
82
+ }
50
83
  const integrations = config.integrations || config.providers || ['claude-code'];
51
84
  const agentManager = new AgentManager();
52
85
  const updateDocs = flags['docs-only'] || (!flags['docs-only'] && !flags['commands-only']);
@@ -13,10 +13,16 @@ export default class Version extends Command {
13
13
  try {
14
14
  const packageJsonPath = path.join(__dirname, '../../../package.json');
15
15
  const packageJson = await fs.readJson(packageJsonPath);
16
- console.log(chalk.cyan(`\nClavix v${packageJson.version}\n`));
16
+ this.log(chalk.cyan(`\nClavix v${packageJson.version}\n`));
17
17
  }
18
- catch {
19
- console.log(chalk.red('\n✗ Could not determine version\n'));
18
+ catch (error) {
19
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error reading package.json';
20
+ this.error(chalk.red('Could not determine version') +
21
+ '\n' +
22
+ chalk.gray(` Error: ${errorMessage}`) +
23
+ '\n' +
24
+ chalk.yellow(' Hint: Try reinstalling Clavix with ') +
25
+ chalk.cyan('npm install -g clavix'));
20
26
  }
21
27
  }
22
28
  }