@aiready/contract-enforcement 0.2.5 → 0.2.6

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @aiready/contract-enforcement@0.2.5 build /Users/pengcao/projects/aiready/packages/contract-enforcement
2
+ > @aiready/contract-enforcement@0.2.6 build /Users/pengcao/projects/aiready/packages/contract-enforcement
3
3
  > tsup src/index.ts --format cjs,esm --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,11 +8,11 @@ CLI tsup v8.5.1
8
8
  CLI Target: es2020
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/index.js 18.01 KB
12
- CJS ⚡️ Build success in 241ms
13
- ESM dist/index.mjs 16.58 KB
14
- ESM ⚡️ Build success in 241ms
11
+ ESM dist/index.mjs 17.28 KB
12
+ ESM ⚡️ Build success in 37ms
13
+ CJS dist/index.js 18.72 KB
14
+ CJS ⚡️ Build success in 39ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 4082ms
16
+ DTS ⚡️ Build success in 4782ms
17
17
  DTS dist/index.d.ts 2.45 KB
18
18
  DTS dist/index.d.mts 2.45 KB
@@ -1,16 +1,16 @@
1
1
 
2
- > @aiready/contract-enforcement@0.2.4 test /Users/pengcao/projects/aiready/packages/contract-enforcement
2
+ > @aiready/contract-enforcement@0.2.5 test /Users/pengcao/projects/aiready/packages/contract-enforcement
3
3
  > vitest run
4
4
 
5
5
 
6
6
   RUN  v4.1.1 /Users/pengcao/projects/aiready/packages/contract-enforcement
7
7
 
8
8
  ✓ src/__tests__/scoring.test.ts (7 tests) 3ms
9
- ✓ src/__tests__/detector.test.ts (14 tests) 16ms
10
- ✓ src/__tests__/provider.test.ts (5 tests) 19ms
9
+ ✓ src/__tests__/detector.test.ts (14 tests) 105ms
10
+ ✓ src/__tests__/provider.test.ts (5 tests) 3ms
11
11
 
12
12
   Test Files  3 passed (3)
13
13
   Tests  26 passed (26)
14
-  Start at  21:12:44
15
-  Duration  2.44s (transform 1.37s, setup 0ms, import 4.00s, tests 38ms, environment 0ms)
14
+  Start at  20:27:25
15
+  Duration  4.35s (transform 1.54s, setup 0ms, import 6.86s, tests 111ms, environment 0ms)
16
16
 
package/dist/index.js CHANGED
@@ -109,7 +109,6 @@ function countOptionalChainDepth(node) {
109
109
  return depth;
110
110
  }
111
111
  function isLiteral(node) {
112
- if (!node) return false;
113
112
  if (node.type === "Literal") return true;
114
113
  if (node.type === "TemplateLiteral" && node.expressions.length === 0)
115
114
  return true;
@@ -119,10 +118,9 @@ function isLiteral(node) {
119
118
  return false;
120
119
  }
121
120
  function isProcessEnvAccess(node) {
122
- return node?.type === "MemberExpression" && node.object?.type === "MemberExpression" && node.object.object?.name === "process" && node.object.property?.name === "env";
121
+ return node?.type === "MemberExpression" && node.object?.type === "MemberExpression" && node.object.object?.type === "Identifier" && node.object.object.name === "process" && node.object.property?.type === "Identifier" && node.object.property.name === "env";
123
122
  }
124
123
  function isSstResourceAccess(node) {
125
- if (!node) return false;
126
124
  let current = node;
127
125
  if (current.type === "ChainExpression") {
128
126
  current = current.expression;
@@ -145,9 +143,13 @@ function isSwallowedCatch(body, filePath) {
145
143
  const stmt = body[0];
146
144
  if (stmt.type === "ExpressionStatement" && stmt.expression?.type === "CallExpression") {
147
145
  const callee = stmt.expression.callee;
148
- if (callee?.object?.name === "console") return true;
146
+ if (callee?.type === "MemberExpression" && callee.object?.type === "Identifier" && callee.object.name === "console")
147
+ return true;
149
148
  if (isUiComponent) {
150
- const calleeName = callee?.name || callee?.property?.name || "";
149
+ let calleeName = "";
150
+ if (callee?.type === "Identifier") calleeName = callee.name;
151
+ else if (callee?.type === "MemberExpression" && callee.property.type === "Identifier")
152
+ calleeName = callee.property.name;
151
153
  if (/telemetry|analytics|track|logEvent/i.test(calleeName)) {
152
154
  return false;
153
155
  }
@@ -193,9 +195,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
193
195
  import_core.Severity.Major,
194
196
  "`as any` type assertion bypasses type safety",
195
197
  filePath,
196
- node.loc?.start.line ?? 0,
197
- node.loc?.start.column ?? 0,
198
- getLineContent(code, node.loc?.start.line ?? 0)
198
+ node.loc.start.line,
199
+ node.loc.start.column,
200
+ getLineContent(code, node.loc.start.line)
199
201
  )
200
202
  );
201
203
  }
@@ -209,9 +211,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
209
211
  import_core.Severity.Major,
210
212
  "`as unknown` double-cast bypasses type safety",
211
213
  filePath,
212
- node.loc?.start.line ?? 0,
213
- node.loc?.start.column ?? 0,
214
- getLineContent(code, node.loc?.start.line ?? 0)
214
+ node.loc.start.line,
215
+ node.loc.start.column,
216
+ getLineContent(code, node.loc.start.line)
215
217
  )
216
218
  );
217
219
  }
