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:
opensip <tool> --versionprints the tool's version (<verb> <semver>,
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.
--config <path>(the explicitopensip-cli.config.ymloverride) is now on
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, andgraph-entry-points. The two filtered views were folded into the unifiedgraphoutput; all three data slices (rules, entry points, catalog summary) are now reachable from the singlegraphinvocation.
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) andduplicate-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
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
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:
scaffolded— content matches a current-template byte-for-byte.stale-scaffolded— was scaffolded for a language not in the current
detection set (e.g. example-check-rust.mjs after re-running with
--language typescript). Preserved by --keep.
custom— anything else (user-authored).
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
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:
- If a key is already configured, show its masked value.
- Ask for a new OpenSIP Cloud API key.
- If the prompt is blank, cancel without changing the file.
- Save the key to
~/.opensip-cli/config.yml. - Test the key against the cloud entitlement endpoint. Verification is best-effort; the key stays saved if the endpoint is unreachable so offline setup still works.
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:
- Primary entry points with ready-to-use examples (including
sessions show latest --tool <fit|graph|sim> --json --filter errors-only --filter top:20andsessions list --json --summary-only). - Common composable agent workflows.
- Notes on the core output shapes (
SignalEnvelope,SessionReplayResultwithfidelity: "projection", etc.). reservedNames— the host-owned root commands a Tool cannot mount and the built-in suite names a configured suite cannot claim (ADR-0159).hostSupport— the honest, process-only platform-support assessment (Plan 02); the localmatchis neverexact, so distinguish it from the row's publishedstatus.- Bounded
projectContext.targetConventionswhen the project declares any. - Explicit call-out that human-readable renderers (tables, banners) are unchanged.
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
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
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 group — opensip 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:
opensip <tool>— resolve the binary →execFile(no shell) → read the native report → persist the raw artifact → normalize toSignals → emit + deliver. Inherits the common flags (--json,--cwd,--quiet,--report-to, …) and the gate flags below.opensip <tool> doctor— probe the binary and report readiness: binary (found / path / resolution layer), version (detected vs.minVersion), network posture, install hint when missing, and areadyverdict. Exits0when ready,2when not (--jsonfor the structuredAdapterDoctorReport), so CI can gate on it. Rundoctorfirst — it surfaces the local-binary and DB-cache prerequisites below.opensip <tool> version— print the resolved binary version + path (--jsonfor the structured shape).
Local-binary + DB-cache prerequisites (surfaced by doctor):
- Every adapter — needs the wrapped scanner binary on
PATHor pinned with
binaries.<tool>.path / OPENSIP_<TOOL>_BIN.
- Configuration-backed tools — ast-grep requires an
sgconfig.yml/
.ast-grep.yml; SpotBugs requires compiled Java classes; PMD uses the Java
quickstart ruleset by default.
- Network/cache tools — Semgrep
auto, govulncheck, cargo-deny, and
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:
--gate-save— capture the current findings as the project baseline in the SQLite store (mutually exclusive with--gate-compare).--gate-compare— diff against the saved baseline; exit non-zero on a net-new finding (thefailOnDegradedreserved key). Findings are fingerprinted with the line-shift-tolerantmessage-hashstrategy.--json— emit theSignalEnvelopefor machine consumption (the same envelope the gate and egress read).
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:
- Print every target path and its size before acting.
- Refuse to run when no targets exist (
--projectagainst a directory that contains no OpenSIP CLI state is a no-op, not a destructive accident). In project mode without--purge, a repo that has only authored content and no.runtime/also becomes a no-op and tells you what it kept. - Do not remove the npm-global binary — the running binary can't safely self-delete. The user-mode success message prints the next step (
npm uninstall -g opensip-cli); the project-mode success message points back at the user-mode command for the matching cleanup.
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:
- On the coffee-cup banner, the version line shows
(<new-version> available)and a dim↑ Update: curl -fsSL https://opensip.ai/cli/install.sh | bashline prints just below the banner. - On the
--jsonpath, which renders no banner, the same upgrade command is printed as a one-line note on stderr.
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
../50-extend/01-plugin-authoring.md— write a check, recipe, scenario, or full Tool plugin.06-dashboard.md— the HTML report's structure and lifecycle.../70-reference/03-configuration.md— every field ofopensip-cli.config.yml.
MCP compact audit surface: see Connect MCP clients and ADR-0152..0154.