sf-intelligence 0.1.1 → 0.1.5

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.
Files changed (4) hide show
  1. package/bin/sfi.js +0 -0
  2. package/dist/index.js +2403 -401
  3. package/package.json +16 -16
  4. package/LICENSE +0 -201
package/dist/index.js CHANGED
@@ -944,7 +944,7 @@ var init_resolve_index = __esm({
944
944
  });
945
945
 
946
946
  // ../graph/dist/src/resolve.js
947
- var DEFAULT_LIMIT, MAX_LIMIT, MIN_BASE, MATCHED_FLOOR, NONE_THRESHOLD, EXACT_THRESHOLD, EXACT_COVERAGE, CONTENDER_RATIO, LENGTH_RATIO_FLOOR, SYNONYM_SCORE, POP_K, COVERAGE_EXP, STRONG_ANCHOR, PARENT_MATCH_BONUS, TYPE_WEIGHT, typeWeight, scoreToken, rollupKind, buildEvidence, resolveComponents;
947
+ var DEFAULT_LIMIT, MAX_LIMIT, MIN_BASE, MATCHED_FLOOR, NONE_THRESHOLD, EXACT_THRESHOLD, EXACT_COVERAGE, CONTENDER_RATIO, LENGTH_RATIO_FLOOR, SYNONYM_SCORE, POP_K, COVERAGE_EXP, STRONG_ANCHOR, TYPE_WEIGHT, typeWeight, scoreToken, rollupKind, buildEvidence, resolveComponents;
948
948
  var init_resolve = __esm({
949
949
  "../graph/dist/src/resolve.js"() {
950
950
  "use strict";
@@ -964,7 +964,6 @@ var init_resolve = __esm({
964
964
  POP_K = 0.08;
965
965
  COVERAGE_EXP = 0.5;
966
966
  STRONG_ANCHOR = 0.9;
967
- PARENT_MATCH_BONUS = 1.1;
968
967
  TYPE_WEIGHT = {
969
968
  CustomObject: 1,
970
969
  CustomField: 0.95,
@@ -1028,6 +1027,7 @@ var init_resolve = __esm({
1028
1027
  const minBase = options?.minScore ?? MIN_BASE;
1029
1028
  const queryTokens = tokenizeText(query);
1030
1029
  const normQuery = normalizeName(query);
1030
+ const querySpaceWords = new Set(query.trim().split(/\s+/).map(normalizeName).filter((w) => w.length > 0));
1031
1031
  let index;
1032
1032
  try {
1033
1033
  index = await getResolveIndex(store);
@@ -1038,6 +1038,12 @@ var init_resolve = __esm({
1038
1038
  });
1039
1039
  }
1040
1040
  const candidateIdx = gatherCandidates(index, queryTokens, normQuery);
1041
+ const objectNormNames = /* @__PURE__ */ new Set();
1042
+ for (const n of index.nodes) {
1043
+ if (n.type === "CustomObject")
1044
+ objectNormNames.add(n.normName);
1045
+ }
1046
+ const namedObjectWords = new Set([...querySpaceWords].filter((w) => objectNormNames.has(w)));
1041
1047
  const typeFilter = options?.types !== void 0 && options.types.length > 0 ? new Set(options.types) : null;
1042
1048
  const parentFilter = options?.parentId;
1043
1049
  const pass1 = [];
@@ -1080,7 +1086,8 @@ var init_resolve = __esm({
1080
1086
  if (perToken[i].score > globalBest[i])
1081
1087
  globalBest[i] = perToken[i].score;
1082
1088
  }
1083
- const wholeExact = normQuery.length >= 2 && node.normName === normQuery && query.includes(".") === node.apiName.includes(".");
1089
+ const crossObjectFieldDecoy = node.type === "CustomField" && namedObjectWords.size > 0 && (node.parentApiName === null || !namedObjectWords.has(normalizeName(node.parentApiName)));
1090
+ const wholeExact = normQuery.length >= 2 && node.normName === normQuery && query.includes(".") === node.apiName.includes(".") && !crossObjectFieldDecoy;
1084
1091
  pass1.push({ node, perToken, wholeExact, parentMatched });
1085
1092
  }
1086
1093
  const anchorIdx = [];
@@ -1092,6 +1099,7 @@ var init_resolve = __esm({
1092
1099
  const scored = [];
1093
1100
  const coverageById = /* @__PURE__ */ new Map();
1094
1101
  const wholeExactIds = /* @__PURE__ */ new Set();
1102
+ const parentMatchedIds = /* @__PURE__ */ new Set();
1095
1103
  for (const c of pass1) {
1096
1104
  const matched = c.perToken.filter((t) => t.score >= MATCHED_FLOOR);
1097
1105
  if (matched.length === 0 && !c.wholeExact)
@@ -1106,13 +1114,15 @@ var init_resolve = __esm({
1106
1114
  continue;
1107
1115
  const type = c.node.type;
1108
1116
  const refs = c.node.inbound;
1109
- const score = base * typeWeight(type) * (1 + POP_K * Math.log10(1 + refs)) * (c.parentMatched ? PARENT_MATCH_BONUS : 1);
1117
+ const score = base * typeWeight(type) * (1 + POP_K * Math.log10(1 + refs));
1110
1118
  const kind = c.wholeExact ? "exact" : rollupKind(matched);
1111
1119
  const nodeTokenSet = new Set(c.node.tokens);
1112
1120
  const matchedNodeTokens = new Set(matched.map((m) => m.matchedToken).filter((t) => nodeTokenSet.has(t)));
1113
1121
  coverageById.set(c.node.id, c.wholeExact ? 1 : matchedNodeTokens.size / c.node.tokens.length);
1114
1122
  if (c.wholeExact)
1115
1123
  wholeExactIds.add(c.node.id);
1124
+ if (c.parentMatched)
1125
+ parentMatchedIds.add(c.node.id);
1116
1126
  scored.push({
1117
1127
  id: c.node.id,
1118
1128
  type,
@@ -1134,6 +1144,10 @@ var init_resolve = __esm({
1134
1144
  const tierB = b.base >= EXACT_THRESHOLD ? 0 : 1;
1135
1145
  if (tierA !== tierB)
1136
1146
  return tierA - tierB;
1147
+ const pmA = parentMatchedIds.has(a.id) ? 0 : 1;
1148
+ const pmB = parentMatchedIds.has(b.id) ? 0 : 1;
1149
+ if (pmA !== pmB)
1150
+ return pmA - pmB;
1137
1151
  if (a.score !== b.score)
1138
1152
  return b.score - a.score;
1139
1153
  return a.id < b.id ? -1 : 1;
@@ -1145,7 +1159,7 @@ var init_resolve = __esm({
1145
1159
  if (top === void 0 || bestBase < NONE_THRESHOLD) {
1146
1160
  disposition = "none";
1147
1161
  } else {
1148
- const contenders = candidates.filter((c) => c.score >= top.score * CONTENDER_RATIO);
1162
+ const contenders = candidates.filter((c) => c.score >= top.score * CONTENDER_RATIO || parentMatchedIds.has(c.id));
1149
1163
  const topCoverage = coverageById.get(top.id) ?? 0;
1150
1164
  disposition = top.base >= EXACT_THRESHOLD && contenders.length === 1 && topCoverage >= EXACT_COVERAGE ? "exact" : "ambiguous";
1151
1165
  }
@@ -2482,7 +2496,7 @@ var init_finding_suppression = __esm({
2482
2496
 
2483
2497
  // ../mcp/dist/src/tools/crud-fls-audit.js
2484
2498
  import { z as z2 } from "zod";
2485
- var CRUD_FLS_TOOL, CRUD_FLS_MAX_LIMIT, CRUD_FLS_DEFAULT_LIMIT, LIST_PAGE_SIZE, CRUD_FLS_RULES, SCANNED_TYPES, SEVERITY_SET, Q80_FALSE_POSITIVE_DISCLOSURE, CROSS_METHOD_DATAFLOW_DISCLOSURE, DYNAMIC_SOQL_DISCLOSURE, crudFlsAuditInputSchema, coerceIssue, compareClassById, crudFlsAuditHandler;
2499
+ var CRUD_FLS_TOOL, CRUD_FLS_MAX_LIMIT, CRUD_FLS_DEFAULT_LIMIT, LIST_PAGE_SIZE, CRUD_FLS_RULES, SCANNED_TYPES, SEVERITY_SET, Q80_FALSE_POSITIVE_DISCLOSURE, CROSS_METHOD_DATAFLOW_DISCLOSURE, DYNAMIC_SOQL_DISCLOSURE, crudFlsAuditInputSchema, CRUD_FLS_PAYLOAD_BUDGET_BYTES, trimEntryFindings, fitClassesToBudget, coerceIssue, compareClassById, crudFlsAuditHandler;
2486
2500
  var init_crud_fls_audit = __esm({
2487
2501
  "../mcp/dist/src/tools/crud-fls-audit.js"() {
2488
2502
  "use strict";
@@ -2512,8 +2526,39 @@ var init_crud_fls_audit = __esm({
2512
2526
  CROSS_METHOD_DATAFLOW_DISCLOSURE = "cross-method dataflow is invisible \u2014 a method that delegates the dangerous operation to a helper is analyzed in isolation; the helper behavior is invisible. Spot-check the call chain before treating a finding as a bug.";
2513
2527
  DYNAMIC_SOQL_DISCLOSURE = "dynamic SOQL (Database.query) strings are stripped before pattern passes; the embedded SQL is invisible to the FLS recognizer.";
2514
2528
  crudFlsAuditInputSchema = z2.object({
2515
- limit: z2.number().int().min(1).max(CRUD_FLS_MAX_LIMIT).optional()
2529
+ limit: z2.number().int().min(1).max(CRUD_FLS_MAX_LIMIT).optional(),
2530
+ offset: z2.number().int().min(0).optional()
2516
2531
  });
2532
+ CRUD_FLS_PAYLOAD_BUDGET_BYTES = 36e3;
2533
+ trimEntryFindings = (entry, budgetBytes) => {
2534
+ const kept = [];
2535
+ let used = Buffer.byteLength(JSON.stringify({ ...entry, findings: [] }), "utf8");
2536
+ for (const finding of entry.findings) {
2537
+ const size = Buffer.byteLength(JSON.stringify(finding), "utf8") + 1;
2538
+ if (kept.length > 0 && used + size > budgetBytes)
2539
+ break;
2540
+ kept.push(finding);
2541
+ used += size;
2542
+ }
2543
+ return { ...entry, findings: kept, findingsTruncated: true };
2544
+ };
2545
+ fitClassesToBudget = (classes, budgetBytes) => {
2546
+ const kept = [];
2547
+ let used = 0;
2548
+ for (const entry of classes) {
2549
+ const size = Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
2550
+ if (kept.length === 0 && size > budgetBytes) {
2551
+ kept.push(trimEntryFindings(entry, budgetBytes));
2552
+ return { kept, trimmed: true };
2553
+ }
2554
+ if (kept.length > 0 && used + size > budgetBytes) {
2555
+ return { kept, trimmed: true };
2556
+ }
2557
+ kept.push(entry);
2558
+ used += size;
2559
+ }
2560
+ return { kept, trimmed: false };
2561
+ };
2517
2562
  coerceIssue = (raw) => {
2518
2563
  if (raw === null || typeof raw !== "object")
2519
2564
  return null;
@@ -2586,8 +2631,11 @@ var init_crud_fls_audit = __esm({
2586
2631
  }
2587
2632
  }
2588
2633
  const classes = [...perClass.values()].sort(compareClassById);
2589
- const truncated = classes.length > limit;
2590
- const slice = classes.slice(0, limit);
2634
+ const offset = input2.offset ?? 0;
2635
+ const page = classes.slice(offset, offset + limit);
2636
+ const { kept, trimmed } = fitClassesToBudget(page, CRUD_FLS_PAYLOAD_BUDGET_BYTES);
2637
+ const returnedEnd = offset + kept.length;
2638
+ const truncated = returnedEnd < classes.length;
2591
2639
  const boundaries = classes.length === 0 ? [] : [
2592
2640
  Q80_FALSE_POSITIVE_DISCLOSURE,
2593
2641
  CROSS_METHOD_DATAFLOW_DISCLOSURE,
@@ -2595,13 +2643,19 @@ var init_crud_fls_audit = __esm({
2595
2643
  ];
2596
2644
  return ok({
2597
2645
  data: {
2598
- classes: slice,
2646
+ classes: kept,
2599
2647
  totalClassCount: classes.length,
2600
2648
  totalFindingCount,
2601
2649
  suppressedFindingCount,
2602
2650
  byRule,
2603
2651
  boundaries,
2604
- truncated
2652
+ limit,
2653
+ offset,
2654
+ truncated,
2655
+ ...truncated ? { nextOffset: returnedEnd } : {},
2656
+ ...trimmed ? {
2657
+ note: `Response trimmed to ${kept.length} of ${page.length} classes (${classes.length} total) to stay under the ~45 KB MCP response limit. Advance with offset += ${kept.length} for the rest.`
2658
+ } : {}
2605
2659
  },
2606
2660
  vaultState: {
2607
2661
  sourceTreeHash: ctx.manifest.sourceTreeHash,
@@ -7193,7 +7247,10 @@ var init_event_subscribers = __esm({
7193
7247
  "Flow"
7194
7248
  ]);
7195
7249
  eventSubscribersInputSchema = z29.object({
7196
- eventId: z29.string().min(1),
7250
+ // Optional (R0791 fix): OMIT to get the catalog of ALL Platform Events with
7251
+ // their subscriber counts — answers "what platform events does this org
7252
+ // publish?". Supply it for the subscriber list of one specific event.
7253
+ eventId: z29.string().min(1).optional(),
7197
7254
  limit: z29.number().int().min(1).max(EVENT_SUBSCRIBERS_MAX_LIMIT).optional()
7198
7255
  });
7199
7256
  validateEventId = (eventId) => {
@@ -7228,6 +7285,46 @@ var init_event_subscribers = __esm({
7228
7285
  };
7229
7286
  compareSubscribers2 = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
7230
7287
  eventSubscribersHandler = async (ctx, input2) => {
7288
+ const limit = input2.limit ?? EVENT_SUBSCRIBERS_DEFAULT_LIMIT;
7289
+ if (input2.eventId === void 0) {
7290
+ const nodesResult = await listNodesByType(ctx.graph, "CustomObject", {
7291
+ limit: EVENT_SUBSCRIBERS_MAX_LIMIT
7292
+ });
7293
+ if (!nodesResult.ok) {
7294
+ return err({ kind: "internal", message: `graph query failed: ${nodesResult.error.message}` });
7295
+ }
7296
+ const events = [];
7297
+ for (const node of nodesResult.value) {
7298
+ if (!node.apiName.endsWith(EVENT_API_NAME_SUFFIX))
7299
+ continue;
7300
+ const inEdges = await listEdges(ctx.graph, node.id, {
7301
+ direction: "in",
7302
+ edgeType: "listensTo"
7303
+ });
7304
+ if (!inEdges.ok) {
7305
+ return err({ kind: "internal", message: `graph query failed: ${inEdges.error.message}` });
7306
+ }
7307
+ let subscriberCount = 0;
7308
+ for (const edge of inEdges.value) {
7309
+ const sub = await getNodeById(ctx.graph, edge.fromId);
7310
+ if (!sub.ok) {
7311
+ return err({ kind: "internal", message: `graph query failed: ${sub.error.message}` });
7312
+ }
7313
+ if (sub.value !== null && SUBSCRIBER_NODE_TYPES2.has(sub.value.type)) {
7314
+ subscriberCount += 1;
7315
+ }
7316
+ }
7317
+ events.push({ eventId: node.id, eventApiName: node.apiName, subscriberCount });
7318
+ }
7319
+ events.sort((a, b) => a.eventId < b.eventId ? -1 : a.eventId > b.eventId ? 1 : 0);
7320
+ return ok({
7321
+ data: { subscribers: [], eventApiName: null, events: events.slice(0, limit) },
7322
+ vaultState: {
7323
+ sourceTreeHash: ctx.manifest.sourceTreeHash,
7324
+ refreshedAt: ctx.manifest.refreshedAt
7325
+ }
7326
+ });
7327
+ }
7231
7328
  const apiName = validateEventId(input2.eventId);
7232
7329
  if (apiName === null) {
7233
7330
  return err({
@@ -7236,7 +7333,6 @@ var init_event_subscribers = __esm({
7236
7333
  path: "eventId"
7237
7334
  });
7238
7335
  }
7239
- const limit = input2.limit ?? EVENT_SUBSCRIBERS_DEFAULT_LIMIT;
7240
7336
  const edgesResult = await listEdges(ctx.graph, input2.eventId, {
7241
7337
  direction: "in",
7242
7338
  edgeType: "listensTo"
@@ -7460,6 +7556,23 @@ var init_explain_apex_method = __esm({
7460
7556
  }
7461
7557
  });
7462
7558
 
7559
+ // ../mcp/dist/src/tools/phantom-node.js
7560
+ var phantomAwareNotFoundMessage;
7561
+ var init_phantom_node = __esm({
7562
+ "../mcp/dist/src/tools/phantom-node.js"() {
7563
+ "use strict";
7564
+ init_src();
7565
+ phantomAwareNotFoundMessage = async (ctx, id, kindLabel) => {
7566
+ const inbound = await listEdges(ctx.graph, id, { direction: "in" });
7567
+ const refs = inbound.ok ? inbound.value.length : 0;
7568
+ if (refs === 0) {
7569
+ return `no ${kindLabel} with id ${id}`;
7570
+ }
7571
+ return `\`${id}\` is referenced by ${refs} other component(s) in this org (e.g. code, tests, or permission grants) but its own ${kindLabel} definition was never retrieved into the vault \u2014 typically a managed-package component or one outside the retrieve scope. Run \`sfi refresh\` if it should be retrievable; otherwise treat it as external.`;
7572
+ };
7573
+ }
7574
+ });
7575
+
7463
7576
  // ../mcp/dist/src/tools/explain-field.js
7464
7577
  import { z as z31 } from "zod";
7465
7578
  var CUSTOM_FIELD_PREFIX, CUSTOM_METADATA_DEFINITION_SUFFIX, explainFieldInputSchema, readFieldType2, readFieldFormula, readFieldReferenceTo, readFieldDescription, readFieldInlineHelpText, readFieldRequired, readFieldLabel2, parentTypeApiName2, shouldIncludeRecordValues, readFieldApiName, findValueForField, compareRecordValues, collectRecordValues, explainFieldHandler;
@@ -7468,6 +7581,7 @@ var init_explain_field = __esm({
7468
7581
  "use strict";
7469
7582
  init_dist();
7470
7583
  init_src();
7584
+ init_phantom_node();
7471
7585
  CUSTOM_FIELD_PREFIX = "CustomField:";
7472
7586
  CUSTOM_METADATA_DEFINITION_SUFFIX = "__mdt";
7473
7587
  explainFieldInputSchema = z31.object({
@@ -7581,7 +7695,7 @@ var init_explain_field = __esm({
7581
7695
  if (node === null) {
7582
7696
  return err({
7583
7697
  kind: "component-not-found",
7584
- message: `no field with id ${input2.fieldId}`,
7698
+ message: await phantomAwareNotFoundMessage(ctx, input2.fieldId, "CustomField"),
7585
7699
  path: input2.fieldId
7586
7700
  });
7587
7701
  }
@@ -9900,7 +10014,7 @@ var init_code_quality_patterns = __esm({
9900
10014
  return [];
9901
10015
  const issues = [];
9902
10016
  const seen = /* @__PURE__ */ new Set();
9903
- const collect = (re, op, sobject) => {
10017
+ const collect2 = (re, op, sobject) => {
9904
10018
  const r = new RegExp(re.source, "g");
9905
10019
  let m;
9906
10020
  while ((m = r.exec(stripped)) !== null) {
@@ -9920,8 +10034,8 @@ var init_code_quality_patterns = __esm({
9920
10034
  });
9921
10035
  }
9922
10036
  };
9923
- collect(DML_STATEMENT_PATTERN, "dml", null);
9924
- collect(DML_DATABASE_CALL_PATTERN, "dml", null);
10037
+ collect2(DML_STATEMENT_PATTERN, "dml", null);
10038
+ collect2(DML_DATABASE_CALL_PATTERN, "dml", null);
9925
10039
  return issues;
9926
10040
  };
9927
10041
  INLINE_SOQL_PATTERN = /\[\s*SELECT\b([\s\S]*?)\]/gi;
@@ -10384,14 +10498,32 @@ var init_src4 = __esm({
10384
10498
 
10385
10499
  // ../mcp/dist/src/tools/field-access-audit.js
10386
10500
  import { z as z35 } from "zod";
10387
- var CUSTOM_FIELD_PREFIX3, PERMISSION_TYPE_VALUES, GRANTOR_TYPES, APEX_NODE_TYPES, fieldAccessAuditInputSchema, resolvePermissionLevel, permissionMatches, compareGrants, compareApexAccess, fieldAccessAuditHandler;
10501
+ var CUSTOM_FIELD_PREFIX3, synthesizeFieldNode, PERMISSION_TYPE_VALUES, GRANTOR_TYPES, APEX_NODE_TYPES, fieldAccessAuditInputSchema, resolvePermissionLevel, permissionMatches, compareGrants, compareApexAccess, fieldAccessAuditHandler;
10388
10502
  var init_field_access_audit = __esm({
10389
10503
  "../mcp/dist/src/tools/field-access-audit.js"() {
10390
10504
  "use strict";
10391
10505
  init_dist();
10392
10506
  init_src();
10393
10507
  init_src4();
10508
+ init_phantom_node();
10394
10509
  CUSTOM_FIELD_PREFIX3 = "CustomField:";
10510
+ synthesizeFieldNode = (fieldId) => {
10511
+ const afterPrefix = fieldId.slice(CUSTOM_FIELD_PREFIX3.length);
10512
+ const dot = afterPrefix.lastIndexOf(".");
10513
+ const apiName = dot >= 0 ? afterPrefix.slice(dot + 1) : afterPrefix;
10514
+ return {
10515
+ id: fieldId,
10516
+ type: "CustomField",
10517
+ apiName,
10518
+ label: null,
10519
+ parentId: dot >= 0 ? `CustomObject:${afterPrefix.slice(0, dot)}` : null,
10520
+ sourcePath: "",
10521
+ lastModifiedDate: null,
10522
+ lastModifiedBy: null,
10523
+ apiVersion: null,
10524
+ properties: {}
10525
+ };
10526
+ };
10395
10527
  PERMISSION_TYPE_VALUES = ["read", "edit", "all"];
10396
10528
  GRANTOR_TYPES = /* @__PURE__ */ new Set([
10397
10529
  "Profile",
@@ -10445,14 +10577,6 @@ var init_field_access_audit = __esm({
10445
10577
  });
10446
10578
  }
10447
10579
  const fieldNode = fieldResult.value;
10448
- if (fieldNode === null) {
10449
- return err({
10450
- kind: "component-not-found",
10451
- message: `no field with id ${fieldId}`,
10452
- path: fieldId
10453
- });
10454
- }
10455
- const detection = detectPiiClassificationWithReason(fieldNode);
10456
10580
  const grantedByResult = await listEdges(ctx.graph, fieldId, {
10457
10581
  direction: "in",
10458
10582
  edgeType: "grantedBy"
@@ -10472,6 +10596,16 @@ var init_field_access_audit = __esm({
10472
10596
  message: `graph query failed: ${allIncomingResult.error.message}`
10473
10597
  });
10474
10598
  }
10599
+ if (fieldNode === null && allIncomingResult.value.length === 0) {
10600
+ return err({
10601
+ kind: "component-not-found",
10602
+ message: await phantomAwareNotFoundMessage(ctx, fieldId, "CustomField"),
10603
+ path: fieldId
10604
+ });
10605
+ }
10606
+ const notModeled = fieldNode === null;
10607
+ const effectiveField = fieldNode ?? synthesizeFieldNode(fieldId);
10608
+ const detection = detectPiiClassificationWithReason(effectiveField);
10475
10609
  let profilesWithRead = 0;
10476
10610
  let profilesWithEdit = 0;
10477
10611
  let profilesWithUnknown = 0;
@@ -10553,7 +10687,11 @@ var init_field_access_audit = __esm({
10553
10687
  return ok({
10554
10688
  data: {
10555
10689
  fieldId,
10556
- fieldLabel: fieldNode.label ?? fieldNode.apiName,
10690
+ fieldLabel: effectiveField.label ?? effectiveField.apiName,
10691
+ notModeled,
10692
+ ...notModeled ? {
10693
+ notModeledNote: `\`${fieldId}\`'s own field definition was not retrieved into the vault \u2014 standard fields and managed-package fields are not modeled. The grants and Apex access below are read from the permission/usage edges (accurate); the field's data type, formula, and description are unavailable, and the PII classification is inferred from the field name alone.`
10694
+ } : {},
10557
10695
  piiClassification: detection.piiClassification,
10558
10696
  piiCategory: detection.piiCategory,
10559
10697
  grants: [...grants].sort(compareGrants),
@@ -10585,6 +10723,7 @@ var init_safe_to_delete_field = __esm({
10585
10723
  init_dist();
10586
10724
  init_src();
10587
10725
  init_src2();
10726
+ init_phantom_node();
10588
10727
  CUSTOM_FIELD_PREFIX4 = "CustomField:";
10589
10728
  EXAMPLES_PER_CATEGORY_LIMIT = 5;
10590
10729
  CATEGORY_ORDER = [
@@ -10793,10 +10932,49 @@ var init_safe_to_delete_field = __esm({
10793
10932
  });
10794
10933
  }
10795
10934
  if (nodeResult.value === null) {
10796
- return err({
10797
- kind: "component-not-found",
10798
- message: `no field with id ${fieldId}`,
10799
- path: fieldId
10935
+ const inboundResult = await listEdges(ctx.graph, fieldId, {
10936
+ direction: "in"
10937
+ });
10938
+ if (!inboundResult.ok) {
10939
+ return err({
10940
+ kind: "internal",
10941
+ message: `graph query failed: ${inboundResult.error.message}`
10942
+ });
10943
+ }
10944
+ if (inboundResult.value.length === 0) {
10945
+ return err({
10946
+ kind: "component-not-found",
10947
+ message: await phantomAwareNotFoundMessage(ctx, fieldId, "CustomField"),
10948
+ path: fieldId
10949
+ });
10950
+ }
10951
+ return ok({
10952
+ data: {
10953
+ fieldId,
10954
+ verdict: "review",
10955
+ reasoning: [
10956
+ {
10957
+ category: "unknown",
10958
+ verdict: "review",
10959
+ count: inboundResult.value.length,
10960
+ examples: [],
10961
+ note: `This field's own definition was not retrieved into the vault \u2014 standard fields and managed-package fields are not modeled. It is referenced by ${inboundResult.value.length} component(s)/grant(s). NOT proven safe to delete: a standard field cannot be deleted via metadata, and a not-modeled field cannot be fully assessed. Run \`sfi refresh\` if it should be retrievable, or treat it as external.`
10962
+ }
10963
+ ],
10964
+ trust: {
10965
+ provenance: "offline_snapshot",
10966
+ confidence: "declared",
10967
+ freshness: { snapshotRefreshedAt: ctx.manifest.refreshedAt },
10968
+ completeness: { status: "partial" },
10969
+ limitations: [
10970
+ "The field's own definition is not in the vault (standard or managed-package field); this verdict is based on inbound references only."
10971
+ ]
10972
+ }
10973
+ },
10974
+ vaultState: {
10975
+ sourceTreeHash: ctx.manifest.sourceTreeHash,
10976
+ refreshedAt: ctx.manifest.refreshedAt
10977
+ }
10800
10978
  });
10801
10979
  }
10802
10980
  if (nodeResult.value.properties["system"] === true) {
@@ -13287,6 +13465,11 @@ var init_find_dead_code = __esm({
13287
13465
  FALSE) AS is_own_entry_point
13288
13466
  FROM nodes
13289
13467
  WHERE type IN (${placeholders})
13468
+ -- Standard fields (api name has no __c suffix) are platform fields:
13469
+ -- not deletable and not "dead code". Only custom / managed-package
13470
+ -- fields (which end in __c) are valid CustomField dead-code candidates
13471
+ -- (NI-6: stops IsPartner/IsCustomerPortal/etc. being flagged dead).
13472
+ AND NOT (type = 'CustomField' AND api_name NOT LIKE '%\\_\\_c' ESCAPE '\\')
13290
13473
  ),
13291
13474
  incoming AS (
13292
13475
  SELECT e.to_id AS cid,
@@ -16144,7 +16327,7 @@ var init_generate_architecture_overview = __esm({
16144
16327
 
16145
16328
  // ../mcp/dist/src/tools/pii-inventory.js
16146
16329
  import { z as z60 } from "zod";
16147
- var PII_INVENTORY_MAX_LIMIT, PII_INVENTORY_DEFAULT_LIMIT, SCAN_PAGE_SIZE, CLASSIFICATION_FILTER_VALUES, CATEGORY_FILTER_VALUES, piiInventoryInputSchema, emptyClassificationCounts, emptyCategoryCounts, classificationMatches, categoryMatches, readDataType, readDescription, compareFields, fetchAllCustomFields2, piiInventoryHandler;
16330
+ var PII_INVENTORY_MAX_LIMIT, PII_INVENTORY_DEFAULT_LIMIT, SCAN_PAGE_SIZE, CLASSIFICATION_FILTER_VALUES, CATEGORY_FILTER_VALUES, piiInventoryInputSchema, PII_PAYLOAD_BUDGET_BYTES, fitFieldsToBudget, emptyClassificationCounts, emptyCategoryCounts, classificationMatches, categoryMatches, readDataType, readDescription, compareFields, fetchAllCustomFields2, piiInventoryHandler;
16148
16331
  var init_pii_inventory = __esm({
16149
16332
  "../mcp/dist/src/tools/pii-inventory.js"() {
16150
16333
  "use strict";
@@ -16165,8 +16348,23 @@ var init_pii_inventory = __esm({
16165
16348
  piiInventoryInputSchema = z60.object({
16166
16349
  classification: z60.enum(CLASSIFICATION_FILTER_VALUES).optional(),
16167
16350
  category: z60.enum(CATEGORY_FILTER_VALUES).optional(),
16168
- limit: z60.number().int().min(1).max(PII_INVENTORY_MAX_LIMIT).optional()
16351
+ limit: z60.number().int().min(1).max(PII_INVENTORY_MAX_LIMIT).optional(),
16352
+ offset: z60.number().int().min(0).optional()
16169
16353
  });
16354
+ PII_PAYLOAD_BUDGET_BYTES = 38e3;
16355
+ fitFieldsToBudget = (fields, budgetBytes) => {
16356
+ const kept = [];
16357
+ let used = 0;
16358
+ for (const field of fields) {
16359
+ const size = Buffer.byteLength(JSON.stringify(field), "utf8") + 1;
16360
+ if (kept.length > 0 && used + size > budgetBytes) {
16361
+ return { kept, trimmed: true };
16362
+ }
16363
+ kept.push(field);
16364
+ used += size;
16365
+ }
16366
+ return { kept, trimmed: false };
16367
+ };
16170
16368
  emptyClassificationCounts = () => ({
16171
16369
  pii: 0,
16172
16370
  sensitive: 0,
@@ -16253,17 +16451,26 @@ var init_pii_inventory = __esm({
16253
16451
  });
16254
16452
  }
16255
16453
  const sorted = [...matched].sort(compareFields);
16256
- const truncated = sorted.length > limit;
16257
- const fields = sorted.slice(0, limit);
16454
+ const offset = input2.offset ?? 0;
16455
+ const page = sorted.slice(offset, offset + limit);
16456
+ const { kept, trimmed } = fitFieldsToBudget(page, PII_PAYLOAD_BUDGET_BYTES);
16457
+ const returnedEnd = offset + kept.length;
16458
+ const truncated = returnedEnd < sorted.length;
16258
16459
  return ok({
16259
16460
  data: {
16260
- fields,
16461
+ fields: kept,
16261
16462
  summary: {
16262
16463
  total: matched.length,
16263
16464
  byClassification,
16264
16465
  byCategory
16265
16466
  },
16266
- truncated
16467
+ limit,
16468
+ offset,
16469
+ truncated,
16470
+ ...truncated ? { nextOffset: returnedEnd } : {},
16471
+ ...trimmed ? {
16472
+ note: `Response trimmed to ${kept.length} of ${page.length} matched fields to stay under the ~45 KB MCP response limit. Advance with offset += ${kept.length} for the rest.`
16473
+ } : {}
16267
16474
  },
16268
16475
  vaultState: {
16269
16476
  sourceTreeHash: ctx.manifest.sourceTreeHash,
@@ -16977,6 +17184,7 @@ var init_get_component = __esm({
16977
17184
  init_dist();
16978
17185
  init_src();
16979
17186
  init_src2();
17187
+ init_phantom_node();
16980
17188
  DEFAULT_COMPONENT_BODY_MAX_BYTES = 3e4;
16981
17189
  getComponentInputSchema = z64.object({
16982
17190
  id: z64.string().min(1),
@@ -16991,9 +17199,10 @@ var init_get_component = __esm({
16991
17199
  });
16992
17200
  }
16993
17201
  if (nodeResult.value === null) {
17202
+ const kindLabel = input2.id.includes(":") ? input2.id.slice(0, input2.id.indexOf(":")) : "component";
16994
17203
  return err({
16995
17204
  kind: "component-not-found",
16996
- message: `no node with id ${input2.id}`,
17205
+ message: await phantomAwareNotFoundMessage(ctx, input2.id, kindLabel),
16997
17206
  path: input2.id
16998
17207
  });
16999
17208
  }
@@ -17448,11 +17657,201 @@ var init_get_subgraph = __esm({
17448
17657
  }
17449
17658
  });
17450
17659
 
17660
+ // ../mcp/dist/src/knowledge-topics.js
17661
+ var TRAILHEAD, HELP, KNOWLEDGE_TOPICS, resolveTopicKey;
17662
+ var init_knowledge_topics = __esm({
17663
+ "../mcp/dist/src/knowledge-topics.js"() {
17664
+ "use strict";
17665
+ TRAILHEAD = { label: "Trailhead", url: "https://trailhead.salesforce.com" };
17666
+ HELP = { label: "Salesforce Help", url: "https://help.salesforce.com" };
17667
+ KNOWLEDGE_TOPICS = Object.freeze({
17668
+ "flow-vs-apex": {
17669
+ title: "Flow vs Apex \u2014 when to use which",
17670
+ summary: "Prefer declarative record-triggered Flow for most automation; reach for an Apex trigger only when you need logic Flow cannot do well: complex bulk processing, callouts with fine control, recursion management, custom error handling, or operations across many records/objects in one transaction. Keep one record-triggered Flow (or one trigger) per object/timing and delegate to a handler. Avoid mixing Workflow Rules / Process Builder with Flow on the same object.",
17671
+ docs: [
17672
+ { label: "Record-Triggered Automation (Architects)", url: "https://architect.salesforce.com/decision-guides/trigger-automation" },
17673
+ TRAILHEAD
17674
+ ]
17675
+ },
17676
+ "order-of-execution": {
17677
+ title: "Apex order of execution on save",
17678
+ summary: `On save Salesforce runs, in order: system validation, before-save record-triggered flows, before triggers, system + custom validation rules, duplicate rules, after triggers, assignment/auto-response/workflow rules, processes & after-save flows, escalation rules, roll-up summary recalculation, sharing recalculation, then commit and post-commit (async, email, @future). Knowing the order explains why a field looks stale or an automation "didn't fire".`,
17679
+ docs: [
17680
+ { label: "Apex Developer Guide \u2014 Triggers and Order of Execution", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_order_of_execution.htm" }
17681
+ ]
17682
+ },
17683
+ "governor-limits": {
17684
+ title: "Apex governor limits to design around",
17685
+ summary: "Per-transaction limits force bulk-safe design: 100 SOQL queries (200 async), 150 DML statements, 50,000 rows retrieved, 10s sync / 60s async CPU time, 6 MB sync / 12 MB async heap, 100 callouts, 10 emails. Never put SOQL/DML inside loops; bulkify; query selectively; move heavy work to Batch/Queueable.",
17686
+ docs: [
17687
+ { label: "Apex Developer Guide \u2014 Execution Governors and Limits", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm" }
17688
+ ]
17689
+ },
17690
+ "async-apex": {
17691
+ title: "Asynchronous Apex options",
17692
+ summary: "Future (@future): simplest fire-and-forget, primitives only, for callouts/decoupling \u2014 no chaining, no monitoring. Queueable: like future but accepts objects, can chain, returns a job id. Batch (Database.Batchable): process millions of records in chunks (start/execute/finish), higher limits. Schedulable: run Apex on a cron schedule (often to enqueue a Batch). Choose by data volume, need to chain/monitor, and scheduling.",
17693
+ docs: [
17694
+ { label: "Apex Developer Guide \u2014 Asynchronous Apex", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_async_overview.htm" },
17695
+ TRAILHEAD
17696
+ ]
17697
+ },
17698
+ "trigger-framework": {
17699
+ title: "Apex trigger framework pattern",
17700
+ summary: "Adopt one-trigger-per-object that immediately delegates to a handler class; keep zero business logic in the trigger body. A framework gives you context routing (before/after, insert/update/...), recursion control (static guards), and bulk-safe handler methods. Popular patterns: a simple hand-rolled handler base class, or community frameworks. Consistency matters more than which framework.",
17701
+ docs: [
17702
+ { label: "Apex Developer Guide \u2014 Triggers", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers.htm" },
17703
+ TRAILHEAD
17704
+ ]
17705
+ },
17706
+ "bulkification": {
17707
+ title: "Bulkified Apex best practices",
17708
+ summary: "Assume every trigger/method processes up to 200 records. Query once with maps keyed by id, never inside loops; collect DML into lists and do one insert/update outside loops; use collections and Maps for lookups; pass record collections, not single records. Test with \u2265200-record bulk data, not one record.",
17709
+ docs: [
17710
+ { label: "Apex Developer Guide \u2014 Bulk Trigger Idioms", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_bulk_idioms.htm" }
17711
+ ]
17712
+ },
17713
+ "apex-testing": {
17714
+ title: "Apex testing, coverage, and test data",
17715
+ summary: "Production deploys require \u226575% org-wide Apex coverage and all tests passing (each trigger must have some coverage). Coverage is a floor, not the goal: assert real behavior with System.assertEquals on distinct expected/actual values. Build test data in @isTest test-data-factory classes; use Test.startTest/stopTest for limits and async; avoid SeeAllData=true. Structure: one test class per class, descriptive methods, positive + negative + bulk cases.",
17716
+ docs: [
17717
+ { label: "Apex Developer Guide \u2014 Testing Apex", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing.htm" }
17718
+ ]
17719
+ },
17720
+ "apex-callouts": {
17721
+ title: "Making external callouts from Apex",
17722
+ summary: "Use Named Credentials for endpoints + auth (never hardcode URLs/secrets); call via HttpRequest/HttpResponse or external services. Callouts cannot follow uncommitted DML in the same transaction \u2014 do them before DML or from async (@future(callout=true)/Queueable). Set timeouts, handle non-200s, and write tests with HttpCalloutMock.",
17723
+ docs: [
17724
+ { label: "Apex Developer Guide \u2014 Invoking Callouts Using Apex", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts.htm" }
17725
+ ]
17726
+ },
17727
+ "sfdx-source-driven-dev": {
17728
+ title: "Source-driven development with SFDX",
17729
+ summary: "Treat metadata as source in version control. Use a Salesforce DX project (sfdx-project.json), scratch orgs for feature development, and the `sf` CLI to retrieve/deploy/convert source. Pair with a CI/CD pipeline (validate + run tests on PR, deploy on merge) and unlocked packages for modular delivery.",
17730
+ docs: [
17731
+ { label: "Salesforce DX Developer Guide", url: "https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_intro.htm" },
17732
+ TRAILHEAD
17733
+ ]
17734
+ },
17735
+ "package-strategy": {
17736
+ title: "Unlocked packages & modular structure",
17737
+ summary: "Decompose the org into unlocked packages aligned to functional domains, each with a clear dependency direction (no cycles). Packages give versioning, clean install/upgrade, and ownership boundaries. Start with a small number of coarse packages; split as ownership and dependencies clarify. Keep a base/common package for shared components.",
17738
+ docs: [
17739
+ { label: "SFDX \u2014 Unlocked Packages", url: "https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_unlocked_pkg_intro.htm" },
17740
+ TRAILHEAD
17741
+ ]
17742
+ },
17743
+ "profiles-vs-permission-sets": {
17744
+ title: "Profiles vs Permission Sets",
17745
+ summary: 'Modern best practice: keep profiles minimal (login hours, default record types, page layouts as needed) and grant capabilities via Permission Sets and Permission Set Groups. This avoids "profile proliferation", makes access additive and auditable, and lets you assign least-privilege bundles per role. Salesforce is moving permissions toward permission sets.',
17746
+ docs: [HELP, TRAILHEAD]
17747
+ },
17748
+ "owd-sharing-model": {
17749
+ title: "OWD & the sharing model (greenfield)",
17750
+ summary: "Set Org-Wide Defaults to the most restrictive level each object needs (Private / Public Read Only / Public Read-Write), then OPEN UP access with the role hierarchy, sharing rules (ownership- or criteria-based), permission sets (View All/Modify All), teams, and manual/Apex sharing. Design least-privilege: start Private and grant deliberately. Plan sharing recalculation cost for large data volumes.",
17751
+ docs: [HELP, TRAILHEAD]
17752
+ },
17753
+ "standard-vs-custom-objects": {
17754
+ title: "Standard vs custom objects",
17755
+ summary: "Use standard objects (Account, Contact, Opportunity, Case, Lead, ...) whenever they fit \u2014 they come with built-in features, reports, and integrations. Create custom objects only for concepts standard objects do not represent. Prefer extending a standard object with custom fields over cloning it. Consider the data model, relationships, and license implications before adding custom objects.",
17756
+ docs: [HELP, TRAILHEAD]
17757
+ },
17758
+ "naming-conventions": {
17759
+ title: "Naming & documentation conventions",
17760
+ summary: "Establish conventions from day one: consistent API names (PascalCase objects, clear field suffixes), prefixes for app/domain, description + help-text on every field, and a documented automation/trigger naming scheme. Conventions make the org self-describing and reduce technical debt. Record them in a living standards doc.",
17761
+ docs: [TRAILHEAD, HELP]
17762
+ },
17763
+ "sandbox-environment-strategy": {
17764
+ title: "Sandbox & environment strategy",
17765
+ summary: "Use a tiered environment path: developer/scratch orgs for build, an integration/partial sandbox for merged work, a full or partial sandbox for UAT/staging, then production. Match sandbox type to need (Developer/Developer Pro for config/code, Partial/Full for data-dependent testing). Automate refresh + seeding and gate promotion with CI tests.",
17766
+ docs: [HELP, TRAILHEAD]
17767
+ },
17768
+ "single-vs-multi-org": {
17769
+ title: "Single-org vs multi-org strategy",
17770
+ summary: "Prefer a single org when business units share processes, data, and customers \u2014 it maximizes the 360\xB0 view and minimizes integration/duplication cost. Choose multi-org when regulatory isolation, radically different processes, divestiture risk, or per-region governance demand hard boundaries. Weigh org limits, governance maturity, and the cost of cross-org integration before splitting.",
17771
+ docs: [{ label: "Architect decision guides", url: "https://architect.salesforce.com/decision-guides" }, TRAILHEAD]
17772
+ },
17773
+ "data-retention-archiving": {
17774
+ title: "Data retention & archiving strategy",
17775
+ summary: "Plan retention up front: classify data by regulatory/operational need, define how long each object is kept hot, and archive or purge the rest. Options include Big Objects for low-cost archival, scheduled batch purges, external/offline archives, and field-history/event-log retention settings. Designing this early prevents storage-limit and LDV pain later.",
17776
+ docs: [HELP, TRAILHEAD]
17777
+ },
17778
+ "large-data-volumes": {
17779
+ title: "Large data volumes (LDV) planning",
17780
+ summary: "At millions of records, design for selectivity: indexed/selective filters, skinny tables (via Support) for hot reports, careful sharing (ownership/data skew avoidance), Big Objects for archival, and bulk/async data loads. Watch for non-selective SOQL, account/ownership skew, and sharing-recalculation cost. Plan partitioning and reporting strategy before volume arrives.",
17781
+ docs: [{ label: "LDV Best Practices (Architects)", url: "https://architect.salesforce.com/well-architected/adaptable/resilient" }, TRAILHEAD]
17782
+ },
17783
+ "release-management": {
17784
+ title: "Release management & deployment pipeline",
17785
+ summary: "Establish a source-of-truth VCS, environment promotion path (dev \u2192 integration \u2192 UAT \u2192 prod), and a CI/CD pipeline that validates + runs Apex tests on every change and deploys on merge. Use unlocked packages or change sets/metadata API, automate with the `sf` CLI or a DevOps tool, and keep deployments small and frequent. Gate prod with required coverage + review.",
17786
+ docs: [TRAILHEAD, { label: "Salesforce DX Developer Guide", url: "https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_intro.htm" }]
17787
+ },
17788
+ "well-architected": {
17789
+ title: "Salesforce Well-Architected principles",
17790
+ summary: "Salesforce Well-Architected frames a healthy org as Trusted (secure, compliant, reliable), Easy (intentional, automated, resilient \u2014 minimal complexity/debt), and Adaptable (composable, scalable). Use it as a checklist when designing: least-privilege security, consolidated automation, documented conventions, modular packaging, and scalable data design.",
17791
+ docs: [{ label: "Salesforce Well-Architected", url: "https://architect.salesforce.com/well-architected/overview" }, TRAILHEAD]
17792
+ },
17793
+ "license-types": {
17794
+ title: "License types & planning",
17795
+ summary: "Match license types to user needs: full Salesforce/CRM licenses for internal power users, Platform licenses for users who only need custom apps + a few standard objects, and Experience Cloud licenses for external community users. Permission Set Licenses add capabilities on top. Count licenses by persona early \u2014 they drive cost and constrain object/feature access.",
17796
+ docs: [HELP, TRAILHEAD]
17797
+ },
17798
+ "integration-patterns": {
17799
+ title: "Integration patterns",
17800
+ summary: "Pick the pattern by need: Request-Reply (synchronous UI callouts), Fire-and-Forget (Platform Events / outbound), Batch Data Sync (scheduled bulk), Remote Call-In (inbound REST/SOAP API), and UI Update from external (streaming/CDC). Prefer middleware/event-driven over point-to-point at scale; use Named Credentials for auth and design idempotent retries + error handling.",
17801
+ docs: [{ label: "Integration Patterns (Architects)", url: "https://architect.salesforce.com/decision-guides/integrate-salesforce" }, TRAILHEAD]
17802
+ }
17803
+ });
17804
+ resolveTopicKey = (topic) => {
17805
+ const t = topic.trim().toLowerCase();
17806
+ if (t.length === 0)
17807
+ return null;
17808
+ const keys = Object.keys(KNOWLEDGE_TOPICS);
17809
+ if (keys.includes(t))
17810
+ return t;
17811
+ const hit = keys.find((k) => k.includes(t) || t.includes(k) || KNOWLEDGE_TOPICS[k].title.toLowerCase().includes(t));
17812
+ return hit ?? null;
17813
+ };
17814
+ }
17815
+ });
17816
+
17817
+ // ../mcp/dist/src/tools/guidance.js
17818
+ import { z as z68 } from "zod";
17819
+ var guidanceInputSchema, DISCLOSURE6, guidanceHandler;
17820
+ var init_guidance = __esm({
17821
+ "../mcp/dist/src/tools/guidance.js"() {
17822
+ "use strict";
17823
+ init_dist();
17824
+ init_knowledge_topics();
17825
+ guidanceInputSchema = z68.object({
17826
+ topic: z68.string().min(1).optional()
17827
+ });
17828
+ DISCLOSURE6 = "General Salesforce best-practice guidance with links to official docs \u2014 NOT specific to this org or its metadata. For org-specific answers, use the vault tools (schema, automation, permissions, impact) or the opt-in live plane.";
17829
+ guidanceHandler = async (ctx, input2) => {
17830
+ const availableTopics = Object.entries(KNOWLEDGE_TOPICS).map(([key2, t]) => ({ key: key2, title: t.title }));
17831
+ const key = input2.topic !== void 0 ? resolveTopicKey(input2.topic) : null;
17832
+ const guidance = key !== null ? KNOWLEDGE_TOPICS[key] ?? null : null;
17833
+ return ok({
17834
+ data: {
17835
+ topic: key,
17836
+ guidance,
17837
+ matched: guidance !== null,
17838
+ availableTopics,
17839
+ disclosure: DISCLOSURE6
17840
+ },
17841
+ vaultState: {
17842
+ sourceTreeHash: ctx.manifest.sourceTreeHash,
17843
+ refreshedAt: ctx.manifest.refreshedAt
17844
+ }
17845
+ });
17846
+ };
17847
+ }
17848
+ });
17849
+
17451
17850
  // ../mcp/dist/src/tools/health-check.js
17452
17851
  import { existsSync as existsSync3 } from "node:fs";
17453
17852
  import { stat as stat4 } from "node:fs/promises";
17454
17853
  import { join as join7 } from "node:path";
17455
- import { z as z68 } from "zod";
17854
+ import { z as z69 } from "zod";
17456
17855
  var SKIPPED_FILES_DEGRADED_THRESHOLD, healthCheckInputSchema, probeGraph, probeSourceHash, probeRenderComplete, healthCheckHandler;
17457
17856
  var init_health_check = __esm({
17458
17857
  "../mcp/dist/src/tools/health-check.js"() {
@@ -17461,7 +17860,7 @@ var init_health_check = __esm({
17461
17860
  init_src();
17462
17861
  init_src2();
17463
17862
  SKIPPED_FILES_DEGRADED_THRESHOLD = 100;
17464
- healthCheckInputSchema = z68.object({});
17863
+ healthCheckInputSchema = z69.object({});
17465
17864
  probeGraph = async (ctx) => {
17466
17865
  try {
17467
17866
  const result = await listNodesByType(ctx.graph, "CustomObject", { limit: 1 });
@@ -17581,7 +17980,7 @@ var init_health_check = __esm({
17581
17980
  // ../mcp/dist/src/tools/integration-procedure-chain.js
17582
17981
  import { readFile as readFile10 } from "node:fs/promises";
17583
17982
  import { XMLParser as XMLParser3, XMLValidator as XMLValidator3 } from "fast-xml-parser";
17584
- import { z as z69 } from "zod";
17983
+ import { z as z70 } from "zod";
17585
17984
  var IP_PREFIX, DATA_TRANSFORM_PREFIX, ROOT_ELEMENT3, DATARAPTOR_ACTION_TYPES, REST_ACTION_TYPE, NESTED_IP_ACTION_TYPE, REMOTE_ACTION_TYPE, RESPONSE_ACTION_TYPE, NATIVE_VS_VLOCITY_DISCLOSURE2, APEX_COUPLING_DEFERRAL_DISCLOSURE, RECORD_LEVEL_BOUNDARY_DISCLOSURE, REST_REACHABILITY_DISCLOSURE, integrationProcedureChainInputSchema, unwrapSingle3, toArray3, coerceBoolean2, coerceNumber, nonEmptyString, parsePropertySetConfig, BOUNDARIES_VERBATIM, integrationProcedureChainHandler, walkActions, resolveExternalEndpoints, readNodeStringProperty, readNodeNumberProperty, readNodeBooleanProperty;
17586
17985
  var init_integration_procedure_chain = __esm({
17587
17986
  "../mcp/dist/src/tools/integration-procedure-chain.js"() {
@@ -17605,9 +18004,9 @@ var init_integration_procedure_chain = __esm({
17605
18004
  APEX_COUPLING_DEFERRAL_DISCLOSURE = "v3.2 captures OmniStudio components and intra-OmniStudio call chains (`dispatchesOmniAction`). The Apex-to-OmniProcess coupling (`implements omnistudio.VlocityOpenInterface` etc.) is a v3.3 follow-up \u2014 those edges are NOT yet in the graph.";
17606
18005
  RECORD_LEVEL_BOUNDARY_DISCLOSURE = "v3.2 walks the OmniScript / IP / Card metadata XML. The actual user-entered data and runtime state lives in OmniProcessElement and related SObject records; that is record-level data, out of scope for v0.1's read-the-metadata posture.";
17607
18006
  REST_REACHABILITY_DISCLOSURE = "REST endpoint URLs are surfaced with `parsed` confidence (from the propertySetConfig JSON blob); v3.2 does NOT probe the URL, verify the endpoint is reachable, or resolve the Named Credential against live state.";
17608
- integrationProcedureChainInputSchema = z69.object({
17609
- integrationProcedureId: z69.string().min(1),
17610
- includeChildPropertySetConfig: z69.boolean().optional()
18007
+ integrationProcedureChainInputSchema = z70.object({
18008
+ integrationProcedureId: z70.string().min(1),
18009
+ includeChildPropertySetConfig: z70.boolean().optional()
17611
18010
  });
17612
18011
  unwrapSingle3 = (value) => Array.isArray(value) ? value[0] : value;
17613
18012
  toArray3 = (value) => {
@@ -17909,7 +18308,7 @@ var init_integration_procedure_chain = __esm({
17909
18308
  });
17910
18309
 
17911
18310
  // ../mcp/dist/src/tools/last-modified.js
17912
- import { z as z70 } from "zod";
18311
+ import { z as z71 } from "zod";
17913
18312
  var LAST_MODIFIED_UNENRICHED_DISCLOSURE, LAST_MODIFIED_ENRICHED_DISCLOSURE, lastModifiedInputSchema, extractLastModifiedDate2, extractLastModifiedBy2, extractApiVersion, lastModifiedHandler;
17914
18313
  var init_last_modified = __esm({
17915
18314
  "../mcp/dist/src/tools/last-modified.js"() {
@@ -17918,8 +18317,8 @@ var init_last_modified = __esm({
17918
18317
  init_src();
17919
18318
  LAST_MODIFIED_UNENRICHED_DISCLOSURE = "v1.7 Tooling API enrichment has not run for this vault. Run `sfi refresh --with-tooling-api --target-org <alias>` to populate lastModifiedDate / lastModifiedBy / apiVersion for the enriched types.";
17920
18319
  LAST_MODIFIED_ENRICHED_DISCLOSURE = "Freshness fields populated. lastModifiedDate / lastModifiedBy reflect the Tooling API at enrichment time (or the DX-source extractor when the type carries a `<lastModifiedDate>` element).";
17921
- lastModifiedInputSchema = z70.object({
17922
- componentId: z70.string().min(1)
18320
+ lastModifiedInputSchema = z71.object({
18321
+ componentId: z71.string().min(1)
17923
18322
  });
17924
18323
  extractLastModifiedDate2 = (legacy, properties) => {
17925
18324
  const propsValue = properties["lastModifiedDate"];
@@ -17995,17 +18394,17 @@ var init_last_modified = __esm({
17995
18394
  });
17996
18395
 
17997
18396
  // ../mcp/dist/src/tools/layout-for-user.js
17998
- import { z as z71 } from "zod";
18397
+ import { z as z72 } from "zod";
17999
18398
  var layoutForUserInputSchema, step, evaluateProfileLookup, readLayoutAssignments, layoutTargetsObject, canonicaliseLayoutId, findLayoutAssignment, pickFlexiPageForObject, evaluateLightningPageLookup, layoutForUserHandler;
18000
18399
  var init_layout_for_user = __esm({
18001
18400
  "../mcp/dist/src/tools/layout-for-user.js"() {
18002
18401
  "use strict";
18003
18402
  init_dist();
18004
18403
  init_src();
18005
- layoutForUserInputSchema = z71.object({
18006
- objectApiName: z71.string().min(1),
18007
- recordTypeId: z71.string().min(1).optional(),
18008
- profileId: z71.string().min(1)
18404
+ layoutForUserInputSchema = z72.object({
18405
+ objectApiName: z72.string().min(1),
18406
+ recordTypeId: z72.string().min(1).optional(),
18407
+ profileId: z72.string().min(1)
18009
18408
  });
18010
18409
  step = (stage, verdict, reason, value) => value === void 0 ? { stage, verdict, reason } : { stage, verdict, reason, value };
18011
18410
  evaluateProfileLookup = async (ctx, profileId) => {
@@ -18223,7 +18622,7 @@ var init_layout_for_user = __esm({
18223
18622
  });
18224
18623
 
18225
18624
  // ../mcp/dist/src/tools/list-components.js
18226
- import { z as z72 } from "zod";
18625
+ import { z as z73 } from "zod";
18227
18626
  var COMPONENT_TYPES2, LIST_MAX_LIMIT2, LIST_DEFAULT_LIMIT2, LIST_PAYLOAD_BUDGET_BYTES, fitNodesToBudget, listComponentsInputSchema, listComponentsHandler;
18228
18627
  var init_list_components = __esm({
18229
18628
  "../mcp/dist/src/tools/list-components.js"() {
@@ -18332,11 +18731,11 @@ var init_list_components = __esm({
18332
18731
  }
18333
18732
  return { kept, trimmed: false };
18334
18733
  };
18335
- listComponentsInputSchema = z72.object({
18336
- type: z72.enum(COMPONENT_TYPES2).optional(),
18337
- parentId: z72.string().min(1).optional(),
18338
- limit: z72.number().int().min(1).max(LIST_MAX_LIMIT2).optional(),
18339
- offset: z72.number().int().min(0).optional()
18734
+ listComponentsInputSchema = z73.object({
18735
+ type: z73.enum(COMPONENT_TYPES2).optional(),
18736
+ parentId: z73.string().min(1).optional(),
18737
+ limit: z73.number().int().min(1).max(LIST_MAX_LIMIT2).optional(),
18738
+ offset: z73.number().int().min(0).optional()
18340
18739
  });
18341
18740
  listComponentsHandler = async (ctx, input2) => {
18342
18741
  if (input2.type === void 0) {
@@ -18963,7 +19362,7 @@ var init_live_consent = __esm({
18963
19362
  // ../mcp/dist/src/tools/live-plane.js
18964
19363
  import { execFile as execFile2 } from "node:child_process";
18965
19364
  import { promisify as promisify2 } from "node:util";
18966
- import { z as z73 } from "zod";
19365
+ import { z as z74 } from "zod";
18967
19366
  var nodeExecFile2, redactSecrets, LIVE_PLANE_DISCLOSURE, MAX_SAMPLE_ROWS, liveEnabledSchema, isLivePlaneEnabled, liveTrust, resolveOrg, resolveLiveAccess, liveConsentRequiredError, gateLive, getLiveAuth, runSfJson, apiPath, restGet, liveDescribeInputSchema, liveDescribeHandler, liveCountInputSchema, OBJECT_API_NAME_RE, resolveCountSoql, assertCountSoql, liveCountHandler, liveSampleInputSchema, capSampleSoql, liveSampleHandler, liveFieldPopulationInputSchema, liveFieldPopulationHandler, liveOrgLimitsInputSchema, liveOrgLimitsHandler, MAX_INACTIVE_USER_ROWS, DEFAULT_INACTIVE_DAYS, MS_PER_DAY, liveInactiveUsersInputSchema, soqlDateTime, liveInactiveUsersHandler, DEFAULT_LICENSE_INACTIVE_DAYS, MAX_RECLAIM_ROWS, LICENSE_USAGE_DISCLOSURE, liveLicenseUsageInputSchema, toUtilization, renderLicenseUsageMarkdown, liveLicenseUsageHandler, liveConsentInputSchema, consentTrust, liveConsentHandler, liveQuery, MAX_DETAIL_ROWS, daysAgoSoql, daysSince, livePlaneVaultState, UNAVAILABLE_ERROR, assertSoqlIdentifier, soqlLiteral, MAX_GROUP_BUCKETS, DEFAULT_STALE_DAYS, DEFAULT_RECENT_DAYS, liveGroupCountInputSchema, liveGroupCountHandler, liveStaleRecordsInputSchema, liveStaleRecordsHandler, liveRecentActivityInputSchema, liveRecentActivityHandler, buildEqualityWhere, aggregateCountFromRow, liveAggregateInputSchema, liveAggregateHandler, MAX_DUPLICATE_GROUPS, liveDuplicateCheckInputSchema, liveDuplicateCheckHandler, MAX_OWNER_BUCKETS, liveOwnerBreakdownInputSchema, liveOwnerBreakdownHandler, DEFAULT_REPORT_STALE_DAYS, liveReportUsageInputSchema, liveReportUsageHandler, liveFolderAccessInputSchema, liveFolderAccessHandler, DEFAULT_TEMPLATE_STALE_DAYS, CLASSIC_TEMPLATE_TYPES, liveEmailTemplateUsageInputSchema, liveEmailTemplateUsageHandler, DEFAULT_HEALTH_DAYS, LIMIT_RISK_THRESHOLD, liveOrgHealthInputSchema, liveOrgHealthHandler, liveStorageByObjectInputSchema, liveStorageByObjectHandler, liveDataSkewInputSchema, liveSetupAuditTrailInputSchema, liveSecurityExposureInputSchema;
18968
19367
  var init_live_plane = __esm({
18969
19368
  "../mcp/dist/src/tools/live-plane.js"() {
@@ -18976,8 +19375,8 @@ var init_live_plane = __esm({
18976
19375
  redactSecrets = (message) => message.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/\b00D[A-Za-z0-9]{12,}![A-Za-z0-9._~+/=-]{20,}\b/g, "[REDACTED_TOKEN]");
18977
19376
  LIVE_PLANE_DISCLOSURE = "Live org data is read-only, queried at call time via the Salesforce CLI. It does not update the vault. Enable with SFI_LIVE_PLANE_ENABLED=1 or pass liveEnabled: true.";
18978
19377
  MAX_SAMPLE_ROWS = 200;
18979
- liveEnabledSchema = z73.object({
18980
- liveEnabled: z73.boolean().optional()
19378
+ liveEnabledSchema = z74.object({
19379
+ liveEnabled: z74.boolean().optional()
18981
19380
  });
18982
19381
  isLivePlaneEnabled = (input2) => {
18983
19382
  if (input2 === true)
@@ -19055,8 +19454,8 @@ var init_live_plane = __esm({
19055
19454
  }
19056
19455
  };
19057
19456
  liveDescribeInputSchema = liveEnabledSchema.extend({
19058
- objectApiName: z73.string().min(1),
19059
- orgAlias: z73.string().min(1).optional()
19457
+ objectApiName: z74.string().min(1),
19458
+ orgAlias: z74.string().min(1).optional()
19060
19459
  });
19061
19460
  liveDescribeHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19062
19461
  const gate = await gateLive(ctx, input2);
@@ -19084,9 +19483,9 @@ var init_live_plane = __esm({
19084
19483
  // Either `soql` (a SELECT COUNT() query) OR `objectApiName` (count every row
19085
19484
  // of that object). Both optional at the schema level; the handler requires
19086
19485
  // exactly one and turns objectApiName into `SELECT COUNT() FROM <object>`.
19087
- soql: z73.string().min(1).optional(),
19088
- objectApiName: z73.string().min(1).optional(),
19089
- orgAlias: z73.string().min(1).optional()
19486
+ soql: z74.string().min(1).optional(),
19487
+ objectApiName: z74.string().min(1).optional(),
19488
+ orgAlias: z74.string().min(1).optional()
19090
19489
  });
19091
19490
  OBJECT_API_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
19092
19491
  resolveCountSoql = (input2) => {
@@ -19150,9 +19549,9 @@ var init_live_plane = __esm({
19150
19549
  });
19151
19550
  };
19152
19551
  liveSampleInputSchema = liveEnabledSchema.extend({
19153
- soql: z73.string().min(1),
19154
- limit: z73.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
19155
- orgAlias: z73.string().min(1).optional()
19552
+ soql: z74.string().min(1),
19553
+ limit: z74.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
19554
+ orgAlias: z74.string().min(1).optional()
19156
19555
  });
19157
19556
  capSampleSoql = (soql, limit) => {
19158
19557
  const trimmed = soql.trim().replace(/;\s*$/, "");
@@ -19189,9 +19588,9 @@ var init_live_plane = __esm({
19189
19588
  });
19190
19589
  };
19191
19590
  liveFieldPopulationInputSchema = liveEnabledSchema.extend({
19192
- objectApiName: z73.string().min(1),
19193
- fieldApiName: z73.string().min(1),
19194
- orgAlias: z73.string().min(1).optional()
19591
+ objectApiName: z74.string().min(1),
19592
+ fieldApiName: z74.string().min(1),
19593
+ orgAlias: z74.string().min(1).optional()
19195
19594
  });
19196
19595
  liveFieldPopulationHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19197
19596
  const gate = await gateLive(ctx, input2);
@@ -19237,7 +19636,7 @@ var init_live_plane = __esm({
19237
19636
  });
19238
19637
  };
19239
19638
  liveOrgLimitsInputSchema = liveEnabledSchema.extend({
19240
- orgAlias: z73.string().min(1).optional()
19639
+ orgAlias: z74.string().min(1).optional()
19241
19640
  });
19242
19641
  liveOrgLimitsHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19243
19642
  const gate = await gateLive(ctx, input2);
@@ -19268,14 +19667,14 @@ var init_live_plane = __esm({
19268
19667
  liveInactiveUsersInputSchema = liveEnabledSchema.extend({
19269
19668
  /** Inactivity threshold in days (default 30). A user is "inactive" if their
19270
19669
  * last login is older than this — or they have never logged in. */
19271
- days: z73.number().int().min(1).max(3650).optional(),
19670
+ days: z74.number().int().min(1).max(3650).optional(),
19272
19671
  /** Include non-Standard user types (integration/system/etc.). Default false
19273
19672
  * → only human (Standard) users, the usual intent of "who hasn't logged in". */
19274
- includeAllUserTypes: z73.boolean().optional(),
19673
+ includeAllUserTypes: z74.boolean().optional(),
19275
19674
  /** Max detail rows returned (default + hard cap 500). The TOTAL count is
19276
19675
  * always reported separately, so a capped list never understates the count. */
19277
- limit: z73.number().int().min(1).max(MAX_INACTIVE_USER_ROWS).optional(),
19278
- orgAlias: z73.string().min(1).optional()
19676
+ limit: z74.number().int().min(1).max(MAX_INACTIVE_USER_ROWS).optional(),
19677
+ orgAlias: z74.string().min(1).optional()
19279
19678
  });
19280
19679
  soqlDateTime = (d) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
19281
19680
  liveInactiveUsersHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
@@ -19341,10 +19740,10 @@ var init_live_plane = __esm({
19341
19740
  LICENSE_USAGE_DISCLOSURE = 'License counts are live UserLicense / PermissionSetLicense state. "Reclaimable seats" is a PROXY \u2014 it groups active users who have not logged in within the window by their license; it does NOT measure actual feature usage, and some dormant seats are held intentionally (seasonal staff, service/integration accounts mis-typed as Standard, compliance holds). Per-feature-license usage (Marketing User, Knowledge User, etc.) is NOT covered. This tool is READ-ONLY: it never deprovisions or reassigns a license \u2014 verify each seat before reclaiming it.';
19342
19741
  liveLicenseUsageInputSchema = liveEnabledSchema.extend({
19343
19742
  /** Dormancy window for reclaimable seats, in days (default 90). */
19344
- inactiveDays: z73.number().int().min(1).max(3650).optional(),
19743
+ inactiveDays: z74.number().int().min(1).max(3650).optional(),
19345
19744
  /** Max reclaimable-seat groups returned (default + hard cap 200). */
19346
- limit: z73.number().int().min(1).max(MAX_RECLAIM_ROWS).optional(),
19347
- orgAlias: z73.string().min(1).optional()
19745
+ limit: z74.number().int().min(1).max(MAX_RECLAIM_ROWS).optional(),
19746
+ orgAlias: z74.string().min(1).optional()
19348
19747
  });
19349
19748
  toUtilization = (rows, nameKey) => rows.map((r) => {
19350
19749
  const total = Number(r.TotalLicenses ?? 0);
@@ -19432,13 +19831,13 @@ var init_live_plane = __esm({
19432
19831
  }
19433
19832
  });
19434
19833
  };
19435
- liveConsentInputSchema = z73.object({
19834
+ liveConsentInputSchema = z74.object({
19436
19835
  /** Org alias/username; defaults to the vault's source org. */
19437
- orgAlias: z73.string().min(1).optional(),
19836
+ orgAlias: z74.string().min(1).optional(),
19438
19837
  /** Grant standing consent for the org (persists across sessions). */
19439
- grant: z73.boolean().optional(),
19838
+ grant: z74.boolean().optional(),
19440
19839
  /** Revoke standing consent for the org. */
19441
- revoke: z73.boolean().optional()
19840
+ revoke: z74.boolean().optional()
19442
19841
  });
19443
19842
  consentTrust = () => ({
19444
19843
  provenance: "offline_snapshot",
@@ -19525,12 +19924,12 @@ var init_live_plane = __esm({
19525
19924
  DEFAULT_STALE_DAYS = 90;
19526
19925
  DEFAULT_RECENT_DAYS = 7;
19527
19926
  liveGroupCountInputSchema = liveEnabledSchema.extend({
19528
- objectApiName: z73.string().min(1),
19529
- groupByField: z73.string().min(1),
19530
- limit: z73.number().int().min(1).max(MAX_GROUP_BUCKETS).optional(),
19531
- filterField: z73.string().min(1).optional(),
19532
- filterValue: z73.union([z73.string(), z73.number(), z73.boolean()]).optional(),
19533
- orgAlias: z73.string().min(1).optional()
19927
+ objectApiName: z74.string().min(1),
19928
+ groupByField: z74.string().min(1),
19929
+ limit: z74.number().int().min(1).max(MAX_GROUP_BUCKETS).optional(),
19930
+ filterField: z74.string().min(1).optional(),
19931
+ filterValue: z74.union([z74.string(), z74.number(), z74.boolean()]).optional(),
19932
+ orgAlias: z74.string().min(1).optional()
19534
19933
  });
19535
19934
  liveGroupCountHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19536
19935
  const gate = await gateLive(ctx, input2);
@@ -19598,12 +19997,12 @@ ${renderTrustFooter(trust)}`;
19598
19997
  });
19599
19998
  };
19600
19999
  liveStaleRecordsInputSchema = liveEnabledSchema.extend({
19601
- objectApiName: z73.string().min(1),
19602
- staleDays: z73.number().int().min(1).max(3650).optional(),
19603
- dateField: z73.string().min(1).optional(),
19604
- includeNeverSet: z73.boolean().optional(),
19605
- limit: z73.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
19606
- orgAlias: z73.string().min(1).optional()
20000
+ objectApiName: z74.string().min(1),
20001
+ staleDays: z74.number().int().min(1).max(3650).optional(),
20002
+ dateField: z74.string().min(1).optional(),
20003
+ includeNeverSet: z74.boolean().optional(),
20004
+ limit: z74.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
20005
+ orgAlias: z74.string().min(1).optional()
19607
20006
  });
19608
20007
  liveStaleRecordsHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19609
20008
  const gate = await gateLive(ctx, input2);
@@ -19665,11 +20064,11 @@ ${renderTrustFooter(trust)}`;
19665
20064
  });
19666
20065
  };
19667
20066
  liveRecentActivityInputSchema = liveEnabledSchema.extend({
19668
- objectApiName: z73.string().min(1),
19669
- days: z73.number().int().min(1).max(365).optional(),
19670
- activity: z73.enum(["created", "modified", "both"]).optional(),
19671
- limit: z73.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
19672
- orgAlias: z73.string().min(1).optional()
20067
+ objectApiName: z74.string().min(1),
20068
+ days: z74.number().int().min(1).max(365).optional(),
20069
+ activity: z74.enum(["created", "modified", "both"]).optional(),
20070
+ limit: z74.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
20071
+ orgAlias: z74.string().min(1).optional()
19673
20072
  });
19674
20073
  liveRecentActivityHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19675
20074
  const gate = await gateLive(ctx, input2);
@@ -19747,11 +20146,11 @@ ${renderTrustFooter(trust)}`;
19747
20146
  return 0;
19748
20147
  };
19749
20148
  liveAggregateInputSchema = liveEnabledSchema.extend({
19750
- objectApiName: z73.string().min(1),
19751
- fieldApiName: z73.string().min(1),
19752
- filterField: z73.string().min(1).optional(),
19753
- filterValue: z73.union([z73.string(), z73.number(), z73.boolean()]).optional(),
19754
- orgAlias: z73.string().min(1).optional()
20149
+ objectApiName: z74.string().min(1),
20150
+ fieldApiName: z74.string().min(1),
20151
+ filterField: z74.string().min(1).optional(),
20152
+ filterValue: z74.union([z74.string(), z74.number(), z74.boolean()]).optional(),
20153
+ orgAlias: z74.string().min(1).optional()
19755
20154
  });
19756
20155
  liveAggregateHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19757
20156
  const gate = await gateLive(ctx, input2);
@@ -19811,12 +20210,12 @@ ${renderTrustFooter(trust)}`;
19811
20210
  };
19812
20211
  MAX_DUPLICATE_GROUPS = 100;
19813
20212
  liveDuplicateCheckInputSchema = liveEnabledSchema.extend({
19814
- objectApiName: z73.string().min(1),
19815
- fieldApiName: z73.string().min(1),
19816
- limit: z73.number().int().min(1).max(MAX_DUPLICATE_GROUPS).optional(),
19817
- filterField: z73.string().min(1).optional(),
19818
- filterValue: z73.union([z73.string(), z73.number(), z73.boolean()]).optional(),
19819
- orgAlias: z73.string().min(1).optional()
20213
+ objectApiName: z74.string().min(1),
20214
+ fieldApiName: z74.string().min(1),
20215
+ limit: z74.number().int().min(1).max(MAX_DUPLICATE_GROUPS).optional(),
20216
+ filterField: z74.string().min(1).optional(),
20217
+ filterValue: z74.union([z74.string(), z74.number(), z74.boolean()]).optional(),
20218
+ orgAlias: z74.string().min(1).optional()
19820
20219
  });
19821
20220
  liveDuplicateCheckHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19822
20221
  const gate = await gateLive(ctx, input2);
@@ -19875,11 +20274,11 @@ ${renderTrustFooter(trust)}`;
19875
20274
  };
19876
20275
  MAX_OWNER_BUCKETS = 100;
19877
20276
  liveOwnerBreakdownInputSchema = liveEnabledSchema.extend({
19878
- objectApiName: z73.string().min(1),
19879
- limit: z73.number().int().min(1).max(MAX_OWNER_BUCKETS).optional(),
19880
- filterField: z73.string().min(1).optional(),
19881
- filterValue: z73.union([z73.string(), z73.number(), z73.boolean()]).optional(),
19882
- orgAlias: z73.string().min(1).optional()
20277
+ objectApiName: z74.string().min(1),
20278
+ limit: z74.number().int().min(1).max(MAX_OWNER_BUCKETS).optional(),
20279
+ filterField: z74.string().min(1).optional(),
20280
+ filterValue: z74.union([z74.string(), z74.number(), z74.boolean()]).optional(),
20281
+ orgAlias: z74.string().min(1).optional()
19883
20282
  });
19884
20283
  liveOwnerBreakdownHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19885
20284
  const gate = await gateLive(ctx, input2);
@@ -19956,9 +20355,9 @@ ${renderTrustFooter(trust)}`;
19956
20355
  };
19957
20356
  DEFAULT_REPORT_STALE_DAYS = 90;
19958
20357
  liveReportUsageInputSchema = liveEnabledSchema.extend({
19959
- staleDays: z73.number().int().min(1).max(3650).optional(),
19960
- limit: z73.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
19961
- orgAlias: z73.string().min(1).optional()
20358
+ staleDays: z74.number().int().min(1).max(3650).optional(),
20359
+ limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20360
+ orgAlias: z74.string().min(1).optional()
19962
20361
  });
19963
20362
  liveReportUsageHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
19964
20363
  const gate = await gateLive(ctx, input2);
@@ -20010,9 +20409,9 @@ ${renderTrustFooter(trust)}`;
20010
20409
  });
20011
20410
  };
20012
20411
  liveFolderAccessInputSchema = liveEnabledSchema.extend({
20013
- folderType: z73.enum(["Report", "Dashboard", "Email", "Document", "all"]).optional(),
20014
- limit: z73.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20015
- orgAlias: z73.string().min(1).optional()
20412
+ folderType: z74.enum(["Report", "Dashboard", "Email", "Document", "all"]).optional(),
20413
+ limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20414
+ orgAlias: z74.string().min(1).optional()
20016
20415
  });
20017
20416
  liveFolderAccessHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
20018
20417
  const gate = await gateLive(ctx, input2);
@@ -20067,9 +20466,9 @@ ${renderTrustFooter(trust)}`;
20067
20466
  DEFAULT_TEMPLATE_STALE_DAYS = 180;
20068
20467
  CLASSIC_TEMPLATE_TYPES = /* @__PURE__ */ new Set(["text", "html", "custom", "visualforce"]);
20069
20468
  liveEmailTemplateUsageInputSchema = liveEnabledSchema.extend({
20070
- staleDays: z73.number().int().min(1).max(3650).optional(),
20071
- limit: z73.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20072
- orgAlias: z73.string().min(1).optional()
20469
+ staleDays: z74.number().int().min(1).max(3650).optional(),
20470
+ limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20471
+ orgAlias: z74.string().min(1).optional()
20073
20472
  });
20074
20473
  liveEmailTemplateUsageHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
20075
20474
  const gate = await gateLive(ctx, input2);
@@ -20134,8 +20533,8 @@ ${renderTrustFooter(trust)}`;
20134
20533
  DEFAULT_HEALTH_DAYS = 7;
20135
20534
  LIMIT_RISK_THRESHOLD = 0.8;
20136
20535
  liveOrgHealthInputSchema = liveEnabledSchema.extend({
20137
- days: z73.number().int().min(1).max(90).optional(),
20138
- orgAlias: z73.string().min(1).optional()
20536
+ days: z74.number().int().min(1).max(90).optional(),
20537
+ orgAlias: z74.string().min(1).optional()
20139
20538
  });
20140
20539
  liveOrgHealthHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
20141
20540
  const gate = await gateLive(ctx, input2);
@@ -20202,9 +20601,9 @@ ${renderTrustFooter(trust)}`;
20202
20601
  });
20203
20602
  };
20204
20603
  liveStorageByObjectInputSchema = liveEnabledSchema.extend({
20205
- limit: z73.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20206
- objectApiNames: z73.array(z73.string().min(1)).max(80).optional(),
20207
- orgAlias: z73.string().min(1).optional()
20604
+ limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20605
+ objectApiNames: z74.array(z74.string().min(1)).max(80).optional(),
20606
+ orgAlias: z74.string().min(1).optional()
20208
20607
  });
20209
20608
  liveStorageByObjectHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
20210
20609
  const gate = await gateLive(ctx, input2);
@@ -20247,25 +20646,25 @@ ${renderTrustFooter(trust)}`;
20247
20646
  });
20248
20647
  };
20249
20648
  liveDataSkewInputSchema = liveEnabledSchema.extend({
20250
- objectApiName: z73.string().min(1),
20251
- ownerField: z73.string().min(1).optional(),
20252
- threshold: z73.number().int().min(1).optional(),
20253
- limit: z73.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20254
- orgAlias: z73.string().min(1).optional()
20649
+ objectApiName: z74.string().min(1),
20650
+ ownerField: z74.string().min(1).optional(),
20651
+ threshold: z74.number().int().min(1).optional(),
20652
+ limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20653
+ orgAlias: z74.string().min(1).optional()
20255
20654
  });
20256
20655
  liveSetupAuditTrailInputSchema = liveEnabledSchema.extend({
20257
- days: z73.number().int().min(1).max(180).optional(),
20258
- limit: z73.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20259
- orgAlias: z73.string().min(1).optional()
20656
+ days: z74.number().int().min(1).max(180).optional(),
20657
+ limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
20658
+ orgAlias: z74.string().min(1).optional()
20260
20659
  });
20261
20660
  liveSecurityExposureInputSchema = liveEnabledSchema.extend({
20262
- orgAlias: z73.string().min(1).optional()
20661
+ orgAlias: z74.string().min(1).optional()
20263
20662
  });
20264
20663
  }
20265
20664
  });
20266
20665
 
20267
20666
  // ../mcp/dist/src/tools/live-drift-check.js
20268
- import { z as z74 } from "zod";
20667
+ import { z as z75 } from "zod";
20269
20668
  var FIELD_PAGE_SIZE2, liveDriftCheckInputSchema, BOUNDARIES8, isCustomField2, diffFields, liveFieldNames, liveDriftCheckHandler;
20270
20669
  var init_live_drift_check = __esm({
20271
20670
  "../mcp/dist/src/tools/live-drift-check.js"() {
@@ -20274,11 +20673,11 @@ var init_live_drift_check = __esm({
20274
20673
  init_src();
20275
20674
  init_live_plane();
20276
20675
  FIELD_PAGE_SIZE2 = 500;
20277
- liveDriftCheckInputSchema = z74.object({
20278
- objectApiName: z74.string().min(1),
20279
- orgAlias: z74.string().min(1).optional(),
20676
+ liveDriftCheckInputSchema = z75.object({
20677
+ objectApiName: z75.string().min(1),
20678
+ orgAlias: z75.string().min(1).optional(),
20280
20679
  /** Opt-in to the live plane (mirrors the other live_* tools). */
20281
- liveEnabled: z74.boolean().optional()
20680
+ liveEnabled: z75.boolean().optional()
20282
20681
  });
20283
20682
  BOUNDARIES8 = Object.freeze([
20284
20683
  "Compares the offline snapshot to a LIVE read-only describe; requires the live plane (SFI_LIVE_PLANE_ENABLED or liveEnabled:true). Does not mutate the org.",
@@ -20346,7 +20745,7 @@ var init_live_drift_check = __esm({
20346
20745
  });
20347
20746
 
20348
20747
  // ../mcp/dist/src/tools/lookup-record.js
20349
- import { z as z75 } from "zod";
20748
+ import { z as z76 } from "zod";
20350
20749
  var RECORD_NODE_TYPES, CUSTOM_METADATA_RECORD_PREFIX, CUSTOM_SETTING_RECORD_PREFIX, lookupRecordInputSchema, classifyRecordId, normalizeValueEntry, readRecordValues, readTypeApiName, lookupRecordHandler;
20351
20750
  var init_lookup_record = __esm({
20352
20751
  "../mcp/dist/src/tools/lookup-record.js"() {
@@ -20359,8 +20758,8 @@ var init_lookup_record = __esm({
20359
20758
  ]);
20360
20759
  CUSTOM_METADATA_RECORD_PREFIX = "CustomMetadataRecord:";
20361
20760
  CUSTOM_SETTING_RECORD_PREFIX = "CustomSettingRecord:";
20362
- lookupRecordInputSchema = z75.object({
20363
- recordId: z75.string().min(1)
20761
+ lookupRecordInputSchema = z76.object({
20762
+ recordId: z76.string().min(1)
20364
20763
  });
20365
20764
  classifyRecordId = (recordId) => {
20366
20765
  if (recordId.startsWith(CUSTOM_METADATA_RECORD_PREFIX)) {
@@ -20453,14 +20852,14 @@ var init_lookup_record = __esm({
20453
20852
  });
20454
20853
 
20455
20854
  // ../mcp/dist/src/tools/manifest.js
20456
- import { z as z76 } from "zod";
20855
+ import { z as z77 } from "zod";
20457
20856
  var getManifestInputSchema, getManifestHandler;
20458
20857
  var init_manifest2 = __esm({
20459
20858
  "../mcp/dist/src/tools/manifest.js"() {
20460
20859
  "use strict";
20461
20860
  init_dist();
20462
20861
  init_src2();
20463
- getManifestInputSchema = z76.object({});
20862
+ getManifestInputSchema = z77.object({});
20464
20863
  getManifestHandler = async (ctx, _input) => {
20465
20864
  const data = {
20466
20865
  ...ctx.manifest,
@@ -20478,7 +20877,7 @@ var init_manifest2 = __esm({
20478
20877
  });
20479
20878
 
20480
20879
  // ../mcp/dist/src/tools/meaningful-test-audit.js
20481
- import { z as z77 } from "zod";
20880
+ import { z as z78 } from "zod";
20482
20881
  var LIST_PAGE_SIZE8, APEX_CLASS_PREFIX5, MEANINGFUL_TEST_DISCLOSURE, meaningfulTestAuditInputSchema, isTestClass3, readNonNegativeInt, collectFakeAssertions, buildEntry, compareEntries2, meaningfulTestAuditHandler;
20483
20882
  var init_meaningful_test_audit = __esm({
20484
20883
  "../mcp/dist/src/tools/meaningful-test-audit.js"() {
@@ -20488,8 +20887,8 @@ var init_meaningful_test_audit = __esm({
20488
20887
  LIST_PAGE_SIZE8 = 500;
20489
20888
  APEX_CLASS_PREFIX5 = "ApexClass:";
20490
20889
  MEANINGFUL_TEST_DISCLOSURE = "v2.7 meaningful_test_audit ranks test classes by the v2.1 R2 fake-assertion recognizer output and an assertions-per-KB density heuristic. The recognizer matches System.assert* invocations against literal tokens; assertions via helper methods (MyTestHelper.assertField) and framework wrappers are invisible. A test class with a high fakeAssertionCount may have meaningful tests via a custom assertion helper the recognizer cannot see. When qualityIssues is absent the v2.1 R2 pass has not run for this vault; entries surface with fakeAssertionCount: 0 and the rank is driven by density alone.";
20491
- meaningfulTestAuditInputSchema = z77.object({
20492
- classFilter: z77.array(z77.string().min(1)).max(LIST_PAGE_SIZE8).optional()
20890
+ meaningfulTestAuditInputSchema = z78.object({
20891
+ classFilter: z78.array(z78.string().min(1)).max(LIST_PAGE_SIZE8).optional()
20493
20892
  });
20494
20893
  isTestClass3 = (node) => node.properties["isTest"] === true;
20495
20894
  readNonNegativeInt = (node, key) => {
@@ -20594,7 +20993,7 @@ var init_meaningful_test_audit = __esm({
20594
20993
  });
20595
20994
 
20596
20995
  // ../mcp/dist/src/tools/method-reachability.js
20597
- import { z as z78 } from "zod";
20996
+ import { z as z79 } from "zod";
20598
20997
  var REACHABILITY_BFS_DEPTH, APEX_CLASS_PREFIX6, APEX_TRIGGER_PREFIX4, REACHABILITY_DISCLOSURE, methodReachabilityInputSchema, isApexCallable3, isTestClass4, entryKindsFor, upstreamWalk, compareEntryHits, compareReachingTests, methodReachabilityHandler;
20599
20998
  var init_method_reachability = __esm({
20600
20999
  "../mcp/dist/src/tools/method-reachability.js"() {
@@ -20606,8 +21005,8 @@ var init_method_reachability = __esm({
20606
21005
  APEX_CLASS_PREFIX6 = "ApexClass:";
20607
21006
  APEX_TRIGGER_PREFIX4 = "ApexTrigger:";
20608
21007
  REACHABILITY_DISCLOSURE = "v2.7 method_reachability ships CLASS granularity (method-level promised in v2.7.1). Dynamic dispatch (Type.forName) and reflective invocation are invisible \u2014 a class genuinely invoked at runtime via reflection or framework wiring will surface as likely-dead-code. Trigger framework base classes (TriggerHandler, fflib) may be partially invisible. BFS is capped at depth 3.";
20609
- methodReachabilityInputSchema = z78.object({
20610
- classApiName: z78.string().min(1)
21008
+ methodReachabilityInputSchema = z79.object({
21009
+ classApiName: z79.string().min(1)
20611
21010
  });
20612
21011
  isApexCallable3 = (id) => id.startsWith(APEX_CLASS_PREFIX6) || id.startsWith(APEX_TRIGGER_PREFIX4);
20613
21012
  isTestClass4 = (node) => node.properties["isTest"] === true;
@@ -20747,15 +21146,15 @@ var init_method_reachability = __esm({
20747
21146
  });
20748
21147
 
20749
21148
  // ../mcp/dist/src/tools/naming-convention-report.js
20750
- import { z as z79 } from "zod";
21149
+ import { z as z80 } from "zod";
20751
21150
  var namingConventionReportInputSchema, namingConventionReportHandler;
20752
21151
  var init_naming_convention_report = __esm({
20753
21152
  "../mcp/dist/src/tools/naming-convention-report.js"() {
20754
21153
  "use strict";
20755
21154
  init_dist();
20756
21155
  init_src4();
20757
- namingConventionReportInputSchema = z79.object({
20758
- scope: z79.string().min(1).optional()
21156
+ namingConventionReportInputSchema = z80.object({
21157
+ scope: z80.string().min(1).optional()
20759
21158
  });
20760
21159
  namingConventionReportHandler = async (ctx, input2) => {
20761
21160
  const result = await recognizeNamingConventions(ctx.graph, input2.scope !== void 0 ? { scope: input2.scope } : {});
@@ -20785,7 +21184,7 @@ var init_naming_convention_report = __esm({
20785
21184
  // ../mcp/dist/src/tools/omniscript-flow.js
20786
21185
  import { readFile as readFile12 } from "node:fs/promises";
20787
21186
  import { XMLParser as XMLParser4 } from "fast-xml-parser";
20788
- import { z as z80 } from "zod";
21187
+ import { z as z81 } from "zod";
20789
21188
  var OMNISCRIPT_PREFIX, DISPATCH_EDGE_TYPE, NATIVE_VS_VLOCITY_DISCLOSURE3, RECORD_LEVEL_DISCLOSURE, APEX_COUPLING_DEFERRAL_DISCLOSURE2, omniscriptFlowInputSchema, isOmniScriptId, unwrapSingle4, toArray4, toNullableString, coerceBoolean3, toNullableNumber, parsePropertySetConfig2, walkElements, readSteps, readStringProp, readNumberProp, readBoolProp, compareDispatched, omniscriptFlowHandler;
20790
21189
  var init_omniscript_flow = __esm({
20791
21190
  "../mcp/dist/src/tools/omniscript-flow.js"() {
@@ -20798,9 +21197,9 @@ var init_omniscript_flow = __esm({
20798
21197
  NATIVE_VS_VLOCITY_DISCLOSURE3 = "v3.2 recognizes Industries Native XML shapes (file extensions `.os-meta.xml`, `.oip-meta.xml`, `.rpt-meta.xml`, `.ouc-meta.xml`, `.decisionTable-meta.xml`). Legacy Vlocity-managed-package components (namespace `vlocity_cmt__`) are NOT extracted by v3.2. Mid-migration orgs may show partial coverage.";
20799
21198
  RECORD_LEVEL_DISCLOSURE = "v3.2 walks the OmniScript / IP / Card metadata XML. The actual user-entered data and runtime state lives in OmniProcessElement and related SObject records; that is record-level data, out of scope for v0.1's read-the-metadata posture.";
20800
21199
  APEX_COUPLING_DEFERRAL_DISCLOSURE2 = "v3.2 captures OmniStudio components and intra-OmniStudio call chains (`dispatchesOmniAction`). The Apex-to-OmniProcess coupling (`implements omnistudio.VlocityOpenInterface` etc.) is a v3.3 follow-up \u2014 those edges are NOT yet in the graph.";
20801
- omniscriptFlowInputSchema = z80.object({
20802
- omniScriptId: z80.string().min(1),
20803
- includeChildPropertySetConfig: z80.boolean().optional()
21200
+ omniscriptFlowInputSchema = z81.object({
21201
+ omniScriptId: z81.string().min(1),
21202
+ includeChildPropertySetConfig: z81.boolean().optional()
20804
21203
  });
20805
21204
  isOmniScriptId = (id) => id.startsWith(OMNISCRIPT_PREFIX);
20806
21205
  unwrapSingle4 = (value) => Array.isArray(value) ? value[0] : value;
@@ -21047,7 +21446,7 @@ var init_omniscript_flow = __esm({
21047
21446
  // ../mcp/dist/src/tools/omniuicard-widget-breakdown.js
21048
21447
  import { readFile as readFile13 } from "node:fs/promises";
21049
21448
  import { XMLParser as XMLParser5, XMLValidator as XMLValidator4 } from "fast-xml-parser";
21050
- import { z as z81 } from "zod";
21449
+ import { z as z82 } from "zod";
21051
21450
  var OMNI_UI_CARD_PREFIX, ROOT_ELEMENT4, PROPERTY_SET_CONFIG_PARSING_DISCLOSURE, NATIVE_VS_VLOCITY_DISCLOSURE4, omniuicardWidgetBreakdownInputSchema, unwrapSingle5, parseJsonBlob, walkWidgets, countWidgets, buildStates, readStringArrayProperty, readNullableStringProperty, readNullableNumberProperty, readBooleanProperty2, projectDispatchedAction, sortDispatchedActions, buildStatesFromSourceXml, omniuicardWidgetBreakdownHandler;
21052
21451
  var init_omniuicard_widget_breakdown = __esm({
21053
21452
  "../mcp/dist/src/tools/omniuicard-widget-breakdown.js"() {
@@ -21059,8 +21458,8 @@ var init_omniuicard_widget_breakdown = __esm({
21059
21458
  ROOT_ELEMENT4 = "OmniUiCard";
21060
21459
  PROPERTY_SET_CONFIG_PARSING_DISCLOSURE = "widget breakdown parses the propertySetConfig JSON blob. FlexCard authors can edit the raw blob in the OmniStudio designer; widget order in the breakdown follows the JSON's declared order, not the visual designer's drag-drop order.";
21061
21460
  NATIVE_VS_VLOCITY_DISCLOSURE4 = "v3.2 recognizes Industries Native XML shapes (file extensions `.os-meta.xml`, `.oip-meta.xml`, `.rpt-meta.xml`, `.ouc-meta.xml`, `.decisionTable-meta.xml`). Legacy Vlocity-managed-package components (namespace `vlocity_cmt__`) are NOT extracted by v3.2. Mid-migration orgs may show partial coverage.";
21062
- omniuicardWidgetBreakdownInputSchema = z81.object({
21063
- omniUiCardId: z81.string().min(1)
21461
+ omniuicardWidgetBreakdownInputSchema = z82.object({
21462
+ omniUiCardId: z82.string().min(1)
21064
21463
  });
21065
21464
  unwrapSingle5 = (value) => Array.isArray(value) ? value[0] : value;
21066
21465
  parseJsonBlob = (raw) => {
@@ -21353,10 +21752,11 @@ var init_soe_payload_bounds = __esm({
21353
21752
  SOE_MAX_PAYLOAD_BYTES = 4e4;
21354
21753
  KEEP_ALL_AT_OR_BELOW = 4;
21355
21754
  sizeOf = (payload) => Buffer.byteLength(JSON.stringify(payload), "utf8");
21356
- enforceSoeByteBudget = (payload, steps) => {
21755
+ enforceSoeByteBudget = (payload, containers) => {
21357
21756
  if (sizeOf(payload) <= SOE_MAX_PAYLOAD_BYTES) {
21358
- return { truncated: false, actionsOmitted: 0 };
21757
+ return { truncated: false, actionsOmitted: 0, conditionalsTrimmed: 0, stepsOmitted: 0 };
21359
21758
  }
21759
+ const steps = containers.flat();
21360
21760
  let totalOmitted = 0;
21361
21761
  const trimTo = (step3, keep) => {
21362
21762
  if (keep >= step3.actions.length)
@@ -21395,15 +21795,82 @@ var init_soe_payload_bounds = __esm({
21395
21795
  break;
21396
21796
  trimTo(target, Math.floor(target.actions.length / 2));
21397
21797
  }
21398
- return { truncated: totalOmitted > 0, actionsOmitted: totalOmitted };
21798
+ let conditionalsTrimmed = 0;
21799
+ for (let guard = 0; guard < 1e5; guard += 1) {
21800
+ if (sizeOf(payload) <= SOE_MAX_PAYLOAD_BYTES)
21801
+ break;
21802
+ let target;
21803
+ let targetBytes = 0;
21804
+ for (const s of steps) {
21805
+ const cond = s.conditional;
21806
+ if (cond === void 0 || s.conditionalTruncated)
21807
+ continue;
21808
+ if (cond.expression === "" && cond.fieldRefs.length === 0)
21809
+ continue;
21810
+ const b = Buffer.byteLength(JSON.stringify(cond), "utf8");
21811
+ if (target === void 0 || b > targetBytes) {
21812
+ target = s;
21813
+ targetBytes = b;
21814
+ }
21815
+ }
21816
+ if (target === void 0)
21817
+ break;
21818
+ target.conditional = {
21819
+ conditionContextId: target.conditional.conditionContextId,
21820
+ expression: "",
21821
+ fieldRefs: []
21822
+ };
21823
+ target.conditionalTruncated = true;
21824
+ conditionalsTrimmed += 1;
21825
+ }
21826
+ let stepsOmitted = 0;
21827
+ for (let guard = 0; guard < 1e6; guard += 1) {
21828
+ if (sizeOf(payload) <= SOE_MAX_PAYLOAD_BYTES)
21829
+ break;
21830
+ let target;
21831
+ let targetBytes = 0;
21832
+ for (const c of containers) {
21833
+ if (c.length <= 1)
21834
+ continue;
21835
+ const b = Buffer.byteLength(JSON.stringify(c), "utf8");
21836
+ if (target === void 0 || b > targetBytes) {
21837
+ target = c;
21838
+ targetBytes = b;
21839
+ }
21840
+ }
21841
+ if (target === void 0)
21842
+ break;
21843
+ target.pop();
21844
+ stepsOmitted += 1;
21845
+ }
21846
+ return {
21847
+ truncated: totalOmitted > 0 || conditionalsTrimmed > 0 || stepsOmitted > 0,
21848
+ actionsOmitted: totalOmitted,
21849
+ conditionalsTrimmed,
21850
+ stepsOmitted
21851
+ };
21852
+ };
21853
+ soeTruncationNote = (result) => {
21854
+ const budgetKb = Math.round(SOE_MAX_PAYLOAD_BYTES / 1e3);
21855
+ const parts = [];
21856
+ if (result.actionsOmitted > 0) {
21857
+ parts.push(`${result.actionsOmitted} per-step action edge(s) across the heaviest steps were omitted (see each step's \`actionsOmitted\`)`);
21858
+ }
21859
+ if (result.conditionalsTrimmed > 0) {
21860
+ parts.push(`${result.conditionalsTrimmed} step condition(s) had their expression/fieldRefs dropped \u2014 the \`conditionContextId\` remains, fetch it with \`get_component\` for the full condition (see each step's \`conditionalTruncated\`)`);
21861
+ }
21862
+ if (result.stepsOmitted > 0) {
21863
+ parts.push(`${result.stepsOmitted} trailing step(s) were dropped to fit (the tail-most async/post-save steps; \`summary.totalSteps\` still reports the true total) \u2014 query a single event with \`what_happens_on_save\` to see them all`);
21864
+ }
21865
+ const lead = result.stepsOmitted > 0 ? `Response trimmed to fit the ~${budgetKb} KB MCP response budget` : `Response trimmed to fit the ~${budgetKb} KB MCP response budget: every save-order STEP is present and in order, but`;
21866
+ return `${lead} ${parts.join("; ")}. Query a single object/event for full detail.`;
21399
21867
  };
21400
- soeTruncationNote = (n) => `Response trimmed to fit the ~${Math.round(SOE_MAX_PAYLOAD_BYTES / 1e3)} KB MCP response budget: every save-order STEP is present and in order, but ${n} per-step action edge(s) across the heaviest steps were omitted (see each step's \`actionsOmitted\`). Query a single object/event or use \`get_edges\` on a specific step's component for its full action list.`;
21401
21868
  }
21402
21869
  });
21403
21870
 
21404
21871
  // ../mcp/dist/src/tools/order-of-execution.js
21405
- import { z as z82 } from "zod";
21406
- var DISCLOSURE6, SOE_EVENTS, orderOfExecutionInputSchema, workflowMatchesEvent, flowMatchesEvent, triggerMatchesEvent, surfaceFirstCondition, buildActions, buildStep, fetchParentedFirers, fetchTriggersOnFirers, buildAsyncSteps, ASSIGNMENT_TYPES, APPROVAL_TYPES, VALIDATION_TYPES, FLOW_TYPES, TRIGGER_TYPES, WORKFLOW_TYPES, composeForEvent, orderOfExecutionHandler;
21872
+ import { z as z83 } from "zod";
21873
+ var DISCLOSURE7, SOE_EVENTS, orderOfExecutionInputSchema, workflowMatchesEvent, flowMatchesEvent, triggerMatchesEvent, surfaceFirstCondition, buildActions, buildStep, fetchParentedFirers, fetchTriggersOnFirers, buildAsyncSteps, ASSIGNMENT_TYPES, APPROVAL_TYPES, VALIDATION_TYPES, FLOW_TYPES, TRIGGER_TYPES, WORKFLOW_TYPES, composeForEvent, orderOfExecutionHandler;
21407
21874
  var init_order_of_execution = __esm({
21408
21875
  "../mcp/dist/src/tools/order-of-execution.js"() {
21409
21876
  "use strict";
@@ -21411,10 +21878,10 @@ var init_order_of_execution = __esm({
21411
21878
  init_src();
21412
21879
  init_soe_admission();
21413
21880
  init_soe_payload_bounds();
21414
- DISCLOSURE6 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
21881
+ DISCLOSURE7 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
21415
21882
  SOE_EVENTS = ["insert", "update", "delete", "undelete"];
21416
- orderOfExecutionInputSchema = z82.object({
21417
- objectApiName: z82.string().min(1)
21883
+ orderOfExecutionInputSchema = z83.object({
21884
+ objectApiName: z83.string().min(1)
21418
21885
  });
21419
21886
  workflowMatchesEvent = (triggerType, event) => {
21420
21887
  if (typeof triggerType !== "string")
@@ -21762,13 +22229,13 @@ var init_order_of_execution = __esm({
21762
22229
  objectApiName: input2.objectApiName,
21763
22230
  objectModeled,
21764
22231
  byEvent,
21765
- disclosure: composeSoeDisclosure(DISCLOSURE6, objectModeled)
22232
+ disclosure: composeSoeDisclosure(DISCLOSURE7, objectModeled)
21766
22233
  };
21767
- const allSteps = SOE_EVENTS.flatMap((event) => byEvent[event].soe);
21768
- const budget = enforceSoeByteBudget(data, allSteps);
22234
+ const containers = SOE_EVENTS.map((event) => byEvent[event].soe);
22235
+ const budget = enforceSoeByteBudget(data, containers);
21769
22236
  if (budget.truncated) {
21770
22237
  data.truncated = true;
21771
- data.disclosure = `${data.disclosure} ${soeTruncationNote(budget.actionsOmitted)}`;
22238
+ data.disclosure = `${data.disclosure} ${soeTruncationNote(budget)}`;
21772
22239
  }
21773
22240
  return ok({
21774
22241
  data,
@@ -21782,7 +22249,7 @@ var init_order_of_execution = __esm({
21782
22249
  });
21783
22250
 
21784
22251
  // ../mcp/dist/src/tools/org-history.js
21785
- import { z as z83 } from "zod";
22252
+ import { z as z84 } from "zod";
21786
22253
  var DEFAULT_LIMIT8, MAX_LIMIT9, orgHistoryInputSchema, BOUNDARIES9, orgHistoryHandler;
21787
22254
  var init_org_history = __esm({
21788
22255
  "../mcp/dist/src/tools/org-history.js"() {
@@ -21791,8 +22258,8 @@ var init_org_history = __esm({
21791
22258
  init_history_store();
21792
22259
  DEFAULT_LIMIT8 = 50;
21793
22260
  MAX_LIMIT9 = 500;
21794
- orgHistoryInputSchema = z83.object({
21795
- limit: z83.number().int().min(1).max(MAX_LIMIT9).optional()
22261
+ orgHistoryInputSchema = z84.object({
22262
+ limit: z84.number().int().min(1).max(MAX_LIMIT9).optional()
21796
22263
  });
21797
22264
  BOUNDARIES9 = Object.freeze([
21798
22265
  "History only covers refreshes performed since the continuous-learning store shipped; older or single-refresh vaults yield a short/empty timeline.",
@@ -21823,7 +22290,7 @@ var init_org_history = __esm({
21823
22290
  });
21824
22291
 
21825
22292
  // ../mcp/dist/src/tools/org-pulse.js
21826
- import { z as z84 } from "zod";
22293
+ import { z as z85 } from "zod";
21827
22294
  var DEFAULT_LIMIT9, MAX_LIMIT10, orgPulseInputSchema, ORG_PULSE_DISCLOSURE, orgPulseHandler;
21828
22295
  var init_org_pulse = __esm({
21829
22296
  "../mcp/dist/src/tools/org-pulse.js"() {
@@ -21832,8 +22299,8 @@ var init_org_pulse = __esm({
21832
22299
  init_src();
21833
22300
  DEFAULT_LIMIT9 = 10;
21834
22301
  MAX_LIMIT10 = 50;
21835
- orgPulseInputSchema = z84.object({
21836
- limit: z84.number().int().min(1).max(MAX_LIMIT10).optional()
22302
+ orgPulseInputSchema = z85.object({
22303
+ limit: z85.number().int().min(1).max(MAX_LIMIT10).optional()
21837
22304
  });
21838
22305
  ORG_PULSE_DISCLOSURE = "Freshness and contributor signals come from each component's lastModifiedDate / lastModifiedBy. A plain `sf project retrieve` does NOT populate those \u2014 they need a refresh enriched via the Tooling API. If coverage is ~0% and the contributor list is empty, that means the data was not captured at refresh time, NOT that the org has no history. Run a tooling-API-enabled refresh to populate it.";
21839
22306
  orgPulseHandler = async (ctx, input2) => {
@@ -21868,7 +22335,7 @@ var init_org_pulse = __esm({
21868
22335
  });
21869
22336
 
21870
22337
  // ../mcp/dist/src/tools/outbound-message-catalog.js
21871
- import { z as z85 } from "zod";
22338
+ import { z as z86 } from "zod";
21872
22339
  var OUTBOUND_MESSAGE_CATALOG_MAX_ENTRIES, outboundMessageCatalogInputSchema, OUTBOUND_MESSAGE_DISCLOSURE, readOptionalString2, readBoolean, readFields, readName, apiNameToObjectKey, collectInvokers, compareEntries3, compareInvokers, outboundMessageCatalogHandler;
21873
22340
  var init_outbound_message_catalog = __esm({
21874
22341
  "../mcp/dist/src/tools/outbound-message-catalog.js"() {
@@ -21876,8 +22343,8 @@ var init_outbound_message_catalog = __esm({
21876
22343
  init_dist();
21877
22344
  init_src();
21878
22345
  OUTBOUND_MESSAGE_CATALOG_MAX_ENTRIES = 500;
21879
- outboundMessageCatalogInputSchema = z85.object({
21880
- objectFilter: z85.string().min(1).optional()
22346
+ outboundMessageCatalogInputSchema = z86.object({
22347
+ objectFilter: z86.string().min(1).optional()
21881
22348
  });
21882
22349
  OUTBOUND_MESSAGE_DISCLOSURE = "Endpoint URLs are captured verbatim from the `<outboundMessages><endpointUrl>` element and NOT VALIDATED \u2014 v2.8 does not probe the URL, does not confirm the destination exists, and does not confirm the message is invoked at runtime. Runtime registration via a custom Apex caller or a programmatically-modified workflow rule is invisible to the offline extractor.";
21883
22350
  readOptionalString2 = (properties, key) => {
@@ -21994,7 +22461,7 @@ var init_outbound_message_catalog = __esm({
21994
22461
  });
21995
22462
 
21996
22463
  // ../mcp/dist/src/tools/package-impact.js
21997
- import { z as z86 } from "zod";
22464
+ import { z as z87 } from "zod";
21998
22465
  var DEFAULT_LIMIT10, MAX_LIMIT11, INVENTORY_SAMPLE, EDGE_SCAN_CAP, PACKAGE_IMPACT_DISCLOSURE, packageImpactInputSchema, namespaceOf, buildInventory, buildImpact, packageImpactHandler;
21999
22466
  var init_package_impact = __esm({
22000
22467
  "../mcp/dist/src/tools/package-impact.js"() {
@@ -22006,9 +22473,9 @@ var init_package_impact = __esm({
22006
22473
  INVENTORY_SAMPLE = 5;
22007
22474
  EDGE_SCAN_CAP = 2e3;
22008
22475
  PACKAGE_IMPACT_DISCLOSURE = 'package_impact detects managed/namespaced components by API-name prefix: a name is namespaced iff its leaf splits into >= 3 "__"-delimited segments (NS__Object__c). This reliably catches namespaced objects, fields, and custom metadata \u2014 the bulk of what a package adds \u2014 but MISSES managed Apex referenced via dot-notation (NS.ClassName) and namespaced components without a standard suffix. The vault holds only what `sf project retrieve` pulled: a package\u2019s INTERNAL components are usually NOT retrieved, so packageComponentCount reflects what you can SEE, not the package\u2019s full footprint. "no-detected-dependencies" means NO STATIC evidence in retrieved metadata that your components reference this namespace \u2014 it does NOT prove the package is safe to uninstall (dynamic SOQL, Type.forName("NS.X"), merge-field/formula references, and unretrieved metadata are invisible). Always validate an uninstall in a sandbox first.';
22009
- packageImpactInputSchema = z86.object({
22010
- namespace: z86.string().min(1).optional(),
22011
- limit: z86.number().int().min(1).max(MAX_LIMIT11).optional()
22476
+ packageImpactInputSchema = z87.object({
22477
+ namespace: z87.string().min(1).optional(),
22478
+ limit: z87.number().int().min(1).max(MAX_LIMIT11).optional()
22012
22479
  });
22013
22480
  namespaceOf = (idOrName) => {
22014
22481
  const colon = idOrName.indexOf(":");
@@ -22147,7 +22614,7 @@ var init_package_impact = __esm({
22147
22614
  });
22148
22615
 
22149
22616
  // ../mcp/dist/src/tools/process-builder-migration-candidates.js
22150
- import { z as z87 } from "zod";
22617
+ import { z as z88 } from "zod";
22151
22618
  var PROCESS_BUILDER_MAX_LIMIT, PROCESS_BUILDER_DEFAULT_LIMIT, LIST_PAGE_SIZE9, PROCESS_BUILDER_RETIREMENT_NOTE, WORKFLOW_RULE_RETIREMENT_NOTE, BOUNDARIES10, processBuilderMigrationCandidatesInputSchema, propertyNumber, propertyBoolean, propertyString2, countOutgoingEdges, classifyProcessBuilder, classifyWorkflowRule, classifyApprovalProcess, migrationNoteForProcessBuilder, migrationNoteForWorkflowRule, migrationNoteForApprovalProcess, complexityRank, compareByComplexity, compareByApiName2, compareByParent, sortFor, processBuilderMigrationCandidatesHandler;
22152
22619
  var init_process_builder_migration_candidates = __esm({
22153
22620
  "../mcp/dist/src/tools/process-builder-migration-candidates.js"() {
@@ -22163,12 +22630,12 @@ var init_process_builder_migration_candidates = __esm({
22163
22630
  "the complexity classification is heuristic based on edge counts and time-trigger presence; complex business logic in a single-decision Process Builder may rank as 'simple' but require manual review for migration.",
22164
22631
  "the migration tool itself (Setup \u2192 Migrate to Flow) does not run here \u2014 this tool produces the inventory and per-rule guidance."
22165
22632
  ]);
22166
- processBuilderMigrationCandidatesInputSchema = z87.object({
22167
- includeWorkflowRules: z87.boolean().optional(),
22168
- includeApprovalProcesses: z87.boolean().optional(),
22169
- activeOnly: z87.boolean().optional(),
22170
- sortBy: z87.enum(["complexity", "object", "name"]).optional(),
22171
- limit: z87.number().int().min(1).max(PROCESS_BUILDER_MAX_LIMIT).optional()
22633
+ processBuilderMigrationCandidatesInputSchema = z88.object({
22634
+ includeWorkflowRules: z88.boolean().optional(),
22635
+ includeApprovalProcesses: z88.boolean().optional(),
22636
+ activeOnly: z88.boolean().optional(),
22637
+ sortBy: z88.enum(["complexity", "object", "name"]).optional(),
22638
+ limit: z88.number().int().min(1).max(PROCESS_BUILDER_MAX_LIMIT).optional()
22172
22639
  });
22173
22640
  propertyNumber = (node, key) => {
22174
22641
  const v = node.properties[key];
@@ -22525,7 +22992,7 @@ var init_clarify = __esm({
22525
22992
  });
22526
22993
 
22527
22994
  // ../mcp/dist/src/tools/resolve.js
22528
- import { z as z88 } from "zod";
22995
+ import { z as z89 } from "zod";
22529
22996
  var RESOLVE_MAX_LIMIT, RESOLVE_DISCLOSURE, resolveInputSchema, resolveHandler;
22530
22997
  var init_resolve2 = __esm({
22531
22998
  "../mcp/dist/src/tools/resolve.js"() {
@@ -22536,11 +23003,11 @@ var init_resolve2 = __esm({
22536
23003
  init_clarify();
22537
23004
  RESOLVE_MAX_LIMIT = 50;
22538
23005
  RESOLVE_DISCLOSURE = "These are typo-tolerant, fuzzy-ranked guesses at which component you meant \u2014 heuristic, not declared. disposition 'exact' = one confident match; 'ambiguous' = several plausible candidates, confirm the right one before acting; 'none' = nothing matched confidently (any listed items are weak near-misses). A high score is string similarity, not proof \u2014 verify the candidate's canonical id and label.";
22539
- resolveInputSchema = z88.object({
22540
- query: z88.string().min(1),
22541
- types: z88.array(z88.string()).optional(),
22542
- parentId: z88.string().min(1).optional(),
22543
- limit: z88.number().int().min(1).max(RESOLVE_MAX_LIMIT).optional()
23006
+ resolveInputSchema = z89.object({
23007
+ query: z89.string().min(1),
23008
+ types: z89.array(z89.string()).optional(),
23009
+ parentId: z89.string().min(1).optional(),
23010
+ limit: z89.number().int().min(1).max(RESOLVE_MAX_LIMIT).optional()
22544
23011
  });
22545
23012
  resolveHandler = async (ctx, input2) => {
22546
23013
  const result = await resolveComponents(ctx.graph, input2.query, {
@@ -22597,11 +23064,41 @@ var init_resolve2 = __esm({
22597
23064
  import { appendFile, mkdir as mkdir6 } from "node:fs/promises";
22598
23065
  import { homedir as homedir2 } from "node:os";
22599
23066
  import { dirname as dirname5, join as join9 } from "node:path";
22600
- var normalize, routeText, RULES, classifyQuestion, gapLogPath, logGapIfAny;
23067
+ var normalize, deriveSaveEvent, deriveKnowledgeTopic, routeText, RULES, classifyQuestion, gapLogPath, logGapIfAny;
22601
23068
  var init_intent_router = __esm({
22602
23069
  "../mcp/dist/src/intent-router.js"() {
22603
23070
  "use strict";
22604
23071
  normalize = (q) => q.trim().toLowerCase().replace(/\s+/g, " ");
23072
+ deriveSaveEvent = (q) => {
23073
+ if (/\b(undelet|restor)/.test(q))
23074
+ return "undelete";
23075
+ if (/\b(creat|insert)/.test(q))
23076
+ return "insert";
23077
+ if (/\b(delet|remov)/.test(q))
23078
+ return "delete";
23079
+ return "update";
23080
+ };
23081
+ deriveKnowledgeTopic = (q) => {
23082
+ if (/\b(flow\s+(vs\.?|versus|or)\s+apex|apex\s+(vs\.?|versus|or)\s+flow)\b/.test(q))
23083
+ return "flow-vs-apex";
23084
+ if (/\bwhen\s+(should\s+i|to)\s+use\b.*\bflow\b/.test(q))
23085
+ return "flow-vs-apex";
23086
+ if (/\b(apex\s+)?trigger\s+framework\b/.test(q))
23087
+ return "trigger-framework";
23088
+ if (/\basync(hronous)?\s+apex\b/.test(q) || /\b(future|queueable|batch|schedulable)\b.*\b(vs\.?|versus|when\s+to|which\s+to|each\s+appropriate|options?)\b/.test(q))
23089
+ return "async-apex";
23090
+ if (/\b(sfdx|source[-\s]driven\s+development|scratch\s+orgs?)\b/.test(q))
23091
+ return "sfdx-source-driven-dev";
23092
+ if (/\bunlocked\s+packages?\b/.test(q))
23093
+ return "package-strategy";
23094
+ if (/\bsingle[-\s]org\b.*\bmulti[-\s]org\b/.test(q))
23095
+ return "single-vs-multi-org";
23096
+ if (/\bdata\s+(retention|archiv)/.test(q))
23097
+ return "data-retention-archiving";
23098
+ if (/\b(large\s+data\s+volumes?|\bldv\b)/.test(q))
23099
+ return "large-data-volumes";
23100
+ return void 0;
23101
+ };
22605
23102
  routeText = (question) => {
22606
23103
  const normalized = normalize(question).replace(/\s*\[[^\]]+\]\s*$/, "");
22607
23104
  const stripped = normalized.replace(/^.*?\b(answer this|route this|do not guess|tell me|include|show what|fail closed|flag anything|use the safest|state what)\b[^:]*:\s*/, "");
@@ -22663,7 +23160,10 @@ var init_intent_router = __esm({
22663
23160
  needsResolve: false,
22664
23161
  reason: "Storage, API usage, and governor headroom are live org telemetry.",
22665
23162
  patterns: [
22666
- /\b(org|governor)\s+limits?\b/,
23163
+ // "governor limits" → live runtime headroom, BUT not when the question is
23164
+ // about static Apex risk ("governor limit risks in our Apex", "SOQL in
23165
+ // loops") — that's the vault governor-risks scan (NI-7 misroute fix).
23166
+ /\b(org|governor)\s+limits?\b(?!.*\b(risk|apex|loop|soql|dml|static|trigger|class)\b)/,
22667
23167
  /\b(api|daily)\s+(usage|calls?|limit)\b/,
22668
23168
  /\b(data|file)\s+storage\b/,
22669
23169
  /\bhow\s+much\s+(storage|api|data)\b/,
@@ -22697,6 +23197,25 @@ var init_intent_router = __esm({
22697
23197
  /\brecords?\s+by\s+owner\b/
22698
23198
  ]
22699
23199
  },
23200
+ {
23201
+ // METADATA counts are vault, not live. "How many layouts / fields / objects
23202
+ // / profiles / validation rules ... [per X]" was stolen by the live
23203
+ // group-count/record-count rules and misrouted to live GROUP BY (B30.1).
23204
+ // Record counts (accounts, contacts, rows) fall through to the live rules
23205
+ // below. Placed AFTER field-population so "how many fields are populated"
23206
+ // stays hybrid/live.
23207
+ intent: "metadata-count",
23208
+ plane: "vault",
23209
+ tools: ["sfi.list_components", "sfi.get_component"],
23210
+ liveRequired: false,
23211
+ needsResolve: false,
23212
+ reason: "Counting metadata components (layouts, fields, objects, profiles, validation rules, flows, classes, record types, list views) is a vault list_components count \u2014 not live record data.",
23213
+ patterns: [
23214
+ /\bhow\s+many\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/,
23215
+ // fields, but NOT field usage/population (those are unused-fields / field-population)
23216
+ /\bhow\s+many\b.*\b(custom\s+)?fields?\b(?!.*\b(used|populated|filled|actually|unused|empty|blank|set|values?)\b)/
23217
+ ]
23218
+ },
22700
23219
  {
22701
23220
  intent: "group-count",
22702
23221
  plane: "live",
@@ -22884,7 +23403,27 @@ var init_intent_router = __esm({
22884
23403
  patterns: [
22885
23404
  /\b(page\s+)?layouts?\b.*\b(who|access|assigned|profile|sees?|user)\b/,
22886
23405
  /\bwho\s+(sees|has|can)\b.*\blayouts?\b/,
22887
- /\bwhich\s+layout\b/
23406
+ /\bwhich\s+layout\b/,
23407
+ // "what/which (page) layouts show|contain|have a FIELD" — a field->layout
23408
+ // question (vs the who-sees-layout above) used to fall through (B21).
23409
+ /\b(what|which)\s+(page\s+)?layouts?\b.*\b(show|contain|display|include|have|with|for)\b.*\bfield\b/
23410
+ ]
23411
+ },
23412
+ {
23413
+ // Layout INVENTORY + CONTENTS (vs layout-access's who-sees-which). "How many
23414
+ // layouts exist for X", "what fields / related lists / quick actions are on
23415
+ // the Y layout" used to fall through to generic schema or unrouted (B21.1).
23416
+ intent: "layout-inventory",
23417
+ plane: "vault",
23418
+ tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
23419
+ liveRequired: false,
23420
+ needsResolve: false,
23421
+ reason: "Page-layout inventory (how many / which layouts on an object) and contents (fields, related lists, quick actions on a named layout) are modeled in the vault \u2014 Layout is a covered type.",
23422
+ patterns: [
23423
+ /\bhow\s+many\b.*\blayouts?\b/,
23424
+ /\b(what|which|list)\b.*\b(page\s+)?layouts?\b.*\b(exist|are\s+there|for\s+the|on\s+the|does|available)\b/,
23425
+ /\bwhat\b.*\b(fields?|related\s+lists?|quick\s+actions?|sections?|buttons?)\b.*\bon\b.*\blayout\b/,
23426
+ /\b(related\s+lists?|quick\s+actions?)\b.*\b(on|appear|for)\b.*\blayout\b/
22888
23427
  ]
22889
23428
  },
22890
23429
  {
@@ -22925,9 +23464,13 @@ var init_intent_router = __esm({
22925
23464
  needsResolve: false,
22926
23465
  reason: "God-mode / over-permission detection is a vault permission synthesis.",
22927
23466
  patterns: [
22928
- /\b(over[-\s]?permission|god[-\s]?mode|too\s+much\s+access|modify\s+all|view\s+all)\b/,
23467
+ /\b(over[-\s]?permission(ed|s)?|god[-\s]?mode|too\s+much\s+access|modify\s+all|view\s+all)\b/,
22929
23468
  /\bwho\s+is\s+(an?\s+)?admin\b/,
22930
- /\b(permission|access)\s+(risk|sprawl|hygiene)\b/
23469
+ /\b(permission|access)\s+(risk|sprawl|hygiene)\b/,
23470
+ // Specific high-risk system permissions — "who can author apex / customize
23471
+ // the application / manage users / modify metadata / view setup" — all
23472
+ // answered by the permission_risk_report over-privilege pass.
23473
+ /\bwho\s+(can|has)\b.*\b(author\s+apex|customi[sz]e\s+(the\s+)?application|manage\s+(users|sharing|roles|profiles)|modify\s+metadata|view\s+setup)\b/
22931
23474
  ]
22932
23475
  },
22933
23476
  {
@@ -23027,14 +23570,21 @@ var init_intent_router = __esm({
23027
23570
  liveRequired: false,
23028
23571
  needsResolve: true,
23029
23572
  reason: "Order of execution / what runs on save is reconstructed from the vault graph. what_happens_on_save needs an explicit DML event \u2014 default to 'update' (or insert/delete to match the question) when none is stated.",
23573
+ suggestArgs: (q) => ({ event: deriveSaveEvent(q) }),
23030
23574
  patterns: [
23031
23575
  /\b(trigger\s+order|order\s+of\s+execution)\b/,
23032
- /\bwhat\s+(happens|runs|fires)\b.*\b(on\s+save|when\b.*\b(created|saved|updated|inserted|deleted))\b/,
23576
+ /\bwhat\s+(happens|runs|fires)\b.*\b(on\s+save|when\b.*\b(created|saved|updated|inserted|deleted|undeleted|restored))\b/,
23033
23577
  // "which/what flows|triggers|VRs|workflows run|fire when ..." — the
23034
23578
  // "which" phrasing was a router gap (e.g. "which flows run when a Case is
23035
23579
  // created"), so the question fell through to unrouted.
23036
23580
  /\b(what|which)\s+(triggers?|automation|flows?|validation\s+rules?|workflows?)\b.*\b(fire|run|execute|happen)\b/,
23037
- /\b(flows?|triggers?|automation)\b.*\bwhen\b.*\b\w+\s+is\b.*\b(created|updated|inserted|deleted|saved)\b/
23581
+ /\b(flows?|triggers?|automation)\b.*\bwhen\b.*\b\w+\s+is\b.*\b(created|updated|inserted|deleted|saved)\b/,
23582
+ // "what APEX/code/class runs when ..." — a noun between "what" and the
23583
+ // verb (vs the adjacent "what runs") used to fall through (B21).
23584
+ /\bwhat\s+(apex|code|class(es)?)\b.*\bruns?\b.*\bwhen\b/,
23585
+ // "what happens when a Case STATUS CHANGES" — a status/stage change is a
23586
+ // save-order event the "(created|updated|...)" verb list missed (B21).
23587
+ /\bwhat\s+(happens|runs|fires)\b.*\b(status|stage)\b.*\bchang/
23038
23588
  ]
23039
23589
  },
23040
23590
  {
@@ -23050,6 +23600,25 @@ var init_intent_router = __esm({
23050
23600
  /\bwhat\s+changed\b.*\bfield\b.*\bon\s+save\b/
23051
23601
  ]
23052
23602
  },
23603
+ {
23604
+ // What a validation rule enforces / its error — get_component on the rule.
23605
+ // MUST precede automation-on-object, which else steals "what does the X
23606
+ // validation rule on <object> enforce" (it has "validation rule … on
23607
+ // account") and sends it to automation_build_advisor instead of the rule's
23608
+ // formula+error (NI-11). "What validation rules exist/run" stays schema /
23609
+ // trigger-order (those lack the enforce/does/error verbs below).
23610
+ intent: "explain-validation-rule",
23611
+ plane: "vault",
23612
+ tools: ["sfi.resolve", "sfi.get_component"],
23613
+ liveRequired: false,
23614
+ needsResolve: true,
23615
+ reason: "A validation rule's condition and error message are in the vault \u2014 resolve the rule, then get_component for its formula and error text.",
23616
+ patterns: [
23617
+ /\bvalidation\s+rules?\b.*\b(enforce|does|do\b|check|mean|prevent|block|error|message|stop)\b/,
23618
+ /\bwhat\s+(does|error|message)\b.*\bvalidation\s+rule\b/,
23619
+ /\b(error|message)\b.*\bvalidation\s+rule\b.*\b(show|display|return)\b/
23620
+ ]
23621
+ },
23053
23622
  {
23054
23623
  intent: "automation-on-object",
23055
23624
  plane: "vault",
@@ -23256,6 +23825,23 @@ var init_intent_router = __esm({
23256
23825
  /\borg\s+(risk|health)\b/
23257
23826
  ]
23258
23827
  },
23828
+ {
23829
+ // "Which classes implement <interface>" (Batchable, Schedulable, Queueable,
23830
+ // RestResource, ...) — grep Apex source for the implements clause. The
23831
+ // apex-search verbs (uses/references) didn't cover "implement" (B21.16/17,
23832
+ // E.4-6 / BL-13 interface filters).
23833
+ intent: "interface-implementers",
23834
+ plane: "vault",
23835
+ tools: ["sfi.search_apex_source", "sfi.find_apex_usages"],
23836
+ liveRequired: false,
23837
+ needsResolve: false,
23838
+ reason: "Classes implementing an interface (Batchable / Schedulable / Queueable / RestResource) are found by grepping Apex source for the implements clause.",
23839
+ patterns: [
23840
+ /\b(which|what)\s+(classes?|apex)\b.*\bimplements?\b/,
23841
+ /\bimplements?\b.*\b(batchable|schedulable|queueable|database\.\w+|interface)\b/,
23842
+ /\b(batchable|schedulable|queueable)\b.*\b(classes?|implement)\b/
23843
+ ]
23844
+ },
23259
23845
  {
23260
23846
  intent: "apex-search",
23261
23847
  plane: "vault",
@@ -23283,19 +23869,53 @@ var init_intent_router = __esm({
23283
23869
  /\bapi\b.*\b(connections?|surfaces?)\b/
23284
23870
  ]
23285
23871
  },
23872
+ {
23873
+ // "What platform events does this org publish / exist?" → CATALOG mode
23874
+ // (event_subscribers with NO eventId). No named component to resolve —
23875
+ // distinct from who-subscribes-to-EVENT-X below. NI-1: follow-up to the D6
23876
+ // catalog fix so the route stops telling the agent to resolve a component
23877
+ // that was never named.
23878
+ intent: "event-catalog",
23879
+ plane: "vault",
23880
+ tools: ["sfi.event_subscribers"],
23881
+ liveRequired: false,
23882
+ needsResolve: false,
23883
+ reason: "Lists every Platform Event the org publishes with its subscriber count (event_subscribers catalog mode \u2014 call with no eventId).",
23884
+ patterns: [
23885
+ /\bwhat\s+(platform\s+events?|cdc\s+channels?)\b/,
23886
+ /\b(platform\s+events?)\b.*\b(publish|emit|defined|exist|list|are\s+there)\b/
23887
+ ]
23888
+ },
23286
23889
  {
23287
23890
  intent: "event-subscribers",
23288
23891
  plane: "vault",
23289
23892
  tools: ["sfi.resolve", "sfi.event_subscribers", "sfi.cdc_subscribers"],
23290
23893
  liveRequired: false,
23291
23894
  needsResolve: true,
23292
- reason: "Who subscribes to a platform event / change data capture channel.",
23895
+ reason: "Who subscribes to a specific platform event / change data capture channel.",
23293
23896
  patterns: [
23294
23897
  /\b(platform\s+events?|change\s+data\s+capture|cdc)\b.*\b(subscrib|listen|consum)\b/,
23295
23898
  /\bwho\s+(subscribes?|listens?)\b/,
23296
23899
  /\b(subscribers?|subscriptions?)\b.*\bevents?\b/
23297
23900
  ]
23298
23901
  },
23902
+ {
23903
+ // Inbound Apex REST (@RestResource) — "what REST endpoints are exposed".
23904
+ // MUST precede the outbound `endpoints` rule, which would otherwise grab
23905
+ // "endpoints" and misroute to the outbound catalog (B21.15).
23906
+ intent: "rest-endpoints",
23907
+ plane: "vault",
23908
+ tools: ["sfi.search_apex_source", "sfi.find_apex_usages"],
23909
+ liveRequired: false,
23910
+ needsResolve: false,
23911
+ reason: "Inbound Apex REST endpoints are @RestResource/@Http* classes \u2014 grep Apex source for the annotations.",
23912
+ patterns: [
23913
+ /\b(apex\s+)?rest\s+(endpoints?|resources?|services?|api)\b/,
23914
+ /\b@?restresource\b/,
23915
+ /\b(inbound|exposed|expose)\b.*\brest\b/,
23916
+ /\brest\b.*\b(endpoints?|resources?)\b.*\b(expose|exposed|apex|class)\b/
23917
+ ]
23918
+ },
23299
23919
  {
23300
23920
  intent: "endpoints",
23301
23921
  plane: "vault",
@@ -23360,6 +23980,24 @@ var init_intent_router = __esm({
23360
23980
  /\b(safe\s+to\s+delete|can\s+i\s+delete|ok\s+to\s+(delete|remove))\b/
23361
23981
  ]
23362
23982
  },
23983
+ {
23984
+ // MUST precede impact-analysis + what-if-field: a question about changing a
23985
+ // field's stored VALUE (not its type/required/deletion) routes to the
23986
+ // value-change tier. The discriminator is the word "value(s)" near a
23987
+ // change/impact verb; schema what-ifs say "field type" / "delete" / "required".
23988
+ intent: "value-change",
23989
+ plane: "vault",
23990
+ tools: ["sfi.resolve", "sfi.value_change_audit", "sfi.what_if_change_field_value"],
23991
+ liveRequired: false,
23992
+ needsResolve: true,
23993
+ reason: "Changing a field's stored VALUE (not its schema) has a distinct blast radius \u2014 identity / integration-key / uniqueness / automation / cross-object \u2014 surfaced by value_change_audit (a set of fields on an object) or what_if_change_field_value (one field). Distinct from the type/required/delete what-ifs.",
23994
+ patterns: [
23995
+ /\bvalue[-\s]changes?\b/,
23996
+ /\b(chang|updat|edit|modif|bulk[-\s]?updat)\w*\b[^.?!]{0,40}\bvalues?\b/,
23997
+ /\bvalues?\b[^.?!]{0,40}\b(impact|affect|break|desync|safe|risk)\w*/,
23998
+ /\b(impact|affect|safe|risky?)\b[^.?!]{0,70}\b(chang|updat|edit|modif)\w*\b[^.?!]{0,40}\bvalues?\b/
23999
+ ]
24000
+ },
23363
24001
  {
23364
24002
  intent: "impact-analysis",
23365
24003
  plane: "vault",
@@ -23572,6 +24210,37 @@ var init_intent_router = __esm({
23572
24210
  ]
23573
24211
  },
23574
24212
  // === Schema / naming (general — near the end) =============================
24213
+ {
24214
+ // KNOWLEDGE plane (B1) — greenfield/best-practice asks with NO org-specific
24215
+ // answer. Patterns are deliberately narrow (only asks no vault rule already
24216
+ // catches: flow-vs-apex, trigger framework, async options, SFDX, unlocked
24217
+ // packages) so org-specific questions still route to vault/live. Other
24218
+ // greenfield topics (governor limits, callouts, coverage, naming) are caught
24219
+ // by their vault rules earlier and stay org-specific; their curated topics
24220
+ // remain reachable via an explicit sfi.guidance { topic } call.
24221
+ intent: "guidance",
24222
+ plane: "knowledge",
24223
+ tools: ["sfi.guidance"],
24224
+ liveRequired: false,
24225
+ needsResolve: false,
24226
+ reason: "General Salesforce best-practice question with no org-specific answer \u2014 sfi.guidance returns a curated summary + official doc links (not org data).",
24227
+ suggestArgs: (q) => {
24228
+ const topic = deriveKnowledgeTopic(q);
24229
+ return topic !== void 0 ? { topic } : void 0;
24230
+ },
24231
+ patterns: [
24232
+ /\b(flow\s+(vs\.?|versus|or)\s+apex|apex\s+(vs\.?|versus|or)\s+flow)\b/,
24233
+ /\bwhen\s+(should\s+i|to)\s+use\b.*\bflow\b/,
24234
+ /\b(apex\s+)?trigger\s+framework\b/,
24235
+ /\basync(hronous)?\s+apex\b/,
24236
+ /\b(future|queueable|batch|schedulable)\b.*\b(vs\.?|versus|when\s+to|which\s+to|each\s+appropriate|options?)\b/,
24237
+ /\b(sfdx|source[-\s]driven\s+development|scratch\s+orgs?)\b/,
24238
+ /\bunlocked\s+packages?\b/,
24239
+ /\bsingle[-\s]org\b.*\bmulti[-\s]org\b/,
24240
+ /\bdata\s+(retention|archiv)/,
24241
+ /\b(large\s+data\s+volumes?|\bldv\b)/
24242
+ ]
24243
+ },
23575
24244
  {
23576
24245
  intent: "resolve-lookup",
23577
24246
  plane: "vault",
@@ -23599,6 +24268,92 @@ var init_intent_router = __esm({
23599
24268
  /\b(suffix|prefix)\b.*\b(convention|standard)\b/
23600
24269
  ]
23601
24270
  },
24271
+ {
24272
+ // Lightning record pages / FlexiPages — assignment + component breakdown.
24273
+ // "Which Lightning record page is assigned to X", "what components are on
24274
+ // the Y record page" fell through to schema/unrouted (B21.2).
24275
+ intent: "flexipage",
24276
+ plane: "vault",
24277
+ tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
24278
+ liveRequired: false,
24279
+ needsResolve: false,
24280
+ reason: "Lightning record pages (FlexiPage) \u2014 assignment and component breakdown \u2014 are modeled in the vault.",
24281
+ patterns: [
24282
+ /\b(lightning\s+(record\s+)?page|flexipage)s?\b/,
24283
+ /\b(component|assign|which|what)\b.*\brecord\s+pages?\b/,
24284
+ /\brecord\s+pages?\b.*\b(assign|component|layout|for\s+the)\b/
24285
+ ]
24286
+ },
24287
+ {
24288
+ // Picklist-value differences across record types — get_component on each
24289
+ // RecordType shows its picklist value sets. Fell through (B21.4).
24290
+ intent: "record-type-picklist",
24291
+ plane: "vault",
24292
+ tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
24293
+ liveRequired: false,
24294
+ needsResolve: false,
24295
+ reason: "Record-type picklist-value assignments are in the vault \u2014 list the record types, then get_component each to compare picklist values.",
24296
+ patterns: [
24297
+ /\brecord\s+types?\b.*\b(picklist|differ|difference|values?)\b/,
24298
+ /\bpicklist\s+values?\b.*\b(record\s+types?|differ|between|across)\b/,
24299
+ /\bwhich\s+picklist\s+values?\b.*\brecord\s+type/
24300
+ ]
24301
+ },
24302
+ {
24303
+ // Approval processes — list + steps. Fell through to schema/unrouted (B21.6).
24304
+ intent: "approval-process",
24305
+ plane: "vault",
24306
+ tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
24307
+ liveRequired: false,
24308
+ needsResolve: false,
24309
+ reason: "Approval processes and their steps are modeled in the vault (ApprovalProcess).",
24310
+ patterns: [
24311
+ /\bapproval\s+process(es)?\b/,
24312
+ /\bapproval\s+steps?\b/,
24313
+ /\bapproval\b.*\b(steps?|process|who\s+approves?|approvers?|stages?)\b/
24314
+ ]
24315
+ },
24316
+ {
24317
+ // List views inventory (B21.8). Note: ListView is retrieved but not
24318
+ // graph-modeled (notModeled), so relationships are limited — list only.
24319
+ intent: "list-views",
24320
+ plane: "vault",
24321
+ tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
24322
+ liveRequired: false,
24323
+ needsResolve: false,
24324
+ reason: "List views are catalogued in the vault via list_components (ListView). Coverage note: ListView is retrieved but not graph-modeled.",
24325
+ patterns: [/\blist\s+views?\b/]
24326
+ },
24327
+ {
24328
+ // Custom Settings vs Custom Metadata Types — both retrieved as CustomObject.
24329
+ // Disambiguation gap (B21.9).
24330
+ intent: "custom-settings-cmdt",
24331
+ plane: "vault",
24332
+ tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
24333
+ liveRequired: false,
24334
+ needsResolve: false,
24335
+ reason: "Custom Settings and Custom Metadata Types are modeled as CustomObject in the vault \u2014 list + get_component to see what they store.",
24336
+ patterns: [
24337
+ /\bcustom\s+settings?\b/,
24338
+ /\bcustom\s+metadata\s+types?\b/,
24339
+ /\b(cmdt|__mdt)\b/,
24340
+ /\bhierarch(y|ical|ic)\b.*\bsettings?\b/
24341
+ ]
24342
+ },
24343
+ {
24344
+ // Difference between two profiles / permission sets (B21.10).
24345
+ intent: "compare-profiles",
24346
+ plane: "vault",
24347
+ tools: ["sfi.resolve", "sfi.compare_components"],
24348
+ liveRequired: false,
24349
+ needsResolve: true,
24350
+ reason: "Comparing two profiles or permission sets is a vault diff \u2014 resolve both, then compare_components.",
24351
+ patterns: [
24352
+ /\b(difference|differ|compare|versus|vs\.?)\b.*\bprofiles?\b/,
24353
+ /\bprofiles?\b.*\b(difference|differ|compare|versus|vs\b)\b/,
24354
+ /\bcompare\b.*\b(permission\s+sets?|profiles?)\b/
24355
+ ]
24356
+ },
23602
24357
  {
23603
24358
  intent: "schema",
23604
24359
  plane: "vault",
@@ -23633,6 +24388,7 @@ var init_intent_router = __esm({
23633
24388
  }
23634
24389
  for (const rule of RULES) {
23635
24390
  if (rule.patterns.some((p) => p.test(q))) {
24391
+ const suggestedArgs = rule.suggestArgs?.(q);
23636
24392
  return {
23637
24393
  question,
23638
24394
  plane: rule.plane,
@@ -23641,7 +24397,8 @@ var init_intent_router = __esm({
23641
24397
  liveRequired: rule.liveRequired,
23642
24398
  needsResolve: rule.needsResolve,
23643
24399
  reason: rule.reason,
23644
- gap: rule.gap ?? null
24400
+ gap: rule.gap ?? null,
24401
+ ...suggestedArgs !== void 0 ? { suggestedArgs } : {}
23645
24402
  };
23646
24403
  }
23647
24404
  }
@@ -23689,7 +24446,7 @@ var init_intent_router = __esm({
23689
24446
  });
23690
24447
 
23691
24448
  // ../mcp/dist/src/tools/route-question.js
23692
- import { z as z89 } from "zod";
24449
+ import { z as z90 } from "zod";
23693
24450
  var routeQuestionInputSchema, routeTrust, routeQuestionHandler;
23694
24451
  var init_route_question = __esm({
23695
24452
  "../mcp/dist/src/tools/route-question.js"() {
@@ -23697,11 +24454,11 @@ var init_route_question = __esm({
23697
24454
  init_dist();
23698
24455
  init_answer_render();
23699
24456
  init_intent_router();
23700
- routeQuestionInputSchema = z89.object({
24457
+ routeQuestionInputSchema = z90.object({
23701
24458
  /** The user's plain-language question. */
23702
- question: z89.string().min(1),
24459
+ question: z90.string().min(1),
23703
24460
  /** Append a gap entry to the local backlog when the route has one (default true). */
23704
- logGap: z89.boolean().optional()
24461
+ logGap: z90.boolean().optional()
23705
24462
  });
23706
24463
  routeTrust = () => ({
23707
24464
  provenance: "offline_snapshot",
@@ -23732,7 +24489,7 @@ var init_route_question = __esm({
23732
24489
  });
23733
24490
 
23734
24491
  // ../mcp/dist/src/tools/scheduled-job-catalog.js
23735
- import { z as z90 } from "zod";
24492
+ import { z as z91 } from "zod";
23736
24493
  var SCHEDULED_JOB_CATALOG_MAX_CLASSES, scheduledJobCatalogInputSchema, SCHEDULED_JOB_CATALOG_DISCLOSURE, readCronExpressions, readEdgeCronExpression, readIsSchedulable, collectScheduledCalls, compareEntries4, compareCalls, scheduledJobCatalogHandler;
23737
24494
  var init_scheduled_job_catalog = __esm({
23738
24495
  "../mcp/dist/src/tools/scheduled-job-catalog.js"() {
@@ -23740,7 +24497,7 @@ var init_scheduled_job_catalog = __esm({
23740
24497
  init_dist();
23741
24498
  init_src();
23742
24499
  SCHEDULED_JOB_CATALOG_MAX_CLASSES = 500;
23743
- scheduledJobCatalogInputSchema = z90.object({});
24500
+ scheduledJobCatalogInputSchema = z91.object({});
23744
24501
  SCHEDULED_JOB_CATALOG_DISCLOSURE = "Scanning for System.schedule() invocations is heuristic \u2014 the v0.3 Apex scanner detects literal call sites only, NOT runtime registration via Tooling API. Runtime schedules require Tooling API access (CronTrigger / AsyncApexJob). A class flagged `isSchedulable: true` may not currently be scheduled; conversely, a class scheduled via a helper-wrapper or dynamic class load is invisible to the scanner.";
23745
24502
  readCronExpressions = (properties) => {
23746
24503
  const raw = properties["cronExpressions"];
@@ -23829,7 +24586,7 @@ var init_scheduled_job_catalog = __esm({
23829
24586
 
23830
24587
  // ../mcp/dist/src/tools/search-apex-source.js
23831
24588
  import { readFile as readFile14 } from "node:fs/promises";
23832
- import { z as z91 } from "zod";
24589
+ import { z as z92 } from "zod";
23833
24590
  var SEARCH_APEX_SOURCE_MAX_LIMIT, SEARCH_APEX_SOURCE_DEFAULT_LIMIT, APEX_SUFFIXES, searchApexSourceInputSchema, searchApexSourceHandler, buildMatcher, searchFile;
23834
24591
  var init_search_apex_source = __esm({
23835
24592
  "../mcp/dist/src/tools/search-apex-source.js"() {
@@ -23839,10 +24596,10 @@ var init_search_apex_source = __esm({
23839
24596
  SEARCH_APEX_SOURCE_MAX_LIMIT = 200;
23840
24597
  SEARCH_APEX_SOURCE_DEFAULT_LIMIT = 50;
23841
24598
  APEX_SUFFIXES = [".cls", ".trigger"];
23842
- searchApexSourceInputSchema = z91.object({
23843
- query: z91.string().min(1),
23844
- regex: z91.boolean().optional(),
23845
- limit: z91.number().int().min(1).max(SEARCH_APEX_SOURCE_MAX_LIMIT).optional()
24599
+ searchApexSourceInputSchema = z92.object({
24600
+ query: z92.string().min(1),
24601
+ regex: z92.boolean().optional(),
24602
+ limit: z92.number().int().min(1).max(SEARCH_APEX_SOURCE_MAX_LIMIT).optional()
23846
24603
  });
23847
24604
  searchApexSourceHandler = async (ctx, input2) => {
23848
24605
  const limit = input2.limit ?? SEARCH_APEX_SOURCE_DEFAULT_LIMIT;
@@ -23919,7 +24676,7 @@ var init_search_apex_source = __esm({
23919
24676
  });
23920
24677
 
23921
24678
  // ../mcp/dist/src/tools/search-components.js
23922
- import { z as z92 } from "zod";
24679
+ import { z as z93 } from "zod";
23923
24680
  var SEARCH_MAX_LIMIT2, searchComponentsInputSchema, SUGGESTIONS_NOTE, searchComponentsHandler;
23924
24681
  var init_search_components = __esm({
23925
24682
  "../mcp/dist/src/tools/search-components.js"() {
@@ -23927,10 +24684,10 @@ var init_search_components = __esm({
23927
24684
  init_dist();
23928
24685
  init_src();
23929
24686
  SEARCH_MAX_LIMIT2 = 100;
23930
- searchComponentsInputSchema = z92.object({
23931
- query: z92.string().min(1),
23932
- limit: z92.number().int().min(1).max(SEARCH_MAX_LIMIT2).optional(),
23933
- types: z92.array(z92.string()).optional()
24687
+ searchComponentsInputSchema = z93.object({
24688
+ query: z93.string().min(1),
24689
+ limit: z93.number().int().min(1).max(SEARCH_MAX_LIMIT2).optional(),
24690
+ types: z93.array(z93.string()).optional()
23934
24691
  });
23935
24692
  SUGGESTIONS_NOTE = "No exact substring matches. These are typo-tolerant, fuzzy-ranked guesses (heuristic) at what you may have meant \u2014 verify the canonical id before acting.";
23936
24693
  searchComponentsHandler = async (ctx, input2) => {
@@ -23981,7 +24738,7 @@ var init_search_components = __esm({
23981
24738
 
23982
24739
  // ../mcp/dist/src/tools/search-flow-metadata.js
23983
24740
  import { readFile as readFile15 } from "node:fs/promises";
23984
- import { z as z93 } from "zod";
24741
+ import { z as z94 } from "zod";
23985
24742
  var SEARCH_FLOW_METADATA_MAX_LIMIT, SEARCH_FLOW_METADATA_DEFAULT_LIMIT, FLOW_FILE_SUFFIX, searchFlowMetadataInputSchema, searchFlowMetadataHandler, buildMatcher2, searchFile2;
23986
24743
  var init_search_flow_metadata = __esm({
23987
24744
  "../mcp/dist/src/tools/search-flow-metadata.js"() {
@@ -23991,10 +24748,10 @@ var init_search_flow_metadata = __esm({
23991
24748
  SEARCH_FLOW_METADATA_MAX_LIMIT = 200;
23992
24749
  SEARCH_FLOW_METADATA_DEFAULT_LIMIT = 50;
23993
24750
  FLOW_FILE_SUFFIX = ".flow-meta.xml";
23994
- searchFlowMetadataInputSchema = z93.object({
23995
- query: z93.string().min(1),
23996
- regex: z93.boolean().optional(),
23997
- limit: z93.number().int().min(1).max(SEARCH_FLOW_METADATA_MAX_LIMIT).optional()
24751
+ searchFlowMetadataInputSchema = z94.object({
24752
+ query: z94.string().min(1),
24753
+ regex: z94.boolean().optional(),
24754
+ limit: z94.number().int().min(1).max(SEARCH_FLOW_METADATA_MAX_LIMIT).optional()
23998
24755
  });
23999
24756
  searchFlowMetadataHandler = async (ctx, input2) => {
24000
24757
  const limit = input2.limit ?? SEARCH_FLOW_METADATA_DEFAULT_LIMIT;
@@ -24071,14 +24828,14 @@ var init_search_flow_metadata = __esm({
24071
24828
  });
24072
24829
 
24073
24830
  // ../mcp/dist/src/tools/snapshot-trend.js
24074
- import { z as z94 } from "zod";
24831
+ import { z as z95 } from "zod";
24075
24832
  var trendInputSchema, TREND_DISCLOSURE, trendHandler, churnInputSchema, CHURN_DISCLOSURE, diffNodeSets, churnHandler;
24076
24833
  var init_snapshot_trend = __esm({
24077
24834
  "../mcp/dist/src/tools/snapshot-trend.js"() {
24078
24835
  "use strict";
24079
24836
  init_dist();
24080
24837
  init_src2();
24081
- trendInputSchema = z94.object({});
24838
+ trendInputSchema = z95.object({});
24082
24839
  TREND_DISCLOSURE = "Trend points come from persisted `sfi snapshot create` captures. Successful `sfi refresh` auto-captures a snapshot unless `meta/config.json` sets `snapshotOnRefresh: false`.";
24083
24840
  trendHandler = async (ctx, _input) => {
24084
24841
  const listed = await listSnapshots(ctx.vaultRoot);
@@ -24104,9 +24861,9 @@ var init_snapshot_trend = __esm({
24104
24861
  }
24105
24862
  });
24106
24863
  };
24107
- churnInputSchema = z94.object({
24108
- fromLabel: z94.string().min(1).optional(),
24109
- toLabel: z94.string().min(1).optional()
24864
+ churnInputSchema = z95.object({
24865
+ fromLabel: z95.string().min(1).optional(),
24866
+ toLabel: z95.string().min(1).optional()
24110
24867
  });
24111
24868
  CHURN_DISCLOSURE = 'Churn compares two persisted snapshot labels by structural id/hash only \u2014 not semantic "risk". Use `sfi.diff_snapshots` for the full slice.';
24112
24869
  diffNodeSets = (fromNodes, toNodes) => {
@@ -24244,7 +25001,7 @@ var init_coverage_trust = __esm({
24244
25001
  });
24245
25002
 
24246
25003
  // ../mcp/dist/src/tools/unassigned-permission-sets.js
24247
- import { z as z95 } from "zod";
25004
+ import { z as z96 } from "zod";
24248
25005
  var UNASSIGNED_MAX_LIMIT, UNASSIGNED_DEFAULT_LIMIT, LIST_PAGE_SIZE10, BOUNDARIES11, unassignedPermissionSetsInputSchema, namespacePrefixOf2, propertyString3, propertyBoolean2, propertyNumberOrNull, detectEnrichmentStatus, compareById2, unassignedPermissionSetsHandler;
24249
25006
  var init_unassigned_permission_sets = __esm({
24250
25007
  "../mcp/dist/src/tools/unassigned-permission-sets.js"() {
@@ -24258,10 +25015,10 @@ var init_unassigned_permission_sets = __esm({
24258
25015
  "tooling-api freshness reflects when v1.7 R2 last ran; live org assignment changes since then are not reflected. To refresh, run `sfi refresh --classify-permissions`.",
24259
25016
  "unknownAssignmentCount values require v1.7 Tooling API enrichment to resolve \u2014 they are NOT counted toward unassignedCount."
24260
25017
  ]);
24261
- unassignedPermissionSetsInputSchema = z95.object({
24262
- includeManagedPackage: z95.boolean().optional(),
24263
- includeMutingPermissionSets: z95.boolean().optional(),
24264
- limit: z95.number().int().min(1).max(UNASSIGNED_MAX_LIMIT).optional()
25018
+ unassignedPermissionSetsInputSchema = z96.object({
25019
+ includeManagedPackage: z96.boolean().optional(),
25020
+ includeMutingPermissionSets: z96.boolean().optional(),
25021
+ limit: z96.number().int().min(1).max(UNASSIGNED_MAX_LIMIT).optional()
24265
25022
  });
24266
25023
  namespacePrefixOf2 = (apiName) => {
24267
25024
  const idx = apiName.indexOf("__");
@@ -24402,7 +25159,7 @@ var init_unassigned_permission_sets = __esm({
24402
25159
  });
24403
25160
 
24404
25161
  // ../mcp/dist/src/tools/unused-components.js
24405
- import { z as z96 } from "zod";
25162
+ import { z as z97 } from "zod";
24406
25163
  var UNUSED_MAX_LIMIT, UNUSED_DEFAULT_LIMIT, LIST_PAGE_SIZE11, DEFAULT_UNUSED_TYPES, COMPONENT_TYPES3, unusedComponentsInputSchema, INVISIBLE_REFERENCES_NOTES, DEFAULT_INVISIBLE_REFERENCES_NOTE, noteForType, ENTRY_POINT_TYPES, isInactiveEntryPoint, isUnused, isTestApexClass, compareById3, compareGlobally, scanType, unusedComponentsHandler;
24407
25164
  var init_unused_components = __esm({
24408
25165
  "../mcp/dist/src/tools/unused-components.js"() {
@@ -24477,9 +25234,9 @@ var init_unused_components = __esm({
24477
25234
  "CustomMetadataRecord",
24478
25235
  "CustomSettingRecord"
24479
25236
  ];
24480
- unusedComponentsInputSchema = z96.object({
24481
- types: z96.array(z96.enum(COMPONENT_TYPES3)).optional(),
24482
- limit: z96.number().int().min(1).max(UNUSED_MAX_LIMIT).optional()
25237
+ unusedComponentsInputSchema = z97.object({
25238
+ types: z97.array(z97.enum(COMPONENT_TYPES3)).optional(),
25239
+ limit: z97.number().int().min(1).max(UNUSED_MAX_LIMIT).optional()
24483
25240
  });
24484
25241
  INVISIBLE_REFERENCES_NOTES = Object.freeze({
24485
25242
  ApexClass: "Dynamic Apex (Type.forName), reflective dispatch, and Tooling API references are invisible to the v1.x scanner; spot-check before deleting.",
@@ -24623,7 +25380,7 @@ var init_unused_components = __esm({
24623
25380
  });
24624
25381
 
24625
25382
  // ../mcp/dist/src/tools/unused-fields-deep.js
24626
- import { z as z97 } from "zod";
25383
+ import { z as z98 } from "zod";
24627
25384
  var UNUSED_FIELDS_DEEP_REQUIRED_COVERAGE, completenessForUnusedFieldsDeep, UNUSED_FIELDS_DEEP_MAX_LIMIT, UNUSED_FIELDS_DEEP_DEFAULT_LIMIT, LIST_PAGE_SIZE12, FRONTEND_REFERENCE_TYPES, INVISIBILITY_WARNINGS, BOUNDARIES12, unusedFieldsDeepInputSchema, containsApiName, propertyString4, propertyStringArray2, criteriaItemsText, layoutSectionFields, relatedListFields, buildCorpora, checkNoIncomingEdges, checkNoFormulaTextReferences, checkNoLayoutReferences, checkNoSoqlStringReferences, checkNoUnresolvedApexReferences, checkNoLwcAuraVfReferences, checkNoConditionalContextReferences, checkNoIntegrationExposure, parseParentApiName2, fieldTypeOf2, isCustomField3, namespacePrefixOf3, computeConfidence, recommendedActionFor, compareById4, unusedFieldsDeepHandler;
24628
25385
  var init_unused_fields_deep = __esm({
24629
25386
  "../mcp/dist/src/tools/unused-fields-deep.js"() {
@@ -24674,11 +25431,11 @@ var init_unused_fields_deep = __esm({
24674
25431
  BOUNDARIES12 = Object.freeze([
24675
25432
  "even after checking formula expressions, layout placements, SOQL strings, conditional contexts, LWC / Aura / VF references, and integration exposure, the scanner cannot see dynamic SOQL, LWC dynamic field access (record[fieldName]), Apex reflective access (obj.get(...)), or runtime metadata references. Treat a 'high-confidence unused' flag as 'no static evidence of use' rather than 'definitely unused.'"
24676
25433
  ]);
24677
- unusedFieldsDeepInputSchema = z97.object({
24678
- parentObjectFilter: z97.string().min(1).optional(),
24679
- excludeManagedPackage: z97.boolean().optional(),
24680
- excludeStandardFields: z97.boolean().optional(),
24681
- limit: z97.number().int().min(1).max(UNUSED_FIELDS_DEEP_MAX_LIMIT).optional()
25434
+ unusedFieldsDeepInputSchema = z98.object({
25435
+ parentObjectFilter: z98.string().min(1).optional(),
25436
+ excludeManagedPackage: z98.boolean().optional(),
25437
+ excludeStandardFields: z98.boolean().optional(),
25438
+ limit: z98.number().int().min(1).max(UNUSED_FIELDS_DEEP_MAX_LIMIT).optional()
24682
25439
  });
24683
25440
  containsApiName = (text, apiName) => text.toLowerCase().includes(apiName.toLowerCase());
24684
25441
  propertyString4 = (node, key) => {
@@ -25056,7 +25813,7 @@ var init_unused_fields_deep = __esm({
25056
25813
  });
25057
25814
 
25058
25815
  // ../mcp/dist/src/tools/tech-debt-score.js
25059
- import { z as z98 } from "zod";
25816
+ import { z as z99 } from "zod";
25060
25817
  var LIST_PAGE_SIZE13, DEFAULT_WEIGHTS, Q115_DISCLOSURE, SCORE_DIRECTION_DISCLOSURE, WEIGHT_SCHEME_DISCLOSURE, SCALE_FACTORS, ALLOWED_WEIGHT_KEYS, techDebtScoreInputSchema, bandFor, contribFor, recommendationFor, weightedScore, computeApiVersionDistribution, computeCodeQualityCounts, computeFreshnessCounts, techDebtScoreHandler;
25061
25818
  var init_tech_debt_score = __esm({
25062
25819
  "../mcp/dist/src/tools/tech-debt-score.js"() {
@@ -25102,8 +25859,8 @@ var init_tech_debt_score = __esm({
25102
25859
  "apiVersions",
25103
25860
  "unassignedGrants"
25104
25861
  ];
25105
- techDebtScoreInputSchema = z98.object({
25106
- excludeCategories: z98.array(z98.enum([
25862
+ techDebtScoreInputSchema = z99.object({
25863
+ excludeCategories: z99.array(z99.enum([
25107
25864
  "deadWeight",
25108
25865
  "legacyAutomation",
25109
25866
  "codeQuality",
@@ -25114,13 +25871,13 @@ var init_tech_debt_score = __esm({
25114
25871
  // `.passthrough()` keeps unknown weight keys in the parsed input so
25115
25872
  // the handler can surface them in a structured `invalid-query`
25116
25873
  // refusal rather than silently dropping them at the Zod boundary.
25117
- weights: z98.object({
25118
- deadWeight: z98.number().min(0).max(1).optional(),
25119
- legacyAutomation: z98.number().min(0).max(1).optional(),
25120
- codeQuality: z98.number().min(0).max(1).optional(),
25121
- freshness: z98.number().min(0).max(1).optional(),
25122
- apiVersions: z98.number().min(0).max(1).optional(),
25123
- unassignedGrants: z98.number().min(0).max(1).optional()
25874
+ weights: z99.object({
25875
+ deadWeight: z99.number().min(0).max(1).optional(),
25876
+ legacyAutomation: z99.number().min(0).max(1).optional(),
25877
+ codeQuality: z99.number().min(0).max(1).optional(),
25878
+ freshness: z99.number().min(0).max(1).optional(),
25879
+ apiVersions: z99.number().min(0).max(1).optional(),
25880
+ unassignedGrants: z99.number().min(0).max(1).optional()
25124
25881
  }).passthrough().optional()
25125
25882
  });
25126
25883
  bandFor = (score) => {
@@ -25480,12 +26237,13 @@ var init_tech_debt_score = __esm({
25480
26237
  });
25481
26238
 
25482
26239
  // ../mcp/dist/src/tools/synthesis-reports.js
25483
- import { z as z99 } from "zod";
25484
- var synthesisInputSchema, orgRiskReportInputSchema, fieldCleanupCandidatesInputSchema, automationRiskReportInputSchema, permissionRiskReportInputSchema, releaseReadinessReportInputSchema, SYNTHESIS_DISCLOSURE, rankSeverity, sortFindings, coverageTrust, orgRiskReportHandler, fieldCleanupCandidatesHandler, automationRiskReportHandler, permissionRiskReportHandler, releaseReadinessReportHandler;
26240
+ import { z as z100 } from "zod";
26241
+ var synthesisInputSchema, orgRiskReportInputSchema, fieldCleanupCandidatesInputSchema, automationRiskReportInputSchema, permissionRiskReportInputSchema, releaseReadinessReportInputSchema, SYNTHESIS_DISCLOSURE, rankSeverity, sortFindings, coverageTrust, orgRiskReportHandler, fieldCleanupCandidatesHandler, automationRiskReportHandler, SYSTEM_PERMISSION_SEVERITY, PRIVILEGE_SCAN_CAP, ESCALATION_EXAMPLE_CAP, ROSTER_CAP, grantorFinding, analyzeOverPrivilege, permissionRiskReportHandler, releaseReadinessReportHandler;
25485
26242
  var init_synthesis_reports = __esm({
25486
26243
  "../mcp/dist/src/tools/synthesis-reports.js"() {
25487
26244
  "use strict";
25488
26245
  init_dist();
26246
+ init_src();
25489
26247
  init_src2();
25490
26248
  init_coverage_trust();
25491
26249
  init_crud_fls_audit();
@@ -25495,8 +26253,8 @@ var init_synthesis_reports = __esm({
25495
26253
  init_tech_debt_score();
25496
26254
  init_unassigned_permission_sets();
25497
26255
  init_unused_fields_deep();
25498
- synthesisInputSchema = z99.object({
25499
- limit: z99.number().int().min(1).max(500).optional()
26256
+ synthesisInputSchema = z100.object({
26257
+ limit: z100.number().int().min(1).max(500).optional()
25500
26258
  });
25501
26259
  orgRiskReportInputSchema = synthesisInputSchema;
25502
26260
  fieldCleanupCandidatesInputSchema = synthesisInputSchema;
@@ -25650,9 +26408,209 @@ var init_synthesis_reports = __esm({
25650
26408
  }
25651
26409
  });
25652
26410
  };
26411
+ SYSTEM_PERMISSION_SEVERITY = {
26412
+ ModifyAllData: "critical",
26413
+ ViewAllData: "critical",
26414
+ AuthorApex: "high",
26415
+ CustomizeApplication: "high",
26416
+ ManageUsers: "high",
26417
+ ManageInternalUsers: "high",
26418
+ ManageProfilesPermissionsets: "high",
26419
+ ModifyMetadata: "high",
26420
+ ManageSharing: "high",
26421
+ ManageRoles: "high",
26422
+ ManagePasswordPolicies: "high",
26423
+ ManageLoginAccessPolicies: "high",
26424
+ ViewAllUsers: "medium",
26425
+ ViewSetup: "medium",
26426
+ PasswordNeverExpires: "medium"
26427
+ };
26428
+ PRIVILEGE_SCAN_CAP = 500;
26429
+ ESCALATION_EXAMPLE_CAP = 5;
26430
+ ROSTER_CAP = 100;
26431
+ grantorFinding = (node, type, riskyPerms, modifyAllObjects, viewAllObjects, worst, examples) => {
26432
+ if (riskyPerms.length === 0 && modifyAllObjects === 0 && viewAllObjects === 0) {
26433
+ return null;
26434
+ }
26435
+ const parts = [];
26436
+ if (riskyPerms.length > 0) {
26437
+ parts.push(`system perms: ${[...riskyPerms].sort().join(", ")}`);
26438
+ }
26439
+ if (modifyAllObjects > 0) {
26440
+ parts.push(`Modify All on ${modifyAllObjects} object(s)`);
26441
+ }
26442
+ if (viewAllObjects > 0) {
26443
+ parts.push(`View All on ${viewAllObjects} object(s)`);
26444
+ }
26445
+ return {
26446
+ rank: 0,
26447
+ severity: worst ?? "medium",
26448
+ category: "over-privilege",
26449
+ summary: `${type} ${node.apiName} grants ${parts.join("; ")}`,
26450
+ evidence: [node.id, ...examples],
26451
+ confidence: "declared"
26452
+ };
26453
+ };
26454
+ analyzeOverPrivilege = async (ctx, limit) => {
26455
+ const findings = [];
26456
+ const modifyAllDataGrantors = [];
26457
+ const viewAllDataGrantors = [];
26458
+ const scanned = { profiles: 0, permissionSets: 0, permissionSetGroups: 0 };
26459
+ const permsetRisk = /* @__PURE__ */ new Map();
26460
+ for (const type of ["Profile", "PermissionSet"]) {
26461
+ const nodesResult = await listNodesByType(ctx.graph, type, {
26462
+ limit: PRIVILEGE_SCAN_CAP
26463
+ });
26464
+ if (!nodesResult.ok)
26465
+ continue;
26466
+ for (const node of nodesResult.value) {
26467
+ if (type === "Profile")
26468
+ scanned.profiles += 1;
26469
+ else
26470
+ scanned.permissionSets += 1;
26471
+ const riskyPerms = [];
26472
+ let worst = null;
26473
+ const ups = node.properties["userPermissions"];
26474
+ if (Array.isArray(ups)) {
26475
+ for (const perm of ups) {
26476
+ if (typeof perm !== "string")
26477
+ continue;
26478
+ const sev = SYSTEM_PERMISSION_SEVERITY[perm];
26479
+ if (sev === void 0)
26480
+ continue;
26481
+ riskyPerms.push(perm);
26482
+ if (worst === null || rankSeverity(sev) > rankSeverity(worst)) {
26483
+ worst = sev;
26484
+ }
26485
+ if (perm === "ModifyAllData" && modifyAllDataGrantors.length < ROSTER_CAP) {
26486
+ modifyAllDataGrantors.push(node.id);
26487
+ }
26488
+ if (perm === "ViewAllData" && viewAllDataGrantors.length < ROSTER_CAP) {
26489
+ viewAllDataGrantors.push(node.id);
26490
+ }
26491
+ }
26492
+ }
26493
+ let modifyAllObjects = 0;
26494
+ let viewAllObjects = 0;
26495
+ const examples = [];
26496
+ const outResult = await listEdges(ctx.graph, node.id, {
26497
+ direction: "out",
26498
+ edgeType: "grantedBy"
26499
+ });
26500
+ if (outResult.ok) {
26501
+ for (const edge of outResult.value) {
26502
+ if (!edge.toId.startsWith("CustomObject:"))
26503
+ continue;
26504
+ if (edge.properties["modifyAllRecords"] === true) {
26505
+ modifyAllObjects += 1;
26506
+ if (examples.length < ESCALATION_EXAMPLE_CAP)
26507
+ examples.push(edge.toId);
26508
+ } else if (edge.properties["viewAllRecords"] === true) {
26509
+ viewAllObjects += 1;
26510
+ if (examples.length < ESCALATION_EXAMPLE_CAP)
26511
+ examples.push(edge.toId);
26512
+ }
26513
+ }
26514
+ }
26515
+ if (modifyAllObjects > 0 && (worst === null || rankSeverity("high") > rankSeverity(worst))) {
26516
+ worst = "high";
26517
+ } else if (viewAllObjects > 0 && worst === null) {
26518
+ worst = "medium";
26519
+ }
26520
+ if (type === "PermissionSet") {
26521
+ permsetRisk.set(node.id, {
26522
+ perms: riskyPerms,
26523
+ modAll: modifyAllObjects,
26524
+ viewAll: viewAllObjects
26525
+ });
26526
+ }
26527
+ const finding = grantorFinding(node, type, riskyPerms, modifyAllObjects, viewAllObjects, worst, examples);
26528
+ if (finding !== null)
26529
+ findings.push(finding);
26530
+ }
26531
+ }
26532
+ const psgResult = await listNodesByType(ctx.graph, "PermissionSetGroup", {
26533
+ limit: PRIVILEGE_SCAN_CAP
26534
+ });
26535
+ if (psgResult.ok) {
26536
+ for (const psg of psgResult.value) {
26537
+ scanned.permissionSetGroups += 1;
26538
+ const members = psg.properties["permissionSets"];
26539
+ if (!Array.isArray(members))
26540
+ continue;
26541
+ const conferred = /* @__PURE__ */ new Set();
26542
+ let aggModAll = 0;
26543
+ let aggViewAll = 0;
26544
+ const riskyMembers = [];
26545
+ for (const member of members) {
26546
+ if (typeof member !== "string")
26547
+ continue;
26548
+ const risk = permsetRisk.get(`PermissionSet:${member}`);
26549
+ if (risk === void 0)
26550
+ continue;
26551
+ if (risk.perms.length === 0 && risk.modAll === 0 && risk.viewAll === 0) {
26552
+ continue;
26553
+ }
26554
+ for (const perm of risk.perms)
26555
+ conferred.add(perm);
26556
+ aggModAll += risk.modAll;
26557
+ aggViewAll += risk.viewAll;
26558
+ if (riskyMembers.length < ESCALATION_EXAMPLE_CAP) {
26559
+ riskyMembers.push(`PermissionSet:${member}`);
26560
+ }
26561
+ }
26562
+ if (conferred.size === 0 && aggModAll === 0 && aggViewAll === 0)
26563
+ continue;
26564
+ let worst = "medium";
26565
+ if (conferred.has("ModifyAllData") || conferred.has("ViewAllData")) {
26566
+ worst = "critical";
26567
+ } else if (aggModAll > 0 || [...conferred].some((p) => SYSTEM_PERMISSION_SEVERITY[p] === "high")) {
26568
+ worst = "high";
26569
+ }
26570
+ if (conferred.has("ModifyAllData") && modifyAllDataGrantors.length < ROSTER_CAP) {
26571
+ modifyAllDataGrantors.push(psg.id);
26572
+ }
26573
+ if (conferred.has("ViewAllData") && viewAllDataGrantors.length < ROSTER_CAP) {
26574
+ viewAllDataGrantors.push(psg.id);
26575
+ }
26576
+ const muting = psg.properties["mutingPermissionSets"];
26577
+ const hasMuting = Array.isArray(muting) && muting.length > 0;
26578
+ const parts = [];
26579
+ if (conferred.size > 0) {
26580
+ parts.push(`system perms: ${[...conferred].sort().join(", ")}`);
26581
+ }
26582
+ if (aggModAll > 0) {
26583
+ parts.push(`Modify All on ${aggModAll} object(s) (aggregate)`);
26584
+ }
26585
+ if (aggViewAll > 0) {
26586
+ parts.push(`View All on ${aggViewAll} object(s) (aggregate)`);
26587
+ }
26588
+ findings.push({
26589
+ rank: 0,
26590
+ severity: worst,
26591
+ category: "over-privilege",
26592
+ summary: `PermissionSetGroup ${psg.apiName} confers via member permission set(s) ${parts.join("; ")}` + (hasMuting ? " (has a muting permission set \u2014 effective perms may be lower)" : ""),
26593
+ evidence: [psg.id, ...riskyMembers],
26594
+ confidence: "declared"
26595
+ });
26596
+ }
26597
+ }
26598
+ const ranked = [...findings].sort((a, b) => rankSeverity(b.severity) - rankSeverity(a.severity));
26599
+ return {
26600
+ findings: ranked.slice(0, limit),
26601
+ privilege: {
26602
+ modifyAllDataGrantors: [...modifyAllDataGrantors].sort(),
26603
+ viewAllDataGrantors: [...viewAllDataGrantors].sort(),
26604
+ overPrivilegedGrantorCount: findings.length,
26605
+ scanned
26606
+ }
26607
+ };
26608
+ };
25653
26609
  permissionRiskReportHandler = async (ctx, input2) => {
25654
26610
  const limit = input2.limit ?? 50;
25655
26611
  const findings = [];
26612
+ const overPriv = await analyzeOverPrivilege(ctx, limit);
26613
+ findings.push(...overPriv.findings);
25656
26614
  const unassigned = await unassignedPermissionSetsHandler(ctx, { limit });
25657
26615
  if (unassigned.ok) {
25658
26616
  for (const row of unassigned.value.data.unassigned.slice(0, limit)) {
@@ -25688,6 +26646,7 @@ var init_synthesis_reports = __esm({
25688
26646
  data: {
25689
26647
  findings: sortFindings(findings),
25690
26648
  auditTotals,
26649
+ privilege: overPriv.privilege,
25691
26650
  trust: coverageTrust(ctx),
25692
26651
  disclosure: SYNTHESIS_DISCLOSURE
25693
26652
  },
@@ -25729,8 +26688,109 @@ var init_synthesis_reports = __esm({
25729
26688
  }
25730
26689
  });
25731
26690
 
26691
+ // ../mcp/dist/src/tools/synthesize-answer.js
26692
+ import { z as z101 } from "zod";
26693
+ var CANONICAL_ID_WHOLE, CANONICAL_ID_INLINE, CAVEAT_KEY, FACT_KEY, COUNT_ARRAY_KEY, MAX_CITATIONS, MAX_BULLETS, MAX_CAVEATS, DISCLOSURE8, synthesizeAnswerInputSchema, pushCapped, collect, parseCitation, synthesizeAnswerHandler;
26694
+ var init_synthesize_answer = __esm({
26695
+ "../mcp/dist/src/tools/synthesize-answer.js"() {
26696
+ "use strict";
26697
+ init_dist();
26698
+ CANONICAL_ID_WHOLE = /^[A-Z][A-Za-z0-9]+:[^\s]+$/;
26699
+ CANONICAL_ID_INLINE = /\b[A-Z][A-Za-z0-9]+:[A-Za-z0-9_./-]*[A-Za-z0-9_]/g;
26700
+ CAVEAT_KEY = /^(disclosure|caveat|caveats|note|notes|boundary|boundaries|limitation|limitations|notModeledNote|honesty|warning|warnings)$/i;
26701
+ FACT_KEY = /^(count|total|totalCount|totalClassCount|totalFindingCount|totalGapsCount|verdict|disposition|status|coverageStatus|riskLevel|truncated|notModeled|plane|intent|confidence|matchKind|fieldLabel|fieldId|piiClassification|piiCategory)$/;
26702
+ COUNT_ARRAY_KEY = /^(grants|findings|gaps|components|candidates|edges|nodes|reasoning|viaApexAccess|fields|matches|classes|tools)$/;
26703
+ MAX_CITATIONS = 200;
26704
+ MAX_BULLETS = 60;
26705
+ MAX_CAVEATS = 40;
26706
+ DISCLOSURE8 = "This answer skeleton is composed ONLY from the JSON supplied in `input`: every citation is an id that appears verbatim in that input, every caveat is carried from it, and no components, counts, or facts were invented. When a `draft` is supplied, `hallucinatedIds` lists canonical ids in the draft that are NOT in the source \u2014 remove or re-ground them before answering. Prose wording is the caller\u2019s job; this tool guarantees grounding, not sentences.";
26707
+ synthesizeAnswerInputSchema = z101.object({
26708
+ input: z101.unknown(),
26709
+ question: z101.string().optional(),
26710
+ draft: z101.string().optional()
26711
+ });
26712
+ pushCapped = (arr, s, cap) => {
26713
+ if (arr.length < cap && !arr.includes(s))
26714
+ arr.push(s);
26715
+ };
26716
+ collect = (value, key, out) => {
26717
+ if (typeof value === "string") {
26718
+ if (out.ids.size < MAX_CITATIONS && CANONICAL_ID_WHOLE.test(value)) {
26719
+ out.ids.add(value);
26720
+ }
26721
+ if (key !== null && CAVEAT_KEY.test(key) && value.trim().length > 0) {
26722
+ pushCapped(out.caveats, value, MAX_CAVEATS);
26723
+ } else if (key !== null && FACT_KEY.test(key)) {
26724
+ pushCapped(out.bullets, `${key}: ${value}`, MAX_BULLETS);
26725
+ }
26726
+ return;
26727
+ }
26728
+ if (typeof value === "number" || typeof value === "boolean") {
26729
+ if (key !== null && FACT_KEY.test(key)) {
26730
+ pushCapped(out.bullets, `${key}: ${String(value)}`, MAX_BULLETS);
26731
+ }
26732
+ return;
26733
+ }
26734
+ if (Array.isArray(value)) {
26735
+ if (key !== null && CAVEAT_KEY.test(key)) {
26736
+ for (const item of value) {
26737
+ if (typeof item === "string" && item.trim().length > 0) {
26738
+ pushCapped(out.caveats, item, MAX_CAVEATS);
26739
+ }
26740
+ }
26741
+ } else if (key !== null && COUNT_ARRAY_KEY.test(key)) {
26742
+ pushCapped(out.bullets, `${key}: ${value.length} item(s)`, MAX_BULLETS);
26743
+ }
26744
+ for (const item of value)
26745
+ collect(item, null, out);
26746
+ return;
26747
+ }
26748
+ if (value !== null && typeof value === "object") {
26749
+ for (const [k, v] of Object.entries(value))
26750
+ collect(v, k, out);
26751
+ }
26752
+ };
26753
+ parseCitation = (id) => {
26754
+ const colon = id.indexOf(":");
26755
+ return {
26756
+ id,
26757
+ type: id.slice(0, colon),
26758
+ apiName: id.slice(colon + 1)
26759
+ };
26760
+ };
26761
+ synthesizeAnswerHandler = async (ctx, input2) => {
26762
+ const out = { ids: /* @__PURE__ */ new Set(), caveats: [], bullets: [] };
26763
+ collect(input2.input, null, out);
26764
+ const citations = [...out.ids].sort().map(parseCitation);
26765
+ let hallucinatedIds;
26766
+ let groundedIds;
26767
+ if (input2.draft !== void 0) {
26768
+ const draftIds = [...new Set(input2.draft.match(CANONICAL_ID_INLINE) ?? [])];
26769
+ groundedIds = draftIds.filter((id) => out.ids.has(id)).sort();
26770
+ hallucinatedIds = draftIds.filter((id) => !out.ids.has(id)).sort();
26771
+ }
26772
+ const qPrefix = input2.question !== void 0 && input2.question.trim().length > 0 ? `Q: ${input2.question.trim()} \u2014 ` : "";
26773
+ const summary = `${qPrefix}Grounded in the supplied tool output: ${citations.length} component(s) cited, ${out.bullets.length} key fact(s), ${out.caveats.length} caveat(s)` + (hallucinatedIds !== void 0 ? `, ${hallucinatedIds.length} ungrounded id(s) in the draft` : "") + ".";
26774
+ return ok({
26775
+ data: {
26776
+ summary,
26777
+ bullets: out.bullets,
26778
+ citations,
26779
+ caveats: out.caveats,
26780
+ ...input2.draft !== void 0 ? { hallucinatedIds: hallucinatedIds ?? [], groundedIds: groundedIds ?? [] } : {},
26781
+ disclosure: DISCLOSURE8
26782
+ },
26783
+ vaultState: {
26784
+ sourceTreeHash: ctx.manifest.sourceTreeHash,
26785
+ refreshedAt: ctx.manifest.refreshedAt
26786
+ }
26787
+ });
26788
+ };
26789
+ }
26790
+ });
26791
+
25732
26792
  // ../mcp/dist/src/tools/test-coverage-for-method.js
25733
- import { z as z100 } from "zod";
26793
+ import { z as z102 } from "zod";
25734
26794
  var COVERAGE_BFS_DEPTH, COVERAGE_EDGE_TYPES, APEX_CLASS_PREFIX7, APEX_TRIGGER_PREFIX5, COVERAGE_DISCLOSURE2, testCoverageForMethodInputSchema, isApexCallable4, isTestClass5, upstreamWalk2, testCoverageForMethodHandler;
25735
26795
  var init_test_coverage_for_method = __esm({
25736
26796
  "../mcp/dist/src/tools/test-coverage-for-method.js"() {
@@ -25746,9 +26806,9 @@ var init_test_coverage_for_method = __esm({
25746
26806
  APEX_CLASS_PREFIX7 = "ApexClass:";
25747
26807
  APEX_TRIGGER_PREFIX5 = "ApexTrigger:";
25748
26808
  COVERAGE_DISCLOSURE2 = "v2.7 test_coverage_for_method ships CLASS granularity (method-level promised in v2.7.1). The methodName input is echoed verbatim into the response but does NOT subset the upstream walk. The upstream walk follows both callsApex and dispatchesAsync incoming edges, so coverage exercised via async dispatch (Database.executeBatch, System.enqueueJob, System.schedule) is included. Dynamic dispatch (Type.forName) and reflective invocation are still invisible. BFS is capped at depth 3; coverage chains longer than 3 hops surface as uncovered even when they exist.";
25749
- testCoverageForMethodInputSchema = z100.object({
25750
- classApiName: z100.string().min(1),
25751
- methodName: z100.string().min(1).optional()
26809
+ testCoverageForMethodInputSchema = z102.object({
26810
+ classApiName: z102.string().min(1),
26811
+ methodName: z102.string().min(1).optional()
25752
26812
  });
25753
26813
  isApexCallable4 = (id) => id.startsWith(APEX_CLASS_PREFIX7) || id.startsWith(APEX_TRIGGER_PREFIX5);
25754
26814
  isTestClass5 = (node) => node.properties["isTest"] === true;
@@ -25848,8 +26908,8 @@ var init_test_coverage_for_method = __esm({
25848
26908
  });
25849
26909
 
25850
26910
  // ../mcp/dist/src/tools/test-coverage-gaps.js
25851
- import { z as z101 } from "zod";
25852
- var LIST_PAGE_SIZE14, CLASS_FILTER_MAX_SIZE, MAX_COVERAGE_DEPTH, MEANINGFUL_ASSERTION_DISCLOSURE, DYNAMIC_DISPATCH_DISCLOSURE, DEPTH_CAP_DISCLOSURE, testCoverageGapsInputSchema, collectFakeAssertions2, isTestClass6, collectCoveringTestClasses, recommendationFor2, compareGapById, emptyByStatus, testCoverageGapsHandler;
26911
+ import { z as z103 } from "zod";
26912
+ var LIST_PAGE_SIZE14, CLASS_FILTER_MAX_SIZE, TEST_COVERAGE_GAPS_MAX_LIMIT, TEST_COVERAGE_GAPS_DEFAULT_LIMIT, TEST_COVERAGE_GAPS_PAYLOAD_BUDGET_BYTES, MAX_COVERAGE_DEPTH, MEANINGFUL_ASSERTION_DISCLOSURE, DYNAMIC_DISPATCH_DISCLOSURE, DEPTH_CAP_DISCLOSURE, testCoverageGapsInputSchema, fitGapsToBudget, collectFakeAssertions2, isTestClass6, collectCoveringTestClasses, recommendationFor2, compareGapById, emptyByStatus, testCoverageGapsHandler;
25853
26913
  var init_test_coverage_gaps = __esm({
25854
26914
  "../mcp/dist/src/tools/test-coverage-gaps.js"() {
25855
26915
  "use strict";
@@ -25857,13 +26917,31 @@ var init_test_coverage_gaps = __esm({
25857
26917
  init_src();
25858
26918
  LIST_PAGE_SIZE14 = 500;
25859
26919
  CLASS_FILTER_MAX_SIZE = 500;
26920
+ TEST_COVERAGE_GAPS_MAX_LIMIT = 500;
26921
+ TEST_COVERAGE_GAPS_DEFAULT_LIMIT = 200;
26922
+ TEST_COVERAGE_GAPS_PAYLOAD_BUDGET_BYTES = 38e3;
25860
26923
  MAX_COVERAGE_DEPTH = 3;
25861
26924
  MEANINGFUL_ASSERTION_DISCLOSURE = "the meaningful-assertion heuristic recognizes System.assertEquals(expected, actual) patterns with distinct expected/actual tokens, plus System.assert(condition) with a non-literal condition. Assertions via helper methods or framework wrappers are invisible. A class flagged fake-coverage may actually have meaningful tests via a custom assertion helper.";
25862
26925
  DYNAMIC_DISPATCH_DISCLOSURE = "reachability via callsApex does NOT cover dynamic dispatch (Type.forName) or reflective invocation. A class genuinely tested via dynamic dispatch will surface as uncovered by this heuristic.";
25863
26926
  DEPTH_CAP_DISCLOSURE = `the coverage BFS is capped at depth ${MAX_COVERAGE_DEPTH}; coverage chains longer than ${MAX_COVERAGE_DEPTH} hops surface as uncovered even when they exist.`;
25864
- testCoverageGapsInputSchema = z101.object({
25865
- classFilter: z101.array(z101.string().min(1)).max(CLASS_FILTER_MAX_SIZE).optional()
26927
+ testCoverageGapsInputSchema = z103.object({
26928
+ classFilter: z103.array(z103.string().min(1)).max(CLASS_FILTER_MAX_SIZE).optional(),
26929
+ limit: z103.number().int().min(1).max(TEST_COVERAGE_GAPS_MAX_LIMIT).optional(),
26930
+ offset: z103.number().int().min(0).optional()
25866
26931
  });
26932
+ fitGapsToBudget = (gaps, budgetBytes) => {
26933
+ const kept = [];
26934
+ let used = 0;
26935
+ for (const gap of gaps) {
26936
+ const size = Buffer.byteLength(JSON.stringify(gap), "utf8") + 1;
26937
+ if (kept.length > 0 && used + size > budgetBytes) {
26938
+ return { kept, trimmed: true };
26939
+ }
26940
+ kept.push(gap);
26941
+ used += size;
26942
+ }
26943
+ return { kept, trimmed: false };
26944
+ };
25867
26945
  collectFakeAssertions2 = (node) => {
25868
26946
  const raw = node.properties["qualityIssues"];
25869
26947
  if (!Array.isArray(raw))
@@ -26022,6 +27100,12 @@ var init_test_coverage_gaps = __esm({
26022
27100
  for (const g of sorted) {
26023
27101
  byStatus[g.coverageStatus] += 1;
26024
27102
  }
27103
+ const limit = input2.limit ?? TEST_COVERAGE_GAPS_DEFAULT_LIMIT;
27104
+ const offset = input2.offset ?? 0;
27105
+ const page = sorted.slice(offset, offset + limit);
27106
+ const { kept, trimmed } = fitGapsToBudget(page, TEST_COVERAGE_GAPS_PAYLOAD_BUDGET_BYTES);
27107
+ const returnedEnd = offset + kept.length;
27108
+ const truncated = returnedEnd < sorted.length;
26025
27109
  const boundaries = sorted.length === 0 ? [] : [
26026
27110
  MEANINGFUL_ASSERTION_DISCLOSURE,
26027
27111
  DYNAMIC_DISPATCH_DISCLOSURE,
@@ -26029,10 +27113,17 @@ var init_test_coverage_gaps = __esm({
26029
27113
  ];
26030
27114
  return ok({
26031
27115
  data: {
26032
- gaps: sorted,
27116
+ gaps: kept,
26033
27117
  totalGapsCount: sorted.length,
26034
27118
  byStatus,
26035
- boundaries
27119
+ boundaries,
27120
+ limit,
27121
+ offset,
27122
+ truncated,
27123
+ ...truncated ? { nextOffset: returnedEnd } : {},
27124
+ ...trimmed ? {
27125
+ note: `Response trimmed to ${kept.length} of ${page.length} gaps (${sorted.length} total) to stay under the ~45 KB MCP response limit. Advance with offset += ${kept.length} for the rest.`
27126
+ } : {}
26036
27127
  },
26037
27128
  vaultState: {
26038
27129
  sourceTreeHash: ctx.manifest.sourceTreeHash,
@@ -26044,7 +27135,7 @@ var init_test_coverage_gaps = __esm({
26044
27135
  });
26045
27136
 
26046
27137
  // ../mcp/dist/src/tools/tests-for-change.js
26047
- import { z as z102 } from "zod";
27138
+ import { z as z104 } from "zod";
26048
27139
  var TESTS_FOR_CHANGE_BFS_DEPTH, COVERAGE_EDGE_TYPES2, APEX_CLASS_PREFIX8, APEX_TRIGGER_PREFIX6, MAX_CHANGED_ITEMS, TESTS_FOR_CHANGE_DISCLOSURE, testsForChangeInputSchema, isApexCallable5, isTestClass7, upstreamWalk3, sortIds, testsForChangeHandler;
26049
27140
  var init_tests_for_change = __esm({
26050
27141
  "../mcp/dist/src/tools/tests-for-change.js"() {
@@ -26058,8 +27149,8 @@ var init_tests_for_change = __esm({
26058
27149
  APEX_TRIGGER_PREFIX6 = "ApexTrigger:";
26059
27150
  MAX_CHANGED_ITEMS = 500;
26060
27151
  TESTS_FOR_CHANGE_DISCLOSURE = "tests_for_change selects at CLASS granularity (a changed method on a covered class still selects that class\u2019s tests; method-level resolution promised in v2.7.1). The upstream walk follows both callsApex and dispatchesAsync incoming edges, so coverage via async dispatch (Database.executeBatch, System.enqueueJob, System.schedule) is included. Dynamic dispatch (Type.forName) and reflective invocation are invisible \u2014 a test reaching the change only via reflection is missed. Managed-package test classes are invisible. BFS is capped at depth 3; coverage chains longer than 3 hops surface as uncovered even when they exist. A changed component in uncoveredChanges is UNGUARDED \u2014 running the selected set will NOT exercise it; run the full suite when any change is uncovered or you suspect a deep chain.";
26061
- testsForChangeInputSchema = z102.object({
26062
- changedComponents: z102.array(z102.string().min(1)).min(1).max(MAX_CHANGED_ITEMS)
27152
+ testsForChangeInputSchema = z104.object({
27153
+ changedComponents: z104.array(z104.string().min(1)).min(1).max(MAX_CHANGED_ITEMS)
26063
27154
  });
26064
27155
  isApexCallable5 = (id) => id.startsWith(APEX_CLASS_PREFIX8) || id.startsWith(APEX_TRIGGER_PREFIX6);
26065
27156
  isTestClass7 = (node) => node.properties["isTest"] === true;
@@ -26219,9 +27310,711 @@ var init_tests_for_change = __esm({
26219
27310
  }
26220
27311
  });
26221
27312
 
27313
+ // ../mcp/dist/src/tools/value-change-classification.js
27314
+ var CUSTOM_FIELD_PREFIX11, SEV_RANK, parseFieldId, leafName, DERIVED_DATA_TYPES, SYSTEM_AUDIT_FIELDS, classifyMutability, STANDARD_IDLOOKUP, classifyUpsertKey, IDENTITY_CATALOG, lookupIdentityCatalog, severityRank, maxSeverity, NAME_LEXICON, CONF_RANK, classifyRole, classifyField;
27315
+ var init_value_change_classification = __esm({
27316
+ "../mcp/dist/src/tools/value-change-classification.js"() {
27317
+ "use strict";
27318
+ init_field_properties();
27319
+ CUSTOM_FIELD_PREFIX11 = "CustomField:";
27320
+ SEV_RANK = {
27321
+ info: 0,
27322
+ low: 1,
27323
+ medium: 2,
27324
+ high: 3,
27325
+ critical: 4
27326
+ };
27327
+ parseFieldId = (id) => {
27328
+ if (!id.startsWith(CUSTOM_FIELD_PREFIX11))
27329
+ return null;
27330
+ const rest = id.slice(CUSTOM_FIELD_PREFIX11.length);
27331
+ const dot = rest.indexOf(".");
27332
+ if (dot < 0)
27333
+ return null;
27334
+ return { object: rest.slice(0, dot), field: rest.slice(dot + 1) };
27335
+ };
27336
+ leafName = (apiName) => {
27337
+ const i = apiName.lastIndexOf(".");
27338
+ return i >= 0 ? apiName.slice(i + 1) : apiName;
27339
+ };
27340
+ DERIVED_DATA_TYPES = /* @__PURE__ */ new Set(["Summary", "AutoNumber"]);
27341
+ SYSTEM_AUDIT_FIELDS = /* @__PURE__ */ new Set([
27342
+ "Id",
27343
+ "IsDeleted",
27344
+ "CreatedDate",
27345
+ "CreatedById",
27346
+ "LastModifiedDate",
27347
+ "LastModifiedById",
27348
+ "SystemModstamp",
27349
+ "LastActivityDate",
27350
+ "LastViewedDate",
27351
+ "LastReferencedDate"
27352
+ ]);
27353
+ classifyMutability = (node, fieldName) => {
27354
+ const formula = node.properties["formula"];
27355
+ if (typeof formula === "string" && formula.trim() !== "") {
27356
+ const expr = formula.trim();
27357
+ return {
27358
+ mutability: "derived",
27359
+ reason: `Formula field \u2014 value is computed from \`${expr}\`. Change the source field(s), not this one.`,
27360
+ sourceFormula: expr
27361
+ };
27362
+ }
27363
+ const dataType = readFieldDataType(node);
27364
+ if (DERIVED_DATA_TYPES.has(dataType)) {
27365
+ return {
27366
+ mutability: "derived",
27367
+ reason: `${dataType} field \u2014 value is system-computed, not directly editable.`
27368
+ };
27369
+ }
27370
+ const name = fieldName ?? leafName(node.apiName);
27371
+ if (SYSTEM_AUDIT_FIELDS.has(name)) {
27372
+ return {
27373
+ mutability: "derived",
27374
+ reason: `${name} is a system audit field \u2014 not user-writable.`
27375
+ };
27376
+ }
27377
+ return { mutability: "writable", reason: "Directly editable field." };
27378
+ };
27379
+ STANDARD_IDLOOKUP = {
27380
+ "*": /* @__PURE__ */ new Set(["Id"]),
27381
+ User: /* @__PURE__ */ new Set(["Username", "FederationIdentifier"])
27382
+ };
27383
+ classifyUpsertKey = (node, object, fieldName) => {
27384
+ const signals = [];
27385
+ if (node.properties["externalId"] === true)
27386
+ signals.push("externalId");
27387
+ if (node.properties["unique"] === true)
27388
+ signals.push("unique");
27389
+ const idLookup = (STANDARD_IDLOOKUP[object]?.has(fieldName) ?? false) || (STANDARD_IDLOOKUP["*"]?.has(fieldName) ?? false);
27390
+ if (idLookup)
27391
+ signals.push("idLookup");
27392
+ return { isUpsertKey: signals.length > 0, signals };
27393
+ };
27394
+ IDENTITY_CATALOG = {
27395
+ "User.Username": { role: "Login identity (global-unique)", severity: "critical" },
27396
+ "User.FederationIdentifier": { role: "SSO / SAML federation subject", severity: "high" },
27397
+ "User.EmployeeNumber": { role: "Employee number \u2014 common HRIS integration key", severity: "high" },
27398
+ "User.CommunityNickname": { role: "Experience Cloud display (org-unique)", severity: "medium" },
27399
+ "User.Alias": { role: "Short display name", severity: "low" },
27400
+ "User.Email": { role: "User email \u2014 login & notifications", severity: "high" },
27401
+ "Contact.Email": { role: "Contact email / common match key", severity: "medium" }
27402
+ };
27403
+ lookupIdentityCatalog = (object, field) => IDENTITY_CATALOG[`${object}.${field}`] ?? null;
27404
+ severityRank = (s) => SEV_RANK[s];
27405
+ maxSeverity = (a, b) => SEV_RANK[a] >= SEV_RANK[b] ? a : b;
27406
+ NAME_LEXICON = [
27407
+ { test: /(_SIS_ID|SIS_?Key|Comment_SIS_ID)__c$/i, role: "SIS integration key (name pattern)", severity: "high" },
27408
+ { test: /(Marketo|Sparkroom|Pardot)/i, role: "Marketing-platform key (name pattern)", severity: "medium" },
27409
+ { test: /(_uuid|_guid)__c$/i, role: "External UUID/GUID key (name pattern)", severity: "medium" },
27410
+ { test: /Federation/i, role: "Federation / SSO identity (name pattern)", severity: "high" }
27411
+ ];
27412
+ CONF_RANK = { potential: 0, likely: 1, confirmed: 2 };
27413
+ classifyRole = (upsert, mutability, object, fieldName) => {
27414
+ if (mutability.mutability === "derived") {
27415
+ return {
27416
+ role: "Derived / computed \u2014 value not directly changeable",
27417
+ severity: "low",
27418
+ confidence: "confirmed",
27419
+ signals: [mutability.reason]
27420
+ };
27421
+ }
27422
+ const candidates = [];
27423
+ const catalog = IDENTITY_CATALOG[`${object}.${fieldName}`];
27424
+ if (catalog !== void 0) {
27425
+ candidates.push({
27426
+ role: catalog.role,
27427
+ severity: catalog.severity,
27428
+ // Corroborated by an upsert flag → confirmed; otherwise a known but
27429
+ // org-config-dependent default → likely.
27430
+ confidence: upsert.isUpsertKey ? "confirmed" : "likely",
27431
+ signal: "standard-identity-field catalog"
27432
+ });
27433
+ }
27434
+ if (upsert.isUpsertKey) {
27435
+ candidates.push({
27436
+ role: "Integration / upsert key",
27437
+ severity: "high",
27438
+ confidence: "confirmed",
27439
+ signal: `upsert key (${upsert.signals.join(" + ")})`
27440
+ });
27441
+ }
27442
+ const lex = NAME_LEXICON.find((r) => r.test.test(fieldName));
27443
+ if (lex !== void 0) {
27444
+ candidates.push({
27445
+ role: lex.role,
27446
+ severity: lex.severity,
27447
+ confidence: "potential",
27448
+ signal: "name pattern"
27449
+ });
27450
+ }
27451
+ if (candidates.length === 0) {
27452
+ return {
27453
+ role: "Standard editable field",
27454
+ severity: "low",
27455
+ confidence: "confirmed",
27456
+ signals: []
27457
+ };
27458
+ }
27459
+ const top = candidates.reduce((best, c) => {
27460
+ if (SEV_RANK[c.severity] !== SEV_RANK[best.severity]) {
27461
+ return SEV_RANK[c.severity] > SEV_RANK[best.severity] ? c : best;
27462
+ }
27463
+ return CONF_RANK[c.confidence] > CONF_RANK[best.confidence] ? c : best;
27464
+ });
27465
+ return {
27466
+ role: top.role,
27467
+ severity: top.severity,
27468
+ confidence: top.confidence,
27469
+ signals: candidates.map((c) => c.signal)
27470
+ };
27471
+ };
27472
+ classifyField = (node) => {
27473
+ const parsed = parseFieldId(node.id);
27474
+ const object = parsed?.object ?? "";
27475
+ const field = parsed?.field ?? leafName(node.apiName);
27476
+ const mutability = classifyMutability(node, field);
27477
+ const upsertKey = classifyUpsertKey(node, object, field);
27478
+ const role = classifyRole(upsertKey, mutability, object, field);
27479
+ return { fieldId: node.id, object, field, mutability, upsertKey, role };
27480
+ };
27481
+ }
27482
+ });
27483
+
27484
+ // ../mcp/dist/src/tools/value-change-risk.js
27485
+ var SOURCE_CATEGORY, EMPTY_EDGE_SUMMARY, buildBuckets, buildDisclosures, recommendedChecksFor, overallSeverityOf, summarizeIncomingEdges, gateFederationIdentifier, detectShadowJoin, extractQuotedLiterals, normalizeExpr, findValueCouplings, assessValueChange;
27486
+ var init_value_change_risk = __esm({
27487
+ "../mcp/dist/src/tools/value-change-risk.js"() {
27488
+ "use strict";
27489
+ init_dist();
27490
+ init_src();
27491
+ init_value_change_classification();
27492
+ SOURCE_CATEGORY = {
27493
+ ValidationRule: "automation",
27494
+ WorkflowRule: "automation",
27495
+ Flow: "automation",
27496
+ ApprovalProcess: "automation",
27497
+ AssignmentRule: "automation",
27498
+ EscalationRule: "automation",
27499
+ AutoResponseRule: "automation",
27500
+ DuplicateRule: "automation",
27501
+ CustomField: "automation",
27502
+ // a formula field referencing this one
27503
+ ApexClass: "code",
27504
+ ApexTrigger: "code",
27505
+ LightningComponentBundle: "code",
27506
+ AuraDefinitionBundle: "code",
27507
+ VisualforcePage: "code",
27508
+ VisualforceComponent: "code",
27509
+ ExternalService: "integration",
27510
+ ExternalDataSource: "integration",
27511
+ NamedCredential: "integration",
27512
+ Layout: "display",
27513
+ QuickAction: "display",
27514
+ FlexiPage: "display",
27515
+ ListView: "display"
27516
+ };
27517
+ EMPTY_EDGE_SUMMARY = {
27518
+ automation: [],
27519
+ code: [],
27520
+ integration: [],
27521
+ display: []
27522
+ };
27523
+ buildBuckets = (classification, edges) => {
27524
+ const { object, field, mutability, upsertKey } = classification;
27525
+ if (mutability.mutability === "derived") {
27526
+ return [
27527
+ {
27528
+ bucket: "save-pipeline",
27529
+ severity: "info",
27530
+ confidence: "confirmed",
27531
+ summary: `Not directly changeable \u2014 ${mutability.reason}`,
27532
+ evidence: []
27533
+ }
27534
+ ];
27535
+ }
27536
+ const buckets = [];
27537
+ const identity = lookupIdentityCatalog(object, field);
27538
+ if (identity !== null) {
27539
+ buckets.push({
27540
+ bucket: "identity",
27541
+ severity: identity.severity,
27542
+ confidence: upsertKey.isUpsertKey ? "confirmed" : "likely",
27543
+ summary: `${identity.role} \u2014 changing the value alters how this record is identified/authenticated.`,
27544
+ evidence: ["standard-identity-field catalog"]
27545
+ });
27546
+ }
27547
+ const keySignals = upsertKey.signals.filter((s) => s === "externalId" || s === "idLookup");
27548
+ if (keySignals.length > 0) {
27549
+ const designedKey = keySignals.includes("externalId");
27550
+ buckets.push({
27551
+ bucket: "integration-key",
27552
+ severity: designedKey ? "high" : "medium",
27553
+ confidence: "confirmed",
27554
+ summary: designedKey ? "Upsert / integration match key (External ID) \u2014 changing the value breaks inbound matching (next sync no-matches \u2192 duplicate or error) and desyncs external systems keyed on the old value." : "Upsert-capable key (idLookup) \u2014 an integration may upsert records by this field; changing the value could break that matching.",
27555
+ evidence: [...keySignals.map((s) => `${s}=true`), ...edges.integration]
27556
+ });
27557
+ }
27558
+ if (upsertKey.signals.includes("unique")) {
27559
+ buckets.push({
27560
+ bucket: "uniqueness",
27561
+ severity: "medium",
27562
+ confidence: "confirmed",
27563
+ summary: "Unique field \u2014 a new value that collides with an existing record fails to save (DML error).",
27564
+ evidence: ["unique=true"]
27565
+ });
27566
+ }
27567
+ const automationRefs = [...edges.automation, ...edges.code];
27568
+ if (automationRefs.length > 0) {
27569
+ buckets.push({
27570
+ bucket: "automation",
27571
+ severity: edges.automation.length > 0 ? "medium" : "low",
27572
+ confidence: "likely",
27573
+ summary: `${automationRefs.length} automation/code component(s) reference this field; a value change may alter routing, validation, or sharing. Declarative value-couplings (where a rule compares this field to a literal) are surfaced when found; Apex literal comparisons remain invisible.`,
27574
+ evidence: automationRefs.slice(0, 12)
27575
+ });
27576
+ }
27577
+ const isDisplayIdentity = identity !== null && /display|nickname|alias/i.test(identity.role);
27578
+ if (edges.display.length > 0 || isDisplayIdentity) {
27579
+ buckets.push({
27580
+ bucket: "display",
27581
+ severity: "low",
27582
+ confidence: "likely",
27583
+ summary: "Appears on display surfaces (layouts / list views / UI); a value change is user-visible.",
27584
+ evidence: edges.display.slice(0, 8)
27585
+ });
27586
+ }
27587
+ buckets.push({
27588
+ bucket: "save-pipeline",
27589
+ severity: "info",
27590
+ confidence: "likely",
27591
+ summary: `Updating this field fires the save pipeline on ${object} (triggers / flows / rules) \u2014 relevant for bulk value changes.`,
27592
+ evidence: []
27593
+ });
27594
+ return buckets;
27595
+ };
27596
+ buildDisclosures = (buckets) => {
27597
+ const out = [];
27598
+ const has = (b) => buckets.some((x) => x.bucket === b);
27599
+ if (has("integration-key")) {
27600
+ out.push("The metadata declares this an upsert key, but NOT which external system upserts on it \u2014 confirm the key in your middleware / ETL (Data Loader, MuleSoft, etc.) before bulk-changing values.");
27601
+ }
27602
+ if (has("identity")) {
27603
+ out.push("If SSO maps users by this field, changing its value breaks login until the IdP is updated \u2014 verify with your IdP / My Domain SSO.");
27604
+ }
27605
+ if (has("automation")) {
27606
+ out.push("Dynamic SOQL, reflective Apex field access, and managed-package code that compare this field to a value are invisible to the scanner \u2014 the listed automations are the visible subset.");
27607
+ }
27608
+ if (has("cross-object")) {
27609
+ out.push("This key is replicated by name on other objects with no formal relationship \u2014 update the copies together, or they will silently desync.");
27610
+ }
27611
+ out.push("Reports, list-view filters, and manual processes may also key on this value.");
27612
+ return out;
27613
+ };
27614
+ recommendedChecksFor = (buckets) => {
27615
+ const checks = [];
27616
+ const has = (b) => buckets.some((x) => x.bucket === b);
27617
+ if (has("integration-key"))
27618
+ checks.push("Confirm the external upsert key + whether the external system will re-match or duplicate on the new value.");
27619
+ if (has("uniqueness"))
27620
+ checks.push("Confirm the new value does not collide with an existing record (unique constraint).");
27621
+ if (has("identity"))
27622
+ checks.push("Confirm SSO/login impact with your IdP before changing identity values.");
27623
+ if (has("automation"))
27624
+ checks.push("Review the referencing automations for value-literal comparisons that the change would flip.");
27625
+ if (has("cross-object"))
27626
+ checks.push("Update the same-named copies on the other objects together \u2014 they are value-joined with no formal relationship.");
27627
+ checks.push("For bulk changes, stage in a sandbox and watch the save pipeline (row locks, governor limits).");
27628
+ return checks;
27629
+ };
27630
+ overallSeverityOf = (buckets) => buckets.reduce((acc, b) => maxSeverity(acc, b.severity), "info");
27631
+ summarizeIncomingEdges = async (ctx, fieldId) => {
27632
+ const edgesResult = await listEdges(ctx.graph, fieldId, { direction: "in" });
27633
+ if (!edgesResult.ok) {
27634
+ return err({ kind: "internal", message: `graph query failed: ${edgesResult.error.message}` });
27635
+ }
27636
+ const automation = [];
27637
+ const code = [];
27638
+ const integration = [];
27639
+ const display = [];
27640
+ for (const edge of edgesResult.value) {
27641
+ if (edge.edgeType === "parentOf")
27642
+ continue;
27643
+ const fromResult = await getNodeById(ctx.graph, edge.fromId);
27644
+ if (!fromResult.ok) {
27645
+ return err({ kind: "internal", message: `graph query failed: ${fromResult.error.message}` });
27646
+ }
27647
+ const fromNode = fromResult.value;
27648
+ if (fromNode === null)
27649
+ continue;
27650
+ const category = SOURCE_CATEGORY[fromNode.type];
27651
+ if (category === "automation")
27652
+ automation.push(fromNode.id);
27653
+ else if (category === "code")
27654
+ code.push(fromNode.id);
27655
+ else if (category === "integration")
27656
+ integration.push(fromNode.id);
27657
+ else if (category === "display")
27658
+ display.push(fromNode.id);
27659
+ }
27660
+ const dedupe = (xs) => [...new Set(xs)].sort();
27661
+ return ok({
27662
+ automation: dedupe(automation),
27663
+ code: dedupe(code),
27664
+ integration: dedupe(integration),
27665
+ display: dedupe(display)
27666
+ });
27667
+ };
27668
+ gateFederationIdentifier = async (ctx, buckets, codeRefs) => {
27669
+ const samlRes = await listNodesByType(ctx.graph, "SamlSsoConfig", { limit: 200 });
27670
+ if (!samlRes.ok)
27671
+ return err({ kind: "internal", message: `graph query failed: ${samlRes.error.message}` });
27672
+ const apRes = await listNodesByType(ctx.graph, "AuthProvider", { limit: 200 });
27673
+ if (!apRes.ok)
27674
+ return err({ kind: "internal", message: `graph query failed: ${apRes.error.message}` });
27675
+ const configs = samlRes.value;
27676
+ const mappings = configs.map((n) => String(n.properties["identityMapping"] ?? "Username"));
27677
+ const fedConfigs = configs.filter((n) => n.properties["identityMapping"] === "FederationId");
27678
+ const oidcLogin = apRes.value.filter((ap) => {
27679
+ const rh = ap.properties["registrationHandler"];
27680
+ return typeof rh === "string" && rh.length > 0 || ap.properties["providerType"] === "OpenIdConnect";
27681
+ });
27682
+ const others = buckets.filter((b) => b.bucket !== "identity");
27683
+ let identity;
27684
+ let disclosure;
27685
+ if (fedConfigs.length > 0) {
27686
+ identity = {
27687
+ bucket: "identity",
27688
+ severity: "critical",
27689
+ confidence: "confirmed",
27690
+ summary: `SAML SSO maps users by Federation ID (${fedConfigs.length} SamlSsoConfig) \u2014 changing this value breaks SSO login until the IdP is updated.`,
27691
+ evidence: fedConfigs.map((c) => c.id)
27692
+ };
27693
+ } else if (oidcLogin.length > 0) {
27694
+ const handlers = [...new Set(oidcLogin.map((a) => a.properties["registrationHandler"]).filter((s) => typeof s === "string" && s.length > 0))];
27695
+ const handlersRefFed = handlers.filter((h) => codeRefs.includes(`ApexClass:${h}`));
27696
+ if (handlersRefFed.length > 0) {
27697
+ identity = {
27698
+ bucket: "identity",
27699
+ severity: "medium",
27700
+ confidence: "likely",
27701
+ summary: `OIDC / social SSO registration handler(s) ${handlersRefFed.join(", ")} reference FederationIdentifier (heuristic Apex scan) \u2014 it is part of the OIDC identity mapping; changing the value may break login.`,
27702
+ evidence: [...oidcLogin.map((a) => a.id), ...handlersRefFed.map((h) => `ApexClass:${h}`)]
27703
+ };
27704
+ disclosure = "The handler-to-FederationIdentifier link is from the heuristic Apex scanner (token-level); cross-method dataflow, dynamic SOQL, and reflective field access are invisible \u2014 confirm in the handler source + ThirdPartyAccountLink.";
27705
+ } else {
27706
+ identity = {
27707
+ bucket: "identity",
27708
+ severity: "medium",
27709
+ confidence: "likely",
27710
+ summary: `OIDC / social SSO login provider(s) present (${oidcLogin.map((a) => a.apiName).slice(0, 3).join(", ")}); the mapping is in an Apex registration handler \u2014 the heuristic scan found no FederationIdentifier reference there, but it may map it dynamically.`,
27711
+ evidence: oidcLogin.map((a) => a.id)
27712
+ };
27713
+ disclosure = `OIDC/social SSO maps identity in an Apex registration handler${handlers.length > 0 ? ` (${handlers.slice(0, 2).join(", ")})` : ""}; the scan saw no FederationIdentifier reference, but dynamic / reflective field access is invisible \u2014 verify the handler + ThirdPartyAccountLink before changing values.`;
27714
+ }
27715
+ } else if (configs.length > 0) {
27716
+ identity = {
27717
+ bucket: "identity",
27718
+ severity: "low",
27719
+ confidence: "confirmed",
27720
+ summary: `SAML SSO maps by ${[...new Set(mappings)].join("/")}, not Federation ID, and no OIDC login provider is present \u2014 this field is not the SSO subject here.`,
27721
+ evidence: configs.map((c) => c.id)
27722
+ };
27723
+ } else {
27724
+ identity = {
27725
+ bucket: "identity",
27726
+ severity: "low",
27727
+ confidence: "likely",
27728
+ summary: "No SAML SSO config and no OIDC/social login provider in the vault \u2014 Federation ID is not in use for SSO here.",
27729
+ evidence: []
27730
+ };
27731
+ disclosure = "No SamlSsoConfig or login-capable AuthProvider in the vault: if SSO is configured outside retrievable metadata, changing FederationIdentifier could still affect login \u2014 verify with your IdP.";
27732
+ }
27733
+ return ok({ buckets: [identity, ...others], ...disclosure !== void 0 ? { disclosure } : {} });
27734
+ };
27735
+ detectShadowJoin = async (ctx, classification) => {
27736
+ const { object, field, upsertKey } = classification;
27737
+ const keyish = upsertKey.isUpsertKey || /(_ID|_SIS_ID|Key|_uuid|_guid|Code)__c$/i.test(field);
27738
+ if (!keyish)
27739
+ return ok(null);
27740
+ const all = [];
27741
+ let offset = 0;
27742
+ for (; ; ) {
27743
+ const res = await listNodesByType(ctx.graph, "CustomField", { limit: 500, offset });
27744
+ if (!res.ok)
27745
+ return err({ kind: "internal", message: `graph query failed: ${res.error.message}` });
27746
+ all.push(...res.value);
27747
+ if (res.value.length < 500)
27748
+ break;
27749
+ offset += 500;
27750
+ }
27751
+ const objOf = (n) => parseFieldId(n.id)?.object ?? "?";
27752
+ const sameName = all.filter((n) => parseFieldId(n.id)?.field === field);
27753
+ if (sameName.length <= 1)
27754
+ return ok(null);
27755
+ const masters = sameName.filter((n) => n.properties["externalId"] === true);
27756
+ if (masters.length === 0)
27757
+ return ok(null);
27758
+ const otherObjects = sameName.filter((n) => objOf(n) !== object);
27759
+ if (otherObjects.length === 0)
27760
+ return ok(null);
27761
+ const masterObjs = [...new Set(masters.map(objOf))].sort();
27762
+ const copyObjs = [...new Set(sameName.filter((n) => n.properties["externalId"] !== true).map(objOf))].sort();
27763
+ const thisIsMaster = upsertKey.signals.includes("externalId");
27764
+ return ok({
27765
+ bucket: "cross-object",
27766
+ severity: thisIsMaster ? "high" : "medium",
27767
+ confidence: "likely",
27768
+ summary: `'${field}' is replicated on ${sameName.length} objects \u2014 External-ID key on [${masterObjs.join(", ")}]${copyObjs.length > 0 ? `, plain copy on [${copyObjs.join(", ")}]` : ""}. These are value-joined with no formal relationship; changing the value here can silently desync the copies.`,
27769
+ evidence: sameName.map((n) => n.id).sort()
27770
+ });
27771
+ };
27772
+ extractQuotedLiterals = (expr) => {
27773
+ const out = [];
27774
+ const re = /["']([^"']{1,40})["']/g;
27775
+ let m;
27776
+ while ((m = re.exec(expr)) !== null)
27777
+ out.push(m[1]);
27778
+ return out;
27779
+ };
27780
+ normalizeExpr = (expr) => typeof expr === "string" ? expr.replace(/\s+/g, " ").trim().slice(0, 120) : "";
27781
+ findValueCouplings = async (ctx, fieldId) => {
27782
+ const leaf = parseFieldId(fieldId)?.field ?? fieldId;
27783
+ const re = new RegExp(`\\b${leaf}\\b`);
27784
+ const matched = [];
27785
+ let offset = 0;
27786
+ for (; ; ) {
27787
+ const res = await listNodesByType(ctx.graph, "ConditionalContext", { limit: 500, offset });
27788
+ if (!res.ok)
27789
+ return err({ kind: "internal", message: `graph query failed: ${res.error.message}` });
27790
+ for (const n of res.value) {
27791
+ const refs = Array.isArray(n.properties["fieldRefs"]) ? n.properties["fieldRefs"] : [];
27792
+ const expr = n.properties["expression"];
27793
+ if (refs.includes(fieldId) || typeof expr === "string" && re.test(expr))
27794
+ matched.push(n);
27795
+ }
27796
+ if (res.value.length < 500)
27797
+ break;
27798
+ offset += 500;
27799
+ }
27800
+ const expressions = [...new Set(matched.map((n) => normalizeExpr(n.properties["expression"])).filter((s) => s.length > 0))].slice(0, 8);
27801
+ const literals = [...new Set(matched.flatMap((n) => extractQuotedLiterals(String(n.properties["expression"] ?? ""))))].slice(0, 12);
27802
+ return ok({ expressions, literals });
27803
+ };
27804
+ assessValueChange = async (ctx, node) => {
27805
+ const classification = classifyField(node);
27806
+ const mutable = classification.mutability.mutability === "writable";
27807
+ const edgesResult = mutable ? await summarizeIncomingEdges(ctx, node.id) : ok(EMPTY_EDGE_SUMMARY);
27808
+ if (!edgesResult.ok)
27809
+ return err(edgesResult.error);
27810
+ let buckets = buildBuckets(classification, edgesResult.value);
27811
+ const extraDisclosures = [];
27812
+ if (mutable && classification.field === "FederationIdentifier") {
27813
+ const gated = await gateFederationIdentifier(ctx, buckets, edgesResult.value.code);
27814
+ if (!gated.ok)
27815
+ return err(gated.error);
27816
+ buckets = gated.value.buckets;
27817
+ if (gated.value.disclosure !== void 0)
27818
+ extraDisclosures.push(gated.value.disclosure);
27819
+ }
27820
+ if (mutable) {
27821
+ const shadow = await detectShadowJoin(ctx, classification);
27822
+ if (!shadow.ok)
27823
+ return err(shadow.error);
27824
+ if (shadow.value !== null)
27825
+ buckets = [...buckets, shadow.value];
27826
+ }
27827
+ if (mutable) {
27828
+ const cp = await findValueCouplings(ctx, node.id);
27829
+ if (!cp.ok)
27830
+ return err(cp.error);
27831
+ if (cp.value.expressions.length > 0) {
27832
+ const existing = buckets.find((b) => b.bucket === "automation");
27833
+ const enriched = {
27834
+ bucket: "automation",
27835
+ severity: existing?.severity ?? "medium",
27836
+ confidence: "confirmed",
27837
+ summary: `Value-coupled in ${cp.value.expressions.length} declarative condition(s)${cp.value.literals.length > 0 ? ` (literals: ${cp.value.literals.slice(0, 6).join(", ")})` : ""} \u2014 changing the value may flip them. e.g. ${cp.value.expressions.slice(0, 3).join(" | ")}`,
27838
+ evidence: [...existing?.evidence ?? [], ...cp.value.expressions.slice(0, 6)]
27839
+ };
27840
+ buckets = [...buckets.filter((b) => b.bucket !== "automation"), enriched];
27841
+ }
27842
+ }
27843
+ return ok({
27844
+ fieldId: node.id,
27845
+ object: classification.object,
27846
+ field: classification.field,
27847
+ mutable,
27848
+ overallSeverity: overallSeverityOf(buckets),
27849
+ buckets,
27850
+ disclosures: mutable ? [...buildDisclosures(buckets), ...extraDisclosures] : [],
27851
+ recommendedChecks: mutable ? recommendedChecksFor(buckets) : ["Change the source field(s) instead \u2014 this value is derived."]
27852
+ });
27853
+ };
27854
+ }
27855
+ });
27856
+
27857
+ // ../mcp/dist/src/tools/value-change-audit.js
27858
+ import { z as z105 } from "zod";
27859
+ var VALUE_CHANGE_REQUIRED_COVERAGE, MAX_ROWS, PAGE, DISCLOSURE9, GLOBAL_DISCLOSURES, valueChangeAuditInputSchema, coverageCaveatFor3, listObjectFields, isCandidate, buildRow2, valueChangeAuditHandler;
27860
+ var init_value_change_audit = __esm({
27861
+ "../mcp/dist/src/tools/value-change-audit.js"() {
27862
+ "use strict";
27863
+ init_dist();
27864
+ init_src();
27865
+ init_src2();
27866
+ init_value_change_classification();
27867
+ init_value_change_risk();
27868
+ VALUE_CHANGE_REQUIRED_COVERAGE = [
27869
+ "CustomField",
27870
+ "ValidationRule",
27871
+ "Flow",
27872
+ "ApexClass",
27873
+ "ApexTrigger",
27874
+ "WorkflowRule",
27875
+ "Layout",
27876
+ "SharingRule",
27877
+ "DuplicateRule"
27878
+ ];
27879
+ MAX_ROWS = 200;
27880
+ PAGE = 500;
27881
+ DISCLOSURE9 = "value_change_audit ranks fields by the impact of changing their stored VALUE (not schema). Auto-detect surfaces upsert keys (externalId/unique/idLookup), identity-catalog fields, and name-lexicon matches \u2014 it can miss a value-sensitive field that carries none of those signals, and the per-row blast radius inherits what_if_change_field_value\u2019s boundaries (external upsert systems, the IdP side of SSO, and dynamic/managed-package code are invisible).";
27882
+ GLOBAL_DISCLOSURES = [
27883
+ "External systems that upsert on these keys live outside org metadata \u2014 confirm them in your middleware / ETL.",
27884
+ "SSO identity mapping (which field the IdP asserts) is read from SamlSsoConfig when present; verify your IdP.",
27885
+ "Reports, list-view filters, and manual processes may key on these values."
27886
+ ];
27887
+ valueChangeAuditInputSchema = z105.object({
27888
+ object: z105.string().min(1),
27889
+ fields: z105.array(z105.string()).optional(),
27890
+ verbosity: z105.enum(["summary", "detail"]).optional()
27891
+ });
27892
+ coverageCaveatFor3 = (ctx) => {
27893
+ const coverage = summarizeCoverage(ctx.manifest, VALUE_CHANGE_REQUIRED_COVERAGE);
27894
+ if (coverage.status === "complete")
27895
+ return void 0;
27896
+ const missingCoverage = coverage.missingCoverage.length > 0 ? coverage.missingCoverage : [...VALUE_CHANGE_REQUIRED_COVERAGE];
27897
+ return {
27898
+ status: coverage.status === "partial" ? "partial" : "unknown",
27899
+ missingCoverage,
27900
+ message: `Value-change audit is incomplete because the vault lacks coverage for: ${missingCoverage.join(", ")}.`
27901
+ };
27902
+ };
27903
+ listObjectFields = async (ctx, objectId) => {
27904
+ const all = [];
27905
+ let offset = 0;
27906
+ for (; ; ) {
27907
+ const res = await listNodesByType(ctx.graph, "CustomField", { parentId: objectId, limit: PAGE, offset });
27908
+ if (!res.ok)
27909
+ return err({ kind: "internal", message: `graph query failed: ${res.error.message}` });
27910
+ all.push(...res.value);
27911
+ if (res.value.length < PAGE)
27912
+ break;
27913
+ offset += PAGE;
27914
+ }
27915
+ return ok(all);
27916
+ };
27917
+ isCandidate = (node) => {
27918
+ const c = classifyField(node);
27919
+ if (c.mutability.mutability === "derived")
27920
+ return false;
27921
+ return c.upsertKey.isUpsertKey || lookupIdentityCatalog(c.object, c.field) !== null || severityRank(c.role.severity) >= severityRank("medium");
27922
+ };
27923
+ buildRow2 = (assessment, node, verbosity) => {
27924
+ const classification = classifyField(node);
27925
+ const topReasons = [...assessment.buckets].filter((b) => b.severity !== "info").sort((a, b) => severityRank(b.severity) - severityRank(a.severity)).slice(0, 3).map((b) => `${b.bucket} (${b.severity})`);
27926
+ return {
27927
+ field: assessment.field,
27928
+ fieldId: assessment.fieldId,
27929
+ role: classification.role.role,
27930
+ overallSeverity: assessment.overallSeverity,
27931
+ mutable: assessment.mutable,
27932
+ topReasons,
27933
+ confidence: classification.role.confidence,
27934
+ disclosureCount: assessment.disclosures.length,
27935
+ ...verbosity === "detail" ? { buckets: assessment.buckets } : {}
27936
+ };
27937
+ };
27938
+ valueChangeAuditHandler = async (ctx, input2) => {
27939
+ const object = input2.object;
27940
+ const objectId = `CustomObject:${object}`;
27941
+ const verbosity = input2.verbosity ?? "summary";
27942
+ let candidates = [];
27943
+ let autoDetected;
27944
+ let scannedFieldCount;
27945
+ const notFound = [];
27946
+ if (input2.fields !== void 0 && input2.fields.length > 0) {
27947
+ autoDetected = false;
27948
+ scannedFieldCount = input2.fields.length;
27949
+ for (const f of input2.fields) {
27950
+ const id = f.startsWith("CustomField:") ? f : `CustomField:${object}.${f}`;
27951
+ const nodeResult = await getNodeById(ctx.graph, id);
27952
+ if (!nodeResult.ok)
27953
+ return err({ kind: "internal", message: `graph query failed: ${nodeResult.error.message}` });
27954
+ if (nodeResult.value === null) {
27955
+ notFound.push(f);
27956
+ continue;
27957
+ }
27958
+ candidates.push(nodeResult.value);
27959
+ }
27960
+ } else {
27961
+ autoDetected = true;
27962
+ const fieldsResult = await listObjectFields(ctx, objectId);
27963
+ if (!fieldsResult.ok)
27964
+ return err(fieldsResult.error);
27965
+ scannedFieldCount = fieldsResult.value.length;
27966
+ candidates = fieldsResult.value.filter(isCandidate);
27967
+ }
27968
+ const rows = [];
27969
+ for (const node of candidates) {
27970
+ const assessmentResult = await assessValueChange(ctx, node);
27971
+ if (!assessmentResult.ok)
27972
+ return err(assessmentResult.error);
27973
+ rows.push(buildRow2(assessmentResult.value, node, verbosity));
27974
+ }
27975
+ rows.sort((a, b) => severityRank(b.overallSeverity) - severityRank(a.overallSeverity) || (a.field < b.field ? -1 : a.field > b.field ? 1 : 0));
27976
+ const truncated = rows.length > MAX_ROWS;
27977
+ const shownRows = truncated ? rows.slice(0, MAX_ROWS) : rows;
27978
+ const summary = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
27979
+ for (const r of rows)
27980
+ summary[r.overallSeverity] += 1;
27981
+ const coverageCaveat = coverageCaveatFor3(ctx);
27982
+ const confidence = rows.some((r) => r.topReasons.some((t) => t.startsWith("automation"))) ? "heuristic" : "declared";
27983
+ return ok({
27984
+ data: {
27985
+ object,
27986
+ autoDetected,
27987
+ scannedFieldCount,
27988
+ rows: shownRows,
27989
+ truncated,
27990
+ summary,
27991
+ globalDisclosures: GLOBAL_DISCLOSURES,
27992
+ ...notFound.length > 0 ? { notFound } : {},
27993
+ ...coverageCaveat !== void 0 ? { coverageCaveat } : {},
27994
+ trust: {
27995
+ provenance: "offline_snapshot",
27996
+ confidence,
27997
+ freshness: { snapshotRefreshedAt: ctx.manifest.refreshedAt },
27998
+ completeness: {
27999
+ status: coverageCaveat === void 0 ? "complete" : coverageCaveat.status,
28000
+ ...coverageCaveat !== void 0 ? { missingCoverage: coverageCaveat.missingCoverage } : {}
28001
+ },
28002
+ limitations: [DISCLOSURE9, ...coverageCaveat !== void 0 ? [coverageCaveat.message] : []]
28003
+ },
28004
+ disclosure: DISCLOSURE9
28005
+ },
28006
+ vaultState: {
28007
+ sourceTreeHash: ctx.manifest.sourceTreeHash,
28008
+ refreshedAt: ctx.manifest.refreshedAt
28009
+ }
28010
+ });
28011
+ };
28012
+ }
28013
+ });
28014
+
26222
28015
  // ../mcp/dist/src/tools/what-happens-on-save.js
26223
- import { z as z103 } from "zod";
26224
- var DISCLOSURE7, ALLOWED_EVENTS, whatHappensOnSaveInputSchema, workflowMatchesEvent2, flowMatchesEvent2, triggerMatchesEvent2, surfaceFirstCondition2, buildActions2, buildStep2, fetchParentedFirers2, fetchTriggersOnFirers2, buildAsyncSteps2, ASSIGNMENT_TYPES2, APPROVAL_TYPES2, VALIDATION_TYPES2, FLOW_TYPES2, TRIGGER_TYPES2, WORKFLOW_TYPES2, whatHappensOnSaveHandler;
28016
+ import { z as z106 } from "zod";
28017
+ var DISCLOSURE10, ALLOWED_EVENTS, whatHappensOnSaveInputSchema, workflowMatchesEvent2, flowMatchesEvent2, triggerMatchesEvent2, surfaceFirstCondition2, buildActions2, buildStep2, fetchParentedFirers2, fetchTriggersOnFirers2, buildAsyncSteps2, ASSIGNMENT_TYPES2, APPROVAL_TYPES2, VALIDATION_TYPES2, FLOW_TYPES2, TRIGGER_TYPES2, WORKFLOW_TYPES2, whatHappensOnSaveHandler;
26225
28018
  var init_what_happens_on_save = __esm({
26226
28019
  "../mcp/dist/src/tools/what-happens-on-save.js"() {
26227
28020
  "use strict";
@@ -26229,7 +28022,7 @@ var init_what_happens_on_save = __esm({
26229
28022
  init_src();
26230
28023
  init_soe_admission();
26231
28024
  init_soe_payload_bounds();
26232
- DISCLOSURE7 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
28025
+ DISCLOSURE10 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
26233
28026
  ALLOWED_EVENTS = [
26234
28027
  "insert",
26235
28028
  "update",
@@ -26237,13 +28030,13 @@ var init_what_happens_on_save = __esm({
26237
28030
  "delete",
26238
28031
  "undelete"
26239
28032
  ];
26240
- whatHappensOnSaveInputSchema = z103.object({
26241
- objectApiName: z103.string().min(1),
28033
+ whatHappensOnSaveInputSchema = z106.object({
28034
+ objectApiName: z106.string().min(1),
26242
28035
  // Accept "after update" / "Before Insert" etc.: lower-case and drop the
26243
28036
  // before/after timing prefix so the bare DML event matches the enum. The
26244
28037
  // SOE walker models both timings internally; the event arg selects the row.
26245
- event: z103.preprocess((v) => typeof v === "string" ? v.trim().toLowerCase().replace(/^(?:before|after)\s+/, "") : v, z103.enum(ALLOWED_EVENTS)),
26246
- recordTypeId: z103.string().min(1).optional()
28038
+ event: z106.preprocess((v) => typeof v === "string" ? v.trim().toLowerCase().replace(/^(?:before|after)\s+/, "") : v, z106.enum(ALLOWED_EVENTS)),
28039
+ recordTypeId: z106.string().min(1).optional()
26247
28040
  });
26248
28041
  workflowMatchesEvent2 = (triggerType, event) => {
26249
28042
  if (typeof triggerType !== "string")
@@ -26609,12 +28402,12 @@ var init_what_happens_on_save = __esm({
26609
28402
  conditionalSteps: conditionalCount,
26610
28403
  asyncFanOut
26611
28404
  },
26612
- disclosure: composeSoeDisclosure(DISCLOSURE7, objectModeled)
28405
+ disclosure: composeSoeDisclosure(DISCLOSURE10, objectModeled)
26613
28406
  };
26614
- const budget = enforceSoeByteBudget(data, soe);
28407
+ const budget = enforceSoeByteBudget(data, [soe]);
26615
28408
  if (budget.truncated) {
26616
28409
  data.truncated = true;
26617
- data.disclosure = `${data.disclosure} ${soeTruncationNote(budget.actionsOmitted)}`;
28410
+ data.disclosure = `${data.disclosure} ${soeTruncationNote(budget)}`;
26618
28411
  }
26619
28412
  return ok({
26620
28413
  data,
@@ -26627,9 +28420,111 @@ var init_what_happens_on_save = __esm({
26627
28420
  }
26628
28421
  });
26629
28422
 
28423
+ // ../mcp/dist/src/tools/what-if-change-field-value.js
28424
+ import { z as z107 } from "zod";
28425
+ var CUSTOM_FIELD_PREFIX12, VALUE_CHANGE_REQUIRED_COVERAGE2, DISCLOSURE11, whatIfChangeFieldValueInputSchema, coverageCaveatFor4, whatIfChangeFieldValueHandler;
28426
+ var init_what_if_change_field_value = __esm({
28427
+ "../mcp/dist/src/tools/what-if-change-field-value.js"() {
28428
+ "use strict";
28429
+ init_dist();
28430
+ init_src();
28431
+ init_src2();
28432
+ init_value_change_risk();
28433
+ CUSTOM_FIELD_PREFIX12 = "CustomField:";
28434
+ VALUE_CHANGE_REQUIRED_COVERAGE2 = [
28435
+ "CustomField",
28436
+ "ValidationRule",
28437
+ "Flow",
28438
+ "ApexClass",
28439
+ "ApexTrigger",
28440
+ "WorkflowRule",
28441
+ "Layout",
28442
+ "SharingRule",
28443
+ "DuplicateRule"
28444
+ ];
28445
+ DISCLOSURE11 = "Value-change impact analyzes what breaks if this field\u2019s stored VALUE changes (not its schema) \u2014 distinct from what_if_change_field_type, which walks references. Identity / integration-key / uniqueness verdicts come from the field\u2019s own metadata (externalId / unique / idLookup, identity catalog); automation buckets surface the declarative value-literal coupling (the value a rule compares this field to); Apex literal comparisons remain invisible. The vault cannot see external upsert systems, the IdP side of SSO, or dynamic / managed-package code \u2014 see disclosures.";
28446
+ whatIfChangeFieldValueInputSchema = z107.object({
28447
+ fieldId: z107.string().min(1),
28448
+ newValue: z107.string().optional()
28449
+ });
28450
+ coverageCaveatFor4 = (ctx) => {
28451
+ const coverage = summarizeCoverage(ctx.manifest, VALUE_CHANGE_REQUIRED_COVERAGE2);
28452
+ if (coverage.status === "complete")
28453
+ return void 0;
28454
+ const missingCoverage = coverage.missingCoverage.length > 0 ? coverage.missingCoverage : [...VALUE_CHANGE_REQUIRED_COVERAGE2];
28455
+ return {
28456
+ status: coverage.status === "partial" ? "partial" : "unknown",
28457
+ missingCoverage,
28458
+ message: `Value-change impact is incomplete because the vault lacks coverage for: ${missingCoverage.join(", ")}. Absence of impacts in those families means "not checked", not "safe".`
28459
+ };
28460
+ };
28461
+ whatIfChangeFieldValueHandler = async (ctx, input2) => {
28462
+ if (!input2.fieldId.startsWith(CUSTOM_FIELD_PREFIX12)) {
28463
+ return err({
28464
+ kind: "invalid-query",
28465
+ message: `fieldId must start with '${CUSTOM_FIELD_PREFIX12}'; got '${input2.fieldId}'`,
28466
+ path: "fieldId"
28467
+ });
28468
+ }
28469
+ const fieldId = input2.fieldId;
28470
+ const nodeResult = await getNodeById(ctx.graph, fieldId);
28471
+ if (!nodeResult.ok) {
28472
+ return err({ kind: "internal", message: `graph query failed: ${nodeResult.error.message}` });
28473
+ }
28474
+ if (nodeResult.value === null) {
28475
+ return err({ kind: "component-not-found", message: `no field with id ${fieldId}`, path: fieldId });
28476
+ }
28477
+ const assessmentResult = await assessValueChange(ctx, nodeResult.value);
28478
+ if (!assessmentResult.ok)
28479
+ return err(assessmentResult.error);
28480
+ const a = assessmentResult.value;
28481
+ const recommendedChecks = input2.newValue !== void 0 ? [
28482
+ `Verify the proposed value "${input2.newValue}" does not collide on a unique/external-ID field and is accepted by referencing automation.`,
28483
+ ...a.recommendedChecks
28484
+ ] : a.recommendedChecks;
28485
+ const coverageCaveat = coverageCaveatFor4(ctx);
28486
+ const hasAutomation = a.buckets.some((b) => b.bucket === "automation");
28487
+ const confidence = hasAutomation ? "heuristic" : "declared";
28488
+ return ok({
28489
+ data: {
28490
+ fieldId,
28491
+ object: a.object,
28492
+ field: a.field,
28493
+ mutable: a.mutable,
28494
+ overallSeverity: a.overallSeverity,
28495
+ buckets: a.buckets,
28496
+ disclosures: a.disclosures,
28497
+ recommendedChecks,
28498
+ ...input2.newValue !== void 0 ? { newValue: input2.newValue } : {},
28499
+ ...coverageCaveat !== void 0 ? { coverageCaveat } : {},
28500
+ trust: {
28501
+ provenance: "offline_snapshot",
28502
+ confidence,
28503
+ freshness: { snapshotRefreshedAt: ctx.manifest.refreshedAt },
28504
+ completeness: {
28505
+ status: coverageCaveat === void 0 ? "complete" : coverageCaveat.status,
28506
+ ...coverageCaveat !== void 0 ? { missingCoverage: coverageCaveat.missingCoverage } : {}
28507
+ },
28508
+ limitations: [
28509
+ DISCLOSURE11,
28510
+ ...a.disclosures,
28511
+ ...coverageCaveat !== void 0 ? [coverageCaveat.message] : []
28512
+ ]
28513
+ },
28514
+ disclosure: DISCLOSURE11
28515
+ },
28516
+ vaultState: {
28517
+ sourceTreeHash: ctx.manifest.sourceTreeHash,
28518
+ refreshedAt: ctx.manifest.refreshedAt
28519
+ }
28520
+ });
28521
+ };
28522
+ }
28523
+ });
28524
+
26630
28525
  // ../mcp/dist/src/tools/what-if-change-method-signature.js
26631
- import { z as z104 } from "zod";
26632
- var APEX_CLASS_PREFIX9, DISCLOSURE8, whatIfChangeMethodSignatureInputSchema, readMethodName, isTestClass8, buildExplanation2, classifyCaller, aggregateVerdict4, compareImpacts, compareIds, collectCallers, collectCoveringTests, whatIfChangeMethodSignatureHandler;
28526
+ import { z as z108 } from "zod";
28527
+ var APEX_CLASS_PREFIX9, DISCLOSURE12, whatIfChangeMethodSignatureInputSchema, readMethodName, isTestClass8, buildExplanation2, classifyCaller, aggregateVerdict4, compareImpacts, compareIds, collectCallers, collectCoveringTests, whatIfChangeMethodSignatureHandler;
26633
28528
  var init_what_if_change_method_signature = __esm({
26634
28529
  "../mcp/dist/src/tools/what-if-change-method-signature.js"() {
26635
28530
  "use strict";
@@ -26637,12 +28532,13 @@ var init_what_if_change_method_signature = __esm({
26637
28532
  init_src();
26638
28533
  init_coerce_id();
26639
28534
  init_coverage_trust();
28535
+ init_phantom_node();
26640
28536
  APEX_CLASS_PREFIX9 = "ApexClass:";
26641
- DISCLOSURE8 = "callers identified via the v1.4 apex-scanner are at heuristic confidence; dynamic dispatch via Type.forName + invoke is invisible. Test classes are identified by @isTest + naming convention (className + 'Test' suffix) and by coversTest edges; a test class that doesn't follow the naming convention and doesn't carry a @TestVisible-tagged covering reference may be missed.";
26642
- whatIfChangeMethodSignatureInputSchema = z104.object({
26643
- classApiName: z104.string().min(1),
26644
- methodName: z104.string().min(1),
26645
- newSignature: z104.string().optional()
28537
+ DISCLOSURE12 = "callers identified via the v1.4 apex-scanner are at heuristic confidence; dynamic dispatch via Type.forName + invoke is invisible. Test classes are identified by @isTest + naming convention (className + 'Test' suffix) and by coversTest edges; a test class that doesn't follow the naming convention and doesn't carry a @TestVisible-tagged covering reference may be missed.";
28538
+ whatIfChangeMethodSignatureInputSchema = z108.object({
28539
+ classApiName: z108.string().min(1),
28540
+ methodName: z108.string().min(1),
28541
+ newSignature: z108.string().optional()
26646
28542
  });
26647
28543
  readMethodName = (edge) => {
26648
28544
  const raw = edge.properties["methodName"];
@@ -26776,7 +28672,7 @@ var init_what_if_change_method_signature = __esm({
26776
28672
  if (nodeResult.value === null) {
26777
28673
  return err({
26778
28674
  kind: "component-not-found",
26779
- message: `no ApexClass with id ${classId}`,
28675
+ message: await phantomAwareNotFoundMessage(ctx, classId, "ApexClass"),
26780
28676
  path: classId
26781
28677
  });
26782
28678
  }
@@ -26823,7 +28719,7 @@ var init_what_if_change_method_signature = __esm({
26823
28719
  verdict: coverage.verdict,
26824
28720
  ...coverage.coverageCaveat !== void 0 ? { coverageCaveat: coverage.coverageCaveat } : {},
26825
28721
  trust: coverage.trust,
26826
- disclosure: DISCLOSURE8
28722
+ disclosure: DISCLOSURE12
26827
28723
  },
26828
28724
  vaultState: {
26829
28725
  sourceTreeHash: ctx.manifest.sourceTreeHash,
@@ -26835,8 +28731,8 @@ var init_what_if_change_method_signature = __esm({
26835
28731
  });
26836
28732
 
26837
28733
  // ../mcp/dist/src/tools/what-if-deactivate-flow.js
26838
- import { z as z105 } from "zod";
26839
- var FLOW_PREFIX3, DISCLOSURE9, whatIfDeactivateFlowInputSchema, stripPrefix2, readFlowStatus2, classifyOutgoingEdge, buildExplanation3, aggregateVerdict5, compareImpacts2, collectFiringConditions, whatIfDeactivateFlowHandler;
28734
+ import { z as z109 } from "zod";
28735
+ var FLOW_PREFIX3, DISCLOSURE13, whatIfDeactivateFlowInputSchema, stripPrefix2, readFlowStatus2, classifyOutgoingEdge, buildExplanation3, aggregateVerdict5, compareImpacts2, collectFiringConditions, whatIfDeactivateFlowHandler;
26840
28736
  var init_what_if_deactivate_flow = __esm({
26841
28737
  "../mcp/dist/src/tools/what-if-deactivate-flow.js"() {
26842
28738
  "use strict";
@@ -26845,9 +28741,9 @@ var init_what_if_deactivate_flow = __esm({
26845
28741
  init_coerce_id();
26846
28742
  init_coverage_trust();
26847
28743
  FLOW_PREFIX3 = "Flow:";
26848
- DISCLOSURE9 = "v2.3 what-if analysis is composition over the v2.2 vault state. Deactivating a Flow stops every action listed in impacts; the Flow's definition remains in the org and a later reactivation restores the effects. Apex code that conditionally invokes the Flow via Flow.Interview or @InvocableMethod chains is invisible to the heuristic walker; review callers via sfi.find_code_usages targeting the Flow id before relying on this finding.";
26849
- whatIfDeactivateFlowInputSchema = z105.object({
26850
- flowId: z105.string().min(1)
28744
+ DISCLOSURE13 = "v2.3 what-if analysis is composition over the v2.2 vault state. Deactivating a Flow stops every action listed in impacts; the Flow's definition remains in the org and a later reactivation restores the effects. Apex code that conditionally invokes the Flow via Flow.Interview or @InvocableMethod chains is invisible to the heuristic walker; review callers via sfi.find_code_usages targeting the Flow id before relying on this finding.";
28745
+ whatIfDeactivateFlowInputSchema = z109.object({
28746
+ flowId: z109.string().min(1)
26851
28747
  });
26852
28748
  stripPrefix2 = (id) => {
26853
28749
  const colonIdx = id.indexOf(":");
@@ -27005,7 +28901,7 @@ var init_what_if_deactivate_flow = __esm({
27005
28901
  verdict: coverage.verdict,
27006
28902
  ...coverage.coverageCaveat !== void 0 ? { coverageCaveat: coverage.coverageCaveat } : {},
27007
28903
  trust: coverage.trust,
27008
- disclosure: DISCLOSURE9
28904
+ disclosure: DISCLOSURE13
27009
28905
  },
27010
28906
  vaultState: {
27011
28907
  sourceTreeHash: ctx.manifest.sourceTreeHash,
@@ -27017,8 +28913,8 @@ var init_what_if_deactivate_flow = __esm({
27017
28913
  });
27018
28914
 
27019
28915
  // ../mcp/dist/src/tools/what-if-disable-trigger.js
27020
- import { z as z106 } from "zod";
27021
- var APEX_TRIGGER_PREFIX7, DISCLOSURE10, whatIfDisableTriggerInputSchema, readTriggerStatus, readTriggerEvents, classifyOutgoingEdge2, buildExplanation4, aggregateVerdict6, compareImpacts3, findParentObject, whatIfDisableTriggerHandler;
28916
+ import { z as z110 } from "zod";
28917
+ var APEX_TRIGGER_PREFIX7, DISCLOSURE14, whatIfDisableTriggerInputSchema, readTriggerStatus, readTriggerEvents, classifyOutgoingEdge2, buildExplanation4, aggregateVerdict6, compareImpacts3, findParentObject, whatIfDisableTriggerHandler;
27022
28918
  var init_what_if_disable_trigger = __esm({
27023
28919
  "../mcp/dist/src/tools/what-if-disable-trigger.js"() {
27024
28920
  "use strict";
@@ -27026,9 +28922,9 @@ var init_what_if_disable_trigger = __esm({
27026
28922
  init_src();
27027
28923
  init_coverage_trust();
27028
28924
  APEX_TRIGGER_PREFIX7 = "ApexTrigger:";
27029
- DISCLOSURE10 = "v2.3 what-if analysis is composition over the v2.2 vault state. Disabling a trigger stops every action listed in impacts; the trigger definition remains in the org and a later re-enable restores the effects. The v0.3 apex-scanner's edge confidence is heuristic for most outgoing edges; spot-check the trigger body when a finding's confidence is heuristic. Indirect dispatch via trigger framework base classes (TriggerHandler, fflib) may be partially invisible to the recognizer.";
27030
- whatIfDisableTriggerInputSchema = z106.object({
27031
- triggerId: z106.string().min(1)
28925
+ DISCLOSURE14 = "v2.3 what-if analysis is composition over the v2.2 vault state. Disabling a trigger stops every action listed in impacts; the trigger definition remains in the org and a later re-enable restores the effects. The v0.3 apex-scanner's edge confidence is heuristic for most outgoing edges; spot-check the trigger body when a finding's confidence is heuristic. Indirect dispatch via trigger framework base classes (TriggerHandler, fflib) may be partially invisible to the recognizer.";
28926
+ whatIfDisableTriggerInputSchema = z110.object({
28927
+ triggerId: z110.string().min(1)
27032
28928
  });
27033
28929
  readTriggerStatus = (node) => {
27034
28930
  const raw = node.properties["status"];
@@ -27179,7 +29075,7 @@ var init_what_if_disable_trigger = __esm({
27179
29075
  verdict: coverage.verdict,
27180
29076
  ...coverage.coverageCaveat !== void 0 ? { coverageCaveat: coverage.coverageCaveat } : {},
27181
29077
  trust: coverage.trust,
27182
- disclosure: DISCLOSURE10
29078
+ disclosure: DISCLOSURE14
27183
29079
  },
27184
29080
  vaultState: {
27185
29081
  sourceTreeHash: ctx.manifest.sourceTreeHash,
@@ -27191,22 +29087,22 @@ var init_what_if_disable_trigger = __esm({
27191
29087
  });
27192
29088
 
27193
29089
  // ../mcp/dist/src/tools/what-if-merge-profiles.js
27194
- import { z as z107 } from "zod";
27195
- var PROFILE_PREFIX, DISCLOSURE11, MERGE_DEFAULT_LIMIT, MERGE_MAX_LIMIT, whatIfMergeProfilesInputSchema, readUserPermissions, readTabVisibilities, readLayoutAssignments2, readRecordTypeVisibilities, classifyGrantTarget, stripPrefix3, fieldGrantToLevel, gatherGrants, gatherProfileSettings, objectFlagsEqual, recordTypeVisibilityEqual, compareScalarMaps, compareObjectPermissions, compareFieldPermissions, compareTabVisibilities, compareLayoutAssignments, compareRecordTypeVisibilities, fetchProfile, sortConflicts, whatIfMergeProfilesHandler;
29090
+ import { z as z111 } from "zod";
29091
+ var PROFILE_PREFIX, DISCLOSURE15, MERGE_DEFAULT_LIMIT, MERGE_MAX_LIMIT, whatIfMergeProfilesInputSchema, readUserPermissions, readTabVisibilities, readLayoutAssignments2, readRecordTypeVisibilities, classifyGrantTarget, stripPrefix3, fieldGrantToLevel, gatherGrants, gatherProfileSettings, objectFlagsEqual, recordTypeVisibilityEqual, compareScalarMaps, compareObjectPermissions, compareFieldPermissions, compareTabVisibilities, compareLayoutAssignments, compareRecordTypeVisibilities, fetchProfile, sortConflicts, whatIfMergeProfilesHandler;
27196
29092
  var init_what_if_merge_profiles = __esm({
27197
29093
  "../mcp/dist/src/tools/what-if-merge-profiles.js"() {
27198
29094
  "use strict";
27199
29095
  init_dist();
27200
29096
  init_src();
27201
29097
  PROFILE_PREFIX = "Profile:";
27202
- DISCLOSURE11 = "v2.3 surfaces conflicts but does NOT auto-resolve. Recommended policies are heuristic; manually verify each conflict before applying. Profile-edition rollup (e.g., admin-level overrides) is not modeled.";
29098
+ DISCLOSURE15 = "v2.3 surfaces conflicts but does NOT auto-resolve. Recommended policies are heuristic; manually verify each conflict before applying. Profile-edition rollup (e.g., admin-level overrides) is not modeled.";
27203
29099
  MERGE_DEFAULT_LIMIT = 120;
27204
29100
  MERGE_MAX_LIMIT = 2e3;
27205
- whatIfMergeProfilesInputSchema = z107.object({
27206
- profileIdA: z107.string().min(1),
27207
- profileIdB: z107.string().min(1),
27208
- limit: z107.number().int().min(1).max(MERGE_MAX_LIMIT).optional(),
27209
- offset: z107.number().int().min(0).optional()
29101
+ whatIfMergeProfilesInputSchema = z111.object({
29102
+ profileIdA: z111.string().min(1),
29103
+ profileIdB: z111.string().min(1),
29104
+ limit: z111.number().int().min(1).max(MERGE_MAX_LIMIT).optional(),
29105
+ offset: z111.number().int().min(0).optional()
27210
29106
  });
27211
29107
  readUserPermissions = (profile) => {
27212
29108
  const raw = profile.properties["userPermissions"];
@@ -27591,7 +29487,7 @@ var init_what_if_merge_profiles = __esm({
27591
29487
  const page = conflicts.slice(offset, offset + limit);
27592
29488
  const hasMore = offset + page.length < conflicts.length;
27593
29489
  const truncated = hasMore || offset > 0;
27594
- const disclosure = truncated ? `${DISCLOSURE11} Returning conflicts ${offset}\u2013${offset + page.length} of ${conflicts.length} (page size ${limit}); summary.byCategory / byPolicy hold the COMPLETE counts. Page through the rest with offset/limit.` : DISCLOSURE11;
29490
+ const disclosure = truncated ? `${DISCLOSURE15} Returning conflicts ${offset}\u2013${offset + page.length} of ${conflicts.length} (page size ${limit}); summary.byCategory / byPolicy hold the COMPLETE counts. Page through the rest with offset/limit.` : DISCLOSURE15;
27595
29491
  return ok({
27596
29492
  data: {
27597
29493
  profileIdA,
@@ -27620,8 +29516,8 @@ var init_what_if_merge_profiles = __esm({
27620
29516
  });
27621
29517
 
27622
29518
  // ../mcp/dist/src/tools/what-if-remove-picklist-value.js
27623
- import { z as z108 } from "zod";
27624
- var CUSTOM_FIELD_PREFIX11, PICKLIST_TYPES2, PICKLIST_VALUE_COVERAGE, DISCLOSURE12, coverageCaveatFor3, whatIfRemovePicklistValueInputSchema, buildValueNeedles, containsAnyNeedle, extractHaystackTexts, classifyCategory2, buildExplanation5, findValueInConditionalContexts, aggregateVerdict7, whatIfRemovePicklistValueHandler;
29519
+ import { z as z112 } from "zod";
29520
+ var CUSTOM_FIELD_PREFIX13, PICKLIST_TYPES2, PICKLIST_VALUE_COVERAGE, DISCLOSURE16, coverageCaveatFor5, whatIfRemovePicklistValueInputSchema, buildValueNeedles, containsAnyNeedle, extractHaystackTexts, classifyCategory2, buildExplanation5, findValueInConditionalContexts, aggregateVerdict7, whatIfRemovePicklistValueHandler;
27625
29521
  var init_what_if_remove_picklist_value = __esm({
27626
29522
  "../mcp/dist/src/tools/what-if-remove-picklist-value.js"() {
27627
29523
  "use strict";
@@ -27629,7 +29525,7 @@ var init_what_if_remove_picklist_value = __esm({
27629
29525
  init_src();
27630
29526
  init_src2();
27631
29527
  init_field_properties();
27632
- CUSTOM_FIELD_PREFIX11 = "CustomField:";
29528
+ CUSTOM_FIELD_PREFIX13 = "CustomField:";
27633
29529
  PICKLIST_TYPES2 = /* @__PURE__ */ new Set([
27634
29530
  "Picklist",
27635
29531
  "MultiselectPicklist"
@@ -27647,8 +29543,8 @@ var init_what_if_remove_picklist_value = __esm({
27647
29543
  "ListView",
27648
29544
  "FlexiPage"
27649
29545
  ];
27650
- DISCLOSURE12 = "Apex code referencing the picklist value as a string literal is recognized only for static literals. Variable-based picklist comparisons (`if (account.Industry__c == myVar)`), dynamic SOQL strings, and reflective field access via `obj.get('FieldName')` are invisible to the recognizer; review dynamic comparisons separately before removing the value.";
27651
- coverageCaveatFor3 = (ctx) => {
29546
+ DISCLOSURE16 = "Apex code referencing the picklist value as a string literal is recognized only for static literals. Variable-based picklist comparisons (`if (account.Industry__c == myVar)`), dynamic SOQL strings, and reflective field access via `obj.get('FieldName')` are invisible to the recognizer; review dynamic comparisons separately before removing the value.";
29547
+ coverageCaveatFor5 = (ctx) => {
27652
29548
  const coverage = summarizeCoverage(ctx.manifest, PICKLIST_VALUE_COVERAGE);
27653
29549
  if (coverage.status === "complete")
27654
29550
  return void 0;
@@ -27659,9 +29555,9 @@ var init_what_if_remove_picklist_value = __esm({
27659
29555
  message: `Picklist-value removal impact is incomplete because the vault lacks coverage for: ${missingCoverage.join(", ")}. Absence of references in those families means "not checked", not "safe".`
27660
29556
  };
27661
29557
  };
27662
- whatIfRemovePicklistValueInputSchema = z108.object({
27663
- fieldId: z108.string().min(1),
27664
- value: z108.string().min(1)
29558
+ whatIfRemovePicklistValueInputSchema = z112.object({
29559
+ fieldId: z112.string().min(1),
29560
+ value: z112.string().min(1)
27665
29561
  });
27666
29562
  buildValueNeedles = (value) => [
27667
29563
  `'${value}'`,
@@ -27760,10 +29656,10 @@ var init_what_if_remove_picklist_value = __esm({
27760
29656
  return "risky";
27761
29657
  };
27762
29658
  whatIfRemovePicklistValueHandler = async (ctx, input2) => {
27763
- if (!input2.fieldId.startsWith(CUSTOM_FIELD_PREFIX11)) {
29659
+ if (!input2.fieldId.startsWith(CUSTOM_FIELD_PREFIX13)) {
27764
29660
  return err({
27765
29661
  kind: "invalid-query",
27766
- message: `fieldId must start with '${CUSTOM_FIELD_PREFIX11}'; got '${input2.fieldId}'`,
29662
+ message: `fieldId must start with '${CUSTOM_FIELD_PREFIX13}'; got '${input2.fieldId}'`,
27767
29663
  path: "fieldId"
27768
29664
  });
27769
29665
  }
@@ -27842,7 +29738,7 @@ var init_what_if_remove_picklist_value = __esm({
27842
29738
  }
27843
29739
  const sortedImpacts = [...impactsById.values()].sort((a, b) => a.componentId < b.componentId ? -1 : a.componentId > b.componentId ? 1 : 0);
27844
29740
  const compatibility = sortedImpacts.length === 0 ? "review" : "breaking";
27845
- const coverageCaveat = coverageCaveatFor3(ctx);
29741
+ const coverageCaveat = coverageCaveatFor5(ctx);
27846
29742
  const rawVerdict = aggregateVerdict7(sortedImpacts);
27847
29743
  const verdict = rawVerdict === "safe" && coverageCaveat !== void 0 ? "review" : rawVerdict;
27848
29744
  return ok({
@@ -27863,11 +29759,11 @@ var init_what_if_remove_picklist_value = __esm({
27863
29759
  ...coverageCaveat !== void 0 ? { missingCoverage: coverageCaveat.missingCoverage } : {}
27864
29760
  },
27865
29761
  limitations: [
27866
- DISCLOSURE12,
29762
+ DISCLOSURE16,
27867
29763
  ...coverageCaveat !== void 0 ? [coverageCaveat.message] : []
27868
29764
  ]
27869
29765
  },
27870
- disclosure: DISCLOSURE12
29766
+ disclosure: DISCLOSURE16
27871
29767
  },
27872
29768
  vaultState: {
27873
29769
  sourceTreeHash: ctx.manifest.sourceTreeHash,
@@ -27879,8 +29775,8 @@ var init_what_if_remove_picklist_value = __esm({
27879
29775
  });
27880
29776
 
27881
29777
  // ../mcp/dist/src/tools/what-if-split-profile.js
27882
- import { z as z109 } from "zod";
27883
- var PROFILE_PREFIX2, PERMISSION_SET_PREFIX, SPLIT_DEFAULT_LIMIT, SPLIT_MAX_LIMIT, DISCLOSURE13, whatIfSplitProfileInputSchema, tokenize4, makeCandidate, resolveTargets, overlapCount, keywordMatch, domainClusterMatch, assignGrant, splitGrants, fetchProfile2, sortAssignments, sortUnassigned, whatIfSplitProfileHandler;
29778
+ import { z as z113 } from "zod";
29779
+ var PROFILE_PREFIX2, PERMISSION_SET_PREFIX, SPLIT_DEFAULT_LIMIT, SPLIT_MAX_LIMIT, DISCLOSURE17, whatIfSplitProfileInputSchema, tokenize4, makeCandidate, resolveTargets, overlapCount, keywordMatch, domainClusterMatch, assignGrant, splitGrants, fetchProfile2, sortAssignments, sortUnassigned, whatIfSplitProfileHandler;
27884
29780
  var init_what_if_split_profile = __esm({
27885
29781
  "../mcp/dist/src/tools/what-if-split-profile.js"() {
27886
29782
  "use strict";
@@ -27890,12 +29786,12 @@ var init_what_if_split_profile = __esm({
27890
29786
  PERMISSION_SET_PREFIX = "PermissionSet:";
27891
29787
  SPLIT_DEFAULT_LIMIT = 120;
27892
29788
  SPLIT_MAX_LIMIT = 2e3;
27893
- DISCLOSURE13 = "v2.3 split clustering is approximate; the greedy keyword-match heuristic is fail-conservative. Review every assignment before applying \u2014 grants the heuristic could not place are surfaced in unassignedSettings rather than forced into an inappropriate target. Layout assignments, tab visibilities, and record-type visibilities are not split (Profile-only settings in the Salesforce metadata model).";
27894
- whatIfSplitProfileInputSchema = z109.object({
27895
- profileId: z109.string().min(1),
27896
- targetPermSets: z109.array(z109.string().min(1)).min(1),
27897
- limit: z109.number().int().min(1).max(SPLIT_MAX_LIMIT).optional(),
27898
- offset: z109.number().int().min(0).optional()
29789
+ DISCLOSURE17 = "v2.3 split clustering is approximate; the greedy keyword-match heuristic is fail-conservative. Review every assignment before applying \u2014 grants the heuristic could not place are surfaced in unassignedSettings rather than forced into an inappropriate target. Layout assignments, tab visibilities, and record-type visibilities are not split (Profile-only settings in the Salesforce metadata model).";
29790
+ whatIfSplitProfileInputSchema = z113.object({
29791
+ profileId: z113.string().min(1),
29792
+ targetPermSets: z113.array(z113.string().min(1)).min(1),
29793
+ limit: z113.number().int().min(1).max(SPLIT_MAX_LIMIT).optional(),
29794
+ offset: z113.number().int().min(0).optional()
27899
29795
  });
27900
29796
  tokenize4 = (raw) => {
27901
29797
  const spaced = raw.replace(/[._\-/]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2");
@@ -28156,7 +30052,7 @@ var init_what_if_split_profile = __esm({
28156
30052
  const page = assignments.slice(offset, offset + limit);
28157
30053
  const hasMore = offset + page.length < assignments.length;
28158
30054
  const truncated = hasMore || offset > 0;
28159
- const disclosure = truncated ? `${DISCLOSURE13} Returning assignments ${offset}\u2013${offset + page.length} of ${assignments.length} (page size ${limit}); summary.byTarget holds the COMPLETE per-target counts. Page through the remaining grants with offset/limit.` : DISCLOSURE13;
30055
+ const disclosure = truncated ? `${DISCLOSURE17} Returning assignments ${offset}\u2013${offset + page.length} of ${assignments.length} (page size ${limit}); summary.byTarget holds the COMPLETE per-target counts. Page through the remaining grants with offset/limit.` : DISCLOSURE17;
28160
30056
  return ok({
28161
30057
  data: {
28162
30058
  profileId,
@@ -28184,7 +30080,7 @@ var init_what_if_split_profile = __esm({
28184
30080
  });
28185
30081
 
28186
30082
  // ../mcp/dist/src/tools/why-cant-user-see-record.js
28187
- import { z as z110 } from "zod";
30083
+ import { z as z114 } from "zod";
28188
30084
  var OWD_VISIBLE, OWD_RESTRICTED, ALL_INTERNAL_USERS_GROUP_ID, ROLE_HIERARCHY_MAX_DEPTH, whyCantUserSeeRecordInputSchema, step2, evaluateOWD, grantsReadOrBetter, evaluatePermissionGrants, walkRoleHierarchy, evaluateRoleHierarchy, buildUserMembership, fetchSharingRules, ownerRuleMatches, evaluateOwnerSharingRules, evaluateCriteriaSharingRules, listObjectChildRules, evaluateRestrictionRules, evaluateScopingRules, evaluatePermissionSetGroups, UNKNOWN_TAIL, aggregateVerdict8, PROFILE_PREFIX3, PERMISSION_SET_PREFIX2, ROLE_PREFIX, GROUP_PREFIX, QUEUE_PREFIX, coerceGroupId, coerceUserContext, whyCantUserSeeRecordHandler;
28189
30085
  var init_why_cant_user_see_record = __esm({
28190
30086
  "../mcp/dist/src/tools/why-cant-user-see-record.js"() {
@@ -28205,13 +30101,13 @@ var init_why_cant_user_see_record = __esm({
28205
30101
  ]);
28206
30102
  ALL_INTERNAL_USERS_GROUP_ID = "Group:AllInternalUsers";
28207
30103
  ROLE_HIERARCHY_MAX_DEPTH = 100;
28208
- whyCantUserSeeRecordInputSchema = z110.object({
28209
- componentId: z110.string().min(1),
28210
- userContext: z110.object({
28211
- profileId: z110.string().min(1).optional(),
28212
- permissionSetIds: z110.array(z110.string().min(1)).optional(),
28213
- roleId: z110.string().min(1).optional(),
28214
- groupIds: z110.array(z110.string().min(1)).optional()
30104
+ whyCantUserSeeRecordInputSchema = z114.object({
30105
+ componentId: z114.string().min(1),
30106
+ userContext: z114.object({
30107
+ profileId: z114.string().min(1).optional(),
30108
+ permissionSetIds: z114.array(z114.string().min(1)).optional(),
30109
+ roleId: z114.string().min(1).optional(),
30110
+ groupIds: z114.array(z114.string().min(1)).optional()
28215
30111
  }).refine((uc) => uc.profileId !== void 0 || uc.permissionSetIds !== void 0 && uc.permissionSetIds.length > 0 || uc.roleId !== void 0 || uc.groupIds !== void 0 && uc.groupIds.length > 0, {
28216
30112
  message: "userContext must supply at least one of: profileId, permissionSetIds, roleId, groupIds"
28217
30113
  })
@@ -28581,17 +30477,17 @@ var init_why_cant_user_see_record = __esm({
28581
30477
  });
28582
30478
 
28583
30479
  // ../mcp/dist/src/tools/why-field-changed.js
28584
- import { z as z111 } from "zod";
28585
- var CUSTOM_FIELD_PREFIX12, DISCLOSURE14, whyFieldChangedInputSchema, surfaceFirstCondition3, surfaceTriggerEvent, buildWriter, whyFieldChangedHandler;
30480
+ import { z as z115 } from "zod";
30481
+ var CUSTOM_FIELD_PREFIX14, DISCLOSURE18, whyFieldChangedInputSchema, surfaceFirstCondition3, surfaceTriggerEvent, buildWriter, whyFieldChangedHandler;
28586
30482
  var init_why_field_changed = __esm({
28587
30483
  "../mcp/dist/src/tools/why-field-changed.js"() {
28588
30484
  "use strict";
28589
30485
  init_dist();
28590
30486
  init_src();
28591
- CUSTOM_FIELD_PREFIX12 = "CustomField:";
28592
- DISCLOSURE14 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
28593
- whyFieldChangedInputSchema = z111.object({
28594
- fieldId: z111.string().min(1)
30487
+ CUSTOM_FIELD_PREFIX14 = "CustomField:";
30488
+ DISCLOSURE18 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
30489
+ whyFieldChangedInputSchema = z115.object({
30490
+ fieldId: z115.string().min(1)
28595
30491
  });
28596
30492
  surfaceFirstCondition3 = async (ctx, writerId) => {
28597
30493
  const edgesResult = await listEdges(ctx.graph, writerId, {
@@ -28641,10 +30537,10 @@ var init_why_field_changed = __esm({
28641
30537
  return ok(triggerEvent === void 0 ? withCondition : { ...withCondition, triggerEvent });
28642
30538
  };
28643
30539
  whyFieldChangedHandler = async (ctx, input2) => {
28644
- if (!input2.fieldId.startsWith(CUSTOM_FIELD_PREFIX12)) {
30540
+ if (!input2.fieldId.startsWith(CUSTOM_FIELD_PREFIX14)) {
28645
30541
  return err({
28646
30542
  kind: "invalid-query",
28647
- message: `fieldId must start with '${CUSTOM_FIELD_PREFIX12}'; got '${input2.fieldId}'`,
30543
+ message: `fieldId must start with '${CUSTOM_FIELD_PREFIX14}'; got '${input2.fieldId}'`,
28648
30544
  path: "fieldId"
28649
30545
  });
28650
30546
  }
@@ -28704,7 +30600,7 @@ var init_why_field_changed = __esm({
28704
30600
  fieldId,
28705
30601
  writers: sortedWriters,
28706
30602
  summary: { declaredCount, heuristicCount },
28707
- disclosure: DISCLOSURE14
30603
+ disclosure: DISCLOSURE18
28708
30604
  },
28709
30605
  vaultState: {
28710
30606
  sourceTreeHash: ctx.manifest.sourceTreeHash,
@@ -28725,7 +30621,7 @@ __export(tools_exports, {
28725
30621
  registerTools: () => registerTools
28726
30622
  });
28727
30623
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
28728
- var SEARCH_COMPONENTS_INPUT_SCHEMA, CAPABILITIES_INPUT_SCHEMA, ORG_PULSE_INPUT_SCHEMA, FLEET_FIND_INPUT_SCHEMA, RESOLVE_INPUT_SCHEMA, GET_COMPONENT_INPUT_SCHEMA, GET_EDGES_INPUT_SCHEMA, LIST_COMPONENTS_INPUT_SCHEMA, GET_SUBGRAPH_INPUT_SCHEMA, SEARCH_APEX_SOURCE_INPUT_SCHEMA, SEARCH_FLOW_METADATA_INPUT_SCHEMA, NAMING_CONVENTION_REPORT_INPUT_SCHEMA, GET_MANIFEST_INPUT_SCHEMA, COVERAGE_REPORT_INPUT_SCHEMA, BASELINE_ACKNOWLEDGE_INPUT_SCHEMA, BASELINE_STATUS_INPUT_SCHEMA, TREND_INPUT_SCHEMA, CHURN_INPUT_SCHEMA, LIVE_ENABLED_PROPERTY, LIVE_DESCRIBE_INPUT_SCHEMA, LIVE_COUNT_INPUT_SCHEMA, LIVE_SAMPLE_INPUT_SCHEMA, LIVE_FIELD_POPULATION_INPUT_SCHEMA, LIVE_ORG_LIMITS_INPUT_SCHEMA, LIVE_INACTIVE_USERS_INPUT_SCHEMA, LIVE_LICENSE_USAGE_INPUT_SCHEMA, LIVE_GROUP_COUNT_INPUT_SCHEMA, LIVE_STALE_RECORDS_INPUT_SCHEMA, LIVE_RECENT_ACTIVITY_INPUT_SCHEMA, LIVE_AGGREGATE_INPUT_SCHEMA, LIVE_DUPLICATE_CHECK_INPUT_SCHEMA, LIVE_OWNER_BREAKDOWN_INPUT_SCHEMA, LIVE_STORAGE_BY_OBJECT_INPUT_SCHEMA, LIVE_REPORT_USAGE_INPUT_SCHEMA, LIVE_FOLDER_ACCESS_INPUT_SCHEMA, LIVE_EMAIL_TEMPLATE_USAGE_INPUT_SCHEMA, LIVE_ORG_HEALTH_INPUT_SCHEMA, LIVE_CONSENT_INPUT_SCHEMA, ROUTE_QUESTION_INPUT_SCHEMA, SYNTHESIS_INPUT_SCHEMA, HEALTH_CHECK_INPUT_SCHEMA, GET_IMPACT_INPUT_SCHEMA, FIND_FORMULA_REFERENCES_INPUT_SCHEMA, FIND_APEX_USAGES_INPUT_SCHEMA, FIND_CODE_USAGES_INPUT_SCHEMA, WHY_CANT_USER_SEE_RECORD_INPUT_SCHEMA, LAYOUT_FOR_USER_INPUT_SCHEMA, INTEGRATION_MAP_INPUT_SCHEMA, EVENT_SUBSCRIBERS_INPUT_SCHEMA, LOOKUP_RECORD_INPUT_SCHEMA, EXPLAIN_FIELD_INPUT_SCHEMA, SAFE_TO_DELETE_FIELD_INPUT_SCHEMA, UNUSED_COMPONENTS_INPUT_SCHEMA, DIFF_SNAPSHOTS_INPUT_SCHEMA, COMPARE_COMPONENTS_INPUT_SCHEMA, PII_INVENTORY_INPUT_SCHEMA, FIELD_ACCESS_AUDIT_INPUT_SCHEMA, FIELD_360_INPUT_SCHEMA, FIELD_LINEAGE_INPUT_SCHEMA, ORG_OVERVIEW_INPUT_SCHEMA, DOMAIN_CLUSTERS_INPUT_SCHEMA, CHANGED_SINCE_INPUT_SCHEMA, LAST_MODIFIED_INPUT_SCHEMA, WHAT_HAPPENS_ON_SAVE_INPUT_SCHEMA, WHY_FIELD_CHANGED_INPUT_SCHEMA, ORDER_OF_EXECUTION_INPUT_SCHEMA, EXPLAIN_FLOW_INPUT_SCHEMA, EXPLAIN_APEX_METHOD_INPUT_SCHEMA, EXPLAIN_FORMULA_INPUT_SCHEMA, UNUSED_FIELDS_DEEP_INPUT_SCHEMA, PROCESS_BUILDER_MIGRATION_CANDIDATES_INPUT_SCHEMA, UNASSIGNED_PERMISSION_SETS_INPUT_SCHEMA, EMPTY_QUEUES_AND_GROUPS_INPUT_SCHEMA, TECH_DEBT_SCORE_INPUT_SCHEMA, CODE_QUALITY_AUDIT_INPUT_SCHEMA, GOVERNOR_LIMIT_RISKS_INPUT_SCHEMA, FIND_HARDCODED_VALUES_INPUT_SCHEMA, CRUD_FLS_AUDIT_INPUT_SCHEMA, TEST_COVERAGE_GAPS_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_TYPE_INPUT_SCHEMA, WHAT_IF_REMOVE_PICKLIST_VALUE_INPUT_SCHEMA, WHAT_IF_MAKE_FIELD_REQUIRED_INPUT_SCHEMA, WHAT_IF_DEACTIVATE_FLOW_INPUT_SCHEMA, WHAT_IF_DISABLE_TRIGGER_INPUT_SCHEMA, WHAT_IF_CHANGE_METHOD_SIGNATURE_INPUT_SCHEMA, WHAT_IF_MERGE_PROFILES_INPUT_SCHEMA, WHAT_IF_SPLIT_PROFILE_INPUT_SCHEMA, GENERATE_DATA_DICTIONARY_INPUT_SCHEMA, GENERATE_ADMIN_HANDBOOK_INPUT_SCHEMA, GENERATE_ARCHITECTURE_OVERVIEW_INPUT_SCHEMA, GENERATE_SHARING_SUMMARY_INPUT_SCHEMA, GENERATE_COMPLIANCE_REPORT_INPUT_SCHEMA, GENERATE_ONBOARDING_DOC_INPUT_SCHEMA, CALL_GRAPH_INPUT_SCHEMA, DOWNSTREAM_EFFECTS_INPUT_SCHEMA, TEST_COVERAGE_FOR_METHOD_INPUT_SCHEMA, MEANINGFUL_TEST_AUDIT_INPUT_SCHEMA, METHOD_REACHABILITY_INPUT_SCHEMA, TESTS_FOR_CHANGE_INPUT_SCHEMA, PACKAGE_IMPACT_INPUT_SCHEMA, CDC_SUBSCRIBERS_INPUT_SCHEMA, ASYNC_CHAIN_DEPTH_INPUT_SCHEMA, SCHEDULED_JOB_CATALOG_INPUT_SCHEMA, OUTBOUND_MESSAGE_CATALOG_INPUT_SCHEMA, ENDPOINT_CATALOG_INPUT_SCHEMA, FIELD_MEANING_INPUT_SCHEMA, DISAMBIGUATE_CONCEPTS_INPUT_SCHEMA, FIELD_PROVENANCE_INPUT_SCHEMA, FIND_FIELD_ANYWHERE_INPUT_SCHEMA, FIND_SEMANTIC_FIELD_INPUT_SCHEMA, FIND_HARDCODED_VALUES_ANYWHERE_INPUT_SCHEMA, FIND_CLONE_PATTERNS_INPUT_SCHEMA, FIND_DEAD_CODE_INPUT_SCHEMA, CPQ_RULE_CHAIN_INPUT_SCHEMA, CPQ_QUOTE_TEMPLATE_BREAKDOWN_INPUT_SCHEMA, CPQ_DEPENDENCY_MAP_INPUT_SCHEMA, COMPARE_VAULTS_INPUT_SCHEMA, COMPARE_OBJECT_ACROSS_VAULTS_INPUT_SCHEMA, COMPARE_PROFILE_ACROSS_VAULTS_INPUT_SCHEMA, FIELD_MAPPING_BETWEEN_OBJECTS_INPUT_SCHEMA, INTEGRATION_PROCEDURE_CHAIN_INPUT_SCHEMA, OMNISCRIPT_FLOW_INPUT_SCHEMA, OMNIUICARD_WIDGET_BREAKDOWN_INPUT_SCHEMA, DATATRANSFORM_FIELD_MAP_INPUT_SCHEMA, DECISION_TABLE_BROWSE_INPUT_SCHEMA, FIND_DEPENDENCY_CYCLES_INPUT_SCHEMA, APEX_TEST_COVERAGE_INPUT_SCHEMA, AUTOMATION_BUILD_ADVISOR_INPUT_SCHEMA, APEX_BUILD_ADVISOR_INPUT_SCHEMA, FIELD_CHANGE_ADVISOR_INPUT_SCHEMA, LIVE_DRIFT_CHECK_INPUT_SCHEMA, ORG_HISTORY_INPUT_SCHEMA, V01_TOOLS, KNOWN_TOOL_NAMES, dispatchTool, runTool, registerTools, MAX_RESPONSE_BYTES, jsonResult;
30624
+ var SEARCH_COMPONENTS_INPUT_SCHEMA, CAPABILITIES_INPUT_SCHEMA, SYNTHESIZE_ANSWER_INPUT_SCHEMA, ORG_PULSE_INPUT_SCHEMA, FLEET_FIND_INPUT_SCHEMA, RESOLVE_INPUT_SCHEMA, GET_COMPONENT_INPUT_SCHEMA, GET_EDGES_INPUT_SCHEMA, LIST_COMPONENTS_INPUT_SCHEMA, GET_SUBGRAPH_INPUT_SCHEMA, SEARCH_APEX_SOURCE_INPUT_SCHEMA, SEARCH_FLOW_METADATA_INPUT_SCHEMA, NAMING_CONVENTION_REPORT_INPUT_SCHEMA, GET_MANIFEST_INPUT_SCHEMA, COVERAGE_REPORT_INPUT_SCHEMA, BASELINE_ACKNOWLEDGE_INPUT_SCHEMA, BASELINE_STATUS_INPUT_SCHEMA, TREND_INPUT_SCHEMA, CHURN_INPUT_SCHEMA, LIVE_ENABLED_PROPERTY, LIVE_DESCRIBE_INPUT_SCHEMA, LIVE_COUNT_INPUT_SCHEMA, LIVE_SAMPLE_INPUT_SCHEMA, LIVE_FIELD_POPULATION_INPUT_SCHEMA, LIVE_ORG_LIMITS_INPUT_SCHEMA, LIVE_INACTIVE_USERS_INPUT_SCHEMA, LIVE_LICENSE_USAGE_INPUT_SCHEMA, LIVE_GROUP_COUNT_INPUT_SCHEMA, LIVE_STALE_RECORDS_INPUT_SCHEMA, LIVE_RECENT_ACTIVITY_INPUT_SCHEMA, LIVE_AGGREGATE_INPUT_SCHEMA, LIVE_DUPLICATE_CHECK_INPUT_SCHEMA, LIVE_OWNER_BREAKDOWN_INPUT_SCHEMA, LIVE_STORAGE_BY_OBJECT_INPUT_SCHEMA, LIVE_REPORT_USAGE_INPUT_SCHEMA, LIVE_FOLDER_ACCESS_INPUT_SCHEMA, LIVE_EMAIL_TEMPLATE_USAGE_INPUT_SCHEMA, LIVE_ORG_HEALTH_INPUT_SCHEMA, LIVE_CONSENT_INPUT_SCHEMA, ROUTE_QUESTION_INPUT_SCHEMA, SYNTHESIS_INPUT_SCHEMA, HEALTH_CHECK_INPUT_SCHEMA, GET_IMPACT_INPUT_SCHEMA, FIND_FORMULA_REFERENCES_INPUT_SCHEMA, FIND_APEX_USAGES_INPUT_SCHEMA, FIND_CODE_USAGES_INPUT_SCHEMA, WHY_CANT_USER_SEE_RECORD_INPUT_SCHEMA, LAYOUT_FOR_USER_INPUT_SCHEMA, INTEGRATION_MAP_INPUT_SCHEMA, EVENT_SUBSCRIBERS_INPUT_SCHEMA, LOOKUP_RECORD_INPUT_SCHEMA, GUIDANCE_INPUT_SCHEMA, EXPLAIN_FIELD_INPUT_SCHEMA, SAFE_TO_DELETE_FIELD_INPUT_SCHEMA, UNUSED_COMPONENTS_INPUT_SCHEMA, DIFF_SNAPSHOTS_INPUT_SCHEMA, COMPARE_COMPONENTS_INPUT_SCHEMA, PII_INVENTORY_INPUT_SCHEMA, FIELD_ACCESS_AUDIT_INPUT_SCHEMA, FIELD_360_INPUT_SCHEMA, FIELD_LINEAGE_INPUT_SCHEMA, ORG_OVERVIEW_INPUT_SCHEMA, DOMAIN_CLUSTERS_INPUT_SCHEMA, CHANGED_SINCE_INPUT_SCHEMA, LAST_MODIFIED_INPUT_SCHEMA, WHAT_HAPPENS_ON_SAVE_INPUT_SCHEMA, WHY_FIELD_CHANGED_INPUT_SCHEMA, ORDER_OF_EXECUTION_INPUT_SCHEMA, EXPLAIN_FLOW_INPUT_SCHEMA, EXPLAIN_APEX_METHOD_INPUT_SCHEMA, EXPLAIN_FORMULA_INPUT_SCHEMA, UNUSED_FIELDS_DEEP_INPUT_SCHEMA, PROCESS_BUILDER_MIGRATION_CANDIDATES_INPUT_SCHEMA, UNASSIGNED_PERMISSION_SETS_INPUT_SCHEMA, EMPTY_QUEUES_AND_GROUPS_INPUT_SCHEMA, TECH_DEBT_SCORE_INPUT_SCHEMA, CODE_QUALITY_AUDIT_INPUT_SCHEMA, GOVERNOR_LIMIT_RISKS_INPUT_SCHEMA, FIND_HARDCODED_VALUES_INPUT_SCHEMA, CRUD_FLS_AUDIT_INPUT_SCHEMA, TEST_COVERAGE_GAPS_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_VALUE_INPUT_SCHEMA, VALUE_CHANGE_AUDIT_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_TYPE_INPUT_SCHEMA, WHAT_IF_REMOVE_PICKLIST_VALUE_INPUT_SCHEMA, WHAT_IF_MAKE_FIELD_REQUIRED_INPUT_SCHEMA, WHAT_IF_DEACTIVATE_FLOW_INPUT_SCHEMA, WHAT_IF_DISABLE_TRIGGER_INPUT_SCHEMA, WHAT_IF_CHANGE_METHOD_SIGNATURE_INPUT_SCHEMA, WHAT_IF_MERGE_PROFILES_INPUT_SCHEMA, WHAT_IF_SPLIT_PROFILE_INPUT_SCHEMA, GENERATE_DATA_DICTIONARY_INPUT_SCHEMA, GENERATE_ADMIN_HANDBOOK_INPUT_SCHEMA, GENERATE_ARCHITECTURE_OVERVIEW_INPUT_SCHEMA, GENERATE_SHARING_SUMMARY_INPUT_SCHEMA, GENERATE_COMPLIANCE_REPORT_INPUT_SCHEMA, GENERATE_ONBOARDING_DOC_INPUT_SCHEMA, CALL_GRAPH_INPUT_SCHEMA, DOWNSTREAM_EFFECTS_INPUT_SCHEMA, TEST_COVERAGE_FOR_METHOD_INPUT_SCHEMA, MEANINGFUL_TEST_AUDIT_INPUT_SCHEMA, METHOD_REACHABILITY_INPUT_SCHEMA, TESTS_FOR_CHANGE_INPUT_SCHEMA, PACKAGE_IMPACT_INPUT_SCHEMA, CDC_SUBSCRIBERS_INPUT_SCHEMA, ASYNC_CHAIN_DEPTH_INPUT_SCHEMA, SCHEDULED_JOB_CATALOG_INPUT_SCHEMA, OUTBOUND_MESSAGE_CATALOG_INPUT_SCHEMA, ENDPOINT_CATALOG_INPUT_SCHEMA, FIELD_MEANING_INPUT_SCHEMA, DISAMBIGUATE_CONCEPTS_INPUT_SCHEMA, FIELD_PROVENANCE_INPUT_SCHEMA, FIND_FIELD_ANYWHERE_INPUT_SCHEMA, FIND_SEMANTIC_FIELD_INPUT_SCHEMA, FIND_HARDCODED_VALUES_ANYWHERE_INPUT_SCHEMA, FIND_CLONE_PATTERNS_INPUT_SCHEMA, FIND_DEAD_CODE_INPUT_SCHEMA, CPQ_RULE_CHAIN_INPUT_SCHEMA, CPQ_QUOTE_TEMPLATE_BREAKDOWN_INPUT_SCHEMA, CPQ_DEPENDENCY_MAP_INPUT_SCHEMA, COMPARE_VAULTS_INPUT_SCHEMA, COMPARE_OBJECT_ACROSS_VAULTS_INPUT_SCHEMA, COMPARE_PROFILE_ACROSS_VAULTS_INPUT_SCHEMA, FIELD_MAPPING_BETWEEN_OBJECTS_INPUT_SCHEMA, INTEGRATION_PROCEDURE_CHAIN_INPUT_SCHEMA, OMNISCRIPT_FLOW_INPUT_SCHEMA, OMNIUICARD_WIDGET_BREAKDOWN_INPUT_SCHEMA, DATATRANSFORM_FIELD_MAP_INPUT_SCHEMA, DECISION_TABLE_BROWSE_INPUT_SCHEMA, FIND_DEPENDENCY_CYCLES_INPUT_SCHEMA, APEX_TEST_COVERAGE_INPUT_SCHEMA, AUTOMATION_BUILD_ADVISOR_INPUT_SCHEMA, APEX_BUILD_ADVISOR_INPUT_SCHEMA, FIELD_CHANGE_ADVISOR_INPUT_SCHEMA, LIVE_DRIFT_CHECK_INPUT_SCHEMA, ORG_HISTORY_INPUT_SCHEMA, V01_TOOLS, KNOWN_TOOL_NAMES, dispatchTool, runTool, registerTools, MAX_RESPONSE_BYTES, jsonResult;
28729
30625
  var init_tools = __esm({
28730
30626
  "../mcp/dist/src/tools/index.js"() {
28731
30627
  "use strict";
@@ -28792,6 +30688,7 @@ var init_tools = __esm({
28792
30688
  init_get_impact();
28793
30689
  init_get_subgraph();
28794
30690
  init_governor_limit_risks();
30691
+ init_guidance();
28795
30692
  init_health_check();
28796
30693
  init_integration_map();
28797
30694
  init_integration_procedure_chain();
@@ -28824,6 +30721,7 @@ var init_tools = __esm({
28824
30721
  init_search_flow_metadata();
28825
30722
  init_snapshot_trend();
28826
30723
  init_synthesis_reports();
30724
+ init_synthesize_answer();
28827
30725
  init_tech_debt_score();
28828
30726
  init_test_coverage_for_method();
28829
30727
  init_test_coverage_gaps();
@@ -28831,8 +30729,10 @@ var init_tools = __esm({
28831
30729
  init_unassigned_permission_sets();
28832
30730
  init_unused_components();
28833
30731
  init_unused_fields_deep();
30732
+ init_value_change_audit();
28834
30733
  init_what_happens_on_save();
28835
30734
  init_what_if_change_field_type();
30735
+ init_what_if_change_field_value();
28836
30736
  init_what_if_change_method_signature();
28837
30737
  init_what_if_deactivate_flow();
28838
30738
  init_what_if_disable_trigger();
@@ -28855,6 +30755,14 @@ var init_tools = __esm({
28855
30755
  type: "object",
28856
30756
  properties: {}
28857
30757
  });
30758
+ SYNTHESIZE_ANSWER_INPUT_SCHEMA = Object.freeze({
30759
+ type: "object",
30760
+ properties: {
30761
+ input: {},
30762
+ question: { type: "string" },
30763
+ draft: { type: "string" }
30764
+ }
30765
+ });
28858
30766
  ORG_PULSE_INPUT_SCHEMA = Object.freeze({
28859
30767
  type: "object",
28860
30768
  properties: {
@@ -29375,8 +31283,7 @@ var init_tools = __esm({
29375
31283
  properties: {
29376
31284
  eventId: { type: "string", minLength: 1 },
29377
31285
  limit: { type: "integer", minimum: 1, maximum: 500 }
29378
- },
29379
- required: ["eventId"]
31286
+ }
29380
31287
  });
29381
31288
  LOOKUP_RECORD_INPUT_SCHEMA = Object.freeze({
29382
31289
  type: "object",
@@ -29385,6 +31292,12 @@ var init_tools = __esm({
29385
31292
  },
29386
31293
  required: ["recordId"]
29387
31294
  });
31295
+ GUIDANCE_INPUT_SCHEMA = Object.freeze({
31296
+ type: "object",
31297
+ properties: {
31298
+ topic: { type: "string", minLength: 1 }
31299
+ }
31300
+ });
29388
31301
  EXPLAIN_FIELD_INPUT_SCHEMA = Object.freeze({
29389
31302
  type: "object",
29390
31303
  properties: {
@@ -29492,7 +31405,8 @@ var init_tools = __esm({
29492
31405
  type: "string",
29493
31406
  enum: ["identifier", "contact", "financial", "health", "all"]
29494
31407
  },
29495
- limit: { type: "integer", minimum: 1, maximum: 500 }
31408
+ limit: { type: "integer", minimum: 1, maximum: 500 },
31409
+ offset: { type: "integer", minimum: 0 }
29496
31410
  }
29497
31411
  });
29498
31412
  FIELD_ACCESS_AUDIT_INPUT_SCHEMA = Object.freeze({
@@ -29782,7 +31696,8 @@ var init_tools = __esm({
29782
31696
  CRUD_FLS_AUDIT_INPUT_SCHEMA = Object.freeze({
29783
31697
  type: "object",
29784
31698
  properties: {
29785
- limit: { type: "integer", minimum: 1, maximum: 500 }
31699
+ limit: { type: "integer", minimum: 1, maximum: 500 },
31700
+ offset: { type: "integer", minimum: 0 }
29786
31701
  }
29787
31702
  });
29788
31703
  TEST_COVERAGE_GAPS_INPUT_SCHEMA = Object.freeze({
@@ -29792,9 +31707,30 @@ var init_tools = __esm({
29792
31707
  type: "array",
29793
31708
  items: { type: "string", minLength: 1 },
29794
31709
  maxItems: 500
29795
- }
31710
+ },
31711
+ limit: { type: "integer", minimum: 1, maximum: 500 },
31712
+ offset: { type: "integer", minimum: 0 }
29796
31713
  }
29797
31714
  });
31715
+ WHAT_IF_CHANGE_FIELD_VALUE_INPUT_SCHEMA = Object.freeze({
31716
+ type: "object",
31717
+ properties: {
31718
+ fieldId: { type: "string", minLength: 1 },
31719
+ newValue: { type: "string" }
31720
+ },
31721
+ required: ["fieldId"],
31722
+ additionalProperties: false
31723
+ });
31724
+ VALUE_CHANGE_AUDIT_INPUT_SCHEMA = Object.freeze({
31725
+ type: "object",
31726
+ properties: {
31727
+ object: { type: "string", minLength: 1 },
31728
+ fields: { type: "array", items: { type: "string" } },
31729
+ verbosity: { type: "string", enum: ["summary", "detail"] }
31730
+ },
31731
+ required: ["object"],
31732
+ additionalProperties: false
31733
+ });
29798
31734
  WHAT_IF_CHANGE_FIELD_TYPE_INPUT_SCHEMA = Object.freeze({
29799
31735
  type: "object",
29800
31736
  properties: {
@@ -30277,9 +32213,14 @@ var init_tools = __esm({
30277
32213
  description: 'Product self-description: what this knowledge base can answer. Returns a categorized capability map (with example natural-language questions per area), the live registered-tool count, the recommended conversational pattern (call sfi.resolve first; ask a clarifying question on ambiguous; offer /sfi-refresh or stop on none), the three slash commands, and the v0.1 read-only/offline boundary. Takes no arguments. Call when the user asks "what can you do / what can I ask?" or to orient a fresh session.',
30278
32214
  inputSchema: CAPABILITIES_INPUT_SCHEMA
30279
32215
  },
32216
+ {
32217
+ name: "sfi.synthesize_answer",
32218
+ description: "Answer-layer grounding pass: turns the JSON returned by prior sfi.* tool call(s) into a structured, citation-grounded answer skeleton \u2014 `summary`, `bullets` (headline facts extracted from the input), `citations` (ONLY canonical ids present in the input, parsed to type + apiName), and `caveats` (honesty/limitation strings carried verbatim). Pass the source tool output as `input` (any JSON), optionally the user `question` (echoed into the summary), and optionally a `draft` narrative \u2014 when given, `hallucinatedIds` lists canonical ids in the draft that do NOT appear in the source so they can be removed before answering. Pure transform: reads ONLY `input`, never the graph or live org, so it can never add a fact it was not handed. Prose wording stays with the caller; this guarantees grounding, not sentences.",
32219
+ inputSchema: SYNTHESIZE_ANSWER_INPUT_SCHEMA
32220
+ },
30280
32221
  {
30281
32222
  name: "sfi.route_question",
30282
- description: "Front-door router: map a plain-language question to the plane that answers it (vault | live | hybrid | unknown) and the ordered sfi.* tools to call \u2014 so the user never types a tool name. Read-only; it suggests a route, it does not answer. Tells you when to sfi.resolve a named component first, whether the opt-in live plane is required, and \u2014 when the question hits a capability we lack \u2014 returns an honest 'unknown'/gap and logs it for the backlog instead of fabricating. Call this first on a vague/broad question to decide which tool(s) to run.",
32223
+ description: "Front-door router: map a plain-language question to the plane that answers it (vault | live | hybrid | unknown) and the ordered sfi.* tools to call \u2014 so the user never types a tool name. Read-only; it suggests a route, it does not answer. 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 and logs it for the backlog instead of fabricating. Call this first on a vague/broad question to decide which tool(s) to run.",
30283
32224
  inputSchema: ROUTE_QUESTION_INPUT_SCHEMA
30284
32225
  },
30285
32226
  {
@@ -30464,7 +32405,7 @@ var init_tools = __esm({
30464
32405
  },
30465
32406
  {
30466
32407
  name: "sfi.permission_risk_report",
30467
- description: "Ranked permission risks: unassigned permission sets and CRUD/FLS audit totals.",
32408
+ description: "Ranked permission-risk report, leading with OVER-PRIVILEGE read straight from the extracted profile / permission-set metadata: every Profile or PermissionSet that grants a god-mode or administrative system permission (Modify All Data / View All Data = critical; Author Apex, Customize Application, Manage Users, Manage Profiles/PermSets, Modify Metadata, Manage Sharing, Manage Roles, password/login policies = high) OR object-level View All / Modify All, surfaced as ONE aggregated finding per grantor (severity = the worst signal; system perms + a per-grantor count of objects escalated). PermissionSetGroups are analysed too: a PSG's effective god-mode is aggregated from its MEMBER permission sets (so a user who gets Modify All Data via a group is caught), with the muting permission set noted but not subtracted (v1 honesty boundary). A `privilege` block rosters the `modifyAllDataGrantors` / `viewAllDataGrantors` (profiles, permission sets, AND groups) and the `overPrivilegedGrantorCount`. Also rolls in unassigned permission sets and CRUD/FLS audit totals. Answers 'who has god mode / Modify All / View All / who is an admin / who is over-permissioned'. Read-only, declared confidence (literal metadata flags, not heuristics); `limit` (default 50) caps the findings.",
30468
32409
  inputSchema: SYNTHESIS_INPUT_SCHEMA
30469
32410
  },
30470
32411
  {
@@ -30504,9 +32445,14 @@ var init_tools = __esm({
30504
32445
  },
30505
32446
  {
30506
32447
  name: "sfi.event_subscribers",
30507
- description: "Given a Platform Event id (`CustomObject:{ApiName}__e`), list every subscriber (ApexTrigger, ApexClass, Flow) that emits an incoming `listensTo` edge into the event. Returns each subscriber's identity, the extractor that emitted the edge, and the edge-level subscription metadata. Honest empty list when no subscribers exist; `invalid-query` when the id is not a Platform Event canonical form.",
32448
+ description: 'Given a Platform Event id (`CustomObject:{ApiName}__e`), list every subscriber (ApexTrigger, ApexClass, Flow) that emits an incoming `listensTo` edge into the event. OMIT `eventId` for CATALOG mode: every Platform Event in the org with its subscriber count (`events[]`) \u2014 answers "what platform events does this org publish?" (then `subscribers` is `[]` and `eventApiName` is `null`). Single-event mode returns each subscriber\'s identity, the emitting extractor, and edge-level subscription metadata. Honest empty list when no subscribers exist; `invalid-query` when a supplied id is not a Platform Event canonical form.',
30508
32449
  inputSchema: EVENT_SUBSCRIBERS_INPUT_SCHEMA
30509
32450
  },
32451
+ {
32452
+ name: "sfi.guidance",
32453
+ description: "General Salesforce best-practice guidance \u2014 the `knowledge` plane for greenfield / New-Org questions that have NO org-specific answer (Flow vs Apex, order of execution, governor limits, async Apex, trigger frameworks, bulkification, Apex testing, callouts, SFDX, unlocked packages, profiles vs permission sets, OWD/sharing, standard vs custom objects, naming, sandboxes). With `topic` (a key like `flow-vs-apex`, or a phrase that loose-matches) it returns a curated summary plus links to official Salesforce docs; without `topic` it lists available topics. Explicitly NOT specific to this org (see `disclosure`) \u2014 it points to authoritative docs and never fabricates vault data.",
32454
+ inputSchema: GUIDANCE_INPUT_SCHEMA
32455
+ },
30510
32456
  {
30511
32457
  name: "sfi.find_code_usages",
30512
32458
  description: "List the code source files (ApexClass, ApexTrigger, LightningComponentBundle, AuraDefinitionBundle, VisualforcePage, VisualforceComponent) that read, write, call, or reference a component. Strict superset of `sfi.find_apex_usages`: same Apex-source coverage plus the v1.4 frontend tier. Filters incoming `readsFrom`/`writesTo`/`callsApex`/`references` edges to those originating from one of the six code node types; optional `nodeTypes` narrows to a single producer (e.g., `['LightningComponentBundle']` for LWC-only). LWC apex-import callsApex edges are `declared`; LWC field reads, Aura field accesses, and VF field touches are `heuristic`; VF controller/extension references are `declared`.",
@@ -30524,7 +32470,7 @@ var init_tools = __esm({
30524
32470
  },
30525
32471
  {
30526
32472
  name: "sfi.safe_to_delete_field",
30527
- description: "Given a CustomField canonical id (`CustomField:{Object}.{Field}`), composes every incoming dependency edge into a confidence-weighted deletion verdict: `safe` (no incoming edges), `risky` (heuristic-confidence Apex/LWC references that need spot-checking), `blocking` (declared Flow/ValidationRule/Layout/formula dependencies the platform will refuse to drop), or `unknown` (only unrecognised edges). Each category in the `reasoning` array carries its referrer count, up to 5 example referrers (full list via `sfi.get_impact`), and a per-category note explaining the honesty boundary. Does NOT consult the Tooling API for runtime dependency confirmation (deferred to v1.7+ `dependsOnFromApi` enrichment).",
32473
+ description: "Given a CustomField canonical id (`CustomField:{Object}.{Field}`), composes every incoming dependency edge into a confidence-weighted deletion verdict: `safe` (no incoming edges), `risky` (heuristic-confidence Apex/LWC references that need spot-checking), `blocking` (declared Flow/ValidationRule/Layout/formula dependencies the platform will refuse to drop), `unknown` (only unrecognised edges), or `review` (NOT proven safe \u2014 incomplete coverage, OR a standard / managed-package field with no node of its own but referenced by edges: it is reviewed from those edges with a not-modeled caveat instead of returning component-not-found; B12). Each category in the `reasoning` array carries its referrer count, up to 5 example referrers (full list via `sfi.get_impact`), and a per-category note explaining the honesty boundary. Does NOT consult the Tooling API for runtime dependency confirmation (deferred to v1.7+ `dependsOnFromApi` enrichment).",
30528
32474
  inputSchema: SAFE_TO_DELETE_FIELD_INPUT_SCHEMA
30529
32475
  },
30530
32476
  {
@@ -30557,6 +32503,16 @@ var init_tools = __esm({
30557
32503
  description: "Decision-support tool: before changing a field, see the whole blast radius in one briefing. Given `fieldId`, synthesises `makeRequired` (verdict + create-path impact count from what_if_make_field_required), `deletion` (verdict + blocking/risky dependency counts from safe_to_delete_field), and \u2014 when `newType` is given \u2014 `changeType` (compatibility + verdict + reference count from what_if_change_field_type), plus combined `recommendations`. Does NOT change anything (backend knowledge layer). Honesty axis: inherits the composed tools' boundaries \u2014 dataflow into Apex insert/update and dynamic/reflective field access are invisible, so verdicts mean 'investigate', not guarantees. `component-not-found` when the fieldId is not a CustomField in the vault.",
30558
32504
  inputSchema: FIELD_CHANGE_ADVISOR_INPUT_SCHEMA
30559
32505
  },
32506
+ {
32507
+ name: "sfi.what_if_change_field_value",
32508
+ description: "Value-change impact (Data Steward / Identity & Integration lens): given a CustomField `fieldId`, what breaks if its stored VALUE changes \u2014 NOT its schema (use what_if_change_field_type for type/required/delete). Returns impact buckets (identity / integration-key / uniqueness / automation / save-pipeline / display), an overall severity, honesty-surface disclosures, and recommended pre-change checks. Identity / key / uniqueness verdicts come from the field's own metadata (externalId / unique / idLookup, identity catalog) \u2014 so a value change is flagged even on a field with ZERO references (e.g. a SAML federation key). Derived fields (formula / roll-up / auto-number) return mutable:false and re-route to their source. Honesty axis: the vault cannot see external upsert systems, the IdP side of SSO, or dynamic / managed-package code; automation buckets surface declarative value-literal couplings (the value a rule compares this field to); Apex literal comparisons remain invisible. Optional `newValue` adds a targeted collision/acceptance check. `component-not-found` when the fieldId is not a CustomField in the vault.",
32509
+ inputSchema: WHAT_IF_CHANGE_FIELD_VALUE_INPUT_SCHEMA
32510
+ },
32511
+ {
32512
+ name: "sfi.value_change_audit",
32513
+ description: "Batch value-change audit (Data Steward lens): given an `object` and optionally a list of `fields`, risk-ranks the impact of changing each field's stored VALUE \u2014 the portfolio version of what_if_change_field_value. WITHOUT `fields`, auto-detects the value-sensitive fields on the object (upsert keys via externalId/unique/idLookup, identity-catalog fields, name-lexicon matches). Each row carries an overall severity, role, top impact reasons, confidence, and disclosure count; `verbosity:'detail'` inlines full buckets. Returns a severity summary + global disclosures; unknown explicit fields come back in `notFound`. This answers 'tell me if changing any of these has an impact on {object}'. Honesty axis: auto-detect can miss a value-sensitive field carrying none of those signals; per-row blast radius inherits what_if_change_field_value's boundaries (external upsert systems, IdP side of SSO, dynamic/managed-package code invisible).",
32514
+ inputSchema: VALUE_CHANGE_AUDIT_INPUT_SCHEMA
32515
+ },
30560
32516
  {
30561
32517
  name: "sfi.live_drift_check",
30562
32518
  description: "Offline\u2194live contradiction detection (requires the opt-in live plane). For `objectApiName`, compares the fields the vault recorded at the last refresh against a LIVE read-only describe and reports `onlyInVault` (fields in the snapshot the live org no longer returns \u2014 deleted/renamed/permission-hidden since refresh; the high-signal STALE indicator), `onlyInLiveCustom` (custom fields added live since refresh, filtered to `__`-suffixed to avoid standard-field noise), `inSync`, and a plain-language `interpretation`. The only check that uses BOTH planes at once; never mutates the org. Honesty axis: the vault models extracted custom fields + standard object definitions (not standard fields), so onlyInVault is the trustworthy drift signal. Pass `liveEnabled:true` or set SFI_LIVE_PLANE_ENABLED.",
@@ -30589,12 +32545,12 @@ var init_tools = __esm({
30589
32545
  },
30590
32546
  {
30591
32547
  name: "sfi.pii_inventory",
30592
- description: "Enumerate every CustomField in the vault, classify each with the v2.0d `pii-detection` recognizer (which inspects API name, declared data type, and description text), then emit a structured inventory. Filter by `classification` (`'pii' | 'sensitive' | 'all'`) and/or `category` (`'identifier' | 'contact' | 'financial' | 'health' | 'all'`); both default to `'all'`. Each emitted field carries its classification, category, data type, description, and a plain-English `reason` naming the rule that fired. `summary` reports the full per-classification and per-category counts across the matched set; `truncated` flips true when the response slice was trimmed to `limit` (default 200, max 500). The recognizer is heuristic \u2014 a field with no name-token match and no description signal classifies as `public` even if it stores PII at runtime; `EncryptedText`-typed fields ALWAYS classify as `sensitive` because the encryption type IS the declaration.",
32548
+ description: "Enumerate every CustomField in the vault, classify each with the v2.0d `pii-detection` recognizer (which inspects API name, declared data type, and description text), then emit a structured inventory. Filter by `classification` (`'pii' | 'sensitive' | 'all'`) and/or `category` (`'identifier' | 'contact' | 'financial' | 'health' | 'all'`); both default to `'all'`. Each emitted field carries its classification, category, data type, description, and a plain-English `reason` naming the rule that fired. `summary` reports the full per-classification and per-category counts across the matched set. The response is paginated: `limit` (default 200, max 500) and `offset` (default 0) page through the inventory, and a per-response ~38 KB byte budget trims the `fields` slice further when a page would exceed it, so the result never trips the global ~45 KB MCP response limit; `truncated` flips true when more matching fields remain, with `nextOffset` carrying the cursor to advance (plus a `note` when a page was byte-trimmed). The recognizer is heuristic \u2014 a field with no name-token match and no description signal classifies as `public` even if it stores PII at runtime; `EncryptedText`-typed fields ALWAYS classify as `sensitive` because the encryption type IS the declaration.",
30593
32549
  inputSchema: PII_INVENTORY_INPUT_SCHEMA
30594
32550
  },
30595
32551
  {
30596
32552
  name: "sfi.field_access_audit",
30597
- description: "Given a CustomField canonical id (`CustomField:{Object}.{Field}`), cross-walk every Profile and PermissionSet that grants access to the field via incoming `grantedBy` edges. `grants` carries one entry per (Profile or PermissionSet, permission level) pair where permission is `'read'` / `'edit'` / `'unknown'` (the last meaning the older extractor did not populate the per-flag axis). `summary` reports the unfiltered counts split four ways (profilesWithRead, profilesWithEdit, permSetsWithRead, permSetsWithEdit). `viaApexAccess` enumerates ApexClass / ApexTrigger nodes with incoming `readsFrom` / `writesTo` edges to the field \u2014 a user with execute permission on one of those classes may access the field through that code path even when the metadata-grant audit reports no direct grant. Optional `permissionType` (`'read' | 'edit' | 'all'`, default `'all'`) narrows the emitted `grants` array. Honesty axis: this is the v2.0d.0 permission-grant-level audit; criteria-based and account-team sharing rules are deferred to v2.0d.1. Invalid prefix surfaces as `invalid-query`; unknown ids surface as `component-not-found`.",
32553
+ description: "Given a CustomField canonical id (`CustomField:{Object}.{Field}`), cross-walk every Profile and PermissionSet that grants access to the field via incoming `grantedBy` edges. `grants` carries one entry per (Profile or PermissionSet, permission level) pair where permission is `'read'` / `'edit'` / `'unknown'` (the last meaning the older extractor did not populate the per-flag axis). `summary` reports the unfiltered counts split four ways (profilesWithRead, profilesWithEdit, permSetsWithRead, permSetsWithEdit). `viaApexAccess` enumerates ApexClass / ApexTrigger nodes with incoming `readsFrom` / `writesTo` edges to the field \u2014 a user with execute permission on one of those classes may access the field through that code path even when the metadata-grant audit reports no direct grant. Optional `permissionType` (`'read' | 'edit' | 'all'`, default `'all'`) narrows the emitted `grants` array. A standard or managed-package field with no node of its own but referenced by fieldPermissions / Apex edges is still audited from those edges with `notModeled: true` + a `notModeledNote` (grants are accurate; data type / formula are unavailable and PII is inferred from the field name) \u2014 only an id with no node AND no inbound references is `component-not-found` (B12). Honesty axis: this is the v2.0d.0 permission-grant-level audit; criteria-based and account-team sharing rules are deferred to v2.0d.1. Invalid prefix surfaces as `invalid-query`.",
30598
32554
  inputSchema: FIELD_ACCESS_AUDIT_INPUT_SCHEMA
30599
32555
  },
30600
32556
  {
@@ -30689,12 +32645,12 @@ var init_tools = __esm({
30689
32645
  },
30690
32646
  {
30691
32647
  name: "sfi.crud_fls_audit",
30692
- description: "v2.1 R3 CRUD/FLS enforcement audit. Walks every ApexClass / ApexTrigger node's `properties.qualityIssues[]`, narrows to the two CRUD/FLS rules (`missing-crud-check`, `missing-fls-check`), groups findings by class, and surfaces the verbatim Q80 disclosure naming the HIGH false-positive rate inherited from ApexQualitySemantics.md \xA7\xA7 6-7. Each class entry carries its identity and a per-finding list (rule / severity / location / explanation). `totalFindingCount` / `byRule` report the FULL pre-slice counts. `limit` defaults to 100 (max 500); the slice is over CLASSES. Honesty axis (verbatim, surfaced in `boundaries[]` when at least one finding qualifies): the Q80 false-positive disclosure \u2014 'custom security utility methods are invisible to the recognizer; this finding may be a false positive if your org uses a helper like SecurityUtils.canCreate(account)' \u2014 is the load-bearing honesty surface for this tool. Also surfaced: cross-method dataflow is invisible; dynamic SOQL strings (Database.query) are stripped before pattern passes.",
32648
+ description: "v2.1 R3 CRUD/FLS enforcement audit. Walks every ApexClass / ApexTrigger node's `properties.qualityIssues[]`, narrows to the two CRUD/FLS rules (`missing-crud-check`, `missing-fls-check`), groups findings by class, and surfaces the verbatim Q80 disclosure naming the HIGH false-positive rate inherited from ApexQualitySemantics.md \xA7\xA7 6-7. Each class entry carries its identity and a per-finding list (rule / severity / location / explanation). `totalFindingCount` / `byRule` report the FULL pre-slice counts. The class list is paginated: `limit` defaults to 100 (max 500) and `offset` (default 0) page over CLASSES, and a per-response ~36 KB byte budget trims the page further when a page would exceed it, so the result never trips the global ~45 KB MCP response limit; `truncated` flips true when more classes remain, with `nextOffset` to advance (plus a `note` when byte-trimmed, and a per-class `findingsTruncated` flag in the rare case one class's findings alone overflow). Honesty axis (verbatim, surfaced in `boundaries[]` when at least one finding qualifies): the Q80 false-positive disclosure \u2014 'custom security utility methods are invisible to the recognizer; this finding may be a false positive if your org uses a helper like SecurityUtils.canCreate(account)' \u2014 is the load-bearing honesty surface for this tool. Also surfaced: cross-method dataflow is invisible; dynamic SOQL strings (Database.query) are stripped before pattern passes.",
30693
32649
  inputSchema: CRUD_FLS_AUDIT_INPUT_SCHEMA
30694
32650
  },
30695
32651
  {
30696
32652
  name: "sfi.test_coverage_gaps",
30697
- description: "v2.1 R3 test-coverage-gap surface. Combines three signals: (1) `properties.isTest === true` identifies test classes (excluded from the scan), (2) BFS over incoming `callsApex` edges (capped at depth 3) collects the test classes reaching each non-test class, (3) `qualityIssues[]` `fake-assertion` findings on those test classes mark meaninglessly-covered classes. Classifies each non-test ApexClass into one of three coverage statuses \u2014 `uncovered` (no test reaches it within depth 3), `fake-coverage` (covered, but EVERY covering test has fake-assertion findings), `low-quality-coverage` (covered, but SOME covering test has fake-assertion findings). Each gap entry carries `componentId` / `apiName` / `coverageStatus` / `coveringTestClassIds[]` / `fakeAssertions[]` / `recommendedAction`. `byStatus` reports the per-status counts. Optional `classFilter[]` narrows the scan to specific ApexClass ids (capped at 500). Honesty axis (verbatim, surfaced in `boundaries[]` when at least one gap qualifies): the meaningful-assertion heuristic recognizes `System.assertEquals(expected, actual)` with distinct tokens; assertions via helper methods or framework wrappers are invisible. Reachability via `callsApex` does NOT cover dynamic dispatch. BFS is capped at depth 3.",
32653
+ description: "v2.1 R3 test-coverage-gap surface. Combines three signals: (1) `properties.isTest === true` identifies test classes (excluded from the scan), (2) BFS over incoming `callsApex` edges (capped at depth 3) collects the test classes reaching each non-test class, (3) `qualityIssues[]` `fake-assertion` findings on those test classes mark meaninglessly-covered classes. Classifies each non-test ApexClass into one of three coverage statuses \u2014 `uncovered` (no test reaches it within depth 3), `fake-coverage` (covered, but EVERY covering test has fake-assertion findings), `low-quality-coverage` (covered, but SOME covering test has fake-assertion findings). Each gap entry carries `componentId` / `apiName` / `coverageStatus` / `coveringTestClassIds[]` / `fakeAssertions[]` / `recommendedAction`. `byStatus` reports the per-status counts. The gap list is paginated: `limit` (default 200, max 500) and `offset` (default 0) page over gap entries, and a per-response ~38 KB byte budget trims the page further when a page would exceed it, so the result never trips the global ~45 KB MCP response limit; `truncated` flips true when more gaps remain, with `nextOffset` to advance (plus a `note` when byte-trimmed). Optional `classFilter[]` narrows the scan to specific ApexClass ids (capped at 500). Honesty axis (verbatim, surfaced in `boundaries[]` when at least one gap qualifies): the meaningful-assertion heuristic recognizes `System.assertEquals(expected, actual)` with distinct tokens; assertions via helper methods or framework wrappers are invisible. Reachability via `callsApex` does NOT cover dynamic dispatch. BFS is capped at depth 3.",
30698
32654
  inputSchema: TEST_COVERAGE_GAPS_INPUT_SCHEMA
30699
32655
  },
30700
32656
  {
@@ -31024,6 +32980,10 @@ var init_tools = __esm({
31024
32980
  return runTool(ctx, args, resolveInputSchema, resolveHandler);
31025
32981
  case "sfi.capabilities":
31026
32982
  return runTool(ctx, args, capabilitiesInputSchema, capabilitiesHandler);
32983
+ case "sfi.guidance":
32984
+ return runTool(ctx, args, guidanceInputSchema, guidanceHandler);
32985
+ case "sfi.synthesize_answer":
32986
+ return runTool(ctx, args, synthesizeAnswerInputSchema, synthesizeAnswerHandler);
31027
32987
  case "sfi.org_pulse":
31028
32988
  return runTool(ctx, args, orgPulseInputSchema, orgPulseHandler);
31029
32989
  case "sfi.fleet_find":
@@ -31194,6 +33154,10 @@ var init_tools = __esm({
31194
33154
  return runTool(ctx, args, testCoverageGapsInputSchema, testCoverageGapsHandler);
31195
33155
  case "sfi.what_if_change_field_type":
31196
33156
  return runTool(ctx, args, whatIfChangeFieldTypeInputSchema, whatIfChangeFieldTypeHandler);
33157
+ case "sfi.what_if_change_field_value":
33158
+ return runTool(ctx, args, whatIfChangeFieldValueInputSchema, whatIfChangeFieldValueHandler);
33159
+ case "sfi.value_change_audit":
33160
+ return runTool(ctx, args, valueChangeAuditInputSchema, valueChangeAuditHandler);
31197
33161
  case "sfi.what_if_remove_picklist_value":
31198
33162
  return runTool(ctx, args, whatIfRemovePicklistValueInputSchema, whatIfRemovePicklistValueHandler);
31199
33163
  case "sfi.what_if_make_field_required":
@@ -37255,11 +39219,30 @@ var extractEnterpriseMetadata = async (path, config) => {
37255
39219
  apiName = deriveComponentApiName(path, config.suffix);
37256
39220
  }
37257
39221
  const fieldRefs = extractFieldRefs(text.value, parentObjectApiName);
39222
+ const nodeId = `${config.type}:${apiName}`;
39223
+ const childRefEdges = [];
39224
+ const childRefSummary = {};
39225
+ for (const spec of config.childRefs ?? []) {
39226
+ const values = [...new Set(extractXmlValues(text.value, spec.element))].sort();
39227
+ if (values.length > 0)
39228
+ childRefSummary[spec.element] = values;
39229
+ for (const value of values) {
39230
+ childRefEdges.push({
39231
+ fromId: nodeId,
39232
+ toId: `${spec.toType}:${value}`,
39233
+ edgeType: "references",
39234
+ confidence: "declared",
39235
+ source: EXTRACTOR_SOURCE10,
39236
+ properties: { referenceKind: spec.referenceKind }
39237
+ });
39238
+ }
39239
+ }
37258
39240
  const node = makeNode(config.type, apiName, path, parentObjectApiName === null ? null : `CustomObject:${parentObjectApiName}`, {
37259
39241
  fieldRefs,
37260
- rawReferenceCount: fieldRefs.length
39242
+ rawReferenceCount: fieldRefs.length,
39243
+ ...childRefSummary
37261
39244
  });
37262
- const edges = fieldRefs.map((fieldId) => ({
39245
+ const fieldRefEdges = fieldRefs.map((fieldId) => ({
37263
39246
  fromId: node.id,
37264
39247
  toId: fieldId,
37265
39248
  edgeType: "references",
@@ -37267,7 +39250,7 @@ var extractEnterpriseMetadata = async (path, config) => {
37267
39250
  source: EXTRACTOR_SOURCE10,
37268
39251
  properties: { referenceKind: "fieldRef" }
37269
39252
  }));
37270
- return ok({ nodes: [node], edges });
39253
+ return ok({ nodes: [node], edges: [...fieldRefEdges, ...childRefEdges] });
37271
39254
  };
37272
39255
  var extractReport = (path) => extractEnterpriseMetadata(path, { type: "Report", suffix: ".report-meta.xml" });
37273
39256
  var extractDashboard = (path) => extractEnterpriseMetadata(path, { type: "Dashboard", suffix: ".dashboard-meta.xml" });
@@ -37280,7 +39263,22 @@ var extractReportType = (path) => extractEnterpriseMetadata(path, { type: "Repor
37280
39263
  var extractFlexiPage = (path) => extractEnterpriseMetadata(path, { type: "FlexiPage", suffix: ".flexipage-meta.xml" });
37281
39264
  var extractPermissionSetGroup = (path) => extractEnterpriseMetadata(path, {
37282
39265
  type: "PermissionSetGroup",
37283
- suffix: ".permissionsetgroup-meta.xml"
39266
+ suffix: ".permissionsetgroup-meta.xml",
39267
+ // A PSG's effective permissions are the UNION of its member permission
39268
+ // sets' grants, minus the muting permission set's. Capture both so the
39269
+ // permission analysis can flow god-mode / object grants through the group.
39270
+ childRefs: [
39271
+ {
39272
+ element: "permissionSets",
39273
+ toType: "PermissionSet",
39274
+ referenceKind: "permissionSetGroupMember"
39275
+ },
39276
+ {
39277
+ element: "mutingPermissionSets",
39278
+ toType: "MutingPermissionSet",
39279
+ referenceKind: "mutingPermissionSet"
39280
+ }
39281
+ ]
37284
39282
  });
37285
39283
  var extractMutingPermissionSet = (path) => extractEnterpriseMetadata(path, {
37286
39284
  type: "MutingPermissionSet",
@@ -43165,10 +45163,14 @@ var extractRole = async (path) => {
43165
45163
  return ok({ nodes: [node], edges });
43166
45164
  };
43167
45165
 
45166
+ // ../extractors/dist/src/saml-sso-config.js
45167
+ init_dist();
45168
+ import { XMLParser as XMLParser53, XMLValidator as XMLValidator52 } from "fast-xml-parser";
45169
+
43168
45170
  // ../extractors/dist/src/sharing-rules.js
43169
45171
  init_dist();
43170
45172
  import { readFile as readFile66 } from "node:fs/promises";
43171
- import { XMLParser as XMLParser53, XMLValidator as XMLValidator52 } from "fast-xml-parser";
45173
+ import { XMLParser as XMLParser54, XMLValidator as XMLValidator53 } from "fast-xml-parser";
43172
45174
  var SHARING_RULES_FILE_SUFFIX = ".sharingRules-meta.xml";
43173
45175
  var ROOT_ELEMENT52 = "SharingRules";
43174
45176
  var EXTRACTOR_SOURCE28 = "sharing-rule-extractor";
@@ -43229,7 +45231,7 @@ var readAndValidateXml48 = async (path) => {
43229
45231
  cause
43230
45232
  });
43231
45233
  }
43232
- const validation = XMLValidator52.validate(xmlText);
45234
+ const validation = XMLValidator53.validate(xmlText);
43233
45235
  if (validation !== true) {
43234
45236
  return err({ kind: "parse-error", path, message: validation.err.msg });
43235
45237
  }
@@ -43419,7 +45421,7 @@ var extractSharingRules = async (path) => {
43419
45421
  const xmlResult = await readAndValidateXml48(path);
43420
45422
  if (!xmlResult.ok)
43421
45423
  return xmlResult;
43422
- const parser = new XMLParser53({
45424
+ const parser = new XMLParser54({
43423
45425
  ignoreAttributes: true,
43424
45426
  parseTagValue: false,
43425
45427
  trimValues: true,
@@ -43466,7 +45468,7 @@ var extractSharingRules = async (path) => {
43466
45468
  // ../extractors/dist/src/static-resource.js
43467
45469
  init_dist();
43468
45470
  import { readFile as readFile67 } from "node:fs/promises";
43469
- import { XMLParser as XMLParser54, XMLValidator as XMLValidator53 } from "fast-xml-parser";
45471
+ import { XMLParser as XMLParser55, XMLValidator as XMLValidator54 } from "fast-xml-parser";
43470
45472
  var STATIC_RESOURCE_FILE_SUFFIX = ".resource-meta.xml";
43471
45473
  var ROOT_ELEMENT53 = "StaticResource";
43472
45474
  var REQUIRED_ELEMENTS24 = ["cacheControl"];
@@ -43491,7 +45493,7 @@ var readAndValidateXml49 = async (path) => {
43491
45493
  cause
43492
45494
  });
43493
45495
  }
43494
- const validation = XMLValidator53.validate(xmlText);
45496
+ const validation = XMLValidator54.validate(xmlText);
43495
45497
  if (validation !== true) {
43496
45498
  return err({ kind: "parse-error", path, message: validation.err.msg });
43497
45499
  }
@@ -43522,7 +45524,7 @@ var extractStaticResource = async (path) => {
43522
45524
  const xmlResult = await readAndValidateXml49(path);
43523
45525
  if (!xmlResult.ok)
43524
45526
  return xmlResult;
43525
- const parser = new XMLParser54({
45527
+ const parser = new XMLParser55({
43526
45528
  ignoreAttributes: true,
43527
45529
  parseTagValue: false,
43528
45530
  trimValues: true,
@@ -43576,7 +45578,7 @@ var extractStaticResource = async (path) => {
43576
45578
  init_dist();
43577
45579
  import { readFile as readFile68 } from "node:fs/promises";
43578
45580
  import { basename as basename10, dirname as dirname13 } from "node:path";
43579
- import { XMLParser as XMLParser55, XMLValidator as XMLValidator54 } from "fast-xml-parser";
45581
+ import { XMLParser as XMLParser56, XMLValidator as XMLValidator55 } from "fast-xml-parser";
43580
45582
  var VALIDATION_RULE_FILE_SUFFIX = ".validationRule-meta.xml";
43581
45583
  var ROOT_ELEMENT54 = "ValidationRule";
43582
45584
  var VALIDATION_RULES_DIR_NAME = "validationRules";
@@ -43611,7 +45613,7 @@ var readAndValidateXml50 = async (path) => {
43611
45613
  cause
43612
45614
  });
43613
45615
  }
43614
- const validation = XMLValidator54.validate(xmlText);
45616
+ const validation = XMLValidator55.validate(xmlText);
43615
45617
  if (validation !== true) {
43616
45618
  return err({ kind: "parse-error", path, message: validation.err.msg });
43617
45619
  }
@@ -43659,7 +45661,7 @@ var extractValidationRule = async (path) => {
43659
45661
  const xmlResult = await readAndValidateXml50(path);
43660
45662
  if (!xmlResult.ok)
43661
45663
  return xmlResult;
43662
- const parser = new XMLParser55({
45664
+ const parser = new XMLParser56({
43663
45665
  ignoreAttributes: true,
43664
45666
  parseTagValue: false,
43665
45667
  trimValues: true,
@@ -43730,7 +45732,7 @@ var extractValidationRule = async (path) => {
43730
45732
  init_dist();
43731
45733
  init_src3();
43732
45734
  import { readFile as readFile69 } from "node:fs/promises";
43733
- import { XMLParser as XMLParser56, XMLValidator as XMLValidator55 } from "fast-xml-parser";
45735
+ import { XMLParser as XMLParser57, XMLValidator as XMLValidator56 } from "fast-xml-parser";
43734
45736
  var COMPONENT_FILE_SUFFIX = ".component";
43735
45737
  var META_FILE_EXT3 = "-meta.xml";
43736
45738
  var ROOT_ELEMENT55 = "ApexComponent";
@@ -43756,14 +45758,14 @@ var readAndValidateXml51 = async (path, missingMessage) => {
43756
45758
  cause
43757
45759
  });
43758
45760
  }
43759
- const validation = XMLValidator55.validate(xmlText);
45761
+ const validation = XMLValidator56.validate(xmlText);
43760
45762
  if (validation !== true) {
43761
45763
  return err({ kind: "parse-error", path, message: validation.err.msg });
43762
45764
  }
43763
45765
  return ok(xmlText);
43764
45766
  };
43765
45767
  var parseMetaXml5 = (xmlText, path) => {
43766
- const parser = new XMLParser56({
45768
+ const parser = new XMLParser57({
43767
45769
  ignoreAttributes: true,
43768
45770
  parseTagValue: false,
43769
45771
  trimValues: true,
@@ -43971,7 +45973,7 @@ var extractVisualforceComponent = async (path) => {
43971
45973
  init_dist();
43972
45974
  init_src3();
43973
45975
  import { readFile as readFile70 } from "node:fs/promises";
43974
- import { XMLParser as XMLParser57, XMLValidator as XMLValidator56 } from "fast-xml-parser";
45976
+ import { XMLParser as XMLParser58, XMLValidator as XMLValidator57 } from "fast-xml-parser";
43975
45977
  var PAGE_FILE_SUFFIX = ".page";
43976
45978
  var META_FILE_EXT4 = "-meta.xml";
43977
45979
  var ROOT_ELEMENT56 = "ApexPage";
@@ -43998,14 +46000,14 @@ var readAndValidateXml52 = async (path, missingMessage) => {
43998
46000
  cause
43999
46001
  });
44000
46002
  }
44001
- const validation = XMLValidator56.validate(xmlText);
46003
+ const validation = XMLValidator57.validate(xmlText);
44002
46004
  if (validation !== true) {
44003
46005
  return err({ kind: "parse-error", path, message: validation.err.msg });
44004
46006
  }
44005
46007
  return ok(xmlText);
44006
46008
  };
44007
46009
  var parseMetaXml6 = (xmlText, path) => {
44008
- const parser = new XMLParser57({
46010
+ const parser = new XMLParser58({
44009
46011
  ignoreAttributes: true,
44010
46012
  parseTagValue: false,
44011
46013
  trimValues: true,
@@ -44219,7 +46221,7 @@ var extractVisualforcePage = async (path) => {
44219
46221
  init_dist();
44220
46222
  import { readFile as readFile71 } from "node:fs/promises";
44221
46223
  import { basename as basename11, dirname as dirname14 } from "node:path";
44222
- import { XMLParser as XMLParser58, XMLValidator as XMLValidator57 } from "fast-xml-parser";
46224
+ import { XMLParser as XMLParser59, XMLValidator as XMLValidator58 } from "fast-xml-parser";
44223
46225
  var FILE_SUFFIX5 = ".webLink-meta.xml";
44224
46226
  var ROOT_ELEMENT57 = "WebLink";
44225
46227
  var DIR_NAME4 = "webLinks";
@@ -44257,7 +46259,7 @@ var readAndValidateXml53 = async (path) => {
44257
46259
  cause
44258
46260
  });
44259
46261
  }
44260
- const validation = XMLValidator57.validate(xmlText);
46262
+ const validation = XMLValidator58.validate(xmlText);
44261
46263
  if (validation !== true) {
44262
46264
  return err({ kind: "parse-error", path, message: validation.err.msg });
44263
46265
  }
@@ -44284,7 +46286,7 @@ var extractWebLink = async (path) => {
44284
46286
  const xmlResult = await readAndValidateXml53(path);
44285
46287
  if (!xmlResult.ok)
44286
46288
  return xmlResult;
44287
- const parser = new XMLParser58({
46289
+ const parser = new XMLParser59({
44288
46290
  ignoreAttributes: true,
44289
46291
  parseTagValue: false,
44290
46292
  trimValues: true,
@@ -44359,7 +46361,7 @@ var extractWebLink = async (path) => {
44359
46361
  // ../extractors/dist/src/workflow-rule.js
44360
46362
  init_dist();
44361
46363
  import { readFile as readFile72 } from "node:fs/promises";
44362
- import { XMLParser as XMLParser59, XMLValidator as XMLValidator58 } from "fast-xml-parser";
46364
+ import { XMLParser as XMLParser60, XMLValidator as XMLValidator59 } from "fast-xml-parser";
44363
46365
  var WORKFLOW_FILE_SUFFIX = ".workflow-meta.xml";
44364
46366
  var ROOT_ELEMENT58 = "Workflow";
44365
46367
  var EXTRACTOR_SOURCE29 = "workflow-rule-extractor";
@@ -44435,7 +46437,7 @@ var readAndValidateXml54 = async (path) => {
44435
46437
  cause
44436
46438
  });
44437
46439
  }
44438
- const validation = XMLValidator58.validate(xmlText);
46440
+ const validation = XMLValidator59.validate(xmlText);
44439
46441
  if (validation !== true) {
44440
46442
  return err({ kind: "parse-error", path, message: validation.err.msg });
44441
46443
  }
@@ -44749,7 +46751,7 @@ var extractWorkflowRule = async (path) => {
44749
46751
  const xmlResult = await readAndValidateXml54(path);
44750
46752
  if (!xmlResult.ok)
44751
46753
  return xmlResult;
44752
- const parser = new XMLParser59({
46754
+ const parser = new XMLParser60({
44753
46755
  ignoreAttributes: true,
44754
46756
  parseTagValue: false,
44755
46757
  trimValues: true,
@@ -46748,7 +48750,7 @@ var registerStatusCommand = (program) => {
46748
48750
  // dist/src/program.js
46749
48751
  var readVersion = () => {
46750
48752
  if (true)
46751
- return "0.1.1";
48753
+ return "0.1.5";
46752
48754
  const pkgUrl = new URL("../../package.json", import.meta.url);
46753
48755
  const raw = readFileSync2(fileURLToPath(pkgUrl), "utf8");
46754
48756
  const parsed = JSON.parse(raw);