You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
| Breaking | Major | Removed exports or members; required parameter added; optional parameter or property made required; a parameter's rest-ness changed; parameter or property type changed; return type changed; a member's `static`/`protected`/`abstract` modifier changed |
547
-
| Non-breaking | Minor | Optional parameter added; rest parameter added; required parameter or property made optional|
569
+
| Non-breaking | Minor | Optional parameter added; rest parameter added; required parameter or property made optional; a type change whose only diff is swapping an unresolvable specifier for an exported one (a reference repair, see Configuration)|
548
570
| Addition | Minor | New exports, new interface/class members |
549
571
550
572
The analyzer compares parameters, return types, property types, and enum values
Copy file name to clipboardExpand all lines: src/analyzers/ai-analyzer.ts
+34-1Lines changed: 34 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -235,7 +235,8 @@ Reasoning rules:
235
235
9. Adding a *new optional* property to an object type is non-breaking, for both input and output types: existing consumers neither passed it nor relied on reading it. (This is distinct from rule 4, which is about flipping an *existing* output field to optional.)
236
236
10. \`Pick\`, \`Omit\`, \`Partial\`, \`Required\`, and other mapped types preserve each member's optionality from the source type. Resolve the member against the referenced source definition before judging; a property newly included by a \`Pick\` (or surviving an \`Omit\`) is optional in the result whenever the source declares it optional, so do not treat it as required unless the source does.
237
237
11. Adding a *required* property to a type is breaking only when consumers construct or assign values of that type (a parameter / input position, or an object literal they author against the type). For a type consumers only read (a return / response / output type, e.g. the resolved value of a \`Promise<T>\` return or a hook result field), adding a property is non-breaking. Determine the direction from the "Usage sites" block: a \`Promise<T>\` result, a function return, or a result field is output. If any usage is an input position, or no usage sites are shown, keep "breaking".
238
-
12. A reference whose module specifier is not a public, exported entry point of its package is breaking regardless of structural shape: the consumer cannot resolve the module, so the type degrades to \`any\` (skipLibCheck) or fails to compile (TS2307). Treat a specifier that contains a \`/_chunks/\` segment, ends in a content-hashed bundler chunk basename (a \`-<hash>\` suffix), or names a subpath a package blocks/omits in its \`exports\` as non-resolvable. Do NOT downgrade such a change on structural-equivalence grounds (rule 2 does not apply): "the shape is identical" is true but irrelevant when the consumer never receives the type.
238
+
12. A reference whose module specifier is not a public, exported entry point of its package is breaking regardless of structural shape: the consumer cannot resolve the module, so the type degrades to \`any\` (skipLibCheck) or fails to compile (TS2307). Treat a specifier that contains a \`/_chunks/\` segment, ends in a content-hashed bundler chunk basename (a \`-<hash>\` suffix), or names a subpath a package blocks/omits in its \`exports\` as non-resolvable. Do NOT downgrade such a change on structural-equivalence grounds (rule 2 does not apply): "the shape is identical" is true but irrelevant when the consumer never receives the type. When a change carries a \`referenceResolutions\` list, entries with \`"deterministic": true\` were resolved against the dependency's actual \`exports\` map: \`blocked\` means consumers cannot resolve the specifier, \`exported\` means it is a public entry point. Trust those over any guess from the specifier's path shape. An \`"unknown"\` verdict verified nothing (dependency not installed, no \`exports\` map, or a relative/absolute specifier); fall back to the shape heuristics above for those.
239
+
13. The inverse swap is a repair, not a break: when a change's only difference is that a non-resolvable specifier (per rule 12) was replaced by one whose \`referenceResolutions\` verdict is \`exported\`, the old type was never consumable (it errored or degraded to \`any\`), so no well-typed consumer can be broken by the swap. Judge it "non-breaking" even though the imported alias name changed with the specifier (bundlers minify chunk-internal names, e.g. \`Xm\` for \`Without\`). A change carrying \`repairedReference\` was already verified this way deterministically; confirm it as non-breaking rather than escalating.
239
240
240
241
Output protocol:
241
242
- Always respond by calling the submit_review tool. Never reply with plain text.
@@ -538,6 +539,28 @@ export class AiChangeAnalyzer {
538
539
continue;
539
540
}
540
541
542
+
// The mirror guard for repaired references (issue #98): the detector's
543
+
// downgrade is deterministic (every dropped specifier unconsumable, every
544
+
// introduced one exported, signature otherwise identical), and the swap
545
+
// is the entire diff, so a model escalation cannot be adding information.
546
+
// Record the opinion without applying it; the reporter explains why.
547
+
constisEscalation=
548
+
change.type===ChangeType.NON_BREAKING&&
549
+
verdictType===ChangeType.BREAKING;
550
+
if(isEscalation&&change.repairedReference){
551
+
enriched.push({
552
+
...change,
553
+
aiAnalysis: {
554
+
source: "ai-suggested-escalation",
555
+
confidence: clamp01(v.confidence),
556
+
rationale: v.rationale,
557
+
migration: undefined,
558
+
model: this.model,
559
+
},
560
+
});
561
+
continue;
562
+
}
563
+
541
564
constoverrode=verdictType!==change.type;
542
565
if(overrode)this.overriddenCount+=1;
543
566
constaiAnalysis: AiAnalysis={
@@ -610,6 +633,16 @@ export class AiChangeAnalyzer {
610
633
// referenced type definitions ride in the surface block.
611
634
beforeSnippet: capSnippet(c.beforeSnippet),
612
635
afterSnippet: capSnippet(c.afterSnippet),
636
+
// Deterministic exports-map verdicts for dropped/introduced specifiers
637
+
// (rules 12/13), so the model never guesses resolvability from path
638
+
// shapes. Only present when the specifier sets differ, so the common
639
+
// change pays no tokens for them.
640
+
...(c.referenceResolutions
641
+
? {referenceResolutions: c.referenceResolutions}
642
+
: {}),
643
+
...(c.repairedReference
644
+
? {repairedReference: c.repairedReference}
645
+
: {}),
613
646
}));
614
647
615
648
// Compact JSON (no indentation): this block is in the non-cached part of
0 commit comments