CLI reference
Everything ships in one package:
npm install --save-dev @suss/clisuss init [<directory>] [--plain | --write [--overwrite]]
suss extract [-p <tsconfig> | --dir <directory>] [--lang typescript|python|ruby] [-f <pack>[=<config.json>] ...] [-o <output.json>] [--files <f1> <f2> ...] [--gaps strict|permissive|silent]
suss inspect [<summaries.json> | --dir <directory>]
suss inspect --diff <before.json | directory> <after.json | directory>
suss inspect --flow "<METHOD> <url>" [<summaries.json> | --dir <directory>] [--entry <name>] [--scope <document>] [--json]
suss check [--dir <directory>] [--intent <intent-dir>] [--all] [--json] [-o <output>]
suss check <provider.json> <consumer.json> [--all] [--json] [-o <output>]
suss check [--dir <directory>] --at <file[:line] | boundary | summary-id> [--json]
suss ask "<question>" [--dir <directory> | <summaries.json>] [--all] [--json]
suss map [<directory>] [--dir <summaries>] [-o <page.html>] [--links editor|github]
suss contract --from <source> <spec> [-o <output.json>]
suss corroborate --experimental [-p <tsconfig> | --dir <directory>] [-f <pack> ...] [-o <output.json>]
suss infer stub <package> [-p <tsconfig> | --dir <directory>] [-o <file | ->]
suss infer intent --from <summaries.json | directory> [-o <directory> | --into <directory>]
suss infer prd --from <intent-directory> [-o <directory> | --into <directory>]
suss intent outcomes --from <intent-directory> [--json]
suss intent check <change-list> --before <dir | file> --after <dir | file> [--prompts <file>] [--json]
suss intent keep <change-list> --dir <dir | file> --audience <text> [--into <directory>]
suss --versionIn a synopsis, <...> marks a required value and [...] an optional one.
Commands
| Command | Reads | Writes |
|---|---|---|
| suss init | package.json, schemas and deploy templates on disk | The commands to run, and suss.json if you accept it or pass --write |
| suss extract | TypeScript, Python or Ruby source, through the packs you name | Summary JSON |
| suss contract | One of ten declared sources: an OpenAPI document, a deploy template, a database schema, and so on | Summary JSON, the same structure extract writes |
| suss check | Summary files, and optionally a folder of intent docs | Findings, as text or JSON |
| suss inspect | A summary file, a folder of them, or two of them | Text for a person, or JSON under --diff and --flow |
| suss ask | Summary files, and the source for a why question | One answer, as text or JSON |
| suss map | The project, the way a bare check reads it, or a folder of summaries | One HTML page of the routes, fields, tables and env vars, and the summaries behind it |
| suss corroborate (experimental) | Source, through the express or fastify packs | Summaries annotated with what running the code showed |
| suss infer | Observed calls, summaries, or curated intent docs | A YAML draft for you to finish |
| suss intent | Intent docs, or a change list and the summaries from before and after a change | Outcome ids, verdicts on each change, or intent docs kept from a change list |
extract, inspect and check run with no arguments at all. Each one reads suss.json, or picks the packs init would pick when there is no file, and prints the commands it ran to stderr.
The summary JSON is the artifact every other command works from. inspect renders it and check compares two of them, so anything either one reports is also there in the JSON for you to read yourself. Two commands compute an answer that isn't in any file. inspect --flow walks the routing a set of summaries declares, and suss ask takes a question about one boundary and works out the answer from the summaries.
Top-level flags
| Flag | What it does |
|---|---|
-h, --help | Print the usage above and exit 0. Running suss with no command, or any command with --help, prints the same thing. |
-v, --version | Print the installed version and exit 0. |
Environment variables
An interactive run ends with one line on stderr when a newer suss is on the registry. You never get that line when the output is piped, and two variables turn it off everywhere.
| Variable | Effect |
|---|---|
NO_COLOR | Set it to anything and output is plain text with no ANSI colour. |
TERM=dumb | The same. Colour is also off whenever stdout is not a TTY, so a piped or redirected run is plain without you asking. |
CI | Set it to anything and the update notice is off. suss init detects CI separately and prints its commands rather than prompting, the same as --plain. |
SUSS_NO_UPDATE_NOTICE | Set it to anything and the update notice is off. |
SUSS_CACHE_DIR | A directory to keep suss's own files in, instead of .suss in the project. See Where suss writes. The --cache-dir flag overrides it. |
SUSS_CACHE_MAX_MB | How many megabytes the extraction cache keeps, 1024 when it is not set. See Where suss writes. |
NODE_OPTIONS | A heap size set here with --max-old-space-size is the one suss runs with. Without one, suss picks its own, as Memory describes. |
Where suss writes
A command that extracts keeps a cache so the next run reads only what changed. It goes in .suss/cache under the folder being read, with a .gitignore inside it. In a git worktree, it goes in the same folder of the main checkout instead, so every worktree of a repository shares one cache, and a new worktree's first run reads again only the files that differ from what another checkout stored. In a checkout you may only read, that write fails or dirties the tree. Point the cache somewhere else with --cache-dir, or with SUSS_CACHE_DIR for every command in a shell:
suss check --cache-dir /tmp/suss-cache
SUSS_CACHE_DIR=/tmp/suss-cache suss extract --dir . -o /tmp/summaries.jsoninit, extract, inspect, check, ask and map take the flag, and the flag wins over the variable. map writes its page and the summaries behind it to .suss in the project, or, with the cache moved, to a folder for the project under map in the cache directory. Several projects can share one directory, because each gets a subfolder named for a hash of its path. For the extraction cache in a git repository, the hash is of the repository and the folder's place in it, so the worktrees still share one cache there. The MCP server takes the same flag as an argument (suss-mcp <project> --cache-dir <path>) or reads the variable, and keeps its record for the plugin's hooks there too.
The extraction cache keeps 1 GB, or the number of megabytes SUSS_CACHE_MAX_MB says. When a write takes it past that, suss removes what the checkouts used longest ago stored, and a file two checkouts share stays as long as one of them does. The checkout that is writing keeps what it stored however large it is. What a removed worktree stored goes at the next write.
With the cache moved, a command writes into the project only where you asked it to: -o, --into, --out-dir, and the files init --write creates. Everything else goes to the system temp folder (the update-check stamp, the temporary summaries a bare check reads, and the MCP server's socket).
Memory
suss runs with a V8 heap limit of 8 GB. Node picks a smaller limit from the machine's memory, about 4 GB on a 16 GB machine, and extracting a large TypeScript service with every pack suss init suggests can use more than that. Under Node's limit such a run stops partway with "JavaScript heap out of memory".
The limit is a ceiling on growth, so a small project uses no more memory than it would under Node's default. A large one uses more, because V8 lets the heap grow before it collects garbage instead of collecting over and over near a lower limit. On a NestJS service of about 7,000 units with eleven packs, peak memory went from 4.7 GB to 6.2 GB, and the run got faster. If memory matters more than time on a large project, set a lower size as shown below. On a machine or container with less than about 11 GB, suss keeps the limit to three quarters of the memory there. A run that outgrows it then stops with V8's heap error, and the operating system does not have to kill it.
To choose the size yourself, put it in NODE_OPTIONS. suss leaves a size set there alone, whether it is larger or smaller than its own:
NODE_OPTIONS=--max-old-space-size=12288 suss extractsuss raises the limit by starting itself again with --max-old-space-size, before it loads anything else. On Node 22.15 and later the new process replaces the first one and keeps its process id. On older versions of Node, and on Windows, the first process stays as a parent: it passes on SIGINT, SIGTERM and SIGHUP, and exits with the child's exit code. The MCP server, suss-mcp, does the same. With that parent in place, a debugger started with node --inspect attaches to the parent, which only waits for the child. To debug a run there, put a size in NODE_OPTIONS so suss does not start itself again.
Where output goes
| Stream | What arrives there |
|---|---|
| stdout | Summary JSON from extract and contract, the report from inspect, check and ask, and the JSON those write under --json |
| stderr | "Wrote N summaries to PATH", extraction warnings, the commands a no-argument run chose, error messages |
A run without -o puts nothing but JSON on stdout, so suss extract ... | jq works. -o PATH moves that JSON into the file; extract and contract then write one acknowledgement line to stderr.
Exit codes lists what every command returns to the shell.