[mine-blindspots hyperthrive corpus] DeadReferencePointer skips leading-slash repo-relative paths (false negative) #162

Closed
opened 2026-07-27 15:07:11 +00:00 by jared · 2 comments
Owner

Context

Cross-project filing from a mine-blindspots run against the hyperthrive-websites repo as external corpus. Bucket (a): "rule exists, logic gap" — DeadReferencePointer/PathReferenceScanner false-negative (misses a dead reference), the mirror-image of the existing false-positive tickets #129/#150 on the same code.

Problem

hyperthrive-websites/docs/onboarding-tasks.md:5:

1. **Inventory the machine.** Scan Jared's other Claude Code project directories. Record in `/inventory/repos.md`: which are Astro, which are WordPress/Rails...

hyperthrive-websites/context/workflow/delivery.md:12:

- Swap checklist lives in `/delivery/swap-checklist.md`.

Neither /inventory/repos.md nor /delivery/swap-checklist.md exists anywhere in the hyperthrive-websites repo (confirmed: no inventory/ or delivery/ directory exists at repo root; onboarding-tasks.md:6 even documents that /inventory/ "this repo never grew"). Both are genuinely dead, repo-relative references written with a leading slash — but DeadReferencePointer never flags either.

Detection

plugins/os-aidd-lint/lib/aidd_lint/path_reference_scanner.rb:99-101:

def outside_repo?(match)
  match[0].start_with?("/") || match.pre_match.end_with?("~")
end

Any matched path starting with / is unconditionally treated as outside_repo? (an absolute filesystem path) and excluded from paths_in/paths_in_document before DeadReferencePointer ever gets a chance to check it against disk (comment at lines 91-93 states "this codebase's own convention never writes a repo-relative reference with a leading slash" — a convention this external corpus violates). The result: both dead references are silently invisible to the cop, identical in effect to a false negative on a path that should be checked and would fail.

Correction

Before classifying a leading-slash path as outside_repo?, attempt repo-root-relative resolution first (File.expand_path(path, root) treating the leading / as repo-root-relative, not filesystem-root-relative) and only fall back to the current unconditional exclusion if no enclosing repo root is found (e.g., scanning a file with no Config.repo_root_for result).

Pass/fail examples

  • Must fail (flag as dead): /inventory/repos.md referenced from a file inside a repo whose root contains no inventory/ directory.
  • Must pass (no flag, resolves): a leading-slash reference to a path that does exist relative to repo root, once repo-root-relative resolution is attempted.
  • Must still be excluded (genuinely outside repo): a path with no enclosing repo root at all (Config.repo_root_for returns nil) — falls back to current behavior.

Provenance

/os-aidd-lint:mine-blindspots run, external corpus hyperthrive-websites, bucket (a), cop citation path_reference_scanner.rb:99-101 (outside_repo?), evidence hyperthrive-websites/docs/onboarding-tasks.md:5 and hyperthrive-websites/context/workflow/delivery.md:12. Verified independently by a second agent pass against the live cop source and the live corpus files (confirmed neither /inventory/ nor /delivery/ exists in the repo). 2026-07-27. Related to but distinct from #129/#150 (false-positive classes on the same outside_repo?/resolution logic) — this is the false-negative mirror case and should be triaged alongside them since a single resolution-logic fix may address both directions.

Fix direction

Attempt repo-root-relative resolution for leading-slash paths before excluding them as outside_repo?, so a leading-slash reference that doesn't resolve from repo root is still flagged as dead rather than silently skipped.


Discoverer: hyperthrive-websites, session id unavailable, 2026-07-27. Filed from a mine-blindspots run against this repo as external corpus.

