@paths.design/caws-cli 7.0.2 → 8.0.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.
Files changed (217) hide show
  1. package/dist/budget-derivation.js +5 -4
  2. package/dist/commands/diagnose.js +24 -19
  3. package/dist/commands/init.js +51 -4
  4. package/dist/commands/quality-gates.js +147 -9
  5. package/dist/commands/specs.js +148 -14
  6. package/dist/commands/status.js +2 -2
  7. package/dist/commands/tool.js +2 -4
  8. package/dist/config/index.js +17 -8
  9. package/dist/generators/working-spec.js +19 -6
  10. package/dist/scaffold/git-hooks.js +245 -46
  11. package/dist/scaffold/index.js +53 -7
  12. package/dist/templates/.caws/tools/README.md +21 -0
  13. package/dist/templates/.cursor/README.md +311 -0
  14. package/dist/templates/.cursor/hooks/audit.sh +55 -0
  15. package/dist/templates/.cursor/hooks/block-dangerous.sh +83 -0
  16. package/dist/templates/.cursor/hooks/caws-quality-check.sh +52 -0
  17. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +130 -0
  18. package/dist/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
  19. package/dist/templates/.cursor/hooks/format.sh +38 -0
  20. package/dist/templates/.cursor/hooks/naming-check.sh +64 -0
  21. package/dist/templates/.cursor/hooks/scan-secrets.sh +46 -0
  22. package/dist/templates/.cursor/hooks/scope-guard.sh +52 -0
  23. package/dist/templates/.cursor/hooks/validate-spec.sh +83 -0
  24. package/dist/templates/.cursor/hooks.json +59 -0
  25. package/dist/templates/.cursor/rules/00-claims-verification.mdc +144 -0
  26. package/dist/templates/.cursor/rules/01-working-style.mdc +50 -0
  27. package/dist/templates/.cursor/rules/02-quality-gates.mdc +370 -0
  28. package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
  29. package/dist/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
  30. package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
  31. package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
  32. package/dist/templates/.cursor/rules/07-process-ops.mdc +20 -0
  33. package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
  34. package/dist/templates/.cursor/rules/09-docstrings.mdc +89 -0
  35. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +390 -0
  36. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +385 -0
  37. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +516 -0
  38. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +588 -0
  39. package/dist/templates/.cursor/rules/README.md +148 -0
  40. package/dist/templates/.github/copilot/instructions.md +311 -0
  41. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
  42. package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
  43. package/dist/templates/.vscode/launch.json +56 -0
  44. package/dist/templates/.vscode/settings.json +93 -0
  45. package/dist/templates/.windsurf/workflows/caws-guided-development.md +92 -0
  46. package/dist/templates/COMMIT_CONVENTIONS.md +86 -0
  47. package/dist/templates/OIDC_SETUP.md +300 -0
  48. package/dist/templates/agents.md +1047 -0
  49. package/dist/templates/codemod/README.md +1 -0
  50. package/dist/templates/codemod/test.js +93 -0
  51. package/dist/templates/docs/README.md +150 -0
  52. package/dist/templates/scripts/quality-gates/check-god-objects.js +146 -0
  53. package/dist/templates/scripts/quality-gates/run-quality-gates.js +50 -0
  54. package/dist/templates/scripts/v3/analysis/todo_analyzer.py +1997 -0
  55. package/dist/tool-loader.js +6 -1
  56. package/dist/tool-validator.js +8 -2
  57. package/dist/utils/detection.js +4 -3
  58. package/dist/utils/git-lock.js +119 -0
  59. package/dist/utils/gitignore-updater.js +148 -0
  60. package/dist/utils/project-analysis.js +176 -16
  61. package/dist/utils/quality-gates.js +48 -7
  62. package/dist/utils/spec-resolver.js +27 -3
  63. package/dist/utils/yaml-validation.js +156 -0
  64. package/dist/validation/spec-validation.js +81 -2
  65. package/package.json +2 -2
  66. package/templates/.caws/schemas/waivers.schema.json +30 -0
  67. package/templates/.caws/schemas/working-spec.schema.json +133 -0
  68. package/templates/.caws/templates/working-spec.template.yml +74 -0
  69. package/templates/.caws/tools/README.md +21 -0
  70. package/templates/.caws/tools/scope-guard.js +208 -0
  71. package/templates/.caws/tools-allow.json +331 -0
  72. package/templates/.caws/waivers.yml +19 -0
  73. package/templates/.cursor/hooks/scope-guard.sh +2 -2
  74. package/templates/.cursor/hooks/validate-spec.sh +42 -7
  75. package/dist/budget-derivation.d.ts +0 -74
  76. package/dist/budget-derivation.d.ts.map +0 -1
  77. package/dist/cicd-optimizer.d.ts +0 -142
  78. package/dist/cicd-optimizer.d.ts.map +0 -1
  79. package/dist/commands/archive.d.ts +0 -50
  80. package/dist/commands/archive.d.ts.map +0 -1
  81. package/dist/commands/burnup.d.ts +0 -6
  82. package/dist/commands/burnup.d.ts.map +0 -1
  83. package/dist/commands/diagnose.d.ts +0 -52
  84. package/dist/commands/diagnose.d.ts.map +0 -1
  85. package/dist/commands/evaluate.d.ts +0 -8
  86. package/dist/commands/evaluate.d.ts.map +0 -1
  87. package/dist/commands/init.d.ts +0 -5
  88. package/dist/commands/init.d.ts.map +0 -1
  89. package/dist/commands/iterate.d.ts +0 -8
  90. package/dist/commands/iterate.d.ts.map +0 -1
  91. package/dist/commands/mode.d.ts +0 -24
  92. package/dist/commands/mode.d.ts.map +0 -1
  93. package/dist/commands/plan.d.ts +0 -49
  94. package/dist/commands/plan.d.ts.map +0 -1
  95. package/dist/commands/provenance.d.ts +0 -32
  96. package/dist/commands/provenance.d.ts.map +0 -1
  97. package/dist/commands/quality-gates.d.ts +0 -52
  98. package/dist/commands/quality-gates.d.ts.map +0 -1
  99. package/dist/commands/quality-monitor.d.ts +0 -17
  100. package/dist/commands/quality-monitor.d.ts.map +0 -1
  101. package/dist/commands/specs.d.ts +0 -71
  102. package/dist/commands/specs.d.ts.map +0 -1
  103. package/dist/commands/status.d.ts +0 -44
  104. package/dist/commands/status.d.ts.map +0 -1
  105. package/dist/commands/templates.d.ts +0 -74
  106. package/dist/commands/templates.d.ts.map +0 -1
  107. package/dist/commands/tool.d.ts +0 -13
  108. package/dist/commands/tool.d.ts.map +0 -1
  109. package/dist/commands/troubleshoot.d.ts +0 -8
  110. package/dist/commands/troubleshoot.d.ts.map +0 -1
  111. package/dist/commands/tutorial.d.ts +0 -55
  112. package/dist/commands/tutorial.d.ts.map +0 -1
  113. package/dist/commands/validate.d.ts +0 -15
  114. package/dist/commands/validate.d.ts.map +0 -1
  115. package/dist/commands/waivers.d.ts +0 -8
  116. package/dist/commands/waivers.d.ts.map +0 -1
  117. package/dist/commands/workflow.d.ts +0 -85
  118. package/dist/commands/workflow.d.ts.map +0 -1
  119. package/dist/config/index.d.ts +0 -29
  120. package/dist/config/index.d.ts.map +0 -1
  121. package/dist/config/modes.d.ts +0 -225
  122. package/dist/config/modes.d.ts.map +0 -1
  123. package/dist/constants/spec-types.d.ts +0 -41
  124. package/dist/constants/spec-types.d.ts.map +0 -1
  125. package/dist/error-handler.d.ts +0 -164
  126. package/dist/error-handler.d.ts.map +0 -1
  127. package/dist/generators/jest-config.d.ts +0 -32
  128. package/dist/generators/jest-config.d.ts.map +0 -1
  129. package/dist/generators/working-spec.d.ts +0 -13
  130. package/dist/generators/working-spec.d.ts.map +0 -1
  131. package/dist/index-new.d.ts +0 -5
  132. package/dist/index-new.d.ts.map +0 -1
  133. package/dist/index-new.js +0 -317
  134. package/dist/index.d.ts +0 -5
  135. package/dist/index.d.ts.map +0 -1
  136. package/dist/index.js.backup +0 -4711
  137. package/dist/minimal-cli.d.ts +0 -3
  138. package/dist/minimal-cli.d.ts.map +0 -1
  139. package/dist/policy/PolicyManager.d.ts +0 -104
  140. package/dist/policy/PolicyManager.d.ts.map +0 -1
  141. package/dist/scaffold/cursor-hooks.d.ts +0 -7
  142. package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
  143. package/dist/scaffold/git-hooks.d.ts +0 -20
  144. package/dist/scaffold/git-hooks.d.ts.map +0 -1
  145. package/dist/scaffold/index.d.ts +0 -20
  146. package/dist/scaffold/index.d.ts.map +0 -1
  147. package/dist/spec/SpecFileManager.d.ts +0 -146
  148. package/dist/spec/SpecFileManager.d.ts.map +0 -1
  149. package/dist/test-analysis.d.ts +0 -182
  150. package/dist/test-analysis.d.ts.map +0 -1
  151. package/dist/tool-interface.d.ts +0 -236
  152. package/dist/tool-interface.d.ts.map +0 -1
  153. package/dist/tool-loader.d.ts +0 -77
  154. package/dist/tool-loader.d.ts.map +0 -1
  155. package/dist/tool-validator.d.ts +0 -72
  156. package/dist/tool-validator.d.ts.map +0 -1
  157. package/dist/utils/detection.d.ts +0 -7
  158. package/dist/utils/detection.d.ts.map +0 -1
  159. package/dist/utils/finalization.d.ts +0 -17
  160. package/dist/utils/finalization.d.ts.map +0 -1
  161. package/dist/utils/project-analysis.d.ts +0 -14
  162. package/dist/utils/project-analysis.d.ts.map +0 -1
  163. package/dist/utils/quality-gates.d.ts +0 -49
  164. package/dist/utils/quality-gates.d.ts.map +0 -1
  165. package/dist/utils/spec-resolver.d.ts +0 -88
  166. package/dist/utils/spec-resolver.d.ts.map +0 -1
  167. package/dist/utils/typescript-detector.d.ts +0 -63
  168. package/dist/utils/typescript-detector.d.ts.map +0 -1
  169. package/dist/validation/spec-validation.d.ts +0 -43
  170. package/dist/validation/spec-validation.d.ts.map +0 -1
  171. package/dist/waivers-manager.d.ts +0 -167
  172. package/dist/waivers-manager.d.ts.map +0 -1
  173. package/templates/apps/tools/caws/COMPLETION_REPORT.md +0 -331
  174. package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +0 -360
  175. package/templates/apps/tools/caws/README.md +0 -463
  176. package/templates/apps/tools/caws/TEST_STATUS.md +0 -365
  177. package/templates/apps/tools/caws/attest.js +0 -357
  178. package/templates/apps/tools/caws/ci-optimizer.js +0 -642
  179. package/templates/apps/tools/caws/config.ts +0 -245
  180. package/templates/apps/tools/caws/cross-functional.js +0 -876
  181. package/templates/apps/tools/caws/dashboard.js +0 -1112
  182. package/templates/apps/tools/caws/flake-detector.ts +0 -362
  183. package/templates/apps/tools/caws/gates.js +0 -198
  184. package/templates/apps/tools/caws/gates.ts +0 -271
  185. package/templates/apps/tools/caws/language-adapters.ts +0 -381
  186. package/templates/apps/tools/caws/language-support.d.ts +0 -367
  187. package/templates/apps/tools/caws/language-support.d.ts.map +0 -1
  188. package/templates/apps/tools/caws/language-support.js +0 -585
  189. package/templates/apps/tools/caws/legacy-assessment.ts +0 -408
  190. package/templates/apps/tools/caws/legacy-assessor.js +0 -764
  191. package/templates/apps/tools/caws/mutant-analyzer.js +0 -734
  192. package/templates/apps/tools/caws/perf-budgets.ts +0 -349
  193. package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
  194. package/templates/apps/tools/caws/property-testing.js +0 -707
  195. package/templates/apps/tools/caws/provenance.d.ts +0 -14
  196. package/templates/apps/tools/caws/provenance.d.ts.map +0 -1
  197. package/templates/apps/tools/caws/provenance.js +0 -132
  198. package/templates/apps/tools/caws/provenance.js.backup +0 -73
  199. package/templates/apps/tools/caws/provenance.ts +0 -211
  200. package/templates/apps/tools/caws/security-provenance.ts +0 -483
  201. package/templates/apps/tools/caws/shared/base-tool.ts +0 -281
  202. package/templates/apps/tools/caws/shared/config-manager.ts +0 -366
  203. package/templates/apps/tools/caws/shared/gate-checker.ts +0 -849
  204. package/templates/apps/tools/caws/shared/types.ts +0 -444
  205. package/templates/apps/tools/caws/shared/validator.ts +0 -305
  206. package/templates/apps/tools/caws/shared/waivers-manager.ts +0 -174
  207. package/templates/apps/tools/caws/spec-test-mapper.ts +0 -391
  208. package/templates/apps/tools/caws/test-quality.js +0 -578
  209. package/templates/apps/tools/caws/validate.js +0 -76
  210. package/templates/apps/tools/caws/validate.ts +0 -228
  211. package/templates/apps/tools/caws/waivers.js +0 -344
  212. /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/waivers.schema.json +0 -0
  213. /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/working-spec.schema.json +0 -0
  214. /package/{templates/apps/tools/caws → dist/templates/.caws}/templates/working-spec.template.yml +0 -0
  215. /package/{templates/apps/tools/caws → dist/templates/.caws/tools}/scope-guard.js +0 -0
  216. /package/{templates/apps/tools/caws → dist/templates/.caws}/tools-allow.json +0 -0
  217. /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;