suss init
suss init looks at a project, works out which packs it needs, and offers to install them and write the configuration. Run it once when you add suss to a repository, so the later commands need no flags.
suss init [<directory>] [--plain | --write [--overwrite]]| Argument or flag | Default | What it does |
|---|---|---|
<directory> | the current directory | Where to look. |
--plain | off | Print the commands instead of asking. Piped output and CI print either way. |
--write | off | Print the commands, then write suss.json and each pack's config file without asking. A coding agent has no terminal to answer the questions in, so this is how it finishes setting a project up. When a suss.json is already there, init leaves it alone and says so. |
--overwrite | off | With --write, replace a suss.json that is already there with what init found. init refuses it without --write. |
--cache-dir <path> | .suss/cache under the project, or SUSS_CACHE_DIR | Where any extraction init runs keeps its cache. See Where suss writes. |
What it reads
It reads package.json, the Python manifests and the Gemfile for dependencies, and the directory for schemas and deploy templates. An OpenAPI document is found by its name, openapi.yaml or swagger.json, or by the version line at its top. Before it prints a contract command, init runs that contract's reader on the file or folder, and leaves the command out when the reader finds nothing it can read there, such as a folder of stories whose default export has no component. The report lists those files under "Contracts with nothing to read", with the reason. When every boundary in one file is also in another file read by the same reader, such as a bundled spec built from a split source, or one tag group of a larger spec, init prints a command for the larger file only, so a finding is not reported once per copy. Of two files with the same boundaries it keeps the one nearer the top of the project, and at the same depth the smaller one, which is usually the source rather than a bundle. The report lists the others under "Contracts another file already declares". init also leaves out an extract that would have no file to read, such as one for a folder whose nearest tsconfig is above it and leaves it out, and lists it under "Code with nothing to read". At a monorepo root it also reads the workspace declaration, from package.json workspaces, pnpm-workspace.yaml, lerna.json or turbo.json, and asks which packages to set up.
Every folder below the root with its own package.json, pyproject.toml or Gemfile is a project of its own, whether or not a workspace file lists it, so a server folder and a client folder side by side each get their commands. A folder below the root is shown when a pack matched there, or when its own manifest could not be read and it has source of its own. A Gemfile that only drives a mobile build, for example, does not get a section.
Shared workspace packages
A service in a workspace usually depends on sibling packages, such as packages/db with the database helpers every service calls. The service's read follows its calls into the sibling's code, so init gives that read the packs that recognise calls, such as a database or AWS pack, that the sibling's dependencies match, at any depth. A framework pack a sibling's dependency matches is left out, since a read finds routes and handlers only in its own files. The report says where each one came from, as in pg pg in dependencies of @shop/db. A sibling's dev dependencies are left out, since none of them run in the service. init finds a sibling through the node_modules link npm, yarn or pnpm writes, or, before an install, through the workspace declaration.
A workspace package whose packs only read calls made elsewhere, with no framework to find a route, a handler or a component in it, gets no extract of its own: on its own it would come back empty. Its section lists the packages whose reads follow calls into it instead, or says that nothing reaches it when no package that suss reads depends on it. A contract in it, such as a Prisma schema, still gets its command.
Shared code
TypeScript read through apps/api and apps/web, which depend on it
no extract of its own, since its packs (pg, node) don't find a route or handler in it to start fromfetch and Net::HTTP come with the language, so no manifest lists them. Their packs alone set a project up only once a file in the project's own source calls the library, and the report says which file. For TypeScript and JavaScript, the project's own source is what its tsconfig includes, or, for a folder without one, what the nearest tsconfig above it includes there. Without a tsconfig, as in a Rails app's app/javascript, every file in the project counts. In every language, files under a scripts, tools, config, test, public, node_modules, vendor, dist, build or coverage directory don't count, and neither do *.config.* files, tests, or minified and bundled files.
When the project depends on a framework suss knows and has no pack for, such as Django, tRPC or Grape, the report says so, including when other packs matched.
The commands for a project in a folder below the root name that folder with --dir and start every path from the root, so you can paste them where you ran init. Each writes its own file in one summaries/ folder, and a folder with more than one language gets --lang on every extract.
What it writes
Interactively it asks four things: whether to install the packs, whether to write what it found to suss.json, whether to run the first extract and check, and whether to write a .sussignore and a CI workflow. Nothing reaches disk unless you accept it.
With --write it asks nothing. It prints the same commands --plain prints, then writes suss.json for every project it found packs for, the same selection the interactive form starts with. It leaves the install, the .sussignore and the CI workflow to the interactive form.
Either way, writing suss.json also writes the config file for each pack that takes one, such as suss.graphql-ruby.json, and suss.json lists the pack with its file, as in graphql-ruby=suss.graphql-ruby.json. The file gets the values a project the library's own generator made has, or the values the project already states elsewhere: the activerecord pack reads which database is behind the connection from the adapter lines in config/database.yml. A config file that is already there is kept as it is. When a pack needs a value that init can't work out, it leaves that pack out of suss.json and says what to write, because listing it would stop the whole extract for its language.
suss.json is what suss extract reads when it is given no -f, and what suss inspect, suss check and the MCP server read when they are given nothing. Without the file, those commands pick the same packs init would have picked, and print their choice on stderr.
Every path in suss.json starts from the folder the file is in. In a monorepo, each package gets its own read, with the package's folder as dir and its tsconfig as project, such as "dir": "apps/web", "project": "apps/web/tsconfig.json". suss 0.34.0 left dir out and wrote each tsconfig path from inside its package. A command that reads a file like that gives each read the folder of the package init finds with the same packs, and says so on stderr. suss init --write --overwrite writes the folders into the file.
Example
$ suss init --plain✓ Found 4 things to read in /home/dana/petstore
Your code
axios axios in dependencies
fetch TypeScript sources, and fetch reads what the language itself ships
What your code reaches
node TypeScript sources, and node reads what the language itself ships
Declared contracts
openapi an OpenAPI document at openapi.json
1. Install suss
npm install --save-dev @suss/cli
2. Read each side into one folder
suss extract -f axios -f fetch -f node -o summaries/code.json
suss contract --from openapi openapi.json -o summaries/openapi.json
3. Compare them
suss check --dir summaries/The plain run continues with a .sussignore sketch and a note on running it in CI.
Exit code
0, including a --write run that left an existing suss.json alone. A flag init does not take, or --overwrite without --write, exits 1. Declining every question, cancelling, and a failed install all end the same way; a failed install prints what npm said and leaves you the command to retry. Exit codes has the rest.