## Context Cross-project filing from a `mine-blindspots` run against the `hyperthrive-websites` repo as external corpus. Bucket (a): "rule exists, logic gap" — `DeadReferencePointer`/`PathReferenceScanner` false-negative (misses a dead reference), the mirror-image of the existing false-positive tickets #129/#150 on the same code. ### Problem `hyperthrive-websites/docs/onboarding-tasks.md:5`: ``` 1. **Inventory the machine.** Scan Jared's other Claude Code project directories. Record in `/inventory/repos.md`: which are Astro, which are WordPress/Rails... ``` `hyperthrive-websites/context/workflow/delivery.md:12`: ``` - Swap checklist lives in `/delivery/swap-checklist.md`. ``` Neither `/inventory/repos.md` nor `/delivery/swap-checklist.md` exists anywhere in the `hyperthrive-websites` repo (confirmed: no `inventory/` or `delivery/` directory exists at repo root; `onboarding-tasks.md:6` even documents that `/inventory/` "this repo never grew"). Both are genuinely dead, repo-relative references written with a leading slash — but `DeadReferencePointer` never flags either. ### Detection `plugins/os-aidd-lint/lib/aidd_lint/path_reference_scanner.rb:99-101`: ```ruby def outside_repo?(match) match[0].start_with?("/") || match.pre_match.end_with?("~") end ``` Any matched path starting with `/` is unconditionally treated as `outside_repo?` (an absolute filesystem path) and excluded from `paths_in`/`paths_in_document` before `DeadReferencePointer` ever gets a chance to check it against disk (comment at lines 91-93 states "this codebase's own convention never writes a repo-relative reference with a leading slash" — a convention this external corpus violates). The result: both dead references are silently invisible to the cop, identical in effect to a false negative on a path that should be checked and would fail. ### Correction Before classifying a leading-slash path as `outside_repo?`, attempt repo-root-relative resolution first (`File.expand_path(path, root)` treating the leading `/` as repo-root-relative, not filesystem-root-relative) and only fall back to the current unconditional exclusion if no enclosing repo root is found (e.g., scanning a file with no `Config.repo_root_for` result). ### Pass/fail examples - **Must fail (flag as dead):** `/inventory/repos.md` referenced from a file inside a repo whose root contains no `inventory/` directory. - **Must pass (no flag, resolves):** a leading-slash reference to a path that *does* exist relative to repo root, once repo-root-relative resolution is attempted. - **Must still be excluded (genuinely outside repo):** a path with no enclosing repo root at all (`Config.repo_root_for` returns nil) — falls back to current behavior. ### Provenance `/os-aidd-lint:mine-blindspots` run, external corpus `hyperthrive-websites`, bucket (a), cop citation `path_reference_scanner.rb:99-101` (`outside_repo?`), evidence `hyperthrive-websites/docs/onboarding-tasks.md:5` and `hyperthrive-websites/context/workflow/delivery.md:12`. Verified independently by a second agent pass against the live cop source and the live corpus files (confirmed neither `/inventory/` nor `/delivery/` exists in the repo). 2026-07-27. Related to but distinct from #129/#150 (false-positive classes on the same `outside_repo?`/resolution logic) — this is the false-negative mirror case and should be triaged alongside them since a single resolution-logic fix may address both directions. ### Fix direction Attempt repo-root-relative resolution for leading-slash paths before excluding them as `outside_repo?`, so a leading-slash reference that doesn't resolve from repo root is still flagged as dead rather than silently skipped. -------- **Discoverer:** hyperthrive-websites, session id unavailable, 2026-07-27. Filed from a mine-blindspots run against this repo as external corpus.
Author
Owner

Work started — branch fix/162-leading-slash-repo-relative-paths. Classified OBVIOUS. Fix shape: thread an optional from_file: through PathReferenceScanner.paths_in/paths_in_document so a leading-slash path is only surfaced when the referencing file has an enclosing repo root; candidate_paths then tries the path as written (absolute) and repo-root-relative (leading slash stripped). Default no-arg behavior is unchanged, so Tier-2 AuthoritySourceResolver is untouched by construction.

Work started — branch `fix/162-leading-slash-repo-relative-paths`. Classified OBVIOUS. Fix shape: thread an optional `from_file:` through `PathReferenceScanner.paths_in`/`paths_in_document` so a leading-slash path is only surfaced when the referencing file has an enclosing repo root; `candidate_paths` then tries the path as written (absolute) *and* repo-root-relative (leading slash stripped). Default no-arg behavior is unchanged, so Tier-2 AuthoritySourceResolver is untouched by construction.
Author
Owner

Green on branch fix/162-leading-slash-repo-relative-paths (commit bf02a35). Not closing — needs sign-off.

Fix. PathReferenceScanner.paths_in / paths_in_document now take an optional from_file:. Given one with an enclosing repo root, a leading-slash path is surfaced as checkable, and candidate_paths tries it two ways: as written (absolute) and repo-root-relative. The slash has to be stripped for the second candidate — File.expand_path ignores its base for an absolute path, so both candidates would otherwise collapse to the same string and root-relative resolution would silently never happen. DeadReferencePointer passes document.path; Tier 2's AuthoritySourceResolver calls the no-arg form and is unchanged by construction (verified: 0 authority sources resolved from leading-slash paths across the in-scope corpus). That answers the #129 blast-radius warning for this change.

