Skip to content

fix(core,metrics): renames follow the file — no phantom old => new path, lifecycle carried across the move - #104

Merged
ceccode merged 1 commit into
mainfrom
fix/renames-follow-the-file
Oct 1, 2026
Merged

ceccode merged 1 commit into
mainfrom
fix/renames-follow-the-file

Conversation

@ceccode

@ceccode ceccode commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Found by an external review (18 September) and reproduced before touching anything. It flatters, and by a lot. old.ts → git mv new.ts → edit new.ts produced three paths in the stream — old.ts, old.ts => new.ts and new.ts — with the rename filed as a modification of a file that never existed. Every rename in a repository was one more eligible file nothing could ever retouch, and the real file's later edits landed under a name its first touch had never been recorded against.

Reproduction (fixture)

Case Before After
old.ts (1 Jan) → git mv new.ts (2 Jan) → edit new.ts (4 Jan), 7-day horizon 0/3, 0% — three "files", none retouched 1/1, 100% — one file, retouched on day 3

What changed

  • core — git log --numstat names a renamed file as old => new or dir/{old => new}/file (on react-router: 1,911 brace-form, 45 plain-form). splitRenamePath parses both forms, including a brace group at the start of the path and one with an empty side. The stream records the file under its new name with previousPath and status renamed (additive field, no schema bump). git show in PR scope gets the same parsing.
  • metrics — persistence is one chronological pass; a lifecycle follows the file across a rename. A pure rename (no lines changed) is not a touch: nothing about the code happened, only its address. A rename that also edits is one. Hotfix antecedents carry across the move the same way.

Dogfood: six repositories, same HEAD, before → after (30-day horizon, full history)

Repo Renames Retouched Eligible Rate Trend headline
react-router 1,956 3,212 → 2,730 7,560 → 4,639 42.5% → 58.9% 27.4→48.5% becomes 27.9→49.7%
aspire many 7,695 → 6,920 19,166 → 13,929 40.2% → 49.7% 48.7→33.0% becomes 49.6→33.0%
tailwindcss 144 commits 1,381 → 1,080 3,672 → 2,377 37.6% → 45.4% unchanged
commander.js 14 commits 100 → 99 408 → 365 24.5% → 27.1% unchanged
evidtrail few 110 → 111 161 → 160 68.3% → 69.4% unchanged
varano-239 none unchanged unchanged 64.3% —

The direction is the same everywhere: eligible files fall (the phantom old => new entries and the duplicate lifecycles under the new name disappear), retouches move slightly, and the rate rises — on rename-heavy repositories by eight to sixteen points. The phantoms had been padding the denominator with files nothing could touch. A repository with no renames does not move at all.

Tests

  • paths.test.ts: plain form, brace form with prefix/suffix, brace group at the start, empty-side brace without a double slash, an arrow that is not a rename.
  • collect.test.ts: a real git mv yields one file under the new name with previousPath, and no phantom path anywhere in the stream.
  • persistence.test.ts: the reviewer's case (0/3 → 1/1); a pure rename is not a touch and opens no lifecycle; a rename with edits is a touch.
  • outcome-correlation.test.ts: a hotfix on the new name links to the last touch under the old one.

pnpm build / typecheck / lint green; tests 307 → 317.

@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

evidtrail ✅ 1 commit — agent 1 — every commit in this change set carries provenance.

Details — scope, provenance, limits

Scope: base..HEAD @ e6eebf29d703 — 11 files touched. A change-set view, not repository history or deployed state; time-based signals (rapid retouch, trend) are omitted because a fresh PR has not had a comparable observation window.

Evidence: 100% coverage — declared 1 · inferred 0 · none 0.

Autonomy level Commits
agent 1

Limits

  • Evidence coverage is 100.0%: a missing signal remains unknown; it is not evidence of human authorship and a defaultMode prior does not turn it into observed provenance.
  • Commit scope is pr at e6eebf2: only commits in base..HEAD are included. This is a change-set view, not repository history, merge status, or deployed production state.
  • Rapid-retouch rates and trends are omitted from the PR report because a fresh change set has not had a comparable observation window.
  • AI tagging uses declarations and conservative heuristics; tool use that leaves no commit evidence cannot be recovered from git alone.

…path, lifecycle carried across the move

Found by an external review and reproduced. `old.ts` → `git mv new.ts` →
edit `new.ts` produced THREE paths in the stream: `old.ts`,
`old.ts => new.ts` and `new.ts`, the rename filed as a modification of a
file that never existed. Every rename was one more eligible file nothing
could retouch, and the real file's edits landed under a name its first
touch was never recorded against. Rate biased down: it flatters.

- core: `git log --numstat` names a renamed file as `old => new` or
  `dir/{old => new}/file` (1,911 brace-form and 45 plain-form on
  react-router). `splitRenamePath` parses both; the stream records the
  file under its new name with `previousPath` and status `renamed`
  (additive field, no schema bump). `git show` in PR scope gets the same.
- metrics: persistence is one chronological pass; a lifecycle follows the
  file across a rename. A pure rename (0/0) is not a touch — only the
  address changed. A rename with edits is. Hotfix antecedents move with
  the file the same way.

Fixture: 0/3 (0%) → 1/1 (100%).

AI-Mode: agent
@ceccode
ceccode force-pushed the fix/renames-follow-the-file branch from 837453c to 6e82189 Compare October 1, 2026 06:50
@ceccode
ceccode merged commit 0987339 into main Oct 1, 2026
3 checks passed
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