Current prototype command reference

Updated Jun 22, 2026

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-definition
  • observe.get-card-hover
  • observe.graph-slice
  • observe.find-references
  • observe.call-hierarchy
  • verify.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-indexed LINE:COL position within the symbol.
  • --detail (optional) — progressive detail level, controlling how much information the card contains. One of minimal, signature, structure (default), semantic, or full.
  • --format (optional) — output format. Currently, only json (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 the symbol and provenance fields.
  • signature — adds the signature block (for callable symbols) exposing the callable display string, parameters, and return type. Non-callable symbols (classes, variables, constants) may omit signature or structure it differently.
  • structure (default) — further adds doc, structure, and basic metrics. May include attachments.
  • semantic — attempts LSP enrichment via textDocument/hover. When the language server is available and supports hover, the card's lsp field 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 with file://.
  • --position (required) — 1-indexed LINE:COL position within the root symbol.
  • --depth (optional) — maximum traversal depth. Default: 2.
  • --direction (optional) — traversal direction. One of in, out, or both (default).
  • --edge-types (optional) — comma-separated list of edge types to follow. Any combination of call, import, config. Default: all three.
  • --min-confidence (optional) — minimum edge confidence threshold between 0.0 and 1.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 of minimal, signature, structure (default), semantic, or full.
  • --node-detail (optional) — detail level for neighbouring node cards. One of minimal (default), signature, structure, semantic, or full.

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:

  1. weaverd parses --provider, --refactoring, --file, and --position.
  2. It validates that --provider is a known actuator name and that --refactoring is a supported user-facing operation.
  3. It maps rename to rename-symbol, infers the target language from the path, and validates the explicit provider against that capability request.
  4. It emits a structured CapabilityResolution record describing that routing decision.
  5. The file content is read from the workspace and sent to the plugin in-band.
  6. The plugin executes rename-symbol using position and new_name.
  7. The plugin returns a unified diff for the modified file.
  8. Weaver validates the diff via the Double-Lock safety harness (syntax then semantic checks).
  9. 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 rope provider:
  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-analyzer provider:
  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:

  • rope for Python (timeout_secs = 30, capabilities = ["rename-symbol"])
  • rust-analyzer for 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)