@@ -226,9 +228,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
226
228
  import_core.Severity.Minor,
227
229
  `Optional chain depth of ${depth} indicates missing structural guarantees`,
228
230
  filePath,
229
- node.loc?.start.line ?? 0,
230
- node.loc?.start.column ?? 0,
231
- getLineContent(code, node.loc?.start.line ?? 0)
231
+ node.loc.start.line,
232
+ node.loc.start.column,
233
+ getLineContent(code, node.loc.start.line)
232
234
  )
233
235
  );
234
236
  }
@@ -242,9 +244,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
242
244
  import_core.Severity.Minor,
243
245
  "Nullish coalescing with literal default suggests missing upstream type guarantee",
244
246
  filePath,
245
- node.loc?.start.line ?? 0,
246
- node.loc?.start.column ?? 0,
247
- getLineContent(code, node.loc?.start.line ?? 0)
247
+ node.loc.start.line,
248
+ node.loc.start.column,
249
+ getLineContent(code, node.loc.start.line)
248
250
  )
249
251
  );
250
252
  }
@@ -259,9 +261,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
259
261
  import_core.Severity.Major,
260
262
  "Error is swallowed in catch block \u2014 failures will be silent",
261
263
  filePath,
262
- node.handler.loc?.start.line ?? 0,
263
- node.handler.loc?.start.column ?? 0,
264
- getLineContent(code, node.handler.loc?.start.line ?? 0)
264
+ node.handler.loc.start.line,
265
+ node.handler.loc.start.column,
266
+ getLineContent(code, node.handler.loc.start.line)
265
267
  )
266
268
  );
267
269
  }
@@ -274,9 +276,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
274
276
  import_core.Severity.Minor,
275
277
  "Environment variable with fallback \u2014 use a validated env schema instead",
276
278
  filePath,
277
- node.loc?.start.line ?? 0,
278
- node.loc?.start.column ?? 0,
279
- getLineContent(code, node.loc?.start.line ?? 0)
279
+ node.loc.start.line,
280
+ node.loc.start.column,
281
+ getLineContent(code, node.loc.start.line)
280
282
  )
281
283
  );
282
284
  }
@@ -296,16 +298,19 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
296
298
  import_core.Severity.Info,
297
299
  "Guard clause could be eliminated with non-nullable type at source",
298
300
  filePath,
299
- node.loc?.start.line ?? 0,
300
- node.loc?.start.column ?? 0,
301
- getLineContent(code, node.loc?.start.line ?? 0)
301
+ node.loc.start.line,
302
+ node.loc.start.column,
303
+ getLineContent(code, node.loc.start.line)
302
304
  )
303
305
  );
304
306
  }
305
307
  }
306
308
  if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.params) {
307
309
  for (const param of node.params) {
308
- const typeAnno = param.typeAnnotation?.typeAnnotation ?? param.typeAnnotation;
310
+ let typeAnno;
311
+ if ("typeAnnotation" in param && param.typeAnnotation) {
312
+ typeAnno = param.typeAnnotation.typeAnnotation;
313
+ }
309
314
  if (typeAnno?.type === "TSAnyKeyword") {
310
315
  counts["any-parameter"]++;
311
316
  issues.push(
@@ -314,27 +319,33 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
314
319
  import_core.Severity.Major,
315
320
  "Parameter typed as `any` bypasses type safety",
316
321
  filePath,
317
- param.loc?.start.line ?? 0,
318
- param.loc?.start.column ?? 0,
319
- getLineContent(code, param.loc?.start.line ?? 0)
322
+ param.loc.start.line,
323
+ param.loc.start.column,
324
+ getLineContent(code, param.loc.start.line)
320
325
  )
321
326
  );
322
327
  }
323
328
  }
