sf-intelligence 0.3.1 → 0.3.2

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 (3) hide show
  1. package/dist/index.js +587 -214
  2. package/package.json +1 -1
  3. package/server.json +4 -2
package/dist/index.js CHANGED
@@ -86,6 +86,35 @@ var init_exec_helper = __esm({
86
86
  }
87
87
  });
88
88
 
89
+ // ../core/dist/src/path-portable.js
90
+ import { isAbsolute as nodeIsAbsolute, posix, relative as nodeRelative, sep } from "node:path";
91
+ var PATH_SEPARATORS, toPosixPath, splitPathSegments, toRelativePosix, collapseHome;
92
+ var init_path_portable = __esm({
93
+ "../core/dist/src/path-portable.js"() {
94
+ "use strict";
95
+ PATH_SEPARATORS = /[\\/]+/;
96
+ toPosixPath = (p) => p.replace(/\\/g, "/");
97
+ splitPathSegments = (p) => p.split(PATH_SEPARATORS).filter((s) => s.length > 0);
98
+ toRelativePosix = (root, abs) => {
99
+ const rel = nodeRelative(root, abs);
100
+ if (rel === "" || rel.startsWith("..") || nodeIsAbsolute(rel)) {
101
+ return toPosixPath(abs);
102
+ }
103
+ return sep === posix.sep ? rel : rel.split(sep).join(posix.sep);
104
+ };
105
+ collapseHome = (abs, home) => {
106
+ if (home.length === 0)
107
+ return abs;
108
+ const rel = nodeRelative(home, abs);
109
+ if (rel === "")
110
+ return "~";
111
+ if (rel.startsWith("..") || nodeIsAbsolute(rel))
112
+ return abs;
113
+ return `~/${toPosixPath(rel)}`;
114
+ };
115
+ }
116
+ });
117
+
89
118
  // ../core/dist/src/network-policy.js
90
119
  import { AsyncLocalStorage } from "node:async_hooks";
91
120
  var MODE_OVERRIDE, VALID_MODES, getNetworkMode, withNetworkMode, isUpdateCheckOptedIn, CI_ENV_MARKERS, isUpdateCheckForcedOff, purposeAllowed, assertNetworkAllowed, describeNetworkPolicy;
