sf-intelligence 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2315,7 +2315,8 @@ var init_resolve = __esm({
2315
2315
  const kind = c2.wholeExact ? "exact" : rollupKind(matched);
2316
2316
  const nodeTokenSet = new Set(c2.node.tokens);
2317
2317
  const matchedNodeTokens = new Set(matched.map((m2) => m2.matchedToken).filter((t2) => nodeTokenSet.has(t2)));
2318
- coverageById.set(c2.node.id, c2.wholeExact ? 1 : matchedNodeTokens.size / c2.node.tokens.length);
2318
+ const nameCoverage = c2.wholeExact ? 1 : matchedNodeTokens.size / c2.node.tokens.length;
2319
+ coverageById.set(c2.node.id, nameCoverage);
2319
2320
  if (c2.wholeExact)
2320
2321
  wholeExactIds.add(c2.node.id);
2321
2322
  if (c2.parentMatched)
@@ -2328,6 +2329,7 @@ var init_resolve = __esm({
2328
2329
  parentApiName: c2.node.parentApiName,
2329
2330
  score: Number(score.toFixed(6)),
2330
2331
  base: Number(base.toFixed(6)),
2332
+ nameCoverage: Number(nameCoverage.toFixed(6)),
2331
2333
  matchKind: kind,
2332
2334
  evidence: c2.wholeExact ? `exact name match on "${query.trim()}"` : buildEvidence(kind, matched)
2333
2335
  });
@@ -21345,7 +21347,7 @@ var init_live_consent = __esm({
21345
21347
  });
21346
21348
 
21347
21349
  // ../mcp/dist/src/answer-render.js
21348
- var cell, renderTrustFooter, mdTable, renderResolveMarkdown, formatDeltas, renderOrgOverviewMarkdown, renderLiveCountMarkdown, renderFieldPopulationMarkdown, renderInactiveUsersMarkdown, renderRouteMarkdown;
21350
+ var cell, renderTrustFooter, mdTable, renderResolveMarkdown, formatDeltas, renderOrgOverviewMarkdown, renderLiveCountMarkdown, renderFieldPopulationMarkdown, renderInactiveUsersMarkdown, renderContextApplied, renderRouteMarkdown;
21349
21351
  var init_answer_render = __esm({
21350
21352
  "../mcp/dist/src/answer-render.js"() {
21351
21353
  "use strict";
@@ -21459,9 +21461,16 @@ ${table}${capped}
21459
21461
 
21460
21462
  ${renderTrustFooter(data.trust)}`;
21461
21463
  };
21464
+ renderContextApplied = (contextApplied) => {
21465
+ const substituted = contextApplied.substitutedComponentId ?? contextApplied.selection;
21466
+ return `Context applied: '${contextApplied.anaphor}'` + (substituted !== void 0 ? ` \u2192 ${substituted}` : "") + " (from previous turn)" + (contextApplied.inheritedTool !== void 0 ? `; tool inherited: \`${contextApplied.inheritedTool}\`.` : ".");
21467
+ };
21462
21468
  renderRouteMarkdown = (route) => {
21469
+ const contextLine = route.contextApplied !== void 0 ? `
21470
+
21471
+ ${renderContextApplied(route.contextApplied)}` : "";
21463
21472
  if (route.plane === "unknown") {
21464
- return `I don't have a tool for **"${route.question}"** yet \u2014 ${route.reason} ${route.gap ? `(logged as \`${route.gap.category}\`).` : ""}`;
21473
+ return `I don't have a tool for **"${route.question}"** yet \u2014 ${route.reason} ${route.gap ? `(logged as \`${route.gap.category}\`).` : ""}` + contextLine;
21465
21474
  }
21466
21475
  const steps = [];
21467
21476
  let plannedTools = route.tools;
@@ -21491,7 +21500,7 @@ ${route.plan.map((step3, i2) => {
21491
21500
 
21492
21501
  Plan: ${steps.join(" \u2192 ")}` + enterprise + clarification + compoundPlan + (route.gap ? `
21493
21502
 
21494
- _Partial: ${route.gap.note}_` : "");
21503
+ _Partial: ${route.gap.note}_` : "") + contextLine;
21495
21504
  };
21496
21505
  }
21497
21506
  });
@@ -24182,7 +24191,7 @@ ${renderTrustFooter(trust)}`;
24182
24191
  import { appendFile as appendFile3, mkdir as mkdir8 } from "node:fs/promises";
24183
24192
  import { homedir as homedir3 } from "node:os";
24184
24193
  import { dirname as dirname18, join as join13 } from "node:path";
24185
- var riskForIntent, routeFromRule, routeForSelectedIntent, normalize, NAMED_COMPONENT_ID, NAMED_FIELD_ID, deriveSaveEvent, deriveKnowledgeTopic, deriveListType, FIELD_PARENT_OBJECTS, deriveFieldListParent, deriveMetadataParentId, NON_OBJECT_CAPTURES, deriveObjectApiFromQuestion, deriveImpactHops, FIELD_MAP_OBJECT, deriveFieldMappingArgs, deriveLayoutForUserArgs, derivePiiInventoryArgs, deriveMetadataCountArgs, deriveOmniType, routeText, RULES, alternativeFromIntent, semanticAlternatives, intentLabel, classifyQuestion, gapLogPath, logGapIfAny;
24194
+ var riskForIntent, routeFromRule, routeForSelectedIntent, normalize, NAMED_COMPONENT_ID, NAMED_FIELD_ID, deriveSaveEvent, deriveKnowledgeTopic, deriveListType, FIELD_PARENT_OBJECTS, deriveFieldListParent, deriveMetadataParentId, NON_OBJECT_CAPTURES, deriveObjectApiFromQuestion, deriveImpactHops, FIELD_MAP_OBJECT, deriveFieldMappingArgs, deriveLayoutForUserArgs, derivePiiInventoryArgs, deriveMetadataCountArgs, deriveOmniType, routeText, RULES, alternativeFromIntent, semanticAlternatives, intentLabel, COMPLEMENTARY_ALTERNATIVE_PAIRS, NAMES_COMPONENT_FOR_LAST_MODIFIED, classifyQuestion, gapLogPath, logGapIfAny;
24186
24195
  var init_intent_router = __esm({
24187
24196
  "../mcp/dist/src/intent-router.js"() {
24188
24197
  "use strict";
@@ -25159,7 +25168,9 @@ var init_intent_router = __esm({
25159
25168
  needsResolve: false,
25160
25169
  reason: "Login activity (LastLoginDate) is runtime state that lives only in the org.",
25161
25170
  patterns: [
25162
- /\b(who|users?|people)\b.*\b(log(ged)?\s?in|login|active|inactive|dormant)\b/,
25171
+ // `in(to)?` — "which users logged INTO the org last week" is the same
25172
+ // LastLoginDate read; the bare `in\b` missed the fused "into" (P4).
25173
+ /\b(who|users?|people)\b.*\b(log(ged)?\s?in(to)?|login|active|inactive|dormant)\b/,
25163
25174
  /\b(inactive|dormant|stale|unused)\b.*\busers?\b/,
25164
25175
  /\bhaven'?t\b.*\blog(ged)?\s?in\b/,
25165
25176
  /\blast\s+login\b/,
@@ -25184,15 +25195,24 @@ var init_intent_router = __esm({
25184
25195
  // near a field/value (so "empty queues" doesn't get swallowed here).
25185
25196
  /\b(populated|filled)\b/,
25186
25197
  // "field population for X" / "population rate" — the noun "population"
25187
- // (vs the adjective "populated"). Battery gap.
25188
- /\b(field\s+)?population\b/,
25198
+ // (vs the adjective "populated"). Battery gap. Guarded (P4): "the FLOW
25199
+ // THAT fires the 'General Population RR Group' step" uses "population"
25200
+ // inside a quoted flow-step name — a flow-search ask, not field fill.
25201
+ /^(?!.*\bflows?\s+that\b).*\b(field\s+)?population\b/,
25189
25202
  // `fields?`/`values?` — `\b(field|value)\b` missed the PLURALS, so
25190
25203
  // "which Account fields are empty" fell through to metadata-count
25191
25204
  // (vault plane) instead of this hybrid live-data intent.
25192
25205
  /\b(empty|blank|null)\b.*\b(fields?|values?)\b/,
25193
25206
  /\b(fields?|values?)\b.*\b(empty|blank|null|populated|filled)\b/,
25194
25207
  /\bhow\s+many\b.*\b(have|with|without)\b.*\b(field|value|filled|set)\b/,
25195
- /\b(actually|really)\s+(populated|filled)\b/
25208
+ /\b(actually|really)\s+(populated|filled)\b/,
25209
+ // Router-v2 P4 needs-live reachability: "fill rate", "completeness of
25210
+ // key fields", and "how many X have a blank Y" are all the same live
25211
+ // per-field population read. Queue/group emptiness and missing
25212
+ // DESCRIPTIONS stay vault (excluded).
25213
+ /\bfill\s+rates?\b/,
25214
+ /\bcompleteness\b.*\b(fields?|data|records?)\b|\b(fields?|data)\b.*\bcompleteness\b/,
25215
+ /\bhow\s+many\b(?!.*\b(queues?|groups?|descriptions?|help\s+text)\b)[^.?!]*\b(blank|null|missing)\b/
25196
25216
  ]
25197
25217
  },
25198
25218
  {
@@ -25228,7 +25248,11 @@ var init_intent_router = __esm({
25228
25248
  /^(?!.*(__mdt\b|\bcustom\s+metadata\b|\bcustom\s+settings?\b)).*\b(show|give|sample|example)s?\b.*\b(records?|rows?)\b/,
25229
25249
  /\b(show|give)\s+me\s+\d+\b/,
25230
25250
  /\bsample\s+\d+\b/,
25231
- /\b\d+\s+(sample|example)\s+\w+\b/
25251
+ /\b\d+\s+(sample|example)\s+\w+\b/,
25252
+ // Router-v2 P4: a literal Salesforce record ID (15/18 chars, leading 0 +
25253
+ // keyprefix — "did lead 00Q5x000004abcd convert?") is a live row lookup;
25254
+ // the vault never holds record data.
25255
+ /\b0[0-9a-z]{2}[0-9a-z]{12}(?:[0-9a-z]{3})?\b/
25232
25256
  ]
25233
25257
  },
25234
25258
  {
@@ -25290,7 +25314,10 @@ var init_intent_router = __esm({
25290
25314
  // components (P0a). Keyed on the word "users" alone — metadata-count
25291
25315
  // questions ("how many profiles / layouts / … , and which is assigned to
25292
25316
  // each profile") never say "users", so they still fire here.
25293
- /\bhow\s+many\b(?!.*\busers?\b).*\b(page\s+layouts?|layouts?|custom\s+objects?|profiles?|permission\s+sets?|validation\s+rules?|flows?|(apex\s+)?classes?|triggers?|record\s+types?|list\s+views?|report\s+types?|record\s+pages?|flexipages?|approval\s+process(es)?|custom\s+settings?|quick\s+actions?|sharing\s+rules?|named\s+credentials?|picklists?)\b/,
25317
+ // The second lookahead (P4): "how many RECORDS per record type" is a
25318
+ // live GROUP BY over record data — the metadata noun ("record type") is
25319
+ // the grouping key, not the thing being counted.
25320
+ /\bhow\s+many\b(?!.*\busers?\b)(?!.*\brecords?\s+(?:per|by|for\s+each)\b).*\b(page\s+layouts?|layouts?|custom\s+objects?|profiles?|permission\s+sets?|validation\s+rules?|flows?|(apex\s+)?classes?|triggers?|record\s+types?|list\s+views?|report\s+types?|record\s+pages?|flexipages?|approval\s+process(es)?|custom\s+settings?|quick\s+actions?|sharing\s+rules?|named\s+credentials?|picklists?)\b/,
25294
25321
  /\bhow\s+many\b.*\blayouts?\b.*\b(per|for\s+each|by)\b.*\bprofiles?\b/,
25295
25322
  /\blayouts?\b.*\b(per|for\s+each|by)\b.*\bprofiles?\b/,
25296
25323
  // fields, but NOT field usage/population (those are unused-fields / field-population)
@@ -25312,7 +25339,16 @@ var init_intent_router = __esm({
25312
25339
  /\b(by|per)\s+(status|stage|type|industry|record\s+type)\b/,
25313
25340
  /\bhow\s+many\b.*\b(in|with)\s+(each|every)\b/,
25314
25341
  // "how many Applications with status Submitted" — filtered live COUNT, not vault metadata-count (B21).
25315
- /\bhow\s+many\b.*\bwith\s+(status|stage|type|record\s+type)\b/
25342
+ /\bhow\s+many\b.*\bwith\s+(status|stage|type|record\s+type)\b/,
25343
+ // Router-v2 P4: imperative "count X grouped by Y" / "count of X by Y" /
25344
+ // "count X records by Y" — the same live GROUP BY, phrased without
25345
+ // "how many".
25346
+ /\bcount\b[^.?!]{0,60}\bgrouped?\s+by\b/,
25347
+ /\bcount\s+of\b[^.?!]{0,60}\bby\b/,
25348
+ /\bcount\b[^.?!]{0,60}\brecords?\s+by\b/,
25349
+ // "who's in the ADA Team Queue" — queue/public-group membership is a
25350
+ // live GroupMember read; the vault holds the queue definition only.
25351
+ /\bwho(?:'?s|\s+is|\s+are)?\b[^.?!]{0,40}\bin\s+the\b[^.?!]{0,50}\b(queues?|public\s+groups?)\b/
25316
25352
  ]
25317
25353
  },
25318
25354
  {
@@ -25325,7 +25361,10 @@ var init_intent_router = __esm({
25325
25361
  patterns: [
25326
25362
  /\b(average|avg|mean|minimum|min|maximum|max|sum|total)\b.*\b(field|value|amount|revenue|score)\b/,
25327
25363
  /\bwhat\s+is\s+the\b.*\b(average|avg|min|max|sum)\b/,
25328
- /\bhow\s+(big|large|small)\b.*\b(on\s+average|average)\b/
25364
+ /\bhow\s+(big|large|small)\b.*\b(on\s+average|average)\b/,
25365
+ // Router-v2 P4: "average number of X per Y" — a live AVG/GROUP BY ask
25366
+ // that named no field noun.
25367
+ /\baverage\s+number\s+of\b/
25329
25368
  ]
25330
25369
  },
25331
25370
  {
@@ -25336,8 +25375,12 @@ var init_intent_router = __esm({
25336
25375
  needsResolve: false,
25337
25376
  reason: "Duplicate detection needs live GROUP BY + HAVING on field values.",
25338
25377
  patterns: [
25339
- /\bduplicate\b.*\b(records?|values?|emails?|contacts?|accounts?|fields?|rows?)\b/,
25340
- /\b(records?|values?|emails?|contacts?|accounts?)\b.*\bduplicate\b/,
25378
+ // `duplicat\w*` — "most DUPLICATED email domain" is the same live GROUP
25379
+ // BY + HAVING read; the bare `duplicate` missed the participle (P4).
25380
+ /\bduplicat\w*\b.*\b(records?|values?|emails?|domains?|contacts?|accounts?|fields?|rows?)\b/,
25381
+ // Guarded: "which FIELDS are duplicated" is a SCHEMA-redundancy ask
25382
+ // (vault), not live record duplicates — exclude the fields subject.
25383
+ /^(?!.*\bfields?\b[^.?!]{0,40}\bduplicat)\b.*\b(records?|values?|emails?|domains?|contacts?|accounts?)\b.*\bduplicat\w*\b/,
25341
25384
  /\bsame\b.*\b(email|phone|name|value)\b/
25342
25385
  ]
25343
25386
  },
@@ -25360,7 +25403,14 @@ var init_intent_router = __esm({
25360
25403
  // vault cannot answer it at all (eval family D).
25361
25404
  /\b(which|what)\b[^.?!]{0,40}\bobjects?\b[^.?!]{0,50}\bempty\b/,
25362
25405
  /\bempty\b[^.?!]{0,20}\b(custom\s+)?objects?\b/,
25363
- /\bobjects?\b[^.?!]{0,40}\b(no|zero|barely\s+any|hardly\s+any)\s+(records?|rows?|data)\b/
25406
+ /\bobjects?\b[^.?!]{0,40}\b(no|zero|barely\s+any|hardly\s+any)\s+(records?|rows?|data)\b/,
25407
+ // Router-v2 P4: growth and per-object size asks are the same live
25408
+ // per-object COUNT read ("which objects have grown the fastest",
25409
+ // "inventory of every custom object and roughly how big each is").
25410
+ /\bobjects?\b[^.?!]{0,60}\b(grown|growth|growing)\b/,
25411
+ /\b(grown|growth|growing)\b[^.?!]{0,60}\bobjects?\b/,
25412
+ /\bobjects?\b[^.?!]{0,60}\bhow\s+big\b/,
25413
+ /\bhow\s+big\b[^.?!]{0,60}\bobjects?\b/
25364
25414
  ]
25365
25415
  },
25366
25416
  {
@@ -25371,8 +25421,10 @@ var init_intent_router = __esm({
25371
25421
  needsResolve: false,
25372
25422
  reason: "A record COUNT is live data; the offline vault holds metadata only.",
25373
25423
  patterns: [
25374
- /\bhow\s+many\b.*\b(records?|rows?|accounts?|contacts?|opportunit|leads?|cases?)\b/,
25424
+ /\bhow\s+many\b.*\b(records?|rows?|accounts?|contacts?|opportunit(?:y|ies)?|leads?|cases?)\b/,
25375
25425
  /\b(count|number)\s+of\b.*\b(records?|rows?)\b/,
25426
+ // Router-v2 P4: imperative "count <X> records" (no "of", no "how many").
25427
+ /\bcount\b[^.?!]{0,60}\brecords?\b/,
25376
25428
  /\bhow\s+many\b.*\bin\s+(the\s+)?(org|production|prod)\b/,
25377
25429
  /\blive\s+count\b/,
25378
25430
  // A TEMPORAL qualifier cues live data regardless of the noun — "how
@@ -25382,7 +25434,7 @@ var init_intent_router = __esm({
25382
25434
  // win: the metadata-count rule sits earlier. fired/ran/logged forms
25383
25435
  // are excluded so automation/login activity asks don't collapse into
25384
25436
  // a bare record count.
25385
- /\bhow\s+many\b(?!.*\b(fired|ran|executed|triggered|logged)\b).*\b(right\s+now|currently|at\s+the\s+moment|as\s+of\s+(now|today)|today)\b/,
25437
+ /\bhow\s+many\b(?!.*\b(fired|ran|executed|triggered|logged)\b).*\b(right\s+now|currently|at\s+the\s+moment|as\s+of\s+(now|today)|today|this\s+(term|semester|quarter))\b/,
25386
25438
  /\b(count|number)\s+of\b.*\b(right\s+now|currently|at\s+the\s+moment|today)\b/,
25387
25439
  // Named sObject totals without the word "records" — "how many Opportunities
25388
25440
  // are there" (TEST-SANDBOX-ROUTER-first-user). Exclude non-SF platforms
@@ -25415,7 +25467,53 @@ var init_intent_router = __esm({
25415
25467
  patterns: [
25416
25468
  /\b(recent(ly)?|last\s+\d+\s+days?|this\s+week|past\s+week)\b.*\b(created|modified|updated|changed|added)\b/,
25417
25469
  /\b(created|modified|updated|new)\b.*\b(recent(ly)?|last\s+\d+\s+days?|this\s+week)\b/,
25418
- /\bwhat\s+(was|were)\b.*\b(created|modified|updated|changed)\b.*\b(recent|last)\b/
25470
+ /\bwhat\s+(was|were)\b.*\b(created|modified|updated|changed)\b.*\b(recent|last)\b/,
25471
+ // Router-v2 P4: "who's been making the most changes lately" / "busiest
25472
+ // user by record edits" — the same recent-modified read, cut by editor.
25473
+ /\bwho\b[^.?!]{0,40}\b(making|made)\b[^.?!]{0,30}\bchanges\b/,
25474
+ /\bbusiest\s+users?\b/
25475
+ ]
25476
+ },
25477
+ {
25478
+ // Live picklist VALUE USAGE — "which Case.Status values are never used",
25479
+ // "distribution of Status values", "most common Resolution_Code__c".
25480
+ // Distinct from picklist-values (vault, DECLARED value set): usage /
25481
+ // distribution / most-common language means live GROUP BY counts per
25482
+ // value (router-v2 P4 needs-live reachability). Placed in the live block
25483
+ // so it wins over the vault picklist-values rule further down.
25484
+ intent: "picklist-usage",
25485
+ plane: "live",
25486
+ tools: ["sfi.live_picklist_usage"],
25487
+ liveRequired: true,
25488
+ needsResolve: false,
25489
+ reason: "How picklist/field VALUES are actually used across records (counts per value, never-used values) is live GROUP BY data \u2014 the vault only declares the value set.",
25490
+ // NOTE the distance windows allow an interior dot (`Case.Status`) — a
25491
+ // dotted field reference must not break the sentence-bounded window.
25492
+ patterns: [
25493
+ /\bpicklist\b(?:[^.?!]|\.(?=\w)){0,80}\b(never\s+used|actually\s+used|usage|distribut\w+|frequen\w+)\b/,
25494
+ /\bvalues?\b(?:[^.?!]|\.(?=\w)){0,60}\b(never|rarely)\s+used\b/,
25495
+ /\b(distribution|breakdown)\s+of\b(?:[^.?!]|\.(?=\w)){0,60}\bvalues?\b/,
25496
+ /\bhow\s+are\b(?:[^.?!]|\.(?=\w)){0,40}\bvalues?\b(?:[^.?!]|\.(?=\w)){0,40}\bdistributed\b/,
25497
+ /\b(most|least)\s+common\b(?:[^.?!]|\.(?=\w)){0,50}\b(value|status|code|__c)\b/,
25498
+ /\b(?:whats?|what\s+is)\s+the\s+most\s+common\b/
25499
+ ]
25500
+ },
25501
+ {
25502
+ // Live automation execution — "did the flow FIRE yesterday?" is a runtime
25503
+ // question about actual executions (FlowInterview / job traces), not the
25504
+ // automation catalog (router-v2 P4). Temporal anchor required so the
25505
+ // metadata asks ("what fires on save") never land here.
25506
+ intent: "automation-fired",
25507
+ plane: "live",
25508
+ tools: ["sfi.live_automation_fired"],
25509
+ liveRequired: true,
25510
+ needsResolve: false,
25511
+ reason: "Whether an automation actually RAN (and when) is runtime execution state \u2014 live_automation_fired reads it; the vault only holds the automation definitions.",
25512
+ patterns: [
25513
+ /\b(flow|trigger|automation|process|it)\b[^.?!]{0,60}\bfired\b[^.?!]{0,50}\b(yesterday|today|last\s+\w+|this\s+\w+|recently)\b/,
25514
+ /\bfired\b[^.?!]{0,30}\b(yesterday|today|last\s+night|recently)\b/,
25515
+ /\bdid\b[^.?!]{0,60}\b(flow|trigger|automation)\b[^.?!]{0,50}\b(fire|run|execute|actually\s+fire)\b/,
25516
+ /\b(flow|trigger|automation)\b[^.?!]{0,40}\b(actually\s+(ran|fired|executed))\b/
25419
25517
  ]
25420
25518
  },
25421
25519
  {
@@ -25429,7 +25527,11 @@ var init_intent_router = __esm({
25429
25527
  /\b(org|org'?s?)\b.*\b(on\s+fire|unhealthy|health|healthy)\b/,
25430
25528
  /\b(failed|faulted|stuck)\b.*\b(jobs?|batch|async|apex\s+job)\b/,
25431
25529
  /\bpaused\b.*\bflows?\b/i,
25432
- /\bis\s+my\s+org\b.*\b(ok|fine|broken|failing)\b/
25530
+ /\bis\s+my\s+org\b.*\b(ok|fine|broken|failing)\b/,
25531
+ // Router-v2 P4: "give me a health snapshot of the org" — the noun order
25532
+ // put "health" before "org", which the first pattern missed, and the
25533
+ // word "snapshot" was stolen by the vault snapshot-diff rule.
25534
+ /\bhealth\s+snapshot\b/
25433
25535
  ]
25434
25536
  },
25435
25537
  {
@@ -25525,7 +25627,13 @@ var init_intent_router = __esm({
25525
25627
  /\b(useless|unused|stale|dead)\b.*\breports?\b/,
25526
25628
  /\b(dashboards?)\b.*\b(unused|stale|broken|refresh)\b/,
25527
25629
  /\breports?\b.*\b(not\s+run|haven'?t\s+been\s+run)\b/,
25528
- /\breports?\b.*\b(last\s+year|in\s+the\s+last)\b/
25630
+ /\breports?\b.*\b(last\s+year|in\s+the\s+last)\b/,
25631
+ // "which reports are ACTUALLY USED" — run-history is live LastRunDate
25632
+ // (router-v2 P4; previously a funnel plane near-tie that BLOCKED).
25633
+ /\breports?\b[^.?!]{0,40}\bactually\s+used\b/,
25634
+ // Router-v2 P4: "reports/dashboards nobody looks at / no one uses" —
25635
+ // the colloquial unused-ask without the word "unused".
25636
+ /\b(reports?|dashboards?)\b[^.?!]{0,60}\b(nobody|no\s+one)\b[^.?!]{0,40}\b(looks?|uses?|runs?|view\w*|open\w*)\b/
25529
25637
  ]
25530
25638
  },
25531
25639
  {
@@ -25544,7 +25652,7 @@ var init_intent_router = __esm({
25544
25652
  /\b(who\s+can|access\s+to)\b.*\bfolders?\b/,
25545
25653
  // Folder-gated artifacts asked about by ACCESS without the word "folder":
25546
25654
  // "who can see/access/view this report/dashboard/document".
25547
- /\bwho\s+can\s+(access|see|view|open)\b.*\b(reports?|dashboards?|documents?)\b/,
25655
+ /\bwho\s+can\s+(access|see|view|open|run)\b.*\b(reports?|dashboards?|documents?)\b/,
25548
25656
  /\b(access|visib(le|ility))\b.*\b(reports?|dashboards?|documents?)\s+folders?\b/,
25549
25657
  /\b(dashboards?|reports?)\b.*\bshared\b.*\b(with|to)\b/
25550
25658
  ]
@@ -26116,6 +26224,8 @@ var init_intent_router = __esm({
26116
26224
  // Admin-level / least-privilege / security-gap phrasings (baseline-300).
26117
26225
  /\b(which|what)\s+users?\b.*\badmin\b/,
26118
26226
  /\busers?\b.*\badmin[-\s]level\b/,
26227
+ // "do we have too many admins" (router-v2 P4).
26228
+ /\btoo\s+many\s+admins?\b/,
26119
26229
  /\bleast\s+privilege\b/,
26120
26230
  /\bsecurity\s+gaps?\b.*\b(profile|permission)/,
26121
26231
  /\bpermission\s+sets?\b.*\binstead\s+of\b.*\bprofiles?\b/
@@ -26849,6 +26959,9 @@ var init_intent_router = __esm({
26849
26959
  patterns: [
26850
26960
  /\b(which|what)\s+flows?\b.*\b(references?|uses?|mentions?|touch(es)?|calls?)\b/,
26851
26961
  /\bflows?\b.*\b(that|which)\b.*\b(references?|uses?)\b/,
26962
+ // Router-v2 P4: "pull the flow that FIRES/SENDS/CREATES the '<step
26963
+ // name>' step" — find-a-flow-by-what-it-does is a Flow-XML grep.
26964
+ /\bflows?\s+that\s+(fires?|sends?|creates?|assigns?|updates?|contains?|does)\b/,
26852
26965
  /\b(sync|stamping|affiliate|marketo|applicant|partner|budget)\b.*\bflows?\b/,
26853
26966
  /\bflows?\b.*\b(sync|stamping|affiliate|marketo|applicant|partner|budget)\b/,
26854
26967
  /\bflows?\b.*\b(send|sends|email)\b/,
@@ -26970,7 +27083,9 @@ var init_intent_router = __esm({
26970
27083
  // runs bulk") or test coverage ("which test class covers Y … the bulk
26971
27084
  // case"), those earlier rules win by order — this guard keeps the bare
26972
27085
  // word from firing even when their patterns miss a phrasing.
26973
- /^(?!.*\b(?:what\s+(?:fires|runs|happens)|test\s+class)\b).*\b(bulk|bulkif|unbounded)\b/,
27086
+ // …and "WHICH CLASS HANDLES the Boomi integration for BULK loads" is a
27087
+ // find-the-class source grep (apex-search), not a limit report (P4).
27088
+ /^(?!.*\b(?:what\s+(?:fires|runs|happens)|test\s+class|(?:which|what)\s+(?:apex\s+)?class(?:es)?\s+(?:handles?|processes?|implements?))\b).*\b(bulk|bulkif|unbounded)\b/,
26974
27089
  /\bcpu\s+time\b/,
26975
27090
  /\bheap\s+size\b/,
26976
27091
  /\bmost\s+dml\b/,
@@ -27301,10 +27416,26 @@ var init_intent_router = __esm({
27301
27416
  liveRequired: false,
27302
27417
  needsResolve: false,
27303
27418
  reason: "Text grep over Apex source + usage edges.",
27419
+ // A dotted CODE LITERAL in the question ("System.debug") is the grep text —
27420
+ // bind it so the routed search_apex_source call is executable as-is.
27421
+ suggestArgs: (q2) => {
27422
+ const literal = q2.match(/\b[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\b/)?.[0];
27423
+ return literal !== void 0 ? { query: literal } : void 0;
27424
+ },
27304
27425
  patterns: [
27305
27426
  /\bfind\b.*\b(class|apex|code)\b.*\b(mentions?|references?|uses?|calls?|reads?|writes?|with)\b/,
27306
27427
  /\b(which|what)\s+(classes?|apex)\b.*\b(mentions?|references?|uses?|touch(es)?|reads?|writes?|calls?|invokes?)\b/,
27307
27428
  /\bsearch\b.*\b(apex|code)\b/,
27429
+ // Router-v2 P4 (q522 family): a CODE-LITERAL search — "does anything
27430
+ // call System.debug", "leftover System.debug statements" — is a source
27431
+ // grep, never the runtime-audit-trail fallback ("debug logs" in the
27432
+ // prose must not swallow it; this rule sits earlier, so the literal
27433
+ // wins).
27434
+ /\bsystem\.debug\b/,
27435
+ /\b(?:calls?|calling|invokes?|references?)\b[^.?!]{0,40}\b[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\s*\(/,
27436
+ // "which apex class HANDLES the Boomi integration" — find-the-class-by-
27437
+ // what-it-does is a source/usage grep, not a code-quality report.
27438
+ /\b(?:which|what)\s+(?:apex\s+)?class(?:es)?\b[^.?!]{0,60}\b(?:handles?|processes?|implements?|integrates?|owns?)\b/,
27308
27439
  // "which classes make HTTP callouts and what endpoints do they hit" —
27309
27440
  // HTTP callouts live in Apex SOURCE, so the answer is a source grep,
27310
27441
  // not the outbound endpoint catalog and never field lineage (eval
@@ -28035,7 +28166,7 @@ var init_intent_router = __esm({
28035
28166
  patterns: [
28036
28167
  /\bfield\s*360\b/,
28037
28168
  /\b360\b[^.?!]{0,20}\b(on|of|for)\b[^.?!]{0,20}(?:[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*|[a-z][a-z0-9_]*__c)\b/,
28038
- new RegExp(`\\b(fulls+(?:picture|profile)|everythings+thats+(?:touches|uses))\\b[^.?!]{0,30}\\b${NAMED_FIELD_ID}\\b`)
28169
+ new RegExp(`\\b(full\\s+(?:picture|profile)|everything\\s+that\\s+(?:touches|uses))\\b[^.?!]{0,30}\\b${NAMED_FIELD_ID}\\b`)
28039
28170
  ]
28040
28171
  },
28041
28172
  {
@@ -28604,6 +28735,12 @@ var init_intent_router = __esm({
28604
28735
  };
28605
28736
  return labels[intent] ?? `the ${intent.replaceAll("-", " ")} analysis`;
28606
28737
  };
28738
+ COMPLEMENTARY_ALTERNATIVE_PAIRS = /* @__PURE__ */ new Set([
28739
+ "impact-analysis|safe-to-delete",
28740
+ "impact-analysis|what-if-field",
28741
+ "who-can-access-object|object-access"
28742
+ ]);
28743
+ NAMES_COMPONENT_FOR_LAST_MODIFIED = /[a-z0-9]_[a-z0-9]|__(?:c|mdt|e|x|b|kav)\b|\w+\.\w+|\b(?:flow|flows|class|classes|trigger|triggers|validation\s+rule|permission\s+set|profile|layout|object|field|component|page|dashboard|report)\b/;
28607
28744
  classifyQuestion = (question) => {
28608
28745
  const q2 = routeText(question);
28609
28746
  if (q2.length === 0) {
@@ -28626,12 +28763,32 @@ var init_intent_router = __esm({
28626
28763
  const matches = RULES.filter((rule) => rule.patterns.some((p2) => p2.test(q2)));
28627
28764
  const first = matches[0];
28628
28765
  if (first !== void 0) {
28629
- const primary = routeFromRule(question, q2, first);
28630
- const alternatives = [...semanticAlternatives(q2, primary.intent)].filter((alternative, i2, all) => alternative.intent !== primary.intent && all.findIndex((candidate) => candidate.intent === alternative.intent) === i2).slice(0, 3);
28631
- const clarification = alternatives.length > 0 ? {
28766
+ let primary = routeFromRule(question, q2, first);
28767
+ let alternatives = [...semanticAlternatives(q2, primary.intent)].filter((alternative, i2, all) => alternative.intent !== primary.intent && all.findIndex((candidate) => candidate.intent === alternative.intent) === i2).slice(0, 3);
28768
+ if (primary.intent === "runtime-audit-trail" && alternatives.some((alternative) => alternative.intent === "last-modified") && NAMES_COMPONENT_FOR_LAST_MODIFIED.test(q2)) {
28769
+ const lastModifiedRule = RULES.find((rule) => rule.intent === "last-modified");
28770
+ if (lastModifiedRule !== void 0) {
28771
+ primary = routeFromRule(question, q2, lastModifiedRule);
28772
+ alternatives = [];
28773
+ }
28774
+ }
28775
+ const complementary = alternatives.filter((alternative) => COMPLEMENTARY_ALTERNATIVE_PAIRS.has(`${primary.intent}|${alternative.intent}`));
28776
+ const blocking = alternatives.filter((alternative) => !complementary.includes(alternative));
28777
+ const stackedTools = complementary.length > 0 ? [
28778
+ ...primary.tools,
28779
+ ...complementary.flatMap((alternative) => alternative.tools).filter((tool) => !primary.tools.includes(tool))
28780
+ ] : primary.tools;
28781
+ if (complementary.length > 0) {
28782
+ primary = {
28783
+ ...primary,
28784
+ tools: stackedTools,
28785
+ reason: `${primary.reason} The question also admits a complementary reading (${complementary.map((a2) => intentLabel(a2.intent)).join("; ")}) \u2014 its tools are stacked after the primary so either or both can run; no clarification needed.`
28786
+ };
28787
+ }
28788
+ const clarification = blocking.length > 0 ? {
28632
28789
  required: true,
28633
- question: `Which result do you want first: ${intentLabel(primary.intent)}, or ${alternatives.map((a2) => intentLabel(a2.intent)).join(", ")}?`,
28634
- options: [primary.intent, ...alternatives.map((a2) => a2.intent)],
28790
+ question: `Which result do you want first: ${intentLabel(primary.intent)}, or ${blocking.map((a2) => intentLabel(a2.intent)).join(", ")}?`,
28791
+ options: [primary.intent, ...blocking.map((a2) => a2.intent)],
28635
28792
  fallback: {
28636
28793
  intent: primary.intent,
28637
28794
  warning: `Clarification was unavailable. Provisional route: ${primary.intent}. Show the user the alternatives before executing it.`
@@ -28639,7 +28796,7 @@ var init_intent_router = __esm({
28639
28796
  } : null;
28640
28797
  return {
28641
28798
  ...primary,
28642
- confidence: alternatives.length === 0 ? "high" : "low",
28799
+ confidence: blocking.length > 0 ? "low" : complementary.length > 0 ? "medium" : "high",
28643
28800
  alternatives,
28644
28801
  clarification,
28645
28802
  plan: [{
@@ -54928,6 +55085,7 @@ var init_resolve2 = __esm({
54928
55085
  parentApiName: a2.parentApiName,
54929
55086
  score: a2.score,
54930
55087
  base: a2.base,
55088
+ nameCoverage: 1,
54931
55089
  matchKind: "exact",
54932
55090
  evidence: a2.evidence
54933
55091
  })),
@@ -55102,11 +55260,1785 @@ var init_retrieve_blindspot_report = __esm({
55102
55260
  }
55103
55261
  });
55104
55262
 
55263
+ // ../mcp/dist/src/context-resolution.js
55264
+ var PRONOUN_ANCHORS, REPARAM_ANCHORS, detectPronounAnchor, detectReparamAnchor, ANAPHOR_ONLY, isAnaphorOnly, ORDINAL_INDEX, SELECTION_SHAPE, NON_DESCRIPTOR_TOKENS, detectClarificationSelection, COMPONENT_ID_SHAPE, validatePreviousContext, CONTINUATION_TOOL_TYPES, continuationToolCompatible, REPARAM_FILLER, extractReparamTarget;
55265
+ var init_context_resolution = __esm({
55266
+ "../mcp/dist/src/context-resolution.js"() {
55267
+ "use strict";
55268
+ PRONOUN_ANCHORS = [
55269
+ /\bthat\s+one\b/i,
55270
+ /\bthis\s+one\b/i,
55271
+ /\bthe\s+same\b/i,
55272
+ /\bthe\s+other(?:\s+one)?\b/i,
55273
+ /\beach\s+of\s+them\b/i,
55274
+ /\bany\s+of\s+them\b/i,
55275
+ /\beither(?:\s+one)?\b/i,
55276
+ /\bboth\b/i,
55277
+ /\bit\b/i,
55278
+ /\bits\b/i,
55279
+ /\bthey\b/i,
55280
+ /\bthem\b/i,
55281
+ /\bthose\b/i,
55282
+ /\bthese\b/i
55283
+ ];
55284
+ REPARAM_ANCHORS = [
55285
+ /\bwhat\s+about\b/i,
55286
+ /\bhow\s+about\b/i,
55287
+ /\band\s+(?:on|for|the)\b/i,
55288
+ /\bsame\s+(?:for|on|thing|question)\b/i,
55289
+ /\bwhat\s+does\s+.*\s+look\s+like\s+on\b/i,
55290
+ /\bthe\s+(?:first|second|third|last)\s+one\b/i,
55291
+ /\bthe\s+[A-Za-z0-9_]+\s+one\b/i
55292
+ ];
55293
+ detectPronounAnchor = (question) => {
55294
+ for (const anchor of PRONOUN_ANCHORS) {
55295
+ const match = question.match(anchor);
55296
+ if (match !== null)
55297
+ return match[0];
55298
+ }
55299
+ return null;
55300
+ };
55301
+ detectReparamAnchor = (question) => {
55302
+ for (const anchor of REPARAM_ANCHORS) {
55303
+ const match = question.match(anchor);
55304
+ if (match !== null)
55305
+ return match[0];
55306
+ }
55307
+ return null;
55308
+ };
55309
+ ANAPHOR_ONLY = /* @__PURE__ */ new Set([
55310
+ "it",
55311
+ "its",
55312
+ "they",
55313
+ "them",
55314
+ "those",
55315
+ "these",
55316
+ "both",
55317
+ "either",
55318
+ "that one",
55319
+ "this one",
55320
+ "the same",
55321
+ "the other",
55322
+ "the other one",
55323
+ "same",
55324
+ "other",
55325
+ "either one"
55326
+ ]);
55327
+ isAnaphorOnly = (phrase) => ANAPHOR_ONLY.has(phrase.trim().toLowerCase());
55328
+ ORDINAL_INDEX = {
55329
+ first: 0,
55330
+ second: 1,
55331
+ third: 2
55332
+ };
55333
+ SELECTION_SHAPE = /^\s*(?:(?:show\s+me|pick|use|go\s+with|i\s+meant|let'?s\s+go\s+with|the\s+answer\s+is)\s+)?the\s+([A-Za-z0-9_]+)\s+one\s*[?.!]*\s*$/i;
55334
+ NON_DESCRIPTOR_TOKENS = /* @__PURE__ */ new Set(["other", "same", "right", "wrong"]);
55335
+ detectClarificationSelection = (question, options) => {
55336
+ const match = question.match(SELECTION_SHAPE);
55337
+ if (match === null)
55338
+ return null;
55339
+ const token = match[1].toLowerCase();
55340
+ if (NON_DESCRIPTOR_TOKENS.has(token))
55341
+ return null;
55342
+ const anaphor = `the ${match[1]} one`;
55343
+ if (token === "last") {
55344
+ const selection = options.at(-1);
55345
+ return selection === void 0 ? { kind: "re-ask", anaphor } : { kind: "selected", anaphor, selection };
55346
+ }
55347
+ const ordinal = ORDINAL_INDEX[token];
55348
+ if (ordinal !== void 0) {
55349
+ const selection = options[ordinal];
55350
+ return selection === void 0 ? { kind: "re-ask", anaphor } : { kind: "selected", anaphor, selection };
55351
+ }
55352
+ const containing = options.filter((option) => option.toLowerCase().includes(token));
55353
+ return containing.length === 1 ? { kind: "selected", anaphor, selection: containing[0] } : { kind: "re-ask", anaphor };
55354
+ };
55355
+ COMPONENT_ID_SHAPE = /^[A-Za-z]+:.+$/;
55356
+ validatePreviousContext = (previous, isRegisteredTool) => {
55357
+ const ignored = [];
55358
+ let cleaned = previous;
55359
+ if (previous.tool !== void 0 && !isRegisteredTool(previous.tool)) {
55360
+ ignored.push(`previous.tool '${previous.tool}' is not a registered tool; ignored`);
55361
+ cleaned = { ...cleaned, tool: void 0 };
55362
+ }
55363
+ if (previous.componentId !== void 0 && !COMPONENT_ID_SHAPE.test(previous.componentId)) {
55364
+ ignored.push(`previous.componentId '${previous.componentId}' is not a canonical Type:Name id; ignored`);
55365
+ cleaned = { ...cleaned, componentId: void 0 };
55366
+ }
55367
+ return { previous: cleaned, ignored };
55368
+ };
55369
+ CONTINUATION_TOOL_TYPES = /* @__PURE__ */ new Map([
55370
+ ["sfi.call_graph", /* @__PURE__ */ new Set(["ApexClass", "ApexTrigger"])],
55371
+ ["sfi.method_reachability", /* @__PURE__ */ new Set(["ApexClass", "ApexTrigger"])],
55372
+ ["sfi.explain_apex_method", /* @__PURE__ */ new Set(["ApexClass", "ApexTrigger"])],
55373
+ ["sfi.who_can_access_object", /* @__PURE__ */ new Set(["CustomObject"])],
55374
+ ["sfi.object_access_audit", /* @__PURE__ */ new Set(["CustomObject"])],
55375
+ ["sfi.field_access_audit", /* @__PURE__ */ new Set(["CustomField"])],
55376
+ ["sfi.explain_flow", /* @__PURE__ */ new Set(["Flow"])],
55377
+ ["sfi.who_can_run", /* @__PURE__ */ new Set(["Flow"])],
55378
+ ["sfi.what_if_deactivate_flow", /* @__PURE__ */ new Set(["Flow"])],
55379
+ ["sfi.explain_field", /* @__PURE__ */ new Set(["CustomField"])],
55380
+ ["sfi.field_360", /* @__PURE__ */ new Set(["CustomField"])],
55381
+ ["sfi.field_lineage", /* @__PURE__ */ new Set(["CustomField"])],
55382
+ ["sfi.safe_to_delete_field", /* @__PURE__ */ new Set(["CustomField"])],
55383
+ ["sfi.explain_formula", /* @__PURE__ */ new Set(["CustomField"])],
55384
+ ["sfi.what_happens_on_save", /* @__PURE__ */ new Set(["CustomObject"])],
55385
+ ["sfi.order_of_execution", /* @__PURE__ */ new Set(["CustomObject"])],
55386
+ ["sfi.what_if_disable_trigger", /* @__PURE__ */ new Set(["ApexTrigger"])]
55387
+ ]);
55388
+ continuationToolCompatible = (tool, resolvedType) => {
55389
+ if (resolvedType === null)
55390
+ return true;
55391
+ const compatible = CONTINUATION_TOOL_TYPES.get(tool);
55392
+ return compatible === void 0 || compatible.has(resolvedType);
55393
+ };
55394
+ REPARAM_FILLER = /* @__PURE__ */ new Set([
55395
+ "what",
55396
+ "whats",
55397
+ "how",
55398
+ "about",
55399
+ "and",
55400
+ "same",
55401
+ "for",
55402
+ "on",
55403
+ "the",
55404
+ "a",
55405
+ "an",
55406
+ "of",
55407
+ "in",
55408
+ "to",
55409
+ "does",
55410
+ "do",
55411
+ "is",
55412
+ "are",
55413
+ "look",
55414
+ "like",
55415
+ "way",
55416
+ "thing",
55417
+ "question",
55418
+ "too",
55419
+ "also",
55420
+ "instead",
55421
+ "then",
55422
+ "now",
55423
+ "with",
55424
+ "combined",
55425
+ "anything",
55426
+ "everything",
55427
+ "else",
55428
+ "one"
55429
+ ]);
55430
+ extractReparamTarget = (question) => {
55431
+ const tokens = question.replace(/[?!.,;:]+/g, " ").split(/\s+/).filter((token) => token.length > 0).filter((token) => !REPARAM_FILLER.has(token.toLowerCase()));
55432
+ if (tokens.length === 0 || tokens.length > 4)
55433
+ return null;
55434
+ return tokens.join(" ");
55435
+ };
55436
+ }
55437
+ });
55438
+
55439
+ // ../mcp/dist/src/refusal-gates.js
55440
+ var INJECTION_PATTERNS, EXFIL_VERB, EXFIL_QUANTIFIER, EXFIL_SENSITIVE, EXFIL_VALUES, EXFIL_SINGLE_VALUE, INJECTION_DISCLOSURE, WRITE_VERB, WRITE_VERB_FILLER, WRITE_EXCLUDER, WRITE_SENTENCE_INITIAL, WRITE_LEAD_IN, WRITE_TRAILING_FRAME, WRITE_MAKE_SCHEMA, WRITE_GIVE_GRANT, WRITE_GIVE_INITIAL, WRITE_CHAINED_DUPE_DELETE, readOnlyAlternativeFor, RUNTIME_TRIGGERS, RUNTIME_WINDOW, RUNTIME_INCIDENT, runtimeDisclosure, EXTERNAL_SYSTEM, POLICY_ASK, RETENTION_POLICY_ASK, CONSENT_PROCESS_ASK, OPINION_ASK, SHOULD_WE, METADATA_NOUN, DELIVERY_ASK, WRITE_CODE_ASK, outOfScopeDisclosure, detectRefusalShape;
55441
+ var init_refusal_gates = __esm({
55442
+ "../mcp/dist/src/refusal-gates.js"() {
55443
+ "use strict";
55444
+ INJECTION_PATTERNS = [
55445
+ /\bignore\s+(?:your|all|the)\s+(?:previous|prior|read-?only)\s+(?:instructions?|rules?|restrictions?)\b/i,
55446
+ /\bpretend\s+the\s+(?:previous|prior)\s+rules\b/i,
55447
+ /\byou\s+are\s+now\s+(?:in\s+)?(?:admin|deploy|god)\s*[- ]?mode\b/i,
55448
+ /\byou'?re\s+actually\s+an?\b[^.?!]*\bbot\b/i,
55449
+ /\b(?:print|reveal|show)\s+(?:me\s+)?your\s+(?:system\s+)?prompt\b/i
55450
+ ];
55451
+ EXFIL_VERB = /\b(?:dump|export|give\s+me|tell\s+me|list|retrieve)\b/i;
55452
+ EXFIL_QUANTIFIER = /\b(?:every|all|actual|the\s+real|random)\b/i;
55453
+ EXFIL_SENSITIVE = /\b(?:ssn|social\s+security|pii|ferpa|hipaa)\b/i;
55454
+ EXFIL_VALUES = /\bvalues?\b|\bstored\b/i;
55455
+ EXFIL_SINGLE_VALUE = /\b(?:what(?:'s|s|\s+is)|show\s+me|give\s+me|pull)\s+the\s+(?:ssn|social\s+security(?:\s+number)?)\b(?!\s+fields?\b)[^.?!]{0,60}\b(?:lead|contact|record|student|person|user|account|applicant)s?\b/i;
55456
+ INJECTION_DISCLOSURE = "REFUSED: this request asks me to override instructions or exfiltrate record values. sf-intelligence reads metadata only; it never returns stored record values and its instructions are not overridable.";
55457
+ WRITE_VERB = "(?:create|add|delete|remove|update|change|(?<!permission\\s)set|deploy|assign|reassign|revoke|deactivate|activate|disable|enable|merge|reset|rename|fix|grant|provision|turn\\s+(?:on|off)|clean\\s+up|migrate|convert|insert|upsert|push|publish|install|uninstall|schedule|upgrade|downgrade|throttle|purge)";
55458
+ WRITE_VERB_FILLER = "(?:(?:bulk|mass|batch|just|please|go\\s+ahead\\s+and)\\s+)*";
55459
+ WRITE_EXCLUDER = /\b(?:am\s+i|can\s+i|could\s+i|do\s+i|who\s+can|who\s+is\s+able|allowed\s+to|able\s+to|what\s+if|what\s+would|would\s+happen|is\s+it\s+safe|safe\s+to|before\s+i|if\s+i|should\s+i|how\s+do\s+i|how\s+would\s+i|what\s+happens\s+when)\b/i;
55460
+ WRITE_SENTENCE_INITIAL = new RegExp(`(?:^|[.!?;]\\s+)(?:please\\s+|just\\s+)?${WRITE_VERB_FILLER}(${WRITE_VERB}\\b[^.?!;]{0,80})`, "i");
55461
+ WRITE_LEAD_IN = new RegExp(`\\b(?:please|just|go\\s+ahead\\s+and|can\\s+(?:you|u)|could\\s+(?:you|u)|would\\s+(?:you|u)|you\\s+should|i\\s+need\\s+you\\s+to)\\s+(?:please\\s+|just\\s+)?${WRITE_VERB_FILLER}(${WRITE_VERB}\\b[^.?!;]{0,80})`, "i");
55462
+ WRITE_TRAILING_FRAME = new RegExp(`\\b(${WRITE_VERB}\\b[^.?!;]{0,80}?\\b(?:for\\s+me|right\\s+now|and\\s+confirm)\\b[^.?!;]{0,20})`, "i");
55463
+ WRITE_MAKE_SCHEMA = new RegExp(`\\b(?:can|could|would)\\s+(?:you|u)\\b[^.?!;]{0,10}\\s(make\\b[^.?!;]{0,60}\\b(?:required|mandatory|optional|unique|read[-\\s]?only|editable|visible)\\b)`, "i");
55464
+ WRITE_GIVE_GRANT = new RegExp(`\\b(?:can|could|would)\\s+(?:you|u)\\b[^.?!;]{0,30}\\b(give\\s+(?!me\\b)[^.?!;]{0,60}\\b(?:profile|permission|perm\\s+set|access|user)\\b[^.?!;]{0,40})`, "i");
55465
+ WRITE_GIVE_INITIAL = /(?:^|[.!?;]\s+)(give\s+(?!me\b)[^.?!;]{0,60}\b(?:admin|access|permission|profile)\b[^.?!;]{0,30})/i;
55466
+ WRITE_CHAINED_DUPE_DELETE = /\b(?:and|then)\s+(?:just\s+)?((?:delete|remove|purge|merge)\s+(?:the\s+)?dup(?:e|licate)s?\b[^.?!;]{0,30})/i;
55467
+ readOnlyAlternativeFor = (verbPhrase, question) => {
55468
+ const v2 = verbPhrase.toLowerCase();
55469
+ const q2 = question.toLowerCase();
55470
+ if (/^(?:delete|remove)\b/.test(v2) && /\bfield\b|__c\b/.test(q2)) {
55471
+ return "sfi.safe_to_delete_field";
55472
+ }
55473
+ if (/^(?:deactivate|disable|turn)\b/.test(v2) && /\bflow\b/.test(q2)) {
55474
+ return "sfi.what_if_deactivate_flow";
55475
+ }
55476
+ if (/^disable\b/.test(v2) && /\btrigger\b/.test(q2)) {
55477
+ return "sfi.what_if_disable_trigger";
55478
+ }
55479
+ if (/^(?:change|convert)\b/.test(v2) && /\bfield\b/.test(q2)) {
55480
+ return "sfi.what_if_change_field_type";
55481
+ }
55482
+ if (/^merge\b/.test(v2) && /\bprofile/.test(q2)) {
55483
+ return "sfi.what_if_merge_profiles";
55484
+ }
55485
+ if (/^make\b/.test(v2) && /\brequired|mandatory\b/.test(v2)) {
55486
+ return "sfi.what_if_make_field_required";
55487
+ }
55488
+ if (/^give\b/.test(v2) || /^grant\b/.test(v2) && /\b(?:profile|permission)\b/.test(q2)) {
55489
+ return "sfi.permission_risk_report";
55490
+ }
55491
+ if (/^(?:upgrade|downgrade|install|uninstall)\b/.test(v2) && /\bpackage\b/.test(q2)) {
55492
+ return "sfi.package_impact";
55493
+ }
55494
+ if (/^throttle\b/.test(v2) || /\basync\b/.test(q2)) {
55495
+ return "sfi.async_chain_depth";
55496
+ }
55497
+ if (/dup(?:e|licate)s?\b/.test(v2)) {
55498
+ return "sfi.live_duplicate_check";
55499
+ }
55500
+ return "sfi.get_impact";
55501
+ };
55502
+ RUNTIME_TRIGGERS = [
55503
+ [/\blogin\s+history\b|\baudit\s+trail\s+of\s+logins?\b/i, "login history"],
55504
+ [/\badoption\b|\bhow\s+often\s+do\s+users\s+actually\s+use\b/i, "adoption/usage telemetry"],
55505
+ [
55506
+ /\bping\s+(?:the\s+)?\S+|\bendpoints?\b[^.?!]*\bup\b|\b(?:returned|returning|throwing|threw)\s+errors?\b/i,
55507
+ "endpoint health"
55508
+ ],
55509
+ [/\brunning[-\s]user\s+context\b/i, "the runtime running-user context"]
55510
+ ];
55511
+ RUNTIME_WINDOW = /\b(?:this\s+week|yesterday|last\s+night|in\s+the\s+\w+\s+incident)\b/i;
55512
+ RUNTIME_INCIDENT = /\b(?:errors?|failed|failures?|incident|outage|gated)\b/i;
55513
+ runtimeDisclosure = (topic) => `HONEST GAP: ${topic} is runtime telemetry sf-intelligence does not model \u2014 the vault holds metadata; the live plane covers counts/samples/limits, not event logs.`;
55514
+ EXTERNAL_SYSTEM = /\b(sharepoint|jira|confluence|google\s+drive|onedrive|slack\s+workspace|s3\s+bucket)\b/i;
55515
+ POLICY_ASK = /\bwhat(?:'s|s|\s+is)\s+our\b(?![^.?!]{0,60}\b(?:password|session|sharing|lockout|ip|login)\b)[^.?!]{0,60}\bpolic(?:y|ies)\b/i;
55516
+ RETENTION_POLICY_ASK = /\bretention\s+(?:polic(?:y|ies)|schedule|rules?)\b/i;
55517
+ CONSENT_PROCESS_ASK = /\bhow\s+do(?:es)?\s+(?:students?|users?|customers?|people|contacts?)\s+consent\b/i;
55518
+ OPINION_ASK = /\bwhat\s+do\s+you\s+think\b/i;
55519
+ SHOULD_WE = /\bshould\s+we\b/i;
55520
+ METADATA_NOUN = /\b(?:field|object|flow|trigger|class|profile|permission|layout|validation|record\s+type|apex|report|dashboard|picklist|component|rule)\b|__(?:c|mdt|e|x|b|kav)\b/i;
55521
+ DELIVERY_ASK = /\bemail\s+me\b|\bsend\s+(?:this|it)\s+to\b|\bpost\s+(?:this\s+|it\s+)?to\s+slack\b/i;
55522
+ WRITE_CODE_ASK = /\bwrite\s+(?:me\s+)?(?:an?\s+)?(?:apex|lwc|trigger|class|component)\b[^.?!]*\b(?:that|to)\b/i;
55523
+ outOfScopeDisclosure = (topic) => `OUT OF SCOPE: sf-intelligence answers questions about this org's Salesforce metadata. ${topic} is outside that boundary (no external systems, no policy authorship, no code generation, no message delivery).`;
55524
+ detectRefusalShape = (question) => {
55525
+ const q2 = question.replace(/[‘’ʼ]/g, "'").replace(/\s+/g, " ").trim();
55526
+ if (q2.length === 0)
55527
+ return null;
55528
+ const injected = INJECTION_PATTERNS.some((p2) => p2.test(q2)) || EXFIL_VERB.test(q2) && EXFIL_QUANTIFIER.test(q2) && EXFIL_SENSITIVE.test(q2) && EXFIL_VALUES.test(q2) || EXFIL_SINGLE_VALUE.test(q2);
55529
+ if (injected) {
55530
+ return { kind: "injection-exfiltration", disclosure: INJECTION_DISCLOSURE };
55531
+ }
55532
+ if (!WRITE_EXCLUDER.test(q2)) {
55533
+ const verbPhrase = (WRITE_SENTENCE_INITIAL.exec(q2) ?? WRITE_LEAD_IN.exec(q2) ?? WRITE_TRAILING_FRAME.exec(q2) ?? WRITE_MAKE_SCHEMA.exec(q2) ?? WRITE_GIVE_GRANT.exec(q2) ?? WRITE_GIVE_INITIAL.exec(q2) ?? WRITE_CHAINED_DUPE_DELETE.exec(q2))?.[1]?.trim().replace(/[,;:]$/, "");
55534
+ if (verbPhrase !== void 0 && verbPhrase.length > 0) {
55535
+ const alternative = readOnlyAlternativeFor(verbPhrase, q2);
55536
+ return {
55537
+ kind: "write-imperative",
55538
+ disclosure: `REFUSED (read-only boundary): sf-intelligence never mutates the org \u2014 it cannot ${verbPhrase}. I can show you the read-side analysis instead: ${alternative}.`,
55539
+ readOnlyAlternative: alternative
55540
+ };
55541
+ }
55542
+ }
55543
+ const runtimeTopic = RUNTIME_TRIGGERS.find(([pattern]) => pattern.test(q2))?.[1] ?? (RUNTIME_WINDOW.test(q2) && RUNTIME_INCIDENT.test(q2) ? "runtime incident forensics" : void 0);
55544
+ if (runtimeTopic !== void 0) {
55545
+ return { kind: "runtime-analytics", disclosure: runtimeDisclosure(runtimeTopic) };
55546
+ }
55547
+ const externalSystem = EXTERNAL_SYSTEM.exec(q2)?.[1];
55548
+ const outOfScopeTopic = externalSystem !== void 0 ? `"${externalSystem}"` : RETENTION_POLICY_ASK.test(q2) ? "A data-retention policy (organizational governance, not org metadata)" : CONSENT_PROCESS_ASK.test(q2) ? "A consent process (organizational governance, not org metadata)" : POLICY_ASK.test(q2) ? "Org policy authorship" : OPINION_ASK.test(q2) ? "An opinion" : DELIVERY_ASK.test(q2) ? "Message delivery" : WRITE_CODE_ASK.test(q2) ? "Code generation" : SHOULD_WE.test(q2) && !METADATA_NOUN.test(q2) ? "An org-decision recommendation" : void 0;
55549
+ if (outOfScopeTopic !== void 0) {
55550
+ return { kind: "out-of-scope", disclosure: outOfScopeDisclosure(outOfScopeTopic) };
55551
+ }
55552
+ return null;
55553
+ };
55554
+ }
55555
+ });
55556
+
55557
+ // ../mcp/dist/src/funnel-utterances.js
55558
+ var FUNNEL_UTTERANCES;
55559
+ var init_funnel_utterances = __esm({
55560
+ "../mcp/dist/src/funnel-utterances.js"() {
55561
+ "use strict";
55562
+ FUNNEL_UTTERANCES = {
55563
+ "sfi.search_components": [
55564
+ "is there a validation rule that stops me from saving a Contact without a Status__c?",
55565
+ "find me a validation rule touching Priority__c",
55566
+ "does a rule exist blocking Case creation if Subject is blank?",
55567
+ "is there a rule that fires when Opportunity.Stage = Closed Lost",
55568
+ "search for anything referencing <PermissionName>",
55569
+ "find a validation rule whose formula checks $Permission.<X>",
55570
+ "what custom permission does <PermSet> do \u2014 sounds like it skips a validation",
55571
+ "I heard we have a flow that assigns Cases to the Support queue \u2014 which one and what's the criteria?",
55572
+ "is there a workflow rule that emails someone when a Lead is converted?",
55573
+ "find anything that references the External_Id__c field on Account",
55574
+ "do we have a duplicate rule on Contact?",
55575
+ "search for all components that touch the Amount__c field",
55576
+ "what validation rules exist on the Order object?",
55577
+ "show me every component that references the <PermSet> permission set",
55578
+ "is there an approval process on Opportunity?",
55579
+ "find me anything that blocks record creation when a required field is empty"
55580
+ ],
55581
+ "sfi.resolve": [
55582
+ "pull the <vague-field-name> field \u2014 <PermSet> implies we have one",
55583
+ "i keep seeing <prefix>__ everywhere \u2014 whats that prefix",
55584
+ "do we have a field for tracking renewal dates?",
55585
+ "find me the field that stores the customer tier",
55586
+ "I think there's a flow called something like case assignment \u2014 look it up",
55587
+ "look up the thing that sounds like external identifier on Account",
55588
+ "we have something called Priority \u2014 is that a field or a picklist value?",
55589
+ "find the object that handles billing addresses",
55590
+ "is there a permission set that sounds like it grants read-only access to financials?",
55591
+ "I keep hearing about a <fuzzy-name> flow \u2014 can you find it?",
55592
+ "what's the canonical name for the field that stores the SLA deadline?",
55593
+ "look up Status__c \u2014 which object is it on?",
55594
+ "do we have anything for tracking escalation dates on Case?"
55595
+ ],
55596
+ "sfi.capabilities": [
55597
+ "what can you actually do?",
55598
+ "what tools do you have available?",
55599
+ "give me a rundown of everything I can ask you about",
55600
+ "what kinds of questions can you answer about this org?",
55601
+ "what's your capability list?",
55602
+ "I'm new here \u2014 what does this thing do?",
55603
+ "show me the full menu of what you support"
55604
+ ],
55605
+ "sfi.list_analyses": [
55606
+ "what analyses have been run on this org?",
55607
+ "show me all saved analyses",
55608
+ "which analysis reports are available?",
55609
+ "list the pre-built analyses I can run",
55610
+ "what analysis types does this support?",
55611
+ "what named analyses can I run against this org?"
55612
+ ],
55613
+ "sfi.describe_analysis": [
55614
+ "what does the tech debt analysis actually check?",
55615
+ "explain what the permission risk analysis looks at",
55616
+ "describe what the coverage report covers",
55617
+ "what does the org risk report measure exactly?",
55618
+ "what inputs does the automation risk analysis need?",
55619
+ "how does the code quality audit work under the hood?"
55620
+ ],
55621
+ "sfi.run_analysis": [
55622
+ "run the tech debt analysis now",
55623
+ "kick off the permission risk report",
55624
+ "execute the automation risk analysis",
55625
+ "can you run the coverage report for me?",
55626
+ "start the org risk report analysis",
55627
+ "go ahead and run the coverage report analysis"
55628
+ ],
55629
+ "sfi.synthesize_answer": [
55630
+ "can you pull together everything you know about how Case assignment works?",
55631
+ "synthesize an answer about our sharing model for Opportunity",
55632
+ "give me a consolidated view of everything touching the Lead conversion process",
55633
+ "piece together what you know about our CPQ setup",
55634
+ "summarize all the relevant metadata about our Account hierarchy",
55635
+ "combine everything you found and give me a clean answer"
55636
+ ],
55637
+ "sfi.route_question": [
55638
+ "I'm not sure which tool to use \u2014 who can edit the Amount__c field?",
55639
+ "figure out the best way to answer: why did this record change?",
55640
+ "where do I even start to understand why a flow failed?",
55641
+ "I have a broad question about our data model \u2014 route me to the right thing",
55642
+ "what's the best tool to find out if a field is used anywhere?",
55643
+ "I'm not sure which tool answers 'what runs on Contact save' \u2014 which one?"
55644
+ ],
55645
+ "sfi.org_pulse": [
55646
+ "give me a quick health snapshot of the org",
55647
+ "what's the current state of this Salesforce instance?",
55648
+ "pulse check \u2014 is this org healthy?",
55649
+ "quick status: how is the org doing right now?",
55650
+ "is there anything alarming about this org at a glance?",
55651
+ "one-liner on org health"
55652
+ ],
55653
+ "sfi.org_card": [
55654
+ "show me the org's basic info \u2014 edition, limits, creation date",
55655
+ "what edition is this org?",
55656
+ "give me the org ID and basic metadata",
55657
+ "what sandbox type is this?",
55658
+ "who's the org admin and what's the org name?",
55659
+ "pull the orientation snapshot for this org"
55660
+ ],
55661
+ "sfi.fleet_find": [
55662
+ "find all orgs in the fleet that have the <Package> package installed",
55663
+ "which orgs in our fleet have Flows on the Case object?",
55664
+ "search across all our orgs for a specific component",
55665
+ "show me which orgs have this permission set configured",
55666
+ "which fleet orgs still have Process Builder active?",
55667
+ "search all registered orgs for a component called <ComponentName>"
55668
+ ],
55669
+ "sfi.fleet_drift_ranking": [
55670
+ "which orgs in our fleet have drifted most from the baseline?",
55671
+ "rank our orgs by how much they've diverged from each other",
55672
+ "show me which fleet orgs are most out of sync",
55673
+ "drift ranking across our sandbox fleet",
55674
+ "which org has changed the most since last baseline?",
55675
+ "which of our orgs is most out of date and needs a refresh first?"
55676
+ ],
55677
+ "sfi.get_component": [
55678
+ "show me the full metadata for the <FlowName> flow",
55679
+ "pull up the <ApexClass> class definition",
55680
+ "get me the raw component data for the Contact validation rule",
55681
+ "fetch the permission set definition for <PermSet>",
55682
+ "show me the layout metadata for Case",
55683
+ "pull the full metadata for <FlowName>"
55684
+ ],
55685
+ "sfi.list_components": [
55686
+ "whats on the Account object \u2014 give me the whole field list",
55687
+ "list all custom fields on Contact",
55688
+ "show me every flow on this org",
55689
+ "what record types exist on Case?",
55690
+ "enumerate all permission sets",
55691
+ "show me all active validation rules on Contact",
55692
+ "Which duplicate matching rules exist but are inactive \u2014 deployed and forgotten?",
55693
+ "list every Apex class in this org",
55694
+ "what custom objects do we have?",
55695
+ "show me all the triggers on Account",
55696
+ "what approval processes are deployed?",
55697
+ "list all custom labels",
55698
+ "give me every email template in the org"
55699
+ ],
55700
+ "sfi.get_edges": [
55701
+ "what are the edges between <ApexClass> and the objects it touches?",
55702
+ "show me all dependency edges for the <FlowName> flow",
55703
+ "what connects to <ComponentName> in the metadata graph?",
55704
+ "enumerate the graph edges for this permission set",
55705
+ "which components does <ApexClass> directly reference?",
55706
+ "list all the outgoing dependencies from Account"
55707
+ ],
55708
+ "sfi.get_subgraph": [
55709
+ "give me the dependency subgraph around the Case object",
55710
+ "show me everything in the graph connected to <ApexClass>",
55711
+ "pull the subgraph for flows touching Opportunity",
55712
+ "what's the local neighborhood of <ComponentName> in the dependency graph?",
55713
+ "give me a slice of the graph centered on <FlowName>",
55714
+ "show me a 2-hop neighborhood around the Contact object"
55715
+ ],
55716
+ "sfi.search_apex_source": [
55717
+ "search the Apex code for any reference to External_Id__c",
55718
+ "find all Apex that calls Database.insert on Contact",
55719
+ "grep the codebase for 'SOQL injection'",
55720
+ "which Apex classes reference the <PermSet> permission?",
55721
+ "find Apex source that contains a hardcoded email address",
55722
+ "search Apex for any class that uses future methods",
55723
+ "look for Apex code that queries the Order object"
55724
+ ],
55725
+ "sfi.search_flow_metadata": [
55726
+ "I heard we have a flow that assigns Cases to the Support queue \u2014 which one and what's the criteria?",
55727
+ "find a flow that references Priority__c",
55728
+ "search flow XML for a specific field value",
55729
+ "which flows contain a Decision element?",
55730
+ "find flows that call the <ApexClass> Apex class",
55731
+ "which flows have a Get Records step on the Account object?",
55732
+ "search for flows that send emails",
55733
+ "find every flow that creates an Opportunity"
55734
+ ],
55735
+ "sfi.get_naming_convention_report": [
55736
+ "do our field names follow any consistent pattern?",
55737
+ "is there a naming convention for our custom objects?",
55738
+ "are our Apex class names consistent?",
55739
+ "what naming patterns do we use for permission sets?",
55740
+ "check if our flow names follow a standard",
55741
+ "are there naming convention violations in our org?"
55742
+ ],
55743
+ "sfi.get_manifest": [
55744
+ "export a package.xml for all our custom components",
55745
+ "give me the manifest of everything in this org",
55746
+ "what's in the deployment manifest?",
55747
+ "show me the package manifest for our custom metadata",
55748
+ "generate a manifest I can use for a deployment",
55749
+ "what's in the current vault manifest?"
55750
+ ],
55751
+ "sfi.coverage_report": [
55752
+ "what's our overall Apex test coverage?",
55753
+ "give me a coverage summary for all Apex classes",
55754
+ "which classes have the lowest test coverage?",
55755
+ "are we above the 75% coverage threshold?",
55756
+ "show me the coverage report",
55757
+ "which triggers have zero test coverage?",
55758
+ "overall code coverage percentage for this org"
55759
+ ],
55760
+ "sfi.retrieve_blindspot_report": [
55761
+ "what metadata types are we NOT tracking in our deployments?",
55762
+ "show me the retrieval blindspots \u2014 what we're missing",
55763
+ "which metadata is deployed but not in our source control?",
55764
+ "what's falling through the cracks in our CI/CD pipeline?",
55765
+ "are there any metadata types we can't retrieve?",
55766
+ "what metadata is referenced but not captured in this vault?"
55767
+ ],
55768
+ "sfi.health_check": [
55769
+ "is this org healthy?",
55770
+ "run a health check on the org",
55771
+ "what's wrong with this org?",
55772
+ "give me a health assessment",
55773
+ "check for obvious issues with this org",
55774
+ "is this org in good shape?"
55775
+ ],
55776
+ "sfi.baseline_acknowledge": [
55777
+ "acknowledge the current baseline",
55778
+ "mark this as the new baseline",
55779
+ "accept the current state as baseline",
55780
+ "set the baseline to today's snapshot",
55781
+ "confirm I've reviewed the baseline findings",
55782
+ "suppress this heuristic finding so it stops showing up"
55783
+ ],
55784
+ "sfi.baseline_status": [
55785
+ "what was the baseline state of the org?",
55786
+ "show me what changed since the baseline",
55787
+ "when was the last baseline taken?",
55788
+ "is the org still at baseline or has it drifted?",
55789
+ "compare current state to baseline",
55790
+ "which findings have been suppressed in the SAST baseline?"
55791
+ ],
55792
+ "sfi.live_describe": [
55793
+ "describe the Case object \u2014 fields, picklists, everything",
55794
+ "what fields does the Contact object have in the live org?",
55795
+ "pull the live object description for Opportunity",
55796
+ "show me the actual picklist values for Status on Case right now",
55797
+ "describe Order to me \u2014 what fields exist in production?",
55798
+ "describe the Account sObject from the live org"
55799
+ ],
55800
+ "sfi.live_stale_check": [
55801
+ "is the vault up to date or stale?",
55802
+ "when was the last refresh?",
55803
+ "how old is the cached metadata?",
55804
+ "is this data current or should I refresh?",
55805
+ "check if the vault needs refreshing",
55806
+ "has the live org changed since the last vault refresh?"
55807
+ ],
55808
+ "sfi.live_count": [
55809
+ "how many Contacts are in the org?",
55810
+ "count all open Cases",
55811
+ "how many Opportunities are in Closed Won stage?",
55812
+ "how many Accounts exist in production?",
55813
+ "total number of active leads",
55814
+ "how many open Cases are there right now?"
55815
+ ],
55816
+ "sfi.live_sample": [
55817
+ "show me a few sample Contact records",
55818
+ "give me some example Cases to look at",
55819
+ "pull a random sample of Opportunity records",
55820
+ "show me what Account data actually looks like",
55821
+ "grab a handful of Order records for me to inspect",
55822
+ "give me a sample of 10 Opportunities with Stage = Prospecting"
55823
+ ],
55824
+ "sfi.live_field_population": [
55825
+ "what percentage of Contacts have the Email field filled in?",
55826
+ "how populated is Priority__c on Case?",
55827
+ "which fields on Account are mostly empty?",
55828
+ "what's the fill rate for Amount__c on Opportunity?",
55829
+ "are users actually filling in the Description field on Case?",
55830
+ "what percentage of Contacts have the Mobile field filled in?"
55831
+ ],
55832
+ "sfi.live_group_count": [
55833
+ "how many users are in the Support group?",
55834
+ "count the members of each public group",
55835
+ "how many people are in the queue right now?",
55836
+ "show me group sizes across all public groups",
55837
+ "how many members does the Sales territory group have?",
55838
+ "how are Cases distributed by Status right now?"
55839
+ ],
55840
+ "sfi.live_stale_records": [
55841
+ "which Cases haven't been updated in over 90 days?",
55842
+ "show me Opportunities that have been sitting in the same stage for months",
55843
+ "find Leads that are more than 6 months old and still open",
55844
+ "which Accounts haven't had any activity in a year?",
55845
+ "stale Contacts \u2014 people we haven't touched in forever",
55846
+ "which Accounts haven't been updated in over 180 days?"
55847
+ ],
55848
+ "sfi.live_recent_activity": [
55849
+ "what's changed in the org in the last week?",
55850
+ "show me recently modified records",
55851
+ "what was last touched on the Contact object?",
55852
+ "recent activity on Opportunity \u2014 who's been updating records?",
55853
+ "what records have been created in the last 24 hours?",
55854
+ "what Leads were created in the last 7 days?"
55855
+ ],
55856
+ "sfi.live_aggregate": [
55857
+ "what's the total pipeline value by stage?",
55858
+ "average Case resolution time",
55859
+ "sum of Amount__c on open Opportunities",
55860
+ "count and sum of Orders by status",
55861
+ "aggregate the revenue by Account region",
55862
+ "what's the average Opportunity Amount across open deals?"
55863
+ ],
55864
+ "sfi.live_duplicate_check": [
55865
+ "do we have duplicate Contacts in the org?",
55866
+ "find duplicate Accounts by name and email",
55867
+ "how many duplicate Lead records do we have?",
55868
+ "show me potential duplicate Opportunities",
55869
+ "run a duplicate check on Contact",
55870
+ "are there any duplicate email addresses on Contact?"
55871
+ ],
55872
+ "sfi.live_owner_breakdown": [
55873
+ "who owns the most Opportunities?",
55874
+ "show me Case distribution by owner",
55875
+ "which reps have the most open Leads?",
55876
+ "breakdown of Accounts by owner",
55877
+ "how are Contacts distributed across sales reps?",
55878
+ "how many Cases does each queue own right now?"
55879
+ ],
55880
+ "sfi.live_storage_by_object": [
55881
+ "which objects are taking up the most storage?",
55882
+ "how much data storage is the Case object using?",
55883
+ "show me storage consumption broken down by object",
55884
+ "which objects have the most records?",
55885
+ "storage breakdown \u2014 where is the data?",
55886
+ "which objects are taking up the most storage?"
55887
+ ],
55888
+ "sfi.live_org_limits": [
55889
+ "are we close to any governor limits?",
55890
+ "how much API call budget do we have left today?",
55891
+ "show me current org limits and usage",
55892
+ "are we near our data storage limit?",
55893
+ "what limits is this org approaching?",
55894
+ "how close are we to hitting any governor limits?"
55895
+ ],
55896
+ "sfi.live_inactive_users": [
55897
+ "which users haven't logged in recently?",
55898
+ "show me inactive Salesforce users",
55899
+ "who hasn't logged in in over 90 days?",
55900
+ "list users that are burning licenses but not active",
55901
+ "find users we should probably deactivate",
55902
+ "who hasn't logged in for more than 60 days?"
55903
+ ],
55904
+ "sfi.live_license_usage": [
55905
+ "how many Salesforce licenses are we using?",
55906
+ "show me license utilization",
55907
+ "how many licenses are available vs consumed?",
55908
+ "are we over or under our license count?",
55909
+ "which license types do we have and how many are used?",
55910
+ "how many Salesforce licenses are actually being used vs assigned?"
55911
+ ],
55912
+ "sfi.live_consent": [
55913
+ "which Contacts have opted out of email?",
55914
+ "show me consent status for marketing contacts",
55915
+ "how many people have unsubscribed?",
55916
+ "what's the email opt-out rate?",
55917
+ "show me GDPR consent flags on Contact",
55918
+ "do I have live org access enabled for this session?"
55919
+ ],
55920
+ "sfi.live_report_usage": [
55921
+ "which reports are actually being run?",
55922
+ "show me report usage stats",
55923
+ "what are the most popular reports?",
55924
+ "which reports have never been run?",
55925
+ "find reports that nobody has opened in 6 months",
55926
+ "which reports haven't been run in over 6 months?"
55927
+ ],
55928
+ "sfi.live_folder_access": [
55929
+ "who has access to the Sales reports folder?",
55930
+ "show me report folder permissions",
55931
+ "which users can see the Executive dashboards folder?",
55932
+ "who can access the shared document library?",
55933
+ "folder-level access for the Finance reports",
55934
+ "are any report or dashboard folders publicly accessible?"
55935
+ ],
55936
+ "sfi.live_email_template_usage": [
55937
+ "which email templates are actually being used?",
55938
+ "show me email template utilization stats",
55939
+ "are there email templates nobody is using?",
55940
+ "what templates are being sent most frequently?",
55941
+ "which email templates are dead \u2014 never triggered?",
55942
+ "which email templates are using Classic format instead of Lightning?"
55943
+ ],
55944
+ "sfi.live_org_health": [
55945
+ "how healthy is this org right now \u2014 live check",
55946
+ "live health status of the org",
55947
+ "run a live health assessment",
55948
+ "anything actively broken in the org right now?",
55949
+ "live org health snapshot",
55950
+ "are there any failed async jobs or paused flow interviews right now?"
55951
+ ],
55952
+ "sfi.live_automation_fired": [
55953
+ "which flows have actually fired recently?",
55954
+ "show me automation execution history",
55955
+ "has the <FlowName> flow run in the last week?",
55956
+ "which triggers fired yesterday?",
55957
+ "what automation has been active recently?",
55958
+ "does the <FlowName> flow actually run in production or is it dead?"
55959
+ ],
55960
+ "sfi.live_picklist_usage": [
55961
+ "which picklist values on Status are actually being used?",
55962
+ "show me picklist value usage for Case.Priority",
55963
+ "which Stage values on Opportunity are most common?",
55964
+ "are there any picklist values that nobody selects?",
55965
+ "picklist value distribution on Contact.LeadSource",
55966
+ "which Stage values on Opportunity are never actually used in production?"
55967
+ ],
55968
+ "sfi.live_budget": [
55969
+ "what's our API call usage vs budget this month?",
55970
+ "show me current spending against limits",
55971
+ "how much of our governor budget are we consuming?",
55972
+ "are we on track to hit any limits this month?",
55973
+ "live budget check \u2014 API, storage, everything",
55974
+ "how many live queries have I used this session?"
55975
+ ],
55976
+ "sfi.org_risk_report": [
55977
+ "Give me the bottom line: three sentences on whether I should be confident or worried about this org going into production.",
55978
+ "I have a leadership offsite next week and I want to tell a clean story about the state of our Salesforce platform \u2014 the good, the bad",
55979
+ "what's the health of this org?",
55980
+ "is this org production-ready?",
55981
+ "what are the biggest risks in this org?",
55982
+ "give me a risk summary for this org",
55983
+ "executive summary of org risk",
55984
+ "what should I be worried about before we go live?",
55985
+ "pull together the top risks I need to present to leadership",
55986
+ "is there anything that would block a production release right now?"
55987
+ ],
55988
+ "sfi.field_cleanup_candidates": [
55989
+ "which fields can we safely delete?",
55990
+ "show me fields that are never populated",
55991
+ "what custom fields are candidates for cleanup?",
55992
+ "find fields that nobody uses and we could remove",
55993
+ "which deprecated fields should we clean up?",
55994
+ "list empty fields on Contact that are safe to remove"
55995
+ ],
55996
+ "sfi.automation_risk_report": [
55997
+ "which of our flows and triggers are high risk?",
55998
+ "show me automation that could cause problems",
55999
+ "what's the riskiest automation in this org?",
56000
+ "find automations that could hit governor limits",
56001
+ "which flows run synchronously and could slow things down?",
56002
+ "automation risk overview \u2014 what should we fix first?"
56003
+ ],
56004
+ "sfi.permission_risk_report": [
56005
+ "who has too much access in this org?",
56006
+ "which users have Modify All permissions they shouldn't?",
56007
+ "show me permission risks \u2014 overprivileged users",
56008
+ "find profiles that have more access than they need",
56009
+ "which permission sets grant dangerous access?",
56010
+ "security risks from permissions \u2014 give me the highlights"
56011
+ ],
56012
+ "sfi.release_readiness_report": [
56013
+ "is this org ready for the next release?",
56014
+ "give me a release readiness assessment",
56015
+ "what blockers exist before we can deploy?",
56016
+ "are we ready to promote to production?",
56017
+ "show me the release readiness checklist",
56018
+ "what do we need to fix before the next release?"
56019
+ ],
56020
+ "sfi.get_impact": [
56021
+ "what's the impact of changing <ComponentName>?",
56022
+ "show me what gets affected if I delete <Field__c>",
56023
+ "impact analysis for modifying the <FlowName> flow",
56024
+ "what would break if I change <ApexClass>?",
56025
+ "downstream impact of removing <ComponentName>",
56026
+ "what breaks if I delete the <Field__c> field on Account?"
56027
+ ],
56028
+ "sfi.blast_radius_live": [
56029
+ "blast radius of changing <Object>.<Field> from currency to text",
56030
+ "live blast radius if I deactivate <FlowName>",
56031
+ "how many records would be affected if I change this field?",
56032
+ "what's the blast radius of making Priority__c required on Case?",
56033
+ "if I change this picklist value, how many records does that hit?",
56034
+ "how many records would be affected if I remove the External_Id__c field?"
56035
+ ],
56036
+ "sfi.find_formula_references": [
56037
+ "which formula fields reference Priority__c?",
56038
+ "find all formulas that use the Amount__c field",
56039
+ "what formulas reference the Status field on Case?",
56040
+ "are there any formulas that read from External_Id__c?",
56041
+ "which roll-up summary fields reference Order.Amount__c?",
56042
+ "which formula fields reference the Amount__c field on Opportunity?"
56043
+ ],
56044
+ "sfi.find_apex_usages": [
56045
+ "where is <ApexClass> used in other Apex code?",
56046
+ "find all callers of the <Method> method",
56047
+ "which Apex classes instantiate <ApexClass>?",
56048
+ "what code calls into the <ApexClass> service class?",
56049
+ "who uses the <UtilityClass> utility class?",
56050
+ "which Apex classes read or write to the Status__c field on Case?"
56051
+ ],
56052
+ "sfi.effective_permissions": [
56053
+ "does the <Profile> profile have access to the Account object?",
56054
+ "Lead convert failed with 'missing Edit permission' \u2014 whose perm is that?",
56055
+ "what can <Profile> actually do \u2014 union of profile plus all their perm sets?",
56056
+ "combine <Profile> and <PermSet> and show me the effective access",
56057
+ "does assigning <PermSet> override the profile restriction on Case?",
56058
+ "what's the max-wins access for this user?",
56059
+ "can the Sales profile create Opportunity records?",
56060
+ "show me the effective field-level security for <Profile> on Contact",
56061
+ "what's the net permission for a user with <Profile> and <PermSet>?",
56062
+ "does this user actually have delete access on Account?",
56063
+ "what objects can the <Profile> profile read?",
56064
+ "effective read/write/delete access for <Profile> on Order"
56065
+ ],
56066
+ "sfi.who_can_run": [
56067
+ "who can run the <Report> report?",
56068
+ "which users are allowed to execute <FlowName>?",
56069
+ "who has permission to run this Apex class?",
56070
+ "which profiles can see the Sales dashboard?",
56071
+ "who can actually execute this scheduled job?",
56072
+ "which profiles have permission to run the <FlowName> flow?"
56073
+ ],
56074
+ "sfi.who_can_access_object": [
56075
+ "who can access the Order object?",
56076
+ "which profiles and perm sets give read access to Case?",
56077
+ "who can see custom object <ObjectName>?",
56078
+ "which users have access to the financial data object?",
56079
+ "show me everyone who can view Account records",
56080
+ "who can see records on the Order object?"
56081
+ ],
56082
+ "sfi.why_cant_user_see_record": [
56083
+ "user can't see the Case record \u2014 why?",
56084
+ "John's not seeing the Account \u2014 what's blocking them?",
56085
+ "a user is getting access denied on Opportunity \u2014 what's wrong?",
56086
+ "why is this record invisible to a specific user?",
56087
+ "I need to figure out why a user can't open this record",
56088
+ "user says they get a 'No access' error on a Contact record \u2014 what gives?"
56089
+ ],
56090
+ "sfi.layout_for_user": [
56091
+ "what page layout does <Profile> see on Case?",
56092
+ "which layout gets shown to the Sales rep on Opportunity?",
56093
+ "show me the layout assignment for the <Profile> profile on Contact",
56094
+ "which record type and layout does this user get on Account?",
56095
+ "what layout will a support agent see on a Case?",
56096
+ "which page layout does the <Profile> profile see on Contact?"
56097
+ ],
56098
+ "sfi.user_ability": [
56099
+ "can <Username> edit Cases?",
56100
+ "does this user have permission to delete Contacts?",
56101
+ "what can a specific user actually do in the org?",
56102
+ "check if <Username> can create Opportunities",
56103
+ "what record types can this user access on Case?",
56104
+ "what flows can users with the <Profile> profile actually run?"
56105
+ ],
56106
+ "sfi.profile_security": [
56107
+ "how locked down is the <Profile> profile?",
56108
+ "show me the security settings for the Sales profile",
56109
+ "does <Profile> have any dangerous permissions like Modify All?",
56110
+ "what system permissions does the <Profile> profile have?",
56111
+ "security review for the support agent profile",
56112
+ "which profiles have Apex execute permissions?"
56113
+ ],
56114
+ "sfi.lightning_pages": [
56115
+ "what Lightning pages exist for the Case object?",
56116
+ "show me all App Builder pages",
56117
+ "which Lightning pages use the <Component> component?",
56118
+ "what's on the Account record page?",
56119
+ "list all Lightning Experience record pages",
56120
+ "which Lightning record pages are assigned to the Case object?"
56121
+ ],
56122
+ "sfi.list_view_sharing": [
56123
+ "who can see the 'My Open Cases' list view?",
56124
+ "which list views on Contact are visible to everyone?",
56125
+ "show me sharing settings for list views on Opportunity",
56126
+ "who has access to the Sales team list view?",
56127
+ "list views on Account \u2014 who can see which ones?",
56128
+ "who can see the 'My Open Cases' list view on Case?"
56129
+ ],
56130
+ "sfi.app_access": [
56131
+ "which users can see the Sales Cloud app?",
56132
+ "who has access to the Service Console?",
56133
+ "what apps is the <Profile> profile allowed to use?",
56134
+ "which Lightning apps are available to the support team?",
56135
+ "show me app access by profile",
56136
+ "which profiles can open the Service app?"
56137
+ ],
56138
+ "sfi.tab_availability": [
56139
+ "which tabs can the <Profile> profile see?",
56140
+ "is the Order tab visible to sales reps?",
56141
+ "which objects have tabs hidden from the support profile?",
56142
+ "show me tab settings for the <Profile> profile",
56143
+ "which tabs are set to 'Default Off' for a specific profile?",
56144
+ "which tabs are visible to users with the <Profile> profile?"
56145
+ ],
56146
+ "sfi.lifecycle_process": [
56147
+ "what's the Case lifecycle \u2014 statuses and transitions?",
56148
+ "show me the Opportunity stage progression",
56149
+ "what are the valid Lead status transitions?",
56150
+ "describe the record lifecycle for Order",
56151
+ "what's the path an Account goes through from creation to active?",
56152
+ "what happens when an Opportunity moves to Closed Won?"
56153
+ ],
56154
+ "sfi.layout_assignments": [
56155
+ "which layouts are assigned to which profiles on Case?",
56156
+ "show me all layout assignments for Contact",
56157
+ "what layout does the admin profile use on Account?",
56158
+ "layout to profile mapping on Opportunity",
56159
+ "which record type gets which layout on Case?",
56160
+ "which profiles are assigned to the Account Layout page layout?"
56161
+ ],
56162
+ "sfi.integration_map": [
56163
+ "what external systems does this org integrate with?",
56164
+ "show me all the integrations in this org",
56165
+ "what APIs does this Salesforce instance call out to?",
56166
+ "which third-party systems are connected?",
56167
+ "integration topology \u2014 what talks to what?",
56168
+ "what external systems is this org connected to?"
56169
+ ],
56170
+ "sfi.event_subscribers": [
56171
+ "what's subscribed to the Case platform event?",
56172
+ "who listens to <PlatformEvent__e>?",
56173
+ "show me all event subscribers in this org",
56174
+ "which flows or Apex classes consume platform events?",
56175
+ "what processes trigger off <EventName__e>?",
56176
+ "who listens to the <Event__e> platform event?"
56177
+ ],
56178
+ "sfi.guidance": [
56179
+ "how should I think about cleaning up this org?",
56180
+ "what's the recommended approach for migrating Process Builder flows?",
56181
+ "guide me through reducing technical debt in our Apex",
56182
+ "what's best practice for permission set management?",
56183
+ "how do I approach a security review in Salesforce?",
56184
+ "what's the recommended approach for building automation on Salesforce \u2014 Flow vs Apex?"
56185
+ ],
56186
+ "sfi.find_code_usages": [
56187
+ "where is <ClassName> referenced in the codebase?",
56188
+ "find all usages of <MethodName>",
56189
+ "which components use <CustomLabel>?",
56190
+ "where is the <ApexClass> class called from?",
56191
+ "find everything that references this utility class",
56192
+ "which LWC components reference the Account.AnnualRevenue field?"
56193
+ ],
56194
+ "sfi.lookup_record": [
56195
+ "look up the Case record with ID 5001234",
56196
+ "find the Account named Acme Corp",
56197
+ "pull up the Contact with this email address",
56198
+ "look up Order number 1234",
56199
+ "fetch the record for this Opportunity",
56200
+ "what are the values in the <MetadataRecord__mdt> custom metadata record?"
56201
+ ],
56202
+ "sfi.explain_field": [
56203
+ "what does the External_Id__c field on Contact mean?",
56204
+ "explain what Priority__c is used for on Case",
56205
+ "what's the purpose of Amount__c on Opportunity?",
56206
+ "what does this custom field do and where does it come from?",
56207
+ "explain the business meaning of Status__c on Order",
56208
+ "what are the picklist values on Opportunity.Stage?"
56209
+ ],
56210
+ "sfi.safe_to_delete_field": [
56211
+ "is it safe to delete Priority__c?",
56212
+ "can I remove this field without breaking anything?",
56213
+ "what would break if I deleted External_Id__c?",
56214
+ "is <Field__c> safe to delete?",
56215
+ "which fields are safe to remove from Contact?",
56216
+ "I want to clean up unused fields \u2014 is this one safe to drop?"
56217
+ ],
56218
+ "sfi.unused_components": [
56219
+ "which components in this org are not used anywhere?",
56220
+ "find all dead code \u2014 Apex classes that are never called",
56221
+ "what custom objects have no records and aren't referenced?",
56222
+ "show me unused permission sets",
56223
+ "which flows are deployed but never run?",
56224
+ "dead components \u2014 Apex, flows, whatever \u2014 give me the list"
56225
+ ],
56226
+ "sfi.find_dependency_cycles": [
56227
+ "whats the dependency cycle situation between the Course objects \u2014 triggers on <Object1>, <Object2>, <Object3>",
56228
+ "we have <Object1> and <Object2> \u2014 are they in a dependency loop with each other through triggers or flows?",
56229
+ "which Apex classes call each other in a circle?",
56230
+ "is there a circular dependency in our trigger code?",
56231
+ "do any triggers chain back into themselves?",
56232
+ "find recursive call chains in Apex",
56233
+ "do we have any circular flow invocations?",
56234
+ "which flows call each other in a loop?",
56235
+ "check for circular dependencies in the automation layer",
56236
+ "are there any infinite loops possible in our trigger/flow setup?"
56237
+ ],
56238
+ "sfi.apex_test_coverage": [
56239
+ "what's the test coverage for <ApexClass>?",
56240
+ "show me line-level coverage for the <TriggerName> trigger",
56241
+ "which lines in <ApexClass> are not covered by tests?",
56242
+ "does <ApexClass> meet the 75% coverage threshold?",
56243
+ "what's the coverage breakdown for the order management class?",
56244
+ "which Apex classes have no test coverage at all?"
56245
+ ],
56246
+ "sfi.automation_build_advisor": [
56247
+ "should I build this as a flow or a trigger?",
56248
+ "what's the recommended automation approach for updating Case on Contact change?",
56249
+ "I need to auto-populate a field on save \u2014 flow or Apex?",
56250
+ "advise me on the best way to automate account territory assignment",
56251
+ "how should I implement this business rule \u2014 flow, trigger, or process builder?",
56252
+ "before I add a new flow on Account, what already runs there?"
56253
+ ],
56254
+ "sfi.apex_build_advisor": [
56255
+ "how should I structure this Apex class?",
56256
+ "best practice for writing a trigger on Case?",
56257
+ "I'm building a batch job \u2014 any guidance?",
56258
+ "recommend an Apex pattern for async processing of Orders",
56259
+ "how do I avoid governor limits in this Apex scenario?",
56260
+ "before I write Apex for Opportunity, what patterns does existing code use?"
56261
+ ],
56262
+ "sfi.field_change_advisor": [
56263
+ "I want to add a new required field to Case \u2014 what should I consider?",
56264
+ "advise me on changing the type of Amount__c",
56265
+ "what do I need to think about before adding a lookup field to Contact?",
56266
+ "guidance on making External_Id__c a unique field",
56267
+ "field change advisory for renaming Status__c",
56268
+ "give me the full risk briefing before I modify the Priority__c field"
56269
+ ],
56270
+ "sfi.what_if_change_field_value": [
56271
+ "what happens if I change the Stage to Closed Won on these records?",
56272
+ "if I bulk-update Priority__c on all open Cases to High, what automation fires?",
56273
+ "what changes downstream when I set Status to Active on an Account?",
56274
+ "what would happen if someone changes the Amount__c to zero on an Opportunity?",
56275
+ "simulate changing Contact.LeadSource from Web to Partner",
56276
+ "if someone changes the External_Id__c value on an Account record, what integration breaks?"
56277
+ ],
56278
+ "sfi.value_change_audit": [
56279
+ "who changed the Status__c field on this Case and when?",
56280
+ "show me the history of changes to Amount__c on Opportunity",
56281
+ "audit trail for Priority__c changes on Case",
56282
+ "which users are changing Stage on Opportunity most frequently?",
56283
+ "show me all the value changes to External_Id__c in the last month",
56284
+ "which fields on Contact are risky to change values on?"
56285
+ ],
56286
+ "sfi.live_drift_check": [
56287
+ "has anything changed in the live org since the last vault refresh?",
56288
+ "check if the live org has drifted from the cached metadata",
56289
+ "are there new fields in production that aren't in our vault?",
56290
+ "live drift check \u2014 anything deployed since the last snapshot?",
56291
+ "compare live org to our stored metadata",
56292
+ "has the live org added or removed fields on Account since the last refresh?"
56293
+ ],
56294
+ "sfi.what_changed_since_refresh": [
56295
+ "what's been deployed since the last refresh?",
56296
+ "show me changes since we last pulled the org metadata",
56297
+ "what metadata has changed since the vault was updated?",
56298
+ "anything new or modified since the last snapshot?",
56299
+ "delta since last refresh \u2014 what changed?",
56300
+ "what types of components changed in the last refresh?"
56301
+ ],
56302
+ "sfi.org_history": [
56303
+ "show me the change history for this org",
56304
+ "what major changes have happened to this org over time?",
56305
+ "org changelog \u2014 what's been modified recently?",
56306
+ "historical view of org changes",
56307
+ "what was deployed to this org in the last quarter?",
56308
+ "show me the refresh history for this org"
56309
+ ],
56310
+ "sfi.diff_snapshots": [
56311
+ "compare the current org snapshot to last month's",
56312
+ "diff between the two most recent vault snapshots",
56313
+ "what changed between snapshot A and snapshot B?",
56314
+ "show me the diff between our production and sandbox snapshots",
56315
+ "compare two org states and tell me what's different",
56316
+ "compare the pre-release snapshot to the post-release snapshot"
56317
+ ],
56318
+ "sfi.churn": [
56319
+ "which components change most frequently?",
56320
+ "show me high-churn metadata \u2014 things we keep modifying",
56321
+ "what parts of the org are in constant flux?",
56322
+ "which Apex classes get touched every sprint?",
56323
+ "high-churn fields on Case \u2014 what keeps changing?",
56324
+ "which components had the most changes between these two snapshots?"
56325
+ ],
56326
+ "sfi.trend": [
56327
+ "how has the number of Cases grown over time?",
56328
+ "show me the trend in Opportunity creation",
56329
+ "is our lead volume increasing or decreasing?",
56330
+ "trend analysis for Account record creation",
56331
+ "how has our automation complexity changed over the last year?",
56332
+ "show me component count growth over time across all snapshots"
56333
+ ],
56334
+ "sfi.compare_components": [
56335
+ "compare the Contact layout between sandbox and production",
56336
+ "show me the diff between <FlowName> v1 and v2",
56337
+ "compare the Case validation rules in two orgs",
56338
+ "what's different between the <Profile> profile in sandbox vs prod?",
56339
+ "compare the <ApexClass> class across environments",
56340
+ "are these two record types actually different or just named differently?"
56341
+ ],
56342
+ "sfi.export_manifest": [
56343
+ "export the full org manifest",
56344
+ "create a package.xml with all custom components",
56345
+ "export a deployment manifest for the custom objects",
56346
+ "give me a manifest I can use to retrieve this org's metadata",
56347
+ "export all custom Apex and flows to a manifest",
56348
+ "generate a package.xml for these components so I can deploy them"
56349
+ ],
56350
+ "sfi.pii_inventory": [
56351
+ "which fields in this org store personally identifiable information?",
56352
+ "show me all PII fields \u2014 SSN, email, phone, etc.",
56353
+ "GDPR compliance \u2014 which fields hold personal data?",
56354
+ "inventory of fields that could contain sensitive customer data",
56355
+ "which objects store PII that we need to mask in sandboxes?",
56356
+ "which fields in this org store personally identifiable information?"
56357
+ ],
56358
+ "sfi.field_access_audit": [
56359
+ "who can read and write the Amount__c field?",
56360
+ "which profiles can edit SSN or sensitive financial fields?",
56361
+ "show me field-level security for Priority__c across all profiles",
56362
+ "who has write access to External_Id__c?",
56363
+ "field access audit for Status__c on Case",
56364
+ "who has read access to the SSN field on Contact?"
56365
+ ],
56366
+ "sfi.object_access_audit": [
56367
+ "which profiles can create, read, edit, delete Order records?",
56368
+ "show me CRUD permissions on Case by profile",
56369
+ "who has delete access to Account?",
56370
+ "object-level permission audit for Contact",
56371
+ "show me which profiles have full CRUD on custom object <ObjectName>",
56372
+ "which profiles have Create access on the Case object?"
56373
+ ],
56374
+ "sfi.recordtype_availability": [
56375
+ "which record types on Case are available to the Support profile?",
56376
+ "which record types does the <Profile> profile have access to on Opportunity?",
56377
+ "show me record type availability on Account by profile",
56378
+ "can the sales rep create a Partner Account record type?",
56379
+ "which record types on Contact are visible to the marketing team?",
56380
+ "which record types can the <Profile> profile create on Case?"
56381
+ ],
56382
+ "sfi.org_overview": [
56383
+ "break down the codebase footprint for me \u2014 LWC vs Aura vs Visualforce, and how much legacy VF debt is there",
56384
+ "how many custom objects does this org have?",
56385
+ "what are the top objects in this org?",
56386
+ "give me a tour of this Salesforce org",
56387
+ "what's in this org?",
56388
+ "quick inventory of the codebase",
56389
+ "org summary \u2014 what are we working with?",
56390
+ "high-level overview of this Salesforce instance",
56391
+ "describe this org to me like I just joined the team",
56392
+ "how big is this org \u2014 fields, flows, classes, the works?",
56393
+ "what's the overall complexity of this org?",
56394
+ "give me a lay of the land for this Salesforce environment"
56395
+ ],
56396
+ "sfi.domain_clusters": [
56397
+ "what functional areas does this org cover?",
56398
+ "show me the business domain clusters in this Salesforce org",
56399
+ "how is the metadata organized by business function?",
56400
+ "what logical groups of objects exist in this org?",
56401
+ "cluster the org's objects by business domain",
56402
+ "can you group the objects in this org into logical domain areas?"
56403
+ ],
56404
+ "sfi.changed_since": [
56405
+ "what changed in the org since last Tuesday?",
56406
+ "show me everything modified after January 1st",
56407
+ "what metadata was updated this week?",
56408
+ "changes since the last release",
56409
+ "what's been modified since the last sprint?",
56410
+ "what components were modified after 2024-01-01?"
56411
+ ],
56412
+ "sfi.last_modified": [
56413
+ "who last modified the <FlowName> flow?",
56414
+ "when was the Case layout last changed?",
56415
+ "who touched the <ApexClass> class most recently?",
56416
+ "when was Priority__c last updated?",
56417
+ "last modified date for all custom fields on Contact",
56418
+ "when was the Account trigger last changed and by whom?"
56419
+ ],
56420
+ "sfi.what_happens_on_save": [
56421
+ "I need everything that touches Case on save \u2014 flows, triggers, PB, validation. Full save cascade please.",
56422
+ "Ugh, we keep hitting record-lock errors \u2014 which of our automations run concurrently on Contact and could be colliding?",
56423
+ "give me a rundown of every automation that involves a Case",
56424
+ "what runs when I create a new Opportunity record?",
56425
+ "what automation fires on Account update?",
56426
+ "what triggers exist on Contact?",
56427
+ "if I save an Opportunity, what order does everything fire in?",
56428
+ "is there any automation on Order I should know about before adding more?",
56429
+ "walk me through everything Salesforce does when I hit Save on a Case",
56430
+ "before I add a new flow to Contact, what else is already running on save?",
56431
+ "which flows fire synchronously on Lead save?",
56432
+ "complete save-event automation inventory for the Account object",
56433
+ "what happens behind the scenes when I create a new Contact?"
56434
+ ],
56435
+ "sfi.why_field_changed": [
56436
+ "which flows write to Contact.Status__c?",
56437
+ "what sets the value of Opportunity.Amount__c?",
56438
+ "who or what writes to Priority__c on Case?",
56439
+ "trace every writer to External_Id__c",
56440
+ "what automation populates the Status field on Order?",
56441
+ "does any trigger or flow update Amount__c?",
56442
+ "We loaded leads from an external system and Converted_from_Lead__c is populated \u2014 what writes it?",
56443
+ "how is Priority__c getting set \u2014 is it a user, a flow, or a trigger?",
56444
+ "the Status field keeps changing on its own \u2014 what's causing it?",
56445
+ "trace all the things that could write to Case.Status__c",
56446
+ "which automation is responsible for populating Amount__c?"
56447
+ ],
56448
+ "sfi.order_of_execution": [
56449
+ "in what order do triggers, flows, and validation rules fire on Case save?",
56450
+ "walk me through the Salesforce order of execution for Opportunity",
56451
+ "when does before-trigger run vs after-trigger vs flow?",
56452
+ "is validation run before or after triggers?",
56453
+ "what's the order of operations when I save a Contact record?",
56454
+ "does the flow run before or after the Apex trigger?"
56455
+ ],
56456
+ "sfi.explain_flow": [
56457
+ "walk me through <FlowName>'s screens and decisions",
56458
+ "walk me thru the <FlowName> fault handling \u2014 if a step fails, does it retry, skip, or halt?",
56459
+ "Does <FlowName> invoke any Apex, and if so which class/method?",
56460
+ "explain the <FlowName> flow end to end \u2014 it's a screen flow, what's the user actually doing in it?",
56461
+ "what is <FlowName> for?",
56462
+ "when does <FlowName> fire and what does it change?",
56463
+ "break down <FlowName> logic",
56464
+ "what records does <FlowName> create or update?",
56465
+ "I inherited this flow and have no idea what it does \u2014 explain it to me",
56466
+ "summarize <FlowName> for a non-technical stakeholder",
56467
+ "what's the happy path through <FlowName>?",
56468
+ "does <FlowName> have any error handling?",
56469
+ "what variables does <FlowName> use?",
56470
+ "which object does <FlowName> primarily operate on?"
56471
+ ],
56472
+ "sfi.explain_apex_method": [
56473
+ "what does the <MethodName> method in <ApexClass> do?",
56474
+ "explain the <MethodName> method \u2014 inputs, outputs, side effects",
56475
+ "walk me through the logic of <ApexClass>.<MethodName>",
56476
+ "what does this method return and what does it modify?",
56477
+ "explain the business logic in the <MethodName> method",
56478
+ "what does the AccountController class do?"
56479
+ ],
56480
+ "sfi.explain_formula": [
56481
+ "what does this formula field actually calculate?",
56482
+ "explain the formula on <Field__c> in plain English",
56483
+ "break down this validation rule formula for me",
56484
+ "what's this formula doing \u2014 it's hard to read",
56485
+ "explain the formula logic on Amount__c",
56486
+ "what does this formula evaluate to and when?"
56487
+ ],
56488
+ "sfi.unused_fields_deep": [
56489
+ "which fields on Case have zero data and aren't referenced by anything?",
56490
+ "deep analysis of unused fields \u2014 both empty and unreferenced",
56491
+ "show me fields that are populated but never read by any automation",
56492
+ "find fields that appear in no layouts, no flows, no Apex",
56493
+ "which custom fields on Account are completely orphaned?",
56494
+ "which fields on Account have zero usage anywhere in the org?"
56495
+ ],
56496
+ "sfi.process_builder_migration_candidates": [
56497
+ "which Process Builder processes should we migrate to Flow?",
56498
+ "show me all Process Builder automations still active",
56499
+ "which processes are candidates for Flow migration?",
56500
+ "what Process Builder is left that we need to retire?",
56501
+ "identify Process Builder processes that would be straightforward to convert",
56502
+ "which Process Builders should we migrate to Flow first?"
56503
+ ],
56504
+ "sfi.unassigned_permission_sets": [
56505
+ "are there permission sets that aren't assigned to anyone?",
56506
+ "which permission sets are deployed but have zero users?",
56507
+ "find permission sets that are effectively dead \u2014 no assignments",
56508
+ "show me unassigned permission sets we could clean up",
56509
+ "permission sets nobody has \u2014 are there any?",
56510
+ "which permission sets aren't assigned to any user?"
56511
+ ],
56512
+ "sfi.empty_queues_and_groups": [
56513
+ "are there any queues with no members?",
56514
+ "show me empty public groups",
56515
+ "which queues have nobody in them?",
56516
+ "find groups and queues that are effectively abandoned",
56517
+ "empty queue check \u2014 anything routing to nowhere?",
56518
+ "which queues have no members assigned to them?"
56519
+ ],
56520
+ "sfi.tech_debt_score": [
56521
+ "what's our overall tech debt score?",
56522
+ "how much technical debt does this org carry?",
56523
+ "give me a technical debt rating for this Salesforce org",
56524
+ "debt score breakdown \u2014 what's contributing most?",
56525
+ "quantify the technical debt in this org",
56526
+ "what's the overall technical debt score for this org?"
56527
+ ],
56528
+ "sfi.code_quality_audit": [
56529
+ "how good is our Apex code quality?",
56530
+ "find code quality issues in our Apex classes",
56531
+ "audit the Apex code for common anti-patterns",
56532
+ "show me code quality violations",
56533
+ "which Apex classes have the most quality issues?",
56534
+ "are there any SOQL queries inside loops in our Apex code?"
56535
+ ],
56536
+ "sfi.governor_limit_risks": [
56537
+ "which parts of our Apex are at risk of hitting governor limits?",
56538
+ "find SOQL queries inside loops",
56539
+ "which triggers could hit DML limits?",
56540
+ "governor limit risk assessment for our Apex",
56541
+ "which batch jobs are close to the 50M record limit?",
56542
+ "find places where we're at risk of CPU timeout"
56543
+ ],
56544
+ "sfi.find_hardcoded_values": [
56545
+ "find hardcoded IDs in the Apex code",
56546
+ "which Apex classes have hardcoded email addresses?",
56547
+ "find hardcoded profile names in our triggers",
56548
+ "are there any hardcoded record type IDs in the code?",
56549
+ "show me hardcoded values in Apex that should be in Custom Settings",
56550
+ "are there hardcoded IDs or email addresses in our Apex code?"
56551
+ ],
56552
+ "sfi.crud_fls_audit": [
56553
+ "are we enforcing FLS in our Apex?",
56554
+ "which Apex classes skip CRUD/FLS checks?",
56555
+ "find Apex that queries without checking field-level security",
56556
+ "CRUD and FLS audit for our triggers",
56557
+ "which classes are vulnerable to privilege escalation via missing FLS?",
56558
+ "which Apex classes skip CRUD or FLS checks?"
56559
+ ],
56560
+ "sfi.test_coverage_gaps": [
56561
+ "where are the gaps in our test coverage?",
56562
+ "which Apex classes have coverage below 75%?",
56563
+ "find untested code paths in our triggers",
56564
+ "coverage gaps across all Apex",
56565
+ "which classes would block a deployment due to low coverage?",
56566
+ "which Apex classes have fake or missing test coverage?"
56567
+ ],
56568
+ "sfi.what_if_change_field_type": [
56569
+ "what happens if I change the type of Case.Priority__c from picklist to text?",
56570
+ "blast radius of changing Opportunity.Amount__c from currency to number",
56571
+ "if I convert Contact.External_Id__c from Text to EncryptedText, what breaks?",
56572
+ "can I safely change <Field> type from Number to Text?",
56573
+ "what downstream impact if I make Status__c a lookup instead of text?",
56574
+ "impact of changing field type on Account.<Field__c>",
56575
+ "I want to change a Text field to a Long Text Area \u2014 what's the risk?",
56576
+ "what breaks if I change the data type of this field?",
56577
+ "if I convert this currency field to a formula field, what automation breaks?",
56578
+ "blast radius of making Amount__c a formula field instead of editable"
56579
+ ],
56580
+ "sfi.what_if_remove_picklist_value": [
56581
+ "what happens if I delete the 'Pending' value from Case.Status?",
56582
+ "if I remove the 'Cold' picklist value from Lead.Rating, what records are affected?",
56583
+ "can I safely remove the 'Legacy' stage from Opportunity.StageName?",
56584
+ "what breaks if I remove a picklist value that records already use?",
56585
+ "impact of removing 'On Hold' from Case.Status picklist",
56586
+ "what breaks if I remove 'Closed - Duplicate' from Case.Status?"
56587
+ ],
56588
+ "sfi.what_if_make_field_required": [
56589
+ "what happens if I make Priority__c required on Case?",
56590
+ "if I make Amount__c required on Opportunity, what existing records would violate that?",
56591
+ "impact of making Contact.External_Id__c required",
56592
+ "can I safely make this field required without breaking flows?",
56593
+ "what automation would break if I make this field mandatory?",
56594
+ "what would break if I made Account.Website required?"
56595
+ ],
56596
+ "sfi.what_if_deactivate_flow": [
56597
+ "what breaks if I deactivate <FlowName>?",
56598
+ "if I turn off this flow, what processes depend on it?",
56599
+ "impact of deactivating the Case assignment flow",
56600
+ "can I safely deactivate <FlowName> without breaking anything?",
56601
+ "what would stop working if I switch off <FlowName>?",
56602
+ "what happens if I deactivate the <FlowName> flow?"
56603
+ ],
56604
+ "sfi.what_if_disable_trigger": [
56605
+ "what breaks if I disable the Contact trigger?",
56606
+ "impact of disabling <TriggerName>",
56607
+ "if I turn off this trigger, what automation stops running?",
56608
+ "can I safely disable the Account trigger?",
56609
+ "what would I lose if the Case trigger was off?",
56610
+ "if I disable the Account trigger, what stops working?"
56611
+ ],
56612
+ "sfi.what_if_change_method_signature": [
56613
+ "what breaks if I change the signature of <MethodName>?",
56614
+ "impact of renaming the <MethodName> method in <ApexClass>",
56615
+ "if I add a parameter to <MethodName>, what callers need updating?",
56616
+ "which classes call <MethodName> and would be affected by signature change?",
56617
+ "can I remove a parameter from <MethodName> without breaking anything?",
56618
+ "what breaks if I rename the calculateTotal() method signature?"
56619
+ ],
56620
+ "sfi.what_if_merge_profiles": [
56621
+ "I want to consolidate the permission set group \u2014 what would break if I merge <PermSetA> and <PermSetB>?",
56622
+ "can I combine the Sales and Sales Manager profiles?",
56623
+ "what conflicts exist between <ProfileA> and <ProfileB>?",
56624
+ "merge <Profile1> into <Profile2> \u2014 what are the collisions?",
56625
+ "what permission conflicts come up if I merge these two profiles?",
56626
+ "impact of combining two permission sets into one",
56627
+ "if I consolidate our three sales profiles into one, what conflicts arise?",
56628
+ "what would I lose or gain by merging <PermSet1> and <PermSet2>?"
56629
+ ],
56630
+ "sfi.what_if_split_profile": [
56631
+ "should I split the Admin profile into two?",
56632
+ "impact of splitting the <Profile> profile by region",
56633
+ "how would I split the Sales profile into read-only and full-access variants?",
56634
+ "what's involved in splitting a monolithic profile into permission sets?",
56635
+ "if I break this profile into two, what do I need to account for?",
56636
+ "how would I split the <Profile> profile into multiple permission sets?"
56637
+ ],
56638
+ "sfi.generate_data_dictionary": [
56639
+ "generate a data dictionary for this org",
56640
+ "create documentation of all our fields and objects",
56641
+ "build a data dictionary I can share with the data team",
56642
+ "export field descriptions and types for all custom objects",
56643
+ "generate a schema reference document",
56644
+ "generate a data dictionary for the Contact object"
56645
+ ],
56646
+ "sfi.generate_admin_handbook": [
56647
+ "write an admin handbook for this org",
56648
+ "generate a guide for onboarding a new Salesforce admin",
56649
+ "create an administrative reference document",
56650
+ "build an ops runbook for our Salesforce configuration",
56651
+ "document the admin processes in this org",
56652
+ "create an admin handbook for this org"
56653
+ ],
56654
+ "sfi.generate_architecture_overview": [
56655
+ "generate an architecture overview for this org",
56656
+ "write a technical architecture document",
56657
+ "create a high-level architecture summary for stakeholders",
56658
+ "document the overall structure of this Salesforce implementation",
56659
+ "generate an architecture diagram description for this org",
56660
+ "write an architecture overview document for this Salesforce org"
56661
+ ],
56662
+ "sfi.generate_sharing_summary": [
56663
+ "generate a sharing model summary for this org",
56664
+ "document how record sharing works in this org",
56665
+ "create a sharing and visibility overview",
56666
+ "write up the sharing architecture \u2014 OWD, roles, sharing rules",
56667
+ "generate a sharing model document I can review with security",
56668
+ "generate a sharing model summary for this org"
56669
+ ],
56670
+ "sfi.generate_compliance_report": [
56671
+ "generate a compliance report for this org",
56672
+ "create a GDPR compliance overview",
56673
+ "document our data handling practices from a compliance perspective",
56674
+ "produce a security and compliance report",
56675
+ "generate a report for an auditor about our data controls",
56676
+ "produce a compliance report for data privacy fields in this org"
56677
+ ],
56678
+ "sfi.generate_onboarding_doc": [
56679
+ "generate an onboarding guide for new users",
56680
+ "create a getting started document for sales reps",
56681
+ "write onboarding documentation for the support team",
56682
+ "generate a new hire guide to our Salesforce org",
56683
+ "create documentation for someone who has never used our Salesforce before",
56684
+ "generate an onboarding document for a new admin joining this org"
56685
+ ],
56686
+ "sfi.call_graph": [
56687
+ "Are there any flows nested inside other flows that make things hard to follow?",
56688
+ "what does <ApexClass> call downstream?",
56689
+ "trace the full call chain from <TriggerName>",
56690
+ "what classes does <ApexClass> depend on transitively?",
56691
+ "upstream callers of <ApexClass>",
56692
+ "what's the call depth on <MethodName>?",
56693
+ "show me the full dependency chain for <ApexClass>",
56694
+ "which flows invoke other flows?",
56695
+ "what's the transitive call graph from <ApexClass>?",
56696
+ "give me all callers and callees for <ApexClass>",
56697
+ "how deep is the call stack for <TriggerName>?",
56698
+ "subflow map \u2014 which flows call other flows?"
56699
+ ],
56700
+ "sfi.downstream_effects": [
56701
+ "what does changing <Field__c> ripple out to?",
56702
+ "show me the downstream effects of modifying <ApexClass>",
56703
+ "if I change <FlowName>, what else is affected?",
56704
+ "downstream impact of updating the Case status picklist",
56705
+ "what does it affect if <ComponentName> changes?",
56706
+ "what side effects does the AccountTrigger handler produce downstream?"
56707
+ ],
56708
+ "sfi.test_coverage_for_method": [
56709
+ "what tests cover the <MethodName> method?",
56710
+ "which test classes exercise <ApexClass>.<MethodName>?",
56711
+ "is there a test for the <MethodName> method?",
56712
+ "show me the test coverage for this specific method",
56713
+ "what unit tests hit <MethodName>?",
56714
+ "which test classes exercise the processPayment() method?"
56715
+ ],
56716
+ "sfi.meaningful_test_audit": [
56717
+ "are our tests actually meaningful or just padding coverage?",
56718
+ "which tests have no real assertions?",
56719
+ "find tests that just inflate coverage without testing anything",
56720
+ "audit our test suite for meaningful assertions",
56721
+ "are these tests actually checking anything useful?",
56722
+ "which of our test classes have no real assertions \u2014 just System.assert(true)?"
56723
+ ],
56724
+ "sfi.method_reachability": [
56725
+ "is <MethodName> reachable from any active flow or trigger?",
56726
+ "can this method actually be called in production?",
56727
+ "which entry points reach <MethodName>?",
56728
+ "is <ApexClass>.<MethodName> dead code?",
56729
+ "what paths lead to <MethodName> being invoked?",
56730
+ "is the AccountService class reachable from any trigger or REST endpoint?"
56731
+ ],
56732
+ "sfi.tests_for_change": [
56733
+ "which tests do I need to run after changing <ApexClass>?",
56734
+ "what test classes cover the code I'm about to modify?",
56735
+ "I changed <FlowName> \u2014 which tests should I run?",
56736
+ "which tests are relevant to my changes in <ApexClass>?",
56737
+ "give me the test suite to run before deploying <ComponentName>",
56738
+ "if I change the AccountService and CaseHandler classes, which tests should I run?"
56739
+ ],
56740
+ "sfi.cdc_subscribers": [
56741
+ "what's subscribed to Change Data Capture events on Contact?",
56742
+ "who listens to CDC events on Account?",
56743
+ "show me all Change Data Capture subscribers",
56744
+ "which flows or external systems consume CDC events?",
56745
+ "what processes react to Account change events?",
56746
+ "which flows or triggers listen to Account CDC change events?"
56747
+ ],
56748
+ "sfi.async_chain_depth": [
56749
+ "how deep is the async chain for <ApexClass>?",
56750
+ "are there chained queueables that go too deep?",
56751
+ "show me the async job chain depth",
56752
+ "how many levels of chained async jobs do we have?",
56753
+ "which async jobs kick off other async jobs?",
56754
+ "how deep is the async call chain from the BatchProcessor class?"
56755
+ ],
56756
+ "sfi.scheduled_job_catalog": [
56757
+ "what scheduled jobs are running in this org?",
56758
+ "show me all the scheduled Apex jobs",
56759
+ "which batch jobs are scheduled?",
56760
+ "list all cron-based jobs in the org",
56761
+ "what's running on a schedule in this org?",
56762
+ "which Apex classes are scheduled to run on a cron schedule?"
56763
+ ],
56764
+ "sfi.outbound_message_catalog": [
56765
+ "what outbound messages are configured?",
56766
+ "show me all outbound SOAP messages",
56767
+ "which workflow rules send outbound messages?",
56768
+ "list all outbound message endpoints",
56769
+ "what external systems do we push data to via outbound messages?",
56770
+ "what outbound messages does this org send and to which endpoints?"
56771
+ ],
56772
+ "sfi.endpoint_catalog": [
56773
+ "what external endpoints does this org call out to?",
56774
+ "show me all named credentials and callout URLs",
56775
+ "which Apex classes make HTTP callouts?",
56776
+ "list all integration endpoints",
56777
+ "what APIs does our Salesforce code talk to?",
56778
+ "list all inbound and outbound API endpoints in this org"
56779
+ ],
56780
+ "sfi.field_meaning": [
56781
+ "what does Priority__c mean in our business context?",
56782
+ "explain the business purpose of External_Id__c",
56783
+ "what's the semantic meaning of Status__c on Order?",
56784
+ "what does Amount__c represent \u2014 is it revenue, cost, or something else?",
56785
+ "in plain English, what does this field track?",
56786
+ "what does the External_Id__c field on Account actually mean in this org?"
56787
+ ],
56788
+ "sfi.disambiguate_concepts": [
56789
+ "we have both Priority__c and Case.Priority \u2014 which one should I use?",
56790
+ "there are three things called Status in this org \u2014 which one matters?",
56791
+ "Amount__c vs Amount \u2014 what's the difference?",
56792
+ "we have External_Id__c on both Contact and Account \u2014 are they related?",
56793
+ "clarify the difference between <ConceptA> and <ConceptB> in this org",
56794
+ "is 'Status' the same concept as 'Stage' in this org or are they different?"
56795
+ ],
56796
+ "sfi.field_provenance": [
56797
+ "<Field__c> is populated but I don't know how \u2014 is it manually entered or automated?",
56798
+ "is Priority__c a formula or is it written by code?",
56799
+ "what's the source of truth for Contact.External_Id__c?",
56800
+ "how does Status__c get its value on Order?",
56801
+ "is Amount__c auto-populated or user-entered?",
56802
+ "where does the data in this field come from?",
56803
+ "is this field set by a trigger, a flow, or does a user type it in?",
56804
+ "trace the origin of values in <Field__c>"
56805
+ ],
56806
+ "sfi.find_field_anywhere": [
56807
+ "find all objects that have a field called External_Id__c",
56808
+ "is there a Priority field on any object in this org?",
56809
+ "search for any field named Status across all objects",
56810
+ "find fields named Amount__ on any object",
56811
+ "which objects have a field called Description?",
56812
+ "where is the Priority__c field used across flows, Apex, and layouts?"
56813
+ ],
56814
+ "sfi.find_semantic_field": [
56815
+ "find me a field that tracks customer churn risk",
56816
+ "is there a field that stores the contract end date?",
56817
+ "find a field related to billing frequency",
56818
+ "what field would hold escalation priority?",
56819
+ "find a field that represents the customer's account tier",
56820
+ "do we have a field that stores the customer's loyalty tier?"
56821
+ ],
56822
+ "sfi.find_hardcoded_values_anywhere": [
56823
+ "find hardcoded values anywhere \u2014 Apex, flows, validation rules, everywhere",
56824
+ "search all metadata types for hardcoded email addresses",
56825
+ "hardcoded IDs anywhere in the org \u2014 not just Apex",
56826
+ "find hardcoded profile names in flows too, not just triggers",
56827
+ "check flows and validation rules for hardcoded values, not just code",
56828
+ "are there hardcoded IDs in our validation rule formulas?"
56829
+ ],
56830
+ "sfi.find_clone_patterns": [
56831
+ "are there Apex classes that look like copy-paste of each other?",
56832
+ "find duplicate code patterns in our Apex",
56833
+ "which classes have very similar logic that should be refactored?",
56834
+ "show me clone candidates in our codebase",
56835
+ "find near-duplicate implementations across Apex classes",
56836
+ "are there Apex classes that look structurally identical to AccountHandler?"
56837
+ ],
56838
+ "sfi.find_dead_code": [
56839
+ "find Apex methods that are never called",
56840
+ "which Apex classes are completely unreferenced?",
56841
+ "show me dead code \u2014 methods, classes, that nothing calls",
56842
+ "find unreachable code paths in our Apex",
56843
+ "which classes can I safely delete because nothing uses them?",
56844
+ "which Apex classes have no callers and look completely dead?"
56845
+ ],
56846
+ "sfi.package_impact": [
56847
+ "I keep hearing '<PackageName>' thrown around. What is it, how deep is it in our org, and are we locked into it?",
56848
+ "how entangled is the <Package> package in our customizations?",
56849
+ "if we uninstall <Package> what breaks?",
56850
+ "what does our org depend on from <Package>?",
56851
+ "impact of removing the <PackageName> managed package",
56852
+ "show me everything we've built on top of <Package>",
56853
+ "how many of our custom components reference <Package> components?"
56854
+ ],
56855
+ "sfi.cpq_rule_chain": [
56856
+ "show me the CPQ pricing rule chain for this product",
56857
+ "what CPQ rules fire when configuring a quote?",
56858
+ "trace the CPQ rule execution for this product bundle",
56859
+ "which CPQ validation and pricing rules apply to Order products?",
56860
+ "show me the CPQ rule dependencies",
56861
+ "what's the order of CPQ product rules that apply to the Bundle product?"
56862
+ ],
56863
+ "sfi.cpq_quote_template_breakdown": [
56864
+ "break down the CPQ quote template structure",
56865
+ "what sections and line items does this quote template include?",
56866
+ "explain the CPQ quote template layout",
56867
+ "show me the components of our primary quote template",
56868
+ "what's in the standard CPQ quote template?",
56869
+ "what sections are in the Standard Quote Template?"
56870
+ ],
56871
+ "sfi.cpq_dependency_map": [
56872
+ "show me the CPQ dependency map",
56873
+ "how do CPQ objects relate to each other?",
56874
+ "map out the CPQ data model in this org",
56875
+ "what CPQ objects and relationships do we have configured?",
56876
+ "dependency graph for our CPQ implementation",
56877
+ "what SBQQ__ field references exist in this org's CPQ configuration?"
56878
+ ],
56879
+ "sfi.field_360": [
56880
+ "give me a 360 view of the Priority__c field",
56881
+ "full profile of External_Id__c \u2014 who uses it, what sets it, where does it appear",
56882
+ "complete field analysis for Amount__c on Opportunity",
56883
+ "everything about Status__c on Case \u2014 definition, usage, writers, layouts",
56884
+ "field 360 for Contact.LeadSource \u2014 the full picture",
56885
+ "give me the complete picture of the Amount__c field on Opportunity \u2014 everything touching it"
56886
+ ],
56887
+ "sfi.field_lineage": [
56888
+ "show me the lineage of Priority__c \u2014 where does it come from and where does it go?",
56889
+ "trace the data lineage for Amount__c from source to destination",
56890
+ "where does External_Id__c originate and how does it flow through the org?",
56891
+ "field lineage for Status__c \u2014 what populates it and what reads it?",
56892
+ "data lineage for a key field on Account",
56893
+ "trace where the Opportunity.Amount__c field gets its value from and what it feeds"
56894
+ ],
56895
+ "sfi.compare_vaults": [
56896
+ "compare our production org to our sandbox",
56897
+ "show me differences between the two vault snapshots",
56898
+ "what's different between our dev sandbox and production?",
56899
+ "compare the two org configurations",
56900
+ "vault diff between production and staging",
56901
+ "how does our sandbox differ from production in terms of metadata?"
56902
+ ],
56903
+ "sfi.promotion_readiness": [
56904
+ "is the sandbox ready to promote to production?",
56905
+ "what's blocking promotion from dev to production?",
56906
+ "promotion readiness check for this sandbox",
56907
+ "are there any conflicts that would prevent a promotion?",
56908
+ "show me what needs to be resolved before we can promote",
56909
+ "what sandbox-only components need to be promoted to production?"
56910
+ ],
56911
+ "sfi.compare_object_across_vaults": [
56912
+ "compare the Case object between sandbox and production",
56913
+ "how does the Contact object differ across our orgs?",
56914
+ "show me differences in the Account schema between two vaults",
56915
+ "compare custom object <ObjectName> across environments",
56916
+ "Case object schema diff between dev and prod",
56917
+ "are the Contact fields the same in sandbox vs production?"
56918
+ ],
56919
+ "sfi.compare_profile_across_vaults": [
56920
+ "compare the Sales profile between sandbox and production",
56921
+ "how does <Profile> differ across our orgs?",
56922
+ "show me profile differences between environments",
56923
+ "compare admin profile between dev and production",
56924
+ "which permissions exist in production that are missing from sandbox?",
56925
+ "does the <Profile> profile have the same grants in sandbox vs prod?"
56926
+ ],
56927
+ "sfi.field_mapping_between_objects": [
56928
+ "how does the Contact object map to Account?",
56929
+ "show me field mappings between Lead and Contact/Account on conversion",
56930
+ "what fields get mapped during Case to Opportunity conversion?",
56931
+ "field mapping between Order and Quote objects",
56932
+ "how do fields on <Object1> map to fields on <Object2>?",
56933
+ "map the Lead fields to Contact fields for conversion \u2014 what would lose data?"
56934
+ ],
56935
+ "sfi.datatransform_field_map": [
56936
+ "show me the DataTransform field mappings",
56937
+ "what field mappings are defined in this DataTransform?",
56938
+ "explain the field mapping configuration for this DataTransform",
56939
+ "what source and target fields does this DataTransform map?",
56940
+ "DataTransform mapping details for the Contact integration",
56941
+ "what field mappings does the <DataRaptorName> DataRaptor define?"
56942
+ ],
56943
+ "sfi.decision_table_browse": [
56944
+ "show me the decision tables in this org",
56945
+ "browse the Decision Table for pricing rules",
56946
+ "what decision tables exist on the Case object?",
56947
+ "list all configured decision tables",
56948
+ "show me the entries in the routing decision table",
56949
+ "what are the input and output parameters of the <DecisionTable> decision table?"
56950
+ ],
56951
+ "sfi.integration_procedure_chain": [
56952
+ "show me the OmniStudio Integration Procedure chain for <IntegrationProcedure>",
56953
+ "what does this Integration Procedure call?",
56954
+ "trace the IP call chain from <IntegrationProcedure>",
56955
+ "which Integration Procedures call each other?",
56956
+ "explain the Integration Procedure flow for this use case",
56957
+ "walk me through the steps in the <IPName> Integration Procedure"
56958
+ ],
56959
+ "sfi.omniscript_flow": [
56960
+ "walk me through the OmniScript flow for <OmniScriptName>",
56961
+ "explain the screens and steps in this OmniScript",
56962
+ "what elements does this OmniScript contain?",
56963
+ "describe the OmniScript user journey for <OmniScriptName>",
56964
+ "what Integration Procedures does this OmniScript call?",
56965
+ "what screens and steps does the <OmniScriptName> OmniScript walk the user through?"
56966
+ ],
56967
+ "sfi.omniuicard_widget_breakdown": [
56968
+ "break down the widgets in this OmniUI Card",
56969
+ "what components make up this FlexCard?",
56970
+ "show me the widget structure of <OmniUiCardName>",
56971
+ "explain the layout of this OmniUI Card",
56972
+ "what data sources does this FlexCard use?",
56973
+ "what widgets are in the <FlexCardName> FlexCard?"
56974
+ ],
56975
+ "sfi.find_component_usages": [
56976
+ "why cant i see the <ComponentName> thing, is it a permission",
56977
+ "who has the <PermSet> perm set",
56978
+ "who's allowed to view the <ComponentName> component?",
56979
+ "what uses the <ComponentName> \u2014 everywhere it's referenced or invoked",
56980
+ "where is this flow referenced?",
56981
+ "what calls into <ComponentName>?",
56982
+ "is <ComponentName> used anywhere?",
56983
+ "does anything still use <ComponentName>?",
56984
+ "find every place <ComponentName> is referenced",
56985
+ "which layouts include the <ComponentName> component?",
56986
+ "which flows invoke <ComponentName>?",
56987
+ "where is this custom label used?",
56988
+ "what uses the <PermSet> permission set \u2014 show me all assignments and references"
56989
+ ],
56990
+ "sfi.installed_package_catalog": [
56991
+ "what managed packages are installed in this org?",
56992
+ "which packages do we rely on?",
56993
+ "i keep seeing <prefix>__ everywhere \u2014 whats that prefix and what owns it?",
56994
+ "show me all installed AppExchange packages",
56995
+ "give me an inventory of all managed packages",
56996
+ "which packages are installed and what version are they on?",
56997
+ "what third-party packages are we running?",
56998
+ "list all packages \u2014 namespace, publisher, version"
56999
+ ],
57000
+ "sfi.annotations": [
57001
+ "show me all annotations for <ComponentName>",
57002
+ "what notes have been added to this component?",
57003
+ "are there any annotations on the <FlowName> flow?",
57004
+ "list component annotations in this org",
57005
+ "show me existing metadata annotations",
57006
+ "what ownership or lifecycle annotations have been added to components?"
57007
+ ],
57008
+ "sfi.propose_annotation": [
57009
+ "suggest an annotation for <ComponentName>",
57010
+ "what annotation would you add to this Apex class?",
57011
+ "propose a description for the <FlowName> flow",
57012
+ "generate an annotation for this component based on what it does",
57013
+ "write a description I can add to <ComponentName>",
57014
+ "flag the Amount__c field as PII-sensitive so this is recorded"
57015
+ ],
57016
+ "sfi.component_history": [
57017
+ "show me the change history for <ComponentName>",
57018
+ "who has modified the <FlowName> flow over time?",
57019
+ "what changes have been made to <ApexClass> historically?",
57020
+ "component changelog for <ComponentName>",
57021
+ "has <ComponentName> changed recently?",
57022
+ "when was the AccountTrigger last changed and what did it look like before?"
57023
+ ],
57024
+ "sfi.component_as_of": [
57025
+ "show me what <ComponentName> looked like last month",
57026
+ "what did the <FlowName> flow look like before the last change?",
57027
+ "show me <ApexClass> as of the January snapshot",
57028
+ "what was the state of <ComponentName> at the time of the release?",
57029
+ "restore view of <ComponentName> from a specific date",
57030
+ "show me what the Account trigger looked like at the v1.0 tag"
57031
+ ]
57032
+ };
57033
+ }
57034
+ });
57035
+
55105
57036
  // ../mcp/dist/src/semantic-funnel.js
