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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/backscroll/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ allowed-tools:

# Backscroll

Execute `backscroll --skill` exactly as a standalone command, then follow the complete instructions printed to stdout. Treat that output as authoritative.
Execute `backscroll skill` exactly as a standalone command, then follow the complete instructions printed to stdout. Treat that output as authoritative.
34 changes: 2 additions & 32 deletions .githooks/post-merge
Original file line number Diff line number Diff line change
@@ -1,37 +1,7 @@
#!/usr/bin/env bash
# Post-merge: install inputs, propagate doc aggregates

[ -z "$HOME" ] && HOME=$(eval echo ~)
# Post-merge: propagate documentation aggregates.

REPO_ROOT="$(git rev-parse --show-toplevel)"

install_input_presets() {
local config_dir inputs_dest preset dest
if [ -n "${BACKSCROLL_CONFIG_DIR:-}" ]; then
config_dir="$BACKSCROLL_CONFIG_DIR"
elif [ "$(uname -s)" = "Darwin" ]; then
config_dir="$HOME/Library/Application Support"
else
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}"
fi

inputs_dest="$config_dir/backscroll/inputs"
mkdir -p "$inputs_dest"
for preset in claude.inputs.toml pi.inputs.toml omp.inputs.toml codex.inputs.toml; do
dest="$inputs_dest/$preset"
if [ -e "$dest" ] && [ "${BACKSCROLL_FORCE_INPUTS:-0}" != "1" ]; then
echo "backscroll: $dest exists, skipping"
continue
fi
cp "$REPO_ROOT/inputs/$preset" "$dest"
echo "backscroll: installed input preset → $dest"
done
}

# Existing product-owned links follow the stable clone automatically. Never
# replace skill destinations implicitly; see docs/skill-installation.md.
echo "backscroll: skills unchanged; explicit installation: docs/skill-installation.md"
install_input_presets

# Propagate aggregate values after merge (fix stale parent READMEs).
# Propagate aggregate values after merge.
rootline fix --all "$REPO_ROOT/docs/roadmap/" 2>/dev/null || true
31 changes: 1 addition & 30 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Pre-push: validate docs, check drift, install input presets
# Pre-push: validate docs and check drift

remote="$1"
REPO_ROOT="$(git rev-parse --show-toplevel)"
Expand Down Expand Up @@ -66,32 +66,3 @@ while read local_ref local_sha remote_ref remote_sha; do
echo "CI gate passed."
fi
done

install_input_presets() {
local config_dir inputs_dest preset dest
if [ -n "${BACKSCROLL_CONFIG_DIR:-}" ]; then
config_dir="$BACKSCROLL_CONFIG_DIR"
elif [ "$(uname -s)" = "Darwin" ]; then
config_dir="$HOME/Library/Application Support"
else
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}"
fi

inputs_dest="$config_dir/backscroll/inputs"
mkdir -p "$inputs_dest"
for preset in claude.inputs.toml pi.inputs.toml omp.inputs.toml decisions.inputs.toml opencode.inputs.toml codex.inputs.toml; do
dest="$inputs_dest/$preset"
if [ -e "$dest" ] && [ "${BACKSCROLL_FORCE_INPUTS:-0}" != "1" ]; then
echo "backscroll: $dest exists, skipping"
continue
fi
cp "$REPO_ROOT/inputs/$preset" "$dest"
echo "backscroll: installed input preset → $dest"
done
}

