
Slb
- 2 installs
- 74 repo stars
- Updated August 4, 2026
- dicklesworthstone/simultaneous_launch_button
Enforces a two-person rule so a coding agent's destructive commands (rm -rf, force push, DROP TABLE) require peer review before executing.
About
Requires peer approval before running risky commands from AI agents, classifying commands into risk tiers with a notary daemon that verifies approvals. A developer uses it to gate destructive operations across coordinated agents.
- Risk tiers from SAFE to CRITICAL with 0-2 required approvals
- Five execution gates including hash tamper detection and atomic claim
Slb by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,786 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dicklesworthstone/simultaneous_launch_button --skill slbAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 74 |
| Last updated | August 4, 2026 |
| Repository | dicklesworthstone/simultaneous_launch_button ↗ |
What it does
Enforces a two-person rule so a coding agent's destructive commands (rm -rf, force push, DROP TABLE) require peer review before executing.
Files
<!-- TOC: Quick Start | THE EXACT PROMPT | Risk Tiers | Workflow | References -->
SLB — Simultaneous Launch Button
Core Capability: Two-person rule for running potentially destructive commands from AI coding agents. When an agent wants to run something risky, SLB requires peer review and explicit approval before execution.
Why This Exists
Coding agents can get tunnel vision, hallucinate, or misunderstand context. A second reviewer (ideally with a different model/tooling) catches mistakes before they become irreversible.
Critical: Commands run in YOUR shell environment, not on a server. The daemon is a NOTARY (verifies approvals), not an executor.
---
Quick Start
# Install
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/slb/main/scripts/install.sh | bash
# Initialize project
cd /path/to/project
slb init
# Start session
slb session start --agent "GreenLake" --program "claude-code" --model "opus"---
THE EXACT PROMPT — Basic Workflow
# 1. Run a dangerous command (blocks until approved)
slb run "rm -rf ./build" --reason "Clean build artifacts" --session-id <id>
# 2. Another agent reviews and approves
slb pending # See what's waiting
slb review <request-id> # View full details
slb approve <request-id> --session-id <reviewer-id> --comment "Looks safe"
# 3. Original command executes automatically after approval---
Risk Tiers
| Tier | Approvals | Auto-approve | Examples |
|---|---|---|---|
| CRITICAL | 2+ | Never | rm -rf /, DROP DATABASE, terraform destroy, git push --force |
| DANGEROUS | 1 | Never | rm -rf ./build, git reset --hard, kubectl delete, DROP TABLE |
| CAUTION | 0 | After 30s | rm file.txt, git branch -d, npm uninstall |
| SAFE | 0 | Immediately | rm *.log, git stash, kubectl delete pod |
---
Essential Commands
| Category | Command | Description |
|---|---|---|
| Session | slb session start --agent <name> | Start agent session |
| Session | slb session list | Show active sessions |
| Request | slb run "<cmd>" --reason "..." | Run dangerous command |
| Review | slb pending | List pending requests |
| Review | slb approve <id> --session-id <id> | Approve request |
| Review | slb reject <id> --reason "..." | Reject request |
| Hook | slb hook install | Install Claude Code hook |
| Pattern | slb patterns test "<cmd>" | Check command tier |
---
Claude Code Hook
# Install hook
slb hook install
# Hook actions:
# - allow: Command proceeds (SAFE tier)
# - ask: User prompted (CAUTION tier)
# - block: Blocked, must use `slb request` (DANGEROUS/CRITICAL tier)---
Execution Verification (5 Gates)
| Gate | Check |
|---|---|
| 1. Status | Request must be APPROVED |
| 2. Expiry | Approval TTL must not have elapsed |
| 3. Hash | SHA-256 hash must match (tamper detection) |
| 4. Tier | Risk tier must still match |
| 5. First-Executor | Atomic claim prevents race conditions |
---
Emergency Override
For true emergencies, humans can bypass with extensive logging:
slb emergency-execute "rm -rf /tmp/broken" --reason "System emergency"---
References
| Topic | Reference |
|---|---|
| Full command reference | COMMANDS.md |
| Pattern matching & tiers | PATTERNS.md |
| Configuration | CONFIG.md |
| Security design | SECURITY.md |
# SQLite databases
*.db
*.db?*
*.db-journal
*.db-wal
*.db-shm
# Daemon runtime files
daemon.lock
daemon.log
daemon.pid
bd.sock
sync-state.json
.sync.lock
# Local version tracking (prevents upgrade notification spam after git ops)
.local_version
# Legacy database files
db.sqlite
bd.db
# Merge artifacts (temporary files from 3-way merge)
beads.base.jsonl
beads.base.meta.json
beads.left.jsonl
beads.left.meta.json
beads.right.jsonl
beads.right.meta.json
# Keep JSONL exports and config (source of truth for git)
!issues.jsonl
!metadata.json
!config.json
# Local history backups
.br_history/
# bv (beads viewer) lock file
.bv.lock
0.47.1
# Beads Configuration File
# This file configures default behavior for all bd commands in this repository
# All settings can also be set via environment variables (BD_* prefix)
# or overridden with command-line flags
# Issue prefix for this repository (used by bd init)
# If not set, bd init will auto-detect from directory name
# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc.
# issue-prefix: ""
# Use no-db mode: load from JSONL, no SQLite, write back after each command
# When true, bd will use .beads/issues.jsonl as the source of truth
# instead of SQLite database
# no-db: false
# Disable daemon for RPC communication (forces direct database access)
# no-daemon: false
# Disable auto-flush of database to JSONL after mutations
# no-auto-flush: false
# Disable auto-import from JSONL when it's newer than database
# no-auto-import: false
# Enable JSON output by default
# json: false
# Default actor for audit trails (overridden by BD_ACTOR or --actor)
# actor: ""
# Path to database (overridden by BEADS_DB or --db)
# db: ""
# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON)
# auto-start-daemon: true
# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE)
# flush-debounce: "5s"
# Multi-repo configuration (experimental - bd-307)
# Allows hydrating from multiple repositories and routing writes to the correct JSONL
# repos:
# primary: "." # Primary repo (where this database lives)
# additional: # Additional repos to hydrate from (read-only)
# - ~/beads-planning # Personal planning repo
# - ~/work-planning # Work planning repo
# Integration settings (access with 'bd config get/set')
# These are stored in the database, not in this file:
# - jira.url
# - jira.project
# - linear.url
# - linear.api-key
# - github.org
# - github.repo
# - sync.branch - Git branch for beads commits (use BEADS_SYNC_BRANCH env var or bd config set)
{
"database": "beads.db",
"jsonl_export": "issues.jsonl"
}
# Use bd merge for beads JSONL files
.beads/beads.jsonl merge=beads
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GO_VERSION: "1.24"
permissions:
contents: read
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Verify go.mod is tidy
run: |
go mod tidy
git diff --exit-code go.mod go.sum
- name: Check formatting
run: |
gofmt_output=$(gofmt -l .)
if [ -n "$gofmt_output" ]; then
echo "::error::Files not formatted with gofmt:"
echo "$gofmt_output"
exit 1
fi
- name: Run go vet
run: go vet ./...
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: latest
args: --timeout=5m
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
include:
- os: windows-latest
skip-race: true
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Build
run: go build -v ./...
- name: Run tests (with race detector)
if: ${{ !matrix.skip-race }}
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Run tests (without race detector)
if: ${{ matrix.skip-race }}
run: go test -v -coverprofile=coverage.out ./...
- name: Upload coverage
if: matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v5
with:
files: coverage.out
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
coverage:
name: Coverage Check
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [test]
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Generate coverage
run: go test -coverprofile=coverage.out -covermode=atomic ./...
- name: Check coverage threshold
run: |
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
echo "Coverage: ${COVERAGE}%"
THRESHOLD=80
if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
echo "::error::Coverage ${COVERAGE}% is below threshold of ${THRESHOLD}%"
exit 1
fi
echo "Coverage ${COVERAGE}% meets threshold of ${THRESHOLD}%"
- name: Generate HTML report
run: go tool cover -html=coverage.out -o coverage.html
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.html
retention-days: 14
build:
name: Build (${{ matrix.goos }}/${{ matrix.goarch }})
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
- goos: darwin
goarch: amd64
- goos: darwin
goarch: arm64
- goos: windows
goarch: amd64
- goos: freebsd
goarch: amd64
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Build
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
run: |
EXT=""
if [ "${{ matrix.goos }}" = "windows" ]; then
EXT=".exe"
fi
go build -ldflags="-s -w" -o build/slb-${{ matrix.goos }}-${{ matrix.goarch }}${EXT} ./cmd/slb
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: slb-${{ matrix.goos }}-${{ matrix.goarch }}
path: build/slb-*
retention-days: 7
security:
name: Security Scan
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Run govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: Run gosec
uses: securego/gosec@v2.22.1
with:
args: -fmt sarif -out gosec-results.sarif ./...
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: gosec-results.sarif
goreleaser-check:
name: GoReleaser Check
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Check GoReleaser config
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: "~> v2"
args: check
- name: Dry-run release
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: "~> v2"
args: release --snapshot --skip=publish,sbom,sign --clean
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
timeout-minutes: 10
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
deny-licenses: GPL-3.0, AGPL-3.0
integration:
name: Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [build]
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: slb-linux-amd64
path: build/
- name: Make executable
run: chmod +x build/slb-linux-amd64
- name: Test version command
run: ./build/slb-linux-amd64 version
- name: Test help command
run: ./build/slb-linux-amd64 --help
- name: Test invalid command
run: |
if ./build/slb-linux-amd64 invalid-command 2>/dev/null; then
echo "::error::Expected failure for invalid command"
exit 1
fi
ci-success:
name: CI Success
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [lint, test, coverage, build, security, goreleaser-check, integration]
if: always()
steps:
- name: Check all jobs passed
run: |
if [[ "${{ needs.lint.result }}" != "success" ]] ||
[[ "${{ needs.test.result }}" != "success" ]] ||
[[ "${{ needs.coverage.result }}" != "success" ]] ||
[[ "${{ needs.build.result }}" != "success" ]] ||
[[ "${{ needs.security.result }}" != "success" ]] ||
[[ "${{ needs.goreleaser-check.result }}" != "success" ]] ||
[[ "${{ needs.integration.result }}" != "success" ]]; then
echo "::error::One or more jobs failed"
exit 1
fi
echo "All CI jobs passed successfully!"
# .github/workflows/notify-acfs.yml
#
# Notifies ACFS (Agentic Coding Flywheel Setup) when installer scripts change
# or new releases are published, triggering checksum updates.
#
# Setup:
# 1. Create a PAT with 'contents:read' on agentic_coding_flywheel_setup
# 2. Add it as a secret named ACFS_REPO_DISPATCH_TOKEN in this repo
#
# Related: agentic_coding_flywheel_setup
#
name: Notify ACFS
on:
push:
branches: [main, master]
paths:
- 'install.sh'
- 'scripts/install.sh'
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
jobs:
notify:
name: Dispatch to ACFS
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Validate dispatch token
env:
TOKEN: ${{ secrets.ACFS_REPO_DISPATCH_TOKEN }}
run: |
if [ -z "$TOKEN" ]; then
echo "::error::Missing secret ACFS_REPO_DISPATCH_TOKEN"
echo "Add a PAT with repo access for Dicklesworthstone/agentic_coding_flywheel_setup"
exit 1
fi
- name: Trigger ACFS checksum update
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.ACFS_REPO_DISPATCH_TOKEN }}
repository: Dicklesworthstone/agentic_coding_flywheel_setup
event-type: upstream-changed
client-payload: |
{
"tool": "${{ github.event.repository.name }}",
"repo": "${{ github.repository }}",
"ref": "${{ github.ref }}",
"sha": "${{ github.sha }}",
"event": "${{ github.event_name }}",
"actor": "${{ github.actor }}",
"timestamp": "${{ github.event.head_commit.timestamp || github.event.release.created_at }}"
}
- name: Log dispatch
run: |
echo "Dispatched upstream-changed event to ACFS"
echo " Tool: ${{ github.event.repository.name }}"
echo " Event: ${{ github.event_name }}"
echo " SHA: ${{ github.sha }}"
echo " Triggered by: ${{ github.actor }}"
name: Release
on:
push:
tags:
- "v*"
env:
GO_VERSION: "1.24"
permissions:
contents: write
packages: write
id-token: write
jobs:
release:
name: Release
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Install Syft
uses: anchore/sbom-action/download-syft@v0
- name: Extract version
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COSIGN_EXPERIMENTAL: "true"
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 30
docker:
name: Docker Build
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [release]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Check for Dockerfile
id: dockerfile
run: |
if [ -f Dockerfile ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Set up QEMU
if: steps.dockerfile.outputs.exists == 'true'
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
if: steps.dockerfile.outputs.exists == 'true'
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: steps.dockerfile.outputs.exists == 'true'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
if: steps.dockerfile.outputs.exists == 'true'
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
- name: Build and push
if: steps.dockerfile.outputs.exists == 'true'
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true
sbom: true
- name: Skip Docker
if: steps.dockerfile.outputs.exists == 'false'
run: echo "No Dockerfile found, skipping Docker build"
verify:
name: Verify Release
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [release]
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: List artifacts
run: |
echo "=== Release artifacts ==="
ls -la dist/
echo ""
echo "=== Checksums ==="
cat dist/checksums.txt || echo "No checksums file"
- name: Verify Linux binary
run: |
set -euo pipefail
# Look for a built binary in any dist subdir, then fall back to
# extracting the linux/amd64 tar.gz. Goreleaser's per-target
# subdirectory naming shifted between minor versions, so we cannot
# rely on a fixed pattern like dist/slb_*_linux_amd64/slb.
BIN="$(find dist -type f -name slb -path '*linux*amd64*' 2>/dev/null | head -n 1)"
if [ -z "${BIN}" ]; then
ARCHIVE="$(ls -1 dist/slb_*_linux_amd64.tar.gz 2>/dev/null | head -n 1)"
if [ -z "${ARCHIVE}" ]; then
echo "::error::no linux/amd64 binary or archive found under dist/"
ls -lR dist/ || true
exit 1
fi
mkdir -p dist/_verify
tar -xzf "${ARCHIVE}" -C dist/_verify
BIN="$(find dist/_verify -type f -name slb | head -n 1)"
fi
echo "Using binary: ${BIN}"
chmod +x "${BIN}"
"${BIN}" version
announce:
name: Announce Release
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [release, verify]
permissions:
contents: read
steps:
- name: Create release summary
run: |
VERSION="${{ needs.release.outputs.version }}"
echo "# SLB v${VERSION} Released" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Installation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### curl (Linux/macOS)" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "curl -sSL https://github.com/${{ github.repository }}/releases/download/v${VERSION}/slb_${VERSION}_linux_amd64.tar.gz | tar xz" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Go install" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "go install github.com/${{ github.repository }}/cmd/slb@v${VERSION}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Homebrew" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "brew install dicklesworthstone/tap/slb" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
# Binaries
*.exe
dist/
build/
# Test coverage
coverage.out
coverage.html
# IDE
.idea/
.vscode/
vscode/node_modules/
vscode/out/
vscode/package-lock.json
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Go
vendor/
# Test binaries
*.test
coverage.out
dashboard_debug.txt
build/slb
slb
# SLB runtime logs
.slb/
# bv (beads viewer) local config and caches
.bv/
a.out
# SQLite in-memory database artifacts
:memory:
# macOS resource forks
._*
# Beads ephemeral files
.beads/last-touched
# golangci-lint configuration for slb
# https://golangci-lint.run/usage/configuration/
# Updated for golangci-lint v2.x format
version: "2"
run:
timeout: 5m
tests: true
linters:
enable:
- errcheck
- govet
- ineffassign
- staticcheck
disable:
# Disable unused linter - too many false positives in development
- unused
settings:
errcheck:
# Don't check error returns in defer statements in test files
check-type-assertions: false
check-blank: false
# Exclude common patterns that are safe to ignore
exclude-functions:
# In test code, these are often cleanup operations where failure doesn't matter
- (io.Closer).Close
- (*os.File).Close
- (*database/sql.Tx).Rollback
- (*database/sql.DB).Close
- (net.Conn).Close
- (*net.Conn).SetReadDeadline
- os.Chmod
- io.Copy
- (*bytes.Buffer).Write
- (net.Conn).Write
- os.MkdirAll
- json.Unmarshal
- (*bytes.Buffer).ReadFrom
exclusions:
# Exclude some issues in test files
rules:
# Allow unchecked error returns in test cleanup (defer statements)
- path: _test\.go
linters:
- errcheck
# Ignore ineffassign for intentional reassignments
- linters:
- ineffassign
text: "ineffectual assignment"
# Ignore staticcheck style suggestions in existing code
- linters:
- staticcheck
text: "S1008|S1017|S1030|S1039"
# Ignore deprecated API warnings - these are backwards compatibility concerns
- linters:
- staticcheck
text: "SA1019"
# Ignore nil context in tests
- path: _test\.go
linters:
- staticcheck
text: "SA1012"
# Ignore empty branch checks - these are often intentional patterns
- linters:
- staticcheck
text: "SA9003"
# Ignore unused value warnings in development code
- linters:
- staticcheck
text: "SA4006"
# Ignore possible nil pointer dereference in test helpers
- linters:
- staticcheck
text: "SA5011"
formatters:
enable:
- gofmt
- goimports
# GoReleaser configuration for SLB
# https://goreleaser.com
version: 2
project_name: slb
before:
hooks:
- go mod tidy
- go generate ./...
builds:
- id: slb
main: ./cmd/slb
binary: slb
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
ignore:
- goos: windows
goarch: arm64
ldflags:
- -s -w
- -X github.com/Dicklesworthstone/slb/internal/cli.version={{.Version}}
- -X github.com/Dicklesworthstone/slb/internal/cli.commit={{.Commit}}
- -X github.com/Dicklesworthstone/slb/internal/cli.date={{.Date}}
archives:
- id: default
formats:
- tar.gz
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- .Arch }}
format_overrides:
- goos: windows
formats:
- zip
files:
- README*
- PLAN_TO_MAKE_SLB.md
checksum:
name_template: "checksums.txt"
algorithm: sha256
sboms:
- artifacts: archive
cmd: syft
args: ["$artifact", "--output", "spdx-json=$document"]
signs:
- cmd: cosign
env:
- COSIGN_EXPERIMENTAL=1
certificate: "${artifact}.pem"
args:
- sign-blob
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "${artifact}"
- "--yes"
artifacts: checksum
output: true
snapshot:
version_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
- "^ci:"
- "^chore:"
- Merge pull request
- Merge branch
release:
github:
owner: Dicklesworthstone
name: slb
draft: false
prerelease: auto
name_template: "v{{.Version}}"
homebrew_casks:
- name: slb
binaries:
- slb
repository:
owner: Dicklesworthstone
name: homebrew-tap
branch: main
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
directory: Casks
homepage: https://github.com/Dicklesworthstone/slb
description: "Simultaneous Launch Button - Two-person rule for dangerous commands"
caveats: |
SLB (Simultaneous Launch Button) implements two-person authorization.
Quick start:
slb init # Initialize in current project
slb create "rm -rf /" # Create a launch request
slb list # List pending requests
# Scoop bucket publishing
scoops:
- name: slb
repository:
owner: Dicklesworthstone
name: scoop-bucket
branch: main
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
homepage: https://github.com/Dicklesworthstone/slb
description: "Simultaneous Launch Button - Two-person rule for dangerous commands"
license: MIT
nfpms:
- id: default
package_name: slb
file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
vendor: Dicklesworthstone
homepage: https://github.com/Dicklesworthstone/slb
maintainer: Dicklesworthstone
description: "Simultaneous Launch Button - Two-person rule for dangerous commands"
license: MIT
formats:
- deb
- rpm
- apk
bindir: /usr/bin
announce:
skip: true
Agent-Friendliness Report: slb (Simultaneous Launch Button)
Date: 2026-01-25 Bead: bd-19g Analyst: Claude Code Agent (cc)
Executive Summary
slb is a well-designed tool for multi-agent safety coordination. It has good robot mode support but needs some improvements for optimal agent usage.
Overall Score: 7/10 (Good foundation, minor issues)
Current State
Strengths
| Area | Rating | Notes |
|---|---|---|
| TOON Support | ✓ Excellent | --toon, --output toon flags, graceful fallback |
| JSON Output | ✓ Good | --json on most commands |
| Documentation | ✓ Good | AGENTS.md, README.md, SKILL.md exist |
| Command Structure | ✓ Good | Clear subcommand hierarchy |
| Error Messages | ✓ Good | Helpful error output |
Issues Found
| Issue | Severity | Description |
|---|---|---|
| Flag conflict panic | HIGH | slb patterns list panics due to -t shorthand conflict |
| Check output format | MEDIUM | slb check outputs Go map syntax, not JSON |
| Missing --json on check | LOW | slb check doesn't support --json flag |
Detailed Analysis
1. Documentation Audit
| Document | Status | Notes |
|---|---|---|
| README.md | ✓ Exists | 34 KB, comprehensive |
| AGENTS.md | ✓ Exists | 19 KB, good coverage |
| SKILL.md | ✓ Exists | 18 KB, Claude Code skill |
| RESEARCH_FINDINGS.md | ✓ Exists | TOON integration documented |
Recommendation: Documentation is in good shape.
2. Robot Mode Completeness
| Command | --json | --toon | Notes |
|---|---|---|---|
pending | ✓ | ✓ | Works |
history | ✓ | ✓ | Works |
config show | ✓ | ✓ | Works |
daemon status | ✓ | ✓ | Works |
session list | ✓ | ✓ | Works |
patterns list | ✗ PANIC | ✗ PANIC | BUG: -t flag conflict |
check | ✗ | ✗ | Outputs Go map format |
show | ✓ | ✓ | Works |
watch | NDJSON | ✓ | Streaming |
3. Bug Report: Flag Conflict
Command: slb patterns list --json Error: Panic due to -t shorthand conflict
panic: unable to redefine 't' shorthand in "patterns" flagset:
it's already used for "tier" flagRoot Cause: Both --toon (global) and --tier (patterns command) use -t shorthand.
Fix: Change --tier shorthand to -T or remove shorthand from global --toon.
4. CLI Ergonomics
Good patterns:
- Clear subcommand hierarchy
- Consistent
--json/--toonflags - Good help text
Issues:
slb checkoutputs non-standard format- Missing JSON Schema documentation
- No
--schemaflag for self-describing output
5. Agent Workflow Quality
Two-person rule workflow: 1. Agent A: slb request 'rm -rf /tmp/old' → Creates request 2. Agent B: slb pending --json → Sees pending request 3. Agent B: slb approve <id> → Approves 4. Agent A: slb execute <id> → Runs command
Workflow is well-designed for multi-agent coordination.
Recommendations
Priority 1: Fix Critical Bug
// In patterns.go, change:
cmd.Flags().StringVarP(&tier, "tier", "t", "", "filter by tier")
// To:
cmd.Flags().StringVarP(&tier, "tier", "T", "", "filter by tier")Priority 2: Fix slb check Output
Current:
map[command:rm -rf / is_safe:false matched_pattern:^rm\s+(-[rf]+\s+)+/($|\s) min_approvals:2 needs_approval:true tier:critical]Should be:
{
"command": "rm -rf /",
"is_safe": false,
"tier": "critical",
"needs_approval": true,
"min_approvals": 2,
"matched_pattern": "^rm\\s+(-[rf]+\\s+)+/($|\\s)"
}Priority 3: Add JSON Schema Support
slb schema pending # Emit JSON Schema for pending output
slb schema config # Emit JSON Schema for config outputTest Commands for Agents
# Quick health check
slb version
slb daemon status --json
# Check command classification
slb check 'rm -rf /tmp'
slb check 'git status'
# List pending requests
slb pending --toon
# Watch for requests (for reviewing agent)
slb watch --output jsonAcceptance Criteria Status
From bd-19g:
- [x] Documentation Audit completed
- [x] Robot Mode Completeness evaluated
- [x] CLI Ergonomics evaluated
- [x] Agent Interaction Patterns documented
- [x] Safety Model evaluated
- [x] Bug found and documented
Related Beads
- bd-19g: This re-underwriting bead
- bd-3ua: TOON research (complete)
- bd-2ti: TOON integration (complete)
Files to Modify
1. cmd/patterns.go - Fix -t flag conflict 2. cmd/check.go - Add proper JSON output 3. internal/output/ - Consider adding JSON Schema support
AGENTS.md — slb
Guidelines for AI coding agents working in this Go codebase.
---
RULE 0 - THE FUNDAMENTAL OVERRIDE PREROGATIVE
If I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.
---
RULE NUMBER 1: NO FILE DELETION
YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION. Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.
YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.
---
Irreversible Git & Filesystem Actions — DO NOT EVER BREAK GLASS
1. Absolutely forbidden commands: git reset --hard, git clean -fd, rm -rf, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences. 2. No guessing: If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. "I think it's safe" is never acceptable. 3. Safer alternatives first: When cleanup or rollbacks are needed, request permission to use non-destructive options (git status, git diff, git stash, copying to backups) before ever considering a destructive command. 4. Mandatory explicit plan: Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate. 5. Document the confirmation: When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.
---
Git Branch: ONLY Use main, NEVER master
The default branch is `main`. The `master` branch exists only for legacy URL compatibility.
- All work happens on `main` — commits, PRs, feature branches all merge to
main - Never reference `master` in code or docs — if you see
masteranywhere, it's a bug that needs fixing - The `master` branch must stay synchronized with `main` — after pushing to
main, also push tomaster:
git push origin main:masterIf you see `master` referenced anywhere: 1. Update it to main 2. Ensure master is synchronized: git push origin main:master
---
Toolchain: Go & Make
We use Go and Make in this project.
- Go version: 1.24+ (as per
go.mod) - Build:
make buildorgo build ./... - Test:
make testorgo test ./... - Format: Always run
go fmt ./...before committing - Dependencies: Managed via
go.modandgo.sum - Lint:
golangci-lint run ./...(viamake lint)
Key Dependencies
| Package | Purpose |
|---|---|
spf13/cobra | CLI framework (commands, flags, completions) |
spf13/viper | Configuration loading (TOML, env, flags) |
BurntSushi/toml | TOML configuration file parsing |
modernc.org/sqlite | Pure-Go SQLite for request/session/review persistence |
charmbracelet/bubbletea | Terminal UI framework (dashboard, review screens) |
charmbracelet/bubbles | Reusable TUI components |
charmbracelet/lipgloss | TUI styling and layout |
charmbracelet/log | Structured logging |
fsnotify/fsnotify | Filesystem watching for daemon mode |
google/uuid | UUID generation for request/session IDs |
mattn/go-shellwords | Shell command tokenization and quoting |
golang.org/x/term | Terminal size detection |
Build Variables
Version, commit, and date are injected via -ldflags at build time:
LDFLAGS := -ldflags "-X .../cli.version=$(VERSION) -X .../cli.commit=$(COMMIT) -X .../cli.date=$(DATE)"---
Code Editing Discipline
No Script-Based Changes
NEVER run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.
- Always make code changes manually, even when there are many instances
- For many simple changes: use parallel subagents
- For subtle/complex changes: do them methodically yourself
No File Proliferation
If you want to change something or add a feature, revise existing code files in place.
NEVER create variations like:
mainV2.gomain_improved.gomain_enhanced.go
New files are reserved for genuinely new functionality that makes zero sense to include in any existing file. The bar for creating new files is incredibly high.
---
Backwards Compatibility
We do not care about backwards compatibility—we're in early development with no users. We want to do things the RIGHT way with NO TECH DEBT.
- Never create "compatibility shims"
- Never create wrapper functions for deprecated APIs
- Just fix the code directly
---
Compiler Checks (CRITICAL)
After any substantive code changes, you MUST verify no errors were introduced:
# Build the project
go build ./...
# Run go vet
go vet ./...
# Run linter
golangci-lint run ./...
# Verify formatting
go fmt ./...If you see errors, carefully understand and resolve each issue. Read sufficient context to fix them the RIGHT way.
---
Testing
Testing Policy
Every package includes _test.go files alongside the implementation. Tests must cover:
- Happy path
- Edge cases (empty input, max values, boundary conditions)
- Error conditions
Integration tests live alongside unit tests and use build tags or test name conventions.
Running Tests
# Run all tests
make test
# or: go test -v ./...
# Run unit tests only (short mode)
make test-unit
# or: go test -v -short ./...
# Run integration tests only
make test-integration
# or: go test -v -run Integration ./...
# Run with race detector
make test-race
# or: go test -v -race ./...
# Generate coverage report
make test-coverage
# or: go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out -o coverage.htmlTest Categories
| Package | Focus Areas |
|---|---|
internal/cli | Command parsing, flag validation, output formatting, shell completion |
internal/core | Request lifecycle, risk scoring, state machine transitions, rate limiting, dry-run mode, rollback, normalization, pattern matching |
internal/config | Configuration loading, defaults, validation |
internal/db | SQLite persistence, schema migrations, request/review/session CRUD, outcome tracking, race conditions |
internal/daemon | IPC client/server, hook queries, notifications, lifecycle, TCP transport, timeouts |
internal/git | Git integration, repository detection, history |
internal/integrations | Agent Mail, Claude hooks, Cursor integration |
internal/tui | Dashboard, review screens, components, themes |
internal/e2e | End-to-end integration tests |
---
Logging & Console Output
- Prefer the shared
charmbracelet/loglogger over rawfmt.Println. - No random console logs in UI components; if needed, make them dev-only and clean them up.
- Log structured context: IDs, user, request, model, etc.
- If a logger helper exists, you must use it; do not invent a different pattern.
---
Third-Party Library Usage
If you aren't 100% sure how to use a third-party library, SEARCH ONLINE to find the latest documentation and current best practices.
---
Contribution Policy
Remove any mention of contributing/contributors from README and don't reinsert it.
---
slb — This Project
This is the project you're working on. slb (Simultaneous Launch Button) is a cross-platform CLI tool implementing a "two-person rule" (inspired by nuclear launch protocols) for potentially destructive commands executed by AI coding agents.
What It Does
When an AI agent wants to run a dangerous command (e.g., rm -rf, kubectl delete node, DROP DATABASE), it must submit the command for peer review by another agent. Only when a second agent independently evaluates the reasoning and approves does the command execute. This creates a deliberate friction point that forces reconsideration of destructive actions.
Why It Exists
AI agents can hallucinate, get tunnel vision, or misunderstand context. A fresh perspective from a second agent — especially one with different training/architecture — catches errors before they become irreversible disasters. The primary use case is multiple AI agents working in parallel on the same codebase, where one agent's mistake could destroy another's work or critical infrastructure.
Architecture
Agent → slb request → Risk Scoring → ┬─ Low risk → Auto-approve (configurable)
└─ High risk → Peer Review Required
│
slb pending (reviewer)
│
slb review → approve/reject
│
slb execute → Command runs
│
Outcome recordedProject Structure
slb/
├── cmd/slb/main.go # Entry point
├── internal/
│ ├── cli/ # Cobra commands (request, approve, reject, show, status, etc.)
│ ├── config/ # Configuration loading, defaults, validation
│ ├── core/ # Domain logic: risk scoring, state machine, request lifecycle
│ ├── db/ # SQLite persistence: requests, reviews, sessions, outcomes
│ ├── daemon/ # Background daemon: IPC, hook queries, notifications, timeouts
│ ├── git/ # Git integration: repo detection, history
│ ├── integrations/ # Agent Mail, Claude hooks, Cursor integration
│ ├── output/ # Output formatting (JSON, table, etc.)
│ ├── tui/ # Bubbletea TUI: dashboard, review, components, themes
│ ├── testutil/ # Shared test utilities
│ ├── e2e/ # End-to-end integration tests
│ └── utils/ # Shared utility functions
├── Makefile # Build, test, lint, release targets
├── go.mod / go.sum # Go module definition
└── codecov.yml # Coverage configurationKey Files by Package
| Package | Key Files | Purpose |
|---|---|---|
internal/cli | request.go, approve.go, reject.go, review.go | Command submission, approval, rejection workflows |
internal/cli | run.go, execute.go, status.go, show.go | Atomic run, execution, status checking, request display |
internal/cli | daemon.go, hook.go, session.go, watch_*.go | Daemon control, git hook integration, session management |
internal/cli | emergency.go, rollback.go, history.go | Emergency override, rollback, audit history |
internal/core | request.go, review.go, session.go | Request creation, review logic, session lifecycle |
internal/core | risk.go, statemachine.go, ratelimit.go | Risk classification, state transitions, rate limiting |
internal/core | dryrun.go, rollback.go, command.go | Dry-run simulation, rollback, command parsing |
internal/core | patterns.go, normalize.go, attachments.go | Dangerous pattern matching, command normalization, file attachments |
internal/db | db.go, types.go, enums.go, migrations.go | Database initialization, domain types, enums, schema migrations |
internal/db | requests.go, reviews.go, sessions.go, outcomes.go | CRUD for requests, reviews, sessions, execution outcomes |
internal/db | patterns.go | Dangerous command pattern storage and matching |
internal/daemon | daemon.go, ipc.go, ipc_client.go | Background daemon, IPC protocol, client connections |
internal/daemon | hook_query.go, notifications.go, timeout.go | Git hook integration, agent notifications, timeout enforcement |
internal/integrations | agentmail.go, claudehooks.go, cursor.go | MCP Agent Mail, Claude Code hooks, Cursor IDE integration |
internal/tui | tui.go, app.go | TUI application, bubbletea model |
Core Domain Types
| Type | Purpose |
|---|---|
Request | A command submitted for peer review with justification, risk tier, status |
Review | An approval or rejection decision by a reviewing agent |
Session | An agent session (requester or reviewer identity) |
RiskTier | Risk classification of a command (determines auto-approve vs. peer review) |
RequestStatus | State machine: pending, approved, rejected, executed, expired, cancelled |
Decision | Approve or reject |
CommandSpec | Parsed command with shell flag, working directory |
Justification | Structured reasoning for why the command should execute |
Attachment | Context file attached to a request for reviewer reference |
Key Design Decisions
- Client-side execution: Commands execute on the requesting agent's machine, not the daemon
- Command hash binding: Approved hash must match execution hash to prevent tampering
- Dynamic quorum: Configurable number of approvals required (default: 1)
- Rate limiting: Prevents abuse by limiting request frequency per session
- Sensitive data redaction: Custom patterns to redact secrets from review display
- snake_case JSON contract: All JSON output uses snake_case for consistency
- Pure-Go SQLite (
modernc.org/sqlite): No CGo dependency for portability - Atomic `slb run`: Single command that submits, waits for review, and executes
- State machine enforcement: All request status transitions validated by state machine
- Git hook integration: Pre-commit/pre-push hooks can intercept dangerous commands
- Daemon mode: Background process watches for pending requests and manages timeouts
- Agent Mail integration: Notifications and coordination via MCP Agent Mail
---
MCP Agent Mail — Multi-Agent Coordination
Agent Mail is already available as an MCP server; do not treat it as a CLI you must shell out to. MCP Agent Mail should be available to you as an MCP server; if it's not, then flag to the user. They might need to start Agent Mail using the am alias or by running cd "<directory_where_they_installed_agent_mail>/mcp_agent_mail" && bash scripts/run_server_with_token.sh if the alias isn't available or isn't working.
What Agent Mail gives:
- Identities, inbox/outbox, searchable threads.
- Advisory file reservations (leases) to avoid agents clobbering each other.
- Persistent artifacts in git (human-auditable).
Core patterns:
1. Same repo
- Register identity:
ensure_projectthenregister_agentwith the repo's absolute path asproject_key.- Reserve files before editing:
file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true).- Communicate:
send_message(..., thread_id="FEAT-123").fetch_inbox, thenacknowledge_message.- Fast reads:
resource://inbox/{Agent}?project=<abs-path>&limit=20.resource://thread/{id}?project=<abs-path>&include_bodies=true.- Optional:
- Set
AGENT_NAMEso the pre-commit guard can block conflicting commits. WORKTREES_ENABLED=1andAGENT_MAIL_GUARD_MODE=warnduring trials.- Check hooks with
mcp-agent-mail guard status .and identity withmcp-agent-mail mail status ..
2. Multiple repos in one product
- Option A: Same
project_keyfor all; use specific reservations (frontend/**,backend/**). - Option B: Different projects linked via:
macro_contact_handshakeorrequest_contact/respond_contact.- Use a shared
thread_id(e.g., ticket key) for cross-repo threads.
Macros vs granular:
- Prefer macros when speed is more important than fine-grained control:
macro_start_session,macro_prepare_thread,macro_file_reservation_cycle,macro_contact_handshake.- Use granular tools when you need explicit behavior.
Product bus:
- Create/ensure product:
mcp-agent-mail products ensure MyProduct --name "My Product". - Link repo:
mcp-agent-mail products link MyProduct .. - Inspect:
mcp-agent-mail products status MyProduct. - Search:
mcp-agent-mail products search MyProduct "br-123 OR \"release plan\"" --limit 50. - Product inbox:
mcp-agent-mail products inbox MyProduct YourAgent --limit 50 --urgent-only --include-bodies. - Summaries:
mcp-agent-mail products summarize-thread MyProduct "br-123" --per-thread-limit 100 --no-llm.
Server-side tools (for orchestrators) include:
ensure_product(product_key|name)products_link(product_key, project_key)resource://product/{key}search_messages_product(product_key, query, limit=20)
Common pitfalls:
- "from_agent not registered" -> call
register_agentwith correctproject_key. FILE_RESERVATION_CONFLICT-> adjust patterns, wait for expiry, or use non-exclusive reservation.- Auth issues with JWT+JWKS -> bearer token with
kidmatching server JWKS; static bearer only when JWT disabled.
---
Beads (br) — Dependency-Aware Issue Tracking
Beads provides a lightweight, dependency-aware issue database and CLI (br - beads_rust) for selecting "ready work," setting priorities, and tracking status. It complements MCP Agent Mail's messaging and file reservations.
Important: br is non-invasive—it NEVER runs git commands automatically. You must manually commit changes after br sync --flush-only.
SQLite/WAL Caution: br uses SQLite with WAL mode. Always run br sync --flush-only before git operations to ensure .beads/ files are consistent.
Conventions
- Single source of truth: Beads for task status/priority/dependencies; Agent Mail for conversation and audit
- Shared identifiers: Use Beads issue ID (e.g.,
br-123) as Mailthread_idand prefix subjects with[br-123] - Reservations: When starting a task, call
file_reservation_paths()with the issue ID inreason
Typical Agent Flow
1. Pick ready work (Beads):
br ready --json # Choose highest priority, no blockers2. Reserve edit surface (Mail):
file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true, reason="br-123")3. Announce start (Mail):
send_message(..., thread_id="br-123", subject="[br-123] Start: <title>", ack_required=true)4. Work and update: Reply in-thread with progress
5. Complete and release:
br close 123 --reason "Completed"
br sync --flush-only # Export to JSONL (no git operations) release_file_reservations(project_key, agent_name, paths=["src/**"])Final Mail reply: [br-123] Completed with summary
Mapping Cheat Sheet
| Concept | Value |
|---|---|
Mail thread_id | br-### |
| Mail subject | [br-###] ... |
File reservation reason | br-### |
| Commit messages | Include br-### for traceability |
---
bv — Graph-Aware Triage Engine
bv is a graph-aware triage engine for Beads projects (.beads/beads.jsonl). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.
Scope boundary: bv handles what to work on (triage, priority, planning). For agent-to-agent coordination (messaging, work claiming, file reservations), use MCP Agent Mail.
*CRITICAL: Use ONLY `--robot- flags. Bare bv` launches an interactive TUI that blocks your session.**
The Workflow: Start With Triage
`bv --robot-triage` is your single entry point. It returns:
quick_ref: at-a-glance counts + top 3 picksrecommendations: ranked actionable items with scores, reasons, unblock infoquick_wins: low-effort high-impact itemsblockers_to_clear: items that unblock the most downstream workproject_health: status/type/priority distributions, graph metricscommands: copy-paste shell commands for next steps
bv --robot-triage # THE MEGA-COMMAND: start here
bv --robot-next # Minimal: just the single top pick + claim commandCommand Reference
Planning:
| Command | Returns |
|---|---|
--robot-plan | Parallel execution tracks with unblocks lists |
--robot-priority | Priority misalignment detection with confidence |
Graph Analysis:
| Command | Returns |
|---|---|
--robot-insights | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |
--robot-label-health | Per-label health: health_level, velocity_score, staleness, blocked_count |
--robot-label-flow | Cross-label dependency: flow_matrix, dependencies, bottleneck_labels |
--robot-label-attention [--attention-limit=N] | Attention-ranked labels |
History & Change Tracking:
| Command | Returns |
|---|---|
--robot-history | Bead-to-commit correlations |
--robot-diff --diff-since <ref> | Changes since ref: new/closed/modified issues, cycles |
Other:
| Command | Returns |
|---|---|
--robot-burndown <sprint> | Sprint burndown, scope changes, at-risk items |
| `--robot-forecast <id\ | all>` |
--robot-alerts | Stale issues, blocking cascades, priority mismatches |
--robot-suggest | Hygiene: duplicates, missing deps, label suggestions |
| `--robot-graph [--graph-format=json\ | dot\ |
--export-graph <file.html> | Interactive HTML visualization |
Scoping & Filtering
bv --robot-plan --label backend # Scope to label's subgraph
bv --robot-insights --as-of HEAD~30 # Historical point-in-time
bv --recipe actionable --robot-plan # Pre-filter: ready to work
bv --recipe high-impact --robot-triage # Pre-filter: top PageRank
bv --robot-triage --robot-triage-by-track # Group by parallel work streams
bv --robot-triage --robot-triage-by-label # Group by domainUnderstanding Robot Output
All robot JSON includes:
data_hash— Fingerprint of source beads.jsonlstatus— Per-metric state:computed|approx|timeout|skipped+ elapsed msas_of/as_of_commit— Present when using--as-of
Two-phase analysis:
- Phase 1 (instant): degree, topo sort, density
- Phase 2 (async, 500ms timeout): PageRank, betweenness, HITS, eigenvector, cycles
jq Quick Reference
bv --robot-triage | jq '.quick_ref' # At-a-glance summary
bv --robot-triage | jq '.recommendations[0]' # Top recommendation
bv --robot-plan | jq '.plan.summary.highest_impact' # Best unblock target
bv --robot-insights | jq '.status' # Check metric readiness
bv --robot-insights | jq '.Cycles' # Circular deps (must fix!)---
UBS — Ultimate Bug Scanner
Golden Rule: ubs <changed-files> before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
Commands
ubs file.go file2.go # Specific files (< 1s) — USE THIS
ubs $(git diff --name-only --cached) # Staged files — before commit
ubs --only=go,toml . # Language filter (3-5x faster)
ubs --ci --fail-on-warning . # CI mode — before PR
ubs . # Whole project (ignores vendor/, etc.)Output Format
Warning Category (N errors)
file.go:42:5 - Issue description
Suggested fix
Exit code: 1Parse: file:line:col -> location | fix suggestion -> how to fix | Exit 0/1 -> pass/fail
Fix Workflow
1. Read finding -> category + fix suggestion 2. Navigate file:line:col -> view context 3. Verify real issue (not false positive) 4. Fix root cause (not symptom) 5. Re-run ubs <file> -> exit 0 6. Commit
Bug Severity
- Critical (always fix): Memory safety, data races, SQL injection, command injection
- Important (production): Unchecked errors, resource leaks, overflow checks
- Contextual (judgment): TODO/FIXME, fmt.Println debugging
---
RCH — Remote Compilation Helper
RCH offloads go build, go test, golangci-lint, and other compilation commands to a fleet of 8 remote Contabo VPS workers instead of building locally. This prevents compilation storms from overwhelming csd when many agents run simultaneously.
RCH is installed at `~/.local/bin/rch` and is hooked into Claude Code's PreToolUse automatically. Most of the time you don't need to do anything if you are Claude Code — builds are intercepted and offloaded transparently.
To manually offload a build:
rch exec -- go build ./...
rch exec -- go test ./...
rch exec -- golangci-lint run ./...Quick commands:
rch doctor # Health check
rch workers probe --all # Test connectivity to all 8 workers
rch status # Overview of current state
rch queue # See active/waiting buildsIf rch or its workers are unavailable, it fails open — builds run locally as normal.
Note for Codex/GPT-5.2: Codex does not have the automatic PreToolUse hook, but you can (and should) still manually offload compute-intensive compilation commands using rch exec -- <command>. This avoids local resource contention when multiple agents are building simultaneously.
---
ast-grep vs ripgrep
Use `ast-grep` when structure matters. It parses code and matches AST nodes, ignoring comments/strings, and can safely rewrite code.
- Refactors/codemods: rename APIs, change import forms
- Policy checks: enforce patterns across a repo
- Editor/automation: LSP mode,
--jsonoutput
Use `ripgrep` when text is enough. Fastest way to grep literals/regex.
- Recon: find strings, TODOs, log lines, config values
- Pre-filter: narrow candidate files before ast-grep
Rule of Thumb
- Need correctness or applying changes ->
ast-grep - Need raw speed or hunting text ->
rg - Often combine:
rgto shortlist files, thenast-grepto match/modify
Go Examples
# Find structured code (ignores comments)
ast-grep run -l Go -p 'func $NAME($$$ARGS) $RET { $$$BODY }'
# Find all unchecked error returns
ast-grep run -l Go -p '$_, err := $FUNC($$$); $$$'
# Quick textual hunt
rg -n 'fmt.Println' -t go
# Combine speed + precision
rg -l -t go 'Println' | xargs ast-grep run -l Go -p 'fmt.Println($$$)' --json---
Morph Warp Grep — AI-Powered Code Search
Use `mcp__morph-mcp__warp_grep` for exploratory "how does X work?" questions. An AI agent expands your query, greps the codebase, reads relevant files, and returns precise line ranges with full context.
Use `ripgrep` for targeted searches. When you know exactly what you're looking for.
Use `ast-grep` for structural patterns. When you need AST precision for matching/rewriting.
When to Use What
| Scenario | Tool | Why |
|---|---|---|
| "How does the two-person rule work?" | warp_grep | Exploratory; don't know where to start |
| "Where is the risk scoring implemented?" | warp_grep | Need to understand architecture |
"Find all uses of db.CreateRequest" | ripgrep | Targeted literal search |
"Find files with fmt.Println" | ripgrep | Simple pattern |
"Replace all log.Print with log.Info" | ast-grep | Structural refactor |
warp_grep Usage
mcp__morph-mcp__warp_grep(
repoPath: "/dp/slb",
query: "How does the request approval state machine work?"
)Returns structured results with file paths, line ranges, and extracted code snippets.
Anti-Patterns
- Don't use
warp_grepto find a specific function name -> useripgrep - Don't use
ripgrepto understand "how does X work" -> wastes time with manual reads - Don't use
ripgrepfor codemods -> risks collateral edits
---
cass — Cross-Agent Search
cass indexes prior agent conversations (Claude Code, Codex, Cursor, Gemini, ChatGPT, etc.) so we can reuse solved problems.
Rules:
- Never run bare
cass(TUI). Always use--robotor--json.
Examples:
cass health
cass search "authentication error" --robot --limit 5
cass view /path/to/session.jsonl -n 42 --json
cass expand /path/to/session.jsonl -n 42 -C 3 --json
cass capabilities --json
cass robot-docs guideTips:
- Use
--fields minimalfor lean output. - Filter by agent with
--agent. - Use
--days Nto limit to recent history.
stdout is data-only, stderr is diagnostics; exit code 0 means success.
Treat cass as a way to avoid re-solving problems other agents already handled.
---
Memory System: cass-memory
The Cass Memory System (cm) is a tool for giving agents an effective memory based on the ability to quickly search across previous coding agent sessions across an array of different coding agent tools (e.g., Claude Code, Codex, Gemini-CLI, Cursor, etc.) and projects (and even across multiple machines, optionally) and then reflect on what they find and learn in new sessions to draw out useful lessons and takeaways; these lessons are then stored and can be queried and retrieved later, much like how human memory works.
The cm onboard command guides you through analyzing historical sessions and extracting valuable rules.
Quick Start
# 1. Check status and see recommendations
cm onboard status
# 2. Get sessions to analyze (filtered by gaps in your playbook)
cm onboard sample --fill-gaps
# 3. Read a session with rich context
cm onboard read /path/to/session.jsonl --template
# 4. Add extracted rules (one at a time or batch)
cm playbook add "Your rule content" --category "debugging"
# Or batch add:
cm playbook add --file rules.json
# 5. Mark session as processed
cm onboard mark-done /path/to/session.jsonlBefore starting complex tasks, retrieve relevant context:
cm context "<task description>" --jsonThis returns:
- relevantBullets: Rules that may help with your task
- antiPatterns: Pitfalls to avoid
- historySnippets: Past sessions that solved similar problems
- suggestedCassQueries: Searches for deeper investigation
Protocol
1. START: Run cm context "<task>" --json before non-trivial work 2. WORK: Reference rule IDs when following them (e.g., "Following b-8f3a2c..." 3. FEEDBACK: Leave inline comments when rules help/hurt:
// [cass: helpful b-xyz] - reason// [cass: harmful b-xyz] - reason
4. END: Just finish your work. Learning happens automatically.
Key Flags
| Flag | Purpose |
|---|---|
--json | Machine-readable JSON output (required!) |
--limit N | Cap number of rules returned |
--no-history | Skip historical snippets for faster response |
stdout = data only, stderr = diagnostics. Exit 0 = success.
<!-- bv-agent-instructions-v1 -->
---
Beads Workflow Integration
This project uses beads_rust (br) for issue tracking. Issues are stored in .beads/ and tracked in git.
Important: br is non-invasive—it NEVER executes git commands. After br sync --flush-only, you must manually run git add .beads/ && git commit.
Essential Commands
# View issues (launches TUI - avoid in automated sessions)
bv
# CLI commands for agents (use these instead)
br ready # Show issues ready to work (no blockers)
br list --status=open # All open issues
br show <id> # Full issue details with dependencies
br create --title="..." --type=task --priority=2
br update <id> --status=in_progress
br close <id> --reason "Completed"
br close <id1> <id2> # Close multiple issues at once
br sync --flush-only # Export to JSONL (NO git operations)Workflow Pattern
1. Start: Run br ready to find actionable work 2. Claim: Use br update <id> --status=in_progress 3. Work: Implement the task 4. Complete: Use br close <id> 5. Sync: Run br sync --flush-only then manually commit
Key Concepts
- Dependencies: Issues can block other issues.
br readyshows only unblocked work. - Priority: P0=critical, P1=high, P2=medium, P3=low, P4=backlog (use numbers, not words)
- Types: task, bug, feature, epic, chore
- Blocking:
br dep add <issue> <depends-on>to add dependencies
Session Protocol
Before ending any session, run this checklist:
git status # Check what changed
git add <files> # Stage code changes
br sync --flush-only # Export beads to JSONL
git add .beads/ # Stage beads changes
git commit -m "..." # Commit everything together
git push # Push to remoteBest Practices
- Check
br readyat session start to find available work - Update status as you work (in_progress -> closed)
- Create new issues with
br createwhen you discover tasks - Use descriptive titles and set appropriate priority/type
- Always
br sync --flush-only && git add .beads/before ending session
<!-- end-bv-agent-instructions -->
Landing the Plane (Session Completion)
When ending a work session, you MUST complete ALL steps below.
MANDATORY WORKFLOW:
1. File issues for remaining work - Create issues for anything that needs follow-up 2. Run quality gates (if code changed) - Tests, linters, builds 3. Update issue status - Close finished work, update in-progress items 4. Sync beads - br sync --flush-only to export to JSONL 5. Hand off - Provide context for next session
---
Note for Codex/GPT-5.2:
You constantly bother me and stop working with concerned questions that look similar to this:
Unexpected changes (need guidance)
- Working tree still shows edits I did not make in go.mod, go.sum, internal/cli/request.go. Please advise whether to keep/commit/revert these before any further work. I did not touch them.
Next steps (pick one)
1. Decide how to handle the unrelated modified files above so we can resume cleanly.
2. ...NEVER EVER DO THAT AGAIN. The answer is literally ALWAYS the same: those are changes created by the potentially dozen of other agents working on the project at the same time. This is not only a common occurrence, it happens multiple times PER MINUTE. The way to deal with it is simple: you NEVER, under ANY CIRCUMSTANCE, stash, revert, overwrite, or otherwise disturb in ANY way the work of other agents. Just treat those changes identically to changes that you yourself made. Just fool yourself into thinking YOU made the changes and simply don't recall it for some reason.
---
Note on Built-in TODO Functionality
Also, if I ask you to explicitly use your built-in TODO functionality, don't complain about this and say you need to use beads. You can use built-in TODOs if I tell you specifically to do so. Always comply with such orders.
Changelog
All notable changes to SLB (Simultaneous Launch Button) are documented in this file.
SLB is a cross-platform CLI that implements a two-person rule for running potentially destructive commands from AI coding agents. It provides risk-based command classification, peer review enforcement, and full audit logging for multi-agent workflows.
Format follows Keep a Changelog. This project uses Semantic Versioning.
---
[Unreleased] -- changes on main since v0.2.0
Compare: `v0.2.0...main`
Pattern Matching
- Fallback SQL DELETE detection for compound commands: The pattern engine now detects dangerous
DELETE FROMstatements embedded inside compound commands (e.g.,psql -c "DELETE FROM users; DROP TABLE x;"), closing a gap where SQL wrapped in shell commands could evade classification (`c40b097`)
Output Formats
- TOON format support: Token-efficient encoding via
tru/trbinary for structured CLI output, reducing token consumption when agents consumeslboutput (`d2154d9`) - TOON output simplified and refactored; support for both
truandtrbinary names (`9c741cc`, `62803d9`, `de41dbb`) - `--stats` flag and
SLB_OUTPUT_FORMATenvironment variable for output format control (`91241f6`)
CLI Fixes
slb checknow outputs human-readable text instead of raw Go map syntax (`99cda5b`)- Status update failures after command execution are now logged instead of silently swallowed (`2a5110e`)
- Tier flag updated from
-tto-Tin pattern tests to avoid flag collision (`f56eeb0`)
Licensing & Branding
- License changed to MIT with OpenAI/Anthropic Rider (`badc986`, `b7becfe`)
- README updated to reference new license (`a82e130`)
- GitHub social preview image added (1280x640) (`28c2bf7`)
Build & CI
- Resolved errcheck lint errors for unchecked error returns (`0981efd`)
- golangci-lint v2 compatibility fixes and configuration refactored for better code quality checks (`15bc242`, `4eec3b6`, `8aee4ec`, `3782619`)
- CI workflow improved with security and reliability enhancements; test reliability fixes (`4acadd0`, `a1737b4`)
- Go module dependencies updated to latest stable versions (`d66801f`)
- ACFS checksum dispatch and notification workflows added (`f0fe162`, `94a35eb`)
Documentation
- README: prioritize Homebrew/Scoop installation methods over direct download (`0b0307c`)
- AGENTS.md updated with latest multi-agent conventions (`a5a4d59`)
---
[v0.2.0] -- 2026-01-13
GitHub Release: `v0.2.0` (published 2026-01-14) Compare: `v0.1.0...v0.2.0`
This release adds Claude Code hook integration for automatic command interception, enables Homebrew and Scoop auto-publishing via GoReleaser, and resolves a batch of state machine, pattern matching, and hook bugs discovered during post-v0.1.0 stabilization.
Claude Code Hook Integration
The major feature of this release: a complete slb hook subcommand suite that intercepts Bash tool calls before execution in Claude Code sessions.
- Hook infrastructure:
slb hook generate,install,uninstall,status,testcommands with a Python guard script (~/.slb/hooks/slb_guard.py) that classifies risk and communicates with the SLB daemon via Unix socket (`39c2f87`) - Returns
allow,ask, orblockaction to Claude Code based on risk tier - Fail-closed: dangerous commands blocked when SLB daemon is unavailable
Package Distribution
- GoReleaser auto-publishing to Homebrew (
brew install dicklesworthstone/tap/slb) and Scoop (scoop install dicklesworthstone/slb) -- packages now auto-update on every release (`995dd17`) - GoReleaser config updated for v2 format (
folder->directory); Homebrew skipped temporarily until tap repo was ready (`87db783`, `8764edd`) - Claude Code
SKILL.mdadded for automatic capability discovery (`2926cda`)
Security
- Compound command quote bypass vulnerability fixed: Shell-aware splitting now correctly handles quoted separators, preventing commands like
echo ";" && rm -rf /from being misclassified as safe (`dffc948`)
State Machine Fixes
- Transitions now prevent panics on short request IDs (`ba4510d`)
- Cancel command and
StatusEscalatedstate transitions corrected (`1476f11`) - Escalated requests can now be reviewed (previously silently rejected) (`00b213c`)
Pattern Matching Fixes
- Compound command tier precedence corrected -- highest-risk segment now properly determines overall tier (`7fd44d9`)
IsSafeflag initialization corrected for compound command classification (`036c75a`)rm -frpattern bug resolved (previously onlyrm -rfwas matched) (`bb9f88a`)
Execution Fixes
exit_codeanddurationonly set when command result is actually available, preventing nil dereferences (`1c733e8`)- Edge cases in command truncation and event type mapping handled (`8d48a9e`)
- Missing
Execution/Rollbackparsing inscanRequestsdatabase query (`bc94544`)
Hook Fixes
- Corrected bounds check in
slb_guard.pysubstring matching (`24157ef`) - Fixed Python hook daemon communication path (`7268863`)
hookTestCmdvalidation and Python fallback caution handling corrected (`571971e`)- macOS test compatibility issues resolved (`bb9f88a`)
Documentation
- Comprehensive README written covering all features: request lifecycle, execution verification gates, pattern engine internals, TUI dashboard, agent mail, outcome tracking, session management, emergency overrides (`f7d32a7`, `786899a`)
- AI writing patterns removed from README (`05b3036`)
---
[v0.1.0] -- 2025-12-24
GitHub Release: `v0.1.0` (published 2025-12-25)
The initial public release of SLB, built from scratch in ~11 days (2025-12-13 to 2025-12-24) with 264 commits, comprehensive test coverage (80%+ CI threshold), and cross-platform binaries via GoReleaser.
Command Classification Engine
The core of SLB: a shell-aware pattern matching engine that classifies commands into risk tiers before execution.
- Four-tier risk classification: CRITICAL (2+ approvals, never auto-approve), DANGEROUS (1 approval), CAUTION (auto-approve after 30s delay), SAFE (immediate, no review) (`c4561db`)
- Shell-aware normalization: Strips wrapper prefixes (
sudo,doas,env,time,nohup), extracts inner commands frombash -c '...', resolves relative paths to absolute (`c4561db`) - Compound command splitting: Commands joined by
&&,||,;,|are split and classified independently -- the highest-risk segment determines the overall tier (`c4561db`) - Fail-safe parse handling: Unparseable commands (unbalanced quotes, complex escapes) get their tier upgraded one level (SAFE -> CAUTION, CAUTION -> DANGEROUS, etc.) (`c4561db`)
- Runtime pattern management via
slb patterns list|test|add-- agents can add patterns but not remove them (`a515b09`) - Critical patterns for disk destruction (
dd of=/dev/) and system file changes added (`b3dd913`) - Edge case gaps in risk classification closed (`1225afb`)
Request Lifecycle & Execution
The complete workflow from requesting approval through execution and rollback.
- `slb run`: Atomic check-request-wait-execute pipeline -- the primary command for agents (`a515b09`)
- Client-side execution: Commands run in the calling process's shell environment, inheriting AWS credentials, kubeconfig, virtualenvs, SSH agents, database connection strings (`71d5808`)
- Command hash binding: SHA-256 hash computed at request time, verified before execution -- any modification after approval is rejected (`c4561db`)
- Five execution verification gates: status check, approval expiry, command hash match, tier consistency, first-executor-wins atomicity (`71d5808`)
- Dry run pre-flight for supported commands:
terraform plan,kubectl diff,git diff(`d1e8bde`) - Rollback state capture: Filesystem tar archives, git state (HEAD, branch, dirty files), Kubernetes manifests captured before execution for potential rollback via
slb rollback(`d1e8bde`) - Emergency override:
slb emergency-executewith mandatory reason, hash acknowledgment, and permanent audit record for true emergencies (`a515b09`) - Request state machine with well-defined transitions: PENDING -> APPROVED/REJECTED/CANCELLED/TIMEOUT -> EXECUTING -> EXECUTED/EXEC_FAIL/TIMED_OUT (`c4561db`)
- Approval TTL enforcement: 30 minutes standard, 10 minutes for CRITICAL (`c4561db`)
CLI Commands
The full command-line interface for agents and human reviewers.
- `slb init`: Project initialization creating
.slb/directory withstate.db,config.toml,pending/, sessions, and logs (`feb8fab`) - Request plumbing:
slb request,slb status [--wait],slb pending [--all-projects],slb cancel(`a515b09`) - Peer review:
slb review,slb approve,slb rejectwith--target-projectflag for cross-project reviews (`d4894a7`, `701df4c`) - Execution:
slb execute,slb emergency-execute,slb rollback(`4f1acc0`) - Session management:
slb session start|end|resume|list|heartbeat|gc|reset-limits(`a515b09`) - History & search:
slb historywith full-text search (-q), tier/status/agent/date filtering;slb showwith--with-reviews,--with-execution,--with-attachments(`a515b09`) - Outcome tracking:
slb outcome record|list|statsfor execution feedback to improve classification over time (`6c3b272`) - Event streaming:
slb watchwith real-time NDJSON output, polling fallback, and--auto-approve-cautionfor reviewer agents (`b958a38`) - Daemon management:
slb daemon start|stop|status(`cf17daa`) - IDE integration generators:
slb integrations claude-hooksandslb integrations cursor-rules(`7fd6a7d`) - Shell completions:
slb completion bash|zsh|fish(`a515b09`) - Request attachments:
--attachfor files/images,--attach-cmdfor command output (`a515b09`) - JSON and YAML output formats (
--output json,--output yaml,--json) (`de1fe83`) - Structured exit codes (0=success, 1=error, 2=invalid args, 3=not found, 4=permission denied, 5=timeout, 6=rate limited) (`a515b09`)
Storage & Database
- SQLite with WAL mode and FTS5 full-text search for request history queries (`f5c8e40`)
ListAllRequests, runtime pattern changes, and enhanced query layer (`0c7e07d`)isUniqueConstraintErrorfixed to not incorrectly match FOREIGN KEY errors (`5561d52`)
Daemon & IPC
The background daemon provides real-time notifications and execution verification.
- Unix socket IPC server with JSON-RPC 2.0 protocol:
hook_query,hook_health,verify_execution,subscribemethods (`9fe267f`) - TCP transport mode for Docker containers and remote agents with auth and IP whitelisting (`007a1fd`)
- Webhook notification system for external alerting integrations (Slack, etc.) (`4a635db`)
- Desktop notifications via AppleScript (macOS), notify-send (Linux), PowerShell (Windows) (`007a1fd`)
- File watcher monitoring
pending/directory for new request JSON files (`007a1fd`) - Timeout handling with configurable actions:
escalate(default),auto_reject,auto_approve_warn(`007a1fd`) - IPC server refuses to delete non-socket files, preventing accidental data loss (`662ffee`)
TUI Dashboard
An interactive terminal UI for human reviewers to monitor and act on pending requests.
- Three-panel layout: Agents (active sessions), Pending Requests (sorted by urgency), Activity feed (real-time) (`6eea51f`)
- Interactive reviews: Approve/reject requests directly from the TUI with keyboard shortcuts (`88c25c8`, `ac3f30e`)
- Multi-view navigation: Pattern management view, history browser with FTS search (`fcc6b68`, `8f5248d`)
- Pattern removal review: Human-in-the-loop view for reviewing agent-proposed pattern removals (`6bba671`)
- Component library: StatusBadge, AgentCard, Timeline, icons (`f2dca58`, `de22580`)
Configuration System
- Hierarchical TOML configuration with five priority levels: built-in defaults < user config (
~/.slb/config.toml) < project config (.slb/config.toml) < environment variables (SLB_*) < CLI flags (`58084cb`) - Viper config library integration (`9d1b783`)
- Cross-project reviews with configurable review pools (`58084cb`)
- Trusted self-approval with mandatory delay for designated agents (`58084cb`)
- Conflict resolution policies:
any_rejection_blocks(default),first_wins,human_breaks_tie(`58084cb`) - Different-model requirement with timeout escalation to human reviewers (`9d1b783`, `d43682d`)
- Rate limiting with configurable actions:
reject,queue,warn(`58084cb`) - Dynamic quorum scaling based on active reviewer count (`58084cb`)
Security
- Session key verification on review submission -- prevents review forgery via HMAC signatures (`59e183d`)
- Strict file permissions:
.slb/directory enforced at 0700,state.dband config files at 0600 (`0e69925`) - TOCTOU, injection, and regex vulnerability patches (`edb3e30`)
- Path traversal fix in command normalization (`01b2719`)
- Optimistic locking on
UpdateRequestStatusto fix race conditions in concurrent review (`c6cda51`) - Transactional review submission for concurrency safety (`20039ad`)
shouldAutoApproveCautionextracted as pure function to eliminate P0 security-critical side effects (`621a9ce`)- State machine hardened with strict transition validation (`fa3918c`)
- Duplicate command hashing logic removed to prevent divergence (`699c318`)
Build & CI
- Go module foundation with Makefile build system (`c2376d6`)
- CI/CD pipeline with security scanning via gosec and staticcheck (`1a82a81`)
- GoReleaser cross-platform binaries: Linux amd64/arm64 (tar.gz, deb, rpm, apk), macOS amd64/arm64 (tar.gz), Windows amd64 (zip), with SBOM generation and cosign signatures (`cc17518`)
- Codecov integration with 80% coverage threshold, raised from initial 35% (`101ef24`, `450dc6c`)
Testing
Extensive test coverage achieved across all packages:
- Core: 90.2% coverage (`012584a`)
- CLI: 87% coverage (`4cd7c6a`)
- Daemon: 85% coverage (`b7c65cb`)
- TUI: 97.6% coverage (`6c45bae`)
- Watch command: 90% coverage (`23264c4`)
- Database patterns: 90%+ coverage (`c470dee`)
- Testutil: 91% coverage (`9fcd437`)
- E2E harness: 84.1% coverage (`a3f0650`)
- Test infrastructure foundation package (
internal/testutil) with fixtures and helpers (`966e90d`) - E2E test suites: multi-agent approval workflow (`561133f`), risk tier classification (`f0b9eec`), session and timeout management (`1b5d91c`), git and filesystem rollback (`87834d1`)
- Flaky test ID generation fixed (`e9473e4`)
- IPC server start/stop race conditions fixed (`cb1b7fa`)
- Zombie process prevention in daemon tests (`ba8cae3`)
Other
- Git history audit trail and IDE integration packages (`9921510`)
- Output formatting and utility packages (`006b24a`)
- Version command refactored to use structured output package (`59b1b2f`)
- Context propagation in CLI run commands (`b612262`)
- Contribution policy documented in README (`50917db`)
- Comprehensive README documentation (`7f08706`)
---
Pre-release Development -- 2025-12-13
The project was conceived, designed, and substantially built on 2025-12-13. The initial planning document (PLAN_TO_MAKE_SLB.md) went through rapid iteration to v2.0.0, incorporating atomic slb run, client-side execution, command hash binding, dynamic quorum, and improved SQL patterns.
- `eca7c4a` -- Initial system documentation and AGENTS.md with multi-agent command authorization guidelines
- `3cf711b` -- Initial planning transcript documenting key concepts, approval processes, and pattern management design
- `f205128` -- PLAN_TO_MAKE_SLB.md v2.0.0 with major design revisions
---
<!-- link definitions --> [Unreleased]: https://github.com/Dicklesworthstone/slb/compare/v0.2.0...main [v0.2.0]: https://github.com/Dicklesworthstone/slb/compare/v0.1.0...v0.2.0 [v0.1.0]: https://github.com/Dicklesworthstone/slb/releases/tag/v0.1.0
// Package main provides the entry point for the SLB (Simultaneous Launch Button) CLI.
// SLB implements a two-person rule system for dangerous command authorization.
package main
import (
"os"
"github.com/Dicklesworthstone/slb/internal/cli"
)
func main() {
if err := cli.Execute(); err != nil {
os.Exit(1)
}
}
# Codecov configuration for SLB
# https://docs.codecov.com/docs/codecov-yaml
coverage:
# Overall project coverage targets
status:
project:
default:
target: 80% # Require 80% overall coverage
threshold: 2% # Allow 2% coverage drop before failing
informational: false
# Coverage requirements for new code in PRs
patch:
default:
target: 80% # New code should have 80% coverage
threshold: 5% # More lenient for patches
informational: false
# Round coverage to one decimal place
precision: 1
range: "60...100"
# Ignore certain paths from coverage
ignore:
- "**/*_test.go"
- "**/testutil/**"
- "**/mocks/**"
- "cmd/**" # Entry point - tested via E2E
# Comment configuration for PRs
comment:
layout: "header, diff, flags, components"
behavior: default
require_changes: false
require_base: false
require_head: true
# Flags for different test types
flags:
unit:
paths:
- internal/
carryforward: true
e2e:
paths:
- tests/e2e/
carryforward: true
# GitHub checks integration
github_checks:
annotations: true
module github.com/Dicklesworthstone/slb
go 1.24.13
require (
github.com/BurntSushi/toml v1.6.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/log v0.4.2
github.com/fsnotify/fsnotify v1.9.0
github.com/google/uuid v1.6.0
github.com/mattn/go-shellwords v1.0.12
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/term v0.39.0
modernc.org/sqlite v1.44.2
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/x/ansi v0.11.4 // indirect
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.7.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
modernc.org/libc v1.67.6 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE=
github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.1 h1:RjM8gnVbFbgI67SBekIC7ihFpyXwRPYWXn9BZActHbw=
github.com/clipperhouse/uax29/v2 v2.3.1/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.44.2 h1:EdYqXeBpKFJjg8QYnw6E71MpANkoxyuYi+g68ugOL8g=
modernc.org/sqlite v1.44.2/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
package cli
import (
"fmt"
"path/filepath"
"time"
"github.com/Dicklesworthstone/slb/internal/config"
"github.com/Dicklesworthstone/slb/internal/core"
"github.com/Dicklesworthstone/slb/internal/db"
"github.com/Dicklesworthstone/slb/internal/integrations"
"github.com/Dicklesworthstone/slb/internal/output"
"github.com/spf13/cobra"
)
var (
flagApproveSessionID string
flagApproveSessionKey string
flagApproveComments string
flagApproveTargetProject string
// Structured response flags
flagApproveReasonResponse string
flagApproveEffectResponse string
flagApproveGoalResponse string
flagApproveSafetyResponse string
)
func init() {
approveCmd.Flags().StringVarP(&flagApproveSessionID, "session-id", "s", "", "reviewer session ID (required)")
approveCmd.Flags().StringVarP(&flagApproveSessionKey, "session-key", "k", "", "session HMAC key for signing (required)")
approveCmd.Flags().StringVarP(&flagApproveComments, "comments", "m", "", "additional comments")
approveCmd.Flags().StringVar(&flagApproveTargetProject, "target-project", "", "target project path for cross-project approvals")
// Structured response flags for justification fields
approveCmd.Flags().StringVar(&flagApproveReasonResponse, "reason-response", "", "response to the reason justification")
approveCmd.Flags().StringVar(&flagApproveEffectResponse, "effect-response", "", "response to the expected effect")
approveCmd.Flags().StringVar(&flagApproveGoalResponse, "goal-response", "", "response to the goal")
approveCmd.Flags().StringVar(&flagApproveSafetyResponse, "safety-response", "", "response to the safety argument")
rootCmd.AddCommand(approveCmd)
}
var approveCmd = &cobra.Command{
Use: "approve <request-id>",
Short: "Approve a pending request",
Long: `Approve a command request, allowing it to proceed.
The approval is cryptographically signed with your session key to ensure
authenticity. Your session must be active, and you cannot approve your own
requests (unless you are a trusted self-approve agent).
For cross-project reviews, use --target-project to specify which project's
database contains the request you want to approve.
Examples:
slb approve abc123 -s $SESSION_ID -k $SESSION_KEY
slb approve abc123 -s $SESSION_ID -k $SESSION_KEY -m "Looks safe"
slb approve abc123 -s $SESSION_ID -k $SESSION_KEY --reason-response "Valid use case"
slb approve abc123 -s $SESSION_ID -k $SESSION_KEY --target-project /path/to/other/project`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
requestID := args[0]
// Validate required flags
if flagApproveSessionID == "" {
return fmt.Errorf("--session-id is required")
}
if flagApproveSessionKey == "" {
return fmt.Errorf("--session-key is required")
}
// Determine project and database path
project, err := projectPath()
if err != nil && flagApproveTargetProject == "" {
return err
}
// Use target project if specified (for cross-project approvals)
dbPath := GetDB()
if flagApproveTargetProject != "" {
project = flagApproveTargetProject
dbPath = filepath.Join(flagApproveTargetProject, ".slb", "state.db")
}
// Open database
dbConn, err := db.OpenAndMigrate(dbPath)
if err != nil {
return fmt.Errorf("opening database: %w", err)
}
defer dbConn.Close()
// Build review options
opts := core.ReviewOptions{
SessionID: flagApproveSessionID,
SessionKey: flagApproveSessionKey,
RequestID: requestID,
Decision: db.DecisionApprove,
Responses: db.ReviewResponse{
ReasonResponse: flagApproveReasonResponse,
EffectResponse: flagApproveEffectResponse,
GoalResponse: flagApproveGoalResponse,
SafetyResponse: flagApproveSafetyResponse,
},
Comments: flagApproveComments,
}
// Create review service and submit
reviewSvc := core.NewReviewService(dbConn, core.DefaultReviewConfig())
reviewSvc.SetNotifier(buildAgentMailNotifier(project))
result, err := reviewSvc.SubmitReview(opts)
if err != nil {
return fmt.Errorf("submitting approval: %w", err)
}
// Build output
type approvalResult struct {
ReviewID string `json:"review_id"`
RequestID string `json:"request_id"`
Decision string `json:"decision"`
Approvals int `json:"approvals"`
Rejections int `json:"rejections"`
RequestStatusChanged bool `json:"request_status_changed"`
NewRequestStatus string `json:"new_request_status,omitempty"`
CreatedAt string `json:"created_at"`
}
resp := approvalResult{
ReviewID: result.Review.ID,
RequestID: requestID,
Decision: string(result.Review.Decision),
Approvals: result.Approvals,
Rejections: result.Rejections,
RequestStatusChanged: result.RequestStatusChanged,
CreatedAt: result.Review.CreatedAt.Format(time.RFC3339),
}
if result.RequestStatusChanged {
resp.NewRequestStatus = string(result.NewRequestStatus)
}
out := output.New(output.Format(GetOutput()))
if GetOutput() == "json" {
return out.Write(resp)
}
// Human-readable output
fmt.Printf("Approved request %s\n", requestID)
fmt.Printf("Review ID: %s\n", resp.ReviewID)
fmt.Printf("Approvals: %d, Rejections: %d\n", resp.Approvals, resp.Rejections)
if result.RequestStatusChanged {
fmt.Printf("Request status changed to: %s\n", resp.NewRequestStatus)
if result.NewRequestStatus == db.StatusApproved {
fmt.Println("Request is now approved and ready for execution!")
}
}
return nil
},
}
// buildAgentMailNotifier constructs a notifier from config; falls back to no-op on errors/disabled.
func buildAgentMailNotifier(project string) integrations.RequestNotifier {
cfg, err := config.Load(config.LoadOptions{
ProjectDir: project,
ConfigPath: flagConfig,
})
if err != nil {
return integrations.NoopNotifier{}
}
if !cfg.Integrations.AgentMailEnabled {
return integrations.NoopNotifier{}
}
return integrations.NewAgentMailClient(project, cfg.Integrations.AgentMailThread, "")
}
package cli
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/Dicklesworthstone/slb/internal/testutil"
)
func TestCollectAttachments_EmptyFlags(t *testing.T) {
_ = testutil.NewHarness(t) // Ensure test cleanup
flags := AttachmentFlags{}
attachments, err := CollectAttachments(context.Background(), flags)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(attachments) != 0 {
t.Errorf("expected 0 attachments with empty flags, got %d", len(attachments))
}
}
func TestCollectAttachments_FileAttachment(t *testing.T) {
h := testutil.NewHarness(t)
// Create a test file
testFile := filepath.Join(h.ProjectDir, "test.txt")
if err := os.WriteFile(testFile, []byte("test content"), 0644); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
flags := AttachmentFlags{
Files: []string{testFile},
}
attachments, err := CollectAttachments(context.Background(), flags)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(attachments) != 1 {
t.Fatalf("expected 1 attachment, got %d", len(attachments))
}
// Attachment has Type, Content, and Metadata fields
if attachments[0].Type != "file" {
t.Errorf("expected type 'file', got %q", attachments[0].Type)
}
if attachments[0].Content == "" {
t.Error("expected non-empty content")
}
}
func TestCollectAttachments_FileNotFound(t *testing.T) {
_ = testutil.NewHarness(t)
flags := AttachmentFlags{
Files: []string{"/nonexistent/path/file.txt"},
}
_, err := CollectAttachments(context.Background(), flags)
if err == nil {
t.Fatal("expected error for nonexistent file")
}
}
func TestCollectAttachments_MultipleFiles(t *testing.T) {
h := testutil.NewHarness(t)
// Create test files
file1 := filepath.Join(h.ProjectDir, "file1.txt")
file2 := filepath.Join(h.ProjectDir, "file2.txt")
if err := os.WriteFile(file1, []byte("content 1"), 0644); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
if err := os.WriteFile(file2, []byte("content 2"), 0644); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
flags := AttachmentFlags{
Files: []string{file1, file2},
}
attachments, err := CollectAttachments(context.Background(), flags)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(attachments) != 2 {
t.Errorf("expected 2 attachments, got %d", len(attachments))
}
}
func TestCollectAttachments_ContextCommand(t *testing.T) {
_ = testutil.NewHarness(t)
flags := AttachmentFlags{
Contexts: []string{"echo hello"},
}
attachments, err := CollectAttachments(context.Background(), flags)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(attachments) != 1 {
t.Fatalf("expected 1 attachment, got %d", len(attachments))
}
// Context commands produce a "context" type attachment
if attachments[0].Type != "context" {
t.Errorf("expected type 'context', got %q", attachments[0].Type)
}
}
func TestCollectAttachments_FailingContextCommand(t *testing.T) {
_ = testutil.NewHarness(t)
flags := AttachmentFlags{
Contexts: []string{"nonexistent-command-xyz"},
}
_, err := CollectAttachments(context.Background(), flags)
// Command may or may not fail depending on shell behavior
// Just verify no panic occurs
_ = err
}
func TestCollectAttachments_ScreenshotNotFound(t *testing.T) {
_ = testutil.NewHarness(t)
flags := AttachmentFlags{
Screenshots: []string{"/nonexistent/screenshot.png"},
}
_, err := CollectAttachments(context.Background(), flags)
if err == nil {
t.Fatal("expected error for nonexistent screenshot")
}
}
func TestAttachmentFlags_Struct(t *testing.T) {
// Verify AttachmentFlags struct can be used properly
flags := AttachmentFlags{
Files: []string{"file1.txt", "file2.txt"},
Contexts: []string{"ls -la", "git status"},
Screenshots: []string{"screen.png"},
}
if len(flags.Files) != 2 {
t.Errorf("expected 2 files, got %d", len(flags.Files))
}
if len(flags.Contexts) != 2 {
t.Errorf("expected 2 contexts, got %d", len(flags.Contexts))
}
if len(flags.Screenshots) != 1 {
t.Errorf("expected 1 screenshot, got %d", len(flags.Screenshots))
}
}
// TestCollectAttachments_ValidScreenshot tests loading a valid screenshot.
func TestCollectAttachments_ValidScreenshot(t *testing.T) {
h := testutil.NewHarness(t)
// Create a minimal valid PNG file (1x1 pixel)
// PNG signature + IHDR chunk + IDAT chunk + IEND chunk
pngData := []byte{
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
0x00, 0x00, 0x00, 0x0D, // IHDR length
0x49, 0x48, 0x44, 0x52, // IHDR
0x00, 0x00, 0x00, 0x01, // width: 1
0x00, 0x00, 0x00, 0x01, // height: 1
0x08, 0x02, // 8-bit RGB
0x00, 0x00, 0x00, // compression, filter, interlace
0x90, 0x77, 0x53, 0xDE, // CRC
0x00, 0x00, 0x00, 0x0C, // IDAT length
0x49, 0x44, 0x41, 0x54, // IDAT
0x08, 0xD7, 0x63, 0xF8, 0xFF, 0xFF, 0xFF, 0x00, 0x05, 0xFE, 0x02, 0xFE, // compressed data
0xA2, 0x76, 0xD0, 0x3A, // CRC
0x00, 0x00, 0x00, 0x00, // IEND length
0x49, 0x45, 0x4E, 0x44, // IEND
0xAE, 0x42, 0x60, 0x82, // CRC
}
screenshotPath := filepath.Join(h.ProjectDir, "test_screenshot.png")
if err := os.WriteFile(screenshotPath, pngData, 0644); err != nil {
t.Fatalf("failed to create test screenshot: %v", err)
}
flags := AttachmentFlags{
Screenshots: []string{screenshotPath},
}
attachments, err := CollectAttachments(context.Background(), flags)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(attachments) != 1 {
t.Fatalf("expected 1 attachment, got %d", len(attachments))
}
// Screenshot should produce a "screenshot" type attachment
if attachments[0].Type != "screenshot" {
t.Errorf("expected type 'screenshot', got %q", attachments[0].Type)
}
}
// TestCollectAttachments_MixedTypes tests loading multiple attachment types.
func TestCollectAttachments_MixedTypes(t *testing.T) {
h := testutil.NewHarness(t)
// Create a test file
filePath := filepath.Join(h.ProjectDir, "test.txt")
if err := os.WriteFile(filePath, []byte("test content"), 0644); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
flags := AttachmentFlags{
Files: []string{filePath},
Contexts: []string{"echo hello"},
}
attachments, err := CollectAttachments(context.Background(), flags)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(attachments) != 2 {
t.Fatalf("expected 2 attachments, got %d", len(attachments))
}
// Verify we got both types
hasFile := false
hasContext := false
for _, a := range attachments {
if a.Type == "file" {
hasFile = true
}
if a.Type == "context" {
hasContext = true
}
}
if !hasFile {
t.Error("expected file attachment")
}
if !hasContext {
t.Error("expected context attachment")
}
}