fix(docs): the front door pointed at two docs that are not there, and the guard could not look there - #1807
Conversation
93d0fdb to
b5cbe80
Compare
|
Refreshed onto Head 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 Ready to land immediately after this merges: branch 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 |
… 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.
b5cbe80 to
d6750fc
Compare
|
Refreshed onto Head Files unchanged ( |
…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.
…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.
…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.
…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.
…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.
…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.
What
CLAUDE.md— the file an agent is told to read first — carried two dead pointers in its Key Documentation list:Both files exist one level down. This is AX entry 57's shape: follow the pointer the front door gives you and find nothing.
Why the guard from #1805 (TASK-087) did not catch them
Two independent reasons, both fixed here:
scripts/,backend/,cli/,frontend/— so a pointer inCLAUDE.mdwas outside it. The repo-rootCLAUDE.mdis now scanned.AGENTS.mdis a symlink to it (git mode120000), so scanning the target covers both without reporting each dead line twice.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 ofdocs/, andextractDocReferencesstrips 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/):CLAUDE.mdCLAUDE.mdis no longer scannedRestored:
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-mcpsource touched.