|
| 1 | +--- |
| 2 | +name: use-ig |
| 3 | +description: Use when working in a project that is onboarded to `ig` (infragraphify) and you need to answer how repos/services connect, where the code for a concept or endpoint lives, how a frontend call reaches its backend handler, what infrastructure backs a service, or the blast radius of a change — instead of grepping a monorepo. Triggers on cross-service/cross-repo tracing, FE↔BE API coupling, code↔infra mapping, impact/what-breaks questions, "how does X flow through the system". |
| 4 | +--- |
| 5 | + |
| 6 | +# Using ig to answer questions about a project |
| 7 | + |
| 8 | +**Praxis-MCP read route — queries run server-side via `praxis mcp ig`; no local |
| 9 | +`ig` needed.** (If praxis is unavailable, ig ships a native local-`ig` copy of |
| 10 | +this skill instead. Only one is ever installed at a time.) |
| 11 | + |
| 12 | +`ig` holds a prebuilt **catalog** for a project — a set of code and infra graphs |
| 13 | +joined at their shared boundaries. When a question spans repos or services, or |
| 14 | +asks "what connects to X / what breaks if I change X", query the catalog before |
| 15 | +grepping source. Every read runs on the Praxis server under org-managed |
| 16 | +credentials; your laptop needs no `ig` binary, no synced tree, no cloud secrets. |
| 17 | + |
| 18 | +**The division of labor:** ig states facts (graphs, provenance, source |
| 19 | +file+line). YOU decide, act, and ask the user. The MCP tools are read-only — |
| 20 | +they never build, never prompt, never touch your working copy. |
| 21 | + |
| 22 | +**Boundary — the six `praxis mcp ig` tools are the whole read interface.** Do |
| 23 | +not reach for a local `ig`/`graphify` binary and do not try to `praxis ig sync`; |
| 24 | +in this route reads execute server-side. (Building/refreshing a catalog is a |
| 25 | +separate CI/setup concern that still uses `ig` on a builder host — not your job |
| 26 | +here.) |
| 27 | + |
| 28 | +## First: list the catalogs |
| 29 | + |
| 30 | +There is **NO default catalog** and every read tool needs BOTH a `catalog` and a |
| 31 | +`member`. Start here: |
| 32 | + |
| 33 | +``` |
| 34 | +praxis mcp ig ig_list_catalogs |
| 35 | +``` |
| 36 | + |
| 37 | +It returns the org's catalogs as `name + version`, each with its **members**. |
| 38 | +Pick the `<catalog>` your question is about and pass it as `--arg catalog=<name>` |
| 39 | +to every other tool. A repo can be a member of MORE THAN ONE catalog (e.g. |
| 40 | +`control-plane` in both `capillary-cloud` and `saas-cp`); because `calls` edges |
| 41 | +only appear between members present in the SAME catalog, "who calls |
| 42 | +control-plane" is catalog-relative — ask each catalog. |
| 43 | + |
| 44 | +## The `member` arg — pick your lens (REQUIRED) |
| 45 | + |
| 46 | +A catalog is a **graph of graphs**, so a node is addressed *per member*. Every |
| 47 | +read tool takes `--arg member=<m>` and it selects which graph you read: |
| 48 | + |
| 49 | +| `member=` | You reach | Use for | |
| 50 | +|---|---|---| |
| 51 | +| **`catalog`** | the connective tissue: `service:*` / `route:*` / `graph:*` interface nodes + the cross-repo `calls` / `deploys_as` / `provisions` edges | "how do repos/services connect", "what calls X", frontend↔backend, code↔infra | |
| 52 | +| **`<repo>`** (e.g. `control-plane`) | that repo's code graph (functions, files, symbols) | "where does the code for X live", per-repo blast radius | |
| 53 | +| **`infra`** | the Facets infra graph (module types, datastores) | "what infra module backs X" | |
| 54 | + |
| 55 | +The literal member name `catalog` is the graph-of-graphs itself — that is where |
| 56 | +the interface/connection layer lives. `service:*`/`route:*` nodes are NOT inside |
| 57 | +a repo member (`ig_explain --arg member=control-plane --arg target="service:x"` |
| 58 | +returns "no node"); read them with `--arg member=catalog`. |
| 59 | + |
| 60 | +## The catalog model — read this before running commands |
| 61 | + |
| 62 | +``` |
| 63 | + CATALOG (one project) |
| 64 | + graph:control-plane-ui-react ──calls(http, 469)──▶ graph:control-plane ← indicative connection |
| 65 | + │ deploys_as │ deploys_as |
| 66 | + ▼ ▼ |
| 67 | + service:control-plane-react service:control-plane-service |
| 68 | + ▲ provisions ▲ provisions |
| 69 | + └──────────────── graph:infra ─────────────────────┘ |
| 70 | +
|
| 71 | + MEMBERS (each a full graph) |
| 72 | + ┌ control-plane-ui-react (code) ┐ ┌ control-plane (code) ┐ ┌ infra ┐ |
| 73 | + │ .approveRelease() ─calls─▶ │ │ route:POST …/approve │ │ mongo │ |
| 74 | + │ route:POST …/approveRelease ──┼───┼── handled_by ─▶ .approveRelease() │ redis │ |
| 75 | + └───────────────────────────────┘ └──────────────────────┘ └───────┘ |
| 76 | + ▲ the SAME route node is in both members: the off-page |
| 77 | + connector that joins frontend call site → backend handler |
| 78 | +``` |
| 79 | + |
| 80 | +- **member** — one repo's graph (`kind=code`) or the Facets infra graph (`kind=infra`). |
| 81 | +- **graph node** (`graph:<member>`) — a whole member inside the catalog. |
| 82 | +- **interface node** — a shared boundary: `service:*` (a Facets service), `route:*` |
| 83 | + (an HTTP endpoint), `queue:*` (a queue/topic), `pkg:*` (a package). |
| 84 | +- **connection** — a typed cross-member edge: `deploys_as` (code → its service), |
| 85 | + `provisions` (infra → a service), `provides`/`consumes` (a member ↔ an interface). |
| 86 | +- **calls edge** — `graph:A ──calls(http, N)──▶ graph:B`: indicative "A calls B |
| 87 | + over HTTP N times", rolled up from shared `route:` nodes. The concrete link is |
| 88 | + the route node itself, present in both members (frontend `calls` it, backend is |
| 89 | + `handled_by` it). |
| 90 | + |
| 91 | +Two caveats seen in practice: a `service:*` id for the same physical service can |
| 92 | +be spelled differently across catalogs (`service:control-plane-service` in one vs |
| 93 | +`service:control-plane` in another — they mirror each project's infra naming), so |
| 94 | +rediscover it per catalog rather than reusing an id; and a wrong `target` yields |
| 95 | +a plain "no node" with no fuzzy suggestion. |
| 96 | + |
| 97 | +## Command surface (the six `praxis mcp ig` tools) |
| 98 | + |
| 99 | +Every read tool takes `--arg catalog=<c>` (from `ig_list_catalogs`) and, except |
| 100 | +`ig_list_catalogs`/`ig_catalog`, `--arg member=<m>` (the lens — see above). |
| 101 | +Output is **token-budgeted TEXT** written for an LLM to read. |
| 102 | + |
| 103 | +``` |
| 104 | +praxis mcp ig ig_list_catalogs → the org's catalogs (name + version + members); START HERE |
| 105 | +praxis mcp ig ig_catalog --arg catalog=<c> → topology MAP: members + service:* interfaces + deploys_as/provisions/calls edges |
| 106 | +praxis mcp ig ig_explain --arg catalog=<c> --arg member=<m> --arg target=<node> → node card (source file+line, edges) — the workhorse |
| 107 | +praxis mcp ig ig_impact --arg catalog=<c> --arg member=<m> --arg target=<node> [--arg depth=N] [--arg relation=R] [--arg context=C] → what a change to <node> reaches downstream |
| 108 | +praxis mcp ig ig_query --arg catalog=<c> --arg member=<m> --arg query="<free text>" [--arg relation=R] [--arg context=C] → BFS from name-matched start nodes (NOT semantic search) |
| 109 | +praxis mcp ig ig_path --arg catalog=<c> --arg member=<m> --arg from_node=<a> --arg to_node=<b> → shortest path between two nodes, hop by hop |
| 110 | +``` |
| 111 | + |
| 112 | +- **`ig_query` takes FREE TEXT.** `--arg query="where does approveRelease reach |
| 113 | + the DB"` or a symbol name — ig seeds the BFS from the name-matched start nodes. |
| 114 | + It has NO `depth` (ig hardcodes query depth); scope it instead with `--arg |
| 115 | + relation=calls` / `--arg context=call` (repeat by passing the arg again) or cap |
| 116 | + output with `--arg budget=N` (tokens). `ig_impact` takes the same filters plus |
| 117 | + a real `--arg depth=N`. |
| 118 | +- `ig_catalog` is the map — START HERE for "how do these repos/services connect". |
| 119 | + It needs no `member` (it IS the cross-member view). |
| 120 | +- `ig_path` answers "how does A reach B" with the concrete hop-by-hop edge chain. |
| 121 | +- `ig_explain` is the workhorse: it lands on a node and prints its source |
| 122 | + file+line, degree, and edges. Reach for it to locate a symbol (in a `<repo>` |
| 123 | + member) or an interface node (in the `catalog` member). |
| 124 | +- `ig_impact` traverses code edges downstream from `target` — "what does a change |
| 125 | + to this node reach". `depth` bounds the walk. |
| 126 | +- `ig_query` seeds from name-matched start nodes and walks — see the gotcha below. |
| 127 | + |
| 128 | +## Recipes |
| 129 | + |
| 130 | +**How do the repos/services connect?** `ig_catalog --arg catalog=<c>` — the |
| 131 | +one-shot topology map (members + `service:*` interfaces + `deploys_as` / |
| 132 | +`provisions` / `calls` edges). Start here, then drill into a specific interface |
| 133 | +with `ig_explain --arg member=catalog --arg target="service:<name>"`. |
| 134 | + |
| 135 | +**Trace a frontend call to its backend handler.** From the `ig_catalog` map (or |
| 136 | +`ig_explain --arg member=catalog --arg target="service:<name>"` / the `route:` |
| 137 | +node) find the cross-repo `calls` edge, then drop into the backend `<repo>` |
| 138 | +member — `ig_explain --arg member=<be-repo> --arg target="<handler-symbol>"` — to |
| 139 | +land on the handler. The interface node in the `catalog` member is the join. |
| 140 | + |
| 141 | +**How does A reach B?** `ig_path --arg member=<repo> --arg from_node=<a> --arg |
| 142 | +to_node=<b>` — the concrete hop-by-hop edge chain between two symbols (or "no |
| 143 | +path"). Faster than eyeballing `ig_impact` output when you have both endpoints. |
| 144 | + |
| 145 | +**What breaks if I change a symbol?** `ig_impact --arg member=<repo> --arg |
| 146 | +target="<symbol>"` for internal downstream (scope with `--arg relation=calls` or |
| 147 | +`--arg depth=N`). For an HTTP endpoint, also `ig_explain --arg member=catalog |
| 148 | +--arg target="route:<METHOD /path>"`: its cross-member `calls` edge is the real |
| 149 | +coupling (a route/contract change forces the OpenAPI client + its callers to |
| 150 | +change), which `ig_impact` alone won't show. |
| 151 | + |
| 152 | +**What infra backs a service?** Read the `provisions` connections on the |
| 153 | +`service:*` node in the `catalog` member (`ig_explain --arg member=catalog --arg |
| 154 | +target="service:<name>"`), then enumerate the `infra` member with `ig_query --arg |
| 155 | +member=infra --arg query="<resource>"`. Infra nodes are Facets module types |
| 156 | +(`service/*`, `s3/*`, `mongo`, `artifact/*`) and are often coarse/degree-0 — |
| 157 | +expect module-type + datastore names, not a fully wired topology. |
| 158 | + |
| 159 | +**Where does the code for a concept live?** `ig_query --arg member=<repo> --arg |
| 160 | +query="<free-text question>"` to BFS from name-matched nodes, or `ig_explain |
| 161 | +--arg member=<repo> --arg target="<name-or-substring>"` to land on one node and |
| 162 | +follow its edges (more precise when you know the symbol). The node card's source |
| 163 | +file+line is relative to that member's repo root. |
| 164 | + |
| 165 | +## Gotchas (these are where agents lose time) |
| 166 | + |
| 167 | +- **`ig_query` is BFS traversal, not semantic search.** It picks a small set of |
| 168 | + best-name-match start nodes (not an OR over every word in the term string), then |
| 169 | + walks edges. A vague term (`"release"`) starts inside an unrelated node and |
| 170 | + wanders. Seed with ONE specific symbol, or skip it and `ig_explain` the node |
| 171 | + directly. |
| 172 | +- **Wrong `member` = "no node", even for the right target.** Interface nodes |
| 173 | + (`service:*`, `route:*`, `graph:*`) live ONLY in the `catalog` member; code |
| 174 | + symbols live only in their `<repo>` member. Asking for a `service:*` in a repo |
| 175 | + member, or a code symbol in `catalog`, returns "no node". If a target you're |
| 176 | + sure exists isn't found, you're probably in the wrong lens — switch `member` |
| 177 | + (`catalog` for connections, `<repo>` for code, `infra` for modules). |
| 178 | +- **A wrong `target` returns "no node" with NO fuzzy suggestion.** Don't guess ids |
| 179 | + blindly — start from `ig_list_catalogs`, then `ig_explain` a label/substring you |
| 180 | + are confident about, and copy the exact `ID:` it prints for follow-up calls. |
| 181 | +- **Reference a node by label/substring first; use the full ID to disambiguate.** |
| 182 | + A unique label works (`"approveRelease"`, or a route label `"POST |
| 183 | + …/approveRelease"`). When several nodes share a label, `ig_explain` may |
| 184 | + auto-pick one; copy the full `ID:` line from its output and pass THAT to |
| 185 | + `ig_impact` to be unambiguous. |
| 186 | +- **`ig_impact` on an HTTP entry point returns "No affected nodes" — that IS the |
| 187 | + answer:** the endpoint has no internal callers because it's invoked over HTTP. |
| 188 | + `ig_impact` traverses code edges; it is not an HTTP blast-radius oracle. Use the |
| 189 | + `route:` node + `calls` edge for cross-member coupling. |
| 190 | +- **Node ids differ by member kind** — code = a `path_symbol` slug |
| 191 | + (`v2_src_..._approverelease`), infra = module-type paths (`s3/default/0.2`, |
| 192 | + `service/deployment`, `mongo`, `artifact/*`). Both are accepted as `target`. |
| 193 | + |
| 194 | +## Worked example (real: capillary-cloud) |
| 195 | + |
| 196 | +``` |
| 197 | +praxis mcp ig ig_list_catalogs |
| 198 | + → capillary-cloud 2026.07.06-071024 members: control-plane, control-plane-ui-react, infra |
| 199 | + saas-cp 2026.07.05-… (pick capillary-cloud) |
| 200 | +praxis mcp ig ig_explain --arg catalog=capillary-cloud --arg member=catalog \ |
| 201 | + --arg target="service:control-plane-service" |
| 202 | + → Type: service <-- infra [provisions] --> control-plane [deploys_as] (the join) |
| 203 | +praxis mcp ig ig_explain --arg catalog=capillary-cloud --arg member=control-plane \ |
| 204 | + --arg target="approveRelease" |
| 205 | + → .approveRelease() UiDeploymentController.java:L449 Degree 6 (backend handler) |
| 206 | +praxis mcp ig ig_impact --arg catalog=capillary-cloud --arg member=control-plane \ |
| 207 | + --arg target="approveRelease" |
| 208 | + → the controller is an HTTP entry point, so internal downstream is minimal — the |
| 209 | + real blast radius is the shared route: node + the calls edge (a contract change |
| 210 | + forces the OpenAPI client + its callers to change). |
| 211 | +``` |
| 212 | + |
| 213 | +The shared `route:` node answers "which frontend calls which backend"; the |
| 214 | +`service:*`/`provisions` connections answer "what infra backs it". |
0 commit comments