Skip to content

Commit 935a90e

Browse files
committed
feat: add release candidate bundles
1 parent 914fb02 commit 935a90e

8 files changed

Lines changed: 677 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7777
- `brigade work review init/plan/run/runs/show/import-findings/findings/finding-show/closeout` for explicit local multi-harness code review producers, receipts, normalized findings, imported finding resolution, local closeout records, and `code-review` work inbox imports without automatic fixes or remote mutation.
7878
- `brigade work verify plan/run/runs/show` and `brigade work closeout <session-id-or-latest>` for local verification receipts and work closeout records that collect task acceptance, test command results, scanner sweep status, code review closeout state, handoff draft status, and session evidence without CI or remote mutation.
7979
- `brigade release plan/doctor/run/runs/show` for local release-readiness receipts that collect work closeout, verification, review closeout, scanner sweep, security, handoff, content-guard, docs, changelog, roadmap, and git-state evidence without pushing, tagging, or mutating remotes.
80+
- `brigade release candidate plan/build/list/show/archive` for local release candidate bundles with readiness evidence, release notes drafts, manual-only publish plans, changed file lists, blockers, warnings, and content-guard summaries without pushing, tagging, or creating releases.
8081
- `brigade work backup init/status/doctor/import-issues` for read-only local backup health summaries and `backup-health` inbox imports.
8182
- Backup health checks for stale snapshots, failed or stale checks, failed or stale prunes, missing summaries, overdue restore rehearsals, and unsafe private summary fields.
8283
- `brigade tools init/list/show/search/describe/contracts/call plan/call queue/call list/call show/call approve/call reject/call hold/call run/runtime/policy/plan/apply/doctor/import-issues`, plus `brigade tools run list/show/latest/replay` and `brigade tools checkpoint list/show/approve/reject/resume`, for portable tool, slash command, skill, superpower, script, and MCP catalog discovery plus explicit projection writes, read-only call planning, local call approval review, explicit approved script and local MCP execution, run history inspection, replay review, checkpointed resume, runtime supervision, and host-local execution policy.

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ brigade release doctor
160160
brigade release run
161161
brigade release runs
162162
brigade release show <run-id>
163+
brigade release candidate plan
164+
brigade release candidate build
165+
brigade release candidate list
166+
brigade release candidate show <candidate-id>
167+
brigade release candidate archive <candidate-id>
163168
brigade handoff doctor
164169
brigade handoff lint
165170
brigade handoff list
@@ -542,10 +547,14 @@ Release readiness commands:
542547
- `brigade release doctor` runs local publish checks such as content-guard when available and reports blockers.
543548
- `brigade release run` writes a release-readiness receipt under `.brigade/release/runs/`.
544549
- `brigade release runs` and `brigade release show <run-id>` inspect local release receipts.
550+
- `brigade release candidate plan` previews a local release candidate bundle.
551+
- `brigade release candidate build` writes a local bundle under `.brigade/release/candidates/`.
552+
- `brigade release candidate list`, `show`, and `archive` inspect or archive local candidate bundles.
545553

546554
Release readiness is a local publish gate. It reviews latest work closeout, verification, code review closeout, scanner sweep state, security health, handoff draft health, content-guard results, git state, and docs/changelog/roadmap touch warnings. It never pushes, tags, creates releases, comments remotely, or mutates remotes.
547555

548556
See [`docs/release-readiness.md`](docs/release-readiness.md) for the receipt contract and local-only boundary.
557+
See [`docs/release-candidates.md`](docs/release-candidates.md) for the candidate bundle files and manual-only publish plan.
549558

550559
### Memory And Bootstrap Health
551560

