@kernlang/review 3.3.9 → 3.4.1

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.
Files changed (100) hide show
  1. package/dist/call-graph.d.ts +10 -0
  2. package/dist/call-graph.js +138 -9
  3. package/dist/call-graph.js.map +1 -1
  4. package/dist/concept-rules/auth-drift.js +2 -0
  5. package/dist/concept-rules/auth-drift.js.map +1 -1
  6. package/dist/concept-rules/auth-propagation-drift.js +2 -0
  7. package/dist/concept-rules/auth-propagation-drift.js.map +1 -1
  8. package/dist/concept-rules/body-shape-drift.d.ts +1 -1
  9. package/dist/concept-rules/body-shape-drift.js +5 -3
  10. package/dist/concept-rules/body-shape-drift.js.map +1 -1
  11. package/dist/concept-rules/contract-drift.js +3 -1
  12. package/dist/concept-rules/contract-drift.js.map +1 -1
  13. package/dist/concept-rules/contract-method-drift.js +2 -0
  14. package/dist/concept-rules/contract-method-drift.js.map +1 -1
  15. package/dist/concept-rules/index.js +2 -33
  16. package/dist/concept-rules/index.js.map +1 -1
  17. package/dist/concept-rules/request-validation-drift.js +3 -0
  18. package/dist/concept-rules/request-validation-drift.js.map +1 -1
  19. package/dist/concept-rules/root-cause.d.ts +4 -0
  20. package/dist/concept-rules/root-cause.js +31 -0
  21. package/dist/concept-rules/root-cause.js.map +1 -0
  22. package/dist/concept-rules/unbounded-collection-query.js +2 -0
  23. package/dist/concept-rules/unbounded-collection-query.js.map +1 -1
  24. package/dist/concept-rules/unhandled-api-error-shape.js +2 -0
  25. package/dist/concept-rules/unhandled-api-error-shape.js.map +1 -1
  26. package/dist/default-export.d.ts +41 -0
  27. package/dist/default-export.js +76 -0
  28. package/dist/default-export.js.map +1 -0
  29. package/dist/eval.d.ts +67 -0
  30. package/dist/eval.js +177 -0
  31. package/dist/eval.js.map +1 -0
  32. package/dist/file-context.js +32 -13
  33. package/dist/file-context.js.map +1 -1
  34. package/dist/file-role.d.ts +6 -0
  35. package/dist/file-role.js +27 -0
  36. package/dist/file-role.js.map +1 -1
  37. package/dist/framework-seeds.d.ts +46 -0
  38. package/dist/framework-seeds.js +245 -0
  39. package/dist/framework-seeds.js.map +1 -0
  40. package/dist/git-env.d.ts +1 -0
  41. package/dist/git-env.js +25 -0
  42. package/dist/git-env.js.map +1 -0
  43. package/dist/graph.js +246 -21
  44. package/dist/graph.js.map +1 -1
  45. package/dist/index.d.ts +10 -2
  46. package/dist/index.js +200 -56
  47. package/dist/index.js.map +1 -1
  48. package/dist/llm-bridge.d.ts +35 -9
  49. package/dist/llm-bridge.js +50 -23
  50. package/dist/llm-bridge.js.map +1 -1
  51. package/dist/mappers/ts-concepts.js +87 -20
  52. package/dist/mappers/ts-concepts.js.map +1 -1
  53. package/dist/path-canonical.d.ts +34 -0
  54. package/dist/path-canonical.js +85 -0
  55. package/dist/path-canonical.js.map +1 -0
  56. package/dist/policy.d.ts +22 -0
  57. package/dist/policy.js +47 -0
  58. package/dist/policy.js.map +1 -0
  59. package/dist/project-context.d.ts +135 -0
  60. package/dist/project-context.js +563 -0
  61. package/dist/project-context.js.map +1 -0
  62. package/dist/public-api.d.ts +21 -0
  63. package/dist/public-api.js +17 -2
  64. package/dist/public-api.js.map +1 -1
  65. package/dist/reporter.js +22 -0
  66. package/dist/reporter.js.map +1 -1
  67. package/dist/rule-quality.d.ts +58 -0
  68. package/dist/rule-quality.js +357 -0
  69. package/dist/rule-quality.js.map +1 -0
  70. package/dist/rules/dead-code.d.ts +2 -2
  71. package/dist/rules/dead-code.js +88 -4
  72. package/dist/rules/dead-code.js.map +1 -1
  73. package/dist/rules/index.d.ts +22 -0
  74. package/dist/rules/index.js +32 -0
  75. package/dist/rules/index.js.map +1 -1
  76. package/dist/rules/kern-source.d.ts +4 -0
  77. package/dist/rules/kern-source.js +183 -0
  78. package/dist/rules/kern-source.js.map +1 -1
  79. package/dist/rules/react.js +52 -3
  80. package/dist/rules/react.js.map +1 -1
  81. package/dist/rules/suggest-kern-primitive.js +0 -1
  82. package/dist/rules/suggest-kern-primitive.js.map +1 -1
  83. package/dist/semantic-diff.js +2 -0
  84. package/dist/semantic-diff.js.map +1 -1
  85. package/dist/suppression/apply-suppression.js +2 -0
  86. package/dist/suppression/apply-suppression.js.map +1 -1
  87. package/dist/suppression/parse-directives.d.ts +13 -5
  88. package/dist/suppression/parse-directives.js +62 -8
  89. package/dist/suppression/parse-directives.js.map +1 -1
  90. package/dist/suppression/types.d.ts +9 -0
  91. package/dist/suppression/types.js +6 -1
  92. package/dist/suppression/types.js.map +1 -1
  93. package/dist/taint-crossfile.js +15 -8
  94. package/dist/taint-crossfile.js.map +1 -1
  95. package/dist/telemetry.d.ts +126 -0
  96. package/dist/telemetry.js +303 -0
  97. package/dist/telemetry.js.map +1 -0
  98. package/dist/types.d.ts +165 -1
  99. package/dist/types.js.map +1 -1
  100. package/package.json +4 -3
