Answers code-source-focused developer questions across the full org code surface: Apex classes, Apex triggers, and the v1.4 frontend tier (LWC, Aura, Visualforce). Triggers: "where is field X used in Apex", "find LWC components reading this field", "what calls this Apex from VF", "Aura references to this object", "who reads/writes this field from code", "what classes call MyClass.someMethod", "is it safe to rename this method", "find all Apex references to this", "what Apex/LWC/VF touches this object". Call `sfi.run_analysis` with `{ "name": "sfi.find_code_usages", "args": { … } }` (incoming readsFrom/writesTo/callsApex/references edges from ApexClass, ApexTrigger, LightningComponentBundle, AuraDefinitionBundle, VisualforcePage, or VisualforceComponent nodes) — the single code-usage tool. For strictly Apex-only questions, narrow it with `nodeTypes: ['ApexClass','ApexTrigger']` (the former `find_apex_usages` Apex-only view, now folded in). Optionally follows up with `sfi.search_apex_source` to surface the actu
For "where is X used / who references X / what depends on X" — for ANY component
type — call sfi.run_analysis with { "name": "sfi.find_component_usages", "args": { … } }, or the family specialist
(find_code_usages for code, find_field_anywhere for a
field, layout_assignments for a layout). Route by VERB: describe questions
(what is / list / what values) use describe tools, NOT usage tools. Never
improvise a multi-tool fan-out without citing evidence tiers (graph edge
confidence + grep text-match). An empty result is "no static evidence in the
vault" — NEVER "nothing uses this".
Overview
The developer persona has one headline tool, sfi.find_code_usages,
with an Apex-only narrowing MODE.
sfi.find_code_usages (v1.4 broaden) is the default. Given a
targetId, it walks incoming edges of type readsFrom,
writesTo, callsApex, or references whose source node is any
of ApexClass, ApexTrigger, LightningComponentBundle,
AuraDefinitionBundle, VisualforcePage, or VisualforceComponent
and returns each referrer alongside the edge's metadata. It answers
"which source files anywhere in the code surface touch this
component?".
Apex-only narrowing. Pass nodeTypes: ['ApexClass','ApexTrigger']
(and, if desired, edgeTypes: ['readsFrom','writesTo','callsApex'])
to get the Apex-source-only view — the same answer the former
sfi.find_apex_usages tool gave (now folded into this tool as a
hidden back-compat alias). Use when the user's question is explicitly
Apex-scoped ("where is X used in Apex", "refactor Apex method",
"Apex callers only") — the smaller result list keeps the surface
focused.
sfi.find_code_usages answers the developer-persona question — "which
source files touch this component?" — that developers ask before
renaming a field, refactoring a method, or removing dead code.
Edge confidence varies by producer and edge type. Apex edges come
from two producers. The default-on parser-grade Apex AST pass
(source: 'apex-ast', confidence: 'parsed') resolves receivers
through a symbol table and emits most of the field reads/writes and
cross-class calls on a current vault; it is supplemented by a
heuristic recall scanner (source: 'apex-scanner', confidence: 'heuristic') that backfills files the AST could not parse and
patterns it does not resolve. Cite each edge's own confidence — do
not assume Apex means heuristic.
The LWC/Aura/VF scanner emits declared confidence for the
unambiguous cases (LWC @salesforce/apex/Class.method imports, VF
controller=/extensions= attributes) and heuristic confidence
for everything else (LWC field reads, Aura attribute references, VF
expression bindings). The result lists from either tool will include
false positives and miss real references. A clean find means "the
scanner saw these matches; verify before refactoring," not "these are
the only places this is referenced." The Boundary disclosure
section below names what the scanners cannot see.
v1.4 broadens to LWC/Aura/VF
sfi.find_code_usages is the v1.4 broader tool. Use it for any
code-persona question that is not explicitly Apex-scoped — and even
in many Apex-scoped questions, the broad (default) result is the
"right" answer because real refactors care about LWC and VF callers
too. Narrow to nodeTypes: ['ApexClass','ApexTrigger'] only when the
user pins the answer to Apex.
Key confidence rules to cite when reporting v1.4 results:
LWC apex imports are declared. A statement like
import processOpportunity from '@salesforce/apex/OpportunityService.process'
produces a callsApex edge from
LightningComponentBundle:opportunityCard to
ApexClass:OpportunityService at confidence: 'declared' — the
import path is the declaration; there is no inference. Cite as
such.
LWC field reads are heuristic. Per the v1.4 R3a report, the
scanner does not distinguish a schema-import field (e.g.,
import FIELD from '@salesforce/schema/Account.Industry') from a
body-text field reference; both default to heuristic. State the
caveat explicitly when reporting.
Aura field accesses are heuristic. The scanner pattern-matches
Object.Field__c shapes in the markup and the controller/helper
JS; it does not resolve v.{attr} framework attributes (those are
intentionally excluded — see Boundary disclosure).
VF controller / extension references are declared. A
<apex:page controller="OpportunityController" extensions="OppExt1,OppExt2">
produces three declared references edges from the VF page to the
three classes. The attribute IS the declaration.
All VF/Aura field touches and expressions in HTML are
heuristic. Field name strings inside JS literals, dynamic
binding expressions, and attribute-embedded computed names all
default to heuristic — the scanner cannot resolve them
symbolically.
When the user's question is broad ("what touches this from code?"),
default to sfi.find_code_usages and surface the per-tier breakdown.
When the user pins to a single tier ("what LWC components touch
this?"), call sfi.run_analysis with { "name": "sfi.find_code_usages", "args": { … } } with nodeTypes: ['LightningComponentBundle']. Only narrow to nodeTypes: ['ApexClass','ApexTrigger'] when the user is explicit about Apex-only.
When to fire
Fire on developer-flavored phrasing. Concrete triggers:
"Where is X used in Apex…" — "where is Account.Industry__c
used in Apex?", "where do we read/write this field from code?",
"what Apex code touches this object?". → sfi.find_code_usages.
"Where is X used in code…" — "where is Industry__c touched
from the codebase?", "what reads this field anywhere in code?",
"every code reference to MyClass". → sfi.find_code_usages.
"Find LWC / Aura / VF referrers…" — "find LWC components
reading this field", "which LWC bundles call this Apex method?",
"what Aura components reference this object?", "what VF pages
touch this field?", "what Visualforce uses
OpportunityController?". → sfi.find_code_usages with
nodeTypes narrowed (e.g.,
['LightningComponentBundle']).
"Who reads / writes / calls…" — "who reads
Contact.Tier__c?", "what classes write to Industry__c?", "what
classes call OpportunityService.process?", "who invokes
MyClass.run?". → sfi.find_code_usages (default broad answer)
or narrow to nodeTypes: ['ApexClass','ApexTrigger'] if the user
pins to Apex.
"What Apex/code references / touches…" — "find all Apex
references to this field", "what code touches this trigger?",
"what references MyClass?". → broad vs. narrow split as above.
"Is it safe to rename…" — "is it safe to rename this Apex
method?", "can I rename processOpportunity?", "what breaks in
code if I rename this field?". → sfi.find_code_usages is the
safer default because a rename breaks LWC @salesforce/apex
imports and VF controller attributes too, not just Apex callers.
"Refactor / trace…" — "refactor Industry__c across Apex",
"trace this method through the codebase", "show me the call sites
for MyClass.helper". → broad by default; narrow if user pins.
When NOT to fire
Defer to another skill when:
The user asks for full cross-metadata impact ("what breaks if I
change X?", "what depends on this field?", "is it safe to
deprecate?"). Those questions need Flow, layout, validation rule,
and permission edges too — defer to architect-impact-analysis →
sfi.get_impact.
The user asks a schema lookup question ("what fields does
Account have?", "what's the type of Industry__c?"). Defer to
answering-org-questions → sfi.list_components or
sfi.get_component.
The user asks about a naming pattern or convention ("what's our
convention for status fields?"). Defer to
recognizing-naming-conventions →
sfi.get_naming_convention_report.
The user asks where a literal string appears in Apex ("find any
class that mentions Database.upsert"). That's a grep question
for sfi.search_apex_source directly — no graph lookup needed.
The user wants Flow callers ("what flows call this Apex
class?"). sfi.find_code_usages (in any node-type narrowing)
deliberately excludes Flow-emitted callsApex edges; route through
architect-impact-analysis for the full callsApex picture.
Steps
Walk these in order. The pattern is "tool gives the file; grep gives
the line."
Step 1 — Identify the target component ID
Translate the user's reference into a canonical ID. Both tools accept
the same targetId formats — what differs is the referrer type,
not the target. Common targets:
ApexClass:{ClassName} — e.g., ApexClass:OpportunityService. The
v1.4 broadening means LWC @salesforce/apex callers and VF
controller= references will now surface here too.
CustomObject:{ApiName} — e.g., CustomObject:Account (returns
code referrers of the object itself; rare).
LightningComponentBundle:{name},
AuraDefinitionBundle:{name}, VisualforcePage:{Name},
VisualforceComponent:{Name} — when the target IS a frontend
component (e.g., "what references this LWC bundle?").
If the user's phrasing is unambiguous, translate directly. If it
could match multiple components, run sfi.search_components first
and confirm the top match with the user before proceeding.
Apex-only question (narrow nodeTypes for the smaller, Apex-scoped
surface — same parsed-plus-heuristic edges, just filtered to
ApexClass/ApexTrigger referrers; this is the folded-in
find_apex_usages view):
limit — defaults to 50, max 500. Real components rarely have
more than a handful of code referrers; the default is almost always
right. Raise it only when the user asks for an exhaustive sweep.
edgeTypes — defaults to all four (readsFrom, writesTo,
callsApex, references). Narrow only when the user pins the
question ("which classes write to this field?" → ['writesTo']).
An empty array is allowed and returns an empty result.
nodeTypes — defaults to all six code node types. Narrow to a
single tier when the user pins it (['LightningComponentBundle']
for LWC-only, ['VisualforcePage', 'VisualforceComponent'] for VF,
['ApexClass', 'ApexTrigger'] for the Apex-only view — the folded-in
find_apex_usages behavior). An empty array is allowed and returns an
empty result.
The hidden sfi.find_apex_usages back-compat alias still resolves by
exact name (it delegates here with nodeTypes fixed to
['ApexClass','ApexTrigger'] and edgeTypes restricted to the Apex
triad), but it is un-advertised — prefer sfi.find_code_usages with
the narrowing above.
An empty usages list means "the scanner saw no matching
references." It does not mean "no code references this
component" — see the boundary disclosure below.
Step 3 — Group by edgeType and present
Bucket usages by edgeType so the developer sees reads, writes,
and calls separately. Within each bucket, sort by id (the API
already returns the list sorted by (id, edgeType) ASC; preserve
that order).
Every referrer gets its canonical id in backticks. Every edge gets
its own confidence cited explicitly, every time — parsed (with
source: apex-ast) for AST-resolved Apex edges, heuristic (with
source: apex-scanner) for the recall-scanner backfill and for
LWC/Aura/VF field reads, declared for LWC apex imports and VF
controller/extension attributes. Never blanket-label the bucket
heuristic.
Step 4 — Optionally call sfi.run_analysis with { "name": "sfi.search_apex_source", "args": { … } } to surface lines
The scanner gives you the file that referenced the target. To
surface the actual line, follow up with sfi.search_apex_source
using the field or method name as the query:
{ "query": "Industry__c", "limit": 25 }
This is the "tool gives the file; grep gives the line" pattern. Run
the second call when the result list is short and the developer is
likely to act on it (rename, refactor, deprecate). Skip it when
there are many matches and the developer is just scanning the impact
surface.
Reporting format
Worked example. User asks: "Where do we write to
Account.Industry__c from Apex?"
Apex usages of CustomField:Account.Industry__c (per-edge
confidence — most parsed from the default-on Apex AST, one
heuristic scanner-backfill):
Writes (writesTo) — 2 referrers
ApexClass:AccountTriggerHandler — source: apex-ast,
confidence: parsed, properties: { offset: 412, length: 23 }.
The AST resolved the receiver to Account.
ApexClass:AccountReportBuilder — source: apex-scanner,
confidence: heuristic, properties: { offset: 215, length: 18 }.
This file fell back to the recall scanner (AST parse failure), so
the read is a heuristic match on acc.Industry__c with acc
unresolved — spot-check with sfi.search_apex_source({ query: 'Industry__c' }).
The two parsed writes are high-confidence; the one heuristic
read needs verification.
Boundary: the default-on Apex AST pass resolves inline static
SOQL ([SELECT ... WHERE Industry__c ...]) and CONSTANT-string
Database.query('SELECT Industry__c FROM Account') field
references at confidence: 'parsed' — those appear as real edges
above. Still invisible: string-BUILT dynamic SOQL
(Database.query('SELECT ' + f + ...)), dynamic field access
(record.get('Industry__c')), and reflective writes via
SObject.put. Real references may exist that this list misses;
files the AST failed to parse fall back to the regex scanner only
(the manifest's apexAst block counts them).
Group by edgeType with counts. Cite every component by canonical
ID. State each edge's own confidence and source (parsed /
apex-ast for AST-resolved edges, heuristic / apex-scanner for
scanner-backfill edges) — never blanket-label the bucket. Name the
boundary; point the developer at the second-step grep.
Boundary disclosure
The Apex tier runs a parser-grade AST pass by default (source: apex-ast, confidence: parsed) with a heuristic recall scanner
(source: apex-scanner, confidence: heuristic) backfilling files
the AST cannot parse and patterns it does not resolve. The frontend
LWC/Aura/VF tier is a regex scanner — intentionally heuristic for the
common cases, with only the unambiguous declared cases (LWC
@salesforce/apex imports, VF controller/extension attributes) at
confidence: 'declared'. The AST resolves dot-access, cross-class
calls, and inline static SOQL, but it is a parser, not a full
dataflow engine — the residual limits below stay invisible. Surface
this list whenever the developer is about to act on the result.
Apex tier limits (residual, after the default-on AST pass)
Dynamic field access is invisible.record.get('Industry__c')
and SObject.put('Industry__c', value) are string arguments; the
scanner blanks strings before pattern-matching. Zero edges.
Only string-BUILT SOQL is invisible. The default-on AST pass
(source: apex-ast, confidence: 'parsed') fully parses inline
static SOQL in [ ... ] brackets — SELECT, WHERE, ORDER BY, and
GROUP BY fields all yield field-level edges — and also parses
CONSTANT-string Database.query('SELECT ...') literals. What
remains invisible is SOQL assembled from string concatenation or
variables (Database.query('SELECT ' + f + ' FROM ...')). On a
file the AST failed to parse (counted in the manifest apexAst
block), only the regex scanner ran and SOQL field references for
that file are unreliable.
Reflective method dispatch is invisible. A helper like
getFieldValue(record, 'Industry__c') produces no edges; the
field name is a string argument.
Intra-class self-calls produce no cross-class edge. The AST
recognizes a bare doStuff(x) as a self-call, but a class calling
its own method is not a callsApex graph edge (there's no second
class to point at), so it won't surface as a referrer of another
component. Cross-class calls (ClassName.method() and instance
calls on a typed receiver ARE resolved and emit parsed edges.
Receiver resolution is a symbol-table pass, not full type
inference. The default-on AST resolves acc.Industry__c to
CustomField:Account.Industry__c (a parsed edge) when acc is
declared, parameter-typed, or for-each-bound as Account. A
receiver whose type the local symbol table cannot establish (deep
generics, chained returns, untyped sObject) falls to the recall
scanner, which emits a heuristic edge keyed on the variable name
(CustomField:acc.Industry__c) — treat those as approximate.
LWC scanner (v1.4) limits
LWC record[fieldName] dynamic field access is invisible.record[this.fieldName] and record[someVar] are computed
property accesses; the scanner cannot resolve the bracket
expression. Zero edges.
LWC @wire(getRecord, ...) static-field detection works for
fixed lists.@wire(getRecord, { recordId: '$recordId', fields: [INDUSTRY_FIELD] }) resolves INDUSTRY_FIELD when the symbol
comes from an @salesforce/schema/Account.Industry import. If the
fields list is built dynamically (fields: this.fieldsToFetch),
the scanner sees no field reference.
LWC field-import vs. body-text reads are not distinguished.
Per the v1.4 R3a report, both default to confidence: 'heuristic'.
A schema-import field that is only ever imported (not used in the
body) still produces a heuristic edge; consumers cannot use
confidence alone to separate "imported but unused" from "imported
and read."
Aura scanner (v1.4) limits
Aura framework-internal v.{attr} is intentionally NOT
extracted.v.recordId, v.records, and other component-local
attribute references are framework-level state; they do not
correspond to a CustomField or CustomObject and emitting edges
for them would produce massive false-positive noise. The scanner
silently skips them.
Aura dynamic field expressions are invisible.v.record[fieldName]
and {!v.record[expr]} are runtime-evaluated; the scanner does not
resolve them.
Visualforce scanner (v1.4) limits
VF expressions inside HTML attribute strings work. A binding
like <apex:inputField value="{!account.Industry__c}"/> produces a
heuristic readsFrom edge — the {! } shape is recognised inside
attribute strings.
VF expressions inside JS string literals don't. A block like
<script>var x = '{!account.Industry__c}';</script> is a JS
string literal; the scanner blanks JS strings the same way it
blanks Apex strings. Zero edges for that line.
VF controller and extension references are declared. The
controller= and extensions= attributes ARE the declaration;
the edge confidence is declared, not heuristic.
Treat missing edges as "may exist; the static passes couldn't see
them." For present edges, read the confidence: a parsed
(apex-ast) edge is a high-confidence, symbol-resolved match; a
heuristic (apex-scanner or frontend-scanner) edge is a best-effort
pattern match that needs a human pass before any irreversible change.
Heuristic does not mean "approximately correct." Declared edges (LWC
apex imports, VF controller/extension attributes) still need
spot-checks when the referenced class is renamed, but they will not
produce false positives — the import or attribute name IS the
contract.
Anti-patterns
Mistake
Why it's wrong
Blanket-labeling every Apex edge heuristic.
Read the per-edge confidence. Most Apex reads/writes/calls on a current vault are parsed (source: apex-ast) — the AST resolved the receiver. Only the recall-scanner backfill (source: apex-scanner) is heuristic. Cite each edge's own tier; don't demote a parsed edge.
Presenting a heuristic scanner edge as ground truth ("OpportunityService writes to this field").
A heuristic (apex-scanner) edge is a fallback pattern match. Say so: "the scanner saw OpportunityService write to a .Industry__c literal but couldn't resolve the receiver — verify the variable is an Account reference before acting." (A parsedapex-ast edge already resolved the receiver — cite it as high-confidence.)
Omitting the sfi.search_apex_source follow-up on a non-trivial match.
The scanner gives the file; the grep gives the line. A developer who is about to rename a method needs the exact call sites, not just a list of files.
Conflating Apex referrers with Flow referrers.
sfi.find_code_usages excludes Flow-emitted callsApex edges by design (in any node-type narrowing). If the user wants "what calls this Apex from anywhere including Flow," route to architect-impact-analysis → sfi.get_impact.
Narrowing to nodeTypes: ['ApexClass','ApexTrigger'] when the user's question is code-broad.
The Apex-only narrowing returns only ApexClass/ApexTrigger referrers. A rename that breaks an LWC @salesforce/apex import or a VF controller= attribute will not show up. Default to the BROAD sfi.find_code_usages (all node types) for any "where is X used in code" or "is it safe to rename" question; only narrow when the user pins to Apex.
Citing an LWC callsApex edge as heuristic.
LWC's @salesforce/apex/Class.method imports produce confidence: 'declared' edges. Cite them as such — the import path IS the declaration. Same for VF controller= and extensions= attribute references.
Claiming "no code references this" when the result is empty.
An empty list means "the static passes saw nothing" (the parsed AST pass covers dot-access + inline static SOQL + constant-string Database.query; the regex scanners cover the rest). String-BUILT dynamic SOQL, dynamic access, reflective calls, LWC record[fieldName], Aura v.{attr}, and VF JS string expressions are all invisible. Restate the boundary; suggest sfi.search_apex_source to grep raw source.
Going to limit: 500 "to be thorough" by default.
The default 50 is the right call. Raise it only when the user asks for an exhaustive sweep.
Skipping the boundary disclosure because the result list is non-empty.
The disclosure is the developer's protection against false confidence. A non-empty list is not a complete list — the developer needs both halves to make a safe refactor decision.
Example interaction
User: "Is it safe to rename processOpportunity on
OpportunityService? I want to make sure no Apex calls it."
Claude's flow:
Translate → targetId: 'ApexClass:OpportunityService',
edgeTypes: ['callsApex'] (the question pins to call sites),
nodeTypes: ['ApexClass','ApexTrigger'] (pinned to Apex callers).
Callsfi.run_analysis with { "name": "sfi.find_code_usages", "args": { … } } with that input.
Receive (hypothetical): two callsApex referrers.
Callsfi.run_analysis with { "name": "sfi.search_apex_source", "args": { … } } with query: 'OpportunityService.processOpportunity' to surface the lines.
Respond:
Apex call sites for ApexClass:OpportunityService (filtered to
callsApex; both AST-resolved at confidence: parsed):
Both edges are parsed: the AST resolved the OpportunityService
receiver and the processOpportunity call-site. It does not,
however, resolve dynamic dispatch — before renaming, check for
indirect invocation (Type.forName(...).newInstance(), reflective
dispatch), which no static pass sees.
Example interaction — code-broad field rename (v1.4)
User: "I want to rename Account.Industry__c to
Account.IndustrySegment__c. What touches this field anywhere in
code?"
Claude's flow:
Translate → targetId: 'CustomField:Account.Industry__c'. The
question is code-broad ("anywhere in code"), so call the broader
tool with default edges.
Callsfi.run_analysis with { "name": "sfi.find_code_usages", "args": { … } } with { targetId: 'CustomField:Account.Industry__c' }.
Receive (hypothetical): one Apex read, one LWC read via
@wire(getRecord), one VF page binding.
Callsfi.run_analysis with { "name": "sfi.search_apex_source", "args": { … } } with query: 'Industry__c', limit: 25 to surface the Apex lines.
Respond:
Code usages of CustomField:Account.Industry__c across all six
code surfaces (default broad query, no nodeTypes filter):
Reads (readsFrom) — 3 referrers across 3 tiers
ApexClass:AccountReportBuilder — source: apex-ast,
confidence: parsed, properties: { line: 215 }. The AST
resolved acc to Account. High-confidence.
LightningComponentBundle:accountTile — source: lwc-aura-vf-scanner, confidence: heuristic, properties: { wirePath: 'Account.Industry' }. The scanner saw the
schema-import field in the @wire(getRecord, ...) fields list.
VisualforcePage:AccountReport — source: lwc-aura-vf-scanner,
confidence: heuristic, properties: { line: 22 }. The scanner
saw {!account.Industry__c} in an attribute string.
The Apex read is parsed (high-confidence); the two frontend
edges are heuristic. Before renaming:
The LWC @wire(getRecord) schema import must be updated; the
v1.4 scanner could not distinguish schema-import from body-text
reads, so check accountTile/accountTile.js for an
@salesforce/schema/Account.Industry import line.
The VF binding {!account.Industry__c} in
AccountReport.page:22 must be updated by hand.
Inline static SOQL and constant-string Database.query
references ARE covered (parsed-confidence apex-ast edges).
Still invisible: string-BUILT dynamic SOQL, LWC
record[fieldName] dynamic access, Aura v.{attr} framework
attributes, and VF <script> JS string literals. Run
sfi.search_apex_source for Industry__c to catch
concatenated SOQL strings, and do a manual grep across
LWC/Aura/VF source for bracket and template-string references.
Heuristic boundary: the v1.4 LWC scanner cannot distinguish a
schema-import field that is imported-but-unused from one that is
imported-and-read in the template/JS body. The edge may be a
false positive if the bundle imports
@salesforce/schema/Account.Industry but never references the
value. Also invisible: any record[fieldName] dynamic access in
the bundle's controller JS.
Verification
Before sending a response, confirm:
I translated the user's reference into a canonical targetId
(CustomField:Object.Field, ApexClass:Name,
ApexTrigger:Name, LightningComponentBundle:name,
AuraDefinitionBundle:name, VisualforcePage:Name, or
VisualforceComponent:Name), confirming via
sfi.search_components when ambiguous.
I picked the right SCOPE by user intent: broad
sfi.find_code_usages (all node types) for any code-broad
question (default), narrowed to nodeTypes: ['ApexClass','ApexTrigger'] only when the user explicitly pins
to Apex.
When the user pinned to a single code tier (e.g., "what LWC
bundles touch this?"), I narrowed with
nodeTypes: ['LightningComponentBundle'] (or the appropriate
subset) rather than calling the broad tool and post-filtering
by hand.
I called the tool and grouped results by edgeType (and by
code tier when the result spans multiple tiers).
For non-trivial result lists, I followed up with
sfi.search_apex_source to surface the lines.
I cited every referrer by canonical ID and labeled the edges
with their actual per-edge confidence: parsed for AST-resolved
Apex edges (source: apex-ast — the default and dominant case);
heuristic for the Apex recall-scanner backfill and for
LWC/Aura/VF field reads and dynamic expressions; declared for
LWC @salesforce/apex callsApex edges and VF controller/
extension references. I did NOT blanket-label Apex edges
heuristic.
I appended the boundary disclosure relevant to the result
tier — Apex-scanner invisible patterns when Apex referrers are
present; LWC scanner limits (record[fieldName], schema-import
vs. body-read indistinguishability) when LWC referrers are
present; Aura v.{attr} exclusion when Aura referrers are
present; VF JS-string blanking when VF referrers are present.
I did not present a heuristic edge as ground truth, and I did
not claim an empty result means "no references exist."
I did not present a declared edge with a heuristic disclaimer
(LWC apex imports and VF controller/extension attributes do
not need the "scanner may be wrong" caveat).
Grounding & routing (shared contract). For a vague or broad ask, call sfi.route_question first — in the default hybrid mode it returns a meaning-ranked toolCandidates shortlist (which YOU pick from) plus a suggested plane and a route hint (and whether to sfi.resolve a name first). Default tool profile is core: only the core spine (including sfi.live_consent) is directly invokable. For every other sfi.* analysis, call sfi.run_analysis with { "name": "sfi.<tool>", "args": { … } } (or follow route_question.invoke, which already wraps non-core steps). Optional: sfi.describe_analysis first when args are unclear. Every org fact must come from an sfi.* tool call, cited by its canonical id — never from memory. Build the answer only from what the tools returned, then pass it through sfi.synthesize_answer, which flags any hallucinatedIds (canonical ids no tool produced). Full cascade: using-sf-intelligence.
1---2name: developer-apex-refactor3description: Answers code-source-focused developer questions across the full org code surface: Apex classes, Apex triggers, and the v1.4 frontend tier (LWC, Aura, Visualforce). Triggers: "where is field X used in Apex", "find LWC components reading this field", "what calls this Apex from VF", "Aura references to this object", "who reads/writes this field from code", "what classes call MyClass.someMethod", "is it safe to rename this method", "find all Apex references to this", "what Apex/LWC/VF touches this object". Call `sfi.run_analysis` with `{ "name": "sfi.find_code_usages", "args": { … } }` (incoming readsFrom/writesTo/callsApex/references edges from ApexClass, ApexTrigger, LightningComponentBundle, AuraDefinitionBundle, VisualforcePage, or VisualforceComponent nodes) — the single code-usage tool. For strictly Apex-only questions, narrow it with `nodeTypes: ['ApexClass','ApexTrigger']` (the former `find_apex_usages` Apex-only view, now folded in). Optionally follows up with `sfi.search_apex_source` to surface the actu4---56# Developer Apex refactor78## Usage & discovery (§C3 contract)910For "where is X used / who references X / what depends on X" — for ANY component11type — call `sfi.run_analysis` with `{ "name": "sfi.find_component_usages", "args": { … } }`, or the family specialist12(`find_code_usages` for code, `find_field_anywhere` for a13field, `layout_assignments` for a layout). Route by VERB: *describe* questions14(what is / list / what values) use describe tools, NOT usage tools. Never15improvise a multi-tool fan-out without citing evidence tiers (graph edge16`confidence` + grep `text-match`). An empty result is "no static evidence in the17vault" — NEVER "nothing uses this".1819## Overview2021The developer persona has **one** headline tool, `sfi.find_code_usages`,22with an Apex-only narrowing MODE.2324- **`sfi.find_code_usages`** (v1.4 broaden) is the default. Given a25 `targetId`, it walks **incoming** edges of type `readsFrom`,26 `writesTo`, `callsApex`, or `references` whose source node is any27 of `ApexClass`, `ApexTrigger`, `LightningComponentBundle`,28 `AuraDefinitionBundle`, `VisualforcePage`, or `VisualforceComponent`29 and returns each referrer alongside the edge's metadata. It answers30 "which *source files anywhere in the code surface* touch this31 component?".32- **Apex-only narrowing.** Pass `nodeTypes: ['ApexClass','ApexTrigger']`33 (and, if desired, `edgeTypes: ['readsFrom','writesTo','callsApex']`)34 to get the Apex-source-only view — the same answer the former35 `sfi.find_apex_usages` tool gave (now folded into this tool as a36 hidden back-compat alias). Use when the user's question is explicitly37 Apex-scoped ("where is X used **in Apex**", "refactor Apex method",38 "Apex callers only") — the smaller result list keeps the surface39 focused.4041`sfi.find_code_usages` answers the developer-persona question — "which42source files touch this component?" — that developers ask before43renaming a field, refactoring a method, or removing dead code.4445Edge confidence varies by producer and edge type. Apex edges come46from **two** producers. The default-on parser-grade Apex AST pass47(`source: 'apex-ast'`, `confidence: 'parsed'`) resolves receivers48through a symbol table and emits most of the field reads/writes and49cross-class calls on a current vault; it is supplemented by a50heuristic recall scanner (`source: 'apex-scanner'`, `confidence:51'heuristic'`) that backfills files the AST could not parse and52patterns it does not resolve. Cite each edge's own confidence — do53not assume Apex means heuristic.54The LWC/Aura/VF scanner emits **declared** confidence for the55unambiguous cases (LWC `@salesforce/apex/Class.method` imports, VF56`controller=`/`extensions=` attributes) and **heuristic** confidence57for everything else (LWC field reads, Aura attribute references, VF58expression bindings). The result lists from either tool will include59false positives and miss real references. A clean find means "the60scanner saw these matches; verify before refactoring," not "these are61the only places this is referenced." The *Boundary disclosure*62section below names what the scanners cannot see.6364## v1.4 broadens to LWC/Aura/VF6566`sfi.find_code_usages` is the v1.4 broader tool. Use it for any67code-persona question that is not explicitly Apex-scoped — and even68in many Apex-scoped questions, the broad (default) result is the69"right" answer because real refactors care about LWC and VF callers70too. Narrow to `nodeTypes: ['ApexClass','ApexTrigger']` only when the71user pins the answer to Apex.7273Key confidence rules to cite when reporting v1.4 results:7475- **LWC apex imports are `declared`.** A statement like76 `import processOpportunity from '@salesforce/apex/OpportunityService.process'`77 produces a `callsApex` edge from78 `LightningComponentBundle:opportunityCard` to79 `ApexClass:OpportunityService` at `confidence: 'declared'` — the80 import path is the declaration; there is no inference. Cite as81 such.82- **LWC field reads are `heuristic`.** Per the v1.4 R3a report, the83 scanner does not distinguish a schema-import field (e.g.,84 `import FIELD from '@salesforce/schema/Account.Industry'`) from a85 body-text field reference; both default to `heuristic`. State the86 caveat explicitly when reporting.87- **Aura field accesses are `heuristic`.** The scanner pattern-matches88 `Object.Field__c` shapes in the markup and the controller/helper89 JS; it does not resolve `v.{attr}` framework attributes (those are90 intentionally excluded — see *Boundary disclosure*).91- **VF controller / extension references are `declared`.** A92 `<apex:page controller="OpportunityController" extensions="OppExt1,OppExt2">`93 produces three declared `references` edges from the VF page to the94 three classes. The attribute IS the declaration.95- **All VF/Aura field touches and expressions in HTML are96 `heuristic`.** Field name strings inside JS literals, dynamic97 binding expressions, and attribute-embedded computed names all98 default to heuristic — the scanner cannot resolve them99 symbolically.100101When the user's question is broad ("what touches this from code?"),102default to `sfi.find_code_usages` and surface the per-tier breakdown.103When the user pins to a single tier ("what *LWC* components touch104this?"), call `sfi.run_analysis` with `{ "name": "sfi.find_code_usages", "args": { … } }` with `nodeTypes:105['LightningComponentBundle']`. Only narrow to `nodeTypes:106['ApexClass','ApexTrigger']` when the user is explicit about Apex-only.107108## When to fire109110Fire on developer-flavored phrasing. Concrete triggers:111112- **"Where is X used in Apex…"** — "where is `Account.Industry__c`113 used in Apex?", "where do we read/write this field from code?",114 "what Apex code touches this object?". → `sfi.find_code_usages`.115- **"Where is X used in code…"** — "where is `Industry__c` touched116 from the codebase?", "what reads this field anywhere in code?",117 "every code reference to `MyClass`". → `sfi.find_code_usages`.118- **"Find LWC / Aura / VF referrers…"** — "find LWC components119 reading this field", "which LWC bundles call this Apex method?",120 "what Aura components reference this object?", "what VF pages121 touch this field?", "what Visualforce uses122 `OpportunityController`?". → `sfi.find_code_usages` with123 `nodeTypes` narrowed (e.g.,124 `['LightningComponentBundle']`).125- **"Who reads / writes / calls…"** — "who reads126 `Contact.Tier__c`?", "what classes write to `Industry__c`?", "what127 classes call `OpportunityService.process`?", "who invokes128 `MyClass.run`?". → `sfi.find_code_usages` (default broad answer)129 or narrow to `nodeTypes: ['ApexClass','ApexTrigger']` if the user130 pins to Apex.131- **"What Apex/code references / touches…"** — "find all Apex132 references to this field", "what code touches this trigger?",133 "what references `MyClass`?". → broad vs. narrow split as above.134- **"Is it safe to rename…"** — "is it safe to rename this Apex135 method?", "can I rename `processOpportunity`?", "what breaks in136 code if I rename this field?". → `sfi.find_code_usages` is the137 safer default because a rename breaks LWC `@salesforce/apex`138 imports and VF controller attributes too, not just Apex callers.139- **"Refactor / trace…"** — "refactor `Industry__c` across Apex",140 "trace this method through the codebase", "show me the call sites141 for `MyClass.helper`". → broad by default; narrow if user pins.142143## When NOT to fire144145Defer to another skill when:146147- **The user asks for full cross-metadata impact** ("what breaks if I148 change X?", "what depends on this field?", "is it safe to149 deprecate?"). Those questions need Flow, layout, validation rule,150 and permission edges too — defer to `architect-impact-analysis` →151 `sfi.get_impact`.152- **The user asks a schema lookup question** ("what fields does153 Account have?", "what's the type of `Industry__c`?"). Defer to154 `answering-org-questions` → `sfi.list_components` or155 `sfi.get_component`.156- **The user asks about a naming pattern or convention** ("what's our157 convention for status fields?"). Defer to158 `recognizing-naming-conventions` →159 `sfi.get_naming_convention_report`.160- **The user asks where a literal string appears in Apex** ("find any161 class that mentions `Database.upsert`"). That's a grep question162 for `sfi.search_apex_source` directly — no graph lookup needed.163- **The user wants Flow callers** ("what flows call this Apex164 class?"). `sfi.find_code_usages` (in any node-type narrowing)165 deliberately excludes Flow-emitted `callsApex` edges; route through166 `architect-impact-analysis` for the full `callsApex` picture.167168## Steps169170Walk these in order. The pattern is "tool gives the file; grep gives171the line."172173### Step 1 — Identify the target component ID174175Translate the user's reference into a canonical ID. Both tools accept176the same `targetId` formats — what differs is the *referrer* type,177not the target. Common targets:178179- `CustomField:{Object}.{Field}` — e.g.,180 `CustomField:Account.Industry__c`.181- `ApexClass:{ClassName}` — e.g., `ApexClass:OpportunityService`. The182 v1.4 broadening means LWC `@salesforce/apex` callers and VF183 `controller=` references will now surface here too.184- `ApexTrigger:{TriggerName}` — e.g., `ApexTrigger:AccountTrigger`.185- `CustomObject:{ApiName}` — e.g., `CustomObject:Account` (returns186 code referrers of the object itself; rare).187- `LightningComponentBundle:{name}`,188 `AuraDefinitionBundle:{name}`, `VisualforcePage:{Name}`,189 `VisualforceComponent:{Name}` — when the target IS a frontend190 component (e.g., "what references this LWC bundle?").191192If the user's phrasing is unambiguous, translate directly. If it193could match multiple components, run `sfi.search_components` first194and confirm the top match with the user before proceeding.195196### Step 2 — Call `sfi.run_analysis` with `{ "name": "sfi.find_code_usages", "args": { … } }`197198Default invocation (broad — the v1.4 tool):199200```json201{ "targetId": "CustomField:Account.Industry__c" }202```203204LWC-only narrowing:205206```json207{208 "targetId": "CustomField:Account.Industry__c",209 "nodeTypes": ["LightningComponentBundle"]210}211```212213Apex-only question (narrow `nodeTypes` for the smaller, Apex-scoped214surface — same parsed-plus-heuristic edges, just filtered to215ApexClass/ApexTrigger referrers; this is the folded-in216`find_apex_usages` view):217218```json219{220 "targetId": "CustomField:Account.Industry__c",221 "nodeTypes": ["ApexClass", "ApexTrigger"]222}223```224225Optional parameters (`sfi.find_code_usages`):226227- **`limit`** — defaults to 50, max 500. Real components rarely have228 more than a handful of code referrers; the default is almost always229 right. Raise it only when the user asks for an exhaustive sweep.230- **`edgeTypes`** — defaults to all four (`readsFrom`, `writesTo`,231 `callsApex`, `references`). Narrow only when the user pins the232 question ("which classes *write* to this field?" → `['writesTo']`).233 An empty array is allowed and returns an empty result.234- **`nodeTypes`** — defaults to all six code node types. Narrow to a235 single tier when the user pins it (`['LightningComponentBundle']`236 for LWC-only, `['VisualforcePage', 'VisualforceComponent']` for VF,237 `['ApexClass', 'ApexTrigger']` for the Apex-only view — the folded-in238 `find_apex_usages` behavior). An empty array is allowed and returns an239 empty result.240241The hidden `sfi.find_apex_usages` back-compat alias still resolves by242exact name (it delegates here with `nodeTypes` fixed to243`['ApexClass','ApexTrigger']` and `edgeTypes` restricted to the Apex244triad), but it is un-advertised — prefer `sfi.find_code_usages` with245the narrowing above.246247The response shape:248249```json250{251 "data": {252 "usages": [253 {254 "id": "ApexClass:OpportunityService",255 "type": "ApexClass",256 "apiName": "OpportunityService",257 "edgeType": "readsFrom",258 "source": "apex-ast",259 "confidence": "parsed",260 "properties": { "offset": 412, "length": 18 }261 },262 {263 "id": "LightningComponentBundle:accountTile",264 "type": "LightningComponentBundle",265 "apiName": "accountTile",266 "edgeType": "readsFrom",267 "source": "lwc-aura-vf-scanner",268 "confidence": "heuristic",269 "properties": { "wirePath": "Account.Industry" }270 }271 ]272 }273}274```275276An empty `usages` list means "the scanner saw no matching277references." It does **not** mean "no code references this278component" — see the boundary disclosure below.279280### Step 3 — Group by `edgeType` and present281282Bucket `usages` by `edgeType` so the developer sees reads, writes,283and calls separately. Within each bucket, sort by `id` (the API284already returns the list sorted by `(id, edgeType)` ASC; preserve285that order).286287Every referrer gets its canonical `id` in backticks. Every edge gets288its own `confidence` cited explicitly, every time — `parsed` (with289`source: apex-ast`) for AST-resolved Apex edges, `heuristic` (with290`source: apex-scanner`) for the recall-scanner backfill and for291LWC/Aura/VF field reads, `declared` for LWC apex imports and VF292controller/extension attributes. Never blanket-label the bucket293`heuristic`.294295### Step 4 — Optionally call `sfi.run_analysis` with `{ "name": "sfi.search_apex_source", "args": { … } }` to surface lines296297The scanner gives you the *file* that referenced the target. To298surface the actual line, follow up with `sfi.search_apex_source`299using the field or method name as the query:300301```json302{ "query": "Industry__c", "limit": 25 }303```304305This is the "tool gives the file; grep gives the line" pattern. Run306the second call when the result list is short and the developer is307likely to act on it (rename, refactor, deprecate). Skip it when308there are many matches and the developer is just scanning the impact309surface.310311## Reporting format312313Worked example. User asks: *"Where do we write to314`Account.Industry__c` from Apex?"*315316> Apex usages of `CustomField:Account.Industry__c` (per-edge317> confidence — most `parsed` from the default-on Apex AST, one318> `heuristic` scanner-backfill):319>320> **Writes (writesTo) — 2 referrers**321> - `ApexClass:AccountTriggerHandler` — `source: apex-ast`,322> `confidence: parsed`, `properties: { offset: 412, length: 23 }`.323> The AST resolved the receiver to `Account`.324> - `ApexClass:OpportunityService` — `source: apex-ast`,325> `confidence: parsed`, `properties: { offset: 1287, length: 19 }`.326>327> **Reads (readsFrom) — 1 referrer**328> - `ApexClass:AccountReportBuilder` — `source: apex-scanner`,329> `confidence: heuristic`, `properties: { offset: 215, length: 18 }`.330> This file fell back to the recall scanner (AST parse failure), so331> the read is a heuristic match on `acc.Industry__c` with `acc`332> unresolved — spot-check with `sfi.search_apex_source({ query:333> 'Industry__c' })`.334>335> The two `parsed` writes are high-confidence; the one `heuristic`336> read needs verification.337>338> Boundary: the default-on Apex AST pass resolves inline static339> SOQL (`[SELECT ... WHERE Industry__c ...]`) and CONSTANT-string340> `Database.query('SELECT Industry__c FROM Account')` field341> references at `confidence: 'parsed'` — those appear as real edges342> above. Still invisible: string-BUILT dynamic SOQL343> (`Database.query('SELECT ' + f + ...)`), dynamic field access344> (`record.get('Industry__c')`), and reflective writes via345> `SObject.put`. Real references may exist that this list misses;346> files the AST failed to parse fall back to the regex scanner only347> (the manifest's `apexAst` block counts them).348349Group by `edgeType` with counts. Cite every component by canonical350ID. State each edge's own `confidence` and `source` (`parsed` /351`apex-ast` for AST-resolved edges, `heuristic` / `apex-scanner` for352scanner-backfill edges) — never blanket-label the bucket. Name the353boundary; point the developer at the second-step grep.354355## Boundary disclosure356357The Apex tier runs a parser-grade AST pass by default (`source:358apex-ast`, `confidence: parsed`) with a heuristic recall scanner359(`source: apex-scanner`, `confidence: heuristic`) backfilling files360the AST cannot parse and patterns it does not resolve. The frontend361LWC/Aura/VF tier is a regex scanner — intentionally heuristic for the362common cases, with only the unambiguous declared cases (LWC363`@salesforce/apex` imports, VF controller/extension attributes) at364`confidence: 'declared'`. The AST resolves dot-access, cross-class365calls, and inline static SOQL, but it is a parser, not a full366dataflow engine — the residual limits below stay invisible. Surface367this list whenever the developer is about to act on the result.368369### Apex tier limits (residual, after the default-on AST pass)370371- **Dynamic field access is invisible.** `record.get('Industry__c')`372 and `SObject.put('Industry__c', value)` are string arguments; the373 scanner blanks strings before pattern-matching. Zero edges.374- **Only string-BUILT SOQL is invisible.** The default-on AST pass375 (source: `apex-ast`, `confidence: 'parsed'`) fully parses inline376 static SOQL in `[ ... ]` brackets — SELECT, WHERE, ORDER BY, and377 GROUP BY fields all yield field-level edges — and also parses378 CONSTANT-string `Database.query('SELECT ...')` literals. What379 remains invisible is SOQL assembled from string concatenation or380 variables (`Database.query('SELECT ' + f + ' FROM ...')`). On a381 file the AST failed to parse (counted in the manifest `apexAst`382 block), only the regex scanner ran and SOQL field references for383 that file are unreliable.384- **Reflective method dispatch is invisible.** A helper like385 `getFieldValue(record, 'Industry__c')` produces no edges; the386 field name is a string argument.387- **Intra-class self-calls produce no cross-class edge.** The AST388 recognizes a bare `doStuff(x)` as a self-call, but a class calling389 its own method is not a `callsApex` graph edge (there's no second390 class to point at), so it won't surface as a referrer of another391 component. Cross-class calls (`ClassName.method(`) and instance392 calls on a typed receiver ARE resolved and emit `parsed` edges.393- **Receiver resolution is a symbol-table pass, not full type394 inference.** The default-on AST resolves `acc.Industry__c` to395 `CustomField:Account.Industry__c` (a `parsed` edge) when `acc` is396 declared, parameter-typed, or for-each-bound as `Account`. A397 receiver whose type the local symbol table cannot establish (deep398 generics, chained returns, untyped `sObject`) falls to the recall399 scanner, which emits a `heuristic` edge keyed on the variable name400 (`CustomField:acc.Industry__c`) — treat those as approximate.401402### LWC scanner (v1.4) limits403404- **LWC `record[fieldName]` dynamic field access is invisible.**405 `record[this.fieldName]` and `record[someVar]` are computed406 property accesses; the scanner cannot resolve the bracket407 expression. Zero edges.408- **LWC `@wire(getRecord, ...)` static-field detection works for409 fixed lists.** `@wire(getRecord, { recordId: '$recordId', fields:410 [INDUSTRY_FIELD] })` resolves `INDUSTRY_FIELD` when the symbol411 comes from an `@salesforce/schema/Account.Industry` import. If the412 fields list is built dynamically (`fields: this.fieldsToFetch`),413 the scanner sees no field reference.414- **LWC field-import vs. body-text reads are not distinguished.**415 Per the v1.4 R3a report, both default to `confidence: 'heuristic'`.416 A schema-import field that is *only ever imported* (not used in the417 body) still produces a heuristic edge; consumers cannot use418 confidence alone to separate "imported but unused" from "imported419 and read."420421### Aura scanner (v1.4) limits422423- **Aura framework-internal `v.{attr}` is intentionally NOT424 extracted.** `v.recordId`, `v.records`, and other component-local425 attribute references are framework-level state; they do not426 correspond to a `CustomField` or `CustomObject` and emitting edges427 for them would produce massive false-positive noise. The scanner428 silently skips them.429- **Aura dynamic field expressions are invisible.** `v.record[fieldName]`430 and `{!v.record[expr]}` are runtime-evaluated; the scanner does not431 resolve them.432433### Visualforce scanner (v1.4) limits434435- **VF expressions inside HTML attribute strings work.** A binding436 like `<apex:inputField value="{!account.Industry__c}"/>` produces a437 heuristic `readsFrom` edge — the `{! }` shape is recognised inside438 attribute strings.439- **VF expressions inside JS string literals don't.** A block like440 `<script>var x = '{!account.Industry__c}';</script>` is a JS441 string literal; the scanner blanks JS strings the same way it442 blanks Apex strings. Zero edges for that line.443- **VF controller and extension references are `declared`.** The444 `controller=` and `extensions=` attributes ARE the declaration;445 the edge confidence is declared, not heuristic.446447Treat **missing** edges as "may exist; the static passes couldn't see448them." For **present** edges, read the confidence: a `parsed`449(`apex-ast`) edge is a high-confidence, symbol-resolved match; a450`heuristic` (`apex-scanner` or frontend-scanner) edge is a best-effort451pattern match that needs a human pass before any irreversible change.452Heuristic does not mean "approximately correct." Declared edges (LWC453apex imports, VF controller/extension attributes) still need454spot-checks when the referenced class is renamed, but they will not455produce false positives — the import or attribute name IS the456contract.457458## Anti-patterns459460| Mistake | Why it's wrong |461|---|---|462| Blanket-labeling every Apex edge `heuristic`. | Read the per-edge confidence. Most Apex reads/writes/calls on a current vault are `parsed` (`source: apex-ast`) — the AST resolved the receiver. Only the recall-scanner backfill (`source: apex-scanner`) is `heuristic`. Cite each edge's own tier; don't demote a `parsed` edge. |463| Presenting a `heuristic` scanner edge as ground truth ("`OpportunityService` writes to this field"). | A `heuristic` (`apex-scanner`) edge is a fallback pattern match. Say so: "the scanner saw `OpportunityService` write to a `.Industry__c` literal but couldn't resolve the receiver — verify the variable is an `Account` reference before acting." (A `parsed` `apex-ast` edge already resolved the receiver — cite it as high-confidence.) |464| Omitting the `sfi.search_apex_source` follow-up on a non-trivial match. | The scanner gives the file; the grep gives the line. A developer who is about to rename a method needs the exact call sites, not just a list of files. |465| Conflating Apex referrers with Flow referrers. | `sfi.find_code_usages` excludes Flow-emitted `callsApex` edges by design (in any node-type narrowing). If the user wants "what calls this Apex from anywhere including Flow," route to `architect-impact-analysis` → `sfi.get_impact`. |466| Narrowing to `nodeTypes: ['ApexClass','ApexTrigger']` when the user's question is code-broad. | The Apex-only narrowing returns only ApexClass/ApexTrigger referrers. A rename that breaks an LWC `@salesforce/apex` import or a VF `controller=` attribute will not show up. Default to the BROAD `sfi.find_code_usages` (all node types) for any "where is X used in code" or "is it safe to rename" question; only narrow when the user pins to Apex. |467| Citing an LWC `callsApex` edge as `heuristic`. | LWC's `@salesforce/apex/Class.method` imports produce `confidence: 'declared'` edges. Cite them as such — the import path IS the declaration. Same for VF `controller=` and `extensions=` attribute references. |468| Claiming "no code references this" when the result is empty. | An empty list means "the static passes saw nothing" (the parsed AST pass covers dot-access + inline static SOQL + constant-string `Database.query`; the regex scanners cover the rest). String-BUILT dynamic SOQL, dynamic access, reflective calls, LWC `record[fieldName]`, Aura `v.{attr}`, and VF JS string expressions are all invisible. Restate the boundary; suggest `sfi.search_apex_source` to grep raw source. |469| Going to `limit: 500` "to be thorough" by default. | The default 50 is the right call. Raise it only when the user asks for an exhaustive sweep. |470| Skipping the boundary disclosure because the result list is non-empty. | The disclosure is the developer's protection against false confidence. A non-empty list is not a complete list — the developer needs both halves to make a safe refactor decision. |471472## Example interaction473474User: *"Is it safe to rename `processOpportunity` on475`OpportunityService`? I want to make sure no Apex calls it."*476477Claude's flow:4784791. **Translate** → `targetId: 'ApexClass:OpportunityService'`,480 `edgeTypes: ['callsApex']` (the question pins to call sites),481 `nodeTypes: ['ApexClass','ApexTrigger']` (pinned to Apex callers).4822. **Call** `sfi.run_analysis` with `{ "name": "sfi.find_code_usages", "args": { … } }` with that input.4833. **Receive** (hypothetical): two `callsApex` referrers.4844. **Call** `sfi.run_analysis` with `{ "name": "sfi.search_apex_source", "args": { … } }` with `query:485 'OpportunityService.processOpportunity'` to surface the lines.4865. **Respond:**487488> Apex call sites for `ApexClass:OpportunityService` (filtered to489> `callsApex`; both AST-resolved at `confidence: parsed`):490>491> **Calls (callsApex) — 2 referrers**492> - `ApexClass:OpportunityController` — `source: apex-ast`,493> `confidence: parsed`, `properties.methodName: 'processOpportunity'`.494> - `ApexTrigger:OpportunityTrigger` — `source: apex-ast`,495> `confidence: parsed`, `properties.methodName: 'processOpportunity'`.496>497> Grep follow-up (`sfi.search_apex_source`, query498> `OpportunityService.processOpportunity`):499> - `source/classes/OpportunityController.cls:42` —500> `OpportunityService.processOpportunity(opp);`501> - `source/triggers/OpportunityTrigger.trigger:8` —502> `OpportunityService.processOpportunity(Trigger.new);`503>504> Both edges are `parsed`: the AST resolved the `OpportunityService`505> receiver and the `processOpportunity` call-site. It does not,506> however, resolve dynamic dispatch — before renaming, check for507> indirect invocation (`Type.forName(...).newInstance()`, reflective508> dispatch), which no static pass sees.509510### Example interaction — code-broad field rename (v1.4)511512User: *"I want to rename `Account.Industry__c` to513`Account.IndustrySegment__c`. What touches this field anywhere in514code?"*515516Claude's flow:5175181. **Translate** → `targetId: 'CustomField:Account.Industry__c'`. The519 question is code-broad ("anywhere in code"), so call the broader520 tool with default edges.5212. **Call** `sfi.run_analysis` with `{ "name": "sfi.find_code_usages", "args": { … } }` with `{ targetId:522 'CustomField:Account.Industry__c' }`.5233. **Receive** (hypothetical): one Apex read, one LWC read via524 `@wire(getRecord)`, one VF page binding.5254. **Call** `sfi.run_analysis` with `{ "name": "sfi.search_apex_source", "args": { … } }` with `query: 'Industry__c',526 limit: 25` to surface the Apex lines.5275. **Respond:**528529> Code usages of `CustomField:Account.Industry__c` across all six530> code surfaces (default broad query, no `nodeTypes` filter):531>532> **Reads (readsFrom) — 3 referrers across 3 tiers**533> - `ApexClass:AccountReportBuilder` — `source: apex-ast`,534> `confidence: parsed`, `properties: { line: 215 }`. The AST535> resolved `acc` to Account. High-confidence.536> - `LightningComponentBundle:accountTile` — `source:537> lwc-aura-vf-scanner`, `confidence: heuristic`, `properties: {538> wirePath: 'Account.Industry' }`. The scanner saw the539> schema-import field in the `@wire(getRecord, ...)` fields list.540> - `VisualforcePage:AccountReport` — `source: lwc-aura-vf-scanner`,541> `confidence: heuristic`, `properties: { line: 22 }`. The scanner542> saw `{!account.Industry__c}` in an attribute string.543>544> Grep follow-up (`sfi.search_apex_source`, query `Industry__c`):545> - `source/classes/AccountReportBuilder.cls:215` —546> `report.industry = acc.Industry__c;`547>548> The Apex read is `parsed` (high-confidence); the two frontend549> edges are `heuristic`. Before renaming:550> - The LWC `@wire(getRecord)` schema import must be updated; the551> v1.4 scanner could not distinguish schema-import from body-text552> reads, so check `accountTile/accountTile.js` for an553> `@salesforce/schema/Account.Industry` import line.554> - The VF binding `{!account.Industry__c}` in555> `AccountReport.page:22` must be updated by hand.556> - Inline static SOQL and constant-string `Database.query`557> references ARE covered (parsed-confidence `apex-ast` edges).558> Still invisible: string-BUILT dynamic SOQL, LWC559> `record[fieldName]` dynamic access, Aura `v.{attr}` framework560> attributes, and VF `<script>` JS string literals. Run561> `sfi.search_apex_source` for `Industry__c` to catch562> concatenated SOQL strings, and do a manual grep across563> LWC/Aura/VF source for bracket and template-string references.564565### Example interaction — LWC-only narrowing (v1.4)566567User: *"Which LWC components read `Account.Industry__c`?"*568569Claude's flow:5705711. **Translate** → `targetId: 'CustomField:Account.Industry__c'`,572 `nodeTypes: ['LightningComponentBundle']`, `edgeTypes:573 ['readsFrom']` (the question pins both axes).5742. **Call** `sfi.run_analysis` with `{ "name": "sfi.find_code_usages", "args": { … } }` with that input.5753. **Receive** (hypothetical): one LWC bundle.5764. **Respond:**577578> LWC bundles reading `CustomField:Account.Industry__c` (v1.4579> scanner, `confidence: heuristic`, filtered to580> `nodeTypes: ['LightningComponentBundle']` and581> `edgeTypes: ['readsFrom']`):582>583> **Reads (readsFrom) — 1 referrer**584> - `LightningComponentBundle:accountTile` — `source:585> lwc-aura-vf-scanner`, `confidence: heuristic`, `properties: {586> wirePath: 'Account.Industry' }`.587>588> Heuristic boundary: the v1.4 LWC scanner cannot distinguish a589> schema-import field that is imported-but-unused from one that is590> imported-and-read in the template/JS body. The edge may be a591> false positive if the bundle imports592> `@salesforce/schema/Account.Industry` but never references the593> value. Also invisible: any `record[fieldName]` dynamic access in594> the bundle's controller JS.595596## Verification597598Before sending a response, confirm:599600- [ ] I translated the user's reference into a canonical `targetId`601 (`CustomField:Object.Field`, `ApexClass:Name`,602 `ApexTrigger:Name`, `LightningComponentBundle:name`,603 `AuraDefinitionBundle:name`, `VisualforcePage:Name`, or604 `VisualforceComponent:Name`), confirming via605 `sfi.search_components` when ambiguous.606- [ ] I picked the right SCOPE by user intent: broad607 `sfi.find_code_usages` (all node types) for any code-broad608 question (default), narrowed to `nodeTypes:609 ['ApexClass','ApexTrigger']` only when the user explicitly pins610 to Apex.611- [ ] When the user pinned to a single code tier (e.g., "what LWC612 bundles touch this?"), I narrowed with613 `nodeTypes: ['LightningComponentBundle']` (or the appropriate614 subset) rather than calling the broad tool and post-filtering615 by hand.616- [ ] I called the tool and grouped results by `edgeType` (and by617 code tier when the result spans multiple tiers).618- [ ] For non-trivial result lists, I followed up with619 `sfi.search_apex_source` to surface the lines.620- [ ] I cited every referrer by canonical ID and labeled the edges621 with their actual per-edge confidence: `parsed` for AST-resolved622 Apex edges (`source: apex-ast` — the default and dominant case);623 `heuristic` for the Apex recall-scanner backfill and for624 LWC/Aura/VF field reads and dynamic expressions; `declared` for625 LWC `@salesforce/apex` callsApex edges and VF controller/626 extension references. I did NOT blanket-label Apex edges627 `heuristic`.628- [ ] I appended the boundary disclosure relevant to the result629 tier — Apex-scanner invisible patterns when Apex referrers are630 present; LWC scanner limits (`record[fieldName]`, schema-import631 vs. body-read indistinguishability) when LWC referrers are632 present; Aura `v.{attr}` exclusion when Aura referrers are633 present; VF JS-string blanking when VF referrers are present.634- [ ] I did not present a heuristic edge as ground truth, and I did635 not claim an empty result means "no references exist."636- [ ] I did not present a declared edge with a heuristic disclaimer637 (LWC apex imports and VF controller/extension attributes do638 not need the "scanner may be wrong" caveat).639640---641642**Grounding & routing (shared contract).** For a vague or broad ask, call `sfi.route_question` first — in the default hybrid mode it returns a meaning-ranked `toolCandidates` shortlist (which YOU pick from) plus a suggested plane and a `route` hint (and whether to `sfi.resolve` a name first). **Default tool profile is `core`:** only the core spine (including `sfi.live_consent`) is directly invokable. For every other `sfi.*` analysis, call `sfi.run_analysis` with `{ "name": "sfi.<tool>", "args": { … } }` (or follow `route_question.invoke`, which already wraps non-core steps). Optional: `sfi.describe_analysis` first when args are unclear. Every org fact must come from an `sfi.*` tool call, cited by its canonical id — never from memory. Build the answer only from what the tools returned, then pass it through `sfi.synthesize_answer`, which flags any `hallucinatedIds` (canonical ids no tool produced). Full cascade: `using-sf-intelligence`.
Run npx skillmds@latest add pranavnagrecha/developer-apex-refactor in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Answers code-source-focused developer questions across the full org code surface: Apex classes, Apex triggers, and the v1.4 frontend tier (LWC, Aura, Visualforce). Triggers: "where is field X used in Apex", "find LWC components reading this field", "what calls this Apex from VF", "Aura references to this object", "who reads/writes this field from code", "what classes call MyClass.someMethod", "is it safe to rename this method", "find all Apex references to this", "what Apex/LWC/VF touches this object". Call `sfi.run_analysis` with `{ "name": "sfi.find_code_usages", "args": { … } }` (incoming readsFrom/writesTo/callsApex/references edges from ApexClass, ApexTrigger, LightningComponentBundle, AuraDefinitionBundle, VisualforcePage, or VisualforceComponent nodes) — the single code-usage tool. For strictly Apex-only questions, narrow it with `nodeTypes: ['ApexClass','ApexTrigger']` (the former `find_apex_usages` Apex-only view, now folded in). Optionally follows up with `sfi.search_apex_source` to surface the actu It is listed under Web & Frontend on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
PranavNagrecha (@pranavnagrecha) published this skill. Their other Agent Skills are listed on their SkillMD profile.