tensor-grep run & operate
An imperative, copy-pasteable runbook for running tg (the tensor-grep CLI). Ground-truthed
against src/tensor_grep/cli/main.py at released v1.96.0, re-verified
2026-07-24. Every command below is a real @app.command in that file — re-verify with the
commands in Provenance and maintenance before trusting a flag on a
newer version. main.py churns ~100+ lines per release, so treat every main.py:NNNN cite as an
approximate anchor: grep the symbol, don't trust the raw line.
Scope — and when to use a sibling instead
| You need | Use |
|---|---|
| How to type the command / where does output go | this skill |
| Search theory (BM25, trigram index, ripgrep internals, AST/tree-sitter concepts) | code-search-and-retrieval-reference |
The blast-radius-before-editing audit workflow, callers truncation caveats |
tensor-grep-code-audit |
| WHY the front door / registration / backend contract is shaped this way | tensor-grep-architecture-contract |
Adding a tg command or search flag; gates before merging any tg change |
tensor-grep-change-control |
A live bug you just hit while running tg |
tensor-grep-debugging-playbook |
A hang/--deadline overrun on a large repo, or the unscoped-search-refusal campaign history |
tensor-grep-large-repo-scale-campaign |
| "Has this already been tried and lost?" before re-attempting a fix | tensor-grep-failure-archaeology |
Which commands accept --deadline/--ignore/--max-tokens (NOT the same set); env-var/flag axis list |
tensor-grep-config-and-flags |
| Building from source, Rust/Python toolchain setup | tensor-grep-build-and-env |
Interpreting doctor/dogfood fields or a result_incomplete/partial/deadline_limit/exit-2 payload — what it PROVES |
tensor-grep-diagnostics-and-tooling |
Running the pytest/ruff/mypy gates; the exit-code-contract test pattern (tests/unit/test_cli_deadline_flag.py) |
tensor-grep-validation-and-qa |
| Claiming/reading a speed number | tensor-grep-benchmark-and-proof-toolkit |
| Release mechanics, publish gates, external positioning claims | tensor-grep-release-and-positioning |
If you are about to change tg code rather than run it, stop and load
tensor-grep-change-control first — this skill assumes the CLI surface as shipped.
0. Confirm you are running the real thing first
Every command below assumes a working, correctly-resolved tg on PATH. Windows in particular can
silently resolve a stale or foreign tg (see .claude/skills/tensor-grep/SKILL.md "Start Here").
Before trusting any output:
tg --version
tg doctor --json
tg doctor --json (§8) is the single first check for launcher drift, version mismatch, and
Windows shell escaping pitfalls — run it, not tg --version alone, when something looks wrong.
1. Command-anatomy quick table (the moat commands)
All symbol/orientation commands are path-first: tg <command> PATH [SYMBOL|QUERY]. PATH
defaults to . (current directory) everywhere below unless noted. Add --json to any of them for
machine-readable output (--json is a real per-command flag, not a global one — pass it after the
command name).
| Command | Positional form | Purpose |
|---|---|---|
tg orient PATH |
PATH |
One-call codebase orientation: central files by import in-degree, entry points, symbol map, AST snippets |
tg search PATTERN PATH --rank |
PATTERN PATH |
Text search with a real hit re-ranked by BM25 relevance instead of grep order |
tg find PATH "query" |
PATH QUERY |
Whole-repo natural-language hybrid search (BM25 [+ CPU dense] -> RRF -> budget-fitted file:line); v1.77.0, #189 |
tg defs PATH SYMBOL |
PATH SYMBOL |
Exact definition locations |
tg source PATH SYMBOL |
PATH SYMBOL |
Full source block for a symbol |
tg refs PATH SYMBOL |
PATH SYMBOL |
References to a symbol |
tg callers PATH SYMBOL |
PATH SYMBOL |
Call sites + likely impacted tests |
tg blast-radius PATH SYMBOL |
PATH SYMBOL |
Callers + transitive file/test impact |
tg imports FILE |
FILE |
Forward file-dependency edges (#74; O(1), no repo scan) |
tg importers FILE [ROOT] |
FILE [ROOT] |
Reverse file-dependency edges (bounded scan; --deadline on large roots) |
tg evidence emit PATH |
PATH + --capsule/--manifest |
Aggregate prior outputs into an EvidenceReceipt |
tg codemap PATH |
PATH |
Browsable folder→file→symbol map (--out; slow — prefer /tmp) |
tg map PATH |
PATH |
AST-indexed repo map (files/symbols/coverage); takes --deadline (main.py def map, grep the symbol) and sits under the §11a three-state contract — a scan-truncated map exits 2 (added to this table 2026-08-12; it was missing) |
tg agent PATH "query" |
PATH QUERY |
Actionable Context Capsule — prefer PATH/src for speed |
tg session open PATH |
PATH |
Create a cached repo-map session (returns session_id) |
tg scan --ruleset NAME |
(flag-driven) | Run a built-in security/compliance AST rule pack |
tg run PATTERN PATH |
PATTERN [PATH] |
Bounded AST structural search / guarded rewrite |
tg mcp |
— | Start the MCP stdio server |
tg doctor |
PATH |
System/GPU/cache/AST/daemon/shell diagnostics |
tg route-test PATH "query" |
PATH QUERY |
Diagnose routing agreement between context-render and edit-plan for one query -- reports agreement/warnings |
tg prepare PATH "task" |
PATH QUERY |
One-call edit readiness: composes orient→search→agent→route-test→callers→evidence→ledger into a single call (primary target, confidence, blast-radius floor, validation commands, claim/evidence coordination hooks) |
tg ledger claim|release|list|record|find |
(subcommand-driven) | Advisory multi-agent coordination: claim/release/list a symbol/file scope (Slice 1); record/find a content-addressed finding for reuse (Slice 2, repo-root canonicalized since #850) |
tg dogfood |
(flag-driven) | Wraps agent_readiness.py into one verdict + JSON |
tg upgrade |
— | Upgrade the installed tensor-grep package |
tg calibrate |
-- (no positional; delegates to the native binary) | Measure CPU-vs-GPU crossover thresholds; exit 1 with a remediation pointer if no CUDA-enabled native binary is installed (#596). See docs/gpu_crossover.md. |
tg devices [--json] [--format text|json] |
(flag-driven) | Print routable GPU device IDs + VRAM inventory (collect_device_inventory); the CLI counterpart of the tg_devices MCP tool (S7). See docs/gpu_crossover.md. |
2. Orientation and content search
tg orient C:\repo --json
tg orient C:\repo --max-tokens 6000 --max-central-files 15 # widen the capsule
tg orient C:\repo --ignore "vendor/**" --ignore "core/skills/**" # drop vendor/skill trees from ranking
orient (grep -n "^def orient" src/tensor_grep/cli/main.py -- :9422 as of 2026-08-14, was :8761) takes path (default .), --max-tokens (default 3000, orient's
snippet-token budget — not the same axis as context --max-tokens, §14), --max-central-files
(default 10), --ignore (repeatable glob), --json.
--ignore GLOB (repeatable) excludes a subtree from the centrality ranking so vendor/skill CODE
trees do not outrank real hubs on a harness or monorepo. The glob matches the file basename or the
repo-relative path (--ignore 'seo/**' --ignore 'core/skills/**'). Receipt: shipped for orient in
#392 after a ~1900-file TS repo dogfood showed a central-skills tree crowding out the real code
hubs; the twin flag on tg agent (#397) is below. This is a ranking exclusion, not a scan
exclusion — the files are still walked, just kept out of the "central files" / "primary target" list.
(Note: tg search --ignore is a different, boolean flag — "respect ignore files" — not this glob;
tg docs-coverage --ignore (§13) is a coverage exclusion. See tensor-grep-config-and-flags.)
orient's JSON also carries a suggested_ignore field (orient_capsule.py:1094,
_suggested_ignore_from_deweighted_trees) -- ready-to-paste --ignore globs for whatever
auto-de-weighted vendor/skill trees it found (de-weight, never hard-exclude, by default). v1.75.0
(#593, "M1+M2") broadened this from narrow nested-manifest islands to whole vendor/skill trees: a
new STRONG-0 promotion fires on 5 unambiguous vendor-dir basenames alone (node_modules, vendor,
third_party, _vendored, external_repos -- no manifest needed), and a new STRONG-3 shape
heuristic detects a whole skills/-named tree whose children look like independent leaf skills with
no imports crossing out of the tree (a genuine imported skills/ package stays un-deweighted). M2 in
the same PR also added suggested_ignore parity to tg agent --json, which previously never
surfaced it at all even though tg agent runs the identical de-weight during ranking -- additive-only,
mirroring suggested_scope's convention (present only when non-empty).
tg search "invoice tax" C:\repo --rank --json
--rank (alias --bm25, main.py:7062) re-ranks ripgrep hits by BM25 lexical relevance —
pure CPU, no API key, no model download. Default --format for plain search is rg (exact
ripgrep-style text); use tg search PATTERN PATH --format rg --json for ripgrep JSON Lines, or
--json alone for tensor-grep's own aggregate JSON object, or --ndjson for tensor-grep's
flattened streaming rows. These three JSON shapes are not interchangeable — --json is NOT
rg's JSON Lines schema (--json/--rank/--ndjson/--format at main.py:7054-7202, in
search_command).
tg agent C:\repo "change invoice tax rounding" --json
agent (grep -n "^def agent" src/tensor_grep/cli/main.py -- :10331 as of 2026-08-14, was :9640) is opt-in and takes path then positional query (not --query, which
is a hidden deprecated alias, main.py:9341-9468). Key flags: --max-files (3), --max-sources
(5), --max-tokens (1200), --max-repo-files (2000, _DEFAULT_AGENT_REPO_SCAN_LIMIT; raised from
the old 512 — see §11a's exit-code history below), --provider native|lsp|hybrid,
--gpu-device-ids (opt-in native GPU evidence only — sidecar-routed GPU is reported unsupported),
and --ignore GLOB (repeatable, main.py:9386) — the same vendor/skill-tree ranking exclusion as
orient above, here keeping a vendor/skill tree from being picked as the capsule's primary target
on a harness repo (#397). Before editing from a capsule, check top-level ambiguity.status:
"tie_requires_confirmation" is a hard stop for autonomous edits.
TG_CAPSULE_INLINE_CALLERS (default-OFF, v1.92.0/#697): prepends # tg: callers=N (top: a, b) to the
primary snippet's source and adds an additive snippets[i].inline_structural_annotation field, reusing
already-collected blast-radius evidence (no new scan) at a ~+2.8% token cost. See
tensor-grep-config-and-flags for the full env-var entry.
2a. One-call edit readiness — tg prepare
tg prepare C:\repo\src "improve session daemon timeout" --json
tg prepare C:\repo\src "task" --claim --json # also submits an advisory ledger claim
tg prepare C:\repo\src "task" --out capsule.json --json # persists the capsule to FILE, byte-identical to stdout JSON
prepare (grep -n "^def prepare" src/tensor_grep/cli/main.py -- :11144 as of 2026-08-14, was :10862) composes the orient→search→agent→route-test→callers→evidence→ledger loop
into one call: path then positional query, default 60s deadline (DEFAULT_AGENT_CLI_DEADLINE_SECONDS,
same class as tg agent's cold-path default), --claim (opt-in only, submits an advisory
tg ledger claim), --out FILE (main.py:10562, v1.93.0/#705 — atomic write, refuses a symlink/
dangling-symlink/directory destination, works with --text too). Response carries primary_target +
confidence + ask_user_before_editing, validation_commands, a blast_radius_floor
(callers_count/top_callers/trust summary), and coordination.claim/coordination.evidence hooks so
a caller doesn't have to guess the follow-up argv. A truncated/deadline-partial result reuses the same
symbol-command exit contract as §11a below (exit 2 on any partial/result_incomplete, regardless of
whether a primary target was found) via _scan_incomplete.
2b. Multi-agent coordination — tg ledger (EXPERIMENTAL, advisory)
tg ledger claim C:\repo --symbol open_session --agent-id "$AGENT_ID" --json
tg ledger list C:\repo --json # rolls scope UP -- any subtree PATH under the same repo sees it
tg ledger release C:\repo --symbol open_session --agent-id "$AGENT_ID" --json
tg ledger record C:\repo --receipt receipt.json --artifact-kind evidence-receipt --symbol open_session --agent-id "$AGENT_ID" --json
tg ledger find C:\repo --symbol open_session --artifact-kind evidence-receipt --fresh-only --json
Never blocks an edit — a claim is advisory, and overlaps are reported for the caller to decide, not
enforced. Slice 1 (claim/release/list -- main.py's ledger_claim/ledger_release/ledger_list) canonicalizes its store to
the nearest .git ancestor (worktree-aware; v1.93.0/#706 — before this, each command resolved the store
from the literal PATH argument, so claim core/hooks + list . silently used two different stores).
Slice 2 (ledger_record -- grep -n "^def ledger_record" src/tensor_grep/cli/main.py, :17510 as of 2026-08-14, was :17323; ledger_find -- grep -n "^def ledger_find" src/tensor_grep/cli/main.py, :17604 as of 2026-08-14, was :17417) got the SAME fix in #850
(v1.101.16): both call _ledger_physical_root, so record core/hooks + find . now hit one store.
This line said "unchanged and still literal-path-rooted" for three releases after it stopped being
true -- a reader would treat a subtree miss as expected instead of a regression, or avoid Slice 2
entirely. Re-grep the main.py:NNNNN cites; they drift every release. find's exit contract is a distinct 3-state family from §11a's
symbol-command contract: 0 = at least one fresh finding (revision matches, safe to reuse); 1 =
nothing matched, or matches exist but none are fresh (recompute); 2 = fail-closed (missing --symbol,
corrupt index/blob). Full command reference: tensor-grep-ledger.
3. Symbol navigation (defs / source / refs / callers / blast-radius)
tg defs C:\repo open_file --json
tg source C:\repo open_file --json
tg refs C:\repo open_file --json
tg callers C:\repo open_file --json
tg blast-radius C:\repo open_file --json
All five share the same positional contract: path then symbol_arg, both optional Typer
arguments (defs (grep -n "^def defs" src/tensor_grep/cli/main.py -- :11924 as of 2026-08-14, was :11475) and blast-radius (grep -n "^def blast_radius" src/tensor_grep/cli/main.py -- :12813 as of 2026-08-14, was :12047)). If you type them reversed
(tg defs SYMBOL PATH), the CLI auto-detects it — path that fails Path(path).exists() and a
present symbol_arg get swapped, with a warning on stderr — but write path-first to avoid the
extra hint round-trip (_maybe_swap_reversed_positionals -- grep -n "^def _maybe_swap_reversed_positionals" src/tensor_grep/cli/main.py, :11795 as of 2026-08-14, was :11346; called from
_resolve_path_and_symbol -- grep -n "^def _resolve_path_and_symbol" src/tensor_grep/cli/main.py, :11862 as of 2026-08-14, was :11413). A bare tg defs SYMBOL (single arg) resolves against
the current directory.
A hidden --symbol / --query flag still works and prints a deprecation warning to stderr
(main.py:11056) — treat it as legacy, not the contract; the positional form is canonical.
Common flags: --provider native|lsp|hybrid (default native), --max-repo-files (2000,
_DEFAULT_AGENT_REPO_SCAN_LIMIT — raised from the old 512 the #398→#399 exit-code history below
calls out as "the actual friction"), --json. callers/refs/impact/blast-radius also take
--deadline SECONDS to wall-clock-bound the scan (§12) — and so do defs/source (plus
orient/context/docs-coverage); the #232/#585 waves extended it well past the original
graph-commands-only set. Derive it, don't trust this sentence: tg defs --help | grep deadline.
(This line read "defs/source do not" until 2026-08-01, while §12's own table listed both as
taking it and the pitfall table below explicitly warned against believing a stale "these don't take
it" claim — the doc carried its own correction and its own error at the same time. The --deadline
option on defs even carries an in-source comment naming the v1.71.3 dogfood fix that added it.
A contradiction inside one document is not resolved by whichever half you read first.)
blast-radius
additionally takes --max-depth (3), --max-callers (25), --max-files (25) (in the blast_radius
def, grep -n "^def blast_radius" src/tensor_grep/cli/main.py -- :12813 as of 2026-08-14, was :12047+). defs additionally takes --class TEXT to disambiguate a common method name
by its enclosing class (main.py:11128).
Truncation contract (read §11 before scripting an exit code): when a callers/refs/impact/
blast-radius JSON payload carries "result_incomplete": true (a scan cap) or "partial": true
(a --deadline cutoff), the scan did not finish — treat the list as a floor, never as proof of
zero callers. The exit code encodes this too, and truncation trumps found:
main.py:11344-11470 raises typer.Exit(2) on ANY partial/result_incomplete
(found OR empty) BEFORE the not-found check, and the comment at main.py:11334-11466
records #399's "found-but-truncated exits 0" as tried and overturned by a unanimous
design council (§11). The full audit decision procedure (P2 = truncation,
P7 = "zero callers != dead code") lives in tensor-grep-code-audit; this skill covers how to invoke
the command and how to branch on its exit/flags.
tg callers is Python-first — prefer tg refs for TS/JS symbol navigation. Call-site
resolution matches Python AST call nodes most reliably and can under-match or run for minutes on
large TypeScript/JS repos. Dogfood receipt (v1.19.3, AGENTS.md): on a TS-heavy repo, tg refs
returned 14 reference sites for a symbol where tg callers returned 1. Prefer tg refs there —
and still cross-check with tg scan/grep, because the call graph cannot see
set/list/decorator/dispatch-table registration sites, so even a COMPLETE zero-caller result is
not proof of dead code.
4. Session lifecycle — open, refresh, serve, daemon
Sessions cache the repo-map so repeated context/edit-plan/blast-radius calls skip re-indexing.
tg session open C:\repo --json # returns session_id; capture it
tg session list # sessions for the current root (no ID)
tg session show SESSION_ID
tg session refresh SESSION_ID C:\repo # after file changes
tg session context-render SESSION_ID C:\repo "query"
tg session edit-plan SESSION_ID C:\repo "query"
tg session blast-radius SESSION_ID C:\repo SYMBOL
tg session serve SESSION_ID C:\repo # reads JSONL requests from stdin, --jsonl is default-on
tg session daemon start C:\repo --json # start/reuse the warm localhost daemon
tg session daemon status C:\repo --json
tg session daemon stop C:\repo --json
session open (main.py:12284) takes path (default .) and --max-repo-files (default 512,
the agent-safe cap — this literal is unrelated to the agent-family 2000 default in §2/§3, still 512
as of this pass). session refresh (main.py:12477) and every session <subcmd> SESSION_ID [PATH] command require session_id as the first positional argument — it is not implicit.
session serve (main.py:13155) additionally accepts --refresh-on-stale to refresh once and
retry a request when file changes are detected mid-stream; passing --no-jsonl errors (JSONL is
currently the only serve mode, main.py:13173). session context-render / session context accept
--max-tokens (default 16000, 0 = unbounded) — see §14.
session daemon start/status/stop (session_daemon_app, main.py:238) each take only
PATH — there is no CLI flag for the daemon's idle/uptime limits; those are environment-only:
TG_SESSION_DAEMON_IDLE_SECONDS and TG_SESSION_DAEMON_MAX_UPTIME_SECONDS. daemon start prints
host:port and pid; daemon status/stop report whether a daemon is currently running for
that root. tg session list and tg session daemon status will discover nearby session scopes
when the current directory has no direct session metadata of its own.
5. Checkpoints (rewind before a risky rewrite)
tg checkpoint create C:\repo --json
tg checkpoint list C:\repo --json # one detected scope
tg checkpoint list C:\repo --discover --json # bounded child-scope discovery
tg checkpoint list C:\repo --discover-full --json # exhaustive, can be slow on broad roots
tg checkpoint undo CHECKPOINT_ID C:\repo --json
tg checkpoint undo --last C:\repo --json # restore the newest checkpoint in scope
checkpoint undo (main.py:13362) takes checkpoint_id as an optional positional, or --last to
restore the newest checkpoint for path without naming an ID — do not pass both
(main.py:13381/13510). If checkpoint_id resolves to an existing filesystem path, the error
message suggests --last explicitly (main.py's checkpoint_undo), which is a strong signal the two
positionals (checkpoint_id, path) got confused.
6. AST scan (built-in rule packs) and structural run/rewrite
tg rulesets --json # list built-in packs
tg scan --ruleset RULESET_NAME --path C:\repo\api --json # narrowest useful root first
tg scan --config sgconfig.yml --json # custom ast-grep project config
tg scan --rule my-rule.yml --json # single custom rule, no sgconfig
scan (grep -n "^def scan" src/tensor_grep/cli/main.py -- :14533 as of 2026-08-14, was :14000) accepts positional PATHS, or --path (default .) when using a
built-in ruleset — the two are mutually exclusive (main.py's scan), as are --rule,
--ruleset, and --inline-rules with each other (the same guard in scan). Useful narrowing flags:
--glob/-g, --type/-t, --max-depth, --filter/-f (regex over loaded rule IDs). Baseline
workflow: --baseline FILE / --write-baseline FILE compare or snapshot matched-finding
fingerprints; --suppressions FILE / --write-suppressions FILE mark or record accepted findings
(writing suppressions requires --justification TEXT). --allow-broad-generated-scan opts into an
otherwise-refused scan of a generated/cache/dependency/multi-project root — prefer scoping first.
--ruleset also accepts a handful of RESOLVE-ONLY 1:1 mental-model aliases on top of the 6
canonical pack names (rule_packs.py's _RULE_PACK_ALIASES): auth->auth-safe,
secrets->secrets-basic, crypto->crypto-safe, tls/ssl->tls-safe,
subprocess->subprocess-safe, deserialize/deserialization->deserialization-safe. A real
pack name always wins over an alias; aliases never appear in tg rulesets/list_rule_packs().
security names the shared category all 6 packs belong to, not one pack, so it raises an
actionable error listing the 6 packs instead of guessing.
tg run "function_definition" C:\repo\src --lang python --json
tg run --pattern 'def $NAME($$$ARGS): $$$BODY' --rewrite 'def $NAME($$$ARGS) -> None: $$$BODY' C:\repo --apply --verify
run (grep -n "^def run" src/tensor_grep/cli/main.py -- :17740 as of 2026-08-14, was :17149) takes the AST pattern positionally (or via --pattern/-p) and an
optional PATH; supplying only a path that exists with no pattern is a hard error
(main.py's run, typer.Exit(2)), not a silent zero-match. --rewrite/-r sets the replacement,
--apply writes it, --verify runs tests after applying, --checkpoint wraps the apply in a
checkpoint, -U/--update-all is an ast-grep-compatible alias for apply-all (requires
--rewrite). Read-only structural-search extras: --selector, --strictness, --stdin,
--globs (repeatable, prefix ! to exclude), --filter (text regex over matched nodes),
--files-with-matches. PowerShell users must single-quote patterns containing $ captures (e.g.
'def $NAME($$$ARGS): $$$BODY') or PowerShell expands $NAME before tg sees it.
KEY FACT: tg run already IS ast-grep when the sg binary is on PATH -- AstGrepWrapperBackend
delegates the pattern to sg run -p <pattern> verbatim (ast_wrapper_backend.py:146), so
$NAME/$$$ARGS/--selector/--strictness are already 100% ast-grep-compatible with no
translation layer. When sg is absent, a native-shaped pattern (no $) still runs through tg's
own tree-sitter AstBackend, but that backend speaks a DIFFERENT query DSL than ast-grep
(task #141) -- a $-metavariable pattern is never silently rerouted there (that would silently
mistranslate and return wrong matches, worse than an honest empty result). Instead
_select_ast_backend_for_pattern raises a fail-closed ConfigurationError, which run_command
now catches and reports as a clean Error: ... message + exit 2 (mirroring the Task #166
ConfigurationError handling in main.py's search path), never a raw Python traceback. A
zero-match tg run (exit 1, not an error) additionally emits static/heuristic remediation via
_emit_ast_run_remediation -- idiom shapes (def $NAME($$$ARGS): $$$BODY,
function $NAME($$$) { $$$ }), a tg ast-info pointer, and cheap "no $" / "no --lang" hints
-- on stderr for text modes and as an additive "remediation" --json key; this is tg run-only
and never fires on tg scan (a 0-finding scan is a clean pass, exit 0).
7. MCP server
tg mcp
Starts a stdio MCP server (FastMCP("tensor-grep"), mcp_server.py:120, anyio.run over
_run_mcp_stdio_async -- grep -n "def _run_mcp_stdio_async" src/tensor_grep/cli/mcp_server.py, :7951 as of 2026-08-14, was :7847-7859) — it is meant to be launched by an MCP client
(Claude Desktop, an agent harness), not run interactively and left open in a terminal.
Call tg_mcp_capabilities first in any new client/sandbox — it reports which tools work
without a standalone native tg binary versus which require one (mcp_server.py:1948).
Representative tool names (58 advertised with the default TG_MCP_LEGACY_TOOLS ON — but the
surface was REORGANIZED post-v1.96.0, #98 MCP consolidation Phase-1, verified 2026-08-12:
46 legacy tools (the individual names below) are gated behind TG_MCP_LEGACY_TOOLS
(default ON; _legacy_tools_enabled() / _register_legacy_tool in mcp_server.py);
10 task-shaped meta-tools (tg_navigate, tg_impact, tg_query, tg_context,
tg_explore, tg_session, tg_scan, tg_audit, tg_checkpoint, tg_rewrite — the
_META_MCP_TOOLS tuple) compose the legacy tools by an action selector param and are ALWAYS
registered regardless of the flag; plus 2 always-on singletons (tg_mcp_capabilities,
tg_classify_logs — _SINGLETON_MCP_TOOLS). 46 + 10 + 2 = 58. Flipping TG_MCP_LEGACY_TOOLS
OFF de-advertises the 46 legacy names but keeps the 10 meta + 2 singletons (the meta tools'
dispatch bodies call the legacy functions in-process either way). Re-derive the shape with
grep -n "_META_MCP_TOOLS\|_SINGLETON_MCP_TOOLS\|def _legacy_tools_enabled" src/tensor_grep/cli/mcp_server.py; the full advertised set below — the legacy individual names
first, the 10 meta-tools and tg_classify_logs singleton at the end):
tg_search, tg_find (whole-repo hybrid NL search, agent-callable form of tg find, v1.78.0/#189/#627 —
see docs/harness_api.md), tg_ast_search, tg_symbol_defs, tg_symbol_source,
tg_symbol_refs, tg_symbol_callers, tg_symbol_impact, tg_symbol_blast_radius,
tg_symbol_blast_radius_render, tg_symbol_blast_radius_plan, tg_context_pack,
tg_context_render, tg_edit_plan, tg_agent_capsule, tg_ruleset_scan, tg_rewrite_plan,
tg_rewrite_apply, tg_rewrite_diff, tg_classify_logs, tg_devices, tg_index_search, tg_repo_map,
tg_file_imports, tg_file_importers, tg_session_file_importers,
tg_checkpoint_create / _list / _undo, tg_session_open / _list / _show / _refresh /
_edit_plan / _context_render / _blast_radius*, tg_audit_manifest_verify,
tg_audit_history, tg_audit_diff, tg_review_bundle_create / _verify, tg_navigate, tg_impact,
tg_query, tg_context, tg_explore, tg_session, tg_scan, tg_audit, tg_checkpoint, tg_rewrite.
Gap worth knowing: neither tg prepare nor tg ledger has an MCP tool counterpart as of v1.96.0 —
both are CLI-only. An agent driving tg over MCP cannot reach the one-call edit-readiness CUJ or the
advisory ledger the way a CLI-driven agent can; this is a real surface gap, not an oversight to route
around silently.
tg_rewrite_apply refuses free-form lint_cmd/test_cmd (they shell-execute on the host) unless
the operator opts in with TG_MCP_ALLOW_VALIDATION_COMMANDS=1, returning
code="unsupported_option" otherwise. The primary native rg passthrough now inserts a --
end-of-options sentinel before user/LLM-controlled paths (CWE-88 / the MCP-276 CVE class) — shipped
and unit-tested in #370 (rust_core/src/rg_passthrough.rs:574-598, ripgrep_operand_args). It is not solved
forever: a second native rg-invocation path is the open follow-up (#49), so any new MCP tool
or argv builder that shells out to tg/rg must still insert -- before positionals itself. See
AGENTS.md "Security Hardening Patterns" and tensor-grep-debugging-playbook /
tensor-grep-change-control before adding a new MCP tool that shells out.
8. Diagnostics and operational health — doctor, dogfood, upgrade, repair-launcher
tg doctor --json # full diagnostics, LSP included by default
tg doctor --no-lsp --json # skip external LSP provider probes
tg doctor C:\repo --config sgconfig.yml --json
doctor (grep -n "^def doctor" src/tensor_grep/cli/main.py -- :15329 as of 2026-08-14, was :14763) takes path (default .), --config (default sgconfig.yml),
--with-lsp/--no-lsp (default on), --json. Inspect path_tg_first_launcher_kind,
fresh_shell_path_tg_first_launcher_kind, python_subprocess_path_tg_first_launcher_kind,
shell_escaping_guidance, and any *_is_foreign field before trusting a Windows timing or
routing claim — see .claude/skills/tensor-grep/SKILL.md "Start Here" for the full field list and
tensor-grep-diagnostics-and-tooling for interpreting them in depth.
session_daemon.autostart (v1.93.0/#705, A12(b)): when session_daemon.running is false, doctor
additively reports why — "on-first-use (not yet warmed)" for a daemon that hasn't been touched yet,
or a "disabled (...)" reason string when autostart itself is off — instead of a bare running: false
that reads as broken.
tg dogfood --output artifacts/dogfood_readiness.json
tg dogfood --json --root C:\repo --timeout-s 170
dogfood (grep -n "^def dogfood" src/tensor_grep/cli/main.py -- :15059 as of 2026-08-14, was :14493) runs the agent-readiness gate and prints a one-page verdict; it "writes
only explicit --output and a sibling readiness report" next to it — it does not write anywhere by
default with no --output given (docstring on main.py's dogfood). Flags:
--root (default .), --output PATH, --expected-version (defaults to pyproject.toml),
--json, --progress auto|always|never (stderr only), --progress-interval-s (30.0),
--timeout-s (170.0, the nested agent_readiness.py child budget), --no-shell-probes,
--no-wsl-probe. A non-zero exit means at least one readiness check failed — read
failed_checks in the verdict before treating a release or a change as safe.
tg upgrade
upgrade (grep -n "^def upgrade" src/tensor_grep/cli/main.py -- :15435 as of 2026-08-14, was :14869) upgrades the installed tensor-grep package to the latest PyPI
release. It tries, in order: uv tool install --force first only when the running Python is a
uv tool-managed venv (_is_uv_tool_managed_python -- grep -n "^def _is_uv_tool_managed_python" src/tensor_grep/cli/main.py, :15355 as of 2026-08-14, was :14789, detects .../uv/tools/
in sys.executable), then uv pip install --upgrade --refresh-package tensor-grep, then pip install --upgrade --no-cache-dir. This is the source-aware upgrade path shipped to fix a WSL
uv-tool install getting stranded at a stale version — see tensor-grep-failure-archaeology for
the incident. tg upgrade also verifies the sidecar import/version post-upgrade and schedules a
managed native front-door refresh when the sidecar version moved ahead of the native binary.
tg repair-launcher --json
tg repair-launcher --allow-foreign-rename --json # only for a foreign tg.exe you own
repair-launcher (main.py's repair_launcher, Windows-relevant) removes a verified or self-identifying stale
tensor-grep Python Scripts\tg.exe launcher that shadows the managed native front door on PATH.
--allow-foreign-rename additionally moves aside a foreign (non-tensor-grep) tg.exe — use it
only when you own that binary.
9. Artifact conventions — where state and JSON reports actually land
| Location | What lives there | Created by | Tracked in git? |
|---|---|---|---|
.tensor-grep/sessions/ |
index.json + per-session repo-map payloads |
tg session open |
No — /.tensor-grep/ is gitignored (.gitignore:51) |
.tensor-grep/checkpoints/ |
index.json, per-checkpoint metadata.json + snapshot/ tree |
tg checkpoint create |
No — same .tensor-grep/ ignore rule |
.tg_semantic_index/ (or $TG_SEMANTIC_INDEX_DIR) |
Experimental semantic (dense) index shards | opt-in semantic-search paths | Not committed; experimental subsystem |
artifacts/ |
--output JSON from tg dogfood, scripts/agent_readiness.py, benchmarks/run_*.py |
explicit --output PATH only — nothing is written here by default |
No — artifacts/ in .gitignore:61 is gitignored |
None of these directories are portable outputs to hand to another agent/process by default —
treat them as local cache/scratch. If you need a durable artifact, pass an explicit --output and
copy it somewhere tracked (or attach it to a PR) yourself; tg dogfood/benchmark scripts will not
do that for you.
sgconfig.yml (used by tg scan --config) is a project-level ast-grep config file, not a
tensor-grep cache directory — it lives wherever you point --config, typically the repo root.
10. Unscoped tg search — fail-fast/refuse, not a hang (shipped: #400/v1.40.3, #413/v1.42.0)
tg search PATTERN with no path argument (or --glob X -l without a scoped path) used to walk
the whole tree and could burn the full ripgrep-subprocess timeout before returning. That is now
shipped, released fail-fast/refuse behavior, not an open hang — four layered guards catch the
unscoped case before it reaches a slow walk, plus a wall-clock backstop if all four miss:
- Vendored-root refusal (
_should_refuse_unbounded_vendored_root_scan--grep -n "^def _should_refuse_unbounded_vendored_root_scan" src/tensor_grep/cli/main.py,:5527as of 2026-08-14, was:5094) — a root with a top-levelnode_modules/vendor/external_repos/third_partydir exits 2 instantly (no scan at all) unless--allow-broad-generated-scanopts in. - Workspace-root refusal (
_should_refuse_unbounded_workspace_root_scan--grep -n "^def _should_refuse_unbounded_workspace_root_scan" src/tensor_grep/cli/main.py,:5469as of 2026-08-14, was:5036) — a root with >=3 sibling project directories (a monorepo/workspace parent) is refused the same way. - Large single-project-root refusal (
_should_refuse_unbounded_large_root_scan--grep -n "^def _should_refuse_unbounded_large_root_scan" src/tensor_grep/cli/main.py,:5701as of 2026-08-14, was:5204,#413, dogfood v1.42.0) — closes the remaining gap: a large but non-vendored, non-workspace single-project root (matches neither guard above) refuses instantly via a bounded scandir probe — it checks the already-collected candidate-file count against a 1500-file ceiling (gated identically on--allow-broad-generated-scan/glob-type-depth scope) rather than falling through to the slow per-file Python match loop. - Flag-less bootstrap-passthrough refusal (
bootstrap._search_paths_include_oversized_implicit_root,bootstrap.py:804, v1.92.3/#702, A9) — closes a DEFAULT-path gap the three guards above never covered:bootstrap._run_rg_passthrough(bootstrap.py:1088, the plain flag-less search front door that runs beforemain.py's Typer app is ever reached) had no walk ceiling at all until this shipped — natively reproduced, not a WSL artifact. It fires only whenpaths_defaulted(no explicit PATH given) and the implicit root is overIMPLICIT_SEARCH_WALK_FILE_CEILING = 1500(now defined inio/scan_limits.py:106, re-exported for backward compat fromio/directory_scanner.py:34) — the same single constant guards 1-3 above and the Rustrg_passthrough.rsall import, so all four doors agree on one ceiling. Exits 2 in ~1.7s (was a silent ~60s timeout before this shipped). - Native-walk wall-clock deadline (
compute_native_walk_deadline/native_walk_deadline_exceeded,src/tensor_grep/backends/cpu_backend.py:52,36, checked during the walk) — the last-resort backstop: if none of the four refusals above fire, the native per-file search walk still self-bounds and breaks to a flagged partial (result_incomplete+ a stderr warning) instead of running unbounded.
TG_RG_TIMEOUT_SECONDS (default 60.0 seconds, subprocess_policy.py:75, lowered from 600s in
#288) remains the ripgrep-subprocess-level backstop for the plain rg-passthrough path, but the five
guards above mean an unscoped vendored, workspace, or large root — scoped or not, flag-bearing or
flag-less — now fails in well under two seconds; you should rarely see the 60s subprocess timeout
actually fire on a repo shaped like this one anymore.
Best practice — still scope to a path (cheaper than even the refusal-probe cost, and the only way to get real results instead of an instant refusal):
tg search "pattern" C:\repo # ~0.4s
tg search "pattern" --glob "*.py" C:\repo
# Avoid: tg search "pattern" --glob "*.py" -l (no path -- triggers a refusal or a bounded walk)
Same rule for file listing — prefer a scoped root over tg search --files . --hidden --no-ignore
across a large workspace; see .claude/skills/tensor-grep/SKILL.md for the broad-generated-scan
guardrail (--allow-broad-generated-scan). Task #390's warm-daemon deadline gap is CLOSED:
session_store.WARM_DAEMON_DEFAULT_DEADLINE_SECONDS bounds those requests. Residual deadline
overruns are separate findings, not evidence that the daemon path is unbounded. For remaining
large-repo campaign history, see tensor-grep-large-repo-scale-campaign.
11. Exit codes — the layered contract (READ THIS before scripting tg)
There is no single 0/1/2 table any more. Exit codes are layered by command family. The old "1 = no match, 2 = error" table is materially misleading for symbol commands — those now use a three-
…(truncated)