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.
- package/dist/index.js +587 -214
- package/package.json +1 -1
- 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
|
|
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
|
|
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
|
|
1252
|
-
if (
|
|
1282
|
+
const sep5 = id.indexOf(":");
|
|
1283
|
+
if (sep5 < 0)
|
|
1253
1284
|
return null;
|
|
1254
|
-
return id.slice(0,
|
|
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
|
|
1792
|
-
const type =
|
|
1793
|
-
const apiName =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
4299
|
-
return sep2 ===
|
|
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 &&
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
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
|
|
10462
|
-
if (
|
|
10502
|
+
const allSegments = splitPathSegments(filePath);
|
|
10503
|
+
if (allSegments.length < 2) {
|
|
10463
10504
|
return null;
|
|
10464
10505
|
}
|
|
10465
|
-
const segments =
|
|
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
|
|
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
|
|
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
|
-
${
|
|
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 >
|
|
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
|
-
|
|
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: ["
|
|
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
|
|
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`)
|
|
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).
|
|
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,
|
|
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 =
|
|
59836
|
-
if (omission !==
|
|
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
|
|
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
|
|
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
|
|
83521
|
-
-- isFrameworkSubclass /
|
|
83522
|
-
--
|
|
83523
|
-
--
|
|
83524
|
-
--
|
|
83525
|
-
--
|
|
83526
|
-
--
|
|
83527
|
-
--
|
|
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
|
|
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 = /\
|
|
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 =
|
|
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
|
-
|
|
92073
|
-
|
|
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,
|
|
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`)
|
|
111239
|
-
*
|
|
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
|
|
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
|
|
119231
|
-
|
|
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
|
|
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
|
-
|
|
119255
|
-
|
|
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,
|
|
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
|
-
|
|
121756
|
-
|
|
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
|
|
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
|
-
}, {
|
|
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
|
-
|
|
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) =>
|
|
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 &&
|
|
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 (
|
|
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 =
|
|
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 (
|
|
122151
|
+
if (utf8Bytes4(clone) > reductionCap) {
|
|
121974
122152
|
stringsSlimmed = slimDataStrings(clone);
|
|
121975
122153
|
}
|
|
121976
|
-
if (
|
|
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.
|
|
122095
|
-
return "0.3.
|
|
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.
|
|
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
|
|
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
|
|
123425
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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}${
|
|
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
|
|
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) +
|
|
124752
|
-
const out = resolve7(outDir) +
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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",
|
|
126099
|
-
["Dashboard",
|
|
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
|
|
126106
|
-
//
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
129149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
129809
|
-
if (
|
|
129810
|
-
const n = Number(
|
|
129811
|
-
const unit = (
|
|
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
|
|
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.
|
|
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);
|