Repository navigation
A correctness- and honesty-focused release: closes several MultiDiGraph parallel-edge-drop gaps left
over from the 0.4.0 migration (including two introduced by this release's own new code, caught and
fixed during a multi-round adversarial review before shipping), hardens the Apex extractor against
comment/string false positives, normalizes LLM backend config drift, and fixes ambiguous doc-mention
resolution. Every fix below shipped with a regression test proven to fail on the pre-fix code.
Fixed — MultiDiGraph parallel-edge drops (serve.py, merge-graphs, and the new bfs_impact tool)
- MCP tools and the CLI's
pathcommand now read every parallel edge, not just the first.
serve.py'sget_node,get_neighbors, andshortest_path, plusgraphify-sf path, previously
called the single-edgeedge_data()helper on the default MultiDiGraph, so a node pair with two
relations (e.g.queries+dmlto the same object) silently reported only one of them — the same
class of bug the 0.4.0 MultiDiGraph migration fixed elsewhere, unfixed in the MCP/CLI layer until now.
All four now useedge_datas()and surface every relation.get_node/get_neighborsmay now return
one entry per relation instead of one per neighbor (each entry still shaped
{id, label, sf_type, relation, confidence});shortest_path's per-hoprelation/confidence
becomes a list when a hop has more than one parallel edge (a scalar for the common single-edge case).
If you consume these MCP tools, re-check any caller that assumed exactly one entry per neighbor. merge-graphs/merge-driverno longer drop parallel edges, on either axis. The union previously
went through a plainnx.Graph(), collapsing same-direction parallel edges (e.g.queries+dmlon
one pair) before the multigraph-aware rebuild ever ran; then, after being rewritten to preserve those,
it dropped edges that shared a relation but differed only inoperation(e.g. twodmledges, one
createoneupdate, on the same pair). The union now hashes the full edge identity (minus the
per-graphkey/source_filefields that legitimately vary across inputs), so every distinct parallel
edge survives a 2- or 3-way merge while genuinely-identical duplicates (the base/ours/theirs case)
still coalesce to one.- The new
bfs_impactMCP tool (see Added) now reports every parallel relation to a neighbor. Its
first implementation stored one{relation, confidence}pair per neighbor id in its BFS accumulator,
re-introducing — in code this same release added — the exact parallel-edge collapse fixed above for
the other MCP tools.relation/confidenceare now lists when a neighbor has more than one parallel
edge (a scalar for the common single-edge case), mirroringshortest_path.
Fixed — Apex extraction honesty (comments, strings, method scoping, generics)
- Apex class extraction no longer treats commented-out or string-literal code as real.
insert/
update/etc. inside//,/* */, or a string literal previously produced real edges to a nonexistent
target; comments and string contents are now scrubbed beforeextract_apex_class's DML, method-call,
SOQL,EventBus.publish, and Custom Metadata/Setting-access extraction. - Known gap (disclosed, not fixed this release): Apex HTTP-callout detection
(extract_apex_class's endpoint scan) andextract_apex_trigger's call-scanning still read raw,
unscrubbed source, so commented-out or string-embedded code in those two paths can still produce a
phantom edge. Pre-existing, not introduced by the fix above; tracked for a future release. - Apex
callsedges no longer leak across method boundaries. A method's call-edge scan previously
ran to end-of-file instead of to its own closing brace, so every method accumulated calls that
actually belonged to methods below it in the same class. - Apex
implementsclauses with a generic argument list no longer split incorrectly.
implements Comparator<Account, String>previously split on the comma inside the generic brackets and
emitted a bogus edge treatingStringas an interface. - Apex DML operand→type resolution is now scoped per method, not per class. Two methods reusing the
same local variable name with different declared types (e.g.Account ain one method,Contact ain
another) previously collided viadict.setdefault, silently misattributing the second method's DML.
Fixed — LLM backends, doc mentions, MCP robustness, --verbose accounting
- LLM backend config drift across the 6 supported backends resolved. OpenAI previously had no
max_tokensentry and fell back to a hardcoded8192(half of every sibling backend's16384);
Gemini silently ignored the documentedGRAPHIFY_SF_MAX_OUTPUT_TOKENSenv override;temperaturewas
never passed to Claude's SDK call and was hardcoded (ignoring config) for Bedrock. All 6 backends now
resolvemax_tokens/temperaturethrough the same codepath. Kimi's thinking-disable and Ollama's
context-sizing quirks moved from inline branches to a data-driven per-backend hook with no behavior
change (proven by regression test). Theopenaibackend's effectivemax_tokenschanges from 8192
to 16384 — this changes output size/cost for existing--backend openaiusers; not a silent change. - Doc/PDF cross-reference mentions no longer resolve to an arbitrary match when ambiguous. When a
mention's label matched multiple graph nodes, the resolver previously picked the first one arbitrarily
(e.g. aDocumentSectionheading could win over the realCustomObjectit was named after). It now
prefers non-document candidates, and falls back to emitting all remaining candidates at a reduced
INFERREDconfidence score when the ambiguity is real. A small denylist now filters common English
words that happen to be PascalCase-shaped (suffixed names likeAccount__calways bypass it), and a
mention inside a fenced or inline code span now gets a higher confidence score than one in bare prose. - MCP tool int arguments (
bfs_impact'smax_depth/limitand others) no longer raise on
non-numeric input. A caller-supplied non-numeric value previously raised an uncaughtValueError;
a new_get_inthelper now degrades to the tool's default and clamps out-of-range values. --verbosenode accounting no longer mislabels retained nodes as stubs on--update. Incremental
mode merges with the entire priorgraph.json, so a node carried over unchanged from a file no longer
on disk was counted as a build-injected stub; the stub term now reportsn/a (incremental merge)on
the incremental path, matching the existingn_pruned_edgesguard.
Added
bfs_impactMCP tool — a depth-bounded, direction-aware (forward/reverse/both) blast-radius
traversal, EXTRACTED-edges-only by default (include_inferredopt-in), withrelation_filter,
limit/truncatedreporting, and aby_typebreakdown. Closes the gap between graphify-sf's own
1-hop-onlyget_neighbors/get_nodetools and the multi-hop impact analysis downstream consumers had
to reimplement on top of rawgraph.json.--verbose/-vnode-drop accounting on build/update — prints
N extracted → M deduped-by-label (K merged) → +S stub → F final nodes (P dangling edge(s) pruned)so
the extract→dedup→build node-count pipeline is no longer opaque.
Verification
- 503 tests (up from 451 at 0.4.0),
ruff check/ruff format --checkclean, zero new runtime
dependencies. Every fix above was independently reproduced against a pre-fix checkout before and after
the change, not just asserted by a passing test suite.