@aiready/contract-enforcement 0.2.5 → 0.2.7

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