Skip to content

fix(check): only flag modified exports whose name existed at the base ref - #2677

Open
avengedsevenskull-ctrl wants to merge 2 commits into
optave:mainfrom
avengedsevenskull-ctrl:fix/2674-signatures-added-export
Open

avengedsevenskull-ctrl wants to merge 2 commits into
optave:mainfrom
avengedsevenskull-ctrl:fix/2674-signatures-added-export

Conversation

@avengedsevenskull-ctrl

@avengedsevenskull-ctrl avengedsevenskull-ctrl commented Sep 30, 2026 •

Copy link
Copy Markdown

Summary

check <ref> --signatures flags an exported declaration only when a symbol of the same name existed at the base ref and its declaration line changed. Appends, renames, and newly-exported names are no longer treated as signature modifications.

Why

checkNoSignatureChanges flagged any exported declaration whose line fell inside a changed range. That produced two false-positive facets, both blocking additive work:

  1. Append — a brand-new export (absent at base) trips the predicate.
  2. Rename / promotion — a name that was never exported at base, sitting in a hunk that also removed other lines (e.g. register → registerPanel, plus a new subscribeComposition), trips it because the hunk has removed text.

In both cases no existing exported name changed, so no caller can break. Repro on v3.17.0: append export function brandNewThing(x) { return x; }, codegraph build . --no-incremental, codegraph check HEAD → [FAIL] signatures: brandNewThing, exit 1. Filed as #2674.

Scope snapshot

  • B0 = 427c96f (main); B1 = clean worktree
  • allowlist / actual B1→B2: src/features/check.ts, tests/integration/check.test.ts — match
  • Branch: avengedsevenskull-ctrl:fix/2674-signatures-added-export @ a8254a6

What changed

  • src/features/check.ts: checkNoSignatureChanges takes changedEdits (same new-file coordinate space as changedRanges) and only flags a declaration when the same name appears in one of the file's removed lines. With no edit runs for a file (no base-ref evidence) it falls back to the previous behaviour. Adds an escapeRegExp helper; runPredicates passes diff.changedEdits.
  • tests/integration/check.test.ts: regression tests for the pure-append case and the rename/new-export-in-shared-hunk case; the replaced-declaration case still flags.

Verification

Local test suite and build:

  • npx vitest run tests/integration/check.test.ts → 62/62 passed
  • tsc build clean; biome check src/ tests/ clean for the changed files (one pre-existing, unrelated warning at src/graph/algorithms/louvain.ts:135); commitlint passed

End-to-end with codegraph check HEAD --signatures on a local build of this branch:

Case Result
A. pure append of a new export [PASS] signatures (exit 0) — was FAIL
B. same-name signature edit [FAIL] signatures (exit 1) — real change still caught
C. rename + new export sharing a hunk with removals [PASS] signatures (exit 0) — previously flagged
D. same-name function foo → export function foo [FAIL] signatures — documented residual (see Limitation)

Case C is the reported facet: register → registerPanel plus a new subscribeComposition, in a hunk that also carries removed text.

Repository state

Risk & rollback

Low: predicate-only, no runtime path. Genuine same-name signature edits still flag (covered by the retained test). Rollback = revert these two commits.

Limitation: base-ref evidence is derived from the diff's removed lines rather than a full parse of the base file. A same-name private→public promotion (function foo → export function foo, case D) still flags. Truly matching "exported at base" by name would require parsing the base revision — happy to follow up on that if you prefer it over keeping the predicate synchronous.

Tracker

Refs #2674 (this PR does not close it)

…anges

`checkNoSignatureChanges` flagged any exported declaration whose line fell
inside a changed range, so appending a new export tripped `check <ref>
--signatures` even though the symbol did not exist at the base ref. That
contradicts the predicate's own contract ("Assert no exported
function/method/class declaration lines were modified"). Reproduced on
v3.17.0; filed as optave#2674.

Thread the already-parsed `ParsedDiff.changedEdits` into the predicate (same
new-file coordinate space and start/end as `changedRanges`). A run whose
`removedText` is empty is a pure insertion, so an exported symbol on it is
new at the base ref and cannot be a signature change.

- src/features/check.ts: add a `changedEdits` parameter, skip pure-insertion
  runs when matching a declaration, and pass `diff.changedEdits` from
  `runPredicates`
- tests/integration/check.test.ts: add a regression test for the added-export
  case and assert the replaced-declaration case still flags

Scope: B1 -> B2 = src/features/check.ts, tests/integration/check.test.ts (match)
Verification: npx vitest run tests/integration/check.test.ts -> 61/61 passed; tsc build clean
Refs optave#2674

Impact: 2 functions changed, 5 affected
@github-actions

Copy link
Copy Markdown
Contributor

Heads up: this PR references #2674 without a closing keyword (Closes #N / Fixes #N). If this PR fully resolves #2674, update the description so the issue auto-closes on merge — otherwise disregard this comment.

@github-actions

Copy link
Copy Markdown
Contributor


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

The first cut treated an added-only run as "new" but still flagged a current
export whose name was new whenever its hunk also contained removed text —
e.g. a rename (`register` -> `registerPanel`) or a newly exported helper
added beside a deletion. Those names were never exported at the base ref, so
they cannot break callers; it is the same false positive in its
rename/promotion facet.

Require base-ref evidence by name: a declaration is a violation only if the
same name appears in one of the file's removed lines. Appends, renames and
newly-exported names are skipped; genuine same-name signature edits still
flag. With no edit runs for a file the diff carries no base-ref evidence, so
the predicate falls back to flagging.

- src/features/check.ts: replace the pure-insertion check with a
  name-exists-in-removed-lines check; add an escapeRegExp helper
- tests/integration/check.test.ts: add the rename/new-export-in-shared-hunk
  regression test

Scope: B1 -> B2 = src/features/check.ts, tests/integration/check.test.ts (match)
Verification: npx vitest run tests/integration/check.test.ts -> 62/62 passed
Refs optave#2674

Impact: 2 functions changed, 5 affected
@avengedsevenskull-ctrl avengedsevenskull-ctrl changed the title fix(check): don't flag newly-added exported functions as signature changes fix(check): only flag modified exports whose name existed at the base ref Sep 30, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant