Installing Otto has two phases:
- Part A · Global (once per machine) — the source toolchains/plugins, Otto's own skills, and the session hook. Lives under
~/.claude/(%USERPROFILE%\.claude\on Windows). - Part B · Per-repo (in each project Otto drives) — spec-kit scaffolding, Pocock's repo config, and Otto's memory bootstrap. Lives under the repo's
.claude/,.specify/, and root.
Paths below use
~for your home dir. On Windows that's%USERPROFILE%(PowerShell$HOME). Substitute<OTTO_HOME>(and every<LOCAL-PATH>below) with your own clone path.
| Piece | Scope | Where | How |
|---|---|---|---|
specify CLI (spec-kit) |
global | machine PATH | uv tool install … |
| agent-skills plugin | global | ~/.claude plugins |
/plugin install … (bundles a SessionStart hook) |
| Pocock skills | global | ~/.claude/skills/ |
npx skills@latest add … |
Otto skills (/flow, recipes, conductor, sync, distill) |
global | ~/.claude/skills/ |
register-skills script |
| Otto session hook | global | ~/.claude/settings.json |
edit + otto-session-start script |
spec-kit project files (.specify/, /speckit-* skills, CLAUDE.md block) |
per-repo | repo .claude/ + .specify/ |
specify init . --integration claude --script ps |
Pocock repo config (docs/agents/*, CLAUDE.md block) |
per-repo | repo root + docs/agents/ |
/setup-matt-pocock-skills |
| Otto memory (constitution, CONTEXT, stack.yaml, learnings.md, ADRs) | per-repo | repo root + .specify/ |
copy memory/templates/ (never overwrite) |
| Git disposition — team or private mode | per-repo | .gitignore or .git/info/exclude |
choose in B6 |
Footprint manifest (.otto-manifest.md) |
per-repo | repo root | copy + fill template (B8) |
| Workspace memory (multi-repo efforts) | per-workspace | coordination dir | see scope.md |
- Claude Code — the VS Code / JetBrains extension or the standalone
claudeCLI. (Theclaudecommand is only on PATH if you installed the CLI; the extension doesn't add it, and that's fine — Otto works in either.) - git.
- Python 3.11+ and uv (or pipx) — for spec-kit's CLI.
- Node.js / npx — for Pocock's installer.
- jq — required by agent-skills' SessionStart hook (and used by Otto's hook on macOS/Linux). On Windows the Otto hook uses PowerShell instead, so
jqis only needed for agent-skills. - Install only the sources you'll use — Otto degrades gracefully and reports what's missing.
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git@vX.Y.Z
specify version # verify- Pin a release tag (see ../sources/sources.md for the ref Otto is validated against). pipx works too:
pipx install …. - PATH (Windows):
uv tool installdropsspecify.exein%USERPROFILE%\.local\bin, which may not be on PATH. Runuv tool update-shell(or add that dir to PATH), then fully quit + reopen VS Code — a window reload won't refresh PATH for integrated terminals. Stopgap: invoke by full path,%USERPROFILE%\.local\bin\specify.exe. - This CLI is spec-kit's only global piece; everything else it does is per-repo (Part B1).
Inside Claude Code:
/plugin marketplace add addyosmani/agent-skills
/plugin install agent-skills@addy-agent-skills
- Bundles 23 skills, 7 commands (
/spec /plan /build /test /review /code-simplify /ship— may appear namespaced as/agent-skills:<cmd>), 3 review personas, and a SessionStart hook that auto-loads its meta-skill. The hook needsjqon PATH; without it the skills still work but the auto-loader degrades. - Local-dev alternative:
git clone … && claude --plugin-dir <path>. - Fully global — no per-repo step.
npx skills@latest add mattpocock/skills- Interactive picker — select the skills you want and
setup-matt-pocock-skills(you'll need it per-repo in B2). Installs into your user skills. - Global; its engineering skills expect a per-repo config (Part B2).
Clone Otto and point a stable env var at it, then run the register script (it copies Otto's 14 invokable units into ~/.claude/skills/<name>/SKILL.md, appending a note so each resolves its relative links back to the source):
PowerShell (Windows):
$env:OTTO_HOME = "<LOCAL-PATH>"
[Environment]::SetEnvironmentVariable("OTTO_HOME", $env:OTTO_HOME, "User") # persist
& "$env:OTTO_HOME\install\register-skills.ps1"bash (macOS/Linux/Git-Bash):
export OTTO_HOME="$HOME/otto-dev" # add to ~/.bashrc / ~/.zshrc to persist
bash "$OTTO_HOME/install/register-skills.sh"This registers: /flow, /otto-conductor, /otto-sync, /otto-distill, and the 11 recipes (/otto-greenfield, /otto-feature, /otto-bugfix, /otto-hardening, /otto-refactor, /otto-release, /otto-onboarding, /otto-migration, /otto-assess, /otto-extract, /otto-inception). Re-run it after ANY otto-dev change (pull or local edit) — registered skills are copies, and a stale copy means the host runs old protocol. bash install/verify-otto.sh check 7 detects this drift mechanically (added 2026-07-15, finding R2).
Minimal alternative: register only
/flowand let it route you to the rest on demand (everything is reachable through routing once the hook in A3 is active). Edit the script's unit list, or copy justrouter/flow.md→~/.claude/skills/flow/SKILL.md.
The hook is Otto's agentic face: on every session it injects the routing brain (a pointer to the policy + registry + recipes) and the global learnings/ slice as hints-to-verify.
- The hook scripts ship in
<OTTO_HOME>/install/—otto-session-start.sh(bash) andotto-session-start.ps1(PowerShell). They read$OTTO_HOMEand no-op if Otto isn't present. - Add the hook to
~/.claude/settings.json(create the file if absent). Use the script for your shell:
macOS / Linux:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{ "type": "command", "command": "bash ~/otto-dev/install/otto-session-start.sh" }
]
}
]
}
}Windows (PowerShell):
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{ "type": "command", "command": "powershell -NoProfile -ExecutionPolicy Bypass -File <LOCAL-PATH>\\install\\otto-session-start.ps1" }
]
}
]
}
}- Use the absolute path to your
OTTO_HOMEso updates are picked up automatically. - Pick the interpreter you actually have: Windows ships
powershell(5.1, used above);pwsh(PowerShell 7) is not installed by default — check withwhere pwsh. Using a missing interpreter makes the hook fail silently. - If you already run the agent-skills plugin (which adds its own SessionStart hook), that's fine — Claude Code merges hooks from all scopes; both run.
- Extension caveat (important): in the VS Code / JetBrains extension,
SessionStartadditionalContextmay not surface into the chat — the hook is reliable mainly for theclaudeCLI. If a fresh session shows no Otto context, that's expected in the extension; use A4'sCLAUDE.mdpointer instead (hook-free, always loaded). The hook also no-ops unlessOTTO_HOMEis set in the environment the editor launched with.
The auto-append rule's known failure mode is forgetting to capture. This hook backstops it: once
per substantial session (transcript > ~100 KB, at most one nudge per session via a temp marker) it
blocks the stop and reminds the agent to append any durable lesson to learnings before finishing.
If nothing qualifies, the agent just finishes — you see at most one extra beat per session.
Scripts: otto-learnings-nudge.ps1 (Windows) / otto-learnings-nudge.sh (macOS/Linux — needs jq,
no-ops without it). Add to ~/.claude/settings.json alongside the A3 hook:
Windows (PowerShell):
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "powershell -NoProfile -ExecutionPolicy Bypass -File <LOCAL-PATH>\\install\\otto-learnings-nudge.ps1" }
]
}
]
}
}macOS / Linux:
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "bash ~/otto-dev/install/otto-learnings-nudge.sh" }
]
}
]
}
}- Merge the
"Stop"key into the same"hooks"object as A3's"SessionStart"— don't create a secondhooksblock. - The hook never breaks a session: on any parse error, missing field, or missing
jqit exits 0 silently. - VS Code extension: hooks require a FULL restart. The extension process loads hooks at VS Code startup — a hook added while VS Code is running fires in no session (even new chats) until you fully quit + reopen (a window reload is not enough; the CLI picks changes up per-invocation). Verified 2026-07-15: installed hook, 551 KB session, zero fires; script proven sound by direct invocation. Same class as the A1.1 PATH caveat.
~/.claude/CLAUDE.md is auto-loaded into every session (extension and CLI) with no hook and no OTTO_HOME dependency — so this is the reliable baseline, especially for the VS Code / JetBrains extension where the SessionStart hook (A3) may not surface. Append this block (it only adds a section; your existing preferences stay untouched):
## Otto (dev framework)
- Otto is a recipe-driven dev-workflow system at <LOCAL-PATH>.
For any non-trivial dev task, route through it: invoke /flow (or the right
/otto-<recipe>), following <LOCAL-PATH>\router\routing-policy.md.
- Capability map: <LOCAL-PATH>\registry\capabilities.yaml ;
recipes: <LOCAL-PATH>\workflows\recipes\.
- At the start of Otto work, read <LOCAL-PATH>\learnings\global.md
and treat its entries as hints-to-verify (re-check named files/flags/versions).
- Gates are evidence-based; never auto-commit (propose the git command, I run it).Substitute your OTTO_HOME path if different. Per-repo rules go in the repo's own CLAUDE.md (Part B3).
- Start a new session (a new chat in the VS Code / JetBrains extension, or
claudeif you have the CLI). The hook injects context silently into the agent — it is NOT printed to you. Verify by asking the agent: "what does your context say about Otto?" — it should describe Otto + the global learnings. (To eyeball the hook output directly, run the script manually:powershell -File <OTTO_HOME>\install\otto-session-start.ps1.) - Type
/flow→ it should be recognized. /plugin→ agent-skills listed/enabled.specify version→ prints.~/.claude/skills/→ contains the Otto + Pocock skills.
Run these inside a target repository.
The whole of Part B (B1–B3, B5–B8) in one pass, with a per-repo verify and a printed card of the remaining human steps. From the target repo root:
PowerShell (Windows):
& "$env:OTTO_HOME\install\otto-init.ps1" # defaults: -Mode team -Tracker auto
& "$env:OTTO_HOME\install\otto-init.ps1" -Mode private -Bug -GitInit # example: private repo, bug ext, init gitbash (macOS/Linux/Git-Bash):
bash "$OTTO_HOME/install/otto-init.sh" # defaults: --mode team --tracker auto
bash "$OTTO_HOME/install/otto-init.sh" --mode private --bug --git-initFlags: -Mode team|private (B6) · -Tracker auto|github|gitlab|local (B2 default; auto
reads the git remote) · -Bug (B5) · -SkipSpecKit · -SkipGuardrails · -GitInit ·
-Force (re-init spec-kit; append a fresh manifest run section — managed blocks are static;
delete a sentinel block to re-stamp it).
What it does not do (by design — these are yours): author the constitution
(/speckit-constitution), fill stack.yaml, custom PK decisions (run
/setup-matt-pocock-skills to override the stamped defaults), Part A globals (preflight
reports gaps + install commands), and any git commit. Never overwrites authored files;
re-runs are idempotent. B1–B8 below remain the reference for what each phase writes and why.
specify init . --integration claude --script ps # current repo, Windows (use --script sh on macOS/Linux)
# specify init . --integration claude --script ps --ignore-agent-tools # if the `claude` binary isn't on PATH
# specify init . --force --integration claude --script ps # merge into a non-empty repo--integration claudeis required — the non-interactive default is copilot, not Claude.--script ps|shis required in any non-TTY shell (agent, CI, redirected stdin). Without it, init opens an invisible arrow-key script-type picker and blocks forever at zero CPU right after the setup panel — on Windows,isatty()is true even for NUL/pipes, so spec-kit's_stdin_is_interactive()can't tell it isn't interactive (spec-kitcommands/init.py:375, verified v0.9.5, 2026-07-15). Harmless in a real terminal; always pass it.- VS Code / JetBrains extension users: add
--ignore-agent-tools. spec-kit's "Agent Detection" check looks for the standaloneclaudebinary on PATH, which the extension doesn't install — without the flag,initaborts with "claude not found." The flag skips only that check; scaffolding is identical. - Scaffolds into the repo:
.claude/skills/speckit-*/SKILL.md(9 skills), a managed block inCLAUDE.md(between<!-- SPECKIT … -->markers — your own content is preserved), and.specify/{scripts,templates,memory,workflows,extensions,integrations}(incl..specify/memory/constitution.mdandinit-options.json). spec-kit auto-installs its git and agent-context extensions here (git is opt-out via the deprecated--no-git). - Command naming: in Claude Code, spec-kit commands install as skills invoked with a hyphen —
/speckit-specify,/speckit-plan,/speckit-tasks,/speckit-implement, etc. Otto's docs use the canonical dotted form (/speckit.specify); they refer to the same command — the hyphen is just how Claude renders the skill.
Inside Claude Code, in the repo:
/setup-matt-pocock-skills
- Prompt-driven; writes
docs/agents/{issue-tracker,triage-labels,domain}.mdand an## Agent skillsblock into the repo'sCLAUDE.md(orAGENTS.md). Required before the engineering skills (/triage,/to-issues,/diagnose, …) behave correctly. - Build/extend the shared-language doc with
/grill-with-docs→CONTEXT.md.
Seed any missing memory member from ../memory/templates/ — never overwrite an existing one (validate/extend instead). See ../memory/memory-stack.md for what each does.
constitution.md→ best generated by/speckit-constitution(B1 already seeds.specify/memory/constitution.md); the template is a fallback.CONTEXT.md→ from/grill-with-docs(B2), or the template.CLAUDE.md→ the repo rule file (spec-kit + Pocock both write managed blocks into it; add your own conventions around them).stack.yaml→ fill this in early — it pins runtimes/frameworks/packages + authoritative docs, and drivessource-ground. Copy frommemory/templates/stack.yaml.learnings.md→ optional to pre-create; Otto auto-appends to it on first lesson. (Tracked in git; the session hook reads it.)docs/adr/→ create on first decision (template:memory/templates/ADR.md).
For a coordinated effort across several repos, also create the workspace-level constitution + CONTEXT + stack.yaml in your chosen coordination location, and a workspace spec with the inter-repo contracts. See ../workflows/scope.md.
Enable per repo with specify extension add <id>:
bug(opt-in) → the/speckit-bug-assess → /speckit-bug-fix → /speckit-bug-testtriage workflow, artifacts under.specify/bugs/<slug>/. Otto surfaces it as an alternate path in thebugfixrecipe.git(auto-installed on init) →/speckit-git-validateand/speckit-git-remoteare read-only helpers;initialize/feature/commitmutate git and stay human-gated. Keep itsauto_commithook disabled (the default) so Otto never auto-commits.agent-context(auto-installed) → auto-refreshes the<!-- SPECKIT -->block inCLAUDE.mdafter specify/plan. Background plumbing — no action needed.
specify preset add lean swaps spec-kit's core templates for minimal versions (orthogonal to Otto; affects the spec-kit artifacts, not Otto's routing).
Decide who owns Otto's footprint in this repo's history before the first commit, and record the
choice in .otto-manifest.md (B8) — its disposition columns are the source of truth for the
ignore/exclude lines below.
The rule is ignore regenerable/personal tooling, keep governance + artifacts tracked. Add these
to the repo's .gitignore (Otto's per-repo setup appends them; create the file if absent):
# spec-kit / Claude Code scaffolding (Otto)
.claude/settings.local.json # personal/machine-local; never commit
.specify/scripts/ # vendored; regenerated by specify init
.specify/templates/ # vendored; regenerated by specify initKeep tracked (do NOT ignore): .specify/memory/constitution.md (Otto's governance member), specs/ (spec/plan/tasks artifacts), .claude/skills/, .claude/settings.json, docs/agents/, CLAUDE.md, learnings.md, .otto-manifest.md. Blanket-ignoring .claude/ or .specify/ would drop the constitution + specs from version control — contradicting the memory stack.
For repos where Otto is your tooling, not the project's (client codebases, OSS you contribute
to). Goal: the files work locally exactly as in team mode, git never sees them, and no committed
line reveals Otto exists — which rules out .gitignore, since an Otto entry there is itself
committed and visible to every clone.
The mechanics rest on one git property: ignore rules never apply to tracked files. Exclusions are therefore only "live" where Otto's files were never committed (private repos) and inert in your own repos where they already are tracked — so you can be aggressive without risk:
- Per-clone: append the block below to
.git/info/exclude— identical syntax to.gitignore, but it lives inside.git/, which is never committed or pushed. Invisible to every other clone. - Per-machine (optional): put the same block in a global excludes file
(
git config --global core.excludesFile ~/.config/git/ignore) — set once, covers every repo you ever clone, no per-repo step at all.
# Otto footprint (private mode) — mirror of .otto-manifest.md
.claude/
.specify/
specs/
docs/agents/
CONTEXT.md
stack.yaml
learnings.md
.otto-manifest.mdRules of private mode:
- Never propose commits of Otto files. Git is always human-run (no-auto-git rule); in private
mode Otto additionally stops proposing
git add/commit for any manifest row — the memory members exist on disk only. - The
CLAUDE.mdleak. B1 and B2 write managed blocks into the repo'sCLAUDE.md. If that file is already tracked, no exclude rule can hide your edits (ignore rules don't apply to tracked files) — skip the block-writing steps and carry the rules in~/.claude/CLAUDE.md(global, invisible to the repo) or an excludedCLAUDE.local.mdinstead. IfCLAUDE.mdis untracked, just exclude the whole file. - No safety net. Excluded files have no history and no remote copy — and
git clean -dxdeletes ignored files; never run it in a private-mode repo. Anything durable across projects belongs inotto-dev/learnings/global.md, which is versioned and pushed. - Exclude specific paths, not parents. The project may have its own
docs/or.claude/content; if so, narrow the patterns (e.g.docs/agents/,.claude/skills/speckit-*/) to what the manifest lists rather than excluding a shared parent dir.
Otto's "never auto-commit" rule is a policy; the PK git-safety capability makes it mechanical —
install it early (before any BUILD/SHIP work) so a stray agent git call is blocked, not just discouraged:
- In Claude Code, in the repo, invoke
/git-guardrails-claude-code(Pocock skill — add it vianpx skills@latest add mattpocock/skillsif it isn't already installed). - It writes a PreToolUse hook to
.claude/hooks/block-dangerous-git.shthat blocks dangerous / state-changing git. Keep.claude/hooks/tracked — it's a shared safety net, not personal config. - Backstops spec-kit's git extension (B5): keep that extension's
auto_commitdisabled; this hook catches anything that slips through. Pairs with the advisorygit-practicecapability (Otto proposes; you run).
Finish per-repo setup by writing .otto-manifest.md at the repo root — one row per path the
setup created or modified, plus the chosen disposition mode and install date. Copy
../memory/templates/otto-manifest.md (pre-filled with the
standard B1–B7 rows) and adjust it to what was actually installed — delete rows for skipped
steps, add rows for extras (e.g. the bug extension's .specify/bugs/). Update it whenever a
later step adds an artifact.
The manifest is what makes the footprint legible — every operation on it becomes mechanical:
- Excluding — the B6 ignore/exclude lines are generated from its disposition columns.
- Uninstalling — walk it bottom-up (see Uninstall below).
- Auditing — "what did Otto touch here?" is one file read; check it before pushing from a repo you don't own.
- Drift —
/otto-syncdiffs its rows against disk to spot scaffolding a source release moved.
Team mode: commit it — it documents the scaffolding for the team. Private mode: it's excluded and lists itself.
/flow "add a small endpoint"→ returns a routing card pointing atfeature/SPEC./speckit-specify(hyphen) is available;.specify/and theCLAUDE.mdmanaged block exist.docs/agents/*.mdexist (from B2);stack.yamlis filled./git-guardrails-claude-codehas been run (B7);.claude/hooks/block-dangerous-git.shexists..otto-manifest.mdexists, states the disposition mode (B6), and matches what's on disk; in private mode,git statusshows none of its rows.
- Scope precedence: personal (
~/.claude/) > project (.claude/) > plugin (namespaced). So a project skill namedflowwould shadow the global one — Otto's names are prefixedotto-(except/flow) to avoid clashes. - Settings merge:
~/.claude/settings.json(user) merges with<repo>/.claude/settings.json(project) and.claude/settings.local.json(local, gitignored). Hooks from all scopes run — Otto's global hook + agent-skills' plugin hook coexist. - Plugin commands are namespaced — agent-skills commands may appear as
/agent-skills:specas well as/spec. - spec-kit hyphen vs dot — see B1;
/speckit-specifyin Claude,/speckit.specifyin the canonical docs. jq— only the agent-skills hook hard-requires it; Otto's bash hook degrades to plain stdout without it (and the Windows hook doesn't use it).
- After
git pullinOTTO_HOME: re-run theregister-skillsscript (A2) and run/otto-syncto re-pin source refs + reconcile the registry. - After updating a source (new spec-kit release, etc.):
/otto-sync --driftflags any renamed/removed unit that breaks a registry invocation.
- Otto skills: delete the registered dirs under
~/.claude/skills/(flow,otto-*). Remove the SessionStart hook block from~/.claude/settings.json. - Sources:
/plugin uninstall agent-skills;uv tool uninstall specify-cli; remove Pocock skills from~/.claude/skills/. - Per-repo: walk
.otto-manifest.mdbottom-up — deletecreatedpaths, revertmodifiedones (theCLAUDE.mdmanaged blocks, the.gitignoreblock), remove any.git/info/excludeblock (private mode), then delete the manifest itself. Repos installed before the manifest existed: delete.specify/, theCLAUDE.mdmanaged blocks, anddocs/agents/; check forCONTEXT.md,stack.yaml,learnings.md,docs/adr/before deleting — they may hold authored content worth keeping.