Step-by-step setup for a new machine. Covers prerequisites, installation, MCP configuration, and embedding model options.
| Requirement | Version | Notes |
|---|---|---|
| Node.js | 20–24 LTS | install.sh will prompt to install via NVM if wrong version |
| git | Any | For cloning MCPs |
| jq | Any | For opencode.json merges (macOS: brew install jq) |
| OpenCode | Latest | # OpenCode install: https://opencode.ai |
Optional but recommended:
- LM Studio — for vector embeddings (semantic search). Without it, BM25 keyword search still works.
- Opengrep — preferred SAST engine for security audits (
./install.sh --opengrep, or see §2a below). Falls back to Semgrep if unavailable.
git clone https://github.com/bpmforge/attest.git ~/Code/attest
cd ~/Code/attest
./install.shinstall.sh prompts y/n for each optional MCP, then clones, builds, and registers them. Pass --yes to accept all defaults without prompting. Useful flags: --compact (compact agent variants for 32k local models), --tools (also install the optional code-analysis tools).
Useful flags: --compact (overlay compact agent variants for 32k local models), --tools (also install the optional code-analysis tools — semgrep, knip, vulture, mmdc, …), --project (install into .opencode/ instead of global), --link (symlink for dev).
What it installs:
- Agents, skills, shared protocols, hooks, and scripts →
~/.config/opencode/ bpm-code-search-mcp→~/Code/bpm-code-search-mcp/+ registers ascode-searchMCPbpm-memory-mcp→~/Code/bpm-memory-mcp/+ registers asmemoryMCPplaywright-mcp→ registered vianpx -y @playwright/mcp@latestplaywright-search→~/.local/share/playwright-search/+ registers with Claude Code
The /security agent needs a static-analysis engine. Opengrep is the preferred and
default engine — it's an LGPL-2.1 fork of Semgrep, so it's safe to run against
client/customer code. Semgrep's own hosted registry rules (--config auto, p/*
packs) are internal-use-only and must never be run in a client-facing scan;
client scans use Opengrep + our own rules in bpm-rulepacks instead. Semgrep the
binary remains a documented fallback if Opengrep can't be installed on a given
machine — see references/semgrep-guide.md for full command-by-command detail.
Install Opengrep:
./install.sh --opengrep # auto-installs via the official Opengrep installerThis runs Opengrep's own installer (curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh | bash),
which places the binary at ~/.opengrep/cli/<version> (with a latest symlink) and
links it into ~/.local/bin/opengrep if that directory exists on your PATH. If the
installer can't reach GitHub, install.sh prints the same command plus a link to
https://github.com/opengrep/opengrep so you can install manually, and falls back to
offering a Semgrep install (brew install semgrep / pip install semgrep) instead.
--semgrep is kept as an alias of --opengrep for backward compatibility — it tries
Opengrep first and only installs Semgrep if the Opengrep installer fails.
Verify:
opengrep --version
~/.config/opencode/scripts/doctor.sh # reports "opengrep (preferred SAST engine): ..."CI: .github/workflows/ci.yml currently runs the meta/process test suite
(npm test) only — it does not invoke semgrep-full-audit.sh or the /security
agent, so no SAST engine is required in CI today. If a workflow that actually runs
SAST scans is added later, it should install Opengrep the same way (--opengrep /
the official installer above) rather than falling back to Semgrep.
Both bpm-code-search-mcp and bpm-memory-mcp use vector embeddings for semantic search. BM25 keyword search works without any embedding provider — set it up only if you want semantic ("what does auth?" style) search.
- Download LM Studio
- In the model search bar, find and download:
nomic-ai/nomic-embed-text-v1.5-GGUF - Load it → it will listen on
http://localhost:1234 - No further config needed — the MCPs default to this model and URL
Any text embedding model loaded in LM Studio works. Set these env vars (add to ~/.zshrc or ~/.bashrc):
export LM_STUDIO_MODEL="your-model-name-here" # model ID as shown in LM Studio
export LM_STUDIO_URL="http://localhost:1234" # default port, change if differentCommon alternatives:
| Model | Dimensions | Speed | Quality |
|---|---|---|---|
nomic-ai/nomic-embed-text-v1.5 |
768 | Fast | Good (default) |
text-embedding-nomic-embed-text-v1 |
768 | Fast | Good |
CompendiumLabs/bge-large-en-v1.5-gguf |
1024 | Medium | Better |
CompendiumLabs/bge-small-en-v1.5-gguf |
384 | Fastest | OK |
Important: If you change the embedding model after indexing, you must re-index with
force=true(for code-search) or the existing vectors become incompatible. Memory is provider-sticky — changing the model requires re-embedding stored memories.
Set these env vars:
export LM_STUDIO_URL="https://api.openai.com/v1"
export LM_STUDIO_MODEL="text-embedding-3-small"
export OPENAI_API_KEY="sk-..."bpm-code-search-mcp and bpm-memory-mcp both speak the OpenAI embeddings API format, so this works transparently.
If you don't want to run LM Studio at all, disable vector embeddings:
export EMBEDDING_PROVIDER=noneBoth MCPs will use BM25 keyword search only. This is faster and still effective for exact-match queries — just not semantic ("find code that handles auth" style).
All env vars can be set in ~/.zshrc / ~/.bashrc, or passed inline when starting Claude Code.
| Variable | Default | Purpose |
|---|---|---|
CODE_SEARCH_ROOT |
cwd |
Project root to index. Set per-project or leave as default. |
LM_STUDIO_URL |
http://localhost:1234 |
Embedding API base URL |
LM_STUDIO_MODEL |
text-embedding-nomic-embed-text-v1.5 |
Embedding model name |
First-time per project:
code_index() # builds the index (takes ~30s for medium codebases)
code_index_status() # verify: provider, files, chunks, symbols
The index lives at .code-search/index.db in your project root (gitignored). It's rebuilt automatically when you re-index.
| Variable | Default | Purpose |
|---|---|---|
LM_STUDIO_URL |
http://localhost:1234 |
Embedding API base URL |
LM_STUDIO_MODEL |
text-embedding-nomic-embed-text-v1.5 |
Embedding model name |
EMBEDDING_PROVIDER |
(auto-detect) | Set to none to disable vectors |
CLAUDE_MEMORY_DB_PATH |
~/.claude-memory/memory.db |
Override the DB location |
Memory is shared across all projects by default (project-scoped via project ID). Each project's memories are isolated automatically.
| Variable | Default | Purpose |
|---|---|---|
PLAYWRIGHT_MCP_HEADED |
false |
Set to true to see the browser while it runs |
First use: Chromium is auto-downloaded (~170 MB) on first browser launch. To pre-install:
npx playwright install chromium# Check registered MCPs
# OpenCode reads MCPs from opencode.json — check with:
cat ~/.config/opencode/opencode.json | python3 -m json.tool
# Expected output includes:
# code-search node ~/Code/bpm-code-search-mcp/dist/index.js - ✓ Connected
# memory node ~/Code/bpm-memory-mcp/mcp/memory-server/dist/index.js - ✓ Connected
# playwright npx -y @playwright/mcp@latest - ✓ Connected
# playwright-search node ~/.local/share/playwright-search/dist/mcp.js - ✓ ConnectedRun the self-checks:
~/.config/opencode/scripts/doctor.sh # structure, deps, config, model backend, agent discovery → Status: HEALTHY
~/.config/opencode/scripts/check-tools.sh # which optional analysis tools are present (add --install)When the context window fills, opencode replaces conversation history with a
summary. On smaller models (reported on gpt-5-mini) a long review or coding run
then loses the thread — redoing finished work, forgetting which PRODUCE files it
still owes, or dropping the completion phrase.
The durable fix ships as a plugin: plugins/resume-anchor.ts, installed to
~/.config/opencode/plugins/ and auto-loaded (no config entry needed). It works
on one principle — disk state survives compaction; conversation history does
not — so instead of trying to make the summary better, it recomputes a short
"where am I" anchor from the filesystem and re-injects it on every request:
- the active
docs/work/HANDOFF_*.md, - each
PRODUCEfile marked[done]or[MISSING]by an actualexistsSync, - the exact completion phrase,
STATE.md's singleNextstep, and anyphaseN.mdfiles already written.
Because it never depended on history, a compacted turn gets exactly the same
anchor as an uncompacted one. It also appends must-survive pointers to the
compaction prompt (best-effort), and emits nothing at all on projects with no
HANDOFF/STATE, so it costs nothing when irrelevant. Disable with
EXPERTS_RESUME_ANCHOR=0.
This does not remove the need to write findings to disk as you go — see the
prune note below.
Compaction tuning (compaction in opencode.json; see examples/opencode.json):
| Key | Guidance |
|---|---|
prune |
Drops old tool outputs. Keeps context low, but silently discards earlier file reads — never rely on "I read that file 20 turns ago". Write results to disk as you go. |
tail_turns |
Recent user turns kept verbatim through a compact (default 2). Raise to 4 for long multi-step work. |
reserved |
Token buffer left for compaction. Leave unset. Setting it near the model's input limit wedges the session — observed on gpt-5-mini (input 128k): values from 118000 to 250000 hung it outright. |
Every analysis tool is optional — the agents fall back to grep — so a partial
install is a supported state, not a broken one.
check-tools.sh --install never runs sudo and never calls a package manager.
It installs what it can (npm and pipx tools), prints the real error for
anything that fails, and lists the remaining system prerequisites as commands for
you to run. On a fresh non-root Linux box you will typically see:
npm i -ghits EACCES when the global prefix is root-owned (both the nodesource and distro Node packages do this). The script retries scoped into~/.npm-global— without rewriting your~/.npmrc— then tells you to add~/.npm-global/bintoPATH. Nothing to do but thePATHline.pipxis missing, which blocks semgrep/vulture/radon/lizard. On Ubuntu 24.04 and other PEP 668 "externally-managed" distrospip install --user pipxis refused, so the package manager is the only route:sudo apt install -y pipx && pipx ensurepath.mmdcis never auto-installed.@mermaid-js/mermaid-clipulls puppeteer, which downloads Chromium and needsunzipplus browser libs. Installing it withPUPPETEER_SKIP_DOWNLOADproduces a broken renderer, which is worse than not having it:validate-mermaid.shcleanly skips a missingmmdcand still runs every static Mermaid check. Install it deliberately or not at all. If an earlier attempt half-finished, clear~/.cache/puppeteerfirst — a partial download makes every retry fail differently.
To verify the bare-Linux path yourself (needs podman or docker):
./scripts/test-check-tools-container.sh # builds a bare ubuntu:24.04, runs the installer as a non-root userCI cannot cover this: GitHub's ubuntu-latest ships a writable npm prefix and
unzip, so none of the above reproduces there.
Then start an OpenCode session and test each MCP:
code_index_status() # should show provider + file/chunk counts
session_restore() # should return [] on a fresh install (no memories yet)
browser_navigate("https://example.com") && browser_screenshot()
If LM Studio runs on a different machine (e.g., a home server):
export LM_STUDIO_URL="http://192.168.1.x:1234" # replace with your server IPMake sure LM Studio is configured to accept connections on all interfaces (not just localhost) in its settings.
| Problem | Fix |
|---|---|
cat ~/.config/opencode/opencode.json | python3 -m json.tool shows MCP as "Pending approval" |
Restart OpenCode once and approve it |
| code-search: "no embedding provider available" | Start LM Studio with the embedding model loaded, or set EMBEDDING_PROVIDER=none for BM25-only |
| memory: vector search returns 0 results | LM Studio isn't running or model name doesn't match — check LM_STUDIO_MODEL |
| playwright-mcp: "browser not found" | Run npx playwright install chromium |
| install.sh: "node not found" or wrong version | The installer will prompt to install NVM + Node 24 automatically |
jq: command not found |
brew install jq (macOS) or apt install jq (Linux) |
| MCP built but not registered | Re-run ./install.sh or register manually: Add entry to ~/.config/opencode/opencode.json under "mcp" |
./uninstall.shRemoves all installed files from ~/.claude/. Does not remove MCP repos from ~/Code/ or the memory database (~/.claude-memory/memory.db).