@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,849 +0,0 @@
1
- /**
2
- * CAWS Gate Checker
3
- * Consolidated gate checking logic for coverage, mutation, contracts, and trust score
4
- *
5
- * @author @darianrosebrook
6
- */
7
-
8
- import * as path from 'path';
9
- import { CawsBaseTool } from './base-tool.js';
10
- import {
11
- GateResult,
12
- GateCheckOptions,
13
- MutationData,
14
- ContractTestResults,
15
- TierPolicy,
16
- WaiverConfig,
17
- HumanOverride,
18
- AIAssessment,
19
- } from './types.js';
20
- import { WaiversManager } from './waivers-manager.js';
21
-
22
- export class CawsGateChecker extends CawsBaseTool {
23
- private tierPolicies: Record<number, TierPolicy> = {
24
- 1: {
25
- min_branch: 0.9,
26
- min_mutation: 0.7,
27
- min_coverage: 0.9,
28
- requires_contracts: true,
29
- requires_manual_review: true,
30
- },
31
- 2: {
32
- min_branch: 0.8,
33
- min_mutation: 0.5,
34
- min_coverage: 0.8,
35
- requires_contracts: true,
36
- },
37
- 3: {
38
- min_branch: 0.7,
39
- min_mutation: 0.3,
40
- min_coverage: 0.7,
41
- requires_contracts: false,
42
- },
43
- };
44
-
45
- private waiversManager: WaiversManager;
46
-
47
- constructor() {
48
- super();
49
- this.loadTierPolicies();
50
- this.waiversManager = new WaiversManager();
51
- }
52
-
53
- /**
54
- * Load tier policies from configuration
55
- */
56
- private loadTierPolicies(): void {
57
- const policy = this.loadTierPolicy();
58
- if (policy) {
59
- this.tierPolicies = { ...this.tierPolicies, ...policy };
60
- }
61
- }
62
-
63
- /**
64
- * Auto-detect the correct working directory for coverage/mutation reports in monorepos
65
- */
66
- private findReportDirectory(startPath: string = this.getWorkingDirectory()): string {
67
- // Priority 1: Check if the current directory has the reports or test results
68
- if (
69
- this.hasCoverageReports(startPath) ||
70
- this.hasMutationReports(startPath) ||
71
- this.hasTestResults(startPath)
72
- ) {
73
- return startPath;
74
- }
75
-
76
- // Priority 2: Check for npm workspaces configuration
77
- const packageJsonPath = path.join(startPath, 'package.json');
78
- if (this.pathExists(packageJsonPath)) {
79
- try {
80
- const packageJson = this.readJsonFile<any>(packageJsonPath);
81
- if (packageJson?.workspaces) {
82
- const workspaces = packageJson.workspaces;
83
-
84
- // Handle workspace patterns (e.g., ["packages/*", "iterations/*"])
85
- for (const wsPattern of workspaces) {
86
- if (wsPattern.includes('*')) {
87
- const baseDir = wsPattern.split('*')[0];
88
- const fullBaseDir = path.join(startPath, baseDir);
89
-
90
- if (this.pathExists(fullBaseDir)) {
91
- const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
92
- for (const entry of entries) {
93
- if (entry.isDirectory()) {
94
- const wsPath = path.join(fullBaseDir, entry.name);
95
- if (
96
- this.hasCoverageReports(wsPath) ||
97
- this.hasMutationReports(wsPath) ||
98
- this.hasTestResults(wsPath)
99
- ) {
100
- return wsPath;
101
- }
102
- }
103
- }
104
- }
105
- } else {
106
- // Direct workspace path
107
- const wsPath = path.join(startPath, wsPattern);
108
- if (
109
- this.hasCoverageReports(wsPath) ||
110
- this.hasMutationReports(wsPath) ||
111
- this.hasTestResults(wsPath)
112
- ) {
113
- return wsPath;
114
- }
115
- }
116
- }
117
- }
118
-
119
- // Priority 3: If no reports found in workspaces, look for workspaces with test scripts
120
- if (packageJson?.workspaces) {
121
- for (const wsPattern of workspaces) {
122
- if (wsPattern.includes('*')) {
123
- const baseDir = wsPattern.split('*')[0];
124
- const fullBaseDir = path.join(startPath, baseDir);
125
-
126
- if (this.pathExists(fullBaseDir)) {
127
- const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
128
- for (const entry of entries) {
129
- if (entry.isDirectory()) {
130
- const wsPath = path.join(fullBaseDir, entry.name);
131
- if (this.hasTestScript(wsPath)) {
132
- // Found a workspace with tests, prefer this even without reports
133
- return wsPath;
134
- }
135
- }
136
- }
137
- }
138
- } else {
139
- const wsPath = path.join(startPath, wsPattern);
140
- if (this.hasTestScript(wsPath)) {
141
- return wsPath;
142
- }
143
- }
144
- }
145
- }
146
- } catch (error) {
147
- // Ignore workspace parsing errors
148
- }
149
- }
150
-
151
- // Fall back to original working directory
152
- return startPath;
153
- }
154
-
155
- /**
156
- * Check if a directory has coverage reports
157
- */
158
- private hasCoverageReports(dirPath: string): boolean {
159
- const coveragePath = path.join(dirPath, 'coverage', 'coverage-final.json');
160
- return this.pathExists(coveragePath);
161
- }
162
-
163
- /**
164
- * Check if a directory has mutation reports
165
- */
166
- private hasMutationReports(dirPath: string): boolean {
167
- const mutationPath = path.join(dirPath, 'reports', 'mutation', 'mutation.json');
168
- return this.pathExists(mutationPath);
169
- }
170
-
171
- /**
172
- * Check if a directory has test results
173
- */
174
- private hasTestResults(dirPath: string): boolean {
175
- const testResultsPath = path.join(dirPath, 'test-results');
176
- if (this.pathExists(testResultsPath)) {
177
- try {
178
- const entries = fs.readdirSync(testResultsPath);
179
- return entries.some((entry) => entry.endsWith('.json') || entry.endsWith('.xml'));
180
- } catch (error) {
181
- // Ignore read errors
182
- }
183
- }
184
- return false;
185
- }
186
-
187
- /**
188
- * Check if a directory has a package.json with test scripts
189
- */
190
- private hasTestScript(dirPath: string): boolean {
191
- const packageJsonPath = path.join(dirPath, 'package.json');
192
- if (this.pathExists(packageJsonPath)) {
193
- try {
194
- const packageJson = this.readJsonFile<any>(packageJsonPath);
195
- return !!packageJson?.scripts?.test;
196
- } catch (error) {
197
- // Ignore parse errors
198
- }
199
- }
200
- return false;
201
- }
202
-
203
- /**
204
- * Check if a waiver applies to the given gate
205
- */
206
- private async checkWaiver(
207
- gate: string,
208
- workingDirectory?: string
209
- ): Promise<{
210
- waived: boolean;
211
- waiver?: WaiverConfig;
212
- reason?: string;
213
- }> {
214
- try {
215
- const waivers = await this.waiversManager.getWaiversByGate(gate);
216
- if (waivers.length === 0) {
217
- return { waived: false };
218
- }
219
-
220
- // Check if any waiver applies (for now, return the first active one)
221
- for (const waiver of waivers) {
222
- const status = await this.waiversManager.checkWaiverStatus(waiver.created_at);
223
- if (status.active) {
224
- return { waived: true, waiver };
225
- }
226
- }
227
-
228
- return { waived: false };
229
- } catch (error) {
230
- return { waived: false, reason: `Waiver check failed: ${error}` };
231
- }
232
- }
233
-
234
- /**
235
- * Load and validate working spec from project
236
- */
237
- private async loadWorkingSpec(workingDirectory?: string): Promise<{
238
- spec?: any;
239
- experiment_mode?: boolean;
240
- human_override?: HumanOverride;
241
- ai_assessment?: AIAssessment;
242
- errors?: string[];
243
- }> {
244
- try {
245
- const specPath = path.join(
246
- workingDirectory || this.getWorkingDirectory(),
247
- '.caws/working-spec.yml'
248
- );
249
-
250
- if (!this.pathExists(specPath)) {
251
- return { errors: ['Working spec not found at .caws/working-spec.yml'] };
252
- }
253
-
254
- const spec = await this.readYamlFile(specPath);
255
- if (!spec) {
256
- return { errors: ['Failed to parse working spec'] };
257
- }
258
-
259
- return {
260
- spec,
261
- experiment_mode: spec.experiment_mode,
262
- human_override: spec.human_override,
263
- ai_assessment: spec.ai_assessment,
264
- };
265
- } catch (error) {
266
- return { errors: [`Failed to load working spec: ${error}`] };
267
- }
268
- }
269
-
270
- /**
271
- * Check if human override applies to waive requirements
272
- */
273
- private checkHumanOverride(
274
- humanOverride: HumanOverride | undefined,
275
- requirement: string
276
- ): { waived: boolean; reason?: string } {
277
- if (!humanOverride) {
278
- return { waived: false };
279
- }
280
-
281
- if (humanOverride.waived_requirements?.includes(requirement)) {
282
- return {
283
- waived: true,
284
- reason: `Human override by ${humanOverride.approved_by}: ${humanOverride.reason}`,
285
- };
286
- }
287
-
288
- return { waived: false };
289
- }
290
-
291
- /**
292
- * Check if experiment mode applies reduced requirements
293
- */
294
- private checkExperimentMode(experimentMode: boolean | undefined): {
295
- reduced: boolean;
296
- adjustments?: Record<string, any>;
297
- } {
298
- if (!experimentMode) {
299
- return { reduced: false };
300
- }
301
-
302
- return {
303
- reduced: true,
304
- adjustments: {
305
- skip_mutation: true,
306
- skip_contracts: true,
307
- reduced_coverage: 0.5, // Minimum coverage for experiments
308
- skip_manual_review: true,
309
- },
310
- };
311
- }
312
-
313
- /**
314
- * Check branch coverage against tier requirements
315
- */
316
- async checkCoverage(options: GateCheckOptions): Promise<GateResult> {
317
- try {
318
- // Check waivers and overrides first
319
- const waiverCheck = await this.checkWaiver('coverage', options.workingDirectory);
320
- if (waiverCheck.waived) {
321
- return {
322
- passed: true,
323
- score: 1.0, // Waived checks pass with perfect score
324
- details: {
325
- waived: true,
326
- waiver_reason: waiverCheck.waiver?.reason,
327
- waiver_owner: waiverCheck.waiver?.owner,
328
- },
329
- tier: options.tier,
330
- };
331
- }
332
-
333
- // Load working spec for overrides and experiment mode
334
- const specData = await this.loadWorkingSpec(options.workingDirectory);
335
-
336
- // Check human override
337
- const overrideCheck = this.checkHumanOverride(specData.human_override, 'coverage');
338
- if (overrideCheck.waived) {
339
- return {
340
- passed: true,
341
- score: 1.0,
342
- details: {
343
- overridden: true,
344
- override_reason: overrideCheck.reason,
345
- },
346
- tier: options.tier,
347
- };
348
- }
349
-
350
- // Check experiment mode
351
- const experimentCheck = this.checkExperimentMode(specData.experiment_mode);
352
-
353
- let effectiveTier = options.tier;
354
- if (experimentCheck.reduced && experimentCheck.adjustments?.reduced_coverage) {
355
- // For experiments, use reduced coverage requirement
356
- effectiveTier = 4; // Special experiment tier
357
- this.tierPolicies[4] = {
358
- min_branch: experimentCheck.adjustments.reduced_coverage,
359
- min_mutation: 0,
360
- min_coverage: experimentCheck.adjustments.reduced_coverage,
361
- requires_contracts: false,
362
- requires_manual_review: false,
363
- };
364
- }
365
-
366
- // Auto-detect the correct directory for coverage reports
367
- const reportDir = this.findReportDirectory(
368
- options.workingDirectory || this.getWorkingDirectory()
369
- );
370
- const coveragePath = path.join(reportDir, 'coverage', 'coverage-final.json');
371
-
372
- if (!this.pathExists(coveragePath)) {
373
- return {
374
- passed: false,
375
- score: 0,
376
- details: {
377
- error: 'Coverage report not found. Run tests with coverage first.',
378
- searched_paths: [
379
- path.join(reportDir, 'coverage', 'coverage-final.json'),
380
- path.join(this.getWorkingDirectory(), 'coverage', 'coverage-final.json'),
381
- ],
382
- expected_format: 'Istanbul coverage format (coverage-final.json)',
383
- expected_schema: {
384
- description: 'JSON object with coverage data by file',
385
- example: {
386
- '/path/to/file.js': {
387
- statementMap: {
388
- /* ... */
389
- },
390
- fnMap: {
391
- /* ... */
392
- },
393
- branchMap: {
394
- /* ... */
395
- },
396
- s: {
397
- /* hit counts */
398
- },
399
- f: {
400
- /* function hits */
401
- },
402
- b: {
403
- /* branch hits */
404
- },
405
- },
406
- },
407
- },
408
- run_command: 'npm test -- --coverage --coverageReporters=json',
409
- alternative_commands: [
410
- 'npm run test:coverage',
411
- 'jest --coverage --coverageReporters=json',
412
- 'vitest run --coverage',
413
- ],
414
- workspace_hint:
415
- reportDir !== this.getWorkingDirectory()
416
- ? `Auto-detected workspace: ${path.relative(this.getWorkingDirectory(), reportDir)}`
417
- : 'Run from workspace directory if using monorepo',
418
- waiver_available: true,
419
- waiver_suggestion:
420
- 'If this is an exceptional case, consider creating a coverage waiver',
421
- waiver_command:
422
- 'caws waivers create --title="Coverage waiver" --reason=emergency_hotfix --gates=coverage',
423
- },
424
- errors: [
425
- `Coverage report not found at ${path.relative(this.getWorkingDirectory(), coveragePath)}`,
426
- ],
427
- };
428
- }
429
-
430
- const coverageData = this.readJsonFile<any>(coveragePath);
431
- if (!coverageData) {
432
- return {
433
- passed: false,
434
- score: 0,
435
- details: { error: 'Failed to parse coverage data' },
436
- errors: ['Failed to parse coverage data'],
437
- };
438
- }
439
-
440
- // Calculate coverage from detailed data
441
- let totalStatements = 0;
442
- let coveredStatements = 0;
443
- let totalBranches = 0;
444
- let coveredBranches = 0;
445
- let totalFunctions = 0;
446
- let coveredFunctions = 0;
447
-
448
- for (const file of Object.values(coverageData)) {
449
- const fileData = file as any;
450
- if (fileData.s) {
451
- totalStatements += Object.keys(fileData.s).length;
452
- coveredStatements += Object.values(fileData.s).filter((s: any) => s > 0).length;
453
- }
454
- if (fileData.b) {
455
- for (const branches of Object.values(fileData.b) as number[][]) {
456
- totalBranches += branches.length;
457
- coveredBranches += branches.filter((b: number) => b > 0).length;
458
- }
459
- }
460
- if (fileData.f) {
461
- totalFunctions += Object.keys(fileData.f).length;
462
- coveredFunctions += Object.values(fileData.f).filter((f: any) => f > 0).length;
463
- }
464
- }
465
-
466
- // Calculate percentages
467
- const statementsPct = totalStatements > 0 ? (coveredStatements / totalStatements) * 100 : 0;
468
- const branchesPct = totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0;
469
- const functionsPct = totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0;
470
-
471
- const branchCoverage = branchesPct / 100;
472
- const policy = this.tierPolicies[effectiveTier];
473
- const passed = branchCoverage >= policy.min_branch;
474
-
475
- return {
476
- passed,
477
- score: branchCoverage,
478
- details: {
479
- branch_coverage: branchCoverage,
480
- required_branch: policy.min_branch,
481
- functions_coverage: functionsPct / 100,
482
- lines_coverage: statementsPct / 100,
483
- statements_coverage: statementsPct / 100,
484
- },
485
- };
486
- } catch (error) {
487
- return {
488
- passed: false,
489
- score: 0,
490
- details: { error: `Coverage check failed: ${error}` },
491
- errors: [`Coverage check failed: ${error}`],
492
- };
493
- }
494
- }
495
-
496
- /**
497
- * Check mutation testing score
498
- */
499
- async checkMutation(options: GateCheckOptions): Promise<GateResult> {
500
- try {
501
- // Check waivers and overrides first
502
- const waiverCheck = await this.checkWaiver('mutation', options.workingDirectory);
503
- if (waiverCheck.waived) {
504
- return {
505
- passed: true,
506
- score: 1.0,
507
- details: {
508
- waived: true,
509
- waiver_reason: waiverCheck.waiver?.reason,
510
- waiver_owner: waiverCheck.waiver?.owner,
511
- },
512
- tier: options.tier,
513
- };
514
- }
515
-
516
- // Load working spec for overrides and experiment mode
517
- const specData = await this.loadWorkingSpec(options.workingDirectory);
518
-
519
- // Check human override
520
- const overrideCheck = this.checkHumanOverride(specData.human_override, 'mutation_testing');
521
- if (overrideCheck.waived) {
522
- return {
523
- passed: true,
524
- score: 1.0,
525
- details: {
526
- overridden: true,
527
- override_reason: overrideCheck.reason,
528
- },
529
- tier: options.tier,
530
- };
531
- }
532
-
533
- // Check experiment mode
534
- const experimentCheck = this.checkExperimentMode(specData.experiment_mode);
535
- if (experimentCheck.reduced && experimentCheck.adjustments?.skip_mutation) {
536
- return {
537
- passed: true,
538
- score: 1.0,
539
- details: {
540
- experiment_mode: true,
541
- mutation_skipped: true,
542
- },
543
- tier: options.tier,
544
- };
545
- }
546
-
547
- // Auto-detect the correct directory for mutation reports
548
- const reportDir = this.findReportDirectory(
549
- options.workingDirectory || this.getWorkingDirectory()
550
- );
551
- const mutationPath = path.join(reportDir, 'reports', 'mutation', 'mutation.json');
552
-
553
- if (!this.pathExists(mutationPath)) {
554
- return {
555
- passed: false,
556
- score: 0,
557
- details: {
558
- error: 'Mutation report not found. Run mutation tests first.',
559
- searched_paths: [
560
- path.join(reportDir, 'reports', 'mutation', 'mutation.json'),
561
- path.join(this.getWorkingDirectory(), 'reports', 'mutation', 'mutation.json'),
562
- ],
563
- expected_format: 'Stryker mutation testing JSON report',
564
- expected_schema: {
565
- description: 'JSON object with mutation testing results',
566
- example: {
567
- files: {
568
- /* file-specific results */
569
- },
570
- testFiles: {
571
- /* test file results */
572
- },
573
- mutants: [
574
- {
575
- /* mutant details */
576
- },
577
- ],
578
- metrics: {
579
- killed: 85,
580
- survived: 5,
581
- timeout: 2,
582
- totalDetected: 92,
583
- totalUndetected: 0,
584
- totalValid: 92,
585
- },
586
- },
587
- },
588
- run_command: 'npx stryker run',
589
- alternative_commands: [
590
- 'npm run test:mutation',
591
- 'npx stryker run --configFile stryker.conf.json',
592
- 'yarn mutation:test',
593
- ],
594
- workspace_hint:
595
- reportDir !== this.getWorkingDirectory()
596
- ? `Auto-detected workspace: ${path.relative(this.getWorkingDirectory(), reportDir)}`
597
- : 'Run from workspace directory if using monorepo',
598
- },
599
- errors: [
600
- `Mutation report not found at ${path.relative(this.getWorkingDirectory(), mutationPath)}`,
601
- ],
602
- };
603
- }
604
-
605
- const mutationData = this.readJsonFile<MutationData>(mutationPath);
606
- if (!mutationData) {
607
- return {
608
- passed: false,
609
- score: 0,
610
- details: { error: 'Failed to parse mutation data' },
611
- errors: ['Failed to parse mutation data'],
612
- };
613
- }
614
-
615
- const killed = mutationData.metrics.killed || 0;
616
- const total = mutationData.metrics.totalDetected || 1;
617
- const mutationScore = killed / total;
618
- const policy = this.tierPolicies[options.tier];
619
- const passed = mutationScore >= policy.min_mutation;
620
-
621
- return {
622
- passed,
623
- score: mutationScore,
624
- details: {
625
- mutation_score: mutationScore,
626
- required_mutation: policy.min_mutation,
627
- killed,
628
- total,
629
- survived: mutationData.metrics.survived || 0,
630
- },
631
- };
632
- } catch (error) {
633
- return {
634
- passed: false,
635
- score: 0,
636
- details: { error: `Mutation check failed: ${error}` },
637
- errors: [`Mutation check failed: ${error}`],
638
- };
639
- }
640
- }
641
-
642
- /**
643
- * Check contract test compliance
644
- */
645
- async checkContracts(options: GateCheckOptions): Promise<GateResult> {
646
- try {
647
- // Check waivers and overrides first
648
- const waiverCheck = await this.checkWaiver('contracts', options.workingDirectory);
649
- if (waiverCheck.waived) {
650
- return {
651
- passed: true,
652
- score: 1.0,
653
- details: {
654
- waived: true,
655
- waiver_reason: waiverCheck.waiver?.reason,
656
- waiver_owner: waiverCheck.waiver?.owner,
657
- },
658
- tier: options.tier,
659
- };
660
- }
661
-
662
- const policy = this.tierPolicies[options.tier];
663
-
664
- if (!policy.requires_contracts) {
665
- return {
666
- passed: true,
667
- score: 1.0,
668
- details: { contracts_required: false, tier: options.tier },
669
- };
670
- }
671
-
672
- // Auto-detect the correct directory for contract test results
673
- const reportDir = this.findReportDirectory(
674
- options.workingDirectory || this.getWorkingDirectory()
675
- );
676
- const contractResultsPath = path.join(reportDir, 'test-results', 'contract-results.json');
677
-
678
- if (!this.pathExists(contractResultsPath)) {
679
- return {
680
- passed: false,
681
- score: 0,
682
- details: {
683
- error: 'Contract test results not found',
684
- searched_paths: [
685
- path.join(reportDir, 'test-results', 'contract-results.json'),
686
- path.join(this.getWorkingDirectory(), 'test-results', 'contract-results.json'),
687
- path.join(reportDir, '.caws', 'contract-results.json'),
688
- path.join(this.getWorkingDirectory(), '.caws', 'contract-results.json'),
689
- ],
690
- expected_format:
691
- 'JSON with { tests: [], passed: boolean, numPassed: number, numTotal: number }',
692
- example_command:
693
- 'npm run test:contract -- --json --outputFile=test-results/contract-results.json',
694
- },
695
- errors: [
696
- `Contract test results not found. Searched in: ${[
697
- path.relative(
698
- this.getWorkingDirectory(),
699
- path.join(reportDir, 'test-results', 'contract-results.json')
700
- ),
701
- 'test-results/contract-results.json',
702
- '.caws/contract-results.json',
703
- ].join(', ')}`,
704
- ],
705
- };
706
- }
707
-
708
- const results = this.readJsonFile<ContractTestResults>(contractResultsPath);
709
- if (!results) {
710
- return {
711
- passed: false,
712
- score: 0,
713
- details: { error: 'Failed to parse contract test results' },
714
- errors: ['Failed to parse contract test results'],
715
- };
716
- }
717
-
718
- const passed = results.numPassed === results.numTotal && results.numTotal > 0;
719
-
720
- return {
721
- passed,
722
- score: passed ? 1.0 : 0,
723
- details: {
724
- tests_passed: results.numPassed,
725
- tests_total: results.numTotal,
726
- consumer_tests: results.consumer || false,
727
- provider_tests: results.provider || false,
728
- },
729
- };
730
- } catch (error) {
731
- return {
732
- passed: false,
733
- score: 0,
734
- details: { error: `Contract check failed: ${error}` },
735
- errors: [`Contract check failed: ${error}`],
736
- };
737
- }
738
- }
739
-
740
- /**
741
- * Calculate overall trust score
742
- */
743
- async calculateTrustScore(options: GateCheckOptions): Promise<GateResult> {
744
- try {
745
- // Run all gate checks
746
- const [coverageResult, mutationResult, contractResult] = await Promise.all([
747
- this.checkCoverage(options),
748
- this.checkMutation(options),
749
- this.checkContracts(options),
750
- ]);
751
-
752
- // Load provenance if available
753
- let provenance = null;
754
- try {
755
- const provenancePath = path.join(
756
- options.workingDirectory || this.getWorkingDirectory(),
757
- '.agent/provenance.json'
758
- );
759
- if (this.pathExists(provenancePath)) {
760
- provenance = this.readJsonFile(provenancePath);
761
- }
762
- } catch {
763
- // Provenance not available
764
- }
765
-
766
- // CAWS trust score weights
767
- const weights = {
768
- coverage: 0.3,
769
- mutation: 0.3,
770
- contracts: 0.2,
771
- a11y: 0.1,
772
- perf: 0.1,
773
- };
774
-
775
- // Calculate weighted score
776
- let totalScore = 0;
777
- let totalWeight = 0;
778
-
779
- // Coverage component
780
- totalScore += coverageResult.score * weights.coverage;
781
- totalWeight += weights.coverage;
782
-
783
- // Mutation component
784
- totalScore += mutationResult.score * weights.mutation;
785
- totalWeight += weights.mutation;
786
-
787
- // Contracts component
788
- totalScore += contractResult.score * weights.contracts;
789
- totalWeight += weights.contracts;
790
-
791
- // A11y component (placeholder - would check axe results)
792
- const a11yScore = provenance?.results?.a11y === 'pass' ? 1.0 : 0.5;
793
- totalScore += a11yScore * weights.a11y;
794
- totalWeight += weights.a11y;
795
-
796
- // Performance component (placeholder - would check perf budgets)
797
- const perfScore = provenance?.results?.perf ? 0.8 : 0.5;
798
- totalScore += perfScore * weights.perf;
799
- totalWeight += weights.perf;
800
-
801
- const trustScore = totalScore / totalWeight;
802
- const tierPolicy = this.tierPolicies[options.tier];
803
- const passed = trustScore >= 0.8;
804
-
805
- // Apply tier-specific penalties
806
- let adjustedScore = trustScore;
807
- if (options.tier <= 2 && !contractResult.passed) {
808
- adjustedScore *= 0.8; // 20% penalty for missing contracts on high tiers
809
- }
810
-
811
- return {
812
- passed,
813
- score: adjustedScore,
814
- details: {
815
- tier: options.tier,
816
- tier_policy: tierPolicy,
817
- coverage: coverageResult,
818
- mutation: mutationResult,
819
- contracts: contractResult,
820
- a11y: { score: a11yScore, details: provenance?.results?.a11y },
821
- perf: { score: perfScore, details: provenance?.results?.perf },
822
- raw_score: trustScore,
823
- weights,
824
- },
825
- };
826
- } catch (error) {
827
- return {
828
- passed: false,
829
- score: 0,
830
- details: { error: `Trust score calculation failed: ${error}` },
831
- errors: [`Trust score calculation failed: ${error}`],
832
- };
833
- }
834
- }
835
-
836
- /**
837
- * Get tier policy for a specific tier
838
- */
839
- getTierPolicy(tier: number): TierPolicy | null {
840
- return this.tierPolicies[tier] || null;
841
- }
842
-
843
- /**
844
- * Get all available tiers
845
- */
846
- getAvailableTiers(): number[] {
847
- return Object.keys(this.tierPolicies).map(Number);
848
- }
849
- }