Skip to content

refactor(build): SRP-split build.mjs + numbered pipeline stages + sitemap and prose-check fixes#107

Merged
brettdavies merged 4 commits into
devfrom
refactor/sitemap-prose-helper-and-build-srp
May 21, 2026
Merged

refactor(build): SRP-split build.mjs + numbered pipeline stages + sitemap and prose-check fixes#107
brettdavies merged 4 commits into
devfrom
refactor/sitemap-prose-helper-and-build-srp

Conversation

@brettdavies
Copy link
Copy Markdown
Owner

Summary

Four-commit refactor branch covering one bug fix, one shared-tooling refactor, one doc update, and the headline SRP split of src/build/build.mjs (676 LOC to 267, with pipeline-stage modules numbered in execution order so the directory listing reads as the build narrative).

Changelog

Added

  • /scorecard-schema now appears in dist/sitemap.xml. The page rendered and was reachable by direct URL, but was missing from src/build/06-sitemap.mjs's hardcoded paths so crawlers could not discover it. Flagged as the "near miss" in the convention doc landed in PR feat(nav): expose Skill and Contribute, add footer Source row #106 (docs/solutions/conventions/new-content-page-requires-three-registrations-2026-05-21.md).

Changed

  • scripts/prose-check.sh now sources the LanguageTool wrapper from ~/dotfiles/config/shell/languagetool.sh rather than carrying its own baseline denylist + reachability probe inline. The site script contributes four site-local denylist rules (IN_PRINCIPAL, CONTRACT_CONTACT, TO_DO_HYPHEN, PLURAL_MODIFIER) on top of the baseline 10 in the helper. Drops the script from 281 to 195 lines and keeps universal-rule edits in one place across all four agentnative channel repos.
  • src/build/build.mjs split at the data/render boundary into four new per-stage emit modules. The orchestrator now reads as a 267-line sequential pipeline; each emit module owns one cohesive output surface.

Fixed

  • The "Authoring PR bodies" snippet in RELEASES.md referenced the older bare curl + jaq LanguageTool invocation. Updated to call lt_check /tmp/body.md so the documentation matches the actual script after the prose-check refactor.

Documentation

  • New pipeline-stage modules 02-homepage.mjs, 03-subpages.mjs, 04-scorecards-emit.mjs, 05-llms-emit.mjs each carry module-level docstrings explaining what they emit and where downstream consumers pull from. The 03-subpages.mjs header includes the cross-reference to the three-registrations convention doc so future authors finding that module also find the convention.

Type of Change

  • refactor: Code refactoring (no functional changes)
  • fix: Bug fix (non-breaking change which fixes an issue)
  • docs: Documentation update
  • BREAKING CHANGE: Breaking API change (requires major version bump)

Related Issues/Stories

  • Story: n/a
  • Issue: n/a
  • Architecture: docs/solutions/best-practices/build-module-srp-dry-refactor-20260421.md (the refactor pattern this SRP split applies). Reviewed during the /ce-compound-refresh pass that paired with this work; doc remained valid without edits.
  • Related PRs: builds on feat(nav): expose Skill and Contribute, add footer Source row #106 (which landed the convention doc + the sibling registration gap)

Testing

  • Manual testing completed
  • All tests passing

Test Summary:

  • bun test: 736 pass, 0 fail.
  • bun run lint: 0 errors (biome + markdownlint).
  • bun run build: produces 370 dist/ artifacts.
  • bash scripts/prose-check.sh: 0 blocking, 1296 warning (same totals as before the LT refactor, confirming the dotfiles helper produces equivalent coverage).
  • Byte-identical verification across the SRP split: find dist/ -type f | sort | xargs sha256sum produces the same digest set before and after the refactor and again after the numbering pass. The spec-version-gen banner in src/worker/spec-version.gen.ts updates to reference the renamed path; esbuild strips comments at bundle time so the worker bundle output is unchanged.
  • dist/sitemap.xml row count: 116 (was 115; the new row is /scorecard-schema).
  • node scripts/generate-pack-readme.mjs --check: passes.

Files Modified

