diff --git a/.github/workflows/weekly-release.yml b/.github/workflows/weekly-release.yml index bb4566aef..a5453cb62 100644 --- a/.github/workflows/weekly-release.yml +++ b/.github/workflows/weekly-release.yml @@ -1,9 +1,15 @@ -name: Weekly release +name: Stable release # Tags the newest green commit on main as v, publishes the GitHub # Release, and fast-forwards refs/heads/stable to it (docs/releases.md). Team # installs follow stable, so this is the only thing that moves them. Runs -# every Monday morning Pacific; run it by hand with a ref for a hotfix, or -# with dry_run to see the plan and change nothing. A pull request that touches +# twice a week, Monday and Thursday at 14:00 UTC (the file keeps its +# weekly-release.yml name so its run history and every `gh workflow run` +# reference stay intact). stable moves at most once per 72 hours: a cut inside +# that floor is refused with REFUSED_FLOOR and NEXT_ALLOWED; a scheduled run +# gets 3 hours of grace for cron start jitter, a hand-run cut none. The only +# exception is a security cut: the security input with a ref, naming a GHSA, +# CVE or # the script ties to the released commits. Run it by hand with a +# ref for a hotfix, or with dry_run to see the plan and change nothing. A pull request that touches # the release tooling runs the dry run against main with a read-only token, # which is the only pre-merge exercise of the real workflow. The candidate's # gate must already be green (free-tests and main-backstop check runs @@ -12,8 +18,9 @@ name: Weekly release # runs only there, with main's tooling. on: schedule: - # Monday 14:00 UTC: 07:00 PDT, 06:00 PST. - - cron: '0 14 * * 1' + # Monday and Thursday 14:00 UTC (07:00 PDT, 06:00 PST): exactly 72 h, then + # 96 h, apart; the floor's scheduled grace covers a late Monday start. + - cron: '0 14 * * 1,4' workflow_dispatch: inputs: dry_run: @@ -24,6 +31,10 @@ on: description: 'Hotfix: release this commit of main instead of the newest green one (full 40-hex SHA)' type: string default: '' + security: + description: 'Security cut (needs ref): the reason, naming the GHSA-…, CVE-… or # it fixes; passes the 72-hour floor once verified and lands in the release notes' + type: string + default: '' pull_request: paths: - '.github/workflows/weekly-release.yml' @@ -48,6 +59,9 @@ jobs: contents: write # Reads the candidate's check runs. checks: read + # A security cut reads the security label on the # its reason names. + issues: read + pull-requests: read steps: # The dispatched ref supplies the tooling; the release candidate always # comes from origin/main, which fetch-depth 0 brings along with every @@ -73,16 +87,22 @@ jobs: GH_TOKEN: ${{ github.token }} DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && 'true' || 'false' }} RELEASE_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }} + SECURITY_REASON: ${{ github.event_name == 'workflow_dispatch' && inputs.security || '' }} run: | set -euo pipefail if [ -n "$RELEASE_REF" ] && ! [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]]; then - echo "::error::ref must be the full 40-hex SHA of a commit on main, not '$RELEASE_REF'" + echo "::error::ref must be the full 40-hex SHA of a commit on main" + exit 1 + fi + if [ -n "$SECURITY_REASON" ] && [ -z "$RELEASE_REF" ]; then + echo "::error::a security cut needs ref: the full SHA of the fix commit on main" exit 1 fi args=(--repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY") [ "$DRY_RUN" = true ] && args+=(--dry-run) [ -n "$RELEASE_REF" ] && args+=(--ref "$RELEASE_REF") - echo "## Weekly release ($([ "$DRY_RUN" = true ] && echo 'dry run' || echo 'cut'))" >> "$GITHUB_STEP_SUMMARY" + [ -n "$SECURITY_REASON" ] && args+=(--security "$SECURITY_REASON") + echo "## Stable release ($([ "$DRY_RUN" = true ] && echo 'dry run' || echo 'cut'), $GITHUB_EVENT_NAME)" >> "$GITHUB_STEP_SUMMARY" bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts "${args[@]}" dry-run: @@ -110,5 +130,5 @@ jobs: set -euo pipefail git fetch --quiet origin '+refs/heads/stable:refs/remotes/origin/stable' 2>/dev/null || echo "origin has no stable yet" git fetch --quiet --tags origin - echo "## Weekly release (dry run from PR #${{ github.event.pull_request.number }})" >> "$GITHUB_STEP_SUMMARY" + echo "## Stable release (dry run from PR #${{ github.event.pull_request.number }})" >> "$GITHUB_STEP_SUMMARY" bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts --repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY" --dry-run diff --git a/AGENTS.md b/AGENTS.md index 03f4695b7..4232c097b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,7 @@ Invoke them by name (e.g., `/office-hours`). |-------|-------------| | `/review` | Pre-landing PR review. Finds bugs that pass CI but break in prod; lists every gate edit for gate integrity. | | `/deslop-shared-libs` | Find worthwhile shared-code extractions in recent work. Recommendations only. | +| `/write-skill` | Write or improve an agent skill without bloat: edit ladder, size per level (`gstack-skill-bill`), skill-to-bin drift check, full diets with a before/after test. | | `/test-audit` | Sweep existing tests for low-value, implementation-coupled or duplicate tests. Report-only unless you approve a batch. | | `/codex` | Second opinion via OpenAI Codex. Review, challenge, or consult modes. Available outside the Codex harness. | | `/claude-code` | Second opinion via Claude Code. Review, challenge, or consult modes. Available outside the Claude Code harness. | diff --git a/CHANGELOG.md b/CHANGELOG.md index acbc570ab..57b7c90e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [1.91.289.0] - 2026-10-28 **/plan-design-review, /office-hours and /design-review load 15 to 40 percent fewer bytes per run, with the same or better task results.** Garry picked the skill-diet pilot on 2026-10-27 (A1): "pilot now with the upstream skill-diet on two or three large skills nobody is editing this week (design-review, office-hours, plan-design-review). Each slimmed skill ships only if it passes skill-diet's before/after test and gstack's own paid tests for that skill." All three passed both. Each SKILL.md now holds what every run needs; blocks only some runs use moved into on-demand sections behind a one-line "read when" pointer, and prose the model already knows was cut. Descriptions and triggers are unchanged. @@ -21,6 +21,82 @@ - The one raise: office-hours `maxSizeRatio` 1.1796 → 1.1921, granted by GStack Minder as merge-queue owner. The union grew 1,662 bytes because the Phase 3.5 question now sits in the main file while `CODEX_SECOND_OPINION` keeps its own copy; the measured 156,286 / 131,110 = 1.19202 needs 1.1921 after rounding up (the grant text said 1.1920). Per-run load fell 73,248 → 43,885 bytes. Follow-up: let `CODEX_SECOND_OPINION` take a flag that omits its question when the skeleton asks it, then lower the cap. - `test/helpers/placeholder-allowlist.ts`: the `` entry is deleted. The design-review diet removed the only Phase 8d fence that used it, and the allowlist's own staleness check (`test/env-host-fence-conformance.test.ts`, CEO-12) failed. No timeout, trial count or case kind changed. +## [1.91.288.0] - 2026-10-28 + +**Stable moves twice a week, and never twice in 72 hours unless a security fix needs it.** Garry asked, 2026-10-27: "Do we have rules around stable where we update stable except for security fixes at most once every 72 hours?" There were none: the Monday cut and any hand-run cut moved team installs whenever someone ran it. Now the release cut runs Monday and Thursday at 14:00 UTC ("Ok twice a week"), refuses any move inside 72 hours of the last one, and lets a security fix through only when it can tie the fix to the commits being released. + +### Added +- **The 72-hour floor in `scripts/release-cut.ts`.** Every cut, scheduled or by hand, measures stable's last move from the annotated `v` tag on stable (or its GitHub Release's publish time when later; any other tag on that commit is ignored) and refuses inside 72 hours. The refusal's first two lines are `REFUSED_FLOOR: stable moved to v at