Line

Reference

CLI reference

Every command and flag, generated from the CLI itself.

This page is generated from the CLI's own usage text (packages/core/usage.mjs), which is exactly what construct prints with no arguments, so it always matches the installed version.

Usage text#

Construct

Run 'construct repl' for an interactive shell with detailed built-in help
('help', 'help <topic>') — no need to retype 'node .../construct.mjs' or
--dir on every line.

Four capabilities, one CLI:
  construct create ...    scaffold a feature, a layer, or a whole vertical slice
  construct refactor ...  mechanical, LLM-free moves/renames within the architecture
  construct research ...  read-only: summarize a feature, explain its workflows in English, compute a change's impact, or check environment/tooling
  construct import ...    scaffold layers for an existing, non-Construct file + a breadcrumb to it

  construct create feature <name> [--format json] [--dir <path>]
  construct create layer <name> --feature <feature> --layers <l1,l2,...> [--llm <provider> | --format json] [--dir <path>]
  construct create <layer> <name> --feature <feature> [--llm <provider> | --format json] [--dir <path>]
  construct refactor move <name> --feature <feature> --from <layer> --to <layer> [--format json] [--dir <path>]
  construct refactor rename <name> <newName> --feature <feature> --layer <layer> [--format json] [--dir <path>]
  construct refactor extract-expression <file> [--range <start:end>] [--name <Name>] [--dry-run] [--dir <path>]
    (hoists a PAGE-008/COMPONENT-005/EXPR-004-flagged inline conditional/loop out of <file> into a
    named defineExpression(...) unit under that feature's expressions/ folder, and any hand-authored
    markup it rendered into a companion defineComponent(...) unit — rewrites the call site to match)
  construct summarize <unit-ref> [--kind <k>] [--detail brief|standard|full] [--include a,b] [--format json|markdown] [--dir <path>]
  construct summarize --list [--kind <k>] | --usage   (structured, LLM-free summaries of any feature/file/hook/route/rule/package)
  construct research summarize [--feature <name>] [--format json|md|compact|prose] [--since <ref>] [--dir <path>]
  construct research workflow <feature> [<file>] [--format prose|md|json|scenarios] [--dir <path>]
  construct research doctor [--dir <path>]
  construct research impact <unit-ref>... [--files a,b] [--since <ref>] [--ticket <text>] [--ticket-file <path>] [--depth N] [--max-files N] [--format json|markdown] [--dir <path>]
  construct research impact --usage   (deterministic blast radius: which features/layers/files a change touches, and why)
  construct research spec <file> [--generate [--feature <name>]] [--format json|text] [--dir <path>]
    (checks a machine-spec.v1 file -- an English requirement broken down into states, events, transitions and typed
    functions -- and refuses it with a SPEC-* code, the path and the reason: unreachable state, unknown state/event,
    untyped function, sentence neither covered nor out of scope; see docs/machine-spec.md; exit 1 on any failure)
    (--generate: on an accepted spec, writes the workflow (with its typed state union and named guard stubs) and
    one defineService(...) stub per function, never overwriting an existing file; --feature is used only when the
    spec has no "feature" field, and it is a usage error (exit 2) to have neither)
  construct review <base> <head> [--plan <file>] [--features a,b] [--no-merge-base] [--format json|markdown] [--dir <path>]
  construct review --usage   (read-only PR health between two git refs: scope, unexplained changes, rule regressions, public surface, flow diff; findings split mechanical vs conversation)
  construct test run <feature> [--name <file> --area generated|yours] [--base-url <url>] [--format json|text] [--dir <path>]
    (runs the feature's Playwright tests against the project's own running app (default http://localhost:3000, this machine
    only) and says per failure whether the test harness or the app is at fault; read-only, no LLM; exit 1 on any failure)
  construct template list|show <name>|instantiate <name> [--param key=value]... [--params-json <json>] --templates <dir>
  construct template ...   (named, reusable, parameterised plans: instantiate prints a concrete plan.v1; curated templates load from --templates <dir> or CONSTRUCT_TEMPLATES_DIR, none are bundled)
  construct import <name> --feature <feature> --layers <l1,l2,...> --from <path> [--llm <provider> | --format json] [--dir <path>]
  construct import --plan <path> [--llm <provider> | --format json] [--dir <path>]
  construct import --route <path>  (standalone interactive wizard, run directly — not inside repl)

'construct import' scaffolds the requested layers and drops a TODO(import)
comment in each pointing at the source file, then by default stops there —
reading that file and writing the real ported logic is left to you (or
whichever LLM you choose) as an explicit next step. '--plan <path>' is the
whole-feature form: given a plan ({ feature, units: [{ name, layers, from },
...] }) produced by whichever LLM analyzed the old feature and approved by
you, it runs the same step once per unit, in one command.

