Skip to content

Latest commit

 

History

History
493 lines (336 loc) · 18.2 KB

TOOL_CONTRACT.md

File metadata and controls

493 lines (336 loc) · 18.2 KB

Tool Contract Reference — ix-opencode-plugin

API reference for all 7 Ix tools. Each tool calls the ix CLI and returns a formatted markdown string.


Contract rules (all tools)

  1. Input: structured JSON parameters validated by OpenCode's parameter schema
  2. Output: formatted markdown string — never raw JSON, never structured objects
  3. Fallback: if ix is unavailable, return a helpful error with recovery steps rather than throwing
  4. Ix error records: since Ix v0.12.0 a read that cannot answer prints an error record on stdout and exits 1 — {"error":"<code>","message":"…"} in JSON, error code=<code> message="…" in llm. Tools recognise it with runtime/ix-error.ts and return Ix returned an error with the code, the message and Ix's own fix, never a "nothing found" or "clean" result. For workspace_not_mapped, or a graph.status of empty/degraded, the text says the project is not mapped and to run ix map from the project root (or ix-ingest with refresh: true). This is distinct from rule 3: there Ix said nothing (not installed, timed out, no output) and the tool reports ix unavailable.
  5. Depth scaling: heavier analysis phases run only when lighter phases indicate they're needed
  6. Directory: all ix CLI calls run in toolCwd(context) (runtime/paths.ts): context.directory, the directory the session was opened in. OpenCode's worktree is / for a project outside git, so it is only a fallback when directory is missing, and never when it is a filesystem root.
  7. Fields: tools read the fields ix --format json actually emits; tests/success-fixtures.test.ts drives every tool through real CLI output (tests/fixtures/ix-v0.12.0/success/).

The string-only output constraint comes from the OpenCode runtime. Returning objects from tools has caused runtime issues in practice.


Tools

ix-query

Purpose: Look up a symbol, class, file, or subsystem in the Ix graph. Returns role, connections, and importance without reading source code.

Parameters:

Parameter Type Required Description
symbol string yes Symbol name, file path, or subsystem to look up
kind enum no Narrow to: function, class, file, module
path string no Narrow results to a specific directory path

Behavior:

  1. Runs ix locate <symbol> — resolves the entity
  2. Runs ix explain <entity> — gets role, importance, caller/callee counts
  3. Returns formatted summary

If ix locate returns multiple matches, returns the first result plus a note to narrow with --kind or --path.

Example output:

## ix-query: UserService

**Name:** `UserService`
**Kind:** class
**File:** src/services/user.ts
**Subsystem:** auth
**Role:** orchestrator
**Importance:** high
**Callers:** 14
**Callees:** 7

Manages user lifecycle — creation, authentication state, and session binding. Central to the auth subsystem.

Fallback (ix unavailable):

## ix-query: UserService

**ix unavailable.** The Ix graph service is not running or not installed.

command -v ix   # check installation
ix status       # check connection
ix map          # build graph if needed

Error: <error message>

ix-neighbors

Purpose: Get the neighborhood of a symbol: who calls it, what it calls, and what depends on it. Purely graph-based.

Parameters:

Parameter Type Required Default Description
symbol string yes — Symbol, class, or file to get neighbors for
direction enum no all callers, callees, depends, imported-by, all
limit number no 15 Max results per direction (capped at 30)
depth number no 2 Traversal depth for depends (max 3)

Behavior:

  • Runs the appropriate ix callers, ix callees, ix depends, or ix imported-by commands
  • direction: "all" runs callers + callees in parallel
  • Results grouped by direction with counts

Example output:

## ix-neighbors: AuthController

**Callers** (3 total, showing 3):
- `UserRouter` (class) [api] — src/api/user_router.ts
- `SessionRouter` (class) [api] — src/api/session_router.ts
- `AdminRouter` (class) [api] — src/api/admin_router.ts

**Callees** (8 total, showing 8):
- `UserService` (class) [auth] — src/services/user.ts
- `SessionManager` (class) [auth] — src/services/session.ts
- `TokenValidator` (function) [auth] — src/auth/tokens.ts
...

ix-impact

