@paths.design/caws-cli 8.0.1 → 8.1.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.
- package/dist/commands/archive.d.ts +2 -1
- package/dist/commands/archive.d.ts.map +1 -1
- package/dist/commands/archive.js +114 -6
- package/dist/commands/burnup.d.ts.map +1 -1
- package/dist/commands/burnup.js +109 -10
- package/dist/commands/diagnose.js +1 -1
- package/dist/commands/mode.js +24 -14
- package/dist/commands/provenance.js +216 -93
- package/dist/commands/quality-gates.d.ts.map +1 -1
- package/dist/commands/quality-gates.js +3 -1
- package/dist/commands/specs.js +184 -6
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +134 -10
- package/dist/commands/templates.js +2 -2
- package/dist/error-handler.js +6 -98
- package/dist/generators/jest-config-generator.js +242 -0
- package/dist/index.js +4 -7
- package/dist/minimal-cli.js +3 -1
- package/dist/scaffold/claude-hooks.js +316 -0
- package/dist/scaffold/index.js +18 -0
- package/dist/templates/.claude/README.md +190 -0
- package/dist/templates/.claude/hooks/audit.sh +96 -0
- package/dist/templates/.claude/hooks/block-dangerous.sh +90 -0
- package/dist/templates/.claude/hooks/naming-check.sh +97 -0
- package/dist/templates/.claude/hooks/quality-check.sh +68 -0
- package/dist/templates/.claude/hooks/scan-secrets.sh +85 -0
- package/dist/templates/.claude/hooks/scope-guard.sh +105 -0
- package/dist/templates/.claude/hooks/validate-spec.sh +76 -0
- package/dist/templates/.claude/settings.json +95 -0
- package/dist/test-analysis.js +203 -10
- package/dist/utils/error-categories.js +210 -0
- package/dist/utils/quality-gates-utils.js +402 -0
- package/dist/utils/typescript-detector.js +36 -90
- package/dist/validation/spec-validation.js +59 -6
- package/package.json +5 -3
- package/templates/.claude/README.md +190 -0
- package/templates/.claude/hooks/audit.sh +96 -0
- package/templates/.claude/hooks/block-dangerous.sh +90 -0
- package/templates/.claude/hooks/naming-check.sh +97 -0
- package/templates/.claude/hooks/quality-check.sh +68 -0
- package/templates/.claude/hooks/scan-secrets.sh +85 -0
- package/templates/.claude/hooks/scope-guard.sh +105 -0
- package/templates/.claude/hooks/validate-spec.sh +76 -0
- package/templates/.claude/settings.json +95 -0
package/dist/commands/status.js
CHANGED
|
@@ -8,6 +8,7 @@ const fs = require('fs-extra');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const yaml = require('js-yaml');
|
|
10
10
|
const chalk = require('chalk');
|
|
11
|
+
// child_process removed - execSync no longer used directly
|
|
11
12
|
const { safeAsync, outputResult } = require('../error-handler');
|
|
12
13
|
const { parallel } = require('../utils/async-utils');
|
|
13
14
|
|
|
@@ -181,14 +182,114 @@ async function loadWaiverStatus() {
|
|
|
181
182
|
* @returns {Promise<Object>} Quality gates status
|
|
182
183
|
*/
|
|
183
184
|
async function checkQualityGates() {
|
|
184
|
-
// For now, return a placeholder
|
|
185
|
-
// Quality gates are available via CLI or MCP
|
|
186
185
|
return {
|
|
187
186
|
checked: false,
|
|
188
187
|
message: 'Run: caws quality-gates or use MCP tool caws_quality_gates_run for full gate status',
|
|
189
188
|
};
|
|
190
189
|
}
|
|
191
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Get test coverage from coverage reports
|
|
193
|
+
* Looks for common coverage report locations and formats
|
|
194
|
+
* @returns {Promise<Object>} Coverage data with percentage and details
|
|
195
|
+
*/
|
|
196
|
+
async function getTestCoverage() {
|
|
197
|
+
const coverageLocations = [
|
|
198
|
+
// Jest/Istanbul coverage
|
|
199
|
+
{ path: 'coverage/coverage-summary.json', type: 'istanbul' },
|
|
200
|
+
{ path: 'coverage/lcov-report/index.html', type: 'lcov-html' },
|
|
201
|
+
{ path: 'coverage/coverage-final.json', type: 'istanbul-final' },
|
|
202
|
+
// NYC coverage
|
|
203
|
+
{ path: '.nyc_output/coverage-summary.json', type: 'nyc' },
|
|
204
|
+
// C8 coverage
|
|
205
|
+
{ path: 'coverage/c8/coverage-summary.json', type: 'c8' },
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
for (const loc of coverageLocations) {
|
|
210
|
+
const coveragePath = path.join(process.cwd(), loc.path);
|
|
211
|
+
if (await fs.pathExists(coveragePath)) {
|
|
212
|
+
if (loc.type === 'istanbul' || loc.type === 'nyc' || loc.type === 'c8') {
|
|
213
|
+
const content = await fs.readFile(coveragePath, 'utf8');
|
|
214
|
+
const data = JSON.parse(content);
|
|
215
|
+
|
|
216
|
+
// Istanbul/NYC format has a "total" key with coverage percentages
|
|
217
|
+
if (data.total) {
|
|
218
|
+
const lines = data.total.lines?.pct || 0;
|
|
219
|
+
const statements = data.total.statements?.pct || 0;
|
|
220
|
+
const branches = data.total.branches?.pct || 0;
|
|
221
|
+
const functions = data.total.functions?.pct || 0;
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
available: true,
|
|
225
|
+
percentage: Math.round((lines + statements + branches + functions) / 4),
|
|
226
|
+
lines: Math.round(lines),
|
|
227
|
+
statements: Math.round(statements),
|
|
228
|
+
branches: Math.round(branches),
|
|
229
|
+
functions: Math.round(functions),
|
|
230
|
+
source: loc.path,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
} else if (loc.type === 'istanbul-final') {
|
|
234
|
+
// coverage-final.json has per-file data, need to aggregate
|
|
235
|
+
const content = await fs.readFile(coveragePath, 'utf8');
|
|
236
|
+
const data = JSON.parse(content);
|
|
237
|
+
|
|
238
|
+
let totalStatements = 0;
|
|
239
|
+
let coveredStatements = 0;
|
|
240
|
+
|
|
241
|
+
for (const file of Object.values(data)) {
|
|
242
|
+
const s = file.s || {};
|
|
243
|
+
for (const count of Object.values(s)) {
|
|
244
|
+
totalStatements++;
|
|
245
|
+
if (count > 0) coveredStatements++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (totalStatements > 0) {
|
|
250
|
+
const percentage = Math.round((coveredStatements / totalStatements) * 100);
|
|
251
|
+
return {
|
|
252
|
+
available: true,
|
|
253
|
+
percentage,
|
|
254
|
+
source: loc.path,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Try running test coverage command if no reports found
|
|
262
|
+
try {
|
|
263
|
+
// Check if package.json has a coverage script
|
|
264
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
265
|
+
if (await fs.pathExists(pkgPath)) {
|
|
266
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
267
|
+
if (pkg.scripts && (pkg.scripts.coverage || pkg.scripts['test:coverage'])) {
|
|
268
|
+
return {
|
|
269
|
+
available: false,
|
|
270
|
+
percentage: null,
|
|
271
|
+
message: 'Run npm run coverage to generate coverage report',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
// Ignore package.json errors
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
available: false,
|
|
281
|
+
percentage: null,
|
|
282
|
+
message: 'No coverage report found',
|
|
283
|
+
};
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return {
|
|
286
|
+
available: false,
|
|
287
|
+
percentage: null,
|
|
288
|
+
message: `Error reading coverage: ${error.message}`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
192
293
|
/**
|
|
193
294
|
* Get time since last update
|
|
194
295
|
* @param {string} timestamp - ISO timestamp
|
|
@@ -388,7 +489,7 @@ function getProgressColor(percentage) {
|
|
|
388
489
|
* @param {Object} data - Status data
|
|
389
490
|
* @param {string} currentMode - Current CAWS mode
|
|
390
491
|
*/
|
|
391
|
-
function displayVisualStatus(data, currentMode) {
|
|
492
|
+
async function displayVisualStatus(data, currentMode) {
|
|
392
493
|
const { spec, specs, hooks, provenance, waivers, gates } = data;
|
|
393
494
|
const modes = require('../config/modes');
|
|
394
495
|
const tierConfig = modes.getTier(currentMode);
|
|
@@ -497,12 +598,35 @@ function displayVisualStatus(data, currentMode) {
|
|
|
497
598
|
);
|
|
498
599
|
}
|
|
499
600
|
|
|
500
|
-
// Test Coverage
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
601
|
+
// Test Coverage
|
|
602
|
+
const coverage = await getTestCoverage();
|
|
603
|
+
if (coverage.available && coverage.percentage !== null) {
|
|
604
|
+
const coverageColor =
|
|
605
|
+
coverage.percentage >= 80
|
|
606
|
+
? chalk.green
|
|
607
|
+
: coverage.percentage >= 50
|
|
608
|
+
? chalk.yellow
|
|
609
|
+
: chalk.red;
|
|
610
|
+
const coverageBar = createProgressBar(coverage.percentage, 100);
|
|
611
|
+
console.log(
|
|
612
|
+
chalk.gray(
|
|
613
|
+
` Test Coverage: ${coverageColor(`${coverage.percentage}%`)} ${coverageBar}`
|
|
614
|
+
)
|
|
615
|
+
);
|
|
616
|
+
if (coverage.lines !== undefined) {
|
|
617
|
+
console.log(
|
|
618
|
+
chalk.gray(
|
|
619
|
+
` Lines: ${coverage.lines}% | Branches: ${coverage.branches}% | Functions: ${coverage.functions}%`
|
|
620
|
+
)
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
} else {
|
|
624
|
+
console.log(
|
|
625
|
+
chalk.gray(
|
|
626
|
+
` Test Coverage: ${chalk.yellow('N/A')} ${createProgressBar(0, 100)} ${chalk.gray(coverage.message || 'No report')}`
|
|
627
|
+
)
|
|
628
|
+
);
|
|
629
|
+
}
|
|
506
630
|
|
|
507
631
|
// Risk Tier Indicator
|
|
508
632
|
const riskColor =
|
|
@@ -839,7 +963,7 @@ async function statusCommand(options = {}) {
|
|
|
839
963
|
console.log(JSON.stringify(result, null, 2));
|
|
840
964
|
} else {
|
|
841
965
|
// Visual output
|
|
842
|
-
displayVisualStatus(
|
|
966
|
+
await displayVisualStatus(
|
|
843
967
|
{
|
|
844
968
|
spec,
|
|
845
969
|
specs,
|
|
@@ -149,7 +149,7 @@ function listTemplates() {
|
|
|
149
149
|
const totalTemplates = Object.keys(templates).length;
|
|
150
150
|
|
|
151
151
|
if (totalAvailable < totalTemplates) {
|
|
152
|
-
console.log(chalk.yellow(`\n⏳ ${totalTemplates - totalAvailable} templates
|
|
152
|
+
console.log(chalk.yellow(`\n⏳ ${totalTemplates - totalAvailable} additional templates in development`));
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
console.log(chalk.blue('\n📚 Learn more:'));
|
|
@@ -181,7 +181,7 @@ function showTemplateInfo(templateId) {
|
|
|
181
181
|
console.log(chalk.white(`Risk Tier: ${template.tier}`));
|
|
182
182
|
console.log(
|
|
183
183
|
chalk.white(
|
|
184
|
-
`Status: ${template.available ? chalk.green('Available') : chalk.yellow('
|
|
184
|
+
`Status: ${template.available ? chalk.green('Available') : chalk.yellow('In Development')}`
|
|
185
185
|
)
|
|
186
186
|
);
|
|
187
187
|
|
package/dist/error-handler.js
CHANGED
|
@@ -5,104 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
PERMISSION: 'permission',
|
|
15
|
-
FILESYSTEM: 'filesystem',
|
|
16
|
-
NETWORK: 'network',
|
|
17
|
-
CONFIGURATION: 'configuration',
|
|
18
|
-
USER_INPUT: 'user_input',
|
|
19
|
-
DEPENDENCY: 'dependency',
|
|
20
|
-
UNKNOWN: 'unknown',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Error code mappings for common system errors
|
|
25
|
-
*/
|
|
26
|
-
const ERROR_CODES = {
|
|
27
|
-
EACCES: ERROR_CATEGORIES.PERMISSION,
|
|
28
|
-
EPERM: ERROR_CATEGORIES.PERMISSION,
|
|
29
|
-
ENOENT: ERROR_CATEGORIES.FILESYSTEM,
|
|
30
|
-
ENOTFOUND: ERROR_CATEGORIES.NETWORK,
|
|
31
|
-
ECONNREFUSED: ERROR_CATEGORIES.NETWORK,
|
|
32
|
-
ETIMEDOUT: ERROR_CATEGORIES.NETWORK,
|
|
33
|
-
ENOSPC: ERROR_CATEGORIES.FILESYSTEM,
|
|
34
|
-
EEXIST: ERROR_CATEGORIES.FILESYSTEM,
|
|
35
|
-
EISDIR: ERROR_CATEGORIES.FILESYSTEM,
|
|
36
|
-
ENOTDIR: ERROR_CATEGORIES.FILESYSTEM,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get error category from error object or message
|
|
41
|
-
* @param {Error|string} error - Error object or message
|
|
42
|
-
* @returns {string} Error category
|
|
43
|
-
*/
|
|
44
|
-
function getErrorCategory(error) {
|
|
45
|
-
const errorMessage = typeof error === 'string' ? error : error.message;
|
|
46
|
-
const errorCode = typeof error === 'object' && error.code ? error.code : null;
|
|
47
|
-
|
|
48
|
-
// Check error codes first
|
|
49
|
-
if (errorCode && ERROR_CODES[errorCode]) {
|
|
50
|
-
return ERROR_CODES[errorCode];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Check message patterns
|
|
54
|
-
const lowerMessage = errorMessage.toLowerCase();
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
lowerMessage.includes('validation') ||
|
|
58
|
-
lowerMessage.includes('invalid') ||
|
|
59
|
-
lowerMessage.includes('required')
|
|
60
|
-
) {
|
|
61
|
-
return ERROR_CATEGORIES.VALIDATION;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
lowerMessage.includes('permission') ||
|
|
66
|
-
lowerMessage.includes('access') ||
|
|
67
|
-
lowerMessage.includes('denied')
|
|
68
|
-
) {
|
|
69
|
-
return ERROR_CATEGORIES.PERMISSION;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
lowerMessage.includes('file') ||
|
|
74
|
-
lowerMessage.includes('directory') ||
|
|
75
|
-
lowerMessage.includes('path')
|
|
76
|
-
) {
|
|
77
|
-
return ERROR_CATEGORIES.FILESYSTEM;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
lowerMessage.includes('network') ||
|
|
82
|
-
lowerMessage.includes('connection') ||
|
|
83
|
-
lowerMessage.includes('timeout')
|
|
84
|
-
) {
|
|
85
|
-
return ERROR_CATEGORIES.NETWORK;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
lowerMessage.includes('config') ||
|
|
90
|
-
lowerMessage.includes('setting') ||
|
|
91
|
-
lowerMessage.includes('option')
|
|
92
|
-
) {
|
|
93
|
-
return ERROR_CATEGORIES.CONFIGURATION;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
lowerMessage.includes('input') ||
|
|
98
|
-
lowerMessage.includes('prompt') ||
|
|
99
|
-
lowerMessage.includes('answer')
|
|
100
|
-
) {
|
|
101
|
-
return ERROR_CATEGORIES.USER_INPUT;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return ERROR_CATEGORIES.UNKNOWN;
|
|
105
|
-
}
|
|
8
|
+
const {
|
|
9
|
+
ERROR_CATEGORIES,
|
|
10
|
+
ERROR_CODES,
|
|
11
|
+
getErrorCategory,
|
|
12
|
+
getCategorySuggestions,
|
|
13
|
+
} = require('./utils/error-categories');
|
|
106
14
|
|
|
107
15
|
/**
|
|
108
16
|
* Enhanced error class with category and recovery suggestions
|
|
@@ -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
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @fileoverview CAWS CLI - Scaffolding tool for Coding Agent Workflow System
|
|
5
|
-
* Provides commands to initialize new projects and scaffold existing ones with CAWS
|
|
5
|
+
* Provides commands to initialize new projects and scaffold existing ones with CAWS.
|
|
6
|
+
* Includes spec management, quality gates, and AI-assisted development workflows.
|
|
6
7
|
* @author @darianrosebrook
|
|
7
8
|
*/
|
|
8
9
|
|
|
@@ -435,12 +436,8 @@ program
|
|
|
435
436
|
.option('-v, --verbose', 'Show detailed error information', false)
|
|
436
437
|
.action(qualityMonitorCommand);
|
|
437
438
|
|
|
438
|
-
// Troubleshoot command
|
|
439
|
-
//
|
|
440
|
-
// .command('troubleshoot [guide]')
|
|
441
|
-
// .description('Display troubleshooting guides for common CAWS issues')
|
|
442
|
-
// .option('-l, --list', 'List all available troubleshooting guides', false)
|
|
443
|
-
// .action(troubleshootCommand);
|
|
439
|
+
// Troubleshoot command available via: caws diagnose --troubleshoot <guide>
|
|
440
|
+
// The standalone command was consolidated into the diagnose command.
|
|
444
441
|
|
|
445
442
|
// Tool command
|
|
446
443
|
program
|
package/dist/minimal-cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @fileoverview Minimal CAWS CLI
|
|
4
|
+
* @fileoverview Minimal CAWS CLI for testing and development
|
|
5
|
+
* Provides a lightweight CLI structure with version and help commands
|
|
6
|
+
* for quick verification of CLI functionality.
|
|
5
7
|
* @author @darianrosebrook
|
|
6
8
|
*/
|
|
7
9
|
|