Pass --llm <provider> (currently: claude, ollama) to have import do the
writing for you instead: it calls that provider once per generated file —
never once for the whole batch — with that file's layer constraints and the
old source, and writes the result directly. Review LLM-written output
before trusting it.

'construct create'/'construct generate' accept the same --llm <provider>
flag: when given, that provider writes a real implementation into each
generated file in place of the plain template stub — one call per file,
that file's layer constraints included, same as import's fill above. Which
layers/files get created is still always decided deterministically; --llm
only changes what ends up inside the file(s) that were already going to be
created. Omit --llm and every generated file is the exact same plain
template stub as before. Locating files, scaffolding, and deciding feature
shape never involve an LLM either way.

'construct import --route <path>' is the guided, whole-feature form. <path>
is a real router route — a URL like /v2/home, or the folder that owns its
page.tsx — and seeds the first one; it then asks for as many more as the
old feature actually spans (e.g. /v2/home and /v2/home/details), traces
each one's real import graph (page -> Client -> hooks/components, stopping
at known external boundaries) rather than reading a hand-picked directory,
auto-creates the destination feature if it doesn't exist yet (never touches
it if it does), analyzes everything with ONE combined "claude" call, shows
you the plan, and on approval builds it, then immediately runs validate and
tells you exactly what's left — a TODO(import) count to fill in, or a
reminder to review LLM-written output. A URL-style route needs to know
where your app/ directory is; it asks once and reuses it for every further
route this session. It manages its own input, so run it directly from your
shell — not typed inside 'construct repl', which is already reading input.

Each of the above has a flat equivalent (unchanged, still supported):

