repowise installs a set of lightweight hooks so context reaches your agent, and your index stays fresh, with zero effort on your part. They fall into two families:
- Git hooks keep the wiki and graph in sync with your code.
- Agent hooks feed graph, git, health, and decision context into Claude Code and Codex at the exact moments the agent needs it.
Every agent hook shares the same guarantees: no LLM calls, no network, only
local SQLite (wiki.db) and git reads. They are import-isolated (cold start
under ~500ms), and any failure exits 0 silently, so a broken environment never
crashes or blocks your agent.
| Hook | Family | Installed by | Fires on | What it does |
|---|---|---|---|---|
| Post-commit auto-sync | git | repowise hook install (or the repowise init prompt) |
every git commit |
Runs repowise update in the background so the wiki tracks your code |
| SessionStart context | Claude Code | repowise init |
session startup / resume / clear |
Live index-freshness line, core-tool trust rule, and the standing decisions relevant to this session |
| PostToolUse enrichment | Claude Code | repowise init |
Grep / Glob / Read / Edit / Write / repowise MCP calls |
Graph context on searches, read-intelligence notices, and edit-time "governed by" decision notices |
| Wrong-path rescue | Claude Code | repowise init |
a Read / Edit / Write / Grep / Glob / NotebookEdit that failed on a path this tree does not have |
Names the file when exactly one indexed file carries that basename; silent otherwise |
| Command-rewrite (distill) | Claude Code | repowise hook rewrite install (opt-in) |
Bash / PowerShell |
Rewrites noisy commands to repowise distill <cmd>; auto-allowed by default, set permission: ask to approve each one |
| Codex context + staleness | Codex | repowise init --codex |
SessionStart / edit / shell | Reminds Codex to use the MCP tools and flags stale context after edits |
Every agent hook records what it said and whether the agent acted on it — see
repowise hook stats.
The wiki, graph, and health scores are only as current as your last index. The
post-commit hook closes that gap: after every commit it runs repowise update
in the background, so documentation, dependency edges, and code-health follow
your code without you thinking about it. Your terminal is never blocked.
repowise hook install # install for the current repo
repowise hook install --workspace # install for all repos in the workspace
repowise hook status # check whether the hook is installed
repowise hook uninstall # remove itThe hook is marker-delimited, so it coexists safely with other tools' hooks
(linters, formatters, commit-msg checks) in the same post-commit file: repowise
only ever touches the block between its own markers. See
AUTO_SYNC.md for the full sync model, including how git worktrees
seed from the base checkout.
Prefer to keep updates manual? Skip this hook and run
repowise updateyourself. The agent hooks below will remind you when the index falls behind.
Installed automatically during repowise init into your global
~/.claude/settings.json. Existing user hooks are always preserved, and legacy
repowise entries are migrated in place on the next init / update. All of them
route through the repowise-augment console script (a standalone entry point
that does not load the full CLI).
repowise init --no-editor-setup skips this whole group, along with the MCP
server registration that shares the same file. Reach for it when the repo is
temporary (a scratch clone, a worktree, a benchmark loop) and you do not want
that machine-wide config to move. REPOWISE_SKIP_EDITOR_SETUP=1 is the same
switch for CI. The index itself is identical either way. To register the repo
afterwards, re-run repowise init in it without the flag.
The generated CLAUDE.md is static between reindexes, so it can't say whether
the index is current right now. This hook adds a short per-session block so the
agent starts with calibrated trust instead of discovering staleness mid-task:
- Index current → one line saying so, plus the core-tool pointer.
- Update running → a positive "catching up" notice (never a stale scare).
- Index behind → indexed vs
HEADwith a changed-file count, and the target-scoped trust rule (astale_warningfires only when a file a response actually served has changed).
It also carries the relevance-ranked standing decisions for this session.
repowise scores the repo's active decisions against the session's likely working
set (dirty and staged files, files changed on the branch vs main, the previous
session's edited files, and branch-name tokens), expanded one hop through import
edges and co-change partners. The top few land under a hard ~400-token cap.
Relevance or silence: nothing clears the floor, nothing is injected, and
decisions are never shown just for being high-confidence. Repo-wide rules mined
from your own corrections are the one exception: a rule like "use the shared
logger, not print" applies everywhere rather than to specific files, so it
competes at a flat base relevance.
One hook covers several jobs, matched on
Grep, Glob, Read, Edit, Write, and repowise MCP calls:
Grep/Glob enrichment. When Claude Code runs a broad or zero-result search,
repowise appends focused context pulled straight from wiki.db:
| Field | What it tells the agent |
|---|---|
| Symbols | Functions, classes, and methods defined in the file |
| Imported by | Which files depend on this file (reverse dependency) |
| Depends on | What this file imports (forward dependency) |
| Git signals | Hotspot status, bus factor, and owner |
So an agent that greps for PageGenerator immediately knows what depends on it,
what it depends on, and that it is a hotspot, without a separate MCP call:
[repowise] 2 related file(s) found:
packages/core/.../page_generator.py
Symbols: function:_now_iso, class:PageGenerator, method:__init__
Imported by: init_cmd.py, update_cmd.py, generation/__init__.py
Depends on: context_assembler.py, base.py, models.py
Git: HOTSPOT, bus-factor=1, owner=RaghavChamadiya
Search-flood digests. A grep that returns 50+ matches also gets a compact
per-file digest: every matched file with its match count and two anchor line
numbers, ranked by graph centrality when the index can rank them, and an explicit
(N more files, M matches) tail for anything past the top ten.
With hooks.search_digest: true in .repowise/config.yaml, written by the same
yes/no as the rewrite hook, and toggled afterwards with repowise hook search-digest install | uninstall | status, that digest replaces the raw
match list rather than riding alongside it. Re-run the search scoped to a file it
names, or read those lines directly, to see any match in full. Savings appear in
repowise saved under the search_digest filter, and a repo with it off still
gets the counterfactual number.
Two cases are deliberately left alone. A single-file context grep (-C,
-A, -B) is never digested: that context is exactly what the agent asked for,
and Claude Code renders those results without a path prefix, so they are not
parsed as a multi-file flood in the first place. And files_with_matches results
carry no match text to replace: the file list is already a digest.
Read-intelligence. On Read of an indexed file, repowise emits a per-file
stale-read notice when the file changed after the session's previous read of it,
and points at the cheaper get_context(..., include=["skeleton"]) for
structure-level questions.
With hooks.read_skeleton: true in .repowise/config.yaml — which repowise init writes from the same yes/no as the rewrite hook, and which repowise hook read-skeleton install | uninstall | status toggles afterwards — that pointer
becomes an action: an
unbounded Read of a large indexed file returns the file's skeleton instead of
the file, once per file per session. Signatures stay, keeping their real line
numbers; bodies collapse to ... N lines (a-b) markers carrying 1-indexed ranges,
so the agent can range-read any elided span back — the same reversibility contract
repowise distill makes for shell output. Reading the file again with no range
returns it whole. Savings appear in repowise saved under the read_skeleton
filter. In a repo that has it off, the same Reads are still measured, and
repowise saved reports what they would have saved — a number about size only,
never about whether the agent could work from a skeleton.
One consequence is worth knowing: a Read the agent saw only as a skeleton still
satisfies Claude Code's read-before-edit precondition, so an Edit (especially
with replace_all) or a Write could touch bodies it never saw. Editing such a
file raises a one-line warning, once per file, until the file is read in full.
Re-reads of unchanged files. With hooks.read_reread: true — same consent,
toggled afterwards with repowise hook read-reread install | uninstall | status — a Read of a file the session already read comes back as a short
notice naming that earlier read, instead of the content. The content is already
in context a few tool calls up, so sending it twice buys nothing.
The gate is arithmetic rather than a judgement: the same range must have been
served, no Edit or Write may have come between, and the bytes must hash the
same. When they do not, the agent gets the file and a line saying it changed
on disk without an edit in this session — a git checkout, a formatter, another
agent. That is worth more than the bytes were, because nothing else in the
session can discover it.
Two rules bound how wrong this can be. It is never applied twice in a row for the same file, so if a context compaction dropped the earlier copy, one more Read always returns the content; the notice says exactly that. And a Read that any surface replaced records no content observation at all, so the agent is never told it already has bytes that a skeleton stood in for.
That second rule is also what makes this a narrow surface rather than a broad
one, and it is worth knowing before you turn it on. The two Read surfaces are
substitutes, and the skeleton takes the valuable half first: on a large indexed
file the first Read is served as a skeleton and records no content observation,
so the second Read has nothing to compare against and is served whole. What
reaches the collapse is small files, unindexed files, and third-and-later reads
of the same range. Expect a modest, steady trim rather than a headline. Savings
appear under the read_reread filter, and a repo with it off still gets the
counterfactual, so you can see what it would have done before enabling it.
Searches that time out. On Windows a Glob can exhaust ripgrep's 20-second
budget and return nothing at all — not "no matches", nothing, after twenty
seconds of waiting. A glob is a path query and the index already holds every
path, so repowise answers it offline at the moment the failure happens, naming
the matching paths and counting any it did not list. The win here is wall clock
rather than tokens. Brace expansion ({a,b}) is declined rather than
half-matched, and zero indexed matches stays silent: the index having nothing to
say is not the tree having no such file.
Edit-time "governed by" decisions. When the agent edits a file governed by an
architectural decision (via decision_node_links), it gets a one-line notice
with the rationale, at most once per session per decision and only a few times
per session total. This is how a decision reaches the agent at the moment it is
about to violate (or honor) it.
Every injected decision id is recorded locally in
.repowise/sessions/sessions.db. On the next repowise update, the session miner
checks whether the guidance was followed or contradicted by your corrections in
that session, and relaxes or bumps the decision's staleness accordingly, so
guidance that stops being true stops being injected. This is the feedback loop
behind "learns from your sessions" (see the README and
decisions layer).
An agent that knows a file exists but guesses the wrong directory for it gets back "Path does not exist" and burns a turn hunting. The index already knows where that filename lives, so the failure is answerable at the moment it happens:
[repowise] core/git_indexer/fix_events.py is not in this tree.
The only indexed fix_events.py is core/ingestion/git_indexer/fix_events.py
It speaks only when the basename resolves to exactly one indexed file that is still on disk. Everything else is silence, and each case is a distinct way to be confidently wrong:
- An ambiguous basename. Naming one of a dozen
registry.pyis worse than saying nothing, because the agent has no cheap way to tell a rescue from a fact. - A directory target. "Which file did you mean" is not the question a missing directory asks.
- A path in another checkout. A sibling worktree has its own index; this one has no standing to answer for it.
- A failure Claude Code already answered. It prints its own "Did you mean" for some of these, and repeating it is worse than silence.
- The path that just failed. The index can hold a row for a file that is not on disk right now, and pointing back at the failed path is the worst thing this surface could say.
Most path-not-found failures therefore get silence, and that gap is the design
rather than a shortfall in it. repowise hook stats reports what this surface
actually did on your machine.
Most of what an agent reads from a shell command is noise: 300 lines of passing
tests around 4 failures, full commit bodies for "what changed recently". The
rewrite hook intercepts noisy Bash / PowerShell commands and rewrites them to
repowise distill <cmd>, which compresses the output errors-first
before the agent reads it, exit code preserved and every omission reversible.
repowise hook rewrite install # or answer Yes at the `repowise init` prompt
repowise hook rewrite status
repowise hook rewrite uninstall- Defaults to
allow, so a rewrite runs without a prompt. That is not a permission escalation: a rewrite is alwaysrepowise distill <one recognized command>from a closed family set, never an arbitrary command smuggled behind the wrapper. Setpermission: askunderdistill.commandsin.repowise/config.yamlto approve each one instead. - Never rewrites compound commands, redirections, or watch modes. The one pipe
shape it handles (macOS/Linux) is a single stage into
head,tail,greporrg, quoted whole so it runs unchanged inside distill's own shell. - Installing also adds
Bash(repowise distill:*)/PowerShell(repowise distill:*)topermissions.allow, so an already-approved command family doesn't start re-prompting just because its string changed.
Per-repo behavior lives under distill.commands in .repowise/config.yaml
(CONFIG.md). Track what it saved with repowise saved.
Written to project-local .codex/hooks.json by repowise init --codex (they do
not touch your global ~/.codex/config.toml):
- SessionStart → a short developer note reminding Codex to use the repowise MCP tools for architecture, search, risk, decisions, and dead-code analysis, plus the same relevance-ranked standing decisions Claude Code receives.
- PostToolUse (the shell tool, and
apply_patch/Edit/Write) → after a successfulgit commit,merge,rebase,cherry-pickorpull, comparesHEADagainst the last indexed commit and flags that indexed context may be stale, pointing atrepowise update.
UserPromptSubmit is no longer registered here. It carried no matcher, so it
fired on every prompt, and what it returned was the
static MCP-usage note SessionStart had already delivered in the same session:
one process start per prompt to repeat a block the agent was holding.
An install that predates the retirement is repaired in place the next time this
repo's .codex/hooks.json is written, because the merge is additive and could
not otherwise retire anything. That means repowise agents refresh, or a
repowise init that selects Codex (the interactive checklist pre-ticks it when
the repo is already wired, and --codex selects it outright). A plain
repowise init --yes does not write Codex config at all, so it does not
migrate either. A hook you wrote on that event is left alone, and a timeout
you raised yourself is never moved.
Codex names its shell tool shell_command on current releases and Bash on
older ones, so the matcher covers both. The Claude Code hook deliberately does
not watch shell commands: it has Read / Grep / Glob tools and a
SessionStart freshness line, so the shell adds cost without adding reach. Codex
has neither, which is why it keeps the surface.
Full Codex setup: CODEX.md.
The agent hooks keep a local ledger in .repowise/sessions/sessions.db: what
each hook said, and whether the agent went on to do what it pointed at.
repowise hook stats # per-surface firing counts and action rates
repowise hook backfill --all-projects # seed it from your existing transcriptsThe verdict comes from your own Claude Code transcripts — a firing is paired
with the tool calls that followed it — so the numbers are yours, not a
benchmark. repowise update classifies recent sessions; hook backfill covers
history. Nothing leaves the machine.
Notices that ask for nothing (the stale-read warning, the silent
read-after-served measurement) report n/a rather than a rate. hook stats
also reports hook invocation counts and wall time, including the calls that
returned silence.
Upgrading from a release before firings were keyed by their text: run
repowise hook backfill --resetonce, or older rows are counted separately from the replayed ones. It never touches decisions.
repowise init writes these entries into ~/.claude/settings.json (Claude Code)
and .codex/hooks.json (Codex when --codex is passed):
| Client | Hook type | Matcher | Command |
|---|---|---|---|
| Claude Code | SessionStart |
startup|resume|clear |
repowise-augment 1 |
| Claude Code | PostToolUse |
Grep|Glob|Read|Edit|Write|mcp__.*[Rr]epowise.*__.* |
repowise-augment 1 |
| Claude Code | PreToolUse (opt-in) |
Bash|PowerShell |
repowise-rewrite |
| Codex | SessionStart |
startup|resume|clear |
context reminder |
| Codex | PostToolUse |
Bash|shell_command, apply_patch|Edit|Write |
staleness check |
SessionStart deliberately excludes compact: the block usually survives
compaction in the summary, and re-emitting it there would double it up. init
also sets env.ENABLE_TOOL_SEARCH=true so the MCP tool schemas load on demand
rather than sitting in every session's standing context (an existing value you
set, including a deliberate false, is left untouched).
For manual debugging, the underlying entry points can be run directly:
repowise-augment # invoked by the agent hooks; prints what it would inject
repowise augment # equivalent Click subcommandThe two are complementary:
- Hooks are passive, automatic, and cost the agent nothing. They fire on every search, edit, or session start whether or not the agent is thinking about graph context.
- MCP tools are active and on-demand, with richer output. Reach for them when the agent needs full documentation, a risk assessment, decision history, or dependency tracing.
For most day-to-day coding, the hooks supply enough context on their own; the MCP tools are there for deeper investigation.
Footnotes
-
The command is written wrapped in a presence check rather than as the bare name:
if command -v repowise-augment >/dev/null 2>&1; then exec repowise-augment; fiThe Claude Code plugin ships these hooks independently of the CLI, so "plugin installed,
repowisenot installed" is a supported state — and a partially written install reaches it too (on Windows an MCP server holdsrepowise.exeopen, so an installer can abort after writing only some console scripts). Unguarded, either state printscommand not foundon every matched tool call: non-blocking, unactionable, and endless. The guard is POSIX (command -v+exec, verified undersh,bashanddash) and forwards stdin unchanged, so the hook behaves identically when the script is present. An older install carrying the bare name is rewritten on the nextrepowise init. Codex hooks keep the bare name: their execution model is not documented as shell-based, and a directlyexec'd guard would try to run a binary namedif. ↩ ↩2