@paths.design/caws-cli 7.0.2 → 7.0.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.
- package/dist/budget-derivation.js +5 -4
- package/dist/commands/diagnose.js +24 -19
- package/dist/commands/init.js +51 -4
- package/dist/commands/specs.js +40 -1
- package/dist/commands/status.js +2 -2
- package/dist/commands/tool.js +2 -3
- package/dist/config/index.js +17 -8
- package/dist/generators/working-spec.js +19 -6
- package/dist/scaffold/git-hooks.js +127 -29
- package/dist/scaffold/index.js +53 -7
- package/dist/templates/.caws/tools/README.md +20 -0
- package/dist/templates/.cursor/README.md +311 -0
- package/dist/templates/.cursor/hooks/audit.sh +55 -0
- package/dist/templates/.cursor/hooks/block-dangerous.sh +83 -0
- package/dist/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/dist/templates/.cursor/hooks/caws-scope-guard.sh +130 -0
- package/dist/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/dist/templates/.cursor/hooks/format.sh +38 -0
- package/dist/templates/.cursor/hooks/naming-check.sh +64 -0
- package/dist/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/dist/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/dist/templates/.cursor/hooks/validate-spec.sh +83 -0
- package/dist/templates/.cursor/hooks.json +59 -0
- package/dist/templates/.cursor/rules/00-claims-verification.mdc +144 -0
- package/dist/templates/.cursor/rules/01-working-style.mdc +50 -0
- package/dist/templates/.cursor/rules/02-quality-gates.mdc +370 -0
- package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
- package/dist/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
- package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
- package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
- package/dist/templates/.cursor/rules/07-process-ops.mdc +20 -0
- package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
- package/dist/templates/.cursor/rules/09-docstrings.mdc +89 -0
- package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +390 -0
- package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +385 -0
- package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +516 -0
- package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +588 -0
- package/dist/templates/.cursor/rules/README.md +148 -0
- package/dist/templates/.github/copilot/instructions.md +311 -0
- package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/dist/templates/.vscode/launch.json +56 -0
- package/dist/templates/.vscode/settings.json +93 -0
- package/dist/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/dist/templates/COMMIT_CONVENTIONS.md +86 -0
- package/dist/templates/OIDC_SETUP.md +300 -0
- package/dist/templates/agents.md +1047 -0
- package/dist/templates/codemod/README.md +1 -0
- package/dist/templates/codemod/test.js +93 -0
- package/dist/templates/docs/README.md +150 -0
- package/dist/templates/scripts/quality-gates/check-god-objects.js +146 -0
- package/dist/templates/scripts/quality-gates/run-quality-gates.js +50 -0
- package/dist/templates/scripts/v3/analysis/todo_analyzer.py +1997 -0
- package/dist/tool-loader.js +6 -1
- package/dist/tool-validator.js +8 -2
- package/dist/utils/detection.js +4 -3
- package/dist/utils/git-lock.js +118 -0
- package/dist/utils/gitignore-updater.js +148 -0
- package/dist/utils/quality-gates.js +47 -7
- package/dist/utils/spec-resolver.js +23 -3
- package/dist/utils/yaml-validation.js +155 -0
- package/dist/validation/spec-validation.js +81 -2
- package/package.json +2 -2
- package/templates/.caws/schemas/waivers.schema.json +30 -0
- package/templates/.caws/schemas/working-spec.schema.json +133 -0
- package/templates/.caws/templates/working-spec.template.yml +74 -0
- package/templates/.caws/tools/README.md +20 -0
- package/templates/.caws/tools/scope-guard.js +208 -0
- package/templates/.caws/tools-allow.json +331 -0
- package/templates/.caws/waivers.yml +19 -0
- package/templates/.cursor/hooks/scope-guard.sh +2 -2
- package/templates/.cursor/hooks/validate-spec.sh +42 -7
- package/templates/apps/tools/caws/COMPLETION_REPORT.md +0 -331
- package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +0 -360
- package/templates/apps/tools/caws/README.md +0 -463
- package/templates/apps/tools/caws/TEST_STATUS.md +0 -365
- package/templates/apps/tools/caws/attest.js +0 -357
- package/templates/apps/tools/caws/ci-optimizer.js +0 -642
- package/templates/apps/tools/caws/config.ts +0 -245
- package/templates/apps/tools/caws/cross-functional.js +0 -876
- package/templates/apps/tools/caws/dashboard.js +0 -1112
- package/templates/apps/tools/caws/flake-detector.ts +0 -362
- package/templates/apps/tools/caws/gates.js +0 -198
- package/templates/apps/tools/caws/gates.ts +0 -271
- package/templates/apps/tools/caws/language-adapters.ts +0 -381
- package/templates/apps/tools/caws/language-support.d.ts +0 -367
- package/templates/apps/tools/caws/language-support.d.ts.map +0 -1
- package/templates/apps/tools/caws/language-support.js +0 -585
- package/templates/apps/tools/caws/legacy-assessment.ts +0 -408
- package/templates/apps/tools/caws/legacy-assessor.js +0 -764
- package/templates/apps/tools/caws/mutant-analyzer.js +0 -734
- package/templates/apps/tools/caws/perf-budgets.ts +0 -349
- package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
- package/templates/apps/tools/caws/property-testing.js +0 -707
- package/templates/apps/tools/caws/provenance.d.ts +0 -14
- package/templates/apps/tools/caws/provenance.d.ts.map +0 -1
- package/templates/apps/tools/caws/provenance.js +0 -132
- package/templates/apps/tools/caws/provenance.js.backup +0 -73
- package/templates/apps/tools/caws/provenance.ts +0 -211
- package/templates/apps/tools/caws/security-provenance.ts +0 -483
- package/templates/apps/tools/caws/shared/base-tool.ts +0 -281
- package/templates/apps/tools/caws/shared/config-manager.ts +0 -366
- package/templates/apps/tools/caws/shared/gate-checker.ts +0 -849
- package/templates/apps/tools/caws/shared/types.ts +0 -444
- package/templates/apps/tools/caws/shared/validator.ts +0 -305
- package/templates/apps/tools/caws/shared/waivers-manager.ts +0 -174
- package/templates/apps/tools/caws/spec-test-mapper.ts +0 -391
- package/templates/apps/tools/caws/test-quality.js +0 -578
- package/templates/apps/tools/caws/validate.js +0 -76
- package/templates/apps/tools/caws/validate.ts +0 -228
- package/templates/apps/tools/caws/waivers.js +0 -344
- /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/waivers.schema.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/working-spec.schema.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/templates/working-spec.template.yml +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws/tools}/scope-guard.js +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/tools-allow.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/waivers.yml +0 -0
|
@@ -1,578 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @fileoverview CAWS Test Quality Analyzer
|
|
5
|
-
* Analyzes test files for meaningful assertions and quality indicators beyond coverage
|
|
6
|
-
* @author @darianrosebrook
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Test quality scoring criteria
|
|
14
|
-
*/
|
|
15
|
-
const QUALITY_CRITERIA = {
|
|
16
|
-
ASSERTION_DENSITY: {
|
|
17
|
-
weight: 0.25,
|
|
18
|
-
description: 'Ratio of assertions to test functions',
|
|
19
|
-
thresholds: { excellent: 0.8, good: 0.6, poor: 0.3 },
|
|
20
|
-
},
|
|
21
|
-
EDGE_CASE_COVERAGE: {
|
|
22
|
-
weight: 0.2,
|
|
23
|
-
description: 'Coverage of edge cases and error conditions',
|
|
24
|
-
thresholds: { excellent: 0.7, good: 0.5, poor: 0.2 },
|
|
25
|
-
},
|
|
26
|
-
DESCRIPTIVE_NAMING: {
|
|
27
|
-
weight: 0.15,
|
|
28
|
-
description: 'Quality of test names and descriptions',
|
|
29
|
-
thresholds: { excellent: 0.8, good: 0.6, poor: 0.3 },
|
|
30
|
-
},
|
|
31
|
-
SETUP_TEARDOWN: {
|
|
32
|
-
weight: 0.1,
|
|
33
|
-
description: 'Proper test setup and teardown',
|
|
34
|
-
thresholds: { excellent: 0.9, good: 0.7, poor: 0.4 },
|
|
35
|
-
},
|
|
36
|
-
MOCKING_QUALITY: {
|
|
37
|
-
weight: 0.15,
|
|
38
|
-
description: 'Appropriate use of mocks and test doubles',
|
|
39
|
-
thresholds: { excellent: 0.8, good: 0.6, poor: 0.3 },
|
|
40
|
-
},
|
|
41
|
-
SPEC_COVERAGE: {
|
|
42
|
-
weight: 0.15,
|
|
43
|
-
description: 'Alignment with acceptance criteria from working spec',
|
|
44
|
-
thresholds: { excellent: 0.9, good: 0.7, poor: 0.4 },
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Analyze a single test file for quality metrics
|
|
50
|
-
* @param {string} filePath - Path to test file
|
|
51
|
-
* @param {Object} _spec - Working specification for spec coverage check
|
|
52
|
-
* @returns {Object} Quality analysis results
|
|
53
|
-
*/
|
|
54
|
-
function analyzeTestFile(filePath, _spec = null) {
|
|
55
|
-
let content = '';
|
|
56
|
-
let language = 'javascript';
|
|
57
|
-
|
|
58
|
-
// Determine language from file extension
|
|
59
|
-
const ext = path.extname(filePath);
|
|
60
|
-
if (ext === '.py') {
|
|
61
|
-
language = 'python';
|
|
62
|
-
} else if (ext === '.java') {
|
|
63
|
-
language = 'java';
|
|
64
|
-
} else if (ext === '.js' || ext === '.ts') {
|
|
65
|
-
language = 'javascript';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
content = fs.readFileSync(filePath, 'utf8');
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.warn(`⚠️ Could not read test file: ${filePath}`);
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const lines = content.split('\n');
|
|
76
|
-
const analysis = {
|
|
77
|
-
file: path.basename(filePath),
|
|
78
|
-
language,
|
|
79
|
-
totalLines: lines.length,
|
|
80
|
-
testFunctions: 0,
|
|
81
|
-
assertions: 0,
|
|
82
|
-
edgeCases: 0,
|
|
83
|
-
descriptiveNames: 0,
|
|
84
|
-
properSetup: false,
|
|
85
|
-
properTeardown: false,
|
|
86
|
-
mocksUsed: false,
|
|
87
|
-
specAlignment: 0,
|
|
88
|
-
issues: [],
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// Language-specific analysis
|
|
92
|
-
switch (language) {
|
|
93
|
-
case 'javascript':
|
|
94
|
-
analyzeJavaScriptTest(content, lines, analysis, _spec);
|
|
95
|
-
break;
|
|
96
|
-
case 'python':
|
|
97
|
-
analyzePythonTest(content, lines, analysis, _spec);
|
|
98
|
-
break;
|
|
99
|
-
case 'java':
|
|
100
|
-
analyzeJavaTest(content, lines, analysis, _spec);
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return analysis;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Analyze JavaScript/TypeScript test file
|
|
109
|
-
*/
|
|
110
|
-
function analyzeJavaScriptTest(content, lines, analysis, _spec) {
|
|
111
|
-
// Count test functions (describe/it/test blocks in Jest/Mocha)
|
|
112
|
-
const testPatterns = [/\b(describe|it|test)\s*\(/g, /\btest\s*\(\s*['"`][^'"`]*['"`]/g];
|
|
113
|
-
|
|
114
|
-
testPatterns.forEach((pattern) => {
|
|
115
|
-
const matches = content.match(pattern);
|
|
116
|
-
if (matches) {
|
|
117
|
-
analysis.testFunctions += matches.length;
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Count assertions
|
|
122
|
-
const assertionPatterns = [
|
|
123
|
-
/\.toBe\s*\(/g,
|
|
124
|
-
/\.toEqual\s*\(/g,
|
|
125
|
-
/\.toContain\s*\(/g,
|
|
126
|
-
/\.toHaveBeenCalled/g,
|
|
127
|
-
/\.toHaveBeenCalledWith/g,
|
|
128
|
-
/\bexpect\s*\(/g,
|
|
129
|
-
/\bassert\s*\(/g,
|
|
130
|
-
/\bshould\s*\(/g,
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
assertionPatterns.forEach((pattern) => {
|
|
134
|
-
const matches = content.match(pattern);
|
|
135
|
-
if (matches) {
|
|
136
|
-
analysis.assertions += matches.length;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Check for edge cases (error conditions, null/undefined, boundaries)
|
|
141
|
-
const edgeCasePatterns = [
|
|
142
|
-
/catch\s*\(/g,
|
|
143
|
-
/throw\s+/g,
|
|
144
|
-
/Error\s*\(/g,
|
|
145
|
-
/null\s*,?\s*undefined/g,
|
|
146
|
-
/boundary|edge|limit|extreme/g,
|
|
147
|
-
/invalid|malformed|corrupt/g,
|
|
148
|
-
];
|
|
149
|
-
|
|
150
|
-
edgeCasePatterns.forEach((pattern) => {
|
|
151
|
-
const matches = content.match(pattern);
|
|
152
|
-
if (matches) {
|
|
153
|
-
analysis.edgeCases += matches.length;
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Check for descriptive naming
|
|
158
|
-
const describeBlocks = content.match(/describe\s*\(\s*['"`]([^'"`]+)['"`]/g);
|
|
159
|
-
if (describeBlocks) {
|
|
160
|
-
describeBlocks.forEach((block) => {
|
|
161
|
-
if (block.length > 20 && !/\btest|spec|should\b/.test(block)) {
|
|
162
|
-
analysis.descriptiveNames++;
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Check for setup/teardown
|
|
168
|
-
analysis.properSetup = /\bbeforeEach|beforeAll|setup\b/.test(content);
|
|
169
|
-
analysis.properTeardown = /\bafterEach|afterAll|teardown\b/.test(content);
|
|
170
|
-
|
|
171
|
-
// Check for mocking
|
|
172
|
-
analysis.mocksUsed = /\b(mock|spy|stub|jest\.mock|sinon\.)/.test(content);
|
|
173
|
-
|
|
174
|
-
// Check spec alignment if spec provided
|
|
175
|
-
if (_spec && _spec.acceptance) {
|
|
176
|
-
const specKeywords = _spec.acceptance
|
|
177
|
-
.flatMap((ac) => [ac.given, ac.when, ac.then].filter(Boolean))
|
|
178
|
-
.join(' ')
|
|
179
|
-
.toLowerCase();
|
|
180
|
-
|
|
181
|
-
const testContent = content.toLowerCase();
|
|
182
|
-
const matchedTerms = specKeywords
|
|
183
|
-
.split(/\s+/)
|
|
184
|
-
.filter((term) => term.length > 3 && testContent.includes(term)).length;
|
|
185
|
-
|
|
186
|
-
analysis.specAlignment = Math.min(
|
|
187
|
-
matchedTerms / Math.max(specKeywords.split(/\s+/).length, 1),
|
|
188
|
-
1
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Identify issues
|
|
193
|
-
if (analysis.testFunctions > 0 && analysis.assertions / analysis.testFunctions < 0.5) {
|
|
194
|
-
analysis.issues.push('Low assertion density - tests may not be properly validating behavior');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (analysis.edgeCases === 0 && analysis.testFunctions > 3) {
|
|
198
|
-
analysis.issues.push('No edge case testing detected - consider adding error condition tests');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (!analysis.properSetup && analysis.testFunctions > 5) {
|
|
202
|
-
analysis.issues.push('Missing test setup - consider using beforeEach for common setup');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (!analysis.mocksUsed && /\bimport.*from/.test(content)) {
|
|
206
|
-
analysis.issues.push(
|
|
207
|
-
'External dependencies detected but no mocking - consider adding mocks for better isolation'
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Analyze Python test file
|
|
214
|
-
*/
|
|
215
|
-
function analyzePythonTest(content, lines, analysis, _spec) {
|
|
216
|
-
// Count test functions
|
|
217
|
-
const testMatches = content.match(/\bdef\s+test_\w+/g);
|
|
218
|
-
analysis.testFunctions = testMatches ? testMatches.length : 0;
|
|
219
|
-
|
|
220
|
-
// Count assertions
|
|
221
|
-
const assertionPatterns = [
|
|
222
|
-
/\bassert\s+/g,
|
|
223
|
-
/\bself\.assert/g,
|
|
224
|
-
/\bassertEqual\b/g,
|
|
225
|
-
/\bassertTrue\b/g,
|
|
226
|
-
/\bassertFalse\b/g,
|
|
227
|
-
/\bassertRaises\b/g,
|
|
228
|
-
/\bassertIn\b/g,
|
|
229
|
-
/\bassertIsNone\b/g,
|
|
230
|
-
];
|
|
231
|
-
|
|
232
|
-
assertionPatterns.forEach((pattern) => {
|
|
233
|
-
const matches = content.match(pattern);
|
|
234
|
-
if (matches) {
|
|
235
|
-
analysis.assertions += matches.length;
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// Check for edge cases
|
|
240
|
-
const edgeCasePatterns = [
|
|
241
|
-
/with\s+pytest\.raises/g,
|
|
242
|
-
/try:\s*except/g,
|
|
243
|
-
/ValueError|TypeError|Exception/g,
|
|
244
|
-
/None\s*,?\s*null/g,
|
|
245
|
-
/boundary|edge|limit|extreme/g,
|
|
246
|
-
/invalid|malformed|corrupt/g,
|
|
247
|
-
];
|
|
248
|
-
|
|
249
|
-
edgeCasePatterns.forEach((pattern) => {
|
|
250
|
-
const matches = content.match(pattern);
|
|
251
|
-
if (matches) {
|
|
252
|
-
analysis.edgeCases += matches.length;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Check for descriptive naming
|
|
257
|
-
const classMatches = content.match(/class\s+Test\w+/g);
|
|
258
|
-
if (classMatches) {
|
|
259
|
-
classMatches.forEach((className) => {
|
|
260
|
-
if (className.length > 8 && !/\bTest\b/.test(className)) {
|
|
261
|
-
analysis.descriptiveNames++;
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Check for setup/teardown
|
|
267
|
-
analysis.properSetup = /\bsetUp\b|\bfixtures?\b/.test(content);
|
|
268
|
-
analysis.properTeardown = /\btearDown\b/.test(content);
|
|
269
|
-
|
|
270
|
-
// Check for mocking
|
|
271
|
-
analysis.mocksUsed = /\b(mocking|patch|MagicMock|Mock)\b/.test(content);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Analyze Java test file
|
|
276
|
-
*/
|
|
277
|
-
function analyzeJavaTest(content, lines, analysis, _spec) {
|
|
278
|
-
// Count test methods
|
|
279
|
-
const testMatches = content.match(/@\w*Test\s*public\s+void\s+\w+/g);
|
|
280
|
-
analysis.testFunctions = testMatches ? testMatches.length : 0;
|
|
281
|
-
|
|
282
|
-
// Count assertions
|
|
283
|
-
const assertionPatterns = [
|
|
284
|
-
/\bassert/g,
|
|
285
|
-
/\bfail\s*\(/g,
|
|
286
|
-
/\bAssert\.assert/g,
|
|
287
|
-
/\bAssertions\.assert/g,
|
|
288
|
-
];
|
|
289
|
-
|
|
290
|
-
assertionPatterns.forEach((pattern) => {
|
|
291
|
-
const matches = content.match(pattern);
|
|
292
|
-
if (matches) {
|
|
293
|
-
analysis.assertions += matches.length;
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// Check for edge cases
|
|
298
|
-
const edgeCasePatterns = [
|
|
299
|
-
/@Test.*expected\s*=/g,
|
|
300
|
-
/try\s*{\s*.*\s*}\s*catch/g,
|
|
301
|
-
/Exception|Error/g,
|
|
302
|
-
/null\s*,?\s*boundary/g,
|
|
303
|
-
/invalid|malformed|corrupt/g,
|
|
304
|
-
];
|
|
305
|
-
|
|
306
|
-
edgeCasePatterns.forEach((pattern) => {
|
|
307
|
-
const matches = content.match(pattern);
|
|
308
|
-
if (matches) {
|
|
309
|
-
analysis.edgeCases += matches.length;
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// Check for setup/teardown
|
|
314
|
-
analysis.properSetup = /@BeforeEach|@Before/.test(content);
|
|
315
|
-
analysis.properTeardown = /@AfterEach|@After/.test(content);
|
|
316
|
-
|
|
317
|
-
// Check for mocking
|
|
318
|
-
analysis.mocksUsed = /@Mock|Mockito|when\(|verify\(/.test(content);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Calculate overall quality score for a test file
|
|
323
|
-
* @param {Object} analysis - Test analysis results
|
|
324
|
-
* @returns {number} Quality score (0-100)
|
|
325
|
-
*/
|
|
326
|
-
function calculateQualityScore(analysis) {
|
|
327
|
-
if (analysis.testFunctions === 0) return 0;
|
|
328
|
-
|
|
329
|
-
const scores = {};
|
|
330
|
-
|
|
331
|
-
// Assertion density score
|
|
332
|
-
const assertionDensity =
|
|
333
|
-
analysis.testFunctions > 0 ? analysis.assertions / analysis.testFunctions : 0;
|
|
334
|
-
scores.assertionDensity = normalizeScore(
|
|
335
|
-
assertionDensity,
|
|
336
|
-
QUALITY_CRITERIA.ASSERTION_DENSITY.thresholds
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
// Edge case coverage score
|
|
340
|
-
const edgeCaseRatio =
|
|
341
|
-
analysis.testFunctions > 0 ? analysis.edgeCases / analysis.testFunctions : 0;
|
|
342
|
-
scores.edgeCaseCoverage = normalizeScore(
|
|
343
|
-
edgeCaseRatio,
|
|
344
|
-
QUALITY_CRITERIA.EDGE_CASE_COVERAGE.thresholds
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
// Descriptive naming score (simplified)
|
|
348
|
-
const namingScore = analysis.descriptiveNames > 0 ? 1 : 0.5;
|
|
349
|
-
scores.descriptiveNaming = normalizeScore(
|
|
350
|
-
namingScore,
|
|
351
|
-
QUALITY_CRITERIA.DESCRIPTIVE_NAMING.thresholds
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
// Setup/teardown score
|
|
355
|
-
const setupScore = (analysis.properSetup ? 0.5 : 0) + (analysis.properTeardown ? 0.5 : 0);
|
|
356
|
-
scores.setupTeardown = normalizeScore(setupScore, QUALITY_CRITERIA.SETUP_TEARDOWN.thresholds);
|
|
357
|
-
|
|
358
|
-
// Mocking quality score
|
|
359
|
-
scores.mockingQuality = analysis.mocksUsed
|
|
360
|
-
? normalizeScore(0.8, QUALITY_CRITERIA.MOCKING_QUALITY.thresholds)
|
|
361
|
-
: normalizeScore(0.3, QUALITY_CRITERIA.MOCKING_QUALITY.thresholds);
|
|
362
|
-
|
|
363
|
-
// Spec alignment score
|
|
364
|
-
scores.specCoverage = normalizeScore(
|
|
365
|
-
analysis.specAlignment,
|
|
366
|
-
QUALITY_CRITERIA.SPEC_COVERAGE.thresholds
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
// Calculate weighted score
|
|
370
|
-
let totalScore = 0;
|
|
371
|
-
Object.keys(QUALITY_CRITERIA).forEach((criterion) => {
|
|
372
|
-
totalScore += scores[criterion.toLowerCase()] * QUALITY_CRITERIA[criterion].weight;
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
return Math.round(totalScore * 100);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Normalize a raw score to 0-1 scale based on thresholds
|
|
380
|
-
*/
|
|
381
|
-
function normalizeScore(value, thresholds) {
|
|
382
|
-
if (value >= thresholds.excellent) return 1.0;
|
|
383
|
-
if (value >= thresholds.good) return 0.8;
|
|
384
|
-
if (value >= thresholds.poor) return 0.5;
|
|
385
|
-
return 0.2;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Analyze all test files in a directory
|
|
390
|
-
* @param {string} testDir - Directory containing test files
|
|
391
|
-
* @param {Object} _spec - Working specification
|
|
392
|
-
* @returns {Object} Analysis summary
|
|
393
|
-
*/
|
|
394
|
-
function analyzeTestDirectory(testDir, _spec = null) {
|
|
395
|
-
const results = {
|
|
396
|
-
files: [],
|
|
397
|
-
summary: {
|
|
398
|
-
totalFiles: 0,
|
|
399
|
-
totalTests: 0,
|
|
400
|
-
totalAssertions: 0,
|
|
401
|
-
averageQualityScore: 0,
|
|
402
|
-
issues: [],
|
|
403
|
-
},
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
try {
|
|
407
|
-
const files = fs.readdirSync(testDir);
|
|
408
|
-
|
|
409
|
-
files.forEach((file) => {
|
|
410
|
-
const filePath = path.join(testDir, file);
|
|
411
|
-
const stat = fs.statSync(filePath);
|
|
412
|
-
|
|
413
|
-
if (stat.isFile() && /\.(test|spec)\.(js|ts|py|java)$/.test(file)) {
|
|
414
|
-
const analysis = analyzeTestFile(filePath, _spec);
|
|
415
|
-
if (analysis) {
|
|
416
|
-
const qualityScore = calculateQualityScore(analysis);
|
|
417
|
-
analysis.qualityScore = qualityScore;
|
|
418
|
-
|
|
419
|
-
results.files.push(analysis);
|
|
420
|
-
results.summary.totalFiles++;
|
|
421
|
-
results.summary.totalTests += analysis.testFunctions;
|
|
422
|
-
results.summary.totalAssertions += analysis.assertions;
|
|
423
|
-
|
|
424
|
-
if (analysis.issues.length > 0) {
|
|
425
|
-
results.summary.issues.push(...analysis.issues.map((issue) => `${file}: ${issue}`));
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
// Calculate average quality score
|
|
432
|
-
if (results.files.length > 0) {
|
|
433
|
-
const totalScore = results.files.reduce((sum, file) => sum + file.qualityScore, 0);
|
|
434
|
-
results.summary.averageQualityScore = Math.round(totalScore / results.files.length);
|
|
435
|
-
}
|
|
436
|
-
} catch (error) {
|
|
437
|
-
console.error(`❌ Error analyzing test directory: ${error.message}`);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return results;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Generate recommendations based on analysis
|
|
445
|
-
* @param {Object} results - Analysis results
|
|
446
|
-
* @returns {Array} Recommendations
|
|
447
|
-
*/
|
|
448
|
-
function generateRecommendations(results) {
|
|
449
|
-
const recommendations = [];
|
|
450
|
-
|
|
451
|
-
if (results.summary.averageQualityScore < 70) {
|
|
452
|
-
recommendations.push({
|
|
453
|
-
type: 'critical',
|
|
454
|
-
message:
|
|
455
|
-
'Overall test quality is below acceptable threshold. Consider improving test meaningfulness.',
|
|
456
|
-
suggestions: [
|
|
457
|
-
'Add more assertions per test function',
|
|
458
|
-
'Include edge case and error condition testing',
|
|
459
|
-
'Improve test naming for better clarity',
|
|
460
|
-
'Ensure proper setup/teardown procedures',
|
|
461
|
-
],
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (results.summary.totalAssertions / Math.max(results.summary.totalTests, 1) < 0.5) {
|
|
466
|
-
recommendations.push({
|
|
467
|
-
type: 'warning',
|
|
468
|
-
message: 'Low assertion density detected across tests.',
|
|
469
|
-
suggestions: [
|
|
470
|
-
'Each test should validate expected behavior with assertions',
|
|
471
|
-
'Avoid tests that only check if code runs without errors',
|
|
472
|
-
'Add assertions for return values, side effects, and state changes',
|
|
473
|
-
],
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const filesWithoutEdgeCases = results.files.filter(
|
|
478
|
-
(f) => f.edgeCases === 0 && f.testFunctions > 2
|
|
479
|
-
);
|
|
480
|
-
if (filesWithoutEdgeCases.length > 0) {
|
|
481
|
-
recommendations.push({
|
|
482
|
-
type: 'info',
|
|
483
|
-
message: `${filesWithoutEdgeCases.length} test file(s) lack edge case coverage.`,
|
|
484
|
-
suggestions: [
|
|
485
|
-
'Add tests for null/undefined inputs',
|
|
486
|
-
'Test boundary conditions and error scenarios',
|
|
487
|
-
'Include tests for invalid or malformed data',
|
|
488
|
-
],
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return recommendations;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// CLI interface
|
|
496
|
-
if (require.main === module) {
|
|
497
|
-
const command = process.argv[2];
|
|
498
|
-
const testDir = process.argv[3] || 'tests';
|
|
499
|
-
|
|
500
|
-
switch (command) {
|
|
501
|
-
case 'analyze':
|
|
502
|
-
console.log(`🔍 Analyzing test quality in: ${testDir}`);
|
|
503
|
-
|
|
504
|
-
// Try to load working spec for spec alignment check
|
|
505
|
-
let spec = null;
|
|
506
|
-
const specPath = '.caws/working-spec.yaml';
|
|
507
|
-
if (fs.existsSync(specPath)) {
|
|
508
|
-
try {
|
|
509
|
-
const yaml = require('js-yaml');
|
|
510
|
-
spec = yaml.load(fs.readFileSync(specPath, 'utf8'));
|
|
511
|
-
console.log('✅ Loaded working spec for alignment analysis');
|
|
512
|
-
} catch (error) {
|
|
513
|
-
console.warn('⚠️ Could not load working spec for alignment analysis');
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const results = analyzeTestDirectory(testDir, spec);
|
|
518
|
-
|
|
519
|
-
console.log('\n📊 Test Quality Analysis Results:');
|
|
520
|
-
console.log(` Files analyzed: ${results.summary.totalFiles}`);
|
|
521
|
-
console.log(` Test functions: ${results.summary.totalTests}`);
|
|
522
|
-
console.log(` Total assertions: ${results.summary.totalAssertions}`);
|
|
523
|
-
console.log(` Average quality score: ${results.summary.averageQualityScore}/100`);
|
|
524
|
-
|
|
525
|
-
if (results.files.length > 0) {
|
|
526
|
-
console.log('\n📋 File-by-file breakdown:');
|
|
527
|
-
results.files.forEach((file) => {
|
|
528
|
-
console.log(
|
|
529
|
-
` ${file.file}: ${file.qualityScore}/100 (${file.testFunctions} tests, ${file.assertions} assertions)`
|
|
530
|
-
);
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (results.summary.issues.length > 0) {
|
|
535
|
-
console.log('\n⚠️ Issues found:');
|
|
536
|
-
results.summary.issues.forEach((issue) => {
|
|
537
|
-
console.log(` - ${issue}`);
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const recommendations = generateRecommendations(results);
|
|
542
|
-
if (recommendations.length > 0) {
|
|
543
|
-
console.log('\n💡 Recommendations:');
|
|
544
|
-
recommendations.forEach((rec) => {
|
|
545
|
-
console.log(` [${rec.type.toUpperCase()}] ${rec.message}`);
|
|
546
|
-
rec.suggestions.forEach((suggestion) => {
|
|
547
|
-
console.log(` • ${suggestion}`);
|
|
548
|
-
});
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Exit with error code if quality is poor
|
|
553
|
-
if (results.summary.averageQualityScore < 70) {
|
|
554
|
-
console.error('\n❌ Test quality below acceptable threshold');
|
|
555
|
-
process.exit(1);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
break;
|
|
559
|
-
|
|
560
|
-
default:
|
|
561
|
-
console.log('CAWS Test Quality Analyzer');
|
|
562
|
-
console.log('Usage:');
|
|
563
|
-
console.log(' node test-quality.js analyze [test-directory]');
|
|
564
|
-
console.log('');
|
|
565
|
-
console.log('Examples:');
|
|
566
|
-
console.log(' node test-quality.js analyze tests/unit');
|
|
567
|
-
console.log(' node test-quality.js analyze tests/');
|
|
568
|
-
process.exit(1);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
module.exports = {
|
|
573
|
-
analyzeTestFile,
|
|
574
|
-
analyzeTestDirectory,
|
|
575
|
-
calculateQualityScore,
|
|
576
|
-
generateRecommendations,
|
|
577
|
-
QUALITY_CRITERIA,
|
|
578
|
-
};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview CAWS Validation Tool
|
|
3
|
-
* @author @darianrosebrook
|
|
4
|
-
*
|
|
5
|
-
* Note: For enhanced TypeScript version with schema validation, use validate.ts
|
|
6
|
-
* This .js version provides basic validation for backward compatibility
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Validates a working specification file
|
|
11
|
-
* @param {string} specPath - Path to the working specification file
|
|
12
|
-
* @returns {Object} Validation result with valid boolean and errors array
|
|
13
|
-
*/
|
|
14
|
-
function validateWorkingSpec(specPath) {
|
|
15
|
-
try {
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const yaml = require('js-yaml');
|
|
18
|
-
|
|
19
|
-
if (!fs.existsSync(specPath)) {
|
|
20
|
-
return {
|
|
21
|
-
valid: false,
|
|
22
|
-
errors: [{ message: `Specification file not found: ${specPath}` }],
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const specContent = fs.readFileSync(specPath, 'utf8');
|
|
27
|
-
const spec = yaml.load(specContent);
|
|
28
|
-
|
|
29
|
-
// Basic validation
|
|
30
|
-
const errors = [];
|
|
31
|
-
|
|
32
|
-
if (!spec.id) errors.push({ message: 'Missing required field: id' });
|
|
33
|
-
if (!spec.title) errors.push({ message: 'Missing required field: title' });
|
|
34
|
-
if (!spec.risk_tier) errors.push({ message: 'Missing required field: risk_tier' });
|
|
35
|
-
|
|
36
|
-
if (spec.risk_tier && (spec.risk_tier < 1 || spec.risk_tier > 3)) {
|
|
37
|
-
errors.push({ message: 'Risk tier must be 1, 2, or 3' });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
|
|
41
|
-
errors.push({ message: 'Scope IN must not be empty' });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
valid: errors.length === 0,
|
|
46
|
-
errors: errors,
|
|
47
|
-
};
|
|
48
|
-
} catch (error) {
|
|
49
|
-
return {
|
|
50
|
-
valid: false,
|
|
51
|
-
errors: [{ message: `Validation error: ${error.message}` }],
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Handle direct script execution
|
|
57
|
-
if (require.main === module) {
|
|
58
|
-
const specPath = process.argv[2];
|
|
59
|
-
if (!specPath) {
|
|
60
|
-
console.error('Usage: node validate.js <spec-path>');
|
|
61
|
-
console.log('');
|
|
62
|
-
console.log('Note: For enhanced schema validation, use: npx tsx validate.ts spec <spec-path>');
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const result = validateWorkingSpec(specPath);
|
|
67
|
-
if (result.valid) {
|
|
68
|
-
console.log('✅ Working specification is valid');
|
|
69
|
-
} else {
|
|
70
|
-
console.error('❌ Working specification is invalid:');
|
|
71
|
-
result.errors.forEach((error) => console.error(` - ${error.message}`));
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
module.exports = validateWorkingSpec;
|