# Skill replacement is an explicit, digest-approved operator action, never a
# side effect of pushing from a potentially disposable worktree.
# See docs/skill-installation.md and scripts/install-skills.py.
echo "backscroll: skills unchanged; explicit installation: docs/skill-installation.md"
install_input_presets
17 changes: 9 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,16 @@ jobs:
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- uses: actions/setup-python@v7
with:
python-version: '3.9'
- name: Test isolated skill installation and recipe retrieval
- name: Test native skill installation, presets, and recipe retrieval
run: |
mkdir -p .local-evidence
go build -o .local-evidence/backscroll ./cmd/backscroll
export BACKSCROLL_TEST_BINARY="$PWD/.local-evidence/backscroll"
python3 tests/test_skill_install.py -v
test_home=$(mktemp -d)
test_config=$(mktemp -d)
gomodcache=$(go env GOMODCACHE)
gocache=$(go env GOCACHE)
trap 'rm -rf "$test_home" "$test_config"' EXIT
HOME="$test_home" BACKSCROLL_CONFIG_DIR="$test_config" \
GOMODCACHE="$gomodcache" GOCACHE="$gocache" \
go test ./internal/skillinstall ./internal/presets ./cmd/backscroll
- name: "Install-script bash regression suite (issue #74)"
run: |
env -u XDG_CONFIG_HOME \
Expand Down
18 changes: 12 additions & 6 deletions AGENTS.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Hooks live in `.githooks/` and are activated with `git config core.hooksPath .gi
|------|-------------|
| `pre-commit` | `gofmt` check, `go vet`, gitleaks secret scan |
| `commit-msg` | Validates conventional commit format |
| `pre-push` | Validates docs, runs CI, syncs input presets |
| `pre-push` | Validates documentation and runs CI |

## Quality Gates

Expand Down
11 changes: 7 additions & 4 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ test:
config_dir="$(mktemp -d)" && trap 'rm -rf "$config_dir"' EXIT && \
BACKSCROLL_CONFIG_DIR="$config_dir" go test ./...

# Isolated skill installer filesystem + installed-recipe E2E (Python 3.9+)
# Native skill and preset unit tests plus hermetic CLI E2E tests.
test-skills:
mkdir -p .local-evidence
go build -o .local-evidence/backscroll ./cmd/backscroll
BACKSCROLL_TEST_BINARY="$PWD/.local-evidence/backscroll" python3 tests/test_skill_install.py -v
config_dir="$(mktemp -d)" && home="$(mktemp -d)" && \
gomodcache="$(go env GOMODCACHE)" && gocache="$(go env GOCACHE)" && \
trap 'rm -rf "$config_dir" "$home"' EXIT && \
HOME="$home" BACKSCROLL_CONFIG_DIR="$config_dir" \
GOMODCACHE="$gomodcache" GOCACHE="$gocache" \
go test ./internal/skillinstall ./internal/presets ./cmd/backscroll

# Build binary
build:
Expand Down
45 changes: 18 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,46 +38,36 @@ Backscroll ships as a **single self-contained binary**: pure Go, built with CGO
curl -fsSL https://raw.githubusercontent.com/pablontiv/backscroll/master/install.sh | bash
```

Detects your platform (Linux x86_64 / macOS aarch64), installs the binary to `~/.local/bin/`, and installs the shipped Claude, Pi, OMP, OpenCode, and Codex input presets without overwriting existing manifests. The Unix installer uses `BACKSCROLL_CONFIG_DIR` as the config base when set and `$HOME/.config` otherwise, placing presets under `backscroll/inputs/`.
Detects your platform (Linux x86_64 / macOS aarch64), installs the binary to `~/.local/bin/`, and runs `backscroll presets install`. The native command installs the shipped Claude, Pi, OMP, decisions, OpenCode, and Codex presets without overwriting existing manifests. The script passes `BACKSCROLL_CONFIG_DIR` through `--config-home` when the variable is set. Otherwise the native command uses `XDG_CONFIG_HOME` or `$HOME/.config`.

**Windows (PowerShell):**

```powershell
irm https://raw.githubusercontent.com/pablontiv/backscroll/master/install.ps1 | iex
```