One thing the ticket did not anticipate. A naive version of this fix flags 24 new offenses in cc-os, 21 of them false. PATH_PATTERN has no character class for ~, ${}, <> or *, so these surface only their /... tail:

  • ${CLAUDE_PLUGIN_ROOT}/workflows/build-plan.md -> /workflows/build-plan.md
  • skills/<role>/SKILL.md -> /SKILL.md
  • **/PRD.md -> /PRD.md

Same truncation class the existing ~ guard was written for. outside_repo? now only treats a leading-slash match as repo-root-relative when nothing of a path precedes it (start of span, whitespace, or an opening bracket).

Measured over the 99 in-scope files: 49 -> 50 offenses. The single addition is plugins/cc-architect/references/plugin-architecture-philosophy-review.md:9, pointing at /home/jared/dev/cc-plugins/cc-architect/references/plugin-architecture-philosophy.md — a real stale cross-repo absolute path, i.e. a #150-class residual (advisory severity, one instance). Flagged here so it is not later mistaken for a regression.

Ticket evidence re-verified against the live corpus. hyperthrive docs/onboarding-tasks.md has been edited since filing: line 5's /inventory/repos.md is gone, and context/workflow/delivery.md:12 now writes the path relative (already caught). The remaining must-fail case reproduces: onboarding-tasks.md:6 -> "points at /inventory/smartlead-api.md, which does not exist."

Tests: 6 new in tests/path_reference_scanner_test.rb, 3 in tests/cops/dead_reference_pointer_test.rb, covering all three ticket pass/fail cases plus the truncation regressions. Full plugin suite: 301 runs, 725 assertions, 0 failures, 0 errors, 0 skips.

Green on branch `fix/162-leading-slash-repo-relative-paths` (commit bf02a35). Not closing — needs sign-off. **Fix.** `PathReferenceScanner.paths_in` / `paths_in_document` now take an optional `from_file:`. Given one with an enclosing repo root, a leading-slash path is surfaced as checkable, and `candidate_paths` tries it two ways: as written (absolute) and repo-root-relative. The slash has to be stripped for the second candidate — `File.expand_path` ignores its base for an absolute path, so both candidates would otherwise collapse to the same string and root-relative resolution would silently never happen. `DeadReferencePointer` passes `document.path`; **Tier 2's `AuthoritySourceResolver` calls the no-arg form and is unchanged by construction** (verified: 0 authority sources resolved from leading-slash paths across the in-scope corpus). That answers the #129 blast-radius warning for this change. **One thing the ticket did not anticipate.** A naive version of this fix flags 24 new offenses in cc-os, 21 of them false. `PATH_PATTERN` has no character class for `~`, `${}`, `<>` or `*`, so these surface only their `/...` tail: - `${CLAUDE_PLUGIN_ROOT}/workflows/build-plan.md` -> `/workflows/build-plan.md` - `skills/<role>/SKILL.md` -> `/SKILL.md` - `**/PRD.md` -> `/PRD.md` Same truncation class the existing `~` guard was written for. `outside_repo?` now only treats a leading-slash match as repo-root-relative when nothing of a path precedes it (start of span, whitespace, or an opening bracket). **Measured over the 99 in-scope files: 49 -> 50 offenses.** The single addition is `plugins/cc-architect/references/plugin-architecture-philosophy-review.md:9`, pointing at `/home/jared/dev/cc-plugins/cc-architect/references/plugin-architecture-philosophy.md` — a real stale cross-repo absolute path, i.e. a #150-class residual (advisory severity, one instance). Flagged here so it is not later mistaken for a regression. **Ticket evidence re-verified against the live corpus.** hyperthrive `docs/onboarding-tasks.md` has been edited since filing: line 5's `/inventory/repos.md` is gone, and `context/workflow/delivery.md:12` now writes the path relative (already caught). The remaining must-fail case reproduces: `onboarding-tasks.md:6` -> "points at /inventory/smartlead-api.md, which does not exist." **Tests:** 6 new in `tests/path_reference_scanner_test.rb`, 3 in `tests/cops/dead_reference_pointer_test.rb`, covering all three ticket pass/fail cases plus the truncation regressions. Full plugin suite: `301 runs, 725 assertions, 0 failures, 0 errors, 0 skips`.
jared closed this issue 2026-07-27 17:05:57 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#162
No description provided.