Skip to content

Pipeline Plan 59

Seth Ford edited this page Feb 14, 2026 · 2 revisions

The .claude/ directory has security restrictions preventing writes. The plan is complete — let me present it directly.


Implementation Plan: Release Train Automation

Overview

Add scripts/sw-release.sh — a new standalone script that automates batched releases with semantic versioning, changelog generation, GitHub Release creation, and daemon-integrated scheduling. Follows all existing Shipwright patterns: set -euo pipefail, Bash 3.2 compatibility, emit_event, PASS/FAIL test harness, and daemon config flags.

Files to Modify

File Action Purpose
scripts/sw-release.sh Create Release train orchestration — version determination, changelog generation, GitHub Release creation, schedule management
scripts/sw-release-test.sh Create Test suite — 20+ tests covering version determination, changelog generation, batching, config, schedule triggers
scripts/sw Edit Add release command to CLI router + help text
scripts/sw-daemon.sh Edit Add release train scheduling to patrol loop + config loading
scripts/sw-fleet.sh Edit Add downstream release notification when upstream repo releases
package.json Edit Add sw-release-test.sh to test chain
.github/workflows/release.yml Edit Add optional workflow_dispatch trigger

Implementation Steps

Step 1: Create scripts/sw-release.sh — Core Script

Standard Shipwright boilerplate (set -euo pipefail, VERSION="1.10.0", colors, info()/success()/warn()/error(), emit_event, compat.sh sourcing).

Subcommands: status, plan, create, changelog, config, history, schedule-check, help

Core functions:

  1. release_collect_prs()gh pr list --state merged --base main --json number,title,labels,author,mergedAt filtered by mergedAt > last_tag_date. Last tag found via git describe --tags --abbrev=0 2>/dev/null. Returns JSON array.

  2. release_categorize_prs() — Classify PRs using jq. Priority order:

    • breaking — label breaking-change or title contains BREAKING
    • feature — label enhancement/feature, or title starts with feat:
    • fix — label bug/fix, or title starts with fix:
    • docs — label documentation or title starts with docs:
    • refactor — label refactor or title starts with refactor:
    • other — everything else
  3. release_determine_version() — Read current version from package.json via jq. Parse MAJOR.MINOR.PATCH. If any breaking PR → MAJOR+1, minor=0, patch=0. Else if any feature → MINOR+1, patch=0. Else → PATCH+1. Support --force-bump major|minor|patch override.

  4. release_generate_changelog() — Build markdown section grouped by category. Insert after ## [Unreleased] line in existing CHANGELOG.md. Format: - Title (#number) — @author. Atomic write: tmp file + mv.

  5. release_create() — Full flow:

    • Collect → categorize → determine version
    • Call scripts/update-version.sh $new_version
    • Generate changelog
    • git add -A && git commit -m "release: v${new_version}"
    • git tag "v${new_version}"
    • If --push or auto_push=true: git push origin main --tags
    • If not NO_GITHUB: gh release create "v${new_version}" --title "Shipwright v${new_version}" --notes-file <tmp_notes>
    • Emit release.created, release.tagged, release.published
  6. release_check_eligible() — PR count >= batch_size (default 1), no active pipelines (check daemon state), CI passing on main.

  7. release_schedule_check() — Exit 0 if eligible. Modes: manual (always exit 1), pr_count (merged PRs >= batch_size), daily (24h since last tag), weekly (7d since last tag).

Step 2: Configuration — Daemon Config Integration

Add to .claude/daemon-config.json schema under release_train:

{
  "release_train": {
    "enabled": false,
    "schedule": "manual",
    "batch_size": 5,
    "auto_push": false,
    "auto_publish": false,
    "changelog_path": "CHANGELOG.md",
    "version_source": "package.json"
  }
}

In sw-daemon.sh load_config() (~line 416, after patrol settings), add:

RELEASE_TRAIN_ENABLED=$(jq -r '.release_train.enabled // false' "$config_file")
RELEASE_TRAIN_SCHEDULE=$(jq -r '.release_train.schedule // "manual"' "$config_file")
RELEASE_TRAIN_BATCH_SIZE=$(jq -r '.release_train.batch_size // 5' "$config_file")

Step 3: Daemon Poll Loop Integration

After the patrol block (~line 4914) and inside the now_e scope, add:

# Release train check (independent of patrol)
if [[ "${RELEASE_TRAIN_ENABLED:-false}" == "true" ]]; then
    if [[ $((now_e - LAST_RELEASE_CHECK_EPOCH)) -ge "${RELEASE_TRAIN_CHECK_INTERVAL:-300}" ]]; then
        if "$SCRIPT_DIR/sw-release.sh" schedule-check 2>/dev/null; then
            daemon_log INFO "Release train triggered"
            "$SCRIPT_DIR/sw-release.sh" create --auto || daemon_log WARN "Release creation failed"
        fi
        LAST_RELEASE_CHECK_EPOCH=$now_e
    fi
fi

Initialize LAST_RELEASE_CHECK_EPOCH=0 at top alongside LAST_PATROL_EPOCH.

