input (file | directory | `-` stdin)
↓ discovery: kind detection, locale, root, identity (wright-driver::input)
CompilerSession (wright-driver)
├─ source adapter: OPY | Workshop | protocol JSON
├─ validation (WIR)
├─ lowering (HIR → WIR)
├─ analysis (SemanticService: semantic facts, symbols, references, CFG)
├─ emission (Workshop text)
└─ reconstruction (Workshop artifact → canonical OPY source through the
provider's negotiated `reconstruct` capability, #126)
↓
Envelope<T> (typed result + diagnostics + exit code; finding selection
narrows reported sets without touching the verdict, #430)
↓
`wright` CLI: text rendering | JSON serialization
The CLI is a thin argv/presentation layer. Library consumers construct a
[CompilerSession] directly and receive the same typed envelopes; CLI JSON
output is the serialization of that exact model, never a separately formatted
result.
| Command | Purpose | Text-mode stdout |
|---|---|---|
wright compile [INPUT] [--to LOCALE] |
Parse, lower, validate, emit Workshop text; warn on known client import limits | verdict, diagnostics, the written path, and the output SHA-256; the program goes to <input stem>.ow (or -o PATH; -o - prints only the program); --to emits in another Workshop client locale |
wright convert [INPUT] --target opy|ostw |
Reconstruct validated Workshop input as canonical OPY or OSTW source through a provider that negotiates reconstruct (the shipped OPY provider does since 0.3.19; no OSTW provider is shipped) |
the reconstructed source |
wright check [INPUT] |
Parse, lower, validate, and report correctness diagnostics | verdict and validation diagnostics |
wright analyze [INPUT] [--all] |
Summarize Workshop cost, ranked complexity hotspots, performance/stability risk indicators, and cross-cutting state | overview, cost, top hotspots, complexity, risks, and top variables with exact/static/heuristic evidence labels; --all lifts list caps |
wright lint [INPUT] [--all] [--fix [--write]] |
Parse, lower, lint; report findings | finding counts by severity and actionable findings; --all lifts list caps; --fix previews each offered fix's validated diff, --write applies them |
wright lint package <search|install|list|outdated|update|remove> |
Discover, install, and manage GitHub lint rule packages in the Wright-owned store keyed by commit SHA (#844); takes no INPUT and never activates a package |
per-package rows: source, status, SHA, entry directory, rule ids, and error detail (lint configuration and findings) |
wright inspect [INPUT] [--all] |
Parse, lower, and inspect the program overview | overview, rule names, and the detail commands; --all lifts the rule-list cap |
wright inspect rules [INPUT] [--content] [--rule RULE] [--file PATH] [--max N] |
List rules; --content adds each selected rule's conditions and actions as canonical call trees (ADR-0024) |
the rule list, with content when requested |
wright inspect symbols [INPUT] [--only KIND] [--file PATH] [--max N] |
List semantic symbols, optionally narrowed to one kind | the symbol list with resolved locations |
wright inspect refs <NAME> [INPUT] [--only KIND] [--rule RULE] [--file PATH] [--max N] |
References and usage counts for one symbol, addressed by name | usage-count header plus the reference list |
wright inspect cfg <RULE> [INPUT] [--only KIND] [--max N] |
Control-flow graph of one rule, addressed by name | block/edge listing |
wright inspect callgraph [INPUT] [--caller RULE] [--callee SUBROUTINE] [--max N] |
Subroutine call graph (caller rules to callee subroutines) | call edges |
wright inspect cost [INPUT] [--rule RULE] [--no-cache] [--baseline NAME] [--save-baseline NAME] [--max-total N] |
Exact generated-resource counts plus static findings; --rule drills into one rule's element cost, and by default the run compares against the previous run's element cost |
resource counts and findings plus the change against the previous run or named baseline, or the rule's statements and node-name shares |
wright lookup [QUERY] |
Resolve a Workshop or OverPy display name, near spelling, or guess to the owner-accepted spelling and signature (#529); takes no input | one line per entry: signature or spelling, kind, identity |
wright serve [INPUT] [--transport T] [--kind K] [--profile P] [--lint-config YAML] [--rule-file PATH] [--disable-rule ID] [--rule-severity ID:SEV] |
Serve wright-agent/v1 over stdio, JSON-RPC 2.0, or MCP |
one structured response per request |
wright completion <SHELL> |
Generate static completion script for bash, zsh, fish, or powershell | the generated completion script |
wright completion install [SHELL] |
Install generated completion into standard user-local directory | installation progress and guidance |
wright update [self|provider [NAME]] |
Update Wright-managed components: a standalone installation and installed first-party providers | update progress (text only) |
wright agent install |
Install the canonical Wright agent guide into the project's agent skills directory | installation progress and guidance |
wright agent tools [--format messages|json-schema] |
Emit the advertised operations as client tool definitions for code-executing agents | wright-agent/v1 tool-definition document (JSON) |
wright --version prints the implementation version banner
(wright <version> (wright-driver <version>)); the version is the single
authoritative workspace implementation version and is also reported inside
every wright-result/v1 envelope. wright --help is the canonical help
surface.
Commands that take input accept a file path, a project directory, or - for
stdin (wright lookup, wright lint package, wright completion,
wright update, and wright agent install take none). An omitted input uses the current directory. Input kind is detected from the
extension (.opy, .ostw/.del, .json, .txt/.ws) or, for a directory,
from the source files it contains; mixed source kinds fail with structured
ambiguity guidance. Stdin content is auto-detected (protocol JSON starts with
{, otherwise Workshop text). Detection can be overridden with
--kind auto|opy|ostw|workshop|protocol. --locale
overrides Workshop client-locale detection; compile --to <LOCALE> selects
the Workshop output locale independently of the input locale (default: the
input's locale); --root sets the include/project
root; -o/--output chooses the compile output file. --profile <off|compat>
selects the WIR transformation policy (default off) and applies to
Workshop input only (ADR-0028, #725): passing it with an OPY or OSTW
input is a usage error (exit 2) on compile, inspect, and the other
input-taking commands, and in wright serve — at startup when --kind
fixes the type, per request when the kind resolves from the input.
.ostw/.del inputs
remain recognized source kinds for a future provider, but Wright does not ship
a static DEL/OSTW adapter. Every DEL/OSTW workflow fails with the structured
source-provider-unavailable diagnostic (exit 4), without partial output or
an upstream/static fallback. DEL/OSTW provider support is not currently
shipped with Wright and is outside this contract. OPY, Workshop, and protocol
inputs continue through their existing owner-backed paths.
wright compile writes the program to a file and reports only status and
problems: the verdict, diagnostics (including the client import-limit
warnings below), the written path, and the SHA-256 of the written program.
Text and JSON report the same content and neither carries the program text. A
failed compile writes no file.
- The default file is
<input stem>.owin the current directory (the directory name for a directory project).-o PATHchooses another file.-o -prints the program to stdout and nothing else; it cannot be combined with--format json. - An existing output file is replaced only after an interactive
Overwrite <path>? [y/N]confirmation (default No; No exits 1 and writes nothing). Without a terminal the command refuses with exit 2 and a diagnostic naming--force, leaving the file unchanged.--forcereplaces it without asking. - The input source file is never overwritten, with or without
--force; paths are compared after resolution. --to <LOCALE>emits the program in another Workshop client locale, sowright compile --to en-US zh-input.ow -o en.owconverts between client locales without changing the input locale or the input file. An undeclared locale is a usage error; a declared locale missing mappings for the program fails explicitly (missing-mapping) and writes nothing — there is no silent fallback locale.--copyalso copies the program to the system clipboard. A clipboard failure is reported as aclipboard-failedwarning and keeps the written file. On Linux the clipboard is served by the process through X11, so the contents persist after exit only with a clipboard manager running.
wright compile also surfaces known exact Workshop client import
constraints as warnings without failing compilation (#488): a program whose
canonical element count exceeds the Overwatch client's 32768-element import
limit still emits its artifact and exits 0, carrying a
target-element-limit warning that reports the observed count and names the
largest contributing rule. The distinction is deliberate. compile means
Wright produced valid Workshop text, not that the current client imports it
under every resource limit. wright analyze remains the detailed cost and
hotspot surface; check does not evaluate client importability.
The rationale for current-directory defaults, directory targets, and explicit
ownership ambiguity is recorded in
ADR-0016.
wright agent install writes the canonical Wright agent guide, the wright
skill from wrightkit/skills vendored into the binary so the command needs
no Node tooling and no network, into the project's agent skills directory,
default ./.agents/skills/wright/ (--dest DIR selects another skills
directory and installs under DIR/wright/; --dry-run reports the
destination without writing, though the foreign-path refusal below still
applies). The installed content is a SKILL.md plus its
references/ and a BUILD.json that records the Wright version, the
upstream skill pin, and the guide's content hash; it contains no executable
semantic or tool implementation.
The guide teaches an agent to prefer Wright's structured surfaces, such as
capability discovery, check/lint/analyze/inspect, serve, and
validated edits, over textual guessing, to re-validate after edits, and to
surface refused or unsupported capabilities rather than work around them.
Manual installation of the same guide stays possible and unchanged: copy
skills/wright/ from the wrightkit/skills repository into
.agents/skills/ (or run npx skills add wrightkit/skills).
Re-running install refreshes the guide in place: an identical install
reports already-up-to-date, while an older or edited Wright-installed guide
is replaced with the current copy. The directory is recreated, so files
dropped inside it are removed. A path that was not installed by wright agent install (directory, file, or link) is refused unless --force is
passed. Removing the directory uninstalls the guide; nothing outside the
skills directory is touched.
wright agent install --mcp <TARGET> also writes the one entry that
makes a coding-agent harness start wright serve --transport mcp for the
project, so the shipped wright_* tools are discoverable without hand-written
MCP configuration (ADR-0020). --no-guide skips the guide and registers only
the server; --force and --dry-run apply to both parts. It runs from the
project root and writes only project-local config:
| Target | File | Servers key | Session input |
|---|---|---|---|
claude |
.mcp.json |
mcpServers |
${CLAUDE_PROJECT_DIR:-.} |
cursor |
.cursor/mcp.json |
mcpServers |
${workspaceFolder} |
vscode |
.vscode/mcp.json |
servers |
${workspaceFolder} |
Any other target is a usage error; Wright does not guess harness formats. The
harness owns starting the stdio process; Wright adds no daemon or registry.
Claude Code sets CLAUDE_PROJECT_DIR only in the server's own environment, so
its ${...} expansion in args falls back to . (the server's working
directory, the project root in a normal session).
Re-running reports up to date when the wright entry is exactly the one Wright
generates. Any other wright entry, including one that already runs wright serve --transport mcp with extra fields or a different input, is refused
untouched unless --force replaces it (user-added fields such as env are
lost). A Wright MCP entry under a different key is refused as a duplicate.
Other servers and settings are preserved (JSON key order may be normalized). A
config that is not plain JSON, or a symlinked config file or directory, is
refused; writes replace the file atomically.
To remove the server, delete the wright entry from the config file (and the
guide directory if installed); to update, re-run with --force.
wright agent tools prints every operation capabilities advertises as
client tool definitions for code-executing agents, versioned
wright-agent/v1 and deterministic. --format messages (default) emits
Anthropic Messages API tool definitions: name, description (which also
describes the result), input_schema, and
allowed_callers: ["code_execution_20260120"] for programmatic tool
calling; emitted schemas have no recursive $ref, which the API rejects.
--format json-schema emits the same operations as plain JSON Schema for
other harnesses. See
the agent contract.
Commands that report findings (check, analyze, lint, inspect cost)
share the finding-selection options --severity, --rule-id, --file, and
--max, which narrow reported diagnostics/findings without changing verdicts
or exit codes; see lint configuration and findings and
presentation.
Every workflow command accepts the opt-in gating option --fail-on <error|warning|info> (ADR-0030 decision 13): when the complete result's
highest diagnostic or finding severity reaches the threshold — judged like
the verdict, on the full set before selection — the command exits 1 and
reports a FAIL verdict on every renderer and in JSON (ok: false,
exit: 1). The gate only raises a verdict: a result below the threshold is
untouched (a WARN result still exits 0, so gating is off by default), and
a command already failing keeps its own exit code and verdict rather than
reporting FAIL.
The flag configures SessionConfig.fail_on, so the driver — not the CLI —
decides the verdict (#1002): every surface over the same session
configuration, including the lint agent operation, reports the same
ok/exit. Deciding when a workflow enables the gate belongs to the
wright action; the CLI carries no policy.
check, lint, analyze, inspect, and the inspect subcommands report
verdicts and actionable content by default. Text and compact JSON carry the
same items; JSON is for programs processing results. Long lists show at most
20 items, their total, and an expansion hint. Lint may show fewer items to keep its compact result within 5500 bytes before the envelope; --all also lifts this length bound. The ranked analyze report preserves its top-five hotspots, risks, and variables and top-three condition trees. --all (agent field all: true)
lifts the default cap; filters and an explicit --max still select results.
Verdicts and exit codes use the complete set, even when an error is withheld.
The former --brief flag and brief request field are removed. Bare inspect
and analyze no longer expose complete fact dumps: use the inspect
subcommands to obtain a specific detail. The in-process CompilerSession
API retains complete typed results. This pre-1.0 shape change is recorded in
ADR-0025.
inspect owns the semantic query surface: the bare command prints the
bounded summary, and its six subcommands, inspect rules, inspect symbols,
inspect refs, inspect cfg, inspect callgraph, inspect cost, expose
each detail area. They run the same operations the agent contract serves
(rules, symbols, references + usage, cfg, callGraph, costEstimate)
through the session's ToolService, so a CLI result payload equals the
agent operation's payload for the same input. Nesting them under inspect
keeps the top-level command surface to distinct user intents (#439); the
agent request names stay flat operation names, not command paths.
inspect refs <NAME> and inspect cfg <RULE> address their target by its
declared name, a variables/subroutines entry or a rule("name"),
resolved against the loaded program's semantic index inside the driver.
Callers never need the program's numbering, where symbol ids and rule
indexes are different spaces (the agent contract keeps accepting numeric
ids too). An unmatched name is a structured unknown-symbol/unknown-rule
diagnostic with exit 1; a name shared by several candidates is
ambiguous-symbol/ambiguous-rule listing the numeric ids to fall back to.
Resolution never guesses.
-
inspect refsreports theusagecounts (reads,writes,calls,rules) as the header of the reference list; there is no separate usage command. The reference list includes the declaration entry alongside reads and writes, with identifier spans reported by the analyzer (#433). When a source provider's map is statement-grain — no identifier occurrences — a reference's span falls back to the enclosing statement's span, the sameat=inspect costreports (#892). -
inspect symbols --only <KIND>narrows the list toglobalVariable,playerVariable,subroutine, orrule(kebab-case aliases work). It is spelled--onlybecause--kindalready selects the input frontend. The result reports the selected list and its selection total on every surface. -
The query subcommands accept the same selection fields the agent operations take (#531):
inspect rulesadds--rule,--file,--max, and--content(without--max,--contentreturns at most 20 rules unless--allis set),inspect symbolsadds--only/--file/--max,inspect refsadds--only(a reference kind),--rule,--file, and--max,inspect cfgadds--only(a block kind) and--max, andinspect callgraphadds--caller,--callee, and--max. With any selection flag the JSON result wraps the list in{<items>, "selection": {"total", "withheld"}}; an unknown filter value exits 2 with aninvalid-selectiondiagnostic. -
inspect costaccepts the finding-selection options, applied to its findings list exactly as oncostEstimate. ItselementCountmember is the canonicalworkshop-rselement count against the 32768-element client budget. Each rule's share is ranked by count, largest first, whereindexis the canonical rule position; the 5 largest are shown by default and--allshows every rule. The count is a structure count, not a statement of client importability; when the count cannot runelementCountcarries onlybudget(the known client limit still reports) andelementCountUnavailablenames the reason — the count is never reported as zero (ADR-0030 decision 3). Each rule entry and each finding carries its source location (span); a node without a retained span renders and serializes with no location (#722). Likewiseexact.emittedBytesisnullplus anemittedBytesUnavailablereason when emission fails, never0.Run cache and gate (ADR-0027).
compileandinspect costkeep a small project-local run cache under.wright/at the project root and, by default, report the change in element cost against the previous successful run of the same key (resolved input, kind, locale, profile). The comparison is a convenience for people: it never changes the verdict or exit code, and it is not part of the default JSON or agent result — an agent requests a comparison explicitly by passing the reference as inline data.--baseline <name>compares against a named reference (an unknown name is a usage error) and--save-baseline <name>records the current run under a name.--max-total <N>is an explicit gate oninspect cost: exit 1 naming the overage when the element total exceeds N, identical with and without a cache.--no-cacheor a non-emptyWRIGHT_NO_CACHEdisable reading and writing.wright/; add it to your.gitignore— Wright does not edit it for you. A failing run never replaces the previous reference, and cache failures never fail a command.Under a truthy
CIthe implicit previous-run comparison stays off, but the explicit baseline flags still read and write.wright/so a CI cache can carry it between runs (ADR-0030):--save-baselinerecords the run and--baselinecompares against it only when a usable record exists. A missing cache, an unknown name, or a record that is unreadable, incompatible, or keyed to a different input, kind, locale or profile turns the comparison off with no error, warning or output — the verdict and exit code are unchanged. -
inspect cost --rule <NAME>shows where one declared rule's element cost goes (#724): the rule'sCostliest statements(its direct conditions and actions ranked by recursivecount) andBy node name(each node's ownbase_count + adjustmentcost summed per canonical node name, with occurrences). ThebyNodeentries reconcile to the rule'scount. An unknown rule name exits 2 with aninvalid-selectiondiagnostic. The agentcostEstimateoperation accepts the same optionalruleand returnsrule,statements[]andbyNode[]as additive fields. -
persistentObjectshas no standalone command;analyzereports aggregate counts underresult.persistentObjects; use the agent operation for details.
Text output follows the human-first presentation described in
presentation: each query leads with the target identity and
its summary: the program inventory and a bounded rule preview for bare
inspect, kind-grouped symbols with primary locations for symbols, the
usage counts before the reference list for refs, the graph shape before
block detail for cfg, fan-in/fan-out highlights before the edge list for
callgraph, and exact totals before findings for cost. Long lists show a
first page of 20 entries followed by the total and an expansion hint; --format json
reports the essential result in structured form, with the same list cap as text.
wright lookup [QUERY] runs the agent contract's lookup operation
(ADR-0021): it resolves a display name, a near spelling, or a guess to the
language owner's accepted spelling and signature, answering from language
vocabulary alone. It takes no INPUT and never loads or requires a
project. Agents should use it before writing in an unfamiliar language and
when check/compile rejects a name; text output of those commands
prints a one-line hint after each unknown-* diagnostic pointing here
(the structured result is unchanged).
--language workshop|opy(defaultworkshop) selects the owner: Workshop answers from the in-processworkshop-rscatalog; OverPy answers through the configuredopy-rsprovider's LPP lookup capability, and reports an explicitunavailablepayload with exit 3 when the provider does not negotiate it. Wright never guesses locally.--onlynarrows the entry kinds:action,value,event,enum-member,enum,setting,parameter,setting-path,member-action,member-value,structural,operator. This is the union of the kinds both owners emit (#563). A kind one language never emits answers[]on it.--within <IDENTITY>lists what an identity contains: an enum domain's members, a callable's parameters, or a settings path's child segments. An unmatched scope identity refuses withlookup.unknownWithin.--locale <LOCALE>selects the Workshop display-name locale;--limit <N>bounds the entry count (default 3, maximum 10).- Text mode prints one line per entry, the rendered signature for a
callable or otherwise the accepted spelling, with its kind and owner identity.
-f jsonprints the sameresultpayloadservereturns (language,owner,entries[], orunavailable).
The structured editor for raw Workshop is the experimental web
application at web.wrightkit.dev (ADR-0037),
built in the wrightkit/editor repository over the WebAssembly Wright
core. The retired wright ui command and its loopback host are recorded
in ADR-0032.
wright convert [INPUT] --target opy|ostw converts validated Workshop
input through the configured language provider, or refuses the recognized
OSTW target because DEL/OSTW provider support is not currently shipped,
through the shared driver/session conversion operation
(CompilerSession::convert). The CLI is a thin passthrough: it parses argv,
builds the session, calls the driver workflow, and renders the envelope.
Reconstruction logic lives in the language provider rather than the CLI
layer. The driver reuses its own load() path (kind detection, Workshop
parsing, WIR validation), emits the validated program as a canonical
Workshop artifact, and delegates OPY reconstruction to the provider's
lpp/reconstruct; OSTW is an explicit provider boundary and never uses a
static Wright implementation.
- The target flag is required and explicit (
--target opy|ostw); a missing or unknown target is a usage error (exit 2), and--targeton any other command is a usage error too. - Only Workshop input is accepted, and there is no direct conversion
between OPY and OSTW.
A non-Workshop input fails with the structured
convert-input-kinddiagnostic (exit 1). - Workshop-to-OPY reconstruction is available only when the provider
negotiates
reconstruct; the shipped first-partyopy-rsprovider advertises it starting with 0.3.19, and an older or third-party provider without it refuses withcapability-unavailable(exit 3) and produces no source. Workshop-to-OSTW is recognized but unavailable because no DEL/OSTW provider is shipped (source-provider-unavailable). - When a provider does negotiate
reconstruct, the result is canonical reconstructed source for the selected target (result.text) plus its deterministic SHA-256 (result.sha256) and the target (result.target). Reconstruction is semantic, not original-source recovery: comments, formatting, macros, functions, and source abstractions are not recovered (see the support matrices for the exact reconstructed and rejected surfaces). - Non-representable constructs fail deterministically with the
reconstructor's stable structured diagnostics (stage
reconstruction, exit code 3) and never carry partial source. The supported directions and their limits are documented indocs/opy/support-matrix.mdanddocs/ostw/support-matrix.md.
The conversion boundary is covered by the CLI and driver contract tests. They assert the capability-gated Workshop-to-OPY provider handoff and the explicit Workshop-to-OSTW refusal; owner reconstruction semantics are tested in the owning language repository.