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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## 0.22.0 — 2026-05-28

### feat: wire the RCS control loop on `bstack bootstrap` (close the split-brain)

`onboard.sh` (the wizard) wired the control loop via `install-rcs-stability.sh`; `bootstrap.sh` (the `/bstack bootstrap` command — what the SKILL.md tells agents to run) **never did**. Freshly-bootstrapped workspaces had governance files but an **open loop** — no L0/L1 audit hooks, no `.control/audit/`, no closure-arc definitions. And `doctor` reported all of that as soft `[info]`, so the loop being open was invisible.

This release makes `/bstack bootstrap` wire the loop, scaffold the loop definitions, and report loop liveness as a first-class verdict.

### Added

- **`bootstrap.sh` Phase 3.5 — RCS loop wiring.** Bootstrap now calls `install-rcs-stability.sh` (L0 PostToolUse + L1 Stop audit hooks + `.control/audit/` + L3 gates), reusing the same idempotent installer the wizard uses. Guarded by `BSTACK_SKIP_RCS=1` (mirrors `BSTACK_SKIP_SKILLS=1`) for governance-only bootstrap. The `|| true` guard preserves bootstrap's non-blocking contract under `set -e`.
- **`bootstrap.sh` Phase 2 — `.control/arcs.yaml` scaffold.** The closure-contract arcs (the workspace's own editable loop definitions) are now scaffolded from `arcs.yaml.template` alongside CLAUDE.md / AGENTS.md / policy.yaml. (`compute-arc-status.sh` already fell back to the bundled template; scaffolding gives the workspace an editable copy.)
- **`doctor.sh` §23 — Control-loop closure verdict.** The single verdict answering "is the loop wired + connected + running?" — three states: (a) substrate absent, (b) wired-but-idle, (c) wired + running + closing. Composes W (audit hooks + dir) / R (audit logs fresh < 7d) / C (arcs + composite-ω resolvable). The W check reads **both** `settings.json` and `settings.local.json` — Claude Code merges them at runtime, and shared repos legitimately keep machine-local hook paths in the gitignored `settings.local.json` (surfaced by dogfooding on the stimulus repo, whose tracked `settings.json` uses repo-relative vendored hooks). **Soft by default** (audit logs are empty until the first hook fires; a hard default would redden every fresh bootstrap for purely temporal reasons); `BSTACK_LOOP_STRICT=1` promotes "wired-but-idle" to a hard `--strict` gap for CI lanes. §19 already hard-gates the case that matters (a wired loop genuinely diverging), so §23 doesn't double-count it.

### Changed

- `tests/canary/01-fresh-bootstrap.test.sh` — now asserts `PostToolUse` hook, `.control/audit/`, `.control/arcs.yaml`, and the L0/L1 audit markers (covers Phase 3.5 + arcs scaffold). 14/14.
- `references/new-workspace-flow.md` — documents that `bootstrap` (not only `onboard`) wires the loop; adds arcs.yaml + §20–§23 to the doctor-report summary.
- `VERSION` 0.21.10 → 0.22.0.

### Notes

- Idempotent + additive: re-running bootstrap (or bootstrap after onboard, in either order) is a no-op — hook markers (P1/P6/P2/P7/P17 vs L3-G0 vs L0-audit/L1-audit) are all distinct; `scaffold_governance_file` early-returns `[keep]` on existing files.
- `doctor.sh` resolves `WORKSPACE="${BROOMVA_WORKSPACE:-$HOME/broomva}"` — running it from a sub-repo without `BROOMVA_WORKSPACE` set measures the parent workspace, not `$PWD`. (Surfacing this as a possible future UX fix; out of scope here.)

## 0.21.10 — 2026-05-27

### revert: --full-depth no longer needed after broomva/skills restructure
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.21.10
0.22.0
7 changes: 5 additions & 2 deletions references/new-workspace-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ The concrete sequence from `npx skills add broomva/bstack` to a fully-wired RCS-
```bash
npx skills add broomva/bstack # clones bstack into ~/.agents/skills/bstack/
/bstack onboard # wizard: workspace · profile · life · auto-merge
# — or, without the wizard —
/bstack bootstrap # scaffold governance + wire hooks + wire the loop
```

`onboard.sh` runs `bootstrap.sh` (scaffolds governance files), detects tech stack from repo signals, then calls `install-rcs-stability.sh` which deploys the multi-layer audit + enforcement plumbing.
Both paths wire the RCS control loop. `bootstrap.sh` scaffolds governance files (incl. `.control/arcs.yaml`), wires the base hooks, then — in **Phase 3.5** — calls `install-rcs-stability.sh` to deploy the multi-layer audit + enforcement plumbing. `onboard.sh` additionally runs the wizard and detects the tech stack. Skip loop wiring with `BSTACK_SKIP_RCS=1` (governance-only bootstrap). Previously only `onboard` wired the loop; `bootstrap` left it open — that split-brain is closed as of 0.22.0.

## Files deployed into the workspace

| Path | Source | Purpose |
|---|---|---|
| `CLAUDE.md`, `AGENTS.md`, `.control/policy.yaml`, `METALAYER.md` | `assets/templates/*.template` | Governance substrate (P-row primitives table, reflexive trigger rules, gate config) |
| `.control/arcs.yaml` | `arcs.yaml.template` | Closure-contract arcs — the workspace's own editable loop definitions (5-tuple). Scaffolded by `bootstrap.sh` Phase 2 |
| `.githooks/pre-commit` | `githook-pre-commit-l3-rate.sh.template` | G1 — blocks `git commit` over τ_a₃ L3 commit rate (bypassable with `--no-verify`) |
| `.github/workflows/l3-stability.yml` | `gh-workflow-l3-stability.yml.template` | G2 — runs `compute-lambda` + `l3-rate-gate` on every PR touching L3 paths; comments verdict |
| `.claude/settings.json` (merged) | `settings.json.l3-stability-hook.snippet` + `settings.json.multi-layer-hooks.snippet` | 3 hook entries: PreToolUse `L3-G0`, PostToolUse `L0-audit`, Stop `L1-audit` |
Expand All @@ -33,7 +36,7 @@ npx skills add broomva/bstack # clones bstack into ~/.agents/skills/bsta

## What `bstack doctor` reports

§1–§13 v0.13.0 substrate checks · §14 RCS λ compute + drift · §15 G0/G1/G2 wiring · §16 L0 tool-call audit summary · §17 L1 reflex compliance · §18 L2 promotion throttle · §19 multi-layer composite health (`L0=stable L1=stable L2=stable L3=stable` form). New workspaces show §16–§18 as informational "no audit log yet" until first events fire.
§1–§13 v0.13.0 substrate checks · §14 RCS λ compute + drift · §15 G0/G1/G2 wiring · §16 L0 tool-call audit summary · §17 L1 reflex compliance · §18 L2 promotion throttle · §19 multi-layer composite health (`L0=stable L1=stable L2=stable L3=stable` form) · §20 federation registry · §21 closure-contract arcs · §22 composite-ω drift trend · **§23 control-loop closure verdict** — the single "is the loop wired + connected + running?" answer (substrate-absent / wired-but-idle / wired+running+closing). New workspaces show §16–§18 + §23 as informational ("no audit log yet" / "wired but idle") until first events fire; For CI lanes that must fail on an idle loop, run `BSTACK_LOOP_STRICT=1 doctor.sh --strict` — `BSTACK_LOOP_STRICT=1` records the gap but only `--strict` changes the exit code, so **both** are required.

## Common gotchas

Expand Down
42 changes: 38 additions & 4 deletions scripts/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
#!/usr/bin/env bash
# bstack bootstrap — install all 28 Broomva Stack skills + scaffold governance + wire hooks
# bstack bootstrap — install all 31 Broomva Stack skills + scaffold governance + wire hooks
#
# Three phases:
# Four phases + loop wiring:
# 1. Skill install: npx skills add for each ROSTER entry
# 2. Workspace scaffold: install missing CLAUDE.md / AGENTS.md / .control/policy.yaml
# from assets/templates/ (idempotent — never overwrites existing files)
# / .control/arcs.yaml from assets/templates/ (idempotent — never overwrites)
# 3. Hooks wire-up: merge bstack hooks into .claude/settings.json (additive only)
# 3.5 RCS loop wiring: install-rcs-stability.sh deploys L0/L1 audit hooks + audit
# dir + L3 gates (G0/G1/G2 + rcs-parameters.toml) so the control loop is
# actually wired + connected, not just declared. Skip with BSTACK_SKIP_RCS=1
# (governance-only bootstrap). Mirrors the wizard path (onboard.sh).
# 4. bstack doctor --quiet to verify the primitive contract + loop closure (§23).
#
# After completion, runs `bstack doctor --quiet` to verify primitive contract.
# Env escapes: BSTACK_SKIP_SKILLS=1 (skip Phase 1), BSTACK_SKIP_RCS=1 (skip Phase 3.5).
set -e

AGENTS_DIR="${HOME}/.agents/skills"
Expand Down Expand Up @@ -143,6 +148,9 @@ scaffold_governance_file() {
scaffold_governance_file "CLAUDE.md" "CLAUDE.md.template"
scaffold_governance_file "AGENTS.md" "AGENTS.md.template"
scaffold_governance_file ".control/policy.yaml" "policy.yaml.template"
# Closure-contract arcs (the loop DEFINITIONS — the workspace's own editable
# copy; compute-arc-status.sh otherwise falls back to the bundled template).
scaffold_governance_file ".control/arcs.yaml" "arcs.yaml.template"

echo " scaffolded: $scaffolded | preserved: $preserved"

Expand Down Expand Up @@ -211,6 +219,32 @@ else
echo " manual: see assets/templates/settings.json.snippet"
fi

# ─── Phase 3.5: wire the RCS control loop (L0/L1 audit + L3 gates) ─────────
# Closes the split-brain: onboard.sh (the wizard) wired the loop here; the
# bootstrap command did not, leaving freshly-bootstrapped workspaces with
# governance files but an OPEN loop (no audit hooks, no audit dir). This
# deploys L0 PostToolUse + L1 Stop audit hooks + .control/audit/ + L3 gates
# via the same idempotent installer the wizard uses. Skip with BSTACK_SKIP_RCS=1.
if [ "${BSTACK_SKIP_RCS:-0}" = "1" ]; then
echo ""
echo "=== bstack RCS loop wiring ==="
echo "BSTACK_SKIP_RCS=1 — skipping loop wiring (governance-only bootstrap)."
echo "(Run \`bash scripts/install-rcs-stability.sh\` later to wire the loop.)"
else
RCS_INSTALLER="${BOOTSTRAP_SCRIPT_DIR}/install-rcs-stability.sh"
if [ -f "$RCS_INSTALLER" ]; then
echo ""
echo "=== bstack RCS loop wiring ==="
# Non-blocking contract (matches onboard.sh): loop wiring must never abort
# the bootstrap. bootstrap runs `set -e` but NOT `pipefail`, so the
# pipeline's status is sed's (≈always 0) and the installer's exit is already
# discarded; `|| true` is defensive belt-and-suspenders. The installer's
# diagnostics still print through the pipe, and a silently-failed wiring is
# caught downstream by doctor §23 + the canary's audit-dir/marker assertions.
BROOMVA_WORKSPACE="$WORKSPACE_DIR" bash "$RCS_INSTALLER" 2>&1 | sed 's/^/ /' || true
fi
fi

# ─── Phase 4: bstack doctor verification ───────────────────────────────────
# Always-active step; never blocks. Surfaces gaps in AGENTS.md / CLAUDE.md /
# .control/policy.yaml compliance with the bstack primitive contract.
Expand Down
86 changes: 86 additions & 0 deletions scripts/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
# bash scripts/doctor.sh # full report
# bash scripts/doctor.sh --quiet # only warnings
# bash scripts/doctor.sh --strict # exit 1 if any gap found (CI mode)
#
# Env:
# BSTACK_LOOP_STRICT=1 §23 only — treat a wired-but-idle control loop as a
# gap. To FAIL CI on it you must ALSO pass --strict
# (i.e. `BSTACK_LOOP_STRICT=1 doctor.sh --strict`);
# BSTACK_LOOP_STRICT alone records the gap but never
# changes the exit code.

set -uo pipefail

Expand Down Expand Up @@ -1133,6 +1140,85 @@ else
fi
fi

# ── Section 23: Control-loop closure verdict (the "is it wired + running?") ─
# The single verdict answering: is the RCS control loop wired, connected, and
# running on this workspace? Distinct from §19 (the budget/stability lens, which
# already hard-gates a wired-but-diverging loop). §23 composes three signals:
# W (wired) — .claude/settings.json OR settings.local.json carries the
# L0-audit + L1-audit hook markers AND .control/audit/ exists.
# R (running) — an L0/L1 audit log exists, is non-empty, and was written in
# the last 7 days (multi-session cadence).
# C (closing) — closure arcs are declared/resolvable AND composite-ω is
# computable (compute-budget-status.sh present).
# Three states: (a) substrate absent (!W) → info; (b) wired-but-idle (W && !R)
# → SOFT by default, hard gap only under BSTACK_LOOP_STRICT=1 (audit logs are
# empty until the first hook fires, so a hard default would redden every fresh
# bootstrap for purely temporal reasons); (c) W && R → ok.
section "23. Control-loop closure verdict"

LOOP_AUDIT_DIR="$WORKSPACE/.control/audit"
LOOP_STRICT="${BSTACK_LOOP_STRICT:-0}"

# W — wired. Claude Code merges settings.json + settings.local.json at runtime,
# and shared repos legitimately keep machine-local hook paths (which carry an
# absolute bstack path) out of the tracked settings.json by wiring them in the
# gitignored settings.local.json. So check BOTH — the loop is wired if either
# file carries the L0-audit + L1-audit markers.
W_OK=0
if [ -d "$LOOP_AUDIT_DIR" ]; then
_l0=0; _l1=0
for _s in "$WORKSPACE/.claude/settings.json" "$WORKSPACE/.claude/settings.local.json"; do
[ -f "$_s" ] || continue
grep -q '"L0-audit"' "$_s" 2>/dev/null && _l0=1
grep -q '"L1-audit"' "$_s" 2>/dev/null && _l1=1
done
[ "$_l0" = "1" ] && [ "$_l1" = "1" ] && W_OK=1
fi

# R — running (any L0/L1 log non-empty AND modified within 7 days)
R_OK=0
LOOP_FRESH=""
for _log in l0-tools.jsonl l1-reflexes.jsonl; do
_p="$LOOP_AUDIT_DIR/$_log"
if [ -s "$_p" ] && [ -n "$(find "$_p" -mtime -7 2>/dev/null)" ]; then
R_OK=1
LOOP_FRESH="$LOOP_FRESH $_log"
fi
done

# C — closing: the WORKSPACE has declared its own closure arcs AND composite-ω
# is computable. We require the workspace's own .control/arcs.yaml (not the
# bundled template fallback) — otherwise C would be true on every intact bstack
# checkout and "closing" would mean nothing beyond "the repo shipped its files."
C_OK=0
if [ -f "$WORKSPACE/.control/arcs.yaml" ] && [ -f "$BSTACK_REPO/scripts/compute-budget-status.sh" ]; then
C_OK=1
fi

if [ "$W_OK" = "0" ]; then
# (a) substrate absent — legitimate under BSTACK_SKIP_RCS=1 governance-only bootstrap
[ "$QUIET" = "0" ] && echo " [info] control loop NOT wired (L0/L1 audit hooks or .control/audit/ absent)"
[ "$QUIET" = "0" ] && echo " → fix: bash $BSTACK_REPO/scripts/install-rcs-stability.sh (or re-run \`bstack bootstrap\` without BSTACK_SKIP_RCS=1)"
elif [ "$R_OK" = "0" ]; then
# (b) wired but idle — soft by default, hard only under BSTACK_LOOP_STRICT=1
_closing_note="arcs resolvable"; [ "$C_OK" = "0" ] && _closing_note="arcs/composite not resolvable"
if [ "$LOOP_STRICT" = "1" ]; then
gap "control loop WIRED but IDLE (no L0/L1 audit events in last 7d; $_closing_note)" \
"exercise a session so PostToolUse/Stop hooks fire; or unset BSTACK_LOOP_STRICT to treat idle as soft"
else
[ "$QUIET" = "0" ] && echo " [info] control loop WIRED but IDLE (no L0/L1 audit events in last 7d; $_closing_note)"
[ "$QUIET" = "0" ] && echo " → this is normal for a freshly-bootstrapped or intermittent workspace; events accrue as sessions run"
[ "$QUIET" = "0" ] && echo " → CI lanes: run with BSTACK_LOOP_STRICT=1 AND --strict to fail on idle"
fi
else
# (c) wired + running (+ closing)
if [ "$C_OK" = "1" ]; then
ok "control loop: wired + running + closing (audit live:$LOOP_FRESH; arcs + composite-ω resolvable)"
else
ok "control loop: wired + running (audit live:$LOOP_FRESH; arcs/composite not yet resolvable)"
fi
fi

# ── summary ─────────────────────────────────────────────────────────────────
echo ""
TOTAL=$((PASSES + GAPS))
Expand Down
3 changes: 3 additions & 0 deletions scripts/skill-graduate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ should_exclude() {
esac
local ex
for ex in "${EXTRA_EXCLUDES[@]:-}"; do
# SC2053 intentional: $ex is a glob pattern from EXTRA_EXCLUDES; quoting
# the RHS would defeat the pattern match this exclude check depends on.
# shellcheck disable=SC2053
[ -n "$ex" ] && [[ "$item" == $ex ]] && return 0
done
return 1
Expand Down
24 changes: 21 additions & 3 deletions tests/canary/01-fresh-bootstrap.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fi
# Step 2: governance files scaffolded.
echo ""
echo "Step 2: governance files present"
for f in CLAUDE.md AGENTS.md .control/policy.yaml; do
for f in CLAUDE.md AGENTS.md .control/policy.yaml .control/arcs.yaml; do
if [ -f "$TW/$f" ]; then
pass "$f scaffolded"
else
Expand All @@ -76,8 +76,9 @@ echo ""
echo "Step 3: .claude/settings.json wires hooks"
if [ -f "$TW/.claude/settings.json" ]; then
pass "settings.json present"
# Hook events the substrate ships: SessionStart, Stop, PreToolUse.
for ev in SessionStart Stop PreToolUse; do
# Hook events the substrate ships: SessionStart, Stop, PreToolUse, plus
# PostToolUse (L0-audit hook wired by Phase 3.5 loop wiring).
for ev in SessionStart Stop PreToolUse PostToolUse; do
if jq -e --arg ev "$ev" '.hooks[$ev]' "$TW/.claude/settings.json" >/dev/null 2>&1; then
pass "hooks.$ev present"
else
Expand All @@ -88,6 +89,23 @@ else
fail "settings.json missing"
fi

# Step 3.5: RCS control loop wired (Phase 3.5). BSTACK_SKIP_RCS is unset, so
# bootstrap calls install-rcs-stability.sh → L0/L1 audit hooks + audit dir.
echo ""
echo "Step 3.5: RCS control loop wired"
if [ -d "$TW/.control/audit" ]; then
pass ".control/audit/ created"
else
fail ".control/audit/ missing (Phase 3.5 loop wiring did not run)"
fi
if [ -f "$TW/.claude/settings.json" ] \
&& grep -q '"L0-audit"' "$TW/.claude/settings.json" 2>/dev/null \
&& grep -q '"L1-audit"' "$TW/.claude/settings.json" 2>/dev/null; then
pass "L0-audit + L1-audit hook markers present"
else
fail "L0/L1 audit hook markers missing"
fi

# Step 4: doctor runs cleanly (no crash) and produces the expected report
# shape. Doctor reports gaps for primitive mechanisms that depend on
# companion-skill installs not yet present in this minimal fixture — that
Expand Down
Loading