name: hc-preflight description: Site-wide pre-publish health check — frontmatter, share images, metatags, sitemap, build, and draft leakage allowed-tools: Read, Glob, Grep, Bash, Write argument-hint: <optional — pass --strict to fail on any warning, or a path to limit scope to one post>
Pre-flight check for the hungovercoders site before publish. Run from ~/dev/hungovercoders/site/. Walks the site's content, build output, and config to surface anything that would degrade a published post or break a metatags preview. Produces a punch list, not pass/fail per file.
If $ARGUMENTS contains a path or slug, scope checks to that file plus the site-wide checks (build, sitemap). Otherwise run the full audit. --strict flag exits non-zero if any P0/P1 issue is found.
Step 1 — Establish the working state
cd ~/dev/hungovercoders/site
git status --short
git branch --show-current
Capture: branch name, untracked files, modifications.
Branch expectations:
- Preflight is intended to run on a feature branch (
series/<slug>orblog/<slug>) before merging tomain. The Cloudflare preview for that branch is what you're validating; merge-to-mainis what publishes. - If on
main: flag as P1 with the note "preflight is normally run on the publish branch before merge — if you've already merged, the work below is post-mortem rather than gating; if you haven't branched yet, you're about to publish straight to live". - If on a feature branch with uncommitted changes: that's expected mid-flow. Note it informationally. If
--strict, flag as P2 — the merge-to-main step needs a committed tree. - If on a feature branch with a clean tree: ideal state. Note the branch in the report so the user knows what they're about to merge.
Step 2 — Build the site
npm run build 2>&1 | tee /tmp/hc-preflight-build.log
Capture exit code. If non-zero, build failed — stop the audit here and report the build error as the only finding. Everything else is moot until it compiles.
If the build succeeds, note any warnings in the log (Astro deprecation warnings, missing image dimensions, etc.) as P2 findings.
Step 3 — Blog post frontmatter sweep
For every .md file in src/content/blog/ (excluding _drafts/), check:
| Check | Severity |
|---|---|
title present and non-empty |
P0 |
date present and parses as a date |
P0 |
author present (typically dataGriff) |
P1 |
description present, non-empty, and no trailing period |
P1 |
tags is an array containing hungovercoders |
P1 |
image.path present and starts with /assets/<slug>/ |
P0 |
image.path file exists at public/<image.path> (i.e. public/assets/<slug>/link.png) |
P0 |
Filename matches YYYY-MM-DD-<slug>.md |
P1 |
Slug in filename matches the slug component of image.path |
P1 |
Report each violation with the exact file path and the offending line.
Step 4 — Training lesson frontmatter sweep
If training-repos/ contains any linked repos, walk every training-repos/*/docs/*/README.md and check:
| Check | Severity |
|---|---|
title, series, order, description, canonical_url all present |
P0 |
description non-empty and no trailing period |
P1 |
order value matches the directory's leading number (e.g. 02-foo/ → order: 2) |
P1 |
canonical_url starts with https://hungovercoders.com/training/<series>/ |
P1 |
Directory has no duplicate order value across the series |
P0 |
If training-repos/ is empty (because of an exclude list), report this as informational, not a failure.
Step 5 — Built output spot checks
For each blog post that built into dist/client/blog/<slug>/index.html, grep the HTML for:
grep -oE '(og:image|og:type|og:url|twitter:image|<link rel="canonical")[^>]*' dist/client/blog/<slug>/index.html
Verify:
og:type = articleog:urlis absolute (https://hungovercoders.com/blog/<slug>/)og:imageis absolute and points at/assets/<slug>/link.png— not at the generic placeholder_astro/blog-placeholder-...jpgcanonicalis absolute and matchesog:url
Don't sample — check every post. This is the cheap version of running every URL through metatags.io.
Step 5b — Inline asset references resolve
For each blog post and training lesson, grep the markdown for site-absolute asset references (/assets/<slug>/<name>.<ext>) — typically inline screenshots embedded via hc-screenshot — and confirm each referenced file exists under public/<that-path>. Flag broken references as P2 (warning, not block): the build won't fail on a missing image, but the post renders with a broken <img> and the reader sees nothing.
# from inside src/content/blog/<slug>.md, find every /assets/<slug>/<file>.<ext>
grep -oE '/assets/[^)"'\'' ]+\.(png|jpg|jpeg|webp|svg|gif)' src/content/blog/<slug>.md \
| sort -u \
| while read ref; do
[ -f "public${ref}" ] || echo "MISSING: $ref (referenced in $post)"
done
Same logic for training lessons under training-repos/<series>/docs/<NN>-<lesson>/README.md, checking against public/assets/training/<series>/<lesson>/.
The existing image.path frontmatter check (Step 3) covers the social card (link.png) as P0 — that's the unmissable one. This step catches the broader case where a writer added  to the body but forgot to drop the file in. hc-screenshot's own emit step is meant to prevent it, but the check is the belt-and-braces.
Step 6 — Sitemap and feeds
dist/client/sitemap-index.xmlexists- Sitemap references every published blog post URL
dist/client/rss.xmlexists and the most recent published post appears in it- Spot-check the page count in the sitemap against the count of built
dist/client/blog/*/index.htmlfiles
Step 7 — Draft leakage and exclude integrity
- Confirm no
src/content/blog/_drafts/*.mdcontent appears in the build output (grep -r "_drafts" dist/client/returns nothing for content paths —_draftsmay legitimately appear in pagefind indexes; ignore those) - Read
scripts/fetch-training-repos.shandscripts/link-local-repos.sh. If either has anEXCLUDElist, list the excluded items as informational so the user remembers what's deliberately hidden
Step 8 — Nav and empty-state sanity
dist/client/training/index.htmleither lists series cards or shows the "coming soon" empty state — confirm one of those is rendered, not a broken middle ground- Every nav link in the header (
/,/blog,/training,/about) resolves to a builtindex.html
Step 9 — Produce the punch list
Group findings by severity:
- P0 — must fix before publish. Build failures, missing required frontmatter, missing OG images that would fall back to placeholder, duplicate training orders.
- P1 — should fix before publish. Trailing periods on descriptions, mismatched order vs directory, missing tags, missing British spellings if egregious.
- P2 — worth fixing soon. Build warnings, uncommitted changes, schema drift.
For each finding include:
- Severity
- Exact file path (and line number where relevant)
- One-line description
- One-line suggested fix (e.g. "drop trailing period from
description:")
Step 10 — Report to the user
Print to chat:
hc-preflight — <date> <time>
Branch: <current branch> (publish target: main)
Build: PASS / FAIL
P0 issues: N
P1 issues: M
P2 issues: K
P0:
- <file>: <issue> → <fix>
...
P1:
- <file>: <issue> → <fix>
...
Verdict: <ready to merge to main | fix P0 first | …>
If the verdict is "ready to merge to main", remind the user the actual publish step is the merge — git push on the branch only updates the preview URL.
Exit non-zero if --strict and any P0 or P1 issue exists.
Notes on running this
- Run this on the publish branch immediately before merging to
main. It's the publish gate — the merge is what goes live,git pushon the branch only updates the Cloudflare preview. - Build is the first gate — everything else assumes the build passed.
- This skill does not check voice or content quality. For that, run
hc-review-blogorhc-review-seriesseparately. - If
training-repos/is empty (deliberate exclusion of all series), the training half of the audit is skipped — that's fine, not a failure.