feat(checkcmd): report media references that resolve to no file #312

Merged
dominik.polakovics merged 2 commits from afk/300 into main 2026-10-16 18:56:38 +02:00

Closes #300

What

  • harness check media check (internal/checkcmd): every page and translation file, drafts included, gets a content warning on the file holding the reference, once per distinct reference:
    • a library reference (linkrefs.Destinations ∩ media.StoredLibraryRef: prose, string params at any depth, front matter; media/… and /media/… are one reference) whose file is missing under assets/media/. The message is references media/docs/gone.pdf, but the media library has no file assets/media/docs/gone.pdf.
    • a bundle-local image-typed param (elemops.WalkRoles + schema, items rows included) that is not a published file of the page's bundle. The messages are … but the bundle of /about/ has no such file and … but the page is not a bundle, so it has no files of its own. A translation is resolved against its source page's bundle.
  • One owner media.LibraryFile(root, ref) (abs, found, err). handleLinkResolve's media branch, handleMediaInfo, mediaops.resolveFile, the publish pre-flight (with the exported site's media root) and the check all call it. regularFileAt is gone. No ResolvePath+stat or LibrarySitePath+stat pair is left.
  • linkresolve.Resolver.BundleFile(page, sub) exposes the bundle rule, which bundleDir/bundleFile still evaluate in one place.
  • Docs and skills: docs/harness.md "Check" has a new Media. paragraph and a warnings-list entry. scaffold/site/AGENTS.md changes "Verifying a change" and the two media bullets. The verify-site (steps 1 and 5), migrate-site (the after-each-section sentence and §6) and edit-content skills are updated.

