fix(links): a directory link is not a missing file, so a tracked dir now resolves

`checkInternalLinks` compared a link's resolved target only against `present`
(tracked files), so `[x](dir/)` was always LINK-INTERNAL-MISSING even when
every file under it was tracked. Reported by portfolio-optimiser-claude with a
minimal repro; the same defect inflated ERROR counts in voyage, linkedin-studio
and portfolio-optimiser — 12 of the org's 71 measured ERRORs were this one
check, not twelve repo problems.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uwwfdmrfnp7FuGQ4z25RKH
This commit is contained in:
Kjell Tore Guttormsen 2026-08-03 21:57:53 +02:00
commit 3c2a535297
7 changed files with 50 additions and 5 deletions

View file

@ -637,6 +637,26 @@ test('a genuinely missing sibling file is still an ERROR', () => {
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING' && x.level === 'ERROR'), true);
});
// Reported by portfolio-optimiser-claude (coord 20260803T194933Z): `present` is
// the set of tracked FILES, so a directory link never has a member to match,
// even when every file under it is tracked. `have` never contained a directory
// to begin with — the fix derives one from `present`, it does not loosen it.
test('a link to a directory that is genuinely tracked resolves', () => {
const f = checkInternalLinks({
files: { 'README.md': 'See [dir](runs/s10/) and [file](runs/s10/a.json).' },
present: ['README.md', 'runs/s10/a.json'],
});
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING'), false);
});
test('a link to a directory with no tracked files under it is still an ERROR', () => {
const f = checkInternalLinks({
files: { 'README.md': '[ghost](docs/ghost/)' },
present: ['README.md'],
});
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING' && x.level === 'ERROR'), true);
});
test('a required heading present at the wrong level says so', () => {
// llm-security has `### Install` nested under `## Quick Start`. The contract
// wants it at level 2 — the finding should name that, not just "missing".