@@ -464,6 +493,7 @@ var init_src = __esm({
464
493
  init_result();
465
494
  init_api_names();
466
495
  init_exec_helper();
496
+ init_path_portable();
467
497
  init_update_notifier();
468
498
  init_network_policy();
469
499
  }
@@ -494,6 +524,7 @@ var SOURCE_CONFLICT_PROPERTY, isDxCanonicalPath, chooseSourcePath, stableJson, c
494
524
  var init_duplicate_source = __esm({
495
525
  "../graph/dist/src/duplicate-source.js"() {
496
526
  "use strict";
527
+ init_src();
497
528
  init_relativize();
498
529
  SOURCE_CONFLICT_PROPERTY = "sourceConflict";
499
530
  isDxCanonicalPath = (path2) => /(?:^|\/)main\/default\//.test(path2);
@@ -534,7 +565,7 @@ var init_duplicate_source = __esm({
534
565
  })).sort()
535
566
  });
536
567
  commonSuffixSegments = (paths) => {
537
- const split = paths.map((p) => p.split("/"));
568
+ const split = paths.map((p) => splitPathSegments(p));
538
569
  const shortest = Math.min(...split.map((s) => s.length));
539
570
  let n = 0;
540
571
  while (n < shortest) {
@@ -548,7 +579,7 @@ var init_duplicate_source = __esm({
548
579
  layoutRoots = (paths) => {
549
580
  const tail = commonSuffixSegments(paths);
550
581
  const roots = paths.map((p) => {
551
- const segs = p.split("/");
582
+ const segs = splitPathSegments(p);
552
583
  const head = segs.slice(0, Math.max(0, segs.length - tail));
553
584
  return head.length === 0 ? "(source tree root)" : `${head.join("/")}/`;
554
585
  });
@@ -1248,10 +1279,10 @@ var init_apply_change_set = __esm({
1248
1279
  "graph-future-dispatch"
1249
1280
  ]);
1250
1281
  componentTypeFromNodeId = (id) => {
1251
- const sep6 = id.indexOf(":");
1252
- if (sep6 < 0)
1282
+ const sep5 = id.indexOf(":");
1283
+ if (sep5 < 0)
1253
1284
  return null;
1254
- return id.slice(0, sep6);
1285
+ return id.slice(0, sep5);
1255
1286
  };
1256
1287
  INCREMENTAL_DELTA_CAP = 2e3;
1257
1288
  changeSetSize = (cs) => cs.upsertNodes.length + cs.deleteNodeIds.length + cs.upsertEdges.length + cs.deleteEdgeKeys.length;
@@ -1788,9 +1819,9 @@ var init_queries = __esm({
1788
1819
  message: `${label}: ${e.message}`
1789
1820
  });
1790
1821
  makeUnresolvedStubNode = (id) => {
1791
- const sep6 = id.indexOf(":");
1792
- const type = sep6 > 0 ? id.slice(0, sep6) : "";
1793
- const apiName = sep6 >= 0 ? id.slice(sep6 + 1) : id;
1822
+ const sep5 = id.indexOf(":");
1823
+ const type = sep5 > 0 ? id.slice(0, sep5) : "";
1824
+ const apiName = sep5 >= 0 ? id.slice(sep5 + 1) : id;
1794
1825
  return {
1795
1826
  id,
1796
1827
  type,
@@ -3634,9 +3665,13 @@ var init_store = __esm({
3634
3665
  init_migrations();
3635
3666
  isLockConflict = (message) => {
3636
3667
  const m = message.toLowerCase();
3637
- return m.includes("could not set lock") || m.includes("conflicting lock");
3668
+ return m.includes("could not set lock") || m.includes("conflicting lock") || // Windows (ERROR_SHARING_VIOLATION, surfaced through DuckDB's IO Error).
3669
+ m.includes("being used by another process") || m.includes("sharing violation");
3670
+ };
3671
+ lockConflictMessage = (path2, cause) => {
3672
+ const remedy = process.platform === "win32" ? "On Windows the running server must be stopped first: Windows will not let `sfi refresh` replace a database file while another process holds it open, so close your MCP client (or stop `sfi mcp`), re-run the refresh, then reopen the client and retry." : "`sfi refresh` handles this AUTOMATICALLY (it rebuilds into a side file and atomically swaps it in; an open MCP server picks up the new vault on its next call via the refresh epoch \u2014 no restart needed). Any OTHER writer should stop the holding process and retry.";
3673
+ return `vault database at ${path2} is locked by another process \u2014 most likely a running \`sfi mcp\` server (your IDE's MCP integration) or a concurrent \`sfi refresh\`. DuckDB allows only one writer at a time. ${remedy} Underlying error: ${cause}`;
3638
3674
  };
3639
- lockConflictMessage = (path2, cause) => `vault database at ${path2} is locked by another process \u2014 most likely a running \`sfi mcp\` server (your IDE's MCP integration) or a concurrent \`sfi refresh\`. DuckDB allows only one writer at a time. \`sfi refresh\` handles this AUTOMATICALLY (it rebuilds into a side file and atomically swaps it in; an open MCP server picks up the new vault on its next call via the refresh epoch \u2014 no restart needed). Any OTHER writer should stop the holding process and retry. Underlying error: ${cause}`;
3640
3675
  isNativeBindingFailure = (message) => {
3641
3676
  const m = message.toLowerCase();
3642
3677
  return m.includes("dlopen") || m.includes("err_dlopen_failed") || m.includes("cannot open shared object") || m.includes("library not loaded") || m.includes("image not found") || m.includes("invalid elf header") || m.includes("not a valid win32 application") || m.includes("was compiled against a different node") || m.includes("no native build was found") || m.includes("could not locate the bindings") || m.includes("cannot find module") && m.includes("duckdb") || m.includes("cannot find package") && m.includes("duckdb");
@@ -4033,10 +4068,12 @@ var init_family_freshness = __esm({
4033
4068
  }
4034
4069
  if (prev === void 0)
4035
4070
  return entry;
4071
+ const carryConfirmed = entry.retrieveConfirmed !== true && entry.errored !== true && entry.pending !== true && prev.retrieveConfirmed === true;
4036
4072
  return {
4037
4073
  ...entry,
4038
4074
  ...prev.retrievedAt !== void 0 ? { retrievedAt: prev.retrievedAt } : {},
4039
- ...prev.epoch !== void 0 ? { epoch: prev.epoch } : {}
4075
+ ...prev.epoch !== void 0 ? { epoch: prev.epoch } : {},
4076
+ ...carryConfirmed ? { retrieveConfirmed: true } : {}
4040
4077
  };
4041
4078
  });
4042
4079
  };
@@ -4158,7 +4195,7 @@ var init_tombstones = __esm({
4158
4195
  // ../vault/dist/src/hash.js
4159
4196
  import { createHash } from "node:crypto";
4160
4197
  import { readdir, readFile as readFile4, stat } from "node:fs/promises";
4161
- import { join as join4, relative, sep } from "node:path";
4198
+ import { join as join4 } from "node:path";
4162
4199
  var RECORD_SEPARATOR, RECORD_TERMINATOR, computeSourceTreeHash, walkAndHash, hashFile;
4163
4200
  var init_hash = __esm({
4164
4201
  "../vault/dist/src/hash.js"() {
@@ -4221,8 +4258,7 @@ var init_hash = __esm({
4221
4258
  return ok(void 0);
4222
4259
  };
4223
4260
  hashFile = async (rootDir, absolutePath, hash) => {
4224
- const rawRel = relative(rootDir, absolutePath);
4225
- const relPath = sep === "/" ? rawRel : rawRel.split(sep).join("/");
4261
+ const relPath = toRelativePosix(rootDir, absolutePath);
4226
4262
  let bytes;
4227
4263
  try {
4228
4264
  bytes = await readFile4(absolutePath);
@@ -4282,7 +4318,7 @@ var init_layout = __esm({
4282
4318
 
4283
4319
  // ../vault/dist/src/source-path.js
4284
4320
  import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
4285
- import { isAbsolute, join as join6, posix, relative as relative2, sep as sep2 } from "node:path";
4321
+ import { isAbsolute, join as join6, posix as posix2, relative, sep as sep2 } from "node:path";
4286
4322
  var resolveVaultSourcePath, toVaultRelativePosix, matchesAnySuffix, isDxCanonicalPath2, logicalSourceKey, dedupeDuplicateLayouts, collectVaultSourceFiles;
4287
4323
  var init_source_path = __esm({
4288
4324
  "../vault/dist/src/source-path.js"() {
@@ -4295,8 +4331,8 @@ var init_source_path = __esm({
4295
4331
  return join6(vaultRoot, sourcePath);
4296
4332
  };
4297
4333
  toVaultRelativePosix = (vaultRoot, fileAbsPath) => {
4298
- const rel = relative2(vaultRoot, fileAbsPath);
4299
- return sep2 === posix.sep ? rel : rel.split(sep2).join(posix.sep);
4334
+ const rel = relative(vaultRoot, fileAbsPath);
4335
+ return sep2 === posix2.sep ? rel : rel.split(sep2).join(posix2.sep);
4300
4336
  };
4301
4337
  matchesAnySuffix = (name, suffixes) => suffixes.some((suffix) => name.endsWith(suffix));
4302
4338
  isDxCanonicalPath2 = (path2) => /(?:^|\/)main\/default\//.test(path2);
@@ -4505,10 +4541,12 @@ var init_manifest = __esm({
4505
4541
  const confirmedEmpty = (entry) => entry.requested && entry.retrieved === 0 && entry.retrieveConfirmed === true && !entry.errored && !entry.neverModeled && entry.pending !== true;
4506
4542
  const retrievedNotParsed = filtered.filter((entry) => confirmedEmpty(entry) && unparsedContainerTypes.has(entry.type)).map((entry) => entry.type);
4507
4543
  const retrievedNotParsedSet = new Set(retrievedNotParsed);
4508
- const coveredTypes = filtered.filter((entry) => entry.requested && (entry.retrieved > 0 || entry.retrieveConfirmed === true) && !entry.errored && !entry.neverModeled && entry.pending !== true && !retrievedNotParsedSet.has(entry.type)).map((entry) => entry.type);
4544
+ const coveredTypes = filtered.filter((entry) => entry.requested && (entry.retrieved > 0 || entry.retrieveConfirmed === true) && !entry.errored && !entry.neverModeled && entry.pending !== true && // FIX-2: a `capped` row is real, attempted, ATTEMPTED-BUT-PARTIAL
4545
+ // evidence — never `covered`, same as `pending`. See CoverageEntry.capped.
4546
+ entry.capped !== true && !retrievedNotParsedSet.has(entry.type)).map((entry) => entry.type);
4509
4547
  const emptyTypes = filtered.filter((entry) => entry.requested && entry.retrieved === 0 && entry.retrieveConfirmed !== true && !entry.errored && !entry.neverModeled && entry.pending !== true).map((entry) => entry.type);
4510
4548
  const partialTypes = [
4511
- ...filtered.filter((entry) => (entry.errored || entry.pending === true) && !entry.neverModeled).map((entry) => entry.type),
4549
+ ...filtered.filter((entry) => (entry.errored || entry.pending === true || entry.capped === true) && !entry.neverModeled).map((entry) => entry.type),
4512
4550
  ...emptyTypes
4513
4551
  ];
4514
4552
  const notModeledTypes = filtered.filter((entry) => entry.neverModeled).map((entry) => entry.type);
@@ -5908,7 +5946,7 @@ var init_naming_convention = __esm({
5908
5946
  });
5909
5947
 
5910
5948
  // ../patterns/dist/src/pii-detection.js
5911
- var ENCRYPTED_TEXT_DATA_TYPE, EMAIL_DATA_TYPE, CONTACT_INCAPABLE_DATA_TYPES, CUSTOM_FIELD_SUFFIX2, CLASSIFICATION_RANK, SECURITY_CLASSIFICATION_TIERS, getDeclaredSecurityTier, getDeclaredSecurityRaw, toWordSegments, hasRaceSegment, PHI_ACRONYM_RE, hasPhiAcronym, NAME_PATTERNS, MONEY_AMOUNT_TOKENS, MONEY_CAPABLE_DATA_TYPES, GENERIC_CATALOG_PRICE_MARKERS, GENERIC_CATALOG_PARENT_MARKERS, ORGANIZATIONAL_OBJECT_MARKERS, NAME_PATTERNS_LOWERCASED, DESCRIPTION_RULES, stripCustomFieldSuffix2, normalizeDataType, getDataType, parentObjectApiNameLower, isOrganizationalParentObject, isMoneyAmountField, isVenueOrUrlFieldName, getDescriptionLower, matchNamePattern, matchDescriptionRule, stricterClassification, detectHeuristic, detectPiiClassificationWithReason, detectPiiClassification, isRegulatedPiiClassification;
5949
+ var ENCRYPTED_TEXT_DATA_TYPE, EMAIL_DATA_TYPE, CONTACT_INCAPABLE_DATA_TYPES, CUSTOM_FIELD_SUFFIX2, CLASSIFICATION_RANK, SECURITY_CLASSIFICATION_TIERS, getDeclaredSecurityTier, getDeclaredSecurityRaw, toWordSegments, WHOLE_WORD_ONLY_TOKENS, hasRaceSegment, PHI_ACRONYM_RE, hasPhiAcronym, NAME_PATTERNS, MONEY_AMOUNT_TOKENS, MONEY_CAPABLE_DATA_TYPES, GENERIC_CATALOG_PRICE_MARKERS, GENERIC_CATALOG_PARENT_MARKERS, ORGANIZATIONAL_OBJECT_MARKERS, NAME_PATTERNS_LOWERCASED, DESCRIPTION_RULES, stripCustomFieldSuffix2, normalizeDataType, getDataType, parentObjectApiNameLower, isOrganizationalParentObject, isMoneyAmountField, isVenueOrUrlFieldName, getDescriptionLower, matchNamePattern, matchDescriptionRule, stricterClassification, detectHeuristic, detectPiiClassificationWithReason, detectPiiClassification, isRegulatedPiiClassification;
5912
5950
  var init_pii_detection = __esm({
5913
5951
  "../patterns/dist/src/pii-detection.js"() {
5914
5952
  "use strict";
@@ -5948,6 +5986,11 @@ var init_pii_detection = __esm({
5948
5986
  return typeof raw === "string" && raw.length > 0 ? raw.trim() : null;
5949
5987
  };
5950
5988
  toWordSegments = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((s) => s.length > 0).map((s) => s.toLowerCase());
5989
+ WHOLE_WORD_ONLY_TOKENS = /* @__PURE__ */ new Set([
5990
+ "city",
5991
+ "phone",
5992
+ "street"
5993
+ ]);
5951
5994
  hasRaceSegment = (rawApiName) => toWordSegments(rawApiName).includes("race");
5952
5995
  PHI_ACRONYM_RE = /(?<![A-Za-z])PHI(?![A-Za-z])/;
5953
5996
  hasPhiAcronym = (rawText) => PHI_ACRONYM_RE.test(rawText);
@@ -6028,6 +6071,7 @@ var init_pii_detection = __esm({
6028
6071
  "email",
6029
6072
  "mobile",
6030
6073
  "phone",
6074
+ "telephone",
6031
6075
  "fax",
6032
6076
  "address",
6033
6077
  "street",
@@ -6177,7 +6221,8 @@ var init_pii_detection = __esm({
6177
6221
  matchNamePattern = (apiNameLower, apiNameRaw) => {
6178
6222
  for (const rule of NAME_PATTERNS_LOWERCASED) {
6179
6223
  for (const token of rule.tokensLower) {
6180
- if (apiNameLower.includes(token)) {
6224
+ const matched = WHOLE_WORD_ONLY_TOKENS.has(token) ? toWordSegments(apiNameRaw).includes(token) : apiNameLower.includes(token);
6225
+ if (matched) {
6181
6226
  return {
6182
6227
  classification: rule.classification,
6183
6228
  category: rule.category,
@@ -6309,7 +6354,7 @@ var init_pii_detection = __esm({
6309
6354
  });
6310
6355
 
6311
6356
  // ../patterns/dist/src/code-quality-patterns.js
6312
- var COMMENT_OR_STRING_PATTERN, blankOut, stripCommentsAndStrings, offsetToLine, ASSERTION_PATTERN, countAssertions, findMatchingBrace, enclosingBlockStart, APEX_NON_TYPE_KEYWORDS, enclosingMethodStart, matchOpenParenBackward, LOOP_KEYWORD_PATTERN, skipLoopHeader, findBodyOpenBrace, findLoopBodies, SOQL_IN_LOOP_PATTERN, detectSoqlInLoop, DML_IN_LOOP_PATTERN, detectDmlInLoop, KNOWN_KEY_PREFIXES, STRING_LITERAL_PATTERN, ID_15_OR_18_PATTERN, detectHardcodedIds, EMAIL_LITERAL_PATTERN, USERNAME_LITERAL_PATTERN, detectHardcodedEmails, detectHardcodedUsernames, URL_LITERAL_PATTERN, SALESFORCE_DOMAIN_SUFFIXES, isSalesforcePlatformHost, detectHardcodedUrls, DYNAMIC_APEX_PATTERNS, detectDynamicApex, DML_STATEMENT_PATTERN, DML_DATABASE_CALL_PATTERN, CRUD_HINT_PATTERNS, writeCheckedSObjects, hasAnyWriteGate, hasNonTypedWriteGate, COLLECTION_WRAPPER, unwrapType, resolveVarSObjectType, detectMissingCrudCheck, INLINE_SOQL_PATTERN, QUERY_ASSIGN_LHS, readStripFor, detectMissingFlsCheck, DATABASE_QUERY_CALL_START_PATTERN, escapeForRegex, findMatchingParen, findVarAssignment, tokenizeConcatExpr, tokenIsSafe, detectSoqlInjection, WITHOUT_SHARING_CLASS_PATTERN, hasPrecedingComment, detectWithoutSharingNoComment, TRIGGER_HEADER_PATTERN, RECURSION_GUARD_PATTERNS, detectTriggerNoRecursionGuard, detectOldApiVersion, DATABASE_UPSERT_PATTERN, detectDatabaseUpsertNoOptions, FAKE_ASSERT_BOOL_PATTERN, FAKE_ASSERTEQUALS_SELF_PATTERN, FAKE_ASSERTEQUALS_LITERAL_PATTERN, detectFakeAssertion, SANDBOX_LITERAL_PATTERN, detectHardcodedSandboxData, CATCH_HEADER_PATTERN, stripBalancedCalls, SYSTEM_DEBUG_CALL_START, LOGGER_CALL_START, isLogOnlyOrEmpty, detectSwallowedException, detectCodeQualityIssues, LINE_LOCATION_PATTERN, parseLineFromLocation;
6357
+ var COMMENT_OR_STRING_PATTERN, blankOut, stripCommentsAndStrings, offsetToLine, ASSERTION_PATTERN, countAssertions, findMatchingBrace, enclosingBlockStart, APEX_NON_TYPE_KEYWORDS, enclosingMethodStart, matchOpenParenBackward, LOOP_KEYWORD_PATTERN, skipLoopHeader, findBodyOpenBrace, findLoopBodies, SOQL_IN_LOOP_PATTERN, detectSoqlInLoop, DML_IN_LOOP_PATTERN, detectDmlInLoop, KNOWN_KEY_PREFIXES, STRING_LITERAL_PATTERN, ID_15_OR_18_PATTERN, isKnownSalesforceIdLiteral, detectHardcodedIds, EMAIL_LITERAL_PATTERN, USERNAME_LITERAL_PATTERN, detectHardcodedEmails, detectHardcodedUsernames, URL_LITERAL_PATTERN, SALESFORCE_DOMAIN_SUFFIXES, isSalesforcePlatformHost, detectHardcodedUrls, DYNAMIC_APEX_PATTERNS, detectDynamicApex, DML_STATEMENT_PATTERN, DML_DATABASE_CALL_PATTERN, CRUD_HINT_PATTERNS, writeCheckedSObjects, hasAnyWriteGate, hasNonTypedWriteGate, COLLECTION_WRAPPER, unwrapType, resolveVarSObjectType, detectMissingCrudCheck, INLINE_SOQL_PATTERN, QUERY_ASSIGN_LHS, readStripFor, detectMissingFlsCheck, DATABASE_QUERY_CALL_START_PATTERN, escapeForRegex, findMatchingParen, findVarAssignment, tokenizeConcatExpr, tokenIsSafe, detectSoqlInjection, WITHOUT_SHARING_CLASS_PATTERN, hasPrecedingComment, detectWithoutSharingNoComment, TRIGGER_HEADER_PATTERN, RECURSION_GUARD_PATTERNS, detectTriggerNoRecursionGuard, detectOldApiVersion, DATABASE_UPSERT_PATTERN, detectDatabaseUpsertNoOptions, FAKE_ASSERT_BOOL_PATTERN, FAKE_ASSERTEQUALS_SELF_PATTERN, FAKE_ASSERTEQUALS_LITERAL_PATTERN, detectFakeAssertion, SANDBOX_LITERAL_PATTERN, detectHardcodedSandboxData, CATCH_HEADER_PATTERN, stripBalancedCalls, SYSTEM_DEBUG_CALL_START, LOGGER_CALL_START, isLogOnlyOrEmpty, detectSwallowedException, detectCodeQualityIssues, LINE_LOCATION_PATTERN, parseLineFromLocation;
6313
6358
  var init_code_quality_patterns = __esm({
6314
6359
  "../patterns/dist/src/code-quality-patterns.js"() {
6315
6360
  "use strict";
@@ -6589,6 +6634,7 @@ var init_code_quality_patterns = __esm({
6589
6634
  ]);
6590
6635
  STRING_LITERAL_PATTERN = /'((?:\\[\s\S]|[^'\\])*)'/g;
6591
6636
  ID_15_OR_18_PATTERN = /^[a-zA-Z0-9]{15}(?:[a-zA-Z0-9]{3})?$/;
6637
+ isKnownSalesforceIdLiteral = (value) => ID_15_OR_18_PATTERN.test(value) && KNOWN_KEY_PREFIXES.has(value.slice(0, 3));
6592
6638
  detectHardcodedIds = (source) => {
6593
6639
  const issues = [];
6594
6640
  const re = new RegExp(STRING_LITERAL_PATTERN.source, "g");
@@ -9579,7 +9625,7 @@ var init_formula_tokenizer = __esm({
9579
9625
  IDENTIFIER_PATH = /[A-Za-z_][A-Za-z_0-9]*(?:\.[A-Za-z_][A-Za-z_0-9]*)*/g;
9580
9626
  SPECIAL_VARIABLE_PATH = /\$[A-Za-z_][A-Za-z_0-9]*(?:\.[A-Za-z_][A-Za-z_0-9]*)*/g;
9581
9627
  MALFORMED_NUMERIC = /\d+\.\d+\.\d+/;
9582
- NUMERIC_LITERAL = /\d+(?:\.\d+)?/g;
9628
+ NUMERIC_LITERAL = /(?<![A-Za-z0-9_])\d+(?:\.\d+)?(?![A-Za-z0-9_])/g;
9583
9629
  blankOut4 = (text) => text.replace(/[^\n]/g, " ");
9584
9630
  tokenizeFormula = (formula) => {
9585
9631
  if (formula.trim().length === 0) {
@@ -10407,8 +10453,10 @@ var deriveComponentApiName, deriveEntityVariant, deriveParentApiName, deriveNest
10407
10453
  var init_path_utils = __esm({
10408
10454
  "../extractors/dist/src/path-utils.js"() {
10409
10455
  "use strict";
10456
+ init_src();
10410
10457
  deriveComponentApiName = (filePath, suffix) => {
10411
- const base = basename(filePath);
10458
+ const segments = splitPathSegments(filePath);
10459
+ const base = segments[segments.length - 1] ?? "";
10412
10460
  if (suffix.length > 0 && base.endsWith(suffix)) {
10413
10461
  return base.slice(0, base.length - suffix.length);
10414
10462
  }
@@ -10433,16 +10481,9 @@ var init_path_utils = __esm({
10433
10481
  if (parentDirLevel < 1) {
10434
10482
  return "";
10435
10483
  }
10436
- let current = dirname10(filePath);
10437
- for (let step3 = 1; step3 < parentDirLevel; step3 += 1) {
10438
- const next = dirname10(current);
10439
- if (next === current) {
10440
- return "";
10441
- }
10442
- current = next;
10443
- }
10444
- const name = basename(current);
10445
- if (name.length === 0 || name === ".") {
10484
+ const segments = splitPathSegments(filePath);
10485
+ const name = segments[segments.length - 1 - parentDirLevel];
10486
+ if (name === void 0 || name.length === 0 || name === ".") {
10446
10487
  return "";
10447
10488
  }
10448
10489
  return name;
@@ -10458,11 +10499,11 @@ var init_path_utils = __esm({
10458
10499
  };
10459
10500
  deriveEmailTemplateFolderAndName = (filePath, suffix) => {
10460
10501
  const templateName = deriveComponentApiName(filePath, suffix);
10461
- const dir = dirname10(filePath);
10462
- if (dir === "" || dir === "." || dir === "/") {
10502
+ const allSegments = splitPathSegments(filePath);
10503
+ if (allSegments.length < 2) {
10463
10504
  return null;
10464
10505
  }
10465
- const segments = dir.split("/").filter((s) => s.length > 0);
10506
+ const segments = allSegments.slice(0, -1);
10466
10507
  let emailIndex = -1;
10467
10508
  for (let i = segments.length - 1; i >= 0; i -= 1) {
10468
10509
  if (segments[i] === "email") {
@@ -17378,7 +17419,7 @@ var init_enterprise_metadata = __esm({
17378
17419
  EXTRACTOR_SOURCE14 = "enterprise-metadata-extractor";
17379
17420
  deriveFolderedApiName = (filePath, suffix, folderRoot) => {
17380
17421
  const bare = deriveComponentApiName(filePath, suffix);
17381
- const segments = filePath.replace(/\\/g, "/").split("/").filter((s) => s.length > 0);
17422
+ const segments = splitPathSegments(filePath);
17382
17423
  const anchor = Math.max(segments.lastIndexOf("unpackaged"), segments.lastIndexOf("source"));
17383
17424
  const anchored = anchor === -1 ? -1 : segments.indexOf(folderRoot, anchor + 1);
17384
17425
  const rootIndex = anchored !== -1 ? anchored : segments.lastIndexOf(folderRoot);
@@ -31568,10 +31609,10 @@ var init_answer_render = __esm({
31568
31609
  if (rows.length === 0)
31569
31610
  return "";
31570
31611
  const head = `| ${headers.map(cell).join(" | ")} |`;
31571
- const sep6 = `| ${headers.map(() => "---").join(" | ")} |`;
31612
+ const sep5 = `| ${headers.map(() => "---").join(" | ")} |`;
31572
31613
  const body = rows.map((r) => `| ${headers.map((_, i) => cell(r[i])).join(" | ")} |`).join("\n");
31573
31614
  return `${head}
31574
- ${sep6}
31615
+ ${sep5}
31575
31616
  ${body}`;
31576
31617
  };
31577
31618
  renderInterpretationsMarkdown = (data) => {
@@ -43128,7 +43169,7 @@ var init_live_capability = __esm({
43128
43169
  });
43129
43170
 
43130
43171
  // ../mcp/dist/src/tools/apex-reachability.js
43131
- var NOT_USAGE_EDGE_TYPES, USAGE_EDGE_TYPES, UNPROVEN_REGISTRATION_KINDS, CALLABLE_INTERFACE, isFrameworkSubclass, isCallableDispatch, ASYNC_DISPATCH_PROPERTY_KEYS, UI_CONTROLLER_SOURCE_PREFIXES, isTestClassNode, entryKindsFor, isUnprovenRegistrationKind, UNPROVEN_REGISTRATION_DISCLOSURE, isUiControllerEdge, CONFIDENCE_RANK2, weakerConfidence, walkUpstreamUsage, countUnwalkedUsageInEdges;
43172
+ var NOT_USAGE_EDGE_TYPES, USAGE_EDGE_TYPES, UNPROVEN_REGISTRATION_KINDS, CALLABLE_INTERFACE, isFrameworkSubclass, isCallableDispatch, isNamespacedInterfaceImplementation, ASYNC_DISPATCH_PROPERTY_KEYS, UI_CONTROLLER_SOURCE_PREFIXES, isTestClassNode, entryKindsFor, isUnprovenRegistrationKind, UNPROVEN_REGISTRATION_DISCLOSURE, isUiControllerEdge, CONFIDENCE_RANK2, weakerConfidence, walkUpstreamUsage, countUnwalkedUsageInEdges;
43132
43173
  var init_apex_reachability = __esm({
43133
43174
  "../mcp/dist/src/tools/apex-reachability.js"() {
43134
43175
  "use strict";
@@ -43140,6 +43181,7 @@ var init_apex_reachability = __esm({
43140
43181
  UNPROVEN_REGISTRATION_KINDS = [
43141
43182
  "framework-subclass",
43142
43183
  "callable-dispatch",
43184
+ "namespaced-interface",
43143
43185
  // ASYNC-DISPATCH REGISTRATION IS ALSO UNPROVABLE OFFLINE. Added after 16 of
43144
43186
  // 18 org-wide `likely_dead` classes turned out to be Schedulable or Batchable
43145
43187
  // — see `isAsyncDispatchRegistration` for the platform reason.
@@ -43156,6 +43198,10 @@ var init_apex_reachability = __esm({
43156
43198
  const impl = node.properties["implements"];
43157
43199
  return Array.isArray(impl) && impl.includes(CALLABLE_INTERFACE);
43158
43200
  };
43201
+ isNamespacedInterfaceImplementation = (node) => {
43202
+ const impl = node.properties["implements"];
43203
+ return Array.isArray(impl) && impl.some((i) => typeof i === "string" && i.includes("."));
43204
+ };
43159
43205
  ASYNC_DISPATCH_PROPERTY_KEYS = [
43160
43206
  "isQueueable",
43161
43207
  "isBatchable",
@@ -43190,10 +43236,12 @@ var init_apex_reachability = __esm({
43190
43236
  kinds.push("framework-subclass");
43191
43237
  if (isCallableDispatch(node))
43192
43238
  kinds.push("callable-dispatch");
43239
+ if (isNamespacedInterfaceImplementation(node))
43240
+ kinds.push("namespaced-interface");
43193
43241
  return kinds;
43194
43242
  };
43195
43243
  isUnprovenRegistrationKind = (kind) => UNPROVEN_REGISTRATION_KINDS.includes(kind);
43196
- UNPROVEN_REGISTRATION_DISCLOSURE = 'A class that extends a base class from ANOTHER namespace (a managed package or platform framework instantiates its own subclasses), declares the Callable dynamic-invocation interface, or implements Queueable / Database.Batchable / Schedulable is registered OUTSIDE the metadata a vault holds: in a string literal, a Custom Metadata record, managed-package code, anonymous Apex run from the Developer Console or a deployment script, or a CronTrigger record written by Setup > Schedule Apex. CronTrigger is DATA, not metadata \u2014 it is never retrieved, so no refresh can close that gap. None of those registrations mints an edge, so such a class has zero incoming edges by construction. Every one of these signals says the class is BUILT to be invoked from outside this vault; NONE of them proves the registration is live. Treat it as "not dead", never as "proven reachable" \u2014 confirm the registration in the org before relying on it.';
43244
+ UNPROVEN_REGISTRATION_DISCLOSURE = 'A class that extends a base class from ANOTHER namespace (a managed package or platform framework instantiates its own subclasses), declares the Callable dynamic-invocation interface, implements an interface QUALIFIED by another namespace (an AuthProvider record names an Auth.RegistrationHandler implementation by type; a managed package registers its own plugin interface the same way), or implements Queueable / Database.Batchable / Schedulable is registered OUTSIDE the metadata a vault holds: in a string literal, a Custom Metadata record, managed-package code, anonymous Apex run from the Developer Console or a deployment script, or a CronTrigger record written by Setup > Schedule Apex. CronTrigger is DATA, not metadata \u2014 it is never retrieved, so no refresh can close that gap. None of those registrations mints an edge, so such a class has zero incoming edges by construction. Every one of these signals says the class is BUILT to be invoked from outside this vault; NONE of them proves the registration is live. Treat it as "not dead", never as "proven reachable" \u2014 confirm the registration in the org before relying on it.';
43197
43245
  isUiControllerEdge = (edge) => edge.edgeType === "references" && UI_CONTROLLER_SOURCE_PREFIXES.some((p) => edge.fromId.startsWith(p));
43198
43246
  CONFIDENCE_RANK2 = {
43199
43247
  declared: 0,
@@ -49865,7 +49913,7 @@ var init_concept_reasoning = __esm({
49865
49913
  return { envelope: envelope2, reservedBytes: bytes, reservationCapped: true };
49866
49914
  }
49867
49915
  let claimCap = envelope2.claims.length;
49868
- while (bytes > hardMax && claimCap > 1) {
49916
+ while (bytes > hardMax && claimCap > 0) {
49869
49917
  claimCap = Math.floor(claimCap / 2);
49870
49918
  envelope2 = projectConceptReasoning(ctx, reasoned.value, { listCap: 0, maxClaims: claimCap });
49871
49919
  bytes = sizeOf2(envelope2);
@@ -50195,10 +50243,13 @@ var init_coverage_trust = __esm({
50195
50243
  "ApexClass",
50196
50244
  "ApexTrigger",
50197
50245
  "WorkflowRule",
50246
+ "ApprovalProcess",
50247
+ "AutoResponseRule",
50248
+ "AssignmentRule",
50249
+ "EscalationRule",
50198
50250
  "Layout",
50199
50251
  "SharingRule",
50200
50252
  "DuplicateRule",
50201
- "ConditionalContext",
50202
50253
  "Report",
50203
50254
  "Dashboard",
50204
50255
  "ListView",
@@ -53907,8 +53958,7 @@ var init_roster = __esm({
53907
53958
  // knob on the Zod schema; unadvertised it was unreachable from a
53908
53959
  // schema-driven host, which is the same as not shipping it.
53909
53960
  includeConceptReasoning: { type: "boolean" }
53910
- },
53911
- required: ["id"]
53961
+ }
53912
53962
  });
53913
53963
  GET_EDGES_INPUT_SCHEMA = Object.freeze({
53914
53964
  type: "object",
@@ -53991,6 +54041,12 @@ var init_roster = __esm({
53991
54041
  hasInvocableMethod: { type: "boolean" },
53992
54042
  hasAuraEnabledMethod: { type: "boolean" },
53993
54043
  isTest: { type: "boolean" },
54044
+ // Flow metadata: exact match on `properties.triggerObject`.
54045
+ triggerObject: { type: "string", minLength: 1 },
54046
+ // Flow metadata: exact match on `properties.status` (e.g. Active).
54047
+ status: { type: "string", minLength: 1 },
54048
+ // Flow metadata: keep only record-triggered flows (`triggerType` starts with Record).
54049
+ recordTriggered: { type: "boolean" },
53994
54050
  // Description-presence filters. Answer "which <type> have no description".
53995
54051
  // Only meaningful for a type whose extractor captures `<description>`.
53996
54052
  missingDescription: { type: "boolean" },
@@ -54019,9 +54075,12 @@ var init_roster = __esm({
54019
54075
  properties: {
54020
54076
  query: { type: "string", minLength: 1 },
54021
54077
  regex: { type: "boolean" },
54022
- limit: { type: "integer", minimum: 1, maximum: 200 }
54023
- },
54024
- required: ["query"]
54078
+ limit: { type: "integer", minimum: 1, maximum: 200 },
54079
+ // Flow metadata: exact match on `properties.triggerObject`.
54080
+ triggerObject: { type: "string", minLength: 1 },
54081
+ // Return a digest instead of full matches.
54082
+ summarize: { type: "boolean" }
54083
+ }
54025
54084
  });
54026
54085
  NAMING_CONVENTION_REPORT_INPUT_SCHEMA = Object.freeze({
54027
54086
  type: "object",
@@ -54480,24 +54539,32 @@ var init_roster = __esm({
54480
54539
  properties: {
54481
54540
  staleDays: { type: "integer", minimum: 1, maximum: 3650 },
54482
54541
  limit: { type: "integer", minimum: 1, maximum: 500 },
54542
+ nameContains: { type: "string", minLength: 1 },
54543
+ folderName: { type: "string", minLength: 1 },
54483
54544
  ...LIVE_ENABLED_PROPERTY
54484
- }
54545
+ },
54546
+ additionalProperties: false
54485
54547
  });
54486
54548
  LIVE_FOLDER_ACCESS_INPUT_SCHEMA = Object.freeze({
54487
54549
  type: "object",
54488
54550
  properties: {
54489
54551
  folderType: { type: "string", enum: ["Report", "Dashboard", "Email", "Document", "all"] },
54490
54552
  limit: { type: "integer", minimum: 1, maximum: 500 },
54553
+ nameContains: { type: "string", minLength: 1 },
54491
54554
  ...LIVE_ENABLED_PROPERTY
54492
- }
54555
+ },
54556
+ additionalProperties: false
54493
54557
  });
54494
54558
  LIVE_EMAIL_TEMPLATE_USAGE_INPUT_SCHEMA = Object.freeze({
54495
54559
  type: "object",
54496
54560
  properties: {
54497
54561
  staleDays: { type: "integer", minimum: 1, maximum: 3650 },
54498
54562
  limit: { type: "integer", minimum: 1, maximum: 500 },
54563
+ nameContains: { type: "string", minLength: 1 },
54564
+ folderName: { type: "string", minLength: 1 },
54499
54565
  ...LIVE_ENABLED_PROPERTY
54500
- }
54566
+ },
54567
+ additionalProperties: false
54501
54568
  });
54502
54569
  LIVE_ORG_HEALTH_INPUT_SCHEMA = Object.freeze({
54503
54570
  type: "object",
@@ -54742,12 +54809,19 @@ var init_roster = __esm({
54742
54809
  type: "object",
54743
54810
  properties: {
54744
54811
  componentId: { type: "string", minLength: 1 },
54812
+ object: { type: "string", minLength: 1 },
54813
+ objectApiName: { type: "string", minLength: 1 },
54814
+ objectId: { type: "string", minLength: 1 },
54815
+ sharedWithRoleApiName: { type: "string", minLength: 1 },
54816
+ sharedToId: { type: "string", minLength: 1 },
54817
+ sharedTo: { type: "string", minLength: 1 },
54818
+ groupId: { type: "string", minLength: 1 },
54819
+ nameContains: { type: "string", minLength: 1 },
54745
54820
  limit: { type: "number", minimum: 1, maximum: 120 },
54746
54821
  offset: { type: "number", minimum: 0 },
54747
54822
  // CR-22 continuation cursor: opaque token from a prior page's nextCursor.
54748
54823
  cursor: { type: "string", minLength: 1 }
54749
- },
54750
- required: ["componentId"]
54824
+ }
54751
54825
  });
54752
54826
  WHO_CAN_RUN_INPUT_SCHEMA = Object.freeze({
54753
54827
  type: "object",
@@ -54769,8 +54843,7 @@ var init_roster = __esm({
54769
54843
  errorMessage: { type: "string", minLength: 1 },
54770
54844
  text: { type: "string", minLength: 1 },
54771
54845
  object: { type: "string", minLength: 1 }
54772
- },
54773
- required: ["errorText"]
54846
+ }
54774
54847
  });
54775
54848
  EXPLAIN_DEBUG_LOG_INPUT_SCHEMA = Object.freeze({
54776
54849
  type: "object",
@@ -54781,8 +54854,7 @@ var init_roster = __esm({
54781
54854
  text: { type: "string", minLength: 1 },
54782
54855
  content: { type: "string", minLength: 1 },
54783
54856
  object: { type: "string", minLength: 1 }
54784
- },
54785
- required: ["logText"]
54857
+ }
54786
54858
  });
54787
54859
  TRACE_DEBUG_LOG_INPUT_SCHEMA = Object.freeze({
54788
54860
  type: "object",
@@ -54849,6 +54921,7 @@ var init_roster = __esm({
54849
54921
  type: "object",
54850
54922
  properties: {
54851
54923
  componentId: { type: "string", minLength: 1 },
54924
+ objectApiName: { type: "string", minLength: 1 },
54852
54925
  accessLevel: { type: "string", enum: ["read", "edit", "delete", "create"] },
54853
54926
  userContext: {
54854
54927
  type: "object",
@@ -54866,7 +54939,7 @@ var init_roster = __esm({
54866
54939
  }
54867
54940
  }
54868
54941
  },
54869
- required: ["componentId", "userContext"]
54942
+ required: ["userContext"]
54870
54943
  });
54871
54944
  LAYOUT_FOR_USER_INPUT_SCHEMA = Object.freeze({
54872
54945
  type: "object",
@@ -54887,12 +54960,14 @@ var init_roster = __esm({
54887
54960
  type: "object",
54888
54961
  properties: {
54889
54962
  componentId: { type: "string", minLength: 1 },
54963
+ object: { type: "string", minLength: 1, description: "Object name (alias for object scoping)" },
54964
+ objectApiName: { type: "string", minLength: 1, description: "Object API name" },
54965
+ objectId: { type: "string", minLength: 1, description: "Object id" },
54890
54966
  limit: { type: "number", minimum: 1, maximum: 250 },
54891
54967
  offset: { type: "number", minimum: 0 },
54892
54968
  // CR-22 continuation cursor: opaque token from a prior page's nextCursor.
54893
54969
  cursor: { type: "string", minLength: 1 }
54894
- },
54895
- required: ["componentId"]
54970
+ }
54896
54971
  });
54897
54972
  USER_ABILITY_INPUT_SCHEMA = Object.freeze({
54898
54973
  type: "object",
@@ -54932,12 +55007,18 @@ var init_roster = __esm({
54932
55007
  type: "object",
54933
55008
  properties: {
54934
55009
  componentId: { type: "string", minLength: 1 },
55010
+ object: { type: "string", minLength: 1 },
55011
+ objectApiName: { type: "string", minLength: 1 },
55012
+ objectId: { type: "string", minLength: 1 },
55013
+ profile: { type: "string", minLength: 1 },
55014
+ profileApiName: { type: "string", minLength: 1 },
55015
+ profileId: { type: "string", minLength: 1 },
55016
+ profileName: { type: "string", minLength: 1 },
54935
55017
  limit: { type: "number", minimum: 1, maximum: 250 },
54936
55018
  offset: { type: "number", minimum: 0 },
54937
55019
  // CR-22 continuation cursor (object mode): opaque token from a prior page's nextCursor.
54938
55020
  cursor: { type: "string", minLength: 1 }
54939
- },
54940
- required: ["componentId"]
55021
+ }
54941
55022
  });
54942
55023
  PERMISSION_SET_CONSOLIDATION_INPUT_SCHEMA = Object.freeze({
54943
55024
  type: "object",
@@ -55064,6 +55145,7 @@ var init_roster = __esm({
55064
55145
  type: "object",
55065
55146
  properties: {
55066
55147
  eventId: { type: "string", minLength: 1 },
55148
+ eventApiName: { type: "string", minLength: 1, description: "Platform Event API name" },
55067
55149
  limit: { type: "integer", minimum: 1, maximum: 500 }
55068
55150
  }
55069
55151
  });
@@ -55091,9 +55173,9 @@ var init_roster = __esm({
55091
55173
  type: "object",
55092
55174
  properties: {
55093
55175
  fieldId: { type: "string", minLength: 1 },
55176
+ componentId: { type: "string", minLength: 1, description: "Field id (alias for fieldId)" },
55094
55177
  includeRecordValues: { type: "boolean" }
55095
- },
55096
- required: ["fieldId"]
55178
+ }
55097
55179
  });
55098
55180
  SAFE_TO_DELETE_FIELD_INPUT_SCHEMA = Object.freeze({
55099
55181
  type: "object",
@@ -55199,12 +55281,12 @@ var init_roster = __esm({
55199
55281
  type: "object",
55200
55282
  properties: {
55201
55283
  fieldId: { type: "string", minLength: 1 },
55284
+ componentId: { type: "string", minLength: 1, description: "Field id (alias for fieldId)" },
55202
55285
  permissionType: {
55203
55286
  type: "string",
55204
55287
  enum: ["read", "edit", "all"]
55205
55288
  }
55206
- },
55207
- required: ["fieldId"]
55289
+ }
55208
55290
  });
55209
55291
  OBJECT_ACCESS_AUDIT_INPUT_SCHEMA = Object.freeze({
55210
55292
  type: "object",
@@ -55558,6 +55640,7 @@ var init_roster = __esm({
55558
55640
  parentObjectFilter: { type: "string", minLength: 1 },
55559
55641
  excludeManagedPackage: { type: "boolean" },
55560
55642
  excludeStandardFields: { type: "boolean" },
55643
+ staticOnly: { type: "boolean", description: "Skip dynamic-usage pass" },
55561
55644
  limit: { type: "integer", minimum: 1, maximum: 500 },
55562
55645
  offset: { type: "integer", minimum: 0 },
55563
55646
  cursor: { type: "string", minLength: 1 },
@@ -55849,6 +55932,9 @@ var init_roster = __esm({
55849
55932
  GOVERNOR_LIMIT_RISKS_INPUT_SCHEMA = Object.freeze({
55850
55933
  type: "object",
55851
55934
  properties: {
55935
+ componentId: { type: "string", minLength: 1 },
55936
+ classApiName: { type: "string", minLength: 1 },
55937
+ apiName: { type: "string", minLength: 1 },
55852
55938
  limit: { type: "integer", minimum: 1, maximum: 500 },
55853
55939
  offset: { type: "integer", minimum: 0 },
55854
55940
  // CR-22 continuation cursor: opaque token from a prior page's nextCursor.
@@ -56086,7 +56172,9 @@ var init_roster = __esm({
56086
56172
  GENERATE_SHARING_SUMMARY_INPUT_SCHEMA = Object.freeze({
56087
56173
  type: "object",
56088
56174
  properties: {
56089
- objectFilter: { type: "string", minLength: 1 }
56175
+ objectFilter: { type: "string", minLength: 1 },
56176
+ objectApiName: { type: "string", minLength: 1 },
56177
+ componentId: { type: "string", minLength: 1 }
56090
56178
  }
56091
56179
  });
56092
56180
  GENERATE_COMPLIANCE_REPORT_INPUT_SCHEMA = Object.freeze({
@@ -56252,7 +56340,8 @@ var init_roster = __esm({
56252
56340
  CDC_SUBSCRIBERS_INPUT_SCHEMA = Object.freeze({
56253
56341
  type: "object",
56254
56342
  properties: {
56255
- sObjectFilter: { type: "string", minLength: 1 }
56343
+ sObjectFilter: { type: "string", minLength: 1 },
56344
+ objectApiName: { type: "string", minLength: 1 }
56256
56345
  }
56257
56346
  });
56258
56347
  EVENT_TOPOLOGY_INPUT_SCHEMA = Object.freeze({
@@ -56554,6 +56643,11 @@ var init_roster = __esm({
56554
56643
  properties: {
56555
56644
  classApiName: { type: "string", minLength: 1 },
56556
56645
  apexClass: { type: "string", minLength: 1 },
56646
+ className: { type: "string", minLength: 1 },
56647
+ apiName: { type: "string", minLength: 1 },
56648
+ componentId: { type: "string", minLength: 1 },
56649
+ classId: { type: "string", minLength: 1 },
56650
+ apexClassId: { type: "string", minLength: 1 },
56557
56651
  limit: { type: "integer", minimum: 1, maximum: 500 },
56558
56652
  offset: { type: "integer", minimum: 0 },
56559
56653
  cursor: { type: "string", minLength: 1 }
@@ -56574,9 +56668,11 @@ var init_roster = __esm({
56574
56668
  type: "object",
56575
56669
  properties: {
56576
56670
  object: { type: "string", minLength: 1 },
56671
+ componentId: { type: "string", minLength: 1, description: "CustomObject id (alias for object)" },
56672
+ objectApiName: { type: "string", minLength: 1, description: "Object API name" },
56673
+ objectId: { type: "string", minLength: 1, description: "Object id" },
56577
56674
  limit: { type: "integer", minimum: 1, maximum: 200 }
56578
56675
  },
56579
- required: ["object"],
56580
56676
  additionalProperties: false
56581
56677
  });
56582
56678
  AI_EXPOSURE_REPORT_INPUT_SCHEMA = Object.freeze({
@@ -56753,7 +56849,7 @@ var init_roster = __esm({
56753
56849
  },
56754
56850
  {
56755
56851
  name: "sfi.coverage_report",
56756
- description: "Report the vault's self-assessed metadata coverage: covered, partial, not-modeled, and \u2014 during a staged refresh \u2014 pending families (queued by the in-progress tiered build, with `stagedBuild` tier progress; pending types count as missing coverage, so absence answers about them must stay qualified). Use before absence-based or destructive answers. A fourth bucket, `retrievedNotParsed`, is emitted ONLY when a type dispatched by exact filename out of a SHARED retrieve container (today only `settings`) parsed nothing because that container came back WITHOUT its member file \u2014 its row reads `retrieved: 0` alongside `retrieveConfirmed: true`, which would otherwise read as a confirmed-empty org. The container WAS requested and DID return (its other members are the family ranked in `topUncoveredFamilies`) and the refresh DOES parse this type's filename, so nothing was read for it \u2014 whether the org lacks the feature or the file did not come back CANNOT be told from the vault, so treat it as NOT CHECKED rather than as an org fact. Those types leave `covered`, are named in `summary.retrievedNotParsedTypes`, and are folded into `summary.missingCoverage`. Re-running the retrieve cannot close that particular gap \u2014 the container already returned without the member; for `SessionSettings` it never can, because Salesforce emits no `Session.settings-meta.xml` at all (session settings are nested inside `Security.settings-meta.xml`), so closing it is a product change, not an operator action. The disclosure states the per-type reason. The `assignmentData` section covers runtime assignment data (User / PermissionSetAssignment / GroupMember): NOT in the vault by design (a runtime data object, not a retrieve gap) \u2014 it names the consent-gated live tools that answer those questions (sfi.live_permset_holders, sfi.live_user_permsets, sfi.live_group_members, sfi.live_zombie_accounts), whether live consent is currently granted, and whether/when a counts-only facts snapshot (`sfi refresh --with-data-shape`) was captured.",
56852
+ description: "Report the vault's self-assessed metadata coverage: covered, partial, not-modeled, and \u2014 during a staged refresh \u2014 pending families (queued by the in-progress tiered build, with `stagedBuild` tier progress; pending types count as missing coverage, so absence answers about them must stay qualified). A `capped` bucket lists families that WERE attempted and landed a real, non-zero, partial result under a known volume cap (the usage-ranked report/dashboard pull, or its node-persistence ceiling) \u2014 distinct from `pending` (\"not yet attempted at all\"); capped types also count as missing coverage. Use before absence-based or destructive answers. A further bucket, `retrievedNotParsed`, is emitted ONLY when a type dispatched by exact filename out of a SHARED retrieve container (today only `settings`) parsed nothing because that container came back WITHOUT its member file \u2014 its row reads `retrieved: 0` alongside `retrieveConfirmed: true`, which would otherwise read as a confirmed-empty org. The container WAS requested and DID return (its other members are the family ranked in `topUncoveredFamilies`) and the refresh DOES parse this type's filename, so nothing was read for it \u2014 whether the org lacks the feature or the file did not come back CANNOT be told from the vault, so treat it as NOT CHECKED rather than as an org fact. Those types leave `covered`, are named in `summary.retrievedNotParsedTypes`, and are folded into `summary.missingCoverage`. Re-running the retrieve cannot close that particular gap \u2014 the container already returned without the member; for `SessionSettings` it never can, because Salesforce emits no `Session.settings-meta.xml` at all (session settings are nested inside `Security.settings-meta.xml`), so closing it is a product change, not an operator action. The disclosure states the per-type reason. The `assignmentData` section covers runtime assignment data (User / PermissionSetAssignment / GroupMember): NOT in the vault by design (a runtime data object, not a retrieve gap) \u2014 it names the consent-gated live tools that answer those questions (sfi.live_permset_holders, sfi.live_user_permsets, sfi.live_group_members, sfi.live_zombie_accounts), whether live consent is currently granted, and whether/when a counts-only facts snapshot (`sfi refresh --with-data-shape`) was captured.",
56757
56853
  inputSchema: COVERAGE_REPORT_INPUT_SCHEMA
56758
56854
  },
56759
56855
  {
@@ -57340,7 +57436,7 @@ var init_roster = __esm({
57340
57436
  },
57341
57437
  {
57342
57438
  name: "sfi.unused_fields_deep",
57343
- description: "Optional `objectId` narrows the scan to one CustomObject \u2014 accepts the canonical id (`CustomObject:Account`) or a bare api name (`Account`); without it the scan is org-wide. Legacy `parentObjectFilter` (bare api name) is still accepted for back-compat. Scans every CustomField in scope with an eight-tier cross-walk before flagging it as unused: (1) zero incoming usage edges (excluding parentOf containment and grantedBy FLS grants \u2014 access is not usage), (2) no formula-text reference in another CustomField / ValidationRule / WorkflowRule, (3) no layout placement (layoutSections + relatedLists), (4) no SOQL-string match in an ApexClass / ApexTrigger source byproduct, (5) no apex-scanner unresolvedFieldReferences match, (6) no incoming LWC/Aura/VF `references` edge, (7) no ConditionalContext expression-text reference, (8) no `exposes` integration edge. Returns per-field checks, a per-tier invisibility warning list, a confidence tier (`high` for clean+custom+non-managed; `low` for standard/managed), and a recommendedAction. `limit` (default 100, max 500) caps the rows; because each carries the full eight-tier detail, a per-response ~36 KB byte budget trims the page further when it would exceed the global ~45 KB MCP response limit (`truncated` + a `note`), while `totalCount` / `byParentObject` / `byConfidence` keep the UNFILTERED counts; a truncated page returns a `nextCursor` to resume. Pass `format: 'csv'` (, default `'json'`) to get `csv` (this page's rows as CSV \u2014 the eight `checks.*` booleans flattened into `checks_*` columns, with the boundary + freshness disclosures embedded as `#`-prefixed comment lines) instead of `fields` (`fields` is `[]` on a csv page); `totalCount`/`byParentObject`/`byConfidence`/pagination fields are unchanged. Pass `format: 'cleanup'` (, the folded-in `field_cleanup_candidates` view) to additionally get a ranked `findings[]` roster (severity from each field's `confidence`, `summary` = `\"{id} \u2014 {recommendedAction}\"`) parallel to `fields`, plus a `disclosure` carrying the report/dashboard usage caveat (a report-only field reads as unused without `--with-reports`); findings + fields are trimmed TOGETHER to the ~36 KB budget with a `note`. Pass `format: 'proposal'` (Finding #35) to additionally get a `proposal` \u2014 a deploy-ready, LOCAL artifact: a populated `destructiveChanges.xml` bundle of THIS PAGE's `high`-confidence unused fields (+ an empty `package.xml`), each led by an evidence comment carrying per-field recommended actions, the boundary disclosures verbatim, the vault `sourceTreeHash`, and a REVIEW-before-deploy banner. `medium`/`low`-confidence and protected (standard/managed) fields are EXCLUDED from the delete set and their counts disclosed in the evidence; the bundle is capped at 200 members. sfi returns the file STRINGS only \u2014 it NEVER deploys, writes, or connects to the org; a human feeds the files to Gearset / Copado / `sf project deploy`. Validated against `docs/schemas/proposal.schema.json`. Honesty axis (verbatim): string-BUILT dynamic SOQL, LWC dynamic field access, Apex reflective access, and runtime metadata references remain invisible \u2014 inline static SOQL and constant-string Database.query field references ARE resolved (parsed-confidence Apex AST edges counted by tier 1, with the tier-4 soqlStrings text-match as backstop) \u2014 a 'high-confidence unused' flag means 'no static evidence of use', not 'definitely unused'. Fields the heuristic recognizer classifies pii/sensitive carry a machine-readable `piiClassification` field and a compliance escalation PREPENDED to `recommendedAction` (deletion may be irreversible / FERPA-GDPR-PCI-relevant, require data-retention sign-off). Heuristic \u2014 absence is NOT a clearance. Honesty axis (verbatim): string-BUILT dynamic SOQL, LWC dynamic field access, Apex reflective access, and runtime metadata references remain invisible \u2014 inline static SOQL and constant-string Database.query field references ARE resolved (parsed-confidence Apex AST edges counted by tier 1, with the tier-4 soqlStrings text-match as backstop) \u2014 a 'high-confidence unused' flag means 'no static evidence of use', not 'definitely unused'. Fields the heuristic recognizer classifies pii/sensitive carry a machine-readable `piiClassification` field and a compliance escalation PREPENDED to `recommendedAction` (deletion may be irreversible / FERPA-GDPR-PCI-relevant, require data-retention sign-off). Heuristic \u2014 absence is NOT a clearance. HYBRID: pass `liveEnabled: true` (or grant consent) to cross-check every `confidence: 'high'` field ON THE RETURNED PAGE against its live production population before trusting that tier \u2014 real data despite zero static references across all eight tiers may be written by dynamic Apex, an integration, or another blind spot the scanner cannot see. A `populatedCount > 0` DOWNGRADES that field's `confidence` from `high` to `medium` (the tier `medium` already means: no static evidence, but a blind spot could hide a reference) and attaches a `livePopulation` evidence block (`objectApiName`/`fieldApiName`/`totalCount`/`populatedCount`/`populationRate`/`liveQueriedAt`); a zero-population result leaves `high` standing but still attaches the evidence block, confirming the cross-check ran. Bounded to the page (never the full unfiltered scan) so live-query cost tracks `limit`, not `totalCount`, AND further capped to at most the first 3 high-confidence fields per page \u2014 a live wall-clock bound so a large consented org (hundreds of high-confidence unused fields) can never fire hundreds of serial live COUNTs and blow the MCP 60s client timeout; the remaining high-confidence fields keep their STATIC verdict and a `boundaries` line names the cap ('live population checked for the first N of M high-confidence fields on this page \u2026 raise the page or narrow the object to check the rest'). `byConfidence` / `totalCount` stay the STATIC pre-cross-check totals regardless (disclosed in `boundaries` when a downgrade occurs). The live plane is NEVER a hard dependency \u2014 offline stays fully functional; when it is off, unavailable, or a query errors (budget exhausted, org unreachable), the response fails soft to the disclosed static tiers with a `boundaries` line naming the missing evidence ('static-only verdict; live population not checked').",
57439
+ description: "Optional `objectId` narrows the scan to one CustomObject \u2014 accepts the canonical id (`CustomObject:Account`) or a bare api name (`Account`), matched case-insensitively against the vault and echoed back as `appliedScope`; without it the scan is org-wide. An object name that matches nothing in the vault is refused with `invalid-query` rather than silently returning `{fields: [], totalCount: 0}`. Legacy `parentObjectFilter` (bare api name) is still accepted for back-compat. Scans every CustomField in scope with an eight-tier cross-walk before flagging it as unused: (1) zero incoming usage edges (excluding parentOf containment and grantedBy FLS grants \u2014 access is not usage), (2) no formula-text reference in another CustomField / ValidationRule / WorkflowRule, (3) no layout placement (layoutSections + relatedLists), (4) no SOQL-string match in an ApexClass / ApexTrigger source byproduct, (5) no apex-scanner unresolvedFieldReferences match, (6) no incoming LWC/Aura/VF `references` edge, (7) no ConditionalContext expression-text reference, (8) no `exposes` integration edge. Returns per-field checks, a per-tier invisibility warning list, a confidence tier (`high` for clean+custom+non-managed; `low` for standard/managed), and a recommendedAction. `limit` (default 100, max 500) caps the rows; because each carries the full eight-tier detail, a per-response ~36 KB byte budget trims the page further when it would exceed the global ~45 KB MCP response limit (`truncated` + a `note`), while `totalCount` / `byParentObject` / `byConfidence` keep the UNFILTERED counts; a truncated page returns a `nextCursor` to resume. Pass `format: 'csv'` (, default `'json'`) to get `csv` (this page's rows as CSV \u2014 the eight `checks.*` booleans flattened into `checks_*` columns, with the boundary + freshness disclosures embedded as `#`-prefixed comment lines) instead of `fields` (`fields` is `[]` on a csv page); `totalCount`/`byParentObject`/`byConfidence`/pagination fields are unchanged. Pass `format: 'cleanup'` (, the folded-in `field_cleanup_candidates` view) to additionally get a ranked `findings[]` roster (severity from each field's `confidence`, `summary` = `\"{id} \u2014 {recommendedAction}\"`) parallel to `fields`, plus a `disclosure` carrying the report/dashboard usage caveat (a report-only field reads as unused without `--with-reports`); findings + fields are trimmed TOGETHER to the ~36 KB budget with a `note`. Pass `format: 'proposal'` (Finding #35) to additionally get a `proposal` \u2014 a deploy-ready, LOCAL artifact: a populated `destructiveChanges.xml` bundle of THIS PAGE's `high`-confidence unused fields (+ an empty `package.xml`), each led by an evidence comment carrying per-field recommended actions, the boundary disclosures verbatim, the vault `sourceTreeHash`, and a REVIEW-before-deploy banner. `medium`/`low`-confidence and protected (standard/managed) fields are EXCLUDED from the delete set and their counts disclosed in the evidence; the bundle is capped at 200 members. sfi returns the file STRINGS only \u2014 it NEVER deploys, writes, or connects to the org; a human feeds the files to Gearset / Copado / `sf project deploy`. Validated against `docs/schemas/proposal.schema.json`. Honesty axis (verbatim): string-BUILT dynamic SOQL, LWC dynamic field access, Apex reflective access, and runtime metadata references remain invisible \u2014 inline static SOQL and constant-string Database.query field references ARE resolved (parsed-confidence Apex AST edges counted by tier 1, with the tier-4 soqlStrings text-match as backstop) \u2014 a 'high-confidence unused' flag means 'no static evidence of use', not 'definitely unused'. Fields the heuristic recognizer classifies pii/sensitive carry a machine-readable `piiClassification` field and a compliance escalation PREPENDED to `recommendedAction` (deletion may be irreversible / FERPA-GDPR-PCI-relevant, require data-retention sign-off). Heuristic \u2014 absence is NOT a clearance. Honesty axis (verbatim): string-BUILT dynamic SOQL, LWC dynamic field access, Apex reflective access, and runtime metadata references remain invisible \u2014 inline static SOQL and constant-string Database.query field references ARE resolved (parsed-confidence Apex AST edges counted by tier 1, with the tier-4 soqlStrings text-match as backstop) \u2014 a 'high-confidence unused' flag means 'no static evidence of use', not 'definitely unused'. Fields the heuristic recognizer classifies pii/sensitive carry a machine-readable `piiClassification` field and a compliance escalation PREPENDED to `recommendedAction` (deletion may be irreversible / FERPA-GDPR-PCI-relevant, require data-retention sign-off). Heuristic \u2014 absence is NOT a clearance. HYBRID: pass `liveEnabled: true` (or grant consent) to cross-check every `confidence: 'high'` field ON THE RETURNED PAGE against its live production population before trusting that tier \u2014 real data despite zero static references across all eight tiers may be written by dynamic Apex, an integration, or another blind spot the scanner cannot see. A `populatedCount > 0` DOWNGRADES that field's `confidence` from `high` to `medium` (the tier `medium` already means: no static evidence, but a blind spot could hide a reference) and attaches a `livePopulation` evidence block (`objectApiName`/`fieldApiName`/`totalCount`/`populatedCount`/`populationRate`/`liveQueriedAt`); a zero-population result leaves `high` standing but still attaches the evidence block, confirming the cross-check ran. Bounded to the page (never the full unfiltered scan) so live-query cost tracks `limit`, not `totalCount`, AND further capped to at most the first 3 high-confidence fields per page \u2014 a live wall-clock bound so a large consented org (hundreds of high-confidence unused fields) can never fire hundreds of serial live COUNTs and blow the MCP 60s client timeout; the remaining high-confidence fields keep their STATIC verdict and a `boundaries` line names the cap ('live population checked for the first N of M high-confidence fields on this page \u2026 raise the page or narrow the object to check the rest'). `byConfidence` / `totalCount` stay the STATIC pre-cross-check totals regardless (disclosed in `boundaries` when a downgrade occurs). The live plane is NEVER a hard dependency \u2014 offline stays fully functional; when it is off, unavailable, or a query errors (budget exhausted, org unreachable), the response fails soft to the disclosed static tiers with a `boundaries` line naming the missing evidence ('static-only verdict; live population not checked').",
57344
57440
  inputSchema: UNUSED_FIELDS_DEEP_INPUT_SCHEMA
57345
57441
  },
57346
57442
  {
@@ -57499,7 +57595,7 @@ var init_roster = __esm({
57499
57595
  },
57500
57596
  {
57501
57597
  name: "sfi.method_reachability",
57502
- description: 'Given an ApexClass / ApexTrigger (`classApiName` / `componentId` / `apiName` \u2014 a bare name or an `ApexClass:` / `ApexTrigger:` id, interchangeable, resolved + echoed as `appliedScope`), walks upstream USAGE-edge BFS (capped at depth 3) and classifies the reachable upstream set against the entry-point taxonomy: `ApexTrigger` (any), `ApexClass` with `properties.isRestResource === true` (REST), `properties.hasAuraEnabledMethod === true` (Aura), `properties.hasInvocableMethod === true` (Flow / Process Builder), any of `properties.isQueueable` / `properties.isBatchable` / `properties.isSchedulable` (async dispatch), the TARGET ITSELF carrying `properties.isTest === true` (`test-class` \u2014 the test runner is its entry point, so a test class is never dead code), or a `references` in-edge from a VisualforcePage / VisualforceComponent / Aura bundle (`ui-controller` \u2014 the `controller=` binding). Two further kinds cover components registered OUTSIDE Apex, where zero in-edges is expected rather than damning: `framework-subclass` (the class extends a base class in another namespace, so that package or the platform instantiates it) and `callable-dispatch` (it declares the `Callable` dynamic-invocation interface). Neither is PROVEN: each says the class was BUILT for external invocation, not that any wiring is live, and the wiring lives somewhere no edge can reach it. So their hits are floored at `heuristic` confidence, and when they are the ONLY entry points found the disclosure says the class is "not dead" rather than "proven reachable". The walk follows EVERY edge type except `parentOf` (structural containment) and `grantedBy` (a permission grant \u2014 access is not usage), so a class reached only by `dispatchesAsync` or a static-type `references` is no longer reported dead; `walkedEdgeTypes` echoes the traversed set on every response and each hit carries the WEAKEST per-edge `confidence` along its path plus the `viaEdgeTypes` it travelled. Verdict cascade: `entry-point-reachable` (at least one entry point reaches the target), else `test-only-reachable` (at least one test class reaches it), else `likely-dead-code` (neither). Honesty axis (verbatim): dynamic dispatch (Type.forName) and reflective invocation are invisible \u2014 a class genuinely invoked at runtime via reflection will surface as `likely-dead-code`. Trigger framework base classes (TriggerHandler, fflib) may be partially invisible. Invalid prefix surfaces as `invalid-query`; unknown target surfaces as `component-not-found`. A residual `likely-dead-code` verdict appends a pointer to `sfi.find_dead_code`, which additionally greps production source for static-field and type-name usages this walk cannot see. Carries a `soundness` envelope DERIVED from the walk: `complete: false` with a `dynamic-apex` blind spot when any class ON A REACH PATH uses dynamic Apex (a reflective CALLER is what makes the verdict wrong, and the caller is not the root), and an `unwalked-edge-type` blind spot if the walk ever covers less than the full usage set.',
57598
+ description: 'Given an ApexClass / ApexTrigger (`classApiName` / `componentId` / `apiName` \u2014 a bare name or an `ApexClass:` / `ApexTrigger:` id, interchangeable, resolved + echoed as `appliedScope`), walks upstream USAGE-edge BFS (capped at depth 3) and classifies the reachable upstream set against the entry-point taxonomy: `ApexTrigger` (any), `ApexClass` with `properties.isRestResource === true` (REST), `properties.hasAuraEnabledMethod === true` (Aura), `properties.hasInvocableMethod === true` (Flow / Process Builder), any of `properties.isQueueable` / `properties.isBatchable` / `properties.isSchedulable` (async dispatch), the TARGET ITSELF carrying `properties.isTest === true` (`test-class` \u2014 the test runner is its entry point, so a test class is never dead code), or a `references` in-edge from a VisualforcePage / VisualforceComponent / Aura bundle (`ui-controller` \u2014 the `controller=` binding). Three further kinds cover components registered OUTSIDE Apex, where zero in-edges is expected rather than damning: `framework-subclass` (the class extends a base class in another namespace, so that package or the platform instantiates it), `namespaced-interface` (it implements an interface qualified by another namespace, e.g. an AuthProvider\'s `Auth.RegistrationHandler` \u2014 the platform names the implementing class by type), and `callable-dispatch` (it declares the `Callable` dynamic-invocation interface). None of the three is PROVEN: each says the class was BUILT for external invocation, not that any wiring is live, and the wiring lives somewhere no edge can reach it. So their hits are floored at `heuristic` confidence, and when they are the ONLY entry points found the disclosure says the class is "not dead" rather than "proven reachable". The walk follows EVERY edge type except `parentOf` (structural containment) and `grantedBy` (a permission grant \u2014 access is not usage), so a class reached only by `dispatchesAsync` or a static-type `references` is no longer reported dead; `walkedEdgeTypes` echoes the traversed set on every response and each hit carries the WEAKEST per-edge `confidence` along its path plus the `viaEdgeTypes` it travelled. Verdict cascade: `entry-point-reachable` (at least one entry point reaches the target), else `test-only-reachable` (at least one test class reaches it), else `likely-dead-code` (neither). Honesty axis (verbatim): dynamic dispatch (Type.forName) and reflective invocation are invisible \u2014 a class genuinely invoked at runtime via reflection will surface as `likely-dead-code`. Trigger framework base classes (TriggerHandler, fflib) may be partially invisible. Invalid prefix surfaces as `invalid-query`; unknown target surfaces as `component-not-found`. A residual `likely-dead-code` verdict appends a pointer to `sfi.find_dead_code`, which additionally greps production source for static-field and type-name usages this walk cannot see. Carries a `soundness` envelope DERIVED from the walk: `complete: false` with a `dynamic-apex` blind spot when any class ON A REACH PATH uses dynamic Apex (a reflective CALLER is what makes the verdict wrong, and the caller is not the root), and an `unwalked-edge-type` blind spot if the walk ever covers less than the full usage set.',
57503
57599
  inputSchema: METHOD_REACHABILITY_INPUT_SCHEMA
57504
57600
  },
57505
57601
  {
@@ -57591,7 +57687,7 @@ var init_roster = __esm({
57591
57687
  },
57592
57688
  {
57593
57689
  name: "sfi.find_dead_code",
57594
- description: "Cross-cutting dead-code surface \u2014 composes `method_reachability` verdict, entry-point taxonomy (REST / Aura / Invocable / Queueable / Batchable / Schedulable / triggers), and zero-usage detection into a single cascade verdict per candidate: `definitely_dead` (zero incoming USAGE edges and not own entry point), `likely_dead` (test-class-only reach), `uncertain` (entry-point reach or own entry point). A Flow is its OWN entry point unless its status is Obsolete/InvalidDraft (R2-12): an Active, Draft, or unknown-status Flow is `uncertain` (never definitely_dead/likely_dead), because Flow edges are all OUTGOING so a live flow has ~0 incoming edges by nature \u2014 flagging it dead would delete running automation; only Obsolete/InvalidDraft flows fall through to definitely_dead. : subflow invocation (flow-calls-flow) IS now modeled as an incoming `references` edge, so an Obsolete/InvalidDraft flow still invoked as a subflow by another flow reads `uncertain` (it has a live dependent) instead of definitely_dead \u2014 delete the referencing flow first. `parentOf` (structural) and `grantedBy` (Profile / PermissionSet access grants) edges are excluded \u2014 access is not usage, so a class nobody calls or a field nothing references is dead even when profiles grant access to it. Scope to ONE component with `componentId` (`ApexClass:`/`ApexTrigger:`/`Flow:`/`CustomField:` id) \u2014 or a bare class name via `classApiName`/`apiName` \u2014 to get just that node's verdict (`uncertain` included) + `appliedScope`; an unresolved id is `component-not-found`, a non-dead-code prefix is `invalid-query`, never a silent org-wide top-N. Default `types` covers ApexClass / ApexTrigger / Flow / CustomField. `includeUncertain` (default false) suppresses the noisy uncertain bucket. Test classes (properties.isTest === true) are NEVER flagged as dead \u2014 they ARE entry points for the test-runner. Returns `byVerdict` and `byType` tallies across the FULL classified set \u2014 INCLUDING the rows `includeUncertain: false` withholds from `candidates` \u2014 while `totalCount` counts only what is LISTED; the always-present `suppressed` block reports `includeUncertain`, how many `uncertain` rows were withheld (`uncertainWithheld`, a CHECKED 0 when none were), and states that relationship in words, so a short list is never mistakable for a clean scan. Truncated slice flips `truncated: true` and a truncated page returns a `nextCursor` to resume. Honesty axis (verbatim): dynamic dispatch, reflective invocation, framework wiring (TriggerHandler / fflib), and managed-package callers are invisible to the graph edges this tool walks. Carries a `soundness` envelope: `complete: false` with a `dynamic-apex` blind spot when a candidate class uses dynamic Apex \u2014 a class reached only reflectively will read as dead \u2014 so a `dead` verdict on a flagged class needs a human check before deletion. When any CALLER family (LWC, Aura, Flows, FlexiPages, Visualforce, \u2026) has incomplete coverage \u2014 errored retrieve, scoped refresh, or an in-progress staged build \u2014 the response adds a `coverageCaveat` naming the families: an un-retrieved caller would fake death. DYNAMIC REGISTRATION is separated from death: a class that extends a base class in ANOTHER namespace (managed-package and platform frameworks instantiate their own subclasses), that declares the `Callable` interface, or that implements `Queueable` / `Database.Batchable` / `Schedulable` is registered OUTSIDE the metadata a vault holds \u2014 in a string literal, a Custom Metadata record, package code, anonymous Apex, or a `CronTrigger` record written by Setup > Schedule Apex. `CronTrigger` is DATA, not metadata: it is never retrieved, mints no node and no edge, and NO refresh can close that gap. So such a class has ZERO incoming edges by construction, and an absent dispatch site is the EXPECTED reading for a live scheduled job rather than evidence against it. All of them are reported `uncertain`, never `definitely_dead` and never `likely_dead`. They are not proven live either, so confirm the registration \u2014 Setup > Scheduled Jobs / Apex Jobs for an async class \u2014 before deleting.",
57690
+ description: "Cross-cutting dead-code surface \u2014 composes `method_reachability` verdict, entry-point taxonomy (REST / Aura / Invocable / Queueable / Batchable / Schedulable / triggers), and zero-usage detection into a single cascade verdict per candidate: `definitely_dead` (zero incoming USAGE edges and not own entry point), `likely_dead` (test-class-only reach), `uncertain` (entry-point reach or own entry point). A Flow is its OWN entry point unless its status is Obsolete/InvalidDraft (R2-12): an Active, Draft, or unknown-status Flow is `uncertain` (never definitely_dead/likely_dead), because Flow edges are all OUTGOING so a live flow has ~0 incoming edges by nature \u2014 flagging it dead would delete running automation; only Obsolete/InvalidDraft flows fall through to definitely_dead. : subflow invocation (flow-calls-flow) IS now modeled as an incoming `references` edge, so an Obsolete/InvalidDraft flow still invoked as a subflow by another flow reads `uncertain` (it has a live dependent) instead of definitely_dead \u2014 delete the referencing flow first. `parentOf` (structural) and `grantedBy` (Profile / PermissionSet access grants) edges are excluded \u2014 access is not usage, so a class nobody calls or a field nothing references is dead even when profiles grant access to it. Scope to ONE component with `componentId` (`ApexClass:`/`ApexTrigger:`/`Flow:`/`CustomField:` id) \u2014 or a bare class name via `classApiName`/`apiName` \u2014 to get just that node's verdict (`uncertain` included) + `appliedScope`; an unresolved id is `component-not-found`, a non-dead-code prefix is `invalid-query`, never a silent org-wide top-N. Default `types` covers ApexClass / ApexTrigger / Flow / CustomField. `includeUncertain` (default false) suppresses the noisy uncertain bucket. Test classes (properties.isTest === true) are NEVER flagged as dead \u2014 they ARE entry points for the test-runner. Returns `byVerdict` and `byType` tallies across the FULL classified set \u2014 INCLUDING the rows `includeUncertain: false` withholds from `candidates` \u2014 while `totalCount` counts only what is LISTED; the always-present `suppressed` block reports `includeUncertain`, how many `uncertain` rows were withheld (`uncertainWithheld`, a CHECKED 0 when none were), and states that relationship in words, so a short list is never mistakable for a clean scan. Truncated slice flips `truncated: true` and a truncated page returns a `nextCursor` to resume. Honesty axis (verbatim): dynamic dispatch, reflective invocation, framework wiring (TriggerHandler / fflib), and managed-package callers are invisible to the graph edges this tool walks. Carries a `soundness` envelope: `complete: false` with a `dynamic-apex` blind spot when a candidate class uses dynamic Apex \u2014 a class reached only reflectively will read as dead \u2014 so a `dead` verdict on a flagged class needs a human check before deletion. When any CALLER family (LWC, Aura, Flows, FlexiPages, Visualforce, \u2026) has incomplete coverage \u2014 errored retrieve, scoped refresh, or an in-progress staged build \u2014 the response adds a `coverageCaveat` naming the families: an un-retrieved caller would fake death. DYNAMIC REGISTRATION is separated from death: a class that extends a base class in ANOTHER namespace (managed-package and platform frameworks instantiate their own subclasses), that implements an interface qualified by another namespace (e.g. an AuthProvider's `Auth.RegistrationHandler`), that declares the `Callable` interface, or that implements `Queueable` / `Database.Batchable` / `Schedulable` is registered OUTSIDE the metadata a vault holds \u2014 in a string literal, a Custom Metadata record, package code, anonymous Apex, or a `CronTrigger` record written by Setup > Schedule Apex. `CronTrigger` is DATA, not metadata: it is never retrieved, mints no node and no edge, and NO refresh can close that gap. So such a class has ZERO incoming edges by construction, and an absent dispatch site is the EXPECTED reading for a live scheduled job rather than evidence against it. All of them are reported `uncertain`, never `definitely_dead` and never `likely_dead`. They are not proven live either, so confirm the registration \u2014 Setup > Scheduled Jobs / Apex Jobs for an async class \u2014 before deleting.",
57595
57691
  inputSchema: FIND_DEAD_CODE_INPUT_SCHEMA
57596
57692
  },
57597
57693
  {
@@ -59162,7 +59258,7 @@ var init_order_of_execution = __esm({
59162
59258
 
59163
59259
  // ../mcp/dist/src/tools/what-happens-on-save.js
59164
59260
  import { z as z8 } from "zod";
59165
- var DISCLOSURE2, ALLOWED_EVENTS, WHAT_HAPPENS_ON_SAVE_ACCEPTED_KEYS, strictKeyErrorMap2, whatHappensOnSaveInputSchema, ENTITLEMENT_PROCESS_NOTE_CAP, PHASE_FILTER_CONCEPT_REASONING_OFF_NOTE, POST_ENFORCEMENT_DISCLOSURE_HEADROOM_BYTES, CONCEPT_REASONING_MIN_HEADROOM_BYTES, CONCEPT_REASONING_NO_HEADROOM_NOTE, computeFilteredPhaseOmission, workflowMatchesEvent2, flowMatchesEvent2, triggerMatchesEvent2, surfaceFirstCondition2, buildActions2, buildStep2, fetchParentedFirers2, fetchTriggersOnFirers2, buildAsyncSteps2, ASSIGNMENT_TYPES2, APPROVAL_TYPES2, VALIDATION_TYPES2, FLOW_TYPES2, TRIGGER_TYPES2, WORKFLOW_TYPES2, whatHappensOnSaveHandler;
59261
+ var DISCLOSURE2, ALLOWED_EVENTS, WHAT_HAPPENS_ON_SAVE_ACCEPTED_KEYS, strictKeyErrorMap2, whatHappensOnSaveInputSchema, ENTITLEMENT_PROCESS_NOTE_CAP, PHASE_FILTER_CONCEPT_REASONING_OFF_NOTE, POST_ENFORCEMENT_DISCLOSURE_HEADROOM_BYTES, CONCEPT_REASONING_MIN_HEADROOM_BYTES, CONCEPT_REASONING_NO_HEADROOM_NOTE, workflowMatchesEvent2, flowMatchesEvent2, triggerMatchesEvent2, surfaceFirstCondition2, buildActions2, buildStep2, fetchParentedFirers2, fetchTriggersOnFirers2, buildAsyncSteps2, ASSIGNMENT_TYPES2, APPROVAL_TYPES2, VALIDATION_TYPES2, FLOW_TYPES2, TRIGGER_TYPES2, WORKFLOW_TYPES2, whatHappensOnSaveHandler;
59166
59262
  var init_what_happens_on_save = __esm({
59167
59263
  "../mcp/dist/src/tools/what-happens-on-save.js"() {
59168
59264
  "use strict";
@@ -59246,12 +59342,6 @@ var init_what_happens_on_save = __esm({
59246
59342
  POST_ENFORCEMENT_DISCLOSURE_HEADROOM_BYTES = 2e3;
59247
59343
  CONCEPT_REASONING_MIN_HEADROOM_BYTES = 2500;
59248
59344
  CONCEPT_REASONING_NO_HEADROOM_NOTE = (headroom) => `Concept reasoning was NOT attached to this response: the order-of-execution steps are fitted FIRST and left ${headroom} byte(s) of the response budget, below what a reasoning block needs. The steps are the answer, so they keep the budget. No concept layer was checked here \u2014 that is "not checked", not "nothing found". Re-query one \`phase\` at a time for a narrower response with room for it.`;
59249
- computeFilteredPhaseOmission = (phaseCounts, phase, presentCount) => {
59250
- const declared = phaseCounts[phase];
59251
- if (presentCount >= declared)
59252
- return null;
59253
- return { phase, declared, present: presentCount };
59254
- };
59255
59345
  workflowMatchesEvent2 = (triggerType, event) => {
59256
59346
  if (typeof triggerType !== "string")
59257
59347
  return false;
@@ -59832,8 +59922,8 @@ var init_what_happens_on_save = __esm({
59832
59922
  data.disclosure = `${data.disclosure} ${crossPhaseShortfallNote(phasesOmitted)}`;
59833
59923
  }
59834
59924
  } else {
59835
- const omission = computeFilteredPhaseOmission(phaseCounts, input2.phase, data.soe.length);
59836
- if (omission !== null) {
59925
+ const [omission] = computePhasesOmitted(phaseCounts, data.soe, input2.phase);
59926
+ if (omission !== void 0) {
59837
59927
  data.phasesOmitted = [omission];
59838
59928
  data.truncated = true;
59839
59929
  data.disclosure = `${data.disclosure} ${filteredPhaseShortfallNote(omission)}`;
@@ -71243,10 +71333,14 @@ var init_coverage_report = __esm({
71243
71333
  partitionCoverage = (entries, unparsed) => {
71244
71334
  const isRetrievedNotParsed = (entry) => unparsed.has(entry.type) && entry.requested && entry.retrieved === 0 && entry.retrieveConfirmed === true && !entry.errored && !entry.neverModeled && entry.pending !== true;
71245
71335
  return {
71246
- covered: entries.filter((entry) => entry.requested && (entry.retrieved > 0 || entry.retrieveConfirmed === true) && !entry.errored && !entry.neverModeled && entry.pending !== true && !isRetrievedNotParsed(entry)),
71247
- partial: entries.filter((entry) => entry.requested && (entry.retrieved === 0 && entry.retrieveConfirmed !== true || entry.errored) && !entry.neverModeled && entry.pending !== true),
71336
+ covered: entries.filter((entry) => entry.requested && (entry.retrieved > 0 || entry.retrieveConfirmed === true) && !entry.errored && !entry.neverModeled && entry.pending !== true && entry.capped !== true && !isRetrievedNotParsed(entry)),
71337
+ partial: entries.filter((entry) => entry.requested && (entry.retrieved === 0 && entry.retrieveConfirmed !== true || entry.errored) && !entry.neverModeled && entry.pending !== true && entry.capped !== true),
71248
71338
  notModeled: entries.filter((entry) => entry.neverModeled),
71249
71339
  pending: entries.filter((entry) => entry.pending === true && !entry.neverModeled),
71340
+ // FIX-2: `pending` wins when a row somehow carries both (mirrors the
71341
+ // existing pending-over-retrieveConfirmed precedent) — "not yet attempted
71342
+ // at all" is the stronger, more conservative claim.
71343
+ capped: entries.filter((entry) => entry.capped === true && entry.pending !== true && !entry.neverModeled),
71250
71344
  retrievedNotParsed: entries.filter(isRetrievedNotParsed)
71251
71345
  };
71252
71346
  };
@@ -79131,11 +79225,11 @@ ${body}
79131
79225
  const comment = renderEvidenceComment("destructive", componentIds, evidence);
79132
79226
  const destructive = buildDestructiveChanges(componentIds, comment);
79133
79227
  const packageXml = buildEmptyPackageXml(apiVersion, comment);
79134
- const headline = options?.headline ?? `Proposes deletion of ${destructive.grouping.memberCount} component(s) via destructiveChanges.xml (verdict: ${evidence.verdict}).`;
79228
+ const headline2 = options?.headline ?? `Proposes deletion of ${destructive.grouping.memberCount} component(s) via destructiveChanges.xml (verdict: ${evidence.verdict}).`;
79135
79229
  return {
79136
79230
  schemaVersion: PROPOSAL_SCHEMA_VERSION,
79137
79231
  kind: "destructive",
79138
- headline,
79232
+ headline: headline2,
79139
79233
  files: [
79140
79234
  { path: "destructiveChanges.xml", contents: destructive.xml },
79141
79235
  { path: "package.xml", contents: packageXml }
@@ -79158,11 +79252,11 @@ ${body}
79158
79252
  `, `${XML_PROLOG}
79159
79253
  ${comment}
79160
79254
  `);
79161
- const headline = options?.headline ?? `Proposes a package.xml deploy of ${manifest.summary.memberCount} component(s) (verdict: ${evidence.verdict}).`;
79255
+ const headline2 = options?.headline ?? `Proposes a package.xml deploy of ${manifest.summary.memberCount} component(s) (verdict: ${evidence.verdict}).`;
79162
79256
  return {
79163
79257
  schemaVersion: PROPOSAL_SCHEMA_VERSION,
79164
79258
  kind: "deploy",
79165
- headline,
79259
+ headline: headline2,
79166
79260
  files: [{ path: "package.xml", contents: packageXml }],
79167
79261
  summary: {
79168
79262
  kind: "deploy",
@@ -83175,7 +83269,7 @@ var init_search_apex_source = __esm({
83175
83269
 
83176
83270
  // ../mcp/dist/src/tools/find-component-usages.js
83177
83271
  import { z as z74 } from "zod";
83178
- var NON_USAGE_EDGE_TYPES2, GREP_RELIANT_PREFIXES, GRAPH_REFERRER_SAMPLE, GREP_LIMIT_DEFAULT, FRONTEND_SUFFIXES, FRONTEND_DIR_RE, TYPE_EMPTY_NOTES, findComponentUsagesInputSchema, typeOf, apiNameOf, findComponentUsagesHandler;
83272
+ var NON_USAGE_EDGE_TYPES2, GREP_RELIANT_PREFIXES, GRAPH_REFERRER_SAMPLE, GREP_LIMIT_DEFAULT, FRONTEND_SUFFIXES, FRONTEND_DIR_RE, TYPE_EMPTY_NOTES, findComponentUsagesInputSchema, typeOf, apiNameOf, isSelfMatch, findComponentUsagesHandler;
83179
83273
  var init_find_component_usages = __esm({
83180
83274
  "../mcp/dist/src/tools/find-component-usages.js"() {
83181
83275
  "use strict";
@@ -83217,6 +83311,14 @@ var init_find_component_usages = __esm({
83217
83311
  const i = id.indexOf(":");
83218
83312
  return i > 0 ? id.slice(i + 1) : id;
83219
83313
  };
83314
+ isSelfMatch = (matchPath, ownSourcePath) => {
83315
+ if (matchPath === ownSourcePath)
83316
+ return true;
83317
+ if (!FRONTEND_DIR_RE.test(ownSourcePath))
83318
+ return false;
83319
+ const ownDir = ownSourcePath.slice(0, ownSourcePath.lastIndexOf("/") + 1);
83320
+ return ownDir.length > 0 && matchPath.startsWith(ownDir);
83321
+ };
83220
83322
  findComponentUsagesHandler = async (ctx, input2) => {
83221
83323
  const componentId = input2.componentId;
83222
83324
  if (!componentId.includes(":")) {
@@ -83292,6 +83394,12 @@ var init_find_component_usages = __esm({
83292
83394
  }
83293
83395
  }
83294
83396
  }
83397
+ let selfMatchesExcluded = 0;
83398
+ if (node !== null) {
83399
+ const before = grepMatches.length;
83400
+ grepMatches = grepMatches.filter((m) => !isSelfMatch(m.path, node.sourcePath));
83401
+ selfMatchesExcluded = before - grepMatches.length;
83402
+ }
83295
83403
  const hasStaticEvidence = graphReferrerCount > 0 || grepMatches.length > 0;
83296
83404
  const surfaceGrants = targetType === "CustomPermission" || usageEdges.length === 0 && grantEdges.length > 0;
83297
83405
  let grantedBySection;
@@ -83314,6 +83422,9 @@ var init_find_component_usages = __esm({
83314
83422
  `\`graphReferrerCount\` and each group's \`count\` are EDGE counts, not component counts: one referrer contributes one edge per relationship it has to the target (a Flow that reads, writes AND triggers on an object counts 3). ${graphReferrerCount} edge(s) here come from ${distinctReferrerCount} distinct component(s) \u2014 quote \`distinctReferrerCount\` / \`distinctReferrers\` to a human, and note the 25-row \`sample\` cap is on ROWS, so a group with multi-edge referrers shows fewer than 25 distinct components.`,
83315
83423
  "The grep supplement is a literal text match on the api name across Apex AND frontend bundle source \u2014 LWC, Aura, Visualforce ($Label / $Resource / @salesforce module references) \u2014 (`text-match` tier): it can OVER-match (a substring / a different component sharing the name) and UNDER-match (dynamically built references). Treat it as leads, not proof."
83316
83424
  ];
83425
+ if (selfMatchesExcluded > 0) {
83426
+ boundaries.push(`${selfMatchesExcluded} grep match(es) were this component's OWN definition (its declaring file${FRONTEND_DIR_RE.test(node?.sourcePath ?? "") ? " / bundle directory" : ""}) and were EXCLUDED before \`grepMatchCount\` \u2014 a component's own declaration matching its own name is not evidence anything ELSE uses it. ${grepMatches.length === 0 ? "After exclusion, grep found NO other reference." : `After exclusion, ${grepMatches.length} genuine external match(es) remain.`}`);
83427
+ }
83317
83428
  const coverageCaveat = !hasStaticEvidence ? buildEmptyTraversalCoverageCaveat(ctx, GRAPH_TRAVERSAL_REQUIRED_COVERAGE) : void 0;
83318
83429
  if (!hasStaticEvidence) {
83319
83430
  boundaries.push("No static evidence of usage found in this vault \u2014 this is NOT proof that nothing uses it. Dynamic SOQL / reflective access, references from un-modeled families (reports, dashboards, list-view filters, custom-metadata-driven config), and managed packages are invisible to static analysis.");
@@ -83343,7 +83454,8 @@ var init_find_component_usages = __esm({
83343
83454
  query: grepRan ? targetApiName : null,
83344
83455
  matchCount: grepMatches.length,
83345
83456
  matches: grepMatches,
83346
- truncated: grepTruncated
83457
+ truncated: grepTruncated,
83458
+ selfMatchesExcluded
83347
83459
  },
83348
83460
  summary: {
83349
83461
  graphReferrerCount,
@@ -83487,6 +83599,7 @@ var init_find_dead_code = __esm({
83487
83599
  type = 'ApexClass' AND (
83488
83600
  COALESCE(json_extract_string(properties_json, '$.superclass') LIKE '%.%', FALSE)
83489
83601
  OR COALESCE(json_extract_string(properties_json, '$.implements') LIKE '%"${CALLABLE_INTERFACE}"%', FALSE)
83602
+ OR COALESCE(json_extract_string(properties_json, '$.implements') LIKE '%.%', FALSE)
83490
83603
  ${ASYNC_DISPATCH_PROPERTY_KEYS.map((k) => ` OR COALESCE(json_extract_string(properties_json, '$.${k}') = 'true', FALSE)`).join("\n")}
83491
83604
  ),
83492
83605
  FALSE)`;
@@ -83517,14 +83630,16 @@ ${ASYNC_DISPATCH_PROPERTY_KEYS.map((k) => ` OR COALESCE(json_ext
83517
83630
  OR COALESCE(json_extract_string(properties_json, '$.hasInvocableMethod') = 'true', FALSE)
83518
83631
  )),
83519
83632
  FALSE) AS is_own_entry_point,
83520
- -- UNPROVEN dynamic registration. Same two predicates as
83521
- -- isFrameworkSubclass / isCallableDispatch in apex-reachability.ts,
83522
- -- expressed in SQL because this tool's cascade is a single CTE (a
83523
- -- measured ~7x speedup that is not worth losing). A behavioural
83524
- -- drift test runs both over the same fixture and asserts they agree.
83525
- -- A dotted superclass means the base class lives in ANOTHER
83526
- -- namespace, so its owner instantiates the subclass and no local
83527
- -- callsApex edge can exist.
83633
+ -- UNPROVEN dynamic registration. Same predicates as
83634
+ -- isFrameworkSubclass / isNamespacedInterfaceImplementation /
83635
+ -- isCallableDispatch in apex-reachability.ts, expressed in SQL
83636
+ -- because this tool's cascade is a single CTE (a measured ~7x
83637
+ -- speedup that is not worth losing). A behavioural drift test
83638
+ -- runs both over the same fixture and asserts they agree.
83639
+ -- A dotted superclass OR a dotted implemented-interface name
83640
+ -- means the base type lives in ANOTHER namespace, so its owner
83641
+ -- instantiates/dispatches the class and no local callsApex edge
83642
+ -- can exist.
83528
83643
  ${UNPROVEN_REGISTRATION_SQL} AS is_unproven_registration,
83529
83644
  -- Async-dispatch entry point: Queueable / Batchable / Schedulable.
83530
83645
  -- Dispatched by user Apex (enqueueJob / executeBatch / schedule), so
@@ -83652,7 +83767,7 @@ ${NON_USAGE_EDGE_EXCLUSION_SQL}
83652
83767
  };
83653
83768
  escapeForRegex2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83654
83769
  sourceFileApiName = (vaultRelativePath) => {
83655
- const base = vaultRelativePath.split("/").pop() ?? vaultRelativePath;
83770
+ const base = splitPathSegments(vaultRelativePath).at(-1) ?? vaultRelativePath;
83656
83771
  return base.replace(/\.(cls|trigger)$/i, "");
83657
83772
  };
83658
83773
  fetchTestClassApiNames = async (store) => {
@@ -83742,7 +83857,7 @@ ${NON_USAGE_EDGE_EXCLUSION_SQL}
83742
83857
  reasoning = ownEntryPoint ? "component is its own entry point (REST/Aura/Invocable or trigger); platform invokes it" : "reached by an entry-point class (REST resource / AuraEnabled / InvocableMethod / async-dispatch)";
83743
83858
  } else if (row.is_unproven_registration) {
83744
83859
  verdict = "uncertain";
83745
- reasoning = "class is BUILT for dynamic registration \u2014 it extends a base class from another namespace (a managed package or platform framework instantiates its own subclasses) or declares the Callable dynamic-invocation interface. The registration itself lives in a string literal, a Custom Metadata record, or managed-package code and mints no edge, so zero incoming edges is EXPECTED here and is not evidence of death. Not proven live either \u2014 confirm the registration in the org before deleting.";
83860
+ reasoning = "class is BUILT for dynamic registration \u2014 it extends a base class from another namespace (a managed package or platform framework instantiates its own subclasses), implements an interface qualified by another namespace (e.g. an AuthProvider's Auth.RegistrationHandler), or declares the Callable dynamic-invocation interface. The registration itself lives in a string literal, a Custom Metadata record, or managed-package code and mints no edge, so zero incoming edges is EXPECTED here and is not evidence of death. Not proven live either \u2014 confirm the registration in the org before deleting.";
83746
83861
  } else if (incomingCount === 0) {
83747
83862
  verdict = "definitely_dead";
83748
83863
  reasoning = "no incoming non-parentOf edges; no callers, no triggers, no listeners visible to the graph";
@@ -84567,6 +84682,7 @@ var init_find_hardcoded_values_anywhere = __esm({
84567
84682
  "../mcp/dist/src/tools/find-hardcoded-values-anywhere.js"() {
84568
84683
  "use strict";
84569
84684
  init_src();
84685
+ init_src4();
84570
84686
  init_src3();
84571
84687
  init_input_aliases();
84572
84688
  init_page_cursor();
@@ -84576,7 +84692,7 @@ var init_find_hardcoded_values_anywhere = __esm({
84576
84692
  MAX_LIMIT12 = 500;
84577
84693
  DEFAULT_LIMIT12 = 100;
84578
84694
  FIND_HARDCODED_ANYWHERE_TOOL = "sfi.find_hardcoded_values_anywhere";
84579
- SALESFORCE_ID_REGEX = /\b0[0-9a-zA-Z]{14}([0-9a-zA-Z]{3})?\b/g;
84695
+ SALESFORCE_ID_REGEX = /\b[0-9a-zA-Z]{15}([0-9a-zA-Z]{3})?\b/g;
84580
84696
  EMAIL_REGEX = /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g;
84581
84697
  DATE_REGEX = /\b(\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?|\d{1,2}\/\d{1,2}\/\d{4}|\d{1,2}\.\d{1,2}\.\d{4}|\d{4}\/\d{2}\/\d{2})\b/g;
84582
84698
  NUMERIC_REGEX = /\b\d+(\.\d+)?\b/g;
@@ -84634,7 +84750,7 @@ var init_find_hardcoded_values_anywhere = __esm({
84634
84750
  return hits;
84635
84751
  };
84636
84752
  NUMERIC_FP_DISCLOSURE = "the numeric category has very high false-positive rate \u2014 loop counters, array indices, and arithmetic constants all match. The category is suppressed from default searches; opt in explicitly only when looking for specific hardcoded numbers.";
84637
- ID_FP_DISCLOSURE = "the ID-shape search matches Salesforce-id-shaped strings filtered to a known-key-prefix allowlist (~40 prefixes). Arbitrary 15-character alphanumeric strings outside the allowlist are not returned. Strings shaped like an ID that aren't actually IDs (e.g., session keys, hashes) may still match if they happen to start with a known key prefix.";
84753
+ ID_FP_DISCLOSURE = `the ID-shape search matches 15- or 18-character alphanumeric strings filtered to a known-key-prefix allowlist (${KNOWN_KEY_PREFIXES.size} prefixes), the same allowlist the Apex-side hardcoded-id recognizer applies. Arbitrary alphanumeric strings outside the allowlist are not returned. Strings shaped like an ID that aren't actually IDs (e.g., session keys, hashes) may still match if they happen to start with a known key prefix. An id whose object's key prefix is not in that list is NOT reported \u2014 the list covers the standard objects and the custom-object range, not every prefix Salesforce issues.`;
84638
84754
  TEST_CLASS_REFUSAL_DISCLOSURE = "matches in `@isTest`-annotated classes may be intentional test fixtures rather than production hardcoded values; verify the context before treating as a bug.";
84639
84755
  APEX_SOURCE_EMAIL_SCAN_DISCLOSURE = "For the email category, Apex source files were also scanned directly (CR-07 fallback) to catch addresses in classes updated after the last vault refresh. Source-scan matches are comment-stripped but not string-boundary-isolated \u2014 email-shaped tokens in @param JavaDoc or string-concatenation expressions may surface as false positives. Confidence: heuristic.";
84640
84756
  findHardcodedValuesAnywhereInputBaseSchema = z79.object({
@@ -84716,6 +84832,11 @@ var init_find_hardcoded_values_anywhere = __esm({
84716
84832
  const regex = new RegExp(regexForCategory(category).source, "g");
84717
84833
  let m;
84718
84834
  while ((m = regex.exec(text)) !== null) {
84835
+ if (category === "id" && !isKnownSalesforceIdLiteral(m[0])) {
84836
+ if (m.index === regex.lastIndex)
84837
+ regex.lastIndex += 1;
84838
+ continue;
84839
+ }
84719
84840
  hits.push({
84720
84841
  value: m[0],
84721
84842
  index: m.index,
@@ -92068,10 +92189,14 @@ ${rendered.value.body}
92068
92189
  const colonIdx = parentId.indexOf(":");
92069
92190
  return colonIdx >= 0 ? parentId.substring(colonIdx + 1) : null;
92070
92191
  };
92071
- toRelativePath = (vaultRoot, fullPath) => {
92072
- const prefix = vaultRoot.endsWith("/") ? vaultRoot : `${vaultRoot}/`;
92073
- return fullPath.startsWith(prefix) ? fullPath.substring(prefix.length) : fullPath;
92074
- };
92192
+ toRelativePath = (vaultRoot, fullPath) => (
92193
+ // `fullPath` is `join()`-built, so on Windows it is backslash-separated while
92194
+ // the hand-rolled prefix ended in `/` — the strip never fired and the wire
92195
+ // field carried an ABSOLUTE native path where the contract above promises a
92196
+ // vault-relative one. `toRelativePosix` also renders the separator, so this
92197
+ // field and `component-doc-fallback`'s no longer disagree on Windows.
92198
+ toRelativePosix(vaultRoot, fullPath)
92199
+ );
92075
92200
  splitFrontmatter2 = (raw) => {
92076
92201
  if (!raw.startsWith("---\n"))
92077
92202
  return null;
@@ -111166,7 +111291,7 @@ var init_unused_components = __esm({
111166
111291
 
111167
111292
  // ../mcp/dist/src/tools/unused-fields-deep.js
111168
111293
  import { z as z152 } from "zod";
111169
- var UNUSED_FIELDS_DEEP_TOOL, PROPOSAL_REPORT_EXCLUSION_EVIDENCE_CAP, UNUSED_FIELDS_DEEP_REQUIRED_COVERAGE, completenessForUnusedFieldsDeep, UNUSED_FIELDS_DEEP_MAX_LIMIT, UNUSED_FIELDS_DEEP_DEFAULT_LIMIT, UNUSED_FIELDS_DEEP_BYTE_BUDGET, PAGE_CAP5, pageSize5, LIVE_CROSS_CHECK_CAP, FRONTEND_REFERENCE_TYPES, INVISIBILITY_WARNINGS, BOUNDARIES13, unusedFieldsDeepInputSchema, propertyString4, propertyStringArray2, workflowConditionsText, layoutSectionFields, relatedListFields, buildCorpora, buildLoweredCorpora, checkNoIncomingEdgesFromEdges, checkNoFormulaTextReferences, checkNoLayoutReferences, checkNoSoqlStringReferences, checkNoUnresolvedApexReferences, checkNoLwcAuraVfReferencesFromEdges, checkNoConditionalContextReferences, checkNoIntegrationExposureFromEdges, parseParentApiName2, fieldTypeOf2, isCustomField4, namespacePrefixOf4, computeConfidence, PII_DELETION_ESCALATION, recommendedActionFor, compareById5, UNUSED_FIELDS_DEEP_CSV_HEADER, csvRowForUnusedField, resolveParentObjectFilter, CLEANUP_SYNTHESIS_DISCLOSURE, CLEANUP_SEVERITY_RANK, toCleanupFinding, sortCleanupFindings, PROPOSAL_MAX_COMPONENTS, buildUnusedFieldsProposal, unusedFieldsDeepHandler;
111294
+ var UNUSED_FIELDS_DEEP_TOOL, PROPOSAL_REPORT_EXCLUSION_EVIDENCE_CAP, UNUSED_FIELDS_DEEP_REQUIRED_COVERAGE, completenessForUnusedFieldsDeep, UNUSED_FIELDS_DEEP_MAX_LIMIT, UNUSED_FIELDS_DEEP_DEFAULT_LIMIT, UNUSED_FIELDS_DEEP_BYTE_BUDGET, PAGE_CAP5, pageSize5, LIVE_CROSS_CHECK_CAP, FRONTEND_REFERENCE_TYPES, INVISIBILITY_WARNINGS, BOUNDARIES13, unusedFieldsDeepInputSchema, propertyString4, propertyStringArray2, workflowConditionsText, layoutSectionFields, relatedListFields, buildCorpora, buildLoweredCorpora, checkNoIncomingEdgesFromEdges, checkNoFormulaTextReferences, checkNoLayoutReferences, checkNoSoqlStringReferences, checkNoUnresolvedApexReferences, checkNoLwcAuraVfReferencesFromEdges, checkNoConditionalContextReferences, checkNoIntegrationExposureFromEdges, parseParentApiName2, fieldTypeOf2, isCustomField4, namespacePrefixOf4, computeConfidence, PII_DELETION_ESCALATION, recommendedActionFor, compareById5, UNUSED_FIELDS_DEEP_CSV_HEADER, csvRowForUnusedField, CLEANUP_SYNTHESIS_DISCLOSURE, CLEANUP_SEVERITY_RANK, toCleanupFinding, sortCleanupFindings, PROPOSAL_MAX_COMPONENTS, buildUnusedFieldsProposal, unusedFieldsDeepHandler;
111170
111295
  var init_unused_fields_deep = __esm({
111171
111296
  "../mcp/dist/src/tools/unused-fields-deep.js"() {
111172
111297
  "use strict";
@@ -111175,8 +111300,8 @@ var init_unused_fields_deep = __esm({
111175
111300
  init_src4();
111176
111301
  init_src8();
111177
111302
  init_src3();
111178
- init_coerce_id();
111179
111303
  init_coverage_trust();
111304
+ init_input_aliases();
111180
111305
  init_live_plane();
111181
111306
  init_live_population_check();
111182
111307
  init_page_cursor();
@@ -111235,8 +111360,11 @@ var init_unused_fields_deep = __esm({
111235
111360
  /**
111236
111361
  * Optional filter: restrict the scan to fields on a single object.
111237
111362
  * Accepts either the canonical CustomObject id (`CustomObject:Account`) or
111238
- * a bare object api name (`Account`). When supplied the scan returns only
111239
- * fields whose parent object matches; without it the scan is org-wide.
111363
+ * a bare object api name (`Account`), matched case-insensitively against
111364
+ * the vault. When supplied the scan returns only fields whose parent
111365
+ * object matches (echoed back as `appliedScope`); without it the scan is
111366
+ * org-wide. An object name that matches nothing in the vault is refused
111367
+ * with `invalid-query` rather than silently returning an empty result.
111240
111368
  * `objectId` is the primary parameter; `objectApiName` and the legacy
111241
111369
  * `parentObjectFilter` (bare-name) are accepted as synonyms.
111242
111370
  */
@@ -111616,18 +111744,6 @@ var init_unused_fields_deep = __esm({
111616
111744
  entry.checks.noConditionalContextReferences,
111617
111745
  entry.checks.noIntegrationExposure
111618
111746
  ];
111619
- resolveParentObjectFilter = (input2) => {
111620
- const PREFIX3 = "CustomObject:";
111621
- if (input2.objectId !== void 0) {
111622
- const coerced = coercePrefix(input2.objectId, [PREFIX3]);
111623
- if (coerced.startsWith(PREFIX3))
111624
- return coerced.slice(PREFIX3.length);
111625
- return input2.objectId;
111626
- }
111627
- if (input2.objectApiName !== void 0)
111628
- return input2.objectApiName;
111629
- return input2.parentObjectFilter;
111630
- };
111631
111747
  CLEANUP_SYNTHESIS_DISCLOSURE = "Synthesis reports rank offline vault evidence only unless a live tool was composed. Re-run /sfi-refresh and check sfi.coverage_report before acting on absence-based findings.";
111632
111748
  CLEANUP_SEVERITY_RANK = {
111633
111749
  critical: 4,
@@ -111693,7 +111809,12 @@ var init_unused_fields_deep = __esm({
111693
111809
  const limit = input2.limit ?? UNUSED_FIELDS_DEEP_DEFAULT_LIMIT;
111694
111810
  const excludeManaged = input2.excludeManagedPackage ?? true;
111695
111811
  const excludeStandard = input2.excludeStandardFields ?? true;
111696
- const parentObjectFilter = resolveParentObjectFilter(input2);
111812
+ const legacyParentObjectFilter = input2.objectId === void 0 && input2.objectApiName === void 0 ? input2.parentObjectFilter : void 0;
111813
+ const scopeResult = await resolveExistingObjectScope(ctx.graph, { ...input2, objectApiName: input2.objectApiName ?? legacyParentObjectFilter }, { unhandledPrefix: "refuse" });
111814
+ if (!scopeResult.ok)
111815
+ return err(scopeResult.error);
111816
+ const scope = scopeResult.value;
111817
+ const parentObjectFilter = scope?.object;
111697
111818
  const corporaResult = await buildCorpora(ctx);
111698
111819
  if (!corporaResult.ok) {
111699
111820
  return err({
@@ -111884,6 +112005,11 @@ var init_unused_fields_deep = __esm({
111884
112005
  refreshedAt: ctx.manifest.refreshedAt
111885
112006
  };
111886
112007
  const dataWithoutCsv = {
112008
+ // appliedScope FIRST + only when scoped, matching the sibling optionally-
112009
+ // object-scoped tools (flow_bulkification_audit): a bare call omits the
112010
+ // whole block (the pre-scope shape); a scoped one can never be misread as
112011
+ // org-wide.
112012
+ ...scope !== null ? { appliedScope: { componentId: scope.componentId, object: scope.object } } : {},
111887
112013
  fields: input2.format === "csv" ? [] : liveFields,
111888
112014
  totalCount: sorted.length,
111889
112015
  byParentObject,
@@ -111905,6 +112031,7 @@ var init_unused_fields_deep = __esm({
111905
112031
  const buildCleanup = (n2) => {
111906
112032
  const slice = liveFields.slice(0, n2);
111907
112033
  return {
112034
+ ...scope !== null ? { appliedScope: { componentId: scope.componentId, object: scope.object } } : {},
111908
112035
  fields: slice,
111909
112036
  findings: sortCleanupFindings(slice.map(toCleanupFinding)),
111910
112037
  totalCount: sorted.length,
@@ -118919,7 +119046,7 @@ var init_group_membership = __esm({
118919
119046
 
118920
119047
  // ../mcp/dist/src/tools/who-can-access-object.js
118921
119048
  import { z as z170 } from "zod";
118922
- var CUSTOM_OBJECT_PREFIX11, GROUP_MEMBER_PREFIX, DEFAULT_LIMIT26, MAX_LIMIT26, GRANTER_KIND_SAMPLE_LIMIT, PUBLIC_OWD_READ, whoCanAccessObjectInputBaseSchema, whoCanAccessObjectInputSchema, BLIND_SPOTS, flag3, stringProp6, sharingRuleObjectApiName2, ruleAccessToOp, whoCanAccessObjectHandler;
119049
+ var CUSTOM_OBJECT_PREFIX11, GROUP_MEMBER_PREFIX, DEFAULT_LIMIT26, MAX_LIMIT26, GRANTER_KIND_SAMPLE_LIMIT, PUBLIC_OWD_READ, whoCanAccessObjectInputBaseSchema, whoCanAccessObjectInputSchema, BLIND_SPOTS, flag3, stringProp6, utf8Bytes3, sharingRuleObjectApiName2, ruleAccessToOp, whoCanAccessObjectHandler;
118923
119050
  var init_who_can_access_object = __esm({
118924
119051
  "../mcp/dist/src/tools/who-can-access-object.js"() {
118925
119052
  "use strict";
@@ -118929,6 +119056,7 @@ var init_who_can_access_object = __esm({
118929
119056
  init_facts_block();
118930
119057
  init_group_membership();
118931
119058
  init_input_aliases();
119059
+ init_response_budget();
118932
119060
  init_role_hierarchy();
118933
119061
  init_scan_cap();
118934
119062
  init_vault_assignment_disclosure();
@@ -118963,6 +119091,7 @@ var init_who_can_access_object = __esm({
118963
119091
  ]);
118964
119092
  flag3 = (p, k) => p[k] === true;
118965
119093
  stringProp6 = (p, k) => typeof p[k] === "string" ? p[k] : "";
119094
+ utf8Bytes3 = (value) => Buffer.byteLength(JSON.stringify(value) ?? "null", "utf8");
118966
119095
  sharingRuleObjectApiName2 = (rule) => {
118967
119096
  const parentId = rule.parentId;
118968
119097
  if (typeof parentId === "string" && parentId.startsWith("CustomObject:")) {
@@ -119118,6 +119247,9 @@ var init_who_can_access_object = __esm({
119118
119247
  via,
119119
119248
  access: ruleAccessToOp(accessLevel),
119120
119249
  scope: "shared-records",
119250
+ // Disambiguates `granterId|via` when a second rule of the SAME type
119251
+ // shares with the SAME principal — see AccessGranter.sourceRuleId.
119252
+ sourceRuleId: rule.id,
119121
119253
  detail: ruleType === "criteria" ? `criteria sharing rule ${rule.id} (${accessLevel}) shares records matching \`${predicate || "(predicate not extracted)"}\`` : ruleType === "guest" ? `guest sharing rule ${rule.id} (${accessLevel}) shares records matching \`${predicate || "(predicate not extracted)"}\` with the${siteName ? ` '${siteName}'` : ""} Experience Cloud site guest user (record-level + requester context required)` : ruleType === "territory" || ruleType === "territoryGroup" ? `${ruleType} sharing rule ${rule.id} (${accessLevel}) shares records matching \`${predicate || "(predicate not extracted)"}\` by territory assignment (record-level + territory context required)` : `owner sharing rule ${rule.id} (${accessLevel}) shares this user/group's owned records`
119122
119254
  });
119123
119255
  if (edge.toId.startsWith(GROUP_MEMBER_PREFIX)) {
@@ -119139,6 +119271,7 @@ var init_who_can_access_object = __esm({
119139
119271
  via,
119140
119272
  access: ruleAccessToOp(accessLevel),
119141
119273
  scope: "shared-records",
119274
+ sourceRuleId: rule.id,
119142
119275
  detail: `${ruleType || "owner"} sharing rule ${rule.id} (${accessLevel}) shares with group ${edge.toId}, which contains this ${member.memberType} member${subDetail}${unresolved}`
119143
119276
  });
119144
119277
  }
@@ -119166,6 +119299,7 @@ var init_who_can_access_object = __esm({
119166
119299
  via,
119167
119300
  access: ruleAccessToOp(accessLevel),
119168
119301
  scope: "shared-records",
119302
+ sourceRuleId: rule.id,
119169
119303
  detail: `${ruleType || "owner"} sharing rule ${rule.id} (${accessLevel}) shares with ${edge.toId} and its subordinate roles, which include this descendant role${internalNote}`
119170
119304
  });
119171
119305
  }
@@ -119202,35 +119336,16 @@ var init_who_can_access_object = __esm({
119202
119336
  interleaved.push(row);
119203
119337
  }
119204
119338
  }
119205
- const page = interleaved.slice(offset, offset + limit);
119206
- const hasMore = offset + page.length < total;
119207
- const truncated = hasMore || offset > 0;
119208
- const pageRowsByKind = /* @__PURE__ */ new Map();
119209
- for (const g of page) {
119210
- pageRowsByKind.set(g.granterType, (pageRowsByKind.get(g.granterType) ?? 0) + 1);
119211
- }
119212
- const byGranterType = kindOrder.map((granterType) => {
119213
- const rows = byKind.get(granterType) ?? [];
119214
- const distinctIds = [...new Set(rows.map((g) => g.granterId))].sort();
119215
- return {
119216
- granterType,
119217
- rows: rows.length,
119218
- distinctGranters: distinctIds.length,
119219
- rowsOnThisPage: pageRowsByKind.get(granterType) ?? 0,
119220
- sample: distinctIds.slice(0, GRANTER_KIND_SAMPLE_LIMIT)
119221
- };
119222
- });
119223
119339
  const externalOwdNote = externalOwd !== null ? ` External OWD (externalSharingModel): '${externalOwd}' \u2014 controls access for Experience Cloud / community users.` : "";
119224
119340
  const owdNote = owdGrantsAllInternalUsers ? `OWD '${owd}' is PUBLIC \u2014 every internal user can ${owd === "Read" ? "read" : "read and edit"} EVERY record of this object, beyond the principals listed.${externalOwdNote}` : `OWD '${owd}' is private/controlled \u2014 record access flows only from the listed grants/rules plus ownership.${externalOwdNote}`;
119225
119341
  const multiRowNote = total > distinctGranters ? ` ${total} granter rows come from ${distinctGranters} distinct Profile/PermissionSet/role/group(s) \u2014 each independent capability (read/create/edit/delete + View/Modify-All) is its own row, so a principal can appear in several. Count ACTORS by \`summary.distinctGranters\`, not row count.` : "";
119226
- const kindNote = byGranterType.length > 0 ? ` Rows are INTERLEAVED across granter kinds (round-robin), so this page samples every kind rather than one alphabetical block: ${byGranterType.map((k) => `${k.granterType} ${k.rowsOnThisPage.toString()} of ${k.rows.toString()} row(s) / ${k.distinctGranters.toString()} principal(s)`).join("; ")}. \`summary.byGranterType\` carries the complete per-kind counts and a named sample of each kind.` : "";
119227
- const pageNote = truncated ? ` Showing granters ${offset}\u2013${offset + page.length} of ${total}; summary holds the complete counts. Page with offset/limit.` : "";
119228
119342
  const scanTruncated = truncatedTypes.length > 0;
119229
119343
  const scanNote = scanTruncated ? ` ${scanTruncationNote(truncatedTypes, scanLimit)}` : "";
119230
- const containerIds = [
119231
- ...new Set(page.map((g) => g.granterId).filter((id) => id.startsWith("Profile:") || id.startsWith("PermissionSet:")))
119344
+ const fullPage = interleaved.slice(offset, offset + limit);
119345
+ const fullContainerIds = [
119346
+ ...new Set(fullPage.map((g) => g.granterId).filter((id) => id.startsWith("Profile:") || id.startsWith("PermissionSet:")))
119232
119347
  ];
119233
- const dataShape = await readActiveHoldersFor(ctx, containerIds);
119348
+ const fullDataShape = await readActiveHoldersFor(ctx, fullContainerIds);
119234
119349
  const blindSpots = [...BLIND_SPOTS];
119235
119350
  if (restrictionRules.length > 0) {
119236
119351
  blindSpots.push(`Active restriction rule(s) on this object (${restrictionRules.map((r) => r.id).join(", ")}) FILTER records for users matching each rule's user criteria \u2014 any granter row here, including View/Modify All Data, can be narrowed at runtime. Use why_cant_user_see_record for a per-user verdict.`);
@@ -119251,8 +119366,30 @@ var init_who_can_access_object = __esm({
119251
119366
  blindSpots.push(USER_ASSIGNMENT_NOT_IN_VAULT);
119252
119367
  blindSpots.push(SHARING_USER_ENUMERATION_NOT_AVAILABLE);
119253
119368
  }
119254
- return ok({
119255
- data: {
119369
+ const buildData = (pageLimit) => {
119370
+ const page = interleaved.slice(offset, offset + pageLimit);
119371
+ const hasMore = offset + page.length < total;
119372
+ const truncated = hasMore || offset > 0;
119373
+ const pageRowsByKind = /* @__PURE__ */ new Map();
119374
+ for (const g of page) {
119375
+ pageRowsByKind.set(g.granterType, (pageRowsByKind.get(g.granterType) ?? 0) + 1);
119376
+ }
119377
+ const byGranterType = kindOrder.map((granterType) => {
119378
+ const rows = byKind.get(granterType) ?? [];
119379
+ const distinctIds = [...new Set(rows.map((g) => g.granterId))].sort();
119380
+ return {
119381
+ granterType,
119382
+ rows: rows.length,
119383
+ distinctGranters: distinctIds.length,
119384
+ rowsOnThisPage: pageRowsByKind.get(granterType) ?? 0,
119385
+ sample: distinctIds.slice(0, GRANTER_KIND_SAMPLE_LIMIT)
119386
+ };
119387
+ });
119388
+ const kindNote = byGranterType.length > 0 ? ` Rows are INTERLEAVED across granter kinds (round-robin), so this page samples every kind rather than one alphabetical block: ${byGranterType.map((k) => `${k.granterType} ${k.rowsOnThisPage.toString()} of ${k.rows.toString()} row(s) / ${k.distinctGranters.toString()} principal(s)`).join("; ")}. \`summary.byGranterType\` carries the complete per-kind counts and a named sample of each kind.` : "";
119389
+ const pageNote = truncated ? ` Showing granters ${offset}\u2013${offset + page.length} of ${total}; summary holds the complete counts. Page with offset/limit.` : "";
119390
+ const pageContainerIds = new Set(page.map((g) => g.granterId).filter((id) => id.startsWith("Profile:") || id.startsWith("PermissionSet:")));
119391
+ const dataShape = fullDataShape === void 0 ? void 0 : { ...fullDataShape, holders: fullDataShape.holders.filter((h) => pageContainerIds.has(h.id)) };
119392
+ return {
119256
119393
  componentId,
119257
119394
  objectLabel: objectNode.label ?? objectNode.apiName,
119258
119395
  owd,
@@ -119266,7 +119403,7 @@ var init_who_can_access_object = __esm({
119266
119403
  sharedRecordsAccess: total - allRecordsAccess,
119267
119404
  byGranterType
119268
119405
  },
119269
- limit,
119406
+ limit: pageLimit,
119270
119407
  offset,
119271
119408
  hasMore,
119272
119409
  truncated,
@@ -119275,7 +119412,28 @@ var init_who_can_access_object = __esm({
119275
119412
  ...dataShape !== void 0 ? { dataShape } : {},
119276
119413
  blindSpots,
119277
119414
  boundaryNote: `${owdNote} Declared static view (object permissions + sharing-rule targets + system god-mode); record-level paths are in blindSpots.${multiRowNote}${kindNote}${pageNote}${scanNote}`
119278
- },
119415
+ };
119416
+ };
119417
+ const budget = toolLocalPayloadBudgetBytes();
119418
+ let data = buildData(limit);
119419
+ if (utf8Bytes3(data) > budget && data.granters.length > 1) {
119420
+ let lo = 1;
119421
+ let hi = data.granters.length - 1;
119422
+ let best = buildData(1);
119423
+ while (lo <= hi) {
119424
+ const mid = Math.floor((lo + hi) / 2);
119425
+ const candidate = buildData(mid);
119426
+ if (utf8Bytes3(candidate) <= budget) {
119427
+ best = candidate;
119428
+ lo = mid + 1;
119429
+ } else {
119430
+ hi = mid - 1;
119431
+ }
119432
+ }
119433
+ data = best;
119434
+ }
119435
+ return ok({
119436
+ data,
119279
119437
  vaultState: {
119280
119438
  sourceTreeHash: ctx.manifest.sourceTreeHash,
119281
119439
  refreshedAt: ctx.manifest.refreshedAt
@@ -121072,11 +121230,12 @@ var init_why_field_changed = __esm({
121072
121230
 
121073
121231
  // ../mcp/dist/src/tools/tool-dispatch.js
121074
121232
  import { homedir as homedir4 } from "node:os";
121075
- var dispatchTool, toDisclosedVaultPath, stampVaultDisclosure, runTool, SLIM_STRING_THRESHOLD_BYTES2, SLIM_STRING_KEEP_CHARS2, TRUNCATE_KEEP_MIN, utf8Bytes3, HONESTY_LIST_KEYS, HONESTY_CONTAINER_KEYS, collectTrimCandidates, truncateDataArrays, slimDataStrings, jsonResult, NARROWING_KNOB_RE, narrowingKnobs;
121233
+ var dispatchTool, toDisclosedVaultPath, stampVaultDisclosure, runTool, SLIM_STRING_THRESHOLD_BYTES2, SLIM_STRING_KEEP_CHARS2, TRUNCATE_KEEP_MIN, utf8Bytes4, HONESTY_LIST_KEYS, HONESTY_CONTAINER_KEYS, collectTrimCandidates, truncateDataArrays, slimDataStrings, jsonResult, NARROWING_KNOB_RE, narrowingKnobs;
121076
121234
  var init_tool_dispatch = __esm({
121077
121235
  "../mcp/dist/src/tools/tool-dispatch.js"() {
121078
121236
  "use strict";
121079
121237
  init_dist();
121238
+ init_src();
121080
121239
  init_audit();
121081
121240
  init_live_capability();
121082
121241
  init_action_chain();
@@ -121690,8 +121849,15 @@ var init_tool_dispatch = __esm({
121690
121849
  case "sfi.field_provenance":
121691
121850
  return runTool(ctx, args, fieldProvenanceInputSchema, fieldProvenanceHandler);
121692
121851
  // RM-wire — deterministic reasoning-engine surface (offline, cited).
121852
+ // INTERPRET-OVERSIZE-KNOBLESS-REMEDY: `concepts` / `ruleIds` are this
121853
+ // tool's ONLY narrowing params (no limit/offset/hops — its schema does
121854
+ // not match the shared knob regex), so an oversize error must name THEM
121855
+ // explicitly or the remedy prescribes parameters the tool does not have.
121693
121856
  case "sfi.interpret":
121694
- return runTool(ctx, args, interpretInputSchema, interpretHandler);
121857
+ return runTool(ctx, args, interpretInputSchema, interpretHandler, {
121858
+ extraKnobs: ["ruleIds", "concepts"],
121859
+ exampleCall: "sfi.interpret({ componentId, ruleIds: ['SOME_RULE_ID'] })"
121860
+ });
121695
121861
  // v2.2 R2 — universal find-anywhere + discovery surface.
121696
121862
  case "sfi.find_field_anywhere":
121697
121863
  return runTool(ctx, args, findFieldAnywhereInputSchema, findFieldAnywhereHandler);
@@ -121751,10 +121917,14 @@ var init_tool_dispatch = __esm({
121751
121917
  });
121752
121918
  }
121753
121919
  };
121754
- toDisclosedVaultPath = (absPath) => {
121755
- const home = homedir4();
121756
- return home.length > 0 && (absPath === home || absPath.startsWith(`${home}/`)) ? `~${absPath.slice(home.length)}` : absPath;
121757
- };
121920
+ toDisclosedVaultPath = (absPath) => (
121921
+ // `collapseHome` replaces a hand-rolled `startsWith(`${home}/`)`, which never
121922
+ // matched on Windows (`C:\\Users\\alice` + `/`), so the redaction silently
121923
+ // did nothing and every response carried the operator's account name. It also
121924
+ // fixes a case-sensitivity miss (`c:\\users` vs `C:\\Users`) for free, and
121925
+ // maps "the vault IS home" to a bare `~` rather than the absolute path.
121926
+ collapseHome(absPath, homedir4())
121927
+ );
121758
121928
  stampVaultDisclosure = (resp, ctx) => {
121759
121929
  const manifest = ctx.manifest;
121760
121930
  return {
@@ -121767,7 +121937,7 @@ var init_tool_dispatch = __esm({
121767
121937
  }
121768
121938
  };
121769
121939
  };
121770
- runTool = async (ctx, args, schema, handler) => {
121940
+ runTool = async (ctx, args, schema, handler, oversizeExtras) => {
121771
121941
  const parsed = schema.safeParse(args);
121772
121942
  if (!parsed.success) {
121773
121943
  const message = parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
@@ -121775,12 +121945,16 @@ var init_tool_dispatch = __esm({
121775
121945
  error: { kind: "invalid-query", message }
121776
121946
  });
121777
121947
  }
121948
+ const knobs = [
121949
+ .../* @__PURE__ */ new Set([...oversizeExtras?.extraKnobs ?? [], ...narrowingKnobs(schema)])
121950
+ ];
121778
121951
  try {
121779
121952
  const result = await handler(ctx, parsed.data);
121780
121953
  return jsonResult(result.ok ? stampVaultDisclosure(result.value, ctx) : { error: result.error }, {
121781
121954
  args: parsed.data,
121782
- knobs: narrowingKnobs(schema),
121783
- vaultRoot: ctx.vaultRoot
121955
+ knobs,
121956
+ vaultRoot: ctx.vaultRoot,
121957
+ ...oversizeExtras?.exampleCall !== void 0 ? { exampleCall: oversizeExtras.exampleCall } : {}
121784
121958
  });
121785
121959
  } catch (error) {
121786
121960
  console.error("sf-intelligence: runTool internal error in handler:", error);
@@ -121789,13 +121963,16 @@ var init_tool_dispatch = __esm({
121789
121963
  kind: "internal",
121790
121964
  message: "An internal error occurred while handling this tool. The server logged the details."
121791
121965
  }
121792
- }, { knobs: narrowingKnobs(schema) });
121966
+ }, {
121967
+ knobs,
121968
+ ...oversizeExtras?.exampleCall !== void 0 ? { exampleCall: oversizeExtras.exampleCall } : {}
121969
+ });
121793
121970
  }
121794
121971
  };
121795
121972
  SLIM_STRING_THRESHOLD_BYTES2 = 1536;
121796
121973
  SLIM_STRING_KEEP_CHARS2 = 1024;
121797
121974
  TRUNCATE_KEEP_MIN = 10;
121798
- utf8Bytes3 = (value) => Buffer.byteLength(JSON.stringify(value), "utf8");
121975
+ utf8Bytes4 = (value) => Buffer.byteLength(JSON.stringify(value), "utf8");
121799
121976
  HONESTY_LIST_KEYS = /* @__PURE__ */ new Set([
121800
121977
  "blindSpots",
121801
121978
  "boundaries",
@@ -121844,7 +122021,7 @@ var init_tool_dispatch = __esm({
121844
122021
  return { dropped: 0, truncatedPaths: [], keptByPath: {}, originalByPath: {} };
121845
122022
  }
121846
122023
  const record = data;
121847
- const candidates = [...collectTrimCandidates(record)].sort((a, b) => utf8Bytes3(b.owner[b.key]) - utf8Bytes3(a.owner[a.key]));
122024
+ const candidates = [...collectTrimCandidates(record)].sort((a, b) => utf8Bytes4(b.owner[b.key]) - utf8Bytes4(a.owner[a.key]));
121848
122025
  let dropped = 0;
121849
122026
  const truncatedPaths = [];
121850
122027
  const keptByPath = {};
@@ -121853,7 +122030,7 @@ var init_tool_dispatch = __esm({
121853
122030
  let list = candidate.owner[candidate.key];
121854
122031
  const originalLength = list.length;
121855
122032
  const droppedBefore = dropped;
121856
- while (list.length > TRUNCATE_KEEP_MIN && utf8Bytes3(body) > cap3) {
122033
+ while (list.length > TRUNCATE_KEEP_MIN && utf8Bytes4(body) > cap3) {
121857
122034
  const keep = Math.max(TRUNCATE_KEEP_MIN, Math.floor(list.length / 2));
121858
122035
  dropped += list.length - keep;
121859
122036
  list = list.slice(0, keep);
@@ -121864,7 +122041,7 @@ var init_tool_dispatch = __esm({
121864
122041
  keptByPath[candidate.path] = list.length;
121865
122042
  originalByPath[candidate.path] = originalLength;
121866
122043
  }
121867
- if (utf8Bytes3(body) <= cap3)
122044
+ if (utf8Bytes4(body) <= cap3)
121868
122045
  break;
121869
122046
  }
121870
122047
  return { dropped, truncatedPaths, keptByPath, originalByPath };
@@ -121914,9 +122091,10 @@ var init_tool_dispatch = __esm({
121914
122091
  const oversizeResult = (estimatedPayloadBytes, originalErrorKind) => {
121915
122092
  const boundedErrorKind = originalErrorKind?.slice(0, 64);
121916
122093
  const knobs = (narrowing?.knobs ?? []).slice(0, 8).map((knob) => knob.slice(0, 64));
122094
+ const exampleCall = narrowing?.exampleCall?.slice(0, 200);
121917
122095
  const error = {
121918
122096
  kind: "oversize",
121919
- message: `${boundedErrorKind === void 0 ? "This tool's response" : `The '${boundedErrorKind}' error response`} (~${Math.round(estimatedPayloadBytes / 1e3)} KB) exceeds the response budget (~${Math.round(cap3 / 1e3)} KB, SFI_MAX_RESPONSE_BYTES). Re-query with a narrower scope${knobs.length > 0 ? ` \u2014 this tool supports: ${knobs.join(", ")}` : " (filter, pagination, fewer hops)"}.`
122097
+ message: `${boundedErrorKind === void 0 ? "This tool's response" : `The '${boundedErrorKind}' error response`} (~${Math.round(estimatedPayloadBytes / 1e3)} KB) exceeds the response budget (~${Math.round(cap3 / 1e3)} KB, SFI_MAX_RESPONSE_BYTES). Re-query with a narrower scope${knobs.length > 0 ? ` \u2014 this tool supports: ${knobs.join(", ")}` : " (filter, pagination, fewer hops)"}${exampleCall !== void 0 ? `, e.g. ${exampleCall}` : ""}.`
121920
122098
  };
121921
122099
  return result({ error, estimatedPayloadBytes });
121922
122100
  };
@@ -121939,7 +122117,7 @@ var init_tool_dispatch = __esm({
121939
122117
  }
121940
122118
  }
121941
122119
  const withContentPolicy = (value) => isErrorEnvelope || !("data" in value) ? value : { ...value, contentPolicy: ORG_METADATA_CONTENT_POLICY };
121942
- const baseBytes = utf8Bytes3(withContentPolicy(body));
122120
+ const baseBytes = utf8Bytes4(withContentPolicy(body));
121943
122121
  const toEnvelope = (value) => ({
121944
122122
  ...withContentPolicy(value),
121945
122123
  estimatedPayloadBytes: baseBytes
@@ -121970,10 +122148,10 @@ var init_tool_dispatch = __esm({
121970
122148
  originalByPath
121971
122149
  }) : "none";
121972
122150
  let stringsSlimmed = 0;
121973
- if (utf8Bytes3(clone) > reductionCap) {
122151
+ if (utf8Bytes4(clone) > reductionCap) {
121974
122152
  stringsSlimmed = slimDataStrings(clone);
121975
122153
  }
121976
- if (utf8Bytes3(clone) <= reductionCap) {
122154
+ if (utf8Bytes4(clone) <= reductionCap) {
121977
122155
  const args = narrowing?.args ?? {};
121978
122156
  const offset = args["offset"];
121979
122157
  const offsetShaped = "offset" in args || typeof offset === "number";
@@ -122091,8 +122269,8 @@ var init_server = __esm({
122091
122269
  init_tools();
122092
122270
  SERVER_NAME = "sf-intelligence";
122093
122271
  resolveServerVersion = () => {
122094
- if ("0.3.1") {
122095
- return "0.3.1";
122272
+ if ("0.3.2") {
122273
+ return "0.3.2";
122096
122274
  }
122097
122275
  for (const rel of ["../package.json", "../../package.json"]) {
122098
122276
  try {
@@ -122220,6 +122398,132 @@ How to use it well:
122220
122398
  }
122221
122399
  });
122222
122400
 
122401
+ // ../mcp/dist/src/setup-server.js
122402
+ import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
122403
+ import { CallToolRequestSchema as CallToolRequestSchema2, ListToolsRequestSchema as ListToolsRequestSchema2 } from "@modelcontextprotocol/sdk/types.js";
122404
+ var isWindows2, quotePath, setupSteps, headline, setupInstructions, SETUP_TOOL, setupStatusPayload, createSetupServer;
122405
+ var init_setup_server = __esm({
122406
+ "../mcp/dist/src/setup-server.js"() {
122407
+ "use strict";
122408
+ isWindows2 = () => process.platform === "win32";
122409
+ quotePath = (p) => p.includes(" ") ? `"${p}"` : p;
122410
+ setupSteps = (state) => {
122411
+ const vaultFlag = `--vault ${quotePath(state.expectedVaultRoot)}`;
122412
+ const org = state.authedOrgs[0] ?? "<your-org-alias>";
122413
+ const needsInit = state.reason === "no-vault";
122414
+ const steps = [];
122415
+ if (needsInit) {
122416
+ steps.push("Change into the Salesforce DX repo you want to model (the directory holding `sfdx-project.json`).", `Run \`npx -y sf-intelligence init --target-org ${org}\` to create the vault and bind it to one org.`);
122417
+ }
122418
+ steps.push(`Run \`npx -y sf-intelligence refresh --target-org ${org}\` to retrieve metadata and build the vault. First run takes a few minutes on a real org.`);
122419
+ steps.push("Restart this MCP client so the server reconnects against the built vault.");
122420
+ if (state.bindSource.includes("org-kb")) {
122421
+ steps.push(`If the vault already exists somewhere else, the server is looking in the wrong place: it resolved \`./org-kb\` against its launch directory (${quotePath(state.cwd)}), which your MCP client chose \u2014 not you. Pin it instead by adding \`${vaultFlag}\` to the server's args, or setting the \`SFI_VAULT\` environment variable in the server's config.`);
122422
+ }
122423
+ return steps;
122424
+ };
122425
+ headline = (state) => {
122426
+ switch (state.reason) {
122427
+ case "no-vault":
122428
+ return "sf-intelligence is running but has no knowledge base yet \u2014 the user has not built a vault for this project.";
122429
+ case "vault-missing":
122430
+ return "sf-intelligence found a vault config but no built vault \u2014 `refresh` has not completed for this project.";
122431
+ case "manifest-load-failed":
122432
+ return "sf-intelligence found a vault whose manifest could not be read \u2014 it is likely from an interrupted refresh.";
122433
+ case "graph-open-failed":
122434
+ return "sf-intelligence found a vault whose dependency graph could not be opened \u2014 it is likely from an interrupted refresh.";
122435
+ default:
122436
+ return "sf-intelligence could not open a knowledge base for this project.";
122437
+ }
122438
+ };
122439
+ setupInstructions = (state) => [
122440
+ headline(state),
122441
+ "",
122442
+ "IMPORTANT: in this state the server can answer NOTHING about the user's Salesforce org \u2014 no objects, fields, permissions, Apex or Flows. Do not guess, and do not answer org questions from general Salesforce knowledge; say the knowledge base is not built yet.",
122443
+ "",
122444
+ "Call `sfi.setup_status` for the exact next command, then offer to run it for the user. Once the vault is built, this server restarts with the full tool set."
122445
+ ].join("\n");
122446
+ SETUP_TOOL = {
122447
+ name: "sfi.setup_status",
122448
+ description: "Report why sf-intelligence has no knowledge base for this project yet, and the exact ordered commands to build one. Read-only; runs nothing. Call this before telling the user anything about their Salesforce org \u2014 in this state the server has no org data at all.",
122449
+ inputSchema: {
122450
+ type: "object",
122451
+ properties: {},
122452
+ required: [],
122453
+ additionalProperties: false
122454
+ },
122455
+ annotations: {
122456
+ title: "Setup status",
122457
+ readOnlyHint: true,
122458
+ destructiveHint: false,
122459
+ idempotentHint: true,
122460
+ openWorldHint: false
122461
+ }
122462
+ };
122463
+ setupStatusPayload = (state) => ({
122464
+ data: {
122465
+ status: "setup-required",
122466
+ reason: state.reason,
122467
+ detail: state.detail,
122468
+ summary: headline(state),
122469
+ /**
122470
+ * Honest statement of capability. The router/answer surfaces are absent in
122471
+ * this mode, so say so rather than letting a host infer that an empty tool
122472
+ * list means "nothing to report".
122473
+ */
122474
+ canAnswerOrgQuestions: false,
122475
+ lookedForVaultAt: state.expectedVaultRoot,
122476
+ vaultBoundVia: state.bindSource,
122477
+ serverLaunchDirectory: state.cwd,
122478
+ authenticatedOrgs: state.authedOrgs,
122479
+ nextSteps: setupSteps(state),
122480
+ shell: isWindows2() ? "windows" : "posix",
122481
+ /**
122482
+ * The env-var form differs per shell and a wrong one is a dead end for a
122483
+ * non-developer. Give the exact line for the shell they are in.
122484
+ */
122485
+ pinVaultExample: isWindows2() ? `$env:SFI_VAULT = '${state.expectedVaultRoot}'` : `export SFI_VAULT='${state.expectedVaultRoot}'`,
122486
+ docs: "https://sfi.auditforce.cloud"
122487
+ },
122488
+ provenance: "setup_mode",
122489
+ disclosures: [
122490
+ "sf-intelligence is running in setup mode: no vault is bound, so no org metadata is available."
122491
+ ]
122492
+ });
122493
+ createSetupServer = (state) => {
122494
+ const server = new Server2({ name: "sf-intelligence", version: state.version }, {
122495
+ capabilities: { tools: {} },
122496
+ instructions: setupInstructions(state)
122497
+ });
122498
+ server.setRequestHandler(ListToolsRequestSchema2, async () => ({
122499
+ tools: [SETUP_TOOL]
122500
+ }));
122501
+ server.setRequestHandler(CallToolRequestSchema2, async (request) => {
122502
+ if (request.params.name !== SETUP_TOOL.name) {
122503
+ const body2 = {
122504
+ error: {
122505
+ kind: "setup-required",
122506
+ message: `sf-intelligence has no knowledge base for this project yet, so \`${request.params.name}\` cannot answer. Call \`${SETUP_TOOL.name}\` for the exact setup commands.`
122507
+ },
122508
+ provenance: "setup_mode"
122509
+ };
122510
+ return {
122511
+ content: [{ type: "text", text: JSON.stringify(body2) }],
122512
+ structuredContent: body2,
122513
+ isError: true
122514
+ };
122515
+ }
122516
+ const body = setupStatusPayload(state);
122517
+ return {
122518
+ content: [{ type: "text", text: JSON.stringify(body) }],
122519
+ structuredContent: body
122520
+ };
122521
+ });
122522
+ return server;
122523
+ };
122524
+ }
122525
+ });
122526
+
122223
122527
  // ../mcp/dist/src/serve-http.js
122224
122528
  import { timingSafeEqual, createHash as createHash9, randomBytes } from "node:crypto";
122225
122529
  import { readFileSync as readFileSync5, statSync as statSync4 } from "node:fs";
@@ -122454,6 +122758,7 @@ var init_src9 = __esm({
122454
122758
  init_live_session();
122455
122759
  init_intent_router();
122456
122760
  init_server();
122761
+ init_setup_server();
122457
122762
  init_serve_http();
122458
122763
  init_tools();
122459
122764
  init_security_posture_metric();
@@ -122967,7 +123272,7 @@ var init_package_version = __esm({
122967
123272
  "use strict";
122968
123273
  readCliPackageVersion = () => {
122969
123274
  if (true)
122970
- return "0.3.1";
123275
+ return "0.3.2";
122971
123276
  for (const rel of ["../package.json", "../../package.json"]) {
122972
123277
  try {
122973
123278
  const raw = readFileSync6(fileURLToPath4(new URL(rel, import.meta.url)), "utf8");
@@ -122984,11 +123289,12 @@ var init_package_version = __esm({
122984
123289
 
122985
123290
  // dist/src/refresh-pipeline.js
122986
123291
  import { mkdir as mkdir13, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
122987
- import { basename as basename15, dirname as dirname26, join as join39, sep as sep3 } from "node:path";
123292
+ import { basename as basename15, dirname as dirname26, join as join39 } from "node:path";
122988
123293
  var EXTRACT_CACHE_VERSION, SUPPORTED_TYPES, CO_EMITTED_TYPES, coEmits, EXTRACTORS, dispatchFile, BUNDLE_PARENT_DIRS, walkDir, relativeSegments, DX_WRAPPER_SEGMENTS, KNOWN_SIDECAR_SUFFIXES, isKnownSidecar, skipAttributionKey, componentTypeFromSourcePath, REPORT_DASHBOARD_TYPES, FOLDED_REPORT_DASHBOARD_NAME_CAP, FOLDED_NAME_CAP, DEFAULT_REPORT_DASHBOARD_NODE_CAP, reportDashboardNodeCap, PERSISTED_REPORT_PROPERTY_KEYS, PERSISTED_DASHBOARD_PROPERTY_KEYS, PERSISTED_ANALYTICS_EDGE_PROPERTY_KEYS, FILTER_ITEM_KEYS, GROUPING_ITEM_KEYS, BUCKET_ITEM_KEYS, CROSS_FILTER_ITEM_KEYS, CHART_KEYS, projectItems, projectObject, sanitizeAnalyticsProperties, capFoldedNames, applyReportDashboardPersistence, profileIdKeys, buildProfileIdIndex, RESOLVED_PROFILE_REFERENCE_KIND, resolveRestrictionRuleProfileEdges, walkAndExtract, composeDocument, parentApiNameFor, writeNodeDocument, writeIndex, RENDER_PAGE_SIZE, renderVault, parseTypeFilter;
122989
123294
  var init_refresh_pipeline = __esm({
122990
123295
  "dist/src/refresh-pipeline.js"() {
122991
123296
  "use strict";
123297
+ init_src();
122992
123298
  init_src6();
122993
123299
  init_src2();
122994
123300
  init_src8();
@@ -123421,8 +123727,10 @@ var init_refresh_pipeline = __esm({
123421
123727
  }
123422
123728
  };
123423
123729
  relativeSegments = (rootDir, absPath) => {
123424
- const rel = absPath.startsWith(`${rootDir}${sep3}`) ? absPath.slice(rootDir.length + 1) : absPath;
123425
- return rel.split(sep3);
123730
+ const rootSegments = splitPathSegments(rootDir);
123731
+ const absSegments = splitPathSegments(absPath);
123732
+ const isUnderRoot = rootSegments.length > 0 && rootSegments.length <= absSegments.length && rootSegments.every((seg, i) => absSegments[i] === seg);
123733
+ return isUnderRoot ? absSegments.slice(rootSegments.length) : absSegments;
123426
123734
  };
123427
123735
  DX_WRAPPER_SEGMENTS = /* @__PURE__ */ new Set(["main", "default"]);
123428
123736
  KNOWN_SIDECAR_SUFFIXES = [
@@ -124111,11 +124419,12 @@ var init_setup_audit_trail = __esm({
124111
124419
 
124112
124420
  // dist/src/source-reconcile.js
124113
124421
  import { cp, readdir as readdir8, rm as rm3, stat as stat8 } from "node:fs/promises";
124114
- import { basename as basename16, join as join41, relative as relative3, sep as sep4 } from "node:path";
124422
+ import { basename as basename16, join as join41, relative as relative2, sep as sep3 } from "node:path";
124115
124423
  var BUNDLE_PARENT_DIRS2, KNOWN_SIDECAR_SUFFIXES2, isKnownSidecar2, primaryRelPathForSidecar, walkSourceEntries, DISPATCH_PROBE_ROOT, dispatchTailLength, strippedDxWrapper, comparisonKey, authoritativeIndex, RECONCILE_MAX_DELETE_FRACTION, RECONCILE_GUARD_MIN_CONSIDERED, layoutMismatchRefusal, reconcileSourceDeletions, syncAuthoritativeRetrieveIntoSource;
124116
124424
  var init_source_reconcile = __esm({
124117
124425
  "dist/src/source-reconcile.js"() {
124118
124426
  "use strict";
124427
+ init_src();
124119
124428
  init_refresh_pipeline();
124120
124429
  BUNDLE_PARENT_DIRS2 = /* @__PURE__ */ new Set(["lwc", "aura"]);
124121
124430
  KNOWN_SIDECAR_SUFFIXES2 = [
@@ -124156,7 +124465,7 @@ var init_source_reconcile = __esm({
124156
124465
  if (type !== null) {
124157
124466
  found.push({
124158
124467
  absPath: abs,
124159
- relPath: relative3(sourceRoot, abs),
124468
+ relPath: relative2(sourceRoot, abs),
124160
124469
  type,
124161
124470
  isDirectory: true
124162
124471
  });
@@ -124165,7 +124474,7 @@ var init_source_reconcile = __esm({
124165
124474
  await walkDir2(abs);
124166
124475
  }
124167
124476
  } else if (entry.isFile()) {
124168
- const relPath = relative3(sourceRoot, abs);
124477
+ const relPath = relative2(sourceRoot, abs);
124169
124478
  const fileName = entry.name;
124170
124479
  let type = componentTypeFromSourcePath(sourceRoot, abs, false);
124171
124480
  if (type === null && isKnownSidecar2(fileName)) {
@@ -124187,7 +124496,7 @@ var init_source_reconcile = __esm({
124187
124496
  };
124188
124497
  await walkDir2(sourceRoot);
124189
124498
  };
124190
- DISPATCH_PROBE_ROOT = join41(sep4, "__sfi-reconcile-dispatch-probe__");
124499
+ DISPATCH_PROBE_ROOT = join41(sep3, "__sfi-reconcile-dispatch-probe__");
124191
124500
  dispatchTailLength = (segments, isDirectory) => {
124192
124501
  for (let length = 1; length <= segments.length; length += 1) {
124193
124502
  const tail = segments.slice(segments.length - length);
@@ -124205,10 +124514,10 @@ var init_source_reconcile = __esm({
124205
124514
  return segments;
124206
124515
  };
124207
124516
  comparisonKey = (relPath, isDirectory) => {
124208
- const segments = relPath.split(sep4);
124517
+ const segments = splitPathSegments(relPath);
124209
124518
  const fileName = segments[segments.length - 1] ?? relPath;
124210
124519
  const primaryRel = primaryRelPathForSidecar(relPath, fileName);
124211
- const probeSegments = primaryRel === null ? segments : primaryRel.split(sep4);
124520
+ const probeSegments = primaryRel === null ? segments : splitPathSegments(primaryRel);
124212
124521
  const length = dispatchTailLength(probeSegments, isDirectory);
124213
124522
  const tail = length === null ? strippedDxWrapper(segments) : segments.slice(segments.length - length);
124214
124523
  return tail.join("/");
@@ -124262,7 +124571,7 @@ var init_source_reconcile = __esm({
124262
124571
  candidateBundleDirs.add(entry.absPath);
124263
124572
  candidates.push(entry);
124264
124573
  }
124265
- const doomed = candidates.filter((entry) => entry.isDirectory || ![...candidateBundleDirs].some((dir) => entry.absPath.startsWith(`${dir}${sep4}`)));
124574
+ const doomed = candidates.filter((entry) => entry.isDirectory || ![...candidateBundleDirs].some((dir) => entry.absPath.startsWith(`${dir}${sep3}`)));
124266
124575
  const refusalReason = layoutMismatchRefusal(doomed.length, considered.length, authoritative);
124267
124576
  if (refusalReason !== null) {
124268
124577
  return {
@@ -124681,7 +124990,7 @@ Pick a writable path with \`--out <file>\`.
124681
124990
  // dist/src/commands/vault-anonymize.js
124682
124991
  import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
124683
124992
  import { mkdir as mkdir16, readdir as readdir9, readFile as readFile112, writeFile as writeFile13 } from "node:fs/promises";
124684
- import { dirname as dirname28, join as join43, relative as relative4, resolve as resolve7, sep as sep5 } from "node:path";
124993
+ import { dirname as dirname28, join as join43, relative as relative3, resolve as resolve7, sep as sep4 } from "node:path";
124685
124994
  var PHONE_RE, extendedScrubText, buildIdentityReplacements, applyReplacements, META_IDENTITY_KEY, collectVaultIdentities, validateOutDir, isLikelyBinaryFile, walkFiles, copyTextTree, transformMetaJsonIdentityFields, META_COPY_ALLOWLIST, loadLocalForbiddenNamePatterns, residualLeakScan, PSEUDONYMIZE_NOT_IMPLEMENTED_MESSAGE, buildAnonymizedReadme, anonymizeVault, registerVaultAnonymizeSubcommand;
124686
124995
  var init_vault_anonymize = __esm({
124687
124996
  "dist/src/commands/vault-anonymize.js"() {
@@ -124748,8 +125057,8 @@ var init_vault_anonymize = __esm({
124748
125057
  return ok([...identities]);
124749
125058
  };
124750
125059
  validateOutDir = (vaultRoot, outDir) => {
124751
- const src = resolve7(vaultRoot) + sep5;
124752
- const out = resolve7(outDir) + sep5;
125060
+ const src = resolve7(vaultRoot) + sep4;
125061
+ const out = resolve7(outDir) + sep4;
124753
125062
  if (out === src || out.startsWith(src)) {
124754
125063
  return err(`--out (${resolve7(outDir)}) must be OUTSIDE the source vault (${resolve7(vaultRoot)}).`);
124755
125064
  }
@@ -124779,7 +125088,7 @@ var init_vault_anonymize = __esm({
124779
125088
  copyTextTree = async (srcDir, destDir, relLabel, transform, stats) => {
124780
125089
  const files = await walkFiles(srcDir);
124781
125090
  for (const file of files) {
124782
- const rel = relative4(srcDir, file);
125091
+ const rel = relative3(srcDir, file);
124783
125092
  const reportedPath = join43(relLabel, rel);
124784
125093
  if (isLikelyBinaryFile(file)) {
124785
125094
  stats.filesSkipped.push({
@@ -124860,7 +125169,7 @@ var init_vault_anonymize = __esm({
124860
125169
  continue;
124861
125170
  }
124862
125171
  filesScanned += 1;
124863
- const rel = relative4(outDir, file);
125172
+ const rel = relative3(outDir, file);
124864
125173
  for (const identity of identities) {
124865
125174
  if (identity.length > 0 && text.includes(identity)) {
124866
125175
  findings.push({ file: rel, pattern: "identity-literal" });
@@ -126091,27 +126400,29 @@ SAFE but may now carry entries for components that no longer exist.
126091
126400
  const pending2 = new Set(marker.pendingTypes);
126092
126401
  return entries.map((entry) => pending2.has(entry.type) ? { ...entry, requested: true, pending: true } : entry);
126093
126402
  };
126094
- decorateReportsCapCoverage = (entries, stats) => {
126095
- if (stats === void 0)
126403
+ decorateReportsCapCoverage = (entries, stats, previousReportsCap) => {
126404
+ const effective = stats ?? previousReportsCap;
126405
+ if (effective === void 0)
126096
126406
  return entries;
126097
126407
  const landed = /* @__PURE__ */ new Map([
126098
- ["Report", stats.reports],
126099
- ["Dashboard", stats.dashboards]
126408
+ ["Report", effective.reports],
126409
+ ["Dashboard", effective.dashboards]
126100
126410
  ]);
126101
126411
  const proven = (c) => c.retrieved > 0 && c.total <= c.retrieved;
126102
126412
  const decorated = (c) => ({
126103
126413
  requested: true,
126104
126414
  // This is the ONLY place a fold-erased row gets a truthful `retrieved`:
126105
- // the files the retrieve actually landed on disk. Overwriting the
126106
- // structurally-zero node count is the point (FOLD_ERASED_COVERAGE_TYPES).
126415
+ // the files the retrieve actually landed on disk (or, via the fallback,
126416
+ // the last time a retrieve actually landed them). Overwriting the
126417
+ // structurally-unreliable node count is the point (FOLD_ERASED_COVERAGE_TYPES).
126107
126418
  retrieved: c.retrieved,
126108
- ...proven(c) ? { retrieveConfirmed: true } : { pending: true }
126419
+ ...proven(c) ? { retrieveConfirmed: true } : c.retrieved > 0 ? { capped: true } : { pending: true }
126109
126420
  });
126110
126421
  const out = entries.map((entry) => {
126111
126422
  const c = landed.get(entry.type);
126112
126423
  if (c === void 0 || entry.neverModeled)
126113
126424
  return entry;
126114
- const { pending: _wasPending, retrieveConfirmed: _wasConfirmed, ...rest } = entry;
126425
+ const { pending: _wasPending, capped: _wasCapped, retrieveConfirmed: _wasConfirmed, ...rest } = entry;
126115
126426
  return { ...rest, ...decorated(c) };
126116
126427
  });
126117
126428
  for (const [type, c] of landed) {
@@ -126135,8 +126446,8 @@ SAFE but may now carry entries for components that no longer exist.
126135
126446
  return entries.map((entry) => {
126136
126447
  if (!capped.has(entry.type) || entry.neverModeled)
126137
126448
  return entry;
126138
- const { retrieveConfirmed: _dropped, ...rest } = entry;
126139
- return { ...rest, requested: true, pending: true };
126449
+ const { retrieveConfirmed: _dropped, pending: _wasPending, ...rest } = entry;
126450
+ return { ...rest, requested: true, capped: true };
126140
126451
  });
126141
126452
  };
126142
126453
  decorateReportPullCoverage = (entries, disclosure) => {
@@ -126345,7 +126656,16 @@ SAFE but may now carry entries for components that no longer exist.
126345
126656
  }
126346
126657
  const coverageComputedAt = (/* @__PURE__ */ new Date()).toISOString();
126347
126658
  const pullRan = confirmedTypes !== null;
126348
- const coverage = stampFamilyEpochs(decorateProfileGrantCoverage(decoratePendingCoverage(decorateReportPullCoverage(decorateReportNodeCapCoverage(decorateReportsCapCoverage(buildCoverageEntries2(counts, walked.skippedDirectories, requestedTypes, paths.source, walked.failures, confirmedTypes), args.reportsCapStats), args.reportNodeStats), args.reportPull), opts.stagedMarker), profileGrantDisclosure), previousManifest?.coverage, coverageComputedAt, pullRan);
126659
+ const coverage = stampFamilyEpochs(decorateProfileGrantCoverage(decoratePendingCoverage(decorateReportPullCoverage(decorateReportNodeCapCoverage(decorateReportsCapCoverage(
126660
+ buildCoverageEntries2(counts, walked.skippedDirectories, requestedTypes, paths.source, walked.failures, confirmedTypes),
126661
+ args.reportsCapStats,
126662
+ // FIX-2 (coverage-spine): when THIS run has no fresh reportsCap
126663
+ // evidence of its own (a `--no-pull` rebuild, a scoped refresh),
126664
+ // fall back to the last manifest's — real, not fabricated —
126665
+ // capped-retrieve evidence instead of regressing the row to the
126666
+ // FOLD_ERASED "pending" default.
126667
+ previousManifest?.reportsCap
126668
+ ), args.reportNodeStats), args.reportPull), opts.stagedMarker), profileGrantDisclosure), previousManifest?.coverage, coverageComputedAt, pullRan);
126349
126669
  let phantomSummary;
126350
126670
  if (!midBuild && opts.stagedMarker === void 0) {
126351
126671
  try {
@@ -126505,6 +126825,10 @@ SAFE but may now carry entries for components that no longer exist.
126505
126825
  `);
126506
126826
  }
126507
126827
  }
126828
+ const componentTotal = Object.values(counts.components).reduce((sum, n) => sum + (n ?? 0), 0);
126829
+ if (componentTotal === 0) {
126830
+ process.stderr.write("sfi refresh: WARNING \u2014 the build modelled 0 components, so this vault cannot answer any question about the org.\n Most often the metadata is in `force-app/` but the vault reads `org-kb/source/`, or a `--types` filter excluded everything.\n Run `sfi doctor` for the specific check that failed.\n");
126831
+ }
126508
126832
  return {
126509
126833
  // A profile-grant integrity disclosure forces `partial`: the vault built,
126510
126834
  // but its permission graph is untrustworthy — never report clean success.
@@ -126516,7 +126840,18 @@ SAFE but may now carry entries for components that no longer exist.
126516
126840
  // as a profile-grant disclosure: the vault built, but some components were
126517
126841
  // assembled from two different retrievals of the same metadata, so their
126518
126842
  // permissions/properties are unresolved until the stale tree is removed.
126519
- status: walked.failures.length === 0 && args.retrieveFailures.length === 0 && profileGrantDisclosure === null && (walked.duplicateSourcePaths?.conflicting ?? 0) === 0 && args.reportPull === void 0 ? "success" : "partial",
126843
+ // A build that modelled ZERO components is never a success. It used to be:
126844
+ // with no per-file failures the status read `success`, the CLI printed
126845
+ // "Refresh success", `sfi status` reported the vault "locally consistent"
126846
+ // and `sfi quickstart` marked every step done — while the vault answered
126847
+ // every question with nothing. Only `sfi doctor` told the truth. The
126848
+ // realistic way in is a DX repo whose metadata sits in `force-app/` rather
126849
+ // than `org-kb/source/`, which is most of them.
126850
+ //
126851
+ // `partial` rather than `failed`: the pipeline genuinely ran, and the
126852
+ // remedy is the user's (point it at real source, or widen `--types`). The
126853
+ // disclosure below names it so the reason is not left to inference.
126854
+ status: componentTotal === 0 || walked.failures.length > 0 || args.retrieveFailures.length > 0 || profileGrantDisclosure !== null || (walked.duplicateSourcePaths?.conflicting ?? 0) > 0 || args.reportPull !== void 0 ? "partial" : "success",
126520
126855
  counts,
126521
126856
  skippedDirectories: walked.skippedDirectories,
126522
126857
  ...walked.duplicateSourcePaths !== void 0 ? { duplicateSourcePaths: walked.duplicateSourcePaths } : {},
@@ -128369,7 +128704,7 @@ var init_watch = __esm({
128369
128704
  return Math.max(ms, MIN_INTERVAL_MS);
128370
128705
  };
128371
128706
  registerWatchCommand = (program) => {
128372
- const watch = program.command("watch").description("Org-drift watcher: a small detached daemon running one read-only stale-sweep tick per interval (default 15m, floor 5m, \xB110% jitter, separate daily tick budget) so meta/staleness.json stays current. Single-instance per vault (stale pidfiles recovered). Subcommands: status, stop.").option("--interval <duration>", "Tick interval, e.g. 15m or 1h (floor 5m)", "15m").option("--auto-refresh <mode>", "When a tick detects drift, run `sfi refresh --incremental` automatically \u2014 at most once per hour; lock-safe (epoch side-build); a failure logs and the watcher continues. Mode: 'incremental'.").option("--foreground", "Run the loop in THIS process (used by the detached child)").option("--drain-demand-queue", "After a tick, drain queued automation-critical phantom hits (`meta/demand-queue.jsonl`, recorded by sfi.get_component) via the demand-retrieve gate \u2014 at most once per hour; a failure logs and the watcher continues.").action(async (flags) => {
128707
+ const watch = program.command("watch").description("Org-drift watcher: a small detached daemon running one read-only stale-sweep tick per interval (default 15m, floor 5m, \xB110% jitter, separate daily tick budget) so meta/staleness.json stays current. Single-instance per vault (stale pidfiles recovered). Subcommands: status, stop.").option("--interval <duration>", "Tick interval, e.g. 15m or 1h (floor 5m)", "15m").option("--auto-refresh <mode>", "When a tick detects drift, run `sfi refresh --incremental --incremental-graph` automatically \u2014 this CONTACTS THE ORG (`sf project retrieve`), at most once per hour; lock-safe (epoch side-build); a failure logs and the watcher continues. Mode: 'incremental'.").option("--foreground", "Run the loop in THIS process (used by the detached child)").option("--drain-demand-queue", "After a tick, drain queued automation-critical phantom hits (`meta/demand-queue.jsonl`, recorded by sfi.get_component) via the demand-retrieve gate \u2014 at most once per hour; a failure logs and the watcher continues.").action(async (flags) => {
128373
128708
  const cwd = process.cwd();
128374
128709
  const intervalMs = parseInterval(flags.interval);
128375
128710
  if (!existsSync11(join50(cwd, "org-kb", "meta"))) {
@@ -129062,7 +129397,11 @@ var handleInit = async (cwd, flags) => {
129062
129397
  }
129063
129398
  const targetOrg = await resolveTargetOrg(flags, interactive);
129064
129399
  if (targetOrg === null) {
129065
- process.stderr.write("sfi init: no --target-org given and no default Salesforce org found. Pass --target-org <alias>.\n");
129400
+ const detected = interactive ? null : await getDefaultOrgAlias();
129401
+ const hint = detected !== null ? ` Your default org is \`${detected}\` \u2014 if that is the one you want, run:
129402
+ sfi init --target-org ${detected}
129403
+ ` : " Run `sf org list` to see your authenticated orgs.\n";
129404
+ process.stderr.write("sfi init: --target-org is required when stdin is not a terminal. A vault is bound to ONE org and every later answer attaches to that binding, so the product will not pick one for you." + hint);
129066
129405
  return 1;
129067
129406
  }
129068
129407
  return runAndReport({ cwd, targetOrg, vaultRoot, force });
@@ -129072,7 +129411,7 @@ var resolveTargetOrg = async (flags, interactive) => {
129072
129411
  return flags.targetOrg;
129073
129412
  const detected = await getDefaultOrgAlias();
129074
129413
  if (!interactive)
129075
- return detected;
129414
+ return null;
129076
129415
  return input({
129077
129416
  message: "Target org alias:",
129078
129417
  ...detected !== null ? { default: detected } : {}
@@ -129145,12 +129484,24 @@ var prepareMcp = async (opts) => {
129145
129484
  const orgs = await (opts.listOrgs ?? defaultListOrgs)();
129146
129485
  const where = opts.vaultRoot !== void 0 ? ` at ${vaultRoot}` : "";
129147
129486
  const base = `No vault${where}. Run \`sfi init\` followed by \`sfi refresh\`, or point \`sfi mcp --vault <path>\` at an existing org-kb.`;
129148
- const hint = orgs.length > 0 ? ` You are authenticated to ${orgs.length} org(s): ${orgs.slice(0, 8).join(", ")}${orgs.length > 8 ? ", \u2026" : ""}. Run \`sfi init\` to pick the one you want and build its knowledge base \u2014 live answers attach to a vault's org, so the product never guesses which of your orgs to query.` : "";
129149
- return err({ kind: "no-vault", message: base + hint });
129487
+ const names = opts.discloseOrgNames === true ? `: ${orgs.slice(0, 8).join(", ")}${orgs.length > 8 ? ", \u2026" : ""}` : "";
129488
+ const hint = orgs.length > 0 ? ` You are authenticated to ${orgs.length} org(s)${names}. Run \`sfi init\` to pick the one you want and build its knowledge base \u2014 live answers attach to a vault's org, so the product never guesses which of your orgs to query.` : "";
129489
+ return err({
129490
+ kind: "no-vault",
129491
+ message: base + hint,
129492
+ vaultRoot,
129493
+ authedOrgs: orgs
129494
+ });
129150
129495
  }
129151
129496
  const ctxResult = await buildContext(vaultRoot);
129152
129497
  if (!ctxResult.ok) {
129153
- return err({ kind: "buildContext-failed", message: ctxResult.error.message });
129498
+ const boundOrg = await readBoundOrg(paths.config).catch(() => null);
129499
+ return err({
129500
+ kind: "buildContext-failed",
129501
+ message: ctxResult.error.message,
129502
+ vaultRoot,
129503
+ authedOrgs: boundOrg !== null ? [boundOrg] : []
129504
+ });
129154
129505
  }
129155
129506
  const targetOrg = await readBoundOrg(paths.config);
129156
129507
  return ok({
@@ -129180,12 +129531,25 @@ var registerMcpCommand = (program) => {
129180
129531
  const { vaultRoot: boundVault, bindSource } = resolveVaultBinding(cmdOpts.vault, process.env["SFI_VAULT"]);
129181
129532
  const prepared = await prepareMcp({
129182
129533
  cwd: process.cwd(),
129534
+ // A TTY means a human is watching this scroll past in their own
129535
+ // terminal; anything else means a host is capturing it to a log file.
129536
+ discloseOrgNames: process.stderr.isTTY === true,
129183
129537
  ...boundVault !== void 0 ? { vaultRoot: boundVault } : {}
129184
129538
  });
129185
129539
  if (!prepared.ok) {
129186
129540
  process.stderr.write(`sfi mcp: ${prepared.error.message}
129187
129541
  `);
129188
- process.exit(1);
129542
+ const setup = createSetupServer({
129543
+ reason: prepared.error.kind === "no-vault" ? "no-vault" : "vault-missing",
129544
+ detail: prepared.error.message,
129545
+ cwd: process.cwd(),
129546
+ expectedVaultRoot: prepared.error.vaultRoot,
129547
+ bindSource,
129548
+ authedOrgs: prepared.error.authedOrgs,
129549
+ version: readCliPackageVersion()
129550
+ });
129551
+ await startServer(setup);
129552
+ return;
129189
129553
  }
129190
129554
  const { ctx, server, vaultRoot, targetOrg } = prepared.value;
129191
129555
  process.env["SFI_PLUGIN_VERSION"] = readCliPackageVersion();
@@ -129219,7 +129583,7 @@ var makeShutdownOnce = (ctx) => {
129219
129583
 
129220
129584
  // dist/src/commands/demo.js
129221
129585
  init_refresh();
129222
- var buildVersion = () => true ? "0.3.1" : "dev";
129586
+ var buildVersion = () => true ? "0.3.2" : "dev";
129223
129587
  var SHUTDOWN_SIGNALS2 = ["SIGINT", "SIGTERM"];
129224
129588
  var resolveDemoSource = () => {
129225
129589
  let dir = dirname30(fileURLToPath5(import.meta.url));
@@ -129355,6 +129719,15 @@ var runStatus = async (opts) => {
129355
129719
  }
129356
129720
  const currentSourceHash = hashResult.value;
129357
129721
  if (currentSourceHash === manifest.sourceTreeHash) {
129722
+ const componentTotal = Object.values(manifest.components).reduce((sum, n) => sum + (n ?? 0), 0);
129723
+ if (componentTotal === 0) {
129724
+ return {
129725
+ kind: "fresh",
129726
+ message: "Vault built but modelled 0 components \u2014 it cannot answer any question about the org. The metadata is usually in `force-app/` while the vault reads `org-kb/source/`, or a `--types` filter excluded everything. Run `sfi doctor` for the specific check.",
129727
+ manifest,
129728
+ currentSourceHash
129729
+ };
129730
+ }
129358
129731
  return {
129359
129732
  kind: "fresh",
129360
129733
  message: "Vault is locally consistent (source hash matches last refresh). Live org drift is not checked \u2014 run `sfi live_stale_check` when you need live confirmation.",
@@ -129805,10 +130178,10 @@ var parseGapsSince = (raw, now = /* @__PURE__ */ new Date()) => {
129805
130178
  if (raw === void 0 || raw.trim() === "")
129806
130179
  return null;
129807
130180
  const trimmed = raw.trim();
129808
- const relative5 = /^(\d+)\s*(d|h|w|m)$/i.exec(trimmed);
129809
- if (relative5) {
129810
- const n = Number(relative5[1]);
129811
- const unit = (relative5[2] ?? "d").toLowerCase();
130181
+ const relative4 = /^(\d+)\s*(d|h|w|m)$/i.exec(trimmed);
130182
+ if (relative4) {
130183
+ const n = Number(relative4[1]);
130184
+ const unit = (relative4[2] ?? "d").toLowerCase();
129812
130185
  const ms = unit === "h" ? n * 36e5 : unit === "m" ? n * 6e4 : unit === "w" ? n * 7 * 864e5 : n * 864e5;
129813
130186
  return new Date(now.getTime() - ms);
129814
130187
  }
@@ -130097,7 +130470,7 @@ var statusToChangeKind = (status) => {
130097
130470
  return "modified";
130098
130471
  };
130099
130472
  var deriveComponentFromPath = (relPath) => {
130100
- const segments = relPath.split("/").filter((s) => s !== "");
130473
+ const segments = splitPathSegments(relPath);
130101
130474
  if (segments.length === 0)
130102
130475
  return null;
130103
130476
  for (const bundleDir of BUNDLE_PARENT_DIRS) {
@@ -130674,7 +131047,7 @@ init_vault_git();
130674
131047
  init_watch();
130675
131048
  var readVersion = () => {
130676
131049
  if (true)
130677
- return "0.3.1";
131050
+ return "0.3.2";
130678
131051
  const pkgUrl = new URL("../../package.json", import.meta.url);
130679
131052
  const raw = readFileSync10(fileURLToPath6(pkgUrl), "utf8");
130680
131053
  const parsed = JSON.parse(raw);