Purpose: Analyze the blast radius and change risk for a symbol or file. Depth scales with detected risk level.

Parameters:

Parameter Type Required Description
target string yes Symbol name or file path to assess

Behavior:

  1. Runs ix impact <target> --format json — reads riskLevel, riskSummary, atRiskBehavior, propagationBuckets[].region, and the counts under summary: callers/callees for a function, directImporters/directDependents/memberLevelCallers/members for a file or other container. "Total reaching it" is their sum.
  2. Low risk + < 3 reaching it: stops here, returns safe-to-proceed verdict
  3. Medium/high/critical: for a function, also runs ix callers <target> --limit 20 (results[]) for key callers; for a file, lists Ix's topImpactedMembers
  4. Returns risk report with verdict and recommended action

Risk levels: low → SAFE TO PROCEED, medium → REVIEW CALLERS FIRST, high/critical → NEEDS CHANGE PLAN. A withheld riskLevel: "unknown" (hollow or empty graph) → CANNOT ASSESS, with Ix's graph verdict and fix.

Example output:

## Impact: runtime/cli.ts

**Risk level:** HIGH
**Verdict:** NEEDS CHANGE PLAN
**Summary:** High risk — widely shared dependency affecting the tools layer and impact analysis layer.

**Blast radius:**
- Direct importers: 18
- Direct dependents: 0
- Callers of its members: 33
- Members: 4
- Total reaching it: 51
- Subsystems affected: Runtime, Explain, Plugins, Impact

**Key callers:**
- `safeRun` (12 callers) — runtime/cli.ts
- `runIx` (11 callers) — runtime/cli.ts
- `failureDetail` (10 callers) — runtime/cli.ts

**At-risk behaviors:**
- Multiple callers across different subsystems
- Cross-module behavior consistency

**Recommended action:**
- Run `/ix-plan` before editing. This change needs a sequenced plan.

ix-map

Purpose: Get the architectural map of the codebase: all subsystems, cohesion/coupling scores, and codebase stats. Use for orientation before deeper exploration.

Parameters:

Parameter Type Required Default Description
scope string no — Scope to a specific subsystem name or path prefix
include_stats boolean no true Include codebase stats (file count, nodes, edges)

Behavior:

  • Runs ix subsystems, ix subsystems --list, and ix stats in parallel
  • Builds a subsystem table from regions[] (label, label_kind, level, files, cohesion, coupling, confidence); with scope, renders the target region and its children instead
  • Lists scores[] from ix subsystems --list as subsystem health
  • Takes the file count from stats.nodes.byKind (kind file) and totals from nodes.total / edges.total
  • Flags multi-file subsystems with low cohesion (< 0.4) with ⚠. Ix's coupling is an edge-weight count, not a 0–1 ratio, so it is shown but not judged

Example output:

## ix-map

**Codebase:** 55 files · 870 nodes · 1745 edges

**Subsystems** (11, 38 files):

| Subsystem | Kind | Level | Files | Cohesion | Coupling | Confidence |
|-----------|------|-------|-------|----------|----------|------------|
| Explain | module | 1 | 7 | 0.45 | 28.98 | 0.38 |
| Impact | module | 1 | 5 | 0.67 | 33.74 | 0.34 |
| Runtime | module | 1 | 8 | 0.40 | 29.16 | 0.40 |
| Tools ⚠ | system | 5 | 24 | 0.31 | 0 | 0.78 |

**Subsystem health:** Tools (0.63), Agents (0.59), Runtime (0.52), Explain (0.51), Impact (0.50)

ix-ingest

Purpose: Check the Ix graph ingestion status. Can optionally trigger a graph rebuild.

Parameters:

Parameter Type Required Default Description
refresh boolean no false If true, trigger ix map to rebuild the graph
silent boolean no true If refreshing: use ix map --silent

Behavior (status check):

  • Runs ix status --format json if available
  • Falls back to probing ix subsystems --list if ix status is unavailable
  • Reads backend, graphCompleted, mapCompleted, currentRev, lastIngestAt, staleFiles, sampleChangedFiles; graphCompleted: false is reported as not mapped
  • The probe reads scores[].name from ix subsystems --list

Behavior (refresh):

  • Runs ix map (or ix map --silent)
  • Returns confirmation when complete

