@aiready/contract-enforcement 0.2.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.
@@ -0,0 +1,18 @@
1
+
2
+ > @aiready/contract-enforcement@0.2.0 build /Users/pengcao/projects/aiready/packages/contract-enforcement
3
+ > tsup src/index.ts --format cjs,esm --dts
4
+
5
+ CLI Building entry: src/index.ts
6
+ CLI Using tsconfig: tsconfig.json
7
+ CLI tsup v8.5.1
8
+ CLI Target: es2020
9
+ CJS Build start
10
+ ESM Build start
11
+ ESM dist/index.mjs 15.46 KB
12
+ ESM ⚡️ Build success in 76ms
13
+ CJS dist/index.js 16.88 KB
14
+ CJS ⚡️ Build success in 86ms
15
+ DTS Build start
16
+ DTS ⚡️ Build success in 4708ms
17
+ DTS dist/index.d.ts 2.45 KB
18
+ DTS dist/index.d.mts 2.45 KB
@@ -0,0 +1,16 @@
1
+
2
+ > @aiready/contract-enforcement@0.1.0 test /Users/pengcao/projects/aiready/packages/contract-enforcement
3
+ > vitest run
4
+
5
+
6
+  RUN  v4.1.1 /Users/pengcao/projects/aiready/packages/contract-enforcement
7
+
8
+ ✓ src/__tests__/scoring.test.ts (7 tests) 3ms
9
+ ✓ src/__tests__/detector.test.ts (14 tests) 23ms
10
+ ✓ src/__tests__/provider.test.ts (5 tests) 2ms
11
+
12
+  Test Files  3 passed (3)
13
+  Tests  26 passed (26)
14
+  Start at  15:17:54
15
+  Duration  2.10s (transform 511ms, setup 0ms, import 3.00s, tests 28ms, environment 0ms)
16
+
@@ -0,0 +1,69 @@
1
+ import * as _aiready_core from '@aiready/core';
2
+ import { Issue, IssueType } from '@aiready/core';
3
+
4
+ declare const ContractEnforcementProvider: _aiready_core.ToolProvider;
5
+
6
+ type DefensivePattern = 'as-any' | 'as-unknown' | 'deep-optional-chain' | 'nullish-literal-default' | 'swallowed-error' | 'env-fallback' | 'unnecessary-guard' | 'any-parameter' | 'any-return';
7
+ interface ContractEnforcementIssue extends Issue {
8
+ type: IssueType.ContractGap;
9
+ pattern: DefensivePattern;
10
+ context: string;
11
+ }
12
+ interface PatternCounts {
13
+ 'as-any': number;
14
+ 'as-unknown': number;
15
+ 'deep-optional-chain': number;
16
+ 'nullish-literal-default': number;
17
+ 'swallowed-error': number;
18
+ 'env-fallback': number;
19
+ 'unnecessary-guard': number;
20
+ 'any-parameter': number;
21
+ 'any-return': number;
22
+ }
23
+ interface ContractEnforcementOptions {
24
+ rootDir: string;
25
+ include?: string[];
26
+ exclude?: string[];
27
+ onProgress?: (processed: number, total: number, message: string) => void;
28
+ minChainDepth?: number;
29
+ }
30
+ interface DetectionResult {
31
+ issues: ContractEnforcementIssue[];
32
+ counts: PatternCounts;
33
+ totalLines: number;
34
+ }
35
+ interface ScoreResult {
36
+ score: number;
37
+ rating: string;
38
+ dimensions: Record<string, number>;
39
+ recommendations: string[];
40
+ }
41
+ interface ContractEnforcementReport {
42
+ summary: {
43
+ sourceFiles: number;
44
+ totalDefensivePatterns: number;
45
+ defensiveDensity: number;
46
+ score: number;
47
+ rating: string;
48
+ dimensions: {
49
+ typeEscapeHatchScore: number;
50
+ fallbackCascadeScore: number;
51
+ errorTransparencyScore: number;
52
+ boundaryValidationScore: number;
53
+ };
54
+ };
55
+ issues: ContractEnforcementIssue[];
56
+ rawData: PatternCounts & {
57
+ sourceFiles: number;
58
+ totalLines: number;
59
+ };
60
+ recommendations: string[];
61
+ }
62
+
63
+ declare function analyzeContractEnforcement(options: ContractEnforcementOptions): Promise<ContractEnforcementReport>;
64
+
65
+ declare function calculateContractEnforcementScore(counts: PatternCounts, totalLines: number, _fileCount: number): ScoreResult;
66
+
67
+ declare function detectDefensivePatterns(filePath: string, code: string, minChainDepth?: number): DetectionResult;
68
+
69
+ export { type ContractEnforcementIssue, type ContractEnforcementOptions, ContractEnforcementProvider, type ContractEnforcementReport, type DefensivePattern, type DetectionResult, type PatternCounts, analyzeContractEnforcement, calculateContractEnforcementScore, detectDefensivePatterns };
@@ -0,0 +1,69 @@
1
+ import * as _aiready_core from '@aiready/core';
2
+ import { Issue, IssueType } from '@aiready/core';
3
+
4
+ declare const ContractEnforcementProvider: _aiready_core.ToolProvider;
5
+
6
+ type DefensivePattern = 'as-any' | 'as-unknown' | 'deep-optional-chain' | 'nullish-literal-default' | 'swallowed-error' | 'env-fallback' | 'unnecessary-guard' | 'any-parameter' | 'any-return';
7
+ interface ContractEnforcementIssue extends Issue {
8
+ type: IssueType.ContractGap;
9
+ pattern: DefensivePattern;
10
+ context: string;
11
+ }
12
+ interface PatternCounts {
13
+ 'as-any': number;
14
+ 'as-unknown': number;
15
+ 'deep-optional-chain': number;
16
+ 'nullish-literal-default': number;
17
+ 'swallowed-error': number;
18
+ 'env-fallback': number;
19
+ 'unnecessary-guard': number;
20
+ 'any-parameter': number;
21
+ 'any-return': number;
22
+ }
23
+ interface ContractEnforcementOptions {
24
+ rootDir: string;
25
+ include?: string[];
26
+ exclude?: string[];
27
+ onProgress?: (processed: number, total: number, message: string) => void;
28
+ minChainDepth?: number;
29
+ }
30
+ interface DetectionResult {
31
+ issues: ContractEnforcementIssue[];
32
+ counts: PatternCounts;
33
+ totalLines: number;
34
+ }
35
+ interface ScoreResult {
36
+ score: number;
37
+ rating: string;
38
+ dimensions: Record<string, number>;
39
+ recommendations: string[];
40
+ }
41
+ interface ContractEnforcementReport {
42
+ summary: {
43
+ sourceFiles: number;
44
+ totalDefensivePatterns: number;
45
+ defensiveDensity: number;
46
+ score: number;
47
+ rating: string;
48
+ dimensions: {
49
+ typeEscapeHatchScore: number;
50
+ fallbackCascadeScore: number;
51
+ errorTransparencyScore: number;
52
+ boundaryValidationScore: number;
53
+ };
54
+ };
55
+ issues: ContractEnforcementIssue[];
56
+ rawData: PatternCounts & {
57
+ sourceFiles: number;
58
+ totalLines: number;
59
+ };
60
+ recommendations: string[];
61
+ }
62
+
63
+ declare function analyzeContractEnforcement(options: ContractEnforcementOptions): Promise<ContractEnforcementReport>;
64
+
65
+ declare function calculateContractEnforcementScore(counts: PatternCounts, totalLines: number, _fileCount: number): ScoreResult;
66
+
67
+ declare function detectDefensivePatterns(filePath: string, code: string, minChainDepth?: number): DetectionResult;
68
+
69
+ export { type ContractEnforcementIssue, type ContractEnforcementOptions, ContractEnforcementProvider, type ContractEnforcementReport, type DefensivePattern, type DetectionResult, type PatternCounts, analyzeContractEnforcement, calculateContractEnforcementScore, detectDefensivePatterns };
package/dist/index.js ADDED
@@ -0,0 +1,484 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ContractEnforcementProvider: () => ContractEnforcementProvider,
24
+ analyzeContractEnforcement: () => analyzeContractEnforcement,
25
+ calculateContractEnforcementScore: () => calculateContractEnforcementScore,
26
+ detectDefensivePatterns: () => detectDefensivePatterns
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+ var import_core4 = require("@aiready/core");
30
+
31
+ // src/provider.ts
32
+ var import_core3 = require("@aiready/core");
33
+
34
+ // src/analyzer.ts
35
+ var import_fs = require("fs");
36
+ var import_core2 = require("@aiready/core");
37
+
38
+ // src/detector.ts
39
+ var import_typescript_estree = require("@typescript-eslint/typescript-estree");
40
+ var import_core = require("@aiready/core");
41
+
42
+ // src/types.ts
43
+ var ZERO_COUNTS = {
44
+ "as-any": 0,
45
+ "as-unknown": 0,
46
+ "deep-optional-chain": 0,
47
+ "nullish-literal-default": 0,
48
+ "swallowed-error": 0,
49
+ "env-fallback": 0,
50
+ "unnecessary-guard": 0,
51
+ "any-parameter": 0,
52
+ "any-return": 0
53
+ };
54
+
55
+ // src/detector.ts
56
+ function makeIssue(pattern, severity, message, filePath, line, column, context) {
57
+ return {
58
+ type: import_core.IssueType.ContractGap,
59
+ severity,
60
+ pattern,
61
+ message,
62
+ location: { file: filePath, line, column },
63
+ context,
64
+ suggestion: getSuggestion(pattern)
65
+ };
66
+ }
67
+ function getSuggestion(pattern) {
68
+ switch (pattern) {
69
+ case "as-any":
70
+ return "Define a proper type or use type narrowing instead of `as any`.";
71
+ case "as-unknown":
72
+ return "Use a single validated type assertion or schema validation instead of `as unknown as`.";
73
+ case "deep-optional-chain":
74
+ return "Enforce a non-nullable type at the source to eliminate deep optional chaining.";
75
+ case "nullish-literal-default":
76
+ return "Define defaults in a typed config object rather than inline literal fallbacks.";
77
+ case "swallowed-error":
78
+ return "Log or propagate errors \u2014 silent catch blocks hide failures.";
79
+ case "env-fallback":
80
+ return "Use a validated env schema (e.g., Zod) to enforce required variables at startup.";
81
+ case "unnecessary-guard":
82
+ return "Make the parameter non-nullable in the type signature to eliminate the guard.";
83
+ case "any-parameter":
84
+ return "Define a proper type for this parameter instead of `any`.";
85
+ case "any-return":
86
+ return "Define a proper return type instead of `any`.";
87
+ }
88
+ }
89
+ function getLineContent(code, line) {
90
+ const lines = code.split("\n");
91
+ return (lines[line - 1] || "").trim().slice(0, 120);
92
+ }
93
+ function countOptionalChainDepth(node) {
94
+ let depth = 0;
95
+ let current = node;
96
+ while (current) {
97
+ if (current.type === "MemberExpression" && current.optional) {
98
+ depth++;
99
+ current = current.object;
100
+ } else if (current.type === "ChainExpression") {
101
+ current = current.expression;
102
+ } else if (current.type === "CallExpression" && current.optional) {
103
+ depth++;
104
+ current = current.callee;
105
+ } else {
106
+ break;
107
+ }
108
+ }
109
+ return depth;
110
+ }
111
+ function isLiteral(node) {
112
+ if (!node) return false;
113
+ if (node.type === "Literal") return true;
114
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0)
115
+ return true;
116
+ if (node.type === "UnaryExpression" && (node.operator === "-" || node.operator === "+")) {
117
+ return isLiteral(node.argument);
118
+ }
119
+ return false;
120
+ }
121
+ function isProcessEnvAccess(node) {
122
+ return node?.type === "MemberExpression" && node.object?.type === "MemberExpression" && node.object.object?.name === "process" && node.object.property?.name === "env";
123
+ }
124
+ function isSwallowedCatch(body) {
125
+ if (body.length === 0) return true;
126
+ if (body.length === 1) {
127
+ const stmt = body[0];
128
+ if (stmt.type === "ExpressionStatement" && stmt.expression?.type === "CallExpression") {
129
+ const callee = stmt.expression.callee;
130
+ if (callee?.object?.name === "console") return true;
131
+ }
132
+ if (stmt.type === "ThrowStatement") return false;
133
+ }
134
+ return false;
135
+ }
136
+ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
137
+ const issues = [];
138
+ const counts = { ...ZERO_COUNTS };
139
+ const totalLines = code.split("\n").length;
140
+ let ast;
141
+ try {
142
+ ast = (0, import_typescript_estree.parse)(code, {
143
+ filePath,
144
+ loc: true,
145
+ range: true,
146
+ jsx: filePath.endsWith("x")
147
+ });
148
+ } catch {
149
+ return { issues, counts, totalLines };
150
+ }
151
+ const nodesAtFunctionStart = /* @__PURE__ */ new WeakSet();
152
+ function markFunctionParamNodes(node) {
153
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
154
+ const body = node.body?.type === "BlockStatement" ? node.body.body : null;
155
+ if (body && body.length > 0) {
156
+ nodesAtFunctionStart.add(body[0]);
157
+ }
158
+ }
159
+ }
160
+ function visit(node, _parent, _keyInParent) {
161
+ if (!node || typeof node !== "object") return;
162
+ markFunctionParamNodes(node);
163
+ if (node.type === "TSAsExpression" && node.typeAnnotation?.type === "TSAnyKeyword") {
164
+ counts["as-any"]++;
165
+ issues.push(
166
+ makeIssue(
167
+ "as-any",
168
+ import_core.Severity.Major,
169
+ "`as any` type assertion bypasses type safety",
170
+ filePath,
171
+ node.loc?.start.line ?? 0,
172
+ node.loc?.start.column ?? 0,
173
+ getLineContent(code, node.loc?.start.line ?? 0)
174
+ )
175
+ );
176
+ }
177
+ if (node.type === "TSAsExpression" && node.typeAnnotation?.type === "TSUnknownKeyword") {
178
+ counts["as-unknown"]++;
179
+ issues.push(
180
+ makeIssue(
181
+ "as-unknown",
182
+ import_core.Severity.Major,
183
+ "`as unknown` double-cast bypasses type safety",
184
+ filePath,
185
+ node.loc?.start.line ?? 0,
186
+ node.loc?.start.column ?? 0,
187
+ getLineContent(code, node.loc?.start.line ?? 0)
188
+ )
189
+ );
190
+ }
191
+ if (node.type === "ChainExpression") {
192
+ const depth = countOptionalChainDepth(node);
193
+ if (depth >= minChainDepth) {
194
+ counts["deep-optional-chain"]++;
195
+ issues.push(
196
+ makeIssue(
197
+ "deep-optional-chain",
198
+ import_core.Severity.Minor,
199
+ `Optional chain depth of ${depth} indicates missing structural guarantees`,
200
+ filePath,
201
+ node.loc?.start.line ?? 0,
202
+ node.loc?.start.column ?? 0,
203
+ getLineContent(code, node.loc?.start.line ?? 0)
204
+ )
205
+ );
206
+ }
207
+ }
208
+ if (node.type === "LogicalExpression" && node.operator === "??" && isLiteral(node.right)) {
209
+ counts["nullish-literal-default"]++;
210
+ issues.push(
211
+ makeIssue(
212
+ "nullish-literal-default",
213
+ import_core.Severity.Minor,
214
+ "Nullish coalescing with literal default suggests missing upstream type guarantee",
215
+ filePath,
216
+ node.loc?.start.line ?? 0,
217
+ node.loc?.start.column ?? 0,
218
+ getLineContent(code, node.loc?.start.line ?? 0)
219
+ )
220
+ );
221
+ }
222
+ if (node.type === "TryStatement" && node.handler) {
223
+ const catchBody = node.handler.body?.body;
224
+ if (catchBody && isSwallowedCatch(catchBody)) {
225
+ counts["swallowed-error"]++;
226
+ issues.push(
227
+ makeIssue(
228
+ "swallowed-error",
229
+ import_core.Severity.Major,
230
+ "Error is swallowed in catch block \u2014 failures will be silent",
231
+ filePath,
232
+ node.handler.loc?.start.line ?? 0,
233
+ node.handler.loc?.start.column ?? 0,
234
+ getLineContent(code, node.handler.loc?.start.line ?? 0)
235
+ )
236
+ );
237
+ }
238
+ }
239
+ if (node.type === "LogicalExpression" && node.operator === "||" && isProcessEnvAccess(node.left)) {
240
+ counts["env-fallback"]++;
241
+ issues.push(
242
+ makeIssue(
243
+ "env-fallback",
244
+ import_core.Severity.Minor,
245
+ "Environment variable with fallback \u2014 use a validated env schema instead",
246
+ filePath,
247
+ node.loc?.start.line ?? 0,
248
+ node.loc?.start.column ?? 0,
249
+ getLineContent(code, node.loc?.start.line ?? 0)
250
+ )
251
+ );
252
+ }
253
+ if (node.type === "IfStatement" && node.test?.type === "UnaryExpression" && node.test.operator === "!") {
254
+ const consequent = node.consequent;
255
+ let isReturn = false;
256
+ if (consequent.type === "ReturnStatement") {
257
+ isReturn = true;
258
+ } else if (consequent.type === "BlockStatement" && consequent.body?.length === 1 && consequent.body[0].type === "ReturnStatement") {
259
+ isReturn = true;
260
+ }
261
+ if (isReturn && nodesAtFunctionStart.has(node)) {
262
+ counts["unnecessary-guard"]++;
263
+ issues.push(
264
+ makeIssue(
265
+ "unnecessary-guard",
266
+ import_core.Severity.Info,
267
+ "Guard clause could be eliminated with non-nullable type at source",
268
+ filePath,
269
+ node.loc?.start.line ?? 0,
270
+ node.loc?.start.column ?? 0,
271
+ getLineContent(code, node.loc?.start.line ?? 0)
272
+ )
273
+ );
274
+ }
275
+ }
276
+ if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.params) {
277
+ for (const param of node.params) {
278
+ const typeAnno = param.typeAnnotation?.typeAnnotation ?? param.typeAnnotation;
279
+ if (typeAnno?.type === "TSAnyKeyword") {
280
+ counts["any-parameter"]++;
281
+ issues.push(
282
+ makeIssue(
283
+ "any-parameter",
284
+ import_core.Severity.Major,
285
+ "Parameter typed as `any` bypasses type safety",
286
+ filePath,
287
+ param.loc?.start.line ?? 0,
288
+ param.loc?.start.column ?? 0,
289
+ getLineContent(code, param.loc?.start.line ?? 0)
290
+ )
291
+ );
292
+ }
293
+ }
294
+ const returnAnno = node.returnType?.typeAnnotation ?? node.returnType;
295
+ if (returnAnno?.type === "TSAnyKeyword") {
296
+ counts["any-return"]++;
297
+ issues.push(
298
+ makeIssue(
299
+ "any-return",
300
+ import_core.Severity.Major,
301
+ "Return type is `any` \u2014 callers cannot rely on the result shape",
302
+ filePath,
303
+ node.returnType?.loc?.start.line ?? 0,
304
+ node.returnType?.loc?.start.column ?? 0,
305
+ getLineContent(code, node.returnType?.loc?.start.line ?? 0)
306
+ )
307
+ );
308
+ }
309
+ }
310
+ for (const key in node) {
311
+ if (key === "loc" || key === "range" || key === "parent") continue;
312
+ const child = node[key];
313
+ if (Array.isArray(child)) {
314
+ for (const item of child) {
315
+ if (item && typeof item.type === "string") {
316
+ visit(item, node, key);
317
+ }
318
+ }
319
+ } else if (child && typeof child === "object" && typeof child.type === "string") {
320
+ visit(child, node, key);
321
+ }
322
+ }
323
+ }
324
+ visit(ast);
325
+ return { issues, counts, totalLines };
326
+ }
327
+
328
+ // src/scoring.ts
329
+ var DIMENSION_WEIGHTS = {
330
+ typeEscapeHatch: 0.35,
331
+ fallbackCascade: 0.25,
332
+ errorTransparency: 0.2,
333
+ boundaryValidation: 0.2
334
+ };
335
+ function clamp(v, min, max) {
336
+ return Math.max(min, Math.min(max, v));
337
+ }
338
+ function calculateContractEnforcementScore(counts, totalLines, _fileCount) {
339
+ const loc = Math.max(1, totalLines);
340
+ const typeEscapeCount = counts["as-any"] + counts["as-unknown"] + counts["any-parameter"] + counts["any-return"];
341
+ const fallbackCount = counts["deep-optional-chain"] + counts["nullish-literal-default"];
342
+ const errorCount = counts["swallowed-error"];
343
+ const boundaryCount = counts["env-fallback"] + counts["unnecessary-guard"];
344
+ const typeDensity = typeEscapeCount / loc * 1e3;
345
+ const fallbackDensity = fallbackCount / loc * 1e3;
346
+ const errorDensity = errorCount / loc * 1e3;
347
+ const boundaryDensity = boundaryCount / loc * 1e3;
348
+ const typeEscapeHatchScore = clamp(100 - typeDensity * 15, 0, 100);
349
+ const fallbackCascadeScore = clamp(100 - fallbackDensity * 12, 0, 100);
350
+ const errorTransparencyScore = clamp(100 - errorDensity * 25, 0, 100);
351
+ const boundaryValidationScore = clamp(100 - boundaryDensity * 10, 0, 100);
352
+ const score = Math.round(
353
+ typeEscapeHatchScore * DIMENSION_WEIGHTS.typeEscapeHatch + fallbackCascadeScore * DIMENSION_WEIGHTS.fallbackCascade + errorTransparencyScore * DIMENSION_WEIGHTS.errorTransparency + boundaryValidationScore * DIMENSION_WEIGHTS.boundaryValidation
354
+ );
355
+ const rating = score >= 90 ? "excellent" : score >= 75 ? "good" : score >= 60 ? "moderate" : score >= 40 ? "needs-work" : "critical";
356
+ const recommendations = [];
357
+ if (typeEscapeHatchScore < 60) {
358
+ recommendations.push(
359
+ `Reduce type escape hatches (${typeEscapeCount} found): define proper types at system boundaries instead of \`as any\`/parameter \`any\`.`
360
+ );
361
+ }
362
+ if (fallbackCascadeScore < 60) {
363
+ recommendations.push(
364
+ `Reduce fallback cascades (${fallbackCount} found): enforce non-nullable types at the source so consumers don't need \`?.\`/?? fallbacks.`
365
+ );
366
+ }
367
+ if (errorTransparencyScore < 60) {
368
+ recommendations.push(
369
+ `Fix swallowed errors (${errorCount} found): log or propagate errors so failures are visible.`
370
+ );
371
+ }
372
+ if (boundaryValidationScore < 60) {
373
+ recommendations.push(
374
+ `Add boundary validation (${boundaryCount} gaps): use a Zod schema for env vars and API inputs instead of inline fallbacks.`
375
+ );
376
+ }
377
+ return {
378
+ score,
379
+ rating,
380
+ dimensions: {
381
+ typeEscapeHatchScore: Math.round(typeEscapeHatchScore),
382
+ fallbackCascadeScore: Math.round(fallbackCascadeScore),
383
+ errorTransparencyScore: Math.round(errorTransparencyScore),
384
+ boundaryValidationScore: Math.round(boundaryValidationScore)
385
+ },
386
+ recommendations
387
+ };
388
+ }
389
+
390
+ // src/analyzer.ts
391
+ async function analyzeContractEnforcement(options) {
392
+ const files = await (0, import_core2.scanFiles)({
393
+ ...options,
394
+ include: options.include || ["**/*.{ts,tsx,js,jsx}"]
395
+ });
396
+ const allIssues = [];
397
+ const aggregate = { ...ZERO_COUNTS };
398
+ let totalLines = 0;
399
+ await (0, import_core2.runBatchAnalysis)(
400
+ files,
401
+ "scanning for defensive patterns",
402
+ "contract-enforcement",
403
+ options.onProgress,
404
+ async (f) => {
405
+ let code;
406
+ try {
407
+ code = (0, import_fs.readFileSync)(f, "utf-8");
408
+ } catch {
409
+ return { issues: [], counts: { ...ZERO_COUNTS }, totalLines: 0 };
410
+ }
411
+ return detectDefensivePatterns(f, code, options.minChainDepth ?? 3);
412
+ },
413
+ (result) => {
414
+ allIssues.push(...result.issues);
415
+ totalLines += result.totalLines;
416
+ for (const key of Object.keys(aggregate)) {
417
+ aggregate[key] += result.counts[key];
418
+ }
419
+ }
420
+ );
421
+ const totalPatterns = Object.values(aggregate).reduce((a, b) => a + b, 0);
422
+ const density = totalLines > 0 ? Math.round(totalPatterns / totalLines * 1e4) / 10 : 0;
423
+ const scoreResult = calculateContractEnforcementScore(
424
+ aggregate,
425
+ totalLines,
426
+ files.length
427
+ );
428
+ return {
429
+ summary: {
430
+ sourceFiles: files.length,
431
+ totalDefensivePatterns: totalPatterns,
432
+ defensiveDensity: density,
433
+ score: scoreResult.score,
434
+ rating: scoreResult.rating,
435
+ dimensions: {
436
+ typeEscapeHatchScore: scoreResult.dimensions.typeEscapeHatchScore,
437
+ fallbackCascadeScore: scoreResult.dimensions.fallbackCascadeScore,
438
+ errorTransparencyScore: scoreResult.dimensions.errorTransparencyScore,
439
+ boundaryValidationScore: scoreResult.dimensions.boundaryValidationScore
440
+ }
441
+ },
442
+ issues: allIssues,
443
+ rawData: { ...aggregate, sourceFiles: files.length, totalLines },
444
+ recommendations: scoreResult.recommendations
445
+ };
446
+ }
447
+
448
+ // src/provider.ts
449
+ var ContractEnforcementProvider = (0, import_core3.createProvider)({
450
+ id: import_core3.ToolName.ContractEnforcement,
451
+ alias: ["contract", "ce", "enforcement"],
452
+ version: "0.1.0",
453
+ defaultWeight: 10,
454
+ async analyzeReport(options) {
455
+ return analyzeContractEnforcement(options);
456
+ },
457
+ getResults(report) {
458
+ return (0, import_core3.groupIssuesByFile)(report.issues);
459
+ },
460
+ getSummary(report) {
461
+ return report.summary;
462
+ },
463
+ getMetadata(report) {
464
+ return { rawData: report.rawData };
465
+ },
466
+ score(output, _options) {
467
+ const rawData = output.metadata?.rawData ?? {};
468
+ return calculateContractEnforcementScore(
469
+ rawData,
470
+ rawData.totalLines ?? 1,
471
+ rawData.sourceFiles ?? 1
472
+ );
473
+ }
474
+ });
475
+
476
+ // src/index.ts
477
+ import_core4.ToolRegistry.register(ContractEnforcementProvider);
478
+ // Annotate the CommonJS export names for ESM import in node:
479
+ 0 && (module.exports = {
480
+ ContractEnforcementProvider,
481
+ analyzeContractEnforcement,
482
+ calculateContractEnforcementScore,
483
+ detectDefensivePatterns
484
+ });