Skip to content

feat(changelog): stronger release evidence and generated cover images - #77

Merged
nibzard merged 8 commits into
mainfrom
niko/changelog-v2
Jul 28, 2026
Merged

feat(changelog): stronger release evidence and generated cover images#77
nibzard merged 8 commits into
mainfrom
niko/changelog-v2

Conversation

@nibzard

@nibzard nibzard commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

What changed

Source evidence

  • Build changelog evidence from logical pull-request groups, enriched with PR context and changed files.
  • Expand the exact released steel-browser submodule range instead of treating the pointer update as the story.
  • Keep the previous generation cutoff authoritative, apply small repository-specific eligibility rules, and allow honest quiet weeks.
  • Use gpt-5.6-sol with low reasoning effort.
  • Add a side-effect-free historical preview mode for comparing a candidate with a previously published changelog.
  • Request reviews from nibzard and fukouda on generated changelog PRs.

Cover images

  • Vendor the changelog-imagegen tool into scripts/changelog/imagegen/ with its assets and all 71 of its tests, ported to bun test.
  • Ask the drafting model for a coverMotif scene description alongside the changelog body.
  • Render that motif into public/images/changelog/<n>.png and point the draft frontmatter at it: a gpt-image-2 background, an ordered dither, the fixed Figma card layout, quantized to PNG-8.
  • Pick the color grade from one of six time-of-day presets at random per card, so covers do not all land at the same hour. Overridable with --time-of-day or a free-form --color-grade, and recorded in the sidecar JSON.
  • Fall back to the placeholder image whenever a motif is missing or the render fails. A cover problem is reported in the PR body and the run log; it never fails the changelog run.

Why

The source repositories contain uneven commit messages, direct commits, sparse PR descriptions, and release promotion through a submodule. This change improves the evidence supplied to the changelog generator without requiring engineers to change how they work.

Cover images were made by hand for every previous changelog. Since the drafting model already holds the full picture of the week, it can propose the cover scene in the same call, at no extra model round trip.

Reviewing this PR

