@paths.design/caws-cli 3.2.4 → 3.3.0

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.
@@ -137,19 +137,152 @@ async function safeAsync(operation, context = '') {
137
137
  }
138
138
  }
139
139
 
140
+ /**
141
+ * Command-specific error suggestions
142
+ */
143
+ const COMMAND_SUGGESTIONS = {
144
+ 'unknown option': (option, command) => {
145
+ const suggestions = [];
146
+
147
+ // Common typos and alternatives
148
+ const optionMap = {
149
+ '--suggestions': 'Validation includes suggestions by default. Try: caws validate',
150
+ '--suggest': 'Validation includes suggestions by default. Try: caws validate',
151
+ '--help': 'Try: caws --help or caws <command> --help',
152
+ '--json': 'For JSON output, try: caws provenance show --format=json',
153
+ '--dashboard': 'Try: caws provenance show --format=dashboard',
154
+ };
155
+
156
+ if (optionMap[option]) {
157
+ suggestions.push(optionMap[option]);
158
+ } else {
159
+ suggestions.push(`Try: caws ${command || ''} --help for available options`);
160
+ }
161
+
162
+ return suggestions;
163
+ },
164
+
165
+ 'unknown command': (command) => {
166
+ const validCommands = [
167
+ 'init',
168
+ 'validate',
169
+ 'scaffold',
170
+ 'status',
171
+ 'templates',
172
+ 'provenance',
173
+ 'hooks',
174
+ 'burnup',
175
+ 'tool',
176
+ ];
177
+ const similar = findSimilarCommand(command, validCommands);
178
+
179
+ const suggestions = [];
180
+ if (similar) {
181
+ suggestions.push(`Did you mean: caws ${similar}?`);
182
+ }
183
+ suggestions.push(
184
+ 'Available commands: init, validate, scaffold, status, templates, provenance, hooks'
185
+ );
186
+ suggestions.push('Try: caws --help for full command list');
187
+
188
+ return suggestions;
189
+ },
190
+
191
+ 'template not found': () => [
192
+ 'Templates are bundled with CAWS CLI',
193
+ 'Try: caws scaffold (should work automatically)',
194
+ 'If issue persists: npm i -g @paths.design/caws-cli@latest',
195
+ ],
196
+
197
+ 'not a caws project': () => [
198
+ 'Initialize CAWS first: caws init .',
199
+ 'Or create new project: caws init <project-name>',
200
+ 'Check for .caws/working-spec.yaml file',
201
+ ],
202
+ };
203
+
204
+ /**
205
+ * Find similar command using Levenshtein distance
206
+ * @param {string} input - User's input command
207
+ * @param {string[]} validCommands - List of valid commands
208
+ * @returns {string|null} Most similar command or null
209
+ */
210
+ function findSimilarCommand(input, validCommands) {
211
+ if (!input) return null;
212
+
213
+ let minDistance = Infinity;
214
+ let closestMatch = null;
215
+
216
+ for (const cmd of validCommands) {
217
+ const distance = levenshteinDistance(input.toLowerCase(), cmd.toLowerCase());
218
+ if (distance < minDistance && distance <= 2) {
219
+ minDistance = distance;
220
+ closestMatch = cmd;
221
+ }
222
+ }
223
+
224
+ return closestMatch;
225
+ }
226
+
227
+ /**
228
+ * Calculate Levenshtein distance between two strings
229
+ * @param {string} a - First string
230
+ * @param {string} b - Second string
231
+ * @returns {number} Edit distance
232
+ */
233
+ function levenshteinDistance(a, b) {
234
+ const matrix = [];
235
+
236
+ for (let i = 0; i <= b.length; i++) {
237
+ matrix[i] = [i];
238
+ }
239
+
240
+ for (let j = 0; j <= a.length; j++) {
241
+ matrix[0][j] = j;
242
+ }
243
+
244
+ for (let i = 1; i <= b.length; i++) {
245
+ for (let j = 1; j <= a.length; j++) {
246
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
247
+ matrix[i][j] = matrix[i - 1][j - 1];
248
+ } else {
249
+ matrix[i][j] = Math.min(
250
+ matrix[i - 1][j - 1] + 1,
251
+ matrix[i][j - 1] + 1,
252
+ matrix[i - 1][j] + 1
253
+ );
254
+ }
255
+ }
256
+ }
257
+
258
+ return matrix[b.length][a.length];
259
+ }
260
+
140
261
  /**
141
262
  * Get recovery suggestions based on error category
142
263
  * @param {Error} error - Original error
143
264
  * @param {string} category - Error category
265
+ * @param {Object} context - Additional context (command, options, etc.)
144
266
  * @returns {string[]} Array of recovery suggestions
145
267
  */
