Skip to content

fix(docs): the front door pointed at two docs that are not there, and the guard could not look there - #1807

Merged
lilyshen0722 merged 1 commit into
mainfrom
kai/claude-md-anchors
Sep 20, 2026
Merged

lilyshen0722 merged 1 commit into
mainfrom
kai/claude-md-anchors

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

What

CLAUDE.md — the file an agent is told to read first — carried two dead pointers in its Key Documentation list:

- **Discord Integration**:  /docs/DISCORD_INTEGRATION_ARCHITECTURE.md
- **PostgreSQL Migration**: /docs/POSTGRESQL_MIGRATION.md

Both files exist one level down. This is AX entry 57's shape: follow the pointer the front door gives you and find nothing.

docs/discord/DISCORD_INTEGRATION_ARCHITECTURE.md   exists
docs/database/POSTGRESQL_MIGRATION.md              exists

Why the guard from #1805 (TASK-087) did not catch them

Two independent reasons, both fixed here:

  1. Its scope was code only — scripts/, backend/, cli/, frontend/ — so a pointer in CLAUDE.md was outside it. The repo-root CLAUDE.md is now scanned. AGENTS.md is a symlink to it (git mode 120000), so scanning the target covers both without reporting each dead line twice.
  2. DOC_REF's boundary (?<![A-Za-z0-9_/.-]) rejected a leading slash, which is the form that section writes every pointer in. \/? is now optional in front of docs/, and extractDocReferences strips it before resolving. A URL tail is still excluded — the lookbehind fails at both positions it could match at.

The markdown scope stops at the root file: docs/ prose is still the docs room's inventory, as the guard's own comment says.

Proof

Mutations, one mechanism at a time, against the real harness (jest, backend/):

mutation result
M1 the dead anchor is back in CLAUDE.md RED — end-to-end catch by "every referenced doc exists"
M2 CLAUDE.md is no longer scanned RED — "covers the front-door file"
M3 the boundary rejects a leading slash again RED — root-relative extraction + positive control
M4 the leading slash is not stripped before resolving RED — same two tests

Restored: rc=0, 5/5 tests, Tests: 5 passed, 5 total.

Both dead-literal forms are kept out of the test file itself — the guard scans its own test — so the absent-path fixture is assembled from parts and the extraction fixture uses a doc that exists.

Scope

Docs + test only: no version slot, no cli/common-mcp source touched.

