---
purpose: Explain how callstack-diff is built, tested, evaluated, and evolved from agent complaints.
applies_to: Changes under packages/callstack-diff.
entrypoint: src/cli.ts for behavior; default.nix for packaging.
verification: Run `bun run lint`, `bun run test`, `bun run evals`, then build and smoke `.#callstack-diff`.
update_when: The CLI, Effect pipeline, resolver, Vite/Vitest setup, Pi eval harness, or packaging changes.
---

# callstack-diff (csd)

A local Bun + TypeScript tool that uses Effect 4 for its CLI and error pipeline,
and renders and diffs **static** call-stack trees for JS/TS using the
[Oxc](https://oxc.rs) parser (`oxc-parser`). It exists to make markdown plans
and refactors readable at a glance: a shallow, flat tree is usually healthy; a
deep or ballooning one is a slop signal.

## Commands

```
callstack-diff <entry> [paths...] [options]        # render a call-stack tree
callstack-diff diff <entry> [paths...] --from REF  # diff between revisions
callstack-diff skill                               # print the bundled agent skill
```

`csd` is an equivalent short command for every invocation.

`<entry>` is `name`, `Class.method`, or `file.ts#name`. Without `paths`, csd
scans `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` under `--root` (default: cwd),
ignoring `node_modules`, `dist`, `build`, and `.git`.

Key options: `--depth N` (default 6), `--no-branches` (drop if/else/switch
grouping), `--args` (include raw call arguments), `--theme default|libretto|none`,
and for `diff` the `--from <ref>` / `--to <ref>` git revisions (`--to` defaults
to the working tree).

## How it works

1. `cli.ts` defines the command tree with `effect/unstable/cli`; `analyze.ts`,
   `discover.ts`, and `git.ts` put filesystem and Git failures on a tagged
   Effect error channel. Git and tar run through Effect's platform process
   service while the core transforms stay pure.
2. `project.ts` parses each file with `oxc-parser` and indexes function
   declarations, arrow/function consts, and class members by bare name and by
   `Class.method`.
3. `tree.ts` walks the entry body in source order, extracts the outermost
   call/`new` expressions per statement, groups `if/else/switch` into branch
   nodes, and recurses into resolved callee bodies (depth- and cycle-guarded).
4. `diff.ts` aligns two trees' children by label with an LCS and marks nodes
   `+`/`-`/context.
5. `render.ts` prints the box-drawing tree; `git.ts` materializes a ref with
   `git archive` for the `diff` command.

## Resolution is a heuristic

Call resolution is name-based and intentionally simple: it does not do full type
resolution, so dynamic dispatch, re-exports, and same-named symbols across files
resolve to the first match. Bare `foo()` prefers a free function over a class
member. Calls passed as arguments are not expanded as siblings; the resolved
callee body is what gets expanded. Treat the output as a readable approximation,
not a sound call graph.

## Build & packaging

- Runs sources directly on Bun (no bundle step). The runtime uses pinned Effect
  4 beta packages. The canonical agent skill lives at
  `skills/catalog/callstack-diff/SKILL.md`; the package `SKILL.md` symlink serves
  the development CLI, while `default.nix` copies the canonical file into the
  Nix package. The package exposes the CLI as both `callstack-diff` and `csd`.
- Vite provides the test transform/config boundary; Vitest runs unit and CLI
  tests. Neither Vite nor Vitest is part of the production command or Nix
  runtime closure.
- Dependencies, including the native `@oxc-parser/binding-*` packages, are pinned
  in `bun.nix` (generated by bun2nix) and fetched via `fetchBunDeps`.
- `@effect/tsgo`, oxlint, Vite, Vitest, and `vitest-evals` are package-local
  development dependencies. They are omitted from the Nix runtime output.

## Agent complaint loop

The eval lane turns a plain Pi agent's observation into a reproducible complaint,
not an automatic fix:

```bash
bun run test       # deterministic Vitest unit and CLI checks
bun run evals      # load the eval suite; live cases skip without opt-in
bun run evals:pi   # run plain Pi and write ignored vitest-results.json
bunx vitest-evals serve vitest-results.json  # inspect complaint artifacts
```

`evals:pi` disables Pi's discovered extensions, skills, prompt templates,
themes, context files, and sessions. The agent receives the read tool plus one
package-owned `callstack_diff` tool that runs the case's fixed public command;
it receives no shell, edit, or write tool. Use `CALLSTACK_DIFF_EVAL_PROVIDER`
and `CALLSTACK_DIFF_EVAL_MODEL` to select a model, and
`CALLSTACK_DIFF_EVAL_TIMEOUT` to override the 150-second limit.

When an agent finds a bug, limitation, or missing use case:

1. Reproduce it through `csd` before changing behavior.
2. Minimize public inputs under `evals/fixtures/` and add a case with the exact
   command, expected evidence, actual evidence, and proposed regression.
3. Make the deterministic scorer reject vague reports. Keep the raw JSON report
   local; promote only minimized evidence into cases and tests. Never commit
   secrets or private transcripts.
4. Add a Vitest spec or regression test before implementing the fix. A model
   eval supplements deterministic coverage; it never replaces it.
5. Update this file when the complaint changes a documented heuristic or the
   eval boundary.

## Updating dependencies

```bash
bun install                 # refresh bun.lock
bun run lint:setup          # patch package-local oxlint for Effect diagnostics
bun run lint
bun run test
bun run evals
nix run github:nix-community/bun2nix/5a39d717029e94163ac223aee8d5c9946cafed1c -- \
  -l ./bun.lock -o ./bun.nix
nix build .#callstack-diff
```

`lint:setup` mutates ignored files under `node_modules`; run it after a fresh
install. Nix deliberately uses `--ignore-scripts` and `--production`, so this
development patch never runs in the sandbox or ships in the package.

Keep `@types/bun` pinned to a concrete version; `latest` makes bun2nix emit a
non-reproducible lockfile warning.
