Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8f27bbf
docs: regenerate crystallize-consolidate command page
elijahr May 6, 2026
0a1121b
pr-dance: harden no-merge stop against session-level autonomy directives
elijahr May 6, 2026
31b1a0c
docs: regenerate stale skill and command pages
elijahr May 6, 2026
695ca0b
test(installer): register posix_only/windows_only pytest marks
elijahr May 7, 2026
a9d6c6b
test(installer): polish posix_only/windows_only mark scaffolding
elijahr May 7, 2026
cd38cdd
feat(sandbox): pin cco at SHA 9744b9f for L5 Linux sandbox
elijahr May 7, 2026
95db662
test(sandbox): tighten cco SHA pin tests + improve error guidance
elijahr May 7, 2026
323e62d
docs(sandbox): correct WI attribution for L4 components
elijahr May 7, 2026
899148a
feat(installer): add install_aliases_windows() stub for Q-O deferral
elijahr May 7, 2026
ea427f7
fix(installer): polish Windows alias stub per review
elijahr May 7, 2026
24ba686
feat(installer): platform-aware Claude Code alias dispatch
elijahr May 7, 2026
894601c
fix(installer): harden Claude Code alias dispatch per review
elijahr May 7, 2026
6112ea9
docs(readme): document Windows alias + sandbox deferral (WI-7 Q-O)
elijahr May 7, 2026
7ab913e
fix(docs): tighten Windows TBD note per review
elijahr May 7, 2026
988c823
test: mark pre-existing test_aliases.py POSIX-only
elijahr May 7, 2026
448a89c
chore: add ruff to dev deps + minimal config
elijahr May 7, 2026
983fe62
style: ruff auto-fix + manual cleanup (pre-existing violations)
elijahr May 7, 2026
463b9b6
docs: regenerate stale skill and command pages
elijahr May 6, 2026
370d052
feat(installer): add install_agents component for symlink discovery
elijahr May 7, 2026
3b3cabe
refactor(installer): align install_agents action labels with ui.py vo…
elijahr May 7, 2026
079b540
feat(installer): wire install_agents into ClaudeCodeInstaller
elijahr May 7, 2026
c096ad3
refactor(installer): tighten agents cleanup heuristic and aggregation…
elijahr May 7, 2026
510ca65
feat(agents): add canonical implementer.md and schema validation test
elijahr May 7, 2026
793a4ca
refactor(agents): describe current bash gate, harden schema test
elijahr May 7, 2026
a9c57a1
feat(agents): add 8 narrowing-role subagents
elijahr May 7, 2026
512bb14
refactor(agents): align bash-gate framing with current gate scope
elijahr May 7, 2026
3d6b676
test(hooks): isolate Stop-hook tests from real cache and worker-LLM gate
elijahr May 7, 2026
7cffee5
test(agents): close green-mirage gaps in WI-5 schema and uninstall co…
elijahr May 7, 2026
4fa0f8c
docs(changelog): add Phase 5 narrowing-role subagents entry
elijahr May 7, 2026
154f948
fix(installer): broaden F1 test assertion + fix audit doc path reference
elijahr May 7, 2026
5757980
docs(claude): record PR review bot info
elijahr May 7, 2026
8d92a57
Merge branch 'security-architecture-phase-7' into security-architectu…
elijahr May 7, 2026
7daa885
test(installer): update test_aliases.py for spellbook-cco fork (Task 7)
elijahr May 8, 2026
d4c03d7
feat(installer): add spellbook_cco component for fork-pinned cco wrapper
elijahr May 8, 2026
131aba6
test(installer): add tests for spellbook_cco component
elijahr May 8, 2026
36a1022
feat(installer): wire spellbook_cco install/uninstall into Claude Cod…
elijahr May 8, 2026
6b99354
refactor(installer): tui detects spellbook-cco binary on PATH
elijahr May 8, 2026
4f9f914
refactor(installer): install.py offers spellbook-cco aliases
elijahr May 8, 2026
e1e744c
refactor(scripts): spellbook-sandbox gates on spellbook-cco fork pin
elijahr May 8, 2026
f7e54f4
docs(readme): describe spellbook-cco onboarding instead of vanilla cco
elijahr May 8, 2026
1fb8518
test(installer): assert spellbook-cco which() interaction in alias-di…
elijahr May 8, 2026
aa7172f
fix(tests): pin LF line endings for agent snapshot test
elijahr May 8, 2026
c58f8f4
docs(security): rename cco section to spellbook-cco and document roll…
elijahr May 8, 2026
6e00b2e
docs(changelog): add spellbook-cco fork integration entry
elijahr May 8, 2026
a4bd53f
test(installer): mark spellbook_cco install tests posix_only
elijahr May 8, 2026
3df88b3
docs(changelog): relocate Phase 5 entries to [Unreleased] and collaps…
elijahr May 8, 2026
c08859b
Merge remote-tracking branch 'origin/main' into security-architecture…
elijahr May 8, 2026
b9867f7
test(installer): tighten green-mirage assertions on canonical strings
elijahr May 8, 2026
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
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Pin line endings on files where byte-identity is assumed by tests.
# See tests/test_security/test_agent_frontmatter.py for the snapshot test.
agents/*.md text eol=lf
tests/test_security/agent_snapshots.json text eol=lf
72 changes: 72 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,78 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The version bump in the .version file is missing from this pull request. Every PR must include a version bump following semantic versioning. Given the scope of changes (new agents and installer components), a minor bump (0.X.0) is likely required.

References
  1. Every PR must include a version bump in the .version file following semantic versioning. Flag as high severity if missing. (link)


### Added

- **Narrowing-role subagents (security architecture Phase 5).** Nine
new agents in `agents/` define narrowed tool surfaces for common
development verbs: `implementer` for worktree edits, `web-researcher`
(requires Phase 8 devcontainer to be safe in production),
`git-committer` and `git-pusher` for split local/remote git, separate
`pr-creator`/`pr-merger` for `gh pr` operations, `jira-reader`/
`jira-mutator` for Atlassian MCP read vs. write, and `test-runner`
for scoped test invocations. Each agent's `tools:` frontmatter is a
*narrowing* list — `(parent_tools ∩ frontmatter_tools)` — and never
grants capabilities the parent does not already hold. Agents are
discovered via `$CLAUDE_CONFIG_DIR/agents/`; spellbook's installer
now creates per-config-dir symlinks back to `$SPELLBOOK_DIR/agents/`,
with idempotent install/uninstall that preserves user-authored agent
files. Schema-validation tests guard the canonical 5-section body
contract (`Purpose` / `Tools` / `Output Schema` / `Guardrails` /
`Constraints`) and SHA-256-snapshot the existing 7 agents to catch
unintended modification.
- **`installer/components/spellbook_cco.py` component (security
architecture Phase 5).** New installer component clones the
`elijahr/cco` fork at audit-pinned SHA `d7044ef` to
`~/.local/spellbook/cco/` and writes the
`~/.local/bin/spellbook-cco` wrapper. Pin verification is enforced
by default; `SPELLBOOK_INSTALLER_SKIP_FORK_PIN=1` bypasses it with
a stderr `WARNING` line so accidental drift is loud.
- **macOS L5 sandbox layer ships via the fork's hardened SBPL
profile.** The `spellbook-cco` wrapper applies the fork's profile,
which adds DYLD environment scrub, file-read denies for
user-writable dylib paths (preventing `DYLD_INSERT_LIBRARIES`-style
injection), scoped `process-exec` deny+re-allow, and
`mach-priv-task-port` deny. macOS now reaches feature parity with
the Linux sandbox path rather than no-opping.

### Changed

- **`installer/platforms/claude_code.py` macOS path.** No longer
no-ops; chains `install_spellbook_cco` then `install_aliases` so
macOS users get the same hardened sandbox surface as Linux users.
- **`installer/tui.py` and `install.py` detection.** The TUI / CLI
installer now probes for `spellbook-cco` on `PATH` instead of
vanilla `cco`, so re-runs of the installer correctly identify
prior spellbook-managed installs.
- **`scripts/spellbook-sandbox` gating.** The sandbox launcher gates
on `spellbook-cco` (not vanilla `cco`) and verifies the audit-pinned
`d7044ef` SHA by parsing `spellbook-cco --version` output. A version
mismatch fails closed with a remediation hint.
- **`docs/security.md` retitled.** Section is now "Sandboxing with
spellbook-cco (Linux + macOS)". Manual install instructions removed
in favor of `install.py`, which is now the single source of truth
for sandbox setup on both platforms.
- **`README.md` cco onboarding rewritten.** Instructions reference
`spellbook-cco` rather than vanilla `cco`, and point at `install.py`
instead of the upstream `nikvdp/cco` repo for installation.
- **`SPELLBOOK_SANDBOX_SKIP_PIN=1` replaces `SPELLBOOK_SANDBOX_SKIP_CCO_PIN=1`.**
The escape hatch for skipping audit-pin verification has been
renamed for consistency with other `SPELLBOOK_*_SKIP_PIN` variables.
See **Deprecated** for the legacy name's removal timeline.
- **`SPELLBOOK_USE_VANILLA_CCO=1` rollback escape hatch.** If the
hardened fork breaks something on your system, set this env var to
fall back to the upstream vanilla `nikvdp/cco` binary. **This
bypasses the hardened SBPL profile and DYLD scrub**, so only use it
long enough to recover, then unset the env var and re-run
`install.py` so the spellbook-managed sandbox is restored.

### Deprecated

- **`SPELLBOOK_SANDBOX_SKIP_CCO_PIN=1` legacy name.** Superseded by
`SPELLBOOK_SANDBOX_SKIP_PIN=1`. The old name is still accepted for
one release with a stderr `DEPRECATION` warning, then removed.
## [0.63.2] - 2026-05-08

### Fixed
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Spellbook Development

Read and follow the instructions in [AGENTS.md](./AGENTS.md).

### PR Review Bot
- Bot username: gemini-code-assist[bot]
- Re-review comment: /gemini review
- Auto-reviews on PR creation: yes
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,19 +108,38 @@ irm https://raw.githubusercontent.com/axiomantic/spellbook/main/bootstrap.ps1 |

## Sandboxed Usage

Spellbook recommends running AI coding assistants inside [nikvdp/cco](https://github.com/nikvdp/cco)'s sandbox. The `spellbook-sandbox` launcher wraps `cco` with the right read-only allowances so spellbook's skills, hooks, and daemon auth work inside the sandbox while `$HOME` stays hidden.
Spellbook recommends running AI coding assistants inside a `cco` sandbox. `install.py` writes `~/.local/bin/spellbook-cco` automatically — a wrapper around the audit-pinned [elijahr/cco](https://github.com/elijahr/cco) hardened fork (SHA `d7044ef`). The `spellbook-sandbox` launcher invokes `spellbook-cco` with the right read-only allowances so spellbook's skills, hooks, and daemon auth work inside the sandbox while `$HOME` stays hidden. You do not need to install upstream `cco` yourself.

```bash
# Install cco first: https://github.com/nikvdp/cco#quick-start
The `spellbook-cco` wrapper is the only spellbook-supported entry point. Vanilla `cco` exists solely for the post-deploy rollback path described below.

# Launch sandboxed
```bash
# install.py already wrote ~/.local/bin/spellbook-cco; just launch:
spellbook-sandbox # Claude Code (default)
spellbook-sandbox opencode # OpenCode CLI
spellbook-sandbox opencode serve # OpenCode server (for desktop/web app)
spellbook-sandbox codex # Codex
```

The spellbook installer can set up `claude` and `opencode` shell aliases that point to `spellbook-sandbox` automatically. See [docs/security.md](docs/security.md#sandboxing-with-cco-macos) for the full threat model, `--safe` mode details, and OpenCode desktop app integration.
The spellbook installer can set up `claude` and `opencode` shell aliases that point to `spellbook-sandbox` automatically. See [docs/security.md](docs/security.md#sandboxing-with-spellbook-cco-linux--macos) for the full threat model, `--safe` mode details, and OpenCode desktop app integration.

### Rolling back to vanilla cco

If the hardened fork misbehaves on your machine, set `SPELLBOOK_USE_VANILLA_CCO=1` in your shell rc and re-run `install.py`. The installer skips the `spellbook-cco` wrapper, the alias installer gates on `which cco` instead, and `spellbook-sandbox` routes through vanilla [nikvdp/cco](https://github.com/nikvdp/cco) at its legacy pin. Unset the variable and re-run `install.py` to return to the supported path.

### Windows: alias install + sandbox path TBD

Windows native sandboxing and alias installation are deferred to a later work item (open question Q-O). They are not in scope for the current security architecture phase.

Current behavior on Windows:

- The installer's `install_aliases_windows()` is an intentional noop. It returns `skipped_reason="Windows alias install is deferred to a later work item (Q-O)"` and does not modify any PowerShell `$PROFILE`.
- `spellbook-cco` is not written on Windows native; spellbook does not currently sandbox Claude Code (or any other harness) on Windows.
- `cmd.exe` is a known limitation: `doskey` macros do not persist across cmd sessions without an `AutoRun` registry edit, so cmd users are explicitly not served by the alias installer.

Windows users have two options until the Windows path lands:

- Invoke `spellbook-sandbox` directly, only meaningful if `spellbook-cco` (or vanilla `cco` under `SPELLBOOK_USE_VANILLA_CCO=1`) is already on `PATH`.
- Use **WSL2 + the Linux install path** for full sandboxing. This is the recommended option. Run `install.py` inside the WSL Linux distro and the installer will write `spellbook-cco` there automatically.

## What Spellbook Does

Expand Down
93 changes: 93 additions & 0 deletions agents/git-committer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
name: git-committer
description: Use for local git operations only — read, status, diff, log, add, commit, branch, fetch, and worktree. Does NOT push. Bash invocations pass through the spellbook PreToolUse bash gate, which blocks dangerous patterns and surfaces denials to the operator.
tools: Bash, Read
model: inherit
---

## Purpose

Carry out local git work the parent dispatches: stage files, write
commits, inspect history, manage branches and worktrees, and fetch from
remotes. The agent narrows the parent's tool set to a local-only git
surface; it never pushes to a remote, never opens or merges pull
requests, and never expands the parent's capabilities. Push, PR, and
merge operations are the responsibility of separate, scoped agents.

## Tools

`Bash` is the primary tool for git operations: `git status`, `git diff`,
`git log`, `git show`, `git add`, `git commit`, `git branch`,
`git checkout` (for branch switching, never `--`), `git fetch`,
`git worktree`. Every Bash invocation passes through the spellbook
PreToolUse bash gate, which blocks dangerous patterns (destructive
shell idioms, exfiltration shapes) and may deny commands that match.
`Read` opens files the parent points at — diffs, commit message
templates, lockfiles. Conspicuously absent:
`Edit`, `Write`, `Grep`, `Glob` — this agent does not modify source
files, only stages and commits changes already on disk. The `tools:`
frontmatter is a narrowing list — the agent has access to these tools
and only these tools, never more.

## Output Schema

```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "GitCommitterResult",
"type": "object",
"required": ["commit_sha", "branch", "files_committed", "notes"],
"properties": {
"commit_sha": {
"type": ["string", "null"],
"description": "SHA of the commit produced by this run, or null if no commit was made."
},
"branch": {
"type": "string",
"description": "Branch the commit landed on (or current branch if no commit was made)."
},
"files_committed": {
"type": "array",
"items": {"type": "string"},
"description": "Absolute paths of files included in the commit (empty if no commit was made)."
},
"notes": {
"type": "string",
"description": "Free-text notes: deviations, follow-up work, hook denials, or unresolved questions."
}
}
}
```

## Guardrails

- MUST verify the working directory and current branch before any git
invocation; reject the dispatch if either does not match what the
parent specified.
- MUST NOT run `git push`, `git reset --hard`, `git checkout --`,
`git stash drop`, `git rebase`, or any other destructive or
remote-mutating git operation. Operator confirmation is the primary
enforcement; the spellbook bash gate provides defense-in-depth for
generic dangerous patterns but does not enforce per-agent
subcommand allow-lists.
- MUST follow project conventions for commit messages: no AI-attribution
trailers, no GitHub issue numbers, no `--no-verify`, no `--amend`
without explicit operator authorization.
- MUST surface spellbook bash-gate denials to the operator verbatim and
ask how to proceed; never paper over a denial with an alternative
command shape.
- MUST stage only the files the parent named or that fall within the
parent-specified scope; never run `git add -A` or `git add .` to
blanket-stage the working tree.

## Constraints

- `tools:` is a narrowing surface over the parent's toolset — the agent
has Bash and Read, and only those, and cannot escalate.
- Operates in a worktree or the current working directory; does NOT
create new branches or worktrees unless explicitly dispatched to do so.
- Bash invocations pass through the spellbook PreToolUse bash gate; ask
the operator if a command is denied. The agent cannot escalate past a
denial.
- Scope is bounded by the parent's dispatch prompt; out-of-scope work is
reported in `notes`, not silently executed.
94 changes: 94 additions & 0 deletions agents/git-pusher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
name: git-pusher
description: Use for `git push` operations only. Operator confirmation is REQUIRED for every push. Bash invocations pass through the spellbook PreToolUse bash gate, which blocks dangerous patterns and surfaces denials to the operator.
tools: Bash, Read
model: inherit
---

## Purpose

Push committed changes from the local working tree to a remote. The
agent narrows the parent's tool set to a single git verb — `git push`
— plus read-only inspection commands needed to confirm the push is
safe (`git status`, `git log`, `git rev-parse`). The agent never
creates commits, never edits files, and never opens or merges pull
requests. Every push requires explicit operator confirmation.

## Tools

`Bash` is used for `git push` and the read-only git commands that
verify push safety (`git status`, `git log`, `git rev-parse`,
`git remote`, `git diff`). Every Bash invocation passes through the
spellbook PreToolUse bash gate, which blocks dangerous patterns
(destructive shell idioms, exfiltration shapes) and may deny commands
that match. `Read` opens files the parent points at — push
manifests, branch context. Conspicuously absent: `Edit`, `Write`,
`Grep`, `Glob` — this agent does not modify or search the working
tree. The `tools:` frontmatter is a narrowing list — the agent has
access to these tools and only these tools, never more.

## Output Schema

```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "GitPusherResult",
"type": "object",
"required": ["pushed", "branch", "remote_refspec", "commit_range", "notes"],
"properties": {
"pushed": {
"type": "boolean",
"description": "True if a push completed successfully; false if it was declined, denied, or aborted."
},
"branch": {
"type": "string",
"description": "Local branch name that was the source of the push."
},
"remote_refspec": {
"type": "string",
"description": "Refspec pushed to in `<remote>/<ref>` form, where `<ref>` may itself contain slashes (e.g. 'origin/feature-x', 'origin/release/v2', 'upstream/users/alice/topic')."
},
"commit_range": {
"type": ["string", "null"],
"description": "Range of commits pushed in `<old-sha>..<new-sha>` form, or null if no push happened."
},
"notes": {
"type": "string",
"description": "Free-text notes: operator decisions, hook denials, abort reasons, or unresolved questions."
}
}
}
```

## Guardrails

- MUST require explicit operator confirmation for every push; the
agent prints the exact `git push` command it intends to run and
the commit range that will be transmitted, then waits for an
affirmative operator response before invoking it.
- MUST NOT run `git push --force` or `git push --force-with-lease`
without explicit operator authorization that names the target
branch. Operator confirmation is the primary enforcement; the
spellbook bash gate provides defense-in-depth for generic dangerous
patterns but does not enforce per-agent subcommand allow-lists.
- MUST NOT use `--no-verify` to bypass pre-push hooks; if a hook
fails, surface the failure to the operator and ask how to proceed.
- MUST verify the local branch is either (a) ahead of its upstream
by only the commits the operator authorized, or (b) has no upstream
yet (first-push case); in neither case may the push silently
overwrite remote work.
- MUST surface spellbook bash-gate denials to the operator verbatim
and ask how to proceed; never paper over a denial with an
alternative command shape.

## Constraints

- `tools:` is a narrowing surface over the parent's toolset — the
agent has Bash and Read, and only those, and cannot escalate.
- Operates in a worktree or the current working directory; does NOT
switch branches, create commits, or modify the working tree.
- Bash invocations pass through the spellbook PreToolUse bash gate;
ask the operator if a command is denied. The agent cannot escalate
past a denial.
- Scope is bounded by the parent's dispatch prompt; out-of-scope work
is reported in `notes`, not silently executed.
Loading
Loading