ROADMAP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Goal: make Brigade a safe target for local automations that discover useful work
4444
- Close out imported review findings against downstream work. Status: implemented with `brigade work review findings`, `finding-show`, `closeout`, source-fingerprint re-review detection, task review evidence, and daily-loop warnings for unclosed review runs.
4545
- Close out completed work against local verification evidence. Status: implemented with `brigade work verify plan/run/runs/show` and `brigade work closeout`, collecting task acceptance, test command receipts, scanner sweep state, code review closeout state, handoff draft status, and session evidence without CI or remote mutation.
4646
- Check local release readiness before publish operations. Status: implemented with `brigade release plan`, `doctor`, `run`, `runs`, and `show`, collecting work closeout, verification, review, scanner, security, handoff, content-guard, docs, changelog, roadmap, and git-state evidence without pushing, tagging, or mutating remotes.
47+
- Build local release candidate packets before manual publish steps. Status: implemented with `brigade release candidate plan/build/list/show/archive`, local candidate bundles, evidence JSON, release notes drafts, manual-only publish plans, candidate health warnings, and no remote mutation.
4748

4849
## Later Phase: Chat Surface Scanners
4950

docs/release-candidates.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Release Candidates
2+
3+
`brigade release candidate` turns local release readiness evidence into a reviewable candidate packet. It is designed for the step after `brigade release run` and before any manual publish action.
4+
5+
## Commands
6+
7+
```bash
8+
brigade release candidate plan
9+
brigade release candidate build
10+
brigade release candidate list
11+
brigade release candidate show <candidate-id>
12+
brigade release candidate archive <candidate-id>
13+
```
14+
15+
`plan` previews the candidate evidence without writing files.
16+
17+
`build` writes a bundle under:
18+
19+
```text
20+
.brigade/release/candidates/
21+
```
22+
23+
`list` and `show` inspect bundles. `archive` moves a reviewed candidate into the local archive so it no longer appears as the latest candidate.
24+
25+
## Bundle Files
26+
27+
Each candidate contains:
28+
29+
- `RELEASE_CANDIDATE.md`, a human-readable summary of readiness, blockers, warnings, and changed files.
30+
- `RELEASE_NOTES_DRAFT.md`, an editable draft inferred from `CHANGELOG.md`, commit subjects since the base ref, and touched docs. Uncertain sections are marked `review-needed`.
31+
- `PUBLISH_PLAN.md`, a manual checklist for verification, closeout, release doctor, content guard, tag, push, and release creation. Remote-mutating commands are marked manual-only and are not run.
32+
- `EVIDENCE.json`, stable wrapper-friendly evidence for the release readiness receipt, work closeout, verification receipt, code review closeout, scanner sweep state, security state, handoff state, git state, changed files, docs touch status, content-guard summaries, blockers, warnings, and suggested next commands.
33+
34+
## Candidate Health
35+
36+
`brigade release doctor` warns when the latest candidate is stale, references missing receipts, was built from blocked readiness, or no longer matches the current git HEAD.
37+
38+
These are warnings because candidate bundles are review artifacts. They do not replace release readiness checks, verification, code review closeout, or content guard.
39+
40+
## Boundary
41+
42+
Release candidate commands are local and explicit. Brigade does not push, tag, create GitHub releases, mutate pull requests, edit changelogs outside the candidate bundle, upload artifacts, approve content-guard findings, start daemons, install schedulers, or store secrets.

