@paths.design/caws-cli 2.0.0 → 3.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 (50) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +101 -96
  3. package/package.json +3 -3
  4. package/templates/agents.md +820 -0
  5. package/templates/apps/tools/caws/COMPLETION_REPORT.md +331 -0
  6. package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +360 -0
  7. package/templates/apps/tools/caws/README.md +463 -0
  8. package/templates/apps/tools/caws/TEST_STATUS.md +365 -0
  9. package/templates/apps/tools/caws/attest.js +357 -0
  10. package/templates/apps/tools/caws/ci-optimizer.js +642 -0
  11. package/templates/apps/tools/caws/config.ts +245 -0
  12. package/templates/apps/tools/caws/cross-functional.js +876 -0
  13. package/templates/apps/tools/caws/dashboard.js +1112 -0
  14. package/templates/apps/tools/caws/flake-detector.ts +362 -0
  15. package/templates/apps/tools/caws/gates.js +198 -0
  16. package/templates/apps/tools/caws/gates.ts +237 -0
  17. package/templates/apps/tools/caws/language-adapters.ts +381 -0
  18. package/templates/apps/tools/caws/language-support.d.ts +367 -0
  19. package/templates/apps/tools/caws/language-support.d.ts.map +1 -0
  20. package/templates/apps/tools/caws/language-support.js +585 -0
  21. package/templates/apps/tools/caws/legacy-assessment.ts +408 -0
  22. package/templates/apps/tools/caws/legacy-assessor.js +764 -0
  23. package/templates/apps/tools/caws/mutant-analyzer.js +734 -0
  24. package/templates/apps/tools/caws/perf-budgets.ts +349 -0
  25. package/templates/apps/tools/caws/property-testing.js +707 -0
  26. package/templates/apps/tools/caws/provenance.d.ts +14 -0
  27. package/templates/apps/tools/caws/provenance.d.ts.map +1 -0
  28. package/templates/apps/tools/caws/provenance.js +132 -0
  29. package/templates/apps/tools/caws/provenance.ts +211 -0
  30. package/templates/apps/tools/caws/schemas/waivers.schema.json +30 -0
  31. package/templates/apps/tools/caws/schemas/working-spec.schema.json +115 -0
  32. package/templates/apps/tools/caws/scope-guard.js +208 -0
  33. package/templates/apps/tools/caws/security-provenance.ts +483 -0
  34. package/templates/apps/tools/caws/shared/base-tool.ts +281 -0
  35. package/templates/apps/tools/caws/shared/config-manager.ts +366 -0
  36. package/templates/apps/tools/caws/shared/gate-checker.ts +597 -0
  37. package/templates/apps/tools/caws/shared/types.ts +444 -0
  38. package/templates/apps/tools/caws/shared/validator.ts +305 -0
  39. package/templates/apps/tools/caws/shared/waivers-manager.ts +174 -0
  40. package/templates/apps/tools/caws/spec-test-mapper.ts +391 -0
  41. package/templates/apps/tools/caws/templates/working-spec.template.yml +60 -0
  42. package/templates/apps/tools/caws/test-quality.js +578 -0
  43. package/templates/apps/tools/caws/tools-allow.json +331 -0
  44. package/templates/apps/tools/caws/validate.js +76 -0
  45. package/templates/apps/tools/caws/validate.ts +228 -0
  46. package/templates/apps/tools/caws/waivers.js +344 -0
  47. package/templates/apps/tools/caws/waivers.yml +19 -0
  48. package/templates/codemod/README.md +1 -0
  49. package/templates/codemod/test.js +1 -0
  50. package/templates/docs/README.md +150 -0
