@aiready/contract-enforcement 0.2.1 → 0.2.3

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,19 +1,18 @@
1
-
2
- 
3
- > @aiready/contract-enforcement@0.2.1 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 16.88 KB
13
- CJS ⚡️ Build success in 287ms
14
- ESM dist/index.mjs 15.46 KB
15
- ESM ⚡️ Build success in 289ms
16
- DTS Build start
17
- DTS ⚡️ Build success in 5891ms
18
- DTS dist/index.d.ts 2.45 KB
19
- DTS dist/index.d.mts 2.45 KB
1
+
2
+ > @aiready/contract-enforcement@0.2.3 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 190ms
13
+ ESM dist/index.mjs 16.58 KB
14
+ ESM ⚡️ Build success in 190ms
15
+ DTS Build start
16
+ DTS ⚡️ Build success in 4893ms
17
+ DTS dist/index.d.ts 2.45 KB
18
+ DTS dist/index.d.mts 2.45 KB
@@ -1,16 +1,16 @@
1
1
 
2
- > @aiready/contract-enforcement@0.1.0 test /Users/pengcao/projects/aiready/packages/contract-enforcement
2
+ > @aiready/contract-enforcement@0.2.2 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
- ✓ 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
8
+ ✓ src/__tests__/scoring.test.ts (7 tests) 4ms
9
+ ✓ src/__tests__/detector.test.ts (14 tests) 63ms
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  15:17:54
15
-  Duration  2.10s (transform 511ms, setup 0ms, import 3.00s, tests 28ms, environment 0ms)
14
+  Start at  14:30:37
15
+  Duration  4.25s (transform 2.34s, setup 0ms, import 7.40s, tests 69ms, environment 0ms)
16
16
 
package/dist/index.js CHANGED
@@ -121,13 +121,37 @@ function isLiteral(node) {
121
121
  function isProcessEnvAccess(node) {
122
122
  return node?.type === "MemberExpression" && node.object?.type === "MemberExpression" && node.object.object?.name === "process" && node.object.property?.name === "env";
123
123
  }
124
- function isSwallowedCatch(body) {
124
+ function isSstResourceAccess(node) {
125
+ if (!node) return false;
126
+ let current = node;
127
+ if (current.type === "ChainExpression") {
128
+ current = current.expression;
129
+ }
130
+ while (current && current.type === "MemberExpression") {
131
+ if (current.object?.type === "Identifier" && current.object.name === "Resource") {
132
+ return true;
133
+ }
134
+ current = current.object;
135
+ }
136
+ if (current?.type === "Identifier" && current.name === "Resource") {
137
+ return true;
138
+ }
139
+ return false;
140
+ }
141
+ function isSwallowedCatch(body, filePath) {
125
142
  if (body.length === 0) return true;
143
+ const isUiComponent = filePath.endsWith(".tsx") || filePath.endsWith(".jsx");
126
144
  if (body.length === 1) {
127
145
  const stmt = body[0];
128
146
  if (stmt.type === "ExpressionStatement" && stmt.expression?.type === "CallExpression") {
129
147
  const callee = stmt.expression.callee;
130
148
  if (callee?.object?.name === "console") return true;
149
+ if (isUiComponent) {
150
+ const calleeName = callee?.name || callee?.property?.name || "";
151
+ if (/telemetry|analytics|track|logEvent/i.test(calleeName)) {
152
+ return false;
153
+ }
154
+ }
131
155
  }
132
156
  if (stmt.type === "ThrowStatement") return false;
133
157
  }
@@ -161,32 +185,36 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
161
185
  if (!node || typeof node !== "object") return;
162
186
  markFunctionParamNodes(node);
163
187
  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
- );
188
+ if (!isSstResourceAccess(node.expression) && !isProcessEnvAccess(node.expression)) {
189
+ counts["as-any"]++;
190
+ issues.push(
191
+ makeIssue(
192
+ "as-any",
193
+ import_core.Severity.Major,
194
+ "`as any` type assertion bypasses type safety",
195
+ filePath,
196
+ node.loc?.start.line ?? 0,
197
+ node.loc?.start.column ?? 0,
198
+ getLineContent(code, node.loc?.start.line ?? 0)
199
+ )
200
+ );
201
+ }
176
202
  }
177
203
  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
- );
204
+ if (!isSstResourceAccess(node.expression) && !isProcessEnvAccess(node.expression)) {
205
+ counts["as-unknown"]++;
206
+ issues.push(
207
+ makeIssue(
208
+ "as-unknown",
209
+ import_core.Severity.Major,
210
+ "`as unknown` double-cast bypasses type safety",
211
+ filePath,
212
+ node.loc?.start.line ?? 0,
213
+ node.loc?.start.column ?? 0,
214
+ getLineContent(code, node.loc?.start.line ?? 0)
215
+ )
216
+ );
217
+ }
190
218
  }
