Skip to content

feat(releases) phase B: analyzer release pass#6

Merged
znat merged 2 commits intomainfrom
feat/releases-phase-b-sync
May 2, 2026
Merged

feat(releases) phase B: analyzer release pass#6
znat merged 2 commits intomainfrom
feat/releases-phase-b-sync

Conversation

@znat
Copy link
Copy Markdown
Owner

@znat znat commented May 2, 2026

Builds on Phase A (#5, merged). Wires releases end-to-end into the analyzer pipeline. No frontend changes yet — this PR produces the data; Phase C will render it.

What runs after the existing commit walk

  1. Restore prior releases from the deployed site via the extended `SiteFetcher` (Phase A).
  2. Fetch up to `releasesCap` (default 20) recent GitHub releases via plain `fetch()` against the REST API — no new dependency added.
  3. Match PRs in each release window via `compare(prev, this)` → SHA list → on-disk stories.
  4. Compute `inputsHash` over `(tag, sorted story IDs, aggregated stats)`. If an existing release file has the same hash, skip the LLM call.
  5. On hash miss, call the release editor (system prompt + zod schema lifted verbatim from `gitsky/apps/web/lib/release-edition-generator.ts`) → `{quip, releaseStory}` → assemble `Release` → `writeRelease` (with Phase-A write-time schema validation).
  6. Rebuild `data/releases/manifest.json` from final on-disk state.

Configuration

Workflow input Default Behavior
`releases-cap` `20` Max releases per run. `0` disables the pass entirely.
`include-prereleases` `true` Set false to exclude pre-releases from processing.

LLM failures fall back to `{quip: "Another one shipped.", releaseStory: ""}` — the analyzer never bails on one release.

Files

File Change
`action/src/github.ts` + `GitHubRelease` type, `fetchReleases()`, `fetchCompareShas()` via plain fetch (no new dep)
`action/src/release-llm.ts` NEW — `createReleaseEditor()` with lifted gitsky prompt + zod schema
`action/src/release-builder.ts` NEW — pure helpers: `matchStoriesForRelease`, `buildDraft`, `computeInputsHash`, `assembleRelease`
`action/src/category-helpers.ts` NEW — shared `primaryCategoryKey()`
`action/src/schemas.ts` + `ReleaseEditionOutputSchema` (zod) — the LLM output contract
`action/src/index.ts` + `processReleases` + `processOneRelease` orchestration
`.github/workflows/publish.yaml` + `releases-cap`, `include-prereleases` inputs forwarded as env vars

Tests

14 new release-builder cases — order independence of inputs hash, top-5 ranking, aggregate stats, first-release-no-predecessor, hash stability/divergence. 54 total action tests passing (was 40).

What's next

  • Phase C — frontend: components, routes, OG image, JSON-LD, sitemap, font-feed-vollkorn, homepage feed integration

Test plan

  • `yarn typecheck` clean
  • `yarn test` — 54/54 action + 25/25 site green
  • No site code touched; site behavior unchanged
  • CI green
  • Self-deploy still produces a valid site after merge (will write release files but no UI yet)

Summary by CodeRabbit

  • New Features

    • AI-driven release edition generation with structured output and fallback
    • Full release-processing pipeline: fetch, filter (optionally exclude prereleases), cap, compare, draft, and assemble releases
    • REST-based release fetching and improved repo/commit comparison
    • Workflow inputs to control release cap and prerelease inclusion
  • Tests

    • New unit tests validating release assembly, story selection, hashing, and metadata aggregation

Wires releases into the analyzer end-to-end. After the existing
PR/commit walk, the analyzer now:

1. Restores prior releases from the deployed site via the extended
   SiteFetcher (added in Phase A).
2. Fetches up to releasesCap (default 20) recent releases from GitHub
   via plain fetch() against the REST API — no new dependency.
3. For each release, calls compare(prev, this) to get the merge SHA
   list, matches against on-disk stories, builds a draft.
4. Computes inputsHash over (tag, sorted story IDs, stats). If an
   existing release file has the same hash, skips the LLM call entirely
   — idempotent across runs.
5. On hash miss, calls release-llm (lifted prompt + zod from gitsky's
   release-edition-generator.ts) to get quip + 2-3 paragraph release
   story, assembles the Release, writes it via writeRelease (with
   write-time schema validation from Phase A).
6. Rebuilds releases/manifest.json from final on-disk state.

Configuration:
- New workflow inputs releases-cap (default 20) and
  include-prereleases (default true)
- Forwarded as GITPULSE_RELEASES_CAP and GITPULSE_INCLUDE_PRERELEASES
- releasesCap=0 disables the releases pass entirely

LLM call falls back to "Another one shipped." quip when the provider
fails — analyzer never bails on a single release.

Added:
- github.ts: GitHubRelease type, fetchReleases() + fetchCompareShas()
  via plain fetch on the REST API
- release-llm.ts: createReleaseEditor() with the lifted gitsky prompt
  and ReleaseEditionOutputSchema
- release-builder.ts: pure functions matchStoriesForRelease,
  buildDraft, computeInputsHash, assembleRelease — all unit-tested
- category-helpers.ts: primaryCategoryKey() shared helper
- schemas.ts: ReleaseEditionOutputSchema (zod) + the .describe() text
  from gitsky's well-tuned tone constraints
- index.ts: processReleases + processOneRelease orchestration

Tests: 14 new release-builder cases (54 total in action workspace,
was 40). Site untouched.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 961edd1d-a5ea-4e9b-acb8-38526c77d246

📥 Commits

Reviewing files that changed from the base of the PR and between 147a6ac and 37e8718.

📒 Files selected for processing (2)
  • action/src/index.ts
  • action/src/release-builder.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • action/src/index.ts

📝 Walkthrough

Walkthrough

Adds a release-processing pipeline: new workflow inputs, REST GitHub release/compare support, release draft assembly and stable hashing, LLM-driven release edition generation with fallback, and orchestration to produce per-release JSON files and a final release manifest.

Changes

Release Processing Pipeline

Layer / File(s) Summary
Workflow Inputs & Env
.github/workflows/publish.yaml
Adds releases-cap (number, default 20) and include-prereleases (boolean, default true) and forwards them as GITPULSE_RELEASES_CAP and GITPULSE_INCLUDE_PRERELEASES to the analyzer step.
Data Types & Schemas
action/src/schemas.ts, action/src/github.ts
Adds ReleaseEditionOutputSchema/ReleaseEditionOutput for LLM output and a GitHubRelease interface mapping REST release fields.
REST GitHub Client
action/src/github.ts
Stores token on client; adds restJson helper; implements fetchReleases(owner,repo,limit) and fetchCompareShas(owner,repo,base,head) returning mapped releases and commit SHAs.
Category Helper
action/src/category-helpers.ts
Adds primaryCategoryKey(categories) returning 'misc' for empty arrays or the highest-score category key.
Release Draft Builder
action/src/release-builder.ts
Adds matchStoriesForRelease, toTopStory, buildDraft, computeInputsHash, and assembleRelease to rank matched PR stories, compute top-5 vs changelog partitioning, aggregate metadata, and produce a stable inputs hash.
LLM Edition Generation
action/src/release-llm.ts
Adds ReleaseEditionContext, assembleUserPrompt, createReleaseEditor(config) (selects Anthropic/OpenAI, configures structured output), and getFallbackEdition.
Orchestration & Wiring
action/src/index.ts
Main analyzer now conditionally runs processReleases when token present and cap>0; processReleases restores/prunes prior releases, fetches/sorts/filters GitHub releases, pairs each with predecessor, computes compare SHAs, and calls processOneRelease which reuses or generates editions and writes per-release JSONs and a rebuilt manifest.
Tests
action/src/release-builder.test.ts
Vitest suite validating SHA-based story matching, denormalized top-story conversion, draft building (top-5 partitioning, aggregates), inputsHash stability and order-independence, and release assembly.

Sequence Diagram

sequenceDiagram
    participant main as main()
    participant gh as GitHub API
    participant builder as Release Builder
    participant llm as LLM
    participant disk as Disk

    main->>gh: fetchReleases(owner, repo, cap)
    gh-->>main: [releases]
    main->>main: filter/prune & sort releases
    loop for each release (newest-first)
        main->>gh: fetchCompareShas(base, head)
        gh-->>main: [commit SHAs]
        main->>builder: matchStoriesForRelease(stories, SHAs)
        builder-->>main: matched stories
        main->>builder: buildDraft(matched stories, release info)
        builder-->>main: ReleaseDraft (with inputsHash)
        main->>disk: read existing release.json?
        alt inputsHash matches
            main->>disk: rewrite as skipped
        else inputsHash differs
            main->>llm: generateEdition(release context)
            alt LLM succeeds
                llm-->>main: {quip, releaseStory}
            else LLM fails
                main->>main: getFallbackEdition()
            end
            main->>builder: assembleRelease(draft, edition)
            main->>disk: write release.json
        end
    end
    main->>disk: rebuild and write release manifest
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
Releases hop from tag to tale with cheer,
Stories matched, hashed, and polished here,
The LLM hums a quip, the manifest grows,
From GitHub fields to JSON rows,
A rabbit nods — another ship is near.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(releases) phase B: analyzer release pass' directly and specifically summarizes the main change: implementing the release analyzer pass as part of Phase B. It is clear, concise, and immediately conveys the primary purpose of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/releases-phase-b-sync

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@action/src/index.ts`:
- Around line 162-174: The code fetches exactly cfg.releasesCap releases then
filters prereleases and pairs each release with sortedNewestFirst[i+1], which
can drop the predecessor for the oldest kept item; update the logic around
gh.fetchReleases and the handling of cfg.includePrereleases so you fetch enough
releases to preserve the predecessor window (e.g. request more than
cfg.releasesCap — at least cfg.releasesCap+1 — or loop fetching until you have
cfg.releasesCap non-prerelease items when cfg.includePrereleases is false),
apply the prerelease filter before trimming to the cap, and mirror the same fix
in the other block referenced by sortedNewestFirst pairing (the 195-205 area) to
ensure the oldest retained release still has a valid predecessor.
- Around line 149-159: After restoring prior releases (after
fetcher.restorePriorReleases and using priorReleaseManifest), prune stale
release JSON files in cfg.releasesDir before regenerating the manifest: remove
files for releases that no longer meet current filters (e.g.,
includePrereleases), exceed releasesCap, or no longer exist upstream; implement
this by listing JSON files under cfg.releasesDir, parsing their release
IDs/tags, comparing against the current fetched manifest and config
(includePrereleases, releasesCap), and deleting any mismatches (or adding a new
helper like pruneRestoredReleases(manifest, cfg) in the same module to
encapsulate the logic) so the subsequent manifest rebuild only includes valid,
current releases.
- Around line 274-286: When inputsHash matches, don't early-return; instead copy
existing.quip and existing.releaseStory onto the in-memory draft (e.g., set
draft.quip = existing.quip and draft.releaseStory = existing.releaseStory) so
you can skip the LLM call but still proceed to rewrite the JSON file from the
fresh draft and update disk metadata; keep the existing variables (existingPath,
existing, draft, inputsHash) and ensure the code path that triggers the LLM is
bypassed while the file-write logic still runs to persist the new JSON.

In `@action/src/release-builder.ts`:
- Around line 17-23: matchStoriesForRelease is currently returning any story
whose sha matches shaList, which lets direct-push commits (no PR) through and
later yields prNumber: 0 in toTopStory; change the filter to only include
stories that are associated with a PR (e.g., require s.prNumber > 0 or
s.pr?.number > 0) when computing wanted matches from allStories and shaList so
only PR-backed stories enter the release set; update the same logic for the
other occurrence referenced (lines 36-40) where release matching is performed.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ad062a27-e78d-4926-a072-1a73f7c73eef

📥 Commits

Reviewing files that changed from the base of the PR and between 29ee401 and 147a6ac.

📒 Files selected for processing (8)
  • .github/workflows/publish.yaml
  • action/src/category-helpers.ts
  • action/src/github.ts
  • action/src/index.ts
  • action/src/release-builder.test.ts
  • action/src/release-builder.ts
  • action/src/release-llm.ts
  • action/src/schemas.ts

Comment thread action/src/index.ts
Comment thread action/src/index.ts
Comment thread action/src/index.ts
Comment thread action/src/release-builder.ts
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 2 file(s) based on 4 unresolved review comments.

Files modified:

  • action/src/index.ts
  • action/src/release-builder.ts

Commit: 37e8718712d64a04a3f2e40acc0440bdd71111fa

The changes have been pushed to the feat/releases-phase-b-sync branch.

Time taken: 4m 44s

Fixed 2 file(s) based on 4 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@znat znat merged commit fd92c99 into main May 2, 2026
2 checks passed
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