146
- function getRecoverySuggestions(error, category) {
268
+ function getRecoverySuggestions(error, category, context = {}) {
147
269
  const suggestions = [];
270
+ const errorMessage = error.message || '';
271
+
272
+ // Check for command-specific suggestions first
273
+ for (const [pattern, suggestionFn] of Object.entries(COMMAND_SUGGESTIONS)) {
274
+ if (errorMessage.toLowerCase().includes(pattern)) {
275
+ const commandSuggestions = suggestionFn(context.option, context.command);
276
+ suggestions.push(...commandSuggestions);
277
+ return suggestions;
278
+ }
279
+ }
148
280
 
281
+ // Fall back to category-based suggestions
149
282
  switch (category) {
150
283
  case ERROR_CATEGORIES.PERMISSION:
151
284
  suggestions.push('Try running the command with elevated privileges (sudo)');
152
- suggestions.push('Check file/directory permissions with `ls -la`');
285
+ suggestions.push('Check file/directory permissions with: ls -la');
153
286
  break;
154
287
 
155
288
  case ERROR_CATEGORIES.FILESYSTEM:
@@ -163,52 +296,87 @@ function getRecoverySuggestions(error, category) {
163
296
  break;
164
297
 
165
298
  case ERROR_CATEGORIES.VALIDATION:
166
- suggestions.push('Run `caws validate --suggestions` for detailed validation help');
167
- suggestions.push('Check your working spec format against the documentation');
299
+ suggestions.push('Run: caws validate for detailed validation help');
300
+ suggestions.push('Check your working spec format against the schema');
301
+ suggestions.push('See: docs/api/schema.md for specification details');
168
302
  break;
169
303
 
170
304
  case ERROR_CATEGORIES.CONFIGURATION:
171
- suggestions.push('Run `caws init --interactive` to reconfigure your project');
305
+ suggestions.push('Run: caws init --interactive to reconfigure');
172
306
  suggestions.push('Check your .caws directory and configuration files');
307
+ suggestions.push('Try: caws diagnose to identify configuration issues');
173
308
  break;
174
309
 
175
310
  case ERROR_CATEGORIES.NETWORK:
176
311
  suggestions.push('Check your internet connection');
177
312
  suggestions.push('Verify the URL/service is accessible');
313
+ suggestions.push('Try again in a few moments');
178
314
  break;
179
315
 
180
316
  default:
181
- suggestions.push('Run the command with --help for usage information');
182
- suggestions.push('Check the CAWS documentation at docs/README.md');
317
+ suggestions.push('Run: caws --help for usage information');
318
+ suggestions.push('See: docs/agents/full-guide.md for detailed documentation');
183
319
  }
184
320
 
185
321
  return suggestions;
186
322
  }
187
323
 
324
+ /**
325
+ * Get documentation link for error category
326
+ * @param {string} category - Error category
327
+ * @param {Object} context - Additional context
328
+ * @returns {string} Documentation URL
329
+ */
330
+ function getDocumentationLink(category, context = {}) {
331
+ const baseUrl = 'https://github.com/Paths-Design/coding-agent-working-standard/blob/main';
332
+
333
+ const categoryLinks = {
334
+ validation: `${baseUrl}/docs/api/schema.md`,
335
+ configuration: `${baseUrl}/docs/guides/caws-developer-guide.md`,
336
+ filesystem: `${baseUrl}/docs/agents/tutorial.md`,
337
+ permission: `${baseUrl}/SECURITY.md`,
338
+ network: `${baseUrl}/README.md#requirements`,
339
+ };
340
+
341
+ if (context.command) {
342
+ const commandLinks = {
343
+ init: `${baseUrl}/docs/agents/tutorial.md#initialization`,
344
+ validate: `${baseUrl}/docs/api/cli.md#validate`,
345
+ scaffold: `${baseUrl}/docs/api/cli.md#scaffold`,
346
+ provenance: `${baseUrl}/docs/api/cli.md#provenance`,
347
+ hooks: `${baseUrl}/docs/guides/hooks-and-agent-workflows.md`,
348
+ };
349
+
350
+ if (commandLinks[context.command]) {
351
+ return commandLinks[context.command];
352
+ }
353
+ }
354
+
355
+ return categoryLinks[category] || `${baseUrl}/docs/agents/full-guide.md`;
356
+ }
357
+
188
358
  /**
189
359
  * Handle CLI errors with consistent formatting and user guidance
190
360
  * @param {Error} error - Error to handle
361
+ * @param {Object} context - Error context (command, option, etc.)
191
362
  * @param {boolean} exit - Whether to exit the process (default: true)
192
363
  */
193
- function handleCliError(error, exit = true) {
364
+ function handleCliError(error, context = {}, exit = true) {
194
365
  const category = error.category || getErrorCategory(error);
195
- const suggestions = error.suggestions || getRecoverySuggestions(error, category);
366
+ const suggestions = error.suggestions || getRecoverySuggestions(error, category, context);
367
+ const docLink = getDocumentationLink(category, context);
196
368
 
197
369
  // Format error output
198
- console.error(chalk.red(`\n❌ Error (${category}): ${error.message}`));
370
+ console.error(chalk.red(`\n❌ ${error.message}`));
199
371
 
200
372
  if (suggestions && suggestions.length > 0) {
201
373
  console.error(chalk.yellow('\n💡 Suggestions:'));
202
374
  suggestions.forEach((suggestion) => {
203
- console.error(chalk.yellow(` ${suggestion}`));
375
+ console.error(chalk.yellow(` ${suggestion}`));
204
376
  });
205
377
  }
206
378
 
207
- console.error(
208
- chalk.gray(
209
- '\n📖 For more help, visit: https://github.com/Paths-Design/coding-agent-working-standard'
210
- )
211
- );
379
+ console.error(chalk.blue(`\n📚 Documentation: ${docLink}`));
212
380
 
213
381
  if (exit) {
214
382
  process.exit(1);
@@ -250,4 +418,7 @@ module.exports = {
250
418
  handleCliError,
251
419
  validateEnvironment,
252
420
  getRecoverySuggestions,
421
+ getDocumentationLink,
422
+ findSimilarCommand,
423
+ COMMAND_SUGGESTIONS,
253
424
  };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Configure Jest for TypeScript project
3
+ * @param {string} projectDir - Project directory path
4
+ * @param {Object} options - Configuration options
5
+ * @returns {Promise<Object>} Configuration result
6
+ */
7
+ export function configureJestForTypeScript(projectDir?: string, options?: any): Promise<any>;
8
+ /**
9
+ * Generate Jest configuration for TypeScript project
10
+ * @param {Object} options - Configuration options
11
+ * @returns {string} Jest configuration content
12
+ */
13
+ export function generateJestConfig(options?: any): string;
14
+ /**
15
+ * Generate test setup file for TypeScript
16
+ * @returns {string} Setup file content
17
+ */
18
+ export function generateTestSetup(): string;
19
+ /**
20
+ * Install Jest and TypeScript dependencies
21
+ * @param {string} projectDir - Project directory
22
+ * @param {Object} packageJson - Existing package.json
23
+ * @returns {Promise<Object>} Installation result
24
+ */
25
+ export function installJestDependencies(projectDir: string, packageJson: any): Promise<any>;
26
+ /**
27
+ * Get Jest configuration recommendations
28
+ * @param {string} projectDir - Project directory path
29
+ * @returns {Object} Recommendations
30
+ */
31
+ export function getJestRecommendations(projectDir?: string): any;
32
+ //# sourceMappingURL=jest-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jest-config.d.ts","sourceRoot":"","sources":["../../src/generators/jest-config.js"],"names":[],"mappings":"AA+GA;;;;;GAKG;AACH,wDAJW,MAAM,kBAEJ,OAAO,KAAQ,CAgE3B;AAzKD;;;;GAIG;AACH,mDAFa,MAAM,CA0ClB;AAED;;;GAGG;AACH,qCAFa,MAAM,CAiBlB;AAED;;;;;GAKG;AACH,oDAJW,MAAM,qBAEJ,OAAO,KAAQ,CA2B3B;AAwED;;;;GAIG;AACH,oDAHW,MAAM,OAkDhB"}
@@ -0,0 +1,242 @@
1
+ /**
2
+ * @fileoverview Jest Configuration Generator
3
+ * Generates Jest configuration for TypeScript projects
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+ const chalk = require('chalk');
10
+
11
+ /**
12
+ * Generate Jest configuration for TypeScript project
13
+ * @param {Object} options - Configuration options
14
+ * @returns {string} Jest configuration content
15
+ */
16
+ function generateJestConfig(options = {}) {
17
+ const {
18
+ preset = 'ts-jest',
19
+ testEnvironment = 'node',
20
+ rootDir = '.',
21
+ testMatch = ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
22
+ moduleFileExtensions = ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
23
+ collectCoverageFrom = ['src/**/*.ts', '!src/**/*.d.ts', '!src/**/*.test.ts'],
24
+ coverageThreshold = {
25
+ global: {
26
+ branches: 80,
27
+ functions: 80,
28
+ lines: 80,
29
+ statements: 80,
30
+ },
31
+ },
32
+ } = options;
33
+
34
+ const config = {
35
+ preset,
36
+ testEnvironment,
37
+ rootDir,
38
+ testMatch,
39
+ moduleFileExtensions,
40
+ collectCoverageFrom,
41
+ coverageThreshold,
42
+ transform: {
43
+ '^.+\\.tsx?$': [
44
+ 'ts-jest',
45
+ {
46
+ tsconfig: 'tsconfig.json',
47
+ },
48
+ ],
49
+ },
50
+ moduleNameMapper: {
51
+ '^@/(.*)$': '<rootDir>/src/$1',
52
+ },
53
+ };
54
+
55
+ return `module.exports = ${JSON.stringify(config, null, 2)};\n`;
56
+ }
57
+
58
+ /**
59
+ * Generate test setup file for TypeScript
60
+ * @returns {string} Setup file content
61
+ */
62
+ function generateTestSetup() {
63
+ return `/**
64
+ * Jest setup file for TypeScript tests
65
+ * @author @darianrosebrook
66
+ */
67
+
68
+ // Add custom matchers or global test setup here
69
+ beforeAll(() => {
70
+ // Global setup
71
+ });
72
+
73
+ afterAll(() => {
74
+ // Global teardown
75
+ });
76
+ `;
77
+ }
78
+
79
+ /**
80
+ * Install Jest and TypeScript dependencies
81
+ * @param {string} projectDir - Project directory
82
+ * @param {Object} packageJson - Existing package.json
83
+ * @returns {Promise<Object>} Installation result
84
+ */
85
+ async function installJestDependencies(projectDir, packageJson) {
86
+ const dependencies = ['jest', '@types/jest', 'ts-jest'];
87
+
88
+ // Check which dependencies are already installed
89
+ const allDeps = {
90
+ ...packageJson.dependencies,
91
+ ...packageJson.devDependencies,
92
+ };
93
+
94
+ const toInstall = dependencies.filter((dep) => !(dep in allDeps));
95
+
96
+ if (toInstall.length === 0) {
97
+ return {
98
+ installed: false,
99
+ message: 'All Jest dependencies already installed',
100
+ dependencies: [],
101
+ };
102
+ }
103
+
104
+ return {
105
+ installed: false,
106
+ needsInstall: true,
107
+ dependencies: toInstall,
108
+ installCommand: `npm install --save-dev ${toInstall.join(' ')}`,
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Configure Jest for TypeScript project
114
+ * @param {string} projectDir - Project directory path
115
+ * @param {Object} options - Configuration options
116
+ * @returns {Promise<Object>} Configuration result
117
+ */
118
+ async function configureJestForTypeScript(projectDir = process.cwd(), options = {}) {
119
+ const { force = false, quiet = false } = options;
120
+
121
+ // Check if Jest config already exists
122
+ const jestConfigPath = path.join(projectDir, 'jest.config.js');
123
+ if (fs.existsSync(jestConfigPath) && !force) {
124
+ return {
125
+ configured: false,
126
+ skipped: true,
127
+ message: 'Jest configuration already exists',
128
+ path: jestConfigPath,
129
+ };
130
+ }
131
+
132
+ // Generate Jest config
133
+ const jestConfig = generateJestConfig();
134
+ await fs.writeFile(jestConfigPath, jestConfig);
135
+
136
+ if (!quiet) {
137
+ console.log(chalk.green('✅ Created jest.config.js'));
138
+ }
139
+
140
+ // Generate test setup file
141
+ const setupPath = path.join(projectDir, 'tests', 'setup.ts');
142
+ await fs.ensureDir(path.join(projectDir, 'tests'));
143
+ await fs.writeFile(setupPath, generateTestSetup());
144
+
145
+ if (!quiet) {
146
+ console.log(chalk.green('✅ Created tests/setup.ts'));
147
+ }
148
+
149
+ // Update package.json with test script if needed
150
+ const packageJsonPath = path.join(projectDir, 'package.json');
151
+ if (fs.existsSync(packageJsonPath)) {
152
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
153
+
154
+ if (!packageJson.scripts) {
155
+ packageJson.scripts = {};
156
+ }
157
+
158
+ if (!packageJson.scripts.test) {
159
+ packageJson.scripts.test = 'jest';
160
+ packageJson.scripts['test:coverage'] = 'jest --coverage';
161
+ packageJson.scripts['test:watch'] = 'jest --watch';
162
+
163
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
164
+
165
+ if (!quiet) {
166
+ console.log(chalk.green('✅ Added test scripts to package.json'));
167
+ }
168
+ }
169
+ }
170
+
171
+ return {
172
+ configured: true,
173
+ files: [jestConfigPath, setupPath],
174
+ nextSteps: [
175
+ 'Install dependencies: npm install --save-dev jest @types/jest ts-jest',
176
+ 'Run tests: npm test',
177
+ 'Run with coverage: npm run test:coverage',
178
+ ],
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Get Jest configuration recommendations
184
+ * @param {string} projectDir - Project directory path
185
+ * @returns {Object} Recommendations
186
+ */
187
+ function getJestRecommendations(projectDir = process.cwd()) {
188
+ const recommendations = [];
189
+ const hasJestConfig = fs.existsSync(path.join(projectDir, 'jest.config.js'));
190
+ const packageJsonPath = path.join(projectDir, 'package.json');
191
+
192
+ if (fs.existsSync(packageJsonPath)) {
193
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
194
+ const allDeps = {
195
+ ...packageJson.dependencies,
196
+ ...packageJson.devDependencies,
197
+ };
198
+
199
+ if (!hasJestConfig && !('jest' in allDeps)) {
200
+ recommendations.push({
201
+ type: 'missing_framework',
202
+ severity: 'high',
203
+ message: 'No testing framework detected',
204
+ fix: 'Install Jest: npm install --save-dev jest @types/jest ts-jest',
205
+ autoFixable: false,
206
+ });
207
+ }
208
+
209
+ if ('typescript' in allDeps && 'jest' in allDeps && !('ts-jest' in allDeps)) {
210
+ recommendations.push({
211
+ type: 'missing_ts_jest',
212
+ severity: 'high',
213
+ message: 'TypeScript project with Jest but missing ts-jest',
214
+ fix: 'Install ts-jest: npm install --save-dev ts-jest',
215
+ autoFixable: false,
216
+ });
217
+ }
218
+
219
+ if (!hasJestConfig && 'jest' in allDeps) {
220
+ recommendations.push({
221
+ type: 'missing_config',
222
+ severity: 'medium',
223
+ message: 'Jest installed but not configured',
224
+ fix: 'Run: caws scaffold to generate Jest configuration',
225
+ autoFixable: true,
226
+ });
227
+ }
228
+ }
229
+
230
+ return {
231
+ hasIssues: recommendations.length > 0,
232
+ recommendations,
233
+ };
234
+ }
235
+
236
+ module.exports = {
237
+ configureJestForTypeScript,
238
+ generateJestConfig,
239
+ generateTestSetup,
240
+ installJestDependencies,
241
+ getJestRecommendations,
242
+ };