Commit Graph

401 Commits

Author SHA1 Message Date
81a791c5fd refactor(navigation): migrate binders to wireUseCase for getHeader
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 18:08:42 +00:00
dcc7edcd18 refactor(marketing-pages): migrate binders to wireUseCase for both use cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 18:04:34 +00:00
b93ce25b2a refactor(media): migrate binders to wireUseCase for all 3 use cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:59:59 +00:00
43d88be4bd refactor(blog): migrate binders to wireUseCase for all 3 use cases
Replace inline withSpan + withCapture blocks for getArticles,
getArticleBySlug, and createArticle in both bind-production and
bind-dev-seed with wireUseCase calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:55:43 +00:00
88b41798d6 refactor(auth): migrate use-case binders to wireUseCase
Replace inline withSpan + withCapture blocks for signIn, signUp, and
signOut use cases in both bind-production.ts and bind-dev-seed.ts with
wireUseCase calls. Removes 27 lines of boilerplate per binder file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:51:16 +00:00
1bbe866a5c feat(core-shared): add wireUseCase helper to conformance barrel
Encapsulates withSpan(withCapture(withAudit?(factory(deps)))) composition
and container binding into a single helper, eliminating the structural
boilerplate clone groups repeated across every feature binder pair.

Callers pass { container, symbol, factory, deps, feature, layer, name,
tracer, logger, auditLog? } and get back a fully brand-stacked, container-
bound wired value. Idempotent: unbinds an existing symbol before rebinding.

withAudit lives in core-audit which core-shared cannot import (dependency
inversion: core-audit depends on core-shared). The audit path here replicates
the same semantics — forwarding wrapper + __audited brand — without the
circular dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:28:57 +00:00
b96cce5d74 feat: hybrid versioning + automated CHANGELOG via release-please
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>
2026-05-13 17:17:16 +02:00
bf0b049583 feat(coverage): L0 unification — close test gaps in nav, media, mp
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>
2026-05-13 16:39:08 +02:00
6428f10b82 feat(coverage): pnpm mutate (Stryker) + L3 implementation
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>
2026-05-13 16:31:30 +02:00
15db9c48cb refactor(blog,marketing-pages): wire coverage helper + declare manifest bands
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>
2026-05-13 14:13:08 +02:00
f7baa8bfd1 feat(core-shared/conformance): manifest coverage schema + vitest helper
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>
2026-05-13 13:51:13 +02:00
5d61343068 chore: gitignore *.local files at project level + strip phase residuals
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>
2026-05-13 13:10:53 +02:00
2edc76002a refactor(docs): strip residual Phase/Plan setup-history references
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>
2026-05-13 10:28:31 +02:00
318dc05b6e refactor: strip residual R-number test descriptions + Lazar URL from ADR-013 2026-05-13 10:22:17 +02:00
2ceaa08944 chore(template-reset): update snapshots, fix lint, rebuild state
- 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>
2026-05-13 10:17:50 +02:00
841655573b docs(adr): rename ADR-012 — drop Lazar; update title + content + cross-refs
- 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>
2026-05-13 10:07:37 +02:00
17ae157365 refactor: strip Phase/Plan/R-number references from source comments 2026-05-13 09:51:45 +02:00
69d84a598d feat(core-eslint): wire frontend conformance rules into plugin + base
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:41:24 +02:00
159db9e542 feat(core-eslint): atomic-tier-import-direction rule
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:41:01 +02:00
f933ca74ff feat(core-eslint): component-must-have-test rule
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:40:40 +02:00
9ef545b714 feat(core-eslint): component-must-have-story rule
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:40:19 +02:00
f06f7dbadf feat(features): extend assertFeatureConformance to bind-dev-seed paths 2026-05-13 07:35:40 +02:00
171ed20527 refactor(core-eslint): readManifestSource delegates to AST parser 2026-05-13 07:34:00 +02:00
d83d97755e feat(core-eslint): parseManifestFully — AST-based full manifest extraction 2026-05-13 07:33:41 +02:00
79f96a94a1 feat(core-eslint): flip feature-must-have-manifest from warn to error 2026-05-13 00:10:48 +02:00
5fa7de48d8 feat(marketing-pages): conformance manifest + self-asserting bind-production 2026-05-13 00:10:29 +02:00
7953a44203 feat(navigation): conformance manifest + self-asserting bind-production 2026-05-13 00:10:03 +02:00
dc7aafb97f feat(media): conformance manifest + self-asserting bind-production 2026-05-13 00:09:36 +02:00
9bbc068498 feat(blog): conformance manifest + self-asserting bind-production 2026-05-13 00:09:08 +02:00
4d7b25fd15 feat(core-eslint): wire AST-aware conformance rules into plugin + base
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:52:31 +02:00
15e90820d2 feat(core-eslint): no-undeclared-audit rule
AST-aware ESLint rule that catches auditLog.record({ type: "X" }) calls
in use-case files where X is not declared in the matching
manifest.useCases[name].audits array.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:51:42 +02:00
3e2b096c3c feat(core-eslint): no-undeclared-event-publish rule
AST-aware ESLint rule that catches bus.publish("X") calls in use-case
files where X is not declared in the matching manifest.useCases[name].publishes array.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:51:06 +02:00
21a6822d55 feat(core-eslint): use-case name helper (file slug → manifest key) 2026-05-12 23:49:32 +02:00
7cfb78b99e feat(core-eslint): manifest AST parser for per-use-case attributes 2026-05-12 23:49:12 +02:00
b9ac64b36b feat(core-eslint): register conformance plugin + structural rules in base config 2026-05-12 23:24:01 +02:00
8a2c8db955 feat(core-eslint): conformance plugin module + ./plugin export 2026-05-12 23:23:20 +02:00
3117df1c14 feat(core-eslint): required-cores-installed rule 2026-05-12 23:22:43 +02:00
d585b59590 feat(core-eslint): usecase-must-have-test-file rule 2026-05-12 23:21:38 +02:00
b7bb37023f feat(core-eslint): feature-must-have-manifest rule
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:20:37 +02:00
6f8e75accb feat(core-eslint): workspace helper for conformance rules 2026-05-12 23:19:11 +02:00
41ffe9f787 feat(core-eslint): manifest source helper for conformance rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:18:02 +02:00
83f135b5e1 fix(conformance): withCapture propagates inner brands + chain test + docstring fixes 2026-05-12 23:00:50 +02:00
3a6115924b feat(auth): bindProductionAuth self-asserts conformance at tail
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:53:06 +02:00
5e32074c2e feat(core-shared/conformance): assertFeatureConformance helper
Adds boot-time check that every manifest-declared use case is bound
through withSpan (__instrumented) + withCapture (__captured), with
withAudit (__audited) enforced when audits[] is non-empty. Propagates
inner brands through withSpan so the outermost container-resolved
binding carries all brand markers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:49:18 +02:00
bbd9c1e856 feat(core-shared/conformance): re-export ConformanceError 2026-05-12 22:43:57 +02:00
49bd7e0782 feat(core-shared/conformance): ConformanceError class 2026-05-12 22:43:15 +02:00
1d8b30045a feat(core-audit): withAudit wraps and attaches runtime __audited marker 2026-05-12 22:42:16 +02:00
77fddcde78 feat(core-shared/instrumentation): withCapture attaches runtime __captured marker 2026-05-12 22:40:44 +02:00
9e21baf5fb feat(core-shared/instrumentation): withSpan attaches runtime __instrumented marker 2026-05-12 22:39:13 +02:00
a593962b3d feat(core-shared/conformance): runtime brand markers + isX predicates 2026-05-12 22:37:43 +02:00