Context
Discovered as an out-of-scope follow-up while implementing #4740 (part of epic #4737's REES/
deterministic-tier phase) — deliberately not folded into that PR (#4758) to keep it scoped to the
complexity-delta feature and under this repo's preferred small-PR size.
The problem
Three analyzer files in review-enrichment/src/analyzers/ now each carry their own near-identical
private helpers for "fetch a repo file's raw content at a given ref via the GitHub contents API, with a
bounded/streamed reader that caps at 1MB and cancels oversized responses":
Each copy is byte-for-byte or near-byte-for-byte identical (same 1_000_000 byte cap, same
streaming-decoder loop, same abort handling). doc-comment-drift.ts additionally hand-builds its own
GitHub auth headers inline rather than using the shared githubHeaders() helper
(review-enrichment/src/github-headers.ts) that exhaustiveness-drift.ts and complexity-delta.ts
already use — a small pre-existing inconsistency to fix in the same pass.
Fix
Correction (2026-07-11): the target changed. #4741's PR (#4760, duplication-delta.ts) revealed
that a MORE MATURE shared utility already exists and is already in production use:
boundedFetchText/external-fetch.ts (review-enrichment/src/external-fetch.ts) — a typed
ok/failure result (timeout/network_error/response_too_large/circuit_open/etc. as distinct
reasons), a real per-endpoint-category circuit breaker (cooldown after repeated failures), a
configurable timeout (default 1200ms), byte-size capping, and diagnostics/metering hooks. This is
strictly better than the three hand-rolled readBoundedText copies described above (none of which have
a timeout or circuit breaker at all). duplication-delta.ts already migrated onto it correctly.
Do not extract a fourth parallel abstraction. Instead: migrate doc-comment-drift.ts,
exhaustiveness-drift.ts, and complexity-delta.ts to call boundedFetchText (via
options.analysis.fetchText when an AnalysisContext is available, mirroring
duplication-delta.ts's own fetchFileAtHead helper as the reference implementation — same file,
same PR, read it first), deleting their own private readBoundedText/bounded-fetch logic entirely
rather than relocating it. Also switch doc-comment-drift.ts to the shared githubHeaders() helper in
the same pass, as originally planned. Verify byte-identical behavior for all three existing callers via
their existing test suites (doc-comment-drift.test.ts, exhaustiveness-drift.test.ts,
complexity-delta.test.ts) — the same verification rigor #4739's own PR used for its extraction.
Regenerate review-enrichment/analyzer-metadata.json etc. only if descriptor metadata changes (it
shouldn't — this is a pure internal refactor with no behavior change).
Acceptance criteria
Context
Discovered as an out-of-scope follow-up while implementing #4740 (part of epic #4737's REES/
deterministic-tier phase) — deliberately not folded into that PR (#4758) to keep it scoped to the
complexity-delta feature and under this repo's preferred small-PR size.
The problem
Three analyzer files in
review-enrichment/src/analyzers/now each carry their own near-identicalprivate helpers for "fetch a repo file's raw content at a given ref via the GitHub contents API, with a
bounded/streamed reader that caps at 1MB and cancels oversized responses":
doc-comment-drift.ts— inline inscanDocCommentDrift, plus its ownreadBoundedText.exhaustiveness-drift.ts—fetchFileAtHead+ its ownreadBoundedText.complexity-delta.ts(new, Real complexity-delta analyzer (true before/after comparison) #4740/PR feat(rees): add real before/after complexity-delta analyzer #4758) —fetchFileAtHeadSha+ its ownreadBoundedText.Each copy is byte-for-byte or near-byte-for-byte identical (same
1_000_000byte cap, samestreaming-decoder loop, same abort handling).
doc-comment-drift.tsadditionally hand-builds its ownGitHub auth headers inline rather than using the shared
githubHeaders()helper(
review-enrichment/src/github-headers.ts) thatexhaustiveness-drift.tsandcomplexity-delta.tsalready use — a small pre-existing inconsistency to fix in the same pass.
Fix
Correction (2026-07-11): the target changed. #4741's PR (#4760,
duplication-delta.ts) revealedthat a MORE MATURE shared utility already exists and is already in production use:
boundedFetchText/external-fetch.ts(review-enrichment/src/external-fetch.ts) — a typedok/failure result (
timeout/network_error/response_too_large/circuit_open/etc. as distinctreasons), a real per-endpoint-category circuit breaker (cooldown after repeated failures), a
configurable timeout (default 1200ms), byte-size capping, and diagnostics/metering hooks. This is
strictly better than the three hand-rolled
readBoundedTextcopies described above (none of which havea timeout or circuit breaker at all).
duplication-delta.tsalready migrated onto it correctly.Do not extract a fourth parallel abstraction. Instead: migrate
doc-comment-drift.ts,exhaustiveness-drift.ts, andcomplexity-delta.tsto callboundedFetchText(viaoptions.analysis.fetchTextwhen anAnalysisContextis available, mirroringduplication-delta.ts's ownfetchFileAtHeadhelper as the reference implementation — same file,same PR, read it first), deleting their own private
readBoundedText/bounded-fetch logic entirelyrather than relocating it. Also switch
doc-comment-drift.tsto the sharedgithubHeaders()helper inthe same pass, as originally planned. Verify byte-identical behavior for all three existing callers via
their existing test suites (
doc-comment-drift.test.ts,exhaustiveness-drift.test.ts,complexity-delta.test.ts) — the same verification rigor #4739's own PR used for its extraction.Regenerate
review-enrichment/analyzer-metadata.jsonetc. only if descriptor metadata changes (itshouldn't — this is a pure internal refactor with no behavior change).
Acceptance criteria
doc-comment-drift.ts,exhaustiveness-drift.ts, andcomplexity-delta.tsall call the EXISTINGboundedFetchText(external-fetch.ts) instead of their own private bounded-read logic — no newshared module created.
the moved logic against its prior form, not just by eyeballing).
doc-comment-drift.tsuses the sharedgithubHeaders()helper instead of hand-building authheaders inline.
changed (it shouldn't for a pure refactor).