sf-intelligence 0.1.0 → 0.1.4

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 (2) hide show
  1. package/dist/index.js +392 -55
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -1053,12 +1053,34 @@ var init_resolve = __esm({
1053
1053
  qt,
1054
1054
  ...scoreToken(qt, node.tokens)
1055
1055
  }));
1056
+ const nameMatchedSomething = perToken.some((t) => t.score >= MATCHED_FLOOR);
1057
+ let parentMatched = false;
1058
+ if (nameMatchedSomething && node.parentApiName) {
1059
+ const parentTokens = tokenizeText(node.parentApiName);
1060
+ if (parentTokens.length > 0) {
1061
+ for (let i = 0; i < perToken.length; i += 1) {
1062
+ if (perToken[i].score >= MATCHED_FLOOR)
1063
+ continue;
1064
+ const pm = scoreToken(queryTokens[i], parentTokens);
1065
+ if (pm.score > perToken[i].score) {
1066
+ perToken[i] = {
1067
+ qt: queryTokens[i],
1068
+ score: pm.score,
1069
+ kind: "substring",
1070
+ matchedToken: pm.matchedToken
1071
+ };
1072
+ if (pm.score >= MATCHED_FLOOR)
1073
+ parentMatched = true;
1074
+ }
1075
+ }
1076
+ }
1077
+ }
1056
1078
  for (let i = 0; i < perToken.length; i += 1) {
1057
1079
  if (perToken[i].score > globalBest[i])
1058
1080
  globalBest[i] = perToken[i].score;
1059
1081
  }
1060
- const wholeExact = normQuery.length >= 2 && node.normName === normQuery;
1061
- pass1.push({ node, perToken, wholeExact });
1082
+ const wholeExact = normQuery.length >= 2 && node.normName === normQuery && query.includes(".") === node.apiName.includes(".");
1083
+ pass1.push({ node, perToken, wholeExact, parentMatched });
1062
1084
  }
1063
1085
  const anchorIdx = [];
