-
Notifications
You must be signed in to change notification settings - Fork 0
feat(0.23.0): gitignore-aware + non-destructive bootstrap (closes #67) #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| 0.22.0 | ||
| 0.23.0 |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -154,6 +154,62 @@ scaffold_governance_file ".control/arcs.yaml" "arcs.yaml.template" | |||||||||||||
|
|
||||||||||||||
| echo " scaffolded: $scaffolded | preserved: $preserved" | ||||||||||||||
|
|
||||||||||||||
| # ─── Phase 2.6: gitignore reconciliation + public-repo advisory ──────────── | ||||||||||||||
| # The control loop splits into two file classes (the committable-vs-machine-local | ||||||||||||||
| # manifest). This phase reconciles .gitignore against it: | ||||||||||||||
| # - machine-local (paths/telemetry) → MUST be ignored; add if missing. | ||||||||||||||
| # - committable (team-wide, no secrets) → must NOT be ignored; WARN (don't | ||||||||||||||
| # auto-un-ignore — un-ignoring a deliberately-private file is the human's call). | ||||||||||||||
| # Plus a public-repo advisory: on a public remote, committable governance becomes | ||||||||||||||
| # public — surface that, don't decide it silently. Never blocks. | ||||||||||||||
| echo "" | ||||||||||||||
| echo "=== bstack gitignore reconciliation ===" | ||||||||||||||
| GITIGNORE="$WORKSPACE_DIR/.gitignore" | ||||||||||||||
| # Machine-local globs that should always be ignored (absolute paths / runtime telemetry). | ||||||||||||||
| LOCAL_GLOBS=(".control/audit/*.jsonl") | ||||||||||||||
| # Committable substrate the loop needs — warn if a repo ignores these. | ||||||||||||||
| COMMITTABLE_FILES=(".control/arcs.yaml" ".control/rcs-parameters.toml" ".control/policy.yaml") | ||||||||||||||
|
|
||||||||||||||
| if command -v git >/dev/null 2>&1 && git -C "$WORKSPACE_DIR" rev-parse --git-dir >/dev/null 2>&1; then | ||||||||||||||
| # Ensure machine-local globs are ignored (append the missing ones). | ||||||||||||||
| _added_ignores=() | ||||||||||||||
| for glob in "${LOCAL_GLOBS[@]}"; do | ||||||||||||||
| if ! git -C "$WORKSPACE_DIR" check-ignore -q "${glob/\*/x}" 2>/dev/null; then | ||||||||||||||
| _added_ignores+=("$glob") | ||||||||||||||
| fi | ||||||||||||||
| done | ||||||||||||||
| if [ ${#_added_ignores[@]} -gt 0 ]; then | ||||||||||||||
| { | ||||||||||||||
| echo "" | ||||||||||||||
| echo "# bstack RCS control-loop machine-local telemetry (not committed)" | ||||||||||||||
| for g in "${_added_ignores[@]}"; do echo "$g"; done | ||||||||||||||
| } >> "$GITIGNORE" | ||||||||||||||
| echo " [ignore] added ${#_added_ignores[@]} machine-local glob(s) to .gitignore: ${_added_ignores[*]}" | ||||||||||||||
| else | ||||||||||||||
| echo " [ok] machine-local telemetry already ignored" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # Warn if committable substrate is ignored (coverage gap — the loop needs these committed). | ||||||||||||||
| for f in "${COMMITTABLE_FILES[@]}"; do | ||||||||||||||
| if git -C "$WORKSPACE_DIR" check-ignore -q "$f" 2>/dev/null; then | ||||||||||||||
| echo " [warn] $f is gitignored but the loop needs it committed." | ||||||||||||||
| echo " → un-ignore it (after confirming it carries no secrets) so the control loop survives a fresh clone." | ||||||||||||||
| fi | ||||||||||||||
| done | ||||||||||||||
|
|
||||||||||||||
| # Public-repo advisory (graceful if gh missing or not a GitHub remote). | ||||||||||||||
| if command -v gh >/dev/null 2>&1; then | ||||||||||||||
| _vis=$(gh repo view --json visibility --jq .visibility 2>/dev/null || true) | ||||||||||||||
| if [ "$_vis" = "PUBLIC" ]; then | ||||||||||||||
|
Comment on lines
+201
to
+203
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Locate and print the relevant section of scripts/bootstrap.sh (around lines 180-230)
if [ ! -f scripts/bootstrap.sh ]; then
echo "Missing scripts/bootstrap.sh"
exit 1
fi
echo "== scripts/bootstrap.sh (lines 160-240) =="
nl -ba scripts/bootstrap.sh | sed -n '160,240p'
# 2) Print where WORKSPACE_DIR is defined and how it's used (if present)
echo
echo "== WORKSPACE_DIR occurrences =="
rg -n "WORKSPACE_DIR" scripts/bootstrap.sh || true
# 3) Detect any prior cd into workspace (before the gh call)
echo
echo "== cd commands before the gh call section =="
# Print lines up to ~230 and look for cd
nl -ba scripts/bootstrap.sh | sed -n '1,240p' | rg -n "^[[:space:]]*[0-9]+[[:space:]]+cd[[:space:]]" || trueRepository: broomva/bstack Length of output: 163 🌐 Web query:
💡 Result: When you run the command gh repo view without any arguments, the GitHub CLI automatically detects and displays the repository associated with your current working directory [1]. The command uses your local Git configuration to identify the remote repository corresponding to your current directory and retrieves its information—such as the owner, repository name, description, and README—from GitHub [1]. If the current directory is not part of a Git repository, the command will be unable to identify the target and will return an error [2]. Key details regarding this behavior: - Scope: It defaults to the repository linked to the current directory [1]. - Default Branch: By default, gh repo view displays information from the repository's default branch [3]. - No Arguments: Providing no arguments is the standard way to view the repository associated with your current project context, avoiding the need to manually type the owner/repo path [1]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Show scripts/bootstrap.sh around lines 190-215 (avoid nl) =="
if [ ! -f scripts/bootstrap.sh ]; then
echo "Missing scripts/bootstrap.sh"
exit 1
fi
# Print with line numbers via awk
awk 'NR>=190 && NR<=215 { printf "%5d %s\n", NR, $0 }' scripts/bootstrap.sh
echo
echo "== Find gh repo view usages =="
rg -n "gh repo view" scripts/bootstrap.sh || true
echo
echo "== Find WORKSPACE_DIR definition/usage =="
rg -n "WORKSPACE_DIR" scripts/bootstrap.sh || true
echo
echo "== List cd commands before/near the gh repo view call =="
# Print first 250 lines with grep for "cd " lines
awk 'NR<=250 {print NR ":" $0}' scripts/bootstrap.sh | rg -n "^[0-9]+:.*\bcd\b" || trueRepository: broomva/bstack Length of output: 2878 Resolve visibility from
Suggested fix- _vis=$(gh repo view --json visibility --jq .visibility 2>/dev/null || true)
+ _vis=$(cd "$WORKSPACE_DIR" && gh repo view --json visibility --jq .visibility 2>/dev/null || true)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| echo " [advisory] PUBLIC repo — scaffolded governance (CLAUDE.md/AGENTS.md/METALAYER.md/.control/*)" | ||||||||||||||
| echo " is committable and will be PUBLIC. Review for secrets before pushing; keep" | ||||||||||||||
| echo " machine-local files (.claude/settings.json, .control/audit/*.jsonl) ignored." | ||||||||||||||
| fi | ||||||||||||||
| fi | ||||||||||||||
| else | ||||||||||||||
| echo " [skip] not a git repo — gitignore reconciliation skipped" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # ─── Phase 3: wire missing hooks into .claude/settings.json ──────────────── | ||||||||||||||
| # Idempotent: never overwrites existing hook entries. Only adds missing ones. | ||||||||||||||
| echo "" | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,22 +83,37 @@ do_install "$BSTACK_REPO/assets/templates/rcs-parameters.toml.template" \ | |
|
|
||
| # ── 2. Deploy git pre-commit hook ─────────────────────────────────────────── | ||
| echo "2. Git pre-commit hook (.githooks/pre-commit)" | ||
| # If a pre-commit hook exists and is NOT ours, preserve it as .local sidecar | ||
| PRE_COMMIT="$WORKSPACE/.githooks/pre-commit" | ||
| if [ -f "$PRE_COMMIT" ] && [ "$FORCE" = "0" ]; then | ||
| if grep -q "L3 rate gate" "$PRE_COMMIT" 2>/dev/null; then | ||
| echo " [skip] .githooks/pre-commit (already bstack L3 hook)" | ||
| SKIPPED=$((SKIPPED + 1)) | ||
| else | ||
| # Preserve existing as .local | ||
| echo " [info] existing .githooks/pre-commit found — preserving as .pre-commit.local" | ||
| if [ "$DRY_RUN" = "0" ]; then | ||
| mv "$PRE_COMMIT" "$WORKSPACE/.githooks/pre-commit.local" | ||
| chmod +x "$WORKSPACE/.githooks/pre-commit.local" | ||
| fi | ||
| do_install "$BSTACK_REPO/assets/templates/githook-pre-commit-l3-rate.sh.template" \ | ||
| "$PRE_COMMIT" "755" | ||
| if [ -f "$PRE_COMMIT" ] && grep -q "L3 rate gate" "$PRE_COMMIT" 2>/dev/null; then | ||
| # Already our hook — idempotent no-op (even under --force; reinstalling | ||
| # identical content is pointless). | ||
| echo " [skip] .githooks/pre-commit (already bstack L3 hook)" | ||
| SKIPPED=$((SKIPPED + 1)) | ||
|
Comment on lines
+87
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honor This branch skips solely on the marker string, so a workspace that already has the L3 hook can never receive template updates via 🤖 Prompt for AI Agents |
||
| elif [ -f "$PRE_COMMIT" ] \ | ||
| && (cd "$WORKSPACE" && git ls-files --error-unmatch .githooks/pre-commit >/dev/null 2>&1) \ | ||
| && [ "$FORCE" = "0" ]; then | ||
| # TRACKED hook + no --force → NEVER clobber. The committed pre-commit is | ||
| # authoritative; overwriting it destroys a tracked file. Skip + warn. | ||
| # (Bug found dogfooding on a repo with a tracked .githooks/pre-commit: the | ||
| # repo's own hook got replaced even though core.hooksPath ≠ .githooks, so | ||
| # the L3 hook never fired.) --force routes to the preserve-then-install | ||
| # branch below, which DOES create the .pre-commit.local sidecar. | ||
| echo " [skip] .githooks/pre-commit is git-tracked — preserving the repo's committed hook" | ||
| echo " → to add the L3 rate gate: chain it into the existing hook by hand," | ||
| echo " or re-run with --force (which preserves the current hook as .pre-commit.local)." | ||
| SKIPPED=$((SKIPPED + 1)) | ||
| elif [ -f "$PRE_COMMIT" ]; then | ||
| # An existing hook we are about to replace: either an UNTRACKED local hook, | ||
| # or a tracked hook under --force. Preserve it as .pre-commit.local first, | ||
| # then install ours — so the sidecar recovery path the warning promises | ||
| # actually exists in both cases. | ||
| echo " [info] existing .githooks/pre-commit found — preserving as .pre-commit.local" | ||
| if [ "$DRY_RUN" = "0" ]; then | ||
| mv "$PRE_COMMIT" "$WORKSPACE/.githooks/pre-commit.local" | ||
| chmod +x "$WORKSPACE/.githooks/pre-commit.local" | ||
|
Comment on lines
+110
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't overwrite an existing
🤖 Prompt for AI Agents |
||
| fi | ||
| do_install "$BSTACK_REPO/assets/templates/githook-pre-commit-l3-rate.sh.template" \ | ||
| "$PRE_COMMIT" "755" | ||
| else | ||
| do_install "$BSTACK_REPO/assets/templates/githook-pre-commit-l3-rate.sh.template" \ | ||
| "$PRE_COMMIT" "755" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| #!/usr/bin/env bash | ||
| # gitignore-aware-bootstrap — covers the v0.23.0 safety fixes (issue #67): | ||
| # 1. install-l3-stability.sh never clobbers a TRACKED .githooks/pre-commit | ||
| # 2. install-l3-stability.sh still preserves an UNTRACKED pre-commit as .local | ||
| # 3. bootstrap.sh adds machine-local audit-log glob to .gitignore (real git repo) | ||
| # 4. bootstrap.sh warns when committable substrate is gitignored (coverage gap) | ||
|
|
||
| set -uo pipefail | ||
| REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | ||
| PASS=0; FAIL=0; FAILED=() | ||
| pass() { echo " [ok] $1"; PASS=$((PASS + 1)); } | ||
| fail() { echo " [FAIL] $1"; FAIL=$((FAIL + 1)); FAILED+=("$1"); } | ||
|
|
||
| echo "── gitignore-aware-bootstrap ──────────────────────────" | ||
|
|
||
| # ── 1. TRACKED pre-commit is preserved (not clobbered) ────────────────────── | ||
| TW=$(mktemp -d) | ||
| ( | ||
| cd "$TW" && git init -q | ||
| mkdir -p .githooks | ||
| printf '#!/bin/bash\n# repo multimedia validator\n' > .githooks/pre-commit | ||
| chmod +x .githooks/pre-commit | ||
| git add .githooks/pre-commit && git -c user.email=t@t -c user.name=t commit -q -m "hook" | ||
| ) | ||
| BROOMVA_WORKSPACE="$TW" bash "$REPO/scripts/install-l3-stability.sh" >/dev/null 2>&1 | ||
| if grep -q "multimedia validator" "$TW/.githooks/pre-commit"; then | ||
| pass "tracked .githooks/pre-commit preserved (not clobbered)" | ||
| else | ||
| fail "tracked .githooks/pre-commit was clobbered" | ||
| fi | ||
| if [ ! -f "$TW/.githooks/pre-commit.local" ]; then | ||
| pass "no .pre-commit.local created for tracked hook (default)" | ||
| else | ||
| fail ".pre-commit.local was created for a tracked hook (default)" | ||
| fi | ||
|
|
||
| # ── 1b. --force on a TRACKED hook DOES preserve it as .pre-commit.local ────── | ||
| BROOMVA_WORKSPACE="$TW" bash "$REPO/scripts/install-l3-stability.sh" --force >/dev/null 2>&1 | ||
| if grep -q "L3 rate gate" "$TW/.githooks/pre-commit" 2>/dev/null \ | ||
| && grep -q "multimedia validator" "$TW/.githooks/pre-commit.local" 2>/dev/null; then | ||
| pass "--force preserves tracked hook as .pre-commit.local, then installs L3 hook" | ||
| else | ||
| fail "--force did NOT create the promised .pre-commit.local sidecar" | ||
| fi | ||
| rm -rf "$TW" | ||
|
|
||
| # ── 2. UNTRACKED pre-commit IS preserved as sidecar, ours installed ───────── | ||
| TW=$(mktemp -d) | ||
| ( | ||
| cd "$TW" && git init -q | ||
| mkdir -p .githooks | ||
| printf '#!/bin/bash\n# untracked local hook\n' > .githooks/pre-commit | ||
| chmod +x .githooks/pre-commit | ||
| ) | ||
| BROOMVA_WORKSPACE="$TW" bash "$REPO/scripts/install-l3-stability.sh" >/dev/null 2>&1 | ||
| if grep -q "L3 rate gate" "$TW/.githooks/pre-commit" 2>/dev/null \ | ||
| && grep -q "untracked local hook" "$TW/.githooks/pre-commit.local" 2>/dev/null; then | ||
| pass "untracked hook moved to .pre-commit.local, L3 hook installed" | ||
| else | ||
| fail "untracked-hook preservation regressed" | ||
| fi | ||
| rm -rf "$TW" | ||
|
|
||
| # ── 3 + 4. bootstrap gitignore reconciliation on a real git repo ──────────── | ||
| TW=$(mktemp -d); TH=$(mktemp -d) | ||
| ( cd "$TW" && git init -q ) | ||
| HOME="$TH" BROOMVA_WORKSPACE="$TW" BSTACK_SKIP_SKILLS=1 BSTACK_SKIP_RCS=1 \ | ||
| BROOMVA_STATE_DIR="$TH/.cfg" bash "$REPO/scripts/bootstrap.sh" >/dev/null 2>&1 | ||
| if grep -q "control/audit/\*.jsonl" "$TW/.gitignore" 2>/dev/null; then | ||
| pass "bootstrap added machine-local audit-log glob to .gitignore" | ||
| else | ||
| fail "bootstrap did not add audit-log glob to .gitignore" | ||
| fi | ||
| # committable-ignored warning | ||
| echo ".control/arcs.yaml" >> "$TW/.gitignore" | ||
| out=$(HOME="$TH" BROOMVA_WORKSPACE="$TW" BSTACK_SKIP_SKILLS=1 BSTACK_SKIP_RCS=1 \ | ||
| BROOMVA_STATE_DIR="$TH/.cfg" bash "$REPO/scripts/bootstrap.sh" 2>&1) | ||
| if echo "$out" | grep -q "arcs.yaml is gitignored but the loop needs it"; then | ||
| pass "bootstrap warns when committable substrate is gitignored" | ||
| else | ||
| fail "no warning for gitignored committable substrate" | ||
| fi | ||
| # idempotency: the audit-log glob appears exactly once after repeated runs | ||
| n=$(grep -c "^\.control/audit/\*\.jsonl$" "$TW/.gitignore" 2>/dev/null || echo 0) | ||
| if [ "$n" -eq 1 ]; then | ||
| pass "audit-log glob is idempotent (single .gitignore entry after 2 runs)" | ||
| else | ||
| fail "audit-log glob duplicated in .gitignore ($n entries)" | ||
| fi | ||
| rm -rf "$TW" "$TH" | ||
|
|
||
| echo "─────────────────────────────────────" | ||
| echo " Passed: $PASS" | ||
| echo " Failed: $FAIL" | ||
| if [ "$FAIL" -gt 0 ]; then | ||
| for t in "${FAILED[@]}"; do echo " - $t"; done | ||
| exit 1 | ||
| fi | ||
| echo " gitignore-aware-bootstrap passed." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also reconcile
.claude/settings.jsonas machine-local.Phase 2.6 only adds
.control/audit/*.jsonl, but the machine-local set for this flow also includes.claude/settings.json. Without adding it here, bootstrap still leaves a user-specific settings file committable in a fresh repo.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents