From pyeye
Use when understanding, navigating, debugging, refactoring, or extending Python code with the pyeye MCP server. Triggers on "how does X work", "what does", "why is X doing", "what calls/imports/subclasses this", "add feature", "implement", "refactor", "trace", "debug", "extend". Does NOT trigger for explicit "show me"/"read this file"/"print"/"display" requests, "what's the syntax for" language questions, single-line typo/string fixes with an exact location given, adding a new test case only, or when pyeye output for the symbol is already in context. Requires the pyeye MCP server.
How this skill is triggered — by the user, by Claude, or both
Slash command
/pyeye:python-exploreThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build a structural model of Python code with pyeye before reading or changing it —
Build a structural model of Python code with pyeye before reading or changing it — orient cheap, drill on demand.
pyeye answers questions in three widening steps. Start at the cheapest that answers your question; only go wider when you need to.
resolve a name to a canonical handle, then inspect it for
structure, or outline a module/class for its skeleton.expand one edge from a handle to see its immediate
neighbours (members, callees, importers, subclasses…).trace follows edges multiple hops to see structure
across a call chain, import closure, or member tree.Canonical handles are the currency. A handle is Python's own dotted notation —
a.b.c.Name — anchored at the definition site, stable across edits, and the same
no matter which import alias you arrived through. Everything downstream takes a handle.
pyeye returns pointers and structured facts, never source — when you need the actual
code, Read the file:line it points you at.
Rigid gates (non-negotiable):
Read().Flexible (use judgement):
inspect, or a multi-hop trace).Read().| Primitive | Use it to | Returns |
|---|---|---|
resolve(identifier) | Turn a name, dotted path, or file:line into a canonical handle | {handle, kind, scope, location} — or {ambiguous, candidates} |
resolve_at(file, line, column) | Turn a position (stack frame, pasted line) into a handle | same shape as resolve |
inspect(handle) | Answer "what is this?" — kind, signature, docstring, edge_counts | a structural Node, plus kind-dependent fields (e.g. superclasses, re_exports); no source |
outline(handle) | See the structural skeleton of a module or class in one call | a tree of member stubs |
expand(handle, edge) | Walk ONE edge to the immediate neighbours | a list of stubs |
trace(start, follow, …) | Walk edges across multiple hops | a subgraph (nodes + edges) |
Notes that matter:
resolve already includes a location pointer, so it answers "where is this defined?"
on its own — you do not need a follow-up inspect just for location. Use inspect
when you want signature/docstring/edge_counts.resolve then returns
{found: true, ambiguous: true, candidates: [...]}, each candidate carrying
handle/kind/scope/location. Pick the right one, or re-resolve with the full
dotted handle.scope is "project" or "external". Project symbols get full-graph answers;
external symbols (stdlib, site-packages) get project-scoped answers — see the scope
example below.inspect/outline/expand/trace return pointers and
structured facts. Read is the content layer.expand and trace walk these edges. This is the complete set pyeye can answer
reliably today:
| Edge | Direction | Meaning |
|---|---|---|
members | container → children | a class's methods/attributes, or a module's top-level defs |
enclosing_scope | child → parent | the one lexical scope containing this symbol (method → class, etc.) |
callees | function → what it calls | forward call targets resolved from the body |
subclasses | class → its subclasses | project classes that extend this class |
superclasses | class → its bases | the class's direct base classes |
imports | module → what it imports | the module's top-level imports |
imported_by | module → its importers | project modules that import this module |
This is the most important rule in this skill.
"Who calls this?" and "what references this?" cannot be answered reliably yet. The
edges callers, references, read_by, written_by, passed_by, overrides,
overridden_by, decorated_by, and decorates are deferred — they need an indexed
reference backend (Pyright) that isn't wired up yet (#333).
When you ask for one, pyeye refuses (reports it as unsupported) rather than returning
a wrong or empty answer. Likewise, inspect's edge_counts simply omits callers
and references — it does not report them as 0.
Do NOT fake reverse-reference data. Specifically:
grep to guess who calls or references a symbol.find_references or get_call_hierarchy
to fill the gap. They are backed by exactly the reverse search the redesign rejected:
it under-reports non-deterministically — anchored at a definition it can return a
near-empty set for a heavily-used symbol. A confident wrong answer is worse than an
honest "not available."Instead, say so plainly: "pyeye can't give reliable caller/reference data yet (deferred to #333)." Then offer what you can answer:
callees (what it calls).imported_by (who imports it) and imports (what it imports).subclasses / superclasses.members, enclosing_scope.When you read edge_counts, a missing key means "not measured," not "zero." A
present superclasses: 0 means measured-and-none (the class has no bases). An absent
callers key means pyeye didn't measure it — don't read that as "no callers."
digraph python_explore {
rankdir=TB;
"Task involves Python code" [shape=diamond];
"Explicit read/show request?" [shape=diamond];
"Already explored in context?" [shape=diamond];
"Reverse-reference question?" [shape=diamond];
"Respect request, use Read()" [shape=box];
"Use what's in context" [shape=box];
"State the limit honestly, offer forward edges" [shape=box];
"Orient: resolve then inspect / outline" [shape=box];
"Drill / trace as needed" [shape=box];
"Proceed with task" [shape=box];
"Task involves Python code" -> "Explicit read/show request?" [label="yes"];
"Task involves Python code" -> "Proceed with task" [label="no"];
"Explicit read/show request?" -> "Respect request, use Read()" [label="yes"];
"Explicit read/show request?" -> "Already explored in context?" [label="no"];
"Already explored in context?" -> "Use what's in context" [label="yes"];
"Already explored in context?" -> "Reverse-reference question?" [label="no"];
"Reverse-reference question?" -> "State the limit honestly, offer forward edges" [label="yes"];
"Reverse-reference question?" -> "Orient: resolve then inspect / outline" [label="no"];
"Orient: resolve then inspect / outline" -> "Drill / trace as needed";
"Drill / trace as needed" -> "Proceed with task";
"Use what's in context" -> "Proceed with task";
"State the limit honestly, offer forward edges" -> "Proceed with task";
}
Handles below use a placeholder project (myapp.…) — substitute your own. The
pathlib example is real and works anywhere.
"What is Settings?" — orient in one or two calls:
resolve("myapp.config.Settings") -> { handle: "myapp.config.Settings", kind: "class",
scope: "project", location: {...} }
inspect("myapp.config.Settings") -> kind, signature, docstring,
edge_counts: { members: 7, superclasses: 1 }
Ambiguous name — let resolve disambiguate, don't guess:
resolve("Settings") -> { ambiguous: true, candidates: [
{ handle: "myapp.config.Settings", ... },
{ handle: "myapp.cli.Settings", ... } ] }
Pick the candidate you meant, or re-resolve with the full dotted handle.
"I'm looking at this line / stack frame — what is it?" — position to handle:
resolve_at("myapp/cache.py", line=42, column=8) -> { handle: "myapp.cache.Cache.evict", ... }
"What's inside this module?" — skeleton in one call:
outline("myapp.cache") -> tree of (Cache, DependencyTracker, ...) with their methods
"What does this function call?" — forward, reliable:
expand("myapp.cache.Cache.evict", edge="callees") -> stubs for each call target
"Who imports this module?" — reverse, but reliably static:
expand("myapp.cache", edge="imported_by") -> module stubs that import myapp.cache
"What subclasses this base?" — inheritance down:
expand("myapp.plugins.Base", edge="subclasses") -> project classes extending Base
Multi-hop closure — structure across hops:
trace(start="myapp.cache.Cache.evict", follow=["callees"], max_depth=3)
-> subgraph of the 3-hop forward call structure
Project vs external scope — pathlib.Path is external, so subclasses come back
project-scoped:
inspect("pathlib.Path") -> scope: "external", members/signature derived on demand
expand("pathlib.Path", edge="subclasses") -> only classes in THIS project that extend Path
"Who calls Cache.evict?" — the honest refusal:
Reliable caller data isn't available yet —
callersis deferred to the Pyright backend (#333), and faking it with grep or the legacy reference tools would under-report. What I can show: whatCache.evictitself calls (callees), and which modules importmyapp.cache(imported_by). Want either of those?
When it helps the user — a non-trivial change, an ambiguous request, a risky edit —
state what you found before proceeding. Use canonical handles + file:line so the
user can correct you:
**Mental model: `myapp.cache.Cache`** (`myapp/cache.py:31`)
- Depends on: `myapp.config.Settings`, `collections.OrderedDict`
- I can see: 7 members, 1 superclass (measured); subclasses on demand via `expand`
- I cannot see: callers/references (deferred, #333) — change impact on callers is unverified
Scale this to the task. A pure "how does X work?" answer may need no separate summary; a refactor that touches shared code deserves one — and should be explicit that caller impact can't be statically confirmed.
If pyeye is unavailable (server down, tools not responding):
Read()."Read().Don't block — degrade gracefully, but make the limitation visible.
Read() on a Python file you haven't oriented in pyeye.grep to find a definition.grep to answer a relationship/reference question.find_* / get_* legacy tools.If you catch yourself doing any of these: STOP. Orient with pyeye, or say honestly what it can't answer.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub okeefeco/pyeye-mcp --plugin worktree-hook