SIGN IN SIGN UP

fix(plan): open docs linked beside the plan file (#1620)

* fix(plan): open documents linked beside the plan file

Claude Code's ExitPlanMode input carries planFilePath under
~/.claude/plans/, outside the review root, so a relative link to a
sibling document returned 404. The plan server now trusts that path
when the file holds the plan under review, and /api/doc serves the
documents the plan names from its directory. The rest of the
directory stays unreachable.

Closes #1443

* fix(plan): serve only exact plan link targets beside the plan

planNamesPath matched the requested path as a substring of the plan
text, so a link to evidence.md unlocked e.md and [[notes-extended]]
unlocked notes.md, reaching other files in ~/.claude/plans/. The plan
directory now serves only paths equal to one of the plan's link
targets (markdown links, wiki links, HTML href), normalized the same
way on both sides (no fragment or query, percent-decoded, ./ collapsed).

* fix(plan): keep the project-root fallback when the base is the plan directory

The client sends the plan directory as base on every open. When that
directory sits inside the project it was trusted for root resolution,
so HTML links resolved only against it and lost the project-root
fallback. The plan-directory base now feeds only the plan-linked lookup;
root resolution runs without a base, as before. Documents that a linked
file beside the plan shadows a project doc of the same name.

* fix(plan): serve the normalized link target, never the raw request

resolvePlanLinkedDoc matched the normalized key but joined the raw
request onto the plan directory, so a ?/.. or #/.. suffix after a linked
name (linked.md?/../other-plan.md) collapsed to a sibling the plan never
linked. Every check and the path join now read the normalized key, which
also makes a percent-encoded link (my%20doc.md) open the decoded file.
Links written inside fenced code or code spans no longer count as
targets.

* perf(plan): compute plan link targets once, in linear time

The fence-stripping regex backtracked quadratically on a long run of
backticks or tildes (50k chars took ~0.9s), as did the wiki-link
pattern on a run of [[ openers, and targets were recomputed on every
plan-directory /api/doc request, blocking the event loop. Code is now
stripped by a line scan, the wiki and href patterns stop at a newline
or the next opener, and readPlanFile stores the targets on PlanFile.

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
B
Ben Drucker committed
3723ce25b41904c55b1ed4e03f7f3655d31a18f3
Parent: 31a0078
Committed by GitHub <noreply@github.com> on 9/27/2026, 6:19:25 PM