@@ -0,0 +1,357 @@
1
+ import { getRuleRegistry } from './rules/index.js';
2
+ const GUARD_ADVISORY_RULES = new Set([
3
+ 'dead-export',
4
+ 'sync-in-async',
5
+ 'cognitive-complexity',
6
+ 'handler-size',
7
+ 'unhandled-async',
8
+ ]);
9
+ let ruleInfoById;
10
+ function getRuleInfoMap() {
11
+ if (ruleInfoById)
12
+ return ruleInfoById;
13
+ ruleInfoById = new Map();
14
+ for (const info of getRuleRegistry()) {
15
+ if (!ruleInfoById.has(info.id)) {
16
+ ruleInfoById.set(info.id, info);
17
+ }
18
+ }
19
+ return ruleInfoById;
20
+ }
21
+ function inferredPrecision(info) {
22
+ return info.precision ?? 'medium';
23
+ }
24
+ function inferredLifecycle(info, precision) {
25
+ if (info.lifecycle)
26
+ return info.lifecycle;
27
+ if (precision === 'experimental')
28
+ return 'experimental';
29
+ if ((info.rolloutPhase ?? 0) > 0)
30
+ return 'candidate';
31
+ return 'stable';
32
+ }
33
+ function inferredCiDefault(info, precision, lifecycle) {
34
+ if (info.ciDefault)
35
+ return info.ciDefault;
36
+ if (info.severity === 'info')
37
+ return 'off';
38
+ if (precision === 'experimental' || lifecycle === 'experimental')
39
+ return 'off';
40
+ if (precision === 'medium' || lifecycle === 'candidate')
41
+ return 'guarded';
42
+ return 'on';
43
+ }
44
+ export function getRuleQualityProfile(ruleId) {
45
+ const info = getRuleInfoMap().get(ruleId);
46
+ if (!info)
47
+ return undefined;
48
+ const precision = inferredPrecision(info);
49
+ const lifecycle = inferredLifecycle(info, precision);
50
+ const ciDefault = inferredCiDefault(info, precision, lifecycle);
51
+ return { ...info, precision, lifecycle, ciDefault };
52
+ }
53
+ export function isRulePromotedForCi(ruleId) {
54
+ const profile = getRuleQualityProfile(ruleId);
55
+ return profile?.ciDefault === 'on' && profile.lifecycle === 'stable';
56
+ }
57
+ /**
58
+ * Apply review-mode calibration after confidence assignment and before suppression.
59
+ *
60
+ * Guard mode is the default for PR/CI review. It keeps high-signal errors visible,
61
+ * but softens advisory and experimental findings to info so they do not compete
62
+ * with correctness/security findings. Audit mode preserves original severities for
63
+ * local investigations.
64
+ *
65
+ * Idempotent: each finding is calibrated at most once per process lifetime. The
66
+ * `calibrated` flag prevents compounding multipliers when graph-mode rerun unions
67
+ * already-calibrated per-file findings with newly-injected cross-file findings.
68
+ *
69
+ * Records each acting stage on `finding.calibrationTrail` so audit policy can
70
+ * surface the calibration chain without recomputing it.
71
+ */
72
+ export function applyRuleQualityCalibration(findings, config) {
73
+ if (config?.crossStackMode === 'audit')
74
+ return;
75
+ for (const finding of findings) {
76
+ if (finding.calibrated)
77
+ continue;
78
+ const profile = getRuleQualityProfile(finding.ruleId);
79
+ const shouldDemote = GUARD_ADVISORY_RULES.has(finding.ruleId) ||
80
+ profile?.ciDefault === 'off' ||
81
+ profile?.precision === 'experimental' ||
82
+ profile?.lifecycle === 'experimental';
83
+ if (shouldDemote && finding.severity !== 'error') {
84
+ const before = finding.severity;
85
+ finding.severity = 'info';
86
+ recordCalibration(finding, {
87
+ stage: 'rule-quality:demote-advisory',
88
+ factor: 1,
89
+ reason: GUARD_ADVISORY_RULES.has(finding.ruleId)
90
+ ? 'rule on guard-advisory list'
91
+ : `rule lifecycle=${profile?.lifecycle ?? 'unknown'} precision=${profile?.precision ?? 'unknown'}`,
92
+ beforeSeverity: before,
93
+ afterSeverity: 'info',
94
+ });
95
+ }
96
+ if ((profile?.precision === 'experimental' || profile?.lifecycle === 'experimental') &&
97
+ finding.confidence !== undefined &&
98
+ finding.confidence > 0.6) {
99
+ const before = finding.confidence;
100
+ finding.confidence = 0.6;
101
+ recordCalibration(finding, {
102
+ stage: 'rule-quality:experimental-cap',
103
+ factor: 0.6 / before,
104
+ reason: 'experimental rule capped at 0.6',
105
+ beforeConfidence: before,
106
+ afterConfidence: 0.6,
107
+ });
108
+ }
109
+ finding.calibrated = true;
110
+ }
111
+ }
112
+ /** Append a calibration stage to a finding's trail. */
113
+ export function recordCalibration(finding, stage) {
114
+ if (!finding.calibrationTrail)
115
+ finding.calibrationTrail = [];
116
+ finding.calibrationTrail.push(stage);
117
+ }
118
+ /**
119
+ * Per-rule confidence multipliers per file role. Default factor is 1.0 (no change).
120
+ *
121
+ * Design constraints driven by the Phase 1 red-team:
122
+ * - **No wildcards.** Every (role, ruleId) pair must be explicit. A `'*':0`
123
+ * entry would silently nuke security findings on any file misclassified as
124
+ * codegen — every entry is enumerated to avoid that.
125
+ * - **Security-layer rules are excluded by construction** at lookup time
126
+ * (see `roleMultiplierFor`). Even if a future edit accidentally lists a
127
+ * security rule here, it is ignored.
128
+ * - **NaN-safe.** Lookup returns 1 for any unmapped pair; we never let
129
+ * `undefined * confidence` propagate.
130
+ */
131
+ const ROLE_MULTIPLIER = {
132
+ barrel: {
133
+ 'dead-export': 0,
134
+ 'cognitive-complexity': 0,
135
+ 'handler-size': 0,
136
+ 'unhandled-async': 0.5,
137
+ },
138
+ codegen: {
139
+ 'dead-export': 0,
140
+ 'cognitive-complexity': 0,
141
+ 'handler-size': 0,
142
+ 'sync-in-async': 0,
143
+ 'unhandled-async': 0,
144
+ 'extra-code': 0,
145
+ 'inconsistent-pattern': 0,
146
+ 'style-difference': 0,
147
+ 'missing-type': 0,
148
+ },
149
+ example: {
150
+ 'dead-export': 0,
151
+ 'public-api': 0,
152
+ 'unhandled-async': 0.5,
153
+ 'cognitive-complexity': 0.5,
154
+ },
155
+ test: {
156
+ 'handler-size': 0.3,
157
+ 'sync-in-async': 0,
158
+ 'cognitive-complexity': 0.5,
159
+ 'dead-export': 0,
160
+ },
161
+ 'rule-definition': {
162
+ 'cognitive-complexity': 0,
163
+ },
164
+ runtime: {},
165
+ };
166
+ /** Layer prefixes that are protected from any role multiplier. */
167
+ const PROTECTED_LAYER_PREFIXES = ['security'];
168
+ function isProtectedRule(ruleId) {
169
+ const info = getRuleInfoMap().get(ruleId);
170
+ if (!info)
171
+ return false;
172
+ return PROTECTED_LAYER_PREFIXES.some((prefix) => info.layer.startsWith(prefix));
173
+ }
174
+ /** Look up the role-aware multiplier for a finding. Always returns a finite number in [0, 1]. */
175
+ export function roleMultiplierFor(role, ruleId) {
176
+ if (isProtectedRule(ruleId))
177
+ return 1;
178
+ const fromTable = ROLE_MULTIPLIER[role]?.[ruleId];
179
+ if (typeof fromTable !== 'number' || !Number.isFinite(fromTable))
180
+ return 1;
181
+ return Math.max(0, Math.min(1, fromTable));
182
+ }
183
+ /**
184
+ * Maps a kern rule to the external linter rules that already cover the same
185
+ * concern. If the user has any of these enabled at error/warn, kern's finding
186
+ * is duplicate noise and gets demoted to info.
187
+ *
188
+ * Phase 1 red-team explicitly cautioned against a static table — the right
189
+ * long-term answer is querying the live ESLint instance per-file, which honors
190
+ * `overrides`. That requires async pre-warm. As a sync first cut we use the
191
+ * project-level config; coverage is honest about its limits (no per-file
192
+ * overrides honored) but the common case (one .eslintrc.json at root) works.
193
+ *
194
+ * Security-layer rules are NOT listed here: even if eslint has a similar
195
+ * rule, kern's output is independently valuable (different detection model,
196
+ * audit-trail integrity).
197
+ */
198
+ const KERN_TO_EXTERNAL = {
199
+ 'dead-export': {
200
+ eslint: ['no-unused-vars', '@typescript-eslint/no-unused-vars'],
201
+ biome: ['noUnusedVariables'],
202
+ },
203
+ 'unused-import': {
204
+ eslint: ['no-unused-vars', '@typescript-eslint/no-unused-vars', 'unused-imports/no-unused-imports'],
205
+ biome: ['noUnusedImports'],
206
+ },
207
+ 'sync-in-async': {
208
+ eslint: ['@typescript-eslint/no-misused-promises', 'no-misused-promises'],
209
+ },
210
+ 'unhandled-async': {
211
+ eslint: ['@typescript-eslint/no-floating-promises', 'no-floating-promises'],
212
+ },
213
+ };
214
+ /** Look up overlap entry; isProtectedRule already shields the security layer. */
215
+ function overlapTargetsFor(ruleId) {
216
+ if (isProtectedRule(ruleId))
217
+ return undefined;
218
+ return KERN_TO_EXTERNAL[ruleId];
219
+ }
220
+ /**
221
+ * Demote kern findings whose external counterpart is configured at error/warn
222
+ * in the project. Demotion (severity → info, confidence × 0.5) preserves the
223
+ * finding for telemetry while taking it out of the CI gate. Skipped in audit.
224
+ */
225
+ export function applyOverlapCalibration(findings, external, config) {
226
+ if (config?.crossStackMode === 'audit')
227
+ return;
228
+ if (external.eslintEnabledRules.size === 0 && external.biomeEnabledRules.size === 0)
229
+ return;
230
+ for (const finding of findings) {
231
+ if (finding.calibrated)
232
+ continue;
233
+ const targets = overlapTargetsFor(finding.ruleId);
234
+ if (!targets)
235
+ continue;
236
+ const matchedEslint = targets.eslint?.find((r) => external.eslintEnabledRules.has(r));
237
+ const matchedBiome = targets.biome?.find((r) => external.biomeEnabledRules.has(r));
238
+ if (!matchedEslint && !matchedBiome)
239
+ continue;
240
+ const which = matchedEslint ? `eslint:${matchedEslint}` : matchedBiome ? `biome:${matchedBiome}` : 'unknown';
241
+ const beforeSeverity = finding.severity;
242
+ if (finding.severity !== 'error' && finding.severity !== 'info') {
243
+ finding.severity = 'info';
244
+ }
245
+ let beforeConfidence;
246
+ let afterConfidence;
247
+ if (finding.confidence !== undefined) {
248
+ beforeConfidence = finding.confidence;
249
+ afterConfidence = finding.confidence * 0.5;
250
+ finding.confidence = afterConfidence;
251
+ }
252
+ recordCalibration(finding, {
253
+ stage: 'overlap:external-linter',
254
+ factor: 0.5,
255
+ reason: `external linter already enforces ${which}`,
256
+ beforeConfidence,
257
+ afterConfidence,
258
+ ...(beforeSeverity !== finding.severity ? { beforeSeverity, afterSeverity: finding.severity } : {}),
259
+ });
260
+ }
261
+ }
262
+ /**
263
+ * Apply role-aware confidence multipliers. Runs alongside applyRuleQualityCalibration
264
+ * in guard/ci policies; skipped in audit policy.
265
+ *
266
+ * Idempotent via the same `calibrated` flag: once a finding has been through any
267
+ * calibration stage it will not be touched again, so graph-mode rerun on a union
268
+ * does not compound multipliers.
269
+ */
270
+ export function applyRoleAwareConfidence(findings, fileRole, config) {
271
+ if (config?.crossStackMode === 'audit')
272
+ return;
273
+ for (const finding of findings) {
274
+ if (finding.calibrated)
275
+ continue;
276
+ const factor = roleMultiplierFor(fileRole, finding.ruleId);
277
+ if (factor === 1)
278
+ continue;
279
+ if (finding.confidence !== undefined) {
280
+ const before = finding.confidence;
281
+ const after = before * factor;
282
+ finding.confidence = after;
283
+ recordCalibration(finding, {
284
+ stage: 'role-aware:confidence-multiplier',
285
+ factor,
286
+ reason: `role=${fileRole} reduces confidence on rule '${finding.ruleId}'`,
287
+ beforeConfidence: before,
288
+ afterConfidence: after,
289
+ });
290
+ }
291
+ else {
292
+ recordCalibration(finding, {
293
+ stage: 'role-aware:confidence-multiplier',
294
+ factor,
295
+ reason: `role=${fileRole} reduces confidence on rule '${finding.ruleId}' (no native confidence set)`,
296
+ });
297
+ }
298
+ }
299
+ }
300
+ export function applyRuleSupersession(findings, config) {
301
+ if (config?.crossStackMode === 'audit')
302
+ return [...findings];
303
+ const suppressedByRoot = new Map();
304
+ for (const finding of findings) {
305
+ const supersedes = getRuleQualityProfile(finding.ruleId)?.supersedes;
306
+ if (!supersedes || supersedes.length === 0)
307
+ continue;
308
+ const rootKey = findingRootKey(finding);
309
+ const existing = suppressedByRoot.get(rootKey) ?? new Set();
310
+ for (const ruleId of supersedes) {
311
+ existing.add(ruleId);
312
+ }
313
+ suppressedByRoot.set(rootKey, existing);
314
+ }
315
+ return findings.filter((finding) => {
316
+ const suppressed = suppressedByRoot.get(findingRootKey(finding));
317
+ return !suppressed?.has(finding.ruleId);
318
+ });
319
+ }
320
+ function findingRootKey(finding) {
321
+ if (finding.rootCause?.key)
322
+ return finding.rootCause.key;
323
+ const span = finding.primarySpan;
324
+ return `${span.file}:${span.startLine}:${span.startCol}`;
325
+ }
326
+ /**
327
+ * Lightweight self-check for registry hygiene. It is intentionally non-throwing so
328
+ * tests and tooling can decide whether to warn or fail.
329
+ */
330
+ export function validateRuleQualityRegistry() {
331
+ const issues = [];
332
+ const seen = new Set();
333
+ for (const info of getRuleRegistry()) {
334
+ if (seen.has(info.id)) {
335
+ issues.push({ ruleId: info.id, message: 'duplicate rule registry entry' });
336
+ continue;
337
+ }
338
+ seen.add(info.id);
339
+ const profile = getRuleQualityProfile(info.id);
340
+ if (!profile)
341
+ continue;
342
+ if (profile.ciDefault === 'on' && !info.precision) {
343
+ issues.push({ ruleId: info.id, message: 'CI-on rule must declare precision explicitly' });
344
+ }
345
+ if (profile.ciDefault === 'on' && profile.lifecycle !== 'stable') {
346
+ issues.push({ ruleId: info.id, message: 'CI-on rule must be stable' });
347
+ }
348
+ if (profile.lifecycle === 'experimental' && profile.ciDefault === 'on') {
349
+ issues.push({ ruleId: info.id, message: 'experimental rule cannot default to CI-on' });
350
+ }
351
+ if (profile.precision === 'experimental' && profile.severity === 'error') {
352
+ issues.push({ ruleId: info.id, message: 'experimental rule should not default to error severity' });
353
+ }
354
+ }
355
+ return issues;
356
+ }
357
+ //# sourceMappingURL=rule-quality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-quality.js","sourceRoot":"","sources":["../src/rule-quality.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,6 @@
6
6
  */