Modified:

  • RELEASES.md: updated inline LanguageTool example snippet
  • scripts/prose-check.sh: extract LT plumbing to shared ~/dotfiles helper (281 to 195 lines)
  • src/build/build.mjs: split per-stage emit logic out; now 267 lines (was 676), imports the seven numbered pipeline-stage modules in order
  • src/build/06-sitemap.mjs: add /scorecard-schema to canonical paths
  • src/worker/spec-version.gen.ts: regenerated banner reflects renamed source path
  • tests/spec-version-gen.test.ts: updated import path + marker assertion for the renamed source module

Created:

  • src/build/02-homepage.mjs: homepage HTML + MD twin emit (section 6 of the pipeline)
  • src/build/03-subpages.mjs: content-driven HTML + MD twin loop (section 7)
  • src/build/04-scorecards-emit.mjs: leaderboard + per-tool scorecards + badges + coverage + skill surfaces (section 8)
  • src/build/05-llms-emit.mjs: llms.txt + llms-full.txt assembly (section 9)

Renamed:

  • src/build/spec-version-gen.mjs to src/build/00-spec-version-gen.mjs
  • src/build/assets.mjs to src/build/01-assets.mjs
  • src/build/sitemap.mjs to src/build/06-sitemap.mjs

Deleted:

  • None

Key Features

  • Pipeline-stage modules now sort in execution order in ls src/build/. Shared helpers (badge, content, coverage, llms, registry-index, render, scorecards, scorecards-render, shell, skill, util) stay unnumbered because they do not represent a single stage.
  • build.mjs's build() function reads as a thirteen-step orchestrator where each emit step is one call into a numbered module.

Benefits

  • The four extracted modules each have one reason to change. A future change to scorecard rendering touches 04-scorecards-emit.mjs only; a future change to homepage layout touches 02-homepage.mjs only.
  • The numbering ordering aid is decorative but matches the inline section comments in build.mjs. A new contributor reading src/build/ in the editor's file tree sees the same order as the orchestrator's awaits.
  • The prose-check + RELEASES doc updates close the gap between the script's implementation and its documentation, and put the rule denylist in one shared place across all four agentnative channel repos.

Breaking Changes

  • No breaking changes

The dist/ output is byte-identical to the pre-refactor state. The renamed source files have no public consumers outside the repo. The tests/spec-version-gen.test.ts import path updates in lockstep with the rename.

Deployment Notes

  • No special deployment steps required

A normal dev to staging worker deploy ships the build. paths-ignore does not skip changes to src/build/.

Screenshots/Recordings

n/a (no rendered-output changes; dist/ is byte-identical).

Checklist

  • Code follows project conventions and style guidelines
  • Commit messages follow Conventional Commits
  • Self-review of code completed
  • Tests added/updated and passing
  • No new warnings or errors introduced
  • Changes are backward compatible (or breaking changes documented)

Additional Context

The SRP split applies the pattern documented in docs/solutions/best-practices/build-module-srp-dry-refactor-20260421.md. The byte-identical safety net described there (find dist/ -type f | sort | xargs sha256sum) was used at every step.

