Releases become a first-class data entity
Gitpulse now tracks releases as a first-class entity alongside stories. This foundation adds the data shape, write-time validation, and restore-from-site plumbing for Phase B's analyzer to write into.
Release notes just got a proper home in the codebase. Gitpulse now treats releases as a first-class data entity alongside its existing story system — not a discriminated variant, but a sibling with its own storage path and validation rules.
Before this work, the system had no place to store what changed between two tags. Now releases aggregate the PRs merged since the last deployment, with top stories denormalized so detail pages render without chasing down every linked story file. An inputsHash on each release lets the analyzer skip regenerating content when nothing upstream changed.
The storage layer lives in action/src where it belongs: types and schemas define the shape, release-render handles writes with schema validation, and site-fetcher can pull prior releases from a deployed site. Tags containing slashes — common in release naming conventions like "release/v1.0.0" — are properly encoded for the filesystem.
This is the first of three phases. The plumbing is ready for Phase B's analyzer to fetch from GitHub, match PRs by SHA, and call the LLM. Phase C will surface all this in the frontend.