Commands:
  construct init [dir] [--framework nextjs|react-spa] [--no-scaffold]
    (also writes a runnable project shell — package.json, tsconfig, bundler config,
    .gitignore — never overwriting existing files; --no-scaffold skips that part)
  construct feature create <name> [--dir <path>]
  construct generate <layer> <name> --feature <feature> [--llm <provider>] [--dir <path>]
  construct generate page <name> --feature <feature> --from <path> [--dir <path>]
    (ingests an externally-authored JSX file — e.g. a Subframe export — as a
    pristine, presentation-only page + an explicit <Name>PageProps.ts interface,
    instead of scaffolding the usual stub template; 'construct create page ... --from
    <path>' is the equivalent under the create/refactor/research/import grouping)
  construct generate workflow <name> --feature <feature> --from <path-to-json> [--state-union] [--dir <path>]
    (compiles a JSON state-graph descriptor into a real XState v5 machine file
    instead of the usual stub template; 'construct create workflow ... --from <path>'
    is the equivalent under the create/refactor/research/import grouping.
    --state-union also writes <Name>WorkflowState.ts beside it: a <Name>State
    discriminated union, one { status } member per state, plus match<Name>State and
    assertNever<Name>State, which stop compiling when a state is left unhandled)
  construct generate controller <name> --feature <feature> --bind [--envelope <path>] [--dir <path>]
    (auto-wires an already-generated hook into an already-generated pristine page's
    Props interface via exact + fuzzy AST signature matching, instead of the usual
    same-named-page-only stub template; 'construct create controller ... --bind' is
    the equivalent under the create/refactor/research/import grouping)
  construct generate layer <name> --feature <feature> --layers <l1,l2,...> [--llm <provider>] [--dir <path>]
  construct generate tests <feature> [--dry-run] [--prune] [--dir <path>]
    (one LOCKED Playwright spec per workflow scenario into features/<feature>/tests/generated/;
    needs frozen: + nonLayer: globs for tests in architecture.yml; deterministic, no LLM)
  construct generate tests --unit <feature> [--dry-run] [--prune] [--dir <path>]
    (one LOCKED every-path unit test per workflow machine, <machine>--every-path.test.ts in the
    same directory: @xstate/graph walks every reachable state and user-event transition under
    node's test runner, no browser; run with npx tsx --test <file>)
  construct sync [--dir <path>]
  construct validate [--format json] [--dir <path>]
  construct summarize [--feature <name>] [--format json|md|compact|prose] [--since <ref>] [--dir <path>]
  construct doctor [--format json] [--dir <path>]
  construct pipeline run [--dir <path>]   (reads a Context Envelope as JSON on stdin, writes one to stdout)

--dir <path> targets a Construct project nested in a subdirectory (e.g. one
created with 'construct init <path>' inside a larger, unrelated project)
without requiring you to cd into it first.

Generation/refactor order is enforced for real by IMPORT-001, not left as a
convention: a layer whose template references a sibling layer's file (e.g. a
controller importing its page) fails immediately if that file doesn't exist
yet — so 'construct generate layer' always builds requested layers in
dependency order regardless of how you list them, and a refactor move/rename
that leaves a naming mismatch in its new layer is reported right away.

Externally-authored UI (a design tool's output): list its globs under 'frozen:'
in architecture.yml (relative to the project root; may reach outside it). Every
create/generate/import/refactor/pipeline write into a matching path is refused,
and PAGE-007 / COMPONENT-004 / CONTROLLER-002 (warnings by default) flag pages,
components and controllers that re-author markup already in a frozen source
instead of a controller importing it and forwarding props. See the README.

'construct refactor' never rewrites a file's own content or exported
identifier — only its location/name and every other file's import of it.
Whether the result is valid in its new layer (naming, purity, etc.) is
construct validate's job, reported immediately after the move.

Commands at a glance#

Three capabilities, each with its own namespace — a friendlier grouping over the same underlying commands, not a second implementation:

# create — scaffold a feature, a layer, or a whole vertical slice
construct create feature <name> [--format json] [--dir <path>]
construct create layer <name> --feature <feature> --layers <l1,l2,...> [--format json] [--dir <path>]
construct create <layer> <name> --feature <feature> [--format json] [--dir <path>]
construct create service <name> --feature <feature> --openapi <spec> [--dir <path>]

# refactor — mechanical, LLM-free moves/renames within the architecture
construct refactor move <name> --feature <feature> --from <layer> --to <layer> [--format json] [--dir <path>]
construct refactor rename <name> <newName> --feature <feature> --layer <layer> [--format json] [--dir <path>]

# research — read-only: summarize a feature, or check environment/tooling
construct research summarize [--feature <name>] [--format json|md|compact|prose] [--since <ref>] [--dir <path>]
construct research impact <unit-ref>... [--files a,b] [--since <ref>] [--ticket <text>] [--depth N] [--format json|markdown] [--dir <path>]
construct research spec <file> [--format json|text] [--dir <path>]
construct research doctor [--dir <path>]

construct research workflow <feature> [<file>] [--format prose|md|json|scenarios] explains a feature's XState workflows in plain English (each state, every start-to-end scenario, health findings), derived from the real source every time with no LLM — see docs/workflow-narrator.md.

construct research spec <file> [--format json|text] checks a machine-spec.v1 file — an English requirement, sentence by sentence, broken down into states, events, guarded transitions and typed functions, each linked back to the sentence it came from — and refuses it with a SPEC-* code, the path and the reason when a state is unreachable, a transition names an unknown state or event, a function has no types, or a sentence is neither covered nor marked out of scope. Deterministic, no LLM; the spec is the contract a person (or a model, given the schema and the worked example) drafts before anything is generated from it — see docs/machine-spec.md.

construct summarize <ref> [--detail brief|standard|full] [--format json|markdown] (and --list, --usage) returns a deterministic, LLM-free summary of any project, feature, layer, file, hook, workflow, route, rule or package, sized for a bot or a teammate — see docs/unit-summary.md.

construct research impact <ref>... [--files a,b] [--since <ref>] [--ticket <text>] [--depth N] [--format json|markdown] computes the blast radius of a change: which features and layers it touches, why each file is implicated, what is shared across features, and what your rules already say about those files. Every entry is marked derived (computed from the graph) or inferred (reached only from a seed that was guessed from ticket text or proposed by a model), so you can see exactly where judgement entered — see docs/impact-analysis.md.

construct review <base> <head> [--plan <file>] [--features a,b] [--format json|markdown] reports pull request health between two git refs with no model: what changed by feature and layer, changes nobody explained, rule regressions, public-surface changes and the flow diff, with findings split into mechanical fixes and conversations — see docs/pr-health.md. construct template list|show|instantiate prints named, reusable plans from a folder you point at with --templates <dir>; none are bundled.

construct refactor never rewrites a file's own logic or exported identifier — only its location/name and every other file's import of it (including the moved file's own same-layer imports, re-resolved for its new home). Whether the result is valid in its new layer — naming convention, purity, everything else — is construct validate's job, reported immediately after the move so a mismatch shows up right away. There's no persistent activity log: each command prints one clear, scannable line for what it did (Created ..., Moved ... -> ...), and that line is the record — a file that changed without one wasn't done by the tool.

The flat commands below are unchanged and still work — create/refactor/research are just a grouping on top:

construct init [dir]
construct feature create <name> [--dir <path>]
construct generate <layer> <name> --feature <feature> [--dir <path>]
construct generate layer <name> --feature <feature> --layers <l1,l2,...> [--dir <path>]
construct sync [--dir <path>]
construct validate [--dir <path>]
construct validate --format json [--dir <path>]
construct doctor [--format json] [--dir <path>]

Exit codes#

Code Meaning
0 Success (warnings do not fail a run)
1 One or more error-level violations
2 Usage error: unknown command, missing or invalid arguments
3 Internal error, including a model reply that was rejected or a provider call that failed

Output conventions#

Every create, refactor, research and import action ends with one attribution line, [tool: ...] [llm: ...], that states what was done and how many model calls were made. validate --format json and research summarize --format json print pure JSON that can be piped. pipeline run takes a Context Envelope on standard input; see Context Envelope and pipeline.