Skip to content

refactor(repo): reorganize the repository for its first release - #87

Merged
justinmerrell merged 28 commits into
mainfrom
reorg/first-release
Sep 14, 2026
Merged

justinmerrell merged 28 commits into
mainfrom
reorg/first-release

Conversation

@justinmerrell

Copy link
Copy Markdown
Contributor

Reorganizes the repository before its first release, while nothing is tagged and the
ADR 0005 §1 window still lets paths,
$ids, requirement IDs and anchors change freely. Decisions are recorded in three new ADRs:

  • ADR 0021 — the repository is
    organized around the family version, under one name (musher-dev/specifications,
    https://specifications.musher.dev); conformance corpora colocate with their family version;
    one layout module; link-target-only maintenance of accepted ADRs.
  • ADR 0022 — the Musher Document Core
    Specification: shared rules move out of the component spec into specifications/core/v1/
    (no kind, no schema, own release train), with CORE-* requirement IDs.
  • ADR 0023 — schema bundles are
    build output; releases publish immutable GitHub Release assets verified against ledger v2; a
    kind family release waits for core's releasable changes.

What changes

Area Change
Layout specifications/{core,component,blueprint,listing}/v1/ each hold spec.md, conformance/ and, for kind families, schemas/src/ and examples/. Root conformance/ keeps only the fixture-format contract.
Core Envelope, compatibility, items, label grammar, validation layers, YAML profile, format policy, shared diagnostics, conformance claim, editions and security baseline move to core v1. Family specs bind core in a §2 table. Retired IDs: COMP-ENV-*CORE-ENV-*, COMP-YAML-*CORE-YAML-*, BP-ID-001/LIST-ID-001CORE-ITEM-001, BP-ID-002/LIST-ID-002CORE-ITEM-002. Diagnostic codes and phases are unchanged; the three family bundles are structurally identical to main.
Names Every $id, URL and reference uses musher-dev/specifications and specifications.musher.dev (ADRs keep their historical text).
Tooling tools/src grouped by concern; lib/layout.ts is the only place a repository path is spelled, and reading a released ref that lacks an expected path fails loudly. New checks: check:adr, check:generated, check:docs, check:editions, check:title.
Publication Committed schemas/dist/ and root catalog.json removed; bundle.ts --stdout <family>/v1 builds a bundle with no install. Ledger v2 records path, tree, bundleSha256 and requires.core. release-please runs under a GitHub App token, creates draft releases with forced tags; the release job stages deterministic archives, attests, publishes immutable, and deploys once per push. pages.ymldeploy.yml.
Docs README rewritten as a front page; specifications/README.md, per-family READMEs, docs/using-schemas.md, docs/publication.md, docs/conventions.md, tools/README.md and a generated ADR index. CLAUDE.md, CONTRIBUTING and GOVERNANCE updated for the new model.

It also fixes defects that would have stopped the first release: release PRs never ran their
required checks, the release commit had no sign-off, its title failed the title check, record
never refreshed a pending entry, and a release PR could merge without a ledger entry.

Verification

  • task check green at the tip and in a fresh clone with nothing prebuilt (conformance: 207 cases
    across core 13, component 93, blueprint 68, listing 33; 405 tooling tests).
  • Local release dry run: core → component recorded, tagged and staged; archives byte-reproducible;
    CRLF/BOM parser fixtures keep their bytes; the pinned bundle $id and hash match the ledger; the
    core gate blocks a family release after feat(core) and only warns after chore(core).
  • Read-only reviews: residue and links, governance accuracy, newcomer walkthrough, correctness of
    tooling and workflows, prose fidelity of the core extraction. Findings were fixed in this PR.

Governance note

CONTRIBUTING asks for an ADR to be accepted in its own PR first. This reorganization ships as one
PR by maintainer decision; ADRs 0021–0023 are the first commits on the branch
(recorded in ADR 0021's consequences).

Before merging

  1. infra: serve specifications.musher.dev from the musher-schemas Pages project and confirm
    it returns 200 — the first deploy after merge runs site:verify-live against it. Do not add the
    old-host redirect yet.
  2. catalog: stop reading the committed bundle on raw main
    (tests/lib/spec-schemas.ts, .devcontainer/devcontainer.json); point at
    https://specifications.musher.dev/<family>/v1/<family>.schema.json.
  3. foundation-bootstrap: rekey the deploy-credential delivery from spec: to specifications:
    without destroying the secrets (state rm first), and confirm gh secret list.

Before the first release (core/v1.0.0 first)

  1. Enable immutable releases; disable the stale GitHub Pages site.
  2. Create the release GitHub App (Contents RW, Pull requests RW, Administration read); set
    RELEASE_APP_ID and RELEASE_APP_PRIVATE_KEY; replace the signoff placeholder in
    .github/release-please/config.json with its identity (the release job refuses until it matches).
  3. Apply .github/rulesets/release-tags.json (adds refs/tags/core/**).
  4. infra: 301 schemas.musher.dev/*specifications.musher.dev, excluding /telemetry/*
    (agree with observability-schema-registry).
  5. Rehearse the pipeline in a scratch repository. Release PRs chore(main): release component 1.0.0 #1chore(main): release listing 1.0.0 #3 stay draft + do-not-merge;
    release-please will regenerate them and open one for core.

After merge: platform bumps config/spec.ref with the harness changes (core corpus, bundler CLI,
CORE-* IDs); examples switch modelines; cosmetic renames elsewhere.

BEGIN_COMMIT_OVERRIDE
feat(core): add the Musher Document Core Specification
refactor(repo): colocate conformance, rename the host, and build published artifacts instead of committing them
END_COMMIT_OVERRIDE

🤖 Generated with Claude Code

https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf

justinmerrell and others added 28 commits September 13, 2026 14:41
CLAUDE.md, CONTRIBUTING, the commit-msg hook text, the vocabulary file and the
PR template all said the Conventional Commit scope selects the release train.
release-please's manifest mode splits commits by the package paths they change
(src/util/commit-split.ts); the scope never participates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…n, under one name

Records the repository and host names, colocated conformance corpora, the single layout module with loud failures, and the ADR link-maintenance rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Records the core base family: admission test, content and ID map, bindings, no core schema, parser-only core corpus, and edition recording.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Records bundles as build output, draft-then-immutable releases, ledger v2, the core gate, and one deploy per push.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Adds core to every copy of the Conventional Commits scope list that check:commits holds in step, adds the missing deps-dev to the hook's help text, and corrects the lint-pr comment that said the scope drives release-please.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Move tools/src/*.ts into concern folders with git mv, tests beside their module: lib/ (spec.ts renamed layout.ts, git.ts), schema/ (lint, bundle, drift, standards), validation/ (document, validator, semantic, effective), conformance/ (conformance, examples, parity), publication/ (released, ledger, published, compat, changes, site, catalog), render/ (reference, prose, html, traceability) and policy/ (config, rulesets, commits, links); testing/ stays. Imports, taskfiles, package.json scripts and plain-text tools/src mentions outside docs/adr follow, and docs/traceability.md is regenerated. REPO_ROOT in lib/layout.ts now climbs three levels and throws when <REPO_ROOT>/specifications is missing, because a wrong depth made family discovery return nothing and every check pass. version.ts is removed: nothing imported it. No behaviour change; exported names are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
policy/commits.ts repeated the claim corrected elsewhere; release-please splits commits by path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…il loudly on a ref that lacks it

lib/layout.ts now spells every repository path: familyPaths(name, major), the ledger, release-please, traceability and catalog paths, and parseManifestKey/parseSpecPath/conformanceLink. Every other tool asks it. A guard test fails the suite when a path literal appears anywhere else in tools/src.

At a released tag, spec.md, examples/ and the conformance corpus are read through requireAtRef, which throws LayoutError naming the ref and path. Before this, site.ts returned null/[] and compat.ts replayed nothing and passed. discoverFamilies throws if specifications/ has children but yields no family. The built site is byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
tools/src/policy/adr.ts, run as task check:adr in the check aggregate and the Lint job:

- ADR-01: every docs/adr/ file but README.md is named NNNN-kebab.md.
- ADR-02: numbers are contiguous from 0001, unique, and match the H1.
- ADR-03: the header carries Status and Date; relation bullets link only
  existing ADRs, and every cited section has a numbered heading.
- ADR-04: an ADR accepted at the base (BASE_REF, else origin/main) differs
  only in link targets; new ADRs are exempt.

The Lint job checks out full history and passes the pull request's base sha.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…t tests

Move conformance/<family>/v1 to specifications/<family>/v1/conformance for
component, blueprint and listing, per ADR 0021 §2. The root
conformance/README.md stays as the fixture-format contract every corpus
follows. No case, outcome, clause or code changes.

The two byte-sensitive component parser fixtures (012 byte order mark,
013 CRLF) keep their -text exemption in .gitattributes, moved in this same
commit so neither is ever normalized.

familyPaths().conformance now names the corpus inside the family version,
and CONFORMANCE_DIR, which nothing read, is gone. docs/traceability.md is
regenerated. The spec.md corpus links, the conformance README layout and
case paths, CONTRIBUTING, README and CLAUDE.md layout blocks, the
devcontainer yaml.schemas globs, the cspell ignore globs, and the release
archive copy follow the move; the archive still carries conformance/ at
its top level.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
… host specifications.musher.dev

The repository was renamed on GitHub from musher-dev/spec and nothing else
followed. ADR 0021 §1 puts every $id, pinned URL, alias URL and catalog entry
on https://specifications.musher.dev. Nothing is tagged and published.json
records no release, so no served identifier changes.

SITE_HOST and REPO_URL in tools/src/lib/layout.ts carry the change to
everything derived from them: the bundle $ids, catalog.json, the lint.ts
$id prefix and the site's GitHub links. The three source $ids, example
modelines, spec.md Schema lines, README, SECURITY, GOVERNANCE, CLAUDE.md,
RULESETS.md, the devcontainer header, the Pages job name, the build
Taskfile description, tests and comments follow. The tools package is now
@musher-dev/specifications-tools, and bun.lock matches it.

The Cloudflare Pages project keeps its name, musher-schemas, which
musher-dev/infra owns. Accepted ADRs keep their text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Teach every tool that a family version may be the base family core
(ADR 0022): prose and a conformance corpus, no schemas/ and no examples/.
Today's tree, which has no core directory, is unchanged: the site tree,
catalog.json and docs/traceability.md are byte-identical.

- layout: CORE_FAMILY, Family.role ('core' | 'kind'), Family.hasSchema,
  discoverKinds(); discoverFamilies() lists core first; hasPart() says core
  carries no schema and no examples, so a released core tag is not required
  to carry examples/.
- lib/bindings.ts: parse a family spec.md §2 'Core parameter | This family'
  and 'Normative dependencies' tables; null when both are absent, throws on
  a malformed or half-present binding.
- lint: checkEnvelope() holds each kind root to exactly specVersion, kind,
  metadata and spec, all required, closed, specVersion admitting only the
  family major, kind a const equal to the §2 binding when one exists; fail
  when core carries schemas/ ('core ships no schema — ADR 0022'); skip
  families without schemas/src.
- bundle skips schema-less families silently; catalog iterates kinds.
- site: a schema-less family renders prose pages only — no field reference,
  alias, pinned path, versions.json or _headers rule — plus a prose-only
  family index, for both the working tree and released tags.
- ledger (interim until ledger v2): a core release records the family
  version directory as path and null hashes; record, sync, readLedger and
  verifyPublications accept that form only for a schema-less family.
- compat skips core releases; changes reports core prose-only under an
  'affects every family' heading; traceability takes a repo root.
- release plumbing: core package in release-please config and manifest,
  refs/tags/core/** in the release-tags ruleset, and a release archive step
  that ships core without a bundle or examples.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
… corpus)

Add specifications/core/v1/spec.md per ADR 0022: bindings and admission,
the envelope (CORE-ENV-001..007), version compatibility, the item directory
and item identity (CORE-ITEM-001/002), the label grammar, validation layers,
the YAML profile (CORE-YAML-001..012), the format keyword, the shared
diagnostics table, conformance and the core corpus, editions, known debt,
and the security baseline.

Component, blueprint and listing now bind core's parameters in a §2
bindings table followed by a Normative dependencies table, cite core by
link, and no longer declare the moved rules, the duplicate item rules
(BP-ID-001/002, LIST-ID-001/002) or the codes core owns. Component keeps
stub anchors for #compatibility, #validation-layers, #yaml-profile and
#format-policy.

Move component parser 001-013 to specifications/core/v1/conformance with
the .gitattributes -text entries for 012 and 013, delete blueprint and
listing parser 002-004, keep one parser-001 smoke case per family, and
repin structural 002-005, component structural 023/057, blueprint
semantic 003/004 and listing semantic 002/003 to core clauses and IDs.

Retired IDs: COMP-ENV-00n -> CORE-ENV-00n, COMP-YAML-0nn -> CORE-YAML-0nn,
BP-ID-001 + LIST-ID-001 -> CORE-ITEM-001, BP-ID-002 + LIST-ID-002 ->
CORE-ITEM-002.

docs/traceability.md is left for regeneration, and check:conformance
needs the tooling lane (registry = own + core + dependencies).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…se consistency

Replace BASE_FAMILY with registries resolved through each family's §2
Normative dependencies table: a kind family's registry is its own table,
core's, and each declared dependency's; core's is its own. A kind family
that does not list core, or whose table re-declares a core code, fails.

Run core cases through the parser alone, with no bundle and no dispatch on
kind. A core case must be a parser-phase case.yaml with no effective map.
Core codes and CORE-* requirements count as covered by any corpus.

Add checkClauseConsistency (conformance/README.md, the clause row). Fix
the nine cases it caught: blueprint semantic-006 and -026; component
structural-047 and -060 and semantic-003 and -009; listing semantic-013,
-014 and -015.

Extract the spec outline to lib/outline.ts, shared by prose, traceability
and conformance. Traceability output is byte-identical. Rename UNPINNED
keys to CORE-ENV-001/003/004, retarget comments from component §7/§8 and
blueprint §3.1 to core, and regenerate docs/traceability.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…tions

- core §8.1 and ADR 0022 §6: a core case need not be a valid envelope; a kind, where present, is COMPONENT and has no effect
- core §9: family releases are held only on releasable core changes; others warn (ADR 0023 §5)
- core §4.2: the no-directory rule is a pointer to §4.1, not a second statement
- blueprint §3: CORE-ITEM-002 binds the revision only where a listing is present; record that an item with no listing is not rejected
- ADR 0022 §3: record the BP-ID-002 meaning change and the widening of core §11's paths-inside-an-item rules as deliberate generalizations
- family headers: complete behaviour is defined together with the core specification
- turn bare cross-spec section citations into links; code-span core's example citation form
- blueprint: retarget the no-network rule to core v1 §6
- component: "slug grammar" becomes "label grammar"
- core §1.1: a family's diagnostic registry is its own table, core's, and its declared dependencies'
- core §8: merge the conformance paragraphs around the recorded core edition
- core §9: release policy is informative; a minor release adds a code only for an already-rejected condition
- ADR 0022 §3: component's #yaml-profile and #format-policy are inline anchors in its §7 stub

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
The ADRs were drafted before tools/src was grouped by concern; point their four file mentions at the new locations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
resolveBaseRef treated every rev-parse failure as 'origin/main absent'. In a checkout git refuses (dubious ownership under the neutralised config), ADR-04 was skipped silently. Only an absent ref (exit 1, empty stderr) now skips; any other git failure throws.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Schema bundles and the catalog are build output (docs/adr/0023). Remove the
committed specifications/*/v1/schemas/dist bundles and the root catalog.json;
task bundle writes dist/<family>/<major>/<family>.schema.json and
dist/catalog.json, and every check builds bundles in memory.

- schema/sources.ts: ModuleReader with fsReader and gitReader (ls-tree,
  cat-file). schema/bundle.ts: buildBundle(family, {reader, id}),
  familyBundle (memoized on source bytes), pinnedBundle, ensureBundleFile,
  and a node-only CLI: no args writes dist/, --stdout <family>/<major>
  [--version X.Y.Z] prints one bundle. A test scans the import graph.
- layout: DIST_DIR, CATALOG_NAME, CATALOG_FILE under dist/, Family.repoRoot,
  bundleUrl(); FamilyPaths.bundle points under dist/; distDir removed.
- validator, conformance, examples and reference tests use familyBundle;
  standards and parity call ensureBundleFile before the jsonschema CLI.
- site: untagged aliases build in memory, so site:build needs no prior
  bundle; /catalog.json is still served. Interim until the release lane:
  released pinned copies, ledger record and check:published build from the
  sources at the tag (gitReader) or the working tree, and entries record the
  family version directory.
- changes: base via the base commit's own bundler when it has --stdout,
  else gitReader; --diff appends unified bundle diffs; a byte change with no
  schemas/src change is reported as tooling-only; codes and requirement IDs
  that moved into a declared dependency (by ID, same statement, or re-pinned
  cases) are moves, not rejecting removals.
- check:drift becomes check:generated (no tracked dist/, schemas/dist, or
  root catalog.json). Taskfiles, lefthook, cspell, .gitignore,
  .gitattributes, devcontainer bindings and post-create, CI (Schema uploads
  dist/, change report with --diff) and pages.yml follow; README, CLAUDE.md,
  CONTRIBUTING, PR template and RULESETS.md stop describing committed dist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
The sign-off email carries the App's bot user id, not its App id, and the release App needs administration read to confirm immutable releases. The release job refuses to run until the signoff placeholder names the App.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…edger v2

Replace the interim publication tooling with the final ADR 0023 design.

- Ledger v2 (published.json rewritten to version 2): entries record path,
  tree, bundleSha256 (null exactly for core) and requires.core (present
  exactly for kind families). check:ledger allows v1 to v2 only from an
  empty v1 ledger.
- record.ts inserts or updates pending entries from HEAD, never rewrites a
  tagged one, and enforces the pending core gate (core-gate.ts): core
  unreleased, core tag missing, or a releasable core commit since the tag
  fails; non-releasable core commits and a trailing requires.core warn.
- verify.ts checks tagged entries offline by tree id, the tag's own ledger
  and the tagged core gate, with no rebuild; pending entries against HEAD
  and a fresh pinned build. Replaces published.ts and released.ts.
- stage.ts verifies a tag and stages <family>.schema.json and a
  deterministic <family>-v<X.Y.Z>.tar.gz carrying core/spec.md and
  core/conformance/ from core/v<requires.core>; core stages its archive.
- github.ts and fetch.ts verify every tagged release online (published,
  immutable, digests, bytes) into .cache/releases; site.ts serves pinned
  paths only from that cache and restamps released aliases.
- verify-live.ts compares the deployed origin with the ledger.
- compat.ts replays tagged ledger entries under their recorded path.
- Tasks: check:published, check:published:online, check:ledger,
  check:editions, release:record, release:stage, site:fetch, site:build,
  site:deploy, site:verify-live; tools/.bun-version pins Bun 1.3.14.
- Tests for every module, and a local end-to-end pipeline test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…once per push

release-please now authenticates as the release GitHub App, so its pull
requests run the required checks. It creates a draft with a tag forced at
the merge commit, titles release pull requests chore(repo): release..., and
signs its commit off with the configured signoff. release.yml refuses to run
unless that signoff names the minted App's bot user.

release.yml gains a plan job that builds a {tag, release_id} matrix from
release-please's outputs, or from a workflow_dispatch tag for recovery. The
artifacts job checks out the tag, refuses unless immutable releases are
enabled, stages with the tag's own tooling and Bun, attests, and uploads to
the draft by id: matching digests are skipped and differing assets replaced;
a published release is only verified. It then publishes with make_latest=false
and asserts that the release is immutable and holds exactly the staged digests. Kind
families note the core edition they were built against.

pages.yml becomes deploy.yml, run only by workflow_call and
workflow_dispatch: check:published, site:fetch, site:build, site:deploy,
site:verify-live. release.yml calls it last on every run, so every push
deploys exactly once, after any release it cut.

release-ledger.yml acts only on same-repository release-please branches,
checks out without persisted credentials, runs release:record, mints the App
token afterwards, and pushes a commit authored and signed off as the App.

ci.yml adds check:editions to Schema and a cached, verified site:fetch to Site
Build. Every setup-bun reads tools/.bun-version. RELEASE_APP_ID is declared
to actionlint.

See docs/adr/0023-published-bytes-are-immutable-release-assets.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Rewrite README.md as a front page: scope, status, the four families, routes by task, a repository map, and how to contribute. Add specifications/README.md as the home of the family list, how the families relate, the anatomy of a family version, what is normative, and how to implement a family, with a front page for each family. Add docs/README.md as an index by audience, docs/conventions.md for the repository naming table moved out of CLAUDE.md, and docs/using-schemas.md for consumers: which URL, editor binding, pinning, offline use, verification, headers, and the moved host.

Links to docs/publication.md resolve once the sibling documentation commit adds it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…for the new model

CLAUDE.md is slimmed to twelve non-negotiables for the colocated, core-based,
release-asset model, plus a 'Where things are' index. The layout block, the
naming table (now docs/conventions.md) and Known debt are removed.
CONTRIBUTING condenses the ground rules to links, moves the check table to
tools/README.md#checks, and documents path-based release-please across four
packages, the core scope, squash merges and BEGIN_COMMIT_OVERRIDE, and the
no-override rule for core. GOVERNANCE points to ADR 0021 §4, adds changing core
as a structural change, drops check:drift, and moves release mechanics to the
new docs/publication.md. SECURITY verifies immutable releases with gh release
verify and verify-asset.

Forward links to specifications/README.md, docs/conventions.md, tools/README.md
and docs/adr/README.md resolve once sibling commits land.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…vigation

Add tools/src/render/adr-index.ts, which generates the table in
docs/adr/README.md from each ADR header, with relations condensed and
superseded/refined-by back-references computed. The README carries a
hand-written introduction: what an ADR is, when one is required, the
immutability and link-maintenance rule, and how to add one.

task docs regenerates docs/traceability.md and the ADR index, and task
bundle routes through it. task check:docs (tools/src/policy/docs.ts)
regenerates both in memory and names any stale file. It runs in task check
and ci:lint, replacing the inline traceability diff step in ci.yml's Schema
job. That job also drops its duplicate check:editions step, which ci:test
already runs. Job names are unchanged.

Add tools/README.md: runtime, groups, a Checks table in aggregate order with
scripts and rule IDs, the build/release/site tasks and the variables they
take, the layout module, the bundler CLI contract, and test support.

Accuracy fixes: the schemas.musher.dev redirect is described conditionally
(not live yet), the release App needs Administration read, the release-please
signoff placeholder must be replaced before the first release, and a bundler
test fixture names the current host.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Implements the fix-round contract:

1. check:published fails on a manifest version other than 0.0.0 that is
   neither tagged nor recorded.
2. A tagged entry is verified by tree id, the tag's own ledger and core-tag
   ancestry only; releasable-commit classification runs for pending entries
   and in release:stage, never against tagged history.
3. release:stage compares the tag's entry with the default branch's ledger
   (origin/main, BASE_LEDGER_REF), requires examples/ for kind families, strips
   TAR_OPTIONS and GZIP, and clears only this tag's outputs.
4. task check:title validates PR_TITLE against conventional-commits.yaml and
   accepts the release-please title; ci.yml runs it in Lint on pull requests
   and re-runs on edited.
6. The release tag guard requires ancestry of GITHUB_SHA on push and of
   origin/main on dispatch, and the default branch in both.
7. Deploy runs only when plan and artifacts succeeded or were skipped;
   release.yml and deploy.yml refuse dispatch from any ref but main.
8. ALLOW_PENDING_RELEASES=1 (CI Site Build only) turns an unpublished tagged
   release into a warning recorded in the cache, and site:build leaves it out.
   A cache hit is still checked against the listed release.
9. site:build steps receive GITHUB_TOKEN in CI and deploy.
10. check:md and fmt:md exclude release-please's CHANGELOG.md.
11. check:standards and check:parity fail under CI=true without the CLI.
12. ADR-04 blanks only relative link targets.
13. release-ledger commits and pushes with hooks disabled.
14. BREAKING CHANGE counts only in the trailer block; Release-As releases.
15. ADR index back-references read "§N superseded by NNNN".
16. The site index is titled Musher specifications.

Also: 30-second timeouts on GitHub and live-origin requests, one-line CLI
errors in site:fetch, check:compat and release:stage, a bundle memo keyed on
the bytes it builds, lefthook doublestar globs, and devcontainer tidying.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…vigation gaps

Describe the fix-round contract as the pipeline now behaves: an unrecorded
pending release fails check:published, tagged entries are verified by tree,
tag ledger and core ancestry only, release:stage compares against the default
branch ledger and requires examples, the tag guard, deploy conditions and the
main-only dispatch, the CI draft window, hook-less ledger commits, footer-only
BREAKING CHANGE and Release-As, the full pending core-gate failure list, and
the archive root. Enforce-and-document the PR title in Lint (check:title) and
list the CI-only steps.

Close navigation gaps: where released prose is read, the case path and index
step in Making a change, kind family defined once, requirement prefixes linked,
the recorded core edition, the fixture format as normative, the case ID versus
directory convention, and a new-reader pointer to ADRs 0021-0023.

Keep each fact in one home (release assets, required checks, the core override
rule, headers, what is normative) and correct governance claims: planned
Deprecation/Sunset headers, which jobs hold credentials, ADR link-target rules,
what RUL checks verify, the squash-merge settings, and CI's unpinned
ShellCheck.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…fixes

Point check:title at policy/title.ts, document the pending-release cache marker and site:build's environment, and state ADR-04's relative-link rule and a new-reader pointer in the ADR index intro.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
It spawns a real git clone and timed out at bun's 5s default when the suite ran alongside another full check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gc19zuT7UEP39uXufTZHrf
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
@github-actions

Copy link
Copy Markdown

You own some of the paths this pull request changes

@justinmerrell, this pull request touches paths you are a code owner for. Your authorship waives the code-owner review requirement — GitHub cannot request a review from a pull request's author — so nobody is automatically notified about these changes:

  • .github/CODEOWNERS — you are the sole owner; no review will be requested
  • .github/rulesets/RULESETS.md — you are the sole owner; no review will be requested
  • .github/rulesets/release-tags.json — you are the sole owner; no review will be requested

Double-check they are intentional. If you want a second pair of eyes, request a review manually. (Automated notice from the codeowners-notice workflow — ADR 0015.)

@justinmerrell
justinmerrell merged commit 3a9ab6e into main Sep 14, 2026
10 checks passed
@justinmerrell
justinmerrell deleted the reorg/first-release branch September 14, 2026 12:10
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