7
7
  import type { CallGraph } from '../call-graph.js';
8
8
  import { type PublicApiMap } from '../public-api.js';
9
- import type { ReviewFinding } from '../types.js';
10
- export declare function deadExportRule(callGraph: CallGraph, filePath: string, publicApi?: PublicApiMap): ReviewFinding[];
9
+ import type { ReachabilityBlocker, ReviewFinding } from '../types.js';
10
+ export declare function deadExportRule(callGraph: CallGraph, filePath: string, publicApi?: PublicApiMap, blockers?: readonly ReachabilityBlocker[]): ReviewFinding[];
11
11
  export declare function crossFileAsyncRule(callGraph: CallGraph, filePath: string): ReviewFinding[];
@@ -11,8 +11,54 @@ function span(file, line, col = 1) {
11
11
  }
12
12
  // ── Rule: dead-export ───────────────────────────────────────────────────
13
13
  // Exported function/variable that is never imported by any file in the graph.
14
- export function deadExportRule(callGraph, filePath, publicApi) {
14
+ /**
15
+ * The confidence ceiling step 9b applies when a ReachabilityBlocker matches
16
+ * a finding's symbol scope. CAP, not floor: `Math.min(confidence, 0.4)`.
17
+ * A real floor would *raise* low-confidence findings (e.g. a 0.6 codegen-
18
+ * demoted dead-export bouncing back up to 0.4 — wait, 0.4 < 0.6, so a
19
+ * floor wouldn't help here, but the principle holds: blockers indicate
20
+ * uncertainty, they should never strengthen a finding). Codex flagged
21
+ * the floor-vs-cap terminology in plan-review #66 as a HIGH-severity
22
+ * gotcha; this constant is named CAP to make the intent obvious.
23
+ */
24
+ const REACHABILITY_BLOCKER_CAP = 0.4;
25
+ /**
26
+ * Match a (filePath, exportName) blocker against a finding, with the
27
+ * default-alias step 9a wired in: when the seed says `'default'` is
28
+ * blocked but the call graph stored the symbol under its declaration
29
+ * name (`'Page'`), the file's `defaultExportNames` entry tells us the
30
+ * two key the same on-disk symbol.
31
+ *
32
+ * Returns the matched blocker, or undefined when nothing applies.
33
+ */
34
+ function findMatchingBlocker(blockers, filePath, exportName, defaultExportName) {
35
+ for (const b of blockers) {
36
+ if (b.filePath !== filePath)
37
+ continue;
38
+ if (b.exportName === exportName)
39
+ return b;
40
+ // Default alias: blocker says (path, 'default'), and `exportName` IS
41
+ // the file's default — same symbol.
42
+ if (b.exportName === 'default' && defaultExportName === exportName)
43
+ return b;
44
+ }
45
+ return undefined;
46
+ }
47
+ /**
48
+ * Public-API check with the step 9a default-alias wired in: a seed of
49
+ * `(filePath, 'default')` proves `(filePath, internalName)` is public
50
+ * when internalName IS the file's default export.
51
+ */
52
+ function isPublicApiWithDefaultAlias(publicApi, filePath, exportName, defaultExportName) {
53
+ if (isPublicApi(publicApi, filePath, exportName))
54
+ return true;
55
+ if (defaultExportName === exportName && isPublicApi(publicApi, filePath, 'default'))
56
+ return true;
57
+ return false;
58
+ }
59
+ export function deadExportRule(callGraph, filePath, publicApi, blockers = []) {
15
60
  const findings = [];
61
+ const defaultExportName = callGraph.defaultExportNames.get(filePath);
16
62
  for (const key of callGraph.deadExports) {
17
63
  const fn = callGraph.functions.get(key);
18
64
  if (!fn || fn.filePath !== filePath)
@@ -23,7 +69,7 @@ export function deadExportRule(callGraph, filePath, publicApi) {
23
69
  // Skip intentional public API — package.json entries, curated barrels, config overrides.
24
70
  // External consumers (other packages, dynamic loaders, platform entry points) won't
25
71
  // appear in the analyzed graph, so their imports can't be observed.
26
- if (publicApi && isPublicApi(publicApi, fn.filePath, fn.name))
72
+ if (publicApi && isPublicApiWithDefaultAlias(publicApi, fn.filePath, fn.name, defaultExportName))
27
73
  continue;
28
74
  // Class methods inherit public-API status from their enclosing class. The
29
75
  // call graph tracks `Class.method` as a separate exported symbol whose
@@ -40,11 +86,48 @@ export function deadExportRule(callGraph, filePath, publicApi) {
40
86
  // If lots of calls are unresolved, the dead export might actually be used via a dynamic path
41
87
  const totalCalls = [...callGraph.functions.values()].reduce((sum, f) => sum + f.calls.length, 0);
42
88
  const unresolvedRatio = totalCalls > 0 ? callGraph.unresolvedCallCount / totalCalls : 0;
43
- const confidence = unresolvedRatio > 0.3 ? 0.6 : unresolvedRatio > 0.1 ? 0.7 : 0.85;
89
+ const baseConfidence = unresolvedRatio > 0.3 ? 0.6 : unresolvedRatio > 0.1 ? 0.7 : 0.85;
90
+ // Step 9b: symbol-scoped reachability blocker. When the graph couldn't
91
+ // prove this specific (file, name) is unreachable — say a candidate
92
+ // dead export is referenced by an unresolved re-export — the finding
93
+ // STILL emits (so telemetry sees it and fpRateEstimate stays honest)
94
+ // but at info severity with a CAP at 0.4 plus a calibration trail
95
+ // entry. Hard suppression was the v3 design that red-team killed:
96
+ // one weak signal silenced 50 unrelated symbols and fpRateEstimate
97
+ // went out of sync with reality.
98
+ const blocker = findMatchingBlocker(blockers, fn.filePath, fn.name, defaultExportName);
99
+ let severity = 'warning';
100
+ let confidence = baseConfidence;
101
+ let calibrationTrail;
102
+ if (blocker) {
103
+ severity = 'info';
104
+ const before = baseConfidence;
105
+ confidence = Math.min(before, REACHABILITY_BLOCKER_CAP);
106
+ // baseConfidence is one of the dead-export ladder values (0.6/0.7/0.85);
107
+ // it can never be 0, so the division is safe without a guard.
108
+ //
109
+ // INVARIANT — fresh array, never append. The trail is recreated each
110
+ // time deadExportRule fires, so it can't accumulate stages across
111
+ // repeated calls. This pairs with applyRuleQualityCalibration's
112
+ // `calibrated` guard (rule-quality.ts:91) to keep the trail length
113
+ // bounded across the lifetime of any single ReviewFinding object.
114
+ // If a future change introduces persistent ReviewFinding objects
115
+ // reused across watch-mode runs, BOTH guarantees must be revisited
116
+ // — see the idempotence test in rule-quality.test.ts:125.
117
+ calibrationTrail = [
118
+ {
119
+ stage: 'reachability:blocker',
120
+ factor: confidence / before,
121
+ reason: blocker.reason,
122
+ beforeConfidence: before,
123
+ afterConfidence: confidence,
124
+ },
125
+ ];
126
+ }
44
127
  findings.push({
45
128
  source: 'kern',
46
129
  ruleId: 'dead-export',
47
- severity: 'warning',
130
+ severity,
48
131
  category: 'structure',
49
132
  message: `Exported function '${fn.name}' is never imported in the analyzed codebase`,
50
133
  primarySpan: span(filePath, fn.line),
@@ -64,6 +147,7 @@ export function deadExportRule(callGraph, filePath, publicApi) {
64
147
  },
65
148
  ],
66
149
  },
150
+ ...(calibrationTrail ? { calibrationTrail } : {}),
67
151
  });
68
152
  }
69
153
  return findings;
@@ -1 +1 @@
1
- {"version":3,"file":"dead-code.js","sourceRoot":"","sources":["../../src/rules/dead-code.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dead-code.js","sourceRoot":"","sources":["../../src/rules/dead-code.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,28 @@ export interface RuleInfo {
31
31
  * 'experimental' — rule may be noisy; hide-by-default, promote after signal data proves it out.
32
32
  */
33
33
  precision?: 'high' | 'medium' | 'experimental';
34
+ /**
35
+ * Lifecycle controls how aggressively a rule should participate in default
36
+ * guard/CI runs. Omitted entries are inferred from precision and rolloutPhase.
37
+ */
38
+ lifecycle?: 'stable' | 'candidate' | 'experimental';
39
+ /**
40
+ * Default CI posture:
41
+ * - on: participates at declared severity
42
+ * - guarded: can be softened in high-signal guard runs
43
+ * - off: emitted as informational unless audit mode is enabled
44
+ */
45
+ ciDefault?: 'on' | 'guarded' | 'off';
46
+ /**
47
+ * Analysis substrates required for high precision. This is intentionally
48
+ * declarative so new rules have to state their dependency on graph/type/evidence
49
+ * rather than hiding it in implementation comments.
50
+ */
51
+ requires?: Array<'graph' | 'type-info' | 'concepts' | 'taint' | 'external-tool'>;
52
+ /** Rule IDs that this rule makes redundant when both fire on the same root cause. */
53
+ supersedes?: string[];
54
+ /** True when findings from this rule can safely carry structured autofix actions. */
55
+ fixable?: boolean;
34
56
  /**
35
57
  * Wave number in the rollout plan. Used by kern-sight to group new rules
36
58
  * visually and by `--list-rules` to surface what just landed. Wave 0 = substrate.
@@ -1071,6 +1071,38 @@ const REGISTRY = [
1071
1071
  severity: 'warning',
1072
1072
  description: 'Network/DB effect without error recovery',
1073
1073
  },
1074
+ {
1075
+ id: 'auth-drift',
1076
+ layer: 'concept',
1077
+ severity: 'warning',
1078
+ description: 'Authenticated backend route called by raw client request without visible auth propagation',
1079
+ precision: 'high',
1080
+ supersedes: ['auth-propagation-drift', 'unhandled-api-error-shape'],
1081
+ },
1082
+ {
1083
+ id: 'contract-drift',
1084
+ layer: 'concept',
1085
+ severity: 'warning',
1086
+ description: 'Client network call has no matching server route contract',
1087
+ precision: 'high',
1088
+ supersedes: ['unhandled-api-error-shape', 'unbounded-collection-query', 'request-validation-drift'],
1089
+ },
1090
+ {
1091
+ id: 'contract-method-drift',
1092
+ layer: 'concept',
1093
+ severity: 'warning',
1094
+ description: 'Client network method does not match the server route method for the same path',
1095
+ precision: 'high',
1096
+ supersedes: ['unhandled-api-error-shape', 'unbounded-collection-query', 'request-validation-drift'],
1097
+ },
1098
+ {
1099
+ id: 'body-shape-drift',
1100
+ layer: 'concept',
1101
+ severity: 'warning',
1102
+ description: 'Client request body fields differ from backend route body usage',
1103
+ precision: 'high',
1104
+ supersedes: ['request-validation-drift'],
1105
+ },
1074
1106
  {
1075
1107
  id: 'unhandled-api-error-shape',
1076
1108
  layer: 'concept',