191
219
  if (node.type === "ChainExpression") {
192
220
  const depth = countOptionalChainDepth(node);
@@ -206,22 +234,24 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
206
234
  }
207
235
  }
208
236
  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
- );
237
+ if (!isSstResourceAccess(node.left) && !isProcessEnvAccess(node.left)) {
238
+ counts["nullish-literal-default"]++;
239
+ issues.push(
240
+ makeIssue(
241
+ "nullish-literal-default",
242
+ import_core.Severity.Minor,
243
+ "Nullish coalescing with literal default suggests missing upstream type guarantee",
244
+ filePath,
245
+ node.loc?.start.line ?? 0,
246
+ node.loc?.start.column ?? 0,
247
+ getLineContent(code, node.loc?.start.line ?? 0)
248
+ )
249
+ );
250
+ }
221
251
  }
222
252
  if (node.type === "TryStatement" && node.handler) {
223
253
  const catchBody = node.handler.body?.body;
224
- if (catchBody && isSwallowedCatch(catchBody)) {
254
+ if (catchBody && isSwallowedCatch(catchBody, filePath)) {
225
255
  counts["swallowed-error"]++;
226
256
  issues.push(
227
257
  makeIssue(
@@ -345,10 +375,10 @@ function calculateContractEnforcementScore(counts, totalLines, _fileCount) {
345
375
  const fallbackDensity = fallbackCount / loc * 1e3;
346
376
  const errorDensity = errorCount / loc * 1e3;
347
377
  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);
378
+ const typeEscapeHatchScore = clamp(100 - typeDensity * 10, 0, 100);
379
+ const fallbackCascadeScore = clamp(100 - fallbackDensity * 8, 0, 100);
380
+ const errorTransparencyScore = clamp(100 - errorDensity * 15, 0, 100);
381
+ const boundaryValidationScore = clamp(100 - boundaryDensity * 7, 0, 100);
352
382
  const score = Math.round(
353
383
  typeEscapeHatchScore * DIMENSION_WEIGHTS.typeEscapeHatch + fallbackCascadeScore * DIMENSION_WEIGHTS.fallbackCascade + errorTransparencyScore * DIMENSION_WEIGHTS.errorTransparency + boundaryValidationScore * DIMENSION_WEIGHTS.boundaryValidation
354
384
  );
package/dist/index.mjs CHANGED
@@ -94,13 +94,37 @@ function isLiteral(node) {
94
94
  function isProcessEnvAccess(node) {
95
95
  return node?.type === "MemberExpression" && node.object?.type === "MemberExpression" && node.object.object?.name === "process" && node.object.property?.name === "env";
96
96
  }
97
- function isSwallowedCatch(body) {
97
+ function isSstResourceAccess(node) {
98
+ if (!node) return false;
99
+ let current = node;
100
+ if (current.type === "ChainExpression") {
101
+ current = current.expression;
102
+ }
103
+ while (current && current.type === "MemberExpression") {
104
+ if (current.object?.type === "Identifier" && current.object.name === "Resource") {
105
+ return true;
106
+ }
107
+ current = current.object;
108
+ }
109
+ if (current?.type === "Identifier" && current.name === "Resource") {
110
+ return true;
111
+ }
112
+ return false;
113
+ }
114
+ function isSwallowedCatch(body, filePath) {
98
115
  if (body.length === 0) return true;
116
+ const isUiComponent = filePath.endsWith(".tsx") || filePath.endsWith(".jsx");
99
117
  if (body.length === 1) {
100
118
  const stmt = body[0];
101
119
  if (stmt.type === "ExpressionStatement" && stmt.expression?.type === "CallExpression") {
102
120
  const callee = stmt.expression.callee;
103
121
  if (callee?.object?.name === "console") return true;
122
+ if (isUiComponent) {
123
+ const calleeName = callee?.name || callee?.property?.name || "";
124
+ if (/telemetry|analytics|track|logEvent/i.test(calleeName)) {
125
+ return false;
126
+ }
127
+ }
104
128
  }
105
129
  if (stmt.type === "ThrowStatement") return false;
106
130
  }
@@ -134,32 +158,36 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
134
158
  if (!node || typeof node !== "object") return;
135
159
  markFunctionParamNodes(node);
136
160
  if (node.type === "TSAsExpression" && node.typeAnnotation?.type === "TSAnyKeyword") {
137
- counts["as-any"]++;
138
- issues.push(
139
- makeIssue(
140
- "as-any",
141
- Severity.Major,
142
- "`as any` type assertion bypasses type safety",
143
- filePath,
144
- node.loc?.start.line ?? 0,
145
- node.loc?.start.column ?? 0,
146
- getLineContent(code, node.loc?.start.line ?? 0)
147
- )
148
- );
161
+ if (!isSstResourceAccess(node.expression) && !isProcessEnvAccess(node.expression)) {
162
+ counts["as-any"]++;
163
+ issues.push(
164
+ makeIssue(
165
+ "as-any",
166
+ Severity.Major,
167
+ "`as any` type assertion bypasses type safety",
168
+ filePath,
169
+ node.loc?.start.line ?? 0,
170
+ node.loc?.start.column ?? 0,
171
+ getLineContent(code, node.loc?.start.line ?? 0)
172
+ )
173
+ );
174
+ }
149
175
  }
150
176
  if (node.type === "TSAsExpression" && node.typeAnnotation?.type === "TSUnknownKeyword") {
151
- counts["as-unknown"]++;
152
- issues.push(
153
- makeIssue(
154
- "as-unknown",
155
- Severity.Major,
156
- "`as unknown` double-cast bypasses type safety",
157
- filePath,
158
- node.loc?.start.line ?? 0,
159
- node.loc?.start.column ?? 0,
160
- getLineContent(code, node.loc?.start.line ?? 0)
161
- )
162
- );
177
+ if (!isSstResourceAccess(node.expression) && !isProcessEnvAccess(node.expression)) {
178
+ counts["as-unknown"]++;
179
+ issues.push(
180
+ makeIssue(
181
+ "as-unknown",
182
+ 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
+ }
163
191
  }
164
192
  if (node.type === "ChainExpression") {
165
193
  const depth = countOptionalChainDepth(node);
@@ -179,22 +207,24 @@ function detectDefensivePatterns(filePath, code, minChainDepth = 3) {
179
207
  }
180
208
  }
181
209
  if (node.type === "LogicalExpression" && node.operator === "??" && isLiteral(node.right)) {
182
- counts["nullish-literal-default"]++;
183
- issues.push(
184
- makeIssue(
185
- "nullish-literal-default",
186
- Severity.Minor,
187
- "Nullish coalescing with literal default suggests missing upstream type guarantee",
188
- filePath,
189
- node.loc?.start.line ?? 0,
190
- node.loc?.start.column ?? 0,
191
- getLineContent(code, node.loc?.start.line ?? 0)
192
- )
193
- );
210
+ if (!isSstResourceAccess(node.left) && !isProcessEnvAccess(node.left)) {
211
+ counts["nullish-literal-default"]++;
212
+ issues.push(
213
+ makeIssue(
214
+ "nullish-literal-default",
215
+ Severity.Minor,
216
+ "Nullish coalescing with literal default suggests missing upstream type guarantee",
217
+ filePath,
218
+ node.loc?.start.line ?? 0,
219
+ node.loc?.start.column ?? 0,
220
+ getLineContent(code, node.loc?.start.line ?? 0)
221
+ )
222
+ );
223
+ }
194
224
  }
195
225
  if (node.type === "TryStatement" && node.handler) {
196
226
  const catchBody = node.handler.body?.body;
197
- if (catchBody && isSwallowedCatch(catchBody)) {
227
+ if (catchBody && isSwallowedCatch(catchBody, filePath)) {
198
228
  counts["swallowed-error"]++;
199
229
  issues.push(
200
230
  makeIssue(
@@ -318,10 +348,10 @@ function calculateContractEnforcementScore(counts, totalLines, _fileCount) {
318
348
  const fallbackDensity = fallbackCount / loc * 1e3;
319
349
  const errorDensity = errorCount / loc * 1e3;
320
350
  const boundaryDensity = boundaryCount / loc * 1e3;
321
- const typeEscapeHatchScore = clamp(100 - typeDensity * 15, 0, 100);
322
- const fallbackCascadeScore = clamp(100 - fallbackDensity * 12, 0, 100);
323
- const errorTransparencyScore = clamp(100 - errorDensity * 25, 0, 100);
324
- const boundaryValidationScore = clamp(100 - boundaryDensity * 10, 0, 100);
351
+ const typeEscapeHatchScore = clamp(100 - typeDensity * 10, 0, 100);
352
+ const fallbackCascadeScore = clamp(100 - fallbackDensity * 8, 0, 100);
353
+ const errorTransparencyScore = clamp(100 - errorDensity * 15, 0, 100);
354
+ const boundaryValidationScore = clamp(100 - boundaryDensity * 7, 0, 100);
325
355
  const score = Math.round(
326
356
  typeEscapeHatchScore * DIMENSION_WEIGHTS.typeEscapeHatch + fallbackCascadeScore * DIMENSION_WEIGHTS.fallbackCascade + errorTransparencyScore * DIMENSION_WEIGHTS.errorTransparency + boundaryValidationScore * DIMENSION_WEIGHTS.boundaryValidation
327
357
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/contract-enforcement",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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.1"
35
+ "@aiready/core": "0.24.3"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^24.0.0",
package/src/detector.ts CHANGED
@@ -98,8 +98,39 @@ function isProcessEnvAccess(node: any): boolean {
98
98
  );
99
99
  }
100
100
 
101
- function isSwallowedCatch(body: any[]): boolean {
101
+ function isSstResourceAccess(node: any): boolean {
102
+ if (!node) return false;
103
+ let current = node;
104
+ // Handle ChainExpression for optional chaining like Resource.MySecret?.value
105
+ if (current.type === 'ChainExpression') {
106
+ current = current.expression;
107
+ }
108
+
109
+ // Recursively check for 'Resource' identifier at the base of the member chain
110
+ while (current && current.type === 'MemberExpression') {
111
+ if (
112
+ current.object?.type === 'Identifier' &&
113
+ current.object.name === 'Resource'
114
+ ) {
115
+ return true;
116
+ }
117
+ current = current.object;
118
+ }
119
+
120
+ // Also check if the node itself is 'Resource' (rare but possible in some contexts)
121
+ if (current?.type === 'Identifier' && current.name === 'Resource') {
122
+ return true;
123
+ }
124
+
125
+ return false;
126
+ }
127
+
128
+ function isSwallowedCatch(body: any[], filePath: string): boolean {
102
129
  if (body.length === 0) return true;
130
+
131
+ // UI components often have intentional silent catches for telemetry/analytics
132
+ const isUiComponent = filePath.endsWith('.tsx') || filePath.endsWith('.jsx');
133
+
103
134
  if (body.length === 1) {
104
135
  const stmt = body[0];
105
136
  if (
@@ -107,10 +138,20 @@ function isSwallowedCatch(body: any[]): boolean {
107
138
  stmt.expression?.type === 'CallExpression'
108
139
  ) {
109
140
  const callee = stmt.expression.callee;
141
+ // console.log/warn/error is still considered "swallowed" but might be acceptable
110
142
  if (callee?.object?.name === 'console') return true;
143
+
144
+ // If it's a UI component and looks like telemetry, it's a false positive
145
+ if (isUiComponent) {
146
+ const calleeName = callee?.name || callee?.property?.name || '';
147
+ if (/telemetry|analytics|track|logEvent/i.test(calleeName)) {
148
+ return false; // Not "swallowed" in a bad way
149
+ }
150
+ }
111
151
  }
112
152
  if (stmt.type === 'ThrowStatement') return false;
113
153
  }
154
+
114
155
  return false;
115
156
  }
116
157
 
@@ -160,18 +201,24 @@ export function detectDefensivePatterns(
160
201
  node.type === 'TSAsExpression' &&
161
202
  node.typeAnnotation?.type === 'TSAnyKeyword'
162
203
  ) {
163
- counts['as-any']++;
164
- issues.push(
165
- makeIssue(
166
- 'as-any',
167
- Severity.Major,
168
- '`as any` type assertion bypasses type safety',
169
- filePath,
170
- node.loc?.start.line ?? 0,
171
- node.loc?.start.column ?? 0,
172
- getLineContent(code, node.loc?.start.line ?? 0)
173
- )
174
- );
204
+ // Ignore if it's acting on an SST resource or process.env, common "necessary escape hatches"
205
+ if (
206
+ !isSstResourceAccess(node.expression) &&
207
+ !isProcessEnvAccess(node.expression)
208
+ ) {
209
+ counts['as-any']++;
210
+ issues.push(
211
+ makeIssue(
212
+ 'as-any',
213
+ Severity.Major,
214
+ '`as any` type assertion bypasses type safety',
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
+ }
175
222
  }
176
223
 
177
224
  // Pattern: as unknown
@@ -179,18 +226,24 @@ export function detectDefensivePatterns(
179
226
  node.type === 'TSAsExpression' &&
180
227
  node.typeAnnotation?.type === 'TSUnknownKeyword'
181
228
  ) {
182
- counts['as-unknown']++;
183
- issues.push(
184
- makeIssue(
185
- 'as-unknown',
186
- Severity.Major,
187
- '`as unknown` double-cast bypasses type safety',
188
- filePath,
189
- node.loc?.start.line ?? 0,
190
- node.loc?.start.column ?? 0,
191
- getLineContent(code, node.loc?.start.line ?? 0)
192
- )
193
- );
229
+ // Ignore if it's acting on an SST resource or process.env
230
+ if (
231
+ !isSstResourceAccess(node.expression) &&
232
+ !isProcessEnvAccess(node.expression)
233
+ ) {
234
+ counts['as-unknown']++;
235
+ issues.push(
236
+ makeIssue(
237
+ 'as-unknown',
238
+ Severity.Major,
239
+ '`as unknown` double-cast bypasses type safety',
240
+ filePath,
241
+ node.loc?.start.line ?? 0,
242
+ node.loc?.start.column ?? 0,
243
+ getLineContent(code, node.loc?.start.line ?? 0)
244
+ )
245
+ );
246
+ }
194
247
  }
195
248
 
196
249
  // Pattern: deep optional chaining
@@ -218,24 +271,27 @@ export function detectDefensivePatterns(
218
271
  node.operator === '??' &&
219
272
  isLiteral(node.right)
220
273
  ) {
221
- counts['nullish-literal-default']++;
222
- issues.push(
223
- makeIssue(
224
- 'nullish-literal-default',
225
- Severity.Minor,
226
- 'Nullish coalescing with literal default suggests missing upstream type guarantee',
227
- filePath,
228
- node.loc?.start.line ?? 0,
229
- node.loc?.start.column ?? 0,
230
- getLineContent(code, node.loc?.start.line ?? 0)
231
- )
232
- );
274
+ // Ignore if it's an SST Resource or process.env - the standard way to handle fallbacks
275
+ if (!isSstResourceAccess(node.left) && !isProcessEnvAccess(node.left)) {
276
+ counts['nullish-literal-default']++;
277
+ issues.push(
278
+ makeIssue(
279
+ 'nullish-literal-default',
280
+ Severity.Minor,
281
+ 'Nullish coalescing with literal default suggests missing upstream type guarantee',
282
+ filePath,
283
+ node.loc?.start.line ?? 0,
284
+ node.loc?.start.column ?? 0,
285
+ getLineContent(code, node.loc?.start.line ?? 0)
286
+ )
287
+ );
288
+ }
233
289
  }
234
290
 
235
291
  // Pattern: swallowed error
236
292
  if (node.type === 'TryStatement' && node.handler) {
237
293
  const catchBody = node.handler.body?.body;
238
- if (catchBody && isSwallowedCatch(catchBody)) {
294
+ if (catchBody && isSwallowedCatch(catchBody, filePath)) {
239
295
  counts['swallowed-error']++;
240
296
  issues.push(
241
297
  makeIssue(
package/src/scoring.ts CHANGED
@@ -35,10 +35,11 @@ export function calculateContractEnforcementScore(
35
35
  const boundaryDensity = (boundaryCount / loc) * 1000;
36
36
 
37
37
  // Dimension scores: 100 = no patterns, 0 = very high density
38
- const typeEscapeHatchScore = clamp(100 - typeDensity * 15, 0, 100);
39
- const fallbackCascadeScore = clamp(100 - fallbackDensity * 12, 0, 100);
40
- const errorTransparencyScore = clamp(100 - errorDensity * 25, 0, 100);
41
- const boundaryValidationScore = clamp(100 - boundaryDensity * 10, 0, 100);
38
+ // Adjusted to be more lenient - many defensive patterns are valid practices
39
+ const typeEscapeHatchScore = clamp(100 - typeDensity * 10, 0, 100);
40
+ const fallbackCascadeScore = clamp(100 - fallbackDensity * 8, 0, 100);
41
+ const errorTransparencyScore = clamp(100 - errorDensity * 15, 0, 100);
42
+ const boundaryValidationScore = clamp(100 - boundaryDensity * 7, 0, 100);
42
43
 
43
44
  const score = Math.round(
44
45
  typeEscapeHatchScore * DIMENSION_WEIGHTS.typeEscapeHatch +