324
- const returnAnno = node.returnType?.typeAnnotation ?? node.returnType;
329
+ let returnAnno;
330
+ if ("returnType" in node && node.returnType) {
331
+ returnAnno = node.returnType.typeAnnotation;
332
+ }
325
333
  if (returnAnno?.type === "TSAnyKeyword") {
326
- counts["any-return"]++;
327
- issues.push(
328
- makeIssue(
329
- "any-return",
330
- import_core.Severity.Major,
331
- "Return type is `any` \u2014 callers cannot rely on the result shape",
332
- filePath,
333
- node.returnType?.loc?.start.line ?? 0,
334
- node.returnType?.loc?.start.column ?? 0,
335
- getLineContent(code, node.returnType?.loc?.start.line ?? 0)
336
- )
337
- );
334
+ const fnNode = node;
335
+ if (fnNode.returnType?.loc) {
336
+ counts["any-return"]++;
337
+ issues.push(
338
+ makeIssue(
339
+ "any-return",
340
+ import_core.Severity.Major,
341
+ "Return type is `any` \u2014 callers cannot rely on the result shape",
342
+ filePath,
343
+ fnNode.returnType.loc.start.line,
344
+ fnNode.returnType.loc.start.column,
345
+ getLineContent(code, fnNode.returnType.loc.start.line)
346
+ )
347
+ );
348
+ }
338
349
  }
339
350
  }
340
351
  for (const key in node) {
@@ -495,11 +506,24 @@ var ContractEnforcementProvider = (0, import_core3.createProvider)({
495
506
  },
496
507
  score(output, _options) {
497
508
  const rawData = output.metadata?.rawData ?? {};
498
- return calculateContractEnforcementScore(
509
+ const result = calculateContractEnforcementScore(
499
510
  rawData,
500
511
  rawData.totalLines ?? 1,
501
512
  rawData.sourceFiles ?? 1
502
513
  );
514
+ return {
515
+ toolName: import_core3.ToolName.ContractEnforcement,
516
+ score: result.score,
517
+ rating: result.rating,
518
+ recommendations: result.recommendations.map((action) => ({
519
+ action,
520
+ estimatedImpact: 10,
521
+ priority: "medium"
522
+ })),
523
+ dimensions: result.dimensions,
524
+ rawMetrics: rawData,
525
+ factors: []
526
+ };
503
527
  }
504
528
  });
505
529
 
package/dist/index.mjs CHANGED
@@ -82,7 +82,6 @@ function countOptionalChainDepth(node) {
82
82
  return depth;
83
83
  }
84
84
  function isLiteral(node) {
85
- if (!node) return false;
86
85
  if (node.type === "Literal") return true;
87
86
  if (node.type === "TemplateLiteral" && node.expressions.length === 0)
88
87
  return true;
@@ -92,10 +91,9 @@ function isLiteral(node) {
92
91
  return false;
93
92
  }
94
93
  function isProcessEnvAccess(node) {
95
- return node?.type === "MemberExpression" && node.object?.type === "MemberExpression" && node.object.object?.name === "process" && node.object.property?.name === "env";
94
+ return node?.type === "MemberExpression" && node.object?.type === "MemberExpression" && node.object.object?.type === "Identifier" && node.object.object.name === "process" && node.object.property?.type === "Identifier" && node.object.property.name === "env";
96
95
  }
97
96
  function isSstResourceAccess(node) {
98
- if (!node) return false;
99
97
  let current = node;
100
98
  if (current.type === "ChainExpression") {
101
99
  current = current.expression;
@@ -118,9 +116,13 @@ function isSwallowedCatch(body, filePath) {
118
116
  const stmt = body[0];
119
117
  if (stmt.type === "ExpressionStatement" && stmt.expression?.type === "CallExpression") {
120
118
  const callee = stmt.expression.callee;
121
- if (callee?.object?.name === "console") return true;
119
+ if (callee?.type === "MemberExpression" && callee.object?.type === "Identifier" && callee.object.name === "console")
120
+ return true;
122
121
  if (isUiComponent) {
123
- const calleeName = callee?.name || callee?.property?.name || "";
122
+ let calleeName = "";
123
+ if (callee?.type === "Identifier") calleeName = callee.name;
124
+ else if (callee?.type === "MemberExpression" && callee.property.type === "Identifier")
125
+ calleeName = callee.property.name;
124
126
  if (/telemetry|analytics|track|logEvent/i.test(calleeName)) {
125
127
  return false;
126
128
  }
@@ -166,9 +168,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
166
168
  Severity.Major,
167
169
  "`as any` type assertion bypasses type safety",
168
170
  filePath,
169
- node.loc?.start.line ?? 0,
170
- node.loc?.start.column ?? 0,
171
- getLineContent(code, node.loc?.start.line ?? 0)
171
+ node.loc.start.line,
172
+ node.loc.start.column,
173
+ getLineContent(code, node.loc.start.line)
172
174
  )
173
175
  );
174
176
  }
@@ -182,9 +184,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
182
184
  Severity.Major,
183
185
  "`as unknown` double-cast bypasses type safety",
184
186
  filePath,
185
- node.loc?.start.line ?? 0,
186
- node.loc?.start.column ?? 0,
187
- getLineContent(code, node.loc?.start.line ?? 0)
187
+ node.loc.start.line,
188
+ node.loc.start.column,
189
+ getLineContent(code, node.loc.start.line)
188
190
  )
189
191
  );
190
192
  }
@@ -199,9 +201,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
199
201
  Severity.Minor,
200
202
  `Optional chain depth of ${depth} indicates missing structural guarantees`,
201
203
  filePath,
202
- node.loc?.start.line ?? 0,
203
- node.loc?.start.column ?? 0,
204
- getLineContent(code, node.loc?.start.line ?? 0)
204
+ node.loc.start.line,
205
+ node.loc.start.column,
206
+ getLineContent(code, node.loc.start.line)
205
207
  )
