CLI command tree

The user-facing command tree, plus the machine-facing graph export and worker commands that matter to integrators. Use this when you need to look up a flag, not when you're learning what a command is for. For "why", read the relevant subsystem doc.

The grouping mirrors the source split: tool-owned commands (fit, sim, graph, yagni, mcp, and their nested <tool> <verb> children — fit list, fit recipes, graph lookup, etc.) come from each Tool's declared commandSpecs (mounted by the host). CLI-owned commands (audit, suite, init, report, config, sessions, policy, repair, tools, the per-tool <tool> plugin group, configure, agent-catalog, completion, uninstall) live under packages/cli/src/commands/. For the Tier-1/2/3 grammar, export --format convention, and internal visibility rules, see Command surface taxonomy.


Top-level

opensip                          # show welcome banner + next steps
opensip --version                # print version, exit
opensip --help                   # print full help, exit
opensip <command> --help         # per-command help

Per-command flags that appear on most subcommands. The flags shared across the

tool run commands (fit/sim/graph/yagni) — --json, --cwd, -q/--quiet,

-v/--verbose, --debug, --report-to, and --api-key — are declared

once in a common-flag registry and applied via applyCommonFlags, so their

names, short aliases, descriptions, and defaults are identical where applied and

cannot drift (ADR-0021). The four first-party analysis primaries (fit, sim,

graph, and yagni) plus the host-owned audit/suite workflows expose --open

for HTML report generation and browser launch. The separate report command

regenerates a report from stored evidence. -v/--verbose is a uniform "show the detailed

report body" flag whose output is identical in a TTY and a pipe.

Host-guaranteed tool-primary surface. The host mount layer guarantees a

uniform baseline on every tool primary (fit/graph/sim/yagni, and any

third-party tool's run verb) — a tool need not opt in. Each primary always

carries --cwd, --json, --config, --quiet, --verbose, and its own

--version:

e.g. fit 0.1.6, plus a (tool contract v<n>) marker when the tool declares

one). This is distinct from opensip --version, which prints the CLI

version.

every tool primary, not just fit.

The program-level (root) options are --version (the CLI version),

--no-cloud, and --no-plugins. The root --version is host-owned and must

precede any subcommand (opensip --version); a --version after a

subcommand is that tool's own:

| Flag | Effect |

|---|---|

| --version | At the root (opensip --version): the CLI version. After a tool verb (opensip fit --version): that tool's version. |

| --config <path> | Path to opensip-cli.config.yml (overrides default root discovery). Guaranteed on every tool primary. |

| --debug | Enable debug-level logging (events of debug level appear in stderr and the run log file). |

| --quiet | Suppress banner / boxes; print only the pass/fail summary line. (Where supported.) |

| --cwd <path> | Override the project root (default: process.cwd()). Registered on init, fit, sim, graph, yagni, and the <tool> plugin <subcmd> commands. |

| --json | Emit structured JSON on stdout instead of the table renderer. (Per-command — init, fit, sim, graph, yagni.) |

| --no-cloud | Disable OpenSIP Cloud signal sync for this run (program-level). See below. |

| --no-plugins | Skip discovery/loading of installed npm Tool packages for this run (program-level, equivalent to OPENSIP_CLI_SKIP_INSTALLED=1). Bundled first-party tools and project-authored checks/scenarios still load through their normal lanes. |

Global startup/admission failures can occur before any subcommand body runs.

Most configuration failures exit 2; a project-authored Tool sidecar rejected by

the compatibility/trust gate exits 5 (PLUGIN_INCOMPATIBLE) before its module

is imported.

OpenSIP Cloud signal sync

OpenSIP Cloud sync is optional. This repo ships the CLI client and the

SignalBatch wire contract; sync runs only when an OpenSIP API key and a

compatible endpoint are configured. Without a key, the CLI remains fully local.

When configured (an OpenSIP API key via opensip configure or

OPENSIP_API_KEY) and entitled to Cloud signal storage, each deliverable

fit, sim, graph, and yagni run additionally emits its signals (the

findings it already produces) to OpenSIP Cloud for storage. This is **additive

and best-effort**: it leaves whatever local artifacts and records the selected

command mode normally writes unchanged and neither replaces the local runtime

nor forces a generic session row. A Cloud failure never changes the local result

or exit code, although an enabled delivery can add network latency. On a successful sync

you'll see ✓ Sent N signals to OpenSIP Cloud.

fit, sim, and yagni deliver after normal run modes that produce a

SignalEnvelope. graph has extra carrier/export modes: the default render,

--gate-save/--gate-compare, and --report-to deliver; plain --json (a

machine-artifact stream, also the carrier each --workspace child runs under)

and --workspace itself do not. The separate graph export --format catalog

command is a catalog dump for the parent ingestor, not a signal-emitting run.

Run a whole-project graph to sync.

What is sent: each signal's file path, message, suggestion, code-location

hints, and rule metadata. Nothing is sent for users without an API key or

without the entitlement.

Opt out machine-wide in your user config ~/.opensip-cli/config.yml (flat,

alongside apiKey):

cloud:
  sync: false               # disables signal sync for every project on this account
  endpoint: https://...     # optional https override of the built-in URL

Or per project in opensip-cli.config.yml under cli.cloud: (same fields).

A sync: false in either place disables sync — the more restrictive

setting wins. Or opt out per-run with --no-cloud.

This is distinct from --report-to: that path explicitly POSTs SARIF to

any receiver (and can fail a CI build via exit 4), whereas cloud sync emits

native signals to OpenSIP Cloud automatically and best-effort.

JSON run provenance

Run commands that emit a SignalEnvelope also carry host-stamped

declaredInputs metadata in JSON/SARIF/report delivery paths. It records a small

allowlist of verdict-relevant runtime facts: CLI version, Node version, package

manager, platform, tool id, available engine/tool version, and baseline

fingerprint identity. It does not include environment dumps, absolute paths, or

secrets.

Use it when comparing CI or agent runs:

opensip fit --json | jq '.envelope.declaredInputs'

fit — run fitness checks

Tool-owned: packages/fitness/engine/src/tool.ts.

opensip fit
opensip fit --recipe <name>
opensip fit --check <slug>
opensip fit --tags <list>
opensip fit --gate-save
opensip fit --gate-compare

| Flag | Type | Default | Effect |

|---|---|---|---|

| --recipe <name> | string | default | Use a named recipe. Built-in default runs every enabled check. |

| --check <slug> | string | — | Run a single check by slug. Mutually informative with --recipe. |

