chore: remove leftover debug scripts and stale docs

Delete dead dev scratch (debug_python_ast*.js, test_python_inheritance.js),
the obsolete tree-sitter-dart native patch (Dart loads via WASM now and the
package is no longer a dependency), and orphaned docs superseded by the
current code and the agent-eval skill (IMPLEMENTATION_PLAN.md,
DELPHI-SUPPORT.md, run-interactive-test.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-05-21 08:27:12 -05:00
co-authored by Claude Opus 4.7
parent c3f1e273d4
commit 2fc0df7108
7 changed files with 0 additions and 2223 deletions
-157
View File
@@ -1,157 +0,0 @@
# Pascal / Delphi Support for CodeGraph
## Why Delphi?
Delphi (Object Pascal) remains one of the most widely used languages for Windows desktop and enterprise applications. With an estimated **1.53 million active developers** and a strong presence in industries like healthcare, finance, logistics, and government, Delphi projects often involve large, long-lived codebases that benefit significantly from semantic code intelligence.
Many Delphi codebases have grown over decades — making structural understanding, impact analysis, and cross-file navigation exactly the kind of tooling gap CodeGraph is designed to fill.
Adding Delphi support positions CodeGraph as a uniquely valuable tool for a community that has historically been underserved by modern static analysis and AI-assisted development tools.
## What Was Implemented
### Pascal / Object Pascal (tree-sitter)
Full extraction support for `.pas`, `.dpr`, `.dpk`, and `.lpr` files using the `tree-sitter-pascal` grammar:
| Feature | NodeKind | Details |
|---------|----------|---------|
| Units / Programs | `module` | `unit`, `program`, `package`, `library` |
| Classes | `class` | Including inheritance and interface implementation |
| Records | `class` | Treated as classes (consistent with AST structure) |
| Interfaces | `interface` | With GUID support |
| Methods | `method` | Constructor, destructor, procedures, functions |
| Functions / Procedures | `function` | Top-level (non-class) routines |
| Properties | `property` | With read/write accessors |
| Fields | `field` | Class and record fields |
| Constants | `constant` | `const` declarations |
| Enums | `enum` | With enum members |
| Type Aliases | `type_alias` | `type TFoo = ...` |
| Uses / Imports | `import` | `uses` clause extraction |
| Function Calls | — | `calls` edges for call graph |
| Visibility | — | `public`, `private`, `protected` on methods/fields |
| Static Methods | — | `class function` / `class procedure` |
| Containment | — | `contains` edges (class → method, unit → type, etc.) |
| Inheritance | — | `extends` / `implements` edges |
### DFM / FMX Form Files (custom extractor)
Support for Delphi form files (`.dfm` for VCL, `.fmx` for FireMonkey) using a regex-based custom extractor — no tree-sitter grammar exists for this format:
| Feature | NodeKind / EdgeKind | Details |
|---------|---------------------|---------|
| Components | `component` | `object Button1: TButton` |
| Nested hierarchy | `contains` | Panel1 → Button1 |
| Event handlers | `references` (unresolved) | `OnClick = Button1Click` → links UI to Pascal methods |
| `inherited` keyword | `component` | Inherited form components |
| Multi-line properties | — | Correctly skipped during parsing |
| Item collections | — | `<item>...</end>` blocks correctly handled |
The DFM ↔ PAS linkage via event handlers enables **cross-file impact analysis**: renaming a method in `.pas` immediately reveals which UI components reference it.
## Architecture
The implementation follows CodeGraph's established patterns:
- **Pascal extraction** uses the standard `TreeSitterExtractor` with a Pascal-specific `LanguageExtractor` configuration and a `visitPascalNode()` hook for AST nodes that require special handling (e.g., `declType` wrappers, `defProc` implementation bodies)
- **DFM/FMX extraction** uses a `DfmExtractor` class — analogous to `LiquidExtractor` and `SvelteExtractor` — that parses the line-based format with regex
- **Routing** in `extractFromSource()` dispatches `.dfm`/`.fmx` files to `DfmExtractor` before reaching the tree-sitter path
- **`tree-sitter-pascal`** is declared as an `optionalDependency` (consistent with all other grammars), pinned to a specific commit for reproducible builds
## Performance Improvements
Testing with a large Delphi codebase (~3,400 files, ~244k nodes) uncovered performance bottlenecks in the reference resolution pipeline. The following fixes **benefit all languages**, not just Pascal:
| Fix | Scope | Impact |
|-----|-------|--------|
| **Fuzzy match index** — replaced O(n) linear scan with lazily-built case-insensitive `Map` index | `name-matcher.ts` (all languages) | O(1) lookup per ref instead of iterating all nodes |
| **Import mapping cache** — cached per-file import mappings instead of re-reading/re-parsing for every ref | `import-resolver.ts` (all languages) | Eliminated redundant file I/O during resolution |
| **Kind cache** — pre-populated `getNodesByKind` results during warm-up | `resolution/index.ts` (all languages) | Avoided repeated DB queries for the same node kinds |
| **Pascal built-in filtering** — skip known RTL/VCL/FMX identifiers before resolution | `resolution/index.ts` (Pascal-specific) | ~60 built-in identifiers filtered out early |
| **Method index for `defProc`** — replaced O(n) `find()` with `Map` lookup when linking implementation bodies to declarations | `tree-sitter.ts` (Pascal-specific) | O(1) per implementation body |
| **Delphi-specific excludes**`__history/**`, `__recovery/**`, `*.dcu` added to default excludes | `types.ts` (Pascal-specific) | Skips Delphi IDE temp files during indexing |
**Result:** Reference resolution on a large Delphi project dropped from **~30 minutes to ~15 seconds** (120x speedup). The general improvements (fuzzy index, import cache, kind cache) will benefit all CodeGraph users.
## Files Changed
| File | Change |
|------|--------|
| `src/types.ts` | Added `'pascal'` to `Language` type, file patterns to `DEFAULT_CONFIG.include` |
| `src/extraction/grammars.ts` | Grammar loader, extension mappings (`.pas`, `.dpr`, `.dpk`, `.lpr`, `.dfm`, `.fmx`), display name |
| `src/extraction/tree-sitter.ts` | Pascal `LanguageExtractor`, `visitPascalNode()` with 7 helper methods, `DfmExtractor` class, routing in `extractFromSource()`, method index |
| `src/resolution/index.ts` | Pascal built-in filtering, kind cache, cache clearing |
| `src/resolution/import-resolver.ts` | Import mapping cache |
| `src/resolution/name-matcher.ts` | Fuzzy match index (case-insensitive `Map`) |
| `package.json` | `tree-sitter-pascal` in `optionalDependencies` (pinned commit) |
| `__tests__/extraction.test.ts` | 37 new tests covering all Pascal and DFM extraction features |
## Test Results
- **36 new tests**, all passing
- **0 regressions** — the same 28 pre-existing failures (unrelated: missing Swift/Dart grammars, database path issues, MCP truncation test) are unchanged
- Tests cover: language detection, modules, imports, classes, records, interfaces, methods, visibility, static methods, enums, properties, constants, type aliases, calls, containment, full fixture files (UAuth.pas, UTypes.pas, MainForm.dfm)
## Dependency Note
The npm package `tree-sitter-pascal@0.0.1` is outdated (uses NAN bindings, incompatible with Node.js v24+). The implementation uses the actively maintained GitHub repository ([Isopod/tree-sitter-pascal](https://github.com/Isopod/tree-sitter-pascal), v0.10.2) with a pinned commit hash for deterministic builds. This is consistent with how `@sengac/tree-sitter-dart` handles a similar situation.
## Testing Instructions
### Prerequisites
- Node.js >= 18
- npm
- Git
### 1. Clone and build
```bash
git clone -b delphi-support https://github.com/omonien/codegraph.git
cd codegraph
npm install
npm run build
```
### 2. Link globally
```bash
npm link
```
Verify with:
```bash
codegraph --version
```
### 3. Index a Delphi project
```bash
cd /path/to/your/delphi-project
codegraph init -i
codegraph index
```
### 4. Query the code graph
```bash
codegraph status # Show index statistics
codegraph query "TFormMain" # Search for a symbol
codegraph context "What does TCustomer do?" # Build AI context
```
### 5. Set up the MCP server (for Claude Code)
```bash
codegraph install
```
This configures the MCP server, tool permissions, auto-sync hooks, and CLAUDE.md in one step. After that, start Claude Code in the project — CodeGraph tools will be available immediately.
### 6. Clean up
```bash
npm unlink -g @colbymchenry/codegraph # Remove global link
rm -rf /path/to/delphi-project/.codegraph # Remove project index
```
File diff suppressed because it is too large Load Diff
-26
View File
@@ -1,26 +0,0 @@
const { getParser, initGrammars, loadAllGrammars } = require('./dist/extraction/grammars');
(async () => {
await initGrammars();
await loadAllGrammars();
const parser = getParser('python');
const code = `class Child(Parent):
pass`;
const tree = parser.parse(code);
function walk(node, depth = 0) {
const indent = ' '.repeat(depth);
const preview = node.text.substring(0, 30).replace(/\n/g, '\\n');
console.log(`${indent}${node.type} [${node.startPosition.row}:${node.startPosition.column}] "${preview}"`);
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
if (child) walk(child, depth + 1);
}
}
walk(tree.rootNode);
})();
-26
View File
@@ -1,26 +0,0 @@
const { getParser, initGrammars, loadAllGrammars } = require('./dist/extraction/grammars');
(async () => {
await initGrammars();
await loadAllGrammars();
const parser = getParser('python');
const code = `class Child(Parent, Mixin, Base):
pass`;
const tree = parser.parse(code);
function walk(node, depth = 0) {
const indent = ' '.repeat(depth);
const preview = node.text.substring(0, 40).replace(/\n/g, '\\n');
console.log(`${indent}${node.type} "${preview}"`);
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
if (child) walk(child, depth + 1);
}
}
walk(tree.rootNode);
})();
-131
View File
@@ -1,131 +0,0 @@
# Running the agent-behavior test (how agents actually use codegraph)
This explains how to measure **how a Claude Code agent uses the codegraph MCP
tools** on a real repo — which tools it calls (does it lead with
`codegraph_explore`?), how many follow-up `Read`/`Grep`s it does, and the token
cost. Use it when changing tool guidance (`server-instructions.ts`,
`instructions-template.ts`, tool descriptions) or retrieval, to verify the
change actually shifts agent behavior.
Scripts live in `scripts/agent-eval/`.
## Why two harnesses (read this first)
| | Interactive (`itrun.sh`) | Headless (`run-agent.sh`) |
|---|---|---|
| Drives | the real TUI via tmux | `claude -p` print mode |
| Subagent it picks | **Explore** (matches real UX) | general-purpose (diverges) |
| Metrics | tool breakdown (from session logs) + `Done(…)` token summary | exact per-tool calls + tokens/cost (stream-json) |
| Cost | Claude Max subscription | API $ (`total_cost_usd`) |
**Headless `claude -p` does NOT reproduce what users see** — it silently picks
the general-purpose subagent, while interactive sessions delegate to the
read-first **Explore** subagent. So for "what does my session actually do," use
the interactive harness. For a clean per-tool/token breakdown in one shot, use
headless (and ask for the Explore subagent in the prompt if you want that path).
## Prerequisites
- **tmux 3.0+**
- A logged-in `claude` CLI (Claude Max or API).
- codegraph configured as an MCP server (`claude mcp list` shows `codegraph`).
The interactive harness uses your global config, so it runs whatever
`codegraph` resolves to — point that at your dev build (`npm link` / the
symlinked global) to test local changes.
- A target repo, cloned and indexed:
```bash
git clone --depth 1 https://github.com/square/okhttp /tmp/corpus/okhttp
cd /tmp/corpus/okhttp && codegraph init -i
```
Good scale spread for a sweep: Alamofire (~100 files), Excalidraw (~600),
OkHttp (~640), VS Code (~10k).
## Interactive test (the faithful one)
```bash
scripts/agent-eval/itrun.sh <repo-path> <label> "<question>"
```
Example:
```bash
scripts/agent-eval/itrun.sh /tmp/corpus/vscode vscode \
"How does the extension host communicate with the main process?"
```
It opens `claude` in a tmux session, types the question, waits for the agent to
finish, then prints:
- the `Done (N tool uses · Xk tokens · Ym)` subagent summary (from the pane),
- the `Context Xk/1.0M` main-session size,
- a **tool breakdown** parsed from the session logs (main + subagents), ending
in a `VERDICT: codegraph_explore used Nx | Read N | Grep/Bash N` line.
### Startup robustness (so unattended runs don't silently no-op)
Two things bite an unattended driver before the prompt even runs:
- **The `` glyph is drawn ~6s before the input accepts keystrokes.** Waiting
for `` is necessary but not sufficient. The harness sends the prompt, then
**verifies a chunk of it actually landed in the input box**, retrying until it
does — so it can't type into a not-yet-live input and submit nothing.
- **First time claude opens a repo it shows "Is this a project you trust?"**
(which also contains ``). The harness detects that dialog and presses Enter
to accept it before typing.
If the prompt never lands or work never starts, the harness now **fails loudly**
(non-zero exit) instead of capturing an empty pane and reporting a bogus run.
### How completion is detected (the tricky part)
Claude's TUI redraws in place, so you can't just wait for output to stop. The
harness polls `tmux capture-pane` and treats the pane as **busy** when it shows
the spinner's elapsed-time-in-parens — `(8s · …)` / `(1m 3s · …)`, matched by
`\(([0-9]+m )?[0-9]+s ·`. That's the *universal* working signal: it shows during
the pre-stream **thinking** phase (`(8s · thinking with max effort)`, which has
no token arrow yet) *and* during streaming. The `↓ N`/`↑ N` token arrow,
`esc to interrupt`, and `Initializing…` are OR'd in as belt-and-braces (some TUI
versions show one but not the others). It declares **idle** when the `` prompt
is present and not busy for 10 consecutive polls (~5s, long enough to ride out
mid-conversation thinking gaps that briefly drop the spinner). (Technique
adapted from devpit's `WaitForIdle`.)
### Where the breakdown comes from
`parse-session.mjs` reads the newest session log under
`~/.claude/projects/<escaped-cwd>/<session>.jsonl` and its subagent transcripts
under `<session>/subagents/*.jsonl`. The **subagent** file is where the real
tool calls are — the main log only shows the `Agent` delegation. You can run it
standalone:
```bash
node scripts/agent-eval/parse-session.mjs /tmp/corpus/vscode
```
## Headless test (clean tokens, forceable Explore path)
```bash
scripts/agent-eval/run-agent.sh <repo-path> <label> "<question>"
```
Writes stream-json and prints the tool sequence + exact tokens/cost. To
reproduce the Explore-subagent path headlessly, ask for it:
`"Use an Explore subagent to investigate, then answer: …"`.
## Running a sweep
Single runs vary a lot (the VS Code question has ranged 2637 tool uses /
88105k tokens across runs). For a real signal, run N≥3 and take the median:
```bash
for i in 1 2 3; do
scripts/agent-eval/itrun.sh /tmp/corpus/vscode "vscode-$i" "<question>"
done
```
## What "good" looks like
After the explore-first guidance (PR #191), an understanding question should
show the agent **leading with `codegraph_explore`** and using `search`/`node`
to fill gaps — not a wall of `Read`/`Grep`. Example faithful run:
`VERDICT: codegraph_explore used 3x | Read 8 | Grep/Bash 1`. If `explore` is 0
and `Read`/`Grep` dominate, the guidance regressed.
## Output artifacts
Transcripts and logs go to `$AGENT_EVAL_OUT` (default `/tmp/agent-eval/`):
`itrun-<label>.txt` (pane capture), `run-<label>.jsonl` (headless stream-json).
-112
View File
@@ -1,112 +0,0 @@
#!/usr/bin/env node
/**
* Patches tree-sitter-dart to use NAPI bindings compatible with tree-sitter 0.22+
*
* tree-sitter-dart v1.0.0 ships with NAN-style bindings that are incompatible
* with tree-sitter 0.22+ which expects NAPI-style bindings with type-tagged
* externals. This script rewrites the binding files and rebuilds.
*/
const { writeFileSync, existsSync } = require('fs');
const { join } = require('path');
const { execSync } = require('child_process');
const DART_DIR = join(__dirname, '..', 'node_modules', 'tree-sitter-dart');
if (!existsSync(DART_DIR)) {
// tree-sitter-dart not installed, skip
process.exit(0);
}
// Check if already patched (look for NAPI-style binding)
const bindingPath = join(DART_DIR, 'bindings', 'node', 'binding.cc');
const { readFileSync } = require('fs');
try {
const existing = readFileSync(bindingPath, 'utf8');
if (existing.includes('napi.h')) {
// Already patched, check if build exists
const buildPath = join(DART_DIR, 'build', 'Release', 'tree_sitter_dart_binding.node');
if (existsSync(buildPath)) {
console.log('tree-sitter-dart: already patched and built.');
process.exit(0);
}
// Patched but not built, fall through to rebuild
}
} catch {
// Can't read, continue with patch
}
console.log('Patching tree-sitter-dart for NAPI compatibility...');
// Write NAPI-compatible binding.cc
const bindingCC = `#include <napi.h>
typedef struct TSLanguage TSLanguage;
extern "C" TSLanguage *tree_sitter_dart();
// "tree-sitter", "language" hashed with BLAKE2
const napi_type_tag LANGUAGE_TYPE_TAG = {
0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
};
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports["name"] = Napi::String::New(env, "dart");
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_dart());
language.TypeTag(&LANGUAGE_TYPE_TAG);
exports["language"] = language;
return exports;
}
NODE_API_MODULE(tree_sitter_dart_binding, Init)
`;
writeFileSync(bindingPath, bindingCC);
// Write NAPI-compatible binding.gyp
const bindingGyp = `{
"targets": [
{
"target_name": "tree_sitter_dart_binding",
"dependencies": [
"<!(node -p \\"require('node-addon-api').targets\\"):node_addon_api_except"
],
"include_dirs": [
"src"
],
"sources": [
"src/parser.c",
"bindings/node/binding.cc",
"src/scanner.c"
],
"conditions": [
["OS!='win'", {
"cflags_c": [
"-std=c99"
]
}, {
"cflags_c": [
"/std:c11",
"/utf-8"
]
}]
]
}
]
}
`;
writeFileSync(join(DART_DIR, 'binding.gyp'), bindingGyp);
// Rebuild native module
try {
execSync('npx node-gyp rebuild', {
cwd: DART_DIR,
stdio: 'pipe',
timeout: 120000,
});
console.log('tree-sitter-dart: patched and rebuilt successfully.');
} catch (error) {
console.error('Warning: Failed to rebuild tree-sitter-dart native module.');
console.error('Dart language support may not work.');
if (process.env.DEBUG) {
console.error(error.stderr?.toString());
}
}
-35
View File
@@ -1,35 +0,0 @@
const { extractFromSource } = require('./dist/extraction');
const { initGrammars, loadAllGrammars } = require('./dist/extraction/grammars');
(async () => {
await initGrammars();
await loadAllGrammars();
const code = `
class Parent:
pass
class Child(Parent):
pass
class Multiple(Parent, Mixin):
pass
`;
const result = extractFromSource('test.py', code);
console.log('=== NODES ===');
result.nodes.forEach(n => {
console.log(`${n.kind}: ${n.name} (line ${n.startLine})`);
});
console.log('\n=== UNRESOLVED REFERENCES ===');
result.unresolvedReferences.forEach(r => {
console.log(`${r.referenceKind}: ${r.referenceName} (from ${r.fromNodeId})`);
});
console.log('\n=== EDGES ===');
result.edges.forEach(e => {
console.log(`${e.kind}: ${e.source} -> ${e.target}`);
});
})();