@@ -0,0 +1,597 @@
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
+ * Check if a waiver applies to the given gate
65
+ */
66
+ private async checkWaiver(
67
+ gate: string,
68
+ workingDirectory?: string
69
+ ): Promise<{
70
+ waived: boolean;
71
+ waiver?: WaiverConfig;
72
+ reason?: string;
73
+ }> {
74
+ try {
75
+ const waivers = await this.waiversManager.getWaiversByGate(gate);
76
+ if (waivers.length === 0) {
77
+ return { waived: false };
78
+ }
79
+
80
+ // Check if any waiver applies (for now, return the first active one)
81
+ for (const waiver of waivers) {
82
+ const status = await this.waiversManager.checkWaiverStatus(waiver.created_at);
83
+ if (status.active) {
84
+ return { waived: true, waiver };
85
+ }
86
+ }
87
+
88
+ return { waived: false };
89
+ } catch (error) {
90
+ return { waived: false, reason: `Waiver check failed: ${error}` };
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Load and validate working spec from project
96
+ */
97
+ private async loadWorkingSpec(workingDirectory?: string): Promise<{
98
+ spec?: any;
99
+ experiment_mode?: boolean;
100
+ human_override?: HumanOverride;
101
+ ai_assessment?: AIAssessment;
102
+ errors?: string[];
103
+ }> {
104
+ try {
105
+ const specPath = path.join(
106
+ workingDirectory || this.getWorkingDirectory(),
107
+ '.caws/working-spec.yml'
108
+ );
109
+
110
+ if (!this.pathExists(specPath)) {
111
+ return { errors: ['Working spec not found at .caws/working-spec.yml'] };
112
+ }
113
+
114
+ const spec = await this.readYamlFile(specPath);
115
+ if (!spec) {
116
+ return { errors: ['Failed to parse working spec'] };
117
+ }
118
+
119
+ return {
120
+ spec,
121
+ experiment_mode: spec.experiment_mode,
122
+ human_override: spec.human_override,
123
+ ai_assessment: spec.ai_assessment,
124
+ };
125
+ } catch (error) {
126
+ return { errors: [`Failed to load working spec: ${error}`] };
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Check if human override applies to waive requirements
132
+ */
133
+ private checkHumanOverride(
134
+ humanOverride: HumanOverride | undefined,
135
+ requirement: string
136
+ ): { waived: boolean; reason?: string } {
137
+ if (!humanOverride) {
138
+ return { waived: false };
139
+ }
140
+
141
+ if (humanOverride.waived_requirements?.includes(requirement)) {
142
+ return {
143
+ waived: true,
144
+ reason: `Human override by ${humanOverride.approved_by}: ${humanOverride.reason}`,
145
+ };
146
+ }
147
+
148
+ return { waived: false };
149
+ }
150
+
151
+ /**
152
+ * Check if experiment mode applies reduced requirements
153
+ */
154
+ private checkExperimentMode(experimentMode: boolean | undefined): {
155
+ reduced: boolean;
156
+ adjustments?: Record<string, any>;
157
+ } {
158
+ if (!experimentMode) {
159
+ return { reduced: false };
160
+ }
161
+
162
+ return {
163
+ reduced: true,
164
+ adjustments: {
165
+ skip_mutation: true,
166
+ skip_contracts: true,
167
+ reduced_coverage: 0.5, // Minimum coverage for experiments
168
+ skip_manual_review: true,
169
+ },
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Check branch coverage against tier requirements
175
+ */
176
+ async checkCoverage(options: GateCheckOptions): Promise<GateResult> {
177
+ try {
178
+ // Check waivers and overrides first
179
+ const waiverCheck = await this.checkWaiver('coverage', options.workingDirectory);
180
+ if (waiverCheck.waived) {
181
+ return {
182
+ passed: true,
183
+ score: 1.0, // Waived checks pass with perfect score
184
+ details: {
185
+ waived: true,
186
+ waiver_reason: waiverCheck.waiver?.reason,
187
+ waiver_owner: waiverCheck.waiver?.owner,
188
+ },
189
+ tier: options.tier,
190
+ };
191
+ }
192
+
193
+ // Load working spec for overrides and experiment mode
194
+ const specData = await this.loadWorkingSpec(options.workingDirectory);
195
+
196
+ // Check human override
197
+ const overrideCheck = this.checkHumanOverride(specData.human_override, 'coverage');
198
+ if (overrideCheck.waived) {
199
+ return {
200
+ passed: true,
201
+ score: 1.0,
202
+ details: {
203
+ overridden: true,
204
+ override_reason: overrideCheck.reason,
205
+ },
206
+ tier: options.tier,
207
+ };
208
+ }
209
+
210
+ // Check experiment mode
211
+ const experimentCheck = this.checkExperimentMode(specData.experiment_mode);
212
+
213
+ let effectiveTier = options.tier;
214
+ if (experimentCheck.reduced && experimentCheck.adjustments?.reduced_coverage) {
215
+ // For experiments, use reduced coverage requirement
216
+ effectiveTier = 4; // Special experiment tier
217
+ this.tierPolicies[4] = {
218
+ min_branch: experimentCheck.adjustments.reduced_coverage,
219
+ min_mutation: 0,
220
+ min_coverage: experimentCheck.adjustments.reduced_coverage,
221
+ requires_contracts: false,
222
+ requires_manual_review: false,
223
+ };
224
+ }
225
+
226
+ const coveragePath = path.join(
227
+ options.workingDirectory || this.getWorkingDirectory(),
228
+ 'coverage/coverage-final.json'
229
+ );
230
+
231
+ if (!this.pathExists(coveragePath)) {
232
+ return {
233
+ passed: false,
234
+ score: 0,
235
+ details: {
236
+ error: 'Coverage report not found. Run tests with coverage first.',
237
+ },
238
+ errors: ['Coverage report not found'],
239
+ };
240
+ }
241
+
242
+ const coverageData = this.readJsonFile<any>(coveragePath);
243
+ if (!coverageData) {
244
+ return {
245
+ passed: false,
246
+ score: 0,
247
+ details: { error: 'Failed to parse coverage data' },
248
+ errors: ['Failed to parse coverage data'],
249
+ };
250
+ }
251
+
252
+ // Calculate coverage from detailed data
253
+ let totalStatements = 0;
254
+ let coveredStatements = 0;
255
+ let totalBranches = 0;
256
+ let coveredBranches = 0;
257
+ let totalFunctions = 0;
258
+ let coveredFunctions = 0;
259
+
260
+ for (const file of Object.values(coverageData)) {
261
+ const fileData = file as any;
262
+ if (fileData.s) {
263
+ totalStatements += Object.keys(fileData.s).length;
264
+ coveredStatements += Object.values(fileData.s).filter((s: any) => s > 0).length;
265
+ }
266
+ if (fileData.b) {
267
+ for (const branches of Object.values(fileData.b) as number[][]) {
268
+ totalBranches += branches.length;
269
+ coveredBranches += branches.filter((b: number) => b > 0).length;
270
+ }
271
+ }
272
+ if (fileData.f) {
273
+ totalFunctions += Object.keys(fileData.f).length;
274
+ coveredFunctions += Object.values(fileData.f).filter((f: any) => f > 0).length;
275
+ }
276
+ }
277
+
278
+ // Calculate percentages
279
+ const statementsPct = totalStatements > 0 ? (coveredStatements / totalStatements) * 100 : 0;
280
+ const branchesPct = totalBranches > 0 ? (coveredBranches / totalBranches) * 100 : 0;
281
+ const functionsPct = totalFunctions > 0 ? (coveredFunctions / totalFunctions) * 100 : 0;
282
+
283
+ const branchCoverage = branchesPct / 100;
284
+ const policy = this.tierPolicies[effectiveTier];
285
+ const passed = branchCoverage >= policy.min_branch;
286
+
287
+ return {
288
+ passed,
289
+ score: branchCoverage,
290
+ details: {
291
+ branch_coverage: branchCoverage,
292
+ required_branch: policy.min_branch,
293
+ functions_coverage: functionsPct / 100,
294
+ lines_coverage: statementsPct / 100,
295
+ statements_coverage: statementsPct / 100,
296
+ },
297
+ };
298
+ } catch (error) {
299
+ return {
300
+ passed: false,
301
+ score: 0,
302
+ details: { error: `Coverage check failed: ${error}` },
303
+ errors: [`Coverage check failed: ${error}`],
304
+ };
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Check mutation testing score
310
+ */
311
+ async checkMutation(options: GateCheckOptions): Promise<GateResult> {
312
+ try {
313
+ // Check waivers and overrides first
314
+ const waiverCheck = await this.checkWaiver('mutation', options.workingDirectory);
315
+ if (waiverCheck.waived) {
316
+ return {
317
+ passed: true,
318
+ score: 1.0,
319
+ details: {
320
+ waived: true,
321
+ waiver_reason: waiverCheck.waiver?.reason,
322
+ waiver_owner: waiverCheck.waiver?.owner,
323
+ },
324
+ tier: options.tier,
325
+ };
326
+ }
327
+
328
+ // Load working spec for overrides and experiment mode
329
+ const specData = await this.loadWorkingSpec(options.workingDirectory);
330
+
331
+ // Check human override
332
+ const overrideCheck = this.checkHumanOverride(specData.human_override, 'mutation_testing');
333
+ if (overrideCheck.waived) {
334
+ return {
335
+ passed: true,
336
+ score: 1.0,
337
+ details: {
338
+ overridden: true,
339
+ override_reason: overrideCheck.reason,
340
+ },
341
+ tier: options.tier,
342
+ };
343
+ }
344
+
345
+ // Check experiment mode
346
+ const experimentCheck = this.checkExperimentMode(specData.experiment_mode);
347
+ if (experimentCheck.reduced && experimentCheck.adjustments?.skip_mutation) {
348
+ return {
349
+ passed: true,
350
+ score: 1.0,
351
+ details: {
352
+ experiment_mode: true,
353
+ mutation_skipped: true,
354
+ },
355
+ tier: options.tier,
356
+ };
357
+ }
358
+
359
+ const mutationPath = path.join(
360
+ options.workingDirectory || this.getWorkingDirectory(),
361
+ 'reports/mutation/mutation.json'
362
+ );
363
+
364
+ if (!this.pathExists(mutationPath)) {
365
+ return {
366
+ passed: false,
367
+ score: 0,
368
+ details: {
369
+ error: 'Mutation report not found. Run mutation tests first.',
370
+ },
371
+ errors: ['Mutation report not found'],
372
+ };
373
+ }
374
+
375
+ const mutationData = this.readJsonFile<MutationData>(mutationPath);
376
+ if (!mutationData) {
377
+ return {
378
+ passed: false,
379
+ score: 0,
380
+ details: { error: 'Failed to parse mutation data' },
381
+ errors: ['Failed to parse mutation data'],
382
+ };
383
+ }
384
+
385
+ const killed = mutationData.metrics.killed || 0;
386
+ const total = mutationData.metrics.totalDetected || 1;
387
+ const mutationScore = killed / total;
388
+ const policy = this.tierPolicies[options.tier];
389
+ const passed = mutationScore >= policy.min_mutation;
390
+
391
+ return {
392
+ passed,
393
+ score: mutationScore,
394
+ details: {
395
+ mutation_score: mutationScore,
396
+ required_mutation: policy.min_mutation,
397
+ killed,
398
+ total,
399
+ survived: mutationData.metrics.survived || 0,
400
+ },
401
+ };
402
+ } catch (error) {
403
+ return {
404
+ passed: false,
405
+ score: 0,
406
+ details: { error: `Mutation check failed: ${error}` },
407
+ errors: [`Mutation check failed: ${error}`],
408
+ };
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Check contract test compliance
414
+ */
415
+ async checkContracts(options: GateCheckOptions): Promise<GateResult> {
416
+ try {
417
+ // Check waivers and overrides first
418
+ const waiverCheck = await this.checkWaiver('contracts', options.workingDirectory);
419
+ if (waiverCheck.waived) {
420
+ return {
421
+ passed: true,
422
+ score: 1.0,
423
+ details: {
424
+ waived: true,
425
+ waiver_reason: waiverCheck.waiver?.reason,
426
+ waiver_owner: waiverCheck.waiver?.owner,
427
+ },
428
+ tier: options.tier,
429
+ };
430
+ }
431
+
432
+ const policy = this.tierPolicies[options.tier];
433
+
434
+ if (!policy.requires_contracts) {
435
+ return {
436
+ passed: true,
437
+ score: 1.0,
438
+ details: { contracts_required: false, tier: options.tier },
439
+ };
440
+ }
441
+
442
+ const contractResultsPath = path.join(
443
+ options.workingDirectory || this.getWorkingDirectory(),
444
+ 'test-results/contract-results.json'
445
+ );
446
+
447
+ if (!this.pathExists(contractResultsPath)) {
448
+ return {
449
+ passed: false,
450
+ score: 0,
451
+ details: { error: 'Contract test results not found' },
452
+ errors: ['Contract tests not run or results not found'],
453
+ };
454
+ }
455
+
456
+ const results = this.readJsonFile<ContractTestResults>(contractResultsPath);
457
+ if (!results) {
458
+ return {
459
+ passed: false,
460
+ score: 0,
461
+ details: { error: 'Failed to parse contract test results' },
462
+ errors: ['Failed to parse contract test results'],
463
+ };
464
+ }
465
+
466
+ const passed = results.numPassed === results.numTotal && results.numTotal > 0;
467
+
468
+ return {
469
+ passed,
470
+ score: passed ? 1.0 : 0,
471
+ details: {
472
+ tests_passed: results.numPassed,
473
+ tests_total: results.numTotal,
474
+ consumer_tests: results.consumer || false,
475
+ provider_tests: results.provider || false,
476
+ },
477
+ };
478
+ } catch (error) {
479
+ return {
480
+ passed: false,
481
+ score: 0,
482
+ details: { error: `Contract check failed: ${error}` },
483
+ errors: [`Contract check failed: ${error}`],
484
+ };
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Calculate overall trust score
490
+ */
491
+ async calculateTrustScore(options: GateCheckOptions): Promise<GateResult> {
492
+ try {
493
+ // Run all gate checks
494
+ const [coverageResult, mutationResult, contractResult] = await Promise.all([
495
+ this.checkCoverage(options),
496
+ this.checkMutation(options),
497
+ this.checkContracts(options),
498
+ ]);
499
+
500
+ // Load provenance if available
501
+ let provenance = null;
502
+ try {
503
+ const provenancePath = path.join(
504
+ options.workingDirectory || this.getWorkingDirectory(),
505
+ '.agent/provenance.json'
506
+ );
507
+ if (this.pathExists(provenancePath)) {
508
+ provenance = this.readJsonFile(provenancePath);
509
+ }
510
+ } catch {
511
+ // Provenance not available
512
+ }
513
+
514
+ // CAWS trust score weights
515
+ const weights = {
516
+ coverage: 0.3,
517
+ mutation: 0.3,
518
+ contracts: 0.2,
519
+ a11y: 0.1,
520
+ perf: 0.1,
521
+ };
522
+
523
+ // Calculate weighted score
524
+ let totalScore = 0;
525
+ let totalWeight = 0;
526
+
527
+ // Coverage component
528
+ totalScore += coverageResult.score * weights.coverage;
529
+ totalWeight += weights.coverage;
530
+
531
+ // Mutation component
532
+ totalScore += mutationResult.score * weights.mutation;
533
+ totalWeight += weights.mutation;
534
+
535
+ // Contracts component
536
+ totalScore += contractResult.score * weights.contracts;
537
+ totalWeight += weights.contracts;
538
+
539
+ // A11y component (placeholder - would check axe results)
540
+ const a11yScore = provenance?.results?.a11y === 'pass' ? 1.0 : 0.5;
541
+ totalScore += a11yScore * weights.a11y;
542
+ totalWeight += weights.a11y;
543
+
544
+ // Performance component (placeholder - would check perf budgets)
545
+ const perfScore = provenance?.results?.perf ? 0.8 : 0.5;
546
+ totalScore += perfScore * weights.perf;
547
+ totalWeight += weights.perf;
548
+
549
+ const trustScore = totalScore / totalWeight;
550
+ const tierPolicy = this.tierPolicies[options.tier];
551
+ const passed = trustScore >= 0.8;
552
+
553
+ // Apply tier-specific penalties
554
+ let adjustedScore = trustScore;
555
+ if (options.tier <= 2 && !contractResult.passed) {
556
+ adjustedScore *= 0.8; // 20% penalty for missing contracts on high tiers
557
+ }
558
+
559
+ return {
560
+ passed,
561
+ score: adjustedScore,
562
+ details: {
563
+ tier: options.tier,
564
+ tier_policy: tierPolicy,
565
+ coverage: coverageResult,
566
+ mutation: mutationResult,
567
+ contracts: contractResult,
568
+ a11y: { score: a11yScore, details: provenance?.results?.a11y },
569
+ perf: { score: perfScore, details: provenance?.results?.perf },
570
+ raw_score: trustScore,
571
+ weights,
572
+ },
573
+ };
574
+ } catch (error) {
575
+ return {
576
+ passed: false,
577
+ score: 0,
578
+ details: { error: `Trust score calculation failed: ${error}` },
579
+ errors: [`Trust score calculation failed: ${error}`],
580
+ };
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Get tier policy for a specific tier
586
+ */
587
+ getTierPolicy(tier: number): TierPolicy | null {
588
+ return this.tierPolicies[tier] || null;
589
+ }
590
+
591
+ /**
592
+ * Get all available tiers
593
+ */
594
+ getAvailableTiers(): number[] {
595
+ return Object.keys(this.tierPolicies).map(Number);
596
+ }
597
+ }