Skip to content

check-links: verify relative Markdown links resolve to real files - #14

Closed
sergiobuilds wants to merge 2 commits into
LilMGenius:mainfrom
sergiobuilds:pr-c-linkcheck
Closed

check-links: verify relative Markdown links resolve to real files#14
sergiobuilds wants to merge 2 commits into
LilMGenius:mainfrom
sergiobuilds:pr-c-linkcheck

Conversation

@sergiobuilds

Copy link
Copy Markdown
Contributor

New `scripts/check-links.sh` — catches the second most common drift shape after skill renames: a relative link left behind when a file is moved or renamed.

Scope

  • Every `SKILL.md`, every `docs/.md`, every top-level `.md`.
  • Relative link targets only. External (http, https), protocol-relative, mailto, tel, and in-page anchors are out of scope by design — a broken external is a live concern, not a catalog concern.
  • Fragment and query strings are stripped before resolving; a link to a section still counts as a link to a file.

Verification

```
$ bash scripts/check-links.sh
✓ relative links resolve (scanned 30 files)
```

Empirically clean on the current catalog.

Pairs with the other checks

  • `validate-skills.sh` — catalog shape per skill (frontmatter, sections, path registration).
  • `check-skill-refs.sh` — cross-skill vocabulary (typos and orphans between skills).
  • `check-links.sh` (this PR) — path resolution between docs on disk.

The three together cover: the shape of each skill, the resolution of names between them, and the resolution of paths between docs.

CI wiring — one line

Not wired into CI in this PR (OAuth workflow scope). Suggested addition to `.github/workflows/ci.yml`:

```yaml

  • name: Verify relative links resolve
    run: bash scripts/check-links.sh
    ```

New scripts/check-links.sh — catches the second most common drift shape after
skill renames: a relative link left behind when a file is moved or renamed.

Scope:

- Every SKILL.md, every docs/*.md, every top-level *.md.
- Relative link targets only. External (http, https), protocol-relative,
  mailto, tel, and in-page anchors are out of scope by design.
- Fragment and query strings are stripped before resolving; a link to a
  section still counts as a link to a file.

Empirically clean on the current catalog: 30 scanned files, 0 broken links.

Pairs with check-skill-refs.sh (typos and orphans) and validate-skills.sh
(catalog shape). The three together cover: the shape of each skill, the
resolution of names between them, and the resolution of paths between docs.
@LilMGenius LilMGenius reopened this Jul 14, 2026
Three defects called out in cold-read:

1. `grep | sed | while read` piped the loop body into a subshell, so `fail=1`
   set by `err` never made it back to the final exit-code decision. Switch to
   `while read ... done < <(...)` so the loop stays in the current shell and
   `fail` propagates. `count` also stops being lost.

2. The `[text](target)` scan matched `![alt](img.png)` too, so a missing image
   would be flagged as a broken link. Strip well-formed `![alt](path)` from a
   working buffer before the inline scan.

3. Reference links (`[text][ref]` with `[ref]: target`) were not scanned at
   all. Build a per-file `ref → target` map from `[ref]: target` definitions
   and resolve each `[text][ref]` usage against it; unresolved references get
   flagged with a clear "no matching definition" message.

Also guards the empty-files-array case so it fails fast instead of returning
a green success with zero occurrences, and documents the intentionally
unsupported edge cases (collapsed refs, `)` in inline targets, quoted-target
definitions with whitespace) in the header comment.

Verified locally across seven scenarios: clean repo, broken inline link,
missing image (must be ignored), broken reference target, undefined reference
name, anchor-only link, and full restore — exit codes match the intent in
every case.
@sergiobuilds

Copy link
Copy Markdown
Contributor Author

Cold-read flagged three defects: subshell bug swallowed failures, image links were treated as inline links, and reference-form links were not scanned at all. Pushed fix (c236dde):

  • inline loop now runs in the current shell via while read ... done < <(...) so fail propagates and count is not lost.
  • well-formed ![alt](path) is stripped from a working buffer before the inline scan, so a missing image no longer counts as a broken link.
  • reference links are supported: a per-file [ref]: target map is built and every [text][ref] is resolved against it. Undefined references get a clear "no matching definition" message.

Also guards empty-files-array (fails fast instead of green-with-zero) and documents the intentionally unsupported edge cases (collapsed refs, ) in inline targets, quoted-target definitions with whitespace) in the header.

Local verification, seven scenarios:

scenario expected actual
clean repo exit 0 ✓ (565 link occurrences, 30 files)
broken inline [nope](./nowhere.md) exit 1
missing image ![alt](does-not-exist.png) exit 0 (must be ignored)
broken reference [docs][ref] + [ref]: ./nowhere-ref.md exit 1
undefined reference [docs][no-def] exit 1 (no matching def)
anchor-only [top](#introduction) exit 0
restore exit 0

Ready for another look.

@LilMGenius

Copy link
Copy Markdown
Owner

Landed in fc28590 (v0.14.0). Thanks @sergiobuilds! 💚

@LilMGenius LilMGenius closed this Jul 17, 2026
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.

2 participants