Out of scope for this PR: a build-time assertion that every content/*.md file has a corresponding entry in the subPages list. The convention doc that surfaced the /scorecard-schema gap recommends this follow-up; deferred to a future PR.

Same regression class as /contribute (fixed in PR #106 commit df1b28c). The page was registered in src/build/build.mjs `subPages` and built `dist/scorecard-schema.{html,md}`, but `/scorecard-schema` was missing from src/build/sitemap.mjs's hardcoded paths array, so crawlers couldn't discover it. Documented as the "near miss" in `docs/solutions/conventions/new-content-page-requires-three-registrations-2026-05-21.md`.

Add `/scorecard-schema` to the canonical paths list. dist/sitemap.xml now contains 116 entries (was 115), including the missing scorecard-schema row.
…es helper

Extract the per-rule LT denylist baseline, reachability probe, and per-file POST plumbing out of this repo's prose-check.sh and into the shared `~/dotfiles/config/shell/languagetool.sh` helper. The site script now sources the helper and contributes its own four site-local denylist rules (IN_PRINCIPAL, CONTRACT_CONTACT, TO_DO_HYPHEN, PLURAL_MODIFIER) on top of the baseline 10.

Three benefits:
- The baseline denylist (10 LT rules that misfire on RFC 2119 prose, code-fenced markdown, and CLI-domain jargon) now lives in one place across all four agentnative channel repos. Universal-rule additions edit the dotfiles helper, not four divergent scripts.
- The reachability probe and 2-second timeout policy stay consistent across consumers.
- This repo's prose-check.sh drops from 281 to 195 lines, with the LT plumbing replaced by `source "$LT_LIB"`.

Verified: `bash scripts/prose-check.sh` exits 0/1296 (0 blocking, 1296 warnings) — same totals as before the refactor.

Documents the helper contract in the script header: LT_LIB resolves to `${DOTFILES_SHELL_DIR:-$HOME/dotfiles/config/shell}/languagetool.sh`; missing helper produces exit 2 with a clear error pointing at brettdavies/dotfiles.
…lper

Pairs with the prose-check.sh refactor in 86ed7fa. The inline curl-LT example in RELEASES.md's "Authoring PR bodies" section was the original 4-step prose-scrubbing recipe; the actual prose-check.sh now sources `~/dotfiles/config/shell/languagetool.sh` and invokes `lt_check` for the same coverage with less duplication.

Update the snippet to match: replace the bare curl + jaq invocation with `lt_check /tmp/body.md` and point readers at `lt_rules` / `lt_info` for the rule denylist and category whitelist. Cross-references the agentnative-spec voice-enforcement nuance in CONTRIBUTING.md.
The build orchestrator had grown to 676 LOC with mixed responsibilities (homepage assembly, content sub-pages, the entire scorecard surface, llms.txt assembly, plus orchestration). Per the existing best-practices guidance in docs/solutions/best-practices/build-module-srp-dry-refactor-20260421.md, split at the natural data/render boundaries while preserving byte-identical output across all 370 dist/ artifacts.

Four new emit modules carve out the largest cohesive blocks:

- 06-homepage.mjs (160 LOC) — section 6: hero + live-score form + principle listing, plus the index.md twin. Owns the homepage helpers (buildHomepageBody, buildLiveScoreSection) that used to sit at module scope in build.mjs.
- 07-subpages.mjs (60 LOC) — section 7: the content-driven HTML+MD twin loop. Carries the inline reminder that adding a new content/*.md file requires three coordinated registrations (this list, 10-sitemap.mjs's paths, src/build/shell.mjs's nav).
- 08-scorecards-emit.mjs (292 LOC) — section 8 in full: registry loading, corpus invariants, build-time indexes, leaderboard page, per-tool scorecards + badge SVGs + binary-name redirects, stale-file reaping, coverage matrix, and skill manifest surfaces.
- 09-llms-emit.mjs (102 LOC) — section 9: llms.txt index + llms-full.txt full-text bundle.

Pipeline-stage modules now sort in execution order via numeric filename prefixes that match the section comments in build.mjs. Numbering is decorative (build.mjs is the actual order-enforcer via sequential awaits), but the directory listing reads in the order the orchestrator already documents:

- 00-spec-version-gen.mjs (was spec-version-gen.mjs; section 0)
- 01-assets.mjs (was assets.mjs; section 1)
- 06-homepage.mjs (new; section 6)
- 07-subpages.mjs (new; section 7)
- 08-scorecards-emit.mjs (new; section 8)
- 09-llms-emit.mjs (new; section 9)
- 10-sitemap.mjs (was sitemap.mjs; section 10)

Gaps reflect that sections 2 through 5 (the principle render loop) and section 9b (live-score-shell template) and section 11 (post-build invariant check) live inline in build.mjs. Shared helpers (badge, content, coverage, llms, registry-index, render, scorecards, scorecards-render, shell, skill, util) stay unnumbered because they do not represent a single pipeline stage.

build.mjs drops from 676 to 267 LOC (60% reduction). The new build() body reads as a thirteen-step orchestrator with each emit step delegated to a module the directory listing surfaces in the matching section number.

Verified: bun run build emits the same 370 dist/ artifacts with sha256 sums byte-identical to the pre-refactor state. bun test passes 736/736. bun run lint clean. The spec-version-gen banner in src/worker/spec-version.gen.ts updates to reference the renamed path; esbuild strips comments at bundle time so dist/ output is unaffected.
@brettdavies brettdavies force-pushed the refactor/sitemap-prose-helper-and-build-srp branch from 53e2585 to f45b8a5 Compare May 21, 2026 16:46
@brettdavies brettdavies merged commit 4efa8ec into dev May 21, 2026
3 checks passed
@brettdavies brettdavies deleted the refactor/sitemap-prose-helper-and-build-srp branch May 21, 2026 16:48
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.

1 participant