Business-user orientation
"Where is this used?" (§C3 contract)
When a business user asks "where is this field/report/thing used" or "what would
this affect", call sfi.run_analysis with { "name": "sfi.find_component_usages", "args": { … } } (resolve the name first) — it
returns where the component is referenced with plain evidence, or an honest "no
static evidence in the vault" (NEVER "nothing uses it"). Don't confuse this with
describe questions ("what is this field", "what values does it have") which use
the explain/describe tools.
Overview
The business-user persona is a sales rep, service agent, marketing
ops person, or finance approver — somebody who lives inside
Salesforce every day but does not know the metadata model. They
ask in their job's vocabulary, not in __c / parentOf /
PermissionSet vocabulary. They say "what does this field mean",
"what happens when I close this", "why can't I edit this record",
"is this required". They don't say "show me the
CustomField:Opportunity.Stage__c description property" or
"traverse the grantedBy edge from PermissionSet:Sales_Rep."
This skill is the translation layer. It classifies a question
into one of nine intents (eight from v1.0, plus a v1.6 "what does
this config value mean" tier), routes the answerable ones to the
existing sfi.* tools (v1.6 adds sfi.lookup_record and
sfi.explain_field for the record-value tier), surfaces the
result in plain English (translates Boolean → "yes/no", strips
__c suffixes from labels, hides canonical IDs unless the user
asked), and honestly refuses the unanswerable ones. The
honest-refusal axis is the load-bearing one for this persona: a
business user has no way to spot a fabricated answer about their
own org, because everything Claude says sounds plausible. The
guard rail is: do not answer with general Salesforce-product
knowledge when the question is about this org. If v1.x can't
tell, say so plainly and direct the user to the Salesforce UI or
to an admin.
v1.6 broadens to record values
The v1.0 skill ships an honest refusal for "what does this config
value mean" — but only when the record data isn't extracted. v1.6
extracts CustomMetadataType records (__mdt) and CustomSetting
records into the graph, so the skill can now answer:
- "What's the value of Number_Of_Retries for Marketo_Api_Setting Default?"
- "Show me the values for all Clinical_Instruction records"
- "What does the Industry field hold across records?"
- "Is this CMD record masked?" (yes → tell the user; no → show the value)
Two new tools
sfi.lookup_record — given CustomMetadataRecord:Type.Record or
CustomSettingRecord:Type.Record, returns the record's
field/value/valueType array plus isMasked flags.
sfi.explain_field — given CustomField:Type.Field, returns
label/description/type/required plus (for __mdt parent types)
the values that field holds across all records of that type.
Honesty axis (the boundary is still real)
- Masked values (
*** in Salesforce serialization) come through as
value: null, isMasked: true. Surface "this is masked; check Setup
to see the actual value" rather than fabricating.
- CustomSetting records are rarely serialized in DX format. If
sfi.lookup_record returns component-not-found, suggest the user
query the running org with sf data query.
- Record-level data (the specific instance the user is looking at) is
STILL not in the graph. v1.6 surfaces all extracted records, not
"the one Account this user is currently viewing".
Question intents
Every business-user question maps to exactly one of these nine
intents. Pick one before reaching for a tool. The routing decision
column tells you the next move — call a sfi.* tool, defer to
another skill, or refuse honestly.
| Intent |
Sample phrasings |
Routing decision |
| Field meaning |
"What does the Product Name field mean?" / "What is Industry__c for?" / "What's this field?" / "Can you explain this field to me?" |
Use sfi.search_components to normalize the user's phrase into a canonical CustomField: ID, then sfi.get_component. Surface properties.description. Fall back to properties.label + properties.inlineHelpText if description is null. If all three are empty, say so honestly — do not invent meaning from the API name. For __mdt parent types (CustomMetadataDefinition), prefer sfi.explain_field({ fieldId, includeRecordValues: true }) instead — it returns label/description/type/required AND the record values across all records of that type in one call. For "what values are in the X picklist?" use sfi.explain_field on the resolved field — picklistValues carries the declared value set (resolved through the usesValueSet edge for GlobalValueSet-driven fields on 0.1.10+ vaults, cited via picklistValuesSource; null + picklistValuesNote only when the link cannot resolve — never read null as "no values"). |
| Required / optional check |
"Is this field required?" / "Do I have to fill this in?" / "Is the Email field mandatory?" |
sfi.search_components then sfi.get_component on the field. Surface properties.required as "yes" or "no". Note: required here is the metadata-level "always required" flag — validation rules can also make a field effectively required; if the user asks "is this required" and the answer is no, also list any ValidationRule nodes on the parent object (via sfi.get_edges parentOf from the object) before concluding. |
| What-happens-when |
"What happens when I close this opportunity?" / "What runs when I save a case?" / "What fires when I update an Account?" |
List the metadata that fires on save/update/close events for the relevant object. sfi.list_components({ type: 'Flow', parentId: 'CustomObject:Account' }) and sfi.list_components({ type: 'ApexTrigger', parentId: 'CustomObject:Account' }) and sfi.list_components({ type: 'ValidationRule', parentId: 'CustomObject:Account' }). Important: v1.x cannot narrate what each Flow or Trigger does — it can only list what fires. Disclose that boundary. |
| Why-can't-I-do-X |
"Why can't I edit this record?" / "Why doesn't this button work?" / "Why am I seeing 'insufficient privileges'?" / "Why is this read-only?" |
If the user is asking about record visibility ("see this record", "open this record"), defer to admin-sharing-troubleshooting. If the user is asking about field-level permission ("why is this field grayed out", "why can't I edit this field"), call sfi.get_edges with nodeId: 'CustomField:...' and direction: 'in' and edgeType: 'grantedBy' to list the permission sets and profiles that grant access — then ask whether the user's profile/permset is in that list. |
| Process explanation |
"Explain this process to me in English." / "What does this flow do?" / "Walk me through this approval." |
List the relevant Flow or ApexClass nodes via sfi.get_component. Honestly refuse to narrate Flow XML or Apex source as plain-English prose — v1.x stores them as text but does not translate them to narrative. Offer to surface the source (or to list the actions/decisions/record updates the Flow contains, which are extracted as edges), and direct the user to their admin for a narrative walkthrough. |
| Org-wide vocabulary lookup |
"What's a Order_Line__c?" / "What's this object for?" / "What does this object represent?" |
sfi.get_component on the CustomObject: ID. Surface properties.description (object-level) and the count of CustomField children via sfi.list_components({ type: 'CustomField', parentId: 'CustomObject:...' }) as a "fields it tracks" summary. Refuse if the object-level description is null and the object name is opaque (Order_Line__c with no description and no admin annotation means "v1.x can't tell you what this is for"). |
| Where do I find |
"Where do I find the Pipeline report?" / "How do I run the Won Deals dashboard?" / "Where's the list view called 'My Cases'?" |
Partly answerable. Report/dashboard nodes carry their LEAF FOLDER (Report:{LeafFolder}/{Name}), so sfi.list_components({ type: 'Report' }) can name where a report lives — but UI navigation paths are not in the vault, and the node set is capped per type (a pending Report coverage row means the list is a subset). For field usage questions ("is this field used in a report?"), check the field's folded usedInReport / usedInDashboard properties (default capped pull) or run sfi refresh --with-reports for full coverage. ListView column field refs are extracted as graph edges (sfi.find_field_anywhere, sfi.list_components({ type: 'ListView' })); list-view filter evaluation and UI picker paths are not composed in field tools. |
| What does this config value mean |
"What's the value of Number_Of_Retries for Marketo_Api_Setting Default?" / "Show me the values for all Clinical_Instruction records" / "What does the Industry field hold across records?" / "Is this CMD record protected?" |
v1.6+. Route to sfi.lookup_record({ recordId: 'CustomMetadataRecord:Type.Record' }) (or CustomSettingRecord:Type.Record) when the user names a specific record; route to sfi.explain_field({ fieldId: 'CustomField:Type.Field', includeRecordValues: true }) when the user wants the value of a field across all records of a __mdt type. Both tools pass { value: null, isMasked: true } through verbatim for masked (***) values — surface the masked status to the user; never fabricate the underlying value. If sfi.lookup_record returns component-not-found for a CustomSettingRecord: id, that's the v1.6 boundary on CustomSetting serialization — suggest sf data query against the running org. |
| SLA / entitlement / routing |
"What's the SLA on this case?" / "When does this case escalate?" / "What's the entitlement on this account?" / "How are cases routed to agents?" |
Partial answer + honest refusal (R6-18). EntitlementProcess (name, active, SObjectType, version, business hours, and the MilestoneType names it references) and MilestoneType (description, recurrence) are extracted — normalize via sfi.search_components then sfi.get_component / sfi.get_edges to list which milestones apply and whether the process is active. For routing: ServiceChannel (related object, capacity model) and QueueRoutingConfig (routing model, capacity weight, push timeout, overflow queue — a references edge from the owning Queue) are extracted; sfi.get_component on the Queue: id surfaces its routing config. Then honestly refuse the still-unmodeled parts: per-milestone target minutes, whether a SPECIFIC case is on-track or breached, and real-time agent capacity/availability are live-only — direct the user to the Salesforce UI's Entitlement/Milestones related lists. The record-level Entitlement assignment and ServiceContract remain entirely unmodeled. Do not invent SLA numbers from general Salesforce-product knowledge. |
A few cues to break ties:
- The user asks "what does this field mean" → field meaning.
- The user asks "what does this object mean" or "what's this
object for" → org-wide vocabulary lookup (object scope).
- The user asks "why can't I see this record" → defer to
admin-sharing-troubleshooting. The user asks "why can't I
edit this field" → why-can't-I-do-X, field-level permission
flavor.
- The user asks "what happens when I close" → what-happens-when.
The user asks "what does the close-opportunity flow do" →
process explanation (with the honest refusal about narration).
- The user names a report or dashboard folder/UI path → honest UI redirect
(where-do-I-find). Field-usage questions → check folded report/dashboard
properties or
sfi.find_field_anywhere.
- The user names a list view by UI name only → honest UI redirect; column
field-ref questions → graph tools (
find_field_anywhere, list_components).
- The user names an SLA, entitlement, milestone, or case-routing/
queue-assignment question → SLA / entitlement / routing (partial
answer for the modeled process/milestone/routing metadata, honest
refusal for target minutes and live status).
When to fire
Fire this skill on business-user-flavored phrasing — the user is
asking in job vocabulary, not metadata vocabulary. The frontmatter
description is the auto-activation signal; this section is the
human-readable enumeration. Concrete triggers:
- "What does X mean / what is X for?" — "what does the Product
Name field mean?", "what is this field?", "what's the Industry
field for?", "what's an Order Line?".
- "What happens when…" — "what happens when I close this
opportunity?", "what happens when I save this case?", "what runs
when I update an Account?".
- "Why can't I…" — "why can't I edit this record?", "why
doesn't this button work?", "why am I seeing insufficient
privileges?", "why is this field read-only?".
- "Is this required?" — "is this field required?", "do I have
to fill this in?", "is the Email field mandatory?".
- "Where do I find…" — "where do I find the Pipeline report?",
"how do I run the Won Deals dashboard?", "where's the list view
for My Cases?".
- "What's the SLA / entitlement on…" / "How are cases routed…" —
"what's the SLA on this case?", "when does this escalate?",
"what's the entitlement on this account?", "how are cases routed
to agents?", "what's this queue's routing config?".
- "Explain this process / flow to me." — "explain this
approval process", "walk me through the close-opportunity flow",
"what does this automation do?".
- "How is this calculated?" — "how is this rollup
calculated?", "what's the formula behind this field?". (Treat as
field-meaning intent; surface
properties.formula from the
field's get_component response, plus its declared references
edges.)
When NOT to fire
Defer to another skill or refuse honestly when:
- The user is asking a cross-component impact question ("what
breaks if I change this field?", "what depends on this object?",
"is it safe to deprecate this?"). Fire
architect-impact-analysis
instead — that's the architect persona's tool, and it has its own
boundary-disclosure discipline.
- The user is asking an Apex-source question ("where is this
field used in Apex?", "what classes call this method?", "find
references to
MyClass"). Fire developer-apex-refactor
instead.
- The user is asking specifically about record-level
visibility / sharing ("why can't user X see record Y?", "who
can see this record?", "what's the visibility for this account?").
Fire
admin-sharing-troubleshooting instead. The boundary
between this skill's "why-can't-I-do-X" intent and
admin-sharing-troubleshooting is: if the question is about
whether the user has access to a record, defer to admin
sharing. If the question is about what a field/button does or
means, stay here.
- The user is asking a schema-shape question ("what fields
does Account have?", "list every object in the org", "show me
every flow"). Fire
answering-org-questions instead — that
skill's schema and dependency intents are the right cascade.
- The user is asking for a refresh, init, or vault status check.
Fire
refreshing-the-org-vault, /sfi-init, or
pre-flight-checks.
- The user is asking a generic Salesforce-product question with
no reference to their org ("what is an opportunity stage?",
"how does email-to-case work?"). These are general-knowledge
questions, not vault questions. Answer briefly from general
knowledge and offer to look at how their org actually uses
the concept — but flag clearly that the first half of the answer
is generic, not org-specific.
- The user wants record-level data for a specific instance they
are viewing ("what's the current value of THIS account's
Industry?", "how many cases are open?"). Record-level lookups
work for
__mdt records and CustomSetting records via
sfi.lookup_record (v1.6+). HOWEVER: record-level data for
the specific instance the user is viewing (e.g., "this Account I'm
looking at") is still NOT in the graph. v1.6 surfaces all
extracted records, not the one the user is currently viewing.
Tell the user plainly; suggest they query their org or check the
record directly in the UI.
Steps
Walk these in order.
Step 1 — Classify the question into an intent
Pick exactly one intent from the table above. If the question
straddles two — "what does this field mean and why can't I
edit it?" — answer the more specific one first (field meaning),
then offer to run the second (defer to
admin-sharing-troubleshooting for the why-can't-I half if
record-level).
If the question's intent is where-do-I-find, stop and refuse
honestly — do not call any tool to fish for an unmodeled answer.
SLA / entitlement / routing and process-explanation are hybrids:
call sfi.search_components / sfi.get_component (/ sfi.get_edges)
for the metadata that IS modeled, present it, then refuse honestly
for the part that isn't (target minutes and live status for SLA /
entitlement / routing; narrated prose for process explanation). See
Anti-patterns below for why "I'll try anyway" on the unmodeled
half is the wrong move.
Step 2 — Normalize the user's phrase into a canonical ID
Business users almost never type canonical IDs. They say "the
Product Name field", "this opportunity", "the Account object". The
normalization rule:
- If the user's phrase already looks like a canonical ID
(
CustomField:Account.Industry__c,
CustomObject:Order_Line__c), use it directly.
- Otherwise, call
sfi.search_components with the user's exact
phrase (e.g., { query: 'Product Name' }). Use the top match if
the score is clearly above the runner-up; otherwise surface the
top 3 to the user and ask which one they meant.
- Narrow with
types: ['CustomField'] only when the user's
phrasing explicitly pins the type ("the Product Name field" →
types: ['CustomField']; "Order Line" alone → no
types, let the search rank).
- Never synthesize a canonical ID by guessing the suffix
("Product Name" →
Product_Name__c). Guessed IDs hit not-found,
burn a turn, and a different naming convention may apply in
this org.
Step 3 — Call the routed tool
Pick the tool per the intent table. Common patterns:
- Field meaning —
sfi.get_component with the
CustomField: ID. Read properties.description first; fall
back to properties.label and properties.inlineHelpText if
description is null.
- Required check —
sfi.get_component on the field; read
properties.required. If false, also call sfi.list_components
filtered to type: 'ValidationRule' and parentId of the
field's parent object to see if a validation rule makes the
field effectively required.
- What-happens-when —
sfi.list_components({ type: 'Flow', parentId: 'CustomObject:Account' }), then ... 'ApexTrigger' ...,
then ... 'ValidationRule' .... List the components by canonical
ID. Do not narrate what each one does.
- Why-can't-I-do-X (field-level permission) —
sfi.get_edges({ nodeId: 'CustomField:...', direction: 'in', edgeType: 'grantedBy' }). The returned edges name the
PermissionSet: or Profile: nodes that grant access; if none
of those match the user's profile / permset, that's the answer.
- Org-wide vocabulary lookup —
sfi.get_component on the
CustomObject: ID; read properties.description. Optionally
follow with sfi.list_components({ type: 'CustomField', parentId: 'CustomObject:...' }) for a fields summary.
If a tool returns { error: { kind: 'component-not-found', ... } },
go back to Step 2 with synonyms or a broader search.
Step 4 — Translate the result into business-user English
This is where most skills fail the business-user persona. The raw
tool response is in metadata vocabulary; the answer needs to be in
job vocabulary. Apply these rules:
- Prefer
properties.label over apiName. Say "Product Name",
not "Product_Name__c". Mention the API name once, in
parentheses, only if the user asked or is clearly an admin.
- Translate booleans.
properties.required: true → "Yes, this
field is required." false → "No, it's optional" (and check
validation rules per Step 3).
- Translate types.
properties.dataType: 'Text' → "free-text
field". 'Picklist' → "a dropdown with these options:" followed
by properties.picklistValues. 'Number' → "a numeric field".
'Date' / 'DateTime' → "a date field". 'Lookup' → "a link
to another record" (and name properties.referenceTo).
- Hide canonical IDs unless asked. A business user who asks
"what does Product Name mean" does not want
CustomField:Order_Line__c.Product_Name__c in the answer.
Keep the ID in a "for your admin" footer if they want it.
- Use
properties.description verbatim when it exists. It was
written by an admin for exactly this purpose. Don't paraphrase
or "improve" it. If empty, fall back to properties.label +
properties.inlineHelpText. If both are empty, honestly admit
v1.x can't tell (see Step 5 / Anti-patterns).
Step 5 — Refuse honestly when v1.x can't tell
Seven refusal conditions, each with a fixed-form response:
- Description is null AND label is opaque — "The metadata for
this field has no description and no help text. v1.x can't
describe what it's for beyond the label
{label}. Ask your
admin, or check the field's setup page in Salesforce."
- User asked for a report, dashboard, or list view — "Reports,
dashboards, and list views aren't extracted in v1.x. Try the
Reports tab or Dashboards tab in the Salesforce UI for
{org}."
- User asked for SLA target minutes or live entitlement/milestone
status — "This org's EntitlementProcess/MilestoneType metadata
is extracted (which milestones apply, whether the process is
active, business hours), but not the per-milestone target
minutes or whether THIS case is currently on-track or breached —
that's live, per-record data. Check the case's Entitlement
and Milestones related lists in Salesforce, or ask your
admin."
- User asked for process narration (Flow XML → English) —
"v1.x stores Flow XML and Apex source as text, but it doesn't
translate them to a plain-English narrative. I can list what
the flow contains (decisions, record-updates, action-calls),
surface the source text, or you can ask your admin to walk you
through it."
- User asked for record-level data on the specific instance
they are viewing — "v1.6 extracts Custom Metadata Type and
Custom Setting records (use
sfi.lookup_record /
sfi.explain_field for those), but it does NOT read the
specific Account / Case / Opportunity record you are looking
at. To answer what this specific record looks like, open it
in Salesforce or query with sf data query."
- Record value comes back masked (v1.6) —
sfi.lookup_record
or sfi.explain_field returned an entry with value: null, isMasked: true. Tell the user: "That field is protected by
Salesforce — the value is serialized as *** in the metadata
API, which is the marker for a managed-package or protected
value. To see the actual value, open the record in Setup →
Custom Metadata Types in Salesforce, or ask the package
owner / admin." Do NOT guess the underlying value.
- CustomSetting record returns
component-not-found (v1.6) —
CustomSettings are rarely serialized via sf project retrieve.
Tell the user: "CustomSetting records often live in the running
org's data layer rather than in DX metadata, so v1.6 didn't
extract this one. Query the org directly with sf data query
against the CustomSetting's __c table, or check Setup →
Custom Settings → Manage in Salesforce."
Step 6 — Never use general Salesforce-product knowledge as a stand-in
This is the constitutional rule. When the org's description is
empty, do not fall back to your training-data sense of what
"Industry" or "Stage" means on standard objects. The org may have
customized the standard semantics; the metadata is silent; the
right answer is "I can't tell, ask your admin," not "Industry is
the company's primary line of business." A confident generic
answer is worse than an honest refusal: the user has no way to
spot the fabrication.
Reporting format
Worked example. User asks: "What does the Product Name field
mean?"
Claude's flow:
- Classify → field-meaning intent.
- Normalize →
sfi.search_components({ query: 'Product Name', types: ['CustomField'] }) → top match
CustomField:Order_Line__c.Product_Name__c with score 0.94.
- Call
sfi.get_component({ id: 'CustomField:Order_Line__c.Product_Name__c' }).
- Read
properties.description: "The display name of the product this order line links to, copied from Product__c.Name at creation." and properties.label: 'Product Name' and
properties.dataType: 'Text' and properties.required: true.
- Translate and respond:
Product Name is a free-text field on Order Line that
stores the name of the product this order line links to. The
admin's description says: "The display name of the product this
order line links to, copied from Product__c.Name at creation."
It's required (you have to fill it in).
If you want the full metadata: this is
CustomField:Order_Line__c.Product_Name__c in the org
vault.
Note what the response does and doesn't do:
- Uses the label ("Product Name"), not the API name, as the
primary identifier.
- Quotes
properties.description verbatim — it's the admin's
authored answer.
- Translates
dataType: 'Text' → "free-text field" and
required: true → "required (you have to fill it in)".
- Hides the canonical ID in a "for your admin" footer.
- Does not invent generic meaning ("Product Name is the name of
the product") if the description had been empty.
Hybrid example — partial answer + honest refusal (R6-18). User asks:
"What's the SLA on this case?"
Claude's flow:
- Classify → SLA / entitlement / routing intent.
- Normalize + call.
sfi.search_components({ query: 'Case' }) to
confirm the object, then sfi.list_components({ type: 'EntitlementProcess' }) (or sfi.get_component on the resolved
process id) plus sfi.get_edges to list the MilestoneType
targets it references.
- Respond:
This org's Case entitlement process is "Gold Support Process"
(active). It defines three milestones: First Response to
Customer, Escalate Case, and Close Case.
What I can't tell you from the vault: the target minutes for
each milestone, and whether this specific case is currently
on-track or breached — that's live, per-record timer data. Check
the case's Entitlement and Milestones related lists in
Salesforce, or ask your admin for the target times.
The response grounds the process/milestone SHAPE in real extracted
metadata (EntitlementProcess/MilestoneType, R6-18), then honestly
refuses the live number instead of fabricating "24 hours."
Boundary disclosure
v1.x's coverage for the business-user persona has well-defined
gaps. Surface this list when the user asks something v1.x can't
answer, and always include the relevant subset when refusing:
- Reports, dashboards, list views.
- Reports/dashboards: default refresh folds top-500-by-usage field usage
onto
CustomField nodes (usedInReport / usedInDashboard); beyond-cap
members stay pending. Full coverage: sfi refresh --with-reports.
Report/Dashboard NODES are persisted as Report:{LeafFolder}/{Name} /
Dashboard:{LeafFolder}/{Name} (so folder names ARE in the vault), capped per
type — when capped, the coverage row goes pending. Report filter VALUES
and dashboard running-users are never vaulted; UI navigation paths are not
modeled.
- List views: column field refs are extracted (
ListView nodes +
references edges); filter evaluation and UI picker paths are not composed
in field_360. Use sfi.find_field_anywhere / sfi.list_components.
- Record-level data. v1.6 added CustomMetadataRecord and
CustomSettingRecord extraction (use
sfi.lookup_record /
sfi.explain_field). Questions about the specific Account /
Case / Opportunity record the user is currently viewing ("what
does THIS account's Industry field say?") are still NOT
covered — v1.6 surfaces extracted config records, not live
transactional data. Direct the user to sf data query for those.
- Flow-to-narrative translation. v1.x stores Flow XML as text
and extracts its readsFrom / writesTo / triggersOn / callsApex
edges, but it does not narrate the Flow's behavior in plain
English. The user gets a list of what the flow touches, not a
story.
- Apex-to-narrative translation. Same boundary for Apex
classes and triggers — text storage and heuristic edge
extraction, no semantic narration.
- Entitlements, milestones, SLAs (R6-18: partial).
EntitlementProcess (name, active, SObjectType, version,
business hours, referenced MilestoneType names) and
MilestoneType (description, recurrence) ARE extracted
ComponentTypes — the process/milestone SHAPE is answerable.
Per-milestone target minutes, live on-track/breached status, the
record-level Entitlement assignment, and ServiceContract are
still NOT modeled (live/record-level only). (EscalationRule was
listed here as unmodeled in an earlier revision of this skill —
that was stale; it has been an extracted ComponentType since
v1.3.)
- Omni-Channel routing (R6-18).
ServiceChannel (related
object via relatedEntityType, capacity model) and
QueueRoutingConfig (routing model, capacity weight, push
timeout, overflow queue) are extracted, including the
Queue -> QueueRoutingConfig references edge. Real-time agent
capacity/availability/presence is still live-only.
- Quote, Order, Contract approval routing (R7-W8-skill: corrected —
ApprovalProcess was listed here as uncovered; that was stale).
ApprovalProcess IS an extracted ComponentType (since v1.3, object-agnostic
— Opportunity, Quote, Order, Contract, or any custom object): approval
steps and their approver chains (user / role / role-subordinates / queue /
group / hierarchy-field variants), entry-criteria conditions, field-update
actions (writesTo edges to the CustomField each step sets), and
approval/rejection email alerts (sendsEmail edges) are all modeled — the
DECLARED routing shape is answerable. What's still NOT modeled: a specific
record's LIVE in-flight approval state (who it's currently pending on, how
long it's been sitting, its history) — that's live/record-level data, same
boundary as the Entitlements bullet above.
- Live state. Queue depth, recent errors, governor-limit
consumption, currently-running jobs — none of this is in v1.x.
The vault is a snapshot at refresh time.
- Record data. Field values, record counts, who owns what, when a
record last changed. The vault holds schema and source, not rows.
Route record questions to the opt-in live plane or the UI.
The three families this list used to name as "not yet covered" are all
MODELED component types today — do not refuse them: record types and
page-layout assignment by record type (route to
admin-page-layout-routing); UI tabs, apps, quick actions, paths,
global value sets, custom labels, static resources; and email
templates, letterheads and communication sites (route to
admin-legacy-automation).
Treat honest refusal as a feature, not a failure mode. A
business user trusts a confident answer; the protection against
fabricated answers is naming the gap explicitly.
Anti-patterns
| Mistake |
Why it's wrong |
Answering "what does this field mean" with generic Salesforce documentation when properties.description is empty. |
Silent fabrication. The user has no way to tell the answer wasn't grounded in their org. Refuse honestly — "the description is empty in this org's metadata; ask your admin" — and stop. |
Translating Product_Name__c to "Product Name" as a guess at the label instead of reading properties.label. |
API-name-to-label translation is unreliable: an admin may have set the label to "Class Title" while the field is still named Product_Name__c. Always read properties.label. |
| Confidently answering "the SLA on this case is 24 hours" because that's a common default. |
v1.x models WHICH milestones apply (EntitlementProcess/MilestoneType) but not their target minutes. Surface the milestone names/process, then refuse the number and point to the Entitlement related list — any specific SLA number you state is fabricated. |
Forwarding the user's literal question to a tool input ("sfi.get_component({ id: 'the customer field' })"). |
The tool requires a canonical ID. The right move is sfi.search_components({ query: 'the customer field' }) first, then sfi.get_component on the top match. |
| Narrating a Flow as plain-English prose by reading the XML. |
v1.x doesn't have a Flow-to-narrative renderer. The honest answer is "I can list what the flow contains, but I don't translate Flow XML to a story." Offer the structured list; refuse the prose. |
Defaulting to admin-sharing-troubleshooting for every "why can't I" question. |
The routing distinction matters: record-level visibility ("can the user open this record at all") is admin-sharing. Field-level permission ("why is this field grayed out") is this skill's why-can't-I-do-X intent with field-permission flavor. Picking the wrong route loads the wrong tool. |
| Mixing the user's question with another org's conventions. |
Each org is private and specific. Even if 95% of orgs use Stage on Opportunity for sales pipeline, this org may have customized it. Always read from properties.description; never blend with generic Salesforce-product knowledge. |
Hiding unknown or empty-description outcomes to make the answer look complete. |
The user makes a downstream decision (filling out a field, telling a customer an SLA). An incomplete answer presented as complete causes a worse outcome than a complete refusal. Surface the gap. |
Citing canonical IDs (CustomField:Order_Line__c.Product_Name__c) as the primary answer. |
Canonical IDs are admin / architect / developer vocabulary. A business user wants "Product Name" and a one-paragraph explanation. Keep IDs in a footer or hide them unless asked. |
| Trying to answer "where do I find the Pipeline report" by guessing a Reports folder name. |
Don't guess — but don't refuse outright either. Report: nodes ARE persisted (id Report:{LeafFolder}/{DeveloperName}), so the folder is recoverable from the id when the report was in the capped, usage-ranked pull. If it was not pulled, say NOT CHECKED — not "no such report" — and send the user to the UI. A guessed path sends them to a 404. |
Example interactions
Happy path — field meaning
User: "What does the Product Name field mean?"
Claude's flow: classify (field meaning) → search → get_component →
translate. See Reporting format above for the full transcript.
Routed path — sharing question
User: "Why can't I see the Smith account?"
Claude's flow:
- Classify → why-can't-I-do-X, but with record visibility
flavor. This is
admin-sharing-troubleshooting's domain, not
this skill's.
- Defer with a one-sentence handoff: "Record-visibility
questions are handled by the sharing-troubleshooting skill —
it'll walk the OWD / role-hierarchy / sharing-rules cascade for
you. I'll fire it now."
- The handoff message names why the route changed, so the user
sees the skill boundary rather than experiencing an unexplained
topic shift.
Hybrid — entitlement question (R6-18)
User: "What's the SLA on this case?"
See the Reporting format hybrid example above. The response
surfaces the real extracted process/milestone metadata, then names
the still-live gap (target minutes, on-track/breached status) and
stays honest about that boundary.
Partial answer — case routing (R6-18)
User: "How are cases routed to agents?"
Claude's flow:
- Classify → SLA / entitlement / routing intent.
- Normalize + call.
sfi.list_components({ type: 'ServiceChannel' }) to find the Case channel's relatedEntityType, then walk the
Queues that own Case (sfi.get_edges sharedWith from
CustomObject:Case) and read each queue's references edge to its
QueueRoutingConfig.
- Respond:
Cases route through the Case Omni-Channel service channel. The
Case Queue uses the "agent routing" routing config:
Most Available routing model, capacity weight 5.0, with
overflow to Fallback Queue when the primary queue is full.
What I can't tell you: which agents are online right now or their
current capacity — that's live presence data. Check
Omni-Channel Supervisor in Salesforce for real-time agent
status.
The response grounds the routing BEHAVIOR in real extracted metadata
(ServiceChannel/QueueRoutingConfig, R6-18), then honestly refuses the
live presence/capacity half.
Honest refusal — process narration
User: "Explain the close-opportunity flow to me in English."
Claude's flow:
- Classify → process explanation.
- Partial answer + honest refusal. Run
sfi.search_components
to find the flow (e.g., Flow:Close_Opportunity_Approval),
then sfi.get_component for the structured view. Surface the
flow's components from the metadata: triggering object,
readsFrom / writesTo / callsApex edges. But refuse to
narrate the decision tree in prose.
- Respond:
Flow:Close_Opportunity_Approval fires on Opportunity update
when StageName changes to Closed. From the metadata, it:
- reads
CustomField:Opportunity.Amount
- writes
CustomField:Opportunity.Close_Approved__c
- calls
ApexClass:OpportunityApprovalRouter
v1.x stores Flow XML as text and extracts these edges, but it
does not translate the flow's decision tree into a story.
For a narrative walkthrough — what each decision branch does,
in plain English — ask your admin or open the flow in Setup.
The response gives the structured partial (the edges v1.x does
extract), names the boundary, and points to the manual path for
the prose.
Field-level permission
User: "Why can't I edit the Annual Revenue field on Account?"
Claude's flow:
- Classify → why-can't-I-do-X, field-permission flavor (not
record visibility — this is field-level, not record-level).
- Normalize →
sfi.search_components({ query: 'Annual Revenue', types: ['CustomField'] }) →
CustomField:Account.AnnualRevenue.
- Call
sfi.get_edges({ nodeId: 'CustomField:Account.AnnualRevenue', direction: 'in', edgeType: 'grantedBy' }).
- Translate: list the
PermissionSet: and Profile: nodes
that grant access. Ask which one the user is on.
- Respond:
Edit access to Annual Revenue on Account is granted by:
PermissionSet:Sales_Manager
PermissionSet:Finance_Reviewer
Profile:System Administrator
If you're not on one of those, that's why the field is
read-only for you. Ask your admin for one of these permission
sets, or check Setup → My Personal Information in
Salesforce to confirm your current profile and permission sets.
Note: this is field-level editability (Field-Level Security).
If the record itself is
…(truncated)
1---2name: business-user-orientation3description: Answers business-user-flavored Salesforce questions like "what does this field mean in English", "what happens when I close this opportunity", "why can't I do X", "how do I find report Y", "what's the SLA on this case", "how are cases routed to agents", "is this field required", "what's this object for", "how is this calculated". Classifies the intent and routes to the right `sfi.*` tool, OR honestly refuses when v1.x doesn't yet model the answer (record-level data, reports, dashboards, live entitlement-milestone status/timers, flow-to-narrative). R6-18 extracts EntitlementProcess/MilestoneType (which milestones apply, business hours, active state) and ServiceChannel/QueueRoutingConfig (Omni-Channel routing model, capacity, overflow queue) — SLA/ entitlement and case-routing questions get a real partial answer now, not a blanket refusal. Bridges the vocabulary gap between business-user phrasing and the architect/admin tools the product already ships. v1.6 broadens to record-value lookups for Custom Metadata T4---56# Business-user orientation78## "Where is this used?" (§C3 contract)910When a business user asks "where is this field/report/thing used" or "what would11this affect", call `sfi.run_analysis` with `{ "name": "sfi.find_component_usages", "args": { … } }` (resolve the name first) — it12returns where the component is referenced with plain evidence, or an honest "no13static evidence in the vault" (NEVER "nothing uses it"). Don't confuse this with14*describe* questions ("what is this field", "what values does it have") which use15the explain/describe tools.1617## Overview1819The business-user persona is a sales rep, service agent, marketing20ops person, or finance approver — somebody who lives inside21Salesforce every day but does **not** know the metadata model. They22ask in their job's vocabulary, not in `__c` / `parentOf` /23`PermissionSet` vocabulary. They say "what does this field mean",24"what happens when I close this", "why can't I edit this record",25"is this required". They don't say "show me the26`CustomField:Opportunity.Stage__c` description property" or27"traverse the `grantedBy` edge from `PermissionSet:Sales_Rep`."2829This skill is the **translation layer**. It classifies a question30into one of nine intents (eight from v1.0, plus a v1.6 "what does31this config value mean" tier), routes the answerable ones to the32existing `sfi.*` tools (v1.6 adds `sfi.lookup_record` and33`sfi.explain_field` for the record-value tier), surfaces the34result in plain English (translates `Boolean` → "yes/no", strips35`__c` suffixes from labels, hides canonical IDs unless the user36asked), and **honestly refuses** the unanswerable ones. The37honest-refusal axis is the load-bearing one for this persona: a38business user has no way to spot a fabricated answer about their39own org, because everything Claude says sounds plausible. The40guard rail is: **do not answer with general Salesforce-product41knowledge when the question is about *this* org**. If v1.x can't42tell, say so plainly and direct the user to the Salesforce UI or43to an admin.4445## v1.6 broadens to record values4647The v1.0 skill ships an **honest refusal** for "what does this config48value mean" — but only when the record data isn't extracted. v1.649extracts CustomMetadataType records (`__mdt`) and CustomSetting50records into the graph, so the skill can now answer:5152- "What's the value of Number_Of_Retries for Marketo_Api_Setting Default?"53- "Show me the values for all Clinical_Instruction records"54- "What does the Industry field hold across records?"55- "Is this CMD record masked?" (yes → tell the user; no → show the value)5657### Two new tools5859- `sfi.lookup_record` — given `CustomMetadataRecord:Type.Record` or60 `CustomSettingRecord:Type.Record`, returns the record's61 field/value/valueType array plus `isMasked` flags.62- `sfi.explain_field` — given `CustomField:Type.Field`, returns63 label/description/type/required plus (for `__mdt` parent types)64 the values that field holds across all records of that type.6566### Honesty axis (the boundary is still real)6768- Masked values (`***` in Salesforce serialization) come through as69 `value: null, isMasked: true`. Surface "this is masked; check Setup70 to see the actual value" rather than fabricating.71- CustomSetting records are rarely serialized in DX format. If72 `sfi.lookup_record` returns `component-not-found`, suggest the user73 query the running org with `sf data query`.74- Record-level data (the specific instance the user is looking at) is75 STILL not in the graph. v1.6 surfaces all extracted records, not76 "the one Account this user is currently viewing".7778## Question intents7980Every business-user question maps to exactly one of these nine81intents. Pick one before reaching for a tool. The routing decision82column tells you the next move — call a `sfi.*` tool, defer to83another skill, or refuse honestly.8485| Intent | Sample phrasings | Routing decision |86|---|---|---|87| **Field meaning** | "What does the Product Name field mean?" / "What is `Industry__c` for?" / "What's this field?" / "Can you explain this field to me?" | Use `sfi.search_components` to normalize the user's phrase into a canonical `CustomField:` ID, then `sfi.get_component`. Surface `properties.description`. Fall back to `properties.label` + `properties.inlineHelpText` if `description` is null. If all three are empty, **say so honestly** — do not invent meaning from the API name. For `__mdt` parent types (CustomMetadataDefinition), prefer `sfi.explain_field({ fieldId, includeRecordValues: true })` instead — it returns label/description/type/required AND the record values across all records of that type in one call. For "what values are in the X picklist?" use `sfi.explain_field` on the resolved field — `picklistValues` carries the declared value set (resolved through the `usesValueSet` edge for GlobalValueSet-driven fields on 0.1.10+ vaults, cited via `picklistValuesSource`; `null` + `picklistValuesNote` only when the link cannot resolve — never read `null` as "no values"). |88| **Required / optional check** | "Is this field required?" / "Do I have to fill this in?" / "Is the Email field mandatory?" | `sfi.search_components` then `sfi.get_component` on the field. Surface `properties.required` as "yes" or "no". Note: `required` here is the metadata-level "always required" flag — validation rules can also make a field effectively required; if the user asks "is this required" and the answer is no, also list any `ValidationRule` nodes on the parent object (via `sfi.get_edges` `parentOf` from the object) before concluding. |89| **What-happens-when** | "What happens when I close this opportunity?" / "What runs when I save a case?" / "What fires when I update an Account?" | List the metadata that fires on save/update/close events for the relevant object. `sfi.list_components({ type: 'Flow', parentId: 'CustomObject:Account' })` and `sfi.list_components({ type: 'ApexTrigger', parentId: 'CustomObject:Account' })` and `sfi.list_components({ type: 'ValidationRule', parentId: 'CustomObject:Account' })`. **Important: v1.x cannot narrate what each Flow or Trigger *does*** — it can only list what fires. Disclose that boundary. |90| **Why-can't-I-do-X** | "Why can't I edit this record?" / "Why doesn't this button work?" / "Why am I seeing 'insufficient privileges'?" / "Why is this read-only?" | If the user is asking about *record visibility* ("see this record", "open this record"), defer to `admin-sharing-troubleshooting`. If the user is asking about *field-level* permission ("why is this field grayed out", "why can't I edit this field"), call `sfi.get_edges` with `nodeId: 'CustomField:...'` and `direction: 'in'` and `edgeType: 'grantedBy'` to list the permission sets and profiles that grant access — then ask whether the user's profile/permset is in that list. |91| **Process explanation** | "Explain this process to me in English." / "What does this flow do?" / "Walk me through this approval." | List the relevant `Flow` or `ApexClass` nodes via `sfi.get_component`. **Honestly refuse to narrate Flow XML or Apex source as plain-English prose** — v1.x stores them as text but does not translate them to narrative. Offer to surface the source (or to list the actions/decisions/record updates the Flow contains, which are extracted as edges), and direct the user to their admin for a narrative walkthrough. |92| **Org-wide vocabulary lookup** | "What's a `Order_Line__c`?" / "What's this object for?" / "What does this object represent?" | `sfi.get_component` on the `CustomObject:` ID. Surface `properties.description` (object-level) and the count of `CustomField` children via `sfi.list_components({ type: 'CustomField', parentId: 'CustomObject:...' })` as a "fields it tracks" summary. Refuse if the object-level description is null and the object name is opaque (`Order_Line__c` with no description and no admin annotation means "v1.x can't tell you what this is for"). |93| **Where do I find** | "Where do I find the Pipeline report?" / "How do I run the Won Deals dashboard?" / "Where's the list view called 'My Cases'?" | **Partly answerable.** Report/dashboard nodes carry their LEAF FOLDER (`Report:{LeafFolder}/{Name}`), so `sfi.list_components({ type: 'Report' })` can name where a report lives — but UI navigation paths are not in the vault, and the node set is capped per type (a `pending` Report coverage row means the list is a subset). For **field usage** questions ("is this field used in a report?"), check the field's folded `usedInReport` / `usedInDashboard` properties (default capped pull) or run `sfi refresh --with-reports` for full coverage. ListView **column field refs** are extracted as graph edges (`sfi.find_field_anywhere`, `sfi.list_components({ type: 'ListView' })`); list-view **filter evaluation** and UI picker paths are not composed in field tools. |94| **What does this config value mean** | "What's the value of Number_Of_Retries for Marketo_Api_Setting Default?" / "Show me the values for all Clinical_Instruction records" / "What does the Industry field hold across records?" / "Is this CMD record protected?" | **v1.6+.** Route to `sfi.lookup_record({ recordId: 'CustomMetadataRecord:Type.Record' })` (or `CustomSettingRecord:Type.Record`) when the user names a specific record; route to `sfi.explain_field({ fieldId: 'CustomField:Type.Field', includeRecordValues: true })` when the user wants the value of a field across all records of a `__mdt` type. Both tools pass `{ value: null, isMasked: true }` through verbatim for masked (`***`) values — surface the masked status to the user; **never fabricate** the underlying value. If `sfi.lookup_record` returns `component-not-found` for a `CustomSettingRecord:` id, that's the v1.6 boundary on CustomSetting serialization — suggest `sf data query` against the running org. |95| **SLA / entitlement / routing** | "What's the SLA on this case?" / "When does this case escalate?" / "What's the entitlement on this account?" / "How are cases routed to agents?" | **Partial answer + honest refusal (R6-18).** `EntitlementProcess` (name, active, `SObjectType`, version, business hours, and the `MilestoneType` names it references) and `MilestoneType` (description, recurrence) are extracted — normalize via `sfi.search_components` then `sfi.get_component` / `sfi.get_edges` to list which milestones apply and whether the process is active. For routing: `ServiceChannel` (related object, capacity model) and `QueueRoutingConfig` (routing model, capacity weight, push timeout, overflow queue — a `references` edge from the owning `Queue`) are extracted; `sfi.get_component` on the `Queue:` id surfaces its routing config. **Then honestly refuse the still-unmodeled parts:** per-milestone target minutes, whether a SPECIFIC case is on-track or breached, and real-time agent capacity/availability are live-only — direct the user to the Salesforce UI's **Entitlement**/**Milestones** related lists. The record-level `Entitlement` assignment and `ServiceContract` remain entirely unmodeled. Do **not** invent SLA numbers from general Salesforce-product knowledge. |9697A few cues to break ties:9899- The user asks "what does this **field** mean" → field meaning.100- The user asks "what does this **object** mean" or "what's this101 object for" → org-wide vocabulary lookup (object scope).102- The user asks "**why can't I see** this record" → defer to103 `admin-sharing-troubleshooting`. The user asks "**why can't I104 edit** this field" → why-can't-I-do-X, field-level permission105 flavor.106- The user asks "what **happens** when I close" → what-happens-when.107 The user asks "what **does** the close-opportunity flow do" →108 process explanation (with the honest refusal about narration).109- The user names a report or dashboard **folder/UI path** → honest UI redirect110 (where-do-I-find). Field-usage questions → check folded report/dashboard111 properties or `sfi.find_field_anywhere`.112- The user names a list view **by UI name only** → honest UI redirect; column113 field-ref questions → graph tools (`find_field_anywhere`, `list_components`).114- The user names an SLA, entitlement, milestone, or case-routing/115 queue-assignment question → SLA / entitlement / routing (partial116 answer for the modeled process/milestone/routing metadata, honest117 refusal for target minutes and live status).118119## When to fire120121Fire this skill on business-user-flavored phrasing — the user is122asking in job vocabulary, not metadata vocabulary. The frontmatter123description is the auto-activation signal; this section is the124human-readable enumeration. Concrete triggers:125126- **"What does X mean / what is X for?"** — "what does the Product127 Name field mean?", "what is this field?", "what's the Industry128 field for?", "what's an Order Line?".129- **"What happens when…"** — "what happens when I close this130 opportunity?", "what happens when I save this case?", "what runs131 when I update an Account?".132- **"Why can't I…"** — "why can't I edit this record?", "why133 doesn't this button work?", "why am I seeing insufficient134 privileges?", "why is this field read-only?".135- **"Is this required?"** — "is this field required?", "do I have136 to fill this in?", "is the Email field mandatory?".137- **"Where do I find…"** — "where do I find the Pipeline report?",138 "how do I run the Won Deals dashboard?", "where's the list view139 for My Cases?".140- **"What's the SLA / entitlement on…" / "How are cases routed…"** —141 "what's the SLA on this case?", "when does this escalate?",142 "what's the entitlement on this account?", "how are cases routed143 to agents?", "what's this queue's routing config?".144- **"Explain this process / flow to me."** — "explain this145 approval process", "walk me through the close-opportunity flow",146 "what does this automation do?".147- **"How is this calculated?"** — "how is this rollup148 calculated?", "what's the formula behind this field?". (Treat as149 field-meaning intent; surface `properties.formula` from the150 field's `get_component` response, plus its declared `references`151 edges.)152153## When NOT to fire154155Defer to another skill or refuse honestly when:156157- **The user is asking a cross-component impact question** ("what158 breaks if I change this field?", "what depends on this object?",159 "is it safe to deprecate this?"). Fire `architect-impact-analysis`160 instead — that's the architect persona's tool, and it has its own161 boundary-disclosure discipline.162- **The user is asking an Apex-source question** ("where is this163 field used in Apex?", "what classes call this method?", "find164 references to `MyClass`"). Fire `developer-apex-refactor`165 instead.166- **The user is asking specifically about record-level167 visibility / sharing** ("why can't user X see record Y?", "who168 can see this record?", "what's the visibility for this account?").169 Fire `admin-sharing-troubleshooting` instead. The boundary170 between this skill's "why-can't-I-do-X" intent and171 `admin-sharing-troubleshooting` is: **if the question is about172 *whether the user has access to a record*, defer to admin173 sharing. If the question is about *what a field/button does or174 means*, stay here.**175- **The user is asking a schema-shape question** ("what fields176 does Account have?", "list every object in the org", "show me177 every flow"). Fire `answering-org-questions` instead — that178 skill's schema and dependency intents are the right cascade.179- **The user is asking for a refresh, init, or vault status check.**180 Fire `refreshing-the-org-vault`, `/sfi-init`, or181 `pre-flight-checks`.182- **The user is asking a generic Salesforce-product question** with183 no reference to *their* org ("what is an opportunity stage?",184 "how does email-to-case work?"). These are general-knowledge185 questions, not vault questions. Answer briefly from general186 knowledge **and** offer to look at how their org actually uses187 the concept — but flag clearly that the first half of the answer188 is generic, not org-specific.189- **The user wants record-level data for a specific instance they190 are viewing** ("what's the current value of THIS account's191 Industry?", "how many cases are open?"). Record-level lookups192 work for `__mdt` records and CustomSetting records via193 `sfi.lookup_record` (v1.6+). **HOWEVER:** record-level data for194 the specific instance the user is viewing (e.g., "this Account I'm195 looking at") is still NOT in the graph. v1.6 surfaces all196 *extracted* records, not the one the user is currently viewing.197 Tell the user plainly; suggest they query their org or check the198 record directly in the UI.199200## Steps201202Walk these in order.203204### Step 1 — Classify the question into an intent205206Pick exactly one intent from the table above. If the question207straddles two — "what does this field mean **and** why can't I208edit it?" — answer the more specific one first (field meaning),209then offer to run the second (defer to210`admin-sharing-troubleshooting` for the why-can't-I half if211record-level).212213If the question's intent is where-do-I-find, **stop and refuse214honestly** — do not call any tool to fish for an unmodeled answer.215SLA / entitlement / routing and process-explanation are **hybrids**:216call `sfi.search_components` / `sfi.get_component` (/ `sfi.get_edges`)217for the metadata that IS modeled, present it, then refuse honestly218for the part that isn't (target minutes and live status for SLA /219entitlement / routing; narrated prose for process explanation). See220*Anti-patterns* below for why "I'll try anyway" on the *unmodeled*221half is the wrong move.222223### Step 2 — Normalize the user's phrase into a canonical ID224225Business users almost never type canonical IDs. They say "the226Product Name field", "this opportunity", "the Account object". The227normalization rule:228229- If the user's phrase already looks like a canonical ID230 (`CustomField:Account.Industry__c`,231 `CustomObject:Order_Line__c`), use it directly.232- Otherwise, call `sfi.search_components` with the user's exact233 phrase (e.g., `{ query: 'Product Name' }`). Use the top match if234 the `score` is clearly above the runner-up; otherwise surface the235 top 3 to the user and ask which one they meant.236- Narrow with `types: ['CustomField']` only when the user's237 phrasing explicitly pins the type ("the Product Name **field**" →238 `types: ['CustomField']`; "Order Line" alone → no239 `types`, let the search rank).240- **Never** synthesize a canonical ID by guessing the suffix241 ("Product Name" → `Product_Name__c`). Guessed IDs hit `not-found`,242 burn a turn, and a different naming convention may apply in243 *this* org.244245### Step 3 — Call the routed tool246247Pick the tool per the intent table. Common patterns:248249- **Field meaning** — `sfi.get_component` with the250 `CustomField:` ID. Read `properties.description` first; fall251 back to `properties.label` and `properties.inlineHelpText` if252 description is null.253- **Required check** — `sfi.get_component` on the field; read254 `properties.required`. If `false`, also call `sfi.list_components`255 filtered to `type: 'ValidationRule'` and `parentId` of the256 field's parent object to see if a validation rule makes the257 field effectively required.258- **What-happens-when** — `sfi.list_components({ type: 'Flow',259 parentId: 'CustomObject:Account' })`, then `... 'ApexTrigger' ...`,260 then `... 'ValidationRule' ...`. List the components by canonical261 ID. Do **not** narrate what each one does.262- **Why-can't-I-do-X (field-level permission)** —263 `sfi.get_edges({ nodeId: 'CustomField:...', direction: 'in',264 edgeType: 'grantedBy' })`. The returned edges name the265 `PermissionSet:` or `Profile:` nodes that grant access; if none266 of those match the user's profile / permset, that's the answer.267- **Org-wide vocabulary lookup** — `sfi.get_component` on the268 `CustomObject:` ID; read `properties.description`. Optionally269 follow with `sfi.list_components({ type: 'CustomField',270 parentId: 'CustomObject:...' })` for a fields summary.271272If a tool returns `{ error: { kind: 'component-not-found', ... } }`,273go back to Step 2 with synonyms or a broader search.274275### Step 4 — Translate the result into business-user English276277This is where most skills fail the business-user persona. The raw278tool response is in metadata vocabulary; the answer needs to be in279job vocabulary. Apply these rules:280281- **Prefer `properties.label` over `apiName`.** Say "Product Name",282 not "`Product_Name__c`". Mention the API name once, in283 parentheses, only if the user asked or is clearly an admin.284- **Translate booleans.** `properties.required: true` → "Yes, this285 field is required." `false` → "No, it's optional" (and check286 validation rules per Step 3).287- **Translate types.** `properties.dataType: 'Text'` → "free-text288 field". `'Picklist'` → "a dropdown with these options:" followed289 by `properties.picklistValues`. `'Number'` → "a numeric field".290 `'Date'` / `'DateTime'` → "a date field". `'Lookup'` → "a link291 to another record" (and name `properties.referenceTo`).292- **Hide canonical IDs unless asked.** A business user who asks293 "what does Product Name mean" does not want294 `CustomField:Order_Line__c.Product_Name__c` in the answer.295 Keep the ID in a "for your admin" footer if they want it.296- **Use `properties.description` verbatim when it exists.** It was297 written by an admin for exactly this purpose. Don't paraphrase298 or "improve" it. If empty, fall back to `properties.label` +299 `properties.inlineHelpText`. If both are empty, **honestly admit300 v1.x can't tell** (see Step 5 / Anti-patterns).301302### Step 5 — Refuse honestly when v1.x can't tell303304Seven refusal conditions, each with a fixed-form response:3053061. **Description is null AND label is opaque** — "The metadata for307 this field has no description and no help text. v1.x can't308 describe what it's for beyond the label `{label}`. Ask your309 admin, or check the field's setup page in Salesforce."3102. **User asked for a report, dashboard, or list view** — "Reports,311 dashboards, and list views aren't extracted in v1.x. Try the312 **Reports** tab or **Dashboards** tab in the Salesforce UI for313 `{org}`."3143. **User asked for SLA target minutes or live entitlement/milestone315 status** — "This org's EntitlementProcess/MilestoneType metadata316 is extracted (which milestones apply, whether the process is317 active, business hours), but not the per-milestone target318 minutes or whether THIS case is currently on-track or breached —319 that's live, per-record data. Check the case's **Entitlement**320 and **Milestones** related lists in Salesforce, or ask your321 admin."3224. **User asked for process narration (Flow XML → English)** —323 "v1.x stores Flow XML and Apex source as text, but it doesn't324 translate them to a plain-English narrative. I can list what325 the flow contains (decisions, record-updates, action-calls),326 surface the source text, or you can ask your admin to walk you327 through it."3285. **User asked for record-level data on the specific instance329 they are viewing** — "v1.6 extracts Custom Metadata Type and330 Custom Setting records (use `sfi.lookup_record` /331 `sfi.explain_field` for those), but it does NOT read the332 specific Account / Case / Opportunity record you are looking333 at. To answer what *this specific* record looks like, open it334 in Salesforce or query with `sf data query`."3356. **Record value comes back masked (v1.6)** — `sfi.lookup_record`336 or `sfi.explain_field` returned an entry with `value: null,337 isMasked: true`. Tell the user: "That field is protected by338 Salesforce — the value is serialized as `***` in the metadata339 API, which is the marker for a managed-package or protected340 value. To see the actual value, open the record in **Setup →341 Custom Metadata Types** in Salesforce, or ask the package342 owner / admin." Do NOT guess the underlying value.3437. **CustomSetting record returns `component-not-found` (v1.6)** —344 CustomSettings are rarely serialized via `sf project retrieve`.345 Tell the user: "CustomSetting records often live in the running346 org's data layer rather than in DX metadata, so v1.6 didn't347 extract this one. Query the org directly with `sf data query`348 against the CustomSetting's `__c` table, or check **Setup →349 Custom Settings → Manage** in Salesforce."350351### Step 6 — Never use general Salesforce-product knowledge as a stand-in352353This is the constitutional rule. When the org's `description` is354empty, **do not** fall back to your training-data sense of what355"Industry" or "Stage" means on standard objects. The org may have356customized the standard semantics; the metadata is silent; the357right answer is "I can't tell, ask your admin," not "Industry is358the company's primary line of business." A confident generic359answer is worse than an honest refusal: the user has no way to360spot the fabrication.361362## Reporting format363364Worked example. User asks: *"What does the Product Name field365mean?"*366367Claude's flow:3683691. **Classify** → field-meaning intent.3702. **Normalize** → `sfi.search_components({ query: 'Product Name',371 types: ['CustomField'] })` → top match372 `CustomField:Order_Line__c.Product_Name__c` with score 0.94.3733. **Call** `sfi.get_component({ id: 'CustomField:Order_Line__c.Product_Name__c' })`.3744. **Read** `properties.description: "The display name of the375 product this order line links to, copied from Product__c.Name at376 creation."` and `properties.label: 'Product Name'` and377 `properties.dataType: 'Text'` and `properties.required: true`.3785. **Translate and respond:**379380> **Product Name** is a free-text field on Order Line that381> stores the name of the product this order line links to. The382> admin's description says: *"The display name of the product this383> order line links to, copied from `Product__c.Name` at creation."*384> It's required (you have to fill it in).385>386> If you want the full metadata: this is387> `CustomField:Order_Line__c.Product_Name__c` in the org388> vault.389390Note what the response does and doesn't do:391392- Uses the **label** ("Product Name"), not the API name, as the393 primary identifier.394- Quotes `properties.description` verbatim — it's the admin's395 authored answer.396- Translates `dataType: 'Text'` → "free-text field" and397 `required: true` → "required (you have to fill it in)".398- Hides the canonical ID in a "for your admin" footer.399- Does **not** invent generic meaning ("Product Name is the name of400 the product") if the description had been empty.401402**Hybrid example — partial answer + honest refusal (R6-18).** User asks:403*"What's the SLA on this case?"*404405Claude's flow:4064071. **Classify** → SLA / entitlement / routing intent.4082. **Normalize + call.** `sfi.search_components({ query: 'Case' })` to409 confirm the object, then `sfi.list_components({ type:410 'EntitlementProcess' })` (or `sfi.get_component` on the resolved411 process id) plus `sfi.get_edges` to list the `MilestoneType`412 targets it references.4133. **Respond:**414415> This org's Case entitlement process is **"Gold Support Process"**416> (active). It defines three milestones: **First Response to417> Customer**, **Escalate Case**, and **Close Case**.418>419> What I *can't* tell you from the vault: the target minutes for420> each milestone, and whether *this specific* case is currently421> on-track or breached — that's live, per-record timer data. Check422> the case's **Entitlement** and **Milestones** related lists in423> Salesforce, or ask your admin for the target times.424425The response grounds the process/milestone SHAPE in real extracted426metadata (EntitlementProcess/MilestoneType, R6-18), then honestly427refuses the live number instead of fabricating "24 hours."428429## Boundary disclosure430431v1.x's coverage for the business-user persona has well-defined432gaps. Surface this list when the user asks something v1.x can't433answer, and **always** include the relevant subset when refusing:434435- **Reports, dashboards, list views.**436 - **Reports/dashboards:** default refresh folds top-500-by-usage field usage437 onto `CustomField` nodes (`usedInReport` / `usedInDashboard`); beyond-cap438 members stay `pending`. Full coverage: `sfi refresh --with-reports`.439 Report/Dashboard NODES are persisted as `Report:{LeafFolder}/{Name}` /440 `Dashboard:{LeafFolder}/{Name}` (so folder names ARE in the vault), capped per441 type — when capped, the coverage row goes `pending`. Report filter VALUES442 and dashboard running-users are never vaulted; UI navigation paths are not443 modeled.444 - **List views:** column field refs are extracted (`ListView` nodes +445 `references` edges); filter evaluation and UI picker paths are not composed446 in `field_360`. Use `sfi.find_field_anywhere` / `sfi.list_components`.447- **Record-level data.** v1.6 added CustomMetadataRecord and448 CustomSettingRecord extraction (use `sfi.lookup_record` /449 `sfi.explain_field`). Questions about the specific Account /450 Case / Opportunity record the user is currently viewing ("what451 does THIS account's `Industry` field say?") are still NOT452 covered — v1.6 surfaces extracted config records, not live453 transactional data. Direct the user to `sf data query` for those.454- **Flow-to-narrative translation.** v1.x stores Flow XML as text455 and extracts its readsFrom / writesTo / triggersOn / callsApex456 edges, but it does **not** narrate the Flow's behavior in plain457 English. The user gets a list of what the flow touches, not a458 story.459- **Apex-to-narrative translation.** Same boundary for Apex460 classes and triggers — text storage and heuristic edge461 extraction, no semantic narration.462- **Entitlements, milestones, SLAs (R6-18: partial).**463 `EntitlementProcess` (name, active, `SObjectType`, version,464 business hours, referenced `MilestoneType` names) and465 `MilestoneType` (description, recurrence) ARE extracted466 ComponentTypes — the process/milestone SHAPE is answerable.467 Per-milestone target minutes, live on-track/breached status, the468 record-level `Entitlement` assignment, and `ServiceContract` are469 still NOT modeled (live/record-level only). (`EscalationRule` was470 listed here as unmodeled in an earlier revision of this skill —471 that was stale; it has been an extracted ComponentType since472 v1.3.)473- **Omni-Channel routing (R6-18).** `ServiceChannel` (related474 object via `relatedEntityType`, capacity model) and475 `QueueRoutingConfig` (routing model, capacity weight, push476 timeout, overflow queue) are extracted, including the477 `Queue -> QueueRoutingConfig` `references` edge. Real-time agent478 capacity/availability/presence is still live-only.479- **Quote, Order, Contract approval routing (R7-W8-skill: corrected —480 ApprovalProcess was listed here as uncovered; that was stale).**481 `ApprovalProcess` IS an extracted ComponentType (since v1.3, object-agnostic482 — Opportunity, Quote, Order, Contract, or any custom object): approval483 steps and their approver chains (user / role / role-subordinates / queue /484 group / hierarchy-field variants), entry-criteria conditions, field-update485 actions (`writesTo` edges to the `CustomField` each step sets), and486 approval/rejection email alerts (`sendsEmail` edges) are all modeled — the487 DECLARED routing shape is answerable. What's still NOT modeled: a specific488 record's LIVE in-flight approval state (who it's currently pending on, how489 long it's been sitting, its history) — that's live/record-level data, same490 boundary as the Entitlements bullet above.491- **Live state.** Queue depth, recent errors, governor-limit492 consumption, currently-running jobs — none of this is in v1.x.493 The vault is a snapshot at refresh time.494- **Record data.** Field values, record counts, who owns what, when a495 record last changed. The vault holds schema and source, not rows.496 Route record questions to the opt-in live plane or the UI.497498The three families this list used to name as "not yet covered" are all499MODELED component types today — do not refuse them: record types and500page-layout assignment by record type (route to501`admin-page-layout-routing`); UI tabs, apps, quick actions, paths,502global value sets, custom labels, static resources; and email503templates, letterheads and communication sites (route to504`admin-legacy-automation`).505506Treat **honest refusal** as a feature, not a failure mode. A507business user trusts a confident answer; the protection against508fabricated answers is naming the gap explicitly.509510## Anti-patterns511512| Mistake | Why it's wrong |513|---|---|514| Answering "what does this field mean" with generic Salesforce documentation when `properties.description` is empty. | Silent fabrication. The user has no way to tell the answer wasn't grounded in their org. Refuse honestly — "the description is empty in this org's metadata; ask your admin" — and stop. |515| Translating `Product_Name__c` to "Product Name" as a *guess* at the label instead of reading `properties.label`. | API-name-to-label translation is unreliable: an admin may have set the label to "Class Title" while the field is still named `Product_Name__c`. Always read `properties.label`. |516| Confidently answering "the SLA on this case is 24 hours" because that's a common default. | v1.x models WHICH milestones apply (EntitlementProcess/MilestoneType) but not their target minutes. Surface the milestone names/process, then refuse the number and point to the Entitlement related list — any specific SLA number you state is fabricated. |517| Forwarding the user's literal question to a tool input ("`sfi.get_component({ id: 'the customer field' })`"). | The tool requires a canonical ID. The right move is `sfi.search_components({ query: 'the customer field' })` first, then `sfi.get_component` on the top match. |518| Narrating a Flow as plain-English prose by reading the XML. | v1.x doesn't have a Flow-to-narrative renderer. The honest answer is "I can list what the flow contains, but I don't translate Flow XML to a story." Offer the structured list; refuse the prose. |519| Defaulting to `admin-sharing-troubleshooting` for every "why can't I" question. | The routing distinction matters: record-level visibility ("can the user open this record at all") is admin-sharing. Field-level permission ("why is this field grayed out") is this skill's why-can't-I-do-X intent with field-permission flavor. Picking the wrong route loads the wrong tool. |520| Mixing the user's question with another org's conventions. | Each org is private and specific. Even if 95% of orgs use Stage on Opportunity for sales pipeline, *this* org may have customized it. Always read from `properties.description`; never blend with generic Salesforce-product knowledge. |521| Hiding `unknown` or empty-description outcomes to make the answer look complete. | The user makes a downstream decision (filling out a field, telling a customer an SLA). An incomplete answer presented as complete causes a worse outcome than a complete refusal. Surface the gap. |522| Citing canonical IDs (`CustomField:Order_Line__c.Product_Name__c`) as the primary answer. | Canonical IDs are admin / architect / developer vocabulary. A business user wants "Product Name" and a one-paragraph explanation. Keep IDs in a footer or hide them unless asked. |523| Trying to answer "where do I find the Pipeline report" by guessing a Reports folder name. | Don't guess — but don't refuse outright either. `Report:` nodes ARE persisted (id `Report:{LeafFolder}/{DeveloperName}`), so the folder is recoverable from the id when the report was in the capped, usage-ranked pull. If it was not pulled, say NOT CHECKED — not "no such report" — and send the user to the UI. A guessed path sends them to a 404. |524525## Example interactions526527### Happy path — field meaning528529User: *"What does the Product Name field mean?"*530531Claude's flow: classify (field meaning) → search → get_component →532translate. See *Reporting format* above for the full transcript.533534### Routed path — sharing question535536User: *"Why can't I see the Smith account?"*537538Claude's flow:5395401. **Classify** → why-can't-I-do-X, but with *record visibility*541 flavor. This is `admin-sharing-troubleshooting`'s domain, not542 this skill's.5432. **Defer** with a one-sentence handoff: "Record-visibility544 questions are handled by the sharing-troubleshooting skill —545 it'll walk the OWD / role-hierarchy / sharing-rules cascade for546 you. I'll fire it now."5473. The handoff message names *why* the route changed, so the user548 sees the skill boundary rather than experiencing an unexplained549 topic shift.550551### Hybrid — entitlement question (R6-18)552553User: *"What's the SLA on this case?"*554555See the *Reporting format* hybrid example above. The response556surfaces the real extracted process/milestone metadata, then names557the still-live gap (target minutes, on-track/breached status) and558stays honest about that boundary.559560### Partial answer — case routing (R6-18)561562User: *"How are cases routed to agents?"*563564Claude's flow:5655661. **Classify** → SLA / entitlement / routing intent.5672. **Normalize + call.** `sfi.list_components({ type: 'ServiceChannel'568 })` to find the Case channel's `relatedEntityType`, then walk the569 `Queue`s that own Case (`sfi.get_edges` `sharedWith` from570 `CustomObject:Case`) and read each queue's `references` edge to its571 `QueueRoutingConfig`.5723. **Respond:**573574> Cases route through the **Case** Omni-Channel service channel. The575> **Case Queue** uses the **"agent routing"** routing config:576> **Most Available** routing model, capacity weight 5.0, with577> overflow to **Fallback Queue** when the primary queue is full.578>579> What I *can't* tell you: which agents are online right now or their580> current capacity — that's live presence data. Check581> **Omni-Channel Supervisor** in Salesforce for real-time agent582> status.583584The response grounds the routing BEHAVIOR in real extracted metadata585(ServiceChannel/QueueRoutingConfig, R6-18), then honestly refuses the586live presence/capacity half.587588### Honest refusal — process narration589590User: *"Explain the close-opportunity flow to me in English."*591592Claude's flow:5935941. **Classify** → process explanation.5952. **Partial answer + honest refusal.** Run `sfi.search_components`596 to find the flow (e.g., `Flow:Close_Opportunity_Approval`),597 then `sfi.get_component` for the structured view. Surface the598 flow's components from the metadata: triggering object,599 `readsFrom` / `writesTo` / `callsApex` edges. But **refuse to600 narrate** the decision tree in prose.6013. **Respond:**602603> `Flow:Close_Opportunity_Approval` fires on Opportunity update604> when StageName changes to Closed. From the metadata, it:605>606> - reads `CustomField:Opportunity.Amount`607> - writes `CustomField:Opportunity.Close_Approved__c`608> - calls `ApexClass:OpportunityApprovalRouter`609>610> v1.x stores Flow XML as text and extracts these edges, but it611> does **not** translate the flow's decision tree into a story.612> For a narrative walkthrough — what each decision branch does,613> in plain English — ask your admin or open the flow in Setup.614615The response gives the structured partial (the edges v1.x *does*616extract), names the boundary, and points to the manual path for617the prose.618619### Field-level permission620621User: *"Why can't I edit the Annual Revenue field on Account?"*622623Claude's flow:6246251. **Classify** → why-can't-I-do-X, field-permission flavor (not626 record visibility — this is field-level, not record-level).6272. **Normalize** → `sfi.search_components({ query: 'Annual628 Revenue', types: ['CustomField'] })` →629 `CustomField:Account.AnnualRevenue`.6303. **Call** `sfi.get_edges({ nodeId:631 'CustomField:Account.AnnualRevenue', direction: 'in',632 edgeType: 'grantedBy' })`.6334. **Translate**: list the `PermissionSet:` and `Profile:` nodes634 that grant access. Ask which one the user is on.6355. **Respond:**636637> Edit access to **Annual Revenue** on Account is granted by:638>639> - `PermissionSet:Sales_Manager`640> - `PermissionSet:Finance_Reviewer`641> - `Profile:System Administrator`642>643> If you're not on one of those, that's why the field is644> read-only for you. Ask your admin for one of these permission645> sets, or check **Setup → My Personal Information** in646> Salesforce to confirm your current profile and permission sets.647>648> Note: this is field-level *editability* (Field-Level Security).649> If the *record itself* is650651…(truncated)