Notes for review

  • Three warnings, not five, in the reproduction. The acceptance criterion says five, but its own "once per distinct reference per file" rule gives three: the two prose spellings and the front-matter value of /media/docs/gone.pdf are the same reference. The test asserts three and explains why in a comment.
  • .md in a bundle. Following "never a .md file", image="notes.md" is reported even when notes.md exists in the bundle. A .md file merely sitting in a bundle draws nothing.
  • Small behaviour changes in edge cases. No existing test is affected.
    • mediaops used Lstat, so a dangling symlink in the library counted as found; it is now not found.
    • /api/media/info now takes size from the bytes it reads instead of a separate stat.
    • The pre-flight's refs come from PageRefs, which are always StoredLibraryRef-clean, so dropping LibrarySitePath there changes nothing.
  • Hugo branch bundles (assumption checked). Under the pinned Hugo v0.161.1, a branch bundle's .Resources does include files in subdirectories (sub/deep.jpg). .Resources.GetMatch "deep.jpg" still finds nothing, so this check is correct: a bare filename never names a nested file. But linkresolve's sub-path rule (and the docs' "a branch bundle's only directly in its directory") wrongly flags a link like /sec/sub/deep.jpg as NoFile. That needs a follow-up; it is out of scope here.
  • TestHandAuthoredTitleIsClean gained one setup line, a library file its img references. Its assertions are unchanged.

Verification

  • go test ./internal/... ./docs/ ./scaffold/ passes.
  • go test ./cmd/... and go vet ./... pass when run with a placeholder web/dist.
  • gofmt is clean.
  • The new checkcmd tests fail with c.media() disabled.

🤖 Generated with Claude Code

Closes #300 ## What - **`harness check` media check** (`internal/checkcmd`): every page and translation file, drafts included, gets a `content` **warning** on the file holding the reference, once per distinct reference: - a library reference (`linkrefs.Destinations` ∩ `media.StoredLibraryRef`: prose, string params at any depth, front matter; `media/…` and `/media/…` are one reference) whose file is missing under `assets/media/`. The message is `references media/docs/gone.pdf, but the media library has no file assets/media/docs/gone.pdf`. - a bundle-local `image`-typed param (`elemops.WalkRoles` + schema, items rows included) that is not a published file of the page's bundle. The messages are `… but the bundle of /about/ has no such file` and `… but the page is not a bundle, so it has no files of its own`. A translation is resolved against its source page's bundle. - **One owner** `media.LibraryFile(root, ref) (abs, found, err)`. `handleLinkResolve`'s media branch, `handleMediaInfo`, `mediaops.resolveFile`, the publish pre-flight (with the exported site's media root) and the check all call it. `regularFileAt` is gone. No `ResolvePath`+stat or `LibrarySitePath`+stat pair is left. - **`linkresolve.Resolver.BundleFile(page, sub)`** exposes the bundle rule, which `bundleDir`/`bundleFile` still evaluate in one place. - **Docs and skills**: `docs/harness.md` "Check" has a new **Media.** paragraph and a warnings-list entry. `scaffold/site/AGENTS.md` changes "Verifying a change" and the two media bullets. The `verify-site` (steps 1 and 5), `migrate-site` (the after-each-section sentence and §6) and `edit-content` skills are updated. ## Notes for review - **Three warnings, not five, in the reproduction.** The acceptance criterion says five, but its own "once per distinct reference per file" rule gives three: the two prose spellings and the front-matter value of `/media/docs/gone.pdf` are the same reference. The test asserts three and explains why in a comment. - **`.md` in a bundle.** Following "never a `.md` file", `image="notes.md"` is reported even when `notes.md` exists in the bundle. A `.md` file merely sitting in a bundle draws nothing. - **Small behaviour changes in edge cases.** No existing test is affected. - `mediaops` used `Lstat`, so a dangling symlink in the library counted as found; it is now not found. - `/api/media/info` now takes `size` from the bytes it reads instead of a separate stat. - The pre-flight's refs come from `PageRefs`, which are always `StoredLibraryRef`-clean, so dropping `LibrarySitePath` there changes nothing. - **Hugo branch bundles (assumption checked).** Under the pinned Hugo v0.161.1, a branch bundle's `.Resources` *does* include files in subdirectories (`sub/deep.jpg`). `.Resources.GetMatch "deep.jpg"` still finds nothing, so this check is correct: a bare filename never names a nested file. But `linkresolve`'s sub-path rule (and the docs' "a branch bundle's only directly in its directory") wrongly flags a link like `/sec/sub/deep.jpg` as `NoFile`. That needs a follow-up; it is out of scope here. - `TestHandAuthoredTitleIsClean` gained one setup line, a library file its `img` references. Its assertions are unchanged. ## Verification - `go test ./internal/... ./docs/ ./scaffold/` passes. - `go test ./cmd/...` and `go vet ./...` pass when run with a placeholder `web/dist`. - `gofmt` is clean. - The new checkcmd tests fail with `c.media()` disabled. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(checkcmd): report media references that resolve to no file
Some checks failed
ci / build-and-test (pull_request) Failing after 2m41s
ci / release (pull_request) Has been skipped
7c9b503111
harness check now warns, in area content on the file holding it, once per
distinct reference, about a library reference (media/… or /media/…,
anywhere linkrefs.Destinations looks) whose file is not under
assets/media/, and about a bundle-local image param that is not a
published file of the page's bundle (a translation's is its source
page's; a plain page has none).

"Does this library reference name a file of this media root" gets one
owner, media.LibraryFile: the check, GET /api/link/resolve's media branch,
GET /api/media/info, mediaops' file operations and the publish pre-flight
call it instead of pairing ResolvePath or LibrarySitePath with a stat.
The bundle half asks linkresolve's bundle rule through the new
Resolver.BundleFile.

docs/harness.md, AGENTS.md and the verify-site, migrate-site and
edit-content skills describe the warning.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge branch 'main' into afk/300
All checks were successful
ci / build-and-test (pull_request) Successful in 3m27s
ci / release (pull_request) Has been skipped
718b8c1225
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

PASS — validated by the autoland lander.

Signal: labctl pr checks 312 aggregate success on 718b8c1 (run 321, build-and-test green). The first run (320, on 7c9b503) failed on runner infrastructure, not the diff: curl: (6) Could not resolve host: github.com in the Hugo install step, before any Go step ran. The lander merged main into the branch (clean, no conflicts; the branch overlaps #311 on internal/media/library.go, internal/server/{media,linktargets}.go, AGENTS.md and the edit-content skill) to re-run CI on the merged code. Locally verified too, on both the PR head and the merge: npm build/test, go vet, go test with Hugo 0.167.0, go build, gofmt.

Conventions: Conventional Commits title; Closes #300; AGENTS.md, docs/harness.md and the verify-site, migrate-site and edit-content skills updated as the issue requires; one owner (media.LibraryFile) for all four former ResolvePath/LibrarySitePath+stat callers plus the check, and linkresolve.Resolver.BundleFile reuses bundleDir/bundleFile.

Notes (not blocking):

  • Three warnings instead of the acceptance criterion's literal "five" follows the issue's own once-per-distinct-reference rule. No reading of that rule gives five. The test comment explains this.
  • The PR body says the pinned Hugo is v0.161.1. The pin is 0.167.0 (initcmd.DefaultHugoVersion, CI). The linkresolve branch-bundle sub-path follow-up it describes, for the link check, is not filed yet.
[autoland] verdict: pass PASS — validated by the autoland lander. Signal: `labctl pr checks 312` aggregate **success** on 718b8c1 (run 321, build-and-test green). The first run (320, on 7c9b503) failed on runner infrastructure, not the diff: `curl: (6) Could not resolve host: github.com` in the Hugo install step, before any Go step ran. The lander merged main into the branch (clean, no conflicts; the branch overlaps #311 on internal/media/library.go, internal/server/{media,linktargets}.go, AGENTS.md and the edit-content skill) to re-run CI on the merged code. Locally verified too, on both the PR head and the merge: npm build/test, go vet, go test with Hugo 0.167.0, go build, gofmt. Conventions: Conventional Commits title; `Closes #300`; AGENTS.md, docs/harness.md and the verify-site, migrate-site and edit-content skills updated as the issue requires; one owner (`media.LibraryFile`) for all four former ResolvePath/LibrarySitePath+stat callers plus the check, and `linkresolve.Resolver.BundleFile` reuses bundleDir/bundleFile. Notes (not blocking): - Three warnings instead of the acceptance criterion's literal "five" follows the issue's own once-per-distinct-reference rule. No reading of that rule gives five. The test comment explains this. - The PR body says the pinned Hugo is v0.161.1. The pin is 0.167.0 (initcmd.DefaultHugoVersion, CI). The linkresolve branch-bundle sub-path follow-up it describes, for the link check, is not filed yet.
Sign in to join this conversation.
No reviewers
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
Cloonar/deckle!312
No description provided.