Solarch CLI
The bridge between your codebase and the architecture you draw. Drift checking, bidirectional cloud sync, deterministic code generation and live binding — one binary, built for NestJS.
npm install -g @solarch/clicd your-nestjs-reposolarch connect # sign in + link projectsolarch diff --ci # check drift in CI
@solarch/cli reads your NestJS code at the compiler level with ts-morph, extracts the As-Is architecture graph, and compares it against the To-Be architecture you drew in Solarch. Run solarch diff --ci in a pull request and code that violates your architecture cannot merge. Generate deterministic scaffolds from the graph, fill the method bodies under contract, push code-side additions back to the canvas, and keep entities and DTOs in sync as you type. No AI in the parts that need to be reproducible — same graph in, byte-identical code out.
// what it does
scan — extract As-Is
Reads NestJS source at the compiler level (ts-morph) and builds the As-Is architecture graph — controllers, services, repositories, tables, DTOs and edges — straight from decorators.
diff — drift guard
Compares As-Is against the To-Be architecture. --ci emits GitHub annotations and exits 1 on error-level drift; --sarif feeds code-scanning; --to-be runs offline against a pulled graph.
pull / push — two-way sync
Pull the To-Be graph to .solarch/to-be.json; push code-side additions (nodes, edges, list-properties) atomically. Push is rejected on illegal edges, never deletes, and is idempotent.
generate — deterministic scaffold
The Constructor writes code from the cloud graph with no AI — same graph, byte-identical output. Method bodies arrive with @solarch:surgical markers; existing files are skipped unless --force.
fill — surgical body fill
Replaces NOT_IMPLEMENTED bodies with implementations honoring each region's contract (throws/deps/signature), then runs tsc + tests as gates. Only contract-passing fills are saved.
bind / watch — live binding
Bind an entity to a DTO so columns flow to typed, validated properties marked @solarch:bound. watch runs a chokidar daemon: re-runs bindings and prints incremental drift on every save.
// how it works
Your code and the diagram, always in step
Solarch keeps two graphs in step: the As-Is graph the CLI extracts from your code, and the To-Be graph you draw in the Solarch canvas. Every command is a move between those two graphs — scan reads code, diff compares, push and pull sync, generate writes the scaffold the diagram describes. The match cache in .solarch/map.json keeps node identity stable across renames so the comparison stays meaningful.
- ›scan — extract the As-Is graph from NestJS source via ts-morph (controllers, services, repositories, tables, DTOs, edges).
- ›diff — compare As-Is against the To-Be architecture; emit drift findings with severities.
- ›pull / push — download the To-Be graph offline, or write code-side additions back to the cloud.
- ›generate → status → fill — produce a deterministic scaffold, track fill rate, implement bodies under contract.
Code ──scan──▶ As-Is graph ──diff──▶ drift report (blocks merge in CI) │ └──push──▶ Solarch Cloud (missing nodes/edges + properties)Cloud ──pull──▶ .solarch/to-be.json (offline reference)Entity changed ──watch──▶ auto property sync to DTO (bind)
// drift guard in CI
Code that breaks the architecture cannot merge
solarch diff reconciles the scanned code against your To-Be graph and classifies each difference. With --ci it prints GitHub Actions annotations and exits 1 on any error-level finding, failing the job. --sarif emits SARIF 2.1.0 for GitHub code-scanning; --json is machine-readable; --to-be <file> runs fully offline against a pulled graph. Error findings — architecture present but missing in code, an unwired diagram connection, or a code connection that violates the Rules Matrix — block the merge.
- ›DRIFT_NODE_MISSING_IN_CODE / DRIFT_EDGE_MISSING_IN_CODE — a commitment in the diagram was never wired up (error).
- ›DRIFT_ILLEGAL_EDGE — a code connection violates the Rules Matrix legality (error).
- ›DRIFT_NODE_NOT_IN_CLOUD / DRIFT_EDGE_NOT_IN_CLOUD — unapproved expansion in code (warn).
- ›DRIFT_PROPERTY — column/field/method lists differ (info).
- ›Exit codes: 0 = clean or warn/info only · 1 = error-level drift.
- name: Install Solarch CLI run: npm install -g @solarch/cli- name: Login run: solarch login --key "$SOLARCH_API_KEY" env: SOLARCH_API_KEY: ${{ secrets.SOLARCH_API_KEY }}- name: Drift check run: solarch diff --ci
// from diagram to code
Generate deterministically, fill under contract
solarch generate turns the cloud graph into a code scaffold with the Constructor — no AI, so the same graph produces byte-identical output. Method bodies arrive with @solarch:surgical markers; existing files are skipped by default so hand-filled or AI-filled code is never overwritten (--force resets to a fresh scaffold). solarch status reads those markers and reports per-node fill rate, pending members with their job descriptions, contract violations, and marker loss. solarch fill replaces NOT_IMPLEMENTED bodies with implementations that honor each region's contract, then runs tsc and the project's tests as gates — only contract-passing fills are saved.
- ›generate — Constructor output: deterministic, skips existing files, records markers in .solarch/generated.json.
- ›status --ci — exit 1 if skeletons, contract violations, or marker losses remain.
- ›fill --all / --region — per-region LLM → write → contract check → retry, then tsc + test gates.
- ›fix-imports — deterministic import resolution (no AI, no tsc); the AI writes the body, the system owns imports.
solarch generate # write the deterministic scaffoldsolarch status # 12/40 member(s) implemented (30%)solarch fill --all --with-tests # fill bodies under contract, verifysolarch diff # confirm code matches the architecture
// two-way sync
Push code-side additions back to the canvas
solarch push fetches the current graph, builds a plan of nodes and edges to add and list-properties to update, then applies the additions in one atomic call. For list fields — Columns, Fields, Methods, Endpoints, Values — code is the source of truth; other cloud properties are preserved. Push is rejected entirely when illegal edges exist, and it never deletes (removing nodes is canvas-only, by design). Stale-revision conflicts auto-retry once; node-version conflicts prompt keep-cloud / write-code / skip.
- ›pull — download the To-Be graph with its revision to .solarch/to-be.json for offline diff and reference.
- ›push --yes — atomic adds with baseRevision; idempotent (a second push is a no-op via .solarch/map.json).
- ›init — brownfield import: scan an existing repo, create a fresh project from it, write solarch.json.
- ›No --prune flag: the CLI adds and updates, it does not delete from the cloud.
// live binding
Keep entities and DTOs in sync as you type
solarch bind <source> <target> defines a persistent binding in solarch.json and runs the first sync immediately — entity columns flow to DTO properties as typed fields with class-validator decorators. Added fields carry a // @solarch:bound marker, hand-written properties are left alone, and type conflicts are reported rather than overwritten. solarch watch runs a chokidar daemon that re-runs linked bindings and prints an incremental drift summary every time you save; --no-drift makes it binding-only.
solarch bind "src/users/user.entity.ts#User" \ "src/users/create-user.dto.ts#CreateUserDto"solarch watch # daemon: bindings + incremental drift on savesolarch watch --no-drift # bindings only
// files & config
Credentials stay local, config travels with your code
Credentials are stored machine-wide in ~/.solarch/credentials (mode 600) and stay out of the repo. The project link lives in solarch.json — projectId, include/exclude globs, and bindings — and is meant to be committed. The CLI is the single source of truth the Solarch MCP server and VS Code extension consume via @solarch/cli/lib, so the diff engine behaves identically across CLI, MCP and editor. Requires Node 20+.
- ›~/.solarch/credentials — API key, mode 600, home dir (not committed).
- ›solarch.json — projectId, include/exclude, bindings[] — commit this.
- ›.solarch/map.json — code-node ↔ cloud-node match cache (commit it for stable matching after renames).
- ›.solarch/to-be.json — pull output, regenerable (not committed).
// faq
What does solarch diff actually block in CI?
Error-level findings: architecture present in the diagram but missing in code (DRIFT_NODE_MISSING_IN_CODE / DRIFT_EDGE_MISSING_IN_CODE) and code connections that violate the Rules Matrix (DRIFT_ILLEGAL_EDGE). With --ci these exit 1 and fail the job. Unapproved expansions are warnings and property-list mismatches are info — neither fails the build.
Is the generated code written by an AI?
No. solarch generate uses the Constructor, which is deterministic — the same graph produces byte-identical output, no AI involved. AI only enters with solarch fill, which writes method bodies inside @solarch:surgical regions under a fixed contract (throws/deps/signature) and verifies them with tsc and the project's tests. Imports are resolved deterministically by fix-imports, not the model.
Will push or generate overwrite my work?
No. generate skips existing files by default so hand-filled or AI-filled code is never clobbered (use --force to reset to a fresh scaffold). push only adds nodes/edges and updates list-properties — it has no delete path, is rejected entirely if illegal edges exist, and is idempotent: a second push is a no-op.
Can I run drift checks offline?
Yes. solarch pull writes the To-Be graph (with its revision) to .solarch/to-be.json, and solarch diff --to-be .solarch/to-be.json runs the comparison without hitting the API. Rules-Matrix legality checks need a stored login when available; offline, those checks are skipped and noted.
Which frameworks and runtimes are supported?
The CLI targets NestJS codebases and scans src/**/*.ts at the compiler level via ts-morph. It requires Node 20 or newer. Commands like generate and fill require a Build+ plan, and fill needs an LLM key (DEEPSEEK_API_KEY or SOLARCH_FILL_API_KEY).
Put Solarch CLI to work
Architecture that's verified, not guessed.