Keep leaf nodes spread across files when pruning the candidate set - #101
Keep leaf nodes spread across files when pruning the candidate set#101StefanZoerner wants to merge 1 commit into
Conversation
When there are more than LEAF_REDUCTION_THRESHOLD candidates, the node set is pruned down to components that nothing else depends on. In call-graph shaped repositories such as C or Go, almost every function is called from somewhere, so this can collapse the set to a handful of unreferenced functions. On umoria it left 27 of 765 functions, covering only 11 of 46 source files, and the resulting set was small enough to skip LLM clustering entirely. Compare file coverage before and after the pruning. If it drops below half, discard the pruned set and instead cap the full candidate list at LEAF_REDUCTION_THRESHOLD, picking entries round-robin across files so that no file is dropped while another contributes dozens of entries. Also raise the pruning log from DEBUG to INFO, since previously there was no indication at normal verbosity that a reduction had happened at all, and stop rejecting identifiers that merely contain a word like invalid. Refs FSoft-AI4Code#75
|
Thanks for looking into this, and sorry for the slow review. I went through the change carefully and want to explain why I'm hesitant about the fallback before we go further. The leaf nodes from get_leaf_nodes are meant to be the in-degree-0 roots of the dependency graph, i.e. the entry points nothing else depends on. They're not supposed to cover every file. The idea is that they seed the clustering step and each module agent, and the agent then explores downstream from those roots (it gets the full file for each core component plus read_code_components to pull in dependencies). So a root set that only touches 20% of files isn't a bug by itself; the other 80% is supposed to be reached through exploration. The new fallback works against that. When coverage drops below 0.5 it stops using roots and instead ranks each file by descending in-degree, so it picks the most-depended-upon nodes first. Those are hubs and shared helpers, which is pretty much the opposite of an entry point. On a CLI-shaped repo with a few main functions as the true roots, we'd end up handing the clustering step utility code as "core components" and dropping the actual entry points. That flips which branch runs on a 50% threshold, so the same repo could get very different module trees depending on which side it lands on. That said, I suspect you hit a real problem to motivate this. If the docs were missing large parts of a repo, my guess is the exploration side is the weak link rather than the root selection: the agent has to guess component IDs from file content, and there's no tool that lists a component's dependencies. Could you share which repo you were testing on? If that's the issue, I'd rather fix it in a way that keeps roots as the seeds, e.g.:
Happy to discuss either direction. Thanks again for digging into this. |
Refs #75
Problem
With functions included as leaf candidates (840e1f4), umoria produces 770
candidates, which crosses
LEAF_REDUCTION_THRESHOLD(400). The pruning intopo_sort.pythen discards everything that is a dependency of anythingelse, leaving only components nothing calls — 27 of 770, covering 11 of 46
source files. The node set is then small enough to skip LLM clustering
entirely and fall back to whole-repository mode.
The two mechanisms work against each other: the better function inclusion
gets, the more reliably the threshold is crossed and the harder the pruning
cuts.
Change
Compare file coverage before and after the pruning. If it drops below half,
discard the pruned set and cap the full candidate list instead, picking
entries round-robin across files so no file is dropped while another
contributes dozens.
Also raises the pruning log from DEBUG to INFO — previously there was no
indication at normal verbosity that a reduction had happened — and stops
rejecting identifiers that merely contain a word like
invalid.Effect on umoria
Tests
pytest tests/ --ignore=tests/smoke_test_mcp.py: 48 passed, 2 failed. Bothfailures are in
test_gitignore_filtering.pyand occur onmainas well.