1064
1086
  for (let i = 0; i < queryTokens.length; i += 1) {
@@ -1069,6 +1091,7 @@ var init_resolve = __esm({
1069
1091
  const scored = [];
1070
1092
  const coverageById = /* @__PURE__ */ new Map();
1071
1093
  const wholeExactIds = /* @__PURE__ */ new Set();
1094
+ const parentMatchedIds = /* @__PURE__ */ new Set();
1072
1095
  for (const c of pass1) {
1073
1096
  const matched = c.perToken.filter((t) => t.score >= MATCHED_FLOOR);
1074
1097
  if (matched.length === 0 && !c.wholeExact)
@@ -1085,10 +1108,13 @@ var init_resolve = __esm({
1085
1108
  const refs = c.node.inbound;
1086
1109
  const score = base * typeWeight(type) * (1 + POP_K * Math.log10(1 + refs));
1087
1110
  const kind = c.wholeExact ? "exact" : rollupKind(matched);
1088
- const matchedNodeTokens = new Set(matched.map((m) => m.matchedToken));
1111
+ const nodeTokenSet = new Set(c.node.tokens);
1112
+ const matchedNodeTokens = new Set(matched.map((m) => m.matchedToken).filter((t) => nodeTokenSet.has(t)));
1089
1113
  coverageById.set(c.node.id, c.wholeExact ? 1 : matchedNodeTokens.size / c.node.tokens.length);
1090
1114
  if (c.wholeExact)
1091
1115
  wholeExactIds.add(c.node.id);
1116
+ if (c.parentMatched)
1117
+ parentMatchedIds.add(c.node.id);
1092
1118
  scored.push({
1093
1119
  id: c.node.id,
1094
1120
  type,
@@ -1110,6 +1136,10 @@ var init_resolve = __esm({
1110
1136
  const tierB = b.base >= EXACT_THRESHOLD ? 0 : 1;
1111
1137
  if (tierA !== tierB)
1112
1138
  return tierA - tierB;
1139
+ const pmA = parentMatchedIds.has(a.id) ? 0 : 1;
1140
+ const pmB = parentMatchedIds.has(b.id) ? 0 : 1;
1141
+ if (pmA !== pmB)
1142
+ return pmA - pmB;
1113
1143
  if (a.score !== b.score)
1114
1144
  return b.score - a.score;
1115
1145
  return a.id < b.id ? -1 : 1;
@@ -1121,7 +1151,7 @@ var init_resolve = __esm({
1121
1151
  if (top === void 0 || bestBase < NONE_THRESHOLD) {
1122
1152
  disposition = "none";
1123
1153
  } else {
1124
- const contenders = candidates.filter((c) => c.score >= top.score * CONTENDER_RATIO);
1154
+ const contenders = candidates.filter((c) => c.score >= top.score * CONTENDER_RATIO || parentMatchedIds.has(c.id));
1125
1155
  const topCoverage = coverageById.get(top.id) ?? 0;
1126
1156
  disposition = top.base >= EXACT_THRESHOLD && contenders.length === 1 && topCoverage >= EXACT_COVERAGE ? "exact" : "ambiguous";
1127
1157
  }
@@ -1549,6 +1579,7 @@ var init_manifest = __esm({
1549
1579
  const coveredTypes = filtered.filter((entry) => entry.requested && !entry.errored && !entry.neverModeled).map((entry) => entry.type);
1550
1580
  const partialTypes = filtered.filter((entry) => entry.errored && !entry.neverModeled).map((entry) => entry.type);
1551
1581
  const notModeledTypes = filtered.filter((entry) => entry.neverModeled).map((entry) => entry.type);
1582
+ const notRequestedTypes = filtered.filter((entry) => !entry.requested && !entry.neverModeled).map((entry) => entry.type);
1552
1583
  if (wanted !== null) {
1553
1584
  const knownTypes = new Set(filtered.map((entry) => entry.type));
1554
1585
  for (const type of wanted) {
@@ -1557,7 +1588,9 @@ var init_manifest = __esm({
1557
1588
  }
1558
1589
  }
1559
1590
  }
1560
- const missingCoverage = [.../* @__PURE__ */ new Set([...partialTypes, ...notModeledTypes])].sort();
1591
+ const missingCoverage = [
1592
+ .../* @__PURE__ */ new Set([...partialTypes, ...notModeledTypes, ...notRequestedTypes])
1593
+ ].sort();
1561
1594
  const coverageKnown = readCoverageEntries(manifest).length > 0;
1562
1595
  const status = missingCoverage.length > 0 ? "partial" : coverageKnown ? "complete" : "unknown";
1563
1596
  return {
@@ -7615,7 +7648,7 @@ var init_explain_field = __esm({
7615
7648
 
7616
7649
  // ../mcp/dist/src/tools/explain-flow.js
7617
7650
  import { z as z32 } from "zod";
7618
- var FLOW_PREFIX, DISCLOSURE2, explainFlowInputSchema, readFlowLabel, readFlowStatus, readFlowProcessType, readFlowTriggerType, findTriggerObject, collectTriggerConditions, stripObjectPrefix, collectActionCalls, prefixOf, collectRecordLookups, classifyWriteOperation, collectRecordWrites, decisionNameOf, collectDecisions, explainFlowHandler;
7651
+ var FLOW_PREFIX, DISCLOSURE2, explainFlowInputSchema, readFlowLabel, readFlowStatus, readFlowProcessType, readFlowTriggerType, findTriggerObject, collectTriggerConditions, stripObjectPrefix, collectActionCalls, prefixOf, collectRecordLookups, classifyWriteOperation, collectRecordWrites, decisionNameOf, collectDecisions, ALTERNATE_TYPE_PREFIXES, findAlternateTypeId, explainFlowHandler;
7619
7652
  var init_explain_flow = __esm({
7620
7653
  "../mcp/dist/src/tools/explain-flow.js"() {
7621
7654
  "use strict";
@@ -7788,6 +7821,21 @@ var init_explain_flow = __esm({
7788
7821
  }
7789
7822
  return out;
7790
7823
  };
7824
+ ALTERNATE_TYPE_PREFIXES = [
7825
+ "ApexTrigger",
7826
+ "ApexClass",
7827
+ "WorkflowRule",
7828
+ "ValidationRule"
7829
+ ];
7830
+ findAlternateTypeId = async (ctx, bareName) => {
7831
+ for (const prefix of ALTERNATE_TYPE_PREFIXES) {
7832
+ const candidate = `${prefix}:${bareName}`;
7833
+ const r = await getNodeById(ctx.graph, candidate);
7834
+ if (r.ok && r.value !== null)
7835
+ return candidate;
7836
+ }
7837
+ return null;
7838
+ };
7791
7839
  explainFlowHandler = async (ctx, input2) => {
7792
7840
  const coercedFlowId = coercePrefix(input2.flowId, [FLOW_PREFIX]);
7793
7841
  if (!coercedFlowId.startsWith(FLOW_PREFIX)) {
@@ -7806,9 +7854,11 @@ var init_explain_flow = __esm({
7806
7854
  });
7807
7855
  }
7808
7856
  if (nodeResult.value === null) {
7857
+ const bareName = flowId.slice(FLOW_PREFIX.length);
7858
+ const alt = await findAlternateTypeId(ctx, bareName);
7809
7859
  return err({
7810
7860
  kind: "component-not-found",
7811
- message: `no Flow with id ${flowId}`,
7861
+ message: alt ? `no Flow named '${bareName}', but '${alt}' exists \u2014 it is a ${alt.slice(0, alt.indexOf(":"))}, not a Flow. explain_flow only handles Flows; use get_component (or the matching trigger/apex tool) for '${alt}'.` : `no Flow with id ${flowId}`,
7812
7862
  path: flowId
7813
7863
  });
7814
7864
  }
@@ -9345,11 +9395,11 @@ var init_naming_convention = __esm({
9345
9395
  parseScope = (scope) => {
9346
9396
  if (scope === void 0 || scope === "all")
9347
9397
  return ok(null);
9348
- const match = /^CustomField:([^.]+)\.\*$/.exec(scope);
9398
+ const match = /^CustomField:([^.]+)(?:\.\*)?$/.exec(scope);
9349
9399
  if (match === null) {
9350
9400
  return err({
9351
9401
  kind: "invalid-scope",
9352
- message: `unrecognized scope "${scope}"; expected 'all' or 'CustomField:{ObjectApiName}.*'`
9402
+ message: `unrecognized scope "${scope}"; expected 'all', 'CustomField:{ObjectApiName}', or 'CustomField:{ObjectApiName}.*'`
9353
9403
  });
9354
9404
  }
9355
9405
  return ok(match[1]);
@@ -17053,7 +17103,7 @@ var init_get_edges = __esm({
17053
17103
  ];
17054
17104
  getEdgesInputSchema = z65.object({
17055
17105
  nodeId: z65.string().min(1),
17056
- direction: z65.enum(["in", "out", "both"]).optional(),
17106
+ direction: z65.preprocess((v) => v === "incoming" ? "in" : v === "outgoing" ? "out" : v, z65.enum(["in", "out", "both"])).optional(),
17057
17107
  edgeType: z65.enum(EDGE_TYPES).optional(),
17058
17108
  confidence: z65.enum(CONFIDENCE_LEVELS).optional()
17059
17109
  });
@@ -18920,7 +18970,7 @@ var init_live_consent = __esm({
18920
18970
  import { execFile as execFile2 } from "node:child_process";
18921
18971
  import { promisify as promisify2 } from "node:util";
18922
18972
  import { z as z73 } from "zod";
18923
- var nodeExecFile2, redactSecrets, LIVE_PLANE_DISCLOSURE, MAX_SAMPLE_ROWS, liveEnabledSchema, isLivePlaneEnabled, liveTrust, resolveOrg, resolveLiveAccess, liveConsentRequiredError, gateLive, getLiveAuth, runSfJson, apiPath, restGet, liveDescribeInputSchema, liveDescribeHandler, liveCountInputSchema, 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;
18973
+ 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;
18924
18974
  var init_live_plane = __esm({
18925
18975
  "../mcp/dist/src/tools/live-plane.js"() {
18926
18976
  "use strict";
@@ -19037,9 +19087,33 @@ var init_live_plane = __esm({
19037
19087
  });
19038
19088
  };
19039
19089
  liveCountInputSchema = liveEnabledSchema.extend({
19040
- soql: z73.string().min(1),
19090
+ // Either `soql` (a SELECT COUNT() query) OR `objectApiName` (count every row
19091
+ // of that object). Both optional at the schema level; the handler requires
19092
+ // exactly one and turns objectApiName into `SELECT COUNT() FROM <object>`.
19093
+ soql: z73.string().min(1).optional(),
19094
+ objectApiName: z73.string().min(1).optional(),
19041
19095
  orgAlias: z73.string().min(1).optional()
19042
19096
  });
19097
+ OBJECT_API_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
19098
+ resolveCountSoql = (input2) => {
19099
+ if (input2.soql !== void 0)
19100
+ return ok(input2.soql);
19101
+ if (input2.objectApiName !== void 0) {
19102
+ if (!OBJECT_API_NAME_RE.test(input2.objectApiName)) {
19103
+ return err({
19104
+ kind: "invalid-query",
19105
+ message: `objectApiName "${input2.objectApiName}" is not a valid Salesforce object API name.`,
19106
+ path: "objectApiName"
19107
+ });
19108
+ }
19109
+ return ok(`SELECT COUNT() FROM ${input2.objectApiName}`);
19110
+ }
19111
+ return err({
19112
+ kind: "invalid-query",
19113
+ message: "live_count needs either `soql` (a SELECT COUNT() query) or `objectApiName`.",
19114
+ path: "soql"
19115
+ });
19116
+ };
19043
19117
  assertCountSoql = (soql) => {
19044
19118
  const normalized = soql.trim().replace(/\s+/g, " ");
19045
19119
  if (!/^select\s+count\s*\(/i.test(normalized)) {
@@ -19055,7 +19129,10 @@ var init_live_plane = __esm({
19055
19129
  const gate = await gateLive(ctx, input2);
19056
19130
  if (!gate.ok)
19057
19131
  return gate;
19058
- const soqlCheck = assertCountSoql(input2.soql);
19132
+ const soqlResult = resolveCountSoql(input2);
19133
+ if (!soqlResult.ok)
19134
+ return soqlResult;
19135
+ const soqlCheck = assertCountSoql(soqlResult.value);
19059
19136
  if (!soqlCheck.ok)
19060
19137
  return soqlCheck;
19061
19138
  const org = resolveOrg(ctx, input2.orgAlias);
@@ -21223,7 +21300,7 @@ var init_omniuicard_widget_breakdown = __esm({
21223
21300
  });
21224
21301
 
21225
21302
  // ../mcp/dist/src/tools/soe-admission.js
21226
- var OBJECT_NOT_MODELED_BOUNDARY, evaluateSoeAdmission, composeSoeDisclosure;
21303
+ var OBJECT_NOT_MODELED_BOUNDARY, evaluateSoeAdmission, composeSoeDisclosure, soeNotAdmittedMessage;
21227
21304
  var init_soe_admission = __esm({
21228
21305
  "../mcp/dist/src/tools/soe-admission.js"() {
21229
21306
  "use strict";
@@ -21259,9 +21336,142 @@ var init_soe_admission = __esm({
21259
21336
  if (parentOf.value.length > 0) {
21260
21337
  return ok({ admitted: true, objectModeled: false });
21261
21338
  }
21262
- return ok({ admitted: false, objectModeled: false });
21339
+ const inbound = await listEdges(ctx.graph, objectId, { direction: "in" });
21340
+ if (!inbound.ok) {
21341
+ return err(inbound.error.message);
21342
+ }
21343
+ return ok({
21344
+ admitted: false,
21345
+ objectModeled: false,
21346
+ referencedButNotModeled: inbound.value.length > 0
21347
+ });
21263
21348
  };
21264
21349
  composeSoeDisclosure = (baseDisclosure, objectModeled) => objectModeled ? baseDisclosure : `${baseDisclosure} ${OBJECT_NOT_MODELED_BOUNDARY}`;
21350
+ soeNotAdmittedMessage = (objectId, referencedButNotModeled) => referencedButNotModeled ? `\`${objectId}\` is referenced by this org (e.g. permission-set grants) but its definition was never retrieved into this vault \u2014 typically a managed-package or filtered object. Save-order can't be composed without the object's own metadata and automation. Run \`sfi refresh\` if it is retrievable, otherwise treat it as external.` : `no automation or object definition for \`${objectId}\` in this vault`;
21351
+ }
21352
+ });
21353
+
21354
+ // ../mcp/dist/src/tools/soe-payload-bounds.js
21355
+ var SOE_MAX_PAYLOAD_BYTES, KEEP_ALL_AT_OR_BELOW, sizeOf, enforceSoeByteBudget, soeTruncationNote;
21356
+ var init_soe_payload_bounds = __esm({
21357
+ "../mcp/dist/src/tools/soe-payload-bounds.js"() {
21358
+ "use strict";
21359
+ SOE_MAX_PAYLOAD_BYTES = 4e4;
21360
+ KEEP_ALL_AT_OR_BELOW = 4;
21361
+ sizeOf = (payload) => Buffer.byteLength(JSON.stringify(payload), "utf8");
21362
+ enforceSoeByteBudget = (payload, containers) => {
21363
+ if (sizeOf(payload) <= SOE_MAX_PAYLOAD_BYTES) {
21364
+ return { truncated: false, actionsOmitted: 0, conditionalsTrimmed: 0, stepsOmitted: 0 };
21365
+ }
21366
+ const steps = containers.flat();
21367
+ let totalOmitted = 0;
21368
+ const trimTo = (step3, keep) => {
21369
+ if (keep >= step3.actions.length)
21370
+ return;
21371
+ const removed = step3.actions.length - keep;
21372
+ step3.actions = step3.actions.slice(0, keep);
21373
+ step3.actionsOmitted = (step3.actionsOmitted ?? 0) + removed;
21374
+ totalOmitted += removed;
21375
+ };
21376
+ const actionBytes = steps.reduce((n, s) => n + Buffer.byteLength(JSON.stringify(s.actions), "utf8"), 0);
21377
+ const nonActionBytes = Math.max(0, sizeOf(payload) - actionBytes);
21378
+ const budgetForActions = SOE_MAX_PAYLOAD_BYTES - nonActionBytes;
21379
+ if (budgetForActions <= 0) {
21380
+ for (const s of steps)
21381
+ trimTo(s, 0);
21382
+ } else if (actionBytes > budgetForActions) {
21383
+ const ratio = budgetForActions / actionBytes;
21384
+ for (const s of steps) {
21385
+ if (s.actions.length <= KEEP_ALL_AT_OR_BELOW)
21386
+ continue;
21387
+ trimTo(s, Math.max(0, Math.floor(s.actions.length * ratio)));
21388
+ }
21389
+ }
21390
+ for (let guard = 0; guard < 1e5; guard += 1) {
21391
+ if (sizeOf(payload) <= SOE_MAX_PAYLOAD_BYTES)
21392
+ break;
21393
+ let target;
21394
+ for (const s of steps) {
21395
+ if (s.actions.length <= KEEP_ALL_AT_OR_BELOW)
21396
+ continue;
21397
+ if (target === void 0 || s.actions.length > target.actions.length) {
21398
+ target = s;
21399
+ }
21400
+ }
21401
+ if (target === void 0)
21402
+ break;
21403
+ trimTo(target, Math.floor(target.actions.length / 2));
21404
+ }
21405
+ let conditionalsTrimmed = 0;
21406
+ for (let guard = 0; guard < 1e5; guard += 1) {
21407
+ if (sizeOf(payload) <= SOE_MAX_PAYLOAD_BYTES)
21408
+ break;
21409
+ let target;
21410
+ let targetBytes = 0;
21411
+ for (const s of steps) {
21412
+ const cond = s.conditional;
21413
+ if (cond === void 0 || s.conditionalTruncated)
21414
+ continue;
21415
+ if (cond.expression === "" && cond.fieldRefs.length === 0)
21416
+ continue;
21417
+ const b = Buffer.byteLength(JSON.stringify(cond), "utf8");
21418
+ if (target === void 0 || b > targetBytes) {
21419
+ target = s;
21420
+ targetBytes = b;
21421
+ }
21422
+ }
21423
+ if (target === void 0)
21424
+ break;
21425
+ target.conditional = {
21426
+ conditionContextId: target.conditional.conditionContextId,
21427
+ expression: "",
21428
+ fieldRefs: []
21429
+ };
21430
+ target.conditionalTruncated = true;
21431
+ conditionalsTrimmed += 1;
21432
+ }
21433
+ let stepsOmitted = 0;
21434
+ for (let guard = 0; guard < 1e6; guard += 1) {
21435
+ if (sizeOf(payload) <= SOE_MAX_PAYLOAD_BYTES)
21436
+ break;
21437
+ let target;
21438
+ let targetBytes = 0;
21439
+ for (const c of containers) {
21440
+ if (c.length <= 1)
21441
+ continue;
21442
+ const b = Buffer.byteLength(JSON.stringify(c), "utf8");
21443
+ if (target === void 0 || b > targetBytes) {
21444
+ target = c;
21445
+ targetBytes = b;
21446
+ }
21447
+ }
21448
+ if (target === void 0)
21449
+ break;
21450
+ target.pop();
21451
+ stepsOmitted += 1;
21452
+ }
21453
+ return {
21454
+ truncated: totalOmitted > 0 || conditionalsTrimmed > 0 || stepsOmitted > 0,
21455
+ actionsOmitted: totalOmitted,
21456
+ conditionalsTrimmed,
21457
+ stepsOmitted
21458
+ };
21459
+ };
21460
+ soeTruncationNote = (result) => {
21461
+ const budgetKb = Math.round(SOE_MAX_PAYLOAD_BYTES / 1e3);
21462
+ const parts = [];
21463
+ if (result.actionsOmitted > 0) {
21464
+ parts.push(`${result.actionsOmitted} per-step action edge(s) across the heaviest steps were omitted (see each step's \`actionsOmitted\`)`);
21465
+ }
21466
+ if (result.conditionalsTrimmed > 0) {
21467
+ 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\`)`);
21468
+ }
21469
+ if (result.stepsOmitted > 0) {
21470
+ 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`);
21471
+ }
21472
+ 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`;
21473
+ return `${lead} ${parts.join("; ")}. Query a single object/event for full detail.`;
21474
+ };
21265
21475
  }
21266
21476
  });
21267
21477
 
@@ -21274,6 +21484,7 @@ var init_order_of_execution = __esm({
21274
21484
  init_dist();
21275
21485
  init_src();
21276
21486
  init_soe_admission();
21487
+ init_soe_payload_bounds();
21277
21488
  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.";
21278
21489
  SOE_EVENTS = ["insert", "update", "delete", "undelete"];
21279
21490
  orderOfExecutionInputSchema = z82.object({
@@ -21603,7 +21814,7 @@ var init_order_of_execution = __esm({
21603
21814
  if (!admission.value.admitted) {
21604
21815
  return err({
21605
21816
  kind: "component-not-found",
21606
- message: `no automation or object definition for \`${objectId}\` in this vault`,
21817
+ message: soeNotAdmittedMessage(objectId, admission.value.referencedButNotModeled ?? false),
21607
21818
  path: objectId
21608
21819
  });
21609
21820
  }
@@ -21621,13 +21832,20 @@ var init_order_of_execution = __esm({
21621
21832
  }
21622
21833
  byEvent[event] = perEventResult.value;
21623
21834
  }
21835
+ const data = {
21836
+ objectApiName: input2.objectApiName,
21837
+ objectModeled,
21838
+ byEvent,
21839
+ disclosure: composeSoeDisclosure(DISCLOSURE6, objectModeled)
21840
+ };
21841
+ const containers = SOE_EVENTS.map((event) => byEvent[event].soe);
21842
+ const budget = enforceSoeByteBudget(data, containers);
21843
+ if (budget.truncated) {
21844
+ data.truncated = true;
21845
+ data.disclosure = `${data.disclosure} ${soeTruncationNote(budget)}`;
21846
+ }
21624
21847
  return ok({
21625
- data: {
21626
- objectApiName: input2.objectApiName,
21627
- objectModeled,
21628
- byEvent,
21629
- disclosure: composeSoeDisclosure(DISCLOSURE6, objectModeled)
21630
- },
21848
+ data,
21631
21849
  vaultState: {
21632
21850
  sourceTreeHash: ctx.manifest.sourceTreeHash,
21633
21851
  refreshedAt: ctx.manifest.refreshedAt
@@ -22755,7 +22973,9 @@ var init_intent_router = __esm({
22755
22973
  /\bwho\s+can\s+(see|read|view|edit|access)\b\s+[\w\s.]+[?.!]?$/,
22756
22974
  /\b(access|permission|fls|field[-\s]level\s+security)\b.*\b(field|object)\b/,
22757
22975
  /\bwho\s+has\s+access\s+to\b/,
22758
- /\bwhich\s+(profiles?|permission\s+sets?)\b.*\b(grant|access|see|edit)\b/
22976
+ // "allow|read|view" added: "which permission sets allow read on X" was a
22977
+ // router gap (the verbs grant/access/see/edit didn't cover it).
22978
+ /\bwhich\s+(profiles?|permission\s+sets?)\b.*\b(grant|allow|access|see|read|view|edit)\b/
22759
22979
  ]
22760
22980
  },
22761
22981
  {
@@ -22880,11 +23100,15 @@ var init_intent_router = __esm({
22880
23100
  tools: ["sfi.resolve", "sfi.what_happens_on_save", "sfi.order_of_execution"],
22881
23101
  liveRequired: false,
22882
23102
  needsResolve: true,
22883
- reason: "Order of execution / what runs on save is reconstructed from the vault graph.",
23103
+ 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.",
22884
23104
  patterns: [
22885
23105
  /\b(trigger\s+order|order\s+of\s+execution)\b/,
22886
23106
  /\bwhat\s+(happens|runs|fires)\b.*\b(on\s+save|when\b.*\b(created|saved|updated|inserted|deleted))\b/,
22887
- /\bwhat\s+(triggers?|automation|flows?)\b.*\b(fire|run)\b/
23107
+ // "which/what flows|triggers|VRs|workflows run|fire when ..." — the
23108
+ // "which" phrasing was a router gap (e.g. "which flows run when a Case is
23109
+ // created"), so the question fell through to unrouted.
23110
+ /\b(what|which)\s+(triggers?|automation|flows?|validation\s+rules?|workflows?)\b.*\b(fire|run|execute|happen)\b/,
23111
+ /\b(flows?|triggers?|automation)\b.*\bwhen\b.*\b\w+\s+is\b.*\b(created|updated|inserted|deleted|saved)\b/
22888
23112
  ]
22889
23113
  },
22890
23114
  {
@@ -26078,6 +26302,7 @@ var init_what_happens_on_save = __esm({
26078
26302
  init_dist();
26079
26303
  init_src();
26080
26304
  init_soe_admission();
26305
+ init_soe_payload_bounds();
26081
26306
  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.";
26082
26307
  ALLOWED_EVENTS = [
26083
26308
  "insert",
@@ -26088,7 +26313,10 @@ var init_what_happens_on_save = __esm({
26088
26313
  ];
26089
26314
  whatHappensOnSaveInputSchema = z103.object({
26090
26315
  objectApiName: z103.string().min(1),
26091
- event: z103.enum(ALLOWED_EVENTS),
26316
+ // Accept "after update" / "Before Insert" etc.: lower-case and drop the
26317
+ // before/after timing prefix so the bare DML event matches the enum. The
26318
+ // SOE walker models both timings internally; the event arg selects the row.
26319
+ event: z103.preprocess((v) => typeof v === "string" ? v.trim().toLowerCase().replace(/^(?:before|after)\s+/, "") : v, z103.enum(ALLOWED_EVENTS)),
26092
26320
  recordTypeId: z103.string().min(1).optional()
26093
26321
  });
26094
26322
  workflowMatchesEvent2 = (triggerType, event) => {
@@ -26307,7 +26535,7 @@ var init_what_happens_on_save = __esm({
26307
26535
  if (!admission.value.admitted) {
26308
26536
  return err({
26309
26537
  kind: "component-not-found",
26310
- message: `no automation or object definition for \`${objectId}\` in this vault`,
26538
+ message: soeNotAdmittedMessage(objectId, admission.value.referencedButNotModeled ?? false),
26311
26539
  path: objectId
26312
26540
  });
26313
26541
  }
@@ -26444,20 +26672,26 @@ var init_what_happens_on_save = __esm({
26444
26672
  const asyncFanOut = asyncStepsResult.value.length;
26445
26673
  stepIndex += asyncFanOut;
26446
26674
  const conditionalCount = soe.filter((s) => s.conditional !== void 0).length;
26447
- return ok({
26448
- data: {
26449
- objectApiName: input2.objectApiName,
26450
- event: input2.event,
26451
- recordTypeId: input2.recordTypeId ?? null,
26452
- objectModeled,
26453
- soe,
26454
- summary: {
26455
- totalSteps: soe.length,
26456
- conditionalSteps: conditionalCount,
26457
- asyncFanOut
26458
- },
26459
- disclosure: composeSoeDisclosure(DISCLOSURE7, objectModeled)
26675
+ const data = {
26676
+ objectApiName: input2.objectApiName,
26677
+ event: input2.event,
26678
+ recordTypeId: input2.recordTypeId ?? null,
26679
+ objectModeled,
26680
+ soe,
26681
+ summary: {
26682
+ totalSteps: soe.length,
26683
+ conditionalSteps: conditionalCount,
26684
+ asyncFanOut
26460
26685
  },
26686
+ disclosure: composeSoeDisclosure(DISCLOSURE7, objectModeled)
26687
+ };
26688
+ const budget = enforceSoeByteBudget(data, [soe]);
26689
+ if (budget.truncated) {
26690
+ data.truncated = true;
26691
+ data.disclosure = `${data.disclosure} ${soeTruncationNote(budget)}`;
26692
+ }
26693
+ return ok({
26694
+ data,
26461
26695
  vaultState: {
26462
26696
  sourceTreeHash: ctx.manifest.sourceTreeHash,
26463
26697
  refreshedAt: ctx.manifest.refreshedAt
@@ -26467,6 +26701,23 @@ var init_what_happens_on_save = __esm({
26467
26701
  }
26468
26702
  });
26469
26703
 
26704
+ // ../mcp/dist/src/tools/phantom-node.js
26705
+ var phantomAwareNotFoundMessage;
26706
+ var init_phantom_node = __esm({
26707
+ "../mcp/dist/src/tools/phantom-node.js"() {
26708
+ "use strict";
26709
+ init_src();
26710
+ phantomAwareNotFoundMessage = async (ctx, id, kindLabel) => {
26711
+ const inbound = await listEdges(ctx.graph, id, { direction: "in" });
26712
+ const refs = inbound.ok ? inbound.value.length : 0;
26713
+ if (refs === 0) {
26714
+ return `no ${kindLabel} with id ${id}`;
26715
+ }
26716
+ 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.`;
26717
+ };
26718
+ }
26719
+ });
26720
+
26470
26721
  // ../mcp/dist/src/tools/what-if-change-method-signature.js
26471
26722
  import { z as z104 } from "zod";
26472
26723
  var APEX_CLASS_PREFIX9, DISCLOSURE8, whatIfChangeMethodSignatureInputSchema, readMethodName, isTestClass8, buildExplanation2, classifyCaller, aggregateVerdict4, compareImpacts, compareIds, collectCallers, collectCoveringTests, whatIfChangeMethodSignatureHandler;
@@ -26477,6 +26728,7 @@ var init_what_if_change_method_signature = __esm({
26477
26728
  init_src();
26478
26729
  init_coerce_id();
26479
26730
  init_coverage_trust();
26731
+ init_phantom_node();
26480
26732
  APEX_CLASS_PREFIX9 = "ApexClass:";
26481
26733
  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.";
26482
26734
  whatIfChangeMethodSignatureInputSchema = z104.object({
@@ -26616,7 +26868,7 @@ var init_what_if_change_method_signature = __esm({
26616
26868
  if (nodeResult.value === null) {
26617
26869
  return err({
26618
26870
  kind: "component-not-found",
26619
- message: `no ApexClass with id ${classId}`,
26871
+ message: await phantomAwareNotFoundMessage(ctx, classId, "ApexClass"),
26620
26872
  path: classId
26621
26873
  });
26622
26874
  }
@@ -28731,7 +28983,8 @@ var init_tools = __esm({
28731
28983
  type: "object",
28732
28984
  properties: {
28733
28985
  nodeId: { type: "string", minLength: 1 },
28734
- direction: { type: "string", enum: ["in", "out", "both"] },
28986
+ // 'incoming'/'outgoing' are accepted aliases (normalized to in/out).
28987
+ direction: { type: "string", enum: ["in", "out", "both", "incoming", "outgoing"] },
28735
28988
  edgeType: {
28736
28989
  type: "string",
28737
28990
  // Single-sourced from the contracts EDGE_TYPES tuple so the advertised
@@ -28914,9 +29167,12 @@ var init_tools = __esm({
28914
29167
  });
28915
29168
  LIVE_COUNT_INPUT_SCHEMA = Object.freeze({
28916
29169
  type: "object",
28917
- required: ["soql"],
29170
+ // Either `soql` (a SELECT COUNT() query) or `objectApiName` (count all rows).
29171
+ // The one-of requirement is enforced in the handler, not the JSON schema, so
29172
+ // the advertised shape stays simple for clients.
28918
29173
  properties: {
28919
29174
  soql: { type: "string", minLength: 1 },
29175
+ objectApiName: { type: "string", minLength: 1 },
28920
29176
  ...LIVE_ENABLED_PROPERTY
28921
29177
  }
28922
29178
  });
@@ -30195,7 +30451,7 @@ var init_tools = __esm({
30195
30451
  },
30196
30452
  {
30197
30453
  name: "sfi.live_count",
30198
- description: "Opt-in live org: run a SELECT COUNT() SOQL query with strict shape validation. Read-only; never falls back to vault data.",
30454
+ description: "Opt-in live org: count records. Pass `objectApiName` to count every row of an object, or `soql` for a custom SELECT COUNT() query (strict shape validation). Read-only; never falls back to vault data.",
30199
30455
  inputSchema: LIVE_COUNT_INPUT_SCHEMA
30200
30456
  },
30201
30457
  {
@@ -31521,15 +31777,40 @@ var runDoctor = async (opts) => {
31521
31777
  const checks = [];
31522
31778
  const vaultRoot = resolve4(opts.cwd, DEFAULT_VAULT_ROOT);
31523
31779
  const paths = vaultPaths(vaultRoot);
31780
+ const SF_FALLBACK_PATHS = ["/usr/local/bin/sf", "/opt/homebrew/bin/sf"];
31781
+ let sfBin = "sf";
31782
+ let sfDetail = null;
31783
+ let sfOnPath = false;
31524
31784
  try {
31525
31785
  const { stdout } = await run("sf --version");
31526
- checks.push({ name: "Salesforce CLI", status: "pass", detail: stdout.trim().split("\n")[0] ?? "installed" });
31786
+ sfDetail = stdout.trim().split("\n")[0] ?? "installed";
31787
+ sfOnPath = true;
31527
31788
  } catch {
31789
+ for (const abs of SF_FALLBACK_PATHS) {
31790
+ try {
31791
+ const { stdout } = await run(`"${abs}" --version`);
31792
+ sfDetail = stdout.trim().split("\n")[0] ?? "installed";
31793
+ sfBin = `"${abs}"`;
31794
+ break;
31795
+ } catch {
31796
+ }
31797
+ }
31798
+ }
31799
+ if (sfOnPath) {
31800
+ checks.push({ name: "Salesforce CLI", status: "pass", detail: sfDetail ?? "installed" });
31801
+ } else if (sfDetail !== null) {
31802
+ checks.push({
31803
+ name: "Salesforce CLI",
31804
+ status: "warn",
31805
+ detail: `${sfDetail} \u2014 found via absolute path, not on PATH`,
31806
+ fix: "Add the Salesforce CLI directory (e.g. /usr/local/bin or /opt/homebrew/bin) to the PATH of whatever launches sfi; IDE/MCP subprocesses often do not inherit it."
31807
+ });
31808
+ } else {
31528
31809
  checks.push({
31529
31810
  name: "Salesforce CLI",
31530
31811
  status: "fail",
31531
- detail: "`sf` not found on PATH",
31532
- fix: "Install the Salesforce CLI: npm install --global @salesforce/cli"
31812
+ detail: "`sf` not found on PATH or common install locations",
31813
+ fix: "Install the Salesforce CLI (npm install --global @salesforce/cli). If it IS installed, add its directory (/usr/local/bin or /opt/homebrew/bin) to your PATH; IDE/MCP subprocesses often do not inherit it."
31533
31814
  });
31534
31815
  }
31535
31816
  const vaultInit = await pathExists2(paths.config);
@@ -31553,7 +31834,7 @@ var runDoctor = async (opts) => {
31553
31834
  });
31554
31835
  } else {
31555
31836
  try {
31556
- const { stdout } = await run(`sf org display --target-org "${targetOrg}" --json`);
31837
+ const { stdout } = await run(`${sfBin} org display --target-org "${targetOrg}" --json`);
31557
31838
  const parsed = JSON.parse(stdout);
31558
31839
  const status = parsed.result?.connectedStatus;
31559
31840
  if (status === "Connected") {
@@ -35729,8 +36010,8 @@ import { XMLParser as XMLParser19, XMLValidator as XMLValidator18 } from "fast-x
35729
36010
  var APPLICATION_FILE_SUFFIX = ".app-meta.xml";
35730
36011
  var ROOT_ELEMENT18 = "CustomApplication";
35731
36012
  var EXTRACTOR_SOURCE8 = "custom-application-extractor";
35732
- var REQUIRED_ELEMENTS4 = ["label", "navType"];
35733
- var ALLOWED_NAV_TYPE = ["Standard", "Console"];
36013
+ var REQUIRED_ELEMENTS4 = ["label"];
36014
+ var ALLOWED_NAV_TYPE = ["Standard", "Console", "Classic"];
35734
36015
  var unwrapSingle19 = (value) => Array.isArray(value) ? value[0] : value;
35735
36016
  var toArray12 = (value) => {
35736
36017
  if (value === void 0 || value === null)
@@ -35779,6 +36060,8 @@ var validateRoot10 = (parsed, path) => {
35779
36060
  if (rootObj["navType"] === void 0)
35780
36061
  rootObj["navType"] = "Standard";
35781
36062
  }
36063
+ if (rootObj["navType"] === void 0)
36064
+ rootObj["navType"] = "Classic";
35782
36065
  for (const required of REQUIRED_ELEMENTS4) {
35783
36066
  if (rootObj[required] === void 0) {
35784
36067
  return err({
@@ -35903,6 +36186,24 @@ var ROOT_ELEMENT19 = "CustomField";
35903
36186
  var FIELDS_DIR_NAME = "fields";
35904
36187
  var PICKLIST_TYPES3 = ["Picklist", "MultiselectPicklist"];
35905
36188
  var FORMULA_ELEMENT_NAME = "formula";
36189
+ var STANDARD_FIELD_TYPES = {
36190
+ Email: "Email",
36191
+ Phone: "Phone",
36192
+ Fax: "Phone",
36193
+ MobilePhone: "Phone",
36194
+ HomePhone: "Phone",
36195
+ OtherPhone: "Phone",
36196
+ AssistantPhone: "Phone",
36197
+ Website: "Url",
36198
+ CreatedDate: "DateTime",
36199
+ LastModifiedDate: "DateTime",
36200
+ SystemModstamp: "DateTime",
36201
+ LastViewedDate: "DateTime",
36202
+ LastReferencedDate: "DateTime",
36203
+ LastActivityDate: "Date",
36204
+ Birthdate: "Date",
36205
+ IsDeleted: "Checkbox"
36206
+ };
35906
36207
  var unwrapSingle20 = (value) => Array.isArray(value) ? value[0] : value;
35907
36208
  var coerceBoolean12 = (value) => {
35908
36209
  if (typeof value === "boolean")
@@ -35976,7 +36277,8 @@ var validateRoot11 = (parsed, path) => {
35976
36277
  message: "missing required element: <type>"
35977
36278
  });
35978
36279
  }
35979
- rootObj["type"] = "Unknown";
36280
+ const bareName = fullName.includes(".") ? fullName.split(".").pop() : fullName;
36281
+ rootObj["type"] = STANDARD_FIELD_TYPES[bareName] ?? "Unknown";
35980
36282
  }
35981
36283
  return ok(rootObj);
35982
36284
  };
@@ -42326,6 +42628,7 @@ var QUICK_ACTIONS_DIR_NAME = "quickActions";
42326
42628
  var REQUIRED_ELEMENTS20 = ["type"];
42327
42629
  var ALLOWED_ACTION_TYPES = [
42328
42630
  "Create",
42631
+ "Flow",
42329
42632
  "LogACall",
42330
42633
  "LightningComponent",
42331
42634
  "LightningWebComponent",
@@ -42465,6 +42768,19 @@ var buildReferencesEdge = (fromId, actionType, rootObj) => {
42465
42768
  properties: { targetKind: "page" }
42466
42769
  };
42467
42770
  }
42771
+ if (actionType === "Flow") {
42772
+ const name = optionalString23(rootObj, "flowDefinition");
42773
+ if (name === null || name.length === 0)
42774
+ return null;
42775
+ return {
42776
+ fromId,
42777
+ toId: `Flow:${name}`,
42778
+ edgeType: "references",
42779
+ confidence: "declared",
42780
+ source: EXTRACTOR_SOURCE25,
42781
+ properties: { targetKind: "flow" }
42782
+ };
42783
+ }
42468
42784
  return null;
42469
42785
  };
42470
42786
  var extractQuickAction = async (path) => {
@@ -42529,6 +42845,7 @@ var extractQuickAction = async (path) => {
42529
42845
  lightningComponent: optionalString23(rootObj, "lightningComponent"),
42530
42846
  lightningWebComponent: optionalString23(rootObj, "lightningWebComponent"),
42531
42847
  page: optionalString23(rootObj, "page"),
42848
+ flowDefinition: optionalString23(rootObj, "flowDefinition"),
42532
42849
  icon: optionalString23(rootObj, "icon"),
42533
42850
  height: optionalInteger2(rootObj, "height"),
42534
42851
  width: optionalString23(rootObj, "width")
@@ -45879,12 +46196,17 @@ var runRefresh = async (opts) => {
45879
46196
  const paths = vaultPaths(configResult.value.vaultRoot);
45880
46197
  const targetOrg = opts.targetOrg ?? configResult.value.targetOrg;
45881
46198
  const requestedTypes = parseTypeFilter(opts.types);
46199
+ const progress = opts.onProgress ?? (() => {
46200
+ });
45882
46201
  if (!opts.noPull) {
46202
+ progress(`Retrieving metadata from ${targetOrg} (this can take several minutes)...`);
45883
46203
  const pulled = await runSfRetrieve(targetOrg, paths.source, requestedTypes);
45884
46204
  if (!pulled.ok)
45885
46205
  return failed(started, pulled.error, []);
45886
46206
  }
46207
+ progress("Extracting components from retrieved source...");
45887
46208
  const walked = await walkAndExtract(paths.source, requestedTypes);
46209
+ progress(`Extracted ${walked.results.length} component file(s); building graph...`);
45888
46210
  await mkdir9(paths.graph, { recursive: true });
45889
46211
  const storeResult = await openGraph(paths.graphDb);
45890
46212
  if (!storeResult.ok) {
@@ -45953,10 +46275,13 @@ var buildCoverageEntries2 = (counts, skippedDirectories, requestedTypes, sourceR
45953
46275
  };
45954
46276
  var runWithOpenGraph = async (args) => {
45955
46277
  const { store, paths, started, targetOrg, walked, opts, requestedTypes } = args;
46278
+ const progress = opts.onProgress ?? (() => {
46279
+ });
45956
46280
  const importResult = await importExtractionResults(store, walked.results);
45957
46281
  if (!importResult.ok) {
45958
46282
  return failed(started, `importExtractionResults: ${importResult.error.message}`, walked.failures, EMPTY_COUNTS, walked.skippedDirectories);
45959
46283
  }
46284
+ progress("Rendering Markdown vault...");
45960
46285
  let counts;
45961
46286
  try {
45962
46287
  counts = await renderVault(store, paths.root);
@@ -46173,7 +46498,14 @@ var loadVaultConfig = async (cwd) => {
46173
46498
  };
46174
46499
  var METADATA_API_NAME = {
46175
46500
  VisualforcePage: "ApexPage",
46176
- VisualforceComponent: "ApexComponent"
46501
+ VisualforceComponent: "ApexComponent",
46502
+ // Sharing rules are exposed by the org as the aggregate type `SharingRules`
46503
+ // (one file per object, e.g. `Account.sharingRules-meta.xml`).
46504
+ SharingRule: "SharingRules",
46505
+ // Custom-metadata *records* (the rows of a `__mdt` type) are retrieved under
46506
+ // the `CustomMetadata` type as `{Type}.{Record}.md-meta.xml` files. The
46507
+ // `__mdt` type definitions themselves come down separately as CustomObject.
46508
+ CustomMetadataRecord: "CustomMetadata"
46177
46509
  };
46178
46510
  var toApiName = (type) => METADATA_API_NAME[type] ?? type;
46179
46511
  var SF_MAX_BUFFER = 256 * 1024 * 1024;
@@ -46242,7 +46574,8 @@ var runSfRetrieve = async (targetOrg, sourceDir, requestedTypes) => {
46242
46574
  }
46243
46575
  const { included: manifestTypes, dropped } = selectManifestTypes(requestedTypes, orgTypes);
46244
46576
  if (dropped.length > 0) {
46245
- process.stdout.write(`Skipping ${dropped.length} metadata type(s) not present in ${targetOrg}: ${dropped.join(", ")}
46577
+ const labelled = dropped.map((type) => toApiName(type) === type ? type : `${type} (${toApiName(type)})`).join(", ");
46578
+ process.stdout.write(`Skipping ${dropped.length} metadata type(s) the ${targetOrg} describe does not expose: ${labelled}
46246
46579
  `);
46247
46580
  }
46248
46581
  if (manifestTypes.length === 0) {
@@ -46344,7 +46677,11 @@ var registerRefreshCommand = (program) => {
46344
46677
  noPull: flags.pull === false,
46345
46678
  ...flags.targetOrg !== void 0 ? { targetOrg: flags.targetOrg } : {},
46346
46679
  ...flags.types !== void 0 ? { types: flags.types } : {},
46347
- ...flags.withToolingApi === true ? { withToolingApi: true } : {}
46680
+ ...flags.withToolingApi === true ? { withToolingApi: true } : {},
46681
+ // Progress goes to stderr so a multi-minute refresh isn't a silent
46682
+ // wait; stdout stays reserved for the final summary.
46683
+ onProgress: (message) => process.stderr.write(`${message}
46684
+ `)
46348
46685
  });
46349
46686
  process.stdout.write(formatRefreshSummary(result));
46350
46687
  if (result.status !== "success")
@@ -46503,7 +46840,7 @@ var registerStatusCommand = (program) => {
46503
46840
  // dist/src/program.js
46504
46841
  var readVersion = () => {
46505
46842
  if (true)
46506
- return "0.1.0";
46843
+ return "0.1.4";
46507
46844
  const pkgUrl = new URL("../../package.json", import.meta.url);
46508
46845
  const raw = readFileSync2(fileURLToPath(pkgUrl), "utf8");
46509
46846
  const parsed = JSON.parse(raw);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sf-intelligence",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "description": "Offline-first, MCP-first knowledge base for a Salesforce org. Ask about your org's metadata, dependencies, permissions, and automation — grounded in real retrieved metadata. Ships the sfi CLI and an MCP server.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/PranavNagrecha/Salesforce-Intelligence",
@@ -52,15 +52,15 @@
52
52
  "devDependencies": {
53
53
  "esbuild": "^0.28.0",
54
54
  "vitest": "^1.6.0",
55
- "@sf-intelligence/contracts": "0.1.0",
56
- "@sf-intelligence/core": "0.1.0",
57
55
  "@sf-intelligence/extractors": "0.1.0",
58
56
  "@sf-intelligence/graph": "0.1.0",
59
- "@sf-intelligence/mcp": "0.1.0",
60
57
  "@sf-intelligence/patterns": "0.1.0",
58
+ "@sf-intelligence/core": "0.1.0",
59
+ "@sf-intelligence/vault": "0.1.0",
60
+ "@sf-intelligence/contracts": "0.1.0",
61
+ "@sf-intelligence/mcp": "0.1.0",
61
62
  "@sf-intelligence/renderers": "0.1.0",
62
- "@sf-intelligence/tooling-api": "0.1.0",
63
- "@sf-intelligence/vault": "0.1.0"
63
+ "@sf-intelligence/tooling-api": "0.1.0"
64
64
  },
65
65
  "scripts": {
66
66
  "build": "tsc --build && node build.mjs",