fix: make a fresh clone actually work (config, schema indexes, cross-file resolution) - #71
fix: make a fresh clone actually work (config, schema indexes, cross-file resolution)#71r0h1tb wants to merge 4 commits into
Conversation
The committed ast_rag_config.json pointed at four private LAN addresses (Neo4j, Qdrant, the summarizer LLM and a remote embedding server). A fresh clone could not reach any of them, and because that file shadows the built-in defaults it broke the one path that already worked: the defaults in dto/config.py have always been localhost. - untrack ast_rag_config.json and gitignore it, so the defaults apply and an existing local copy survives a pull - add ast_rag_config.example.json pointing at the documented Docker services - add docker-compose.yml so `docker compose up -d` starts exactly what the defaults expect. The docker/*.sh scripts invoke podman, which the README never mentions installing - give Neo4jConfig a connection_timeout and pass it to the driver; the driver default left the CLI apparently hung, with `ast-rag stats` against an unreachable host taking 34s before failing README: install now uses compose and needs no config file, and the language table lists Go, which shipped in #17/#55 but was never added there. Fixes #66
Every index creation failed at indexing time while `index-folder` reported
"Errors: 0", so the graph ran with no indexes at all.
Two causes:
STANDARD_INDEXES is unpacked as (label, property, name) and fed to
create_index, but it also held one fulltext entry shaped
(name, [labels], [properties]). Unpacked as a B-tree index that produced
CREATE INDEX ['name', 'qualified_name'] IF NOT EXISTS
FOR (n:ast_symbol_fulltext) ON (n.['Function', 'Class', 'Method'])
which Neo4j rejects. The entry also duplicated an explicit
create_fulltext_index call a few lines below. Fulltext definitions now live
in their own STANDARD_FULLTEXT_INDEXES list, and the dead loop that tried to
filter them back out of STANDARD_INDEXES is gone.
create_fulltext_index emitted invalid Cypher on three counts:
CREATE FULLTEXT INDEX IF NOT EXISTS ast_symbol_fulltext
FOR ([Function:Class:Method]) ON EACH [name, qualified_name]
Neo4j 5 wants the name before IF NOT EXISTS -- the same ordering rule as
CREATE CONSTRAINT, which test_schema_cypher already covers at the sibling
call site -- labels alternated with | on a bound variable, and qualified
property references. Analyzer options also belong under indexConfig.
Verified against Neo4j 5.18: SHOW INDEXES now lists ast_symbol_fulltext
(FULLTEXT) plus the six RANGE indexes; indexing logs no schema errors.
`init` indexes in two phases: parse every file, collect a project-wide
name -> id map, then resolve edges against it. `index-folder` did not -- it
parsed and extracted edges together, per file, in the worker process, so the
resolver only ever saw the current file's symbols.
The comment in `init` states the consequence exactly: "when that map only
holds the current file's nodes, any reference to a symbol defined elsewhere
is silently dropped". Indexing this repo with `index-folder` produced 330
CALLS edges and zero crossing a file boundary, so `callers`, `refs` and
`call-graph` returned nothing for any symbol used from another module -- the
common case. AGENTS.md points agents at `index-folder`.
index-folder now runs the same two phases. Phase 1 extracts nodes and builds
the symbol map; phase 2 re-parses and resolves edges against it. Trees are
not picklable across processes, so phase 2 re-parses rather than carrying
them over; the map is published through a ProcessPoolExecutor initializer so
it is pickled once per worker instead of once per file.
Indexing ./ast_rag before and after:
edges 1,488 -> 2,223
cross-file CALLS 0 -> 376
`ast-rag callers create_driver` returned "No callers found" before and now
lists its callers across mcp/server.py and services/watcher_service.py.
Cost is a second parse pass: 6s -> 11s for 62 files. Also drops a hardcoded
/home/su/src/local/raged fallback from the worker sys.path setup.
|
good mr, split ast_rag/cli.py it very big now) |
|
I launched raged this week - it didn't work for me. Maybe it's the neo4j version or I still need to update it, or maybe this PR will fix everything) I'll try again in a couple of days and see if it works for me. |
`init` extracts blocks for Python and Rust files and stores them with their
CONTAINS_BLOCK edges. `index-folder` never did, so an index built that way
contained no Block nodes at all and both commands that read them returned
nothing:
$ ast-rag blocks _verify_neo4j
No blocks found.
$ ast-rag lambdas
No lambdas found.
AGENTS.md points agents at `index-folder`, so this was the common path.
Phase 2 already re-parses each file and holds its nodes, which is everything
extract_blocks needs, so blocks are collected there rather than in a third
pass. Indexing ./ast_rag now yields 1,047 blocks (620 if, 180 for, 149 with,
82 try, 6 lambda, 4 while) where it previously yielded zero, and both commands
return real results.
Same class as the cross-file symbol table in the previous commit: work that
`init` does and `index-folder` silently skipped.
|
Pushed one more commit — same class of bug, found by running the CLI against a real index rather than reading code.
$ ast-rag blocks _verify_neo4j
No blocks found.
$ ast-rag lambdas
No lambdas found.
Phase 2 already re-parses each file and holds its nodes — everything Indexing 1,047 blocks = 620 This also explains part of what #64 was seeing: Suite still 236 passed / 0 failed, |
Description
I ran AST-RAG from a clean clone against real Neo4j 5.18 + Qdrant, following
the README exactly. It did not work, and three of the four reasons were bugs
rather than setup. This fixes all four.
Everything below is measured on this branch, not inferred from reading code.
Related Issue
Fixes #62, Fixes #66
Type of Change
What a fresh clone did before
1. It could not connect to anything. The committed
ast_rag_config.jsonpointed at four private LAN addresses — Neo4j, Qdrant, the summarizer LLM and a
remote embedding server. Worse, that file shadows the built-in defaults, which
have always been correct localhost values. So the tracked config broke the one
path that already worked.
ast-rag statson a clean clone:Now:
34s → 11s, and it says what to do.
ast_rag_config.jsonis untracked andgitignored (your local copy survives a pull),
ast_rag_config.example.jsonships instead, and
AST_RAG_*env vars override without editing a file.There was also no compose file —
docker/*.shinvoke podman, which theREADME never mentions installing. Added
docker-compose.ymlmatching thedefaults exactly, so
docker compose up -dis genuinely all it takes.2. Every index creation failed, silently.
index-folderprintedErrors: 0while the log filled with Cypher syntax errors, so the graph ranwith no indexes at all:
The name, label and property slots are swapped. Cause:
STANDARD_INDEXESisunpacked as
(label, property, name)and fed tocreate_index, but held onefulltext entry shaped
(name, [labels], [properties])— which was alsocreated explicitly a few lines below, so it was duplication producing garbage.
Separately
create_fulltext_indexemitted invalid Cypher three ways:Neo4j 5 wants the name before
IF NOT EXISTS— the same ordering ruletest_schema_cypheralready covers forCREATE CONSTRAINT, at the siblingcall site it missed — labels alternated with
|on a bound variable, andqualified property references.
SHOW INDEXESafter this change now listsast_symbol_fulltext(FULLTEXT)plus all six RANGE indexes. Indexing logs zero schema errors.
3.
index-folderdropped every cross-file reference. This is the one worthyour attention.
initindexes in two phases — parse everything, build aproject-wide symbol map, then resolve edges.
index-folderparsed and resolvedper file inside the worker, so the resolver only ever saw one file's symbols.
init's own comment states the consequence: "when that map only holds thecurrent file's nodes, any reference to a symbol defined elsewhere is silently
dropped." And
AGENTS.mdpoints agents atindex-folder.Indexing
./ast_rag:ast-rag callers create_driverreturned "No callers found". It now listscallers across
mcp/server.pyandservices/watcher_service.py.refsandcall-graphwere empty for the same reason and now work.4. README omitted Go (shipped in #17/#55) and documented a two-container
docker rundance plus a hand-written config that is no longer needed.Design notes
carrying trees over. The symbol map is published via a
ProcessPoolExecutorinitializer, so it is pickled once per worker rather than once per file.
real tradeoff. If you would rather keep single-pass speed, the alternative is
a post-pass that resolves dangling references against the symbols already in
Neo4j — happy to do that instead.
create_driver(cfg.neo4j)call sites now route through one_connect(cfg)helper so the reachability check is not duplicated./home/su/src/local/ragedfallback from the workersys.pathsetup.Checklist
pytest tests/ -v)ast-rag evaluate --all— see TestingTesting
Three regression tests added to
tests/test_schema_cypher.py, all confirmed tofail on the unfixed branch:
Full suite:
main@ 41e48af)+3 are the new schema tests. The 4th is a test that skips when Neo4j is
unavailable and now runs, because I had the services up — not a change of mine.
End-to-end from a clean graph with no config file present:
ast-rag evaluate --allwas not run. It needs a populated index plus thesummarizer LLM, which the example config points at
localhost:11434and I haveno model serving. Nothing here touches scoring — flagging rather than ticking.
Additional Notes
Deliberately not done: #57 (SQLite replacing Neo4j+Qdrant). You marked it a
draft with an open question, and it is your architecture call, not something to
slip into a bug-fix PR. Worth saying that this PR removes much of the urgency
behind it — the friction was mostly config and a broken indexing path, not the
databases themselves.
Happy to split this into three PRs if you would rather review them separately;
the commits are already clean along those lines.