@paths.design/caws-cli 2.0.1 → 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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -96
- package/package.json +3 -2
- package/templates/agents.md +820 -0
- package/templates/apps/tools/caws/COMPLETION_REPORT.md +331 -0
- package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +360 -0
- package/templates/apps/tools/caws/README.md +463 -0
- package/templates/apps/tools/caws/TEST_STATUS.md +365 -0
- package/templates/apps/tools/caws/attest.js +357 -0
- package/templates/apps/tools/caws/ci-optimizer.js +642 -0
- package/templates/apps/tools/caws/config.ts +245 -0
- package/templates/apps/tools/caws/cross-functional.js +876 -0
- package/templates/apps/tools/caws/dashboard.js +1112 -0
- package/templates/apps/tools/caws/flake-detector.ts +362 -0
- package/templates/apps/tools/caws/gates.js +198 -0
- package/templates/apps/tools/caws/gates.ts +237 -0
- package/templates/apps/tools/caws/language-adapters.ts +381 -0
- package/templates/apps/tools/caws/language-support.d.ts +367 -0
- package/templates/apps/tools/caws/language-support.d.ts.map +1 -0
- package/templates/apps/tools/caws/language-support.js +585 -0
- package/templates/apps/tools/caws/legacy-assessment.ts +408 -0
- package/templates/apps/tools/caws/legacy-assessor.js +764 -0
- package/templates/apps/tools/caws/mutant-analyzer.js +734 -0
- package/templates/apps/tools/caws/perf-budgets.ts +349 -0
- package/templates/apps/tools/caws/property-testing.js +707 -0
- package/templates/apps/tools/caws/provenance.d.ts +14 -0
- package/templates/apps/tools/caws/provenance.d.ts.map +1 -0
- package/templates/apps/tools/caws/provenance.js +132 -0
- package/templates/apps/tools/caws/provenance.ts +211 -0
- package/templates/apps/tools/caws/schemas/waivers.schema.json +30 -0
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +115 -0
- package/templates/apps/tools/caws/scope-guard.js +208 -0
- package/templates/apps/tools/caws/security-provenance.ts +483 -0
- package/templates/apps/tools/caws/shared/base-tool.ts +281 -0
- package/templates/apps/tools/caws/shared/config-manager.ts +366 -0
- package/templates/apps/tools/caws/shared/gate-checker.ts +597 -0
- package/templates/apps/tools/caws/shared/types.ts +444 -0
- package/templates/apps/tools/caws/shared/validator.ts +305 -0
- package/templates/apps/tools/caws/shared/waivers-manager.ts +174 -0
- package/templates/apps/tools/caws/spec-test-mapper.ts +391 -0
- package/templates/apps/tools/caws/templates/working-spec.template.yml +60 -0
- package/templates/apps/tools/caws/test-quality.js +578 -0
- package/templates/apps/tools/caws/tools-allow.json +331 -0
- package/templates/apps/tools/caws/validate.js +76 -0
- package/templates/apps/tools/caws/validate.ts +228 -0
- package/templates/apps/tools/caws/waivers.js +344 -0
- package/templates/apps/tools/caws/waivers.yml +19 -0
- package/templates/codemod/README.md +1 -0
- package/templates/codemod/test.js +1 -0
- 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
|
+
}
|