Example output (status):

## ix-ingest: status

**Backend:** ok
**Graph:** ingested (rev 1)
**Last ingest:** 2026-10-02T01:14:41.855Z
**Freshness:** current

Example output (refresh):

## ix-ingest: graph refresh

**Status:** Graph refresh complete.
The Ix graph has been rebuilt. Graph data is now current.

Example output (ix unavailable):

## ix-ingest: status

**ix CLI not found.** Install Ix to enable graph-aware features.

command -v ix   # check if installed
ix docker start # start the local backend
ix map          # build initial graph

ix-history

Purpose: Get change history, recorded decisions, and known bugs for a symbol or workspace. Requires Ix Pro. Returns empty results gracefully if Pro is unavailable.

Parameters:

Parameter Type Required Default Description
topic string no — Symbol or topic to filter history/decisions to
include array no ["briefing"] What to fetch: decisions, bugs, changes, briefing

Behavior:

  • Runs ix briefing --format json to check Pro availability
  • If Pro is unavailable, returns a graceful message explaining what requires Pro
  • If Pro is available, fetches the requested data sections
  • With topic, runs ix decisions --topic <topic> for filtered decisions

Pro features:

  • decisions — recorded architectural decisions
  • bugs — open bug reports
  • changes — recent code changes
  • briefing — all of the above plus active goals and plans

Example output (Pro available):

## ix-history: AuthService

**Revision:** rev-4821

**Recent decisions** (2):
- Move session storage to Redis — 2026-03-15
  Motivated by horizontal scaling requirements; sessions now must be stateless.
- Deprecate legacy token format — 2026-02-28
  New tokens use JWT; legacy tokens still accepted until v3.0.

**Open bugs** (1):
- Session expires early on mobile [high] — affects `SessionManager`

Example output (Pro unavailable):

## ix-history

**Ix Pro not available.** History, decisions, and bug tracking require Ix Pro.

Graph-based features (ix-query, ix-neighbors, ix-impact, ix-map) work without Pro.

ix-docs-tool

Purpose: Get a condensed architectural context summary for a symbol, subsystem, or file. Use to inject graph context before making changes — not the same as the /ix-docs skill (which writes full documentation).

Parameters:

Parameter Type Required Default Description
target string yes — Symbol name, file path, or subsystem to summarize
depth enum no standard brief, standard, full

Depth behavior:

Depth What runs
brief ix locate + ix overview only
standard above + ix stats, and ix explain for the first 5 of overview's keyItems
full above + ix impact for change risk context

Example output (standard):

## Context: runtime/cli.ts

_55 files · 870 nodes_

**Kind:** file
**Path:** runtime/cli.ts
**Subsystem:** Tools › Impact
**Contains:** 1 interface, 3 functions

**Key Components:**

- `safeRun` (12 callers) — shared-utility
- `runIx` (11 callers) — shared-utility
- `failureDetail` (10 callers) — shared-utility


ix-locate

Purpose: Text/pattern search across the indexed codebase. Use when you have a keyword or pattern rather than an exact symbol name.

Parameters: pattern (required), limit (default 20, max 100), path (optional), language (optional)

CLI: ix text <pattern> --limit <n> [--path P] [--language L] --format json


ix-explain

Purpose: Full symbol explanation — role, importance level, caller/callee counts, top dependents, and a plain-English description.

Parameters: symbol (required)

CLI: ix explain <symbol> --format json


ix-rank

Purpose: Rank symbols by a graph metric to surface hotspots and high-centrality components.

Parameters: by (default dependents; callers, importers, members), kind (default class), top (default 10, max 50), path (optional)

CLI: ix rank --by <by> --kind <kind> --top <n> [--path P] --format json


ix-stats

Purpose: Graph-wide node/edge counts, file count, and health status. Verify the graph is indexed before running expensive queries.

Parameters: none

CLI: ix stats --format json


ix-subsystems

Purpose: Detailed subsystem listing with file counts, hierarchy levels, confidence signals, and interface counts.

Parameters: none

CLI: ix subsystems --format json


ix-inventory

Purpose: Enumerate files or symbols within a directory path scope from the graph.

