From b3fa912d81daa1f02860d90535fdc1e190cbc9ab Mon Sep 17 00:00:00 2001 From: "Carlos D. Escobar-Valbuena" Date: Thu, 28 May 2026 20:02:08 -0500 Subject: [PATCH 1/3] =?UTF-8?q?feat(0.22.0):=20wire=20RCS=20control=20loop?= =?UTF-8?q?=20on=20bootstrap=20+=20doctor=20=C2=A723=20closure=20verdict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close the split-brain: onboard.sh wired the control loop via install-rcs-stability.sh; bootstrap.sh (the /bstack bootstrap command) never did, leaving freshly-bootstrapped workspaces with governance files but an OPEN loop (no L0/L1 audit hooks, no .control/audit/, no closure-arc definitions). doctor reported all of that as soft [info], so the open loop was invisible. Dep-Chain (P14): - upstream: scripts/install-rcs-stability.sh (reused, unchanged) → install-l3-stability.sh (writes to $WORKSPACE, verified no PWD bug); assets/templates/arcs.yaml.template; compute-budget-status.sh / compute-arc-status.sh. - downstream: scripts/onboard.sh (already wired — now converges with bootstrap); scripts/repair.sh (already offers install-rcs); tests/canary/01; every workspace that runs `bstack bootstrap` or `bstack doctor`. Changes: - bootstrap.sh Phase 3.5: call install-rcs-stability.sh (BSTACK_SKIP_RCS=1 escape; `|| true` preserves non-blocking contract under set -e). Phase 2: scaffold .control/arcs.yaml from template. - doctor.sh §23: control-loop closure verdict (substrate-absent / wired-but-idle / wired+running+closing) from W/R/C signals. Soft by default; BSTACK_LOOP_STRICT=1 promotes wired-but-idle to a hard --strict gap. §19 already hard-gates a wired loop that is genuinely diverging, so §23 does not double-count. - canary/01: assert PostToolUse hook + .control/audit/ + .control/arcs.yaml + L0/L1 markers (14/14). references/new-workspace-flow.md + CHANGELOG + VERSION 0.21.10→0.22.0. Verified: canary 01-05 green; arcs-validation/repair-merge-hooks/onboard suites green; §23 renders correctly across all three states + strict path. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 25 ++++++++ VERSION | 2 +- references/new-workspace-flow.md | 7 ++- scripts/bootstrap.sh | 42 +++++++++++-- scripts/doctor.sh | 79 +++++++++++++++++++++++++ tests/canary/01-fresh-bootstrap.test.sh | 24 +++++++- 6 files changed, 169 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96967f..f19486d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). **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 diff --git a/VERSION b/VERSION index 46cc062..2157409 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.21.10 +0.22.0 diff --git a/references/new-workspace-flow.md b/references/new-workspace-flow.md index a5472e3..fc477eb 100644 --- a/references/new-workspace-flow.md +++ b/references/new-workspace-flow.md @@ -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` | @@ -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 diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index e67d8f0..fc4c809 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -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" @@ -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" @@ -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. diff --git a/scripts/doctor.sh b/scripts/doctor.sh index 2d0f442..e6a0561 100755 --- a/scripts/doctor.sh +++ b/scripts/doctor.sh @@ -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 @@ -1133,6 +1140,78 @@ 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 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_SETTINGS="$WORKSPACE/.claude/settings.json" +LOOP_AUDIT_DIR="$WORKSPACE/.control/audit" +LOOP_STRICT="${BSTACK_LOOP_STRICT:-0}" + +# W — wired +W_OK=0 +if [ -f "$LOOP_SETTINGS" ] && [ -d "$LOOP_AUDIT_DIR" ] \ + && grep -q '"L0-audit"' "$LOOP_SETTINGS" 2>/dev/null \ + && grep -q '"L1-audit"' "$LOOP_SETTINGS" 2>/dev/null; then + 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)) diff --git a/tests/canary/01-fresh-bootstrap.test.sh b/tests/canary/01-fresh-bootstrap.test.sh index b268e23..f774428 100755 --- a/tests/canary/01-fresh-bootstrap.test.sh +++ b/tests/canary/01-fresh-bootstrap.test.sh @@ -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 @@ -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 @@ -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 From 918622a5cc7591cc87675d46a8fac7f9bf80d7ad Mon Sep 17 00:00:00 2001 From: "Carlos D. Escobar-Valbuena" Date: Thu, 28 May 2026 20:12:57 -0500 Subject: [PATCH 2/3] fix(lint): SC2053 disable on intentional glob in skill-graduate.sh exclude check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-existing on main (from #64) — the "Lint shell + JSON templates" gate inherited red on this PR. $ex is a glob pattern from EXTRA_EXCLUDES; quoting the RHS would break the pattern match the exclude check depends on, so the correct fix is a disable directive, not a quote. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/skill-graduate.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/skill-graduate.sh b/scripts/skill-graduate.sh index 1392431..620047b 100755 --- a/scripts/skill-graduate.sh +++ b/scripts/skill-graduate.sh @@ -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 From c6eb5c70488987e2c5b7a14537e246a92deddda0 Mon Sep 17 00:00:00 2001 From: "Carlos D. Escobar-Valbuena" Date: Thu, 28 May 2026 20:19:20 -0500 Subject: [PATCH 3/3] =?UTF-8?q?fix(=C2=A723):=20W-check=20reads=20settings?= =?UTF-8?q?.local.json=20too=20(dogfood-surfaced)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dogfooding 0.22.0 on the stimulus repo surfaced a false-negative: stimulus's tracked .claude/settings.json uses repo-relative vendored hooks, so the L0/L1 audit hooks (which carry an absolute bstack path) belong in the gitignored settings.local.json — which Claude Code merges at runtime. §23's W-check only grepped settings.json, so it reported "NOT wired" while the loop was actually running. Now checks both files. Verified: §23 on stimulus flips to "wired + running + closing" with hooks in settings.local.json; canary 14/14. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- scripts/doctor.sh | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f19486d..45c8ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ This release makes `/bstack bootstrap` wire the loop, scaffold the loop definiti - **`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). **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. +- **`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 diff --git a/scripts/doctor.sh b/scripts/doctor.sh index e6a0561..d4e6bec 100755 --- a/scripts/doctor.sh +++ b/scripts/doctor.sh @@ -1144,8 +1144,8 @@ fi # 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 carries the L0-audit + L1-audit hook -# markers AND .control/audit/ exists. +# 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 @@ -1156,16 +1156,23 @@ fi # bootstrap for purely temporal reasons); (c) W && R → ok. section "23. Control-loop closure verdict" -LOOP_SETTINGS="$WORKSPACE/.claude/settings.json" LOOP_AUDIT_DIR="$WORKSPACE/.control/audit" LOOP_STRICT="${BSTACK_LOOP_STRICT:-0}" -# W — wired +# 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 [ -f "$LOOP_SETTINGS" ] && [ -d "$LOOP_AUDIT_DIR" ] \ - && grep -q '"L0-audit"' "$LOOP_SETTINGS" 2>/dev/null \ - && grep -q '"L1-audit"' "$LOOP_SETTINGS" 2>/dev/null; then - W_OK=1 +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)