Step 4: Fleet Downstream Notifications

In sw-fleet.sh, add fleet_notify_release():

  • Reads fleet config repos array
  • For each repo with a GitHub remote, creates an issue or dispatches event
  • Guarded by notify_releases config flag
  • Emit fleet.release_notification event

Step 5: GitHub Workflow Enhancement

Add workflow_dispatch trigger to .github/workflows/release.yml:

on:
  push:
    tags: ["v*"]
  workflow_dispatch:
    inputs:
      version:
        description: "Version to release (e.g., 1.11.0)"
        required: true

Step 6: CLI Router

In scripts/sw:

  • Add help line: ${CYAN}release${RESET} <cmd> ${BOLD}Release train${RESET} — batched releases with changelogs
  • Add route: release) exec "$SCRIPT_DIR/sw-release.sh" "$@" ;;

Step 7: Test Suite

scripts/sw-release-test.sh following sw-fleet-test.sh pattern. 20+ tests with mock gh/git binaries:

# Test Assertion
1 Version: patch only 1.10.01.10.1
2 Version: minor (feature) 1.10.01.11.0
3 Version: major (breaking) 1.10.02.0.0
4 Version: force override --force-bump minor overrides auto
5 Categorize: label-based enhancement → feature
6 Categorize: title-based feat: → feature
7 Categorize: mixed types Multiple categories correct
8 Changelog: format Keep a Changelog markdown
9 Changelog: PR links (#123) format
10 Changelog: empty categories Omitted from output
11 Changelog: insertion After [Unreleased], preserves existing
12 Eligible: batch size Triggers at N PRs
13 Eligible: daily schedule Triggers after 24h
14 Eligible: weekly schedule Triggers after 7d
15 Eligible: no PRs Returns 1
16 Config: defaults Missing config → sensible defaults
17 Config: custom values Custom config respected
18 Status: output Shows pending PRs + version
19 Plan: dry run Correct format, no side effects
20 Events: emission release.created in events.jsonl

Step 8: Register in package.json

Add && bash scripts/sw-release-test.sh to the test script chain.

Task Checklist

  • Task 1: Create scripts/sw-release.sh with boilerplate, subcommand routing, help text
  • Task 2: Implement release_collect_prs() — query merged PRs since last tag
  • Task 3: Implement release_categorize_prs() — label/title-based PR classification
  • Task 4: Implement release_determine_version() — semantic version bump logic
  • Task 5: Implement release_generate_changelog() — Keep a Changelog format
  • Task 6: Implement release_create() — full release flow: version bump → changelog → tag → GitHub Release
  • Task 7: Implement release_check_eligible() and release_schedule_check() — schedule/threshold triggers
  • Task 8: Implement status, plan, config, history subcommands
  • Task 9: Add daemon config loading for release_train.* keys in sw-daemon.sh
  • Task 10: Add release train scheduling to daemon poll loop in sw-daemon.sh
  • Task 11: Add fleet_notify_release() to sw-fleet.sh for downstream notifications
  • Task 12: Add release command to CLI router in scripts/sw (help + route)
  • Task 13: Add workflow_dispatch trigger to .github/workflows/release.yml
  • Task 14: Create scripts/sw-release-test.sh with 20+ tests
  • Task 15: Register sw-release-test.sh in package.json test chain

Testing Approach

All tests use mocked binaries (gh, git) in a temp directory with NO_GITHUB=true. No real API calls. Tests cover:

  1. PR collection — Mock gh pr list returns canned JSON; verify parsing
  2. Categorization — Feed labeled/titled PRs; verify category assignment
  3. Version math1.10.01.10.1 (patch), 1.11.0 (minor), 2.0.0 (major)
  4. Changelog format — Verify Keep a Changelog markdown structure
  5. Changelog insertion — New section after [Unreleased], existing entries preserved
  6. Schedule logic — Mock timestamps/PR counts; verify trigger/no-trigger
  7. Config — Default values when missing; custom values when present
  8. Event emissionrelease.* events in events.jsonl with correct fields

Integration: npm test runs all 23 test suites including the new one.

Definition of Done

  • shipwright release status shows merged PRs since last release with version preview
  • shipwright release plan generates a dry-run (no side effects)
  • shipwright release create executes full release: version bump, changelog, tag, GitHub Release
  • Semantic version auto-determined from PR labels/titles (breaking→major, feature→minor, fix→patch)
  • Changelog generated in Keep a Changelog format with proper categorization
  • GitHub Release created with tag and auto-generated notes
  • Release schedule configurable via daemon-config.json (manual, pr_count, daily, weekly)
  • Daemon poll loop checks release eligibility when release_train.enabled=true
  • Fleet repos notified of upstream releases when configured
  • CLI router dispatches shipwright release <cmd> correctly
  • 20+ tests pass in sw-release-test.sh covering version determination and changelog generation
  • npm test passes with the new test suite included
  • All scripts maintain Bash 3.2 compatibility
  • Events emitted: release.created, release.tagged, release.published, fleet.release_notification

Clone this wiki locally