# call-depth

_Measures call chains with parsers, in any language._

> Source: https://fullstack.rocks/skills/call-depth
> Code: https://github.com/Fullstack-Rocks/skills/tree/main/skills/call-depth

How deep is this call chain? You can ask an agent to read the code and count, but then you have to spell out the counting rules every time. **call-depth measures it with programs instead.**

The agent traces a call chain from its root (a route, tool, job, CLI or handler). Each project-owned call adds 1 and each memory hop adds 2. A memory hop is a call that go-to-definition can’t follow, like a registry, `getattr`, reflection or a callback. A weighted depth over 3 is a hit.

The scanner works in **any language**, and it needs no runtime from your project.

**Install**

Claude Code:

```shell
npx skills add Fullstack-Rocks/skills --skill call-depth -a claude-code
```

Codex:

```shell
npx skills add Fullstack-Rocks/skills --skill call-depth -a codex
```

Cursor:

```shell
npx skills add Fullstack-Rocks/skills --skill call-depth -a cursor
```

Any agent:

```shell
npx skills add Fullstack-Rocks/skills --skill call-depth
```

Prompt, to paste into your agent:

> Install the call-depth skill from https://github.com/Fullstack-Rocks/skills by running npx skills add Fullstack-Rocks/skills --skill call-depth, then use it whenever you trace a call chain or check complexity and sizes.

It installs best with `under-engineering` and `under-engineering-review`, since both of them call it.

**Try saying**

- How deep is the call chain from POST /orders/{id}/refund?
- Is place\_order too complex?
- Check file, function and folder sizes in app/.

![Claude Code session: the agent loads call-depth, runs scan.py, and reports a weighted depth of 7 for the refund route, with both memory hops at refund_service.py:20 and ledger.py:10](https://storage.googleapis.com/fullstack-rocks-media/skill-call-depth-demo-poster.png)

_[▶ Watch the recording: claude · demo shop](https://storage.googleapis.com/fullstack-rocks-media/skill-call-depth-demo.mp4) — A real Claude Code session on the demo shop, recorded as it happened. Long thinking stretches are sped up, everything else plays at its real pace._

Transcript:

```
> How deep is the call chain from POST /orders/{id}/refund? Use the call-depth skill.
Skill(call-depth) · scan.py and python_scan.py over the refund files.
Weighted depth 7: the two memory hops add 4 of the 7 points.
1. REFUND_HANDLERS[reason](db, order) at refund_service.py:20 looks up the handler in a dict by a string key.
2. getattr(self, f"_write_{kind}")(entry) at ledger.py:10 builds a method name at runtime.
Replacing both with direct calls would bring the deepest path down to 4. Complexity: nothing on this path is over 10.
Also found: the refund route is not registered, and an unknown reason raises a KeyError.
```

## How does it work?

For every measurement the agent uses the first of these that works, and only reads code to confirm a finding or to follow a chain:

1. **A tool the project already uses,** like its linter or its language server.
2. **The bundled scan.py.** Real parsers, lizard and ast-grep rule packs for Python, JS/TS, Go, Ruby, Java, C# and PHP, with no runtime needed from the target project. It reports `FUNCTION_LENGTH`, `COMPLEXITY`, `DYNAMIC_CALL`, `DYNAMIC_ATTR` and `LOOKUP_CALL`, and `layout.py` adds `FILE_LENGTH`, `FOLDER_SIZE` and `FOLDER_NAME`.
3. **Grep patterns,** only for a language `scan.py` reports as `NO_RULES`, with every result labeled as approximate.

A language without a rule pack shows up as `NO_RULES` instead of being skipped silently, and adding a pack is one YAML file plus test fixtures. The scripts need Python 3.10+, and the agent installs ast-grep and lizard itself, at pinned versions and user level.

Here’s what `scripts/scan.py` reports on the demo fixture:

```
DYNAMIC_CALL  app/services/ledger.py:10          getattr(self, f"_write_{kind}")  (calls a method chosen at runtime)
LOOKUP_CALL   app/services/refund_service.py:20  REFUND_HANDLERS[reason](db, order)  (call through a subscript, usually a registry)
COVERED       every scanned language has a rule pack
```

```mermaid
flowchart TD
  R[Root<br/>route, tool, job, CLI] --> S[scan.py]
  S --> A[ast-grep rule packs<br/>DYNAMIC_CALL · LOOKUP_CALL]
  S --> L[lizard<br/>COMPLEXITY · FUNCTION_LENGTH]
  A --> W[Weighted depth<br/>call +1 · memory hop +2]
  L --> W
  W --> H{over 3?}
  H -->|yes| X[Hit: chain + 2 or 3 alternatives]:::hi
  H -->|no| OK[Within the limit]
  classDef hi fill:var(--highlight),stroke:var(--highlight),color:var(--on-highlight)
```

_Measured by parsers, not by reading. A language without a rule pack is NO\_RULES, never skipped._

## But does it work?

**Benchmark · Does the trace come out right?**

The test was a refund route with a weighted depth of 7 and two hidden indirect calls, a dict registry and a getattr dispatch. When the prompt spelled out the counting rules, both versions got the depth and both calls right. The difference is that with the skill nobody has to write those rules into the prompt, and scan.py finds both indirect calls across 58 files in 0.11 s, before the agent reads any code.

![The refund chain from POST /refund to Database.insert: weighted depth 7, with two hidden calls that add 2 each, REFUND_HANDLERS\[reason\] and a getattr dispatch. Claude alone and Claude with call-depth both got the depth and both hidden calls right, and scan.py found both in 0.11 seconds across 58 files.](https://storage.googleapis.com/fullstack-rocks-media/skill-call-depth-proof.svg)

## The other skills

**The other skills**

- [under-engineering](https://fullstack.rocks/skills/under-engineering)
- [um-maybe](https://fullstack.rocks/skills/um-maybe)
- [under-engineering-review](https://fullstack.rocks/skills/under-engineering-review)
- [code-standards](https://fullstack.rocks/skills/code-standards)
