Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
lint:
name: Lint shell + JSON templates
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: ShellCheck (scripts + bin)
run: |
# Only lint files that have a shebang we recognize.
set -euo pipefail
mapfile -t targets < <(
find scripts bin -type f \( -name "*.sh" -o ! -name "*.*" \) 2>/dev/null \
| xargs -I {} sh -c 'head -n1 "{}" | grep -q "^#!.*sh" && echo "{}"' \
| sort -u
)
if [ "${#targets[@]}" -eq 0 ]; then
echo "No shell scripts found to lint."
exit 0
fi
printf ' • %s\n' "${targets[@]}"
# SC1091: don't follow sourced files; SC2155: declare-and-assign exit-code
# warnings — we accept these in bstack's defensive shell style.
shellcheck --severity=warning --exclude=SC1091,SC2155 "${targets[@]}"

- name: Validate JSON templates
run: |
set -euo pipefail
shopt -s nullglob
fail=0
for f in assets/templates/*.snippet; do
if ! jq -e . "$f" >/dev/null 2>&1; then
echo "::error file=$f::invalid JSON shape"
fail=1
else
echo " • $f — valid JSON"
fi
done
exit "$fail"

doctor:
name: bstack doctor (primitive-contract lint)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run doctor against templates
run: |
set -euo pipefail
# doctor.sh expects CLAUDE.md / AGENTS.md / .control/policy.yaml in cwd.
# Stage the templated versions as a fixture so doctor lints what
# downstream installs would see after `bstack bootstrap`.
mkdir -p .control
cp assets/templates/CLAUDE.md.template ./CLAUDE.md 2>/dev/null || true
cp assets/templates/AGENTS.md.template ./AGENTS.md 2>/dev/null || true
cp assets/templates/policy.yaml.template .control/policy.yaml 2>/dev/null || true
bash scripts/doctor.sh --quiet
64 changes: 64 additions & 0 deletions .github/workflows/validate-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Validate release

on:
pull_request:
paths:
- VERSION
- CHANGELOG.md

permissions:
contents: read

jobs:
version-changelog-alignment:
name: VERSION ↔ CHANGELOG match
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Detect VERSION change
id: version
run: |
set -euo pipefail
base="${{ github.event.pull_request.base.sha }}"
if ! git diff --name-only "$base"...HEAD | grep -qx VERSION; then
echo "VERSION not modified in this PR — nothing to validate."
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
new="$(cat VERSION | tr -d '[:space:]')"
if ! printf '%s' "$new" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error file=VERSION::VERSION '$new' is not semver X.Y.Z"
exit 1
fi
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "new=$new" >> "$GITHUB_OUTPUT"

- name: CHANGELOG has matching section
if: steps.version.outputs.changed == 'true'
run: |
set -euo pipefail
new="${{ steps.version.outputs.new }}"
if ! grep -qE "^## ${new}([[:space:]]|$)" CHANGELOG.md; then
echo "::error file=CHANGELOG.md::No '## ${new}' section found — every VERSION bump needs a matching CHANGELOG entry."
exit 1
fi
echo " ✓ CHANGELOG.md has '## ${new}' section"

- name: VERSION moves forward
if: steps.version.outputs.changed == 'true'
run: |
set -euo pipefail
base="${{ github.event.pull_request.base.sha }}"
new="${{ steps.version.outputs.new }}"
old="$(git show "$base:VERSION" 2>/dev/null | tr -d '[:space:]' || echo '0.0.0')"
# Lexicographic comparison works for left-padded semver but not generally.
# Use sort -V for correctness.
highest="$(printf '%s\n%s\n' "$old" "$new" | sort -V | tail -1)"
if [ "$highest" != "$new" ] || [ "$old" = "$new" ]; then
echo "::error file=VERSION::VERSION must increase. Previous: ${old}, this PR: ${new}."
exit 1
fi
echo " ✓ VERSION ${old} → ${new}"
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 0.2.2 — 2026-05-18

### Release infrastructure

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.

- **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.
- **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, retroactive-tag history, cadence guidance, 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 the version must monotonically increase.
- **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).
- **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.

### Migration

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.

## 0.2.1 — 2026-05-16

### Drop legacy fallback shims from the 0.2.0 renumber
Expand Down
66 changes: 66 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Contributing to bstack

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.

## Branch + PR shape

- **Branch names**: `feat/<slug>`, `fix/<slug>`, `chore/<slug>`, `docs/<slug>`.
- **PR title**: Conventional Commits format (`feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:`). Examples in `git log --oneline`.
- **One concern per PR**. Mixing release infrastructure with a new feature makes both harder to revert.
- **Squash on merge**. Linear history.

## Commit messages

Conventional Commits, body explains the *why*. Existing commits are the reference:

```
feat(primitives): renumber so Wait=P9 — restore skill-name↔primitive-number alignment
fix(SKILL.md): compress description to ≤1024 chars per Agent Skill spec
chore(primitives): drop legacy fallback shims from the 0.2.0 renumber
```

## Local validation (before pushing)

```bash
make bstack-check # validates skills + hooks + bridge + policy (if you have the workspace harness)
bash scripts/doctor.sh --quiet # primitive-contract compliance lint
shellcheck scripts/*.sh bin/* # shell hygiene
jq -e . assets/templates/*.snippet >/dev/null # template JSON shape
```

CI runs the same checks via `.github/workflows/ci.yml`.

## Adding a new primitive (P21+)

bstack's L3 stability budget says governance changes are rare and deliberate. Before adding a new `Pn`:

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.
2. The pattern must have: concrete mechanism, stated invariant, stated failure mode it prevents.
3. Add the row to `SKILL.md` §Primitives table.
4. Add the section to `assets/templates/AGENTS.md.template` §Primitives.
5. Update `references/primitives.md` Short-name index (must equal the new total count).
6. Update `scripts/doctor.sh` to lint the new row.
7. Bump VERSION minor and add a CHANGELOG entry — primitive additions are minor releases pre-1.0.

## Adding a skill to the roster

`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.

## Release

See `RELEASE.md`. Short version:

1. Bump `VERSION`.
2. Prepend a section to `CHANGELOG.md` matching the new version.
3. `validate-release.yml` confirms the two are aligned on the PR.
4. After merge, tag and create the GitHub Release (`gh release create vX.Y.Z`).

## Style

- **Shell**: `set -euo pipefail`, quote variables, `shellcheck`-clean.
- **Python**: PEP 8, type hints where useful, no global state.
- **Markdown**: agent-readable surfaces (SKILL.md, AGENTS.md, primitives.md) stay terse and structural. Human-readable docs (RELEASE.md, CONTRIBUTING.md) can be longer.

## Questions

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.
99 changes: 99 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Release process

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.

## Versioning policy (Semantic Versioning)

bstack follows [SemVer 2.0](https://semver.org/) with the **pre-1.0 convention** that minor versions may carry breaking behavior changes:

| Pre-1.0 (`0.x.y`) | Meaning |
|---|---|
| `0.x.0` (minor) | New primitives, new hooks wired by default, behavior-changing default flips. **May break existing installs** — document the migration in CHANGELOG. |
| `0.x.y` (patch) | Bug fixes, doc updates, additive non-default features, doctor lint additions. Safe to auto-upgrade. |

Once 1.0.0 ships, the standard SemVer rules apply (major = breaking, minor = additive backwards-compatible, patch = fixes only).

### Examples

| Change | Bump |
|---|---|
| New primitive `P21` added to table + doctor lint | **Minor** — governance change |
| New optional hook in `settings.json.snippet` | **Minor** — installs that re-run `bstack repair` pick it up |
| `bstack-update-check` switches transport (raw VERSION → GitHub releases API) | **Patch** — internal mechanism, observable behavior unchanged |
| Default flip (`auto_upgrade` defaults to true) | **Minor** — silently changes behavior for existing users |
| Typo fix in CLAUDE.md.template | **Patch** |
| Remove legacy fallback shim that 0.x.y added | **Minor** — breaks pinned-to-shim installs even if "internal" |

## Release checklist

Use this checklist for every release. The CI workflow `validate-release.yml` enforces the VERSION ↔ CHANGELOG alignment automatically; the rest is human discipline.

1. **PR opens with**:
- `VERSION` bumped to the new `X.Y.Z`
- `CHANGELOG.md` prepended with `## X.Y.Z — YYYY-MM-DD` section
- Any breaking changes documented under a `### Migration` subheading
2. **Validate locally** — `bash scripts/doctor.sh --quiet`, `shellcheck scripts/*.sh bin/*`, `jq -e . assets/templates/*.snippet`.
3. **CI passes** — `ci.yml` (lint) + `validate-release.yml` (version/changelog match).
4. **Reviewer approves** — at least one human or `pr-review-toolkit:code-reviewer` agent verdict.
5. **Merge to main** (squash).
6. **Tag + GitHub Release** — `bstack release tag` (≥ 0.2.2) wraps the manual sequence:
```bash
git fetch origin && git checkout main && git pull --ff-only
bstack release tag # validates clean tree, on main, in sync; tags + pushes + creates Release
```
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.

Manual fallback (pre-0.2.2 installs, or if the dispatcher is unavailable):
```bash
VERSION=$(cat VERSION)
git tag -a "v${VERSION}" -m "v${VERSION} — <title from CHANGELOG>"
git push origin "v${VERSION}"
gh release create "v${VERSION}" --title "v${VERSION} — <title>" --notes-file <(awk "/^## ${VERSION}/{flag=1; next} /^## /{flag=0} flag" CHANGELOG.md)
```
7. **Downstream verification**:
- `bin/bstack-update-check --force` from any install should now emit `UPGRADE_AVAILABLE <old> <new>` within the cache TTL window.
- For git installs with `auto_upgrade=true`, the SessionStart hook (≥ 0.3.0) auto-pulls on next session.

## Cadence

bstack has no fixed release cadence. The triggers for a release are:

- A new primitive earns its rule-of-three and gets promoted → **minor**.
- A behavior-changing default flip (anything that affects installs without their action) → **minor**.
- A bundle of fixes/docs is ready to ship → **patch**.
- A critical bug or security issue → **patch**, immediately.

Avoid letting `main` accumulate more than 2-3 unreleased PRs — each unreleased PR is invisible to downstream installs.

## Backporting

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.

## Retroactive tagging (history)

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.

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.

## Update check transport

`bin/bstack-update-check` (≥ 0.2.2) compares the local `VERSION` against:

1. **Primary**: GitHub Releases API — `GET /repos/broomva/bstack/releases/latest`, read `.tag_name`, strip leading `v`.
2. **Fallback**: raw `VERSION` file on `main` (`https://raw.githubusercontent.com/broomva/bstack/main/VERSION`) — used when the API is unreachable or rate-limited.

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.

## Disabling update checks

Downstream users can disable update checks entirely:

```bash
bstack-config set update_check false
```

Or snooze a specific version via the `/bstack-upgrade` interactive flow.

## Questions

See `CONTRIBUTING.md` for the contribution + PR shape. Cadence-or-policy questions belong in repo discussions; mechanical bugs in the release workflow are issues.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.1
0.2.2
Loading
Loading