weaver exposes three command families: the --capabilities probe, daemon
lifecycle commands, and domain operations (observe, act, verify). Domain
commands are sent to the daemon as JSONL; any arguments after the operation are
forwarded verbatim without CLI validation. This section describes the current
implementation only; the 0.1.0 target command surface is resource-first and is
summarized at the start of this guide.
Bare invocation
Running weaver without any arguments prints a short help summary to standard
error and exits with a non-zero status code:
error: command domain must be provided
Usage: weaver <DOMAIN> <OPERATION> [ARG]...
Domains:
observe Query code structure and relationships
act Perform code modifications
verify Validate code correctness
Next command:
weaver --help
This output follows the unified three-part error template: an error statement,
an alternatives block, and a concrete next command. It does not require a
configuration file or a running daemon. Use weaver --help for the full
reference, including global options and the daemon subcommand.
Version
Running weaver --version or weaver -V prints the version string to standard
output and exits with code 0:
weaver 0.1.0
This output does not require a configuration file or a running daemon.
Top-level help
Running weaver --help displays the full command reference to standard output
and exits with code 0. The output includes a purpose statement, quick-start
examples, global options, the daemon subcommand, and a catalogue of all
domains and operations. It also includes the shared configuration flags
--config-path, --daemon-socket, --log-filter, --log-format,
--capability-overrides, and --locale in the Options: section:
Domains and operations:
observe — Query code structure and relationships
get-definition find-references grep
diagnostics call-hierarchy get-card
graph-slice
act — Perform code modifications
rename-symbol apply-edits apply-patch
apply-rewrite refactor
verify — Validate code correctness
diagnostics syntax
This catalogue is built into the binary and does not require a running daemon or configuration file.
The graph-slice operation uses this syntax:
weaver observe graph-slice --uri <URI> --position <LINE:COL> [OPTIONS]
weaver daemon start --help exposes the same six configuration flags in its own
Options: section. As with the top-level command, the help surface is
truthful about the shared config contract, but the flags still need to appear
before daemon start at runtime in order to change behaviour.
Domain-only guidance
Running a domain without an operation fails fast on the client side. Known domains print the valid operations for that domain. Unknown domains also fail fast on the client side, even when an operation token is present. This happens before configuration loading, daemon startup, or socket access. Example:
$ weaver observe
error: operation required for domain 'observe'
Available operations:
get-definition
find-references
grep
diagnostics
call-hierarchy
get-card
graph-slice
Next command:
weaver observe get-definition --help
The listed graph-slice operation accepts:
weaver observe graph-slice --uri <URI> --position <LINE:COL> [OPTIONS]
Unknown domains list the canonical domains instead of printing the operation catalogue:
$ weaver obsrve get-definition --uri file:///tmp/main.rs --position 1:1
error: unknown domain 'obsrve'
Valid domains: observe, act, verify
Did you mean 'observe'?
Next command:
weaver observe get-definition --help
The suggestion line appears only when exactly one valid domain is within edit distance 2 of the supplied token. More distant values omit the suggestion but still provide a next command:
$ weaver bogus get-definition --uri file:///tmp/main.rs --position 1:1
error: unknown domain 'bogus'
Valid domains: observe, act, verify
Next command:
weaver --help
All error messages follow the unified three-part template: error statement, alternatives block, and concrete next command.
Unknown operations are handled differently. The request still reaches the daemon because the daemon router owns the canonical operation list for each domain. Human-readable output now includes the full alternatives returned by the daemon:
$ weaver --output human observe nonexistent
error: unknown operation 'nonexistent' for domain 'observe'
Available operations:
get-definition
find-references
grep
diagnostics
call-hierarchy
get-card
graph-slice
Next command:
weaver observe get-definition --help
The graph-slice alternative in this list maps to:
weaver observe graph-slice --uri <URI> --position <LINE:COL> [OPTIONS]
JSON output forwards the daemon payload unchanged:
{
"status": "error",
"type": "UnknownOperation",
"details": {
"domain": "observe",
"operation": "nonexistent",
"known_operations": [
"get-definition",
"find-references",
"grep",
"diagnostics",
"call-hierarchy",
"get-card",
"graph-slice"
]
}
}
Output formats
Daemon responses are JSON objects with kind set to stream or exit. Stream
messages include a stream field (stdout or stderr) plus a data payload;
exit messages contain a numeric status. The CLI writes each data payload to
the matching host stream and terminates using the exit status provided by the
final exit message. The data payload can be plain text (human-readable) or a
JSON document (machine-readable).
The CLI accepts --output with auto (default), human, and json values.
auto selects human when stdout is a TTY and json when output is
redirected, so JSON pipelines remain stable. Place --output before the
command domain and operation because arguments after the operation are passed
directly to the daemon (for example,
weaver --output human observe get-definition ...).
When --output human is active, commands that return code locations or
diagnostics render context blocks with file headers, line-numbered source
context, and caret spans. If source content is unavailable, the CLI falls back
to the path and range with an explanation of why context could not be shown.
Example JSONL envelope:
{"kind":"stream","stream":"stdout","data":"definition: file:///path/main.rs:42:17\n"}
{"kind":"exit","status":0}
Daemon connections time out after five seconds. The CLI aborts after ten consecutive blank lines and treats missing exit messages as failures.
Capability probe
Syntax:
weaver --capabilities
Output is always JSON (pretty-printed for humans). Example:
{
"languages": {
"python": {
"overrides": {
"observe.call-hierarchy": "force"
}
}
}
}
Daemon lifecycle commands
Syntax:
weaver daemon start
weaver daemon stop
weaver daemon status
Example human-readable output (daemon start):
daemon ready (pid 12345) on unix:///tmp/weaver/uid-1000/weaverd.sock
runtime artefacts stored under /tmp/weaver/uid-1000
Example JSON output written by the daemon health snapshot file
(weaverd.health):
{"status":"ready","pid":12345,"timestamp":1713356400}
Domain commands (`observe`, `act`, `verify`)
Syntax:
weaver <domain> <operation> [ARG ...]
Current capability keys used for Language Server Protocol (LSP)-backed operations:
observe.get-definitionobserve.get-card-hoverobserve.graph-sliceobserve.find-referencesobserve.call-hierarchyverify.diagnostics
observe.get-card-hover controls whether observe get-card --detail semantic
may route textDocument/hover requests for LSP enrichment.
Syntactic operations provided by weaver-syntax use the same domain/operation
shape (observe grep and act apply-rewrite) once they are wired into the
daemon request loop. The examples below are illustrative; the daemon defines
the exact payload schema.
observe get-definition
Syntax:
weaver observe get-definition --uri <URI> --position <LINE:COL>
Both --uri and --position are required. The position uses 1-indexed line
and column numbers (matching editor conventions). The language is inferred from
the file extension: .rs for Rust, .py for Python, and .ts/.tsx for
TypeScript. Unsupported extensions return an error.
Human output:
<PATH>
--> <LINE>:<COL>
|
<LINE> | <CODE>
| ^ definition
JSON payload (written to stdout stream):
[{"uri":"file:///path/to/file.rs","line":42,"column":17}]
The response is an array of definition locations. Each location includes the target URI, line number, and column (all 1-indexed). The array may be empty if no definition is found, or contain multiple entries for overloaded symbols.
observe find-references
Syntax:
weaver observe find-references --uri <URI> --position <LINE:COL>
Human output:
<PATH>
--> <LINE>:<COL>
|
<LINE> | <CODE>
| ^ reference
JSON payload:
{"references":[{"uri":"<URI>","line":12,"column":3}]}
observe call-hierarchy
Syntax:
weaver observe call-hierarchy --uri <URI> --position <LINE:COL>
Human output:
call hierarchy: <SYMBOL> (direction outgoing, depth 2)
JSON payload:
Call hierarchy responses return a call graph. Each node includes its stable identifier, symbol name, kind, location, and optional container. Each edge captures the caller, callee, provenance, and optional call-site position.
{
"nodes": [
{
"id": "/src/lib.rs:10:0:main",
"name": "main",
"kind": "function",
"uri": "file:///src/lib.rs",
"line": 10,
"column": 0,
"container": null
}
],
"edges": [
{
"caller": "/src/lib.rs:10:0:main",
"callee": "/src/lib.rs:42:0:helper",
"source": "lsp",
"call_site": { "line": 12, "column": 4 }
}
]
}
observe get-card
Syntax:
weaver observe get-card --uri <URI> --position <LINE:COL> [--detail <LEVEL>]
Arguments:
--uri(required) — file URI of the source file containing the symbol.--position(required) — 1-indexedLINE:COLposition within the symbol.--detail(optional) — progressive detail level, controlling how much information the card contains. One ofminimal,signature,structure(default),semantic, orfull.--format(optional) — output format. Currently, onlyjson(the default) is supported.
Response:
The response is a discriminated-union JSON envelope keyed on the "status"
field. When "status" is "refusal", the envelope carries a refusal payload
indicating why a card could not be produced. When "status" is "success",
the envelope contains the card payload. The overall shape of the envelope
therefore depends on the "status" value.
observe get-card is Tree-sitter-first. Supported Rust, Python, and TypeScript
files return a deterministic card. Requests for unsupported file types or
positions that do not resolve to a symbol return a structured refusal. When
--detail semantic (or higher) is requested, the handler attempts LSP
enrichment via textDocument/hover to populate the card's lsp field with
hover documentation, type information, and deprecation status. If the language
server is unavailable, the card degrades gracefully to a Tree-sitter-only
extraction with provenance "tree_sitter_degraded_semantic".
observe get-card responses are cached per daemon process by
(path, content hash, language, detail level, line, column). Repeating the
same request against an unchanged file revision reuses the cached card instead
of reparsing the file. When the file contents change, Weaver invalidates stale
cached revisions for that path and records a fresh provenance.extracted_at
timestamp. Cache hits preserve the original extraction timestamp.
When the operation cannot produce a card, the status is "refusal":
{
"status": "refusal",
"refusal": {
"reason": "unsupported_language",
"message": "observe get-card: unsupported language for path /tmp/example.txt",
"requested_detail": "structure"
}
}
On success, the status is "success" and the payload wraps a SymbolCard
object:
{
"status": "success",
"card": {
"card_version": 1,
"symbol": {
"symbol_id": "sym_abc123",
"ref": {
"uri": "file:///src/main.rs",
"range": {
"start": { "line": 10, "column": 0 },
"end": { "line": 42, "column": 1 }
},
"language": "rust",
"kind": "function",
"name": "process_request",
"container": "handlers"
}
},
"signature": {
"display": "fn process_request(req: &Request) -> Response",
"params": [{ "name": "req", "type": "&Request" }],
"returns": "Response"
},
"doc": {
"docstring": "Processes an incoming request.",
"summary": "Processes an incoming request.",
"source": "tree_sitter"
},
"attachments": {
"doc_comments": ["Processes an incoming request."],
"decorators": [],
"normalized": { "decorators": [] },
"bundle_rule": "leading_trivia"
},
"structure": {
"locals": [{ "name": "result", "kind": "variable", "decl_line": 15 }],
"branches": [{ "kind": "if", "line": 18 }]
},
"metrics": { "lines": 33, "cyclomatic": 5 },
"provenance": {
"extracted_at": "2026-03-03T12:34:56Z",
"sources": ["tree_sitter"]
}
}
}
Note: the card.symbol.ref.range uses 0-based line and column numbers in a
half-open interval — start is inclusive and end is exclusive (i.e.
[start, end)). This differs from the --position request flag, which accepts
1-indexed LINE:COL values.
Card fields beyond identity are progressively included based on the detail level:
minimal— returns only thesymbolandprovenancefields.signature— adds thesignatureblock (for callable symbols) exposing the callable display string, parameters, and return type. Non-callable symbols (classes, variables, constants) may omitsignatureor structure it differently.structure(default) — further addsdoc,structure, and basicmetrics. May includeattachments.semantic— attempts LSP enrichment viatextDocument/hover. When the language server is available and supports hover, the card'slspfield is populated with hover documentation, type information, and deprecation status, and provenance includes"lsp_hover". When LSP is unavailable, the card degrades to a Tree-sitter-only extraction with provenance"tree_sitter_degraded_semantic".full— currently degrades to a Tree-sitter-only card with explicit provenance markers; dependency edges and fan-in/out metrics are not yet included.
observe graph-slice
Syntax:
weaver observe graph-slice --uri <URI> --position <LINE:COL> [OPTIONS]
Arguments:
--uri(required) — file URI of the source file containing the root symbol. Must start withfile://.--position(required) — 1-indexedLINE:COLposition within the root symbol.--depth(optional) — maximum traversal depth. Default:2.--direction(optional) — traversal direction. One ofin,out, orboth(default).--edge-types(optional) — comma-separated list of edge types to follow. Any combination ofcall,import,config. Default: all three.--min-confidence(optional) — minimum edge confidence threshold between0.0and1.0. Default:0.5.--max-cards(optional) — maximum number of cards in the budget. Default:30.--max-edges(optional) — maximum number of edges in the budget. Default:200.--max-estimated-tokens(optional) — maximum estimated token count in the budget. Default:4000.--entry-detail(optional) — detail level for the entry card. One ofminimal,signature,structure(default),semantic, orfull.--node-detail(optional) — detail level for neighbouring node cards. One ofminimal(default),signature,structure,semantic, orfull.
Response:
The response is a discriminated-union JSON envelope keyed on the "status"
field. When "status" is "refusal", the envelope carries a structured
refusal explaining why a slice could not be produced. When "status" is
"success", the envelope contains the graph slice.
When the operation cannot produce a slice, the status is "refusal":
{
"status": "refusal",
"schema_version": "graph_slice.v1",
"refusal": {
"reason": "unsupported_language",
"message": "observe graph-slice: unsupported language for 'notes.txt'"
}
}
On success, the response wraps the slice with constraints, cards, edges, and spillover metadata:
{
"status": "success",
"schema_version": "graph_slice.v1",
"slice_version": 1,
"entry": { "symbol_id": "sym_abc123" },
"constraints": {
"depth": 2,
"direction": "both",
"edge_types": ["call", "import", "config"],
"min_confidence": 0.5,
"budget": {
"max_cards": 30,
"max_edges": 200,
"max_estimated_tokens": 4000
},
"entry_detail": "structure",
"node_detail": "minimal"
},
"cards": [
{
"card_version": 1,
"symbol": {
"symbol_id": "sym_abc123",
"ref": {
"uri": "file:///src/main.rs",
"range": {
"start": { "line": 10, "column": 0 },
"end": { "line": 42, "column": 1 }
},
"language": "rust",
"kind": "function",
"name": "process_request",
"container": "handlers"
}
},
"provenance": {
"extracted_at": "2026-03-03T12:34:56Z",
"sources": ["tree_sitter"]
}
}
],
"edges": [],
"spillover": {
"truncated": false,
"frontier": []
}
}
The constraints object reflects the applied request parameters after defaults
are resolved. The cards array contains the extracted symbol cards within the
budget. For prototype archive roadmap item 7.2.1, Weaver builds a deterministic
same-file slice: the entry card plus additional same-file symbol cards that fit
within budget.max_cards. The edges array is therefore currently empty in
runtime responses, while the stable schema already reserves the typed edge
shape for later milestones. The --max-edges CLI flag is accepted for forward
compatibility but has no runtime effect in 7.2.1; only budget.max_cards
limits the number of cards produced.
When traversal exceeds the budget, spillover.truncated is true and
spillover.frontier lists candidate same-file symbols that were discovered but
excluded. spillover.truncated may also be true while spillover.frontier
is empty when the discovery cap, rather than excluded cards, caused truncation.
The discovery_cap_marks_spillover_truncated_when_card_budget_remains test is
the canonical behaviour: spillover.frontier is populated only for discovered
candidate symbols excluded from the response, not for symbols that discovery
limits prevented Weaver from enumerating.
The stable edge schema is already locked even though runtime edges are deferred
to later milestones. When present, every edge will carry a resolution_scope of
full_symbol_table, partial_symbol_table, or lsp.
observe grep
Syntax:
weaver observe grep --pattern <PATTERN> --path <PATH>
Optional flags:
--language <LANG>
Human output:
match: <PATH>:<LINE>:<COL> "$NAME"
JSON payload:
{"matches":[{"start":[1,1],"captures":{"NAME":"foo"}}]}
verify diagnostics
Syntax:
weaver verify diagnostics --uri <URI>
Human output:
<PATH>
--> <LINE>:<COL>
|
<LINE> | <CODE>
| ^ <MESSAGE>
JSON payload:
{"diagnostics":[{"line":12,"column":5,"message":"..."}]}
act apply-patch
Syntax:
weaver act apply-patch < patch.diff
act apply-patch reads a Git-style patch stream from STDIN. The patch may
include SEARCH/REPLACE blocks for modifications, new file mode hunks for file
creation, or deleted file mode entries for deletions. Binary patches are
rejected, and an empty STDIN payload is treated as an error by the CLI.
JSON payload:
{"status":"ok","files_written":1,"files_deleted":0}
Failures return structured error envelopes on stderr and a non-zero exit
status. Verification failures are rendered with the same human-readable output
as other act commands when --output human is selected.
The daemon rejects JSONL request lines larger than 1 MiB, so large patch
streams should be split into multiple act apply-patch invocations.
act apply-rewrite
Syntax:
weaver act apply-rewrite --pattern <PATTERN> --replacement <REPL> --path <PATH>
Human output:
rewrite: <PATH> (replacements 2)
JSON payload:
{"path":"<PATH>","replacements":2,"changed":true}
act refactor
Delegates a refactoring operation to a registered plugin. The plugin runs in a sandboxed process and produces a unified diff that is validated by the Double-Lock safety harness before any filesystem change is committed.
Syntax:
weaver act refactor --provider <PLUGIN> --refactoring <OP> --file <PATH> --position <LINE:COL> [KEY=VALUE...]
Arguments:
Table: act refactor command-line flags
| Flag | Description |
|---|---|
--provider |
Required provider name for the registered plugin. Built-in values are rope for Python rename flows and rust-analyzer for Rust rename flows. |
--refactoring |
Refactoring operation to request (currently rename). The handler maps rename to the rename-symbol capability contract internally. |
--file |
Path to the target file (relative to workspace root). |
--position |
1-indexed LINE:COL position of the symbol used as the rename anchor. |
KEY=VALUE |
Extra key-value arguments forwarded to the plugin. |
The plugin receives the file content in-band as part of the JSONL request and
does not need filesystem access. The daemon validates the resulting diff
through both the syntactic (Tree-sitter) and semantic (LSP) locks before
writing to disk. A plugin response that claims success but does not carry diff
output is refused as a failure: Weaver exits with status 1, prints
act refactor failed: plugin succeeded but did not return diff output, and
leaves the filesystem unchanged.
For the built-in actuators, rename requires --position <LINE:COL> and
new_name=<IDENTIFIER>. weaverd requires all four top-level flags in one
request and rejects incomplete invocations before plugin resolution, file I/O,
or backend startup. The legacy offset=<BYTE_OFFSET> form is accepted only as
a deprecated compatibility path and will be removed in a future release. When
offset= is supplied without --position, weaverd writes the following
warning to stderr before processing the request:
Warning: 'offset=' is deprecated; use '--position LINE:COL' instead.
See the rename position migration guide for upgrade examples.
Parameter semantics and valid values
The act refactor handler requires --provider, --refactoring, --file, and
--position, then forwards any additional KEY=VALUE pairs to the selected
plugin.
Table: act refactor parameter semantics and validation
| Parameter | Meaning | Valid values | Failure conditions |
|---|---|---|---|
--provider |
Provider to use for the refactoring request. | Registered actuator name such as rope or rust-analyzer. |
Missing flag, missing value, or unknown provider name causes failure. |
--refactoring |
Refactoring operation requested from the plugin. The handler maps rename to the rename-symbol capability contract before forwarding to the plugin. |
Currently only rename is implemented by built-in rope and rust-analyzer plugins. |
Missing flag, missing value, or unsupported operation name (for example extract_method) causes failure. |
--file |
Target file to load and refactor. | Workspace-relative path to an existing readable file (for example src/main.py). |
Missing flag, missing value, absolute paths, parent traversal (..), or unreadable/missing files cause failure. |
--position |
Symbol occurrence used as the rename anchor. | 1-indexed LINE:COL value, counting Unicode characters for the column. |
Missing flag, malformed value, zero line or column, or a position outside the file causes failure. |
new_name |
New symbol name used by rename. |
Non-empty string value. | Missing key, non-string value, or empty/whitespace-only value causes failure. |
offset |
Deprecated compatibility spelling for older rename invocations. | Non-negative UTF-8 byte offset. Prefer --position. |
Cannot be combined with --position; malformed values are rejected by the daemon before plugin execution. |
The daemon converts --position to any provider-specific offset required by
the current built-in plugins. Byte offsets are an internal compatibility
detail, not the canonical command interface.
Expected behaviour of the worked examples
Both examples follow the same execution pipeline:
weaverdparses--provider,--refactoring,--file, and--position.- It validates that
--provideris a known actuator name and that--refactoringis a supported user-facing operation. - It maps
renametorename-symbol, infers the target language from the path, and validates the explicit provider against that capability request. - It emits a structured
CapabilityResolutionrecord describing that routing decision. - The file content is read from the workspace and sent to the plugin in-band.
- The plugin executes
rename-symbolusingpositionandnew_name. - The plugin returns a unified diff for the modified file.
- Weaver validates the diff via the Double-Lock safety harness (syntax then semantic checks).
- If validation passes, Weaver writes the file atomically and returns:
{"files_deleted":0,"files_written":1,"status":"ok"}.
When required flags are missing, act refactor returns one deterministic
actionable error instead of failing one flag at a time:
invalid arguments: act refactor requires --provider <plugin>, --refactoring <operation>, --file <path>, and --position <line:col>
Valid alternatives:
- Providers: rope, rust-analyzer
- Refactorings: rename
Next command:
weaver act refactor --provider rope --refactoring rename --file path/to/file.py --position 1:1 new_name=renamed_symbol
When validation fails, parameters are invalid, or the plugin reports an error,
the command exits non-zero and leaves the filesystem unchanged. A plugin
response that reports success without a Diff payload is treated the same way:
Weaver refuses the response, exits with status 1, and does not touch the
filesystem.
Worked examples:
- Python rename with explicit
ropeprovider:
weaver --output json act refactor \
--provider rope \
--refactoring rename \
--file src/main.py \
--position 1:5 \
new_name=renamed_symbol
Example routing rationale emitted before the final success payload:
{
"status": "ok",
"type": "CapabilityResolution",
"details": {
"capability": "rename-symbol",
"language": "python",
"requested_provider": "rope",
"selected_provider": "rope",
"selection_mode": "explicit_provider",
"outcome": "selected",
"candidates": [
{
"provider": "rope",
"accepted": true,
"reason": "matched_language_and_capability"
},
{
"provider": "rust-analyzer",
"accepted": false,
"reason": "unsupported_language"
}
]
}
}
Example final result:
{"files_deleted":0,"files_written":1,"status":"ok"}
- Rust rename with explicit
rust-analyzerprovider:
weaver --output json act refactor \
--provider rust-analyzer \
--refactoring rename \
--file src/main.rs \
--position 1:4 \
new_name=renamed_name
Example final result:
{"files_deleted":0,"files_written":1,"status":"ok"}
The daemon ships with default actuator registrations:
ropefor Python (timeout_secs = 30,capabilities = ["rename-symbol"])rust-analyzerfor Rust (timeout_secs = 60,capabilities = ["rename-symbol"])
By default, it expects plugin executables at:
/usr/bin/weaver-plugin-rope/usr/bin/weaver-plugin-rust-analyzer
Override these paths with:
WEAVER_ROPE_PLUGIN_PATH=/absolute/path/to/weaver-plugin-rope
WEAVER_RUST_ANALYZER_PLUGIN_PATH=/absolute/path/to/weaver-plugin-rust-analyzer
The override path is resolved to an absolute path at daemon startup. If the
plugin executable cannot be launched, act refactor returns a structured
failure and does not modify the filesystem.
The built-in rust-analyzer plugin now declares the same capability contract as
rope for rename flows, even though the CLI continues to accept
--refactoring rename.
In human-readable output mode, Weaver renders the routing rationale as concise text instead of raw JSON, for example:
rename-symbol explicit_provider for python: selected rope (selected)
requested provider: rope
candidate accepted: rope (matched_language_and_capability)
candidate rejected: rust-analyzer (unsupported_language)