Skip to content

Commit 2bdee2c

Browse files
broomvaCarlos D. Escobar-Valbuenaclaude
authored
feat(release): formalize OSS release infrastructure + bstack CLI dispatcher (0.2.2) (#27)
Foundation PR for the 0.2.x → 0.3.0 series. Adds CONTRIBUTING + RELEASE docs, CI workflows, a top-level bstack CLI dispatcher, and switches bstack-update-check to use the GitHub Releases API so dev-branch VERSION bumps no longer leak as "available upgrades" to downstream installs. Files: - NEW bin/bstack — top-level CLI dispatcher (doctor, validate, repair, bootstrap, onboard, revamp, upgrade, config, update-check, wave, release tag, version). Existing sub-binaries remain callable directly. - NEW CONTRIBUTING.md — contribution guide: branch/PR shape, Conventional Commits, primitive-promotion rule, local validation steps. - NEW RELEASE.md — semver policy (pre-1.0: minor = potentially breaking), release checklist using `bstack release tag`, retroactive-tag history, update-check transport docs. - NEW .github/workflows/ci.yml — shellcheck on scripts/*.sh and bin/*, JSON validation for assets/templates/*.snippet, bstack doctor --quiet on templated fixtures. - NEW .github/workflows/validate-release.yml — PR check: if VERSION changed, CHANGELOG.md must have a matching `## X.Y.Z` section and VERSION must monotonically increase. - EDIT bin/bstack-update-check — primary source is GitHub Releases API (/repos/broomva/bstack/releases/latest); raw VERSION on main is the fallback. New env vars: BSTACK_RELEASES_URL, BSTACK_REMOTE_URL. - EDIT VERSION → 0.2.2 - EDIT CHANGELOG.md — 0.2.2 entry Retroactive tags v0.2.0 (322ba23) and v0.2.1 (9170dd3) were pushed 2026-05-18 to give the new transport a stable anchor. Co-authored-by: Carlos D. Escobar-Valbuena <devteam@getstimulus.ai> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 82b6a9c commit 2bdee2c

8 files changed

Lines changed: 541 additions & 6 deletions

File tree

.github/workflows/ci.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
lint:
13+
name: Lint shell + JSON templates
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: ShellCheck (scripts + bin)
19+
run: |
20+
# Only lint files that have a shebang we recognize.
21+
set -euo pipefail
22+
mapfile -t targets < <(
23+
find scripts bin -type f \( -name "*.sh" -o ! -name "*.*" \) 2>/dev/null \
24+
| xargs -I {} sh -c 'head -n1 "{}" | grep -q "^#!.*sh" && echo "{}"' \
25+
| sort -u
26+
)
27+
if [ "${#targets[@]}" -eq 0 ]; then
28+
echo "No shell scripts found to lint."
29+
exit 0
30+
fi
31+
printf ' • %s\n' "${targets[@]}"
32+
# SC1091: don't follow sourced files; SC2155: declare-and-assign exit-code
33+
# warnings — we accept these in bstack's defensive shell style.
34+
shellcheck --severity=warning --exclude=SC1091,SC2155 "${targets[@]}"
35+
36+
- name: Validate JSON templates
37+
run: |
38+
set -euo pipefail
39+
shopt -s nullglob
40+
fail=0
41+
for f in assets/templates/*.snippet; do
42+
if ! jq -e . "$f" >/dev/null 2>&1; then
43+
echo "::error file=$f::invalid JSON shape"
44+
fail=1
45+
else
46+
echo " • $f — valid JSON"
47+
fi
48+
done
49+
exit "$fail"
50+
51+
doctor:
52+
name: bstack doctor (primitive-contract lint)
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v4
56+
57+
- name: Run doctor against templates
58+
run: |
59+
set -euo pipefail
60+
# doctor.sh expects CLAUDE.md / AGENTS.md / .control/policy.yaml in cwd.
61+
# Stage the templated versions as a fixture so doctor lints what
62+
# downstream installs would see after `bstack bootstrap`.
63+
mkdir -p .control
64+
cp assets/templates/CLAUDE.md.template ./CLAUDE.md 2>/dev/null || true
65+
cp assets/templates/AGENTS.md.template ./AGENTS.md 2>/dev/null || true
66+
cp assets/templates/policy.yaml.template .control/policy.yaml 2>/dev/null || true
67+
bash scripts/doctor.sh --quiet
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Validate release
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- VERSION
7+
- CHANGELOG.md
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
version-changelog-alignment:
14+
name: VERSION ↔ CHANGELOG match
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Detect VERSION change
22+
id: version
23+
run: |
24+
set -euo pipefail
25+
base="${{ github.event.pull_request.base.sha }}"
26+
if ! git diff --name-only "$base"...HEAD | grep -qx VERSION; then
27+
echo "VERSION not modified in this PR — nothing to validate."
28+
echo "changed=false" >> "$GITHUB_OUTPUT"
29+
exit 0
30+
fi
31+
new="$(cat VERSION | tr -d '[:space:]')"
32+
if ! printf '%s' "$new" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
33+
echo "::error file=VERSION::VERSION '$new' is not semver X.Y.Z"
34+
exit 1
35+
fi
36+
echo "changed=true" >> "$GITHUB_OUTPUT"
37+
echo "new=$new" >> "$GITHUB_OUTPUT"
38+
39+
- name: CHANGELOG has matching section
40+
if: steps.version.outputs.changed == 'true'
41+
run: |
42+
set -euo pipefail
43+
new="${{ steps.version.outputs.new }}"
44+
if ! grep -qE "^## ${new}([[:space:]]|$)" CHANGELOG.md; then
45+
echo "::error file=CHANGELOG.md::No '## ${new}' section found — every VERSION bump needs a matching CHANGELOG entry."
46+
exit 1
47+
fi
48+
echo " ✓ CHANGELOG.md has '## ${new}' section"
49+
50+
- name: VERSION moves forward
51+
if: steps.version.outputs.changed == 'true'
52+
run: |
53+
set -euo pipefail
54+
base="${{ github.event.pull_request.base.sha }}"
55+
new="${{ steps.version.outputs.new }}"
56+
old="$(git show "$base:VERSION" 2>/dev/null | tr -d '[:space:]' || echo '0.0.0')"
57+
# Lexicographic comparison works for left-padded semver but not generally.
58+
# Use sort -V for correctness.
59+
highest="$(printf '%s\n%s\n' "$old" "$new" | sort -V | tail -1)"
60+
if [ "$highest" != "$new" ] || [ "$old" = "$new" ]; then
61+
echo "::error file=VERSION::VERSION must increase. Previous: ${old}, this PR: ${new}."
62+
exit 1
63+
fi
64+
echo " ✓ VERSION ${old} → ${new}"

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# Changelog
22

3+
## 0.2.2 — 2026-05-18
4+
5+
### Release infrastructure
6+
7+
First formal release with proper OSS tooling. Establishes the foundation that 0.2.3 (`bstack repair` merges hooks) and 0.3.0 (SessionStart auto-upgrade) build on.
8+
9+
- **NEW** `bin/bstack` — top-level CLI dispatcher. Subcommands: `doctor`, `validate`, `repair`, `bootstrap`, `onboard`, `revamp`, `upgrade`, `config`, `update-check`, `wave`, `release tag`, `version`. Existing sub-binaries (`bstack-config`, `bstack-update-check`, `bstack-wave`) remain callable directly — the dispatcher is additive. `bstack release tag` is a maintainer helper that validates the tree, tags `vX.Y.Z`, pushes the tag, and creates the GitHub Release with the matching CHANGELOG section as notes.
10+
- **NEW** `CONTRIBUTING.md` — contribution guide: branch/PR shape, Conventional Commits, primitive-promotion rule, local validation steps.
11+
- **NEW** `RELEASE.md` — semver policy (pre-1.0: minor = potentially breaking), release checklist, retroactive-tag history, cadence guidance, update-check transport docs.
12+
- **NEW** `.github/workflows/ci.yml` — shellcheck on `scripts/*.sh` and `bin/*`, JSON validation for `assets/templates/*.snippet`, `bstack doctor --quiet` on templated fixtures.
13+
- **NEW** `.github/workflows/validate-release.yml` — PR check: if `VERSION` changed, `CHANGELOG.md` must have a matching `## X.Y.Z` section and the version must monotonically increase.
14+
- **CHANGED** `bin/bstack-update-check` — primary source is now the GitHub Releases API (`/repos/broomva/bstack/releases/latest`), with raw `VERSION` on `main` as fallback. **This means dev-branch VERSION bumps no longer leak to downstream installs as "available upgrades"** — only tagged releases do. Two new env vars: `BSTACK_RELEASES_URL` (primary), `BSTACK_REMOTE_URL` (fallback, unchanged behavior).
15+
- **HISTORY** `v0.2.0` and `v0.2.1` tags + GitHub Releases created retroactively on 2026-05-18 to give the update-check transport a stable anchor.
16+
17+
### Migration
18+
19+
None required. Existing installs continue to work — the API-first transport falls back to the raw `VERSION` URL on any failure, so behavior degrades gracefully.
20+
321
## 0.2.1 — 2026-05-16
422

523
### Drop legacy fallback shims from the 0.2.0 renumber

CONTRIBUTING.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Contributing to bstack
2+
3+
Thanks for opening a PR. bstack is the substrate that turns an agent-driven workspace into a self-operating system — every change to it propagates to every install. The contribution rules below exist to keep that propagation reliable.
4+
5+
## Branch + PR shape
6+
7+
- **Branch names**: `feat/<slug>`, `fix/<slug>`, `chore/<slug>`, `docs/<slug>`.
8+
- **PR title**: Conventional Commits format (`feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:`). Examples in `git log --oneline`.
9+
- **One concern per PR**. Mixing release infrastructure with a new feature makes both harder to revert.
10+
- **Squash on merge**. Linear history.
11+
12+
## Commit messages
13+
14+
Conventional Commits, body explains the *why*. Existing commits are the reference:
15+
16+
```
17+
feat(primitives): renumber so Wait=P9 — restore skill-name↔primitive-number alignment
18+
fix(SKILL.md): compress description to ≤1024 chars per Agent Skill spec
19+
chore(primitives): drop legacy fallback shims from the 0.2.0 renumber
20+
```
21+
22+
## Local validation (before pushing)
23+
24+
```bash
25+
make bstack-check # validates skills + hooks + bridge + policy (if you have the workspace harness)
26+
bash scripts/doctor.sh --quiet # primitive-contract compliance lint
27+
shellcheck scripts/*.sh bin/* # shell hygiene
28+
jq -e . assets/templates/*.snippet >/dev/null # template JSON shape
29+
```
30+
31+
CI runs the same checks via `.github/workflows/ci.yml`.
32+
33+
## Adding a new primitive (P21+)
34+
35+
bstack's L3 stability budget says governance changes are rare and deliberate. Before adding a new `Pn`:
36+
37+
1. Confirm rule-of-three: ≥ 3 independent instances of the failure mode the new primitive closes, each documented in `research/notes/` or an entity page.
38+
2. The pattern must have: concrete mechanism, stated invariant, stated failure mode it prevents.
39+
3. Add the row to `SKILL.md` §Primitives table.
40+
4. Add the section to `assets/templates/AGENTS.md.template` §Primitives.
41+
5. Update `references/primitives.md` Short-name index (must equal the new total count).
42+
6. Update `scripts/doctor.sh` to lint the new row.
43+
7. Bump VERSION minor and add a CHANGELOG entry — primitive additions are minor releases pre-1.0.
44+
45+
## Adding a skill to the roster
46+
47+
`SKILL.md` preamble has a `ROSTER=(...)` array of expected skill names. Add yours there. The skill itself lives in its own `broomva/<name>` repo; bstack tracks installation status, not source.
48+
49+
## Release
50+
51+
See `RELEASE.md`. Short version:
52+
53+
1. Bump `VERSION`.
54+
2. Prepend a section to `CHANGELOG.md` matching the new version.
55+
3. `validate-release.yml` confirms the two are aligned on the PR.
56+
4. After merge, tag and create the GitHub Release (`gh release create vX.Y.Z`).
57+
58+
## Style
59+
60+
- **Shell**: `set -euo pipefail`, quote variables, `shellcheck`-clean.
61+
- **Python**: PEP 8, type hints where useful, no global state.
62+
- **Markdown**: agent-readable surfaces (SKILL.md, AGENTS.md, primitives.md) stay terse and structural. Human-readable docs (RELEASE.md, CONTRIBUTING.md) can be longer.
63+
64+
## Questions
65+
66+
Open a discussion in the repo or ping in the workspace channel where bstack is being used. PRs without context get bounced — paste the failure mode, the proposed fix, and the validation you ran.

RELEASE.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Release process
2+
3+
bstack ships as a skill installed via `npx skills add broomva/bstack` (vendored) or `git clone` (git install). Every release must be reachable to both install types and properly tagged so `bin/bstack-update-check` and downstream tooling can discover it.
4+
5+
## Versioning policy (Semantic Versioning)
6+
7+
bstack follows [SemVer 2.0](https://semver.org/) with the **pre-1.0 convention** that minor versions may carry breaking behavior changes:
8+
9+
| Pre-1.0 (`0.x.y`) | Meaning |
10+
|---|---|
11+
| `0.x.0` (minor) | New primitives, new hooks wired by default, behavior-changing default flips. **May break existing installs** — document the migration in CHANGELOG. |
12+
| `0.x.y` (patch) | Bug fixes, doc updates, additive non-default features, doctor lint additions. Safe to auto-upgrade. |
13+
14+
Once 1.0.0 ships, the standard SemVer rules apply (major = breaking, minor = additive backwards-compatible, patch = fixes only).
15+
16+
### Examples
17+
18+
| Change | Bump |
19+
|---|---|
20+
| New primitive `P21` added to table + doctor lint | **Minor** — governance change |
21+
| New optional hook in `settings.json.snippet` | **Minor** — installs that re-run `bstack repair` pick it up |
22+
| `bstack-update-check` switches transport (raw VERSION → GitHub releases API) | **Patch** — internal mechanism, observable behavior unchanged |
23+
| Default flip (`auto_upgrade` defaults to true) | **Minor** — silently changes behavior for existing users |
24+
| Typo fix in CLAUDE.md.template | **Patch** |
25+
| Remove legacy fallback shim that 0.x.y added | **Minor** — breaks pinned-to-shim installs even if "internal" |
26+
27+
## Release checklist
28+
29+
Use this checklist for every release. The CI workflow `validate-release.yml` enforces the VERSION ↔ CHANGELOG alignment automatically; the rest is human discipline.
30+
31+
1. **PR opens with**:
32+
- `VERSION` bumped to the new `X.Y.Z`
33+
- `CHANGELOG.md` prepended with `## X.Y.Z — YYYY-MM-DD` section
34+
- Any breaking changes documented under a `### Migration` subheading
35+
2. **Validate locally**`bash scripts/doctor.sh --quiet`, `shellcheck scripts/*.sh bin/*`, `jq -e . assets/templates/*.snippet`.
36+
3. **CI passes**`ci.yml` (lint) + `validate-release.yml` (version/changelog match).
37+
4. **Reviewer approves** — at least one human or `pr-review-toolkit:code-reviewer` agent verdict.
38+
5. **Merge to main** (squash).
39+
6. **Tag + GitHub Release**`bstack release tag` (≥ 0.2.2) wraps the manual sequence:
40+
```bash
41+
git fetch origin && git checkout main && git pull --ff-only
42+
bstack release tag # validates clean tree, on main, in sync; tags + pushes + creates Release
43+
```
44+
The dispatcher reads `VERSION`, picks the matching `## X.Y.Z` section out of `CHANGELOG.md` as the release notes, and uses the first `### ` heading inside that section as the release title. If `gh` is not installed the tag is still pushed and the command prints the manual `gh release create` invocation.
45+
46+
Manual fallback (pre-0.2.2 installs, or if the dispatcher is unavailable):
47+
```bash
48+
VERSION=$(cat VERSION)
49+
git tag -a "v${VERSION}" -m "v${VERSION} — <title from CHANGELOG>"
50+
git push origin "v${VERSION}"
51+
gh release create "v${VERSION}" --title "v${VERSION} — <title>" --notes-file <(awk "/^## ${VERSION}/{flag=1; next} /^## /{flag=0} flag" CHANGELOG.md)
52+
```
53+
7. **Downstream verification**:
54+
- `bin/bstack-update-check --force` from any install should now emit `UPGRADE_AVAILABLE <old> <new>` within the cache TTL window.
55+
- For git installs with `auto_upgrade=true`, the SessionStart hook (≥ 0.3.0) auto-pulls on next session.
56+
57+
## Cadence
58+
59+
bstack has no fixed release cadence. The triggers for a release are:
60+
61+
- A new primitive earns its rule-of-three and gets promoted → **minor**.
62+
- A behavior-changing default flip (anything that affects installs without their action) → **minor**.
63+
- A bundle of fixes/docs is ready to ship → **patch**.
64+
- A critical bug or security issue → **patch**, immediately.
65+
66+
Avoid letting `main` accumulate more than 2-3 unreleased PRs — each unreleased PR is invisible to downstream installs.
67+
68+
## Backporting
69+
70+
bstack does not maintain release branches. If a fix on `main` is needed urgently on a pinned install, the downstream user pins to a tag and applies the fix locally. There is no `0.2.x` branch to backport to.
71+
72+
## Retroactive tagging (history)
73+
74+
The repo's first tagged release was **v0.2.0** (2026-05-16, commit `322ba23`). Earlier versions (`0.1.0`, etc.) referenced in `CHANGELOG.md` predate the release-infrastructure formalization and are not tagged.
75+
76+
Tags `v0.2.0` and `v0.2.1` were created retroactively on 2026-05-18 as part of the v0.2.2 release-infrastructure work to give `bin/bstack-update-check` a stable anchor to compare against.
77+
78+
## Update check transport
79+
80+
`bin/bstack-update-check` (≥ 0.2.2) compares the local `VERSION` against:
81+
82+
1. **Primary**: GitHub Releases API — `GET /repos/broomva/bstack/releases/latest`, read `.tag_name`, strip leading `v`.
83+
2. **Fallback**: raw `VERSION` file on `main` (`https://raw.githubusercontent.com/broomva/bstack/main/VERSION`) — used when the API is unreachable or rate-limited.
84+
85+
This separation means **development-branch VERSION bumps do not leak as available upgrades to downstream installs** — only tagged releases do. Bump `VERSION` freely on a feature branch; downstream sees nothing until the tag lands.
86+
87+
## Disabling update checks
88+
89+
Downstream users can disable update checks entirely:
90+
91+
```bash
92+
bstack-config set update_check false
93+
```
94+
95+
Or snooze a specific version via the `/bstack-upgrade` interactive flow.
96+
97+
## Questions
98+
99+
See `CONTRIBUTING.md` for the contribution + PR shape. Cadence-or-policy questions belong in repo discussions; mechanical bugs in the release workflow are issues.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.1
1+
0.2.2

0 commit comments

Comments
 (0)