Installs the binary to `%LOCALAPPDATA%\backscroll\bin\`, adds it to your PATH, and installs the shipped Claude, Pi, OMP, and Codex input presets without overwriting existing manifests. The Windows installer uses `BACKSCROLL_CONFIG_DIR` as the config base when set and `<user-home>\.config` otherwise, placing presets under `<config-base>\backscroll\inputs\`. Compatible with Windows PowerShell 5.1+.
Installs the binary to `%LOCALAPPDATA%\backscroll\bin\`, adds it to your PATH, and runs `backscroll presets install`. The native command installs the shipped Claude, Pi, OMP, decisions, OpenCode, and Codex presets without overwriting existing manifests. The Windows installer passes `BACKSCROLL_CONFIG_DIR` through `--config-home` when set and otherwise uses `<user-home>\.config`. Compatible with Windows PowerShell 5.1+.

### Install input presets

Backscroll ships Claude, Pi, OMP, OpenCode, and Codex input presets at `inputs/claude.inputs.toml`, `inputs/pi.inputs.toml`, `inputs/omp.inputs.toml`, `inputs/opencode.inputs.toml`, and `inputs/codex.inputs.toml`. Existing manifests are preserved by default; set `BACKSCROLL_FORCE_INPUTS=1` only when you intentionally want to replace edited presets. Pi subagent sessions are excluded by default and available through an explicit per-input opt-in; see the [Pi input contract](docs/input-contract.md#complete-pi-example). OMP has a dedicated native v3 reader and excludes nested child sessions by default; see the [OMP input contract](docs/input-contract.md#complete-omp-example). For Codex roots, supported records and limits, see the [Codex input contract](docs/input-contract.md#complete-codex-example).
Runtime input config directories:
Backscroll incorporates the Claude, Pi, OMP, decisions, OpenCode, and Codex input presets. Install missing presets with the native command:

| OS | Input manifest directory |
| --- | --- |
| Linux / macOS | `$HOME/.config/backscroll/inputs/` |
| Windows | `<user-home>\.config\backscroll\inputs\` |

Set `BACKSCROLL_CONFIG_DIR` to override the `<config_dir>` base. Both install scripts follow this runtime convention and otherwise use the platform defaults shown above.
```bash
backscroll presets install
```

If you install from a source checkout, copy presets without clobbering existing files:
The command writes to `<config-home>/backscroll/inputs/`. It preserves every existing file by default. Use `--config-home /absolute/path` to select a config home. Use `--overwrite` only when you intend to replace the six managed names.

```bash
config_dir="${BACKSCROLL_CONFIG_DIR:-$HOME/.config}"
mkdir -p "$config_dir/backscroll/inputs"
cp -n inputs/claude.inputs.toml inputs/pi.inputs.toml inputs/omp.inputs.toml inputs/opencode.inputs.toml inputs/codex.inputs.toml "$config_dir/backscroll/inputs/"
backscroll presets install --config-home "$HOME/.config" --overwrite
backscroll validate
backscroll config
```

The command uses `XDG_CONFIG_HOME` when it is set and absolute. Otherwise it uses `$HOME/.config`. `BACKSCROLL_CONFIG_DIR` remains the runtime application override. The Unix bootstrap translates that variable to `--config-home` for compatibility. Pi subagent sessions remain opt-in; see the [Pi input contract](docs/input-contract.md#complete-pi-example). OMP has a dedicated native v3 reader and excludes nested child sessions by default; see the [OMP input contract](docs/input-contract.md#complete-omp-example). For Codex records and limits, see the [Codex input contract](docs/input-contract.md#complete-codex-example).

```powershell
$configDir = if ($env:BACKSCROLL_CONFIG_DIR) { $env:BACKSCROLL_CONFIG_DIR } else { Join-Path $HOME ".config" }
$inputsDir = Join-Path $configDir "backscroll\inputs"
New-Item -ItemType Directory -Force $inputsDir | Out-Null
foreach ($name in "claude.inputs.toml", "pi.inputs.toml", "omp.inputs.toml", "opencode.inputs.toml", "codex.inputs.toml") {
$dest = Join-Path $inputsDir $name
if (-not (Test-Path $dest)) { Copy-Item (Join-Path "inputs" $name) $dest }
}
backscroll presets install --config-home (Join-Path $HOME ".config")
backscroll validate
backscroll config
```
Expand Down Expand Up @@ -252,7 +242,9 @@ On `search`, `--fields minimal|full` controls machine-output density, while `--m

Backscroll is a CLI. Nothing requires an agent, and there is no MCP server — a CLI call costs a fraction of the tokens an MCP tool schema does.

Agents use the same commands with `--robot --fields minimal --max-tokens N`. The discoverable `.claude/skills/backscroll/SKILL.md` is a minimal shim: it tells the runtime to execute `backscroll --skill` and follow stdout. The complete, authoritative instructions are embedded in the binary and printed by that flag without reading configuration or modifying files. The optional [skill installer](docs/skill-installation.md) links Claude, Agents (Codex), and OpenCode to the shim in an explicitly selected stable clone. Installation requires a reviewed inventory digest; existing destinations are backed up and can be restored. Git hooks and binary installers never replace skills implicitly.
Agents use the same commands with `--robot --fields minimal --max-tokens N`. The discoverable `.claude/skills/backscroll/SKILL.md` is a minimal shim. It tells the runtime to execute `backscroll --skill` and follow stdout. `cmd/backscroll/backscroll_skill.md` is the authoritative instruction source. `backscroll skill` and the compatibility alias print those exact embedded instructions without ordinary startup effects.

Run `backscroll skill --install` to install the embedded shim for Claude, Agents/Codex, and OpenCode. The three links point to one immutable managed copy. Unknown destination content requires a reviewed preimage digest. The installer backs up replaced content and returns a durable receipt for `backscroll skill --restore RECEIPT --approve SHA256`. See [skill installation and restoration](docs/skill-installation.md). Git hooks do not install skills or presets.

---