55106
- var STOPWORDS, SYNONYMS, PHRASE_SYNONYMS3, applyPhraseSynonyms3, tokenize6, TOOL_KEYWORDS, PLANE_OVERRIDES, planeForTool, EXCLUDED_FROM_CANDIDATES, liveRequiredForPlane, buildPlaneByTool, cachedPlanes, getPlaneByTool, resolveCandidatePlane, expand, buildToolDocs, cached, buildIndex, getFunnelIndex, CONF_HIGH_TOP1, CONF_HIGH_MARGIN, CONF_HIGH_COVERAGE, CONF_FLOOR_TOP1, CONF_MIN_COVERAGE, CONF_LOW_MARGIN, CONF_RESCUE_TOP1, CONF_RESCUE_COVERAGE, CONF_LOW_COVERAGE, funnelConfidence, semanticCandidates;
57037
+ var STOPWORDS, SYNONYMS, PHRASE_SYNONYMS3, applyPhraseSynonyms3, tokenize6, TOOL_KEYWORDS, PLANE_OVERRIDES, planeForTool, EXCLUDED_FROM_CANDIDATES, liveRequiredForPlane, buildPlaneByTool, cachedPlanes, getPlaneByTool, resolveCandidatePlane, EXPANSION_WEIGHT, expandWeighted, buildToolDocs, cached, buildIndex, getFunnelIndex, CONF_HIGH_TOP1, CONF_HIGH_MARGIN, CONF_HIGH_COVERAGE, CONF_FLOOR_TOP1, CONF_MIN_COVERAGE, CONF_LOW_MARGIN, CONF_RESCUE_TOP1, CONF_RESCUE_COVERAGE, CONF_LOW_COVERAGE, funnelConfidence, semanticCandidates;
55107
57038
  var init_semantic_funnel = __esm({
55108
57039
  "../mcp/dist/src/semantic-funnel.js"() {
55109
57040
  "use strict";
57041
+ init_funnel_utterances();
55110
57042
  init_capabilities();
55111
57043
  init_tools();
55112
57044
  STOPWORDS = /* @__PURE__ */ new Set([
@@ -55236,14 +57168,126 @@ var init_semantic_funnel = __esm({
55236
57168
  blow: ["governor", "limit", "risk", "exceed"],
55237
57169
  populated: ["population", "filled", "fill", "usage"],
55238
57170
  required: ["mandatory", "needed", "blank", "null"],
55239
- walk: ["explain", "trace", "describe", "order", "execution"]
57171
+ walk: ["explain", "trace", "describe", "order", "execution"],
57172
+ // ---- router-v2 P3 vocabulary bridges (informal → technical). Grown from the
57173
+ // labeled funnel-miss diagnosis; every entry is GENERIC Salesforce vocabulary
57174
+ // (never org-specific) and safe at EXPANSION_WEIGHT (originals always win).
57175
+ // "who has / who's assigned" family → grants & assignment vocabulary
57176
+ assigned: ["permission", "permissionset", "granted", "assignment", "member", "usages"],
57177
+ perm: ["permission", "permissions", "permissionset"],
57178
+ granted: ["permission", "access", "assigned", "effective"],
57179
+ holds: ["assigned", "granted", "permission"],
57180
+ allowed: ["permission", "access", "granted", "effective"],
57181
+ actually: ["effective", "permission", "access"],
57182
+ union: ["effective", "permission", "combined"],
57183
+ combine: ["merge", "effective", "union"],
57184
+ consolidate: ["merge", "combine", "profiles", "permission"],
57185
+ conflicts: ["merge", "conflict", "overlap", "duplicate"],
57186
+ cant: ["access", "permission", "visibility", "see", "blocked"],
57187
+ cannot: ["access", "permission", "visibility", "see", "blocked"],
57188
+ unable: ["access", "permission", "visibility", "see"],
57189
+ // "what writes / what sets" family → writer / provenance vocabulary
57190
+ writes: ["write", "writer", "provenance", "populates", "updates", "changed", "field"],
57191
+ wrote: ["writes", "writer", "provenance", "changed"],
57192
+ writer: ["writes", "provenance", "changed", "flow", "trigger"],
57193
+ sets: ["writes", "populates", "value", "changed"],
57194
+ populates: ["writes", "sets", "provenance", "value", "filled"],
57195
+ updates: ["update", "writes", "changed", "modified"],
57196
+ trace: ["lineage", "provenance", "history", "graph", "walk"],
57197
+ // "do we have / is there / find me" family → search & resolve vocabulary
57198
+ find: ["search", "lookup", "locate", "resolve", "matching", "usages"],
57199
+ lookup: ["find", "resolve", "search", "locate"],
57200
+ locate: ["find", "search", "lookup", "resolve"],
57201
+ called: ["named", "name", "resolve", "find"],
57202
+ named: ["called", "name", "resolve", "find"],
57203
+ anywhere: ["everywhere", "usages", "search", "find", "components"],
57204
+ everywhere: ["anywhere", "usages", "search", "find"],
57205
+ contains: ["references", "includes", "search", "element"],
57206
+ referencing: ["usage", "usages", "references", "used"],
57207
+ points: ["references", "usages", "depends"],
57208
+ // "what is this prefix" family → namespace / package vocabulary
57209
+ prefix: ["namespace", "package", "installed", "managed"],
57210
+ namespace: ["package", "prefix", "installed", "managed"],
57211
+ package: ["namespace", "installed", "managed", "prefix", "dependency"],
57212
+ installed: ["package", "packages", "namespace", "catalog"],
57213
+ managed: ["package", "namespace", "installed"],
57214
+ uninstall: ["package", "impact", "remove", "dependency"],
57215
+ entangled: ["package", "dependency", "impact", "uninstall"],
57216
+ // "what fires / what runs on save" family → save-cascade vocabulary
57217
+ fires: ["fire", "run", "runs", "trigger", "execution", "automation", "save"],
57218
+ fired: ["fire", "run", "trigger", "execution", "automation"],
57219
+ happens: ["fires", "runs", "execution", "automation", "save"],
57220
+ saving: ["save", "automation", "validation", "execution", "trigger"],
57221
+ saved: ["save", "automation", "execution", "trigger"],
57222
+ cascade: ["order", "execution", "save", "automation"],
57223
+ blocks: ["validation", "rule", "save", "required", "error"],
57224
+ blocking: ["validation", "rule", "save", "error"],
57225
+ stops: ["validation", "blocks", "save", "rule", "error"],
57226
+ prevents: ["validation", "blocks", "save", "rule"],
57227
+ automations: ["automation", "flow", "trigger", "workflow", "process", "rule"],
57228
+ lock: ["record", "save", "automation", "contention", "error"],
57229
+ // informal call-topology family → usages / call-graph vocabulary
57230
+ calls: ["call", "invoke", "invokes", "usage", "usages", "graph", "references"],
57231
+ invoke: ["call", "calls", "usage", "references", "apex"],
57232
+ invokes: ["call", "calls", "usage", "references", "apex"],
57233
+ invoked: ["call", "calls", "usage", "references"],
57234
+ nested: ["subflow", "call", "graph", "depth", "invoke"],
57235
+ subflow: ["flow", "call", "graph", "nested", "invoke"],
57236
+ subflows: ["flow", "flows", "call", "graph", "nested"],
57237
+ chain: ["call", "graph", "depth", "dependency", "order"],
57238
+ // dependency loops → cycle vocabulary
57239
+ loop: ["cycle", "cycles", "dependency", "recursion", "circular"],
57240
+ loops: ["cycle", "cycles", "dependency", "recursion", "circular"],
57241
+ circular: ["cycle", "cycles", "dependency", "loop"],
57242
+ recursive: ["cycle", "recursion", "loop", "dependency"],
57243
+ // blast-radius / pre-change safety family → impact vocabulary
57244
+ blast: ["impact", "radius", "breaks", "affected", "change"],
57245
+ radius: ["impact", "blast", "affected"],
57246
+ breaks: ["impact", "affected", "depend", "change", "break"],
57247
+ affected: ["impact", "downstream", "breaks"],
57248
+ downstream: ["impact", "effects", "affected", "dependency"],
57249
+ safe: ["impact", "risk", "change", "delete", "breaks"],
57250
+ safely: ["impact", "risk", "change", "breaks"],
57251
+ // error-driven diagnosis family
57252
+ error: ["failed", "failure", "exception", "validation", "blocked"],
57253
+ failed: ["error", "failure", "exception", "permission"],
57254
+ fault: ["error", "exception", "handling", "flow"],
57255
+ handling: ["fault", "error", "exception", "flow"],
57256
+ // exec-summary / org-tour family → overview & risk vocabulary
57257
+ tour: ["overview", "org", "summary", "inventory"],
57258
+ describe: ["overview", "explain", "summary"],
57259
+ breakdown: ["overview", "explain", "walk", "components", "summary"],
57260
+ big: ["size", "overview", "count", "many", "inventory"],
57261
+ size: ["count", "overview", "many", "storage"],
57262
+ leadership: ["risk", "summary", "overview", "report"],
57263
+ executive: ["risk", "summary", "overview", "report"],
57264
+ worried: ["risk", "report", "health"],
57265
+ confident: ["risk", "report", "health"],
57266
+ shape: ["health", "risk", "overview"],
57267
+ // cleanup / dead-code family
57268
+ unused: ["dead", "usages", "components", "cleanup"],
57269
+ dead: ["unused", "code", "usages"],
57270
+ cleanup: ["unused", "dead", "delete", "candidates"],
57271
+ // freshness / coverage stragglers
57272
+ outdated: ["stale", "fresh", "current"],
57273
+ refresh: ["vault", "stale", "fresh", "current", "health"],
57274
+ tested: ["test", "tests", "coverage"],
57275
+ untested: ["coverage", "gaps", "test", "tests"],
57276
+ // queue / assignment routing family
57277
+ queue: ["assignment", "routing", "owner", "group"],
57278
+ routed: ["assignment", "queue", "rule", "routing"]
55240
57279
  };
55241
57280
  PHRASE_SYNONYMS3 = [
55242
57281
  ["social security number", "ssn"],
55243
57282
  ["social security", "ssn"],
55244
57283
  ["date of birth", "dob"],
55245
57284
  ["postal code", "zip"],
55246
- ["zip code", "zip"]
57285
+ ["zip code", "zip"],
57286
+ // router-v2 P3: "break down X for me" is an EXPLAIN ask, but token-split it
57287
+ // becomes 'break' (→ impact/dependency synonyms) + 'down' — the impact
57288
+ // expansion hijacks the ranking. Collapse the phrase to the single token
57289
+ // 'breakdown' (whose SYNONYMS entry bridges to explain/overview/walk).
57290
+ ["break down", "breakdown"]
55247
57291
  ].sort((a2, b2) => b2[0].length - a2[0].length);
55248
57292
  applyPhraseSynonyms3 = (lower) => {
55249
57293
  let out = lower;
@@ -55281,7 +57325,12 @@ var init_semantic_funnel = __esm({
55281
57325
  "sfi.layout_for_user": "which layout does the profile user see page on object",
55282
57326
  "sfi.integration_map": "api limits at risk integration volume callout capacity external",
55283
57327
  "sfi.find_apex_usages": "which flows invoke call use apex classes methods from",
55284
- "sfi.live_folder_access": "who can access report dashboard folder pipeline see view shared"
57328
+ "sfi.live_folder_access": "who can access report dashboard folder pipeline see view shared",
57329
+ // router-v2 P3: the two labeled miss families for this tool are "who has this
57330
+ // permission set" (assignment/grant phrasing) and "what references this
57331
+ // component" — its (long) description dilutes those terms, so the overlay
57332
+ // re-weights them. Referee: funnel-recall blind-spot cases.
57333
+ "sfi.find_component_usages": "who has this permission set assigned granted holds it references referenced still used anywhere"
55285
57334
  };
55286
57335
  PLANE_OVERRIDES = {
55287
57336
  "sfi.blast_radius_live": "live",
@@ -55319,21 +57368,28 @@ var init_semantic_funnel = __esm({
55319
57368
  const plane = planeForTool(toolName);
55320
57369
  return { plane, liveRequired: liveRequiredForPlane(plane) };
55321
57370
  };
55322
- expand = (tokens) => {
55323
- const out = [...tokens];
57371
+ EXPANSION_WEIGHT = 0.5;
57372
+ expandWeighted = (tokens) => {
57373
+ const w2 = /* @__PURE__ */ new Map();
57374
+ for (const t2 of tokens)
57375
+ w2.set(t2, (w2.get(t2) ?? 0) + 1);
55324
57376
  for (const t2 of tokens) {
55325
57377
  const syn = SYNONYMS[t2];
55326
- if (syn !== void 0)
55327
- out.push(...syn);
57378
+ if (syn !== void 0) {
57379
+ for (const s2 of syn)
57380
+ if (!w2.has(s2))
57381
+ w2.set(s2, EXPANSION_WEIGHT);
57382
+ }
55328
57383
  }
55329
- return out;
57384
+ return w2;
55330
57385
  };
55331
57386
  buildToolDocs = () => {
55332
57387
  const docs = /* @__PURE__ */ new Map();
55333
57388
  for (const tool of V01_TOOLS) {
55334
57389
  const nameWords = tool.name.replace(/^sfi\./, "").replace(/_/g, " ");
55335
57390
  const keywords = TOOL_KEYWORDS[tool.name] ?? "";
55336
- docs.set(tool.name, `${nameWords} ${nameWords} ${tool.description} ${keywords}`);
57391
+ const utterances = (FUNNEL_UTTERANCES[tool.name] ?? []).join(" ");
57392
+ docs.set(tool.name, `${nameWords} ${nameWords} ${tool.description} ${keywords} ${utterances}`);
55337
57393
  }
55338
57394
  for (const cat of CATEGORIES) {
55339
57395
  const catText = ` ${cat.title} ${cat.description} ${cat.exampleQuestions.join(" ")}`;
@@ -55415,8 +57471,8 @@ var init_semantic_funnel = __esm({
55415
57471
  const idx = getFunnelIndex();
55416
57472
  const planes = getPlaneByTool();
55417
57473
  const rawQueryTokens = new Set(tokenize6(question, true));
55418
- const qTokens = expand(tokenize6(question, true));
55419
- if (qTokens.length === 0)
57474
+ const qWeights = expandWeighted(tokenize6(question, true));
57475
+ if (qWeights.size === 0)
55420
57476
  return [];
55421
57477
  let coverage = 0;
55422
57478
  if (rawQueryTokens.size > 0) {
@@ -55426,13 +57482,10 @@ var init_semantic_funnel = __esm({
55426
57482
  hits += 1;
55427
57483
  coverage = hits / rawQueryTokens.size;
55428
57484
  }
55429
- const qtf = /* @__PURE__ */ new Map();
55430
- for (const t2 of qTokens)
55431
- qtf.set(t2, (qtf.get(t2) ?? 0) + 1);
55432
57485
  const qvec = /* @__PURE__ */ new Map();
55433
57486
  let qnorm = 0;
55434
- for (const [term, f2] of qtf) {
55435
- const w2 = f2 / qTokens.length * (idx.idf.get(term) ?? 0);
57487
+ for (const [term, weight] of qWeights) {
57488
+ const w2 = weight * (idx.idf.get(term) ?? 0);
55436
57489
  if (w2 > 0) {
55437
57490
  qvec.set(term, w2);
55438
57491
  qnorm += w2 * w2;
@@ -55453,10 +57506,14 @@ var init_semantic_funnel = __esm({
55453
57506
  if (dot <= 0)
55454
57507
  continue;
55455
57508
  const score = dot / qnorm;
57509
+ const rounded = Math.round(score * 1e3) / 1e3;
55456
57510
  const planeEntry = planes.get(tool) ?? { plane: "vault", liveRequired: false };
55457
57511
  scored.push({
55458
57512
  tool,
55459
- score: Math.round(score * 1e3) / 1e3,
57513
+ score: rounded,
57514
+ // Pre-fusion semantic evidence — identical to `score` here; preserved
57515
+ // verbatim through route_question's regex-bonus fusion (P2 §4).
57516
+ cosine: rounded,
55460
57517
  category: idx.toolCategory.get(tool) ?? null,
55461
57518
  plane: planeEntry.plane,
55462
57519
  liveRequired: planeEntry.liveRequired
@@ -55506,7 +57563,7 @@ import { createHash as createHash7 } from "node:crypto";
55506
57563
  import { readFile as readFile82 } from "node:fs/promises";
55507
57564
  import { join as join23, resolve as resolve3 } from "node:path";
55508
57565
  import { z as z115 } from "zod";
55509
- var RESOLVE_FALLBACK_MAX_TOKENS, SAVE_ORDER_INTENTS, SINGLE_ENTITY_INTENTS, COMPARISON_ASIDE, stripComparisonAside, tryResolveFallback, routeQuestionInputSchema, routeTrust, clarificationIdFor, selectedEntityArgsForRoute, extractEntityQuery, inferEntityTypes, refineEntityResolution, applyGlossaryAliases, entityAmbiguityRequiresClarification, splitCompoundQuestion, mixedInventoryAndStoragePlan, PLAN_FAMILY, ASSESSMENT_FAMILY, ROUTE_PREAMBLE_TOOLS, TOOL_COMPATIBLE_TYPES, ACCESS_FLAVORED_TOOLS, resolvedTypeForGuard, applyComponentTypeGuard, REGEX_BONUS, resolveActiveVaultAlias, buildRouteToolArgsMap, mergeRouteHintsIntoCandidates, invokeFromArgsMap, rerankForMode, MARGIN, DESTRUCTIVE_TOOL, INFORMATIONAL_IMPACT_TOOL, planesDiverge, risksDiverge, marginClarification, LIVE_DISCLOSURE_LOOKAHEAD, liveConsentDisclosure, guidanceForMode, buildFunnelCandidates, routerMode, routeQuestionHandler;
57566
+ var RESOLVE_FALLBACK_MAX_TOKENS, SAVE_ORDER_INTENTS, SINGLE_ENTITY_INTENTS, COMPARISON_ASIDE, stripComparisonAside, tryResolveFallback, routeQuestionInputSchema, routeTrust, clarificationIdFor, selectedEntityArgsForRoute, extractEntityQuery, inferEntityTypes, normLiteral, refineEntityResolution, resolveParentQualifier, applyGlossaryAliases, entityAmbiguityRequiresClarification, hygienicClarificationOptions, splitCompoundQuestion, mixedInventoryAndStoragePlan, PLAN_FAMILY, ASSESSMENT_FAMILY, ROUTE_PREAMBLE_TOOLS, TOOL_COMPATIBLE_TYPES, ACCESS_FLAVORED_TOOLS, resolvedTypeForGuard, applyComponentTypeGuard, REGEX_BONUS, resolveActiveVaultAlias, buildRouteToolArgsMap, mergeRouteHintsIntoCandidates, invokeFromArgsMap, rerankForMode, MARGIN, FUNNEL_PRIMARY_MIN_SCORE, DESTRUCTIVE_TOOL, INFORMATIONAL_IMPACT_TOOL, planesDiverge, LIVE_DATA_SIGNAL, resolvePlaneTie, risksDiverge, marginClarification, LIVE_DISCLOSURE_LOOKAHEAD, liveConsentDisclosure, guidanceForMode, buildFunnelCandidates, routerMode, REFUSAL_INTENT, REFUSAL_GAP, refusalResponse, CONTEXT_SAVE_ORDER_TOOLS, CONTEXT_TOOL_ARG_KEYS, GENERIC_SCHEMA_INTENTS, contextArgsFor, buildContextContinuation, routeQuestionHandler;
55510
57567
  var init_route_question = __esm({
55511
57568
  "../mcp/dist/src/tools/route-question.js"() {
55512
57569
  "use strict";
@@ -55514,7 +57571,9 @@ var init_route_question = __esm({
55514
57571
  init_src2();
55515
57572
  init_src3();
55516
57573
  init_answer_render();
57574
+ init_context_resolution();
55517
57575
  init_intent_router();
57576
+ init_refusal_gates();
55518
57577
  init_semantic_funnel();
55519
57578
  init_resolve2();
55520
57579
  init_tool_profile();
@@ -55549,6 +57608,8 @@ var init_route_question = __esm({
55549
57608
  if (disposition !== "exact" && disposition !== "ambiguous" || candidates.length === 0) {
55550
57609
  return null;
55551
57610
  }
57611
+ const hygienicOptions = disposition === "ambiguous" ? hygienicClarificationOptions(candidates.slice(0, 5)) : [];
57612
+ const effectivelyExact = disposition === "exact" || hygienicOptions.length === 1;
55552
57613
  return {
55553
57614
  question,
55554
57615
  plane: "vault",
@@ -55558,13 +57619,13 @@ var init_route_question = __esm({
55558
57619
  needsResolve: true,
55559
57620
  reason: `Names a component (resolve disposition: ${disposition}) but asks nothing specific yet. Resolve it, then pick the analysis you want.`,
55560
57621
  gap: null,
55561
- confidence: disposition === "exact" ? "high" : "low",
57622
+ confidence: effectivelyExact ? "high" : "low",
55562
57623
  risk: "informational",
55563
57624
  alternatives: [],
55564
- clarification: disposition === "ambiguous" ? {
57625
+ clarification: disposition === "ambiguous" && !effectivelyExact ? {
55565
57626
  required: true,
55566
57627
  question: "Several components match. Which component did you mean?",
55567
- options: candidates.slice(0, 5).map((candidate) => candidate.id)
57628
+ options: hygienicOptions.map((candidate) => candidate.id)
55568
57629
  } : null,
55569
57630
  plan: [{
55570
57631
  stepId: "step-1",
@@ -55602,7 +57663,39 @@ var init_route_question = __esm({
55602
57663
  * coverage tools). When set, toolCandidates + a mode-specific guidance line are
55603
57664
  * always attached, regardless of the deterministic route's confidence.
55604
57665
  */
55605
- mode: z115.enum(["ask", "plan", "assessment"]).optional()
57666
+ mode: z115.enum(["ask", "plan", "assessment"]).optional(),
57667
+ /**
57668
+ * Router-v2 P5: HOST-PASSED conversation context. The product is STATELESS —
57669
+ * conversation memory belongs to the host, which passes what the PREVIOUS
57670
+ * turn was about so a terse follow-up ("does it fire on delete too?") can be
57671
+ * resolved; nothing is stored server-side. `context` absent ⇒ every code
57672
+ * path is byte-identical to a call without the feature. Value validation is
57673
+ * FAIL-OPEN (an unregistered `tool` / malformed `componentId` is ignored and
57674
+ * noted in `contextApplied.ignored`); shape errors reject normally. Context
57675
+ * strings are NEVER treated as question text — `previous.question` is only
57676
+ * ever re-dispatched through the full handler (refusal gates included) for
57677
+ * clarification continuation. A self-contained question IGNORES context.
57678
+ */
57679
+ context: z115.object({
57680
+ previous: z115.object({
57681
+ /** Canonical id the prior answer was about, e.g. "Flow:Order_Sync". */
57682
+ componentId: z115.string().min(1).max(256).optional(),
57683
+ /** Object api name in focus last turn, e.g. "Contact". */
57684
+ objectApiName: z115.string().min(1).max(256).optional(),
57685
+ /** sfi.* tool that produced the prior answer. */
57686
+ tool: z115.string().regex(/^sfi\.[a-z0-9_]+$/).optional(),
57687
+ /** route.intent of the prior turn (advisory only). */
57688
+ intent: z115.string().min(1).max(64).optional(),
57689
+ plane: z115.enum(["vault", "live", "hybrid"]).optional(),
57690
+ /** Prior turn's question text — required ONLY for clarification continuation. */
57691
+ question: z115.string().min(1).max(2e3).optional(),
57692
+ /** Open disambiguation set from the prior turn's blocking clarification. */
57693
+ clarification: z115.object({
57694
+ clarificationId: z115.string().min(1).max(64),
57695
+ options: z115.array(z115.string().min(1).max(256)).min(1).max(10)
57696
+ }).strict().optional()
57697
+ }).strict()
57698
+ }).strict().optional()
55606
57699
  });
55607
57700
  routeTrust = () => ({
55608
57701
  provenance: "offline_snapshot",
@@ -55645,7 +57738,7 @@ var init_route_question = __esm({
55645
57738
  return null;
55646
57739
  };
55647
57740
  extractEntityQuery = (question, intent) => {
55648
- const apiReference = question.match(/\b[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)\b|\b[A-Za-z][A-Za-z0-9_]*__(?:c|mdt|e|x|b|kav)\b/)?.[0];
57741
+ const apiReference = question.match(/\b[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)\b|\b[A-Za-z][A-Za-z0-9_]*__(?:c|mdt|e|x|b|kav)\b|\b[A-Za-z][A-Za-z0-9]*(?:_+[A-Za-z0-9]+)+\b/)?.[0];
55649
57742
  if (apiReference !== void 0) {
55650
57743
  if (intent === "what-if-method-signature" && apiReference.includes(".")) {
55651
57744
  return `${apiReference.slice(0, apiReference.indexOf("."))} class`;
@@ -55664,7 +57757,7 @@ var init_route_question = __esm({
55664
57757
  const typedPhrase = typedMatch?.[1];
55665
57758
  if (typedPhrase === void 0)
55666
57759
  return null;
55667
- const cleaned = typedPhrase.replace(/^(?:(?:what|whatever|which|who|where|when|why|how|is|are|can|does|do|did|show|find|explain|locate|list|calls?|invokes?|references?|owns?|edit|read|view|access|change|delete|remove|possible|values?|apex|api|version|data|type|for|of|the|this|that|in|on|to|used|assigned|set|every|all|each|any|me|a|an)\s+)+/i, "").trim();
57760
+ const cleaned = typedPhrase.replace(/^(?:(?:what|whats|whatever|which|who|whos|where|wheres|when|why|how|hows|is|are|can|does|do|did|show|find|explain|locate|list|walk|through|pull|give|gimme|tell|about|break|down|compare|effective|calls?|invokes?|references?|owns?|edit|read|view|access|change|delete|remove|possible|values?|apex|api|version|data|type|for|of|the|this|that|in|on|to|used|assigned|set|every|all|each|any|me|my|us|a|an)\s+)+/i, "").trim();
55668
57761
  if (/\b(?:and|when|should|use|there|any|that|no|available|tools?|difference|between|required\s+fields?|record\s+types?)\b/i.test(cleaned))
55669
57762
  return null;
55670
57763
  const distinctive = cleaned.replace(/\b(?:object|field|flow|class|trigger|page|layout|profile|permission|set|record|type|validation|rule|report|dashboard|data|logic)\b/gi, "").replace(/[^A-Za-z0-9_]+/g, "");
@@ -55677,8 +57770,6 @@ var init_route_question = __esm({
55677
57770
  inferEntityTypes = (query, intent, question) => {
55678
57771
  if (intent === "what-if-method-signature")
55679
57772
  return ["ApexClass"];
55680
- if (/\bfield\b/i.test(question))
55681
- return ["CustomField"];
55682
57773
  if (query.includes(".") && !/\sclass$/i.test(query))
55683
57774
  return ["CustomField"];
55684
57775
  if (/__(?:mdt|e|x|b|kav)$/i.test(query) || /(?:^object\s|\sobject(?:\s+on\s+\w+)?$)/i.test(query))
@@ -55705,18 +57796,40 @@ var init_route_question = __esm({
55705
57796
  return ["Report"];
55706
57797
  if (/(?:^dashboard\s|\sdashboard$)/i.test(query))
55707
57798
  return ["Dashboard"];
57799
+ const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
57800
+ const qualifier = question.match(new RegExp(`\\b${escaped}\\s+(object|flow|field|trigger|profile|permission\\s+set)\\b`, "i"))?.[1]?.toLowerCase().replace(/\s+/g, " ");
57801
+ if (qualifier === "object")
57802
+ return ["CustomObject"];
57803
+ if (qualifier === "flow")
57804
+ return ["Flow"];
57805
+ if (qualifier === "field")
57806
+ return ["CustomField"];
57807
+ if (qualifier === "trigger")
57808
+ return ["ApexTrigger"];
57809
+ if (qualifier === "profile")
57810
+ return ["Profile"];
57811
+ if (qualifier === "permission set")
57812
+ return ["PermissionSet"];
57813
+ if (/\bfield\b/i.test(question))
57814
+ return ["CustomField"];
55708
57815
  return [];
55709
57816
  };
57817
+ normLiteral = (value) => value.toLowerCase().replace(/[^a-z0-9]/g, "");
55710
57818
  refineEntityResolution = (query, resolution) => {
55711
57819
  if (resolution.disposition !== "ambiguous")
55712
57820
  return resolution;
55713
57821
  const normalized = query.toLowerCase();
55714
- const exactApi = resolution.candidates.filter((candidate) => candidate.apiName.toLowerCase() === normalized || candidate.id.toLowerCase().endsWith(`:${normalized}`));
57822
+ const looseQuery = normLiteral(query);
57823
+ const queryHasSpace = /\s/.test(query.trim());
57824
+ const exactApi = resolution.candidates.filter((candidate) => candidate.apiName.toLowerCase() === normalized || candidate.id.toLowerCase().endsWith(`:${normalized}`) || normLiteral(candidate.apiName) === looseQuery && looseQuery.length >= 4 && !(candidate.type === "CustomField" && queryHasSpace));
55715
57825
  const parentQualified = query.match(/^(.+?)\s+field\s+on\s+([A-Za-z][A-Za-z0-9_]*)$/i);
55716
57826
  const normalizedName = parentQualified?.[1]?.replace(/[^a-z0-9]/gi, "").toLowerCase();
55717
57827
  const normalizedParent = parentQualified?.[2]?.replace(/[^a-z0-9]/gi, "").toLowerCase();
55718
57828
  const exactParentField = normalizedName === void 0 || normalizedParent === void 0 ? [] : resolution.candidates.filter((candidate) => candidate.type === "CustomField" && candidate.apiName.replace(/__[a-z]+$/i, "").replace(/[^a-z0-9]/gi, "").toLowerCase() === normalizedName && candidate.parentApiName?.replace(/[^a-z0-9]/gi, "").toLowerCase() === normalizedParent);
55719
- const exact = exactParentField.length === 1 ? exactParentField : exactApi;
57829
+ const parentPlusName = queryHasSpace && looseQuery.length >= 4 ? resolution.candidates.filter((candidate) => candidate.type === "CustomField" && candidate.parentApiName !== null && normLiteral(candidate.parentApiName) + normLiteral(candidate.apiName.replace(/__[a-z]+$/i, "")) === looseQuery) : [];
57830
+ const objectLiteralNames = new Set(exactApi.filter((candidate) => candidate.type === "CustomObject").map((candidate) => normLiteral(candidate.apiName)));
57831
+ const exactApiNoTabTwins = exactApi.filter((candidate) => candidate.type !== "CustomTab" || !objectLiteralNames.has(normLiteral(candidate.apiName)));
57832
+ const exact = exactParentField.length === 1 ? exactParentField : parentPlusName.length === 1 ? parentPlusName : exactApiNoTabTwins.length === 1 ? exactApiNoTabTwins : exactApi;
55720
57833
  if (exact.length !== 1)
55721
57834
  return resolution;
55722
57835
  const winner = exact[0];
@@ -55726,6 +57839,31 @@ var init_route_question = __esm({
55726
57839
  candidates: [winner, ...resolution.candidates.filter((candidate) => candidate.id !== winner.id)]
55727
57840
  };
55728
57841
  };
57842
+ resolveParentQualifier = (question, resolution) => {
57843
+ if (resolution.disposition !== "ambiguous")
57844
+ return resolution;
57845
+ const top = resolution.candidates[0];
57846
+ if (top === void 0)
57847
+ return resolution;
57848
+ const family = resolution.candidates.filter((candidate) => candidate.apiName.toLowerCase() === top.apiName.toLowerCase() && candidate.parentApiName !== null);
57849
+ if (family.length < 2)
57850
+ return resolution;
57851
+ const questionWords = new Set(question.toLowerCase().split(/[^a-z0-9_]+/).filter((word) => word.length > 0));
57852
+ const namesParent = (parentApiName) => {
57853
+ const full = parentApiName.toLowerCase();
57854
+ const bare = full.replace(/__c$/, "").replace(/^[a-z0-9]+__/, "");
57855
+ return questionWords.has(full) || questionWords.has(bare) || questionWords.has(`${bare}s`);
57856
+ };
57857
+ const parentNamed = family.filter((candidate) => namesParent(candidate.parentApiName));
57858
+ if (parentNamed.length !== 1)
57859
+ return resolution;
57860
+ const winner = parentNamed[0];
57861
+ return {
57862
+ ...resolution,
57863
+ disposition: "exact",
57864
+ candidates: [winner, ...resolution.candidates.filter((candidate) => candidate.id !== winner.id)]
57865
+ };
57866
+ };
55729
57867
  applyGlossaryAliases = async (ctx, query, types, resolution) => {
55730
57868
  if (resolution.disposition === "exact")
55731
57869
  return resolution;
@@ -55745,6 +57883,7 @@ var init_route_question = __esm({
55745
57883
  parentApiName: candidate.parentApiName,
55746
57884
  score: candidate.score,
55747
57885
  base: candidate.base,
57886
+ nameCoverage: 1,
55748
57887
  matchKind: "exact",
55749
57888
  evidence: candidate.evidence
55750
57889
  })),
@@ -55762,7 +57901,15 @@ var init_route_question = __esm({
55762
57901
  if (/[A-Za-z0-9_]__(?:c|mdt|e|x|b|kav)\b|\w+\.\w+/.test(query))
55763
57902
  return false;
55764
57903
  const [top, second] = resolution.candidates;
55765
- return top.base >= 0.92 && second.base >= 0.92 && second.score >= top.score * 0.97;
57904
+ const looksLikeNamedComponent = (candidate) => candidate.matchKind !== "fuzzy" && (candidate.nameCoverage ?? 1) >= 0.5;
57905
+ return top.base >= 0.92 && second.base >= 0.92 && second.score >= top.score * 0.97 && looksLikeNamedComponent(top) && looksLikeNamedComponent(second);
57906
+ };
57907
+ hygienicClarificationOptions = (candidates) => {
57908
+ const top = candidates[0];
57909
+ if (top === void 0)
57910
+ return [];
57911
+ const kept = candidates.filter((candidate) => candidate.matchKind !== "fuzzy" && candidate.base >= top.base * 0.9);
57912
+ return kept.length > 0 && kept[0] === top ? kept : [top, ...kept.filter((c2) => c2 !== top)];
55766
57913
  };
55767
57914
  splitCompoundQuestion = (question) => {
55768
57915
  const parts = question.split(/(\?|;|\band\s+then\b|\bthen\b|\band\s+(?=who|what|which|how|where|when|why|is|are|can|should|does|do)\b)/i);
@@ -55940,6 +58087,7 @@ var init_route_question = __esm({
55940
58087
  byTool.set(tool, {
55941
58088
  tool,
55942
58089
  score: fuse(0),
58090
+ cosine: 0,
55943
58091
  category: null,
55944
58092
  plane,
55945
58093
  liveRequired,
@@ -55964,12 +58112,30 @@ var init_route_question = __esm({
55964
58112
  return [...lead, ...rest].slice(0, 8);
55965
58113
  };
55966
58114
  MARGIN = 0.05;
58115
+ FUNNEL_PRIMARY_MIN_SCORE = 0.26;
55967
58116
  DESTRUCTIVE_TOOL = /^sfi\.(safe_to_delete_field|what_if_)/;
55968
58117
  INFORMATIONAL_IMPACT_TOOL = /^sfi\.(get_impact|get_edges|get_subgraph|downstream_effects|find_[a-z_]*usages|find_formula_references|field_lineage)$/;
55969
58118
  planesDiverge = (a2, b2) => {
55970
58119
  const planes = /* @__PURE__ */ new Set([a2.plane, b2.plane]);
55971
58120
  return planes.has("vault") && (planes.has("live") || planes.has("hybrid"));
55972
58121
  };
58122
+ LIVE_DATA_SIGNAL = /\bhow\s+many\b|\bcounts?\b|\bfill\s+rate\b|\bpopulat(?:ed|ion)\b|\bstorage\b|\bdata\s+volume\b|\blog(?:ged)?\s?in(?:to)?\b|\blast\s+login\b|\bright\s+now\b|\bcurrently\b|\bas\s+of\s+(?:now|today)\b|\bin\s+prod(?:uction)?\b|\bsample\s+records?\b|\brows?\b|\brecord\s+data\b|\bactual(?:ly)?\s+(?:records?|values?|data|used?|runs?)\b|\bapi\s+usage\b|\blimits?\s+headroom\b/i;
58123
+ resolvePlaneTie = (cands, question) => {
58124
+ const [top, second] = cands;
58125
+ if (top === void 0 || second === void 0)
58126
+ return [...cands];
58127
+ if (top.score - second.score > MARGIN)
58128
+ return [...cands];
58129
+ if (!planesDiverge(top, second))
58130
+ return [...cands];
58131
+ if (top.fromRoute === true && second.fromRoute === true)
58132
+ return [...cands];
58133
+ const wantsLive = LIVE_DATA_SIGNAL.test(question);
58134
+ const topReachesOrg = top.plane === "live" || top.plane === "hybrid";
58135
+ if (wantsLive === topReachesOrg)
58136
+ return [...cands];
58137
+ return [second, top, ...cands.slice(2)];
58138
+ };
55973
58139
  risksDiverge = (a2, b2) => DESTRUCTIVE_TOOL.test(a2.tool) && INFORMATIONAL_IMPACT_TOOL.test(b2.tool) || DESTRUCTIVE_TOOL.test(b2.tool) && INFORMATIONAL_IMPACT_TOOL.test(a2.tool);
55974
58140
  marginClarification = (cands, existingClarification) => {
55975
58141
  if (existingClarification !== null)
@@ -55981,10 +58147,9 @@ var init_route_question = __esm({
55981
58147
  return null;
55982
58148
  if (top.fromRoute === true && second.fromRoute === true)
55983
58149
  return null;
55984
- const axis = planesDiverge(top, second) ? "plane" : risksDiverge(top, second) ? "risk" : null;
55985
- if (axis === null)
58150
+ if (!risksDiverge(top, second))
55986
58151
  return null;
55987
- const question = axis === "plane" ? `These two tools are equally likely but answer from DIFFERENT planes \u2014 \`${top.tool}\` (${top.plane}) vs \`${second.tool}\` (${second.plane}). A ${top.plane === "vault" ? second.plane : top.plane} answer queries the org at call time (needs the opt-in live plane); a vault answer is the offline catalog. Which did you mean?` : `These two tools are equally likely but one is DESTRUCTIVE and one is read-only \u2014 \`${top.tool}\` vs \`${second.tool}\`. Do you want the read-only impact/usage readout, or the change/delete simulation? Which did you mean?`;
58152
+ const question = `These two tools are equally likely but one is DESTRUCTIVE and one is read-only \u2014 \`${top.tool}\` vs \`${second.tool}\`. Do you want the read-only impact/usage readout, or the change/delete simulation? Which did you mean?`;
55988
58153
  return {
55989
58154
  required: true,
55990
58155
  question,
@@ -56014,10 +58179,197 @@ var init_route_question = __esm({
56014
58179
  };
56015
58180
  buildFunnelCandidates = (route, question, routeToolArgs, mode) => {
56016
58181
  const funnelLimit = mode !== void 0 ? 12 : 8;
56017
- return rerankForMode(mergeRouteHintsIntoCandidates(route, semanticCandidates(question, funnelLimit), routeToolArgs, funnelLimit), mode);
58182
+ return resolvePlaneTie(rerankForMode(mergeRouteHintsIntoCandidates(route, semanticCandidates(question, funnelLimit), routeToolArgs, funnelLimit), mode), question);
56018
58183
  };
56019
58184
  routerMode = () => (process.env.SFI_ROUTER_MODE ?? "").trim().toLowerCase() === "offline" ? "offline" : "hybrid";
58185
+ REFUSAL_INTENT = {
58186
+ "write-imperative": "refused-write",
58187
+ "injection-exfiltration": "refused-injection",
58188
+ "runtime-analytics": "honest-gap-runtime",
58189
+ "out-of-scope": "out-of-scope"
58190
+ };
58191
+ REFUSAL_GAP = {
58192
+ "write-imperative": {
58193
+ category: "write-request",
58194
+ note: "org mutation requested; refused (read-only product)"
58195
+ },
58196
+ "injection-exfiltration": {
58197
+ category: "injection",
58198
+ note: "prompt-injection/exfiltration shape refused"
58199
+ },
58200
+ "runtime-analytics": {
58201
+ category: "runtime-analytics",
58202
+ note: "runtime/ops telemetry the product does not model; honest gap disclosed"
58203
+ },
58204
+ "out-of-scope": {
58205
+ category: "out-of-scope",
58206
+ note: "outside the Salesforce-metadata boundary; refused"
58207
+ }
58208
+ };
58209
+ refusalResponse = async (ctx, input2, shape) => {
58210
+ const wantCands = routerMode() === "hybrid" && shape.kind !== "injection-exfiltration";
58211
+ const cands = wantCands ? rerankForMode(semanticCandidates(input2.question, input2.mode !== void 0 ? 12 : 8), input2.mode) : [];
58212
+ const disclosure = shape.kind === "runtime-analytics" && cands.length > 0 ? `${shape.disclosure} Nearest reads: ${cands.slice(0, 3).map((c2) => c2.tool).join(", ")}.` : shape.disclosure;
58213
+ const refusal = { ...shape, disclosure };
58214
+ const route = {
58215
+ question: input2.question,
58216
+ plane: "unknown",
58217
+ intent: REFUSAL_INTENT[shape.kind],
58218
+ tools: [],
58219
+ liveRequired: false,
58220
+ needsResolve: false,
58221
+ reason: disclosure,
58222
+ gap: REFUSAL_GAP[shape.kind],
58223
+ confidence: "high",
58224
+ // confident IN the refusal, not in any tool
58225
+ risk: "informational",
58226
+ alternatives: [],
58227
+ clarification: null,
58228
+ plan: [],
58229
+ refusal
58230
+ };
58231
+ const logged = input2.logGap === true ? await logGapIfAny(route, void 0, ctx.vaultRoot) : null;
58232
+ const guidance = wantCands ? `${disclosure} Do not execute any tool to satisfy the refused action.` : void 0;
58233
+ return ok({
58234
+ data: {
58235
+ route,
58236
+ executionBlocked: false,
58237
+ gapLogged: logged !== null,
58238
+ rendered: cands.length > 0 ? `${renderRouteMarkdown(route)}
58239
+
58240
+ **Candidate tools (transparency only \u2014 the request itself is refused/gapped; do not execute them to satisfy it):** ${cands.map((c2) => c2.tool).join(", ")}` : renderRouteMarkdown(route),
58241
+ trust: routeTrust(),
58242
+ ...cands.length > 0 ? { toolCandidates: cands } : {},
58243
+ ...guidance !== void 0 ? { guidance } : {}
58244
+ },
58245
+ vaultState: {
58246
+ sourceTreeHash: ctx.manifest.sourceTreeHash,
58247
+ refreshedAt: ctx.manifest.refreshedAt
58248
+ }
58249
+ });
58250
+ };
58251
+ CONTEXT_SAVE_ORDER_TOOLS = /* @__PURE__ */ new Set([
58252
+ "sfi.what_happens_on_save",
58253
+ "sfi.order_of_execution"
58254
+ ]);
58255
+ CONTEXT_TOOL_ARG_KEYS = /* @__PURE__ */ new Map([
58256
+ ["sfi.field_access_audit", "fieldId"],
58257
+ ["sfi.safe_to_delete_field", "fieldId"],
58258
+ ["sfi.field_360", "fieldId"],
58259
+ ["sfi.field_lineage", "fieldId"],
58260
+ ["sfi.explain_field", "fieldId"],
58261
+ ["sfi.explain_flow", "flowId"]
58262
+ ]);
58263
+ GENERIC_SCHEMA_INTENTS = /* @__PURE__ */ new Set([
58264
+ "schema",
58265
+ "metadata-count",
58266
+ "component-lookup"
58267
+ ]);
58268
+ contextArgsFor = (route, winner) => {
58269
+ const primary = route.tools.find((tool) => !ROUTE_PREAMBLE_TOOLS.has(tool));
58270
+ if (primary === void 0)
58271
+ return null;
58272
+ if (SAVE_ORDER_INTENTS.has(route.intent) || CONTEXT_SAVE_ORDER_TOOLS.has(primary)) {
58273
+ const objectApiName = winner.type === "CustomObject" ? winner.apiName : winner.parentApiName;
58274
+ if (objectApiName === null || objectApiName === void 0)
58275
+ return null;
58276
+ return { ...route.suggestedArgs ?? {}, objectApiName };
58277
+ }
58278
+ const key = CONTEXT_TOOL_ARG_KEYS.get(primary) ?? "componentId";
58279
+ return { ...route.suggestedArgs ?? {}, [key]: winner.id };
58280
+ };
58281
+ buildContextContinuation = (question, inheritedTool, winner, anaphor, prependResolve) => {
58282
+ const entry = resolveCandidatePlane(inheritedTool);
58283
+ const tools = prependResolve ? ["sfi.resolve", inheritedTool] : [inheritedTool];
58284
+ const base = {
58285
+ question,
58286
+ plane: entry.plane,
58287
+ intent: "context-continuation",
58288
+ tools,
58289
+ liveRequired: entry.liveRequired,
58290
+ needsResolve: prependResolve,
58291
+ reason: `CONTEXT-DERIVED (advisory): no deterministic intent matched this follow-up on its own; '${anaphor}' was resolved from host-passed conversation context` + (winner !== null ? ` to ${winner.id}` : "") + `, and the previous turn's tool ${inheritedTool} was inherited. Verify the pick, resolve any named component first, and ground with sfi.synthesize_answer.`,
58292
+ gap: null,
58293
+ confidence: "low",
58294
+ risk: DESTRUCTIVE_TOOL.test(inheritedTool) ? "destructive" : "informational",
58295
+ alternatives: [],
58296
+ clarification: null,
58297
+ plan: [{
58298
+ stepId: "step-1",
58299
+ dependsOn: [],
58300
+ question,
58301
+ intent: "context-continuation",
58302
+ plane: entry.plane,
58303
+ tools
58304
+ }]
58305
+ };
58306
+ const boundArgs = winner !== null ? contextArgsFor(base, winner) : null;
58307
+ const withArgs = boundArgs !== null ? { ...base, suggestedArgs: boundArgs } : base;
58308
+ const guarded = applyComponentTypeGuard(withArgs, winner?.type ?? null);
58309
+ const primary = guarded.tools.find((tool) => !ROUTE_PREAMBLE_TOOLS.has(tool));
58310
+ if (primary === void 0)
58311
+ return null;
58312
+ if (winner !== null && !continuationToolCompatible(primary, winner.type)) {
58313
+ return null;
58314
+ }
58315
+ const passedGuardUnchanged = primary === inheritedTool;
58316
+ const finalEntry = resolveCandidatePlane(primary);
58317
+ return {
58318
+ ...guarded,
58319
+ plane: finalEntry.plane,
58320
+ liveRequired: finalEntry.liveRequired,
58321
+ confidence: winner !== null && passedGuardUnchanged ? "medium" : "low",
58322
+ risk: DESTRUCTIVE_TOOL.test(primary) ? "destructive" : "informational",
58323
+ plan: guarded.plan.map((step3) => ({ ...step3, plane: finalEntry.plane }))
58324
+ };
58325
+ };
56020
58326
  routeQuestionHandler = async (ctx, input2) => {
58327
+ const refusalShape = detectRefusalShape(input2.question);
58328
+ if (refusalShape !== null) {
58329
+ if (input2.clarificationResponse !== void 0) {
58330
+ return err({
58331
+ kind: "invalid-query",
58332
+ message: "clarificationResponse was supplied, but this question has no active clarification challenge. Route the question again without a response.",
58333
+ path: "clarificationResponse"
58334
+ });
58335
+ }
58336
+ return refusalResponse(ctx, input2, refusalShape);
58337
+ }
58338
+ const contextInput = input2.context;
58339
+ const validatedContext = contextInput !== void 0 ? validatePreviousContext(contextInput.previous, (tool) => getPlaneByTool().has(tool)) : null;
58340
+ if (validatedContext !== null && validatedContext.previous.clarification !== void 0 && validatedContext.previous.question !== void 0 && input2.clarificationResponse === void 0) {
58341
+ const selected = detectClarificationSelection(input2.question, validatedContext.previous.clarification.options);
58342
+ if (selected !== null) {
58343
+ const inner = selected.kind === "selected" ? await routeQuestionHandler(ctx, {
58344
+ question: validatedContext.previous.question,
58345
+ clarificationResponse: {
58346
+ clarificationId: validatedContext.previous.clarification.clarificationId,
58347
+ selection: selected.selection
58348
+ }
58349
+ }) : await routeQuestionHandler(ctx, {
58350
+ question: validatedContext.previous.question
58351
+ });
58352
+ if (!inner.ok)
58353
+ return inner;
58354
+ const contextApplied2 = {
58355
+ kind: "clarification-selection",
58356
+ anaphor: selected.anaphor,
58357
+ from: "previous.clarification",
58358
+ ...selected.kind === "selected" ? { selection: selected.selection } : {},
58359
+ ...validatedContext.ignored.length > 0 ? { ignored: validatedContext.ignored } : {}
58360
+ };
58361
+ return ok({
58362
+ ...inner.value,
58363
+ data: {
58364
+ ...inner.value.data,
58365
+ route: { ...inner.value.data.route, contextApplied: contextApplied2 },
58366
+ rendered: `${inner.value.data.rendered}
58367
+
58368
+ ${renderContextApplied(contextApplied2)}`
58369
+ }
58370
+ });
58371
+ }
58372
+ }
56021
58373
  let route = classifyQuestion(input2.question);
56022
58374
  if (route.plane === "unknown" && route.intent === "unrouted") {
56023
58375
  const upgraded = await tryResolveFallback(ctx, input2.question);
@@ -56073,14 +58425,52 @@ var init_route_question = __esm({
56073
58425
  const saveOrderIntent = SAVE_ORDER_INTENTS.has(route.intent);
56074
58426
  const suggestedObject = route.suggestedArgs?.["objectApiName"];
56075
58427
  const entityExtractionSource = stripComparisonAside(input2.question, route.intent);
56076
- const entityQuery = !route.needsResolve ? null : saveOrderIntent ? typeof suggestedObject === "string" ? suggestedObject : null : extractEntityQuery(entityExtractionSource, route.intent);
56077
- const entityTypes = entityQuery === null ? [] : saveOrderIntent ? ["CustomObject"] : inferEntityTypes(entityQuery, route.intent, input2.question);
56078
- const entityResolution = entityQuery !== null ? await resolveComponents(ctx.graph, entityQuery, {
58428
+ let entityQuery = !route.needsResolve ? null : saveOrderIntent ? typeof suggestedObject === "string" ? suggestedObject : null : extractEntityQuery(entityExtractionSource, route.intent);
58429
+ let entityTypes = entityQuery === null ? [] : saveOrderIntent ? ["CustomObject"] : inferEntityTypes(entityQuery, route.intent, input2.question);
58430
+ const pronounAnchor = validatedContext !== null ? detectPronounAnchor(input2.question) : null;
58431
+ let contextExactResolution = null;
58432
+ let contextSubstitution = null;
58433
+ let contextGhostComponentId = null;
58434
+ if (pronounAnchor !== null && validatedContext !== null && (entityQuery === null || isAnaphorOnly(entityQuery))) {
58435
+ const previous = validatedContext.previous;
58436
+ if (saveOrderIntent && previous.objectApiName !== void 0) {
58437
+ entityQuery = previous.objectApiName;
58438
+ entityTypes = ["CustomObject"];
58439
+ contextSubstitution = { anaphor: pronounAnchor, from: "previous.objectApiName" };
58440
+ } else if (!saveOrderIntent && previous.componentId !== void 0) {
58441
+ const carried = await getNodeById(ctx.graph, previous.componentId);
58442
+ if (carried.ok && carried.value !== null) {
58443
+ const node = carried.value;
58444
+ contextExactResolution = {
58445
+ disposition: "exact",
58446
+ candidates: [{
58447
+ id: node.id,
58448
+ type: node.type,
58449
+ apiName: node.apiName,
58450
+ label: node.label,
58451
+ parentApiName: node.parentId === null ? null : node.parentId.slice(node.parentId.indexOf(":") + 1),
58452
+ score: 1,
58453
+ base: 1,
58454
+ matchKind: "exact",
58455
+ nameCoverage: 1,
58456
+ evidence: `context: exact id carried from the previous turn (${node.id})`
58457
+ }],
58458
+ queryTokens: []
58459
+ };
58460
+ entityQuery = node.apiName;
58461
+ entityTypes = [node.type];
58462
+ contextSubstitution = { anaphor: pronounAnchor, from: "previous.componentId" };
58463
+ } else if (carried.ok) {
58464
+ contextGhostComponentId = previous.componentId;
58465
+ }
58466
+ }
58467
+ }
58468
+ const entityResolution = entityQuery !== null && contextExactResolution === null ? await resolveComponents(ctx.graph, entityQuery, {
56079
58469
  limit: 5,
56080
58470
  ...entityTypes.length > 0 ? { types: entityTypes } : {}
56081
58471
  }) : null;
56082
58472
  const glossaryAwareEntityResolution = entityQuery !== null && entityResolution?.ok === true ? await applyGlossaryAliases(ctx, entityQuery, entityTypes, entityResolution.value) : null;
56083
- const refinedEntityResolution = entityQuery !== null && glossaryAwareEntityResolution !== null ? refineEntityResolution(entityQuery, glossaryAwareEntityResolution) : null;
58473
+ const refinedEntityResolution = contextExactResolution ?? (entityQuery !== null && glossaryAwareEntityResolution !== null ? resolveParentQualifier(input2.question, refineEntityResolution(entityQuery, glossaryAwareEntityResolution)) : null);
56084
58474
  const entityClarificationRequired = entityQuery !== null && refinedEntityResolution !== null && entityAmbiguityRequiresClarification(entityQuery, refinedEntityResolution);
56085
58475
  let entityEvidence = refinedEntityResolution !== null && refinedEntityResolution.disposition !== "none" ? {
56086
58476
  query: entityQuery,
@@ -56107,13 +58497,37 @@ var init_route_question = __esm({
56107
58497
  clarification: {
56108
58498
  required: true,
56109
58499
  question: "Several components match the named entity. Which component did you mean?",
56110
- options: entityEvidence?.candidates.map((candidate) => candidate.componentId) ?? []
58500
+ // P4 option hygiene: fuzzy grazes and far-below-top rivals are junk,
58501
+ // not options — offer only the plausible candidates.
58502
+ options: hygienicClarificationOptions(entityEvidence?.candidates ?? []).map((candidate) => candidate.componentId)
56111
58503
  }
56112
58504
  };
56113
58505
  } else if (entityEvidence?.disposition === "ambiguous" && route.clarification === null) {
56114
58506
  route = { ...route, confidence: "medium" };
58507
+ } else if (route.intent === "component-lookup" && route.clarification !== null && refinedEntityResolution?.disposition === "exact") {
58508
+ const winner = refinedEntityResolution.candidates[0];
58509
+ route = {
58510
+ ...route,
58511
+ confidence: "high",
58512
+ clarification: null,
58513
+ suggestedArgs: { ...route.suggestedArgs ?? {}, componentId: winner.id }
58514
+ };
56115
58515
  }
56116
58516
  route = applyComponentTypeGuard(route, resolvedTypeForGuard(route, refinedEntityResolution));
58517
+ let contextEntityApplied = null;
58518
+ if (contextSubstitution !== null && route.intent !== "unrouted" && refinedEntityResolution?.disposition === "exact") {
58519
+ const winner = refinedEntityResolution.candidates[0];
58520
+ const bound = contextArgsFor(route, winner);
58521
+ if (bound !== null) {
58522
+ route = { ...route, suggestedArgs: bound };
58523
+ contextEntityApplied = {
58524
+ kind: "entity-substitution",
58525
+ anaphor: contextSubstitution.anaphor,
58526
+ substitutedComponentId: winner.id,
58527
+ from: contextSubstitution.from
58528
+ };
58529
+ }
58530
+ }
56117
58531
  const marginRouteToolArgs = await buildRouteToolArgsMap(route, ctx);
56118
58532
  if (routerMode() === "hybrid" && route.confidence !== "high") {
56119
58533
  const gateCandidates = buildFunnelCandidates(route, input2.question, marginRouteToolArgs, input2.mode);
@@ -56122,6 +58536,7 @@ var init_route_question = __esm({
56122
58536
  route = { ...route, confidence: "low", clarification: marginClar };
56123
58537
  }
56124
58538
  }
58539
+ let premiseFlagged = false;
56125
58540
  if (entityQuery !== null && refinedEntityResolution !== null && refinedEntityResolution.disposition === "none") {
56126
58541
  const bareQuery = entityQuery.replace(/^(?:the|a|an)\s+/i, "").replace(/\s+(?:class(?:es)?|trigger(?:s)?|field(?:s)?|object(?:s)?|flow(?:s)?|component(?:s)?|layout(?:s)?|profile(?:s)?|report(?:s)?|dashboard(?:s)?|rule(?:s)?)$/i, "");
56127
58542
  const bareRetry = bareQuery !== entityQuery && bareQuery.length > 0 ? await resolveComponents(ctx.graph, bareQuery, {
@@ -56149,6 +58564,7 @@ var init_route_question = __esm({
56149
58564
  }))
56150
58565
  };
56151
58566
  } else {
58567
+ premiseFlagged = true;
56152
58568
  const premiseWarning = `PREMISE CHECK: no component matching '${entityQuery}' exists in the vault \u2014 verify the name (a typo, or metadata newer than the last refresh; /sfi-refresh may help). The routed tool fails closed on an unknown component; do not present its error as an answer about a real component.`;
56153
58569
  route = {
56154
58570
  ...route,
@@ -56165,6 +58581,7 @@ var init_route_question = __esm({
56165
58581
  };
56166
58582
  }
56167
58583
  } else if (entityQuery !== null && refinedEntityResolution !== null && refinedEntityResolution.disposition === "ambiguous" && (/__(?:c|mdt|e|x|b|kav)\b/i.test(entityQuery) || entityQuery.includes(".")) && !refinedEntityResolution.candidates.some((candidate) => candidate.apiName.toLowerCase() === entityQuery.toLowerCase() || candidate.id.toLowerCase().endsWith(`:${entityQuery.toLowerCase()}`))) {
58584
+ premiseFlagged = true;
56168
58585
  const premiseWarning = `PREMISE CHECK: no component named '${entityQuery}' exists in the vault \u2014 the listed candidates are fuzzy lookalikes, not the component you named. Verify the name (a typo, or metadata newer than the last refresh; /sfi-refresh may help). The routed tool fails closed on an unknown component; do not present its error as an answer about a real component.`;
56169
58586
  route = {
56170
58587
  ...route,
@@ -56175,6 +58592,146 @@ var init_route_question = __esm({
56175
58592
  entityEvidence = { ...entityEvidence, warning: premiseWarning };
56176
58593
  }
56177
58594
  }
58595
+ if (contextGhostComponentId !== null) {
58596
+ premiseFlagged = true;
58597
+ const premiseWarning = `PREMISE CHECK: the component carried from the previous turn (${contextGhostComponentId}) no longer exists in the vault (deleted, renamed, or newer than the last refresh; /sfi-refresh may help). Do not answer as if it exists \u2014 re-ask with the component named explicitly.`;
58598
+ route = {
58599
+ ...route,
58600
+ confidence: "low",
58601
+ reason: `${route.reason} ${premiseWarning}`
58602
+ };
58603
+ entityEvidence = {
58604
+ query: contextGhostComponentId,
58605
+ typeHints: [],
58606
+ disposition: "none",
58607
+ clarificationRequired: false,
58608
+ warning: premiseWarning,
58609
+ candidates: []
58610
+ };
58611
+ }
58612
+ let contextRouteApplied = null;
58613
+ if (validatedContext !== null && route.clarification === null && !premiseFlagged) {
58614
+ const previous = validatedContext.previous;
58615
+ const stillUnrouted = route.intent === "unrouted" && route.plane === "unknown";
58616
+ const reparamAnchor = detectReparamAnchor(input2.question);
58617
+ const ownRouteWeak = stillUnrouted || GENERIC_SCHEMA_INTENTS.has(route.intent) && route.confidence !== "high";
58618
+ if (reparamAnchor !== null && ownRouteWeak && previous.tool !== void 0) {
58619
+ let target = contextSubstitution === null && refinedEntityResolution?.disposition === "exact" ? refinedEntityResolution.candidates[0] : null;
58620
+ let newEntityBlocked = false;
58621
+ if (target === null) {
58622
+ const phrase = extractReparamTarget(input2.question);
58623
+ if (phrase !== null) {
58624
+ const resolvedTarget = await resolveComponents(ctx.graph, phrase, { limit: 5 });
58625
+ if (resolvedTarget.ok) {
58626
+ const refinedTarget = refineEntityResolution(phrase, resolvedTarget.value);
58627
+ if (refinedTarget.disposition === "exact") {
58628
+ target = refinedTarget.candidates[0];
58629
+ } else if (refinedTarget.disposition === "ambiguous" && entityAmbiguityRequiresClarification(phrase, refinedTarget)) {
58630
+ route = {
58631
+ ...route,
58632
+ confidence: "low",
58633
+ clarification: {
58634
+ required: true,
58635
+ question: "Several components match the named entity. Which component did you mean?",
58636
+ options: hygienicClarificationOptions(refinedTarget.candidates.slice(0, 5)).map((candidate) => candidate.id)
58637
+ }
58638
+ };
58639
+ newEntityBlocked = true;
58640
+ }
58641
+ }
58642
+ }
58643
+ }
58644
+ if (!newEntityBlocked && target !== null) {
58645
+ const continuation = buildContextContinuation(input2.question, previous.tool, target, reparamAnchor, false);
58646
+ if (continuation !== null) {
58647
+ route = continuation;
58648
+ contextRouteApplied = {
58649
+ kind: "reparameterization",
58650
+ anaphor: reparamAnchor,
58651
+ substitutedComponentId: target.id,
58652
+ inheritedTool: previous.tool
58653
+ };
58654
+ }
58655
+ }
58656
+ }
58657
+ if (contextRouteApplied === null && route.clarification === null && stillUnrouted && route.intent === "unrouted" && previous.tool !== void 0) {
58658
+ const anchor = pronounAnchor ?? reparamAnchor;
58659
+ const substitutedExact = contextSubstitution !== null && refinedEntityResolution?.disposition === "exact";
58660
+ if (anchor !== null && (substitutedExact || previous.componentId === void 0)) {
58661
+ const winner = substitutedExact ? refinedEntityResolution.candidates[0] : null;
58662
+ const newEntityAppeared = contextSubstitution === null && entityQuery !== null && !isAnaphorOnly(entityQuery);
58663
+ const continuation = buildContextContinuation(input2.question, previous.tool, winner, anchor, newEntityAppeared);
58664
+ if (continuation !== null) {
58665
+ route = continuation;
58666
+ contextRouteApplied = {
58667
+ kind: "continuation",
58668
+ anaphor: anchor,
58669
+ ...winner !== null ? {
58670
+ substitutedComponentId: winner.id,
58671
+ from: contextSubstitution.from
58672
+ } : {},
58673
+ inheritedTool: previous.tool
58674
+ };
58675
+ }
58676
+ }
58677
+ }
58678
+ }
58679
+ let advisoryCandidates = null;
58680
+ if (route.intent === "unrouted" && route.plane === "unknown" && route.clarification === null && !premiseFlagged) {
58681
+ const cands = buildFunnelCandidates(route, input2.question, marginRouteToolArgs, input2.mode);
58682
+ advisoryCandidates = cands;
58683
+ const resolvedExact = refinedEntityResolution?.disposition === "exact" ? refinedEntityResolution.candidates[0] : void 0;
58684
+ const usable = resolvedExact === void 0 ? cands : cands.filter((candidate) => {
58685
+ const compatible = TOOL_COMPATIBLE_TYPES.get(candidate.tool);
58686
+ return compatible === void 0 || compatible.has(resolvedExact.type);
58687
+ });
58688
+ const top = usable[0];
58689
+ if (top !== void 0 && top.score >= FUNNEL_PRIMARY_MIN_SCORE && top.fromRoute !== true && top.score > 0.25) {
58690
+ const tools = usable.slice(0, 3).map((candidate) => candidate.tool);
58691
+ let advisoryArgs = null;
58692
+ if (resolvedExact !== void 0) {
58693
+ const compatible = TOOL_COMPATIBLE_TYPES.get(top.tool);
58694
+ if (compatible !== void 0 && compatible.has(resolvedExact.type)) {
58695
+ advisoryArgs = top.tool === "sfi.field_access_audit" ? { fieldId: resolvedExact.id } : { componentId: resolvedExact.id };
58696
+ } else if (compatible === void 0 && /get_impact|get_edges|component/.test(top.tool)) {
58697
+ advisoryArgs = { componentId: resolvedExact.id };
58698
+ }
58699
+ }
58700
+ route = {
58701
+ question: input2.question,
58702
+ plane: top.plane,
58703
+ intent: "funnel-advisory",
58704
+ tools,
58705
+ liveRequired: top.liveRequired,
58706
+ needsResolve: entityQuery !== null,
58707
+ reason: "No deterministic intent matched. This route is FUNNEL-DERIVED (advisory): the semantic funnel ranked these tools by meaning alone. Confidence is low by construction \u2014 verify the pick, resolve any named component first, and ground with sfi.synthesize_answer.",
58708
+ gap: null,
58709
+ confidence: "low",
58710
+ risk: DESTRUCTIVE_TOOL.test(top.tool) ? "destructive" : "informational",
58711
+ alternatives: [],
58712
+ clarification: null,
58713
+ plan: [{
58714
+ stepId: "step-1",
58715
+ dependsOn: [],
58716
+ question: input2.question,
58717
+ intent: "funnel-advisory",
58718
+ plane: top.plane,
58719
+ tools
58720
+ }],
58721
+ ...advisoryArgs !== null ? { suggestedArgs: advisoryArgs } : {}
58722
+ };
58723
+ }
58724
+ }
58725
+ const contextApplied = contextRouteApplied ?? contextEntityApplied;
58726
+ if (contextApplied !== null) {
58727
+ route = {
58728
+ ...route,
58729
+ contextApplied: {
58730
+ ...contextApplied,
58731
+ ...validatedContext !== null && validatedContext.ignored.length > 0 ? { ignored: validatedContext.ignored } : {}
58732
+ }
58733
+ };
58734
+ }
56178
58735
  const clarificationId = clarificationIdFor(ctx.manifest.sourceTreeHash, route);
56179
58736
  if (clarificationId !== null && route.clarification !== null) {
56180
58737
  route = {
@@ -56281,7 +58838,7 @@ var init_route_question = __esm({
56281
58838
  const routeToolArgs = await buildRouteToolArgsMap(route, ctx);
56282
58839
  const invoke = toolProfile() === "core" && !executionBlocked ? invokeFromArgsMap(route, routeToolArgs) : void 0;
56283
58840
  const wantCandidates = routerMode() === "hybrid";
56284
- const toolCandidates = wantCandidates ? buildFunnelCandidates(route, input2.question, routeToolArgs, input2.mode) : [];
58841
+ const toolCandidates = wantCandidates ? advisoryCandidates ?? buildFunnelCandidates(route, input2.question, routeToolArgs, input2.mode) : [];
56285
58842
  const guidance = toolCandidates.length > 0 ? guidanceForMode(input2.mode, toolCandidates) : void 0;
56286
58843
  return ok({
56287
58844
  data: {
@@ -65497,6 +68054,40 @@ var init_tools = __esm({
65497
68054
  clarificationId: { type: "string", minLength: 1 },
65498
68055
  selection: { type: "string", minLength: 1 }
65499
68056
  }
68057
+ },
68058
+ mode: { type: "string", enum: ["ask", "plan", "assessment"] },
68059
+ context: {
68060
+ type: "object",
68061
+ required: ["previous"],
68062
+ additionalProperties: false,
68063
+ properties: {
68064
+ previous: {
68065
+ type: "object",
68066
+ additionalProperties: false,
68067
+ properties: {
68068
+ componentId: { type: "string", minLength: 1, maxLength: 256 },
68069
+ objectApiName: { type: "string", minLength: 1, maxLength: 256 },
68070
+ tool: { type: "string", pattern: "^sfi\\.[a-z0-9_]+$" },
68071
+ intent: { type: "string", minLength: 1, maxLength: 64 },
68072
+ plane: { type: "string", enum: ["vault", "live", "hybrid"] },
68073
+ question: { type: "string", minLength: 1, maxLength: 2e3 },
68074
+ clarification: {
68075
+ type: "object",
68076
+ required: ["clarificationId", "options"],
68077
+ additionalProperties: false,
68078
+ properties: {
68079
+ clarificationId: { type: "string", minLength: 1, maxLength: 64 },
68080
+ options: {
68081
+ type: "array",
68082
+ items: { type: "string", minLength: 1, maxLength: 256 },
68083
+ minItems: 1,
68084
+ maxItems: 10
68085
+ }
68086
+ }
68087
+ }
68088
+ }
68089
+ }
68090
+ }
65500
68091
  }
65501
68092
  }
65502
68093
  });
@@ -66886,7 +69477,7 @@ var init_tools = __esm({
66886
69477
  },
66887
69478
  {
66888
69479
  name: "sfi.resolve",
66889
- description: "Typo-tolerant resolver: messy/misspelled text -> ranked candidate components with a disposition (exact|ambiguous|none) + per-candidate evidence. Call FIRST when the user names a component informally; tolerates typos, filler, and the org's own misspellings that search_components cannot. Leading/trailing schema nouns are TYPE hints, not name content: 'SSN field' scores exactly like bare 'SSN' (the noun is stripped from fuzzy matching and instead prefers candidates of the hinted type among equally-confident matches \u2014 it never floats a weak fuzzy match of that type over an exact-name match of another). A dotted 'Object.Field' query is a definitive parent-scoped hit. CONFIRMED glossary annotations act as curated synonyms (candidates marked `glossary-alias`) \u2014 an alias never shadows an exact api-name match, and a synonym shared by two components yields `ambiguous` + clarification. Heuristic; never silently picks.",
69480
+ description: "Typo-tolerant resolver: messy/misspelled text -> ranked candidate components with a disposition (exact|ambiguous|none) + per-candidate evidence. Call FIRST when the user names a component informally; tolerates typos, filler, and the org's own misspellings that search_components cannot. Also the answer to informal lookup asks \u2014 'find / look up / locate the X', 'do we have a field for tracking Y?', 'I think there's a flow called something like Z' \u2014 whenever the user only half-remembers a name or describes the thing instead of naming it. Leading/trailing schema nouns are TYPE hints, not name content: 'SSN field' scores exactly like bare 'SSN' (the noun is stripped from fuzzy matching and instead prefers candidates of the hinted type among equally-confident matches \u2014 it never floats a weak fuzzy match of that type over an exact-name match of another). A dotted 'Object.Field' query is a definitive parent-scoped hit. CONFIRMED glossary annotations act as curated synonyms (candidates marked `glossary-alias`) \u2014 an alias never shadows an exact api-name match, and a synonym shared by two components yields `ambiguous` + clarification. Heuristic; never silently picks.",
66890
69481
  inputSchema: RESOLVE_INPUT_SCHEMA
66891
69482
  },
66892
69483
  {
@@ -66916,7 +69507,7 @@ var init_tools = __esm({
66916
69507
  },
66917
69508
  {
66918
69509
  name: "sfi.route_question",
66919
- description: "Front-door router: for a plain-language question, surface a meaning-ranked shortlist of the sfi.* tools that can answer it \u2014 your host LLM picks which to run \u2014 plus the plane it belongs to (vault | live | hybrid | unknown), so the user never types a tool name. Read-only; it advises, it does not answer. Compound questions carry step ids and `dependsOn` edges: independent steps may run in parallel; a `then`-linked step waits for its prerequisite. On GENUINE ambiguity it fails closed with `executionBlocked`, a clarification id, and offered options; resume deterministically by calling again with the exact same question plus `clarificationResponse: { clarificationId, selection }`. Stale ids and invented selections are rejected. Bare schema nouns in the question (trigger/flow/field/object/profile/permission set/record type) are INTENT vocabulary, never named-entity lookups \u2014 'the Contact trigger' scopes a save-order question to the Contact OBJECT, it does not shop 'trigger' to the resolver; an object-qualified field ('Status__c on Case') is resolved parent-scoped; and an entity the resolver reports `exact` never raises the components-match menu \u2014 that menu is reserved for two genuinely competing components. The resolved TYPE also gates the routed tools: when the named entity resolves to a FLOW, tools that hard-error on a Flow id (call_graph / method_reachability / explain_apex_method, and the object/field access audits) are SWAPPED for the Flow-appropriate ones (who_can_run for access asks, explain_flow \u2014 which narrates the Apex the Flow invokes \u2014 plus get_impact) instead of routing into a guaranteed error. Qualifier words never outrank the HEAD question: 'bulk'/'load' on a save-order or test-coverage ask, 'seats'/'license' on a permission-set-assignment ask, and 'integration'/vendor words on a field-write / profile-security / what-if ask all stay on the head intent (never forced onto governor_limit_risks / live_license_usage / integration_map); field_lineage is reserved for explicit lineage/provenance questions ('where does this data come from'), never a field-adjacent default. FALSE PREMISE: when the question names a component the resolver cannot find \u2014 disposition `none`, or a literal API reference (dotted / __c-suffixed) that none of the fuzzy candidates actually match \u2014 the route is STILL returned (the routed tool fails closed on the unknown id) but its confidence is downgraded and `entityEvidence.warning` carries a premise disclosure (no component matching '<name>' exists in the vault \u2014 verify the name), so a nonexistent component is never answered as if it were real. Honesty routes: asks the surface genuinely cannot answer route to an explicit gap, never a lookalike tool \u2014 e.g. 'which USERS hold permission set X' (PermissionSetAssignment is not modeled; effective_permissions describes GRANTS, not holders); profile user-roster asks route live (live_group_count over User by ProfileId) with a partial-answer disclosure; and cross-vault compare routes carry a disclosure that a SECOND registered vault is required, so a single-vault install is never routed confident-clean into vault-not-found. Tells you when to sfi.resolve a named component first, whether the opt-in live plane is required, surfaces `suggestedArgs` (heuristic per-intent hints \u2014 e.g. `event: 'update'` for a save-order question so you can call `what_happens_on_save` without guessing the DML event), and \u2014 when the question hits a capability we lack \u2014 returns an honest 'unknown'/gap instead of fabricating (set `logGap: true` to also append the gap to the local backlog; off by default, privacy-first per CR-16); under `SFI_TOOL_PROFILE=core` the response also carries `invoke`: the routed tools as EXECUTABLE calls (core tools direct, everything else as the byte-identical `sfi.run_analysis` gateway envelope, suggestedArgs threaded) (a short phrase that merely NAMES a real vault component, with no question, is instead routed to sfi.resolve rather than 'unknown'). In the default hybrid mode the meaning-ranked `toolCandidates` are PRIMARY: every routable question carries the shortlist (offline TF-IDF over the capability map, no neural model, no network) plus a `guidance` line stating the loop YOU own \u2014 read the candidates \u2192 resolve any named component \u2192 pick/sequence the tool(s) \u2192 run them \u2192 ground via sfi.synthesize_answer. YOU decide which to run; the deterministic `route` rides along only as a non-authoritative HINT (suggested tool order + any resolved entity / suggestedArgs). Set `SFI_ROUTER_MODE=offline` for a deterministic, no-LLM route (Design A) where the route is authoritative and candidates are omitted \u2014 for CI / air-gapped hosts. An optional `mode` ('ask' | 'plan' | 'assessment') tailors the guidance and reranks the candidates toward that mode's family \u2014 'plan' favors the what_if_* / impact tools (an ordered change plan), 'assessment' favors the *_risk_report / readiness / coverage tools (a full evaluation), 'ask' is a quick grounded answer. Call this first on a vague/broad question to decide which tool(s) to run.",
69510
+ description: "Front-door router: for a plain-language question, surface a meaning-ranked shortlist of the sfi.* tools that can answer it \u2014 your host LLM picks which to run \u2014 plus the plane it belongs to (vault | live | hybrid | unknown), so the user never types a tool name. Read-only; it advises, it does not answer. Compound questions carry step ids and `dependsOn` edges: independent steps may run in parallel; a `then`-linked step waits for its prerequisite. On GENUINE ambiguity it fails closed with `executionBlocked`, a clarification id, and offered options; resume deterministically by calling again with the exact same question plus `clarificationResponse: { clarificationId, selection }`. Stale ids and invented selections are rejected. CLARIFICATIONS ARE A LAST RESORT (router-v2 P4): a qualifier already in the question pre-answers the menu \u2014 an object word next to a same-named field ('\u2026saving a CASE without a Resolution_Code__c'), a type word after the name ('the X OBJECT/FLOW'), an underscored/dotted literal api name, or a component mention on a who-last-modified ask all AUTO-RESOLVE instead of blocking; a CustomTab twin of a CustomObject never counts as a rival; complementary readings (who-can-access grantors vs CRUD matrix, impact readout vs change/delete simulation on the same ask) STACK their tools in one route instead of asking which-first; a vault-vs-live candidate near-tie is decided by the question's own runtime-data language (live leads WITH the consent disclosure, vault otherwise) rather than blocking \u2014 only the DESTRUCTIVE-vs-read-only tool tie still stops execution; and offered options are HYGIENIC \u2014 fuzzy acronym-graze rivals (SSN vs {A,B,M}SN_*) and far-below-top junk never appear as options. Bare schema nouns in the question (trigger/flow/field/object/profile/permission set/record type) are INTENT vocabulary, never named-entity lookups \u2014 'the Contact trigger' scopes a save-order question to the Contact OBJECT, it does not shop 'trigger' to the resolver; an object-qualified field ('Status__c on Case') is resolved parent-scoped; and an entity the resolver reports `exact` never raises the components-match menu \u2014 that menu is reserved for two genuinely competing components. The resolved TYPE also gates the routed tools: when the named entity resolves to a FLOW, tools that hard-error on a Flow id (call_graph / method_reachability / explain_apex_method, and the object/field access audits) are SWAPPED for the Flow-appropriate ones (who_can_run for access asks, explain_flow \u2014 which narrates the Apex the Flow invokes \u2014 plus get_impact) instead of routing into a guaranteed error. Qualifier words never outrank the HEAD question: 'bulk'/'load' on a save-order or test-coverage ask, 'seats'/'license' on a permission-set-assignment ask, and 'integration'/vendor words on a field-write / profile-security / what-if ask all stay on the head intent (never forced onto governor_limit_risks / live_license_usage / integration_map); field_lineage is reserved for explicit lineage/provenance questions ('where does this data come from'), never a field-adjacent default. FALSE PREMISE: when the question names a component the resolver cannot find \u2014 disposition `none`, or a literal API reference (dotted / __c-suffixed) that none of the fuzzy candidates actually match \u2014 the route is STILL returned (the routed tool fails closed on the unknown id) but its confidence is downgraded and `entityEvidence.warning` carries a premise disclosure (no component matching '<name>' exists in the vault \u2014 verify the name), so a nonexistent component is never answered as if it were real. Honesty routes: asks the surface genuinely cannot answer route to an explicit gap, never a lookalike tool \u2014 e.g. 'which USERS hold permission set X' (PermissionSetAssignment is not modeled; effective_permissions describes GRANTS, not holders); profile user-roster asks route live (live_group_count over User by ProfileId) with a partial-answer disclosure; and cross-vault compare routes carry a disclosure that a SECOND registered vault is required, so a single-vault install is never routed confident-clean into vault-not-found. Tells you when to sfi.resolve a named component first, whether the opt-in live plane is required, surfaces `suggestedArgs` (heuristic per-intent hints \u2014 e.g. `event: 'update'` for a save-order question so you can call `what_happens_on_save` without guessing the DML event), and \u2014 when the question hits a capability we lack \u2014 returns an honest 'unknown'/gap instead of fabricating (set `logGap: true` to also append the gap to the local backlog; off by default, privacy-first per CR-16); under `SFI_TOOL_PROFILE=core` the response also carries `invoke`: the routed tools as EXECUTABLE calls (core tools direct, everything else as the byte-identical `sfi.run_analysis` gateway envelope, suggestedArgs threaded) (a short phrase that merely NAMES a real vault component, with no question, is instead routed to sfi.resolve rather than 'unknown'). In the default hybrid mode the meaning-ranked `toolCandidates` are PRIMARY: every routable question carries the shortlist (offline TF-IDF over the capability map, no neural model, no network) plus a `guidance` line stating the loop YOU own \u2014 read the candidates \u2192 resolve any named component \u2192 pick/sequence the tool(s) \u2192 run them \u2192 ground via sfi.synthesize_answer. YOU decide which to run; the deterministic `route` rides along only as a non-authoritative HINT (suggested tool order + any resolved entity / suggestedArgs). Set `SFI_ROUTER_MODE=offline` for a deterministic, no-LLM route (Design A) where the route is authoritative and candidates are omitted \u2014 for CI / air-gapped hosts. An optional `mode` ('ask' | 'plan' | 'assessment') tailors the guidance and reranks the candidates toward that mode's family \u2014 'plan' favors the what_if_* / impact tools (an ordered change plan), 'assessment' favors the *_risk_report / readiness / coverage tools (a full evaluation), 'ask' is a quick grounded answer. REFUSAL SHAPES (score-independent, evaluated BEFORE any intent, in both router modes): a write/mutation imperative aimed at the agent ('delete the X field for me', 'go ahead and merge\u2026') returns intent `refused-write` with EMPTY tools, a 'REFUSED (read-only boundary)' disclosure, and a read-side alternative to offer instead (safe_to_delete_field / what_if_deactivate_flow / what_if_disable_trigger / what_if_change_field_type / what_if_merge_profiles / get_impact by verb family); prompt-injection or record-value exfiltration ('ignore your previous instructions\u2026', 'dump all SSN values') returns `refused-injection` with toolCandidates AND guidance suppressed entirely; runtime/ops telemetry no tool models (login history, adoption metrics, endpoint errors 'this week', running-user context) returns `honest-gap-runtime` with an 'HONEST GAP' disclosure naming the nearest reads; and non-Salesforce asks (SharePoint/Jira subjects, org-policy authorship, 'email me\u2026' delivery, write-me-code) return `out-of-scope`. Every refusal is NON-EXECUTABLE by shape \u2014 `tools: []`, the structured `route.refusal` field carries { kind, disclosure, readOnlyAlternative? }, `executionBlocked` stays false \u2014 and legitimate permission/hypothetical READS ('am I allowed to edit\u2026', 'who can delete\u2026', 'is it safe to\u2026', 'what would happen if\u2026') are explicit excluders that route normally. FUNNEL-PRIMARY (advisory): when NO deterministic intent matches and nothing else stopped the route (no clarification, and a clean premise \u2014 a question naming a component the resolver cannot find gets the premise disclosure, never an advisory route), a pure-semantic top candidate scoring \u2265 0.26 upgrades the dead 'unrouted' to intent `funnel-advisory`: the top-3 funnel tools, confidence LOW by construction, reason flagged FUNNEL-DERIVED \u2014 treat it as the funnel's advisory pick to verify (resolve the named component, then ground), never a command. Candidate rows also carry `cosine`, the raw pre-fusion semantic score (0 for rows inserted purely from the regex route hint), so a host can tell real semantic support from regex assertion. CONVERSATION CONTEXT (stateless, host-passed \u2014 router-v2 P5): the product stores NO conversation memory; the HOST may pass optional `context.previous` per call ({ componentId, objectApiName, tool, intent, plane, question, clarification }) describing what the PRIOR turn was about, and nothing is stored server-side. With it, terse follow-ups resolve: (1) a pronoun/ellipsis follow-up whose own extraction finds no entity ('does it fire on delete too?') substitutes `previous.componentId` as the entity \u2014 an EXACT-id lookup, never fuzzy \u2014 and, when still unrouted, inherits `previous.tool` as an advisory `context-continuation` route (confidence capped at `medium`, never high; plane/liveRequired always from the live tool registry, never `previous.plane`; the resolved TYPE still gates the inherited tool \u2014 a type-incompatible inheritance is swapped or dropped to funnel-primary, never an executable tool bound to an id that guarantees a hard error); (2) a re-parameterization follow-up ('what about on Contact?') re-runs `previous.tool` against the NEW target from the question itself \u2014 an ambiguous new entity still blocks with a clarification; (3) an ordinal/descriptor pick ('the second one', 'the Contact one') against `previous.clarification` + `previous.question` re-dispatches through the full clarification-continuation contract (stale ids rejected exactly as usual; 0 or \u22652 descriptor matches, or an out-of-range ordinal, re-ask instead of guessing). HONESTY UNDER CONTEXT: refusal gates run on the RAW question BEFORE any context logic \u2014 context never bypasses them and adds no executable path to a refused turn; a carried componentId that no longer resolves gets a context-specific PREMISE disclosure and never advisory-routes; value validation is FAIL-OPEN (an unregistered `tool` / malformed `componentId` is skipped and noted in `contextApplied.ignored`, never a hard error \u2014 shape errors still reject). A SELF-CONTAINED question IGNORES context: no anaphor, or a confident own route, returns a response identical to the no-context call, and omitting `context` keeps behavior byte-identical. When (and only when) context changes the route, the response disclosure `route.contextApplied` carries { kind: 'entity-substitution' | 'continuation' | 'reparameterization' | 'clarification-selection', anaphor, substitutedComponentId?, from?, inheritedTool?, selection?, ignored? } and the rendered text appends a 'Context applied: \u2026' line \u2014 mere presence of the param never emits it. Call this first on a vague/broad question to decide which tool(s) to run.",
66920
69511
  inputSchema: ROUTE_QUESTION_INPUT_SCHEMA
66921
69512
  },
66922
69513
  {
@@ -67386,7 +69977,7 @@ var init_tools = __esm({
67386
69977
  },
67387
69978
  {
67388
69979
  name: "sfi.what_happens_on_save",
67389
- description: "What ALREADY runs when a record on this object is created or updated \u2014 every existing automation (flows, triggers, validation rules, workflow, approvals, assignment rules) that fires on save, in the order it fires. Use it before building a new automation to check what's already there and whether your new process would duplicate or collide with an existing one. Produce the documented Salesforce order-of-execution (SOE) instantiated for THIS org and the given DML event on the target object. Walks the canonical SOE phases in order \u2014 before-save-flows (before-save record-triggered Flows \u2014 `triggersOn` edge `triggerType` RecordBeforeSave \u2014 which run FIRST, ahead of before-triggers; insert/update only), pre-save-validation (ValidationRules), pre-save-triggers + after-triggers (ApexTriggers whose `events` includes a matching `before <event>` / `after <event>` lifecycle entry), save (a documented placeholder for system validation + DB write), post-save-flows (record-triggered AFTER-save Flows whose `recordTriggerType` matches the event), post-save-workflows (WorkflowRules whose `triggerType` matches), post-save-assignment (Lead/Case AssignmentRules + AutoResponseRules + EscalationRules parented to the object), post-save-approval (ApprovalProcesses parented to the object), and post-save-async (ApexClasses dispatched via `dispatchesAsync` from any trigger above). Only ACTIVE automation is listed as execution steps \u2014 Draft/Obsolete Flows and active:false rules/processes are omitted from `soe` and surfaced in `inactiveConfigured` when present. Each step carries the firer's id/type/apiName, the gating `firesWhen` ConditionalContext when one exists, and an actions array enumerating the firer's outgoing edges (excluding structural parentOf/triggersOn/firesWhen). Honesty axis (verbatim): conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether the runtime record satisfies them. Workflow field updates can re-fire before/after-update triggers (a second pass); the composition lists each automation once. A workflow rule's time-dependent actions (workflowTimeTriggers) are SCHEDULED for a record-level offset the offline vault cannot evaluate; the rule is listed once in the synchronous post-save-workflows phase and its time-delayed actions are NOT claimed to fire at save. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.",
69980
+ description: "What ALREADY runs when a record on this object is created or updated \u2014 every existing automation (flows, triggers, validation rules, workflow, approvals, assignment rules) that fires on save, in the order it fires. Use it before building a new automation to check what's already there and whether your new process would duplicate or collide with an existing one. Also the answer to informal asks like 'what fires / runs when I save an Account', 'everything that touches X on save', 'we keep hitting record-lock errors \u2014 what's colliding on this object', 'any automation on X I should know about before adding more' (for WHICH automation is risky, use automation_risk_report instead). Produce the documented Salesforce order-of-execution (SOE) instantiated for THIS org and the given DML event on the target object. Walks the canonical SOE phases in order \u2014 before-save-flows (before-save record-triggered Flows \u2014 `triggersOn` edge `triggerType` RecordBeforeSave \u2014 which run FIRST, ahead of before-triggers; insert/update only), pre-save-validation (ValidationRules), pre-save-triggers + after-triggers (ApexTriggers whose `events` includes a matching `before <event>` / `after <event>` lifecycle entry), save (a documented placeholder for system validation + DB write), post-save-flows (record-triggered AFTER-save Flows whose `recordTriggerType` matches the event), post-save-workflows (WorkflowRules whose `triggerType` matches), post-save-assignment (Lead/Case AssignmentRules + AutoResponseRules + EscalationRules parented to the object), post-save-approval (ApprovalProcesses parented to the object), and post-save-async (ApexClasses dispatched via `dispatchesAsync` from any trigger above). Only ACTIVE automation is listed as execution steps \u2014 Draft/Obsolete Flows and active:false rules/processes are omitted from `soe` and surfaced in `inactiveConfigured` when present. Each step carries the firer's id/type/apiName, the gating `firesWhen` ConditionalContext when one exists, and an actions array enumerating the firer's outgoing edges (excluding structural parentOf/triggersOn/firesWhen). Honesty axis (verbatim): conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether the runtime record satisfies them. Workflow field updates can re-fire before/after-update triggers (a second pass); the composition lists each automation once. A workflow rule's time-dependent actions (workflowTimeTriggers) are SCHEDULED for a record-level offset the offline vault cannot evaluate; the rule is listed once in the synchronous post-save-workflows phase and its time-delayed actions are NOT claimed to fire at save. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.",
67390
69981
  inputSchema: WHAT_HAPPENS_ON_SAVE_INPUT_SCHEMA
67391
69982
  },
67392
69983
  {
@@ -67775,7 +70366,7 @@ var init_tools = __esm({
67775
70366
  },
67776
70367
  {
67777
70368
  name: "sfi.find_component_usages",
67778
- description: 'The universal "where is this component used?" answer for ANY canonical component type (`componentId`) \u2014 one entry point instead of fanning out across find_field_anywhere / find_code_usages / get_impact / grep. Composes two evidence tiers: (1) GRAPH \u2014 incoming dependency edges to the target, grouped by referrer type, each carrying edge `confidence`, EXCLUDING access grants (`grantedBy`) and structural `parentOf` (access is not usage); (2) GREP supplement (`text-match` tier, `includeGrep` default true) \u2014 a literal search of Apex AND frontend bundle source (LWC/Aura/Visualforce \u2014 `$Label`/`$Resource`/`@salesforce` module references) for the api name, catching references the graph does not model (dynamic SOQL, reflective access, CustomMetadataType / CustomLabel / StaticResource refs). `graphReferrers[]` (type + count + sample), `grepSupplement` (matches with path/line/snippet), `summary` (counts + `hasStaticEvidence`), `boundaries[]`, `truncated`. ACCESS-GRANT section: grants stay OUT of `graphReferrers`, but a separate `grantedBy` section (`{count, granters[{id,type}]}`) surfaces the granting containers when the target is a `CustomPermission` (its only incoming edges ARE grants \u2014 this answers "which Profiles / PermissionSets grant it?"; always present for that type, count 0 = no container grants it) or when the target has zero usage edges but incoming `grantedBy` edges; absent otherwise. HONESTY: empty graph + empty grep = "no static evidence in the vault" (in `boundaries`), NEVER "nothing uses this" \u2014 dynamic constructs, un-modeled families (reports/dashboards/list-views), and managed packages are invisible; grants are NOT usage, so `grantedBy` never counts toward `hasStaticEvidence`. Phantom-aware (a referenced-but-not-retrieved target still answers from its edges, including a granted-but-undefined managed-package CustomPermission). Specialized tools (find_field_anywhere, layout_assignments, \u2026) stay for a deeper single-family answer; this unifies the common case. Non-canonical id \u2192 `invalid-query`; an id with no node, no referrers AND no grants \u2192 `component-not-found`.',
70369
+ description: 'The universal "where is this component used?" answer for ANY canonical component type (`componentId`) \u2014 one entry point instead of fanning out across find_field_anywhere / find_code_usages / get_impact / grep. Also the answer to informal asks like \'who has this permission set / which profiles grant it\', \'what references / calls into this component\', \'is this still used anywhere\' \u2014 it answers what points TO a named component (use search_components to find components MATCHING a description). Composes two evidence tiers: (1) GRAPH \u2014 incoming dependency edges to the target, grouped by referrer type, each carrying edge `confidence`, EXCLUDING access grants (`grantedBy`) and structural `parentOf` (access is not usage); (2) GREP supplement (`text-match` tier, `includeGrep` default true) \u2014 a literal search of Apex AND frontend bundle source (LWC/Aura/Visualforce \u2014 `$Label`/`$Resource`/`@salesforce` module references) for the api name, catching references the graph does not model (dynamic SOQL, reflective access, CustomMetadataType / CustomLabel / StaticResource refs). `graphReferrers[]` (type + count + sample), `grepSupplement` (matches with path/line/snippet), `summary` (counts + `hasStaticEvidence`), `boundaries[]`, `truncated`. ACCESS-GRANT section: grants stay OUT of `graphReferrers`, but a separate `grantedBy` section (`{count, granters[{id,type}]}`) surfaces the granting containers when the target is a `CustomPermission` (its only incoming edges ARE grants \u2014 this answers "which Profiles / PermissionSets grant it?"; always present for that type, count 0 = no container grants it) or when the target has zero usage edges but incoming `grantedBy` edges; absent otherwise. HONESTY: empty graph + empty grep = "no static evidence in the vault" (in `boundaries`), NEVER "nothing uses this" \u2014 dynamic constructs, un-modeled families (reports/dashboards/list-views), and managed packages are invisible; grants are NOT usage, so `grantedBy` never counts toward `hasStaticEvidence`. Phantom-aware (a referenced-but-not-retrieved target still answers from its edges, including a granted-but-undefined managed-package CustomPermission). Specialized tools (find_field_anywhere, layout_assignments, \u2026) stay for a deeper single-family answer; this unifies the common case. Non-canonical id \u2192 `invalid-query`; an id with no node, no referrers AND no grants \u2192 `component-not-found`.',
67779
70370
  inputSchema: FIND_COMPONENT_USAGES_INPUT_SCHEMA
67780
70371
  },
67781
70372
  {
@@ -69855,7 +72446,7 @@ var init_package_version = __esm({
69855
72446
  "use strict";
69856
72447
  readCliPackageVersion = () => {
69857
72448
  if (true)
69858
- return "0.1.21";
72449
+ return "0.1.22";
69859
72450
  for (const rel of ["../package.json", "../../package.json"]) {
69860
72451
  try {
69861
72452
  const raw = readFileSync3(fileURLToPath(new URL(rel, import.meta.url)), "utf8");
@@ -136983,10 +139574,10 @@ var init_refresh = __esm({
136983
139574
  progress(`Incremental: reused ${walked.reusedCount} unchanged file(s) from cache.`);
136984
139575
  }
136985
139576
  if (!opts.noPull && (requestedTypes === null || requestedTypes.has("CustomObject"))) {
136986
- const expand2 = objectsToExpandManifest(walked.results);
136987
- if (expand2.length > 0) {
136988
- progress(`Auto-expanding retrieve: ${expand2.length} object(s) your automation references but the wildcard excluded...`);
136989
- const pulled2 = await runSfRetrieveObjects(targetOrg, paths.source, expand2);
139577
+ const expand = objectsToExpandManifest(walked.results);
139578
+ if (expand.length > 0) {
139579
+ progress(`Auto-expanding retrieve: ${expand.length} object(s) your automation references but the wildcard excluded...`);
139580
+ const pulled2 = await runSfRetrieveObjects(targetOrg, paths.source, expand);
136990
139581
  if (pulled2.ok) {
136991
139582
  walked = await walkAndExtract(paths.source, requestedTypes, prevCache);
136992
139583
  } else {
@@ -139779,7 +142370,7 @@ var makeShutdownOnce = (ctx) => {
139779
142370
 
139780
142371
  // dist/src/commands/demo.js
139781
142372
  init_refresh();
139782
- var buildVersion = () => true ? "0.1.21" : "dev";
142373
+ var buildVersion = () => true ? "0.1.22" : "dev";
139783
142374
  var SHUTDOWN_SIGNALS2 = ["SIGINT", "SIGTERM"];
139784
142375
  var resolveDemoSource = () => {
139785
142376
  let dir = dirname21(fileURLToPath2(import.meta.url));
@@ -140764,7 +143355,7 @@ init_vault_git();
140764
143355
  init_watch();
140765
143356
  var readVersion = () => {
140766
143357
  if (true)
140767
- return "0.1.21";
143358
+ return "0.1.22";
140768
143359
  const pkgUrl = new URL("../../package.json", import.meta.url);
140769
143360
  const raw = readFileSync6(fileURLToPath3(pkgUrl), "utf8");
140770
143361
  const parsed = JSON.parse(raw);