@samxu01
samxu01 force-pushed the kai/claude-md-anchors branch from 93d0fdb to b5cbe80 Compare September 20, 2026 04:11
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Refreshed onto 645e0850 (this branch was BEHIND after #1806, and strict: true makes that unmergeable — mergeStateStatus=BEHIND was the truth, so a plain merge was not available). Branch owner takes the refresh.

Head 93d0fdb7 → b5cbe80a, one commit, byte-identical:

$ git range-diff b7f45b40..93d0fdb7 645e0850..b5cbe80a
1:  93d0fdb7 = 1:  b5cbe80a fix(docs): the front door pointed at two docs that are not there…

$ git patch-id --stable   # before / after
2d8a6a0a12de1d804ec232682454f348cb2e5c94
2d8a6a0a12de1d804ec232682454f348cb2e5c94

Same patch-id, same files, Lily on the noreply address only, 5/5 at the new head — so both stamps carry by patch-id, the way they did on #1806.


@vera's residual (70645) — accepted, and deliberately deferred. She is right that "AGENTS.md is a symlink so the target covers both" is a convention the comment describes and nothing checks: replace it with a real copy and AGENTS.md leaves scope with every test still green. The line is written and proved, but not in this PR — adding a commit here would invalidate two stamps that are about to be carried, and the press is authorised on this head.

Ready to land immediately after this merges: branch kai/guard-agents-symlink @ 350ab0e0, cut from b5cbe80a, which adds

it('AGENTS.md is a symlink to the scanned file, not a second copy', () => {
  expect(fs.lstatSync(path.join(REPO_ROOT, 'AGENTS.md')).isSymbolicLink()).toBe(true);
});

and names her second finding in the same comment: a parent-relative ../docs/….md is rejected by the lookbehind, zero instances in the scanned dirs, kept out because its target depends on the referencing file's directory, which this scan does not model. Mutations there: replace AGENTS.md with a real copy of CLAUDE.md → RED (the real event, and the only test that notices), invert the assertion → RED, restored 6/6.

… the guard could not look there

CLAUDE.md — the file an agent is told to read first — carried two dead
pointers in its Key Documentation list:

  - **Discord Integration**:  /docs/DISCORD_INTEGRATION_ARCHITECTURE.md
  - **PostgreSQL Migration**: /docs/POSTGRESQL_MIGRATION.md

Both files exist one level down (docs/discord/, docs/database/). This is
AX entry 57's shape: follow the pointer the front door gives you and find
nothing.

The guard that closed this class (#1805, TASK-087) could not see them,
for two independent reasons, and both are fixed here:

  1. its scope was code only — scripts/, backend/, cli/, frontend/ — so a
     pointer in CLAUDE.md was outside it. The repo-root CLAUDE.md is now
     scanned (AGENTS.md is a symlink to it, git mode 120000, so the
     target covers both without double-reporting each dead line).
  2. DOC_REF's boundary `(?<![A-Za-z0-9_/.-])` rejected a leading slash,
     which is the form that section writes every pointer in. `\/?` is now
     optional in front of `docs/`, and extractDocReferences strips it
     before resolving — a URL tail is still excluded, because the
     lookbehind fails on both positions it could match at.

The markdown scope stops at the root file: docs/ prose is still the docs
room's inventory, as the guard's own comment says.

Mutations (each new mechanism, one at a time, run against the real
harness):

  M1 the dead anchor is back in CLAUDE.md            RED  (end-to-end catch)
  M2 CLAUDE.md is no longer scanned                  RED
  M3 the boundary rejects a leading slash again      RED
  M4 the leading slash is not stripped before resolve RED
  restored, clean run rc=0, 5/5

Both dead-literal forms are kept out of the test file itself: the guard
scans its own test, so the absent-path fixture is assembled from parts
and the extraction fixture uses a doc that exists.
@samxu01
samxu01 force-pushed the kai/claude-md-anchors branch from b5cbe80 to d6750fc Compare September 20, 2026 04:20
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Refreshed onto 34198cf0 (#1808 landed, so this branch was BEHIND again — same required-refresh situation as the first move, and the same instrument).

Head b5cbe80a → d6750fc0, one commit, byte-identical patch set:

$ git range-diff 645e0850..b5cbe80a origin/main..d6750fc0
1:  b5cbe80a = 1:  d6750fc0 fix(docs): the front door pointed at two docs that are not there…

$ git patch-id --stable   # before / after
2d8a6a0a12de1d804ec232682454f348cb2e5c94  b5cbe80a
2d8a6a0a12de1d804ec232682454f348cb2e5c94  d6750fc0

Files unchanged (CLAUDE.md + backend/__tests__/unit/scripts/docReferences.test.js, +51/−12), Lily on the noreply address, 5/5 at the new head — so the stamp at b5cbe80a carries by patch-id.

@lilyshen0722
lilyshen0722 merged commit 2223a0f into main Sep 20, 2026
14 checks passed
samxu01 pushed a commit that referenced this pull request Sep 20, 2026
…nds on (vera 70645)

The scan names CLAUDE.md, and AGENTS.md is covered only because git mode
120000 makes it the same file. That is a convention the comment described and
nothing checked: replace the symlink with a real copy and AGENTS.md leaves
scope with every test still green.

Also names, in the same comment, the other form the boundary rejects — a
parent-relative `../docs/….md` — as a known blind spot rather than leaving it
to be discovered; its target depends on the referencing file's directory,
which this scan does not model, and there are zero instances in the scanned
dirs.

Mutations: replace AGENTS.md with a real copy of CLAUDE.md -> RED (the real
event, and the only test that notices); invert the assertion -> RED. Restored
clean, 6/6.

Held as a follow-up rather than folded into #1807: that head carries two
reviewers' stamps and the press is authorised on it.
samxu01 pushed a commit that referenced this pull request Sep 20, 2026
…nds on (vera 70645, wren 70661)

The scan names CLAUDE.md, and AGENTS.md is covered only because git mode
120000 makes it the same file. That is a convention the comment described and
nothing checked.

Two events take AGENTS.md out of scope, and both are asserted because
`isSymbolicLink()` alone only catches the first:

  - replaced by a real copy    -> not a symlink at all
  - retargeted (`ln -s README.md`) -> still a symlink, pointing somewhere the
    scan does not follow, so `isSymbolicLink()` passes while AGENTS.md has left
    scope. The target is now resolved against the link's own directory and must
    be one of SCANNED_ROOT_FILES, derived from that constant rather than
    hard-coded, so the title and the assertion cannot drift apart.

Also names, in the same comment, the other form the boundary rejects — a
parent-relative `../docs/….md` — as a known blind spot rather than leaving it
to be discovered; its target depends on the referencing file's directory,
which this scan does not model, and there are zero instances in the scanned
dirs.

Mutations: replace AGENTS.md with a real copy -> RED; retarget it to README.md
-> RED (wren's case, and the one the first version of this test let through);
invert the symlink assertion -> RED. Restored clean, 6/6.

Disclosed survivor: loosening the target assertion to `target.length > 0`
survives, because removing a check is undetectable on a tree whose link target
is correct — the retarget mutation is what supplies the failing input, and it
is the case that goes RED.

Held as a follow-up rather than folded into #1807: that head carried two
reviewers' stamps and the press was authorised on it.
samxu01 pushed a commit that referenced this pull request Sep 20, 2026
…nds on (vera 70645, wren 70661)

The scan names CLAUDE.md, and AGENTS.md is covered only because git mode
120000 makes it the same file. That is a convention the comment described and
nothing checked.

Two events take AGENTS.md out of scope, and both are asserted because
`isSymbolicLink()` alone only catches the first:

  - replaced by a real copy    -> not a symlink at all
  - retargeted (`ln -s README.md`) -> still a symlink, pointing somewhere the
    scan does not follow, so `isSymbolicLink()` passes while AGENTS.md has left
    scope. The target is now resolved against the link's own directory and must
    be one of SCANNED_ROOT_FILES, derived from that constant rather than
    hard-coded, so the title and the assertion cannot drift apart.

Also names, in the same comment, the other form the boundary rejects — a
parent-relative `../docs/….md` — as a known blind spot rather than leaving it
to be discovered; its target depends on the referencing file's directory,
which this scan does not model, and there are zero instances in the scanned
dirs.

Mutations: replace AGENTS.md with a real copy -> RED; retarget it to README.md
-> RED (wren's case, and the one the first version of this test let through);
invert the symlink assertion -> RED. Restored clean, 6/6.

Disclosed survivor: loosening the target assertion to `target.length > 0`
survives, because removing a check is undetectable on a tree whose link target
is correct — the retarget mutation is what supplies the failing input, and it
is the case that goes RED.

Held as a follow-up rather than folded into #1807: that head carried two
reviewers' stamps and the press was authorised on it.
samxu01 pushed a commit that referenced this pull request Sep 20, 2026
…nds on (vera 70645, wren 70661)

The scan names CLAUDE.md, and AGENTS.md is covered only because git mode
120000 makes it the same file. That is a convention the comment described and
nothing checked.

Two events take AGENTS.md out of scope, and both are asserted because
`isSymbolicLink()` alone only catches the first:

  - replaced by a real copy    -> not a symlink at all
  - retargeted (`ln -s README.md`) -> still a symlink, pointing somewhere the
    scan does not follow, so `isSymbolicLink()` passes while AGENTS.md has left
    scope. The target is now resolved against the link's own directory and must
    be one of SCANNED_ROOT_FILES, derived from that constant rather than
    hard-coded, so the title and the assertion cannot drift apart.

Also names, in the same comment, the other form the boundary rejects — a
parent-relative `../docs/….md` — as a known blind spot rather than leaving it
to be discovered; its target depends on the referencing file's directory,
which this scan does not model, and there are zero instances in the scanned
dirs.

Mutations: replace AGENTS.md with a real copy -> RED; retarget it to README.md
-> RED (wren's case, and the one the first version of this test let through);
invert the symlink assertion -> RED. Restored clean, 6/6.

Disclosed survivor: loosening the target assertion to `target.length > 0`
survives, because removing a check is undetectable on a tree whose link target
is correct — the retarget mutation is what supplies the failing input, and it
is the case that goes RED.

Held as a follow-up rather than folded into #1807: that head carried two
reviewers' stamps and the press was authorised on it.
lilyshen0722 added a commit that referenced this pull request Sep 20, 2026
…nds on (vera 70645, wren 70661)

The scan names CLAUDE.md, and AGENTS.md is covered only because git mode
120000 makes it the same file. That is a convention the comment described and
nothing checked.

Two events take AGENTS.md out of scope, and both are asserted because
`isSymbolicLink()` alone only catches the first:

  - replaced by a real copy    -> not a symlink at all
  - retargeted (`ln -s README.md`) -> still a symlink, pointing somewhere the
    scan does not follow, so `isSymbolicLink()` passes while AGENTS.md has left
    scope. The target is now resolved against the link's own directory and must
    be one of SCANNED_ROOT_FILES, derived from that constant rather than
    hard-coded, so the title and the assertion cannot drift apart.

Also names, in the same comment, the other form the boundary rejects — a
parent-relative `../docs/….md` — as a known blind spot rather than leaving it
to be discovered; its target depends on the referencing file's directory,
which this scan does not model, and there are zero instances in the scanned
dirs.

Mutations: replace AGENTS.md with a real copy -> RED; retarget it to README.md
-> RED (wren's case, and the one the first version of this test let through);
invert the symlink assertion -> RED. Restored clean, 6/6.

Disclosed survivor: loosening the target assertion to `target.length > 0`
survives, because removing a check is undetectable on a tree whose link target
is correct — the retarget mutation is what supplies the failing input, and it
is the case that goes RED.

Held as a follow-up rather than folded into #1807: that head carried two
reviewers' stamps and the press was authorised on it.
lilyshen0722 added a commit that referenced this pull request Sep 20, 2026
…nds on (vera 70645, wren 70661) (#1809)

The scan names CLAUDE.md, and AGENTS.md is covered only because git mode
120000 makes it the same file. That is a convention the comment described and
nothing checked.

Two events take AGENTS.md out of scope, and both are asserted because
`isSymbolicLink()` alone only catches the first:

  - replaced by a real copy    -> not a symlink at all
  - retargeted (`ln -s README.md`) -> still a symlink, pointing somewhere the
    scan does not follow, so `isSymbolicLink()` passes while AGENTS.md has left
    scope. The target is now resolved against the link's own directory and must
    be one of SCANNED_ROOT_FILES, derived from that constant rather than
    hard-coded, so the title and the assertion cannot drift apart.

Also names, in the same comment, the other form the boundary rejects — a
parent-relative `../docs/….md` — as a known blind spot rather than leaving it
to be discovered; its target depends on the referencing file's directory,
which this scan does not model, and there are zero instances in the scanned
dirs.

Mutations: replace AGENTS.md with a real copy -> RED; retarget it to README.md
-> RED (wren's case, and the one the first version of this test let through);
invert the symlink assertion -> RED. Restored clean, 6/6.

Disclosed survivor: loosening the target assertion to `target.length > 0`
survives, because removing a check is undetectable on a tree whose link target
is correct — the retarget mutation is what supplies the failing input, and it
is the case that goes RED.

Held as a follow-up rather than folded into #1807: that head carried two
reviewers' stamps and the press was authorised on it.
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