Reference: verb ↔ CLI ↔ MCP ↔ library#

Every verb ultimately calls one library function. The CLI and MCP server are thin skins over the same core (tact/cli.py and tact/mcp_server.py). They handle argument parsing, dispatch, and rendering only. This table is the map. Each function’s own docstring (linked via its module) is the deep, load-bearing reference. Read it before relying on an edge case not spelled out below.

Verb

CLI

MCP tool

Library call

One-line contract

Read

tact read PATH

tact_read

tact.read.read_file

Content-hash dedup (unchanged=True via a seen_hashes set) + byte-capped read; the file is always stat’d/hashed in full even when content is truncated.

Edit

tact edit PATH --old --new

tact_edit

tact.apply.apply_edits

Resolve every edit via the ladder, interval-check for overlap, splice descending, write atomically. expected_sha256 is advisory — a mismatch heals, never blocks.

Check

tact check PATH --old --new

tact_check

tact.apply.check_edits

Identical resolution pipeline to edit, writes nothing — a dry-run preview.

Apply patch

tact apply-patch [FILE|-] [--check]

tact_apply_patch

tact.patch.parse_patch → apply_many/check_edits

Parses a unified diff or fenced old/new blocks; writes resolve and stage all files before committing, while checks remain read-only.

Heal

tact heal PATH --old --new

tact_heal

tact.heal.heal

Read-only fix for a failed builtin Edit(path, old_string, new_string); returns a corrected match or a structured refusal, never writes.

Undo

tact undo PATH

tact_undo

tact.undo.undo

Restores the newest journaled pre-image; a toggle of the most recent write, journaling the pre-undo state so undo is itself redoable.

History

tact history PATH

(not exposed)

tact.undo.history

Lists a file’s journaled pre-images, newest first.

Definition

tact def PATH TARGET

tact_definition

LspManager.definition

Name-first (or LINE:CHARACTER) go-to-definition via pyrefly; LspResult(ok=False) on no live server.

References

tact refs PATH TARGET [--no-declaration]

tact_references

LspManager.references

Same resolution, references LSP request.

Hover

tact hover PATH TARGET

tact_hover

LspManager.hover

Type/docstring info at the resolved position.

Symbols

tact sym PATH

tact_symbols

tact.skeleton.skeleton

LSP documentSymbol first; falls back to a dependency-free ast walk for .py files when no server is live; a structured “no skeleton available” result for anything else.

Diagnostics

tact diag PATH

tact_diagnostics

LspManager.diagnostics_for

Every diagnostic currently known for the file, merged across pyrefly + ruff; None (not an empty tuple) means no live server.

Rename

tact rename PATH TARGET NEW_NAME

tact_rename

tact.rename.rename_symbol

LSP-computed WorkspaceEdit, converted into one apply_many transaction. The one verb that is not LSP-optional — no textual fallback.

Anchor

tact anchor PATH --block TEXT [--name NAME]

tact_anchor

tact.anchor.anchor / save_anchor

Captures a block’s position + content for later re-finding; optionally persists it under a name.

Resolve anchor

tact resolve-anchor NAME

tact_resolve_anchor

tact.anchor.load_anchor → resolve_anchor

Byte-identical short-circuit if the file’s hash hasn’t moved; otherwise re-runs the ladder biased toward the anchor’s remembered index.

Delta

tact delta PATH SHA

tact_delta

tact.delta.read_since

“What changed since this hash” — a real unified diff if the undo journal still has the baseline blob, else an honest full-content fallback with a note.

Restart

tact restart PATH

tact_restart

LspManager.restart

Clears a project root’s LSP blacklist and drops live servers, forcing a fresh spawn on next use.

Doctor

tact doctor

(not exposed)

tact.doctor.inspect_installation

Reports CLI, optional MCP, state, and telemetry readiness in human or stable JSON form.

Adoption

tact adoption [--days N]

(not exposed)

tact.adoption.build_report

Summarizes the event stream into an auditable adoption denominator: per-harness eligible counts, refusals, and hook p95.

Hook route

tact hook --harness H [--mode M]

(hook adapter)

tact.routing.route_hook

Parses a native pre-edit payload and renders the selected harness’s native response; --mode defaults to warn, and malformed or out-of-scope operations fail open.

Routing modes#

Mode

Eligible existing-file edit

shadow

Observe and record; emit no intervention.

warn (default)

Observe and record without injecting agent-facing context.

deny

Refuse the builtin edit, state that it was not applied, name the tact CLI/MCP recovery action, and name the way back.

route_hook uses warn when no mode is given. The library is advisory by default, and only a caller’s explicit --mode deny blocks anything.

Routing is content-blind. Existing ordinary files are eligible. Creation, notebooks, unrecognized tools, and malformed payloads remain native. So does any payload tact cannot even normalize into a path: classification failures resolve to allow rather than propagating out of the hook.

Exit codes (CLI only)#

Code

Meaning

0

Success.

1

A structured refusal the library itself reported (ambiguous match, no match, drift-blocked transaction, unavailable LSP capability) — the result is still printed to stdout, only the exit code marks it.

2

A usage error — bad arguments, a missing/non-regular file, anything the library raises FileNotFoundError/ValueError for.

3

An unexpected failure — any other exception main() didn’t anticipate. Under --json the result is a structured error object naming the exception’s type and message, so stdout is never empty; the traceback always goes to stderr, --json or not.

--json is accepted either before or after the verb (tact --json edit ... and tact edit ... --json are equivalent). The selected result, and nothing else, goes to stdout. Supplementary notes (such as a truncation notice or no-live-LSP note) go to stderr.

MCP coverage gap#

history, doctor, and adoption are CLI-only today. The MCP server exposes no tact_history/tact_doctor/tact_adoption tools. This is a real gap, not a documentation oversight. Use the CLI or the library directly for those three verbs from an MCP-only harness until it closes. hook is deliberately absent from MCP. It is a harness adapter invoked by a hook runner, not a verb an agent calls.

Deep reference#

Every module carries its full contract in its own docstring. Run python -c "import tact.X; help(tact.X)", or read the source directly. Start with tact/__init__.py’s package docstring for the overall shape, then the module for the verb you’re using.