Releases: raykuonz/graphify-sf
Release list
v0.4.1
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.
v0.4.0
graphify-sf v0.4.0
Full local-extractable coverage expansion + MultiDiGraph fidelity fix. Every extraction Epic was verified by parsing the actual graph.json with negative controls.
Coverage breadth (pure-additive)
- Integration: Apex/Flow HTTP callouts → NamedCredential/ExternalEndpoint; RemoteSiteSetting fix + endpoint URL; External Data Source/Object; Platform Events; Apex→CMDT/CustomSetting; AuthProvider/CSP/CORS; Workflow Outbound Messages.
- Security/sharing: OWD sharingModel; Sharing Rules/Sets; PermSet Group membership; userPermissions + tab/app visibility; Restriction/Duplicate/Matching Rules.
- Automation: Workflow Field Updates/Tasks; Process Builder typed distinct; platform-event/scheduled flow triggers; Validation Rule standard-field refs.
- UI linkage: LWC schema/label/resource imports; Aura/VF controller wiring; VF standardController; FlexiPage embeds; StaticResource/QuickAction/CustomTab/CustomApplication.
- Data model: Field Sets + Global Value Sets; CompactLayout/ListView/RecordType field refs; polymorphic lookups.
- Reporting/packaging: Report/Dashboard/ReportType dependency chain; InstalledPackage + managed-namespace tagging.
⚠️ Breaking contract change — graph.json is now a MultiDiGraph
graph.json declares "multigraph": true; links entries carry a key; a node-pair may appear multiple times. Fixes the same-direction relation collapse (an Apex class that both queries and dml-writes one object previously kept only one relation). Downstream consumers must read ALL links for a pair (filter, not find).
Honesty layer
EXTRACTED = source fact; INFERRED = heuristic. Runtime-only facts excluded by design.
Full test suite: 451 passed.
v0.3.9
Distribution reliability: the prebuilt CLI binary now ships inside npm via platform-specific scoped subpackages (@graphify-sf/cli-<plat>-<arch>) pulled in through optionalDependencies + os/cpu, so npm install makes no network request on the happy path — even on networks that block GitHub Releases. The GitHub download remains a non-fatal last-resort fallback (0.3.8 behaviour preserved).
Python package version bumped to 0.3.9 for lockstep with npm + the tag (no functional Python changes).
See CHANGELOG for details.
v0.3.8
Non-fatal npm postinstall: a blocked/failed binary download no longer aborts npm install.
install.jsalways exits 0; binary is fetched lazily on first run- Remediation printed:
pipx install graphify-sforexport GRAPHIFY_BIN=/path - New
GRAPHIFY_BINenv escape hatch for air-gapped / proxy-restricted networks - npm package version corrected 0.3.1 -> 0.3.8 (stale version produced a 404 download URL)
- npm regression suite (node:test) + CI job
See CHANGELOG for details.
v0.3.7
Fixed
- macOS: parallel extraction no longer crashes the worker pool and silently truncates the graph. The PyInstaller-frozen binary had no
multiprocessing.freeze_support(), so on macOS/Windows (spawn start method) each pool worker re-executed the binary with Python's bootstrap args (-B --multiprocessing-fork). The CLI parser intercepted them, rejected them (error: unknown command '-B'), and every worker died — leaving only lightweight sequentially-extracted bundles and producing a severely truncated graph (e.g. 572 nodes vs ~9700, with zero CustomObject/CustomField/Flow) while still exiting 0. Linux (fork) was unaffected.freeze_support()now runs first so spawn workers bootstrap correctly. Verified by running the frozen binary under a forcedspawnstart method: full graph restored (9383 nodes, 658 objects, 3239 fields, 166 flows). - A failed worker pool no longer yields a silently-incomplete graph that exits 0. A
BrokenProcessPoolwas previously swallowed; any pool-level failure now aborts parallel, re-runs the full extraction sequentially, and emits a loudWARNING: parallel worker pool failed — falling back to sequential.
Added
GRAPHIFY_SF_MP_STARTenv var to override the multiprocessing start method (spawn/fork).
v0.3.6
Fixed
- Cross-type node merges eliminated.
deduplicate_by_labelno longer conflates distinct components that normalise to the same label (e.g. anApexClassand a similarly-namedLWCComponent, or aCustomObjectand itsCustomTab/Settings/PermissionSet). It wrongly merged 83 node pairs on a real 9k-node org and rewrote their edges onto the wrong survivor. Merging is now gated onsf_type; the intended chunked-duplicate case still works. - Self-referencing lookup fields keep their object→field
containsedge. Previously thecontainsand the field→objectreferencesedge shared an undirected node-pair and one overwrote the other, orphaning the field. The build now keeps the higher-priority relation on collision (containsoutranksreferences). - Together, object→field attachment on the test org went from 99.4% to 100% (18 orphaned fields → 0).
- The post-scan log printed the raw
skippedlist object instead of a count.
Added
.gitignore/.forceignoreare honored by default to cut graph noise, using full gitignore semantics (negation, anchored,**) viapathspec. Ignore files are discovered by walking up from the scanned directory, sographify-sf force-appstill picks up the project-root files.--include-ignoredflag to scan everything regardless, plus an honest per-source skip summary after each scan.
Full multi-relation edge preservation (read+write on the same object, both edges on self-lookups) is the planned 0.4.0 MultiGraph work.
v0.3.5
Fixes the 0.3.4 Apex DML operation feature, which produced zero dml edges on real-world Apex (DML operands are almost always lowercase local variables like insert c;, and the edge guard only accepted capitalized operands). The extractor now resolves the operand variable to its declared SObject type (local declarations, generics, method params) and builds the dml edge to the resolved object, with operation + INFERRED confidence. Unresolvable operands are skipped (no misleading edge); primitives are never DML targets. Known limitation: multiple DML ops against the same object from one component are still merged into one edge in the built graph (per-operation locatable edges planned as a follow-up). See CHANGELOG.md.
v0.3.4
Apex DML edges now preserve the write operation: apex -> object dml edges carry an operation field derived from the DML verb (insert->create, update->update, delete->delete, plus SF-native upsert/merge/undelete preserved). Relation stays dml and confidence stays INFERRED (backward-compatible). Dedup is now by (object, operation). Mirrors the 0.3.3 flow change, so both major write paths (Flow and Apex) now expose read/write operation semantics. See CHANGELOG.md for details.
v0.3.3
Flow record operations now preserve read/write semantics: flow -> object edges carry an operation field (read/create/update/delete) while the relation stays references (backward-compatible). Dedup is now by (object, operation), so a flow that both reads and writes the same object yields distinct edges. See CHANGELOG.md for details.