Skip to content

fix: normalize paths before deduping intent plan steps (#41) - #74

Merged
bigknoxy merged 2 commits into
mainfrom
fix/issue-41-path-normalization-rebased
Aug 17, 2026
Merged

fix: normalize paths before deduping intent plan steps (#41)#74
bigknoxy merged 2 commits into
mainfrom
fix/issue-41-path-normalization-rebased

Conversation

@bigknoxy

Copy link
Copy Markdown
Owner

Rebuilt replacement for #71, which could not be merged. Closes #41.

The bug

generatePlan deduped reference files by raw string. The same file reached as src/a.ts, ./src/a.ts, and /abs/proj/src/a.ts produced one plan step per spelling. Worse, a reference to the definition file spelled differently from the definition escaped the !== definition.file filter and got renamed a second time.

Adds src/core/path-normalize.ts (normalizePath, pathsEqual) and routes both refFiles computations in generatePlan through it.

Why this replaces #71 rather than updating it

#71 introduced these helpers as src/core/paths.ts. That branch was cut at fb1172e, before paths.ts existed on main; main then added it in #54 and #65 as the write-boundary moduleassertWritable, safeWrite, atomicWrite, findProjectRoot, PathDeniedError, and the deny-list that no flag overrides (~/.ssh, ~/.aws, /etc, shell rc files, the telemetry log).

Seven modules import it: router.ts, hash-edit.ts, diff-engine.ts, locking.ts, plan-executor.ts, verify.ts, index.ts. #71 arrived as new file mode 100644 at that path, so landing it as authored would have deleted every write guard in the codebase. That is the source of its CONFLICTING status, and a plausible conflict resolution ("take theirs") silently produces exactly that outcome.

git rebase origin/main on #71 hits an add/add conflict on paths.ts where resolving in commit order renames main's write boundary. Rebuilding on current main was the safer path. The helpers now live in a separate module so the two concerns cannot collide again.

Also dropped from #71

  • The remove-parameter dedupe test — main refuses that operation outright (UnsupportedIntentError, fix(intent): report unresolved plan steps instead of injecting placeholder comments #66), so the test could not run.
  • An unused relative import, unused beforeEach/afterEach imports, an unused cwd local.
  • The undefined-only guard in pathsEqual, which made the relation inconsistent: (null, undefined) compared false while (null, null) and (null, "") compared true. normalizePath already collapses every nullish and blank form to "", so the guard was redundant and wrong.

Added coverage

The two non-trivial branches were untested in #71. Now covered: cwd itself, paths outside cwd staying absolute, a sibling directory sharing a cwd prefix not being mistaken for a child, and the full nullish/blank equality matrix.

Verification

Mutation-tested: reverting either normalization call site fails both new intent tests. The suite is 614 pass / 0 fail; bun run lint:docs is green.

Known constraint (documented, not changed)

normalizePath returns cwd-relative for paths under cwd and absolute otherwise, so its output depends on process.cwd(). That is fine for this use — deduping within a single generatePlan call — but makes it unsafe as a persisted key. locking.ts is deliberately cwd-independent for that reason. Recorded in the CLAUDE.md module map so the distinction survives.

🤖 Generated with Claude Code

The intent planner deduped reference files by raw string, so the same file
reached as "src/a.ts", "./src/a.ts", and "/abs/proj/src/a.ts" produced one
plan step per spelling, and a reference to the definition file spelled
differently from the definition escaped the `!== definition.file` filter and
got renamed a second time.

Adds `src/core/path-normalize.ts` with `normalizePath` and `pathsEqual`, and
routes the two `refFiles` computations in `generatePlan` through them.

Rebuilt on current main rather than merged. The original branch introduced
these helpers as `src/core/paths.ts`, which is now the write-boundary module
(assertWritable, safeWrite, atomicWrite, findProjectRoot, PathDeniedError and
the unoverridable deny-list) imported by seven modules; landing it as authored
would have deleted every write guard in the codebase. The helpers live in a
separate module so the two concerns cannot collide again.

Also dropped from the original branch:
- The remove-parameter dedupe test. Main refuses remove-parameter outright
  (UnsupportedIntentError, #66), so the test could not run.
- An unused `relative` import, unused `beforeEach`/`afterEach` imports, and an
  unused `cwd` local.
- The undefined-only guard in `pathsEqual`, which made the relation
  inconsistent: (null, undefined) compared false while (null, null) and
  (null, "") compared true. `normalizePath` already collapses every nullish
  and blank form to "", so the guard was both redundant and wrong.

Adds coverage for the previously untested branches: cwd itself, paths outside
cwd staying absolute, a sibling directory sharing a cwd prefix not being
mistaken for a child, and the full nullish/blank equality matrix.

Verified by mutation: reverting either normalization call site fails the two
new intent tests. Full suite 614 pass / 0 fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
New core module from #41 was missing from the architecture doc, which the
Docs Verify gate caught. Records the cwd-relative constraint that makes it
unsuitable as a lock key, alongside the locking.ts section that depends on
the opposite property.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bigknoxy
bigknoxy merged commit 01a97f0 into main Aug 17, 2026
4 checks passed
@bigknoxy
bigknoxy deleted the fix/issue-41-path-normalization-rebased branch August 17, 2026 19:32
bigknoxy pushed a commit that referenced this pull request Aug 17, 2026
## [4.0.6](v4.0.5...v4.0.6) (2026-08-17)

### Bug Fixes

* normalize paths before deduping intent plan steps ([#41](#41)) ([#74](#74)) ([01a97f0](01a97f0)), closes [#66](#66)
@bigknoxy

Copy link
Copy Markdown
Owner Author

🎉 This PR is included in version 4.0.6 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P3] Path comparisons are unnormalized — provenance silently returns no results

1 participant