206
208
  );
207
209
  }
@@ -215,9 +217,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
215
217
  Severity.Minor,
216
218
  "Nullish coalescing with literal default suggests missing upstream type guarantee",
217
219
  filePath,
218
- node.loc?.start.line ?? 0,
219
- node.loc?.start.column ?? 0,
220
- getLineContent(code, node.loc?.start.line ?? 0)
220
+ node.loc.start.line,
221
+ node.loc.start.column,
222
+ getLineContent(code, node.loc.start.line)
221
223
  )
222
224
  );
223
225
  }
@@ -232,9 +234,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
232
234
  Severity.Major,
233
235
  "Error is swallowed in catch block \u2014 failures will be silent",
234
236
  filePath,
235
- node.handler.loc?.start.line ?? 0,
236
- node.handler.loc?.start.column ?? 0,
237
- getLineContent(code, node.handler.loc?.start.line ?? 0)
237
+ node.handler.loc.start.line,
238
+ node.handler.loc.start.column,
239
+ getLineContent(code, node.handler.loc.start.line)
238
240
  )
239
241
  );
240
242
  }
@@ -247,9 +249,9 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
247
249
  Severity.Minor,
248
250
  "Environment variable with fallback \u2014 use a validated env schema instead",
249
251
  filePath,
250
- node.loc?.start.line ?? 0,
251
- node.loc?.start.column ?? 0,
252
- getLineContent(code, node.loc?.start.line ?? 0)
252
+ node.loc.start.line,
253
+ node.loc.start.column,
254
+ getLineContent(code, node.loc.start.line)
253
255
  )
254
256
  );
255
257
  }
@@ -269,16 +271,19 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
269
271
  Severity.Info,
270
272
  "Guard clause could be eliminated with non-nullable type at source",
271
273
  filePath,
272
- node.loc?.start.line ?? 0,
273
- node.loc?.start.column ?? 0,
274
- getLineContent(code, node.loc?.start.line ?? 0)
274
+ node.loc.start.line,
275
+ node.loc.start.column,
276
+ getLineContent(code, node.loc.start.line)
275
277
  )
276
278
  );
277
279
  }
278
280
  }
