Skip to content

v0.4.1

Latest

Choose a tag to compare

@raykuonz raykuonz released this 14 Jul 07:25

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 path command now read every parallel edge, not just the first.
    serve.py's get_node, get_neighbors, and shortest_path, plus graphify-sf path, previously
    called the single-edge edge_data() helper on the default MultiDiGraph, so a node pair with two
    relations (e.g. queries + dml to 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 use edge_datas() and surface every relation. get_node/get_neighbors may 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-hop relation/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-driver no longer drop parallel edges, on either axis. The union previously
    went through a plain nx.Graph(), collapsing same-direction parallel edges (e.g. queries + dml on
    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 in operation (e.g. two dml edges, one
    create one update, on the same pair). The union now hashes the full edge identity (minus the
    per-graph key/source_file fields 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_impact MCP 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/confidence are now lists when a neighbor has more than one parallel
    edge (a scalar for the common single-edge case), mirroring shortest_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 before extract_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) and extract_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 calls edges 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 implements clauses 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 treating String as 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 a in one method, Contact a in
    another) previously collided via dict.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_tokens entry and fell back to a hardcoded 8192 (half of every sibling backend's 16384);
    Gemini silently ignored the documented GRAPHIFY_SF_MAX_OUTPUT_TOKENS env override; temperature was
    never passed to Claude's SDK call and was hardcoded (ignoring config) for Bedrock. All 6 backends now
    resolve max_tokens/temperature through 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). The openai backend's effective max_tokens changes from 8192
    to 16384
    — this changes output size/cost for existing --backend openai users; 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. a DocumentSection heading could win over the real CustomObject it was named after). It now
    prefers non-document candidates, and falls back to emitting all remaining candidates at a reduced
    INFERRED confidence score when the ambiguity is real. A small denylist now filters common English
    words that happen to be PascalCase-shaped (suffixed names like Account__c always 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's max_depth/limit and others) no longer raise on
    non-numeric input.
    A caller-supplied non-numeric value previously raised an uncaught ValueError;
    a new _get_int helper now degrades to the tool's default and clamps out-of-range values.
  • --verbose node accounting no longer mislabels retained nodes as stubs on --update. Incremental
    mode merges with the entire prior graph.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 reports n/a (incremental merge) on
    the incremental path, matching the existing n_pruned_edges guard.

Added

  • bfs_impact MCP tool — a depth-bounded, direction-aware (forward/reverse/both) blast-radius
    traversal, EXTRACTED-edges-only by default (include_inferred opt-in), with relation_filter,
    limit/truncated reporting, and a by_type breakdown. Closes the gap between graphify-sf's own
    1-hop-only get_neighbors/get_node tools and the multi-hop impact analysis downstream consumers had
    to reimplement on top of raw graph.json.
  • --verbose/-v node-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 --check clean, 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.