Adapts mattpocock/skills/engineering/improve-codebase-architecture to
this repo. Four files at .claude/skills/improve-codebase-architecture/:
SKILL.md (104 lines):
- Explore -> Present candidates -> Grilling loop process
- "Hard constraints (do not propose violations)" section
enumerating ADRs 006/008/010/012/013/014/015/017/020/021 that
bound the design space
- Repointed at docs/glossary.md (not CONTEXT.md) and
docs/decisions/ (not docs/adr/)
- Exploration shortcuts specific to this repo: pnpm fallow,
pnpm coverage:diff, feature.manifest.ts, pnpm turbo boundaries
- Grilling loop side-effects target the right glossary section
and the next available ADR number (currently 022)
DEEPENING.md (93 lines):
- 4 dependency categories mapped to this repo's reality:
Cat 1 (in-process) -> entities/use-cases/presenters
Cat 2 (local-substitutable) -> our existing real + mock
adapter pattern (every port has both; mocks ARE stand-ins)
Cat 3 (remote but owned) -> cross-feature events via
IEventBus (E0/E1 rules)
Cat 4 (true external) -> Payload, Sentry/OTel, socket.io
(each constrained to its vendor-isolation seam by ADR)
- Seam discipline section recognises DI symbols + manifest entries
as concrete seams alongside .interface.ts files
- Testing strategy: replace not layer (matches ADR-020 L0 + L1)
- Conformance check command list at the end (typecheck, lint,
test --coverage, conformance, fallow:audit, coverage:diff)
INTERFACE-DESIGN.md (66 lines):
- Parallel sub-agent "Design It Twice" pattern preserved
- Every sub-agent brief MUST include glossary terms + ADR
constraints + manifest awareness
- Output items extended with "Manifest + binder impact" and
"ADR conflicts (if any)"
- Comparison axes include conformance impact + coverage delta
- Cross-feature moves flag release-please version-bump
implications (per ADR-021 commit-path targeting)
LANGUAGE.md (79 lines):
- Matt's 7 abstract terms preserved (module, interface,
implementation, depth, seam, adapter, leverage, locality)
- New "Mapping to this repo's identifiers" table — abstract
term -> concrete file shape (e.g. seam -> *.interface.ts +
DI symbol + manifest entry + <gen:*> anchor)
- Rejected framings extended with our reserved meanings
("boundary" stays the ESLint workspace-tag term; "service"
stays the DI port term)
Per user follow-up: vocabulary anchored so that "module" defaults
to "feature" in this repo (since features are our primary unit of
organisation). Abstract refactor sense survives only when the cross-
scale abstraction is the point. Glossary.md updated:
- "Feature" entry adds the "module = feature in refactor sense"
cross-link
- New "Architecture refactor vocabulary" section with 9 terms
(Module, Interface (refactor sense), Implementation, Depth,
Seam, Adapter, Leverage, Locality, Deletion test, Deepening)
— all framed so feature is the primary instance
- Flagged ambiguities entry for "module" rewritten to capture the
three coexisting senses (workspace package / Node ESM / refactor
vocabulary defaulting to feature); new entries for "seam" and
"adapter" to prevent drift with the existing "boundary" / "service"
/ "scope" reservations
Hooks updated:
- session-start.sh skills line lists the new skill
- prompt-context.sh adds a 10th keyword group firing on
refactor / deepening / shallow / architecture / seam / adapter /
interface design / design it twice — inject points at SKILL.md
+ summarises the vocabulary and hard constraints
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the user's gap: when `pnpm turbo gen feature <name>` scaffolds
a new feature, that feature must also be tracked by release-please —
otherwise it sits outside the versioning + changelog pipeline.
The generator now performs three release-please integrations:
1. **CHANGELOG.md seeded at v0.1.0** — new template at
templates/feature/CHANGELOG.md.hbs emits a baseline entry pointing
at ADR-021 + docs/guides/releasing.md so the consumer immediately
sees where future entries will appear.
2. **package.json version field bumped** — templates/feature/
package.json.hbs: "0.0.0" -> "0.1.0", matching the per-feature
baseline established when release-please was set up.
3. **Manifest + config registration via a new custom action** —
lib/release-please-utils.ts exports
registerFeatureInReleasePlease(repoRoot, name) which:
- Reads .release-please-manifest.json, adds
`"packages/<name>": "0.1.0"`, writes back with sorted keys
(root stays first, rest alphabetical) so diffs stay minimal
- Reads release-please-config.json, adds the per-package config
block (package-name, component, changelog-path), writes back
with the same sort
- Idempotent — re-running on an already-tracked feature is a
no-op
- Throws fast if either file is missing (ADR-021 requires
release-please to be set up BEFORE features can register)
The generator wires this in via a function action between the last
file `add` and the next-steps printout. Its return string surfaces
in the generator log so the user sees "Registered @repo/<name> in
release-please tracking".
Tested: 5/5 unit tests cover the happy path, idempotency, sort
order, and both missing-file error paths. Smoke-tested against the
real repo configs (adding a synthetic "demo" feature, then
restoring) — manifest entry appears in the correct sorted position;
config block has the right shape.
Future `pnpm turbo gen feature` invocations cannot leave a feature
untracked. Existing features (auth, blog, media, marketing-pages,
navigation) were registered manually when the release-please epic
landed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the user's ask: versioning + a changelog generated on merging
to main, building on the just-mandated Conventional Commits substrate
(CLAUDE.md Key Conventions).
Architecture: ADR-021. Cookbook: docs/guides/releasing.md.
Initial state — six tracked packages at v0.1.0:
- . -> template-vertical (tag: template-v...)
- packages/auth -> @repo/auth (tag: auth-v...)
- packages/blog -> @repo/blog (tag: blog-v...)
- packages/media -> @repo/media (tag: media-v...)
- packages/marketing-pages -> @repo/marketing-pages (tag: marketing-pages-v...)
- packages/navigation -> @repo/navigation (tag: navigation-v...)
Core packages, tooling, and apps are NOT independently versioned
(ADR-021 rationale: core bumps cascade; apps aren't consumables;
surfacing them would create noise without information).
Configuration:
- release-please-config.json - 6 tracked packages, hybrid scope,
pre-1.0 conservative bump policy
(feat: -> patch, feat!: -> minor),
conventional-commit type mapping
- .release-please-manifest.json - baseline 0.1.0 for all 6 packages
- .github/workflows/release-please.yml - googleapis/release-please-
action@v4 on push to main,
concurrency-gated, write
permissions for the rolling PR
Workflow: on every push to main, release-please scans commits since
the last release tag PER PACKAGE (using commit-path, not the
conventional-commit scope), updates a single rolling release PR with
version bumps + per-package CHANGELOG entries. Merging that PR cuts
per-package tags + GitHub releases.
CHANGELOG files seeded at v0.1.0 baseline:
- CHANGELOG.md (root)
- packages/<feature>/CHANGELOG.md (5 features)
Subsequent versions are appended by release-please from commit
history. Do not edit manually.
Visibility surfaces updated (every agent entry point):
- CLAUDE.md Read First + new "Versioning is hybrid" Key Conventions
bullet (with bump policy summary)
- AGENTS.md preamble - new "Releases:" callout alongside Commits
- docs/glossary.md - new Releasing section with 8 terms (Conventional
Commits, release-please, Hybrid versioning, Tag prefix, Rolling
release PR, Bump targeting, Pre-1.0 bump policy, Release-As trailer,
CHANGELOG.md)
- docs/README.md - guides tree updated with releasing.md
- .claude/hooks/session-start.sh - one-line release reminder
- .claude/hooks/prompt-context.sh - new keyword group for
release/version/bump/semver/tag prompts
Package.json version bumps:
- root: name "template" -> "template-vertical", version "0.1.0"
- packages/auth, blog, media, marketing-pages, navigation: "0.0.0" -> "0.1.0"
Root rename rationale: release-please tags use the package-name + the
component prefix; "template-vertical" matches the repo identity (and
the user's question preview).
First release-please PR after this lands will sweep all subsequent
post-baseline commits into 0.1.1 / 0.2.0 bumps as appropriate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The implementer + reviewer sandcastle prompts already enforce
conventional commits, but the convention was buried in those prompt
templates — agents not running through sandcastle (e.g. interactive
Claude sessions, ad-hoc fixups) had no visible signal that the format
is non-negotiable.
Four visibility surfaces now carry the signal:
- CLAUDE.md Key Conventions: new top bullet stating the spec
(<type>(<scope>): <subject>), the full type list, breaking-change
syntax (!), and three example commits
- AGENTS.md preamble: parallel callout alongside the vocabulary
and quality-gates notes
- .claude/hooks/session-start.sh: one-line reminder in the boot
pointers (every session now sees it at start)
- .claude/hooks/prompt-context.sh: new 10th keyword group fires
when a user prompt mentions commit/message/changelog/conventional,
injecting the full spec into the turn's context
Conventional Commits spec: https://www.conventionalcommits.org/
Verified: the prompt-context hook smoke-tests green when a prompt
containing "commit message" is piped in — it emits the conventional
commits pointer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The docs/ tree had no index — a fresh visitor landing at /docs/
through GitHub's file browser would see just a glossary.md and four
subdirectories with no orientation.
docs/README.md gives:
- Where to start (CLAUDE.md + AGENTS.md links + glossary.md as the
"resolve a term" entry point)
- The full directory tree annotated with what each path holds
- Doc-type table (Glossary / Architecture / ADR / Guide / PRD /
Epic-Story-Task) with lifetime expectations
- "When to put what where" routing rules (new decisions -> ADR,
new how-to -> guide, new term -> glossary, new initiative ->
PRD, new diagram -> architecture/<name>-explainer.html)
- Conventions section codifying the rules already followed
implicitly across the existing docs
No content is duplicated — every section either lists or routes to
existing files. Adds the missing navigation surface so the docs/
tree is discoverable from any entry point.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the last two staleness threads from the docs audit:
1. data-flow-explainer.html had four "Plan-9" / "post-Plan-9"
references in the masthead, lede, fix-up bullet, and bindDevSeed
blurb. Stripped — the architecture is now just "vertical-feature".
No content changes beyond the noun rename.
2. audit-and-compliance-explainer.html had a footer link to
../superpowers/specs/2026-05-11-audit-and-compliance-design.md
(archived to .archive/ earlier). Replaced with a link to
ADR-018, which is the durable design record.
3. data-flow-explainer.html + di-explainer.html were inter-linked
with audit + conformance explainers, but had no markdown entry
point — they were orphans from any guide or architecture doc.
architecture/overview.md gains a new "Interactive explainers"
section listing all four single-file HTML walkthroughs with one
sentence each, so they're discoverable from the documented
entry point. The four pages already cross-link to each other.
Final state (verified by repo-wide grep): zero "Plan-N" / "Phase-N"
/ "docs/superpowers/" references in docs/ (excluding .archive/ which
is gitignored). Legitimate `superpowers:brainstorming` skill refs
in agent-first-workflow-and-conformance.md are preserved — those
reference an external plugin, not a repo path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five spots across three guides referenced the original implementation
phasing (Plan-N / Phase-N nomenclature) from the template's setup era.
Now that the template-reset epic completed and the setup history was
archived, these refs are dead vocabulary for fresh consumers.
guides/scaffolding-a-feature.md:
- "real repo body is a Phase-1 stub" -> "real repo body is a stub"
- "## Phase-1 scope (intentionally limited)" -> "## Scope (intentionally limited)"
- "manually authored as part of Phase-2 wiring" -> "manually authored
as part of the post-scaffold wiring"
guides/adding-a-feature.md:
- "the generator's Phase-1 scope doesn't fit" -> "the generator's
default scope doesn't fit"
guides/tdd-workflow.md:
- "**File naming convention (post-Plan-8):**" -> "**File naming
convention:**"
The Plan-9 refs in docs/architecture/data-flow-explainer.html will be
handled separately along with that file's other staleness in the
next commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The docs/superpowers/{specs,plans}/ directory was archived to .archive/
in an earlier session (and .archive/ is gitignored). Every md link
into that path is now a broken reference for anyone consuming the
template fresh.
Stripped:
- ADR-011: **Spec:** header line
- ADR-015: **Spec:** + **Plan:** header lines
- ADR-016: **Spec:** + **Plan:** header lines + footer "Spec —"
bullet (the design rationale is captured in the ADR body itself)
- ADR-017: **Spec:** + **Plan:** header lines
- ADR-018: **Spec:** + **Plan:** header lines
- guides/realtime.md: inline "the full spec" link + footer
[Spec] entry (folded its description into the ADR-016 entry)
- guides/events-and-jobs.md: inline "the full spec" link
- architecture/vertical-feature-spec.md: stale "Deleted" subsection
referencing docs/superpowers/plans/*
Updated:
- glossary.md "PRD" entry: clarified status flow now matches the
shipped pnpm work prd-ship lifecycle (draft -> in-review ->
approved -> shipped); removed the parenthetical pointing at
docs/superpowers/specs/ as a definition of "spec"
- glossary.md "spec" flagged-ambiguity: rewritten to reflect that
durable design lives in ADRs (docs/decisions/adr-NNN-*.md) and
implementation seeds live in PRDs (docs/work/prds/*.prd.md) —
"spec" should be avoided in this template
Preserved (legitimate refs to the SuperPowers plugin, not the dir):
- agent-first-workflow-and-conformance.md mentions of
`superpowers:brainstorming` — these reference the external
plugin skill, not a file in the repo
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The docs/scaffolding/ directory held two how-to guides (core-package
+ core-ui-component generator references). Both are operationally
identical in shape to docs/guides/scaffolding-a-feature.md — they
just live in a separate top-level docs directory. Consolidating
removes one directory + makes the three scaffolding guides
discoverable as siblings.
Moves (via git mv to preserve history):
docs/scaffolding/core-package-generator.md
-> docs/guides/scaffolding-core-package.md
docs/scaffolding/core-ui-component-generator.md
-> docs/guides/scaffolding-core-ui-component.md
Empty docs/scaffolding/ directory removed.
AGENTS.md (only consumer of the old paths) updated to point at the
new locations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the PRD-lifecycle gap surfaced by the user: when sandcastle
finishes an epic's last task, the seed PRD should auto-flip from
approved -> shipped. Builds the mechanism, wires it into the work
CLI + state index + reviewer prompt + docs.
scripts/work/prd-ship.mjs (new):
- parseFrontmatter / serializeFrontmatter — minimal YAML-ish parser
sufficient for PRD frontmatter (scalar + list shapes)
- flipPrdStatus — pure function: takes PRD text, returns new text
with status=shipped + shipped=<date> + optional shipping-commits.
Refuses to flip draft, idempotent fail-soft on already-shipped,
rejects unexpected statuses
- deriveShippingCommits — best-effort git log of the linked epic
folder for the --auto-commits flag
- findPrdPath — id -> path lookup under docs/work/prds/
- runCli — wiring for `pnpm work prd-ship <id> [--commits|--auto-commits]`
scripts/work/prd-ship.test.mjs (new, 17 tests):
- Frontmatter parser handles scalars + lists + missing frontmatter
- flipPrdStatus covers all transitions + refusals + body/key preservation
- findPrdPath + serializeFrontmatter coverage
scripts/work/state-builder.mjs:
- Epic entries gain a `prd` field
- New computeNeedsPrdShip surfaces epics done with PRD status not yet
shipped: state.needs_prd_ship[] with action commands
scripts/work/cli.mjs:
- New subcommand `pnpm work prd-ship <id>`
.sandcastle/reviewer.prompt.md:
- "Epic close-out: PRD status flip" section instructing reviewer to
check _state.json.needs_prd_ship and run the suggested action
- JSON output extends with prd_shipped: "<id>" | null
docs/work/README.md:
- "PRD lifecycle" section documenting the 4 statuses + auto-flip
Future PRDs follow the lifecycle automatically: decomposer refuses
draft, human flips to approved, sandcastle ships the epic, reviewer
runs prd-ship on the final task, PRD lands as shipped with its
commit trail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the staleness gap after the 10-commit coverage epic shipped.
Doc sync (item 1 from the user's choice):
- CLAUDE.md Quick Start: adds pnpm coverage:aggregate / coverage:diff
/ mutate to the command listing
- CLAUDE.md: new "Sibling architecture: coverage (ADR-020)" section
after the conformance gate table — captures the 4-layer table +
points at docs/guides/coverage.md + ADR-020 + says agents must run
coverage:diff before reporting complete
- AGENTS.md preamble: now lists coverage as a parallel multi-latency
quality system alongside conformance, with the same gate / latency
framing
- PRD frontmatter: status draft -> shipped + shipped date +
shipping-commits list (all 10 SHAs anchoring the trace)
- PRD findings table: each row gets a Resolution column citing the
commit that closed it; conclusion text updated to past tense
- ADR-020 implementation phasing: rewritten as a status table with
each step linked to the commit that shipped it + Boot-time
assertFeatureConformance explicitly marked Deferred with rationale
- docs/guides/coverage.md: removed "Boot wiring lands in the next
story" line; replaced with the deferral rationale + clarified
that two readers (vitest, coverage:diff) consume the manifest
Sandcastle prompts (item 2 from the user's choice):
- .sandcastle/implementer.prompt.md: new "Coverage gates" section
after the conformance-gates list, requiring `pnpm test --coverage`,
`pnpm coverage:aggregate`, and `pnpm coverage:diff` to all pass
before reporting `complete`. Machine-readable JSON shape of
coverage:diff documented (status / uncovered[] / kind enum), with
explicit instructions on how to interpret each kind. Allowlist
expansion requires justification + test.
- .sandcastle/reviewer.prompt.md: AC coverage relabeled to "AC
coverage (acceptance criteria, not test coverage)" to disambiguate;
new check #7 "Coverage gates (ADR-020)" requiring CI's
Coverage — diff (L1) step green + per-layer thresholds met +
no silent allowlist expansion + manifest band drift detection.
Effect: future agent runs through sandcastle now treat coverage as a
first-class blocking gate, parallel to conformance. PRs no longer
discover coverage failures only via CI; the implementer is required
to check before reporting done, and the reviewer is required to
verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the per-layer threshold gaps surfaced by the 2026-05-13 PRD
audit. After this commit all five features pass their declared
100%/100%/95%/100% bands on entities + use-cases + controllers.
media (was: missing @vitest/coverage-v8 + missing vitest config block +
one controller at 86.66% lines / 75% branches)
- Added @vitest/coverage-v8 dev dep
- Applied the standard helper-driven vitest config
- Declared the coverage section in feature.manifest.ts
- Added 2 tests to list-media.controller.test.ts covering the
InputParseError branch (unknown fields + invalid limit)
- Now: 16 files / 80 tests / 97.12% / controllers 100%
marketing-pages (was: get-site-settings.controller at 93.54% lines /
90.9% branches)
- Added 1 test to get-site-settings.controller.test.ts covering the
InputParseError branch on unknown fields
- Now: 22 files / 68 tests / 95.66% / controllers 100%
navigation (was: entities/errors/common.ts at 50% function hits +
get-header.controller at 86.66% lines / 80% branches)
- Root cause: InputParseError class never instantiated in any test
- Added 2 tests to get-header.controller.test.ts covering the
InputParseError branch + verifying the Zod cause is preserved.
One test exercises both gap files at once (controller throws,
InputParseError class is constructed).
- Wired navigation/vitest.config.ts through the shared helper
- Declared the coverage section in feature.manifest.ts
- Now: 11 files / 45 tests / 98.04% / entities + controllers 100%
All 5 features now drive thresholds from the manifest via the helper.
The duplication problem the keystone eliminates is gone.
Repo-wide via `pnpm coverage:aggregate`:
- statements 95.87% (lh 2994 / lf 3123)
- branches 88.91% (brh 433 / brf 487)
- functions 100% (fnh 142 / fnf 142)
- lines 95.87%
`pnpm coverage:diff -- --base HEAD~1` reports status: pass.
coverage/summary.json refreshed in the same commit so the trend
captures the post-unification state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands L3 of the agent-first coverage architecture (ADR-020) — the
mutation-testing layer. Stryker on entities + use-cases (the pure
business-logic surface) catches the third dimension of test quality:
tests that exist + execute the code but assert nothing.
Deps (root devDependencies):
- @stryker-mutator/core ^8.7.0
- @stryker-mutator/vitest-runner ^8.7.0
Shared base: packages/core-testing/stryker.base.json
- testRunner: vitest (uses each feature's vitest.config.ts)
- mutate: src/entities/** + src/application/use-cases/** (excludes
tests, factories, contracts)
- thresholds: high 90 / low 80 / break 80
- reporters: progress + html + json (reports/mutation/{index.html,
mutation.json})
- incremental mode enabled, concurrency 4, timeout 10s
- exposed via @repo/core-testing/stryker.base.json subpath export
Per-feature config: packages/auth/stryker.config.json
- 4-line file that extends the shared base
- Proof-of-concept; other features get a config when L0 unification
closes their existing test gaps
Driver: scripts/coverage/mutate.mjs (zero-dep Node ESM)
- discoverStrykerConfigs: walks packages/* and apps/* for
stryker.config.json
- Supports --filter <name>, --since <ref> (incremental), --json
- Runs Stryker per-feature via node_modules/.bin/stryker run
- Surfaces per-package pass/fail summary; exits 1 on any failure
- Tests: scripts/coverage/mutate.test.mjs (3 tests, all green)
CI: .github/workflows/mutation-nightly.yml
- Cron at 02:30 UTC + workflow_dispatch with filter input
- Uploads reports/mutation/** as artifact (30-day retention)
- On failure, opens a tracking issue labelled mutation-testing
- permissions: contents: read, issues: write
- 60-min timeout (Stryker is slow by design)
Generator: turbo gen feature now scaffolds stryker.config.json from
turbo/generators/templates/feature/stryker.config.json.hbs — new
features ship mutation-ready out of the box.
Guide: docs/guides/coverage.md L3 section fleshed out with run
syntax, config shape, base config inventory, CI behavior, and a
"what you're looking for" primer on mutation scores.
Lockfile churn: pnpm regenerated the lockfile for the new deps;
~5K-line net reduction is collateral (pnpm version drift) but
mechanical.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the day-to-day cookbook for the 4-layer coverage architecture
(ADR-020) and threads it into the discovery path:
docs/guides/coverage.md (new):
- 4 layers at a glance + when each fires
- Single-source-of-truth pattern (feature.manifest.ts coverage:
section) and the three readers (vitest, assertFeatureConformance,
coverage:diff)
- Daily workflow: pnpm test --coverage -> aggregate -> diff
- How to read a failure (stderr human + stdout JSON examples)
- How to fix uncovered slices (TDD walkthrough)
- The full allowlist (test files, configs, docs, scripts, dev
tooling, per-feature excludes)
- Adjusting bands (manifest-first, when to override vitest)
- CI behavior (two workflows: validate + coverage-snapshot)
- Reading the committed trend via git log -- coverage/summary.json
- Mutation testing primer (L3, opt-in, scope, lands in next story)
- Troubleshooting
CLAUDE.md Read First gets the new guide pinned between audit and
template-tiers, with the L0-L3 layer summary inline so agents see the
shape at a glance.
Feature generator updates (turbo/generators/templates/feature/):
- feature.manifest.ts.hbs: new `coverage:` block at <gen:coverage>
anchor scaffolded with the documented defaults + mutationTargets
- vitest.config.ts.hbs: now uses vitestThresholdsFromBands(
DEFAULT_COVERAGE_BANDS) instead of the duplicated literal — new
features ship conformance-compliant by default
Next features generated via `pnpm turbo gen feature` are coverage-
aware from the first commit: bands declared in manifest, vitest
config consumes the helper, no duplication to drift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrates blog and marketing-pages to the same pattern auth landed in
f7baa8b: vitest config consumes vitestThresholdsFromBands(
DEFAULT_COVERAGE_BANDS) instead of the duplicated hand-written
thresholds block, and each manifest declares its coverage section
explicitly.
Verified semantically identical to the previous hand-written
thresholds — the same numbers come out the other end of the helper.
No new regressions:
- blog: 89 tests, 96.33% overall, all bands green
- marketing-pages: 67 tests, 95.28% overall — controllers/ shows a
real 93.54% lines / 90.9% branches gap that has been there since
before this refactor (the previous hand-written threshold was the
same 100%/95%). This is one of the L0 unification work items
listed in the PRD's findings; capturing here as the third feature
with real test gaps (navigation, media, marketing-pages).
Three of five features now drive their vitest thresholds from the
manifest helper: auth ✓, blog ✓, marketing-pages ✓. Navigation and
media stay on the legacy config until L0 unification closes their
test gaps (touching them would expose the same failures and add
nothing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-workflow split per ADR-020:
.github/workflows/ci.yml (existing, extended):
- checkout now uses fetch-depth: 0 so coverage:diff can resolve
origin/<base-ref>...HEAD against the PR's base branch
- new step "Coverage — aggregate (L2)" runs after the test step
(with `if: always()` so the artifact still captures partial state
on test failures)
- new step "Coverage — diff (L1)" runs only on pull_request events,
diffing against origin/${{ github.base_ref }}
- artifact upload extended to include the aggregated
coverage/lcov.info and coverage/summary.json alongside the
per-package files
.github/workflows/coverage-snapshot.yml (new):
- dedicated workflow with `permissions: contents: write` so it can
commit the aggregated coverage/summary.json back to main after
each merge — the committed trend store (ADR-020 L2)
- runs full test + aggregate, then commits summary.json only if it
actually changed (commit body marked [skip ci] so the snapshot
doesn't recurse into itself)
- concurrency: coverage-snapshot ensures only one snapshot at a time
This closes the CI side of the coverage architecture. PRs now fail
fast when changed lines are uncovered, and main's trend history
accumulates automatically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands L2 of the agent-first coverage architecture (ADR-020) — the
aggregated trend store.
Script: scripts/coverage/aggregate.mjs (zero-dep Node ESM)
- discoverLcovs: walks packages/* and apps/* for coverage/lcov.info
- normalizeLcov: rewrites SF entries from package-relative (vitest's
output) to repo-relative, so the merged file matches git diff paths
- summarizeLcov: computes statement/branch/function/line percentages
from LF/LH/BRF/BRH/FNF/FNH summary records
- aggregate: merges all lcovs and returns mergedLcov + summary
- Writes coverage/lcov.info (gitignored — large) and
coverage/summary.json (committed — trend via git log -- ...) with
timestamp, short commit SHA, repo + per-package percentages
Test surface: scripts/coverage/aggregate.test.mjs (10 tests, all green)
- Fixtures at __fixtures__/aggregate-pkg-a.lcov +
aggregate-pkg-b.lcov (synthetic, structured to make percentages
deterministic)
- Covers: path normalization (prefix, absolute, double-prefix
avoidance), summary computation (percentages, zero-division,
rounding), discovery (packages + apps, missing dirs), full
aggregation in a tmp repo
Wired:
- root package.json adds "coverage:aggregate" script
- .gitignore restructured: per-package coverage/ stays ignored,
aggregated /coverage/ ignored EXCEPT summary.json (committed for
trend) and .gitkeep markers
L1 allowlist fix folded in (scripts/coverage/diff.mjs):
- The previous (^|/)coverage/ regex accidentally caught
scripts/coverage/* — replaced with anchored patterns
(^coverage/, ^packages/*/coverage/, ^apps/*/coverage/)
- Allowlist scripts/ and turbo/generators/ since they're dev tooling
tested via node --test, outside vitest's v8 lcov pipeline
Smoke-tested end-to-end:
- pnpm coverage:aggregate merged 3 lcovs (auth + media + navigation
from this session's earlier runs), repo coverage 95.22% statements
- pnpm coverage:diff against HEAD~1 with the new merged lcov reports
PASS — all 6 diff files correctly allowlisted
First committed snapshot of coverage/summary.json lands with this
commit, anchoring the trend history at this state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands L1 of the agent-first coverage architecture (ADR-020) — the
cover-the-diff gate. Reads a merged lcov + git diff against a base
ref, asserts every changed *executable* line was exercised.
Script: scripts/coverage/diff.mjs (zero-dep Node ESM)
- parseLcov: SF -> Map<line, count>; only DA records read
- parseGitDiff: parses --unified=0 output into Map<file, Set<line>>
- computeDiffCoverage: cross-references both, emits result tree
- Allowlist of paths that don't gate (tests, configs, docs, .sh,
DI bootstrap, interfaces, CMS, factories, contracts, UI)
- Path matching handles three lcov path conventions: absolute,
repo-relative, and per-package relative
- CLI flags: --base (default origin/main), --lcov (default
coverage/lcov.info), --json (suppress stderr summary)
- stdout: machine-readable JSON for the dispatch loop
- stderr: human summary
- Exit 0 on pass, 1 on fail or error
Test surface: scripts/coverage/diff.test.mjs (14 tests, all green)
- Fixtures at scripts/coverage/__fixtures__/{sample.lcov,sample-diff.patch}
- Covers: lcov parsing, diff parsing, pass path, uncovered lines,
non-executable line skipping, no-coverage-data detection,
allowlist filtering, end-to-end mixed case, path matching
Wired:
- root package.json adds "coverage:diff" script
- .gitignore anchored so per-package coverage/ stays ignored but
scripts/coverage/ stays tracked
Smoke-tested end-to-end against packages/auth/coverage/lcov.info —
correctly skips shell scripts + manifest files (via allowlist + path
suffix match), correctly flags files not present in the per-package
lcov (which is expected; full repo coverage needs the L2 aggregate
that the next story lands).
CI integration deferred to the L2 aggregate story (the merged
coverage/lcov.info this script reads doesn't exist yet — pnpm
coverage:aggregate produces it).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First implementation milestone of the agent-first coverage architecture
(ADR-020, PRD 2026-05-13). Lands the keystone — coverage bands as a
typed declaration in feature.manifest.ts plus a helper that derives
vitest threshold shapes from them.
New file packages/core-shared/src/conformance/coverage.ts (self-
contained, no relative imports — loadable at vitest config time):
- CoverageBand / CoverageBands / CoverageManifest / VitestThresholds
types
- DEFAULT_COVERAGE_BANDS (baseline 80/75/80/80; entities 100/100/100/
100; use-cases + controllers 100/95/100/100) — matches ADR-011
- DEFAULT_MUTATION_SCORE (80) + DEFAULT_MUTATION_TARGETS (entities +
use-cases)
- getCoverageBands / getMutationConfig — manifest -> resolved bands,
with default fallback for missing layers
- vitestThresholdsFromBands / vitestThresholdsFromManifest — convert
to vitest's coverage.thresholds shape with the layer-to-glob
mapping
define-feature.ts gains the optional coverage field on FeatureManifest
(imports its type from coverage.ts to avoid a relative-import cycle
at config-load time).
Exposed via two subpaths: @repo/core-shared/conformance (re-exports
for source/test code) and @repo/core-shared/conformance/coverage
(direct subpath safe to load from vitest configs, bypasses the index
re-export chain that Node ESM doesn't auto-extension-resolve).
Auth wired as proof-of-concept:
- packages/auth/src/feature.manifest.ts declares its coverage section
- packages/auth/vitest.config.ts imports the helper +
DEFAULT_COVERAGE_BANDS and emits thresholds via
vitestThresholdsFromBands(DEFAULT_COVERAGE_BANDS) — no more
hand-maintained per-glob thresholds block.
Verified: 175/175 tests pass; 14/14 typechecks clean; auth coverage
green (21 tests, 93.77% overall, all per-layer 100% bands hold).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Architecture record for the agent-first coverage initiative seeded by
the 2026-05-13 PRD. Captures the durable decisions:
- 4-layer architecture (L0 vitest, L1 diff, L2 aggregate, L3 mutation)
- Manifest-driven coverage band as single source of truth (vitest +
assertFeatureConformance + pnpm coverage:diff all read from it)
- Cover-the-diff (changed lines), not cover-the-new-code
- Committed coverage/summary.json (no SaaS), trend via git log
- Mutation testing scoped to entities + use-cases, on-demand only
- Machine-first output format (JSON stdout, human stderr)
Glossary gets a new "Coverage" section with 7 entries (coverage band,
L0-L3 layers, diff coverage, mutation testing, mutation score,
coverage/summary.json), plus two relationship rows and a flagged
ambiguity for "coverage" qualifiers.
prompt-context.sh hook gets a 9th keyword group — when a prompt
mentions coverage / uncovered / lcov / mutation / stryker, the
relevant ADR + guide path are injected as additional context for
the turn.
This is the documentation layer of the coverage epic. Implementation
(manifest schema, vitest auto-derive, scripts, boot assertion,
mutation tooling) lands in subsequent stories.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the brainstorm convergence (2026-05-13) into a draft PRD that
seeds the implementation epic. Decisions captured:
- 4-layer coverage architecture (L0 vitest thresholds, L1 diff coverage,
L2 aggregate summary.json, L3 mutation testing on entities + use-cases)
- Manifest-driven coverage band as the single source of truth — vitest
config, assertFeatureConformance boot check, and pnpm coverage:diff
all derive expectations from feature.manifest.ts
- Cover-the-diff (every changed line exercised) not cover-the-new-code
- Aggregate trend committed as coverage/summary.json; no SaaS dep
- Stryker for mutation, on-demand only, NOT in default pnpm test
L0 verification findings (also captured in the PRD):
- auth / blog / marketing-pages: green (100% layer bands hold)
- navigation: real test gaps in entities + controllers (~86%)
- media: missing @vitest/coverage-v8 dep + vitest.config has no
coverage block at all (entirely absent — the most extreme case of
the duplication problem the manifest-driven keystone eliminates)
Status remains draft until the human review pass. Decomposer will
refuse to run on draft.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Project-level Claude Code hooks committed to .claude/settings.json with
scripts under .claude/hooks/. Three tiers:
Tier 1 — hard guards (exit 2 to block the tool call):
- bash-guard.sh: blocks bypass flags (verify-skip, sign-skip), forceful
push variants, destructive resets, force clean, working-tree-wipe
checkouts/restores, force branch delete, amend, and rm -rf against
root or home. Reinforces CLAUDE.md Git Safety Protocol.
- generator-first-nudge.sh: blocks creating a new top-level
packages/<name> or apps/<name> directory by hand. Allows working
inside an existing package. Reinforces the non-negotiable
generator-first rule.
Tier 2 — context injection (stdout becomes additional context):
- session-start.sh: prints glossary, AGENTS.md, workflow CLI, and
conformance pointers on session boot.
- prompt-context.sh: keyword-matches the user prompt against eight
concept groups (events, realtime, audit, instrumentation, manifest,
workflow, DI, boundaries) and injects the relevant ADR + rule
pointers for the turn.
Tier 3 — side-effect automation:
- post-manifest-edit.sh: when Edit/Write touches feature.manifest.ts,
prints the manifest-first ordering reminder plus the per-feature
verify commands.
- stop-check-manifest-tests.sh: at agent Stop time, if the working
tree has manifest changes but no sibling test changes, exits 2 to
force continuation. Loop-guarded via stop_hook_active.
All hooks are bash + jq, use CLAUDE_PROJECT_DIR for safety, and were
smoke-tested end-to-end (block + allow paths both verified).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously .claude/settings.local.json was only excluded by the user's
global ~/.config/git/ignore — fresh clones of this template would have
accidentally committed per-user Claude Code settings. Add an explicit
project-level rule (*.local, *.local.*, **/settings.local.json) so the
template ships with the protection baked in.
Also strips two missed "phase" residuals from
core-shared/instrumentation/otel/init-server-node.ts (the word leaked
through the earlier sweep — generic future-work qualifier, not a
setup-history reference, but cleaner without it).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs/glossary.md is the shared vocabulary source for humans and agents.
Resolves every cross-cutting term used in this repo (feature, use case,
manifest, slice, conformance, dispatch, ...) with one-sentence definitions,
relationships, and flagged ambiguities. Linked from CLAUDE.md "Read First"
and AGENTS.md preamble so every session loads it early.
.claude/skills/ installs four mattpocock skills adapted to this monorepo:
- to-prd: writes to docs/work/prds/ with the repo's PRD frontmatter +
merged user-stories/implementation/testing sections
- grill-with-docs: points at docs/decisions/ + docs/glossary.md; adds
feature.manifest.ts + conformance-rule cross-checks
- grill-me: minor — adds pnpm work / fallow / manifest shortcuts
- handoff: adds the repo's specific don't-duplicate artifacts list
Also fixes a missed "Phase-1" residual in CLAUDE.md's Read First section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final sweep for setup-process bookkeeping not caught by template-reset-v1.
ADRs drop Plan-N qualifiers; spec collapses the historical 11-phase
migration table; scaffolding guide drops "Phase added" column; comment
prefixes referencing R-numbers in test describes / eslint inline comments
are normalized. Architecture-level rule IDs (R40, R52, E0, J0, etc.) are
preserved where they serve as stable cross-references in ADRs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark epic + story status done, check all task boxes, rebuild _state.json.
All checks green: typecheck, tests (360 passing), lint, turbo boundaries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Regenerate audit + realtime core-package e2e snapshots (template
Phase-label changes altered file hashes)
- Fix pre-existing lint error in auth authentication.service.ts:
rename unused params to _user / _sessionId, drop stale eslint-disable
comments that were on wrong lines
- Mark story tasks 1-9 done; rebuild _state.json
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename docs/decisions/adr-012-lazar-conformance.md → adr-012-feature-conventions.md
- Strip "Lazar", "Plan 8/9/10/11", "refactor-logs" refs from all ADRs,
architecture docs, HTML explainers, and feature/core AGENTS.md files
- Update all incoming links in docs/, packages/*/AGENTS.md, HTML explainers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the decision to adopt @ai-hero/sandcastle as the orchestration
substrate for agent-driven development in this template. Records the
8-point decision (workspace dep, .sandcastle/ prompts, Dockerfile,
dispatch.mjs orchestrator, planning vs execute modes, generator-first
reviewer check, bring-your-own-key, per-task max-attempts), the four
alternatives considered (bare CLI / Copilot Workspace / custom-from-
scratch / no orchestrator), and four trade-offs (external dep, token
cost, Docker dependency, manual state mutation in v1).
Surfaces the decision at the top of README.md and AGENTS.md so new
contributors see the agent-driven framing before they hit the package
map or daily commands.
Adds a "Using Sandcastle for agent dispatch" section between the gate
table and Troubleshooting. Covers when to use / not use sandcastle,
prerequisites (Docker + agent API key + .sandcastle/ config), the
dispatch flow, a worked end-to-end example (plan → execute → review →
manual state mutation), troubleshooting (env vars, Docker, timeouts,
rejection modes), and a cost-aware planning-only variant.