279
281
  if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.params) {
280
282
  for (const param of node.params) {
281
- const typeAnno = param.typeAnnotation?.typeAnnotation ?? param.typeAnnotation;
283
+ let typeAnno;
284
+ if ("typeAnnotation" in param && param.typeAnnotation) {
285
+ typeAnno = param.typeAnnotation.typeAnnotation;
286
+ }
282
287
  if (typeAnno?.type === "TSAnyKeyword") {
283
288
  counts["any-parameter"]++;
284
289
  issues.push(
@@ -287,27 +292,33 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
287
292
  Severity.Major,
288
293
  "Parameter typed as `any` bypasses type safety",
289
294
  filePath,
290
- param.loc?.start.line ?? 0,
291
- param.loc?.start.column ?? 0,
292
- getLineContent(code, param.loc?.start.line ?? 0)
295
+ param.loc.start.line,
296
+ param.loc.start.column,
297
+ getLineContent(code, param.loc.start.line)
293
298
  )
294
299
  );
295
300
  }
296
301
  }
297
- const returnAnno = node.returnType?.typeAnnotation ?? node.returnType;
302
+ let returnAnno;
303
+ if ("returnType" in node && node.returnType) {
304
+ returnAnno = node.returnType.typeAnnotation;
305
+ }
298
306
  if (returnAnno?.type === "TSAnyKeyword") {
299
- counts["any-return"]++;
300
- issues.push(
301
- makeIssue(
302
- "any-return",
303
- Severity.Major,
304
- "Return type is `any` \u2014 callers cannot rely on the result shape",
305
- filePath,
306
- node.returnType?.loc?.start.line ?? 0,
307
- node.returnType?.loc?.start.column ?? 0,
308
- getLineContent(code, node.returnType?.loc?.start.line ?? 0)
309
- )
310
- );
307
+ const fnNode = node;
308
+ if (fnNode.returnType?.loc) {
309
+ counts["any-return"]++;
310
+ issues.push(
311
+ makeIssue(
312
+ "any-return",
313
+ Severity.Major,
314
+ "Return type is `any` \u2014 callers cannot rely on the result shape",
315
+ filePath,
316
+ fnNode.returnType.loc.start.line,
317
+ fnNode.returnType.loc.start.column,
318
+ getLineContent(code, fnNode.returnType.loc.start.line)
319
+ )
320
+ );
321
+ }
311
322
  }
312
323
  }