Expand Down Expand Up @@ -334,13 +326,12 @@ The versioned hooks in `.githooks/` are **not active until you point git at them
git config core.hooksPath .githooks
```

Without this, git uses `.git/hooks/` (samples only) and **every push silently skips**:
Without this, git uses `.git/hooks/` and every push skips:

- the `just ci` aggregate-coverage gate when Go files change,
- the AGENTS.md / docs-update validation, and
- input preset synchronization.
- the `just ci` aggregate-coverage gate when Go files change, and
- the AGENTS.md and documentation update validation.

Once activated, `pre-push` runs those gates and syncs input presets; `post-merge` syncs presets and documentation aggregates after a `git pull`/merge. Hooks never install the Backscroll executable or replace skill directories. Install a release with `install.sh`; released binaries then update themselves. Explicitly installed skill links follow the operator-selected stable clone; see [installation and restoration](docs/skill-installation.md).
Once activated, `pre-push` runs those gates. `post-merge` updates documentation aggregates after a merge. Hooks do not install the executable, skills, or presets. Install a release with `install.sh`. Use the native commands for skill and preset installation.

Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (`type(scope): description`).

Expand Down
5 changes: 1 addition & 4 deletions cmd/backscroll/backscroll_skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ If the binary is missing:

```bash
curl -fsSL https://raw.githubusercontent.com/pablontiv/backscroll/master/install.sh | bash
# Optional: copy shipped input presets after the binary is in PATH.
config_dir="${BACKSCROLL_CONFIG_DIR:-$HOME/.config}"
mkdir -p "$config_dir/backscroll/inputs"
cp -n inputs/claude.inputs.toml inputs/pi.inputs.toml inputs/omp.inputs.toml inputs/opencode.inputs.toml inputs/decisions.inputs.toml inputs/codex.inputs.toml "$config_dir/backscroll/inputs/"
# The bootstrap installs the embedded input presets without replacing existing manifests.
```

## 2) When to Invoke (automatic triggers)
Expand Down
21 changes: 0 additions & 21 deletions cmd/backscroll/codex_skill_test.go

This file was deleted.

18 changes: 18 additions & 0 deletions cmd/backscroll/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,25 @@ func runContext(ctx context.Context, stdout, stderr io.Writer, args []string) (r
_, err := io.WriteString(stdout, embeddedBackscrollSkill)
return err
}
if startupBypassInvocation(args) {
return executeRoot(ctx, buildRootCmd(stdout, stderr), args)
}

return runContextWithDependencies(ctx, stdout, stderr, args, newRunDependencies(stdout, stderr))
}

func startupBypassInvocation(args []string) bool {
return len(args) > 0 && (args[0] == "skill" || args[0] == "presets")
}

func executeRoot(ctx context.Context, root *cobra.Command, args []string) error {
if indexPolicyMachineArgs(args) || startupBypassInvocation(args) {
root.SilenceUsage = true
}
root.SetArgs(args)
return root.ExecuteContext(ctx)
}

func runContextWithDependencies(ctx context.Context, stdout, stderr io.Writer, args []string, deps runDependencies) error {
// Keep the informational fast path identical for production and injected runs.
printSkill, err := validateSkillInvocation(args)
Expand All @@ -148,6 +163,9 @@ func runContextWithDependencies(ctx context.Context, stdout, stderr io.Writer, a
if root == nil {
root = buildRootCmd(stdout, stderr)
}
if startupBypassInvocation(args) {
return executeRoot(ctx, root, args)
}
stagingWait := deps.stagingWait
if stagingWait <= 0 {
stagingWait = autoupdateStagingWait
Expand Down
9 changes: 6 additions & 3 deletions cmd/backscroll/prepush_docs_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,19 @@ func TestPrePushDocumentationGateUsesSubstantiveAGENTS(t *testing.T) {
}
}

func TestGitHooksDoNotInstallBinary(t *testing.T) {
func TestGitHooksDoNotInstallBinarySkillsOrPresets(t *testing.T) {
repoRoot := prePushRepositoryRoot(t)
for _, name := range []string{"pre-push", "post-merge"} {
hook, err := os.ReadFile(filepath.Join(repoRoot, ".githooks", name))
if err != nil {
t.Fatal(err)
}
for _, forbidden := range []string{"BACKSCROLL_BIN", "install_backscroll_binary", "go build"} {
for _, forbidden := range []string{
"BACKSCROLL_BIN", "install_backscroll_binary", "go build",
"install_input_presets", "inputs.toml", "presets install", "skills/backscroll",
} {
if strings.Contains(string(hook), forbidden) {
t.Errorf("%s installs a source-built binary via %q", name, forbidden)
t.Errorf("%s contains prohibited installation logic %q", name, forbidden)
}
}
}
Expand Down
Loading
Loading