scripts/changelog/imagegen/** is vendored code. Every file has been diffed against the original tool with formatting normalized, and the only differences are one asset path (../assets/ to ./assets/), Biome reformatting, and the new time-of-day presets. Reviewer attention is better spent on scripts/changelog/cover.ts, the generate-changelog-draft.ts changes, scripts/changelog/prompt.md, and the two workflow files.

Live comparison

The replay found nine released logical change groups and retained four. Every filtered group and the application main versus release gap was spot-checked; no additional shipped customer news was missed. The only plausible fifth item was the false-504/JetStream fix deliberately removed from #35.

Impact

Starting with changelog #36, drafts should be based on stronger release evidence, omit routine/internal noise, and remain short when a week genuinely has little customer-facing news. Historical previews cannot update state, write a changelog, push a branch, or open a PR.

Each weekly run now also spends one gpt-image-2 image generation. Committed covers are 1420x800 PNG-8, roughly 200KB, against roughly 800KB for the hand-made covers they replace. CI and the changelog workflow install Playwright's Chromium, which adds about 30 seconds to each run.

Validation

  • bun run check, bun run typecheck, bun run validate-links, bun run build
  • bun test — 137 passing, including the 71 ported imagegen tests
  • Live historical replay of the feat: cookbook integration #35 generation window
  • Live rehearsal of the full non-preview path in a throwaway worktree: real GitHub collection, real model call, real image generation. It produced changelog-036.mdx pointing at a 1420x800 PNG-8 cover, with meta.json, llms.txt, and state.json all updated correctly, and touched exactly the four paths the workflow commits.

One dependency stays unverified until this runs in Actions: whether the OPENAI_API_KEY secret has gpt-image-2 access. If it does not, the cover step degrades to the placeholder and says so in the PR body, so the changelog still ships. A workflow_dispatch with open_pr: false settles it.

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 28, 2026 11:37am

Request Review

@nibzard

nibzard commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

/blade link BL-R7Y7TQ

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

Got it — github:nibzard is claiming a Blade identity.
Open the console and confirm it there. Nothing changes until you do.
If you did not start this, ignore it and reject the claim in the console.

@nibzard

nibzard commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

/blade review this pr

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

Blade cannot work for you yet, an operator has to approve you first.
Ask junhyoung@steelbrowser.com for the blade:spawn:main capability.
You can still attach this account meanwhile: send link <code> with a code issued from your console profile.

@nibzard

nibzard commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

/blade review this pr

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

I've kicked off a reviewer for PR #77 (niko/changelog-v2, draft, +5718/−280 across 36 files) rather than reviewing it myself since this isn't my PR and it's better to get an independent pass on a self-authored changelog-generation change.

One flag: the spawn came back with a warning that the reviewer role lacks github:clone under your current grants, so it may not be able to actually clone the repo to inspect the diff/code (only the PR metadata via API). If it reports back unable to do a full code review, that'll need an operator to add the grant, not a retry from me. I'll relay the verdict once it closes.

@blade-agent blade-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed the diff plus the full contents of the key files (not just the PR description): scripts/changelog/source.ts, scripts/generate-changelog-draft.ts, scripts/changelog/config.ts, scripts/changelog/cover.ts + imagegen/*, scripts/changelog/README.md, tests/changelog-v2.test.ts, scripts/changelog/state.json, and both changed workflow files. I don't have clone access in this environment, so I read/traced the code rather than executing bun test myself — the test file (tests/changelog-v2.test.ts) is substantial (~35 cases) and does exercise the claims below with mocked fetch, so 44 passing across the suite is plausible.

Claims checked against the code, not just the description:

  • Submodule range expansion (expandReleasedBrowserCommits + resolveSubmoduleRange/parseSubmodulePatch in source.ts): correctly parses the gitlink patch for base/head SHAs, falls back to the contents API at parent/commit refs when no patch is available, and validates both are full 40-char SHAs that differ. A behind compare status (pure rollback) is skipped with a warning; diverged correctly throws instead of silently dropping evidence, per the documented recovery flow in scripts/changelog/README.md. Verified with tests covering ahead/behind/diverged.
  • Cutoff authority / quiet weeks: resolveWindow anchors since to state.until unless an explicit override is given (and rejects an override later than the stored cutoff), replacing the old git-log-derived/DEFAULT_LOOKBACK_DAYS bootstrap approach entirely. main() only calls updateChangelogState after a draft is actually written — on "no commits", "no eligible groups", or "model returned nothing", it returns early without touching state, so the next run's since still starts at the last published cutoff and the window grows cumulatively. This matches the "honest quiet weeks" claim.
  • Historical preview side-effect freedom: in --preview mode, main() never calls updateChangelogMeta/updateChangelogLlms/updateChangelogState/appendGithubOutput, never writes under content/docs/changelog/, and never generates a cover image — all preview writes go to a fresh mkdtemp workspace (source-facts.json, excluded-groups.json, run.json, the draft + review.md). Nothing in the script shells out to git or touches GitHub write endpoints, so "can't push a branch or open a PR" is actually just "the script never tries to," which holds up. Also confirmed preview mode skips requesting an OpenAI key entirely when there are no eligible groups (a real quiet-week replay needs no OPENAI_API_KEY), matching the README.
  • Model/config: DEFAULT_OPENAI_MODEL = 'gpt-5.6-sol', DEFAULT_OPENAI_REASONING_EFFORT = 'low', both overridable via env vars validated against an explicit allow-list (throws on invalid effort). Workflow wires CHANGELOG_OPENAI_MODEL/CHANGELOG_OPENAI_REASONING_EFFORT through from repo vars.
  • Reviewer request: changelog-draft.yml now passes reviewers: nibzard / fukouda to peter-evans/create-pull-request, gated the same way as the rest of the PR-creation step.

Minor, non-blocking observations:

  1. fetchRepositoryCommits (used for the direct-mode repos: cookbook/leaderboard/cli/docs) still caps at 10 pages / 1000 commits and silently stops instead of erroring if a window has more activity than that — pre-existing behavior from before this PR, but the new cumulative "quiet week" window design makes long silent stretches (and therefore larger windows) more likely than before. Might be worth an explicit bound check like the one already used for the 3,000-file commit cap, so a truncated result fails loudly instead of quietly.
  2. The internal_only/routine_maintenance classifiers in source.ts (classifyGroup) rely on hardcoded keyword lists (jetstream, nats, puppet-master, fleet controller, nomad, gcp teardown, etc.) and path prefixes. That's consistent with the PR's own framing ("small repo-specific eligibility rules"), but it's inherently brittle to wording drift in future commit messages — worth a comment in the code calling out that this list needs periodic upkeep.
  3. The cover-image pipeline (scripts/changelog/cover.ts + scripts/changelog/imagegen/*, several new files/deps: sharp, pngjs, playwright) is a substantial, largely independent feature bundled into a PR titled "improve changelog source evidence." It's disclosed in the PR body and looks solid/tested, but reviewers should know this PR is doing much more than the title implies — worth calling out explicitly if it wasn't already discussed.
  4. Hardcoding nibzard/fukouda as reviewers in the workflow YAML is a simple, reasonable choice, but has no fallback if either username becomes invalid (org departure, etc.); peter-evans/create-pull-request will likely just log an error on that field rather than fail the whole run, so low risk, but flagging for awareness.

None of the above block the change; I didn't find gaps between the narrative and the implementation, and the test coverage for the tricky parts (submodule range recovery, window/cutoff math, preview isolation, quiet-week classification) is genuinely there, not just claimed.

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

@nibzard Independent review is in (posted as a COMMENT review on the PR). Verdict: looks good — every headline claim checked against the actual code (not just the description):

  • Submodule range expansion: ahead/behind/diverged handling matches the documented recovery flow.
  • Cutoff authority + honest quiet weeks: state.until only advances after a real draft is written; window grows cumulatively otherwise.
  • Historical preview is genuinely side-effect-free: no writes to changelog files/state/GitHub outputs, only a temp workspace, no OPENAI_API_KEY needed for a quiet-week replay.
  • Model switch to gpt-5.6-sol at low reasoning effort with validated env overrides — confirmed.
  • Reviewer wiring (nibzard/fukouda) is present in the create-pull-request step.

No security, injection, or correctness issues found. Note: it couldn't run bun test itself (no clone access in that session) but read the ~35-case mocked-fetch test file and traced the logic directly against it.

Four non-blocking items worth a look:

  1. fetchRepositoryCommits's pre-existing 1000-commit/10-page cap silently truncates rather than erroring — more reachable now that quiet weeks accumulate.
  2. The eligibility classifier's hardcoded internal-infra keyword list is brittle to wording drift.
  3. The cover-image pipeline (sharp/pngjs/playwright, imagegen/*) is a large, mostly separate feature bundled under a title about "source evidence" — it's disclosed in the PR body but worth calling out explicitly.
  4. Hardcoded reviewer usernames (nibzard/fukouda) have no fallback if either leaves the org — low risk.

Not approved/merged — that's yours to do.

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

Spawned a reviewer for #77 (not self-reviewed since it's the same author's PR). Reviewer read the full diff and source, confirmed all PR claims hold up, found no security/correctness issues, posted a COMMENT review, and flagged 4 minor non-blocking items. Relayed the verdict to @nibzard on the PR thread.

nibzard added 7 commits July 28, 2026 13:18
- Tolerate a rolled-back or rewritten submodule pointer by skipping that
  host commit's expansion with a warning instead of failing the run; the
  application release comparison still fails loudly.
- Bound the application release head with the explicit until timestamp so
  a manual window cannot include commits promoted after its cutoff.
- Write excluded-groups.json to the preview workspace so exclusion
  decisions can be audited locally without exposing them publicly.
- Document the audit path, failure recovery, and manual changelog
  publishing in the generator README.
Time of day was baked into the house style, so every cover rendered at
blue hour whatever the motif asked for, and 'quiet infrastructure beauty'
added towers and cranes to scenes that had none.

The grade now comes from one of six time-of-day presets, picked at random
per card and recorded in the sidecar JSON. The night preset is written
brighter than a real night because the card lays a 20-40% black scrim over
the background.

Also spawn the CLI end-to-end tests synchronously. Asynchronous spawns
deadlocked for 30s about half the time once the Chromium integration tests
had launched browsers in the same runner process.
@nibzard
nibzard force-pushed the niko/changelog-v2 branch from 75288d4 to b123bb5 Compare July 28, 2026 11:18
@nibzard nibzard changed the title feat: improve changelog source evidence feat(changelog): stronger release evidence and generated cover images Jul 28, 2026
@nibzard

nibzard commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

/blade how would you improve or fix "The eligibility classifier's hardcoded internal-infra keyword list is brittle to wording drift."

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

Looked at the actual classifier in scripts/changelog/source.ts (not just the review summary). The brittle bit is here, in classifyGroup for sourceKind === 'application':

if (/\b(jetstream|nats|puppet[- ]?master|fleet controller|nomad|gcp teardown)\b/.test(headlineText)) {
  return 'internal_only';
}

It's a one-off regex of internal tool/service proper nouns buried inline, next to a separate internalPaths regex for file-path-based detection. Three concrete fixes, in order of impact:

  1. Prefer structural signal over keyword text matching. The path-based check (internalPaths) is already far more robust than name-matching prose. If PRs touching internal infra tools reliably touch deploy/, nomad/, terraform/, etc., lean on that and treat the keyword regex as a narrow fallback only for cases where the changed files don't reveal it (e.g., app-code changes that are about an internal service). Right now both run independently as parallel gates — worth checking whether the keyword list is even still needed once path coverage is good.

  2. If keyword matching stays, move it out of the function body into config.ts as an exported, named array (INTERNAL_TOOL_KEYWORDS), same pattern as SKIP_AUTHORS. A flat array of strings is far easier to extend/diff/review than editing a regex alternation, and centralizing it next to the other config makes it visible to whoever renames an internal tool.

  3. Make renaming/adding a tool discoverable. Add a line to scripts/changelog/README.md telling engineers: "if you rename an internal-only service (JetStream, Puppet Master, etc.), update INTERNAL_TOOL_KEYWORDS." Optionally add a couple more test cases in tests/changelog-v2.test.ts for wording variants (e.g. "Puppetmaster" vs "puppet-master", "Fleet Controller v2") so drift gets caught by CI rather than by a wrong changelog entry.

This is advice, not a change I've made — say the word if you want it implemented and I'll delegate to a dev against the niko/changelog-v2 branch.

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

niko (console): yes hardcoding keywords is shit, can we do this structural signal over keyword text matching. simple is beautiful

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

Kicked off a dev to implement this on PR #77's branch (niko/changelog-v2) — remove the brittle keyword regex, rely solely on the existing file-path (internalPaths) structural check, expand paths only if needed to keep real coverage, update tests, and push to the same branch.

Heads up: the spawn came back with a warning that this session lacks the github:code grant, so the dev may not be able to actually push the change (clone/read may work, but pushing likely won't). If it stalls there, that needs an operator to grant it rather than a retry from me — I'll report back what it actually manages once it finishes.

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

niko (console): A child you delegated (thread 5c0c7daa-6ee6-41ba-9c3c-6bb3fef91183) finished — status: failed; summary: Cannot proceed: this session lacks the github:code capability (per the capability notice), so the git_access and git_push tools are unavailable — confirmed by direct invocation, which returned "unknown tool: git_access; available tools: bash, read, write, edit, web_fetch, github, memory, ask, close". The task requires cloning steel-dev/docs, checking out niko/changelog-v2, editing scripts/changelog/source.ts and tests/changelog-v2.test.ts, running the build/test suite, and pushing a commit to update PR #77. None of that is achievable with the tools actually granted here (the github API tool only allows a narrow set of issue/PR-metadata endpoints, not code/content/ref writes). No workaround attempted per instructions — reporting the missing grant instead. PR #77 was not touched.

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

As flagged before spawning: the dev confirmed it has no github:code grant in this session, so it has no way to clone, edit, or push — git_access/git_push aren't even available as tools, and the GitHub API tool only allows issue/PR-metadata endpoints, not content writes. It made no changes; PR #77 is untouched.

This needs an operator to add the github:code grant for this session/actor before a dev can push the fix. Once that's in place, tell me and I'll spawn a fresh dev with the same brief (drop the keyword regex in classifyGroup, rely on the path-based internalPaths check, update tests, push to niko/changelog-v2).

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

niko (console): A child you delegated (thread 5c0c7daa-6ee6-41ba-9c3c-6bb3fef91183) ended its turn without calling close, so it has gone idle and will not continue on its own. Its box and its whole transcript are still intact. Its last message was:

I already reported this above — to summarize for the record: this task can't proceed from my side because the github:code capability (needed for git_access/git_push) wasn't granted to this spawn. That's an operator-side Blade configuration matter, not a GitHub permissions/PAT issue — nothing I can fix by retrying or working around it. PR #77 remains untouched. Once someone with access to Blade's role/capability settings enables github:code for the dev-agent role, a fresh spawn of this same brief would be able to clone, edit, test, and push the changelog fix.

Decide what follows. If that message shows the task is complete, close this thread — your close summary is the final message delivered to whoever is watching, so carry the concrete results and any PR or issue links into it. If work remains, resume it: resume {thread_id:"5c0c7daa-6ee6-41ba-9c3c-6bb3fef91183", note:"<the concrete next step>"} wakes it in that same box with everything it built still there. Do NOT spawn a replacement to redo work this child has already done — a fresh child starts from nothing and pays for the whole task again. Spawn only if the work needs a clean box for a reason you can name.

@blade-agent

blade-agent Bot commented Jul 28, 2026

Copy link
Copy Markdown

No further action from that child will change the outcome — it hit the same wall twice, confirmed it's an operator-side grant issue, not something a retry or workaround fixes. Status stands: PR #77 is untouched, no code changed. This is blocked until an operator enables the github:code capability for the dev role; once that's done, ping me and I'll spawn a fresh dev with the same brief (drop the keyword regex, rely on the path-based internalPaths check, update tests, push to niko/changelog-v2).

@nibzard
nibzard marked this pull request as ready for review July 28, 2026 11:33
The motif comes from a model reading untrusted commit text and lands in
both an image prompt and the public PR body. It is now collapsed to a
single line, so an embedded newline cannot break out of the PR body's
bullet list, and capped at 400 characters.
@nibzard
nibzard merged commit 060d6dc into main Jul 28, 2026
3 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