313
324
  for (const key in node) {
@@ -468,11 +479,24 @@ var ContractEnforcementProvider = createProvider({
468
479
  },
469
480
  score(output, _options) {
470
481
  const rawData = output.metadata?.rawData ?? {};
471
- return calculateContractEnforcementScore(
482
+ const result = calculateContractEnforcementScore(
472
483
  rawData,
473
484
  rawData.totalLines ?? 1,
474
485
  rawData.sourceFiles ?? 1
475
486
  );
487
+ return {
488
+ toolName: ToolName.ContractEnforcement,
489
+ score: result.score,
490
+ rating: result.rating,
491
+ recommendations: result.recommendations.map((action) => ({
492
+ action,
493
+ estimatedImpact: 10,
494
+ priority: "medium"
495
+ })),
496
+ dimensions: result.dimensions,
497
+ rawMetrics: rawData,
498
+ factors: []
499
+ };
476
500
  }
477
501
  });
478
502
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/contract-enforcement",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Measures structural type safety and boundary validation to reduce fallback cascades for AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -32,7 +32,7 @@
32
32
  "homepage": "https://github.com/caopengau/aiready-contract-enforcement",
33
33
  "dependencies": {
34
34
  "@typescript-eslint/typescript-estree": "^8.53.0",
35
- "@aiready/core": "0.24.5"
35
+ "@aiready/core": "0.24.6"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^24.0.0",
package/src/detector.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { parse } from '@typescript-eslint/typescript-estree';
1
+ import { parse, TSESTree } from '@typescript-eslint/typescript-estree';
2
2
  import { Severity, IssueType } from '@aiready/core';
3
3
  import type {
4
4
  ContractEnforcementIssue,
@@ -56,9 +56,9 @@ function getLineContent(code: string, line: number): string {
56
56
  return (lines[line - 1] || '').trim().slice(0, 120);
57
57
  }
58
58
 
59
- function countOptionalChainDepth(node: any): number {
59
+ function countOptionalChainDepth(node: TSESTree.Node): number {
60
60
  let depth = 0;
61
- let current = node;
61
+ let current: TSESTree.Node | undefined = node;
62
62
  while (current) {
63
63
  if (current.type === 'MemberExpression' && current.optional) {
64
64
  depth++;
@@ -75,8 +75,7 @@ function countOptionalChainDepth(node: any): number {
75
75
  return depth;
76
76
  }
77
77
 
78
- function isLiteral(node: any): boolean {
79
- if (!node) return false;
78
+ function isLiteral(node: TSESTree.Node): boolean {
80
79
  if (node.type === 'Literal') return true;
81
80
  if (node.type === 'TemplateLiteral' && node.expressions.length === 0)
82
81
  return true;
@@ -89,18 +88,19 @@ function isLiteral(node: any): boolean {
89
88
  return false;
90
89
  }
91
90
 
92
- function isProcessEnvAccess(node: any): boolean {
91
+ function isProcessEnvAccess(node: TSESTree.Node | undefined): boolean {
93
92
  return (
94
93
  node?.type === 'MemberExpression' &&
95
94
  node.object?.type === 'MemberExpression' &&
96
- node.object.object?.name === 'process' &&
97
- node.object.property?.name === 'env'
95
+ node.object.object?.type === 'Identifier' &&
96
+ node.object.object.name === 'process' &&
97
+ node.object.property?.type === 'Identifier' &&
98
+ node.object.property.name === 'env'
98
99
  );
99
100
  }
100
101
 
101
- function isSstResourceAccess(node: any): boolean {
102
- if (!node) return false;
103
- let current = node;
102
+ function isSstResourceAccess(node: TSESTree.Node): boolean {
103
+ let current: TSESTree.Node = node;
104
104
  // Handle ChainExpression for optional chaining like Resource.MySecret?.value
105
105
  if (current.type === 'ChainExpression') {
106
106
  current = current.expression;
@@ -125,7 +125,10 @@ function isSstResourceAccess(node: any): boolean {
125
125
  return false;
126
126
  }
127
127
 
128
- function isSwallowedCatch(body: any[], filePath: string): boolean {
128
+ function isSwallowedCatch(
129
+ body: TSESTree.Statement[],
130
+ filePath: string
131
+ ): boolean {
129
132
  if (body.length === 0) return true;
130
133
 
131
134
  // UI components often have intentional silent catches for telemetry/analytics
@@ -139,11 +142,23 @@ function isSwallowedCatch(body: any[], filePath: string): boolean {
139
142
  ) {
140
143
  const callee = stmt.expression.callee;
141
144
  // console.log/warn/error is still considered "swallowed" but might be acceptable
142
- if (callee?.object?.name === 'console') return true;
145
+ if (
146
+ callee?.type === 'MemberExpression' &&
147
+ callee.object?.type === 'Identifier' &&
148
+ callee.object.name === 'console'
149
+ )
150
+ return true;
143
151
 
144
152
  // If it's a UI component and looks like telemetry, it's a false positive
145
153
  if (isUiComponent) {
146
- const calleeName = callee?.name || callee?.property?.name || '';
154
+ let calleeName = '';
155
+ if (callee?.type === 'Identifier') calleeName = callee.name;
156
+ else if (
157
+ callee?.type === 'MemberExpression' &&
158
+ callee.property.type === 'Identifier'
159
+ )
160
+ calleeName = callee.property.name;
161
+
147
162
  if (/telemetry|analytics|track|logEvent/i.test(calleeName)) {
148
163
  return false; // Not "swallowed" in a bad way
149
164
  }
@@ -164,7 +179,7 @@ export function detectDefensivePatterns(
164
179
  const counts: PatternCounts = { ...ZERO_COUNTS };
165
180
  const totalLines = code.split('\n').length;
166
181
 
167
- let ast: any;
182
+ let ast: TSESTree.Program;
168
183
  try {
169
184
  ast = parse(code, {
170
185
  filePath,
@@ -176,9 +191,9 @@ export function detectDefensivePatterns(
176
191
  return { issues, counts, totalLines };
177
192
  }
178
193
 
179
- const nodesAtFunctionStart = new WeakSet<any>();
194
+ const nodesAtFunctionStart = new WeakSet<TSESTree.Node>();
180
195
 
181
- function markFunctionParamNodes(node: any) {
196
+ function markFunctionParamNodes(node: TSESTree.Node) {
182
197
  if (
183
198
  node.type === 'FunctionDeclaration' ||
184
199
  node.type === 'FunctionExpression' ||
@@ -191,7 +206,11 @@ export function detectDefensivePatterns(
191
206
  }
192
207
  }
193
208
 
194
- function visit(node: any, _parent?: any, _keyInParent?: string) {
209
+ function visit(
210
+ node: TSESTree.Node | undefined,
211
+ _parent?: TSESTree.Node,
212
+ _keyInParent?: string
213
+ ) {
195
214
  if (!node || typeof node !== 'object') return;
196
215
 
197
216
  markFunctionParamNodes(node);
@@ -213,9 +232,9 @@ export function detectDefensivePatterns(
213
232
  Severity.Major,
214
233
  '`as any` type assertion bypasses type safety',
215
234
  filePath,
216
- node.loc?.start.line ?? 0,
217
- node.loc?.start.column ?? 0,
218
- getLineContent(code, node.loc?.start.line ?? 0)
235
+ node.loc.start.line,
236
+ node.loc.start.column,
237
+ getLineContent(code, node.loc.start.line)
219
238
  )
220
239
  );
221
240
  }
@@ -238,9 +257,9 @@ export function detectDefensivePatterns(
238
257
  Severity.Major,
239
258
  '`as unknown` double-cast bypasses type safety',
240
259
  filePath,
241
- node.loc?.start.line ?? 0,
242
- node.loc?.start.column ?? 0,
243
- getLineContent(code, node.loc?.start.line ?? 0)
260
+ node.loc.start.line,
261
+ node.loc.start.column,
262
+ getLineContent(code, node.loc.start.line)
244
263
  )
245
264
  );
246
265
  }
@@ -257,9 +276,9 @@ export function detectDefensivePatterns(
257
276
  Severity.Minor,
258
277
  `Optional chain depth of ${depth} indicates missing structural guarantees`,
259
278
  filePath,
260
- node.loc?.start.line ?? 0,
261
- node.loc?.start.column ?? 0,
262
- getLineContent(code, node.loc?.start.line ?? 0)
279
+ node.loc.start.line,
280
+ node.loc.start.column,
281
+ getLineContent(code, node.loc.start.line)
263
282
  )
264
283
  );
265
284
  }
@@ -280,9 +299,9 @@ export function detectDefensivePatterns(
280
299
  Severity.Minor,
281
300
  'Nullish coalescing with literal default suggests missing upstream type guarantee',
282
301
  filePath,
283
- node.loc?.start.line ?? 0,
284
- node.loc?.start.column ?? 0,
285
- getLineContent(code, node.loc?.start.line ?? 0)
302
+ node.loc.start.line,
303
+ node.loc.start.column,
304
+ getLineContent(code, node.loc.start.line)
286
305
  )
287
306
  );
288
307
  }
@@ -299,9 +318,9 @@ export function detectDefensivePatterns(
299
318
  Severity.Major,
300
319
  'Error is swallowed in catch block — failures will be silent',
301
320
  filePath,
302
- node.handler.loc?.start.line ?? 0,
303
- node.handler.loc?.start.column ?? 0,
304
- getLineContent(code, node.handler.loc?.start.line ?? 0)
321
+ node.handler.loc.start.line,
322
+ node.handler.loc.start.column,
323
+ getLineContent(code, node.handler.loc.start.line)
305
324
  )
306
325
  );
307
326
  }
@@ -320,9 +339,9 @@ export function detectDefensivePatterns(
320
339
  Severity.Minor,
321
340
  'Environment variable with fallback — use a validated env schema instead',
322
341
  filePath,
323
- node.loc?.start.line ?? 0,
324
- node.loc?.start.column ?? 0,
325
- getLineContent(code, node.loc?.start.line ?? 0)
342
+ node.loc.start.line,
343
+ node.loc.start.column,
344
+ getLineContent(code, node.loc.start.line)
326
345
  )
327
346
  );
328
347
  }
@@ -352,9 +371,9 @@ export function detectDefensivePatterns(
352
371
  Severity.Info,
353
372
  'Guard clause could be eliminated with non-nullable type at source',
354
373
  filePath,
355
- node.loc?.start.line ?? 0,
356
- node.loc?.start.column ?? 0,
357
- getLineContent(code, node.loc?.start.line ?? 0)
374
+ node.loc.start.line,
375
+ node.loc.start.column,
376
+ getLineContent(code, node.loc.start.line)
358
377
  )
359
378
  );
360
379
  }
@@ -368,8 +387,14 @@ export function detectDefensivePatterns(
368
387
  node.params
369
388
  ) {
370
389
  for (const param of node.params) {
371
- const typeAnno =
372
- param.typeAnnotation?.typeAnnotation ?? param.typeAnnotation;
390
+ let typeAnno: TSESTree.TypeNode | undefined;
391
+
392
+ // Handle Identifier, AssignmentPattern, RestElement, etc.
393
+ if ('typeAnnotation' in param && param.typeAnnotation) {
394
+ typeAnno = (param.typeAnnotation as TSESTree.TSTypeAnnotation)
395
+ .typeAnnotation;
396
+ }
397
+
373
398
  if (typeAnno?.type === 'TSAnyKeyword') {
374
399
  counts['any-parameter']++;
375
400
  issues.push(
@@ -378,40 +403,48 @@ export function detectDefensivePatterns(
378
403
  Severity.Major,
379
404
  'Parameter typed as `any` bypasses type safety',
380
405
  filePath,
381
- param.loc?.start.line ?? 0,
382
- param.loc?.start.column ?? 0,
383
- getLineContent(code, param.loc?.start.line ?? 0)
406
+ param.loc.start.line,
407
+ param.loc.start.column,
408
+ getLineContent(code, param.loc.start.line)
384
409
  )
385
410
  );
386
411
  }
387
412
  }
388
413
 
389
414
  // Pattern: any return type
390
- const returnAnno = node.returnType?.typeAnnotation ?? node.returnType;
415
+ let returnAnno: TSESTree.TypeNode | undefined;
416
+ if ('returnType' in node && node.returnType) {
417
+ returnAnno = (node.returnType as TSESTree.TSTypeAnnotation)
418
+ .typeAnnotation;
419
+ }
420
+
391
421
  if (returnAnno?.type === 'TSAnyKeyword') {
392
- counts['any-return']++;
393
- issues.push(
394
- makeIssue(
395
- 'any-return',
396
- Severity.Major,
397
- 'Return type is `any` — callers cannot rely on the result shape',
398
- filePath,
399
- node.returnType?.loc?.start.line ?? 0,
400
- node.returnType?.loc?.start.column ?? 0,
401
- getLineContent(code, node.returnType?.loc?.start.line ?? 0)
402
- )
403
- );
422
+ const fnNode = node as TSESTree.FunctionDeclaration;
423
+ if (fnNode.returnType?.loc) {
424
+ counts['any-return']++;
425
+ issues.push(
426
+ makeIssue(
427
+ 'any-return',
428
+ Severity.Major,
429
+ 'Return type is `any` — callers cannot rely on the result shape',
430
+ filePath,
431
+ fnNode.returnType.loc.start.line,
432
+ fnNode.returnType.loc.start.column,
433
+ getLineContent(code, fnNode.returnType.loc.start.line)
434
+ )
435
+ );
436
+ }
404
437
  }
405
438
  }
406
439
 
407
440
  // Recurse
408
441
  for (const key in node) {
409
442
  if (key === 'loc' || key === 'range' || key === 'parent') continue;
410
- const child = node[key];
443
+ const child = (node as Record<string, any>)[key];
411
444
  if (Array.isArray(child)) {
412
445
  for (const item of child) {
413
446
  if (item && typeof item.type === 'string') {
414
- visit(item, node, key);
447
+ visit(item as TSESTree.Node, node, key);
415
448
  }
416
449
  }
417
450
  } else if (
@@ -419,7 +452,7 @@ export function detectDefensivePatterns(
419
452
  typeof child === 'object' &&
420
453
  typeof child.type === 'string'
421
454
  ) {
422
- visit(child, node, key);
455
+ visit(child as TSESTree.Node, node, key);
423
456
  }
424
457
  }
425
458
  }
package/src/provider.ts CHANGED
@@ -30,11 +30,24 @@ export const ContractEnforcementProvider = createProvider({
30
30
  },
31
31
 
32
32
  score(output: SpokeOutput, _options: ScanOptions) {
33
- const rawData = (output.metadata as any)?.rawData ?? {};
34
- return calculateContractEnforcementScore(
33
+ const rawData = output.metadata?.rawData ?? {};
34
+ const result = calculateContractEnforcementScore(
35
35
  rawData,
36
36
  rawData.totalLines ?? 1,
37
37
  rawData.sourceFiles ?? 1
38
- ) as any;
38
+ );
39
+ return {
40
+ toolName: ToolName.ContractEnforcement,
41
+ score: result.score,
42
+ rating: result.rating,
43
+ recommendations: result.recommendations.map((action) => ({
44
+ action,
45
+ estimatedImpact: 10,
46
+ priority: 'medium' as const,
47
+ })),
48
+ dimensions: result.dimensions,
49
+ rawMetrics: rawData,
50
+ factors: [],
51
+ };
39
52
  },
40
53
  });