Parameters: path (required), kind (default file; class, function, interface, module)

CLI: ix inventory --kind <kind> --path <path> --format json


ix-trace

Purpose: Trace full execution paths through a symbol — upstream callers and downstream callees. Use for complete call chains, not just immediate neighbors.

Parameters: symbol (required), to (optional target for path tracing)

CLI: ix trace <symbol> [--to <target>] --format json


ix-decide

Purpose: Pre-edit policy verdict (ALLOW / REVIEW / BLOCK) with required actions and blast radius evidence.

Parameters: touched_paths (required array), intent (default edit), risk_tolerance (default medium)

CLI: ix impact <path> --format json per touched file (first 5) — synthesizes a conservative verdict from riskLevel and the dependent counts under summary (directImporters + directDependents + memberLevelCallers for a file, callers for a function: the same total Ix's risk inference uses). Subsystems come from propagationBuckets[].region.

Fails closed. ALLOW is only given when every touched path has a parsed impact record with a real risk level and dependent counts.

Situation Verdict
any path riskLevel: critical, or total dependents ≥ 20 (×0.5 / ×2 for risk_tolerance low / high) BLOCK
ix not installed, timed out, or exited with nothing on stdout REVIEW
an Ix error record (workspace_not_mapped, unresolved_target, ambiguous_target, …) REVIEW
output that does not parse, or JSON without riskLevel / dependent counts REVIEW
riskLevel: unknown or a graph block whose status is not ok (Ix withholds risk on a hollow graph) REVIEW
more than 5 paths (the rest are not checked), or no paths REVIEW
any path riskLevel: high or medium, or total dependents ≥ 5 (scaled as above) REVIEW
every path assessed, all low, total dependents below the REVIEW threshold ALLOW

BLOCK outranks the REVIEW rows: a critical path is BLOCK even if another path could not be assessed. A REVIEW for a path Ix could not assess lists that path, the reason, and Ix's own fix.


ix-health

Purpose: Check CLI availability and graph index state.

Parameters: none

CLI: ix status --format json (with ix --version fallback)


ix-smells

Purpose: Report the architecture smells (orphan files, god modules, weak components) for the workspace's graph.

Parameters: limit (default 50, max 200); detect (default false) re-runs detection instead of reading stored claims. ix smells has no path filter; it always covers the whole workspace.

CLI:

  1. ix smells --list (llm, else --format json: {count, smells:[{smell, entity_id, confidence}]}) reads the claims a previous detection run stored.
  2. When none are stored and the graph is confirmed mapped, or when detect is true, the tool runs ix smells (llm, else --format json: {rev, count, candidates:[{file, smell, confidence, signals}]}). Detection stores smell claims for this workspace only; the output says that it ran.

Zero claims is not "clean". Before running detection the tool checks graph health (ix status → graphCompleted, ix stats → nodes.total). An unmapped graph, or one Ix cannot confirm is mapped, gets that answer and no detection run. "No smells detected" is reported only after a detection run on a mapped graph finds none.


Adding a new tool

To add a tool to the plugin:

  1. Create tools/<name>.ts following this template:

    import { runIx } from "../runtime/cli.ts";
    import { toolCwd } from "../runtime/paths.ts";
    
    export const name = "ix-<name>";
    export const description = "...";
    export const parameters = { type: "object", properties: { ... }, required: [...] } as const;
    
    type Params = { ... };
    type Context = { directory: string; worktree?: string };
    
    export async function execute(params: Params, context: Context): Promise<string> {
      const dir = toolCwd(context);
      // call ix CLI, format output as markdown string
      // always return a string — never throw on ix errors
    }
  2. Import and register in plugins/ix-plugin.ts:

    import * as ixNewTool from "../tools/ix-new-tool";
    // Add to plugin.tools array
  3. Follow the contract:

    • Always return a string
    • Handle ix unavailability gracefully
    • Use toolCwd(context) as the working directory (never a bare context.worktree, which is / outside git)
    • Add a test that drives the tool through real ix output (tests/success-fixtures.test.ts)
    • Format output as structured markdown (headers, bullets, tables)
    • Include the tool name and target in the first heading: ## ix-<name>: <target>