| --tags <list> | repeatable / comma-list | [] | Filter checks by tag (intersected with the recipe's selector). Repeatable (--tags a --tags b) or comma-separated (--tags a,b). |

| --exclude <slug> | repeatable | [] | Exclude check by slug. Can be passed multiple times. |

| --list | bool | false | List available checks instead of running. |

| --recipes | bool | false | List available recipes instead of running. |

| --show <session> | string | — | Replay a stored fit session (by id, or latest) instead of running — see sessions show. |

| --json | bool | false | Emit the CommandOutcome JSON on stdout (envelope under .envelope) instead of the table renderer. |

| -v, --verbose | bool | false | Show the detailed report body (per-check findings) inline. Renders identically in a TTY and a pipe (ADR-0021). |

| --report-to <url> | URL | — | POST findings to OpenSIP Cloud or a compatible endpoint. |

| --api-key <key> | string | — | API key for --report-to. |

| --gate-save | bool | false | Save current findings as architecture baseline rows in the active local SQLite store (managed user cache before initialization, project .runtime afterward; host-owned tool_baseline_entries, scoped tool = 'fitness'; ADR-0036), then exit per the failOnErrors/failOnWarnings thresholds (ADR-0020 — the save happens before the exit, so the baseline survives a failing gate). |

| --gate-compare | bool | false | Compare current findings against baseline; exit 1 on regression (toggle with the reserved failOnDegraded key, default on). |

| -q, --quiet | bool | false | Suppress banner. |

| --open | bool | false | Launch the HTML report after run. |

| --config <path> | path | discovered | Override the opensip-cli.config.yml location (defaults to the project root config). |

| --cwd <path> | path | process.cwd() | Target directory. |

| --debug | bool | false | Enable debug-level logging. |

| --filter <token> | repeatable | [] | Agent filter (repeatable). Values: errors-only, warnings-only, category:<name>, source:<tool>, file:<prefix>, high-impact, top:<n>. Composable with --top. See Agent run filters. |

| --top <n> | string | — | Shorthand for --filter top:<n>. |

| --raw | bool | false | Emit the filtered payload without the CommandOutcome wrapper (with --json). |

| --changed | bool | false | Restrict the run to git-changed files (intersects scope targets). |

| --since <ref> | string | — | Git ref base for changed-file detection (implies changed semantics). |

| --include-impacted | bool | false | Expand --changed targets with graph impact callers. If graph/git impact trust is partial or unknown, fitness warns and runs the full target set instead of narrowing unsafely. |

Agent recipes: agent-fast, agent-risk, agent-final — see Use OpenSIP with AI agents.

Mutual exclusion: --gate-save and --gate-compare cannot be combined.

Exit codes: 0 (passed), 1 (violations or regression), 2 (configuration error, including an unknown explicit --check slug), 4 (--report-to upload failure), 5 (a project-authored Tool sidecar was rejected before import). Exit code 3 is reserved by the shared typed-error table for CHECK_NOT_FOUND, but the current fit --check <missing> path treats explicit check selection as invalid configuration and exits 2. A --report-to upload failure exits 4 — but only when the run otherwise passed; a check/gate failure (1) or configuration error (2) takes precedence and is never masked by a reporting failure. This matches graph and the canonical exit-code contract.

See also: 20-fit/04-output-gate-sarif.md, 10-concepts/05-architecture-gate.md.


sim — run simulation scenarios

Tool-owned: packages/simulation/engine/src/tool.ts.

opensip sim
opensip sim --recipe <name>

| Flag | Type | Default | Effect |

|---|---|---|---|

| --recipe <name> | string | built-in default | Run a named sim recipe. |

| --show <session> | string | — | Replay a stored sim session (by id, or latest) instead of running — see sessions show. |

| --cwd <path> | path | process.cwd() | Target directory. |

| --json | bool | false | Emit the CommandOutcome JSON on stdout (envelope under .envelope) instead of the table renderer. |

| -v, --verbose | bool | false | Show the detailed report body (per-scenario findings) inline. Renders identically in a TTY and a pipe (ADR-0021). |

| -q, --quiet | bool | false | Suppress banner. |

| --open | bool | false | Launch the HTML report after run. |

| --report-to <url> | URL | — | POST findings to OpenSIP Cloud or a compatible endpoint. |

| --api-key <key> | string | — | API key for --report-to. |

| --debug | bool | false | Enable debug-level logging. |

| --filter <token> | repeatable | [] | Agent filter — same tokens as fit (except high-impact / file: are N/A). See Agent run filters. |

| --top <n> | string | — | Shorthand for --filter top:<n>. |

| --raw | bool | false | Emit the filtered payload without the CommandOutcome wrapper (with --json). |

Exit codes: 0 (all scenarios passed), 1 (any scenario failed), 2 (config/runtime error, including a run that selected zero scenarios — an empty run fails closed rather than reporting a false pass: no scenario packages installed, or the recipe selector matched none). Exit 0 therefore always means at least one scenario ran and passed.

See also: 30-sim/.


graph — static call-graph + dead-end analysis

Tool-owned: packages/graph/engine/src/tool.ts. The pipeline architecture and cache invalidation are documented in 40-graph/01-stages-and-catalog.md; perf-plan history is recoverable from git -P log -- packages/graph.

# Whole project (language auto-detected)
opensip graph

# Scope to a single subtree
opensip graph packages/core

# Scope to multiple subtrees (one session aggregates results)
opensip graph packages/core packages/cli

# Shell glob expansion (the shell expands; opensip-cli doesn't)
opensip graph 'packages/*/src'

# Fan out across detected workspace units (memory-isolated)
opensip graph --workspace
opensip graph --workspace --concurrency 4

# Force a specific language adapter (suppresses auto-detection)
opensip graph --language typescript
opensip graph --language python packages/services/api

# Other modes
opensip graph --json
opensip graph --no-cache
opensip graph --exact          # force the single-program exact engine (default is the sharded engine)
opensip graph --gate-save
opensip graph --gate-compare
opensip graph --gate-save --sarif graph.sarif   # gate + SARIF 2.1.0 for Code Scanning
opensip graph --report-to <url>

# Scope to a named recipe (a subset of graph rules; default = all rules)
opensip graph --recipe <name>

# List the source files graph would discover for this scope — no build
opensip graph --list-files
opensip graph --list-files --json       # machine-readable: { count, files }
opensip graph --list-files --workspace  # the per-unit fan-out set

graph is the single entry point for static call-graph analysis. The default (non-JSON) output is a one-line summary; pass -v/--verbose to expand the structured terminal report into its detailed sections: catalog summary, findings grouped by rule (top 10 per rule, with overflow indicator), and top 10 inferred entry points. The full data is always available via --json.

| Flag / Argument | Type | Default | Effect |

|---|---|---|---|

| [paths...] | path(s) | — | Positional. Scope the run to one or more existing directories (absolute or relative to --cwd). Multiple paths aggregate into a single report session per D12. The shell handles globs (graph 'packages/*/src'); no glob expansion happens inside the CLI. Mutually exclusive with --workspace. |

| --cwd <path> | string | process.cwd() | Target directory. Adapter is auto-detected by marker files (TypeScript: tsconfig.json/package.json; Python: pyproject.toml/setup.py/setup.cfg; Rust: Cargo.toml; Go: go.mod; Java: pom.xml/build.gradle*). Polyglot repos apply every matched adapter simultaneously (D6). |

| --workspace | bool | false | Fan the run across every workspace unit returned by each detected adapter's discoverWorkspaceUnits hook. Polyglot per D8b: a repo with both a TS pnpm workspace and a Cargo workspace fans out across both adapters' units in one combined run. Memory-isolated (one child process per unit). Mutually exclusive with positional paths. |

| --concurrency <n> | int | cpus()-1 | Concurrency cap for --workspace child processes. |

| --language <name> | string | — | Force a specific language adapter, suppressing marker-based auto-detection. If the discovered file count is zero, exits with code 2 and the message --language <name> matched 0 files under <paths>; check the flag or paths. (D14). |

| --json | bool | false | Output the CommandOutcome JSON document (envelope under .envelope) instead of the unified terminal report. |

| --no-cache | bool | false | Skip the catalog cache and force a full rebuild. |

| --exact | bool | false | Use the single-program exact build engine instead of the default parallel sharded engine. Sharded is the default on shardable (multi-package) repos; both engines resolve cross-package edges through one shared model (exact = the 1-shard case), held equivalent by a directional equivalence guardrail + a pinned-corpus completeness floor (ADR-0033). --exact forces the single-program engine and suits small / single-package repos. A repo that can't shard already uses exact, no flag needed. Engine choice is deterministic and never depends on isTTY (a terminal and CI build the same catalog). |

| --resolution <mode> | string | exact | Edge resolution tier: exact (semantic, uses the type checker) or fast (syntactic, no type checker — ~2× faster cold builds at lower edge fidelity). Invalid values fail loudly at the boundary. Note: --resolution (edge tier) is orthogonal to --exact (build engine). |

| --profile <path> | path | — | Write a graph performance profile JSON artifact with stage timings, run mode, cache verdict, file/function counts, and resolution stats. Relative paths resolve against --cwd. |

| --recipe <name> | string | — | Run a named graph recipe — a subset of the graph rule set. Default (no flag): all rules. An unknown name fails with a configuration error. List recipes with graph recipes. |

| --show <session> | string | — | Replay a stored graph session (by id, or latest) instead of building — see sessions show. |

| --gate-save | bool | false | Save the current Signal fingerprint set as baseline rows in the active local SQLite store (managed user cache before initialization, project .runtime afterward; host-owned tool_baseline_entries, scoped tool = 'graph'; ADR-0036), then exit per graph's fail thresholds — the save happens before the exit. Mutually exclusive with --gate-compare. |

| --gate-compare | bool | false | Compare current Signals to the saved baseline; exit non-zero on regression (toggle with the reserved failOnDegraded key, default on). |

| --sarif <path> | path | — | Also write this run's findings as a SARIF 2.1.0 file (for GitHub Code Scanning) via the shared cli.writeSarif envelope→SARIF seam — the same producer fit --report-to/fit export --format baseline use. Composes with --gate-save: the SARIF is written in the action body after the gate sets its exit code, so the file lands even when the gate fails. Relative paths resolve against --cwd. |

| --report-to <url> | string | — | POST findings to OpenSIP Cloud or a compatible endpoint. |

| --api-key <key> | string | — | API key for --report-to. |

| -v, --verbose | bool | false | Expand the done view to show the detailed catalog, findings-by-rule, and entry-point sections (default: one-line summary only). Renders identically in a TTY and a pipe (ADR-0021). |

| -q, --quiet | bool | false | Suppress banner / boxes; print only the pass-fail summary line. |

| --list-files | bool | false | Discovery-only: resolve and print the source-file set this scope would analyze (whole project, positional subtrees, or --workspace fan-out) and exit — no catalog build. Reuses the adapter's stage-0 discovery, so the list is faithful to a real run (.d.ts excluded, TypeScript extension-priority collisions collapsed, per-tsconfig include/exclude honored). Composes with [paths...], --workspace, and --language; --json emits { count, files }. |

| --debug | bool | false | Enable debug-mode structured log output. |

| --filter <token> | repeatable | [] | Agent filter — includes high-impact (blast/coupling markers). See Agent run filters. |

| --top <n> | string | — | Shorthand for --filter top:<n>. |

| --raw | bool | false | Emit the filtered payload without the CommandOutcome wrapper (with --json). |

Agent recipes: agent-risk, agent-final.

Inspecting discovery (--list-files). graph does not enumerate files the way a filesystem walk would — it asks the language adapter, which for TypeScript means the set the tsconfig resolves (so .d.ts is excluded, an extension-priority collision like a foo.tsx shadowed by a sibling foo.ts is collapsed to the .ts, and each package's include/exclude is honored). --list-files prints exactly that set for the chosen scope and exits before any catalog build, which makes it the cheap, authoritative answer to "what does graph actually see?" The whole-project list and the --workspace list can legitimately differ — the latter is the union of per-package tsconfigs, which may exclude paths (e.g. __fixtures__, root scripts, out-of-src files) the root tree includes. To diff graph's view against the VCS:

opensip graph --list-files --json | jq -r '.files[]' | sort > /tmp/graph.txt
git ls-files '*.ts' '*.tsx' | sort > /tmp/git.txt
comm -23 /tmp/git.txt /tmp/graph.txt   # tracked but NOT discovered
comm -13 /tmp/git.txt /tmp/graph.txt   # discovered but NOT tracked

Polyglot example. In a repo with both a TypeScript pnpm workspace and a Cargo workspace, the polyglot detection applies both adapters in a single run:

# Polyglot repo: TS frontend + Cargo backend
# `--workspace` aggregates units from BOTH adapters and fans out in parallel.
opensip graph --workspace
# → one report session combining TS package results + Cargo member results

Session contract. A single CLI invocation produces a single report session, regardless of how many positional paths or workspace units the run analyzed. Modes that produce machine-readable artifacts instead of report sessions (--json, --gate-save, --gate-compare, --report-to) opt out. Machine-artifact catalog/SARIF exports live on the dedicated graph export --format catalog / graph export --format sarif commands.

Adapter selection. opensip-cli ships first-party graph adapters for TypeScript, Python, Rust, Go, and Java — each is its own publishable npm package under the @opensip-cli/graph- namespace. Default auto-discovery is marker-based and descriptor-driven: node_modules is walked for packages whose package.json declares opensipTools.kind: "graph-adapter" plus the graph-adapter target-domain epoch (built-ins resolve from the CLI install tree). You can pin an exact adapter set under plugins.graphAdapters: in opensip-cli.config.yml; when set, that list replaces auto-discovery. plugins.autoDiscoverGraphAdapters: false disables the scan. Marker-file detection (tsconfig.json, pyproject.toml, Cargo.toml, go.mod, pom.xml/build.gradle) then chooses which discovered adapter(s) apply to the run; positional paths inherit that decision unless --language overrides it.

Exit codes: 0 (success / gate clean), 1 (runtime error / gate regression / any --workspace child failed), 2 (configuration error / D14 zero-file mismatch), 4 (--report-to upload failed), 5 (a project-authored Tool sidecar was rejected before import).

Heap sizing: for projects with > 1000 source files, graph preflights the discovered file count and re-execs itself with a larger V8 heap when the current cap is too small: >1000 files targets 8192 MB and >2500 files targets 12288 MB. The elevation is silent unless --verbose is set; if the machine does not have enough RAM for the target plus OS headroom, graph continues at the current heap and prints a warning. The preflight skips automatically when an explicit scope is present (positional paths, --workspace, or --language). Heap sizing is most acute for the TypeScript adapter, which holds a project-wide ts.Program; tree-sitter adapters parse lazily per file and use far less memory.

OPENSIP_HEAP_NO_MONITOR (env var): during a build, graph runs a V8 heap-pressure monitor that aborts with a readable MemoryPressureError when old-gen usage crosses ~90% of the heap limit — catching an impending OOM before V8 SIGABRTs the process. In unusual GC scenarios (REPL embedding, custom allocators) this guard can fire as a false positive before a real OOM is imminent. Set OPENSIP_HEAP_NO_MONITOR=1 to disable it entirely. This is an escape hatch only: with the monitor off, an actual out-of-memory condition becomes a bare V8 abort instead of a structured error. Prefer raising the heap ceiling (above) or scoping the run (positional paths / --workspace) first.

Catalog storage: graph stores the catalog in the active local SQLite database

(<runtime-root>/datastore.sqlite, graph_catalog row): managed user cache

before initialization, project .runtime afterward. The

wire format carries language (adapter id), cacheKey (an opaque per-adapter

invalidation string — TypeScript: ts-${ts.version}-${tsconfigContentHash};

Python and Rust use language-id-prefixed keys), and a per-file mtime+size

fingerprint. The reconstructed in-memory Catalog shape is what graph rules,

indexes, and report views consume.

Cache behavior: three verdicts — valid (full cache hit), incremental (re-walk only the changed files plus their transitive edge-dependents), invalid (full rebuild). The incremental path makes single-file edits ~6× faster than a --no-cache rebuild while producing byte-identical output. See the cache section in the stages-and-catalog doc.

Entry-point reasons (rendered in the entry-points section): module-init (every file's top-level statements), name-match (main / run / start / register / init / bootstrap / initialize), no-callers-exported (exported with no in-project caller). Bin-entry and tool-registration heuristics remain deferred.

History. v0.2 originally registered three subcommands — graph, graph-orphans, and graph-entry-points. The two filtered views were folded into the unified graph output; all three data slices (rules, entry points, catalog summary) are now reachable from the single graph invocation.


yagni — advisory reduction audit

Tool-owned: packages/yagni/engine/src/tool.ts.

opensip yagni surfaces evidence-backed opportunities to reduce code while preserving behavior. Findings are advisory by default (failOnErrors: 0, failOnWarnings: 0 in config) — the run exits 0 unless you raise those thresholds. Each signal carries metadata.yagni (confidence, preservation argument, validation steps, evidence). Deeper narrative: 55-yagni/01-command-reference.md.

opensip yagni
opensip yagni --json
opensip yagni --min-confidence high
opensip yagni packages/cli/src

ADR-0064: yagni ships two bundled detectors — unused-config-surface (config reduction) and duplicate-body-candidate (exact-duplicate TS function bodies via @opensip-cli/clone-detection). Near-duplicate analysis remains graph-only. Deeper detail: 55-yagni/01-command-reference.md.

| Flag / Argument | Type | Default | Effect |

|---|---|---|---|

| [paths...] | path(s) | — | Positional. Limit analysis to one or more directory subtrees (relative to --cwd). |

| --json | bool | false | Emit the canonical SignalEnvelope on stdout instead of the human renderer. |

| --min-confidence <level> | enum | medium | Filter findings to low, medium, or high. |

| --detector <slug> | string | — | Run only named detectors (repeatable). Bundled: unused-config-surface, duplicate-body-candidate. |

| --category <name> | string | — | Filter by metadata.yagni.reductionCategory (repeatable). |

| --include-tests | bool | false | Include test and fixture files in analysis. |

| --report-to <url> | URL | — | POST findings to OpenSIP Cloud or a compatible endpoint. |

| --open | bool | false | Launch the HTML report after run. |

| -v, --verbose | bool | false | Show evidence, validation steps, and low-confidence findings. |

| -q, --quiet | bool | false | Suppress banner. |

| --cwd <path> | path | process.cwd() | Target directory. |

| --debug | bool | false | Enable debug-level logging. |

Exit codes: 0 by default (advisory). Non-zero only when yagni.failOnErrors / yagni.failOnWarnings thresholds are exceeded, or on configuration/runtime errors (2).

Suppressions: @yagni-ignore-file and @yagni-ignore-next-line (ADR-0014). The yagni-ignore-hygiene fitness check audits directive quality.


mcp — serve the call graph + results to agents over stdio

Tool-owned: packages/mcp/src/command.ts (the bundled @opensip-cli/mcp tool, ADR-0084).

opensip mcp is a long-lived, blocking Model Context Protocol server. Unlike every other command, it does not run an analysis and exit — an MCP-capable coding agent (Claude Code, Codex, …) spawns it as a child process and speaks JSON-RPC over stdio for the whole session. stdout carries only JSON-RPC frames; every log line and diagnostic goes to stderr. The server blocks until stdin reaches EOF (or a graceful SIGINT), then exits 0. Because the protocol genuinely owns stdout, the command is declared output: 'raw-stream' with rawStreamReason: 'mcp-stdio' (see Command surface taxonomy); it emits no SignalEnvelope, persists no session, and renders no banner.

opensip mcp                      # serve over stdio from the current project
opensip mcp --cwd /path/to/repo  # serve a project at an explicit root
opensip mcp --allow-mutations    # opt in to repair_apply_verify

mcp accepts --cwd plus the explicit --allow-mutations opt-in. Every

per-query argument (a symbolId, a depth, a tool filter) is an **MCP

JSON-RPC tool parameter**, not a command-line flag. The equivalent mutation

environment opt-in is OPENSIP_MCP_ALLOW_MUTATIONS=1; read-only remains the

default.

mcp is project-scoped and no-init capable (ADR-0170):

before Init it serves the host-selected managed user-cache datastore for the

discovered project root; after Init it serves project

opensip-cli/.runtime/. It never opens a second datastore, never upgrades a

weak cache identity, and does not create config. The stdio process holds a

shared runtime lease for its lifetime, so opensip init / uninstall may report

busy until the client disconnects. Parent execution history is exposed via the

additive tools list_execution_runs / show_execution_run (distinct from

legacy Session-replay list_runs / show_run). A broken host scope still exits

with typed MCP.DATASTORE_UNAVAILABLE and points at opensip status --json

it does not claim that Init is required.

Trust model. stdio binds no network port and opens no socket, so there is no auth layer — the server inherits the caller's filesystem trust (the agent runs as you). refresh_graph is parse-only (tree-sitter parse + static analysis); it never executes project code or runs build scripts.

The protocol inventory is surface-epoch versioned. Treat initialize/listTools

and get_agent_catalog.mcp.toolNames as authority; reconnect after an executable

surface change. Starting with --allow-mutations adds only

repair_apply_verify.

Graph tools

| Tool | Purpose |

|------|---------|

| search_symbols | Find functions/methods by name. match: "substring" (default) is case-insensitive on simpleName; "exact" is case-sensitive on simpleName; "qualified" is case-sensitive on qualifiedName. Returns a symbolId + bodyHash per match to feed the other tools. |

| get_symbol | Resolve the function/method declared at a file + line into a stable symbolId + bodyHash; detail: "entity" adds bounded callable fields after unique resolution. Ambiguity returns candidates, never a silent pick. |

| impact_files | Compute bounded changed-to-impacted functions/files/packages for explicit project-relative files against one immutable generation. |

| who_calls | Callers of a symbol (reverse call graph), out to depth (default 5, max 5). Occurrence-precise by default; large fan-in is node-capped with coverage reasons. |

| callees_of | Symbols a symbol calls (forward call graph), out to depth. |

| trace_path | A forward call path from one symbol to another within depth; returns the ordered path or { found: false }. |

| blast_radius | Change-impact score for a symbol (body-twin-union identity, labelled): direct callers, transitive callers, and composite score. |

| find_dead_code | Symbols unreachable from any entry point (the graph orphan-subtree rule); each finding carries a symbolId + reason. |

| get_architecture | High-level shape: function/edge counts, languages, top-coupled packages, and blast hotspots. |

| package_dependencies | Labelled package call and/or import edges (production defaults). |

| why_depends | Bounded evidence for why package A depends on package B. |

| package_cycles | Package SCCs/cycles for call, import, or combined edges. |

| refresh_graph | Ensure a fresh catalog: auto-loads a newer persisted generation, rebuilds only when missing/stale or forceRebuild — the only state-changing graph tool. |

Codebase and context reads

| Tool | Purpose |

|------|---------|

| get_file_context | Read bounded inventory facts and role/target/package provenance for one explicit file without source contents. |

| select_tests | Return labelled static test candidates and conservative allowlisted package/project commands for explicit files; never executes them. |

| get_context_status | Replay the latest or named parent-Run task-context manifest and check its recorded graph/snapshot pointers exactly, without rebuilding or rebinding. |

Live runtime evidence (read-only, not a call graph)

| Tool | Purpose |

|------|---------|

| get_runtime_wiring, search_declarations, references_to | Project admitted manifest, provenance, registry, parent/child CommandSpec, host-mount, handler-dispatch, and external-worker posture. Every edge carries source/confidence and unresolved static bridges remain explicit. |

Result and review tools (replay stored runs, never re-run)

| Tool | Purpose |

|------|---------|

| get_agent_catalog | The self-describing catalog of OpenSIP commands an agent can run. |

| list_runs | List recent stored runs (fit/graph/yagni/sim) as lean pointers — id, tool, timing, score/passed, replay command. |

| show_run | Replay a stored run by id, or "latest" (with tool); same filters/raw shape as opensip sessions show. |

| get_latest_findings | The findings from the most recent run of a tool, filterable by severity. |

| review_change | Rebuild the v1 ReviewBrief from persisted suite step sessions. Inputs: suiteRunId, suite, files, and limit. Includes graph freshness and source session ids; does not refresh the graph. |

| compare_to_baseline | Compare a replayed stored run (tool, optional ref) to that tool's stored baseline fingerprints. Returns added/unchanged/resolved counts, bounded finding details, and missing-baseline degradation. |

The result/review tools replay persisted sessions only — they never re-run

fit/graph/sim/yagni. They are the preferred first source for existing

run/finding/history questions. Agents should not grep .runtime/logs, read

datastore.sqlite directly, or re-run a tool to answer a stored-result question

unless fresh execution is explicitly needed. This is enforced by the

mcp-results-no-rerun and mcp-first-agent-guidance fitness checks.

Result tools are repo-scoped to the server's captured project root

(ADR-0130). A

list_runs summary includes the run's recorded cwd; foreign-root rows in a

shared datastore are hidden from list_runs, and show_run on a foreign id

returns not-found. The operator-facing opensip sessions list command remains

unscoped over the selected datastore.

The symbolId contract

search_symbols and get_symbol return a stable symbolId = "<filePath>:<line>:<column>" plus a bodyHash. Use search_symbols.match: "exact" for a case-sensitive simple-name match or "qualified" for a case-sensitive qualified-name match; the default "substring" searches simple names case-insensitively. Every downstream graph tool (who_calls, callees_of, trace_path, blast_radius) accepts that symbolId, not a bare name — so an agent resolves a name once, then traverses. A query that names an ambiguous symbol returns a structured candidate list or error, never a silent pick.

Freshness, auto-swap, and refresh_graph

Every graph result carries project/catalog context, a freshness verdict with verification coverage (complete / partial / missing), and independent page vs coverage fields (ADR-0148, ADR-0149).

Check the canonical configured root, opaque g1: generation identity/source,

freshness reasons, effective filters, evidence kind/confidence, page cursor,

coverage counts, truncated, and hard-cap reasons before claiming complete

evidence. The cursor's project key is distinct from its generation key and is

invalid after project, generation, or query changes. Filters apply before

paging. Exact paths and segment-prefix paths never use raw string prefixes.

Unknown keys, hostile paths/enums, and malformed/stale/tampered cursors return

typed bounded failures. No JSON tool result exceeds 4 MiB.

Production MCP graph access crosses only the public @opensip-cli/graph/read

boundary (ADR-0147).

Occurrence traversal is the default. Explicit body-twin reachability filters

both occurrence-edge endpoints before grouping twins, so excluded test or

generated owners/targets cannot fabricate production reachability. Package

responses keep call and import evidence labelled; runtime-wiring evidence is a

third, separate kind rather than a synthetic static edge.

A long-lived MCP process auto-loads a newer catalog already written by an external opensip graph (cheap identity probe + atomic swap). That is a read, not a rebuild. There is still no auto-build on a missing catalog or on startup.

refresh_graph is the only mutation: it first syncs any externally persisted generation, returns no-op/reloaded when the generation is completely verified fresh, and rebuilds only when missing/stale or forceRebuild is true. Rebuilding parses the whole project — expensive; do not loop it per query. Prefer cursors/filters/limits on high-volume tools.

Result-first guidance

When the user references existing findings ("what were the fit errors?",

"show the last graph run"), an agent should call get_latest_findings (or

show_run / list_runs) to replay the stored result before re-running a

tool. Re-running is expensive and usually unnecessary; the result tools exist

precisely to steer the agent to the persisted result first. See

ADR-0109.

MCP client setup

Register opensip mcp as a stdio server in your coding agent's MCP config. The

client spawns the process, exchanges JSON-RPC over stdin/stdout for the session,

and closes stdin to shut the server down cleanly.

Setup is client-specific (JSON vs TOML, config paths, approval flows). See

Connect MCP clients (Cursor, Claude Code, Codex)

for copy-paste configuration for each client.

Limitations (v1): no cloud egress / no SignalEnvelope delivery; no live render; refresh_graph rebuilds one configured project through the canonical exact-or-default-sharded engine policy (no --workspace fan-out). impact_of_diff is not in the v1 tool surface.


graph lookup — look up function occurrences by name

Tool-owned (graph Tool). Queries the persisted catalog in the project's datastore for every function occurrence whose simple name matches the argument. Useful for "where is saveBaseline defined?" probes without re-running the full graph build.

opensip graph lookup <name>
opensip graph lookup <name> --json

| Flag / Argument | Type | Default | Effect |

|---|---|---|---|

| <name> | string | — | Positional. Function simple name to look up (e.g. saveBaseline). Required. |

| --json | bool | false | Emit a host-stamped CommandOutcome with data.type: "graph-lookup" (see JSON output schema). |

The command reads from the catalog stored in <project>/opensip-cli/.runtime/datastore.sqlite. Run opensip graph at least once first to populate the catalog. JSON mode routes through the host command-result seam — not a bare per-command JSON document.


graph impact — changed→impact analysis

Tool-owned (graph Tool). Read-only analysis of what changed and what depends on it — combining git change detection (or explicit files) with the persisted graph catalog. Rebuilds the catalog when missing (ADR-0085).

opensip graph impact --changed --json
opensip graph impact --since main --json --top 20
opensip graph impact --files packages/core/src/index.ts --json

| Flag | Type | Default | Effect |

|---|---|---|---|

| --changed | bool | false | Use git working-tree / branch diff for changed files. |

| --since <ref> | string | — | Git ref base (<ref>...HEAD). |

| --files <path> | repeatable | [] | Explicit changed file (git-free; works outside a repo). |

| --top <n> | string | — | Cap impacted function rows; sets truncated when capped. |

| --raw | bool | false | Emit GraphImpactResult without the CommandOutcome wrapper. |

| --json | bool | false | Emit structured GraphImpactResult (see JSON output schema). |

Exit codes: 0 (success), 2 (configuration error — not a git repo, missing basis, invalid --top).

graph impact --json includes trust.coverage, trust.fullyVerified,

trust.fallback, and bounded trust.uncertainties[]. See

Impact analysis and trust.

Current results also include optional-for-legacy catalog identity: bounded

builtAt, language, optional resolution mode, and fixed-length SHA-256

digests for the opaque cache key and file fingerprint. Raw keys/fingerprints are

not copied because they can contain absolute local paths. Graph stamps

result.trust onto the delivered envelope's authoritative verification

field and returns a generic session contribution in human, wrapped JSON, and

raw JSON modes. The linked session contains only the bounded report projection

described in the report reference, not the unbounded result.

Agent recipes: agent-risk, agent-final on graph --recipe.


graph index — emit symbol index artifact

Tool-owned (graph Tool). Writes a symbolindex.json file: two maps — name → [{ file, line }, …] and file → [name, …]. Intended for editor tooling and offline cross-reference.

By default the command queries the persisted catalog only (like graph lookup, it does not run analysis). Pass --build to run the graph pipeline first and refresh the catalog before emitting.

opensip graph index
opensip graph index --out path/to/symbolindex.json
opensip graph index --build

| Flag | Type | Default | Effect |

|---|---|---|---|

| --build | bool | false | Run the graph pipeline to refresh the catalog before emitting the index. |

| --cwd <path> | path | process.cwd() | Target directory; --out resolves against this. |

| --out <path> | path | symbolindex.json | Output file path. |

Without --build, reads from the persisted catalog; run opensip graph or opensip graph index --build first if no catalog exists.


graph export --format baseline — export graph gate baseline

Tool-owned (graph Tool). Exports the stored graph gate baseline (the Signal fingerprint set saved by graph --gate-save) from the SQLite datastore to a portable JSON file. Mirrors fit export --format baseline for the graph tool. (The catalog and sarif formats of graph export are the full-pipeline machine exports documented under Internal and machine-facing commands.)

opensip graph export --format baseline --out graph-baseline.json

| Flag | Type | Default | Effect |

|---|---|---|---|

| --format <fmt> | enum | — | Required. baseline (the gate fingerprint JSON), catalog, or sarif. |

| --out <path> | path | — | Required for --format baseline. Output file path for the JSON baseline. |

| --cwd <path> | path | process.cwd() | Target directory. |

| --json | bool | false | Emit a JSON result envelope on stdout instead of the human-readable summary. |

Exit codes: 0 on success, non-zero with a result.exitCode if the baseline is missing or the write fails. Useful for promoting a local baseline into CI or sharing one across machines without copying the SQLite file.


graph recipes — catalog graph recipes

Tool-owned (graph Tool). Mirrors fit recipes for the graph tool: prints the loaded graph-recipe inventory (a graph recipe is a named subset of the graph rule set). Reuses the shared ListRecipesResult contract and renderer.

opensip graph recipes
opensip graph recipes --json

| Flag | Type | Default | Effect |

|---|---|---|---|

| --json | bool | false | Output structured JSON instead of the human-readable list. |

JSON shape (same list-recipes result envelope as fit recipes):

{
  "type": "list-recipes",
  "recipes": [
    { "name": "default", "description": "...", "checkCount": "all rules" }
  ]
}

checkCount is a free-form label reused as a rule count — "all rules" for an all selector, "<n> rules" for an explicit selector, "pattern-based" otherwise.


graph list — catalog graph rules

Tool-owned (graph Tool). Prints the loaded graph-rule inventory (the analog of fit list, which lists checks). Reuses the shared list-checks result envelope and renderer.

opensip graph list
opensip graph list --json

| Flag | Type | Default | Effect |

|---|---|---|---|

| --json | bool | false | Output structured JSON instead of the human-readable list. |


report — open the HTML report

CLI-owned. The cross-tool report command lives at the CLI layer (not inside any one tool) because composition walks every tool's collectReportData contribution via the tool registry. No-init capable: writes under the managed user cache before Init and project .runtime afterward.

opensip report
opensip report --no-open
opensip report --json
opensip report --run <run-id>
opensip report --max-catalog-mb 64

| Flag | Type | Default | Effect |

|---|---|---|---|

| --no-open | bool | false | Write the report but do not launch a browser. |

| --json | bool | false | Emit a { type: 'report', path, opened } JSON envelope on stdout instead of the table renderer. In --json mode the browser is never launched (machine-output contract). |

| --run <run-id> | string | — | Select an exact retained parent Run for the Change Impact view (works outside recent history). Missing/pruned IDs exit 2 (CONFIGURATION.REPORT.RUN_NOT_FOUND); non-audit retained Runs exit 2 (CONFIGURATION.REPORT.CHANGE_IMPACT_UNAVAILABLE). Matched selections write a run-addressed artifact under reports/runs/<sha256>.html and return/launch that path (not mutable latest.html). |

| --max-catalog-mb | number | 8 | Byte budget for the inlined graph catalog, in megabytes. |

Report size and the bounded graph catalog

The report is a single self-contained HTML file you can attach to a PR or

email, so the graph catalog it inlines is bounded — by default to 8 MB. On a

large repository the report therefore ships the highest-blast-radius functions

and omits the rest, and says so:

Functions  4,925 of 31,932 (bounded)
Bounded for sharing — opensip report --max-catalog-mb 64 for the full catalog

Truncation is always a visible state, never a silent one. Raise the budget when

you are exploring a large repo locally rather than producing something to

send someone — a bigger report is slower to open and may be too large to share.

The exhaustive evidence always remains available through opensip graph and the

MCP graph tools, which is the better path for an agent or a deep query.

Unselected reports rewrite <runtime-root>/reports/latest.html as a convenience

alias (managed user cache before initialization, project .runtime afterward).

Exact report --run / audit --open selections write run-addressed files under

reports/runs/ and bind browser launch to that path so concurrent selections

cannot substitute through latest.html. The command launches the browser (unless

--no-open/--json) and exits; the file works without opensip-cli installed.

See also: 70-reference/06-dashboard.md, 80-implementation/03-session-and-persistence.md.


fit list — catalog checks

Tool-owned. Prints the loaded check inventory: slug, description, tags.

opensip fit list
opensip fit list --json

JSON shape:

{
  "type": "list-checks",
  "checks": [{ "slug": "...", "description": "...", "tags": ["..."] }],
  "totalCount": 160
}

Useful for scripting (opensip fit list --json | jq '.checks[].slug') and for verifying that an opensip fit plugin add actually registered the new pack's checks. totalCount is the loaded inventory for the current project, so it increases when project-local checks or installed packs are present.


fit recipes — catalog recipes

Tool-owned. Prints the loaded recipe inventory.

opensip fit recipes
opensip fit recipes --json

JSON shape:

{
  "type": "list-recipes",
  "recipes": [
    { "name": "default", "description": "...", "checkCount": "all checks" },
    { "name": "quick-smoke", "description": "...", "checkCount": "12 checks" },
    { "name": "by-tag", "description": "...", "checkCount": "pattern-based" }
  ]
}

checkCount is a human-readable string set by the recipe's selector — "all checks" for selector.type === 'all', "<n> checks" for explicit selectors, "pattern-based" for tag/pattern selectors. It is never a bare numeric string.


fit export --format baseline — export fit gate baseline as SARIF

Tool-owned (fitness Tool). Exports the stored fit gate baseline (the violation set saved by fit --gate-save) from the SQLite datastore to a SARIF file. Used to promote a local baseline into CI or to feed GitHub Code Scanning.

opensip fit export --format baseline --out fit.sarif

| Flag | Type | Default | Effect |

|---|---|---|---|

| --format <fmt> | enum | — | Required. baseline — the SARIF-shaped gate baseline. |

| --out <path> | path | — | Required. Output file path for the SARIF baseline. |

| --cwd <path> | path | process.cwd() | Project root. |

| --json | bool | false | Emit a JSON result envelope on stdout instead of the human-readable summary. |

The dogfood CI uses this command to write fit.sarif after a fit --gate-save step, then uploads it to GitHub Code Scanning. Exits non-zero with a result.exitCode if no baseline is stored or the write fails.


init — scaffold the project layout

CLI-owned: packages/cli/src/commands/init.ts.

opensip init
opensip init --language <list>
opensip init --keep
opensip init --remove

Zero-config first runs

audit, fit, graph, graph impact, the built-in suite run audit and

suite run agent-context, sessions list, sessions show, and report can run from a supported project

before opensip init. The CLI synthesizes an in-memory

config from language markers, validates it through the normal config schema, and

stores generated runtime state under ~/.opensip-cli/cache/ephemeral/. The

directory name is an implementation detail: this is file-backed,

retention-managed cache storage, not process-temporary storage. It survives

normal command exits and reboots, but a whole project entry can be evicted when

orphaned, stale, or beyond the cache's project limit.

No implicit OpenSIP state is written into a zero-config project. An

explicitly requested export, SARIF, or profile path is the exception. Human

output includes an initialization hint; JSON, help, and SARIF-oriented output stay quiet. Run opensip init when

you want to initialize the project: persist the config, create example files,

refresh agent guidance, and make <project>/opensip-cli/.runtime/ the

project-local runtime location. init transitions a zero-config project to

an initialized project; it is not a third storage location. The project

runtime remains local and gitignored. It contains rebuildable caches and

catalogs alongside retained evidence that is lost when the runtime is removed.

Detects the project's primary language(s) from filesystem markers and writes one

directory tree per registered tool — each tool owns its own example files and

config block; init itself hardcodes no tool. With the bundled fitness +

simulation tools, a project gets:

<cwd>/opensip-cli.config.yml                              # TRACKED
<cwd>/opensip-cli/fit/checks/example-check.mjs            # TRACKED
<cwd>/opensip-cli/fit/recipes/example-recipe.mjs          # TRACKED
<cwd>/opensip-cli/sim/scenarios/example-scenario.mjs      # TRACKED
<cwd>/opensip-cli/sim/recipes/example-recipe.mjs          # TRACKED

Plus appends opensip-cli/.runtime/ to <cwd>/.gitignore, and manages an

OpenSIP MCP-first guidance block for AI agents. AGENTS.md is created when

absent. Existing AGENTS.md, CLAUDE.md, .github/copilot-instructions.md,

.cursorrules, .cursor/rules/opensip.mdc (when .cursor/rules/ exists), and

.windsurfrules are updated in place through a delimited managed block that

preserves custom content.

The scaffolded set equals the registered set: a tool that declares no project

layout (e.g. graph) writes no directory, and a tool installed after init

scaffolds its examples on the next opensip init --keep. If a bundled tool

fails to load, init scaffolds fewer directories and emits a loud

cli.tool.expected_bundled_absent diagnostic so the gap is visible.

The scaffold output is loose .mjs files — the lightest-weight starting point. When a pack outgrows loose files (substantial helpers, tests, more than a dozen checks/scenarios), the customer graduates opensip-cli/<domain>/ to a workspace npm package. Fit packs add the fit-pack marker plus target-domain epoch and load through marker discovery; sim scenario packs use the <scope>/scenarios-* package-name pattern or an explicit plugins.scenarioPackages: pin. See 50-extend/01-plugin-authoring.md for the graduation path.

| Flag | Effect |

|---|---|

| --language <list> | Language list (typescript, rust, …). Repeatable (--language ts --language rust) or comma-separated (--language ts,rust). Overrides detection. |

| --keep | Re-scaffold examples; preserve an existing root config and any custom files in opensip-cli/. |

| --remove | Delete opensip-cli/ entirely, then scaffold fresh. |

| --cwd <path> | Target directory (default: process.cwd()). |

| --json | Emit a structured JSON result instead of the human-readable summary. |

| --debug | Enable debug-level logging. |

Optional tools after pristine init

After init successfully creates a pristine project, the human-readable result

adds an Optional tools for this project (not installed) section after the

existing Try it commands. The section appears only when the result has

type: "init", created: true, state: "pristine", and at least one selected

language.

OpenSIP projects the generated first-party adapter catalog in its stable order.

For a multi-language project, it forms one union across all selected languages;

language-agnostic adapters (languages: []) are included once. Adapters already

present in the effective global or project inventory are omitted. If every

matching adapter is installed, the section is absent.

Each row shows the exact existing global install command. A [networked] marker

describes the adapter's catalog network posture; it does not perform or authorize

network access. Use the second form for a repository-local installation:

opensip tools install @opensip-cli/tool-ruff
opensip tools install @opensip-cli/tool-ruff --project

These rows are advice only. init does not prompt, install a package, execute an

adapter, or change tool trust/configuration. Repeat init, --keep, --remove,

partial-state recovery, guidance refresh, and refusal/error paths do not include

the section. Use

opensip tools list --available

to inspect the full catalog at any time.

With --json, the same recommendations appear under

CommandOutcome.data.optionalTools. The field is absent when the result is

ineligible or no relevant uninstalled adapter remains; it is not emitted as an

empty list. Each row has six fields: id, pkg, network, languages,

installCommand, and projectInstallCommand.

{
  "kind": "init",
  "status": "ok",
  "exitCode": 0,
  "data": {
    "type": "init",
    "created": true,
    "path": "/repo/opensip-cli.config.yml",
    "cwd": "/repo",
    "configFilename": "opensip-cli.config.yml",
    "state": "pristine",
    "languages": ["python"],
    "optionalTools": [
      {
        "id": "ruff",
        "pkg": "@opensip-cli/tool-ruff",
        "network": "local-only",
        "languages": ["python"],
        "installCommand": "opensip tools install @opensip-cli/tool-ruff",
        "projectInstallCommand": "opensip tools install @opensip-cli/tool-ruff --project"
      }
    ]
  }
}

Partial-state handling

After parsing flags init classifies the working directory into one of four states:

| State | opensip-cli.config.yml | opensip-cli/ (excluding .runtime/) | Default | --keep | --remove |

|---|---|---|---|---|---|

| pristine | absent | absent | scaffold | scaffold | scaffold |

| fully-initialized | present | present | refresh .gitignore + managed agent guidance | preserve config; re-scaffold examples; preserve custom files | rm -rf opensip-cli/; scaffold |

| partial-config-only | present | absent | refresh .gitignore + managed agent guidance | preserve config; scaffold the dir | scaffold the dir |

| partial-dir-only | absent | present | exit 2, partial-state error | preserve custom; write YAML | rm -rf opensip-cli/; write YAML; scaffold |

--keep and --remove are mutually exclusive. Use --remove when you want to

replace existing scaffolds.

Each pre-existing file under opensip-cli/ is classified as:

detection set (e.g. example-check-rust.mjs after re-running with

--language typescript). Preserved by --keep.

The InitResult JSON shape carries state, preExistingFiles[],

agentGuidance, and either created: true for scaffolding or refreshed: true

for the non-destructive repeat-init path. On refusal it carries

partialStateError so machine consumers can branch.

Detection markers:

| Marker | Language |

|---|---|

| Cargo.toml | rust |

| pyproject.toml, setup.py, requirements.txt | python |

| go.mod | go |

| pom.xml, build.gradle | java |

| CMakeLists.txt, Makefile | cpp |

| tsconfig.json (or package.json alone with no other marker) | typescript |

Ambiguous detection (multiple markers, no --language) exits 2 with a prompt to specify --language.

Exit codes: 0 (created or refreshed), 2 (ambiguous detection / parse error /

unsafe partial state).


config — validate, migrate, and export project config

CLI-owned: packages/cli/src/commands/host-subcommand-config.ts. Operator workflows over the project opensip-cli.config.yml using the same composed schema the dispatcher validates at pre-action time. Distinct from opensip configure, which manages the user-global OpenSIP Cloud API key.

opensip config validate [--config <path>] [--json] [--cwd <path>]
opensip config schema [--json] [--out <path>] [--cwd <path>]
opensip config migrate [--config <path>] [--dry-run] [--check] [--json] [--cwd <path>]

| Subcommand | Flags | Effect |

|---|---|---|

| validate | --json | Validate the effective project config; on success emit data.type: "config-validate"; invalid config exits 2 (CONFIGURATION_ERROR). |

| schema | --json, --out <path> | Export the composed JSON Schema (data.type: "config-schema"); --out writes the schema file (rejects directory targets). |

| migrate | --json, --dry-run, --check | Normalize the project config to the current schema version (data.type: "config-migrate"). --dry-run reports without writing. --check also avoids writes and exits 2 when a migration would be applied. |

These subcommands use the same config path resolution as tool commands. validate

and schema compose declarations from the live tool registry and admitted

manifests — the same source as

config-and-capabilities.ts.

migrate edits only opensip-cli.config.yml; it does not open the SQLite datastore

or load tool runtimes.


configure — manage user-level settings

CLI-owned: packages/cli/src/commands/configure.ts. Interactive — writes the OpenSIP Cloud API key to ~/.opensip-cli/config.yml and verifies it best-effort against the cloud entitlement endpoint.

opensip configure

Prompts:

The user-level config is shared across every project on the machine. opensip fit --report-to <url> uses the configured key by default unless --api-key overrides it.


Agent run filters (fit, graph, sim)

fit, graph, and sim primary runs support the same agent filter flags on

--json (and sessions show replay uses the same engine). Filters are a

presentation concern only — gate, egress, and session persistence use the

unfiltered envelope (ADR-0085).

| Token | Effect |

|---|---|

| errors-only | Keep critical + high severity signals |

| warnings-only | Keep medium + low severity signals |

| category:<name> | Match signal.category |

| source:<tool> | Match signal source namespace |

| file:<prefix> | Match filePath prefix |

| high-impact | Graph-only: signals with metadata.highImpact |

| top:<n> | Cap count after other predicates |

When any filter is active (or --raw is set), the emitted JSON includes

filtersApplied, originalSignalCount, and returnedSignalCount alongside a

filtered envelope. Unfiltered --json remains byte-compatible with prior

releases.


agent-catalog — structured discovery surface for agents

CLI-owned. A machine-first command that emits a self-describing catalog of the most useful commands, flags, and patterns for AI agents, with emphasis on the sessions/history surface and the agent ergonomics added for historical result inspection.

opensip agent-catalog
opensip agent-catalog --json

The --json output is designed to be consumed directly by agents. It contains:

The --json output is nested under data.catalog in the standard CommandOutcome wrapper.

This is the recommended starting point for any agent that needs to discover how to drive OpenSIP programmatically or inspect prior runs.

Same catalog over the CLI and MCP

opensip agent-catalog --json and the MCP get_agent_catalog tool return the same common catalog body for the same invocation and project — identical entry points, common patterns, output shapes, notes, reservedNames, bounded projectContext.targetConventions, and hostSupport. A single pure assembler in @opensip-cli/contracts produces that body for both transports (ADR-0166). Assembling the catalog is read-only on either transport: it builds no graph, runs no analysis, invokes no Git or tests, and creates no session.

The MCP response adds exactly one extra top-level object the CLI never emits — mcp — carrying live connector diagnosis (version, surfaceEpoch, toolNames, toolCount, mutationPosture, and project.root/project.scope). Treat it as connector identity only: when surfaceEpoch, toolNames, or version no longer match the cached inventory, reconnect the MCP process — refresh_graph rebuilds graph evidence and never repairs a stale connector inventory (ADR-0153). To compare the two surfaces, drop only the top-level mcp object; the rest is byte-identical to the CLI's data.catalog.


policy status, policy explain, policy audit, and policy trust/untrust — local trust policy

CLI-owned. Reads the effective local trust-policy plane used by bootstrap and

host enforcement points. The policy decision point is offline and deterministic:

it layers builtin defaults, user config, project config, and the optional

project-local org cache. It never calls OpenSIP Cloud, npm, GitHub, or a model.

opensip policy status
opensip policy status --json
opensip policy explain installed-tool:audit-sec --action load --json
opensip policy explain baseline:fit --action baseline-save --json
opensip policy audit --json --limit 50
opensip policy audit --out opensip-policy-audit.json
opensip policy trust @acme/checks-internal
opensip policy untrust @acme/checks-internal

| Subcommand | Effect |

|---|---|

| status | Shows mode, ci, source tiers, org-cache state, active exceptions, and current capability-pack grants. |

| explain <subject> | Evaluates one subject/action pair against the current policy and returns reasons plus matched exception ids. |

| audit | Lists local policy decisions persisted in policy_audit_events; --out writes the same JSON result to a file. |

| trust <package> | Grants operator trust to a capability pack on the user-level global config. The grant records the exact package id bound to the pack's provenance (its opensipTools manifest hash) resolved at grant time — re-run after the pack legitimately changes (ADR-0171). |

| untrust <package> | Revokes a capability-pack grant from the user-level global config. |

Subject strings are exact kind:id pairs. Supported kinds are

installed-tool, project-local-tool, user-global-tool, capability-pack,

fitness.disabledChecks, baseline, and runtime-exclude. Supported actions

are load, install, disable-check, runtime-exclude, and

baseline-save.

Strict mode denies unverified non-bundled executable loads/installs and

gate-weakening actions (fitness.disabledChecks, baseline saves) unless an

unexpired exact exception applies. Default mode preserves existing OSS behavior

and records conditioned decisions where provenance is missing or failed.

For capability-pack:... load decisions, external packs are admitted only by

a matching user-level trust grant (policy trust); a denial cites the missing

or provenance-mismatched grant and the exact policy trust command to run.

Allowed decisions also carry a resource decision: bundled first-party packs may

run in-host, while non-bundled packs run through the capability worker bridge,

whose resource guard is advisory defense-in-depth — admission is the enforced

boundary

(ADR-0171).


sessions list, sessions show, and sessions purge — manage session records

CLI-owned. Reads and replays session rows in the active local SQLite datastore

via SessionRepo: the managed user cache before initialization, or

<project>/opensip-cli/.runtime/datastore.sqlite afterward. list, show,

and purge all support zero-config first runs against that active local

evidence store. purge deletes Tool Session rows only (the FK cascade drops

each session's tool-payload row); parent Runs, reports, graph catalogs,

baselines, and other runtime state are preserved. Full project runtime removal

is opensip uninstall --project, not file-path deletion.

Primary surface for inspecting prior runs (especially from agents). See agent-catalog above for the recommended discovery entry point.

opensip sessions list
opensip sessions list --json --summary-only
opensip sessions show <session-id>
opensip sessions show latest --tool fit
opensip sessions show latest --tool fit --json --filter errors-only --filter top:20
opensip sessions show latest --tool graph --json --raw
opensip sessions purge
opensip sessions purge --older-than 7
opensip sessions purge -y

| Subcommand | Flag | Effect |

|---|---|---|

| list | (none) | List every stored session, newest first. |

| list | --summary-only | Omit heavy per-session tool payloads (agent-friendly "menu" mode). The lightweight summary and showCommand hints remain. |

| show | <ref> (positional) | Replay a stored session by id, or latest (requires --tool). Supports relative refs such as previous / latest-N. |

| show | --tool <name> | Required for latest; an optional sanity check for an explicit id. Accepts any registered tool id, but replay output requires that the tool contributed a sessionReplay hook. |

| show | --json | Emit the replayed session (projected SignalEnvelope under the result). |

| show | --filter <type> | Filter the replayed signals (repeatable). Supported values: errors-only (high severity), warnings-only (medium), top:<n>. Composable, e.g. --filter errors-only --filter top:20. Adds filtersApplied, originalSignalCount, and returnedSignalCount to the machine output. |

| show | --raw | With --json: emit only the inner payload (session + envelope + metadata) without the outer CommandOutcome wrapper. Ideal for token-sensitive agents. |

| purge | --older-than <days> | Only delete sessions older than N days. Default: delete all. |

| purge | -y, --yes | Skip the confirmation prompt. |

Session replay. sessions show reconstructs a past run's output from the

stored payload when that tool contributes a sessionReplay projection. The

replay-capable first-party tools are fit, graph, and sim today; they

decode the opaque payload back into a SignalEnvelope. The replay fidelity is

always projection — it is rebuilt from persisted findings, not a re-execution.

Each replay-capable run command also accepts an inline --show <session> flag

(fit --show latest, graph --show <id>, sim --show latest) as a shorthand

for the same replay scoped to that tool. A

missing session, wrong tool, or undecodable payload returns a structured error

(reason/code: not-found, wrong-tool, ambiguous-latest, decode-error)

and exit 2.

The --filter and --raw options (plus --summary-only on list) were added specifically to make historical result inspection efficient for AI agents while leaving all human-readable tables and banners unchanged.

See also: 80-implementation/03-session-and-persistence.md.


repair preview and repair apply — safe stored-session repair actions

CLI-owned. Replays a stored session, selects one signal, selects one

signal.repair.actions[] entry, and either previews the deterministic file

change or applies it with host-owned safety checks.

opensip repair preview latest --tool fit --signal index:0 --action replace-ts-ignore
opensip repair preview <session-id> --tool fit --signal fingerprint:<fingerprint> --action remove-unused-dependency --json
opensip repair apply latest --tool fit --signal id:<signal-id> --action replace-ts-ignore
opensip repair apply latest --tool fit --signal index:0 --action replace-ts-ignore --force
opensip repair apply latest --tool fit --signal index:0 --action replace-ts-ignore --verify --json

| Subcommand | Flag | Effect |

|---|---|---|

| preview | <ref> | Session id, relative session ref, or latest with --tool. |

| preview / apply | --tool <name> | Required. Resolves the stored session's tool identity. |

| preview / apply | --signal <selector> | Required. id:<value>, fingerprint:<value>, or index:<zero-based>. Fingerprints must match one signal. |

| preview / apply | --action <id> | Required for apply; accepted on preview. Selects the action id from signal.repair.actions[]. |

| apply | --force | Bypass the git clean-worktree check. Stale file hashes and unsafe paths are still refused. |

| apply | --verify | After a successful or already-applied repair, rerun deterministic verification. Fitness repairs run fit --check <rule> --changed --include-impacted --json and preserve impact-trust/fallback facts. |

| preview / apply | --json | Emit repair-preview, repair-apply, or repair-apply-verify as a CommandOutcome result. |

preview never mutates files. apply refuses advisory actions, unsupported

action ids, paths outside the project root, symlink escapes, files above 1 MiB,

dirty git targets unless --force is passed, and files whose current hash no

longer matches the preview plan. The first supported action ids are

replace-ts-ignore and remove-unused-dependency; normalize-generated-config

is reserved and currently refused.

With --verify, the result distinguishes verified, partial, unverified,

and skipped. Agents must only claim a repair was verified when

data.verification.status is verified; partial/unverified/skipped results

mean the patch was not proven clean.

MCP exposes the same apply-verify flow through repair_apply_verify only when

the server is started with explicit mutation enabled:

opensip mcp --allow-mutations
# or
OPENSIP_MCP_ALLOW_MUTATIONS=1 opensip mcp

The default MCP server remains read-only and does not register mutating tools.


audit — canonical changed-code review

Host-owned. opensip audit always runs the curated built-in audit definition

through the same concrete executor, suite orchestrator, output seams, Run

ledger, sessions, and exit policy as generic suite run. It is not a Tool, and

the suite name audit is reserved (ADR-0159): config validation rejects a

configured suites.audit, so nothing can shadow the canonical review.

opensip audit
opensip audit --files src/server.ts --json
opensip audit --since main --json
opensip audit --full
opensip audit --open

| Flag | Type | Default | Effect |

|---|---|---|---|

| --cwd <path> | path | current directory | Shared project root for all audit steps. |

| --config <path> | path | discovered config | Override project-config discovery without changing the curated audit definition. |

| --changed | bool | implicit when no selector | Use Git changed-file selection for compatible steps. |

| --since <ref> | string | — | Use a Git diff base for compatible steps. |

| --files <path> | repeatable | [] | Use explicit project-relative changed files; works without Git. |

| --full | bool | false | Run the whole repo. Conflicts with --changed, --since, and --files. |

| --json | bool | false | Emit the ordinary SuiteRunResult in a host CommandOutcome. |

| --quiet | bool | false | Use compact human output. |

| --verbose | bool | false | Include detailed review-brief and per-step human tables. |

| --debug | bool | false | Enable debug diagnostics. |

| --open | bool | false | After persistence, generate and open the selected Change Impact report for an interactive human. |

With no explicit selector, Git repositories use changed scope. If Git scope

cannot be resolved, audit falls back once to full scope and records a fallback

notice/trust degradation; it never silently labels that run changed-only.

Selectors propagate only to steps whose command declares the corresponding

option. The command exits with the same numeric worst-step policy as `suite

run`.

--json returns data.scope, aggregate counts, step verdicts and

steps[].verification, and the host-owned reviewBrief. It also returns

optional data.runId: the authoritative persisted parent Run ID when ledger

persistence succeeded. Absence means persistence was unavailable;

suiteRunId remains legacy correlation identity and is not a replacement.

--open is presentation-only and best effort. JSON, CI, non-TTY, and

remote-shell execution suppresses browser launch. Report generation or launch

failure cannot revise the completed audit result or exit code. Agents and CI

should use --json, never depend on --open.

For project-defined review workflows, use another configured name such as

audit-custom with opensip suite run audit-custom. See

ADR-0155.


suite run/list/add — run configured multi-tool suites

CLI-owned. A suite runs several existing tool commands in one project scope.

The suite invocation owns --cwd, --config, targeting, JSON/report flags, and

cloud/report delivery; individual steps may only set tool-behavior options.

The built-in agent-context preset is the before-edit evidence workflow. It

returns contextManifest with ready | degraded | unavailable, exact parent

Run/RunStep references, source identities, and graph-owned snapshot pointers.

It creates no findings or ReviewBrief and does not execute selected tests.

Inventory and graph are required; selection is required when explicit files are

present. The agent-context name is reserved, so a configured suite cannot

shadow or acquire the host aggregation workflow.

opensip suite list
opensip suite run agent-context --files src/server.ts --json
opensip suite run audit
opensip suite run audit --full
opensip suite run audit --since main --json
opensip suite run audit --files src/server.ts --json
opensip suite run security
opensip suite add security --tool fitness --command fitness --arg recipe=security

| Subcommand | Flag | Effect |

|---|---|---|

| run | <name> | Run every step in suites.<name>.steps and exit with the worst step exit code. |

| run | --cwd <path> | Shared project root for every step. |

| run | --config <path> | Override the discovered opensip-cli.config.yml for the shared suite run scope. |

| run | --json | Emit the suite summary as JSON, including scope, additive aggregate counts, per-step verdict/evidence readiness, and either a host-owned reviewBrief or built-in agent-context manifest. Step output still flows through each step's own output seams. |

| run | --changed | Propagate changed-file selection to compatible steps. Built-in audit defaults to changed semantics when no selector is supplied and git scope resolves. |

| run | --since <ref> | Propagate a git diff base to compatible changed-file steps. |

| run | --files <path> | Propagate explicit changed files to compatible steps. Repeat for multiple files. |

| run | --full | Run the whole repo. This disables the built-in audit changed-scope default and conflicts with --changed, --since, and --files. |

| run | --open | After the suite completes, generate and open the HTML report when browser launch is allowed. For built-in audit, selects the Change Impact surface for the persisted parent Run (same path as top-level opensip audit --open). Other suite names open the ordinary report without inventing a Change Impact selection. JSON, CI, non-TTY, and remote-shell execution suppress browser launch; report failure never changes the suite exit code. |

| list | --json | List configured suites with resolved tool UUIDs and commands. |

| add | <name> | Append a step to suites.<name>.steps in opensip-cli.config.yml. |

| add | --tool <name-or-uuid> | Resolve a loaded tool by display name or stable UUID; the YAML stores the UUID. |

| add | --command <name> | Tool command to run for the step. |

| add | --arg <key=value> | Add a tool option to the step. Repeat for multiple options. |

Suite runs persist a host-owned parent Run and ordered RunSteps. Current JSON

returns optional runId, the authoritative parent identity when persistence

succeeds; each step's optional sessionId is the durable link to tool detail.

suiteRunId and suiteName remain legacy correlation fields on stored sessions.

Suites are intentionally one-scope: use separate CLI invocations when different

tools must scan different roots or target sets.

audit is also a built-in suite preset for generic suite resolution. It runs fitness agent-risk,

graph impact, and high-confidence YAGNI reduction checks through the same suite

plane as user-authored suites. In a git repo, opensip suite run audit runs

changed-scope by default and prints a line such as `Scope: changed (working

tree, 14 files). Use --full` for a whole-repo run; outside git, the default

falls back to full scope with one suite-level notice. The suite name audit

is reserved (ADR-0159): config validation rejects a configured suites.audit,

so suite run audit always runs the same built-in preset as top-level

opensip audit — use another name (for example audit-custom) for a custom

workflow. Suite-level selectors (--changed,

--since, --files) reach only steps whose command declares the matching

option; per-step args still override propagated values.

suite run --json keeps the original step fields (tool, stableId,

command, exitCode, durationMs, error) and additively includes

data.scope (mode, source, optional ref, changedFiles, and notice) plus

data.aggregate (steps, passed, failed, faulted, errors, warnings).

Each data.steps[].verdict is present only when that step emitted a

SignalEnvelope; it carries counts only (passed, errors, warnings,

findings) so suite summaries do not leak signal messages, file paths, symbols,

or match content. Steps may also include verification impact-trust metadata

when a step emitted it.

Current suite JSON also includes data.reviewBrief, a versioned v1 aggregate

that ranks current signals into one review verdict. The brief is intentionally a

bounded projection: topRisks[] and newFindings[] carry source tool, rule,

message, location, severity, optional repair metadata, and signalRef

(tool, suiteRunId, stepIndex, runId, fingerprint, signalIndex) so an

agent or CI job can replay the original evidence. degraded[] records missing

envelopes, faulted steps, missing fingerprints, or failing verdicts without

signals. baselineDelta.available is false until a suite step exposes

baseline-compare evidence; in that case findings are not labeled new unless the

source signal explicitly carries baseline state. Suite-level brief SARIF is not

emitted in this phase; use the source tools' existing SARIF output.

GitHub Action

The root GitHub Action opensip-ai/opensip-cli@v1 wraps the same suite contract

for OSS CI. By default its changed input is true, so it runs

suite run audit --changed --json, writes a

review brief JSON file, emits workflow annotations, and exposes verdict,

issues, new-issues, brief, sarif, and degraded outputs. Optional

comment: true posts or updates one sticky PR comment when pull-request

permissions are available. Optional sarif: true writes SARIF 2.1.0 derived from

the bounded review brief; source-tool SARIF from fit and graph remains the

high-fidelity CLI-owned path.

See ADR-0100

and ADR-0110.

See ADR-0111 for the

built-in audit preset decision and ADR-0129

for the changed-scope default, --full, and fallback semantics.

See ADR-0143 for Run

identity, ADR-0155 for

the reserved root command, and

ADR-0159

for built-in suite-name reservation.

See also: 03-configuration.md#suites,

04-json-output-schema.md#suite-run-results.


<tool> plugin add/remove/list/sync — manage a tool's extension packs

CLI-owned: packages/cli/src/commands/plugin.ts.

The pack-management plugin group is mounted under each pack-supporting tool primary — the domain is bound from the tool, so there is no top-level opensip plugin and no --domain flag. fit and sim support packs; graph does not (its extensibility is language adapters), so it has no plugin group.

opensip fit plugin list
opensip fit plugin add <pkg>
opensip fit plugin remove <pkg>
opensip fit plugin sync

opensip sim plugin list
opensip sim plugin add <pkg>
opensip sim plugin remove <pkg>
opensip sim plugin sync

| Flag | Subcommands | Effect |

|---|---|---|

| --cwd <path> | all | Project root. Default: process.cwd(). |

| --json | all | Structured output. |

Extension packs are project-committed and always project-local: add writes to .runtime/plugins/<domain>/node_modules/<pkg>/ (where <domain> is the tool the subcommand hangs off of — fit or sim) and appends to plugins.<domain>: in opensip-cli.config.yml so teammates reproduce them via sync. Fit packs declare kind: "fit-pack"; sim packs are listed under plugins.sim: / plugins.scenarioPackages: or discovered by the scenarios-* package-name pattern. remove is the inverse. There is no user-global pack path, so there is no --project flag.

<tool> plugin list shows that tool's packs (installed ∩ config-listed) for its own domain only — it does not list whole Tool plugins. <tool> plugin sync installs everything declared under that tool's plugins.<domain>: — the post-clone bootstrap.

Whole Tool plugins (a kind: "tool" package contributing a whole subcommand) are installed/uninstalled with the tools command groupopensip tools install <spec> — NOT here.

See also: 80-implementation/02-plugin-loader.md.


tools list/validate/install/uninstall/data-purge — manage whole Tool plugins

The documented surface for whole Tool plugins (kind: "tool" packages that contribute entire subcommands). See the full reference: 12-tools-command.md.

opensip tools list [--global|--project]
opensip tools list --available [--lang <language>]
opensip tools validate <spec>
opensip tools install <spec> [--global|--project]
opensip tools uninstall <name-or-id> [--global|--project] [--purge-data]
opensip tools data-purge <tool-id>

validate runs the same admission pipeline the CLI's own bootstrap admits tools through — one validator, shared. install is atomic: stage → validate → policy admission → activate; a failed or policy-denied install leaves nothing behind. uninstall never deletes project SQLite data; data-purge deletes rows (sessions, baselines, tool state), never tables. validate and install execute the package's module — see the trust notes in the full reference.

tools list also reports the current local policy outcome for each row when a

run scope has resolved policy.

tools list --available — discover installable adapters

By default tools list shows the effective/installed set. --available

instead lists the opt-in first-party External Tool Adapter catalog — every

@opensip-cli/tool-* scanner adapter you could install — with its covered

languages, network posture, and an installed / not-installed marker, plus the

opensip tools install <package> hint. The catalog is bundled into the CLI (a

build-time projection of each adapter's manifest), so discovery works offline and

before anything is installed.

--lang <language> filters the catalog to adapters that cover that language and

implies --available. The value is canonicalized through the language

registry, so c++, cxx, and cpp are equivalent. Polyglot / language-agnostic

adapters (secret, dependency, and SBOM scanners — languages: []) always match

every --lang filter.

opensip tools list --available                 # the whole opt-in catalog
opensip tools list --available --lang c++      # C/C++ coverage (+ polyglot adapters)
opensip tools list --lang python --json        # machine output; --lang implies --available

The catalog's language coverage is declared on each adapter (languages in its

manifest) — the single source of truth the --lang filter, the bundled catalog,

and this reference all derive from.


External tool adapters (opt-in)

Tool-owned: @opensip-cli/external-tool-adapter + the opt-in adapter packages (ADR-0090 / ADR-0091 / ADR-0092).

An External Tool Adapter wraps a user-installed CLI scanner (gitleaks,

semgrep, ruff, golangci-lint, osv-scanner, trivy, etc.) as a

first-class OpenSIP Tool: it runs the scanner as a subprocess, normalizes its

native output to Signals, and feeds the same envelope/session/gate/egress path

as fit and graph. Adapters are opt-in and not bundled — install the one

you want. To author your own, see External tool adapters.

The full user flow:

opensip tools install @opensip-cli/tool-gitleaks   # opt-in, not bundled
opensip gitleaks doctor                            # binary found? version? posture? ready?
opensip gitleaks                                   # scan → normalize → store → signals
opensip gitleaks --json --gate-compare             # envelope + net-new ratchet

opensip tools install validates the package, installs the validated bytes, and records a managed trust entry for the selected scope. Ambient node_modules Tool packages remain deny-by-default; OPENSIP_CLI_ALLOW_INSTALLED_TOOLS is an override for incident response/manual experiments. The same OPENSIP_<TOOL>_BIN binary override (see Binary resolution) applies to every adapter.

Shipped adapter commands

| Command | Wraps | Scans | Posture |

|---------|-------|-------|---------|

| opensip gitleaks (alias secrets) | gitleaks | Committed secrets in the working tree | local-only |

| opensip osv-scanner (alias osv) | osv-scanner | Dependency vulnerabilities (lockfiles) | local-only |

| opensip trivy | trivy | Vulnerabilities + misconfigurations (filesystem) | local-only |

| opensip semgrep | semgrep | SAST / policy rules | networked when using Semgrep auto config |

| opensip ast-grep (alias sg) | ast-grep | Structural search rules | local-only |

| opensip ruff | ruff | Python lint diagnostics | local-only |

| opensip golangci-lint (alias golangci) | golangci-lint | Go lint aggregation | local-only |

| opensip govulncheck | govulncheck | Go vulnerabilities | networked |

| opensip cargo-deny | cargo-deny | Rust dependency policy | networked |

| opensip bandit | bandit | Python security issues | local-only |

| opensip pip-audit | pip-audit | Python dependency vulnerabilities | networked |

| opensip cargo-clippy (alias clippy) | cargo clippy | Rust lint diagnostics | local-only |

| opensip spotbugs | spotbugs | Java bytecode issues | local-only |

| opensip pmd | pmd | Java source rules | local-only |

| opensip dependency-check (alias owasp-dependency-check) | dependency-check | Dependency vulnerabilities | local-only with a pre-populated DB |

| opensip cppcheck | cppcheck | C/C++ static analysis | local-only |

Each adapter mounts three commands — the primary scan (opensip <tool>), plus nested opensip <tool> doctor and opensip <tool> version:

Local-binary + DB-cache prerequisites (surfaced by doctor):

binaries.<tool>.path / OPENSIP_<TOOL>_BIN.

.ast-grep.yml; SpotBugs requires compiled Java classes; PMD uses the Java

quickstart ruleset by default.

pip-audit may reach advisory/rule services; Trivy and Dependency-Check run

local-only only when their DB caches are already populated.

The gate ratchet, JSON, and the artifact store

Adapters inherit the host-owned baseline ratchet (ADR-0036) verbatim, the same as fit/graph:

The scanner's raw native report persists under <project>/opensip-cli/.runtime/artifacts/<tool>/<runId>/ (host-owned, ADR-0080/ADR-0091): 0600 (owner-only), gitignored, and never egressed — only normalized, redacted Signals leave the process. The host keeps the most-recent run-dirs per tool and prunes the rest after each write, governed by cli.artifacts.keep in opensip-cli.config.yml (default 10; 0 disables pruning). Secret-scanner findings are redacted — only a short non-reversible preview reaches the signal, never the matched credential.

Binary resolution

Resolution is deterministic, first hit wins, and never fetches a binary: an OPENSIP_<TOOL>_BIN env var — OPENSIP_GITLEAKS_BIN, OPENSIP_OSV_SCANNER_BIN, OPENSIP_TRIVY_BIN — then a config-file pin binaries.<tool>.path, then the system PATH:

# env override (highest):
OPENSIP_GITLEAKS_BIN=/opt/homebrew/bin/gitleaks opensip gitleaks doctor
# config-file pin (opensip-cli.config.yml) — every adapter claims its namespace by default:
gitleaks:
  binaries:
    gitleaks:
      path: /opt/homebrew/bin/gitleaks

A missing binary yields a doctor install hint, never an install. Both the env override and the binaries.<tool>.path config pin work out of the box for shipped adapters — the substrate claims each adapter's config namespace by default (no per-adapter config schema required).


Internal and machine-facing commands

These commands are mounted through the same CommandSpec system but are primarily for workers, export jobs, or project automation rather than daily interactive use:

| Command | Owner | Purpose |

|---|---|---|

| opensip graph export --format catalog | graph | Emit a graph catalog artifact for parent ingestion. Uses --catalog-output, tenant/repo/run identity flags, --cwd, --language, and --resolution. |

| opensip graph export --format sarif | graph | Emit graph findings as SARIF for a stored run. Uses --output-sarif, tenant/repo/run identity flags, --cwd, --language, and --resolution. |

| opensip graph-equivalence-check | graph | Contributor guardrail for exact vs. sharded graph-engine equivalence. Uses --cwd, --budget, and --update-budget. |

| opensip graph-run-worker | graph | Internal worker for memory-isolated graph runs. |

| opensip graph-shard-worker <specPath> | graph | Internal worker for sharded catalog builds. |

| opensip fit-run-worker | fitness | Internal worker for memory-isolated fitness runs. |

| opensip sim-run-worker | simulation | Internal worker for memory-isolated simulation runs. |

| opensip __capability-pack-worker <specPath> | host | Hidden worker for resource-isolated external capability packs. |

The worker commands are not the public authoring surface; they exist so parent commands can fan out safely while preserving the same tool-owned execution contracts.


completion — print shell completion script

CLI-owned: packages/cli/src/commands/completion.ts.

opensip completion bash
opensip completion zsh
opensip completion fish

<shell> is required — there's no default.

Pipe to your shell's completion config:

opensip completion zsh > ~/.opensip-cli-completion.zsh
echo "source ~/.opensip-cli-completion.zsh" >> ~/.zshrc

The emitted script is static (your shell sources it once), but its contents are derived from the live CommandSpecs at generation time — the same specs the runtime mounts (assembleCompletionInventory in packages/cli/src/commands/completion.ts). Subcommands and per-command flags come from the populated tool registry plus the host commands, so the script can't drift from the real command surface; a flag-parity test enforces it. Because the inventory is sourced from the runtime registry, discovered third-party tool subcommands and flags are included too, not just the built-in fit/sim/graph families.


uninstall — remove opensip-cli state

CLI-owned: packages/cli/src/commands/uninstall.ts.

opensip uninstall                       # remove ~/.opensip-cli/
opensip uninstall --user                # explicitly remove ~/.opensip-cli/
opensip uninstall --project             # remove project runtime state at cwd
opensip uninstall --project /path/repo  # remove project runtime state at <path>
opensip uninstall --project --purge     # also remove authored content + config
opensip uninstall --dry-run             # print targets, take no action
opensip uninstall --yes                 # skip confirmation prompt

Two modes:

| Mode | Targets removed | When to use |

|---|---|---|

| Default / --user | All of ~/.opensip-cli/: user config, global tools/plugins, and every managed user-cache runtime/database | Remove all per-user OpenSIP CLI state. |

| --project [path] | <path>/opensip-cli/.runtime/ and the matching zero-config user-cache runtime, when present | Remove generated local runtime state for one repo while preserving authored checks, recipes, scenarios, and config. Session/log history, baselines, tool state, and other retained evidence in those runtimes are lost. |

| --project [path] --purge | The same two runtime targets, plus <path>/opensip-cli/ (authored content included) and <path>/opensip-cli.config.yml | Fully disengage from opensip-cli in one repo. Destructive if custom checks/recipes are not committed. |

| Flag | Effect |

|---|---|

| --user | Explicitly choose default user mode. Mutually exclusive with --project. |

| --project [path] | Switch to project mode. Path defaults to cwd. |

| --purge | In project mode, also remove user-authored content under opensip-cli/ and opensip-cli.config.yml. |

| --yes, -y | Skip the [y/N] confirmation prompt. |

| --dry-run | Enumerate targets and total size; make no changes. |

Both modes:

State contract enforced by code: ~/.opensip-cli/ is the user-level root. It

can contain config.yml, update-state.json, managed zero-config runtimes under

cache/ephemeral/, npm-installed global Tool plugins under plugins/, and

global authored Tool sidecars under tools/ (see

paths.ts). Runtime persistence and

logging stay inside the selected per-project runtime rather than writing loose

database or log files at the user root. The default uninstall removes the

whole user-level root.


Upgrading

opensip-cli updates through the same installer used for first-time setup:

curl -fsSL https://opensip.ai/cli/install.sh | bash

The CLI checks npm for a newer version at most once an hour on TTY sessions

(non-blocking product update I/O, not telemetry — see

ADR-0073). The fetch

is rate-limited to that interval, but once a newer version is found the notice

persists on every run until you upgrade — so it's never lost if you miss it

once — and disappears on its own the run after you update. When an update is

available it surfaces without nagging:

Silence the check entirely with OPENSIP_NO_UPDATE=1 (or the conventional NO_UPDATE_NOTIFIER=1). It's also skipped automatically when CI is set or stdout isn't a TTY. Check your installed version any time with opensip --version.

If you installed via a version manager (volta, asdf) or Homebrew, use that tool's upgrade path instead of the curl installer above.


What's next

MCP compact audit surface: see Connect MCP clients and ADR-0152..0154.