OpenCode plugin for GitNexus — automatic code graph indexing, staleness detection, and AI agent hints.
On session start:
- Verifies GitNexus CLI is reachable (disables plugin if not)
- Discovers git repos (current dir + subdirectories)
- Classifies the session: subagents are tracked by
parentID; everything else defaults to main and gets the dynamic graph envelope on its user messages - Hydrates the subagent registry from
client.session.list()so long-lived subagents survive plugin reloads - Shows toast with graph status (delayed 6s to avoid oh-my-openagent spinner overlap)
- Background-refreshes stale indexes
Every chat turn:
- Pushes a static GitNexus rule block onto the system prompt (cache-friendly, byte-identical between turns)
- Prepends the dynamic indexed-repo envelope to the last user message of eligible sessions
- Deduplicates the envelope per session: if the next-turn envelope is byte-identical to what was last injected into this session, injection is skipped — the agent already carries it in conversation history
Tools:
gitnexus_query,gitnexus_context,gitnexus_impact, and othergitnexus_*tools — from the GitNexus MCP server. Available to all agents including subagents (requires permission setup).gitnexus_analyze— plugin tool for building/refreshing the graph. Intended for the main agent (re-indexing is expensive, 3-120s depending on repo size). By default it is NOT delegated to subagents unless explicitly permitted in opencode.json.
Hooks:
| Hook | Trigger | Action |
|---|---|---|
tool.execute.after on skill |
Skill loaded | Inject graph prerequisite for init-deep/init, availability hint for others |
tool.execute.after on bash |
Git mutation detected | Background re-index (cache flips to freshness=refreshing, then back to up_to_date) |
experimental.chat.system.transform |
Every LLM call | Push the static GitNexus rule block onto output.system (idempotent; skipped if plugin disabled or already present) |
experimental.chat.messages.transform |
Every LLM call | Prepend the dynamic graph envelope to the last user message of eligible sessions (main sessions automatically; subagents only if the orchestrator added the [[gitnexus:graph]] marker). Skips injection when the envelope is byte-identical to the last one injected into this session (per-session dedup). |
The plugin contributes two distinct pieces of context per chat turn:
Layer 1 — static system addendum (in system prompt). Rules that don't change between sessions: envelope contract, subagent propagation rules, tool preference, a delegate-to-subagent nudge for open-ended graph work, and (for main sessions) the "build a graph yourself" cost/benefit rule. Module-level constant strings, byte-identical across every turn — provider prefix caches keep matching, so the static block is amortized to roughly cached-input cost (≈5× cheaper than uncached on Anthropic).
There are two variants:
- Full addendum (~510 tokens) — pushed onto main sessions. Covers everything: envelope contract, subagent propagation rules, delegate-to-subagent nudge, and the build-graph rule.
- Lite addendum (~160 tokens) — pushed onto subagent sessions. Covers only the envelope contract and tool preference. Omits subagent propagation (subagents rarely spawn further subagents) and the build-graph rule (
gitnexus_analyzeis intended for the main agent).
The variant is chosen per chat call from the sessionID and the SessionRegistry. The registry tracks subagents positively (by parentID) and defaults unknown sessions to main — the safer failure mode, since the dynamic envelope is independently gated by the [[gitnexus:graph]] marker check.
Layer 2 — dynamic envelope (in user message). Per-instance data that does change: which repos are indexed in this workspace right now, their paths, and freshness state. Read fresh on every turn. The envelope cross-references the system prompt for the rules instead of duplicating them.
The envelope is deduplicated per session: a LastInjectedRegistry memoizes the last envelope injected into each session. If the next-turn envelope is byte-identical (no freshness flips, no repos added/removed), the hook skips injection — the agent already has it in conversation history, and re-injecting would only burn uncached input tokens. The memo clears on session deletion or when <gitnexus_graph> no longer appears in visible history (compaction guard), so the next turn force-re-injects.
Splitting puts the rules in the highest-salience location for instruction-following (system prompt) and keeps data in the natural per-turn refresh location (user message). On warm-cache turns the per-turn cost is significantly lower than the previous monolithic envelope; subagents specifically pay roughly a third of what main sessions pay for the static block. On a stable repo state, a long session pays the envelope cost exactly once instead of N times.
Build and deploy:
npm install && npm run build
cp dist/gitnexus-opencode.js ~/.config/opencode/plugins/Ensure GitNexus MCP is configured in ~/.config/opencode/config.json:
{
"mcp": {
"gitnexus": {
"type": "local",
"command": ["npx", "-y", "gitnexus@1.6.5", "mcp"]
}
}
}OpenCode's explore/librarian agents block non-built-in tools by default. Add permissions to ~/.config/opencode/opencode.json so subagents can use GitNexus MCP tools directly:
{
"permission": {
"gitnexus_query": "allow",
"gitnexus_context": "allow",
"gitnexus_impact": "allow"
}
}Without this, the plugin still works — subagents fall back to GitNexus CLI via bash (slower, ~3-5s overhead per call).
Restart OpenCode.
For full step-by-step installation guide (intended for AI agents), see INSTALL.md.
Create .opencode/gitnexus-opencode.json (project) or ~/.config/opencode/gitnexus-opencode.json (global):
{
"gitnexusVersion": "1.6.5",
"autoRefreshStale": true,
"autoRefreshOnCommit": true
}| Key | Default | Description |
|---|---|---|
gitnexusVersion |
"1.6.5" |
Pin the gitnexus npm version |
autoRefreshStale |
true |
Refresh stale indexes on session start |
autoRefreshOnCommit |
true |
Refresh after git commit/merge/rebase |
npm run build # tsc + esbuild bundleProduces dist/gitnexus-opencode.js — single file, all internal modules bundled, @opencode-ai/plugin as external (resolved from OpenCode runtime).
The static rule block is delivered through experimental.chat.system.transform. On every LLM call the plugin appends one of the two addendum variants to output.system:
- Full for main sessions (and for unknown sessions — conservative default, see below)
- Lite for sessions positively identified as subagents via the
SessionRegistry
Skipped if:
- the plugin is disabled (CLI unreachable), or
- any gitnexus addendum is already present in the array (idempotency, detected via the start sentinel — works across both variants).
Both variants are wrapped between the same sentinels <!-- gitnexus:system:start --> and <!-- gitnexus:system:end -->. Neither variant contains per-instance interpolations, so each is byte-identical between turns and across sessions — provider prefix caches match and the cached-input price applies after the first turn.
The full addendum also nudges the main agent to delegate open-ended exploration or multi-flow tracing to a subagent with the [[gitnexus:graph]] marker, instead of stacking inline gitnexus_* calls. This keeps the main agent's context clean on multi-step graph workflows where each tool response (~2-10KB) would otherwise accumulate in history.
The addendum is pushed for every session where the plugin is active, including ones launched from a directory with no locally indexed repos. In that case the addendum tells the agent to call gitnexus_list_repos once to discover repos indexed elsewhere on the machine.
The SessionRegistry tracks subagent sessions positively (by presence of parentID on session.created) and defaults unknown sessions to main. This is the safe failure mode: misclassifying an unknown session as main only adds harmless extra text to its system prompt, while the user-message envelope is still gated by the [[gitnexus:graph]] marker check in messages.transform — so an unmarked subagent does NOT receive the envelope just because it was misclassified.
On plugin init the registry is hydrated from client.session.list(): every existing session with a parentID is re-registered as a subagent. This way long-lived subagents survive plugin reloads and session compaction without being silently re-classified as main and seeing the wrong addendum variant.
The dynamic envelope is delivered through OpenCode's experimental.chat.messages.transform hook. On every LLM call the plugin:
- Finds the last user message in the transient message array OpenCode is about to send to the model
- Decides eligibility:
- Main session (top-level, no
parentID) — always eligible - Subagent session — eligible only if the orchestrator wrote the explicit opt-in marker
[[gitnexus:graph]]into the subagent's prompt
- Main session (top-level, no
- Per-session dedup check: if the next-turn envelope is byte-identical to the one last injected into this session, skip injection. Strip the opt-in marker if present (otherwise it would leak into subagent prompts as literal text) and exit. The agent already has the envelope in conversation history.
- If the envelope is new (or the registry was cleared by compaction), scrubs any leading
<gitnexus_graph>...</gitnexus_graph>block left over from a previous turn (idempotency), strips the opt-in marker, and prepends the fresh envelope to the user message text. Records the injection in theLastInjectedRegistry. - Logs an
info-level summary line so the behavior is observable from the plugin log file
The envelope itself is small (<summary>, <indexed_repos>, and a <rules> cross-reference back to the system prompt). It is read fresh on every LLM turn, so agents always see the current freshness state (up_to_date / refreshing / may_be_stale / missing).
Compaction guard. The handler clears the per-session memo when no <gitnexus_graph> block survives in the visible message history. After OpenCode compacts a session, the next turn force-re-injects so the agent never operates without the envelope when it expects one.
The tool.execute.after hook watches for git mutation commands (commit, merge, rebase, pull, cherry-pick, switch, reset). When one is detected in a bash call, the plugin:
- Marks the affected repo as refreshing in the hint cache and rebuilds the cache immediately, so the very next user turn sees
freshness="refreshing" - Spawns
gitnexus analyzein the background (detached, output piped to /dev/null) - On process close, marks the repo as no longer refreshing and rebuilds the cache again, so the next turn sees
freshness="up_to_date"(ormay_be_staleif HEAD drifted again during analyze)
The static rule block in the system prompt teaches every main agent how the propagation marker works and when to use it — including which subagent_type values qualify, the path-based override rule, and when to build a graph yourself for deep investigation of an unindexed repo. You don't need to teach this from scratch in your orchestrator system prompt; it's already there.
The orchestrator (Sisyphus, Prometheus, OMO, your custom main agent, etc.) decides per spawn whether the subagent should see the dynamic envelope by including the marker [[gitnexus:graph]] anywhere in the subagent's prompt text. The plugin detects the marker, prepends the envelope, and strips the marker so the subagent never sees it as literal text.
Recommended grant (parent should add the marker):
explore,deep,build,quick,refactor,sisyphus-junior,general— code-investigation agents- Any subagent whose prompt mentions an absolute path inside an indexed repo, regardless of
subagent_type
Recommended skip (parent omits the marker):
librarian, doc/knowledge lookup agentsoracleand other reasoning-only agents- Plan critics (
Momus,Metis), plan builders (Prometheus) - Writing / multimodal / non-code agents
A lite variant of the rule block is pushed onto every subagent's system prompt by the same system.transform hook, so even a subagent spawned without the marker still knows the envelope contract (gitnexus_list_repos for self-discovery if the orchestrator forgot the marker) and the tool preference list. The marker controls only whether the per-instance repo data is delivered as a user-message envelope.
Example spawn (from a main agent):
task(
subagent_type="explore",
prompt="[[gitnexus:graph]] List every caller of buildEnvelope in the repo"
)
Inside the spawned subagent the prompt becomes:
<gitnexus_graph source="gitnexus" version="2" freshness="up_to_date">
<summary>Code knowledge graph is available. Graph is current.</summary>
<indexed_repos>
<repo name="myproj" path="/Users/me/code/myproj"/>
</indexed_repos>
<rules>See the GitNexus section of the system prompt for tool preference and subagent propagation rules (marker [[gitnexus:graph]]).</rules>
</gitnexus_graph>
List every caller of buildEnvelope in the repoThe marker itself is gone — the subagent only sees the envelope and the cleaned prompt.
Remove ~/.config/opencode/plugins/gitnexus-opencode.js and restart OpenCode.
If you added GitNexus permissions to opencode.json, remove the gitnexus_query, gitnexus_context, gitnexus_impact entries from the permission object.
If your orchestrator prompts contain literal [[gitnexus:graph]] markers, those become harmless inert text once the plugin is removed. You can leave them or strip them at your convenience.
For full cleanup see UNINSTALL.md.
MIT