feat(release): batch accumulation controls for standing PR strategy#161
feat(release): batch accumulation controls for standing PR strategy#161goosewobbler merged 6 commits intomainfrom
Conversation
Implements Follow-up 4 from AUTOMATION_PLAN.md, including: - `ci.standingPr.minPackages`: closes the standing PR and noops when fewer than N packages have releasable changes; enforced as a hard gate - `ci.standingPr.minAge`: reports a `pending` commit status on the release branch until the PR has existed for the configured duration - `duration.ts`: minimal duration parser accepting `\d+(s|m|h|d)` - `standing-pr-status.ts`: `postStandingPRStatus` wrapping `repos.createCommitStatus` with context `releasekit/standing-pr` - Manifest schema bumped to v2; `firstUpdatedAt` field preserved across updates so the age clock starts when the PR was first opened - `parseManifest` accepts both v1 and v2 manifests for backward compat - Hourly `schedule` cron trigger added to the standing-pr workflow template so the status check re-evaluates as time passes - Full unit test coverage for new paths https://claude.ai/code/session_011GSASToKFMEpkMtWgytFXY
Release Preview — no release
Updated automatically by ReleaseKit |
Greptile SummaryThis PR implements batch accumulation controls for the standing PR strategy: a All three P1 issues raised in the previous review have been addressed: Confidence Score: 5/5Safe to merge — all three P1 issues from the prior review are addressed and no new blocking issues were found. All previously flagged P1 concerns (missing statuses:write, v1→v2 clock reset, silent minAge fallback) are resolved in this revision. The remaining previously-noted cosmetic issue (formatDuration dropping sub-hour seconds) is P2 and does not affect correctness. Test coverage for the new gates is thorough. No files require special attention.
|
| Filename | Overview |
|---|---|
| packages/release/src/duration.ts | New module with parseDuration and formatDuration helpers; single-unit regex keeps parsing intentionally strict; seconds are dropped when hours are present (cosmetic, previously noted). |
| packages/release/src/standing-pr.ts | Adds minPackages gate (close+noop when below threshold), firstUpdatedAt preservation with v1 createdAt fallback, and minAge status-check logic; all three issues flagged in the prior review are addressed. |
| templates/workflows/standing-pr.yml | Adds hourly schedule trigger to re-evaluate minAge status; statuses: write permission correctly added; job condition updated to include schedule event. |
| packages/config/src/schema.ts | Adds optional minAge (string) and minPackages (positive int) fields to StandingPrConfigSchema with appropriate Zod validators. |
| releasekit.schema.json | JSON schema correctly mirrors the Zod schema additions with integer/minimum constraints on minPackages and string type for minAge. |
| packages/release/test/unit/duration.spec.ts | Full unit-test coverage for parseDuration and formatDuration including edge cases (empty string, unknown units, composite strings, sub-second rounding). |
| packages/release/test/unit/standing-pr.spec.ts | New tests cover v1/v2 compat, minPackages gate (noop + close), minAge pending/success transitions, firstUpdatedAt preservation, v1→v2 createdAt fallback, and status-failure resilience. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[standing-pr update triggered\npush / schedule] --> B{Any releasable\nchanges?}
B -- No --> C[Close existing PR\nwith comment] --> Z[noop]
B -- Yes --> D{minPackages\nconfigured?}
D -- No --> F
D -- Yes --> E{updates.length\n>= minPackages?}
E -- No --> C2[Close existing PR\nwith comment] --> Z
E -- Yes --> F[Reset release branch\nWrite bumps + notes\nCommit + force-push]
F --> G[Capture releaseBranchSha]
G --> H[Create / update PR]
H --> I[Find existing manifest\nPreserve firstUpdatedAt\nor fall back to createdAt]
I --> J[Write v2 manifest comment]
J --> K{minAge\nconfigured?}
K -- No --> L[Post success status]
K -- Yes --> M{parseDuration\nreturns null?}
M -- Yes --> N[warn: gate inactive] --> L
M -- No --> O{PR age\n>= minAge?}
O -- Yes --> L[Post success status\nReady to merge]
O -- No --> P[Post pending status\nWaiting X for minAge]
Reviews (6): Last reviewed commit: "chore: merge main into feature branch" | Re-trigger Greptile
- `duration.ts`: use switch statement for unit multipliers to avoid `noUncheckedIndexedAccess` error on Record index access - `standing-pr.ts`: emit `warn()` when `ci.standingPr.minAge` is set to an unparseable duration string so the user knows the gate is inactive https://claude.ai/code/session_011GSASToKFMEpkMtWgytFXY
repos.createCommitStatus requires this permission. Without it the releasekit/standing-pr status check is never posted, making the minAge gate silently non-functional. https://claude.ai/code/session_011GSASToKFMEpkMtWgytFXY
Resolves conflicts in standing-pr-status.ts, standing-pr.ts, and standing-pr.spec.ts. Preserves batch accumulation controls (minAge, minPackages) while adopting main's refactored postStandingPRStatusSafe wrapper and CommitStatusState type. https://claude.ai/code/session_011GSASToKFMEpkMtWgytFXY
…manifest When a v1 manifest exists (no firstUpdatedAt), fall back to its createdAt instead of current time so the minAge clock isn't reset on first v2 run. https://claude.ai/code/session_011GSASToKFMEpkMtWgytFXY
Resolves conflicts in schema.ts and releasekit.schema.json — adds mergeMethod from main alongside minAge and minPackages from this branch. https://claude.ai/code/session_011GSASToKFMEpkMtWgytFXY
Summary
Implements Follow-up 4 — Batch release accumulation controls from
AUTOMATION_PLAN.md.ci.standingPr.minPackages— hard gate: if fewer than N packages have releasable changes,standing-pr updatenoops and closes any open standing PR with an explanatory commentci.standingPr.minAge— soft gate: posts apendingcommit status (releasekit/standing-pr) on the release branch HEAD until the PR has been open for the configured duration (e.g."6h","30m","1d"); transitions tosuccessonce elapsedfirstUpdatedAtfield preserved across updates;parseManifestaccepts both versions for backward compatschedulecron trigger added to thestanding-pr.ymlworkflow template so the status check re-evaluates as time passes without needing a pushduration.tsmodule withparseDuration/formatDurationhelpersstanding-pr-status.tswrappingrepos.createCommitStatuswith contextreleasekit/standing-prChanged files
packages/config/src/schema.tsminAge,minPackagestoStandingPrConfigSchemareleasekit.schema.jsonpackages/release/src/duration.tsparseDuration,formatDurationpackages/release/src/standing-pr-status.tspostStandingPRStatuspackages/release/src/standing-pr.tsfirstUpdatedAt,minPackagesgate, status check postingtemplates/workflows/standing-pr.ymlscheduletrigger; update job conditionpackages/release/test/unit/duration.spec.tspackages/release/test/unit/standing-pr.spec.tsTest plan
duration.spec.ts+standing-pr.spec.tspassminPackages: 3with 1 changed package → noop, existing PR closed with commentminAge: "6h"with PR < 6h old →pendingstatus postedminAge: "1h"with PR > 1h old →successstatus postedfirstUpdatedAtpreserved across multiplestanding-pr updaterunsfirstUpdatedAt) parsed without errorhttps://claude.ai/code/session_011GSASToKFMEpkMtWgytFXY
Generated by Claude Code