Developer impact and reachability
Overview
This skill is the developer persona's companion to v2.3's what-if composer family and v2.7's deep code-understanding tools. The developer's first-line question is one of five shapes:
- "What breaks if I change X?" — for a specific component +
change kind, the answer is a structured
WhatIfImpactItem[]list grouped by category (metadata-blocker,code-needs-update,integration-touch,test-class-update,invisible-risk,configuration-only). v2.3 composers. - "Where is method M reachable from?" —
sfi.call_graphwithdirection: 'both'walkscallsApexedges in both directions from a root class. - "What does this method touch downstream?" —
sfi.downstream_effectswalks downstreamcallsApexand surfaces side effects (field writes, async dispatches, emails). - "What tests cover this method?" —
sfi.test_coverage_for_methodwalks upstreamcallsApexfrom the target and filters toisTest === trueclasses. - "Is this code dead / where does this Apex actually run from?"
—
sfi.method_reachabilitywalks upstreamcallsApexagainst the entry-point taxonomy and returns one of three verdicts.
Plus a sixth shape:
- "Does this test actually assert anything?" —
sfi.meaningful_test_auditranks every@isTestclass by fake-assertion count and assertion density.
The boundary that matters for developers: v2.7 ships
CLASS-level granularity. A call from ApexClass:A.foo() to
ApexClass:B.bar() produces ONE A → B callsApex edge with no
method partition. The methodName input parameter on
sfi.test_coverage_for_method is ACCEPTED and ECHOED verbatim into
the response so callers can pipeline through a future v2.7.1
method-scoped resolution — but v2.7 does NOT subset coverage by
method. Surface this verbatim.
sfi.method_reachability, sfi.call_graph and
sfi.test_coverage_for_method carry a soundness envelope
(complete / blindSpots[] / staticCoverage) with two blind-spot
kinds. dynamic-apex: the analyzed class uses dynamic Apex, so a
reflective caller can make a reachability verdict wrong and a
test→method mapping incomplete. unwalked-edge-type: the walk
traversed a strict SUBSET of the usage edge types, so a component
reachable only through an un-walked type is absent from the result —
it carries walkedEdgeTypes and unwalkedEdgeTypes by name. This is
why sfi.call_graph (a deliberate callsApex-only walk) is never
complete: true, while sfi.method_reachability (which walks the
whole usage set) can be. Treat complete: false as "verify by reading
the source", and never present it as a full picture.
The v2.3 composer boundary: the composers project, not
predict. Each what-if composer reads the v2.2-vintage vault state,
applies its own per-tool rule set, and returns a
structured impact list. Runtime evaluation, dataflow analysis,
cross-class transitive analysis, and dynamic Apex are all invisible.
Each finding's confidence is the worst confidence on the walk path
(heuristic < parsed < declared).
When to fire
Fire this skill on what-if / reachability / call-graph phrasing. Concrete triggers:
What-if shape
- "What breaks if I deactivate this Flow?" — Use
sfi.what_if_deactivate_flow. - "What breaks if I disable this trigger?" — Use
sfi.what_if_disable_trigger. - "What breaks if I change
processOrder's signature?" — Usesfi.what_if_change_method_signature. - "What breaks if I change
Industry__cfrom Text to Picklist?" — Usesfi.what_if_change_field_type. - "What breaks if I remove the
Tier 1picklist value?" — Usesfi.what_if_remove_picklist_value. - "What breaks if I make
Industry__crequired?" — Usesfi.what_if_make_field_required. - "What if I merge
Sales_RepandSales_Managerprofiles?" — Usesfi.what_if_merge_profiles. - "How would I split
Sales_Repinto per-team perm sets?" — Usesfi.what_if_split_profile.
Reachability / call-graph shape
- "Where is
OpportunityService.processOppcalled from?" / "What calls this method?" — Usesfi.call_graphwithdirection: 'upstream'. - "What does
processOppcall?" / "What does this method invoke downstream?" — Usesfi.call_graphwithdirection: 'downstream'. - "Map the full call chain around
OpportunityService." — Usesfi.call_graphwithdirection: 'both',maxDepth: 3. - "What side effects does
processOpphave downstream?" — Usesfi.downstream_effects(field writes, async dispatches, email sends). - "What tests cover
OpportunityService?" — Usesfi.test_coverage_for_method. - "Is
OpportunityServicedead code? / Where is this class actually reached from?" — Usesfi.method_reachability.
Test-quality shape
- "Does
OpportunityServiceTestactually assert anything?" / "What tests are fake?" — Usesfi.meaningful_test_audit.
When NOT to fire
Defer to another skill when:
- The user asks "where is
OpportunityServiceused in source code?" That's a code-reference question. Defer todeveloper-apex-refactor→sfi.find_code_usages. - The user asks "what fields does Opportunity have?" Schema
lookup; defer to
answering-org-questions. - The user asks "audit my Apex for quality issues." Defer to
developer-code-quality→sfi.code_quality_audit. - The user asks "what's the convention for status fields?"
Pattern recognition; defer to
recognizing-naming-conventions. - The user wants the full cross-component dependency report
("what depends on this field?"). That's
architect-impact-analysis→sfi.get_impact(BFS over every edge type, not justcallsApex). - The user wants live coverage percentages. v2.7 surfaces test
REACHABILITY, not Apex Test Run's actual coverage percentage.
v2.7 is offline; tell the user to run
sf apex test runfor the runtime numbers. - The user wants the recognizer to WRITE the refactor. v2.3 is read-only; it surfaces impact but never generates the deploy package.
The cascade
The 16 tools split by question shape. Pick the right entry point.
Category A — What-if change projection (v2.3, 11 composers)
Each composer reads the v2.2-vintage vault state, applies its own rule set, and returns:
impacts: WhatIfImpactItem[]— one entry per affected component. (The key isimpacts, NOTfindings; no what-if tool emits afindingsarray.)summary— per-tool aggregations (the profile composers only).disclosure: string— the tool's verbatim boundary paragraph. It is a single string, NOT aboundaries: string[]. Surface it verbatim.
Each WhatIfImpactItem carries category, componentId,
componentType, apiName, confidence and explanation. The
category vocabulary is per tool: the shared six are
metadata-blocker | code-needs-update | integration-touch |
test-class-update | invisible-risk | configuration-only, plus
input-only (a dependency the component CONSUMES — deliberately
excluded from the verdict) and, on what_if_deactivate_flow,
broken-caller (a parent Flow invoking this Flow as a subflow) and
embedded-caller (a FlexiPage whose Flow component embeds this Flow).
Two verdict axes, not one. what_if_deactivate_flow and
what_if_disable_trigger return BOTH structuralVerdict — what the
dependency structure says, in practice safe / review / risky /
blocking — and the headline verdict. The shared Verdict union is
safe | review | risky | blocking | unknown | already-inactive, and
already-inactive is the headline whenever the component does not run
today, whatever the structure says. It is never downgraded by
coverage, and it is deliberately NOT folded into safe: safe means
"no impacts at all", so reusing it would make an inactive-but-heavily-
depended-on component read identically to a genuinely inert one.
runtimeState
{status, currentlyRunning, note} carries the runs-today axis on
its own, and currentlyRunning: null means UNKNOWN — not inactive.
entryPoints[] (where the runtime hands control TO the component) is
reported separately from impacts and is not a dependent.
notProvenHarmless appears exactly when no verdict-bearing impact was
found; render it with the safe.
sfi.what_if_deactivate_flow
Walks every outgoing edge from the Flow: triggersOn (the object
listened to), readsFrom/writesTo (record lookups + DML),
callsApex (Apex action calls), sendsEmail (email templates), and
the subflows THIS Flow invokes (references / referenceKind: 'subflow'). Each becomes a WhatIfImpactItem. R6-02 — the incoming
side: parent Flows that invoke THIS Flow as a subflow are BROKEN
CALLERS on deactivation, surfaced as a distinct broken-caller
category, and FlexiPages whose Flow component EMBEDS this Flow
(references / referenceKind: 'embeddedFlow') are EMBEDDED CALLERS,
surfaced as a distinct embedded-caller category — the embed is an
active invocation at render time, not passive access (this used to be
excluded on the "access, not usage" theory; it no longer is).
structuralVerdict: safe (no verdict-bearing impact) / risky
(callsApex only, or broken callers that are all inactive
Draft/Obsolete) / blocking (record write, trigger, email-send, or
subflow-invocation impact; any broken caller that is an ACTIVE parent
Flow; or ANY embedded-caller — a FlexiPage embed forces blocking
UNCONDITIONALLY, because this vault cannot tell whether that page is
assigned to an active app / profile / record page layout).
triggersOn / listensTo are NOT impacts: they are the Flow's own
entryPoints[]. The headline verdict is already-inactive whenever
the Flow does not run today, whatever the structure says — do not
report "deactivating this changes nothing" from an inactive Flow's
empty impact list; report that it is already off and what still
depends on it. referenceKind: 'subflow' and referenceKind: 'embeddedFlow' are the only incoming edges that count as dependents;
every other incoming references kind, plus grantedBy / parentOf,
stays excluded as passive access/structure.
{ "flowId": "Flow:Set_Opportunity_Owner" }
sfi.what_if_disable_trigger
Similar walk for ApexTrigger:. Handler classes via outgoing
callsApex; async dispatches via outgoing dispatchesAsync. The
callsApex handler edges come from the default-on Apex AST pass, so
most code-needs-update findings carry confidence: parsed; the
dispatchesAsync async edges remain heuristic (that async-dispatch
recognizer is still scanner-based). Cite each finding's own
confidence.
sfi.what_if_change_method_signature
Identifies callers via the default-on Apex AST call-site index (the recall scanner backfills files the AST could not parse). The tool reports each finding at its own edge confidence. Caller types:
- Apex static caller —
OpportunityService.processOpp(...)pattern.code-needs-update;parsedfor AST-resolved call-sites,heuristicfor scanner-backfill edges. - Apex instance caller —
obj.processOpp(...)whereobjis typed asOpportunityService. Same (the AST resolves the receiver type through the symbol table). - Apex test caller — an incoming
callsApexedge from a class whoseproperties.isTestis true.test-class-update;parsed/heuristicas above. The tool ALSO walks acoversTestedge, but that edge has no producer anywhere in the product, so it contributes nothing on a real vault — see the disclosure below. - Flow caller (when target is
@InvocableMethod) —callsApexedge + matching<actionName>.code-needs-update,declared.
Surface the verbatim Q105 disclosure:
Callers are identified via the default-on Apex AST call-site index and surface at
parsedconfidence; the recall scanner backfills files the AST could not parse atheuristic. Cite each caller's own confidence. Dynamic dispatch via Type.forName + invoke is invisible to both. Test classes are identified in ONE way that actually works: an incomingcallsApexedge from a class whoseproperties.isTestis true. ThecoversTestedge this tool ALSO walks is declared in the contract but is emitted by NO extractor, graph-build mint, or enricher in this product, so on a real vault that walk ALWAYS returns nothing — Salesforce does not declare test-to-class coverage anywhere in the metadata source format (coverage is a RUNTIME artifact of a test run, readable only from the Tooling API's ApexCodeCoverage). Read an emptytestClassesNeedingUpdateas "test-coverage mapping UNAVAILABLE for this class", NEVER as "no tests cover this class": a test class that exercises the target only indirectly — through a helper, a trigger, or dynamic dispatch — has nocallsApexedge to it and is invisible here.
sfi.what_if_change_field_type
Driven by a fixed field-type compatibility matrix. For each
transition [c] (forward-compatible), [l] (lossy), or [b]
(breaking), emits findings only for references that are type-
sensitive. Categories typically spread across metadata-blocker
(formulas, validation rules), code-needs-update (Apex, LWC),
integration-touch (integration schemas), configuration-only
(layouts).
sfi.what_if_remove_picklist_value
The most dependency-heavy v2.3 tool. Composes over:
- Dependent picklist detection (
properties.controllingField). - Formula reference detection (re-tokenize each formula source).
- Flow decision detection (v2.0a
firesWhen→ConditionalContext→ search for== 'value'/!= 'value'patterns). - Apex string-literal detection (conjunction: ApexClass has the
literal AND has an incoming
readsFrom/writesToedge to the target field).
The hard dependency on v2.0a: without firesWhen edges and
ConditionalContext extraction, Flow decisions keyed on the value
would be invisible. The composer surfaces an error if the vault
is missing v2.0a extraction.
sfi.what_if_change_field_value
The Data-Steward / Identity lens: what breaks if a field's stored
VALUE changes — NOT its schema (that is change_field_type).
Returns impact buckets (identity / integration-key / uniqueness /
automation / save-pipeline / display), an overall severity, and
recommended pre-change checks. Identity / key / uniqueness verdicts
come from the field's OWN metadata (externalId / unique /
idLookup, identity catalog), so a value change is flagged even on
a field with ZERO references (e.g. a SAML federation key). Derived
fields (formula / roll-up / auto-number) return mutable: false
and re-route to their source. Optional newValue adds a targeted
collision/acceptance check.
Surface verbatim: the vault cannot see external upsert systems, the
IdP side of SSO, or dynamic / managed-package code; automation
buckets surface only declarative value-literal couplings — Apex
literal comparisons remain invisible. For the portfolio version
across many fields on an object, use sfi.value_change_audit.
sfi.what_if_make_field_required
Walks the parent object's write paths:
- Layout coverage — layouts without the field display →
configuration-only. - Flow create coverage — Flow
<recordCreates>without the field in<inputAssignments>→metadata-blocker,parsed. - Integration write coverage —
ExternalService/ExternalDataSourcenot declaring the field →integration-touch,declared. - Apex create coverage — NOT WALKED (deferred to dataflow analysis).
Surface the verbatim Apex-invisible disclosure:
The analysis checks layouts (UI input paths), Flow create paths, and integration write surfaces. Apex
insert acc;sites that may or may not set the field are invisible — determining whetheracc.Industry__cwas assigned before the insert requires dataflow analysis. If your org has Apex create paths, verify the field is set before making required.
sfi.what_if_merge_profiles
Takes exactly two profiles; groups grants by (settingType, settingKey) and emits one WhatIfImpactItem per conflict.
Conflict shape depends on setting type. Default
conflictResolution: 'manual-only' surfaces every conflict with
suggestedResolution: 'manual'. Optional 'max' / 'min' for
starting-point recommendations.
Multi-profile merge (3+) is not supported in v2.3.
sfi.what_if_split_profile
Greedy heuristic: each grant goes to the best-keyword-match perm set (API-name token matching), then domain-cluster fallback, then default target. No backtracking. Optimal partitioning is deferred to a future milestone.
Surface verbatim: greedy + fail-conservative; per-org optimal-partitioning override is not supported.
sfi.what_if_assign_permset
The NET access a user would GAIN by assigning a target permission
set (or PSG). Give the target (permissionSetId — a
PermissionSet: / PermissionSetGroup: id or bare name) and a
baseline container ({ profileId?, permissionSetIds?[] } — the
user's current profile + already-assigned sets). It runs the SAME
effective-permissions engine as sfi.effective_permissions TWICE
(baseline WITH vs WITHOUT the target) and diffs the max-wins,
muting-applied EFFECTIVE grant sets. NET-CHANGE CORRECTNESS is the
whole value: a permission the baseline already holds via its
profile or another set cancels out and is NOT counted as gained.
Delta classes: objectPermissions, fieldPermissions,
systemPermissions, customPermissions, recordTypeVisibilities.
Verdict safe on a no-op, else review.
Surface verbatim: the delta is the NET change under max-wins; this
is a hypothetical READ (nothing is assigned); grants are declared
metadata; object permission is not record access (visibility still
depends on OWD + sharing).
sfi.what_if_revoke_permset
The mirror of assign_permset: the NET access a user would LOSE by
revoking a target set whose baseline SHOULD include it. Same twice-
run effective-permissions diff; a permission ALSO granted by the
profile or another assigned set is NOT counted as lost (the user
keeps it). Revoking a set not in the baseline is a disclosed no-op
(targetInBaseline: false). Verdict safe on a no-op, else
review. Same honesty surface as assign_permset.
Category B — Reachability (v2.7, 5 tools)
sfi.call_graph
BFS over callsApex edges from a root ApexClass / ApexTrigger.
Direction: 'upstream' (incoming — who calls me), 'downstream'
(outgoing — what do I call), or 'both'. Default maxDepth: 3.
{ "rootId": "ApexClass:OpportunityService", "direction": "both", "maxDepth": 3 }
Class-granularity is the v2.7 honesty boundary. State it.
Input key is rootId (or the componentId alias) — it is
REQUIRED. There is no classApiName alias on this tool, unlike
method_reachability / explain_apex_method, so a call naming one is
an invalid-query. maxDepth is bounded at 5; edgeTypes widens
the walk beyond callsApex.
The response carries:
nodes[]— every reached class.edges[]— everycallsApexedge walked.cycleDetected: boolean— whether the BFS observed any back-edge.otherUsageInEdges{count,byType} — ALWAYS present. The incoming usage edges this walk did NOT follow. This is what stopsedges: []reading as "no callers": acountof 0 is a CHECKED zero; a non-zero count names exactly what was left unfollowed. Surface it whenever the call list is empty.soundness—complete: falsewith anunwalked-edge-typeblind spot naming the usage edge types (e.g.references,dispatchesAsync) the defaultcallsApex-only walk skipped.disclosure— the verbatim class-granularity disclosure.
sfi.downstream_effects
Composes sfi.call_graph downstream then, for every reachable
class, lists outgoing writesTo (field-write effects),
dispatchesAsync (async dispatch effects), and sendsEmail
(email effects). Returns DownstreamEffect[] categorized by
edgeType.
Callouts are NOT a separate v2.7 edge type (no Apex.Http.send
extractor yet); the disclosure surfaces this gap explicitly.
sfi.test_coverage_for_method
BFS upstream over callsApex from the target, filtered to nodes
with properties.isTest === true. Returns coveringTestClasses[]
sorted by id.
The methodName parameter is ACCEPTED and echoed verbatim into
the response — but v2.7 does NOT subset coverage by method. The
class-granularity honesty boundary is verbatim. Method-level
granularity is deferred to v2.7.1.
sfi.method_reachability
Walks upstream USAGE edges from the target — every EdgeType
EXCEPT parentOf (structural containment) and grantedBy (an access
grant is not a use). It is a DENY-list, not the ['callsApex']
allow-list it used to be: an allow-list can never learn about an edge
type added after it (dispatchesAsync, the Apex scanner's
references), and it is wrong in the direction that calls live code
dead.
ONE walk does both jobs. It classifies each reached node against the entry-point taxonomy:
ApexTrigger:*(any).ApexClasswithproperties.isRestResource === true.ApexClasswithproperties.hasAuraEnabledMethod === true.ApexClasswithproperties.hasInvocableMethod === true.ApexClasswith any ofproperties.isQueueable/isBatchable/isSchedulable.- the ROOT itself when
properties.isTest === true— the test RUNNER is a test class's entry point. Depth 0 ONLY: a test class UPSTREAM of the root is coverage, not an entry point, sotest-only-reachablesurvives. - a class reached by a
referencesedge from a VisualforcePage / VisualforceComponent / AuraDefinitionBundle — thecontroller=binding, derived from the EDGE rather than a node property. - unproven dynamic registration — a class whose
superclassis dotted (another namespace's framework instantiates its own subclasses) or that declares theCallableinterface. These fire at ANY depth, are floored atheuristicconfidence, and establish only that the class is BUILT to be invoked from outside this vault — never that the registration is live.
The same walk marks reached properties.isTest === true classes as
test coverage. There is no second walk.
Combined verdict:
entry-point-reachable— at least one entry point reaches.test-only-reachable— no entry point reaches but at least one test class does.likely-dead-code— neither reaches within the depth cap.
Surface the response's own disclosure verbatim rather than
paraphrasing it. It states the v2.7 class granularity, the depth-3 BFS
cap, and that dynamic dispatch (Type.forName), reflective invocation
and framework wiring are invisible. Two conditional suffixes matter:
- on
likely-dead-codeit adds that this is NOT the org's dead-code verdict —sfi.find_dead_coderuns an additional whole-word source re-check and may downgrade the same class touncertain. Run it before concluding. - when the ONLY entry points found are unproven registrations it says
so, so a bare
entry-point-reachableis never read as proof.
sfi.meaningful_test_audit
For every isTest === true ApexClass, computes:
assertionCount— invocations ofSystem.assert*per v2.1.fakeAssertionCount—qualityIssues[]entries withrule === 'fake-assertion'.density—assertionCount / max(1, sourceBytes / 1000).
Sorts by fakeAssertionCount DESC, density ASC. Top-of-list test
classes are most likely candidates for a meaningfulness audit.
Verbatim disclosure: the heuristic recognizes direct
System.assert* tokens; helper methods
(MyTestHelper.assertField(record, ...)) and framework wrappers
are invisible.
Honesty axes
v2.7 class-granularity boundary (surface on every reachability response — summarised here; quote the response's own disclosure)
v2.7 ships CLASS-level granularity. A call from
ApexClass:A.foo()toApexClass:B.bar()produces ONEA → BcallsApexedge with no method partition. ThemethodNameinput is accepted and echoed into the response so callers can pipeline through future method-scoped resolution — but v2.7 does NOT subset coverage, reachability, or downstream effects by method. Method-level edge resolution is deferred to v2.7.1.
v2.7 invisible-dispatch boundary (surface on every reachability response — the response's own disclosure string is the authoritative wording; this is a summary of it, not a quotation)
Dynamic dispatch (
Type.forName('...').newInstance().method(...)), reflective invocation, same-namespace framework wiring (TriggerHandler / fflib base classes), and managed-package callers are INVISIBLE to the graph edges these tools walk. A class genuinely invoked at runtime via one of these mechanisms will surface aslikely-dead-codeor with an emptycoveringTestClasses[]. Verify before treating as the answer.
Two dynamic-registration shapes are the exception: a class extending a
base class from ANOTHER namespace (a dotted superclass) and a class
declaring Callable are now recognised as framework-subclass /
callable-dispatch entry points at heuristic confidence. They keep
such a class OFF likely-dead-code and map it to uncertain in
sfi.find_dead_code — but they prove only that the class is BUILT for
outside invocation, never that the registration is live. Do not read
either as "reachable"; read it as "not dead, unproven".
v2.3 confidence-floor rule
For each finding, confidence is the worst confidence on the walk
path from the changing component to the affected component. The
order is: heuristic (worst) < parsed < declared (best).
| Walk path | Confidence |
|---|---|
| Profile FLS, layout placement, XML-declared condition | declared |
| Apex AST field reads/writes + cross-class calls (default-on), formula tokenizer, Flow walker, integration schema parser | parsed |
Apex recall scanner (parse-failure/gap backfill), dispatchesAsync async-dispatch recognizer, LWC/Aura/VF frontend scanner |
heuristic |
State the per-finding confidence explicitly; don't paraphrase
heuristic as "likely".
v2.3 per-tool boundaries (selected verbatim phrases)
| Tool | Verbatim phrase |
|---|---|
what_if_change_field_type |
"Lookup → Text and MasterDetail → Text are structurally compatible but lose foreign-key semantics. Roll-up summary fields, sharing-by-parent, and cascade-delete behavior change." |
what_if_remove_picklist_value |
"Apex variable-based comparisons (if (account.Industry__c == myVar)) are invisible. Dynamic SOQL filters by picklist value are invisible. Reports / Dashboards / List Views filtered by this value are NOT extracted." |
what_if_make_field_required |
"Apex insert acc; sites that may or may not set the field are invisible — dataflow analysis required." |
what_if_deactivate_flow |
"Deactivation does NOT delete the Flow; its definition remains and a later reactivation restores every effect listed. Parent Flows invoking this Flow as a declared <subflows> call ARE modeled as broken callers (an Active parent forces blocking); Lightning pages whose Flow component EMBEDS this Flow (declared embeddedFlow references) ARE modeled as embedded callers and always force blocking. The STILL-invisible path is Apex that invokes the Flow via Flow.Interview or @InvocableMethod chains, plus non-metadata launch points (buttons, quick actions)." |
what_if_disable_trigger |
"Apex code that conditionally invokes the disabled trigger logic via a static utility wrapping the same handler is invisible. Test classes using Test.startTest() / Test.stopTest() semantics may depend on the trigger firing — review test setup before disabling." |
what_if_change_method_signature |
"Callers come from the default-on Apex AST call-site index (parsed), with the recall scanner backfilling parse-failure files (heuristic) — cite each caller's own confidence; dynamic dispatch via Type.forName + invoke is invisible to both. Test classes identified by @isTest + naming convention; non-convention test classes may be missed." |
what_if_merge_profiles |
"Multi-profile (3+) merge is not supported in v2.3 — exactly two profiles per call. Tie-break defaults to A wins for setting types where comparators are undefined." |
what_if_split_profile |
"Greedy heuristic with no backtracking. Optimal partitioning (minimum overlap, maximum coverage) requires graph clustering; deferred to future milestone." |
v2.7 v2.1 inheritance — meaningful_test_audit
@isTestrecognition tied to v1.4 extraction. A test class identified ONLY by naming convention (MyClassTestwith no@isTestannotation) WILL NOT haveisTest: true. The audit scope isproperties.isTest === trueset.- Custom assertion helpers invisible. A test class with all
assertions via a
MyTestHelper.expectField(record, ...)helper will surface withassertionCount: 0and rank near the top of the suspicious list — a false positive. Verify before treating as a real concern.
Worked example
User: "What breaks if I deactivate Flow:Set_Opportunity_Owner?"
Claude's flow:
- Classify → what-if shape, deactivate-Flow.
- Fire
sfi.run_analysiswith{ "name": "sfi.what_if_deactivate_flow", "args": { … } }with{ "flowId": "Flow:Set_Opportunity_Owner" }. - Receive (illustrative):
{
"data": {
"appliedScope": { "component": "Flow:Set_Opportunity_Owner", "mode": "component" },
"flowId": "Flow:Set_Opportunity_Owner",
"apiName": "Set_Opportunity_Owner",
"status": "Active",
"runtimeState": { "status": "Active", "currentlyRunning": true, "note": "…" },
"verdict": "blocking",
"structuralVerdict": "blocking",
"entryPoints": [
{ "kind": "triggersOn", "componentId": "CustomObject:Opportunity", "note": "Where the runtime hands control TO this Flow — not a dependent." }
],
"impacts": [
{ "componentId": "CustomField:Opportunity.Owner", "componentType": "CustomField", "apiName": "Opportunity.Owner", "category": "metadata-blocker", "confidence": "parsed", "explanation": "Flow writes to Owner via recordUpdate element 'assign_owner'." },
{ "componentId": "ApexClass:OwnerAssignmentService", "componentType": "ApexClass", "apiName": "OwnerAssignmentService", "category": "code-needs-update", "confidence": "declared", "explanation": "Flow calls OwnerAssignmentService.assign via Apex action." }
],
"firingConditions": [
{ "conditionContextId": "ConditionalContext:Flow:Set_Opportunity_Owner.condition-0", "expression": "RecordType.DeveloperName == 'Standard_Opportunity' AND Owner == null" }
],
"trust": { "…": "…" },
"disclosure": "v2.3 what-if analysis is composition over the v2.2 vault state. …"
}
}
Keys to hold onto: it is impacts, not findings; disclosure is
ONE string, not a boundaries array; an impact row carries
componentType and explanation (there is no type, location or
suggestedAction); a firing condition carries conditionContextId
and expression and nothing else; and triggersOn sits in
entryPoints, not in impacts.
- Respond:
Deactivating
Flow:Set_Opportunity_Owner— verdict: blocking (structural verdictblocking; the Flow is Active, so it runs today). Two impacts:Metadata blockers (1)
CustomField:Opportunity.Owner— the Flow writes Owner viarecordUpdateelementassign_owner. Records saved without another owner-assignment surface will be created with no Owner. (parsed)Code needs update (1)
ApexClass:OwnerAssignmentService— the Flow callsOwnerAssignmentService.assignvia an Apex action. Deactivating the Flow means this Apex action is no longer invoked from this path; if the class is used elsewhere it continues to function. (declared)Entry point (not an impact)
CustomObject:Opportunity— where the runtime hands control TO this Flow. Deactivation silences that trigger, but the object is not something the Flow breaks.Firing conditions silenced
ConditionalContext:Flow:Set_Opportunity_Owner.condition-0—RecordType.DeveloperName == 'Standard_Opportunity' AND Owner == null. Records matching this condition will no longer be auto-assigned.Suggested next checks:
- Confirm no other automation (Apex Trigger, Workflow Rule) writes Opportunity Owner on save. Run
sfi.find_field_anywhereonCustomField:Opportunity.Ownerfiltered towritesToedges.- Verify
OwnerAssignmentService.assignis not the ONLY caller of downstream Apex via incomingcallsApexedges. Runsfi.run_analysiswith{ "name": "sfi.call_graph", "args": { … } }withdirection: 'downstream', rootId: 'ApexClass:OwnerAssignmentService'.Boundaries — quote the response's
disclosurestring VERBATIM (it is one paragraph, not a list). It states that deactivation does NOT delete the Flow, thatinput-onlyrows are dependencies the Flow consumes rather than downstream effects, thatentryPointsnames where the Flow starts, that an ACTIVE parent Flow's subflow call forcesblocking, and that Apex invoking the Flow viaFlow.Interview/@InvocableMethodchains and non-metadata launch points (quick actions, buttons, screen-flow entry) remain invisible.
The response leads with the blocking verdict, organizes by
category, cites canonical IDs + edge confidence, surfaces the
firing conditions (the actual gating logic the deactivation
silences), suggests concrete next steps, and appends the
verbatim v2.3 disclosure.
Anti-patterns
| Mistake | Why it's wrong |
|---|---|
Subsetting sfi.test_coverage_for_method results by the user-supplied methodName. |
The class-granularity boundary is verbatim: v2.7 does NOT subset by method. The methodName is echoed for caller-pipelining only. Surface every covering test class for the target CLASS, with the verbatim disclosure. |
Treating a likely-dead-code verdict from sfi.method_reachability as "this class is safe to delete." |
Dynamic dispatch, reflective invocation, framework wiring, and managed-package callers are invisible. The verdict means "no usage in-edge and no entry-point classifier within depth-3"; verify with sfi.find_dead_code (adds a whole-word source re-check that can downgrade the class to uncertain) and sfi.find_code_usages before deletion. |
Surfacing a what-if finding without its confidence. |
The confidence is the developer's verification axis. A heuristic finding is a candidate for false positive; a declared finding is metadata-sourced. State both. |
Subsetting sfi.what_if_change_field_type results to only metadata-blocker (hiding configuration-only). |
The configuration-only findings (layouts, perm-set assignments) are the developer's "fix this before deploy" surface even though they won't break the deploy. Surface them; group by category, don't drop. |
Calling sfi.what_if_remove_picklist_value against a vault missing v2.0a extraction. |
The composer surfaces an error in this case (Flow decisions keyed on the value would be invisible without firesWhen edges + ConditionalContext nodes). Recover with the v2.0a-not-extracted message; suggest /sfi-refresh. |
Calling sfi.call_graph with maxDepth: 10. |
maxDepth is bounded at 5, so 10 is a rejected invalid-query, not a deep walk. The class-granularity boundary collapses methods together anyway; long chains rarely surface useful structure. Default maxDepth: 3; raise it to at most 5, and only when the user asks for an exhaustive walk. |
Treating cycleDetected: true in sfi.call_graph as "the architecture is broken." |
A queueable class enqueueing itself is the textbook chunking pattern, NOT a bug. Mention the cycle but distinguish self-enqueue from genuine call-graph cycles. |
Skipping the dynamic-dispatch boundary on sfi.test_coverage_for_method results. |
A class genuinely tested via Type.forName(...) will surface with an empty coveringTestClasses[]. State the boundary even when results are non-empty; the user may have OTHER tests that aren't surfacing. |
Recommending a profile split with sfi.what_if_split_profile as the deployable answer. |
The split tool's greedy heuristic produces a STARTING POINT, not the optimal partition. Surface the per-assignment reason so the developer reviews each grant before committing. |
Treating meaningful_test_audit's fake-assertion ranking as "these tests are bad". |
Custom assertion helpers and framework wrappers are invisible. A class with all assertions via a MyTestHelper.expectField(record, ...) helper ranks at the top of the suspicious list but may be genuinely thorough. Verify by reading the test source. |
See also
developer-apex-refactor— for code-reference questions ("where isOpportunityServiceused in source"). The Apex tier is the default-on parser-grade AST (parsed) plus a heuristic recall scanner; the frontend LWC/Aura/VF tier staysheuristic. What-if tools READ those graph edges as their composition input.developer-code-quality— for quality / hygiene questions over the samequalityIssues[]mirror.sfi.meaningful_test_audithere andsfi.test_coverage_gapsthere share the samefake-assertionrule.architect-impact-analysis— for cross-component impact ("what depends on this field"). v0.2sfi.get_impactwalks every edge type; v2.3 composers narrow to a specific change kind.architect-async-and-events— forsfi.async_chain_depth(transitivedispatchesAsyncwalk). Adjacent tosfi.call_graphbut specialized to async dispatch.developer-field-deep-dive— for v3.0 field synthesis (sfi.field_360,sfi.field_lineage) which COMPOSES this skill's per-method walks.
Verification
Before sending a response, confirm:
- I classified the question into one of the three categories (what-if / reachability / test-quality) and fired the right tool.
- For reachability tools (
call_graph,downstream_effects,test_coverage_for_method,method_reachability,meaningful_test_audit), I surfaced the class-granularity boundary verbatim and stated the dynamic-dispatch invisibility. - For
test_coverage_for_method, I did NOT subset by the user-suppliedmethodName; I echoed the methodName for caller-pipelining and reported coverage at class granularity. - For
method_reachability, I cited the verdict (entry-point-reachable/test-only-reachable/likely-dead-code) and stated which entry points reached the target (or that NO entry point reaches if the verdict islikely-dead-code). - For what-if tools, I grouped findings by
category(metadata-blocker/code-needs-update/integration-touch/test-class-update/invisible-risk/configuration-only) and cited per- findingconfidence. - For
what_if_make_field_required, I surfaced the Apex-create-coverage invisibility verbatim. - For
what_if_change_method_signature, I surfaced the Q105 dynamic-dispatch + test-class-naming-convention disclosure. - For
what_if_remove_picklist_value, I confirmed v2.0a extraction ran (the composer errors otherwise). - For
meaningful_test_audit, I cited the custom-assertion- helper invisibility verbatim. - I did NOT recommend a destructive change (delete the class, remove the picklist value, change the field type) without naming the verification step.
- I cited every canonical id in backticks.
**Grounding
…(truncated)