Prepare Repo Context
Prepare reusable local repository context with deterministic tooling while keeping the model context small.
Let <skill-dir> be this skill's directory. Resolve the repository, then resolve <python> to python3 or a Python 3 python. If Git or Python 3 is unavailable, report the missing prerequisite; do not install anything.
If the
repo-contextMCP tools (inspect,build,repository_map,search_symbols,search_docs,source_snippets,related_symbols,changed_symbols,impact_candidates,repository_overview, exposed by this plugin's bundled server) are available in this session, use them for the inspect, build, and query steps below instead of the script: the tool arguments mirror the script's flags and the results are the same objects. The consent rules do not change: passconfirm_state_write: truetobuildonly after the user approved writing state in this conversation.activate,gc,remove, andhookare script-only; run them as described here.Run
<python> <skill-dir>/scripts/prepare_repo_context.py inspect --repo <repo>exactly once. This read-only inspection is authorized by invoking the skill. Do not scan source files yourself.Summarize only the returned engine availability, freshness, eligible/excluded path counts, state usage, required authorizations, and
next_safe_action. Do not put an index, repository-wide file list, or source content into the model context.If
next_safe_actionisbuild-index, explain the estimated scope and ask for state-write authorization. After approval, run<python> <skill-dir>/scripts/prepare_repo_context.py build --repo <repo> --confirm-state-writeexactly once and report its compact result. The initialbuild --confirm-state-writeis standing consent for incremental refresh and for pruning superseded index generations of that repository; that pruning runs after everybuild,activate, and query, not only after a refresh, so the repository converges to one referenced generation once each superseded one has aged past its grace period.If it is
install-native-engine, explain the estimated scope and ask once for network plus state-write authorization. After approval, run<python> <skill-dir>/scripts/prepare_repo_context.py activate --repo <repo> --confirm-network --confirm-state-writeexactly once. This downloads the matching release runtime, verifies its SHA-256 checksum, installs it in user-local TAF state, and prepares the index. Report only the compact result.Once
next_safe_actionisuse-index, stop if the user asked only to prepare context. If they asked a repository question, choose the operation, query text, and filters with<skill-dir>/references/query-routing.md, then run exactly one read-only query:query --operation repository-overview,query --operation repository-map,query --operation search-symbols --query <term>, orquery --operation search-docs --query <term>. An unfamiliar repository ("how is this organized", "where is the code", "where do I start") starts withquery --operation repository-overview: it needs no query text and no identity, and one call returnsgroups, one row per directory prefix with its file, definition, entry-point, document, and configuration counts, its languages, and a representative identity;overview, naming the described root, the counted files, and how many directories the*row folds together; and a ranked file layer infindingsthat leads with entry points and well-known entry file names. Narrow it to one subtree with--path-prefix D/; that value must name whole directory segments, so a file path or a partial segment answers with an empty table and the warningoverview-root-not-a-directory, and naming several prefixes describes only the first in sorted order with the warningoverview-root-first-prefix. It accepts--path-prefixand--languageand rejects--query,--result-id,--direction,--base,--symbol-kind, and--source-type. Its group table has no fixed width; the output budget sizes it, and the table and the file layer take at most half of it each: a table over its half folds its tail into the*row until it fits, a table inside its half is kept whole, and the file layer keeps whatever the table did not spend. So a wider budget buys a wider table as well as more files: this operation defaults to 8000 characters rather than the 4000 the others use,--maximum-output-characters 12000widens the table further, and--path-prefix D/is how to go deeper into one subtree rather than wider over all of them. A relationship question ("who calls X", "what does X depend on", "who uses module M", "what does X import") is a two-step flow: first runsearch-symbols(orrepository-map) to get the anchor'sresult_identity, then runquery --operation related-symbols --result-id <identity> --direction callers|callees|importers|importswith that identity; do not guess an identity or reuse one from a different repository or a stale query. A change question ("what did I change on this branch", "what could my change break") needs no identity and is one step:query --operation changed-symbolsreturns the definitions, entry points, and modules whose lines a changed hunk touches, andquery --operation impact-candidatesreturns their one-hop callers and importers, each candidate carrying inanchorsthe changed symbols it depends on. Both compare the working tree (committed, staged, unstaged, and untracked changes together) with a base resolved as the branch's upstream main, thenorigin/HEAD, then a localmain/master; add--base <ref>only for a base the user named. "What am I about to commit / what does my staged change affect" ischanged-symbols --stagedorimpact-candidates --stagedinstead: it measures the index againstHEADexactly asgit commitwould record it, excluding unstaged and untracked edits;--stagedis exclusive with--base, and over MCP the same request isstaged: true.--baseis rejected by every other operation, and both reject--query,--result-id, and--direction.impact-candidatesfollows at most 64 changed symbols and asks the engine one relationship question per changed symbol and direction (callersfor a changed definition or entry point,importersfor a changed module or definition), so its cost grows with the change set; narrow a large change set with--path-prefix(the filters apply to the changed set of both operations) before widening--maximum-results—--path-prefixnarrows the changed side, not the affected side, so name the directory you changed (the library) and the candidates show where it is used (the app); narrowing to a leaf directory such asapp/yields no candidates.impact-candidatesanswers in two layers, the change set and the candidates, so it defaults to 8000 output characters rather than 4000 as well. Aqueryon a repository whose bound index is behind the working tree refreshes the index incrementally inside the same call (therefreshblock in the result says whether it did and how many paths changed); do not runbuildafter edits or commits. Runbuild --confirm-state-writeonly wheninspectreportsrebuild-index(a runtime upgrade, or a change the broker cannot express as a delta), or whenquerysaysincremental refresh failed. Read the result with<skill-dir>/references/result-contract.md. Fetch source only when the one-linepreviewis not enough, withquery --operation source-snippets --result-id <identity>using identities returned by an earlier query; arelated-symbolsfinding synthesized at module scope (record_kind: modulewith no enclosing function) refusessource-snippetsby design, so report itspath/start_linedirectly instead. Keep the default output budget unless the user explicitly needs more evidence. Over MCP the same rules apply torepository_map,search_symbols,search_docs,source_snippets,related_symbols,changed_symbols,impact_candidates, andrepository_overview(which takes onlypath_prefixes,languages,allow_inferred, and the two budgets);result_idstakes the identities that--result-idwould andbasetakes what--basewould, whileimpact_candidatesaccepts no filters, onlybase,allow_inferred, and the two budgets.Offer the commit-time impact hook when the user asks to be warned at commit time, to "not forget dependents", or is about to commit right after a change question. Run
<python> <skill-dir>/scripts/prepare_repo_context.py hook status --repo <repo>once; it writes no launcher, and likeinspectit performs the standing-consent incremental refresh of the bound index. Report itshookstate (installed,foreign,absent, orredirected) and itsreadiness.next_safe_action. Explain what installing writes before asking: apre-commitlauncher inside the repository's own hooks directory, never tracked by Git, advisory by default — a header, at most five indented file lines, and a trailer on stderr, it never blocks a commit, and it stays silent whenever the index is not ready — and, when TAF's user-local state exists, the smallhook/launcher-targetpointer under it. Ask for hook-write authorization; after approval, run<python> <skill-dir>/scripts/prepare_repo_context.py hook install --repo <repo> --confirm-hook-writeexactly once. Never install without--confirm-hook-write. The flag records the user's approval given in this conversation; a request to set up the warning starts this procedure and is not that approval. Add--chainonly whenstatusreportedforeignand the user wants that existing hook kept: it is moved aside to run before TAF's report, and its exit code still blocks the commit; a chained hook that cannot be run at all (deleted, or no longer executable) is skipped instead, andinstall --chainrefuses a foreign hook that is not executable, since git was not running it either.redirectedmeanscore.hooksPathpoints elsewhere - a hook manager (husky, Lefthook, the pre-commit framework) owns that directory and it is usually tracked and shared, so TAF never installs there andstatuscarries the recipe in itsguidancefield. Do not write the launcher into that shared directory: its absolute paths belong to one machine. Offer the supported route instead -<python> <skill-dir>/scripts/prepare_repo_context.py hook print --repo <repo> [--mode confirm]writes the launcher to stdout and nothing to disk (so it needs no hook-write authorization), the user saves it as an executable file under.git/hooks/, which git never tracks, and calls it from the manager's own hook behindif [ -x .git/hooks/<name> ]; then ... || exit $?; fi- anif, never[ -x ... ] && ..., because hook managers run their hooks undersh -ewhere a failing test would block every teammate's commit. Place it after the manager's own tasks so the report is last. The hook speaks only whileinspectreportsuse-index;buildremains its own separate consent, never triggered by the hook. Its report is one header line -TAF impact: N file(s) depend(s) on this change and are not in this commit,Ncounting the untouched production files, or the test files instead when no production file depends but a test does - then at most five indented detail lines, each<path>:<reference_line>padded so every<-column aligns, then<- <qualified name>naming the changed symbol; production files print first, and test files take a detail line only when no production file depends. A trailing indented line, present whenever something was left out, names the remaining files and points at the agent instead of a command:... and <n> more, plus <t> test files (ask your agent to list TAF impact for this commit), trimmed to whichever count is nonzero,<n>+when the engine itself omitted candidates in some direction, or... and possibly more (...)when nothing exact remains to name. The header is bold on a real TTY stderr withNO_COLORunset andTERMnotdumb; every other line, and every line on a non-TTY stderr (GUI clients, CI, pipes), is plain ASCII. The report is written with one blank line above and below it, and a chained hook runs first, so the block is the last thing the commit writes. A commit that was checked and came back clean says so in one line without those blank lines -TAF impact: no untouched dependents (N changed symbols), orTAF impact: no indexed symbols changedwhen the staged change touched no indexed symbol at all - while every outcome that did not check anything (TAF_HOOK=0, the index not ready, an unreadable staged set, noHEAD, the 3-second wait, no interpreter) stays completely silent. The hook is not interactive by default; a prompt inside pre-commit would hang GUI clients, CI, and an agent's own commits, so add--mode confirmtohook installonly when the user asks to be asked before such a commit. That launcher writes the same report and then asks, on its own marked line on the controlling terminal, whether to continue: onlyyoryescommits, while Enter,n, anything unrecognised, end of input, and no answer within 60 seconds (TAF_HOOK_CONFIRM_TIMEOUT) all abort the commit, because acting for the person means taking the safe action. Where nobody can be asked at all the question is skipped and the commit proceeds -/dev/ttycannot be opened,CI,CLAUDECODEorAI_AGENTis set, orTAF_HOOK_CONFIRM=0- since a question nobody sees must never block a commit. The clean line is never followed by a question. Under--chain, answeringnaborts a commit whose chained hook already ran, so anything it re-staged stays staged.statusreports the installed launcher's mode ashook_mode(advisory,confirm, or null). Questions about the full list go to the agent afterwards.<python> <skill-dir>/scripts/prepare_repo_context.py hook remove --repo <repo> --confirm-hook-writeundoes the install.TAF_HOOK=0 git commitsilences one commit without touching the launcher. The launcher follows the broker that last ran on this machine (a pointer file every successfulpreparecommand buthook runrefreshes, under TAF's own user-local state), so a plugin update is picked up automatically by the next TAF session.statusreports three fields about it:launcher_currentmeans the installed launcher runs this plugin's broker -falsemeans it does not, and re-installing fixes it;launcher_text_currentis the stricter, older byte-for-byte comparison alone, andfalsethere needs nothing on its own, since the pointer can still make the launcher current even when its embedded fallback text is not;launcher_generationnames which launcher template is installed (pointerfor the current self-healing one,embeddedfor an older TAF launcher that predates it). A hook manager that appends its own block after TAF's launcher tripslauncher_text_current: falsebut leaveslauncher_current: true, since TAF's own conditional still runs before whatever was appended - a re-install would still rewrite the whole file and drop that appended block, but nothing requires it. The hook's own query performs the same standing-consent incremental refresh and superseded-generation prune as every query; it never builds, activates, downloads, or removes state.
Current repository identity, worktree identity, commit, dirty fingerprint, and native freshness must agree before context is described as ready. When state.orphan_count is nonzero or state.root_bytes is large, you may mention that <python> <skill-dir>/scripts/prepare_repo_context.py gc (or remove --repo <repo> for this repository) reports reclaimable state; both delete only with --confirm-state-write, which requires the user's explicit state-write authorization. A nonzero state.incompatible_generation_count means the state still holds an index an older runtime wrote, which this one cannot read: gc lists such a record under the category incompatible-generation, and a build --confirm-state-write removes it and rebuilds under that same authorization, reporting the warning incompatible-generation and the old runtime in engine.replaced_generation_version. An exact-binding partial context with next_safe_action: use-index is usable bounded coverage; report its warnings without rebuilding it. Report query findings with their paths, line ranges, evidence class, and preview; a preview is a display hint, never evidence, and you do not infer beyond the evidence class. An impact answer names candidates, not defects: a candidate is a symbol that references something you changed, so report it with its anchors and leave the judgement of whether it breaks to a review of that call. A failed preparation never blocks ordinary Git-based skills.