@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.
Files changed (117) 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/specs.js +40 -1
  5. package/dist/commands/status.js +2 -2
  6. package/dist/commands/tool.js +2 -3
  7. package/dist/config/index.js +17 -8
  8. package/dist/generators/working-spec.js +19 -6
  9. package/dist/scaffold/git-hooks.js +127 -29
  10. package/dist/scaffold/index.js +53 -7
  11. package/dist/templates/.caws/tools/README.md +20 -0
  12. package/dist/templates/.cursor/README.md +311 -0
  13. package/dist/templates/.cursor/hooks/audit.sh +55 -0
  14. package/dist/templates/.cursor/hooks/block-dangerous.sh +83 -0
  15. package/dist/templates/.cursor/hooks/caws-quality-check.sh +52 -0
  16. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +130 -0
  17. package/dist/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
  18. package/dist/templates/.cursor/hooks/format.sh +38 -0
  19. package/dist/templates/.cursor/hooks/naming-check.sh +64 -0
  20. package/dist/templates/.cursor/hooks/scan-secrets.sh +46 -0
  21. package/dist/templates/.cursor/hooks/scope-guard.sh +52 -0
  22. package/dist/templates/.cursor/hooks/validate-spec.sh +83 -0
  23. package/dist/templates/.cursor/hooks.json +59 -0
  24. package/dist/templates/.cursor/rules/00-claims-verification.mdc +144 -0
  25. package/dist/templates/.cursor/rules/01-working-style.mdc +50 -0
  26. package/dist/templates/.cursor/rules/02-quality-gates.mdc +370 -0
  27. package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
  28. package/dist/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
  29. package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
  30. package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
  31. package/dist/templates/.cursor/rules/07-process-ops.mdc +20 -0
  32. package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
  33. package/dist/templates/.cursor/rules/09-docstrings.mdc +89 -0
  34. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +390 -0
  35. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +385 -0
  36. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +516 -0
  37. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +588 -0
  38. package/dist/templates/.cursor/rules/README.md +148 -0
  39. package/dist/templates/.github/copilot/instructions.md +311 -0
  40. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
  41. package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
  42. package/dist/templates/.vscode/launch.json +56 -0
  43. package/dist/templates/.vscode/settings.json +93 -0
  44. package/dist/templates/.windsurf/workflows/caws-guided-development.md +92 -0
  45. package/dist/templates/COMMIT_CONVENTIONS.md +86 -0
  46. package/dist/templates/OIDC_SETUP.md +300 -0
  47. package/dist/templates/agents.md +1047 -0
  48. package/dist/templates/codemod/README.md +1 -0
  49. package/dist/templates/codemod/test.js +93 -0
  50. package/dist/templates/docs/README.md +150 -0
  51. package/dist/templates/scripts/quality-gates/check-god-objects.js +146 -0
  52. package/dist/templates/scripts/quality-gates/run-quality-gates.js +50 -0
  53. package/dist/templates/scripts/v3/analysis/todo_analyzer.py +1997 -0
  54. package/dist/tool-loader.js +6 -1
  55. package/dist/tool-validator.js +8 -2
  56. package/dist/utils/detection.js +4 -3
  57. package/dist/utils/git-lock.js +118 -0
  58. package/dist/utils/gitignore-updater.js +148 -0
  59. package/dist/utils/quality-gates.js +47 -7
  60. package/dist/utils/spec-resolver.js +23 -3
  61. package/dist/utils/yaml-validation.js +155 -0
  62. package/dist/validation/spec-validation.js +81 -2
  63. package/package.json +2 -2
  64. package/templates/.caws/schemas/waivers.schema.json +30 -0
  65. package/templates/.caws/schemas/working-spec.schema.json +133 -0
  66. package/templates/.caws/templates/working-spec.template.yml +74 -0
  67. package/templates/.caws/tools/README.md +20 -0
  68. package/templates/.caws/tools/scope-guard.js +208 -0
  69. package/templates/.caws/tools-allow.json +331 -0
  70. package/templates/.caws/waivers.yml +19 -0
  71. package/templates/.cursor/hooks/scope-guard.sh +2 -2
  72. package/templates/.cursor/hooks/validate-spec.sh +42 -7
  73. package/templates/apps/tools/caws/COMPLETION_REPORT.md +0 -331
  74. package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +0 -360
  75. package/templates/apps/tools/caws/README.md +0 -463
  76. package/templates/apps/tools/caws/TEST_STATUS.md +0 -365
  77. package/templates/apps/tools/caws/attest.js +0 -357
  78. package/templates/apps/tools/caws/ci-optimizer.js +0 -642
  79. package/templates/apps/tools/caws/config.ts +0 -245
  80. package/templates/apps/tools/caws/cross-functional.js +0 -876
  81. package/templates/apps/tools/caws/dashboard.js +0 -1112
  82. package/templates/apps/tools/caws/flake-detector.ts +0 -362
  83. package/templates/apps/tools/caws/gates.js +0 -198
  84. package/templates/apps/tools/caws/gates.ts +0 -271
  85. package/templates/apps/tools/caws/language-adapters.ts +0 -381
  86. package/templates/apps/tools/caws/language-support.d.ts +0 -367
  87. package/templates/apps/tools/caws/language-support.d.ts.map +0 -1
  88. package/templates/apps/tools/caws/language-support.js +0 -585
  89. package/templates/apps/tools/caws/legacy-assessment.ts +0 -408
  90. package/templates/apps/tools/caws/legacy-assessor.js +0 -764
  91. package/templates/apps/tools/caws/mutant-analyzer.js +0 -734
  92. package/templates/apps/tools/caws/perf-budgets.ts +0 -349
  93. package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
  94. package/templates/apps/tools/caws/property-testing.js +0 -707
  95. package/templates/apps/tools/caws/provenance.d.ts +0 -14
  96. package/templates/apps/tools/caws/provenance.d.ts.map +0 -1
  97. package/templates/apps/tools/caws/provenance.js +0 -132
  98. package/templates/apps/tools/caws/provenance.js.backup +0 -73
  99. package/templates/apps/tools/caws/provenance.ts +0 -211
  100. package/templates/apps/tools/caws/security-provenance.ts +0 -483
  101. package/templates/apps/tools/caws/shared/base-tool.ts +0 -281
  102. package/templates/apps/tools/caws/shared/config-manager.ts +0 -366
  103. package/templates/apps/tools/caws/shared/gate-checker.ts +0 -849
  104. package/templates/apps/tools/caws/shared/types.ts +0 -444
  105. package/templates/apps/tools/caws/shared/validator.ts +0 -305
  106. package/templates/apps/tools/caws/shared/waivers-manager.ts +0 -174
  107. package/templates/apps/tools/caws/spec-test-mapper.ts +0 -391
  108. package/templates/apps/tools/caws/test-quality.js +0 -578
  109. package/templates/apps/tools/caws/validate.js +0 -76
  110. package/templates/apps/tools/caws/validate.ts +0 -228
  111. package/templates/apps/tools/caws/waivers.js +0 -344
  112. /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/waivers.schema.json +0 -0
  113. /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/working-spec.schema.json +0 -0
  114. /package/{templates/apps/tools/caws → dist/templates/.caws}/templates/working-spec.template.yml +0 -0
  115. /package/{templates/apps/tools/caws → dist/templates/.caws/tools}/scope-guard.js +0 -0
  116. /package/{templates/apps/tools/caws → dist/templates/.caws}/tools-allow.json +0 -0
  117. /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
- }