docs/release-readiness.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ brigade release doctor
1010
brigade release run
1111
brigade release runs
1212
brigade release show <run-id>
13+
brigade release candidate plan
14+
brigade release candidate build
15+
brigade release candidate list
16+
brigade release candidate show <candidate-id>
17+
brigade release candidate archive <candidate-id>
1318
```
1419

1520
`plan` gathers local evidence and reports readiness without running publish checks or writing a receipt.
@@ -24,6 +29,8 @@ brigade release show <run-id>
2429

2530
`runs` and `show` inspect those receipts.
2631

32+
`release candidate` commands turn readiness receipts into local candidate bundles under `.brigade/release/candidates/`. They do not push, tag, create releases, or mutate remotes. See [`release-candidates.md`](release-candidates.md).
33+
2734
## Evidence
2835

2936
Release readiness includes:
@@ -43,7 +50,7 @@ Release readiness includes:
4350

4451
Blockers include missing or blocked work closeout, missing or failed verification, unclosed review runs, unresolved code-review findings, unresolved scanner sweep issues, open security issues, stale or invalid handoff draft state, content-guard failures, and dirty tracked files.
4552

46-
Warnings include missing content-guard and docs/changelog/roadmap touch expectations. Warnings do not make the receipt blocked by themselves.
53+
Warnings include missing content-guard, docs/changelog/roadmap touch expectations, stale release candidate bundles, missing candidate receipt references, changed git HEAD since candidate build, and candidates built from blocked readiness. Warnings do not make the receipt blocked by themselves.
4754

4855
## Boundary
4956

src/brigade/cli.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,29 @@ def _build_parser() -> argparse.ArgumentParser:
132132
p_release_show.add_argument("run_id", help="Run id, unique prefix, or latest.")
133133
p_release_show.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
134134
p_release_show.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
135+
p_release_candidate = release_sub.add_parser("candidate", help="Build and inspect local release candidate bundles.")
136+
release_candidate_sub = p_release_candidate.add_subparsers(dest="release_candidate_command", metavar="<candidate-command>")
137+
release_candidate_sub.required = True
138+
p_release_candidate_plan = release_candidate_sub.add_parser("plan", help="Plan a release candidate bundle without writing it.")
139+
p_release_candidate_plan.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
140+
p_release_candidate_plan.add_argument("--base-ref", default="origin/main", help="Base ref for changed files and release notes.")
141+
p_release_candidate_plan.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
142+
p_release_candidate_build = release_candidate_sub.add_parser("build", help="Build a local release candidate bundle.")
143+
p_release_candidate_build.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to update.")
144+
p_release_candidate_build.add_argument("--base-ref", default="origin/main", help="Base ref for changed files and release notes.")
145+
p_release_candidate_build.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
146+
p_release_candidate_list = release_candidate_sub.add_parser("list", help="List local release candidate bundles.")
147+
p_release_candidate_list.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
148+
p_release_candidate_list.add_argument("--limit", type=int, default=20, help="Maximum candidates to list.")
149+
p_release_candidate_list.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
150+
p_release_candidate_show = release_candidate_sub.add_parser("show", help="Show one local release candidate bundle.")
151+
p_release_candidate_show.add_argument("candidate_id", help="Candidate id, unique prefix, or latest.")
152+
p_release_candidate_show.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
153+
p_release_candidate_show.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
154+
p_release_candidate_archive = release_candidate_sub.add_parser("archive", help="Archive one local release candidate bundle.")
155+
p_release_candidate_archive.add_argument("candidate_id", help="Candidate id, unique prefix, or latest.")
156+
p_release_candidate_archive.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to update.")
157+
p_release_candidate_archive.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
135158

136159
# handoff
137160
p_handoff = sub.add_parser("handoff", help="Inspect memory handoff inbox health.")
@@ -1185,6 +1208,19 @@ def main(argv=None) -> int:
11851208
return release_cmd.runs(target=args.target, limit=args.limit, json_output=args.json)
11861209
if args.release_command == "show":
11871210
return release_cmd.show(target=args.target, run_id=args.run_id, json_output=args.json)
1211+
if args.release_command == "candidate":
1212+
if args.release_candidate_command == "plan":
1213+
return release_cmd.candidate_plan(target=args.target, base_ref=args.base_ref, json_output=args.json)
1214+
if args.release_candidate_command == "build":
1215+
return release_cmd.candidate_build(target=args.target, base_ref=args.base_ref, json_output=args.json)
1216+
if args.release_candidate_command == "list":
1217+
return release_cmd.candidate_list(target=args.target, limit=args.limit, json_output=args.json)
1218+
if args.release_candidate_command == "show":
1219+
return release_cmd.candidate_show(target=args.target, candidate_id=args.candidate_id, json_output=args.json)
1220+
if args.release_candidate_command == "archive":
1221+
return release_cmd.candidate_archive(target=args.target, candidate_id=args.candidate_id, json_output=args.json)
1222+
parser.error(f"unknown release candidate command: {args.release_candidate_command}")
1223+
return 2
11881224
parser.error(f"unknown release command: {args.release_command}")
11891225
return 2
11901226
if cmd == "handoff":

0 commit comments

Comments
 (0)