sf-intelligence 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/sfi.js +0 -0
- package/dist/index.js +2307 -397
- package/package.json +16 -16
- package/LICENSE +0 -201
package/dist/index.js
CHANGED
|
@@ -1027,6 +1027,7 @@ var init_resolve = __esm({
|
|
|
1027
1027
|
const minBase = options?.minScore ?? MIN_BASE;
|
|
1028
1028
|
const queryTokens = tokenizeText(query);
|
|
1029
1029
|
const normQuery = normalizeName(query);
|
|
1030
|
+
const querySpaceWords = new Set(query.trim().split(/\s+/).map(normalizeName).filter((w) => w.length > 0));
|
|
1030
1031
|
let index;
|
|
1031
1032
|
try {
|
|
1032
1033
|
index = await getResolveIndex(store);
|
|
@@ -1037,6 +1038,12 @@ var init_resolve = __esm({
|
|
|
1037
1038
|
});
|
|
1038
1039
|
}
|
|
1039
1040
|
const candidateIdx = gatherCandidates(index, queryTokens, normQuery);
|
|
1041
|
+
const objectNormNames = /* @__PURE__ */ new Set();
|
|
1042
|
+
for (const n of index.nodes) {
|
|
1043
|
+
if (n.type === "CustomObject")
|
|
1044
|
+
objectNormNames.add(n.normName);
|
|
1045
|
+
}
|
|
1046
|
+
const namedObjectWords = new Set([...querySpaceWords].filter((w) => objectNormNames.has(w)));
|
|
1040
1047
|
const typeFilter = options?.types !== void 0 && options.types.length > 0 ? new Set(options.types) : null;
|
|
1041
1048
|
const parentFilter = options?.parentId;
|
|
1042
1049
|
const pass1 = [];
|
|
@@ -1079,7 +1086,8 @@ var init_resolve = __esm({
|
|
|
1079
1086
|
if (perToken[i].score > globalBest[i])
|
|
1080
1087
|
globalBest[i] = perToken[i].score;
|
|
1081
1088
|
}
|
|
1082
|
-
const
|
|
1089
|
+
const crossObjectFieldDecoy = node.type === "CustomField" && namedObjectWords.size > 0 && (node.parentApiName === null || !namedObjectWords.has(normalizeName(node.parentApiName)));
|
|
1090
|
+
const wholeExact = normQuery.length >= 2 && node.normName === normQuery && query.includes(".") === node.apiName.includes(".") && !crossObjectFieldDecoy;
|
|
1083
1091
|
pass1.push({ node, perToken, wholeExact, parentMatched });
|
|
1084
1092
|
}
|
|
1085
1093
|
const anchorIdx = [];
|
|
@@ -2488,7 +2496,7 @@ var init_finding_suppression = __esm({
|
|
|
2488
2496
|
|
|
2489
2497
|
// ../mcp/dist/src/tools/crud-fls-audit.js
|
|
2490
2498
|
import { z as z2 } from "zod";
|
|
2491
|
-
var CRUD_FLS_TOOL, CRUD_FLS_MAX_LIMIT, CRUD_FLS_DEFAULT_LIMIT, LIST_PAGE_SIZE, CRUD_FLS_RULES, SCANNED_TYPES, SEVERITY_SET, Q80_FALSE_POSITIVE_DISCLOSURE, CROSS_METHOD_DATAFLOW_DISCLOSURE, DYNAMIC_SOQL_DISCLOSURE, crudFlsAuditInputSchema, coerceIssue, compareClassById, crudFlsAuditHandler;
|
|
2499
|
+
var CRUD_FLS_TOOL, CRUD_FLS_MAX_LIMIT, CRUD_FLS_DEFAULT_LIMIT, LIST_PAGE_SIZE, CRUD_FLS_RULES, SCANNED_TYPES, SEVERITY_SET, Q80_FALSE_POSITIVE_DISCLOSURE, CROSS_METHOD_DATAFLOW_DISCLOSURE, DYNAMIC_SOQL_DISCLOSURE, crudFlsAuditInputSchema, CRUD_FLS_PAYLOAD_BUDGET_BYTES, trimEntryFindings, fitClassesToBudget, coerceIssue, compareClassById, crudFlsAuditHandler;
|
|
2492
2500
|
var init_crud_fls_audit = __esm({
|
|
2493
2501
|
"../mcp/dist/src/tools/crud-fls-audit.js"() {
|
|
2494
2502
|
"use strict";
|
|
@@ -2518,8 +2526,39 @@ var init_crud_fls_audit = __esm({
|
|
|
2518
2526
|
CROSS_METHOD_DATAFLOW_DISCLOSURE = "cross-method dataflow is invisible \u2014 a method that delegates the dangerous operation to a helper is analyzed in isolation; the helper behavior is invisible. Spot-check the call chain before treating a finding as a bug.";
|
|
2519
2527
|
DYNAMIC_SOQL_DISCLOSURE = "dynamic SOQL (Database.query) strings are stripped before pattern passes; the embedded SQL is invisible to the FLS recognizer.";
|
|
2520
2528
|
crudFlsAuditInputSchema = z2.object({
|
|
2521
|
-
limit: z2.number().int().min(1).max(CRUD_FLS_MAX_LIMIT).optional()
|
|
2529
|
+
limit: z2.number().int().min(1).max(CRUD_FLS_MAX_LIMIT).optional(),
|
|
2530
|
+
offset: z2.number().int().min(0).optional()
|
|
2522
2531
|
});
|
|
2532
|
+
CRUD_FLS_PAYLOAD_BUDGET_BYTES = 36e3;
|
|
2533
|
+
trimEntryFindings = (entry, budgetBytes) => {
|
|
2534
|
+
const kept = [];
|
|
2535
|
+
let used = Buffer.byteLength(JSON.stringify({ ...entry, findings: [] }), "utf8");
|
|
2536
|
+
for (const finding of entry.findings) {
|
|
2537
|
+
const size = Buffer.byteLength(JSON.stringify(finding), "utf8") + 1;
|
|
2538
|
+
if (kept.length > 0 && used + size > budgetBytes)
|
|
2539
|
+
break;
|
|
2540
|
+
kept.push(finding);
|
|
2541
|
+
used += size;
|
|
2542
|
+
}
|
|
2543
|
+
return { ...entry, findings: kept, findingsTruncated: true };
|
|
2544
|
+
};
|
|
2545
|
+
fitClassesToBudget = (classes, budgetBytes) => {
|
|
2546
|
+
const kept = [];
|
|
2547
|
+
let used = 0;
|
|
2548
|
+
for (const entry of classes) {
|
|
2549
|
+
const size = Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
2550
|
+
if (kept.length === 0 && size > budgetBytes) {
|
|
2551
|
+
kept.push(trimEntryFindings(entry, budgetBytes));
|
|
2552
|
+
return { kept, trimmed: true };
|
|
2553
|
+
}
|
|
2554
|
+
if (kept.length > 0 && used + size > budgetBytes) {
|
|
2555
|
+
return { kept, trimmed: true };
|
|
2556
|
+
}
|
|
2557
|
+
kept.push(entry);
|
|
2558
|
+
used += size;
|
|
2559
|
+
}
|
|
2560
|
+
return { kept, trimmed: false };
|
|
2561
|
+
};
|
|
2523
2562
|
coerceIssue = (raw) => {
|
|
2524
2563
|
if (raw === null || typeof raw !== "object")
|
|
2525
2564
|
return null;
|
|
@@ -2592,8 +2631,11 @@ var init_crud_fls_audit = __esm({
|
|
|
2592
2631
|
}
|
|
2593
2632
|
}
|
|
2594
2633
|
const classes = [...perClass.values()].sort(compareClassById);
|
|
2595
|
-
const
|
|
2596
|
-
const
|
|
2634
|
+
const offset = input2.offset ?? 0;
|
|
2635
|
+
const page = classes.slice(offset, offset + limit);
|
|
2636
|
+
const { kept, trimmed } = fitClassesToBudget(page, CRUD_FLS_PAYLOAD_BUDGET_BYTES);
|
|
2637
|
+
const returnedEnd = offset + kept.length;
|
|
2638
|
+
const truncated = returnedEnd < classes.length;
|
|
2597
2639
|
const boundaries = classes.length === 0 ? [] : [
|
|
2598
2640
|
Q80_FALSE_POSITIVE_DISCLOSURE,
|
|
2599
2641
|
CROSS_METHOD_DATAFLOW_DISCLOSURE,
|
|
@@ -2601,13 +2643,19 @@ var init_crud_fls_audit = __esm({
|
|
|
2601
2643
|
];
|
|
2602
2644
|
return ok({
|
|
2603
2645
|
data: {
|
|
2604
|
-
classes:
|
|
2646
|
+
classes: kept,
|
|
2605
2647
|
totalClassCount: classes.length,
|
|
2606
2648
|
totalFindingCount,
|
|
2607
2649
|
suppressedFindingCount,
|
|
2608
2650
|
byRule,
|
|
2609
2651
|
boundaries,
|
|
2610
|
-
|
|
2652
|
+
limit,
|
|
2653
|
+
offset,
|
|
2654
|
+
truncated,
|
|
2655
|
+
...truncated ? { nextOffset: returnedEnd } : {},
|
|
2656
|
+
...trimmed ? {
|
|
2657
|
+
note: `Response trimmed to ${kept.length} of ${page.length} classes (${classes.length} total) to stay under the ~45 KB MCP response limit. Advance with offset += ${kept.length} for the rest.`
|
|
2658
|
+
} : {}
|
|
2611
2659
|
},
|
|
2612
2660
|
vaultState: {
|
|
2613
2661
|
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
@@ -7199,7 +7247,10 @@ var init_event_subscribers = __esm({
|
|
|
7199
7247
|
"Flow"
|
|
7200
7248
|
]);
|
|
7201
7249
|
eventSubscribersInputSchema = z29.object({
|
|
7202
|
-
|
|
7250
|
+
// Optional (R0791 fix): OMIT to get the catalog of ALL Platform Events with
|
|
7251
|
+
// their subscriber counts — answers "what platform events does this org
|
|
7252
|
+
// publish?". Supply it for the subscriber list of one specific event.
|
|
7253
|
+
eventId: z29.string().min(1).optional(),
|
|
7203
7254
|
limit: z29.number().int().min(1).max(EVENT_SUBSCRIBERS_MAX_LIMIT).optional()
|
|
7204
7255
|
});
|
|
7205
7256
|
validateEventId = (eventId) => {
|
|
@@ -7234,6 +7285,46 @@ var init_event_subscribers = __esm({
|
|
|
7234
7285
|
};
|
|
7235
7286
|
compareSubscribers2 = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
7236
7287
|
eventSubscribersHandler = async (ctx, input2) => {
|
|
7288
|
+
const limit = input2.limit ?? EVENT_SUBSCRIBERS_DEFAULT_LIMIT;
|
|
7289
|
+
if (input2.eventId === void 0) {
|
|
7290
|
+
const nodesResult = await listNodesByType(ctx.graph, "CustomObject", {
|
|
7291
|
+
limit: EVENT_SUBSCRIBERS_MAX_LIMIT
|
|
7292
|
+
});
|
|
7293
|
+
if (!nodesResult.ok) {
|
|
7294
|
+
return err({ kind: "internal", message: `graph query failed: ${nodesResult.error.message}` });
|
|
7295
|
+
}
|
|
7296
|
+
const events = [];
|
|
7297
|
+
for (const node of nodesResult.value) {
|
|
7298
|
+
if (!node.apiName.endsWith(EVENT_API_NAME_SUFFIX))
|
|
7299
|
+
continue;
|
|
7300
|
+
const inEdges = await listEdges(ctx.graph, node.id, {
|
|
7301
|
+
direction: "in",
|
|
7302
|
+
edgeType: "listensTo"
|
|
7303
|
+
});
|
|
7304
|
+
if (!inEdges.ok) {
|
|
7305
|
+
return err({ kind: "internal", message: `graph query failed: ${inEdges.error.message}` });
|
|
7306
|
+
}
|
|
7307
|
+
let subscriberCount = 0;
|
|
7308
|
+
for (const edge of inEdges.value) {
|
|
7309
|
+
const sub = await getNodeById(ctx.graph, edge.fromId);
|
|
7310
|
+
if (!sub.ok) {
|
|
7311
|
+
return err({ kind: "internal", message: `graph query failed: ${sub.error.message}` });
|
|
7312
|
+
}
|
|
7313
|
+
if (sub.value !== null && SUBSCRIBER_NODE_TYPES2.has(sub.value.type)) {
|
|
7314
|
+
subscriberCount += 1;
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
events.push({ eventId: node.id, eventApiName: node.apiName, subscriberCount });
|
|
7318
|
+
}
|
|
7319
|
+
events.sort((a, b) => a.eventId < b.eventId ? -1 : a.eventId > b.eventId ? 1 : 0);
|
|
7320
|
+
return ok({
|
|
7321
|
+
data: { subscribers: [], eventApiName: null, events: events.slice(0, limit) },
|
|
7322
|
+
vaultState: {
|
|
7323
|
+
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
7324
|
+
refreshedAt: ctx.manifest.refreshedAt
|
|
7325
|
+
}
|
|
7326
|
+
});
|
|
7327
|
+
}
|
|
7237
7328
|
const apiName = validateEventId(input2.eventId);
|
|
7238
7329
|
if (apiName === null) {
|
|
7239
7330
|
return err({
|
|
@@ -7242,7 +7333,6 @@ var init_event_subscribers = __esm({
|
|
|
7242
7333
|
path: "eventId"
|
|
7243
7334
|
});
|
|
7244
7335
|
}
|
|
7245
|
-
const limit = input2.limit ?? EVENT_SUBSCRIBERS_DEFAULT_LIMIT;
|
|
7246
7336
|
const edgesResult = await listEdges(ctx.graph, input2.eventId, {
|
|
7247
7337
|
direction: "in",
|
|
7248
7338
|
edgeType: "listensTo"
|
|
@@ -7466,6 +7556,23 @@ var init_explain_apex_method = __esm({
|
|
|
7466
7556
|
}
|
|
7467
7557
|
});
|
|
7468
7558
|
|
|
7559
|
+
// ../mcp/dist/src/tools/phantom-node.js
|
|
7560
|
+
var phantomAwareNotFoundMessage;
|
|
7561
|
+
var init_phantom_node = __esm({
|
|
7562
|
+
"../mcp/dist/src/tools/phantom-node.js"() {
|
|
7563
|
+
"use strict";
|
|
7564
|
+
init_src();
|
|
7565
|
+
phantomAwareNotFoundMessage = async (ctx, id, kindLabel) => {
|
|
7566
|
+
const inbound = await listEdges(ctx.graph, id, { direction: "in" });
|
|
7567
|
+
const refs = inbound.ok ? inbound.value.length : 0;
|
|
7568
|
+
if (refs === 0) {
|
|
7569
|
+
return `no ${kindLabel} with id ${id}`;
|
|
7570
|
+
}
|
|
7571
|
+
return `\`${id}\` is referenced by ${refs} other component(s) in this org (e.g. code, tests, or permission grants) but its own ${kindLabel} definition was never retrieved into the vault \u2014 typically a managed-package component or one outside the retrieve scope. Run \`sfi refresh\` if it should be retrievable; otherwise treat it as external.`;
|
|
7572
|
+
};
|
|
7573
|
+
}
|
|
7574
|
+
});
|
|
7575
|
+
|
|
7469
7576
|
// ../mcp/dist/src/tools/explain-field.js
|
|
7470
7577
|
import { z as z31 } from "zod";
|
|
7471
7578
|
var CUSTOM_FIELD_PREFIX, CUSTOM_METADATA_DEFINITION_SUFFIX, explainFieldInputSchema, readFieldType2, readFieldFormula, readFieldReferenceTo, readFieldDescription, readFieldInlineHelpText, readFieldRequired, readFieldLabel2, parentTypeApiName2, shouldIncludeRecordValues, readFieldApiName, findValueForField, compareRecordValues, collectRecordValues, explainFieldHandler;
|
|
@@ -7474,6 +7581,7 @@ var init_explain_field = __esm({
|
|
|
7474
7581
|
"use strict";
|
|
7475
7582
|
init_dist();
|
|
7476
7583
|
init_src();
|
|
7584
|
+
init_phantom_node();
|
|
7477
7585
|
CUSTOM_FIELD_PREFIX = "CustomField:";
|
|
7478
7586
|
CUSTOM_METADATA_DEFINITION_SUFFIX = "__mdt";
|
|
7479
7587
|
explainFieldInputSchema = z31.object({
|
|
@@ -7587,7 +7695,7 @@ var init_explain_field = __esm({
|
|
|
7587
7695
|
if (node === null) {
|
|
7588
7696
|
return err({
|
|
7589
7697
|
kind: "component-not-found",
|
|
7590
|
-
message:
|
|
7698
|
+
message: await phantomAwareNotFoundMessage(ctx, input2.fieldId, "CustomField"),
|
|
7591
7699
|
path: input2.fieldId
|
|
7592
7700
|
});
|
|
7593
7701
|
}
|
|
@@ -9906,7 +10014,7 @@ var init_code_quality_patterns = __esm({
|
|
|
9906
10014
|
return [];
|
|
9907
10015
|
const issues = [];
|
|
9908
10016
|
const seen = /* @__PURE__ */ new Set();
|
|
9909
|
-
const
|
|
10017
|
+
const collect2 = (re, op, sobject) => {
|
|
9910
10018
|
const r = new RegExp(re.source, "g");
|
|
9911
10019
|
let m;
|
|
9912
10020
|
while ((m = r.exec(stripped)) !== null) {
|
|
@@ -9926,8 +10034,8 @@ var init_code_quality_patterns = __esm({
|
|
|
9926
10034
|
});
|
|
9927
10035
|
}
|
|
9928
10036
|
};
|
|
9929
|
-
|
|
9930
|
-
|
|
10037
|
+
collect2(DML_STATEMENT_PATTERN, "dml", null);
|
|
10038
|
+
collect2(DML_DATABASE_CALL_PATTERN, "dml", null);
|
|
9931
10039
|
return issues;
|
|
9932
10040
|
};
|
|
9933
10041
|
INLINE_SOQL_PATTERN = /\[\s*SELECT\b([\s\S]*?)\]/gi;
|
|
@@ -10390,14 +10498,32 @@ var init_src4 = __esm({
|
|
|
10390
10498
|
|
|
10391
10499
|
// ../mcp/dist/src/tools/field-access-audit.js
|
|
10392
10500
|
import { z as z35 } from "zod";
|
|
10393
|
-
var CUSTOM_FIELD_PREFIX3, PERMISSION_TYPE_VALUES, GRANTOR_TYPES, APEX_NODE_TYPES, fieldAccessAuditInputSchema, resolvePermissionLevel, permissionMatches, compareGrants, compareApexAccess, fieldAccessAuditHandler;
|
|
10501
|
+
var CUSTOM_FIELD_PREFIX3, synthesizeFieldNode, PERMISSION_TYPE_VALUES, GRANTOR_TYPES, APEX_NODE_TYPES, fieldAccessAuditInputSchema, resolvePermissionLevel, permissionMatches, compareGrants, compareApexAccess, fieldAccessAuditHandler;
|
|
10394
10502
|
var init_field_access_audit = __esm({
|
|
10395
10503
|
"../mcp/dist/src/tools/field-access-audit.js"() {
|
|
10396
10504
|
"use strict";
|
|
10397
10505
|
init_dist();
|
|
10398
10506
|
init_src();
|
|
10399
10507
|
init_src4();
|
|
10508
|
+
init_phantom_node();
|
|
10400
10509
|
CUSTOM_FIELD_PREFIX3 = "CustomField:";
|
|
10510
|
+
synthesizeFieldNode = (fieldId) => {
|
|
10511
|
+
const afterPrefix = fieldId.slice(CUSTOM_FIELD_PREFIX3.length);
|
|
10512
|
+
const dot = afterPrefix.lastIndexOf(".");
|
|
10513
|
+
const apiName = dot >= 0 ? afterPrefix.slice(dot + 1) : afterPrefix;
|
|
10514
|
+
return {
|
|
10515
|
+
id: fieldId,
|
|
10516
|
+
type: "CustomField",
|
|
10517
|
+
apiName,
|
|
10518
|
+
label: null,
|
|
10519
|
+
parentId: dot >= 0 ? `CustomObject:${afterPrefix.slice(0, dot)}` : null,
|
|
10520
|
+
sourcePath: "",
|
|
10521
|
+
lastModifiedDate: null,
|
|
10522
|
+
lastModifiedBy: null,
|
|
10523
|
+
apiVersion: null,
|
|
10524
|
+
properties: {}
|
|
10525
|
+
};
|
|
10526
|
+
};
|
|
10401
10527
|
PERMISSION_TYPE_VALUES = ["read", "edit", "all"];
|
|
10402
10528
|
GRANTOR_TYPES = /* @__PURE__ */ new Set([
|
|
10403
10529
|
"Profile",
|
|
@@ -10451,14 +10577,6 @@ var init_field_access_audit = __esm({
|
|
|
10451
10577
|
});
|
|
10452
10578
|
}
|
|
10453
10579
|
const fieldNode = fieldResult.value;
|
|
10454
|
-
if (fieldNode === null) {
|
|
10455
|
-
return err({
|
|
10456
|
-
kind: "component-not-found",
|
|
10457
|
-
message: `no field with id ${fieldId}`,
|
|
10458
|
-
path: fieldId
|
|
10459
|
-
});
|
|
10460
|
-
}
|
|
10461
|
-
const detection = detectPiiClassificationWithReason(fieldNode);
|
|
10462
10580
|
const grantedByResult = await listEdges(ctx.graph, fieldId, {
|
|
10463
10581
|
direction: "in",
|
|
10464
10582
|
edgeType: "grantedBy"
|
|
@@ -10478,6 +10596,16 @@ var init_field_access_audit = __esm({
|
|
|
10478
10596
|
message: `graph query failed: ${allIncomingResult.error.message}`
|
|
10479
10597
|
});
|
|
10480
10598
|
}
|
|
10599
|
+
if (fieldNode === null && allIncomingResult.value.length === 0) {
|
|
10600
|
+
return err({
|
|
10601
|
+
kind: "component-not-found",
|
|
10602
|
+
message: await phantomAwareNotFoundMessage(ctx, fieldId, "CustomField"),
|
|
10603
|
+
path: fieldId
|
|
10604
|
+
});
|
|
10605
|
+
}
|
|
10606
|
+
const notModeled = fieldNode === null;
|
|
10607
|
+
const effectiveField = fieldNode ?? synthesizeFieldNode(fieldId);
|
|
10608
|
+
const detection = detectPiiClassificationWithReason(effectiveField);
|
|
10481
10609
|
let profilesWithRead = 0;
|
|
10482
10610
|
let profilesWithEdit = 0;
|
|
10483
10611
|
let profilesWithUnknown = 0;
|
|
@@ -10559,7 +10687,11 @@ var init_field_access_audit = __esm({
|
|
|
10559
10687
|
return ok({
|
|
10560
10688
|
data: {
|
|
10561
10689
|
fieldId,
|
|
10562
|
-
fieldLabel:
|
|
10690
|
+
fieldLabel: effectiveField.label ?? effectiveField.apiName,
|
|
10691
|
+
notModeled,
|
|
10692
|
+
...notModeled ? {
|
|
10693
|
+
notModeledNote: `\`${fieldId}\`'s own field definition was not retrieved into the vault \u2014 standard fields and managed-package fields are not modeled. The grants and Apex access below are read from the permission/usage edges (accurate); the field's data type, formula, and description are unavailable, and the PII classification is inferred from the field name alone.`
|
|
10694
|
+
} : {},
|
|
10563
10695
|
piiClassification: detection.piiClassification,
|
|
10564
10696
|
piiCategory: detection.piiCategory,
|
|
10565
10697
|
grants: [...grants].sort(compareGrants),
|
|
@@ -10591,6 +10723,7 @@ var init_safe_to_delete_field = __esm({
|
|
|
10591
10723
|
init_dist();
|
|
10592
10724
|
init_src();
|
|
10593
10725
|
init_src2();
|
|
10726
|
+
init_phantom_node();
|
|
10594
10727
|
CUSTOM_FIELD_PREFIX4 = "CustomField:";
|
|
10595
10728
|
EXAMPLES_PER_CATEGORY_LIMIT = 5;
|
|
10596
10729
|
CATEGORY_ORDER = [
|
|
@@ -10799,10 +10932,49 @@ var init_safe_to_delete_field = __esm({
|
|
|
10799
10932
|
});
|
|
10800
10933
|
}
|
|
10801
10934
|
if (nodeResult.value === null) {
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
10935
|
+
const inboundResult = await listEdges(ctx.graph, fieldId, {
|
|
10936
|
+
direction: "in"
|
|
10937
|
+
});
|
|
10938
|
+
if (!inboundResult.ok) {
|
|
10939
|
+
return err({
|
|
10940
|
+
kind: "internal",
|
|
10941
|
+
message: `graph query failed: ${inboundResult.error.message}`
|
|
10942
|
+
});
|
|
10943
|
+
}
|
|
10944
|
+
if (inboundResult.value.length === 0) {
|
|
10945
|
+
return err({
|
|
10946
|
+
kind: "component-not-found",
|
|
10947
|
+
message: await phantomAwareNotFoundMessage(ctx, fieldId, "CustomField"),
|
|
10948
|
+
path: fieldId
|
|
10949
|
+
});
|
|
10950
|
+
}
|
|
10951
|
+
return ok({
|
|
10952
|
+
data: {
|
|
10953
|
+
fieldId,
|
|
10954
|
+
verdict: "review",
|
|
10955
|
+
reasoning: [
|
|
10956
|
+
{
|
|
10957
|
+
category: "unknown",
|
|
10958
|
+
verdict: "review",
|
|
10959
|
+
count: inboundResult.value.length,
|
|
10960
|
+
examples: [],
|
|
10961
|
+
note: `This field's own definition was not retrieved into the vault \u2014 standard fields and managed-package fields are not modeled. It is referenced by ${inboundResult.value.length} component(s)/grant(s). NOT proven safe to delete: a standard field cannot be deleted via metadata, and a not-modeled field cannot be fully assessed. Run \`sfi refresh\` if it should be retrievable, or treat it as external.`
|
|
10962
|
+
}
|
|
10963
|
+
],
|
|
10964
|
+
trust: {
|
|
10965
|
+
provenance: "offline_snapshot",
|
|
10966
|
+
confidence: "declared",
|
|
10967
|
+
freshness: { snapshotRefreshedAt: ctx.manifest.refreshedAt },
|
|
10968
|
+
completeness: { status: "partial" },
|
|
10969
|
+
limitations: [
|
|
10970
|
+
"The field's own definition is not in the vault (standard or managed-package field); this verdict is based on inbound references only."
|
|
10971
|
+
]
|
|
10972
|
+
}
|
|
10973
|
+
},
|
|
10974
|
+
vaultState: {
|
|
10975
|
+
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
10976
|
+
refreshedAt: ctx.manifest.refreshedAt
|
|
10977
|
+
}
|
|
10806
10978
|
});
|
|
10807
10979
|
}
|
|
10808
10980
|
if (nodeResult.value.properties["system"] === true) {
|
|
@@ -13293,6 +13465,11 @@ var init_find_dead_code = __esm({
|
|
|
13293
13465
|
FALSE) AS is_own_entry_point
|
|
13294
13466
|
FROM nodes
|
|
13295
13467
|
WHERE type IN (${placeholders})
|
|
13468
|
+
-- Standard fields (api name has no __c suffix) are platform fields:
|
|
13469
|
+
-- not deletable and not "dead code". Only custom / managed-package
|
|
13470
|
+
-- fields (which end in __c) are valid CustomField dead-code candidates
|
|
13471
|
+
-- (NI-6: stops IsPartner/IsCustomerPortal/etc. being flagged dead).
|
|
13472
|
+
AND NOT (type = 'CustomField' AND api_name NOT LIKE '%\\_\\_c' ESCAPE '\\')
|
|
13296
13473
|
),
|
|
13297
13474
|
incoming AS (
|
|
13298
13475
|
SELECT e.to_id AS cid,
|
|
@@ -16150,7 +16327,7 @@ var init_generate_architecture_overview = __esm({
|
|
|
16150
16327
|
|
|
16151
16328
|
// ../mcp/dist/src/tools/pii-inventory.js
|
|
16152
16329
|
import { z as z60 } from "zod";
|
|
16153
|
-
var PII_INVENTORY_MAX_LIMIT, PII_INVENTORY_DEFAULT_LIMIT, SCAN_PAGE_SIZE, CLASSIFICATION_FILTER_VALUES, CATEGORY_FILTER_VALUES, piiInventoryInputSchema, emptyClassificationCounts, emptyCategoryCounts, classificationMatches, categoryMatches, readDataType, readDescription, compareFields, fetchAllCustomFields2, piiInventoryHandler;
|
|
16330
|
+
var PII_INVENTORY_MAX_LIMIT, PII_INVENTORY_DEFAULT_LIMIT, SCAN_PAGE_SIZE, CLASSIFICATION_FILTER_VALUES, CATEGORY_FILTER_VALUES, piiInventoryInputSchema, PII_PAYLOAD_BUDGET_BYTES, fitFieldsToBudget, emptyClassificationCounts, emptyCategoryCounts, classificationMatches, categoryMatches, readDataType, readDescription, compareFields, fetchAllCustomFields2, piiInventoryHandler;
|
|
16154
16331
|
var init_pii_inventory = __esm({
|
|
16155
16332
|
"../mcp/dist/src/tools/pii-inventory.js"() {
|
|
16156
16333
|
"use strict";
|
|
@@ -16171,8 +16348,23 @@ var init_pii_inventory = __esm({
|
|
|
16171
16348
|
piiInventoryInputSchema = z60.object({
|
|
16172
16349
|
classification: z60.enum(CLASSIFICATION_FILTER_VALUES).optional(),
|
|
16173
16350
|
category: z60.enum(CATEGORY_FILTER_VALUES).optional(),
|
|
16174
|
-
limit: z60.number().int().min(1).max(PII_INVENTORY_MAX_LIMIT).optional()
|
|
16351
|
+
limit: z60.number().int().min(1).max(PII_INVENTORY_MAX_LIMIT).optional(),
|
|
16352
|
+
offset: z60.number().int().min(0).optional()
|
|
16175
16353
|
});
|
|
16354
|
+
PII_PAYLOAD_BUDGET_BYTES = 38e3;
|
|
16355
|
+
fitFieldsToBudget = (fields, budgetBytes) => {
|
|
16356
|
+
const kept = [];
|
|
16357
|
+
let used = 0;
|
|
16358
|
+
for (const field of fields) {
|
|
16359
|
+
const size = Buffer.byteLength(JSON.stringify(field), "utf8") + 1;
|
|
16360
|
+
if (kept.length > 0 && used + size > budgetBytes) {
|
|
16361
|
+
return { kept, trimmed: true };
|
|
16362
|
+
}
|
|
16363
|
+
kept.push(field);
|
|
16364
|
+
used += size;
|
|
16365
|
+
}
|
|
16366
|
+
return { kept, trimmed: false };
|
|
16367
|
+
};
|
|
16176
16368
|
emptyClassificationCounts = () => ({
|
|
16177
16369
|
pii: 0,
|
|
16178
16370
|
sensitive: 0,
|
|
@@ -16259,17 +16451,26 @@ var init_pii_inventory = __esm({
|
|
|
16259
16451
|
});
|
|
16260
16452
|
}
|
|
16261
16453
|
const sorted = [...matched].sort(compareFields);
|
|
16262
|
-
const
|
|
16263
|
-
const
|
|
16454
|
+
const offset = input2.offset ?? 0;
|
|
16455
|
+
const page = sorted.slice(offset, offset + limit);
|
|
16456
|
+
const { kept, trimmed } = fitFieldsToBudget(page, PII_PAYLOAD_BUDGET_BYTES);
|
|
16457
|
+
const returnedEnd = offset + kept.length;
|
|
16458
|
+
const truncated = returnedEnd < sorted.length;
|
|
16264
16459
|
return ok({
|
|
16265
16460
|
data: {
|
|
16266
|
-
fields,
|
|
16461
|
+
fields: kept,
|
|
16267
16462
|
summary: {
|
|
16268
16463
|
total: matched.length,
|
|
16269
16464
|
byClassification,
|
|
16270
16465
|
byCategory
|
|
16271
16466
|
},
|
|
16272
|
-
|
|
16467
|
+
limit,
|
|
16468
|
+
offset,
|
|
16469
|
+
truncated,
|
|
16470
|
+
...truncated ? { nextOffset: returnedEnd } : {},
|
|
16471
|
+
...trimmed ? {
|
|
16472
|
+
note: `Response trimmed to ${kept.length} of ${page.length} matched fields to stay under the ~45 KB MCP response limit. Advance with offset += ${kept.length} for the rest.`
|
|
16473
|
+
} : {}
|
|
16273
16474
|
},
|
|
16274
16475
|
vaultState: {
|
|
16275
16476
|
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
@@ -16983,6 +17184,7 @@ var init_get_component = __esm({
|
|
|
16983
17184
|
init_dist();
|
|
16984
17185
|
init_src();
|
|
16985
17186
|
init_src2();
|
|
17187
|
+
init_phantom_node();
|
|
16986
17188
|
DEFAULT_COMPONENT_BODY_MAX_BYTES = 3e4;
|
|
16987
17189
|
getComponentInputSchema = z64.object({
|
|
16988
17190
|
id: z64.string().min(1),
|
|
@@ -16997,9 +17199,10 @@ var init_get_component = __esm({
|
|
|
16997
17199
|
});
|
|
16998
17200
|
}
|
|
16999
17201
|
if (nodeResult.value === null) {
|
|
17202
|
+
const kindLabel = input2.id.includes(":") ? input2.id.slice(0, input2.id.indexOf(":")) : "component";
|
|
17000
17203
|
return err({
|
|
17001
17204
|
kind: "component-not-found",
|
|
17002
|
-
message:
|
|
17205
|
+
message: await phantomAwareNotFoundMessage(ctx, input2.id, kindLabel),
|
|
17003
17206
|
path: input2.id
|
|
17004
17207
|
});
|
|
17005
17208
|
}
|
|
@@ -17454,11 +17657,201 @@ var init_get_subgraph = __esm({
|
|
|
17454
17657
|
}
|
|
17455
17658
|
});
|
|
17456
17659
|
|
|
17660
|
+
// ../mcp/dist/src/knowledge-topics.js
|
|
17661
|
+
var TRAILHEAD, HELP, KNOWLEDGE_TOPICS, resolveTopicKey;
|
|
17662
|
+
var init_knowledge_topics = __esm({
|
|
17663
|
+
"../mcp/dist/src/knowledge-topics.js"() {
|
|
17664
|
+
"use strict";
|
|
17665
|
+
TRAILHEAD = { label: "Trailhead", url: "https://trailhead.salesforce.com" };
|
|
17666
|
+
HELP = { label: "Salesforce Help", url: "https://help.salesforce.com" };
|
|
17667
|
+
KNOWLEDGE_TOPICS = Object.freeze({
|
|
17668
|
+
"flow-vs-apex": {
|
|
17669
|
+
title: "Flow vs Apex \u2014 when to use which",
|
|
17670
|
+
summary: "Prefer declarative record-triggered Flow for most automation; reach for an Apex trigger only when you need logic Flow cannot do well: complex bulk processing, callouts with fine control, recursion management, custom error handling, or operations across many records/objects in one transaction. Keep one record-triggered Flow (or one trigger) per object/timing and delegate to a handler. Avoid mixing Workflow Rules / Process Builder with Flow on the same object.",
|
|
17671
|
+
docs: [
|
|
17672
|
+
{ label: "Record-Triggered Automation (Architects)", url: "https://architect.salesforce.com/decision-guides/trigger-automation" },
|
|
17673
|
+
TRAILHEAD
|
|
17674
|
+
]
|
|
17675
|
+
},
|
|
17676
|
+
"order-of-execution": {
|
|
17677
|
+
title: "Apex order of execution on save",
|
|
17678
|
+
summary: `On save Salesforce runs, in order: system validation, before-save record-triggered flows, before triggers, system + custom validation rules, duplicate rules, after triggers, assignment/auto-response/workflow rules, processes & after-save flows, escalation rules, roll-up summary recalculation, sharing recalculation, then commit and post-commit (async, email, @future). Knowing the order explains why a field looks stale or an automation "didn't fire".`,
|
|
17679
|
+
docs: [
|
|
17680
|
+
{ label: "Apex Developer Guide \u2014 Triggers and Order of Execution", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_order_of_execution.htm" }
|
|
17681
|
+
]
|
|
17682
|
+
},
|
|
17683
|
+
"governor-limits": {
|
|
17684
|
+
title: "Apex governor limits to design around",
|
|
17685
|
+
summary: "Per-transaction limits force bulk-safe design: 100 SOQL queries (200 async), 150 DML statements, 50,000 rows retrieved, 10s sync / 60s async CPU time, 6 MB sync / 12 MB async heap, 100 callouts, 10 emails. Never put SOQL/DML inside loops; bulkify; query selectively; move heavy work to Batch/Queueable.",
|
|
17686
|
+
docs: [
|
|
17687
|
+
{ label: "Apex Developer Guide \u2014 Execution Governors and Limits", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm" }
|
|
17688
|
+
]
|
|
17689
|
+
},
|
|
17690
|
+
"async-apex": {
|
|
17691
|
+
title: "Asynchronous Apex options",
|
|
17692
|
+
summary: "Future (@future): simplest fire-and-forget, primitives only, for callouts/decoupling \u2014 no chaining, no monitoring. Queueable: like future but accepts objects, can chain, returns a job id. Batch (Database.Batchable): process millions of records in chunks (start/execute/finish), higher limits. Schedulable: run Apex on a cron schedule (often to enqueue a Batch). Choose by data volume, need to chain/monitor, and scheduling.",
|
|
17693
|
+
docs: [
|
|
17694
|
+
{ label: "Apex Developer Guide \u2014 Asynchronous Apex", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_async_overview.htm" },
|
|
17695
|
+
TRAILHEAD
|
|
17696
|
+
]
|
|
17697
|
+
},
|
|
17698
|
+
"trigger-framework": {
|
|
17699
|
+
title: "Apex trigger framework pattern",
|
|
17700
|
+
summary: "Adopt one-trigger-per-object that immediately delegates to a handler class; keep zero business logic in the trigger body. A framework gives you context routing (before/after, insert/update/...), recursion control (static guards), and bulk-safe handler methods. Popular patterns: a simple hand-rolled handler base class, or community frameworks. Consistency matters more than which framework.",
|
|
17701
|
+
docs: [
|
|
17702
|
+
{ label: "Apex Developer Guide \u2014 Triggers", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers.htm" },
|
|
17703
|
+
TRAILHEAD
|
|
17704
|
+
]
|
|
17705
|
+
},
|
|
17706
|
+
"bulkification": {
|
|
17707
|
+
title: "Bulkified Apex best practices",
|
|
17708
|
+
summary: "Assume every trigger/method processes up to 200 records. Query once with maps keyed by id, never inside loops; collect DML into lists and do one insert/update outside loops; use collections and Maps for lookups; pass record collections, not single records. Test with \u2265200-record bulk data, not one record.",
|
|
17709
|
+
docs: [
|
|
17710
|
+
{ label: "Apex Developer Guide \u2014 Bulk Trigger Idioms", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_bulk_idioms.htm" }
|
|
17711
|
+
]
|
|
17712
|
+
},
|
|
17713
|
+
"apex-testing": {
|
|
17714
|
+
title: "Apex testing, coverage, and test data",
|
|
17715
|
+
summary: "Production deploys require \u226575% org-wide Apex coverage and all tests passing (each trigger must have some coverage). Coverage is a floor, not the goal: assert real behavior with System.assertEquals on distinct expected/actual values. Build test data in @isTest test-data-factory classes; use Test.startTest/stopTest for limits and async; avoid SeeAllData=true. Structure: one test class per class, descriptive methods, positive + negative + bulk cases.",
|
|
17716
|
+
docs: [
|
|
17717
|
+
{ label: "Apex Developer Guide \u2014 Testing Apex", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing.htm" }
|
|
17718
|
+
]
|
|
17719
|
+
},
|
|
17720
|
+
"apex-callouts": {
|
|
17721
|
+
title: "Making external callouts from Apex",
|
|
17722
|
+
summary: "Use Named Credentials for endpoints + auth (never hardcode URLs/secrets); call via HttpRequest/HttpResponse or external services. Callouts cannot follow uncommitted DML in the same transaction \u2014 do them before DML or from async (@future(callout=true)/Queueable). Set timeouts, handle non-200s, and write tests with HttpCalloutMock.",
|
|
17723
|
+
docs: [
|
|
17724
|
+
{ label: "Apex Developer Guide \u2014 Invoking Callouts Using Apex", url: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts.htm" }
|
|
17725
|
+
]
|
|
17726
|
+
},
|
|
17727
|
+
"sfdx-source-driven-dev": {
|
|
17728
|
+
title: "Source-driven development with SFDX",
|
|
17729
|
+
summary: "Treat metadata as source in version control. Use a Salesforce DX project (sfdx-project.json), scratch orgs for feature development, and the `sf` CLI to retrieve/deploy/convert source. Pair with a CI/CD pipeline (validate + run tests on PR, deploy on merge) and unlocked packages for modular delivery.",
|
|
17730
|
+
docs: [
|
|
17731
|
+
{ label: "Salesforce DX Developer Guide", url: "https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_intro.htm" },
|
|
17732
|
+
TRAILHEAD
|
|
17733
|
+
]
|
|
17734
|
+
},
|
|
17735
|
+
"package-strategy": {
|
|
17736
|
+
title: "Unlocked packages & modular structure",
|
|
17737
|
+
summary: "Decompose the org into unlocked packages aligned to functional domains, each with a clear dependency direction (no cycles). Packages give versioning, clean install/upgrade, and ownership boundaries. Start with a small number of coarse packages; split as ownership and dependencies clarify. Keep a base/common package for shared components.",
|
|
17738
|
+
docs: [
|
|
17739
|
+
{ label: "SFDX \u2014 Unlocked Packages", url: "https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_unlocked_pkg_intro.htm" },
|
|
17740
|
+
TRAILHEAD
|
|
17741
|
+
]
|
|
17742
|
+
},
|
|
17743
|
+
"profiles-vs-permission-sets": {
|
|
17744
|
+
title: "Profiles vs Permission Sets",
|
|
17745
|
+
summary: 'Modern best practice: keep profiles minimal (login hours, default record types, page layouts as needed) and grant capabilities via Permission Sets and Permission Set Groups. This avoids "profile proliferation", makes access additive and auditable, and lets you assign least-privilege bundles per role. Salesforce is moving permissions toward permission sets.',
|
|
17746
|
+
docs: [HELP, TRAILHEAD]
|
|
17747
|
+
},
|
|
17748
|
+
"owd-sharing-model": {
|
|
17749
|
+
title: "OWD & the sharing model (greenfield)",
|
|
17750
|
+
summary: "Set Org-Wide Defaults to the most restrictive level each object needs (Private / Public Read Only / Public Read-Write), then OPEN UP access with the role hierarchy, sharing rules (ownership- or criteria-based), permission sets (View All/Modify All), teams, and manual/Apex sharing. Design least-privilege: start Private and grant deliberately. Plan sharing recalculation cost for large data volumes.",
|
|
17751
|
+
docs: [HELP, TRAILHEAD]
|
|
17752
|
+
},
|
|
17753
|
+
"standard-vs-custom-objects": {
|
|
17754
|
+
title: "Standard vs custom objects",
|
|
17755
|
+
summary: "Use standard objects (Account, Contact, Opportunity, Case, Lead, ...) whenever they fit \u2014 they come with built-in features, reports, and integrations. Create custom objects only for concepts standard objects do not represent. Prefer extending a standard object with custom fields over cloning it. Consider the data model, relationships, and license implications before adding custom objects.",
|
|
17756
|
+
docs: [HELP, TRAILHEAD]
|
|
17757
|
+
},
|
|
17758
|
+
"naming-conventions": {
|
|
17759
|
+
title: "Naming & documentation conventions",
|
|
17760
|
+
summary: "Establish conventions from day one: consistent API names (PascalCase objects, clear field suffixes), prefixes for app/domain, description + help-text on every field, and a documented automation/trigger naming scheme. Conventions make the org self-describing and reduce technical debt. Record them in a living standards doc.",
|
|
17761
|
+
docs: [TRAILHEAD, HELP]
|
|
17762
|
+
},
|
|
17763
|
+
"sandbox-environment-strategy": {
|
|
17764
|
+
title: "Sandbox & environment strategy",
|
|
17765
|
+
summary: "Use a tiered environment path: developer/scratch orgs for build, an integration/partial sandbox for merged work, a full or partial sandbox for UAT/staging, then production. Match sandbox type to need (Developer/Developer Pro for config/code, Partial/Full for data-dependent testing). Automate refresh + seeding and gate promotion with CI tests.",
|
|
17766
|
+
docs: [HELP, TRAILHEAD]
|
|
17767
|
+
},
|
|
17768
|
+
"single-vs-multi-org": {
|
|
17769
|
+
title: "Single-org vs multi-org strategy",
|
|
17770
|
+
summary: "Prefer a single org when business units share processes, data, and customers \u2014 it maximizes the 360\xB0 view and minimizes integration/duplication cost. Choose multi-org when regulatory isolation, radically different processes, divestiture risk, or per-region governance demand hard boundaries. Weigh org limits, governance maturity, and the cost of cross-org integration before splitting.",
|
|
17771
|
+
docs: [{ label: "Architect decision guides", url: "https://architect.salesforce.com/decision-guides" }, TRAILHEAD]
|
|
17772
|
+
},
|
|
17773
|
+
"data-retention-archiving": {
|
|
17774
|
+
title: "Data retention & archiving strategy",
|
|
17775
|
+
summary: "Plan retention up front: classify data by regulatory/operational need, define how long each object is kept hot, and archive or purge the rest. Options include Big Objects for low-cost archival, scheduled batch purges, external/offline archives, and field-history/event-log retention settings. Designing this early prevents storage-limit and LDV pain later.",
|
|
17776
|
+
docs: [HELP, TRAILHEAD]
|
|
17777
|
+
},
|
|
17778
|
+
"large-data-volumes": {
|
|
17779
|
+
title: "Large data volumes (LDV) planning",
|
|
17780
|
+
summary: "At millions of records, design for selectivity: indexed/selective filters, skinny tables (via Support) for hot reports, careful sharing (ownership/data skew avoidance), Big Objects for archival, and bulk/async data loads. Watch for non-selective SOQL, account/ownership skew, and sharing-recalculation cost. Plan partitioning and reporting strategy before volume arrives.",
|
|
17781
|
+
docs: [{ label: "LDV Best Practices (Architects)", url: "https://architect.salesforce.com/well-architected/adaptable/resilient" }, TRAILHEAD]
|
|
17782
|
+
},
|
|
17783
|
+
"release-management": {
|
|
17784
|
+
title: "Release management & deployment pipeline",
|
|
17785
|
+
summary: "Establish a source-of-truth VCS, environment promotion path (dev \u2192 integration \u2192 UAT \u2192 prod), and a CI/CD pipeline that validates + runs Apex tests on every change and deploys on merge. Use unlocked packages or change sets/metadata API, automate with the `sf` CLI or a DevOps tool, and keep deployments small and frequent. Gate prod with required coverage + review.",
|
|
17786
|
+
docs: [TRAILHEAD, { label: "Salesforce DX Developer Guide", url: "https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_intro.htm" }]
|
|
17787
|
+
},
|
|
17788
|
+
"well-architected": {
|
|
17789
|
+
title: "Salesforce Well-Architected principles",
|
|
17790
|
+
summary: "Salesforce Well-Architected frames a healthy org as Trusted (secure, compliant, reliable), Easy (intentional, automated, resilient \u2014 minimal complexity/debt), and Adaptable (composable, scalable). Use it as a checklist when designing: least-privilege security, consolidated automation, documented conventions, modular packaging, and scalable data design.",
|
|
17791
|
+
docs: [{ label: "Salesforce Well-Architected", url: "https://architect.salesforce.com/well-architected/overview" }, TRAILHEAD]
|
|
17792
|
+
},
|
|
17793
|
+
"license-types": {
|
|
17794
|
+
title: "License types & planning",
|
|
17795
|
+
summary: "Match license types to user needs: full Salesforce/CRM licenses for internal power users, Platform licenses for users who only need custom apps + a few standard objects, and Experience Cloud licenses for external community users. Permission Set Licenses add capabilities on top. Count licenses by persona early \u2014 they drive cost and constrain object/feature access.",
|
|
17796
|
+
docs: [HELP, TRAILHEAD]
|
|
17797
|
+
},
|
|
17798
|
+
"integration-patterns": {
|
|
17799
|
+
title: "Integration patterns",
|
|
17800
|
+
summary: "Pick the pattern by need: Request-Reply (synchronous UI callouts), Fire-and-Forget (Platform Events / outbound), Batch Data Sync (scheduled bulk), Remote Call-In (inbound REST/SOAP API), and UI Update from external (streaming/CDC). Prefer middleware/event-driven over point-to-point at scale; use Named Credentials for auth and design idempotent retries + error handling.",
|
|
17801
|
+
docs: [{ label: "Integration Patterns (Architects)", url: "https://architect.salesforce.com/decision-guides/integrate-salesforce" }, TRAILHEAD]
|
|
17802
|
+
}
|
|
17803
|
+
});
|
|
17804
|
+
resolveTopicKey = (topic) => {
|
|
17805
|
+
const t = topic.trim().toLowerCase();
|
|
17806
|
+
if (t.length === 0)
|
|
17807
|
+
return null;
|
|
17808
|
+
const keys = Object.keys(KNOWLEDGE_TOPICS);
|
|
17809
|
+
if (keys.includes(t))
|
|
17810
|
+
return t;
|
|
17811
|
+
const hit = keys.find((k) => k.includes(t) || t.includes(k) || KNOWLEDGE_TOPICS[k].title.toLowerCase().includes(t));
|
|
17812
|
+
return hit ?? null;
|
|
17813
|
+
};
|
|
17814
|
+
}
|
|
17815
|
+
});
|
|
17816
|
+
|
|
17817
|
+
// ../mcp/dist/src/tools/guidance.js
|
|
17818
|
+
import { z as z68 } from "zod";
|
|
17819
|
+
var guidanceInputSchema, DISCLOSURE6, guidanceHandler;
|
|
17820
|
+
var init_guidance = __esm({
|
|
17821
|
+
"../mcp/dist/src/tools/guidance.js"() {
|
|
17822
|
+
"use strict";
|
|
17823
|
+
init_dist();
|
|
17824
|
+
init_knowledge_topics();
|
|
17825
|
+
guidanceInputSchema = z68.object({
|
|
17826
|
+
topic: z68.string().min(1).optional()
|
|
17827
|
+
});
|
|
17828
|
+
DISCLOSURE6 = "General Salesforce best-practice guidance with links to official docs \u2014 NOT specific to this org or its metadata. For org-specific answers, use the vault tools (schema, automation, permissions, impact) or the opt-in live plane.";
|
|
17829
|
+
guidanceHandler = async (ctx, input2) => {
|
|
17830
|
+
const availableTopics = Object.entries(KNOWLEDGE_TOPICS).map(([key2, t]) => ({ key: key2, title: t.title }));
|
|
17831
|
+
const key = input2.topic !== void 0 ? resolveTopicKey(input2.topic) : null;
|
|
17832
|
+
const guidance = key !== null ? KNOWLEDGE_TOPICS[key] ?? null : null;
|
|
17833
|
+
return ok({
|
|
17834
|
+
data: {
|
|
17835
|
+
topic: key,
|
|
17836
|
+
guidance,
|
|
17837
|
+
matched: guidance !== null,
|
|
17838
|
+
availableTopics,
|
|
17839
|
+
disclosure: DISCLOSURE6
|
|
17840
|
+
},
|
|
17841
|
+
vaultState: {
|
|
17842
|
+
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
17843
|
+
refreshedAt: ctx.manifest.refreshedAt
|
|
17844
|
+
}
|
|
17845
|
+
});
|
|
17846
|
+
};
|
|
17847
|
+
}
|
|
17848
|
+
});
|
|
17849
|
+
|
|
17457
17850
|
// ../mcp/dist/src/tools/health-check.js
|
|
17458
17851
|
import { existsSync as existsSync3 } from "node:fs";
|
|
17459
17852
|
import { stat as stat4 } from "node:fs/promises";
|
|
17460
17853
|
import { join as join7 } from "node:path";
|
|
17461
|
-
import { z as
|
|
17854
|
+
import { z as z69 } from "zod";
|
|
17462
17855
|
var SKIPPED_FILES_DEGRADED_THRESHOLD, healthCheckInputSchema, probeGraph, probeSourceHash, probeRenderComplete, healthCheckHandler;
|
|
17463
17856
|
var init_health_check = __esm({
|
|
17464
17857
|
"../mcp/dist/src/tools/health-check.js"() {
|
|
@@ -17467,7 +17860,7 @@ var init_health_check = __esm({
|
|
|
17467
17860
|
init_src();
|
|
17468
17861
|
init_src2();
|
|
17469
17862
|
SKIPPED_FILES_DEGRADED_THRESHOLD = 100;
|
|
17470
|
-
healthCheckInputSchema =
|
|
17863
|
+
healthCheckInputSchema = z69.object({});
|
|
17471
17864
|
probeGraph = async (ctx) => {
|
|
17472
17865
|
try {
|
|
17473
17866
|
const result = await listNodesByType(ctx.graph, "CustomObject", { limit: 1 });
|
|
@@ -17587,7 +17980,7 @@ var init_health_check = __esm({
|
|
|
17587
17980
|
// ../mcp/dist/src/tools/integration-procedure-chain.js
|
|
17588
17981
|
import { readFile as readFile10 } from "node:fs/promises";
|
|
17589
17982
|
import { XMLParser as XMLParser3, XMLValidator as XMLValidator3 } from "fast-xml-parser";
|
|
17590
|
-
import { z as
|
|
17983
|
+
import { z as z70 } from "zod";
|
|
17591
17984
|
var IP_PREFIX, DATA_TRANSFORM_PREFIX, ROOT_ELEMENT3, DATARAPTOR_ACTION_TYPES, REST_ACTION_TYPE, NESTED_IP_ACTION_TYPE, REMOTE_ACTION_TYPE, RESPONSE_ACTION_TYPE, NATIVE_VS_VLOCITY_DISCLOSURE2, APEX_COUPLING_DEFERRAL_DISCLOSURE, RECORD_LEVEL_BOUNDARY_DISCLOSURE, REST_REACHABILITY_DISCLOSURE, integrationProcedureChainInputSchema, unwrapSingle3, toArray3, coerceBoolean2, coerceNumber, nonEmptyString, parsePropertySetConfig, BOUNDARIES_VERBATIM, integrationProcedureChainHandler, walkActions, resolveExternalEndpoints, readNodeStringProperty, readNodeNumberProperty, readNodeBooleanProperty;
|
|
17592
17985
|
var init_integration_procedure_chain = __esm({
|
|
17593
17986
|
"../mcp/dist/src/tools/integration-procedure-chain.js"() {
|
|
@@ -17611,9 +18004,9 @@ var init_integration_procedure_chain = __esm({
|
|
|
17611
18004
|
APEX_COUPLING_DEFERRAL_DISCLOSURE = "v3.2 captures OmniStudio components and intra-OmniStudio call chains (`dispatchesOmniAction`). The Apex-to-OmniProcess coupling (`implements omnistudio.VlocityOpenInterface` etc.) is a v3.3 follow-up \u2014 those edges are NOT yet in the graph.";
|
|
17612
18005
|
RECORD_LEVEL_BOUNDARY_DISCLOSURE = "v3.2 walks the OmniScript / IP / Card metadata XML. The actual user-entered data and runtime state lives in OmniProcessElement and related SObject records; that is record-level data, out of scope for v0.1's read-the-metadata posture.";
|
|
17613
18006
|
REST_REACHABILITY_DISCLOSURE = "REST endpoint URLs are surfaced with `parsed` confidence (from the propertySetConfig JSON blob); v3.2 does NOT probe the URL, verify the endpoint is reachable, or resolve the Named Credential against live state.";
|
|
17614
|
-
integrationProcedureChainInputSchema =
|
|
17615
|
-
integrationProcedureId:
|
|
17616
|
-
includeChildPropertySetConfig:
|
|
18007
|
+
integrationProcedureChainInputSchema = z70.object({
|
|
18008
|
+
integrationProcedureId: z70.string().min(1),
|
|
18009
|
+
includeChildPropertySetConfig: z70.boolean().optional()
|
|
17617
18010
|
});
|
|
17618
18011
|
unwrapSingle3 = (value) => Array.isArray(value) ? value[0] : value;
|
|
17619
18012
|
toArray3 = (value) => {
|
|
@@ -17915,7 +18308,7 @@ var init_integration_procedure_chain = __esm({
|
|
|
17915
18308
|
});
|
|
17916
18309
|
|
|
17917
18310
|
// ../mcp/dist/src/tools/last-modified.js
|
|
17918
|
-
import { z as
|
|
18311
|
+
import { z as z71 } from "zod";
|
|
17919
18312
|
var LAST_MODIFIED_UNENRICHED_DISCLOSURE, LAST_MODIFIED_ENRICHED_DISCLOSURE, lastModifiedInputSchema, extractLastModifiedDate2, extractLastModifiedBy2, extractApiVersion, lastModifiedHandler;
|
|
17920
18313
|
var init_last_modified = __esm({
|
|
17921
18314
|
"../mcp/dist/src/tools/last-modified.js"() {
|
|
@@ -17924,8 +18317,8 @@ var init_last_modified = __esm({
|
|
|
17924
18317
|
init_src();
|
|
17925
18318
|
LAST_MODIFIED_UNENRICHED_DISCLOSURE = "v1.7 Tooling API enrichment has not run for this vault. Run `sfi refresh --with-tooling-api --target-org <alias>` to populate lastModifiedDate / lastModifiedBy / apiVersion for the enriched types.";
|
|
17926
18319
|
LAST_MODIFIED_ENRICHED_DISCLOSURE = "Freshness fields populated. lastModifiedDate / lastModifiedBy reflect the Tooling API at enrichment time (or the DX-source extractor when the type carries a `<lastModifiedDate>` element).";
|
|
17927
|
-
lastModifiedInputSchema =
|
|
17928
|
-
componentId:
|
|
18320
|
+
lastModifiedInputSchema = z71.object({
|
|
18321
|
+
componentId: z71.string().min(1)
|
|
17929
18322
|
});
|
|
17930
18323
|
extractLastModifiedDate2 = (legacy, properties) => {
|
|
17931
18324
|
const propsValue = properties["lastModifiedDate"];
|
|
@@ -18001,17 +18394,17 @@ var init_last_modified = __esm({
|
|
|
18001
18394
|
});
|
|
18002
18395
|
|
|
18003
18396
|
// ../mcp/dist/src/tools/layout-for-user.js
|
|
18004
|
-
import { z as
|
|
18397
|
+
import { z as z72 } from "zod";
|
|
18005
18398
|
var layoutForUserInputSchema, step, evaluateProfileLookup, readLayoutAssignments, layoutTargetsObject, canonicaliseLayoutId, findLayoutAssignment, pickFlexiPageForObject, evaluateLightningPageLookup, layoutForUserHandler;
|
|
18006
18399
|
var init_layout_for_user = __esm({
|
|
18007
18400
|
"../mcp/dist/src/tools/layout-for-user.js"() {
|
|
18008
18401
|
"use strict";
|
|
18009
18402
|
init_dist();
|
|
18010
18403
|
init_src();
|
|
18011
|
-
layoutForUserInputSchema =
|
|
18012
|
-
objectApiName:
|
|
18013
|
-
recordTypeId:
|
|
18014
|
-
profileId:
|
|
18404
|
+
layoutForUserInputSchema = z72.object({
|
|
18405
|
+
objectApiName: z72.string().min(1),
|
|
18406
|
+
recordTypeId: z72.string().min(1).optional(),
|
|
18407
|
+
profileId: z72.string().min(1)
|
|
18015
18408
|
});
|
|
18016
18409
|
step = (stage, verdict, reason, value) => value === void 0 ? { stage, verdict, reason } : { stage, verdict, reason, value };
|
|
18017
18410
|
evaluateProfileLookup = async (ctx, profileId) => {
|
|
@@ -18229,7 +18622,7 @@ var init_layout_for_user = __esm({
|
|
|
18229
18622
|
});
|
|
18230
18623
|
|
|
18231
18624
|
// ../mcp/dist/src/tools/list-components.js
|
|
18232
|
-
import { z as
|
|
18625
|
+
import { z as z73 } from "zod";
|
|
18233
18626
|
var COMPONENT_TYPES2, LIST_MAX_LIMIT2, LIST_DEFAULT_LIMIT2, LIST_PAYLOAD_BUDGET_BYTES, fitNodesToBudget, listComponentsInputSchema, listComponentsHandler;
|
|
18234
18627
|
var init_list_components = __esm({
|
|
18235
18628
|
"../mcp/dist/src/tools/list-components.js"() {
|
|
@@ -18338,11 +18731,11 @@ var init_list_components = __esm({
|
|
|
18338
18731
|
}
|
|
18339
18732
|
return { kept, trimmed: false };
|
|
18340
18733
|
};
|
|
18341
|
-
listComponentsInputSchema =
|
|
18342
|
-
type:
|
|
18343
|
-
parentId:
|
|
18344
|
-
limit:
|
|
18345
|
-
offset:
|
|
18734
|
+
listComponentsInputSchema = z73.object({
|
|
18735
|
+
type: z73.enum(COMPONENT_TYPES2).optional(),
|
|
18736
|
+
parentId: z73.string().min(1).optional(),
|
|
18737
|
+
limit: z73.number().int().min(1).max(LIST_MAX_LIMIT2).optional(),
|
|
18738
|
+
offset: z73.number().int().min(0).optional()
|
|
18346
18739
|
});
|
|
18347
18740
|
listComponentsHandler = async (ctx, input2) => {
|
|
18348
18741
|
if (input2.type === void 0) {
|
|
@@ -18969,7 +19362,7 @@ var init_live_consent = __esm({
|
|
|
18969
19362
|
// ../mcp/dist/src/tools/live-plane.js
|
|
18970
19363
|
import { execFile as execFile2 } from "node:child_process";
|
|
18971
19364
|
import { promisify as promisify2 } from "node:util";
|
|
18972
|
-
import { z as
|
|
19365
|
+
import { z as z74 } from "zod";
|
|
18973
19366
|
var nodeExecFile2, redactSecrets, LIVE_PLANE_DISCLOSURE, MAX_SAMPLE_ROWS, liveEnabledSchema, isLivePlaneEnabled, liveTrust, resolveOrg, resolveLiveAccess, liveConsentRequiredError, gateLive, getLiveAuth, runSfJson, apiPath, restGet, liveDescribeInputSchema, liveDescribeHandler, liveCountInputSchema, OBJECT_API_NAME_RE, resolveCountSoql, assertCountSoql, liveCountHandler, liveSampleInputSchema, capSampleSoql, liveSampleHandler, liveFieldPopulationInputSchema, liveFieldPopulationHandler, liveOrgLimitsInputSchema, liveOrgLimitsHandler, MAX_INACTIVE_USER_ROWS, DEFAULT_INACTIVE_DAYS, MS_PER_DAY, liveInactiveUsersInputSchema, soqlDateTime, liveInactiveUsersHandler, DEFAULT_LICENSE_INACTIVE_DAYS, MAX_RECLAIM_ROWS, LICENSE_USAGE_DISCLOSURE, liveLicenseUsageInputSchema, toUtilization, renderLicenseUsageMarkdown, liveLicenseUsageHandler, liveConsentInputSchema, consentTrust, liveConsentHandler, liveQuery, MAX_DETAIL_ROWS, daysAgoSoql, daysSince, livePlaneVaultState, UNAVAILABLE_ERROR, assertSoqlIdentifier, soqlLiteral, MAX_GROUP_BUCKETS, DEFAULT_STALE_DAYS, DEFAULT_RECENT_DAYS, liveGroupCountInputSchema, liveGroupCountHandler, liveStaleRecordsInputSchema, liveStaleRecordsHandler, liveRecentActivityInputSchema, liveRecentActivityHandler, buildEqualityWhere, aggregateCountFromRow, liveAggregateInputSchema, liveAggregateHandler, MAX_DUPLICATE_GROUPS, liveDuplicateCheckInputSchema, liveDuplicateCheckHandler, MAX_OWNER_BUCKETS, liveOwnerBreakdownInputSchema, liveOwnerBreakdownHandler, DEFAULT_REPORT_STALE_DAYS, liveReportUsageInputSchema, liveReportUsageHandler, liveFolderAccessInputSchema, liveFolderAccessHandler, DEFAULT_TEMPLATE_STALE_DAYS, CLASSIC_TEMPLATE_TYPES, liveEmailTemplateUsageInputSchema, liveEmailTemplateUsageHandler, DEFAULT_HEALTH_DAYS, LIMIT_RISK_THRESHOLD, liveOrgHealthInputSchema, liveOrgHealthHandler, liveStorageByObjectInputSchema, liveStorageByObjectHandler, liveDataSkewInputSchema, liveSetupAuditTrailInputSchema, liveSecurityExposureInputSchema;
|
|
18974
19367
|
var init_live_plane = __esm({
|
|
18975
19368
|
"../mcp/dist/src/tools/live-plane.js"() {
|
|
@@ -18982,8 +19375,8 @@ var init_live_plane = __esm({
|
|
|
18982
19375
|
redactSecrets = (message) => message.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/\b00D[A-Za-z0-9]{12,}![A-Za-z0-9._~+/=-]{20,}\b/g, "[REDACTED_TOKEN]");
|
|
18983
19376
|
LIVE_PLANE_DISCLOSURE = "Live org data is read-only, queried at call time via the Salesforce CLI. It does not update the vault. Enable with SFI_LIVE_PLANE_ENABLED=1 or pass liveEnabled: true.";
|
|
18984
19377
|
MAX_SAMPLE_ROWS = 200;
|
|
18985
|
-
liveEnabledSchema =
|
|
18986
|
-
liveEnabled:
|
|
19378
|
+
liveEnabledSchema = z74.object({
|
|
19379
|
+
liveEnabled: z74.boolean().optional()
|
|
18987
19380
|
});
|
|
18988
19381
|
isLivePlaneEnabled = (input2) => {
|
|
18989
19382
|
if (input2 === true)
|
|
@@ -19061,8 +19454,8 @@ var init_live_plane = __esm({
|
|
|
19061
19454
|
}
|
|
19062
19455
|
};
|
|
19063
19456
|
liveDescribeInputSchema = liveEnabledSchema.extend({
|
|
19064
|
-
objectApiName:
|
|
19065
|
-
orgAlias:
|
|
19457
|
+
objectApiName: z74.string().min(1),
|
|
19458
|
+
orgAlias: z74.string().min(1).optional()
|
|
19066
19459
|
});
|
|
19067
19460
|
liveDescribeHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19068
19461
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19090,9 +19483,9 @@ var init_live_plane = __esm({
|
|
|
19090
19483
|
// Either `soql` (a SELECT COUNT() query) OR `objectApiName` (count every row
|
|
19091
19484
|
// of that object). Both optional at the schema level; the handler requires
|
|
19092
19485
|
// exactly one and turns objectApiName into `SELECT COUNT() FROM <object>`.
|
|
19093
|
-
soql:
|
|
19094
|
-
objectApiName:
|
|
19095
|
-
orgAlias:
|
|
19486
|
+
soql: z74.string().min(1).optional(),
|
|
19487
|
+
objectApiName: z74.string().min(1).optional(),
|
|
19488
|
+
orgAlias: z74.string().min(1).optional()
|
|
19096
19489
|
});
|
|
19097
19490
|
OBJECT_API_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
19098
19491
|
resolveCountSoql = (input2) => {
|
|
@@ -19156,9 +19549,9 @@ var init_live_plane = __esm({
|
|
|
19156
19549
|
});
|
|
19157
19550
|
};
|
|
19158
19551
|
liveSampleInputSchema = liveEnabledSchema.extend({
|
|
19159
|
-
soql:
|
|
19160
|
-
limit:
|
|
19161
|
-
orgAlias:
|
|
19552
|
+
soql: z74.string().min(1),
|
|
19553
|
+
limit: z74.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
|
|
19554
|
+
orgAlias: z74.string().min(1).optional()
|
|
19162
19555
|
});
|
|
19163
19556
|
capSampleSoql = (soql, limit) => {
|
|
19164
19557
|
const trimmed = soql.trim().replace(/;\s*$/, "");
|
|
@@ -19195,9 +19588,9 @@ var init_live_plane = __esm({
|
|
|
19195
19588
|
});
|
|
19196
19589
|
};
|
|
19197
19590
|
liveFieldPopulationInputSchema = liveEnabledSchema.extend({
|
|
19198
|
-
objectApiName:
|
|
19199
|
-
fieldApiName:
|
|
19200
|
-
orgAlias:
|
|
19591
|
+
objectApiName: z74.string().min(1),
|
|
19592
|
+
fieldApiName: z74.string().min(1),
|
|
19593
|
+
orgAlias: z74.string().min(1).optional()
|
|
19201
19594
|
});
|
|
19202
19595
|
liveFieldPopulationHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19203
19596
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19243,7 +19636,7 @@ var init_live_plane = __esm({
|
|
|
19243
19636
|
});
|
|
19244
19637
|
};
|
|
19245
19638
|
liveOrgLimitsInputSchema = liveEnabledSchema.extend({
|
|
19246
|
-
orgAlias:
|
|
19639
|
+
orgAlias: z74.string().min(1).optional()
|
|
19247
19640
|
});
|
|
19248
19641
|
liveOrgLimitsHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19249
19642
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19274,14 +19667,14 @@ var init_live_plane = __esm({
|
|
|
19274
19667
|
liveInactiveUsersInputSchema = liveEnabledSchema.extend({
|
|
19275
19668
|
/** Inactivity threshold in days (default 30). A user is "inactive" if their
|
|
19276
19669
|
* last login is older than this — or they have never logged in. */
|
|
19277
|
-
days:
|
|
19670
|
+
days: z74.number().int().min(1).max(3650).optional(),
|
|
19278
19671
|
/** Include non-Standard user types (integration/system/etc.). Default false
|
|
19279
19672
|
* → only human (Standard) users, the usual intent of "who hasn't logged in". */
|
|
19280
|
-
includeAllUserTypes:
|
|
19673
|
+
includeAllUserTypes: z74.boolean().optional(),
|
|
19281
19674
|
/** Max detail rows returned (default + hard cap 500). The TOTAL count is
|
|
19282
19675
|
* always reported separately, so a capped list never understates the count. */
|
|
19283
|
-
limit:
|
|
19284
|
-
orgAlias:
|
|
19676
|
+
limit: z74.number().int().min(1).max(MAX_INACTIVE_USER_ROWS).optional(),
|
|
19677
|
+
orgAlias: z74.string().min(1).optional()
|
|
19285
19678
|
});
|
|
19286
19679
|
soqlDateTime = (d) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
19287
19680
|
liveInactiveUsersHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
@@ -19347,10 +19740,10 @@ var init_live_plane = __esm({
|
|
|
19347
19740
|
LICENSE_USAGE_DISCLOSURE = 'License counts are live UserLicense / PermissionSetLicense state. "Reclaimable seats" is a PROXY \u2014 it groups active users who have not logged in within the window by their license; it does NOT measure actual feature usage, and some dormant seats are held intentionally (seasonal staff, service/integration accounts mis-typed as Standard, compliance holds). Per-feature-license usage (Marketing User, Knowledge User, etc.) is NOT covered. This tool is READ-ONLY: it never deprovisions or reassigns a license \u2014 verify each seat before reclaiming it.';
|
|
19348
19741
|
liveLicenseUsageInputSchema = liveEnabledSchema.extend({
|
|
19349
19742
|
/** Dormancy window for reclaimable seats, in days (default 90). */
|
|
19350
|
-
inactiveDays:
|
|
19743
|
+
inactiveDays: z74.number().int().min(1).max(3650).optional(),
|
|
19351
19744
|
/** Max reclaimable-seat groups returned (default + hard cap 200). */
|
|
19352
|
-
limit:
|
|
19353
|
-
orgAlias:
|
|
19745
|
+
limit: z74.number().int().min(1).max(MAX_RECLAIM_ROWS).optional(),
|
|
19746
|
+
orgAlias: z74.string().min(1).optional()
|
|
19354
19747
|
});
|
|
19355
19748
|
toUtilization = (rows, nameKey) => rows.map((r) => {
|
|
19356
19749
|
const total = Number(r.TotalLicenses ?? 0);
|
|
@@ -19438,13 +19831,13 @@ var init_live_plane = __esm({
|
|
|
19438
19831
|
}
|
|
19439
19832
|
});
|
|
19440
19833
|
};
|
|
19441
|
-
liveConsentInputSchema =
|
|
19834
|
+
liveConsentInputSchema = z74.object({
|
|
19442
19835
|
/** Org alias/username; defaults to the vault's source org. */
|
|
19443
|
-
orgAlias:
|
|
19836
|
+
orgAlias: z74.string().min(1).optional(),
|
|
19444
19837
|
/** Grant standing consent for the org (persists across sessions). */
|
|
19445
|
-
grant:
|
|
19838
|
+
grant: z74.boolean().optional(),
|
|
19446
19839
|
/** Revoke standing consent for the org. */
|
|
19447
|
-
revoke:
|
|
19840
|
+
revoke: z74.boolean().optional()
|
|
19448
19841
|
});
|
|
19449
19842
|
consentTrust = () => ({
|
|
19450
19843
|
provenance: "offline_snapshot",
|
|
@@ -19531,12 +19924,12 @@ var init_live_plane = __esm({
|
|
|
19531
19924
|
DEFAULT_STALE_DAYS = 90;
|
|
19532
19925
|
DEFAULT_RECENT_DAYS = 7;
|
|
19533
19926
|
liveGroupCountInputSchema = liveEnabledSchema.extend({
|
|
19534
|
-
objectApiName:
|
|
19535
|
-
groupByField:
|
|
19536
|
-
limit:
|
|
19537
|
-
filterField:
|
|
19538
|
-
filterValue:
|
|
19539
|
-
orgAlias:
|
|
19927
|
+
objectApiName: z74.string().min(1),
|
|
19928
|
+
groupByField: z74.string().min(1),
|
|
19929
|
+
limit: z74.number().int().min(1).max(MAX_GROUP_BUCKETS).optional(),
|
|
19930
|
+
filterField: z74.string().min(1).optional(),
|
|
19931
|
+
filterValue: z74.union([z74.string(), z74.number(), z74.boolean()]).optional(),
|
|
19932
|
+
orgAlias: z74.string().min(1).optional()
|
|
19540
19933
|
});
|
|
19541
19934
|
liveGroupCountHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19542
19935
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19604,12 +19997,12 @@ ${renderTrustFooter(trust)}`;
|
|
|
19604
19997
|
});
|
|
19605
19998
|
};
|
|
19606
19999
|
liveStaleRecordsInputSchema = liveEnabledSchema.extend({
|
|
19607
|
-
objectApiName:
|
|
19608
|
-
staleDays:
|
|
19609
|
-
dateField:
|
|
19610
|
-
includeNeverSet:
|
|
19611
|
-
limit:
|
|
19612
|
-
orgAlias:
|
|
20000
|
+
objectApiName: z74.string().min(1),
|
|
20001
|
+
staleDays: z74.number().int().min(1).max(3650).optional(),
|
|
20002
|
+
dateField: z74.string().min(1).optional(),
|
|
20003
|
+
includeNeverSet: z74.boolean().optional(),
|
|
20004
|
+
limit: z74.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
|
|
20005
|
+
orgAlias: z74.string().min(1).optional()
|
|
19613
20006
|
});
|
|
19614
20007
|
liveStaleRecordsHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19615
20008
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19671,11 +20064,11 @@ ${renderTrustFooter(trust)}`;
|
|
|
19671
20064
|
});
|
|
19672
20065
|
};
|
|
19673
20066
|
liveRecentActivityInputSchema = liveEnabledSchema.extend({
|
|
19674
|
-
objectApiName:
|
|
19675
|
-
days:
|
|
19676
|
-
activity:
|
|
19677
|
-
limit:
|
|
19678
|
-
orgAlias:
|
|
20067
|
+
objectApiName: z74.string().min(1),
|
|
20068
|
+
days: z74.number().int().min(1).max(365).optional(),
|
|
20069
|
+
activity: z74.enum(["created", "modified", "both"]).optional(),
|
|
20070
|
+
limit: z74.number().int().min(1).max(MAX_SAMPLE_ROWS).optional(),
|
|
20071
|
+
orgAlias: z74.string().min(1).optional()
|
|
19679
20072
|
});
|
|
19680
20073
|
liveRecentActivityHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19681
20074
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19753,11 +20146,11 @@ ${renderTrustFooter(trust)}`;
|
|
|
19753
20146
|
return 0;
|
|
19754
20147
|
};
|
|
19755
20148
|
liveAggregateInputSchema = liveEnabledSchema.extend({
|
|
19756
|
-
objectApiName:
|
|
19757
|
-
fieldApiName:
|
|
19758
|
-
filterField:
|
|
19759
|
-
filterValue:
|
|
19760
|
-
orgAlias:
|
|
20149
|
+
objectApiName: z74.string().min(1),
|
|
20150
|
+
fieldApiName: z74.string().min(1),
|
|
20151
|
+
filterField: z74.string().min(1).optional(),
|
|
20152
|
+
filterValue: z74.union([z74.string(), z74.number(), z74.boolean()]).optional(),
|
|
20153
|
+
orgAlias: z74.string().min(1).optional()
|
|
19761
20154
|
});
|
|
19762
20155
|
liveAggregateHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19763
20156
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19817,12 +20210,12 @@ ${renderTrustFooter(trust)}`;
|
|
|
19817
20210
|
};
|
|
19818
20211
|
MAX_DUPLICATE_GROUPS = 100;
|
|
19819
20212
|
liveDuplicateCheckInputSchema = liveEnabledSchema.extend({
|
|
19820
|
-
objectApiName:
|
|
19821
|
-
fieldApiName:
|
|
19822
|
-
limit:
|
|
19823
|
-
filterField:
|
|
19824
|
-
filterValue:
|
|
19825
|
-
orgAlias:
|
|
20213
|
+
objectApiName: z74.string().min(1),
|
|
20214
|
+
fieldApiName: z74.string().min(1),
|
|
20215
|
+
limit: z74.number().int().min(1).max(MAX_DUPLICATE_GROUPS).optional(),
|
|
20216
|
+
filterField: z74.string().min(1).optional(),
|
|
20217
|
+
filterValue: z74.union([z74.string(), z74.number(), z74.boolean()]).optional(),
|
|
20218
|
+
orgAlias: z74.string().min(1).optional()
|
|
19826
20219
|
});
|
|
19827
20220
|
liveDuplicateCheckHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19828
20221
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19881,11 +20274,11 @@ ${renderTrustFooter(trust)}`;
|
|
|
19881
20274
|
};
|
|
19882
20275
|
MAX_OWNER_BUCKETS = 100;
|
|
19883
20276
|
liveOwnerBreakdownInputSchema = liveEnabledSchema.extend({
|
|
19884
|
-
objectApiName:
|
|
19885
|
-
limit:
|
|
19886
|
-
filterField:
|
|
19887
|
-
filterValue:
|
|
19888
|
-
orgAlias:
|
|
20277
|
+
objectApiName: z74.string().min(1),
|
|
20278
|
+
limit: z74.number().int().min(1).max(MAX_OWNER_BUCKETS).optional(),
|
|
20279
|
+
filterField: z74.string().min(1).optional(),
|
|
20280
|
+
filterValue: z74.union([z74.string(), z74.number(), z74.boolean()]).optional(),
|
|
20281
|
+
orgAlias: z74.string().min(1).optional()
|
|
19889
20282
|
});
|
|
19890
20283
|
liveOwnerBreakdownHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19891
20284
|
const gate = await gateLive(ctx, input2);
|
|
@@ -19962,9 +20355,9 @@ ${renderTrustFooter(trust)}`;
|
|
|
19962
20355
|
};
|
|
19963
20356
|
DEFAULT_REPORT_STALE_DAYS = 90;
|
|
19964
20357
|
liveReportUsageInputSchema = liveEnabledSchema.extend({
|
|
19965
|
-
staleDays:
|
|
19966
|
-
limit:
|
|
19967
|
-
orgAlias:
|
|
20358
|
+
staleDays: z74.number().int().min(1).max(3650).optional(),
|
|
20359
|
+
limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
|
|
20360
|
+
orgAlias: z74.string().min(1).optional()
|
|
19968
20361
|
});
|
|
19969
20362
|
liveReportUsageHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
19970
20363
|
const gate = await gateLive(ctx, input2);
|
|
@@ -20016,9 +20409,9 @@ ${renderTrustFooter(trust)}`;
|
|
|
20016
20409
|
});
|
|
20017
20410
|
};
|
|
20018
20411
|
liveFolderAccessInputSchema = liveEnabledSchema.extend({
|
|
20019
|
-
folderType:
|
|
20020
|
-
limit:
|
|
20021
|
-
orgAlias:
|
|
20412
|
+
folderType: z74.enum(["Report", "Dashboard", "Email", "Document", "all"]).optional(),
|
|
20413
|
+
limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
|
|
20414
|
+
orgAlias: z74.string().min(1).optional()
|
|
20022
20415
|
});
|
|
20023
20416
|
liveFolderAccessHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
20024
20417
|
const gate = await gateLive(ctx, input2);
|
|
@@ -20073,9 +20466,9 @@ ${renderTrustFooter(trust)}`;
|
|
|
20073
20466
|
DEFAULT_TEMPLATE_STALE_DAYS = 180;
|
|
20074
20467
|
CLASSIC_TEMPLATE_TYPES = /* @__PURE__ */ new Set(["text", "html", "custom", "visualforce"]);
|
|
20075
20468
|
liveEmailTemplateUsageInputSchema = liveEnabledSchema.extend({
|
|
20076
|
-
staleDays:
|
|
20077
|
-
limit:
|
|
20078
|
-
orgAlias:
|
|
20469
|
+
staleDays: z74.number().int().min(1).max(3650).optional(),
|
|
20470
|
+
limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
|
|
20471
|
+
orgAlias: z74.string().min(1).optional()
|
|
20079
20472
|
});
|
|
20080
20473
|
liveEmailTemplateUsageHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
20081
20474
|
const gate = await gateLive(ctx, input2);
|
|
@@ -20140,8 +20533,8 @@ ${renderTrustFooter(trust)}`;
|
|
|
20140
20533
|
DEFAULT_HEALTH_DAYS = 7;
|
|
20141
20534
|
LIMIT_RISK_THRESHOLD = 0.8;
|
|
20142
20535
|
liveOrgHealthInputSchema = liveEnabledSchema.extend({
|
|
20143
|
-
days:
|
|
20144
|
-
orgAlias:
|
|
20536
|
+
days: z74.number().int().min(1).max(90).optional(),
|
|
20537
|
+
orgAlias: z74.string().min(1).optional()
|
|
20145
20538
|
});
|
|
20146
20539
|
liveOrgHealthHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
20147
20540
|
const gate = await gateLive(ctx, input2);
|
|
@@ -20208,9 +20601,9 @@ ${renderTrustFooter(trust)}`;
|
|
|
20208
20601
|
});
|
|
20209
20602
|
};
|
|
20210
20603
|
liveStorageByObjectInputSchema = liveEnabledSchema.extend({
|
|
20211
|
-
limit:
|
|
20212
|
-
objectApiNames:
|
|
20213
|
-
orgAlias:
|
|
20604
|
+
limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
|
|
20605
|
+
objectApiNames: z74.array(z74.string().min(1)).max(80).optional(),
|
|
20606
|
+
orgAlias: z74.string().min(1).optional()
|
|
20214
20607
|
});
|
|
20215
20608
|
liveStorageByObjectHandler = async (ctx, input2, exec4 = nodeExecFile2) => {
|
|
20216
20609
|
const gate = await gateLive(ctx, input2);
|
|
@@ -20253,25 +20646,25 @@ ${renderTrustFooter(trust)}`;
|
|
|
20253
20646
|
});
|
|
20254
20647
|
};
|
|
20255
20648
|
liveDataSkewInputSchema = liveEnabledSchema.extend({
|
|
20256
|
-
objectApiName:
|
|
20257
|
-
ownerField:
|
|
20258
|
-
threshold:
|
|
20259
|
-
limit:
|
|
20260
|
-
orgAlias:
|
|
20649
|
+
objectApiName: z74.string().min(1),
|
|
20650
|
+
ownerField: z74.string().min(1).optional(),
|
|
20651
|
+
threshold: z74.number().int().min(1).optional(),
|
|
20652
|
+
limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
|
|
20653
|
+
orgAlias: z74.string().min(1).optional()
|
|
20261
20654
|
});
|
|
20262
20655
|
liveSetupAuditTrailInputSchema = liveEnabledSchema.extend({
|
|
20263
|
-
days:
|
|
20264
|
-
limit:
|
|
20265
|
-
orgAlias:
|
|
20656
|
+
days: z74.number().int().min(1).max(180).optional(),
|
|
20657
|
+
limit: z74.number().int().min(1).max(MAX_DETAIL_ROWS).optional(),
|
|
20658
|
+
orgAlias: z74.string().min(1).optional()
|
|
20266
20659
|
});
|
|
20267
20660
|
liveSecurityExposureInputSchema = liveEnabledSchema.extend({
|
|
20268
|
-
orgAlias:
|
|
20661
|
+
orgAlias: z74.string().min(1).optional()
|
|
20269
20662
|
});
|
|
20270
20663
|
}
|
|
20271
20664
|
});
|
|
20272
20665
|
|
|
20273
20666
|
// ../mcp/dist/src/tools/live-drift-check.js
|
|
20274
|
-
import { z as
|
|
20667
|
+
import { z as z75 } from "zod";
|
|
20275
20668
|
var FIELD_PAGE_SIZE2, liveDriftCheckInputSchema, BOUNDARIES8, isCustomField2, diffFields, liveFieldNames, liveDriftCheckHandler;
|
|
20276
20669
|
var init_live_drift_check = __esm({
|
|
20277
20670
|
"../mcp/dist/src/tools/live-drift-check.js"() {
|
|
@@ -20280,11 +20673,11 @@ var init_live_drift_check = __esm({
|
|
|
20280
20673
|
init_src();
|
|
20281
20674
|
init_live_plane();
|
|
20282
20675
|
FIELD_PAGE_SIZE2 = 500;
|
|
20283
|
-
liveDriftCheckInputSchema =
|
|
20284
|
-
objectApiName:
|
|
20285
|
-
orgAlias:
|
|
20676
|
+
liveDriftCheckInputSchema = z75.object({
|
|
20677
|
+
objectApiName: z75.string().min(1),
|
|
20678
|
+
orgAlias: z75.string().min(1).optional(),
|
|
20286
20679
|
/** Opt-in to the live plane (mirrors the other live_* tools). */
|
|
20287
|
-
liveEnabled:
|
|
20680
|
+
liveEnabled: z75.boolean().optional()
|
|
20288
20681
|
});
|
|
20289
20682
|
BOUNDARIES8 = Object.freeze([
|
|
20290
20683
|
"Compares the offline snapshot to a LIVE read-only describe; requires the live plane (SFI_LIVE_PLANE_ENABLED or liveEnabled:true). Does not mutate the org.",
|
|
@@ -20352,7 +20745,7 @@ var init_live_drift_check = __esm({
|
|
|
20352
20745
|
});
|
|
20353
20746
|
|
|
20354
20747
|
// ../mcp/dist/src/tools/lookup-record.js
|
|
20355
|
-
import { z as
|
|
20748
|
+
import { z as z76 } from "zod";
|
|
20356
20749
|
var RECORD_NODE_TYPES, CUSTOM_METADATA_RECORD_PREFIX, CUSTOM_SETTING_RECORD_PREFIX, lookupRecordInputSchema, classifyRecordId, normalizeValueEntry, readRecordValues, readTypeApiName, lookupRecordHandler;
|
|
20357
20750
|
var init_lookup_record = __esm({
|
|
20358
20751
|
"../mcp/dist/src/tools/lookup-record.js"() {
|
|
@@ -20365,8 +20758,8 @@ var init_lookup_record = __esm({
|
|
|
20365
20758
|
]);
|
|
20366
20759
|
CUSTOM_METADATA_RECORD_PREFIX = "CustomMetadataRecord:";
|
|
20367
20760
|
CUSTOM_SETTING_RECORD_PREFIX = "CustomSettingRecord:";
|
|
20368
|
-
lookupRecordInputSchema =
|
|
20369
|
-
recordId:
|
|
20761
|
+
lookupRecordInputSchema = z76.object({
|
|
20762
|
+
recordId: z76.string().min(1)
|
|
20370
20763
|
});
|
|
20371
20764
|
classifyRecordId = (recordId) => {
|
|
20372
20765
|
if (recordId.startsWith(CUSTOM_METADATA_RECORD_PREFIX)) {
|
|
@@ -20459,14 +20852,14 @@ var init_lookup_record = __esm({
|
|
|
20459
20852
|
});
|
|
20460
20853
|
|
|
20461
20854
|
// ../mcp/dist/src/tools/manifest.js
|
|
20462
|
-
import { z as
|
|
20855
|
+
import { z as z77 } from "zod";
|
|
20463
20856
|
var getManifestInputSchema, getManifestHandler;
|
|
20464
20857
|
var init_manifest2 = __esm({
|
|
20465
20858
|
"../mcp/dist/src/tools/manifest.js"() {
|
|
20466
20859
|
"use strict";
|
|
20467
20860
|
init_dist();
|
|
20468
20861
|
init_src2();
|
|
20469
|
-
getManifestInputSchema =
|
|
20862
|
+
getManifestInputSchema = z77.object({});
|
|
20470
20863
|
getManifestHandler = async (ctx, _input) => {
|
|
20471
20864
|
const data = {
|
|
20472
20865
|
...ctx.manifest,
|
|
@@ -20484,7 +20877,7 @@ var init_manifest2 = __esm({
|
|
|
20484
20877
|
});
|
|
20485
20878
|
|
|
20486
20879
|
// ../mcp/dist/src/tools/meaningful-test-audit.js
|
|
20487
|
-
import { z as
|
|
20880
|
+
import { z as z78 } from "zod";
|
|
20488
20881
|
var LIST_PAGE_SIZE8, APEX_CLASS_PREFIX5, MEANINGFUL_TEST_DISCLOSURE, meaningfulTestAuditInputSchema, isTestClass3, readNonNegativeInt, collectFakeAssertions, buildEntry, compareEntries2, meaningfulTestAuditHandler;
|
|
20489
20882
|
var init_meaningful_test_audit = __esm({
|
|
20490
20883
|
"../mcp/dist/src/tools/meaningful-test-audit.js"() {
|
|
@@ -20494,8 +20887,8 @@ var init_meaningful_test_audit = __esm({
|
|
|
20494
20887
|
LIST_PAGE_SIZE8 = 500;
|
|
20495
20888
|
APEX_CLASS_PREFIX5 = "ApexClass:";
|
|
20496
20889
|
MEANINGFUL_TEST_DISCLOSURE = "v2.7 meaningful_test_audit ranks test classes by the v2.1 R2 fake-assertion recognizer output and an assertions-per-KB density heuristic. The recognizer matches System.assert* invocations against literal tokens; assertions via helper methods (MyTestHelper.assertField) and framework wrappers are invisible. A test class with a high fakeAssertionCount may have meaningful tests via a custom assertion helper the recognizer cannot see. When qualityIssues is absent the v2.1 R2 pass has not run for this vault; entries surface with fakeAssertionCount: 0 and the rank is driven by density alone.";
|
|
20497
|
-
meaningfulTestAuditInputSchema =
|
|
20498
|
-
classFilter:
|
|
20890
|
+
meaningfulTestAuditInputSchema = z78.object({
|
|
20891
|
+
classFilter: z78.array(z78.string().min(1)).max(LIST_PAGE_SIZE8).optional()
|
|
20499
20892
|
});
|
|
20500
20893
|
isTestClass3 = (node) => node.properties["isTest"] === true;
|
|
20501
20894
|
readNonNegativeInt = (node, key) => {
|
|
@@ -20600,7 +20993,7 @@ var init_meaningful_test_audit = __esm({
|
|
|
20600
20993
|
});
|
|
20601
20994
|
|
|
20602
20995
|
// ../mcp/dist/src/tools/method-reachability.js
|
|
20603
|
-
import { z as
|
|
20996
|
+
import { z as z79 } from "zod";
|
|
20604
20997
|
var REACHABILITY_BFS_DEPTH, APEX_CLASS_PREFIX6, APEX_TRIGGER_PREFIX4, REACHABILITY_DISCLOSURE, methodReachabilityInputSchema, isApexCallable3, isTestClass4, entryKindsFor, upstreamWalk, compareEntryHits, compareReachingTests, methodReachabilityHandler;
|
|
20605
20998
|
var init_method_reachability = __esm({
|
|
20606
20999
|
"../mcp/dist/src/tools/method-reachability.js"() {
|
|
@@ -20612,8 +21005,8 @@ var init_method_reachability = __esm({
|
|
|
20612
21005
|
APEX_CLASS_PREFIX6 = "ApexClass:";
|
|
20613
21006
|
APEX_TRIGGER_PREFIX4 = "ApexTrigger:";
|
|
20614
21007
|
REACHABILITY_DISCLOSURE = "v2.7 method_reachability ships CLASS granularity (method-level promised in v2.7.1). Dynamic dispatch (Type.forName) and reflective invocation are invisible \u2014 a class genuinely invoked at runtime via reflection or framework wiring will surface as likely-dead-code. Trigger framework base classes (TriggerHandler, fflib) may be partially invisible. BFS is capped at depth 3.";
|
|
20615
|
-
methodReachabilityInputSchema =
|
|
20616
|
-
classApiName:
|
|
21008
|
+
methodReachabilityInputSchema = z79.object({
|
|
21009
|
+
classApiName: z79.string().min(1)
|
|
20617
21010
|
});
|
|
20618
21011
|
isApexCallable3 = (id) => id.startsWith(APEX_CLASS_PREFIX6) || id.startsWith(APEX_TRIGGER_PREFIX4);
|
|
20619
21012
|
isTestClass4 = (node) => node.properties["isTest"] === true;
|
|
@@ -20753,15 +21146,15 @@ var init_method_reachability = __esm({
|
|
|
20753
21146
|
});
|
|
20754
21147
|
|
|
20755
21148
|
// ../mcp/dist/src/tools/naming-convention-report.js
|
|
20756
|
-
import { z as
|
|
21149
|
+
import { z as z80 } from "zod";
|
|
20757
21150
|
var namingConventionReportInputSchema, namingConventionReportHandler;
|
|
20758
21151
|
var init_naming_convention_report = __esm({
|
|
20759
21152
|
"../mcp/dist/src/tools/naming-convention-report.js"() {
|
|
20760
21153
|
"use strict";
|
|
20761
21154
|
init_dist();
|
|
20762
21155
|
init_src4();
|
|
20763
|
-
namingConventionReportInputSchema =
|
|
20764
|
-
scope:
|
|
21156
|
+
namingConventionReportInputSchema = z80.object({
|
|
21157
|
+
scope: z80.string().min(1).optional()
|
|
20765
21158
|
});
|
|
20766
21159
|
namingConventionReportHandler = async (ctx, input2) => {
|
|
20767
21160
|
const result = await recognizeNamingConventions(ctx.graph, input2.scope !== void 0 ? { scope: input2.scope } : {});
|
|
@@ -20791,7 +21184,7 @@ var init_naming_convention_report = __esm({
|
|
|
20791
21184
|
// ../mcp/dist/src/tools/omniscript-flow.js
|
|
20792
21185
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
20793
21186
|
import { XMLParser as XMLParser4 } from "fast-xml-parser";
|
|
20794
|
-
import { z as
|
|
21187
|
+
import { z as z81 } from "zod";
|
|
20795
21188
|
var OMNISCRIPT_PREFIX, DISPATCH_EDGE_TYPE, NATIVE_VS_VLOCITY_DISCLOSURE3, RECORD_LEVEL_DISCLOSURE, APEX_COUPLING_DEFERRAL_DISCLOSURE2, omniscriptFlowInputSchema, isOmniScriptId, unwrapSingle4, toArray4, toNullableString, coerceBoolean3, toNullableNumber, parsePropertySetConfig2, walkElements, readSteps, readStringProp, readNumberProp, readBoolProp, compareDispatched, omniscriptFlowHandler;
|
|
20796
21189
|
var init_omniscript_flow = __esm({
|
|
20797
21190
|
"../mcp/dist/src/tools/omniscript-flow.js"() {
|
|
@@ -20804,9 +21197,9 @@ var init_omniscript_flow = __esm({
|
|
|
20804
21197
|
NATIVE_VS_VLOCITY_DISCLOSURE3 = "v3.2 recognizes Industries Native XML shapes (file extensions `.os-meta.xml`, `.oip-meta.xml`, `.rpt-meta.xml`, `.ouc-meta.xml`, `.decisionTable-meta.xml`). Legacy Vlocity-managed-package components (namespace `vlocity_cmt__`) are NOT extracted by v3.2. Mid-migration orgs may show partial coverage.";
|
|
20805
21198
|
RECORD_LEVEL_DISCLOSURE = "v3.2 walks the OmniScript / IP / Card metadata XML. The actual user-entered data and runtime state lives in OmniProcessElement and related SObject records; that is record-level data, out of scope for v0.1's read-the-metadata posture.";
|
|
20806
21199
|
APEX_COUPLING_DEFERRAL_DISCLOSURE2 = "v3.2 captures OmniStudio components and intra-OmniStudio call chains (`dispatchesOmniAction`). The Apex-to-OmniProcess coupling (`implements omnistudio.VlocityOpenInterface` etc.) is a v3.3 follow-up \u2014 those edges are NOT yet in the graph.";
|
|
20807
|
-
omniscriptFlowInputSchema =
|
|
20808
|
-
omniScriptId:
|
|
20809
|
-
includeChildPropertySetConfig:
|
|
21200
|
+
omniscriptFlowInputSchema = z81.object({
|
|
21201
|
+
omniScriptId: z81.string().min(1),
|
|
21202
|
+
includeChildPropertySetConfig: z81.boolean().optional()
|
|
20810
21203
|
});
|
|
20811
21204
|
isOmniScriptId = (id) => id.startsWith(OMNISCRIPT_PREFIX);
|
|
20812
21205
|
unwrapSingle4 = (value) => Array.isArray(value) ? value[0] : value;
|
|
@@ -21053,7 +21446,7 @@ var init_omniscript_flow = __esm({
|
|
|
21053
21446
|
// ../mcp/dist/src/tools/omniuicard-widget-breakdown.js
|
|
21054
21447
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
21055
21448
|
import { XMLParser as XMLParser5, XMLValidator as XMLValidator4 } from "fast-xml-parser";
|
|
21056
|
-
import { z as
|
|
21449
|
+
import { z as z82 } from "zod";
|
|
21057
21450
|
var OMNI_UI_CARD_PREFIX, ROOT_ELEMENT4, PROPERTY_SET_CONFIG_PARSING_DISCLOSURE, NATIVE_VS_VLOCITY_DISCLOSURE4, omniuicardWidgetBreakdownInputSchema, unwrapSingle5, parseJsonBlob, walkWidgets, countWidgets, buildStates, readStringArrayProperty, readNullableStringProperty, readNullableNumberProperty, readBooleanProperty2, projectDispatchedAction, sortDispatchedActions, buildStatesFromSourceXml, omniuicardWidgetBreakdownHandler;
|
|
21058
21451
|
var init_omniuicard_widget_breakdown = __esm({
|
|
21059
21452
|
"../mcp/dist/src/tools/omniuicard-widget-breakdown.js"() {
|
|
@@ -21065,8 +21458,8 @@ var init_omniuicard_widget_breakdown = __esm({
|
|
|
21065
21458
|
ROOT_ELEMENT4 = "OmniUiCard";
|
|
21066
21459
|
PROPERTY_SET_CONFIG_PARSING_DISCLOSURE = "widget breakdown parses the propertySetConfig JSON blob. FlexCard authors can edit the raw blob in the OmniStudio designer; widget order in the breakdown follows the JSON's declared order, not the visual designer's drag-drop order.";
|
|
21067
21460
|
NATIVE_VS_VLOCITY_DISCLOSURE4 = "v3.2 recognizes Industries Native XML shapes (file extensions `.os-meta.xml`, `.oip-meta.xml`, `.rpt-meta.xml`, `.ouc-meta.xml`, `.decisionTable-meta.xml`). Legacy Vlocity-managed-package components (namespace `vlocity_cmt__`) are NOT extracted by v3.2. Mid-migration orgs may show partial coverage.";
|
|
21068
|
-
omniuicardWidgetBreakdownInputSchema =
|
|
21069
|
-
omniUiCardId:
|
|
21461
|
+
omniuicardWidgetBreakdownInputSchema = z82.object({
|
|
21462
|
+
omniUiCardId: z82.string().min(1)
|
|
21070
21463
|
});
|
|
21071
21464
|
unwrapSingle5 = (value) => Array.isArray(value) ? value[0] : value;
|
|
21072
21465
|
parseJsonBlob = (raw) => {
|
|
@@ -21476,8 +21869,8 @@ var init_soe_payload_bounds = __esm({
|
|
|
21476
21869
|
});
|
|
21477
21870
|
|
|
21478
21871
|
// ../mcp/dist/src/tools/order-of-execution.js
|
|
21479
|
-
import { z as
|
|
21480
|
-
var
|
|
21872
|
+
import { z as z83 } from "zod";
|
|
21873
|
+
var DISCLOSURE7, SOE_EVENTS, orderOfExecutionInputSchema, workflowMatchesEvent, flowMatchesEvent, triggerMatchesEvent, surfaceFirstCondition, buildActions, buildStep, fetchParentedFirers, fetchTriggersOnFirers, buildAsyncSteps, ASSIGNMENT_TYPES, APPROVAL_TYPES, VALIDATION_TYPES, FLOW_TYPES, TRIGGER_TYPES, WORKFLOW_TYPES, composeForEvent, orderOfExecutionHandler;
|
|
21481
21874
|
var init_order_of_execution = __esm({
|
|
21482
21875
|
"../mcp/dist/src/tools/order-of-execution.js"() {
|
|
21483
21876
|
"use strict";
|
|
@@ -21485,10 +21878,10 @@ var init_order_of_execution = __esm({
|
|
|
21485
21878
|
init_src();
|
|
21486
21879
|
init_soe_admission();
|
|
21487
21880
|
init_soe_payload_bounds();
|
|
21488
|
-
|
|
21881
|
+
DISCLOSURE7 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
|
|
21489
21882
|
SOE_EVENTS = ["insert", "update", "delete", "undelete"];
|
|
21490
|
-
orderOfExecutionInputSchema =
|
|
21491
|
-
objectApiName:
|
|
21883
|
+
orderOfExecutionInputSchema = z83.object({
|
|
21884
|
+
objectApiName: z83.string().min(1)
|
|
21492
21885
|
});
|
|
21493
21886
|
workflowMatchesEvent = (triggerType, event) => {
|
|
21494
21887
|
if (typeof triggerType !== "string")
|
|
@@ -21836,7 +22229,7 @@ var init_order_of_execution = __esm({
|
|
|
21836
22229
|
objectApiName: input2.objectApiName,
|
|
21837
22230
|
objectModeled,
|
|
21838
22231
|
byEvent,
|
|
21839
|
-
disclosure: composeSoeDisclosure(
|
|
22232
|
+
disclosure: composeSoeDisclosure(DISCLOSURE7, objectModeled)
|
|
21840
22233
|
};
|
|
21841
22234
|
const containers = SOE_EVENTS.map((event) => byEvent[event].soe);
|
|
21842
22235
|
const budget = enforceSoeByteBudget(data, containers);
|
|
@@ -21856,7 +22249,7 @@ var init_order_of_execution = __esm({
|
|
|
21856
22249
|
});
|
|
21857
22250
|
|
|
21858
22251
|
// ../mcp/dist/src/tools/org-history.js
|
|
21859
|
-
import { z as
|
|
22252
|
+
import { z as z84 } from "zod";
|
|
21860
22253
|
var DEFAULT_LIMIT8, MAX_LIMIT9, orgHistoryInputSchema, BOUNDARIES9, orgHistoryHandler;
|
|
21861
22254
|
var init_org_history = __esm({
|
|
21862
22255
|
"../mcp/dist/src/tools/org-history.js"() {
|
|
@@ -21865,8 +22258,8 @@ var init_org_history = __esm({
|
|
|
21865
22258
|
init_history_store();
|
|
21866
22259
|
DEFAULT_LIMIT8 = 50;
|
|
21867
22260
|
MAX_LIMIT9 = 500;
|
|
21868
|
-
orgHistoryInputSchema =
|
|
21869
|
-
limit:
|
|
22261
|
+
orgHistoryInputSchema = z84.object({
|
|
22262
|
+
limit: z84.number().int().min(1).max(MAX_LIMIT9).optional()
|
|
21870
22263
|
});
|
|
21871
22264
|
BOUNDARIES9 = Object.freeze([
|
|
21872
22265
|
"History only covers refreshes performed since the continuous-learning store shipped; older or single-refresh vaults yield a short/empty timeline.",
|
|
@@ -21897,7 +22290,7 @@ var init_org_history = __esm({
|
|
|
21897
22290
|
});
|
|
21898
22291
|
|
|
21899
22292
|
// ../mcp/dist/src/tools/org-pulse.js
|
|
21900
|
-
import { z as
|
|
22293
|
+
import { z as z85 } from "zod";
|
|
21901
22294
|
var DEFAULT_LIMIT9, MAX_LIMIT10, orgPulseInputSchema, ORG_PULSE_DISCLOSURE, orgPulseHandler;
|
|
21902
22295
|
var init_org_pulse = __esm({
|
|
21903
22296
|
"../mcp/dist/src/tools/org-pulse.js"() {
|
|
@@ -21906,8 +22299,8 @@ var init_org_pulse = __esm({
|
|
|
21906
22299
|
init_src();
|
|
21907
22300
|
DEFAULT_LIMIT9 = 10;
|
|
21908
22301
|
MAX_LIMIT10 = 50;
|
|
21909
|
-
orgPulseInputSchema =
|
|
21910
|
-
limit:
|
|
22302
|
+
orgPulseInputSchema = z85.object({
|
|
22303
|
+
limit: z85.number().int().min(1).max(MAX_LIMIT10).optional()
|
|
21911
22304
|
});
|
|
21912
22305
|
ORG_PULSE_DISCLOSURE = "Freshness and contributor signals come from each component's lastModifiedDate / lastModifiedBy. A plain `sf project retrieve` does NOT populate those \u2014 they need a refresh enriched via the Tooling API. If coverage is ~0% and the contributor list is empty, that means the data was not captured at refresh time, NOT that the org has no history. Run a tooling-API-enabled refresh to populate it.";
|
|
21913
22306
|
orgPulseHandler = async (ctx, input2) => {
|
|
@@ -21942,7 +22335,7 @@ var init_org_pulse = __esm({
|
|
|
21942
22335
|
});
|
|
21943
22336
|
|
|
21944
22337
|
// ../mcp/dist/src/tools/outbound-message-catalog.js
|
|
21945
|
-
import { z as
|
|
22338
|
+
import { z as z86 } from "zod";
|
|
21946
22339
|
var OUTBOUND_MESSAGE_CATALOG_MAX_ENTRIES, outboundMessageCatalogInputSchema, OUTBOUND_MESSAGE_DISCLOSURE, readOptionalString2, readBoolean, readFields, readName, apiNameToObjectKey, collectInvokers, compareEntries3, compareInvokers, outboundMessageCatalogHandler;
|
|
21947
22340
|
var init_outbound_message_catalog = __esm({
|
|
21948
22341
|
"../mcp/dist/src/tools/outbound-message-catalog.js"() {
|
|
@@ -21950,8 +22343,8 @@ var init_outbound_message_catalog = __esm({
|
|
|
21950
22343
|
init_dist();
|
|
21951
22344
|
init_src();
|
|
21952
22345
|
OUTBOUND_MESSAGE_CATALOG_MAX_ENTRIES = 500;
|
|
21953
|
-
outboundMessageCatalogInputSchema =
|
|
21954
|
-
objectFilter:
|
|
22346
|
+
outboundMessageCatalogInputSchema = z86.object({
|
|
22347
|
+
objectFilter: z86.string().min(1).optional()
|
|
21955
22348
|
});
|
|
21956
22349
|
OUTBOUND_MESSAGE_DISCLOSURE = "Endpoint URLs are captured verbatim from the `<outboundMessages><endpointUrl>` element and NOT VALIDATED \u2014 v2.8 does not probe the URL, does not confirm the destination exists, and does not confirm the message is invoked at runtime. Runtime registration via a custom Apex caller or a programmatically-modified workflow rule is invisible to the offline extractor.";
|
|
21957
22350
|
readOptionalString2 = (properties, key) => {
|
|
@@ -22068,7 +22461,7 @@ var init_outbound_message_catalog = __esm({
|
|
|
22068
22461
|
});
|
|
22069
22462
|
|
|
22070
22463
|
// ../mcp/dist/src/tools/package-impact.js
|
|
22071
|
-
import { z as
|
|
22464
|
+
import { z as z87 } from "zod";
|
|
22072
22465
|
var DEFAULT_LIMIT10, MAX_LIMIT11, INVENTORY_SAMPLE, EDGE_SCAN_CAP, PACKAGE_IMPACT_DISCLOSURE, packageImpactInputSchema, namespaceOf, buildInventory, buildImpact, packageImpactHandler;
|
|
22073
22466
|
var init_package_impact = __esm({
|
|
22074
22467
|
"../mcp/dist/src/tools/package-impact.js"() {
|
|
@@ -22080,9 +22473,9 @@ var init_package_impact = __esm({
|
|
|
22080
22473
|
INVENTORY_SAMPLE = 5;
|
|
22081
22474
|
EDGE_SCAN_CAP = 2e3;
|
|
22082
22475
|
PACKAGE_IMPACT_DISCLOSURE = 'package_impact detects managed/namespaced components by API-name prefix: a name is namespaced iff its leaf splits into >= 3 "__"-delimited segments (NS__Object__c). This reliably catches namespaced objects, fields, and custom metadata \u2014 the bulk of what a package adds \u2014 but MISSES managed Apex referenced via dot-notation (NS.ClassName) and namespaced components without a standard suffix. The vault holds only what `sf project retrieve` pulled: a package\u2019s INTERNAL components are usually NOT retrieved, so packageComponentCount reflects what you can SEE, not the package\u2019s full footprint. "no-detected-dependencies" means NO STATIC evidence in retrieved metadata that your components reference this namespace \u2014 it does NOT prove the package is safe to uninstall (dynamic SOQL, Type.forName("NS.X"), merge-field/formula references, and unretrieved metadata are invisible). Always validate an uninstall in a sandbox first.';
|
|
22083
|
-
packageImpactInputSchema =
|
|
22084
|
-
namespace:
|
|
22085
|
-
limit:
|
|
22476
|
+
packageImpactInputSchema = z87.object({
|
|
22477
|
+
namespace: z87.string().min(1).optional(),
|
|
22478
|
+
limit: z87.number().int().min(1).max(MAX_LIMIT11).optional()
|
|
22086
22479
|
});
|
|
22087
22480
|
namespaceOf = (idOrName) => {
|
|
22088
22481
|
const colon = idOrName.indexOf(":");
|
|
@@ -22221,7 +22614,7 @@ var init_package_impact = __esm({
|
|
|
22221
22614
|
});
|
|
22222
22615
|
|
|
22223
22616
|
// ../mcp/dist/src/tools/process-builder-migration-candidates.js
|
|
22224
|
-
import { z as
|
|
22617
|
+
import { z as z88 } from "zod";
|
|
22225
22618
|
var PROCESS_BUILDER_MAX_LIMIT, PROCESS_BUILDER_DEFAULT_LIMIT, LIST_PAGE_SIZE9, PROCESS_BUILDER_RETIREMENT_NOTE, WORKFLOW_RULE_RETIREMENT_NOTE, BOUNDARIES10, processBuilderMigrationCandidatesInputSchema, propertyNumber, propertyBoolean, propertyString2, countOutgoingEdges, classifyProcessBuilder, classifyWorkflowRule, classifyApprovalProcess, migrationNoteForProcessBuilder, migrationNoteForWorkflowRule, migrationNoteForApprovalProcess, complexityRank, compareByComplexity, compareByApiName2, compareByParent, sortFor, processBuilderMigrationCandidatesHandler;
|
|
22226
22619
|
var init_process_builder_migration_candidates = __esm({
|
|
22227
22620
|
"../mcp/dist/src/tools/process-builder-migration-candidates.js"() {
|
|
@@ -22237,12 +22630,12 @@ var init_process_builder_migration_candidates = __esm({
|
|
|
22237
22630
|
"the complexity classification is heuristic based on edge counts and time-trigger presence; complex business logic in a single-decision Process Builder may rank as 'simple' but require manual review for migration.",
|
|
22238
22631
|
"the migration tool itself (Setup \u2192 Migrate to Flow) does not run here \u2014 this tool produces the inventory and per-rule guidance."
|
|
22239
22632
|
]);
|
|
22240
|
-
processBuilderMigrationCandidatesInputSchema =
|
|
22241
|
-
includeWorkflowRules:
|
|
22242
|
-
includeApprovalProcesses:
|
|
22243
|
-
activeOnly:
|
|
22244
|
-
sortBy:
|
|
22245
|
-
limit:
|
|
22633
|
+
processBuilderMigrationCandidatesInputSchema = z88.object({
|
|
22634
|
+
includeWorkflowRules: z88.boolean().optional(),
|
|
22635
|
+
includeApprovalProcesses: z88.boolean().optional(),
|
|
22636
|
+
activeOnly: z88.boolean().optional(),
|
|
22637
|
+
sortBy: z88.enum(["complexity", "object", "name"]).optional(),
|
|
22638
|
+
limit: z88.number().int().min(1).max(PROCESS_BUILDER_MAX_LIMIT).optional()
|
|
22246
22639
|
});
|
|
22247
22640
|
propertyNumber = (node, key) => {
|
|
22248
22641
|
const v = node.properties[key];
|
|
@@ -22599,7 +22992,7 @@ var init_clarify = __esm({
|
|
|
22599
22992
|
});
|
|
22600
22993
|
|
|
22601
22994
|
// ../mcp/dist/src/tools/resolve.js
|
|
22602
|
-
import { z as
|
|
22995
|
+
import { z as z89 } from "zod";
|
|
22603
22996
|
var RESOLVE_MAX_LIMIT, RESOLVE_DISCLOSURE, resolveInputSchema, resolveHandler;
|
|
22604
22997
|
var init_resolve2 = __esm({
|
|
22605
22998
|
"../mcp/dist/src/tools/resolve.js"() {
|
|
@@ -22610,11 +23003,11 @@ var init_resolve2 = __esm({
|
|
|
22610
23003
|
init_clarify();
|
|
22611
23004
|
RESOLVE_MAX_LIMIT = 50;
|
|
22612
23005
|
RESOLVE_DISCLOSURE = "These are typo-tolerant, fuzzy-ranked guesses at which component you meant \u2014 heuristic, not declared. disposition 'exact' = one confident match; 'ambiguous' = several plausible candidates, confirm the right one before acting; 'none' = nothing matched confidently (any listed items are weak near-misses). A high score is string similarity, not proof \u2014 verify the candidate's canonical id and label.";
|
|
22613
|
-
resolveInputSchema =
|
|
22614
|
-
query:
|
|
22615
|
-
types:
|
|
22616
|
-
parentId:
|
|
22617
|
-
limit:
|
|
23006
|
+
resolveInputSchema = z89.object({
|
|
23007
|
+
query: z89.string().min(1),
|
|
23008
|
+
types: z89.array(z89.string()).optional(),
|
|
23009
|
+
parentId: z89.string().min(1).optional(),
|
|
23010
|
+
limit: z89.number().int().min(1).max(RESOLVE_MAX_LIMIT).optional()
|
|
22618
23011
|
});
|
|
22619
23012
|
resolveHandler = async (ctx, input2) => {
|
|
22620
23013
|
const result = await resolveComponents(ctx.graph, input2.query, {
|
|
@@ -22671,11 +23064,41 @@ var init_resolve2 = __esm({
|
|
|
22671
23064
|
import { appendFile, mkdir as mkdir6 } from "node:fs/promises";
|
|
22672
23065
|
import { homedir as homedir2 } from "node:os";
|
|
22673
23066
|
import { dirname as dirname5, join as join9 } from "node:path";
|
|
22674
|
-
var normalize, routeText, RULES, classifyQuestion, gapLogPath, logGapIfAny;
|
|
23067
|
+
var normalize, deriveSaveEvent, deriveKnowledgeTopic, routeText, RULES, classifyQuestion, gapLogPath, logGapIfAny;
|
|
22675
23068
|
var init_intent_router = __esm({
|
|
22676
23069
|
"../mcp/dist/src/intent-router.js"() {
|
|
22677
23070
|
"use strict";
|
|
22678
23071
|
normalize = (q) => q.trim().toLowerCase().replace(/\s+/g, " ");
|
|
23072
|
+
deriveSaveEvent = (q) => {
|
|
23073
|
+
if (/\b(undelet|restor)/.test(q))
|
|
23074
|
+
return "undelete";
|
|
23075
|
+
if (/\b(creat|insert)/.test(q))
|
|
23076
|
+
return "insert";
|
|
23077
|
+
if (/\b(delet|remov)/.test(q))
|
|
23078
|
+
return "delete";
|
|
23079
|
+
return "update";
|
|
23080
|
+
};
|
|
23081
|
+
deriveKnowledgeTopic = (q) => {
|
|
23082
|
+
if (/\b(flow\s+(vs\.?|versus|or)\s+apex|apex\s+(vs\.?|versus|or)\s+flow)\b/.test(q))
|
|
23083
|
+
return "flow-vs-apex";
|
|
23084
|
+
if (/\bwhen\s+(should\s+i|to)\s+use\b.*\bflow\b/.test(q))
|
|
23085
|
+
return "flow-vs-apex";
|
|
23086
|
+
if (/\b(apex\s+)?trigger\s+framework\b/.test(q))
|
|
23087
|
+
return "trigger-framework";
|
|
23088
|
+
if (/\basync(hronous)?\s+apex\b/.test(q) || /\b(future|queueable|batch|schedulable)\b.*\b(vs\.?|versus|when\s+to|which\s+to|each\s+appropriate|options?)\b/.test(q))
|
|
23089
|
+
return "async-apex";
|
|
23090
|
+
if (/\b(sfdx|source[-\s]driven\s+development|scratch\s+orgs?)\b/.test(q))
|
|
23091
|
+
return "sfdx-source-driven-dev";
|
|
23092
|
+
if (/\bunlocked\s+packages?\b/.test(q))
|
|
23093
|
+
return "package-strategy";
|
|
23094
|
+
if (/\bsingle[-\s]org\b.*\bmulti[-\s]org\b/.test(q))
|
|
23095
|
+
return "single-vs-multi-org";
|
|
23096
|
+
if (/\bdata\s+(retention|archiv)/.test(q))
|
|
23097
|
+
return "data-retention-archiving";
|
|
23098
|
+
if (/\b(large\s+data\s+volumes?|\bldv\b)/.test(q))
|
|
23099
|
+
return "large-data-volumes";
|
|
23100
|
+
return void 0;
|
|
23101
|
+
};
|
|
22679
23102
|
routeText = (question) => {
|
|
22680
23103
|
const normalized = normalize(question).replace(/\s*\[[^\]]+\]\s*$/, "");
|
|
22681
23104
|
const stripped = normalized.replace(/^.*?\b(answer this|route this|do not guess|tell me|include|show what|fail closed|flag anything|use the safest|state what)\b[^:]*:\s*/, "");
|
|
@@ -22737,7 +23160,10 @@ var init_intent_router = __esm({
|
|
|
22737
23160
|
needsResolve: false,
|
|
22738
23161
|
reason: "Storage, API usage, and governor headroom are live org telemetry.",
|
|
22739
23162
|
patterns: [
|
|
22740
|
-
|
|
23163
|
+
// "governor limits" → live runtime headroom, BUT not when the question is
|
|
23164
|
+
// about static Apex risk ("governor limit risks in our Apex", "SOQL in
|
|
23165
|
+
// loops") — that's the vault governor-risks scan (NI-7 misroute fix).
|
|
23166
|
+
/\b(org|governor)\s+limits?\b(?!.*\b(risk|apex|loop|soql|dml|static|trigger|class)\b)/,
|
|
22741
23167
|
/\b(api|daily)\s+(usage|calls?|limit)\b/,
|
|
22742
23168
|
/\b(data|file)\s+storage\b/,
|
|
22743
23169
|
/\bhow\s+much\s+(storage|api|data)\b/,
|
|
@@ -22771,6 +23197,25 @@ var init_intent_router = __esm({
|
|
|
22771
23197
|
/\brecords?\s+by\s+owner\b/
|
|
22772
23198
|
]
|
|
22773
23199
|
},
|
|
23200
|
+
{
|
|
23201
|
+
// METADATA counts are vault, not live. "How many layouts / fields / objects
|
|
23202
|
+
// / profiles / validation rules ... [per X]" was stolen by the live
|
|
23203
|
+
// group-count/record-count rules and misrouted to live GROUP BY (B30.1).
|
|
23204
|
+
// Record counts (accounts, contacts, rows) fall through to the live rules
|
|
23205
|
+
// below. Placed AFTER field-population so "how many fields are populated"
|
|
23206
|
+
// stays hybrid/live.
|
|
23207
|
+
intent: "metadata-count",
|
|
23208
|
+
plane: "vault",
|
|
23209
|
+
tools: ["sfi.list_components", "sfi.get_component"],
|
|
23210
|
+
liveRequired: false,
|
|
23211
|
+
needsResolve: false,
|
|
23212
|
+
reason: "Counting metadata components (layouts, fields, objects, profiles, validation rules, flows, classes, record types, list views) is a vault list_components count \u2014 not live record data.",
|
|
23213
|
+
patterns: [
|
|
23214
|
+
/\bhow\s+many\b.*\b(page\s+layouts?|layouts?|custom\s+objects?|profiles?|permission\s+sets?|validation\s+rules?|flows?|(apex\s+)?classes?|triggers?|record\s+types?|list\s+views?|report\s+types?|record\s+pages?|flexipages?|approval\s+process(es)?|custom\s+settings?|quick\s+actions?|sharing\s+rules?|named\s+credentials?|picklists?)\b/,
|
|
23215
|
+
// fields, but NOT field usage/population (those are unused-fields / field-population)
|
|
23216
|
+
/\bhow\s+many\b.*\b(custom\s+)?fields?\b(?!.*\b(used|populated|filled|actually|unused|empty|blank|set|values?)\b)/
|
|
23217
|
+
]
|
|
23218
|
+
},
|
|
22774
23219
|
{
|
|
22775
23220
|
intent: "group-count",
|
|
22776
23221
|
plane: "live",
|
|
@@ -22958,7 +23403,27 @@ var init_intent_router = __esm({
|
|
|
22958
23403
|
patterns: [
|
|
22959
23404
|
/\b(page\s+)?layouts?\b.*\b(who|access|assigned|profile|sees?|user)\b/,
|
|
22960
23405
|
/\bwho\s+(sees|has|can)\b.*\blayouts?\b/,
|
|
22961
|
-
/\bwhich\s+layout\b
|
|
23406
|
+
/\bwhich\s+layout\b/,
|
|
23407
|
+
// "what/which (page) layouts show|contain|have a FIELD" — a field->layout
|
|
23408
|
+
// question (vs the who-sees-layout above) used to fall through (B21).
|
|
23409
|
+
/\b(what|which)\s+(page\s+)?layouts?\b.*\b(show|contain|display|include|have|with|for)\b.*\bfield\b/
|
|
23410
|
+
]
|
|
23411
|
+
},
|
|
23412
|
+
{
|
|
23413
|
+
// Layout INVENTORY + CONTENTS (vs layout-access's who-sees-which). "How many
|
|
23414
|
+
// layouts exist for X", "what fields / related lists / quick actions are on
|
|
23415
|
+
// the Y layout" used to fall through to generic schema or unrouted (B21.1).
|
|
23416
|
+
intent: "layout-inventory",
|
|
23417
|
+
plane: "vault",
|
|
23418
|
+
tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
|
|
23419
|
+
liveRequired: false,
|
|
23420
|
+
needsResolve: false,
|
|
23421
|
+
reason: "Page-layout inventory (how many / which layouts on an object) and contents (fields, related lists, quick actions on a named layout) are modeled in the vault \u2014 Layout is a covered type.",
|
|
23422
|
+
patterns: [
|
|
23423
|
+
/\bhow\s+many\b.*\blayouts?\b/,
|
|
23424
|
+
/\b(what|which|list)\b.*\b(page\s+)?layouts?\b.*\b(exist|are\s+there|for\s+the|on\s+the|does|available)\b/,
|
|
23425
|
+
/\bwhat\b.*\b(fields?|related\s+lists?|quick\s+actions?|sections?|buttons?)\b.*\bon\b.*\blayout\b/,
|
|
23426
|
+
/\b(related\s+lists?|quick\s+actions?)\b.*\b(on|appear|for)\b.*\blayout\b/
|
|
22962
23427
|
]
|
|
22963
23428
|
},
|
|
22964
23429
|
{
|
|
@@ -22999,9 +23464,13 @@ var init_intent_router = __esm({
|
|
|
22999
23464
|
needsResolve: false,
|
|
23000
23465
|
reason: "God-mode / over-permission detection is a vault permission synthesis.",
|
|
23001
23466
|
patterns: [
|
|
23002
|
-
/\b(over[-\s]?permission|god[-\s]?mode|too\s+much\s+access|modify\s+all|view\s+all)\b/,
|
|
23467
|
+
/\b(over[-\s]?permission(ed|s)?|god[-\s]?mode|too\s+much\s+access|modify\s+all|view\s+all)\b/,
|
|
23003
23468
|
/\bwho\s+is\s+(an?\s+)?admin\b/,
|
|
23004
|
-
/\b(permission|access)\s+(risk|sprawl|hygiene)\b
|
|
23469
|
+
/\b(permission|access)\s+(risk|sprawl|hygiene)\b/,
|
|
23470
|
+
// Specific high-risk system permissions — "who can author apex / customize
|
|
23471
|
+
// the application / manage users / modify metadata / view setup" — all
|
|
23472
|
+
// answered by the permission_risk_report over-privilege pass.
|
|
23473
|
+
/\bwho\s+(can|has)\b.*\b(author\s+apex|customi[sz]e\s+(the\s+)?application|manage\s+(users|sharing|roles|profiles)|modify\s+metadata|view\s+setup)\b/
|
|
23005
23474
|
]
|
|
23006
23475
|
},
|
|
23007
23476
|
{
|
|
@@ -23101,14 +23570,21 @@ var init_intent_router = __esm({
|
|
|
23101
23570
|
liveRequired: false,
|
|
23102
23571
|
needsResolve: true,
|
|
23103
23572
|
reason: "Order of execution / what runs on save is reconstructed from the vault graph. what_happens_on_save needs an explicit DML event \u2014 default to 'update' (or insert/delete to match the question) when none is stated.",
|
|
23573
|
+
suggestArgs: (q) => ({ event: deriveSaveEvent(q) }),
|
|
23104
23574
|
patterns: [
|
|
23105
23575
|
/\b(trigger\s+order|order\s+of\s+execution)\b/,
|
|
23106
|
-
/\bwhat\s+(happens|runs|fires)\b.*\b(on\s+save|when\b.*\b(created|saved|updated|inserted|deleted))\b/,
|
|
23576
|
+
/\bwhat\s+(happens|runs|fires)\b.*\b(on\s+save|when\b.*\b(created|saved|updated|inserted|deleted|undeleted|restored))\b/,
|
|
23107
23577
|
// "which/what flows|triggers|VRs|workflows run|fire when ..." — the
|
|
23108
23578
|
// "which" phrasing was a router gap (e.g. "which flows run when a Case is
|
|
23109
23579
|
// created"), so the question fell through to unrouted.
|
|
23110
23580
|
/\b(what|which)\s+(triggers?|automation|flows?|validation\s+rules?|workflows?)\b.*\b(fire|run|execute|happen)\b/,
|
|
23111
|
-
/\b(flows?|triggers?|automation)\b.*\bwhen\b.*\b\w+\s+is\b.*\b(created|updated|inserted|deleted|saved)\b
|
|
23581
|
+
/\b(flows?|triggers?|automation)\b.*\bwhen\b.*\b\w+\s+is\b.*\b(created|updated|inserted|deleted|saved)\b/,
|
|
23582
|
+
// "what APEX/code/class runs when ..." — a noun between "what" and the
|
|
23583
|
+
// verb (vs the adjacent "what runs") used to fall through (B21).
|
|
23584
|
+
/\bwhat\s+(apex|code|class(es)?)\b.*\bruns?\b.*\bwhen\b/,
|
|
23585
|
+
// "what happens when a Case STATUS CHANGES" — a status/stage change is a
|
|
23586
|
+
// save-order event the "(created|updated|...)" verb list missed (B21).
|
|
23587
|
+
/\bwhat\s+(happens|runs|fires)\b.*\b(status|stage)\b.*\bchang/
|
|
23112
23588
|
]
|
|
23113
23589
|
},
|
|
23114
23590
|
{
|
|
@@ -23124,6 +23600,25 @@ var init_intent_router = __esm({
|
|
|
23124
23600
|
/\bwhat\s+changed\b.*\bfield\b.*\bon\s+save\b/
|
|
23125
23601
|
]
|
|
23126
23602
|
},
|
|
23603
|
+
{
|
|
23604
|
+
// What a validation rule enforces / its error — get_component on the rule.
|
|
23605
|
+
// MUST precede automation-on-object, which else steals "what does the X
|
|
23606
|
+
// validation rule on <object> enforce" (it has "validation rule … on
|
|
23607
|
+
// account") and sends it to automation_build_advisor instead of the rule's
|
|
23608
|
+
// formula+error (NI-11). "What validation rules exist/run" stays schema /
|
|
23609
|
+
// trigger-order (those lack the enforce/does/error verbs below).
|
|
23610
|
+
intent: "explain-validation-rule",
|
|
23611
|
+
plane: "vault",
|
|
23612
|
+
tools: ["sfi.resolve", "sfi.get_component"],
|
|
23613
|
+
liveRequired: false,
|
|
23614
|
+
needsResolve: true,
|
|
23615
|
+
reason: "A validation rule's condition and error message are in the vault \u2014 resolve the rule, then get_component for its formula and error text.",
|
|
23616
|
+
patterns: [
|
|
23617
|
+
/\bvalidation\s+rules?\b.*\b(enforce|does|do\b|check|mean|prevent|block|error|message|stop)\b/,
|
|
23618
|
+
/\bwhat\s+(does|error|message)\b.*\bvalidation\s+rule\b/,
|
|
23619
|
+
/\b(error|message)\b.*\bvalidation\s+rule\b.*\b(show|display|return)\b/
|
|
23620
|
+
]
|
|
23621
|
+
},
|
|
23127
23622
|
{
|
|
23128
23623
|
intent: "automation-on-object",
|
|
23129
23624
|
plane: "vault",
|
|
@@ -23330,6 +23825,23 @@ var init_intent_router = __esm({
|
|
|
23330
23825
|
/\borg\s+(risk|health)\b/
|
|
23331
23826
|
]
|
|
23332
23827
|
},
|
|
23828
|
+
{
|
|
23829
|
+
// "Which classes implement <interface>" (Batchable, Schedulable, Queueable,
|
|
23830
|
+
// RestResource, ...) — grep Apex source for the implements clause. The
|
|
23831
|
+
// apex-search verbs (uses/references) didn't cover "implement" (B21.16/17,
|
|
23832
|
+
// E.4-6 / BL-13 interface filters).
|
|
23833
|
+
intent: "interface-implementers",
|
|
23834
|
+
plane: "vault",
|
|
23835
|
+
tools: ["sfi.search_apex_source", "sfi.find_apex_usages"],
|
|
23836
|
+
liveRequired: false,
|
|
23837
|
+
needsResolve: false,
|
|
23838
|
+
reason: "Classes implementing an interface (Batchable / Schedulable / Queueable / RestResource) are found by grepping Apex source for the implements clause.",
|
|
23839
|
+
patterns: [
|
|
23840
|
+
/\b(which|what)\s+(classes?|apex)\b.*\bimplements?\b/,
|
|
23841
|
+
/\bimplements?\b.*\b(batchable|schedulable|queueable|database\.\w+|interface)\b/,
|
|
23842
|
+
/\b(batchable|schedulable|queueable)\b.*\b(classes?|implement)\b/
|
|
23843
|
+
]
|
|
23844
|
+
},
|
|
23333
23845
|
{
|
|
23334
23846
|
intent: "apex-search",
|
|
23335
23847
|
plane: "vault",
|
|
@@ -23357,19 +23869,53 @@ var init_intent_router = __esm({
|
|
|
23357
23869
|
/\bapi\b.*\b(connections?|surfaces?)\b/
|
|
23358
23870
|
]
|
|
23359
23871
|
},
|
|
23872
|
+
{
|
|
23873
|
+
// "What platform events does this org publish / exist?" → CATALOG mode
|
|
23874
|
+
// (event_subscribers with NO eventId). No named component to resolve —
|
|
23875
|
+
// distinct from who-subscribes-to-EVENT-X below. NI-1: follow-up to the D6
|
|
23876
|
+
// catalog fix so the route stops telling the agent to resolve a component
|
|
23877
|
+
// that was never named.
|
|
23878
|
+
intent: "event-catalog",
|
|
23879
|
+
plane: "vault",
|
|
23880
|
+
tools: ["sfi.event_subscribers"],
|
|
23881
|
+
liveRequired: false,
|
|
23882
|
+
needsResolve: false,
|
|
23883
|
+
reason: "Lists every Platform Event the org publishes with its subscriber count (event_subscribers catalog mode \u2014 call with no eventId).",
|
|
23884
|
+
patterns: [
|
|
23885
|
+
/\bwhat\s+(platform\s+events?|cdc\s+channels?)\b/,
|
|
23886
|
+
/\b(platform\s+events?)\b.*\b(publish|emit|defined|exist|list|are\s+there)\b/
|
|
23887
|
+
]
|
|
23888
|
+
},
|
|
23360
23889
|
{
|
|
23361
23890
|
intent: "event-subscribers",
|
|
23362
23891
|
plane: "vault",
|
|
23363
23892
|
tools: ["sfi.resolve", "sfi.event_subscribers", "sfi.cdc_subscribers"],
|
|
23364
23893
|
liveRequired: false,
|
|
23365
23894
|
needsResolve: true,
|
|
23366
|
-
reason: "Who subscribes to a platform event / change data capture channel.",
|
|
23895
|
+
reason: "Who subscribes to a specific platform event / change data capture channel.",
|
|
23367
23896
|
patterns: [
|
|
23368
23897
|
/\b(platform\s+events?|change\s+data\s+capture|cdc)\b.*\b(subscrib|listen|consum)\b/,
|
|
23369
23898
|
/\bwho\s+(subscribes?|listens?)\b/,
|
|
23370
23899
|
/\b(subscribers?|subscriptions?)\b.*\bevents?\b/
|
|
23371
23900
|
]
|
|
23372
23901
|
},
|
|
23902
|
+
{
|
|
23903
|
+
// Inbound Apex REST (@RestResource) — "what REST endpoints are exposed".
|
|
23904
|
+
// MUST precede the outbound `endpoints` rule, which would otherwise grab
|
|
23905
|
+
// "endpoints" and misroute to the outbound catalog (B21.15).
|
|
23906
|
+
intent: "rest-endpoints",
|
|
23907
|
+
plane: "vault",
|
|
23908
|
+
tools: ["sfi.search_apex_source", "sfi.find_apex_usages"],
|
|
23909
|
+
liveRequired: false,
|
|
23910
|
+
needsResolve: false,
|
|
23911
|
+
reason: "Inbound Apex REST endpoints are @RestResource/@Http* classes \u2014 grep Apex source for the annotations.",
|
|
23912
|
+
patterns: [
|
|
23913
|
+
/\b(apex\s+)?rest\s+(endpoints?|resources?|services?|api)\b/,
|
|
23914
|
+
/\b@?restresource\b/,
|
|
23915
|
+
/\b(inbound|exposed|expose)\b.*\brest\b/,
|
|
23916
|
+
/\brest\b.*\b(endpoints?|resources?)\b.*\b(expose|exposed|apex|class)\b/
|
|
23917
|
+
]
|
|
23918
|
+
},
|
|
23373
23919
|
{
|
|
23374
23920
|
intent: "endpoints",
|
|
23375
23921
|
plane: "vault",
|
|
@@ -23434,6 +23980,24 @@ var init_intent_router = __esm({
|
|
|
23434
23980
|
/\b(safe\s+to\s+delete|can\s+i\s+delete|ok\s+to\s+(delete|remove))\b/
|
|
23435
23981
|
]
|
|
23436
23982
|
},
|
|
23983
|
+
{
|
|
23984
|
+
// MUST precede impact-analysis + what-if-field: a question about changing a
|
|
23985
|
+
// field's stored VALUE (not its type/required/deletion) routes to the
|
|
23986
|
+
// value-change tier. The discriminator is the word "value(s)" near a
|
|
23987
|
+
// change/impact verb; schema what-ifs say "field type" / "delete" / "required".
|
|
23988
|
+
intent: "value-change",
|
|
23989
|
+
plane: "vault",
|
|
23990
|
+
tools: ["sfi.resolve", "sfi.value_change_audit", "sfi.what_if_change_field_value"],
|
|
23991
|
+
liveRequired: false,
|
|
23992
|
+
needsResolve: true,
|
|
23993
|
+
reason: "Changing a field's stored VALUE (not its schema) has a distinct blast radius \u2014 identity / integration-key / uniqueness / automation / cross-object \u2014 surfaced by value_change_audit (a set of fields on an object) or what_if_change_field_value (one field). Distinct from the type/required/delete what-ifs.",
|
|
23994
|
+
patterns: [
|
|
23995
|
+
/\bvalue[-\s]changes?\b/,
|
|
23996
|
+
/\b(chang|updat|edit|modif|bulk[-\s]?updat)\w*\b[^.?!]{0,40}\bvalues?\b/,
|
|
23997
|
+
/\bvalues?\b[^.?!]{0,40}\b(impact|affect|break|desync|safe|risk)\w*/,
|
|
23998
|
+
/\b(impact|affect|safe|risky?)\b[^.?!]{0,70}\b(chang|updat|edit|modif)\w*\b[^.?!]{0,40}\bvalues?\b/
|
|
23999
|
+
]
|
|
24000
|
+
},
|
|
23437
24001
|
{
|
|
23438
24002
|
intent: "impact-analysis",
|
|
23439
24003
|
plane: "vault",
|
|
@@ -23646,6 +24210,37 @@ var init_intent_router = __esm({
|
|
|
23646
24210
|
]
|
|
23647
24211
|
},
|
|
23648
24212
|
// === Schema / naming (general — near the end) =============================
|
|
24213
|
+
{
|
|
24214
|
+
// KNOWLEDGE plane (B1) — greenfield/best-practice asks with NO org-specific
|
|
24215
|
+
// answer. Patterns are deliberately narrow (only asks no vault rule already
|
|
24216
|
+
// catches: flow-vs-apex, trigger framework, async options, SFDX, unlocked
|
|
24217
|
+
// packages) so org-specific questions still route to vault/live. Other
|
|
24218
|
+
// greenfield topics (governor limits, callouts, coverage, naming) are caught
|
|
24219
|
+
// by their vault rules earlier and stay org-specific; their curated topics
|
|
24220
|
+
// remain reachable via an explicit sfi.guidance { topic } call.
|
|
24221
|
+
intent: "guidance",
|
|
24222
|
+
plane: "knowledge",
|
|
24223
|
+
tools: ["sfi.guidance"],
|
|
24224
|
+
liveRequired: false,
|
|
24225
|
+
needsResolve: false,
|
|
24226
|
+
reason: "General Salesforce best-practice question with no org-specific answer \u2014 sfi.guidance returns a curated summary + official doc links (not org data).",
|
|
24227
|
+
suggestArgs: (q) => {
|
|
24228
|
+
const topic = deriveKnowledgeTopic(q);
|
|
24229
|
+
return topic !== void 0 ? { topic } : void 0;
|
|
24230
|
+
},
|
|
24231
|
+
patterns: [
|
|
24232
|
+
/\b(flow\s+(vs\.?|versus|or)\s+apex|apex\s+(vs\.?|versus|or)\s+flow)\b/,
|
|
24233
|
+
/\bwhen\s+(should\s+i|to)\s+use\b.*\bflow\b/,
|
|
24234
|
+
/\b(apex\s+)?trigger\s+framework\b/,
|
|
24235
|
+
/\basync(hronous)?\s+apex\b/,
|
|
24236
|
+
/\b(future|queueable|batch|schedulable)\b.*\b(vs\.?|versus|when\s+to|which\s+to|each\s+appropriate|options?)\b/,
|
|
24237
|
+
/\b(sfdx|source[-\s]driven\s+development|scratch\s+orgs?)\b/,
|
|
24238
|
+
/\bunlocked\s+packages?\b/,
|
|
24239
|
+
/\bsingle[-\s]org\b.*\bmulti[-\s]org\b/,
|
|
24240
|
+
/\bdata\s+(retention|archiv)/,
|
|
24241
|
+
/\b(large\s+data\s+volumes?|\bldv\b)/
|
|
24242
|
+
]
|
|
24243
|
+
},
|
|
23649
24244
|
{
|
|
23650
24245
|
intent: "resolve-lookup",
|
|
23651
24246
|
plane: "vault",
|
|
@@ -23673,6 +24268,92 @@ var init_intent_router = __esm({
|
|
|
23673
24268
|
/\b(suffix|prefix)\b.*\b(convention|standard)\b/
|
|
23674
24269
|
]
|
|
23675
24270
|
},
|
|
24271
|
+
{
|
|
24272
|
+
// Lightning record pages / FlexiPages — assignment + component breakdown.
|
|
24273
|
+
// "Which Lightning record page is assigned to X", "what components are on
|
|
24274
|
+
// the Y record page" fell through to schema/unrouted (B21.2).
|
|
24275
|
+
intent: "flexipage",
|
|
24276
|
+
plane: "vault",
|
|
24277
|
+
tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
|
|
24278
|
+
liveRequired: false,
|
|
24279
|
+
needsResolve: false,
|
|
24280
|
+
reason: "Lightning record pages (FlexiPage) \u2014 assignment and component breakdown \u2014 are modeled in the vault.",
|
|
24281
|
+
patterns: [
|
|
24282
|
+
/\b(lightning\s+(record\s+)?page|flexipage)s?\b/,
|
|
24283
|
+
/\b(component|assign|which|what)\b.*\brecord\s+pages?\b/,
|
|
24284
|
+
/\brecord\s+pages?\b.*\b(assign|component|layout|for\s+the)\b/
|
|
24285
|
+
]
|
|
24286
|
+
},
|
|
24287
|
+
{
|
|
24288
|
+
// Picklist-value differences across record types — get_component on each
|
|
24289
|
+
// RecordType shows its picklist value sets. Fell through (B21.4).
|
|
24290
|
+
intent: "record-type-picklist",
|
|
24291
|
+
plane: "vault",
|
|
24292
|
+
tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
|
|
24293
|
+
liveRequired: false,
|
|
24294
|
+
needsResolve: false,
|
|
24295
|
+
reason: "Record-type picklist-value assignments are in the vault \u2014 list the record types, then get_component each to compare picklist values.",
|
|
24296
|
+
patterns: [
|
|
24297
|
+
/\brecord\s+types?\b.*\b(picklist|differ|difference|values?)\b/,
|
|
24298
|
+
/\bpicklist\s+values?\b.*\b(record\s+types?|differ|between|across)\b/,
|
|
24299
|
+
/\bwhich\s+picklist\s+values?\b.*\brecord\s+type/
|
|
24300
|
+
]
|
|
24301
|
+
},
|
|
24302
|
+
{
|
|
24303
|
+
// Approval processes — list + steps. Fell through to schema/unrouted (B21.6).
|
|
24304
|
+
intent: "approval-process",
|
|
24305
|
+
plane: "vault",
|
|
24306
|
+
tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
|
|
24307
|
+
liveRequired: false,
|
|
24308
|
+
needsResolve: false,
|
|
24309
|
+
reason: "Approval processes and their steps are modeled in the vault (ApprovalProcess).",
|
|
24310
|
+
patterns: [
|
|
24311
|
+
/\bapproval\s+process(es)?\b/,
|
|
24312
|
+
/\bapproval\s+steps?\b/,
|
|
24313
|
+
/\bapproval\b.*\b(steps?|process|who\s+approves?|approvers?|stages?)\b/
|
|
24314
|
+
]
|
|
24315
|
+
},
|
|
24316
|
+
{
|
|
24317
|
+
// List views inventory (B21.8). Note: ListView is retrieved but not
|
|
24318
|
+
// graph-modeled (notModeled), so relationships are limited — list only.
|
|
24319
|
+
intent: "list-views",
|
|
24320
|
+
plane: "vault",
|
|
24321
|
+
tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
|
|
24322
|
+
liveRequired: false,
|
|
24323
|
+
needsResolve: false,
|
|
24324
|
+
reason: "List views are catalogued in the vault via list_components (ListView). Coverage note: ListView is retrieved but not graph-modeled.",
|
|
24325
|
+
patterns: [/\blist\s+views?\b/]
|
|
24326
|
+
},
|
|
24327
|
+
{
|
|
24328
|
+
// Custom Settings vs Custom Metadata Types — both retrieved as CustomObject.
|
|
24329
|
+
// Disambiguation gap (B21.9).
|
|
24330
|
+
intent: "custom-settings-cmdt",
|
|
24331
|
+
plane: "vault",
|
|
24332
|
+
tools: ["sfi.resolve", "sfi.list_components", "sfi.get_component"],
|
|
24333
|
+
liveRequired: false,
|
|
24334
|
+
needsResolve: false,
|
|
24335
|
+
reason: "Custom Settings and Custom Metadata Types are modeled as CustomObject in the vault \u2014 list + get_component to see what they store.",
|
|
24336
|
+
patterns: [
|
|
24337
|
+
/\bcustom\s+settings?\b/,
|
|
24338
|
+
/\bcustom\s+metadata\s+types?\b/,
|
|
24339
|
+
/\b(cmdt|__mdt)\b/,
|
|
24340
|
+
/\bhierarch(y|ical|ic)\b.*\bsettings?\b/
|
|
24341
|
+
]
|
|
24342
|
+
},
|
|
24343
|
+
{
|
|
24344
|
+
// Difference between two profiles / permission sets (B21.10).
|
|
24345
|
+
intent: "compare-profiles",
|
|
24346
|
+
plane: "vault",
|
|
24347
|
+
tools: ["sfi.resolve", "sfi.compare_components"],
|
|
24348
|
+
liveRequired: false,
|
|
24349
|
+
needsResolve: true,
|
|
24350
|
+
reason: "Comparing two profiles or permission sets is a vault diff \u2014 resolve both, then compare_components.",
|
|
24351
|
+
patterns: [
|
|
24352
|
+
/\b(difference|differ|compare|versus|vs\.?)\b.*\bprofiles?\b/,
|
|
24353
|
+
/\bprofiles?\b.*\b(difference|differ|compare|versus|vs\b)\b/,
|
|
24354
|
+
/\bcompare\b.*\b(permission\s+sets?|profiles?)\b/
|
|
24355
|
+
]
|
|
24356
|
+
},
|
|
23676
24357
|
{
|
|
23677
24358
|
intent: "schema",
|
|
23678
24359
|
plane: "vault",
|
|
@@ -23707,6 +24388,7 @@ var init_intent_router = __esm({
|
|
|
23707
24388
|
}
|
|
23708
24389
|
for (const rule of RULES) {
|
|
23709
24390
|
if (rule.patterns.some((p) => p.test(q))) {
|
|
24391
|
+
const suggestedArgs = rule.suggestArgs?.(q);
|
|
23710
24392
|
return {
|
|
23711
24393
|
question,
|
|
23712
24394
|
plane: rule.plane,
|
|
@@ -23715,7 +24397,8 @@ var init_intent_router = __esm({
|
|
|
23715
24397
|
liveRequired: rule.liveRequired,
|
|
23716
24398
|
needsResolve: rule.needsResolve,
|
|
23717
24399
|
reason: rule.reason,
|
|
23718
|
-
gap: rule.gap ?? null
|
|
24400
|
+
gap: rule.gap ?? null,
|
|
24401
|
+
...suggestedArgs !== void 0 ? { suggestedArgs } : {}
|
|
23719
24402
|
};
|
|
23720
24403
|
}
|
|
23721
24404
|
}
|
|
@@ -23763,7 +24446,7 @@ var init_intent_router = __esm({
|
|
|
23763
24446
|
});
|
|
23764
24447
|
|
|
23765
24448
|
// ../mcp/dist/src/tools/route-question.js
|
|
23766
|
-
import { z as
|
|
24449
|
+
import { z as z90 } from "zod";
|
|
23767
24450
|
var routeQuestionInputSchema, routeTrust, routeQuestionHandler;
|
|
23768
24451
|
var init_route_question = __esm({
|
|
23769
24452
|
"../mcp/dist/src/tools/route-question.js"() {
|
|
@@ -23771,11 +24454,11 @@ var init_route_question = __esm({
|
|
|
23771
24454
|
init_dist();
|
|
23772
24455
|
init_answer_render();
|
|
23773
24456
|
init_intent_router();
|
|
23774
|
-
routeQuestionInputSchema =
|
|
24457
|
+
routeQuestionInputSchema = z90.object({
|
|
23775
24458
|
/** The user's plain-language question. */
|
|
23776
|
-
question:
|
|
24459
|
+
question: z90.string().min(1),
|
|
23777
24460
|
/** Append a gap entry to the local backlog when the route has one (default true). */
|
|
23778
|
-
logGap:
|
|
24461
|
+
logGap: z90.boolean().optional()
|
|
23779
24462
|
});
|
|
23780
24463
|
routeTrust = () => ({
|
|
23781
24464
|
provenance: "offline_snapshot",
|
|
@@ -23806,7 +24489,7 @@ var init_route_question = __esm({
|
|
|
23806
24489
|
});
|
|
23807
24490
|
|
|
23808
24491
|
// ../mcp/dist/src/tools/scheduled-job-catalog.js
|
|
23809
|
-
import { z as
|
|
24492
|
+
import { z as z91 } from "zod";
|
|
23810
24493
|
var SCHEDULED_JOB_CATALOG_MAX_CLASSES, scheduledJobCatalogInputSchema, SCHEDULED_JOB_CATALOG_DISCLOSURE, readCronExpressions, readEdgeCronExpression, readIsSchedulable, collectScheduledCalls, compareEntries4, compareCalls, scheduledJobCatalogHandler;
|
|
23811
24494
|
var init_scheduled_job_catalog = __esm({
|
|
23812
24495
|
"../mcp/dist/src/tools/scheduled-job-catalog.js"() {
|
|
@@ -23814,7 +24497,7 @@ var init_scheduled_job_catalog = __esm({
|
|
|
23814
24497
|
init_dist();
|
|
23815
24498
|
init_src();
|
|
23816
24499
|
SCHEDULED_JOB_CATALOG_MAX_CLASSES = 500;
|
|
23817
|
-
scheduledJobCatalogInputSchema =
|
|
24500
|
+
scheduledJobCatalogInputSchema = z91.object({});
|
|
23818
24501
|
SCHEDULED_JOB_CATALOG_DISCLOSURE = "Scanning for System.schedule() invocations is heuristic \u2014 the v0.3 Apex scanner detects literal call sites only, NOT runtime registration via Tooling API. Runtime schedules require Tooling API access (CronTrigger / AsyncApexJob). A class flagged `isSchedulable: true` may not currently be scheduled; conversely, a class scheduled via a helper-wrapper or dynamic class load is invisible to the scanner.";
|
|
23819
24502
|
readCronExpressions = (properties) => {
|
|
23820
24503
|
const raw = properties["cronExpressions"];
|
|
@@ -23903,7 +24586,7 @@ var init_scheduled_job_catalog = __esm({
|
|
|
23903
24586
|
|
|
23904
24587
|
// ../mcp/dist/src/tools/search-apex-source.js
|
|
23905
24588
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
23906
|
-
import { z as
|
|
24589
|
+
import { z as z92 } from "zod";
|
|
23907
24590
|
var SEARCH_APEX_SOURCE_MAX_LIMIT, SEARCH_APEX_SOURCE_DEFAULT_LIMIT, APEX_SUFFIXES, searchApexSourceInputSchema, searchApexSourceHandler, buildMatcher, searchFile;
|
|
23908
24591
|
var init_search_apex_source = __esm({
|
|
23909
24592
|
"../mcp/dist/src/tools/search-apex-source.js"() {
|
|
@@ -23913,10 +24596,10 @@ var init_search_apex_source = __esm({
|
|
|
23913
24596
|
SEARCH_APEX_SOURCE_MAX_LIMIT = 200;
|
|
23914
24597
|
SEARCH_APEX_SOURCE_DEFAULT_LIMIT = 50;
|
|
23915
24598
|
APEX_SUFFIXES = [".cls", ".trigger"];
|
|
23916
|
-
searchApexSourceInputSchema =
|
|
23917
|
-
query:
|
|
23918
|
-
regex:
|
|
23919
|
-
limit:
|
|
24599
|
+
searchApexSourceInputSchema = z92.object({
|
|
24600
|
+
query: z92.string().min(1),
|
|
24601
|
+
regex: z92.boolean().optional(),
|
|
24602
|
+
limit: z92.number().int().min(1).max(SEARCH_APEX_SOURCE_MAX_LIMIT).optional()
|
|
23920
24603
|
});
|
|
23921
24604
|
searchApexSourceHandler = async (ctx, input2) => {
|
|
23922
24605
|
const limit = input2.limit ?? SEARCH_APEX_SOURCE_DEFAULT_LIMIT;
|
|
@@ -23993,7 +24676,7 @@ var init_search_apex_source = __esm({
|
|
|
23993
24676
|
});
|
|
23994
24677
|
|
|
23995
24678
|
// ../mcp/dist/src/tools/search-components.js
|
|
23996
|
-
import { z as
|
|
24679
|
+
import { z as z93 } from "zod";
|
|
23997
24680
|
var SEARCH_MAX_LIMIT2, searchComponentsInputSchema, SUGGESTIONS_NOTE, searchComponentsHandler;
|
|
23998
24681
|
var init_search_components = __esm({
|
|
23999
24682
|
"../mcp/dist/src/tools/search-components.js"() {
|
|
@@ -24001,10 +24684,10 @@ var init_search_components = __esm({
|
|
|
24001
24684
|
init_dist();
|
|
24002
24685
|
init_src();
|
|
24003
24686
|
SEARCH_MAX_LIMIT2 = 100;
|
|
24004
|
-
searchComponentsInputSchema =
|
|
24005
|
-
query:
|
|
24006
|
-
limit:
|
|
24007
|
-
types:
|
|
24687
|
+
searchComponentsInputSchema = z93.object({
|
|
24688
|
+
query: z93.string().min(1),
|
|
24689
|
+
limit: z93.number().int().min(1).max(SEARCH_MAX_LIMIT2).optional(),
|
|
24690
|
+
types: z93.array(z93.string()).optional()
|
|
24008
24691
|
});
|
|
24009
24692
|
SUGGESTIONS_NOTE = "No exact substring matches. These are typo-tolerant, fuzzy-ranked guesses (heuristic) at what you may have meant \u2014 verify the canonical id before acting.";
|
|
24010
24693
|
searchComponentsHandler = async (ctx, input2) => {
|
|
@@ -24055,7 +24738,7 @@ var init_search_components = __esm({
|
|
|
24055
24738
|
|
|
24056
24739
|
// ../mcp/dist/src/tools/search-flow-metadata.js
|
|
24057
24740
|
import { readFile as readFile15 } from "node:fs/promises";
|
|
24058
|
-
import { z as
|
|
24741
|
+
import { z as z94 } from "zod";
|
|
24059
24742
|
var SEARCH_FLOW_METADATA_MAX_LIMIT, SEARCH_FLOW_METADATA_DEFAULT_LIMIT, FLOW_FILE_SUFFIX, searchFlowMetadataInputSchema, searchFlowMetadataHandler, buildMatcher2, searchFile2;
|
|
24060
24743
|
var init_search_flow_metadata = __esm({
|
|
24061
24744
|
"../mcp/dist/src/tools/search-flow-metadata.js"() {
|
|
@@ -24065,10 +24748,10 @@ var init_search_flow_metadata = __esm({
|
|
|
24065
24748
|
SEARCH_FLOW_METADATA_MAX_LIMIT = 200;
|
|
24066
24749
|
SEARCH_FLOW_METADATA_DEFAULT_LIMIT = 50;
|
|
24067
24750
|
FLOW_FILE_SUFFIX = ".flow-meta.xml";
|
|
24068
|
-
searchFlowMetadataInputSchema =
|
|
24069
|
-
query:
|
|
24070
|
-
regex:
|
|
24071
|
-
limit:
|
|
24751
|
+
searchFlowMetadataInputSchema = z94.object({
|
|
24752
|
+
query: z94.string().min(1),
|
|
24753
|
+
regex: z94.boolean().optional(),
|
|
24754
|
+
limit: z94.number().int().min(1).max(SEARCH_FLOW_METADATA_MAX_LIMIT).optional()
|
|
24072
24755
|
});
|
|
24073
24756
|
searchFlowMetadataHandler = async (ctx, input2) => {
|
|
24074
24757
|
const limit = input2.limit ?? SEARCH_FLOW_METADATA_DEFAULT_LIMIT;
|
|
@@ -24145,14 +24828,14 @@ var init_search_flow_metadata = __esm({
|
|
|
24145
24828
|
});
|
|
24146
24829
|
|
|
24147
24830
|
// ../mcp/dist/src/tools/snapshot-trend.js
|
|
24148
|
-
import { z as
|
|
24831
|
+
import { z as z95 } from "zod";
|
|
24149
24832
|
var trendInputSchema, TREND_DISCLOSURE, trendHandler, churnInputSchema, CHURN_DISCLOSURE, diffNodeSets, churnHandler;
|
|
24150
24833
|
var init_snapshot_trend = __esm({
|
|
24151
24834
|
"../mcp/dist/src/tools/snapshot-trend.js"() {
|
|
24152
24835
|
"use strict";
|
|
24153
24836
|
init_dist();
|
|
24154
24837
|
init_src2();
|
|
24155
|
-
trendInputSchema =
|
|
24838
|
+
trendInputSchema = z95.object({});
|
|
24156
24839
|
TREND_DISCLOSURE = "Trend points come from persisted `sfi snapshot create` captures. Successful `sfi refresh` auto-captures a snapshot unless `meta/config.json` sets `snapshotOnRefresh: false`.";
|
|
24157
24840
|
trendHandler = async (ctx, _input) => {
|
|
24158
24841
|
const listed = await listSnapshots(ctx.vaultRoot);
|
|
@@ -24178,9 +24861,9 @@ var init_snapshot_trend = __esm({
|
|
|
24178
24861
|
}
|
|
24179
24862
|
});
|
|
24180
24863
|
};
|
|
24181
|
-
churnInputSchema =
|
|
24182
|
-
fromLabel:
|
|
24183
|
-
toLabel:
|
|
24864
|
+
churnInputSchema = z95.object({
|
|
24865
|
+
fromLabel: z95.string().min(1).optional(),
|
|
24866
|
+
toLabel: z95.string().min(1).optional()
|
|
24184
24867
|
});
|
|
24185
24868
|
CHURN_DISCLOSURE = 'Churn compares two persisted snapshot labels by structural id/hash only \u2014 not semantic "risk". Use `sfi.diff_snapshots` for the full slice.';
|
|
24186
24869
|
diffNodeSets = (fromNodes, toNodes) => {
|
|
@@ -24318,7 +25001,7 @@ var init_coverage_trust = __esm({
|
|
|
24318
25001
|
});
|
|
24319
25002
|
|
|
24320
25003
|
// ../mcp/dist/src/tools/unassigned-permission-sets.js
|
|
24321
|
-
import { z as
|
|
25004
|
+
import { z as z96 } from "zod";
|
|
24322
25005
|
var UNASSIGNED_MAX_LIMIT, UNASSIGNED_DEFAULT_LIMIT, LIST_PAGE_SIZE10, BOUNDARIES11, unassignedPermissionSetsInputSchema, namespacePrefixOf2, propertyString3, propertyBoolean2, propertyNumberOrNull, detectEnrichmentStatus, compareById2, unassignedPermissionSetsHandler;
|
|
24323
25006
|
var init_unassigned_permission_sets = __esm({
|
|
24324
25007
|
"../mcp/dist/src/tools/unassigned-permission-sets.js"() {
|
|
@@ -24332,10 +25015,10 @@ var init_unassigned_permission_sets = __esm({
|
|
|
24332
25015
|
"tooling-api freshness reflects when v1.7 R2 last ran; live org assignment changes since then are not reflected. To refresh, run `sfi refresh --classify-permissions`.",
|
|
24333
25016
|
"unknownAssignmentCount values require v1.7 Tooling API enrichment to resolve \u2014 they are NOT counted toward unassignedCount."
|
|
24334
25017
|
]);
|
|
24335
|
-
unassignedPermissionSetsInputSchema =
|
|
24336
|
-
includeManagedPackage:
|
|
24337
|
-
includeMutingPermissionSets:
|
|
24338
|
-
limit:
|
|
25018
|
+
unassignedPermissionSetsInputSchema = z96.object({
|
|
25019
|
+
includeManagedPackage: z96.boolean().optional(),
|
|
25020
|
+
includeMutingPermissionSets: z96.boolean().optional(),
|
|
25021
|
+
limit: z96.number().int().min(1).max(UNASSIGNED_MAX_LIMIT).optional()
|
|
24339
25022
|
});
|
|
24340
25023
|
namespacePrefixOf2 = (apiName) => {
|
|
24341
25024
|
const idx = apiName.indexOf("__");
|
|
@@ -24476,7 +25159,7 @@ var init_unassigned_permission_sets = __esm({
|
|
|
24476
25159
|
});
|
|
24477
25160
|
|
|
24478
25161
|
// ../mcp/dist/src/tools/unused-components.js
|
|
24479
|
-
import { z as
|
|
25162
|
+
import { z as z97 } from "zod";
|
|
24480
25163
|
var UNUSED_MAX_LIMIT, UNUSED_DEFAULT_LIMIT, LIST_PAGE_SIZE11, DEFAULT_UNUSED_TYPES, COMPONENT_TYPES3, unusedComponentsInputSchema, INVISIBLE_REFERENCES_NOTES, DEFAULT_INVISIBLE_REFERENCES_NOTE, noteForType, ENTRY_POINT_TYPES, isInactiveEntryPoint, isUnused, isTestApexClass, compareById3, compareGlobally, scanType, unusedComponentsHandler;
|
|
24481
25164
|
var init_unused_components = __esm({
|
|
24482
25165
|
"../mcp/dist/src/tools/unused-components.js"() {
|
|
@@ -24551,9 +25234,9 @@ var init_unused_components = __esm({
|
|
|
24551
25234
|
"CustomMetadataRecord",
|
|
24552
25235
|
"CustomSettingRecord"
|
|
24553
25236
|
];
|
|
24554
|
-
unusedComponentsInputSchema =
|
|
24555
|
-
types:
|
|
24556
|
-
limit:
|
|
25237
|
+
unusedComponentsInputSchema = z97.object({
|
|
25238
|
+
types: z97.array(z97.enum(COMPONENT_TYPES3)).optional(),
|
|
25239
|
+
limit: z97.number().int().min(1).max(UNUSED_MAX_LIMIT).optional()
|
|
24557
25240
|
});
|
|
24558
25241
|
INVISIBLE_REFERENCES_NOTES = Object.freeze({
|
|
24559
25242
|
ApexClass: "Dynamic Apex (Type.forName), reflective dispatch, and Tooling API references are invisible to the v1.x scanner; spot-check before deleting.",
|
|
@@ -24697,7 +25380,7 @@ var init_unused_components = __esm({
|
|
|
24697
25380
|
});
|
|
24698
25381
|
|
|
24699
25382
|
// ../mcp/dist/src/tools/unused-fields-deep.js
|
|
24700
|
-
import { z as
|
|
25383
|
+
import { z as z98 } from "zod";
|
|
24701
25384
|
var UNUSED_FIELDS_DEEP_REQUIRED_COVERAGE, completenessForUnusedFieldsDeep, UNUSED_FIELDS_DEEP_MAX_LIMIT, UNUSED_FIELDS_DEEP_DEFAULT_LIMIT, LIST_PAGE_SIZE12, FRONTEND_REFERENCE_TYPES, INVISIBILITY_WARNINGS, BOUNDARIES12, unusedFieldsDeepInputSchema, containsApiName, propertyString4, propertyStringArray2, criteriaItemsText, layoutSectionFields, relatedListFields, buildCorpora, checkNoIncomingEdges, checkNoFormulaTextReferences, checkNoLayoutReferences, checkNoSoqlStringReferences, checkNoUnresolvedApexReferences, checkNoLwcAuraVfReferences, checkNoConditionalContextReferences, checkNoIntegrationExposure, parseParentApiName2, fieldTypeOf2, isCustomField3, namespacePrefixOf3, computeConfidence, recommendedActionFor, compareById4, unusedFieldsDeepHandler;
|
|
24702
25385
|
var init_unused_fields_deep = __esm({
|
|
24703
25386
|
"../mcp/dist/src/tools/unused-fields-deep.js"() {
|
|
@@ -24748,11 +25431,11 @@ var init_unused_fields_deep = __esm({
|
|
|
24748
25431
|
BOUNDARIES12 = Object.freeze([
|
|
24749
25432
|
"even after checking formula expressions, layout placements, SOQL strings, conditional contexts, LWC / Aura / VF references, and integration exposure, the scanner cannot see dynamic SOQL, LWC dynamic field access (record[fieldName]), Apex reflective access (obj.get(...)), or runtime metadata references. Treat a 'high-confidence unused' flag as 'no static evidence of use' rather than 'definitely unused.'"
|
|
24750
25433
|
]);
|
|
24751
|
-
unusedFieldsDeepInputSchema =
|
|
24752
|
-
parentObjectFilter:
|
|
24753
|
-
excludeManagedPackage:
|
|
24754
|
-
excludeStandardFields:
|
|
24755
|
-
limit:
|
|
25434
|
+
unusedFieldsDeepInputSchema = z98.object({
|
|
25435
|
+
parentObjectFilter: z98.string().min(1).optional(),
|
|
25436
|
+
excludeManagedPackage: z98.boolean().optional(),
|
|
25437
|
+
excludeStandardFields: z98.boolean().optional(),
|
|
25438
|
+
limit: z98.number().int().min(1).max(UNUSED_FIELDS_DEEP_MAX_LIMIT).optional()
|
|
24756
25439
|
});
|
|
24757
25440
|
containsApiName = (text, apiName) => text.toLowerCase().includes(apiName.toLowerCase());
|
|
24758
25441
|
propertyString4 = (node, key) => {
|
|
@@ -25130,7 +25813,7 @@ var init_unused_fields_deep = __esm({
|
|
|
25130
25813
|
});
|
|
25131
25814
|
|
|
25132
25815
|
// ../mcp/dist/src/tools/tech-debt-score.js
|
|
25133
|
-
import { z as
|
|
25816
|
+
import { z as z99 } from "zod";
|
|
25134
25817
|
var LIST_PAGE_SIZE13, DEFAULT_WEIGHTS, Q115_DISCLOSURE, SCORE_DIRECTION_DISCLOSURE, WEIGHT_SCHEME_DISCLOSURE, SCALE_FACTORS, ALLOWED_WEIGHT_KEYS, techDebtScoreInputSchema, bandFor, contribFor, recommendationFor, weightedScore, computeApiVersionDistribution, computeCodeQualityCounts, computeFreshnessCounts, techDebtScoreHandler;
|
|
25135
25818
|
var init_tech_debt_score = __esm({
|
|
25136
25819
|
"../mcp/dist/src/tools/tech-debt-score.js"() {
|
|
@@ -25176,8 +25859,8 @@ var init_tech_debt_score = __esm({
|
|
|
25176
25859
|
"apiVersions",
|
|
25177
25860
|
"unassignedGrants"
|
|
25178
25861
|
];
|
|
25179
|
-
techDebtScoreInputSchema =
|
|
25180
|
-
excludeCategories:
|
|
25862
|
+
techDebtScoreInputSchema = z99.object({
|
|
25863
|
+
excludeCategories: z99.array(z99.enum([
|
|
25181
25864
|
"deadWeight",
|
|
25182
25865
|
"legacyAutomation",
|
|
25183
25866
|
"codeQuality",
|
|
@@ -25188,13 +25871,13 @@ var init_tech_debt_score = __esm({
|
|
|
25188
25871
|
// `.passthrough()` keeps unknown weight keys in the parsed input so
|
|
25189
25872
|
// the handler can surface them in a structured `invalid-query`
|
|
25190
25873
|
// refusal rather than silently dropping them at the Zod boundary.
|
|
25191
|
-
weights:
|
|
25192
|
-
deadWeight:
|
|
25193
|
-
legacyAutomation:
|
|
25194
|
-
codeQuality:
|
|
25195
|
-
freshness:
|
|
25196
|
-
apiVersions:
|
|
25197
|
-
unassignedGrants:
|
|
25874
|
+
weights: z99.object({
|
|
25875
|
+
deadWeight: z99.number().min(0).max(1).optional(),
|
|
25876
|
+
legacyAutomation: z99.number().min(0).max(1).optional(),
|
|
25877
|
+
codeQuality: z99.number().min(0).max(1).optional(),
|
|
25878
|
+
freshness: z99.number().min(0).max(1).optional(),
|
|
25879
|
+
apiVersions: z99.number().min(0).max(1).optional(),
|
|
25880
|
+
unassignedGrants: z99.number().min(0).max(1).optional()
|
|
25198
25881
|
}).passthrough().optional()
|
|
25199
25882
|
});
|
|
25200
25883
|
bandFor = (score) => {
|
|
@@ -25554,12 +26237,13 @@ var init_tech_debt_score = __esm({
|
|
|
25554
26237
|
});
|
|
25555
26238
|
|
|
25556
26239
|
// ../mcp/dist/src/tools/synthesis-reports.js
|
|
25557
|
-
import { z as
|
|
25558
|
-
var synthesisInputSchema, orgRiskReportInputSchema, fieldCleanupCandidatesInputSchema, automationRiskReportInputSchema, permissionRiskReportInputSchema, releaseReadinessReportInputSchema, SYNTHESIS_DISCLOSURE, rankSeverity, sortFindings, coverageTrust, orgRiskReportHandler, fieldCleanupCandidatesHandler, automationRiskReportHandler, permissionRiskReportHandler, releaseReadinessReportHandler;
|
|
26240
|
+
import { z as z100 } from "zod";
|
|
26241
|
+
var synthesisInputSchema, orgRiskReportInputSchema, fieldCleanupCandidatesInputSchema, automationRiskReportInputSchema, permissionRiskReportInputSchema, releaseReadinessReportInputSchema, SYNTHESIS_DISCLOSURE, rankSeverity, sortFindings, coverageTrust, orgRiskReportHandler, fieldCleanupCandidatesHandler, automationRiskReportHandler, SYSTEM_PERMISSION_SEVERITY, PRIVILEGE_SCAN_CAP, ESCALATION_EXAMPLE_CAP, ROSTER_CAP, grantorFinding, analyzeOverPrivilege, permissionRiskReportHandler, releaseReadinessReportHandler;
|
|
25559
26242
|
var init_synthesis_reports = __esm({
|
|
25560
26243
|
"../mcp/dist/src/tools/synthesis-reports.js"() {
|
|
25561
26244
|
"use strict";
|
|
25562
26245
|
init_dist();
|
|
26246
|
+
init_src();
|
|
25563
26247
|
init_src2();
|
|
25564
26248
|
init_coverage_trust();
|
|
25565
26249
|
init_crud_fls_audit();
|
|
@@ -25569,8 +26253,8 @@ var init_synthesis_reports = __esm({
|
|
|
25569
26253
|
init_tech_debt_score();
|
|
25570
26254
|
init_unassigned_permission_sets();
|
|
25571
26255
|
init_unused_fields_deep();
|
|
25572
|
-
synthesisInputSchema =
|
|
25573
|
-
limit:
|
|
26256
|
+
synthesisInputSchema = z100.object({
|
|
26257
|
+
limit: z100.number().int().min(1).max(500).optional()
|
|
25574
26258
|
});
|
|
25575
26259
|
orgRiskReportInputSchema = synthesisInputSchema;
|
|
25576
26260
|
fieldCleanupCandidatesInputSchema = synthesisInputSchema;
|
|
@@ -25724,9 +26408,209 @@ var init_synthesis_reports = __esm({
|
|
|
25724
26408
|
}
|
|
25725
26409
|
});
|
|
25726
26410
|
};
|
|
26411
|
+
SYSTEM_PERMISSION_SEVERITY = {
|
|
26412
|
+
ModifyAllData: "critical",
|
|
26413
|
+
ViewAllData: "critical",
|
|
26414
|
+
AuthorApex: "high",
|
|
26415
|
+
CustomizeApplication: "high",
|
|
26416
|
+
ManageUsers: "high",
|
|
26417
|
+
ManageInternalUsers: "high",
|
|
26418
|
+
ManageProfilesPermissionsets: "high",
|
|
26419
|
+
ModifyMetadata: "high",
|
|
26420
|
+
ManageSharing: "high",
|
|
26421
|
+
ManageRoles: "high",
|
|
26422
|
+
ManagePasswordPolicies: "high",
|
|
26423
|
+
ManageLoginAccessPolicies: "high",
|
|
26424
|
+
ViewAllUsers: "medium",
|
|
26425
|
+
ViewSetup: "medium",
|
|
26426
|
+
PasswordNeverExpires: "medium"
|
|
26427
|
+
};
|
|
26428
|
+
PRIVILEGE_SCAN_CAP = 500;
|
|
26429
|
+
ESCALATION_EXAMPLE_CAP = 5;
|
|
26430
|
+
ROSTER_CAP = 100;
|
|
26431
|
+
grantorFinding = (node, type, riskyPerms, modifyAllObjects, viewAllObjects, worst, examples) => {
|
|
26432
|
+
if (riskyPerms.length === 0 && modifyAllObjects === 0 && viewAllObjects === 0) {
|
|
26433
|
+
return null;
|
|
26434
|
+
}
|
|
26435
|
+
const parts = [];
|
|
26436
|
+
if (riskyPerms.length > 0) {
|
|
26437
|
+
parts.push(`system perms: ${[...riskyPerms].sort().join(", ")}`);
|
|
26438
|
+
}
|
|
26439
|
+
if (modifyAllObjects > 0) {
|
|
26440
|
+
parts.push(`Modify All on ${modifyAllObjects} object(s)`);
|
|
26441
|
+
}
|
|
26442
|
+
if (viewAllObjects > 0) {
|
|
26443
|
+
parts.push(`View All on ${viewAllObjects} object(s)`);
|
|
26444
|
+
}
|
|
26445
|
+
return {
|
|
26446
|
+
rank: 0,
|
|
26447
|
+
severity: worst ?? "medium",
|
|
26448
|
+
category: "over-privilege",
|
|
26449
|
+
summary: `${type} ${node.apiName} grants ${parts.join("; ")}`,
|
|
26450
|
+
evidence: [node.id, ...examples],
|
|
26451
|
+
confidence: "declared"
|
|
26452
|
+
};
|
|
26453
|
+
};
|
|
26454
|
+
analyzeOverPrivilege = async (ctx, limit) => {
|
|
26455
|
+
const findings = [];
|
|
26456
|
+
const modifyAllDataGrantors = [];
|
|
26457
|
+
const viewAllDataGrantors = [];
|
|
26458
|
+
const scanned = { profiles: 0, permissionSets: 0, permissionSetGroups: 0 };
|
|
26459
|
+
const permsetRisk = /* @__PURE__ */ new Map();
|
|
26460
|
+
for (const type of ["Profile", "PermissionSet"]) {
|
|
26461
|
+
const nodesResult = await listNodesByType(ctx.graph, type, {
|
|
26462
|
+
limit: PRIVILEGE_SCAN_CAP
|
|
26463
|
+
});
|
|
26464
|
+
if (!nodesResult.ok)
|
|
26465
|
+
continue;
|
|
26466
|
+
for (const node of nodesResult.value) {
|
|
26467
|
+
if (type === "Profile")
|
|
26468
|
+
scanned.profiles += 1;
|
|
26469
|
+
else
|
|
26470
|
+
scanned.permissionSets += 1;
|
|
26471
|
+
const riskyPerms = [];
|
|
26472
|
+
let worst = null;
|
|
26473
|
+
const ups = node.properties["userPermissions"];
|
|
26474
|
+
if (Array.isArray(ups)) {
|
|
26475
|
+
for (const perm of ups) {
|
|
26476
|
+
if (typeof perm !== "string")
|
|
26477
|
+
continue;
|
|
26478
|
+
const sev = SYSTEM_PERMISSION_SEVERITY[perm];
|
|
26479
|
+
if (sev === void 0)
|
|
26480
|
+
continue;
|
|
26481
|
+
riskyPerms.push(perm);
|
|
26482
|
+
if (worst === null || rankSeverity(sev) > rankSeverity(worst)) {
|
|
26483
|
+
worst = sev;
|
|
26484
|
+
}
|
|
26485
|
+
if (perm === "ModifyAllData" && modifyAllDataGrantors.length < ROSTER_CAP) {
|
|
26486
|
+
modifyAllDataGrantors.push(node.id);
|
|
26487
|
+
}
|
|
26488
|
+
if (perm === "ViewAllData" && viewAllDataGrantors.length < ROSTER_CAP) {
|
|
26489
|
+
viewAllDataGrantors.push(node.id);
|
|
26490
|
+
}
|
|
26491
|
+
}
|
|
26492
|
+
}
|
|
26493
|
+
let modifyAllObjects = 0;
|
|
26494
|
+
let viewAllObjects = 0;
|
|
26495
|
+
const examples = [];
|
|
26496
|
+
const outResult = await listEdges(ctx.graph, node.id, {
|
|
26497
|
+
direction: "out",
|
|
26498
|
+
edgeType: "grantedBy"
|
|
26499
|
+
});
|
|
26500
|
+
if (outResult.ok) {
|
|
26501
|
+
for (const edge of outResult.value) {
|
|
26502
|
+
if (!edge.toId.startsWith("CustomObject:"))
|
|
26503
|
+
continue;
|
|
26504
|
+
if (edge.properties["modifyAllRecords"] === true) {
|
|
26505
|
+
modifyAllObjects += 1;
|
|
26506
|
+
if (examples.length < ESCALATION_EXAMPLE_CAP)
|
|
26507
|
+
examples.push(edge.toId);
|
|
26508
|
+
} else if (edge.properties["viewAllRecords"] === true) {
|
|
26509
|
+
viewAllObjects += 1;
|
|
26510
|
+
if (examples.length < ESCALATION_EXAMPLE_CAP)
|
|
26511
|
+
examples.push(edge.toId);
|
|
26512
|
+
}
|
|
26513
|
+
}
|
|
26514
|
+
}
|
|
26515
|
+
if (modifyAllObjects > 0 && (worst === null || rankSeverity("high") > rankSeverity(worst))) {
|
|
26516
|
+
worst = "high";
|
|
26517
|
+
} else if (viewAllObjects > 0 && worst === null) {
|
|
26518
|
+
worst = "medium";
|
|
26519
|
+
}
|
|
26520
|
+
if (type === "PermissionSet") {
|
|
26521
|
+
permsetRisk.set(node.id, {
|
|
26522
|
+
perms: riskyPerms,
|
|
26523
|
+
modAll: modifyAllObjects,
|
|
26524
|
+
viewAll: viewAllObjects
|
|
26525
|
+
});
|
|
26526
|
+
}
|
|
26527
|
+
const finding = grantorFinding(node, type, riskyPerms, modifyAllObjects, viewAllObjects, worst, examples);
|
|
26528
|
+
if (finding !== null)
|
|
26529
|
+
findings.push(finding);
|
|
26530
|
+
}
|
|
26531
|
+
}
|
|
26532
|
+
const psgResult = await listNodesByType(ctx.graph, "PermissionSetGroup", {
|
|
26533
|
+
limit: PRIVILEGE_SCAN_CAP
|
|
26534
|
+
});
|
|
26535
|
+
if (psgResult.ok) {
|
|
26536
|
+
for (const psg of psgResult.value) {
|
|
26537
|
+
scanned.permissionSetGroups += 1;
|
|
26538
|
+
const members = psg.properties["permissionSets"];
|
|
26539
|
+
if (!Array.isArray(members))
|
|
26540
|
+
continue;
|
|
26541
|
+
const conferred = /* @__PURE__ */ new Set();
|
|
26542
|
+
let aggModAll = 0;
|
|
26543
|
+
let aggViewAll = 0;
|
|
26544
|
+
const riskyMembers = [];
|
|
26545
|
+
for (const member of members) {
|
|
26546
|
+
if (typeof member !== "string")
|
|
26547
|
+
continue;
|
|
26548
|
+
const risk = permsetRisk.get(`PermissionSet:${member}`);
|
|
26549
|
+
if (risk === void 0)
|
|
26550
|
+
continue;
|
|
26551
|
+
if (risk.perms.length === 0 && risk.modAll === 0 && risk.viewAll === 0) {
|
|
26552
|
+
continue;
|
|
26553
|
+
}
|
|
26554
|
+
for (const perm of risk.perms)
|
|
26555
|
+
conferred.add(perm);
|
|
26556
|
+
aggModAll += risk.modAll;
|
|
26557
|
+
aggViewAll += risk.viewAll;
|
|
26558
|
+
if (riskyMembers.length < ESCALATION_EXAMPLE_CAP) {
|
|
26559
|
+
riskyMembers.push(`PermissionSet:${member}`);
|
|
26560
|
+
}
|
|
26561
|
+
}
|
|
26562
|
+
if (conferred.size === 0 && aggModAll === 0 && aggViewAll === 0)
|
|
26563
|
+
continue;
|
|
26564
|
+
let worst = "medium";
|
|
26565
|
+
if (conferred.has("ModifyAllData") || conferred.has("ViewAllData")) {
|
|
26566
|
+
worst = "critical";
|
|
26567
|
+
} else if (aggModAll > 0 || [...conferred].some((p) => SYSTEM_PERMISSION_SEVERITY[p] === "high")) {
|
|
26568
|
+
worst = "high";
|
|
26569
|
+
}
|
|
26570
|
+
if (conferred.has("ModifyAllData") && modifyAllDataGrantors.length < ROSTER_CAP) {
|
|
26571
|
+
modifyAllDataGrantors.push(psg.id);
|
|
26572
|
+
}
|
|
26573
|
+
if (conferred.has("ViewAllData") && viewAllDataGrantors.length < ROSTER_CAP) {
|
|
26574
|
+
viewAllDataGrantors.push(psg.id);
|
|
26575
|
+
}
|
|
26576
|
+
const muting = psg.properties["mutingPermissionSets"];
|
|
26577
|
+
const hasMuting = Array.isArray(muting) && muting.length > 0;
|
|
26578
|
+
const parts = [];
|
|
26579
|
+
if (conferred.size > 0) {
|
|
26580
|
+
parts.push(`system perms: ${[...conferred].sort().join(", ")}`);
|
|
26581
|
+
}
|
|
26582
|
+
if (aggModAll > 0) {
|
|
26583
|
+
parts.push(`Modify All on ${aggModAll} object(s) (aggregate)`);
|
|
26584
|
+
}
|
|
26585
|
+
if (aggViewAll > 0) {
|
|
26586
|
+
parts.push(`View All on ${aggViewAll} object(s) (aggregate)`);
|
|
26587
|
+
}
|
|
26588
|
+
findings.push({
|
|
26589
|
+
rank: 0,
|
|
26590
|
+
severity: worst,
|
|
26591
|
+
category: "over-privilege",
|
|
26592
|
+
summary: `PermissionSetGroup ${psg.apiName} confers via member permission set(s) ${parts.join("; ")}` + (hasMuting ? " (has a muting permission set \u2014 effective perms may be lower)" : ""),
|
|
26593
|
+
evidence: [psg.id, ...riskyMembers],
|
|
26594
|
+
confidence: "declared"
|
|
26595
|
+
});
|
|
26596
|
+
}
|
|
26597
|
+
}
|
|
26598
|
+
const ranked = [...findings].sort((a, b) => rankSeverity(b.severity) - rankSeverity(a.severity));
|
|
26599
|
+
return {
|
|
26600
|
+
findings: ranked.slice(0, limit),
|
|
26601
|
+
privilege: {
|
|
26602
|
+
modifyAllDataGrantors: [...modifyAllDataGrantors].sort(),
|
|
26603
|
+
viewAllDataGrantors: [...viewAllDataGrantors].sort(),
|
|
26604
|
+
overPrivilegedGrantorCount: findings.length,
|
|
26605
|
+
scanned
|
|
26606
|
+
}
|
|
26607
|
+
};
|
|
26608
|
+
};
|
|
25727
26609
|
permissionRiskReportHandler = async (ctx, input2) => {
|
|
25728
26610
|
const limit = input2.limit ?? 50;
|
|
25729
26611
|
const findings = [];
|
|
26612
|
+
const overPriv = await analyzeOverPrivilege(ctx, limit);
|
|
26613
|
+
findings.push(...overPriv.findings);
|
|
25730
26614
|
const unassigned = await unassignedPermissionSetsHandler(ctx, { limit });
|
|
25731
26615
|
if (unassigned.ok) {
|
|
25732
26616
|
for (const row of unassigned.value.data.unassigned.slice(0, limit)) {
|
|
@@ -25762,6 +26646,7 @@ var init_synthesis_reports = __esm({
|
|
|
25762
26646
|
data: {
|
|
25763
26647
|
findings: sortFindings(findings),
|
|
25764
26648
|
auditTotals,
|
|
26649
|
+
privilege: overPriv.privilege,
|
|
25765
26650
|
trust: coverageTrust(ctx),
|
|
25766
26651
|
disclosure: SYNTHESIS_DISCLOSURE
|
|
25767
26652
|
},
|
|
@@ -25803,8 +26688,109 @@ var init_synthesis_reports = __esm({
|
|
|
25803
26688
|
}
|
|
25804
26689
|
});
|
|
25805
26690
|
|
|
26691
|
+
// ../mcp/dist/src/tools/synthesize-answer.js
|
|
26692
|
+
import { z as z101 } from "zod";
|
|
26693
|
+
var CANONICAL_ID_WHOLE, CANONICAL_ID_INLINE, CAVEAT_KEY, FACT_KEY, COUNT_ARRAY_KEY, MAX_CITATIONS, MAX_BULLETS, MAX_CAVEATS, DISCLOSURE8, synthesizeAnswerInputSchema, pushCapped, collect, parseCitation, synthesizeAnswerHandler;
|
|
26694
|
+
var init_synthesize_answer = __esm({
|
|
26695
|
+
"../mcp/dist/src/tools/synthesize-answer.js"() {
|
|
26696
|
+
"use strict";
|
|
26697
|
+
init_dist();
|
|
26698
|
+
CANONICAL_ID_WHOLE = /^[A-Z][A-Za-z0-9]+:[^\s]+$/;
|
|
26699
|
+
CANONICAL_ID_INLINE = /\b[A-Z][A-Za-z0-9]+:[A-Za-z0-9_./-]*[A-Za-z0-9_]/g;
|
|
26700
|
+
CAVEAT_KEY = /^(disclosure|caveat|caveats|note|notes|boundary|boundaries|limitation|limitations|notModeledNote|honesty|warning|warnings)$/i;
|
|
26701
|
+
FACT_KEY = /^(count|total|totalCount|totalClassCount|totalFindingCount|totalGapsCount|verdict|disposition|status|coverageStatus|riskLevel|truncated|notModeled|plane|intent|confidence|matchKind|fieldLabel|fieldId|piiClassification|piiCategory)$/;
|
|
26702
|
+
COUNT_ARRAY_KEY = /^(grants|findings|gaps|components|candidates|edges|nodes|reasoning|viaApexAccess|fields|matches|classes|tools)$/;
|
|
26703
|
+
MAX_CITATIONS = 200;
|
|
26704
|
+
MAX_BULLETS = 60;
|
|
26705
|
+
MAX_CAVEATS = 40;
|
|
26706
|
+
DISCLOSURE8 = "This answer skeleton is composed ONLY from the JSON supplied in `input`: every citation is an id that appears verbatim in that input, every caveat is carried from it, and no components, counts, or facts were invented. When a `draft` is supplied, `hallucinatedIds` lists canonical ids in the draft that are NOT in the source \u2014 remove or re-ground them before answering. Prose wording is the caller\u2019s job; this tool guarantees grounding, not sentences.";
|
|
26707
|
+
synthesizeAnswerInputSchema = z101.object({
|
|
26708
|
+
input: z101.unknown(),
|
|
26709
|
+
question: z101.string().optional(),
|
|
26710
|
+
draft: z101.string().optional()
|
|
26711
|
+
});
|
|
26712
|
+
pushCapped = (arr, s, cap) => {
|
|
26713
|
+
if (arr.length < cap && !arr.includes(s))
|
|
26714
|
+
arr.push(s);
|
|
26715
|
+
};
|
|
26716
|
+
collect = (value, key, out) => {
|
|
26717
|
+
if (typeof value === "string") {
|
|
26718
|
+
if (out.ids.size < MAX_CITATIONS && CANONICAL_ID_WHOLE.test(value)) {
|
|
26719
|
+
out.ids.add(value);
|
|
26720
|
+
}
|
|
26721
|
+
if (key !== null && CAVEAT_KEY.test(key) && value.trim().length > 0) {
|
|
26722
|
+
pushCapped(out.caveats, value, MAX_CAVEATS);
|
|
26723
|
+
} else if (key !== null && FACT_KEY.test(key)) {
|
|
26724
|
+
pushCapped(out.bullets, `${key}: ${value}`, MAX_BULLETS);
|
|
26725
|
+
}
|
|
26726
|
+
return;
|
|
26727
|
+
}
|
|
26728
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
26729
|
+
if (key !== null && FACT_KEY.test(key)) {
|
|
26730
|
+
pushCapped(out.bullets, `${key}: ${String(value)}`, MAX_BULLETS);
|
|
26731
|
+
}
|
|
26732
|
+
return;
|
|
26733
|
+
}
|
|
26734
|
+
if (Array.isArray(value)) {
|
|
26735
|
+
if (key !== null && CAVEAT_KEY.test(key)) {
|
|
26736
|
+
for (const item of value) {
|
|
26737
|
+
if (typeof item === "string" && item.trim().length > 0) {
|
|
26738
|
+
pushCapped(out.caveats, item, MAX_CAVEATS);
|
|
26739
|
+
}
|
|
26740
|
+
}
|
|
26741
|
+
} else if (key !== null && COUNT_ARRAY_KEY.test(key)) {
|
|
26742
|
+
pushCapped(out.bullets, `${key}: ${value.length} item(s)`, MAX_BULLETS);
|
|
26743
|
+
}
|
|
26744
|
+
for (const item of value)
|
|
26745
|
+
collect(item, null, out);
|
|
26746
|
+
return;
|
|
26747
|
+
}
|
|
26748
|
+
if (value !== null && typeof value === "object") {
|
|
26749
|
+
for (const [k, v] of Object.entries(value))
|
|
26750
|
+
collect(v, k, out);
|
|
26751
|
+
}
|
|
26752
|
+
};
|
|
26753
|
+
parseCitation = (id) => {
|
|
26754
|
+
const colon = id.indexOf(":");
|
|
26755
|
+
return {
|
|
26756
|
+
id,
|
|
26757
|
+
type: id.slice(0, colon),
|
|
26758
|
+
apiName: id.slice(colon + 1)
|
|
26759
|
+
};
|
|
26760
|
+
};
|
|
26761
|
+
synthesizeAnswerHandler = async (ctx, input2) => {
|
|
26762
|
+
const out = { ids: /* @__PURE__ */ new Set(), caveats: [], bullets: [] };
|
|
26763
|
+
collect(input2.input, null, out);
|
|
26764
|
+
const citations = [...out.ids].sort().map(parseCitation);
|
|
26765
|
+
let hallucinatedIds;
|
|
26766
|
+
let groundedIds;
|
|
26767
|
+
if (input2.draft !== void 0) {
|
|
26768
|
+
const draftIds = [...new Set(input2.draft.match(CANONICAL_ID_INLINE) ?? [])];
|
|
26769
|
+
groundedIds = draftIds.filter((id) => out.ids.has(id)).sort();
|
|
26770
|
+
hallucinatedIds = draftIds.filter((id) => !out.ids.has(id)).sort();
|
|
26771
|
+
}
|
|
26772
|
+
const qPrefix = input2.question !== void 0 && input2.question.trim().length > 0 ? `Q: ${input2.question.trim()} \u2014 ` : "";
|
|
26773
|
+
const summary = `${qPrefix}Grounded in the supplied tool output: ${citations.length} component(s) cited, ${out.bullets.length} key fact(s), ${out.caveats.length} caveat(s)` + (hallucinatedIds !== void 0 ? `, ${hallucinatedIds.length} ungrounded id(s) in the draft` : "") + ".";
|
|
26774
|
+
return ok({
|
|
26775
|
+
data: {
|
|
26776
|
+
summary,
|
|
26777
|
+
bullets: out.bullets,
|
|
26778
|
+
citations,
|
|
26779
|
+
caveats: out.caveats,
|
|
26780
|
+
...input2.draft !== void 0 ? { hallucinatedIds: hallucinatedIds ?? [], groundedIds: groundedIds ?? [] } : {},
|
|
26781
|
+
disclosure: DISCLOSURE8
|
|
26782
|
+
},
|
|
26783
|
+
vaultState: {
|
|
26784
|
+
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
26785
|
+
refreshedAt: ctx.manifest.refreshedAt
|
|
26786
|
+
}
|
|
26787
|
+
});
|
|
26788
|
+
};
|
|
26789
|
+
}
|
|
26790
|
+
});
|
|
26791
|
+
|
|
25806
26792
|
// ../mcp/dist/src/tools/test-coverage-for-method.js
|
|
25807
|
-
import { z as
|
|
26793
|
+
import { z as z102 } from "zod";
|
|
25808
26794
|
var COVERAGE_BFS_DEPTH, COVERAGE_EDGE_TYPES, APEX_CLASS_PREFIX7, APEX_TRIGGER_PREFIX5, COVERAGE_DISCLOSURE2, testCoverageForMethodInputSchema, isApexCallable4, isTestClass5, upstreamWalk2, testCoverageForMethodHandler;
|
|
25809
26795
|
var init_test_coverage_for_method = __esm({
|
|
25810
26796
|
"../mcp/dist/src/tools/test-coverage-for-method.js"() {
|
|
@@ -25820,9 +26806,9 @@ var init_test_coverage_for_method = __esm({
|
|
|
25820
26806
|
APEX_CLASS_PREFIX7 = "ApexClass:";
|
|
25821
26807
|
APEX_TRIGGER_PREFIX5 = "ApexTrigger:";
|
|
25822
26808
|
COVERAGE_DISCLOSURE2 = "v2.7 test_coverage_for_method ships CLASS granularity (method-level promised in v2.7.1). The methodName input is echoed verbatim into the response but does NOT subset the upstream walk. The upstream walk follows both callsApex and dispatchesAsync incoming edges, so coverage exercised via async dispatch (Database.executeBatch, System.enqueueJob, System.schedule) is included. Dynamic dispatch (Type.forName) and reflective invocation are still invisible. BFS is capped at depth 3; coverage chains longer than 3 hops surface as uncovered even when they exist.";
|
|
25823
|
-
testCoverageForMethodInputSchema =
|
|
25824
|
-
classApiName:
|
|
25825
|
-
methodName:
|
|
26809
|
+
testCoverageForMethodInputSchema = z102.object({
|
|
26810
|
+
classApiName: z102.string().min(1),
|
|
26811
|
+
methodName: z102.string().min(1).optional()
|
|
25826
26812
|
});
|
|
25827
26813
|
isApexCallable4 = (id) => id.startsWith(APEX_CLASS_PREFIX7) || id.startsWith(APEX_TRIGGER_PREFIX5);
|
|
25828
26814
|
isTestClass5 = (node) => node.properties["isTest"] === true;
|
|
@@ -25922,8 +26908,8 @@ var init_test_coverage_for_method = __esm({
|
|
|
25922
26908
|
});
|
|
25923
26909
|
|
|
25924
26910
|
// ../mcp/dist/src/tools/test-coverage-gaps.js
|
|
25925
|
-
import { z as
|
|
25926
|
-
var LIST_PAGE_SIZE14, CLASS_FILTER_MAX_SIZE, MAX_COVERAGE_DEPTH, MEANINGFUL_ASSERTION_DISCLOSURE, DYNAMIC_DISPATCH_DISCLOSURE, DEPTH_CAP_DISCLOSURE, testCoverageGapsInputSchema, collectFakeAssertions2, isTestClass6, collectCoveringTestClasses, recommendationFor2, compareGapById, emptyByStatus, testCoverageGapsHandler;
|
|
26911
|
+
import { z as z103 } from "zod";
|
|
26912
|
+
var LIST_PAGE_SIZE14, CLASS_FILTER_MAX_SIZE, TEST_COVERAGE_GAPS_MAX_LIMIT, TEST_COVERAGE_GAPS_DEFAULT_LIMIT, TEST_COVERAGE_GAPS_PAYLOAD_BUDGET_BYTES, MAX_COVERAGE_DEPTH, MEANINGFUL_ASSERTION_DISCLOSURE, DYNAMIC_DISPATCH_DISCLOSURE, DEPTH_CAP_DISCLOSURE, testCoverageGapsInputSchema, fitGapsToBudget, collectFakeAssertions2, isTestClass6, collectCoveringTestClasses, recommendationFor2, compareGapById, emptyByStatus, testCoverageGapsHandler;
|
|
25927
26913
|
var init_test_coverage_gaps = __esm({
|
|
25928
26914
|
"../mcp/dist/src/tools/test-coverage-gaps.js"() {
|
|
25929
26915
|
"use strict";
|
|
@@ -25931,13 +26917,31 @@ var init_test_coverage_gaps = __esm({
|
|
|
25931
26917
|
init_src();
|
|
25932
26918
|
LIST_PAGE_SIZE14 = 500;
|
|
25933
26919
|
CLASS_FILTER_MAX_SIZE = 500;
|
|
26920
|
+
TEST_COVERAGE_GAPS_MAX_LIMIT = 500;
|
|
26921
|
+
TEST_COVERAGE_GAPS_DEFAULT_LIMIT = 200;
|
|
26922
|
+
TEST_COVERAGE_GAPS_PAYLOAD_BUDGET_BYTES = 38e3;
|
|
25934
26923
|
MAX_COVERAGE_DEPTH = 3;
|
|
25935
26924
|
MEANINGFUL_ASSERTION_DISCLOSURE = "the meaningful-assertion heuristic recognizes System.assertEquals(expected, actual) patterns with distinct expected/actual tokens, plus System.assert(condition) with a non-literal condition. Assertions via helper methods or framework wrappers are invisible. A class flagged fake-coverage may actually have meaningful tests via a custom assertion helper.";
|
|
25936
26925
|
DYNAMIC_DISPATCH_DISCLOSURE = "reachability via callsApex does NOT cover dynamic dispatch (Type.forName) or reflective invocation. A class genuinely tested via dynamic dispatch will surface as uncovered by this heuristic.";
|
|
25937
26926
|
DEPTH_CAP_DISCLOSURE = `the coverage BFS is capped at depth ${MAX_COVERAGE_DEPTH}; coverage chains longer than ${MAX_COVERAGE_DEPTH} hops surface as uncovered even when they exist.`;
|
|
25938
|
-
testCoverageGapsInputSchema =
|
|
25939
|
-
classFilter:
|
|
26927
|
+
testCoverageGapsInputSchema = z103.object({
|
|
26928
|
+
classFilter: z103.array(z103.string().min(1)).max(CLASS_FILTER_MAX_SIZE).optional(),
|
|
26929
|
+
limit: z103.number().int().min(1).max(TEST_COVERAGE_GAPS_MAX_LIMIT).optional(),
|
|
26930
|
+
offset: z103.number().int().min(0).optional()
|
|
25940
26931
|
});
|
|
26932
|
+
fitGapsToBudget = (gaps, budgetBytes) => {
|
|
26933
|
+
const kept = [];
|
|
26934
|
+
let used = 0;
|
|
26935
|
+
for (const gap of gaps) {
|
|
26936
|
+
const size = Buffer.byteLength(JSON.stringify(gap), "utf8") + 1;
|
|
26937
|
+
if (kept.length > 0 && used + size > budgetBytes) {
|
|
26938
|
+
return { kept, trimmed: true };
|
|
26939
|
+
}
|
|
26940
|
+
kept.push(gap);
|
|
26941
|
+
used += size;
|
|
26942
|
+
}
|
|
26943
|
+
return { kept, trimmed: false };
|
|
26944
|
+
};
|
|
25941
26945
|
collectFakeAssertions2 = (node) => {
|
|
25942
26946
|
const raw = node.properties["qualityIssues"];
|
|
25943
26947
|
if (!Array.isArray(raw))
|
|
@@ -26096,6 +27100,12 @@ var init_test_coverage_gaps = __esm({
|
|
|
26096
27100
|
for (const g of sorted) {
|
|
26097
27101
|
byStatus[g.coverageStatus] += 1;
|
|
26098
27102
|
}
|
|
27103
|
+
const limit = input2.limit ?? TEST_COVERAGE_GAPS_DEFAULT_LIMIT;
|
|
27104
|
+
const offset = input2.offset ?? 0;
|
|
27105
|
+
const page = sorted.slice(offset, offset + limit);
|
|
27106
|
+
const { kept, trimmed } = fitGapsToBudget(page, TEST_COVERAGE_GAPS_PAYLOAD_BUDGET_BYTES);
|
|
27107
|
+
const returnedEnd = offset + kept.length;
|
|
27108
|
+
const truncated = returnedEnd < sorted.length;
|
|
26099
27109
|
const boundaries = sorted.length === 0 ? [] : [
|
|
26100
27110
|
MEANINGFUL_ASSERTION_DISCLOSURE,
|
|
26101
27111
|
DYNAMIC_DISPATCH_DISCLOSURE,
|
|
@@ -26103,10 +27113,17 @@ var init_test_coverage_gaps = __esm({
|
|
|
26103
27113
|
];
|
|
26104
27114
|
return ok({
|
|
26105
27115
|
data: {
|
|
26106
|
-
gaps:
|
|
27116
|
+
gaps: kept,
|
|
26107
27117
|
totalGapsCount: sorted.length,
|
|
26108
27118
|
byStatus,
|
|
26109
|
-
boundaries
|
|
27119
|
+
boundaries,
|
|
27120
|
+
limit,
|
|
27121
|
+
offset,
|
|
27122
|
+
truncated,
|
|
27123
|
+
...truncated ? { nextOffset: returnedEnd } : {},
|
|
27124
|
+
...trimmed ? {
|
|
27125
|
+
note: `Response trimmed to ${kept.length} of ${page.length} gaps (${sorted.length} total) to stay under the ~45 KB MCP response limit. Advance with offset += ${kept.length} for the rest.`
|
|
27126
|
+
} : {}
|
|
26110
27127
|
},
|
|
26111
27128
|
vaultState: {
|
|
26112
27129
|
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
@@ -26118,7 +27135,7 @@ var init_test_coverage_gaps = __esm({
|
|
|
26118
27135
|
});
|
|
26119
27136
|
|
|
26120
27137
|
// ../mcp/dist/src/tools/tests-for-change.js
|
|
26121
|
-
import { z as
|
|
27138
|
+
import { z as z104 } from "zod";
|
|
26122
27139
|
var TESTS_FOR_CHANGE_BFS_DEPTH, COVERAGE_EDGE_TYPES2, APEX_CLASS_PREFIX8, APEX_TRIGGER_PREFIX6, MAX_CHANGED_ITEMS, TESTS_FOR_CHANGE_DISCLOSURE, testsForChangeInputSchema, isApexCallable5, isTestClass7, upstreamWalk3, sortIds, testsForChangeHandler;
|
|
26123
27140
|
var init_tests_for_change = __esm({
|
|
26124
27141
|
"../mcp/dist/src/tools/tests-for-change.js"() {
|
|
@@ -26132,8 +27149,8 @@ var init_tests_for_change = __esm({
|
|
|
26132
27149
|
APEX_TRIGGER_PREFIX6 = "ApexTrigger:";
|
|
26133
27150
|
MAX_CHANGED_ITEMS = 500;
|
|
26134
27151
|
TESTS_FOR_CHANGE_DISCLOSURE = "tests_for_change selects at CLASS granularity (a changed method on a covered class still selects that class\u2019s tests; method-level resolution promised in v2.7.1). The upstream walk follows both callsApex and dispatchesAsync incoming edges, so coverage via async dispatch (Database.executeBatch, System.enqueueJob, System.schedule) is included. Dynamic dispatch (Type.forName) and reflective invocation are invisible \u2014 a test reaching the change only via reflection is missed. Managed-package test classes are invisible. BFS is capped at depth 3; coverage chains longer than 3 hops surface as uncovered even when they exist. A changed component in uncoveredChanges is UNGUARDED \u2014 running the selected set will NOT exercise it; run the full suite when any change is uncovered or you suspect a deep chain.";
|
|
26135
|
-
testsForChangeInputSchema =
|
|
26136
|
-
changedComponents:
|
|
27152
|
+
testsForChangeInputSchema = z104.object({
|
|
27153
|
+
changedComponents: z104.array(z104.string().min(1)).min(1).max(MAX_CHANGED_ITEMS)
|
|
26137
27154
|
});
|
|
26138
27155
|
isApexCallable5 = (id) => id.startsWith(APEX_CLASS_PREFIX8) || id.startsWith(APEX_TRIGGER_PREFIX6);
|
|
26139
27156
|
isTestClass7 = (node) => node.properties["isTest"] === true;
|
|
@@ -26293,9 +27310,711 @@ var init_tests_for_change = __esm({
|
|
|
26293
27310
|
}
|
|
26294
27311
|
});
|
|
26295
27312
|
|
|
27313
|
+
// ../mcp/dist/src/tools/value-change-classification.js
|
|
27314
|
+
var CUSTOM_FIELD_PREFIX11, SEV_RANK, parseFieldId, leafName, DERIVED_DATA_TYPES, SYSTEM_AUDIT_FIELDS, classifyMutability, STANDARD_IDLOOKUP, classifyUpsertKey, IDENTITY_CATALOG, lookupIdentityCatalog, severityRank, maxSeverity, NAME_LEXICON, CONF_RANK, classifyRole, classifyField;
|
|
27315
|
+
var init_value_change_classification = __esm({
|
|
27316
|
+
"../mcp/dist/src/tools/value-change-classification.js"() {
|
|
27317
|
+
"use strict";
|
|
27318
|
+
init_field_properties();
|
|
27319
|
+
CUSTOM_FIELD_PREFIX11 = "CustomField:";
|
|
27320
|
+
SEV_RANK = {
|
|
27321
|
+
info: 0,
|
|
27322
|
+
low: 1,
|
|
27323
|
+
medium: 2,
|
|
27324
|
+
high: 3,
|
|
27325
|
+
critical: 4
|
|
27326
|
+
};
|
|
27327
|
+
parseFieldId = (id) => {
|
|
27328
|
+
if (!id.startsWith(CUSTOM_FIELD_PREFIX11))
|
|
27329
|
+
return null;
|
|
27330
|
+
const rest = id.slice(CUSTOM_FIELD_PREFIX11.length);
|
|
27331
|
+
const dot = rest.indexOf(".");
|
|
27332
|
+
if (dot < 0)
|
|
27333
|
+
return null;
|
|
27334
|
+
return { object: rest.slice(0, dot), field: rest.slice(dot + 1) };
|
|
27335
|
+
};
|
|
27336
|
+
leafName = (apiName) => {
|
|
27337
|
+
const i = apiName.lastIndexOf(".");
|
|
27338
|
+
return i >= 0 ? apiName.slice(i + 1) : apiName;
|
|
27339
|
+
};
|
|
27340
|
+
DERIVED_DATA_TYPES = /* @__PURE__ */ new Set(["Summary", "AutoNumber"]);
|
|
27341
|
+
SYSTEM_AUDIT_FIELDS = /* @__PURE__ */ new Set([
|
|
27342
|
+
"Id",
|
|
27343
|
+
"IsDeleted",
|
|
27344
|
+
"CreatedDate",
|
|
27345
|
+
"CreatedById",
|
|
27346
|
+
"LastModifiedDate",
|
|
27347
|
+
"LastModifiedById",
|
|
27348
|
+
"SystemModstamp",
|
|
27349
|
+
"LastActivityDate",
|
|
27350
|
+
"LastViewedDate",
|
|
27351
|
+
"LastReferencedDate"
|
|
27352
|
+
]);
|
|
27353
|
+
classifyMutability = (node, fieldName) => {
|
|
27354
|
+
const formula = node.properties["formula"];
|
|
27355
|
+
if (typeof formula === "string" && formula.trim() !== "") {
|
|
27356
|
+
const expr = formula.trim();
|
|
27357
|
+
return {
|
|
27358
|
+
mutability: "derived",
|
|
27359
|
+
reason: `Formula field \u2014 value is computed from \`${expr}\`. Change the source field(s), not this one.`,
|
|
27360
|
+
sourceFormula: expr
|
|
27361
|
+
};
|
|
27362
|
+
}
|
|
27363
|
+
const dataType = readFieldDataType(node);
|
|
27364
|
+
if (DERIVED_DATA_TYPES.has(dataType)) {
|
|
27365
|
+
return {
|
|
27366
|
+
mutability: "derived",
|
|
27367
|
+
reason: `${dataType} field \u2014 value is system-computed, not directly editable.`
|
|
27368
|
+
};
|
|
27369
|
+
}
|
|
27370
|
+
const name = fieldName ?? leafName(node.apiName);
|
|
27371
|
+
if (SYSTEM_AUDIT_FIELDS.has(name)) {
|
|
27372
|
+
return {
|
|
27373
|
+
mutability: "derived",
|
|
27374
|
+
reason: `${name} is a system audit field \u2014 not user-writable.`
|
|
27375
|
+
};
|
|
27376
|
+
}
|
|
27377
|
+
return { mutability: "writable", reason: "Directly editable field." };
|
|
27378
|
+
};
|
|
27379
|
+
STANDARD_IDLOOKUP = {
|
|
27380
|
+
"*": /* @__PURE__ */ new Set(["Id"]),
|
|
27381
|
+
User: /* @__PURE__ */ new Set(["Username", "FederationIdentifier"])
|
|
27382
|
+
};
|
|
27383
|
+
classifyUpsertKey = (node, object, fieldName) => {
|
|
27384
|
+
const signals = [];
|
|
27385
|
+
if (node.properties["externalId"] === true)
|
|
27386
|
+
signals.push("externalId");
|
|
27387
|
+
if (node.properties["unique"] === true)
|
|
27388
|
+
signals.push("unique");
|
|
27389
|
+
const idLookup = (STANDARD_IDLOOKUP[object]?.has(fieldName) ?? false) || (STANDARD_IDLOOKUP["*"]?.has(fieldName) ?? false);
|
|
27390
|
+
if (idLookup)
|
|
27391
|
+
signals.push("idLookup");
|
|
27392
|
+
return { isUpsertKey: signals.length > 0, signals };
|
|
27393
|
+
};
|
|
27394
|
+
IDENTITY_CATALOG = {
|
|
27395
|
+
"User.Username": { role: "Login identity (global-unique)", severity: "critical" },
|
|
27396
|
+
"User.FederationIdentifier": { role: "SSO / SAML federation subject", severity: "high" },
|
|
27397
|
+
"User.EmployeeNumber": { role: "Employee number \u2014 common HRIS integration key", severity: "high" },
|
|
27398
|
+
"User.CommunityNickname": { role: "Experience Cloud display (org-unique)", severity: "medium" },
|
|
27399
|
+
"User.Alias": { role: "Short display name", severity: "low" },
|
|
27400
|
+
"User.Email": { role: "User email \u2014 login & notifications", severity: "high" },
|
|
27401
|
+
"Contact.Email": { role: "Contact email / common match key", severity: "medium" }
|
|
27402
|
+
};
|
|
27403
|
+
lookupIdentityCatalog = (object, field) => IDENTITY_CATALOG[`${object}.${field}`] ?? null;
|
|
27404
|
+
severityRank = (s) => SEV_RANK[s];
|
|
27405
|
+
maxSeverity = (a, b) => SEV_RANK[a] >= SEV_RANK[b] ? a : b;
|
|
27406
|
+
NAME_LEXICON = [
|
|
27407
|
+
{ test: /(_SIS_ID|SIS_?Key|Comment_SIS_ID)__c$/i, role: "SIS integration key (name pattern)", severity: "high" },
|
|
27408
|
+
{ test: /(Marketo|Sparkroom|Pardot)/i, role: "Marketing-platform key (name pattern)", severity: "medium" },
|
|
27409
|
+
{ test: /(_uuid|_guid)__c$/i, role: "External UUID/GUID key (name pattern)", severity: "medium" },
|
|
27410
|
+
{ test: /Federation/i, role: "Federation / SSO identity (name pattern)", severity: "high" }
|
|
27411
|
+
];
|
|
27412
|
+
CONF_RANK = { potential: 0, likely: 1, confirmed: 2 };
|
|
27413
|
+
classifyRole = (upsert, mutability, object, fieldName) => {
|
|
27414
|
+
if (mutability.mutability === "derived") {
|
|
27415
|
+
return {
|
|
27416
|
+
role: "Derived / computed \u2014 value not directly changeable",
|
|
27417
|
+
severity: "low",
|
|
27418
|
+
confidence: "confirmed",
|
|
27419
|
+
signals: [mutability.reason]
|
|
27420
|
+
};
|
|
27421
|
+
}
|
|
27422
|
+
const candidates = [];
|
|
27423
|
+
const catalog = IDENTITY_CATALOG[`${object}.${fieldName}`];
|
|
27424
|
+
if (catalog !== void 0) {
|
|
27425
|
+
candidates.push({
|
|
27426
|
+
role: catalog.role,
|
|
27427
|
+
severity: catalog.severity,
|
|
27428
|
+
// Corroborated by an upsert flag → confirmed; otherwise a known but
|
|
27429
|
+
// org-config-dependent default → likely.
|
|
27430
|
+
confidence: upsert.isUpsertKey ? "confirmed" : "likely",
|
|
27431
|
+
signal: "standard-identity-field catalog"
|
|
27432
|
+
});
|
|
27433
|
+
}
|
|
27434
|
+
if (upsert.isUpsertKey) {
|
|
27435
|
+
candidates.push({
|
|
27436
|
+
role: "Integration / upsert key",
|
|
27437
|
+
severity: "high",
|
|
27438
|
+
confidence: "confirmed",
|
|
27439
|
+
signal: `upsert key (${upsert.signals.join(" + ")})`
|
|
27440
|
+
});
|
|
27441
|
+
}
|
|
27442
|
+
const lex = NAME_LEXICON.find((r) => r.test.test(fieldName));
|
|
27443
|
+
if (lex !== void 0) {
|
|
27444
|
+
candidates.push({
|
|
27445
|
+
role: lex.role,
|
|
27446
|
+
severity: lex.severity,
|
|
27447
|
+
confidence: "potential",
|
|
27448
|
+
signal: "name pattern"
|
|
27449
|
+
});
|
|
27450
|
+
}
|
|
27451
|
+
if (candidates.length === 0) {
|
|
27452
|
+
return {
|
|
27453
|
+
role: "Standard editable field",
|
|
27454
|
+
severity: "low",
|
|
27455
|
+
confidence: "confirmed",
|
|
27456
|
+
signals: []
|
|
27457
|
+
};
|
|
27458
|
+
}
|
|
27459
|
+
const top = candidates.reduce((best, c) => {
|
|
27460
|
+
if (SEV_RANK[c.severity] !== SEV_RANK[best.severity]) {
|
|
27461
|
+
return SEV_RANK[c.severity] > SEV_RANK[best.severity] ? c : best;
|
|
27462
|
+
}
|
|
27463
|
+
return CONF_RANK[c.confidence] > CONF_RANK[best.confidence] ? c : best;
|
|
27464
|
+
});
|
|
27465
|
+
return {
|
|
27466
|
+
role: top.role,
|
|
27467
|
+
severity: top.severity,
|
|
27468
|
+
confidence: top.confidence,
|
|
27469
|
+
signals: candidates.map((c) => c.signal)
|
|
27470
|
+
};
|
|
27471
|
+
};
|
|
27472
|
+
classifyField = (node) => {
|
|
27473
|
+
const parsed = parseFieldId(node.id);
|
|
27474
|
+
const object = parsed?.object ?? "";
|
|
27475
|
+
const field = parsed?.field ?? leafName(node.apiName);
|
|
27476
|
+
const mutability = classifyMutability(node, field);
|
|
27477
|
+
const upsertKey = classifyUpsertKey(node, object, field);
|
|
27478
|
+
const role = classifyRole(upsertKey, mutability, object, field);
|
|
27479
|
+
return { fieldId: node.id, object, field, mutability, upsertKey, role };
|
|
27480
|
+
};
|
|
27481
|
+
}
|
|
27482
|
+
});
|
|
27483
|
+
|
|
27484
|
+
// ../mcp/dist/src/tools/value-change-risk.js
|
|
27485
|
+
var SOURCE_CATEGORY, EMPTY_EDGE_SUMMARY, buildBuckets, buildDisclosures, recommendedChecksFor, overallSeverityOf, summarizeIncomingEdges, gateFederationIdentifier, detectShadowJoin, extractQuotedLiterals, normalizeExpr, findValueCouplings, assessValueChange;
|
|
27486
|
+
var init_value_change_risk = __esm({
|
|
27487
|
+
"../mcp/dist/src/tools/value-change-risk.js"() {
|
|
27488
|
+
"use strict";
|
|
27489
|
+
init_dist();
|
|
27490
|
+
init_src();
|
|
27491
|
+
init_value_change_classification();
|
|
27492
|
+
SOURCE_CATEGORY = {
|
|
27493
|
+
ValidationRule: "automation",
|
|
27494
|
+
WorkflowRule: "automation",
|
|
27495
|
+
Flow: "automation",
|
|
27496
|
+
ApprovalProcess: "automation",
|
|
27497
|
+
AssignmentRule: "automation",
|
|
27498
|
+
EscalationRule: "automation",
|
|
27499
|
+
AutoResponseRule: "automation",
|
|
27500
|
+
DuplicateRule: "automation",
|
|
27501
|
+
CustomField: "automation",
|
|
27502
|
+
// a formula field referencing this one
|
|
27503
|
+
ApexClass: "code",
|
|
27504
|
+
ApexTrigger: "code",
|
|
27505
|
+
LightningComponentBundle: "code",
|
|
27506
|
+
AuraDefinitionBundle: "code",
|
|
27507
|
+
VisualforcePage: "code",
|
|
27508
|
+
VisualforceComponent: "code",
|
|
27509
|
+
ExternalService: "integration",
|
|
27510
|
+
ExternalDataSource: "integration",
|
|
27511
|
+
NamedCredential: "integration",
|
|
27512
|
+
Layout: "display",
|
|
27513
|
+
QuickAction: "display",
|
|
27514
|
+
FlexiPage: "display",
|
|
27515
|
+
ListView: "display"
|
|
27516
|
+
};
|
|
27517
|
+
EMPTY_EDGE_SUMMARY = {
|
|
27518
|
+
automation: [],
|
|
27519
|
+
code: [],
|
|
27520
|
+
integration: [],
|
|
27521
|
+
display: []
|
|
27522
|
+
};
|
|
27523
|
+
buildBuckets = (classification, edges) => {
|
|
27524
|
+
const { object, field, mutability, upsertKey } = classification;
|
|
27525
|
+
if (mutability.mutability === "derived") {
|
|
27526
|
+
return [
|
|
27527
|
+
{
|
|
27528
|
+
bucket: "save-pipeline",
|
|
27529
|
+
severity: "info",
|
|
27530
|
+
confidence: "confirmed",
|
|
27531
|
+
summary: `Not directly changeable \u2014 ${mutability.reason}`,
|
|
27532
|
+
evidence: []
|
|
27533
|
+
}
|
|
27534
|
+
];
|
|
27535
|
+
}
|
|
27536
|
+
const buckets = [];
|
|
27537
|
+
const identity = lookupIdentityCatalog(object, field);
|
|
27538
|
+
if (identity !== null) {
|
|
27539
|
+
buckets.push({
|
|
27540
|
+
bucket: "identity",
|
|
27541
|
+
severity: identity.severity,
|
|
27542
|
+
confidence: upsertKey.isUpsertKey ? "confirmed" : "likely",
|
|
27543
|
+
summary: `${identity.role} \u2014 changing the value alters how this record is identified/authenticated.`,
|
|
27544
|
+
evidence: ["standard-identity-field catalog"]
|
|
27545
|
+
});
|
|
27546
|
+
}
|
|
27547
|
+
const keySignals = upsertKey.signals.filter((s) => s === "externalId" || s === "idLookup");
|
|
27548
|
+
if (keySignals.length > 0) {
|
|
27549
|
+
const designedKey = keySignals.includes("externalId");
|
|
27550
|
+
buckets.push({
|
|
27551
|
+
bucket: "integration-key",
|
|
27552
|
+
severity: designedKey ? "high" : "medium",
|
|
27553
|
+
confidence: "confirmed",
|
|
27554
|
+
summary: designedKey ? "Upsert / integration match key (External ID) \u2014 changing the value breaks inbound matching (next sync no-matches \u2192 duplicate or error) and desyncs external systems keyed on the old value." : "Upsert-capable key (idLookup) \u2014 an integration may upsert records by this field; changing the value could break that matching.",
|
|
27555
|
+
evidence: [...keySignals.map((s) => `${s}=true`), ...edges.integration]
|
|
27556
|
+
});
|
|
27557
|
+
}
|
|
27558
|
+
if (upsertKey.signals.includes("unique")) {
|
|
27559
|
+
buckets.push({
|
|
27560
|
+
bucket: "uniqueness",
|
|
27561
|
+
severity: "medium",
|
|
27562
|
+
confidence: "confirmed",
|
|
27563
|
+
summary: "Unique field \u2014 a new value that collides with an existing record fails to save (DML error).",
|
|
27564
|
+
evidence: ["unique=true"]
|
|
27565
|
+
});
|
|
27566
|
+
}
|
|
27567
|
+
const automationRefs = [...edges.automation, ...edges.code];
|
|
27568
|
+
if (automationRefs.length > 0) {
|
|
27569
|
+
buckets.push({
|
|
27570
|
+
bucket: "automation",
|
|
27571
|
+
severity: edges.automation.length > 0 ? "medium" : "low",
|
|
27572
|
+
confidence: "likely",
|
|
27573
|
+
summary: `${automationRefs.length} automation/code component(s) reference this field; a value change may alter routing, validation, or sharing. Declarative value-couplings (where a rule compares this field to a literal) are surfaced when found; Apex literal comparisons remain invisible.`,
|
|
27574
|
+
evidence: automationRefs.slice(0, 12)
|
|
27575
|
+
});
|
|
27576
|
+
}
|
|
27577
|
+
const isDisplayIdentity = identity !== null && /display|nickname|alias/i.test(identity.role);
|
|
27578
|
+
if (edges.display.length > 0 || isDisplayIdentity) {
|
|
27579
|
+
buckets.push({
|
|
27580
|
+
bucket: "display",
|
|
27581
|
+
severity: "low",
|
|
27582
|
+
confidence: "likely",
|
|
27583
|
+
summary: "Appears on display surfaces (layouts / list views / UI); a value change is user-visible.",
|
|
27584
|
+
evidence: edges.display.slice(0, 8)
|
|
27585
|
+
});
|
|
27586
|
+
}
|
|
27587
|
+
buckets.push({
|
|
27588
|
+
bucket: "save-pipeline",
|
|
27589
|
+
severity: "info",
|
|
27590
|
+
confidence: "likely",
|
|
27591
|
+
summary: `Updating this field fires the save pipeline on ${object} (triggers / flows / rules) \u2014 relevant for bulk value changes.`,
|
|
27592
|
+
evidence: []
|
|
27593
|
+
});
|
|
27594
|
+
return buckets;
|
|
27595
|
+
};
|
|
27596
|
+
buildDisclosures = (buckets) => {
|
|
27597
|
+
const out = [];
|
|
27598
|
+
const has = (b) => buckets.some((x) => x.bucket === b);
|
|
27599
|
+
if (has("integration-key")) {
|
|
27600
|
+
out.push("The metadata declares this an upsert key, but NOT which external system upserts on it \u2014 confirm the key in your middleware / ETL (Data Loader, MuleSoft, etc.) before bulk-changing values.");
|
|
27601
|
+
}
|
|
27602
|
+
if (has("identity")) {
|
|
27603
|
+
out.push("If SSO maps users by this field, changing its value breaks login until the IdP is updated \u2014 verify with your IdP / My Domain SSO.");
|
|
27604
|
+
}
|
|
27605
|
+
if (has("automation")) {
|
|
27606
|
+
out.push("Dynamic SOQL, reflective Apex field access, and managed-package code that compare this field to a value are invisible to the scanner \u2014 the listed automations are the visible subset.");
|
|
27607
|
+
}
|
|
27608
|
+
if (has("cross-object")) {
|
|
27609
|
+
out.push("This key is replicated by name on other objects with no formal relationship \u2014 update the copies together, or they will silently desync.");
|
|
27610
|
+
}
|
|
27611
|
+
out.push("Reports, list-view filters, and manual processes may also key on this value.");
|
|
27612
|
+
return out;
|
|
27613
|
+
};
|
|
27614
|
+
recommendedChecksFor = (buckets) => {
|
|
27615
|
+
const checks = [];
|
|
27616
|
+
const has = (b) => buckets.some((x) => x.bucket === b);
|
|
27617
|
+
if (has("integration-key"))
|
|
27618
|
+
checks.push("Confirm the external upsert key + whether the external system will re-match or duplicate on the new value.");
|
|
27619
|
+
if (has("uniqueness"))
|
|
27620
|
+
checks.push("Confirm the new value does not collide with an existing record (unique constraint).");
|
|
27621
|
+
if (has("identity"))
|
|
27622
|
+
checks.push("Confirm SSO/login impact with your IdP before changing identity values.");
|
|
27623
|
+
if (has("automation"))
|
|
27624
|
+
checks.push("Review the referencing automations for value-literal comparisons that the change would flip.");
|
|
27625
|
+
if (has("cross-object"))
|
|
27626
|
+
checks.push("Update the same-named copies on the other objects together \u2014 they are value-joined with no formal relationship.");
|
|
27627
|
+
checks.push("For bulk changes, stage in a sandbox and watch the save pipeline (row locks, governor limits).");
|
|
27628
|
+
return checks;
|
|
27629
|
+
};
|
|
27630
|
+
overallSeverityOf = (buckets) => buckets.reduce((acc, b) => maxSeverity(acc, b.severity), "info");
|
|
27631
|
+
summarizeIncomingEdges = async (ctx, fieldId) => {
|
|
27632
|
+
const edgesResult = await listEdges(ctx.graph, fieldId, { direction: "in" });
|
|
27633
|
+
if (!edgesResult.ok) {
|
|
27634
|
+
return err({ kind: "internal", message: `graph query failed: ${edgesResult.error.message}` });
|
|
27635
|
+
}
|
|
27636
|
+
const automation = [];
|
|
27637
|
+
const code = [];
|
|
27638
|
+
const integration = [];
|
|
27639
|
+
const display = [];
|
|
27640
|
+
for (const edge of edgesResult.value) {
|
|
27641
|
+
if (edge.edgeType === "parentOf")
|
|
27642
|
+
continue;
|
|
27643
|
+
const fromResult = await getNodeById(ctx.graph, edge.fromId);
|
|
27644
|
+
if (!fromResult.ok) {
|
|
27645
|
+
return err({ kind: "internal", message: `graph query failed: ${fromResult.error.message}` });
|
|
27646
|
+
}
|
|
27647
|
+
const fromNode = fromResult.value;
|
|
27648
|
+
if (fromNode === null)
|
|
27649
|
+
continue;
|
|
27650
|
+
const category = SOURCE_CATEGORY[fromNode.type];
|
|
27651
|
+
if (category === "automation")
|
|
27652
|
+
automation.push(fromNode.id);
|
|
27653
|
+
else if (category === "code")
|
|
27654
|
+
code.push(fromNode.id);
|
|
27655
|
+
else if (category === "integration")
|
|
27656
|
+
integration.push(fromNode.id);
|
|
27657
|
+
else if (category === "display")
|
|
27658
|
+
display.push(fromNode.id);
|
|
27659
|
+
}
|
|
27660
|
+
const dedupe = (xs) => [...new Set(xs)].sort();
|
|
27661
|
+
return ok({
|
|
27662
|
+
automation: dedupe(automation),
|
|
27663
|
+
code: dedupe(code),
|
|
27664
|
+
integration: dedupe(integration),
|
|
27665
|
+
display: dedupe(display)
|
|
27666
|
+
});
|
|
27667
|
+
};
|
|
27668
|
+
gateFederationIdentifier = async (ctx, buckets, codeRefs) => {
|
|
27669
|
+
const samlRes = await listNodesByType(ctx.graph, "SamlSsoConfig", { limit: 200 });
|
|
27670
|
+
if (!samlRes.ok)
|
|
27671
|
+
return err({ kind: "internal", message: `graph query failed: ${samlRes.error.message}` });
|
|
27672
|
+
const apRes = await listNodesByType(ctx.graph, "AuthProvider", { limit: 200 });
|
|
27673
|
+
if (!apRes.ok)
|
|
27674
|
+
return err({ kind: "internal", message: `graph query failed: ${apRes.error.message}` });
|
|
27675
|
+
const configs = samlRes.value;
|
|
27676
|
+
const mappings = configs.map((n) => String(n.properties["identityMapping"] ?? "Username"));
|
|
27677
|
+
const fedConfigs = configs.filter((n) => n.properties["identityMapping"] === "FederationId");
|
|
27678
|
+
const oidcLogin = apRes.value.filter((ap) => {
|
|
27679
|
+
const rh = ap.properties["registrationHandler"];
|
|
27680
|
+
return typeof rh === "string" && rh.length > 0 || ap.properties["providerType"] === "OpenIdConnect";
|
|
27681
|
+
});
|
|
27682
|
+
const others = buckets.filter((b) => b.bucket !== "identity");
|
|
27683
|
+
let identity;
|
|
27684
|
+
let disclosure;
|
|
27685
|
+
if (fedConfigs.length > 0) {
|
|
27686
|
+
identity = {
|
|
27687
|
+
bucket: "identity",
|
|
27688
|
+
severity: "critical",
|
|
27689
|
+
confidence: "confirmed",
|
|
27690
|
+
summary: `SAML SSO maps users by Federation ID (${fedConfigs.length} SamlSsoConfig) \u2014 changing this value breaks SSO login until the IdP is updated.`,
|
|
27691
|
+
evidence: fedConfigs.map((c) => c.id)
|
|
27692
|
+
};
|
|
27693
|
+
} else if (oidcLogin.length > 0) {
|
|
27694
|
+
const handlers = [...new Set(oidcLogin.map((a) => a.properties["registrationHandler"]).filter((s) => typeof s === "string" && s.length > 0))];
|
|
27695
|
+
const handlersRefFed = handlers.filter((h) => codeRefs.includes(`ApexClass:${h}`));
|
|
27696
|
+
if (handlersRefFed.length > 0) {
|
|
27697
|
+
identity = {
|
|
27698
|
+
bucket: "identity",
|
|
27699
|
+
severity: "medium",
|
|
27700
|
+
confidence: "likely",
|
|
27701
|
+
summary: `OIDC / social SSO registration handler(s) ${handlersRefFed.join(", ")} reference FederationIdentifier (heuristic Apex scan) \u2014 it is part of the OIDC identity mapping; changing the value may break login.`,
|
|
27702
|
+
evidence: [...oidcLogin.map((a) => a.id), ...handlersRefFed.map((h) => `ApexClass:${h}`)]
|
|
27703
|
+
};
|
|
27704
|
+
disclosure = "The handler-to-FederationIdentifier link is from the heuristic Apex scanner (token-level); cross-method dataflow, dynamic SOQL, and reflective field access are invisible \u2014 confirm in the handler source + ThirdPartyAccountLink.";
|
|
27705
|
+
} else {
|
|
27706
|
+
identity = {
|
|
27707
|
+
bucket: "identity",
|
|
27708
|
+
severity: "medium",
|
|
27709
|
+
confidence: "likely",
|
|
27710
|
+
summary: `OIDC / social SSO login provider(s) present (${oidcLogin.map((a) => a.apiName).slice(0, 3).join(", ")}); the mapping is in an Apex registration handler \u2014 the heuristic scan found no FederationIdentifier reference there, but it may map it dynamically.`,
|
|
27711
|
+
evidence: oidcLogin.map((a) => a.id)
|
|
27712
|
+
};
|
|
27713
|
+
disclosure = `OIDC/social SSO maps identity in an Apex registration handler${handlers.length > 0 ? ` (${handlers.slice(0, 2).join(", ")})` : ""}; the scan saw no FederationIdentifier reference, but dynamic / reflective field access is invisible \u2014 verify the handler + ThirdPartyAccountLink before changing values.`;
|
|
27714
|
+
}
|
|
27715
|
+
} else if (configs.length > 0) {
|
|
27716
|
+
identity = {
|
|
27717
|
+
bucket: "identity",
|
|
27718
|
+
severity: "low",
|
|
27719
|
+
confidence: "confirmed",
|
|
27720
|
+
summary: `SAML SSO maps by ${[...new Set(mappings)].join("/")}, not Federation ID, and no OIDC login provider is present \u2014 this field is not the SSO subject here.`,
|
|
27721
|
+
evidence: configs.map((c) => c.id)
|
|
27722
|
+
};
|
|
27723
|
+
} else {
|
|
27724
|
+
identity = {
|
|
27725
|
+
bucket: "identity",
|
|
27726
|
+
severity: "low",
|
|
27727
|
+
confidence: "likely",
|
|
27728
|
+
summary: "No SAML SSO config and no OIDC/social login provider in the vault \u2014 Federation ID is not in use for SSO here.",
|
|
27729
|
+
evidence: []
|
|
27730
|
+
};
|
|
27731
|
+
disclosure = "No SamlSsoConfig or login-capable AuthProvider in the vault: if SSO is configured outside retrievable metadata, changing FederationIdentifier could still affect login \u2014 verify with your IdP.";
|
|
27732
|
+
}
|
|
27733
|
+
return ok({ buckets: [identity, ...others], ...disclosure !== void 0 ? { disclosure } : {} });
|
|
27734
|
+
};
|
|
27735
|
+
detectShadowJoin = async (ctx, classification) => {
|
|
27736
|
+
const { object, field, upsertKey } = classification;
|
|
27737
|
+
const keyish = upsertKey.isUpsertKey || /(_ID|_SIS_ID|Key|_uuid|_guid|Code)__c$/i.test(field);
|
|
27738
|
+
if (!keyish)
|
|
27739
|
+
return ok(null);
|
|
27740
|
+
const all = [];
|
|
27741
|
+
let offset = 0;
|
|
27742
|
+
for (; ; ) {
|
|
27743
|
+
const res = await listNodesByType(ctx.graph, "CustomField", { limit: 500, offset });
|
|
27744
|
+
if (!res.ok)
|
|
27745
|
+
return err({ kind: "internal", message: `graph query failed: ${res.error.message}` });
|
|
27746
|
+
all.push(...res.value);
|
|
27747
|
+
if (res.value.length < 500)
|
|
27748
|
+
break;
|
|
27749
|
+
offset += 500;
|
|
27750
|
+
}
|
|
27751
|
+
const objOf = (n) => parseFieldId(n.id)?.object ?? "?";
|
|
27752
|
+
const sameName = all.filter((n) => parseFieldId(n.id)?.field === field);
|
|
27753
|
+
if (sameName.length <= 1)
|
|
27754
|
+
return ok(null);
|
|
27755
|
+
const masters = sameName.filter((n) => n.properties["externalId"] === true);
|
|
27756
|
+
if (masters.length === 0)
|
|
27757
|
+
return ok(null);
|
|
27758
|
+
const otherObjects = sameName.filter((n) => objOf(n) !== object);
|
|
27759
|
+
if (otherObjects.length === 0)
|
|
27760
|
+
return ok(null);
|
|
27761
|
+
const masterObjs = [...new Set(masters.map(objOf))].sort();
|
|
27762
|
+
const copyObjs = [...new Set(sameName.filter((n) => n.properties["externalId"] !== true).map(objOf))].sort();
|
|
27763
|
+
const thisIsMaster = upsertKey.signals.includes("externalId");
|
|
27764
|
+
return ok({
|
|
27765
|
+
bucket: "cross-object",
|
|
27766
|
+
severity: thisIsMaster ? "high" : "medium",
|
|
27767
|
+
confidence: "likely",
|
|
27768
|
+
summary: `'${field}' is replicated on ${sameName.length} objects \u2014 External-ID key on [${masterObjs.join(", ")}]${copyObjs.length > 0 ? `, plain copy on [${copyObjs.join(", ")}]` : ""}. These are value-joined with no formal relationship; changing the value here can silently desync the copies.`,
|
|
27769
|
+
evidence: sameName.map((n) => n.id).sort()
|
|
27770
|
+
});
|
|
27771
|
+
};
|
|
27772
|
+
extractQuotedLiterals = (expr) => {
|
|
27773
|
+
const out = [];
|
|
27774
|
+
const re = /["']([^"']{1,40})["']/g;
|
|
27775
|
+
let m;
|
|
27776
|
+
while ((m = re.exec(expr)) !== null)
|
|
27777
|
+
out.push(m[1]);
|
|
27778
|
+
return out;
|
|
27779
|
+
};
|
|
27780
|
+
normalizeExpr = (expr) => typeof expr === "string" ? expr.replace(/\s+/g, " ").trim().slice(0, 120) : "";
|
|
27781
|
+
findValueCouplings = async (ctx, fieldId) => {
|
|
27782
|
+
const leaf = parseFieldId(fieldId)?.field ?? fieldId;
|
|
27783
|
+
const re = new RegExp(`\\b${leaf}\\b`);
|
|
27784
|
+
const matched = [];
|
|
27785
|
+
let offset = 0;
|
|
27786
|
+
for (; ; ) {
|
|
27787
|
+
const res = await listNodesByType(ctx.graph, "ConditionalContext", { limit: 500, offset });
|
|
27788
|
+
if (!res.ok)
|
|
27789
|
+
return err({ kind: "internal", message: `graph query failed: ${res.error.message}` });
|
|
27790
|
+
for (const n of res.value) {
|
|
27791
|
+
const refs = Array.isArray(n.properties["fieldRefs"]) ? n.properties["fieldRefs"] : [];
|
|
27792
|
+
const expr = n.properties["expression"];
|
|
27793
|
+
if (refs.includes(fieldId) || typeof expr === "string" && re.test(expr))
|
|
27794
|
+
matched.push(n);
|
|
27795
|
+
}
|
|
27796
|
+
if (res.value.length < 500)
|
|
27797
|
+
break;
|
|
27798
|
+
offset += 500;
|
|
27799
|
+
}
|
|
27800
|
+
const expressions = [...new Set(matched.map((n) => normalizeExpr(n.properties["expression"])).filter((s) => s.length > 0))].slice(0, 8);
|
|
27801
|
+
const literals = [...new Set(matched.flatMap((n) => extractQuotedLiterals(String(n.properties["expression"] ?? ""))))].slice(0, 12);
|
|
27802
|
+
return ok({ expressions, literals });
|
|
27803
|
+
};
|
|
27804
|
+
assessValueChange = async (ctx, node) => {
|
|
27805
|
+
const classification = classifyField(node);
|
|
27806
|
+
const mutable = classification.mutability.mutability === "writable";
|
|
27807
|
+
const edgesResult = mutable ? await summarizeIncomingEdges(ctx, node.id) : ok(EMPTY_EDGE_SUMMARY);
|
|
27808
|
+
if (!edgesResult.ok)
|
|
27809
|
+
return err(edgesResult.error);
|
|
27810
|
+
let buckets = buildBuckets(classification, edgesResult.value);
|
|
27811
|
+
const extraDisclosures = [];
|
|
27812
|
+
if (mutable && classification.field === "FederationIdentifier") {
|
|
27813
|
+
const gated = await gateFederationIdentifier(ctx, buckets, edgesResult.value.code);
|
|
27814
|
+
if (!gated.ok)
|
|
27815
|
+
return err(gated.error);
|
|
27816
|
+
buckets = gated.value.buckets;
|
|
27817
|
+
if (gated.value.disclosure !== void 0)
|
|
27818
|
+
extraDisclosures.push(gated.value.disclosure);
|
|
27819
|
+
}
|
|
27820
|
+
if (mutable) {
|
|
27821
|
+
const shadow = await detectShadowJoin(ctx, classification);
|
|
27822
|
+
if (!shadow.ok)
|
|
27823
|
+
return err(shadow.error);
|
|
27824
|
+
if (shadow.value !== null)
|
|
27825
|
+
buckets = [...buckets, shadow.value];
|
|
27826
|
+
}
|
|
27827
|
+
if (mutable) {
|
|
27828
|
+
const cp = await findValueCouplings(ctx, node.id);
|
|
27829
|
+
if (!cp.ok)
|
|
27830
|
+
return err(cp.error);
|
|
27831
|
+
if (cp.value.expressions.length > 0) {
|
|
27832
|
+
const existing = buckets.find((b) => b.bucket === "automation");
|
|
27833
|
+
const enriched = {
|
|
27834
|
+
bucket: "automation",
|
|
27835
|
+
severity: existing?.severity ?? "medium",
|
|
27836
|
+
confidence: "confirmed",
|
|
27837
|
+
summary: `Value-coupled in ${cp.value.expressions.length} declarative condition(s)${cp.value.literals.length > 0 ? ` (literals: ${cp.value.literals.slice(0, 6).join(", ")})` : ""} \u2014 changing the value may flip them. e.g. ${cp.value.expressions.slice(0, 3).join(" | ")}`,
|
|
27838
|
+
evidence: [...existing?.evidence ?? [], ...cp.value.expressions.slice(0, 6)]
|
|
27839
|
+
};
|
|
27840
|
+
buckets = [...buckets.filter((b) => b.bucket !== "automation"), enriched];
|
|
27841
|
+
}
|
|
27842
|
+
}
|
|
27843
|
+
return ok({
|
|
27844
|
+
fieldId: node.id,
|
|
27845
|
+
object: classification.object,
|
|
27846
|
+
field: classification.field,
|
|
27847
|
+
mutable,
|
|
27848
|
+
overallSeverity: overallSeverityOf(buckets),
|
|
27849
|
+
buckets,
|
|
27850
|
+
disclosures: mutable ? [...buildDisclosures(buckets), ...extraDisclosures] : [],
|
|
27851
|
+
recommendedChecks: mutable ? recommendedChecksFor(buckets) : ["Change the source field(s) instead \u2014 this value is derived."]
|
|
27852
|
+
});
|
|
27853
|
+
};
|
|
27854
|
+
}
|
|
27855
|
+
});
|
|
27856
|
+
|
|
27857
|
+
// ../mcp/dist/src/tools/value-change-audit.js
|
|
27858
|
+
import { z as z105 } from "zod";
|
|
27859
|
+
var VALUE_CHANGE_REQUIRED_COVERAGE, MAX_ROWS, PAGE, DISCLOSURE9, GLOBAL_DISCLOSURES, valueChangeAuditInputSchema, coverageCaveatFor3, listObjectFields, isCandidate, buildRow2, valueChangeAuditHandler;
|
|
27860
|
+
var init_value_change_audit = __esm({
|
|
27861
|
+
"../mcp/dist/src/tools/value-change-audit.js"() {
|
|
27862
|
+
"use strict";
|
|
27863
|
+
init_dist();
|
|
27864
|
+
init_src();
|
|
27865
|
+
init_src2();
|
|
27866
|
+
init_value_change_classification();
|
|
27867
|
+
init_value_change_risk();
|
|
27868
|
+
VALUE_CHANGE_REQUIRED_COVERAGE = [
|
|
27869
|
+
"CustomField",
|
|
27870
|
+
"ValidationRule",
|
|
27871
|
+
"Flow",
|
|
27872
|
+
"ApexClass",
|
|
27873
|
+
"ApexTrigger",
|
|
27874
|
+
"WorkflowRule",
|
|
27875
|
+
"Layout",
|
|
27876
|
+
"SharingRule",
|
|
27877
|
+
"DuplicateRule"
|
|
27878
|
+
];
|
|
27879
|
+
MAX_ROWS = 200;
|
|
27880
|
+
PAGE = 500;
|
|
27881
|
+
DISCLOSURE9 = "value_change_audit ranks fields by the impact of changing their stored VALUE (not schema). Auto-detect surfaces upsert keys (externalId/unique/idLookup), identity-catalog fields, and name-lexicon matches \u2014 it can miss a value-sensitive field that carries none of those signals, and the per-row blast radius inherits what_if_change_field_value\u2019s boundaries (external upsert systems, the IdP side of SSO, and dynamic/managed-package code are invisible).";
|
|
27882
|
+
GLOBAL_DISCLOSURES = [
|
|
27883
|
+
"External systems that upsert on these keys live outside org metadata \u2014 confirm them in your middleware / ETL.",
|
|
27884
|
+
"SSO identity mapping (which field the IdP asserts) is read from SamlSsoConfig when present; verify your IdP.",
|
|
27885
|
+
"Reports, list-view filters, and manual processes may key on these values."
|
|
27886
|
+
];
|
|
27887
|
+
valueChangeAuditInputSchema = z105.object({
|
|
27888
|
+
object: z105.string().min(1),
|
|
27889
|
+
fields: z105.array(z105.string()).optional(),
|
|
27890
|
+
verbosity: z105.enum(["summary", "detail"]).optional()
|
|
27891
|
+
});
|
|
27892
|
+
coverageCaveatFor3 = (ctx) => {
|
|
27893
|
+
const coverage = summarizeCoverage(ctx.manifest, VALUE_CHANGE_REQUIRED_COVERAGE);
|
|
27894
|
+
if (coverage.status === "complete")
|
|
27895
|
+
return void 0;
|
|
27896
|
+
const missingCoverage = coverage.missingCoverage.length > 0 ? coverage.missingCoverage : [...VALUE_CHANGE_REQUIRED_COVERAGE];
|
|
27897
|
+
return {
|
|
27898
|
+
status: coverage.status === "partial" ? "partial" : "unknown",
|
|
27899
|
+
missingCoverage,
|
|
27900
|
+
message: `Value-change audit is incomplete because the vault lacks coverage for: ${missingCoverage.join(", ")}.`
|
|
27901
|
+
};
|
|
27902
|
+
};
|
|
27903
|
+
listObjectFields = async (ctx, objectId) => {
|
|
27904
|
+
const all = [];
|
|
27905
|
+
let offset = 0;
|
|
27906
|
+
for (; ; ) {
|
|
27907
|
+
const res = await listNodesByType(ctx.graph, "CustomField", { parentId: objectId, limit: PAGE, offset });
|
|
27908
|
+
if (!res.ok)
|
|
27909
|
+
return err({ kind: "internal", message: `graph query failed: ${res.error.message}` });
|
|
27910
|
+
all.push(...res.value);
|
|
27911
|
+
if (res.value.length < PAGE)
|
|
27912
|
+
break;
|
|
27913
|
+
offset += PAGE;
|
|
27914
|
+
}
|
|
27915
|
+
return ok(all);
|
|
27916
|
+
};
|
|
27917
|
+
isCandidate = (node) => {
|
|
27918
|
+
const c = classifyField(node);
|
|
27919
|
+
if (c.mutability.mutability === "derived")
|
|
27920
|
+
return false;
|
|
27921
|
+
return c.upsertKey.isUpsertKey || lookupIdentityCatalog(c.object, c.field) !== null || severityRank(c.role.severity) >= severityRank("medium");
|
|
27922
|
+
};
|
|
27923
|
+
buildRow2 = (assessment, node, verbosity) => {
|
|
27924
|
+
const classification = classifyField(node);
|
|
27925
|
+
const topReasons = [...assessment.buckets].filter((b) => b.severity !== "info").sort((a, b) => severityRank(b.severity) - severityRank(a.severity)).slice(0, 3).map((b) => `${b.bucket} (${b.severity})`);
|
|
27926
|
+
return {
|
|
27927
|
+
field: assessment.field,
|
|
27928
|
+
fieldId: assessment.fieldId,
|
|
27929
|
+
role: classification.role.role,
|
|
27930
|
+
overallSeverity: assessment.overallSeverity,
|
|
27931
|
+
mutable: assessment.mutable,
|
|
27932
|
+
topReasons,
|
|
27933
|
+
confidence: classification.role.confidence,
|
|
27934
|
+
disclosureCount: assessment.disclosures.length,
|
|
27935
|
+
...verbosity === "detail" ? { buckets: assessment.buckets } : {}
|
|
27936
|
+
};
|
|
27937
|
+
};
|
|
27938
|
+
valueChangeAuditHandler = async (ctx, input2) => {
|
|
27939
|
+
const object = input2.object;
|
|
27940
|
+
const objectId = `CustomObject:${object}`;
|
|
27941
|
+
const verbosity = input2.verbosity ?? "summary";
|
|
27942
|
+
let candidates = [];
|
|
27943
|
+
let autoDetected;
|
|
27944
|
+
let scannedFieldCount;
|
|
27945
|
+
const notFound = [];
|
|
27946
|
+
if (input2.fields !== void 0 && input2.fields.length > 0) {
|
|
27947
|
+
autoDetected = false;
|
|
27948
|
+
scannedFieldCount = input2.fields.length;
|
|
27949
|
+
for (const f of input2.fields) {
|
|
27950
|
+
const id = f.startsWith("CustomField:") ? f : `CustomField:${object}.${f}`;
|
|
27951
|
+
const nodeResult = await getNodeById(ctx.graph, id);
|
|
27952
|
+
if (!nodeResult.ok)
|
|
27953
|
+
return err({ kind: "internal", message: `graph query failed: ${nodeResult.error.message}` });
|
|
27954
|
+
if (nodeResult.value === null) {
|
|
27955
|
+
notFound.push(f);
|
|
27956
|
+
continue;
|
|
27957
|
+
}
|
|
27958
|
+
candidates.push(nodeResult.value);
|
|
27959
|
+
}
|
|
27960
|
+
} else {
|
|
27961
|
+
autoDetected = true;
|
|
27962
|
+
const fieldsResult = await listObjectFields(ctx, objectId);
|
|
27963
|
+
if (!fieldsResult.ok)
|
|
27964
|
+
return err(fieldsResult.error);
|
|
27965
|
+
scannedFieldCount = fieldsResult.value.length;
|
|
27966
|
+
candidates = fieldsResult.value.filter(isCandidate);
|
|
27967
|
+
}
|
|
27968
|
+
const rows = [];
|
|
27969
|
+
for (const node of candidates) {
|
|
27970
|
+
const assessmentResult = await assessValueChange(ctx, node);
|
|
27971
|
+
if (!assessmentResult.ok)
|
|
27972
|
+
return err(assessmentResult.error);
|
|
27973
|
+
rows.push(buildRow2(assessmentResult.value, node, verbosity));
|
|
27974
|
+
}
|
|
27975
|
+
rows.sort((a, b) => severityRank(b.overallSeverity) - severityRank(a.overallSeverity) || (a.field < b.field ? -1 : a.field > b.field ? 1 : 0));
|
|
27976
|
+
const truncated = rows.length > MAX_ROWS;
|
|
27977
|
+
const shownRows = truncated ? rows.slice(0, MAX_ROWS) : rows;
|
|
27978
|
+
const summary = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
27979
|
+
for (const r of rows)
|
|
27980
|
+
summary[r.overallSeverity] += 1;
|
|
27981
|
+
const coverageCaveat = coverageCaveatFor3(ctx);
|
|
27982
|
+
const confidence = rows.some((r) => r.topReasons.some((t) => t.startsWith("automation"))) ? "heuristic" : "declared";
|
|
27983
|
+
return ok({
|
|
27984
|
+
data: {
|
|
27985
|
+
object,
|
|
27986
|
+
autoDetected,
|
|
27987
|
+
scannedFieldCount,
|
|
27988
|
+
rows: shownRows,
|
|
27989
|
+
truncated,
|
|
27990
|
+
summary,
|
|
27991
|
+
globalDisclosures: GLOBAL_DISCLOSURES,
|
|
27992
|
+
...notFound.length > 0 ? { notFound } : {},
|
|
27993
|
+
...coverageCaveat !== void 0 ? { coverageCaveat } : {},
|
|
27994
|
+
trust: {
|
|
27995
|
+
provenance: "offline_snapshot",
|
|
27996
|
+
confidence,
|
|
27997
|
+
freshness: { snapshotRefreshedAt: ctx.manifest.refreshedAt },
|
|
27998
|
+
completeness: {
|
|
27999
|
+
status: coverageCaveat === void 0 ? "complete" : coverageCaveat.status,
|
|
28000
|
+
...coverageCaveat !== void 0 ? { missingCoverage: coverageCaveat.missingCoverage } : {}
|
|
28001
|
+
},
|
|
28002
|
+
limitations: [DISCLOSURE9, ...coverageCaveat !== void 0 ? [coverageCaveat.message] : []]
|
|
28003
|
+
},
|
|
28004
|
+
disclosure: DISCLOSURE9
|
|
28005
|
+
},
|
|
28006
|
+
vaultState: {
|
|
28007
|
+
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
28008
|
+
refreshedAt: ctx.manifest.refreshedAt
|
|
28009
|
+
}
|
|
28010
|
+
});
|
|
28011
|
+
};
|
|
28012
|
+
}
|
|
28013
|
+
});
|
|
28014
|
+
|
|
26296
28015
|
// ../mcp/dist/src/tools/what-happens-on-save.js
|
|
26297
|
-
import { z as
|
|
26298
|
-
var
|
|
28016
|
+
import { z as z106 } from "zod";
|
|
28017
|
+
var DISCLOSURE10, ALLOWED_EVENTS, whatHappensOnSaveInputSchema, workflowMatchesEvent2, flowMatchesEvent2, triggerMatchesEvent2, surfaceFirstCondition2, buildActions2, buildStep2, fetchParentedFirers2, fetchTriggersOnFirers2, buildAsyncSteps2, ASSIGNMENT_TYPES2, APPROVAL_TYPES2, VALIDATION_TYPES2, FLOW_TYPES2, TRIGGER_TYPES2, WORKFLOW_TYPES2, whatHappensOnSaveHandler;
|
|
26299
28018
|
var init_what_happens_on_save = __esm({
|
|
26300
28019
|
"../mcp/dist/src/tools/what-happens-on-save.js"() {
|
|
26301
28020
|
"use strict";
|
|
@@ -26303,7 +28022,7 @@ var init_what_happens_on_save = __esm({
|
|
|
26303
28022
|
init_src();
|
|
26304
28023
|
init_soe_admission();
|
|
26305
28024
|
init_soe_payload_bounds();
|
|
26306
|
-
|
|
28025
|
+
DISCLOSURE10 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
|
|
26307
28026
|
ALLOWED_EVENTS = [
|
|
26308
28027
|
"insert",
|
|
26309
28028
|
"update",
|
|
@@ -26311,13 +28030,13 @@ var init_what_happens_on_save = __esm({
|
|
|
26311
28030
|
"delete",
|
|
26312
28031
|
"undelete"
|
|
26313
28032
|
];
|
|
26314
|
-
whatHappensOnSaveInputSchema =
|
|
26315
|
-
objectApiName:
|
|
28033
|
+
whatHappensOnSaveInputSchema = z106.object({
|
|
28034
|
+
objectApiName: z106.string().min(1),
|
|
26316
28035
|
// Accept "after update" / "Before Insert" etc.: lower-case and drop the
|
|
26317
28036
|
// before/after timing prefix so the bare DML event matches the enum. The
|
|
26318
28037
|
// SOE walker models both timings internally; the event arg selects the row.
|
|
26319
|
-
event:
|
|
26320
|
-
recordTypeId:
|
|
28038
|
+
event: z106.preprocess((v) => typeof v === "string" ? v.trim().toLowerCase().replace(/^(?:before|after)\s+/, "") : v, z106.enum(ALLOWED_EVENTS)),
|
|
28039
|
+
recordTypeId: z106.string().min(1).optional()
|
|
26321
28040
|
});
|
|
26322
28041
|
workflowMatchesEvent2 = (triggerType, event) => {
|
|
26323
28042
|
if (typeof triggerType !== "string")
|
|
@@ -26683,7 +28402,7 @@ var init_what_happens_on_save = __esm({
|
|
|
26683
28402
|
conditionalSteps: conditionalCount,
|
|
26684
28403
|
asyncFanOut
|
|
26685
28404
|
},
|
|
26686
|
-
disclosure: composeSoeDisclosure(
|
|
28405
|
+
disclosure: composeSoeDisclosure(DISCLOSURE10, objectModeled)
|
|
26687
28406
|
};
|
|
26688
28407
|
const budget = enforceSoeByteBudget(data, [soe]);
|
|
26689
28408
|
if (budget.truncated) {
|
|
@@ -26701,26 +28420,111 @@ var init_what_happens_on_save = __esm({
|
|
|
26701
28420
|
}
|
|
26702
28421
|
});
|
|
26703
28422
|
|
|
26704
|
-
// ../mcp/dist/src/tools/
|
|
26705
|
-
|
|
26706
|
-
var
|
|
26707
|
-
|
|
28423
|
+
// ../mcp/dist/src/tools/what-if-change-field-value.js
|
|
28424
|
+
import { z as z107 } from "zod";
|
|
28425
|
+
var CUSTOM_FIELD_PREFIX12, VALUE_CHANGE_REQUIRED_COVERAGE2, DISCLOSURE11, whatIfChangeFieldValueInputSchema, coverageCaveatFor4, whatIfChangeFieldValueHandler;
|
|
28426
|
+
var init_what_if_change_field_value = __esm({
|
|
28427
|
+
"../mcp/dist/src/tools/what-if-change-field-value.js"() {
|
|
26708
28428
|
"use strict";
|
|
28429
|
+
init_dist();
|
|
26709
28430
|
init_src();
|
|
26710
|
-
|
|
26711
|
-
|
|
26712
|
-
|
|
26713
|
-
|
|
26714
|
-
|
|
28431
|
+
init_src2();
|
|
28432
|
+
init_value_change_risk();
|
|
28433
|
+
CUSTOM_FIELD_PREFIX12 = "CustomField:";
|
|
28434
|
+
VALUE_CHANGE_REQUIRED_COVERAGE2 = [
|
|
28435
|
+
"CustomField",
|
|
28436
|
+
"ValidationRule",
|
|
28437
|
+
"Flow",
|
|
28438
|
+
"ApexClass",
|
|
28439
|
+
"ApexTrigger",
|
|
28440
|
+
"WorkflowRule",
|
|
28441
|
+
"Layout",
|
|
28442
|
+
"SharingRule",
|
|
28443
|
+
"DuplicateRule"
|
|
28444
|
+
];
|
|
28445
|
+
DISCLOSURE11 = "Value-change impact analyzes what breaks if this field\u2019s stored VALUE changes (not its schema) \u2014 distinct from what_if_change_field_type, which walks references. Identity / integration-key / uniqueness verdicts come from the field\u2019s own metadata (externalId / unique / idLookup, identity catalog); automation buckets surface the declarative value-literal coupling (the value a rule compares this field to); Apex literal comparisons remain invisible. The vault cannot see external upsert systems, the IdP side of SSO, or dynamic / managed-package code \u2014 see disclosures.";
|
|
28446
|
+
whatIfChangeFieldValueInputSchema = z107.object({
|
|
28447
|
+
fieldId: z107.string().min(1),
|
|
28448
|
+
newValue: z107.string().optional()
|
|
28449
|
+
});
|
|
28450
|
+
coverageCaveatFor4 = (ctx) => {
|
|
28451
|
+
const coverage = summarizeCoverage(ctx.manifest, VALUE_CHANGE_REQUIRED_COVERAGE2);
|
|
28452
|
+
if (coverage.status === "complete")
|
|
28453
|
+
return void 0;
|
|
28454
|
+
const missingCoverage = coverage.missingCoverage.length > 0 ? coverage.missingCoverage : [...VALUE_CHANGE_REQUIRED_COVERAGE2];
|
|
28455
|
+
return {
|
|
28456
|
+
status: coverage.status === "partial" ? "partial" : "unknown",
|
|
28457
|
+
missingCoverage,
|
|
28458
|
+
message: `Value-change impact is incomplete because the vault lacks coverage for: ${missingCoverage.join(", ")}. Absence of impacts in those families means "not checked", not "safe".`
|
|
28459
|
+
};
|
|
28460
|
+
};
|
|
28461
|
+
whatIfChangeFieldValueHandler = async (ctx, input2) => {
|
|
28462
|
+
if (!input2.fieldId.startsWith(CUSTOM_FIELD_PREFIX12)) {
|
|
28463
|
+
return err({
|
|
28464
|
+
kind: "invalid-query",
|
|
28465
|
+
message: `fieldId must start with '${CUSTOM_FIELD_PREFIX12}'; got '${input2.fieldId}'`,
|
|
28466
|
+
path: "fieldId"
|
|
28467
|
+
});
|
|
26715
28468
|
}
|
|
26716
|
-
|
|
28469
|
+
const fieldId = input2.fieldId;
|
|
28470
|
+
const nodeResult = await getNodeById(ctx.graph, fieldId);
|
|
28471
|
+
if (!nodeResult.ok) {
|
|
28472
|
+
return err({ kind: "internal", message: `graph query failed: ${nodeResult.error.message}` });
|
|
28473
|
+
}
|
|
28474
|
+
if (nodeResult.value === null) {
|
|
28475
|
+
return err({ kind: "component-not-found", message: `no field with id ${fieldId}`, path: fieldId });
|
|
28476
|
+
}
|
|
28477
|
+
const assessmentResult = await assessValueChange(ctx, nodeResult.value);
|
|
28478
|
+
if (!assessmentResult.ok)
|
|
28479
|
+
return err(assessmentResult.error);
|
|
28480
|
+
const a = assessmentResult.value;
|
|
28481
|
+
const recommendedChecks = input2.newValue !== void 0 ? [
|
|
28482
|
+
`Verify the proposed value "${input2.newValue}" does not collide on a unique/external-ID field and is accepted by referencing automation.`,
|
|
28483
|
+
...a.recommendedChecks
|
|
28484
|
+
] : a.recommendedChecks;
|
|
28485
|
+
const coverageCaveat = coverageCaveatFor4(ctx);
|
|
28486
|
+
const hasAutomation = a.buckets.some((b) => b.bucket === "automation");
|
|
28487
|
+
const confidence = hasAutomation ? "heuristic" : "declared";
|
|
28488
|
+
return ok({
|
|
28489
|
+
data: {
|
|
28490
|
+
fieldId,
|
|
28491
|
+
object: a.object,
|
|
28492
|
+
field: a.field,
|
|
28493
|
+
mutable: a.mutable,
|
|
28494
|
+
overallSeverity: a.overallSeverity,
|
|
28495
|
+
buckets: a.buckets,
|
|
28496
|
+
disclosures: a.disclosures,
|
|
28497
|
+
recommendedChecks,
|
|
28498
|
+
...input2.newValue !== void 0 ? { newValue: input2.newValue } : {},
|
|
28499
|
+
...coverageCaveat !== void 0 ? { coverageCaveat } : {},
|
|
28500
|
+
trust: {
|
|
28501
|
+
provenance: "offline_snapshot",
|
|
28502
|
+
confidence,
|
|
28503
|
+
freshness: { snapshotRefreshedAt: ctx.manifest.refreshedAt },
|
|
28504
|
+
completeness: {
|
|
28505
|
+
status: coverageCaveat === void 0 ? "complete" : coverageCaveat.status,
|
|
28506
|
+
...coverageCaveat !== void 0 ? { missingCoverage: coverageCaveat.missingCoverage } : {}
|
|
28507
|
+
},
|
|
28508
|
+
limitations: [
|
|
28509
|
+
DISCLOSURE11,
|
|
28510
|
+
...a.disclosures,
|
|
28511
|
+
...coverageCaveat !== void 0 ? [coverageCaveat.message] : []
|
|
28512
|
+
]
|
|
28513
|
+
},
|
|
28514
|
+
disclosure: DISCLOSURE11
|
|
28515
|
+
},
|
|
28516
|
+
vaultState: {
|
|
28517
|
+
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
28518
|
+
refreshedAt: ctx.manifest.refreshedAt
|
|
28519
|
+
}
|
|
28520
|
+
});
|
|
26717
28521
|
};
|
|
26718
28522
|
}
|
|
26719
28523
|
});
|
|
26720
28524
|
|
|
26721
28525
|
// ../mcp/dist/src/tools/what-if-change-method-signature.js
|
|
26722
|
-
import { z as
|
|
26723
|
-
var APEX_CLASS_PREFIX9,
|
|
28526
|
+
import { z as z108 } from "zod";
|
|
28527
|
+
var APEX_CLASS_PREFIX9, DISCLOSURE12, whatIfChangeMethodSignatureInputSchema, readMethodName, isTestClass8, buildExplanation2, classifyCaller, aggregateVerdict4, compareImpacts, compareIds, collectCallers, collectCoveringTests, whatIfChangeMethodSignatureHandler;
|
|
26724
28528
|
var init_what_if_change_method_signature = __esm({
|
|
26725
28529
|
"../mcp/dist/src/tools/what-if-change-method-signature.js"() {
|
|
26726
28530
|
"use strict";
|
|
@@ -26730,11 +28534,11 @@ var init_what_if_change_method_signature = __esm({
|
|
|
26730
28534
|
init_coverage_trust();
|
|
26731
28535
|
init_phantom_node();
|
|
26732
28536
|
APEX_CLASS_PREFIX9 = "ApexClass:";
|
|
26733
|
-
|
|
26734
|
-
whatIfChangeMethodSignatureInputSchema =
|
|
26735
|
-
classApiName:
|
|
26736
|
-
methodName:
|
|
26737
|
-
newSignature:
|
|
28537
|
+
DISCLOSURE12 = "callers identified via the v1.4 apex-scanner are at heuristic confidence; dynamic dispatch via Type.forName + invoke is invisible. Test classes are identified by @isTest + naming convention (className + 'Test' suffix) and by coversTest edges; a test class that doesn't follow the naming convention and doesn't carry a @TestVisible-tagged covering reference may be missed.";
|
|
28538
|
+
whatIfChangeMethodSignatureInputSchema = z108.object({
|
|
28539
|
+
classApiName: z108.string().min(1),
|
|
28540
|
+
methodName: z108.string().min(1),
|
|
28541
|
+
newSignature: z108.string().optional()
|
|
26738
28542
|
});
|
|
26739
28543
|
readMethodName = (edge) => {
|
|
26740
28544
|
const raw = edge.properties["methodName"];
|
|
@@ -26915,7 +28719,7 @@ var init_what_if_change_method_signature = __esm({
|
|
|
26915
28719
|
verdict: coverage.verdict,
|
|
26916
28720
|
...coverage.coverageCaveat !== void 0 ? { coverageCaveat: coverage.coverageCaveat } : {},
|
|
26917
28721
|
trust: coverage.trust,
|
|
26918
|
-
disclosure:
|
|
28722
|
+
disclosure: DISCLOSURE12
|
|
26919
28723
|
},
|
|
26920
28724
|
vaultState: {
|
|
26921
28725
|
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
@@ -26927,8 +28731,8 @@ var init_what_if_change_method_signature = __esm({
|
|
|
26927
28731
|
});
|
|
26928
28732
|
|
|
26929
28733
|
// ../mcp/dist/src/tools/what-if-deactivate-flow.js
|
|
26930
|
-
import { z as
|
|
26931
|
-
var FLOW_PREFIX3,
|
|
28734
|
+
import { z as z109 } from "zod";
|
|
28735
|
+
var FLOW_PREFIX3, DISCLOSURE13, whatIfDeactivateFlowInputSchema, stripPrefix2, readFlowStatus2, classifyOutgoingEdge, buildExplanation3, aggregateVerdict5, compareImpacts2, collectFiringConditions, whatIfDeactivateFlowHandler;
|
|
26932
28736
|
var init_what_if_deactivate_flow = __esm({
|
|
26933
28737
|
"../mcp/dist/src/tools/what-if-deactivate-flow.js"() {
|
|
26934
28738
|
"use strict";
|
|
@@ -26937,9 +28741,9 @@ var init_what_if_deactivate_flow = __esm({
|
|
|
26937
28741
|
init_coerce_id();
|
|
26938
28742
|
init_coverage_trust();
|
|
26939
28743
|
FLOW_PREFIX3 = "Flow:";
|
|
26940
|
-
|
|
26941
|
-
whatIfDeactivateFlowInputSchema =
|
|
26942
|
-
flowId:
|
|
28744
|
+
DISCLOSURE13 = "v2.3 what-if analysis is composition over the v2.2 vault state. Deactivating a Flow stops every action listed in impacts; the Flow's definition remains in the org and a later reactivation restores the effects. Apex code that conditionally invokes the Flow via Flow.Interview or @InvocableMethod chains is invisible to the heuristic walker; review callers via sfi.find_code_usages targeting the Flow id before relying on this finding.";
|
|
28745
|
+
whatIfDeactivateFlowInputSchema = z109.object({
|
|
28746
|
+
flowId: z109.string().min(1)
|
|
26943
28747
|
});
|
|
26944
28748
|
stripPrefix2 = (id) => {
|
|
26945
28749
|
const colonIdx = id.indexOf(":");
|
|
@@ -27097,7 +28901,7 @@ var init_what_if_deactivate_flow = __esm({
|
|
|
27097
28901
|
verdict: coverage.verdict,
|
|
27098
28902
|
...coverage.coverageCaveat !== void 0 ? { coverageCaveat: coverage.coverageCaveat } : {},
|
|
27099
28903
|
trust: coverage.trust,
|
|
27100
|
-
disclosure:
|
|
28904
|
+
disclosure: DISCLOSURE13
|
|
27101
28905
|
},
|
|
27102
28906
|
vaultState: {
|
|
27103
28907
|
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
@@ -27109,8 +28913,8 @@ var init_what_if_deactivate_flow = __esm({
|
|
|
27109
28913
|
});
|
|
27110
28914
|
|
|
27111
28915
|
// ../mcp/dist/src/tools/what-if-disable-trigger.js
|
|
27112
|
-
import { z as
|
|
27113
|
-
var APEX_TRIGGER_PREFIX7,
|
|
28916
|
+
import { z as z110 } from "zod";
|
|
28917
|
+
var APEX_TRIGGER_PREFIX7, DISCLOSURE14, whatIfDisableTriggerInputSchema, readTriggerStatus, readTriggerEvents, classifyOutgoingEdge2, buildExplanation4, aggregateVerdict6, compareImpacts3, findParentObject, whatIfDisableTriggerHandler;
|
|
27114
28918
|
var init_what_if_disable_trigger = __esm({
|
|
27115
28919
|
"../mcp/dist/src/tools/what-if-disable-trigger.js"() {
|
|
27116
28920
|
"use strict";
|
|
@@ -27118,9 +28922,9 @@ var init_what_if_disable_trigger = __esm({
|
|
|
27118
28922
|
init_src();
|
|
27119
28923
|
init_coverage_trust();
|
|
27120
28924
|
APEX_TRIGGER_PREFIX7 = "ApexTrigger:";
|
|
27121
|
-
|
|
27122
|
-
whatIfDisableTriggerInputSchema =
|
|
27123
|
-
triggerId:
|
|
28925
|
+
DISCLOSURE14 = "v2.3 what-if analysis is composition over the v2.2 vault state. Disabling a trigger stops every action listed in impacts; the trigger definition remains in the org and a later re-enable restores the effects. The v0.3 apex-scanner's edge confidence is heuristic for most outgoing edges; spot-check the trigger body when a finding's confidence is heuristic. Indirect dispatch via trigger framework base classes (TriggerHandler, fflib) may be partially invisible to the recognizer.";
|
|
28926
|
+
whatIfDisableTriggerInputSchema = z110.object({
|
|
28927
|
+
triggerId: z110.string().min(1)
|
|
27124
28928
|
});
|
|
27125
28929
|
readTriggerStatus = (node) => {
|
|
27126
28930
|
const raw = node.properties["status"];
|
|
@@ -27271,7 +29075,7 @@ var init_what_if_disable_trigger = __esm({
|
|
|
27271
29075
|
verdict: coverage.verdict,
|
|
27272
29076
|
...coverage.coverageCaveat !== void 0 ? { coverageCaveat: coverage.coverageCaveat } : {},
|
|
27273
29077
|
trust: coverage.trust,
|
|
27274
|
-
disclosure:
|
|
29078
|
+
disclosure: DISCLOSURE14
|
|
27275
29079
|
},
|
|
27276
29080
|
vaultState: {
|
|
27277
29081
|
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
@@ -27283,22 +29087,22 @@ var init_what_if_disable_trigger = __esm({
|
|
|
27283
29087
|
});
|
|
27284
29088
|
|
|
27285
29089
|
// ../mcp/dist/src/tools/what-if-merge-profiles.js
|
|
27286
|
-
import { z as
|
|
27287
|
-
var PROFILE_PREFIX,
|
|
29090
|
+
import { z as z111 } from "zod";
|
|
29091
|
+
var PROFILE_PREFIX, DISCLOSURE15, MERGE_DEFAULT_LIMIT, MERGE_MAX_LIMIT, whatIfMergeProfilesInputSchema, readUserPermissions, readTabVisibilities, readLayoutAssignments2, readRecordTypeVisibilities, classifyGrantTarget, stripPrefix3, fieldGrantToLevel, gatherGrants, gatherProfileSettings, objectFlagsEqual, recordTypeVisibilityEqual, compareScalarMaps, compareObjectPermissions, compareFieldPermissions, compareTabVisibilities, compareLayoutAssignments, compareRecordTypeVisibilities, fetchProfile, sortConflicts, whatIfMergeProfilesHandler;
|
|
27288
29092
|
var init_what_if_merge_profiles = __esm({
|
|
27289
29093
|
"../mcp/dist/src/tools/what-if-merge-profiles.js"() {
|
|
27290
29094
|
"use strict";
|
|
27291
29095
|
init_dist();
|
|
27292
29096
|
init_src();
|
|
27293
29097
|
PROFILE_PREFIX = "Profile:";
|
|
27294
|
-
|
|
29098
|
+
DISCLOSURE15 = "v2.3 surfaces conflicts but does NOT auto-resolve. Recommended policies are heuristic; manually verify each conflict before applying. Profile-edition rollup (e.g., admin-level overrides) is not modeled.";
|
|
27295
29099
|
MERGE_DEFAULT_LIMIT = 120;
|
|
27296
29100
|
MERGE_MAX_LIMIT = 2e3;
|
|
27297
|
-
whatIfMergeProfilesInputSchema =
|
|
27298
|
-
profileIdA:
|
|
27299
|
-
profileIdB:
|
|
27300
|
-
limit:
|
|
27301
|
-
offset:
|
|
29101
|
+
whatIfMergeProfilesInputSchema = z111.object({
|
|
29102
|
+
profileIdA: z111.string().min(1),
|
|
29103
|
+
profileIdB: z111.string().min(1),
|
|
29104
|
+
limit: z111.number().int().min(1).max(MERGE_MAX_LIMIT).optional(),
|
|
29105
|
+
offset: z111.number().int().min(0).optional()
|
|
27302
29106
|
});
|
|
27303
29107
|
readUserPermissions = (profile) => {
|
|
27304
29108
|
const raw = profile.properties["userPermissions"];
|
|
@@ -27683,7 +29487,7 @@ var init_what_if_merge_profiles = __esm({
|
|
|
27683
29487
|
const page = conflicts.slice(offset, offset + limit);
|
|
27684
29488
|
const hasMore = offset + page.length < conflicts.length;
|
|
27685
29489
|
const truncated = hasMore || offset > 0;
|
|
27686
|
-
const disclosure = truncated ? `${
|
|
29490
|
+
const disclosure = truncated ? `${DISCLOSURE15} Returning conflicts ${offset}\u2013${offset + page.length} of ${conflicts.length} (page size ${limit}); summary.byCategory / byPolicy hold the COMPLETE counts. Page through the rest with offset/limit.` : DISCLOSURE15;
|
|
27687
29491
|
return ok({
|
|
27688
29492
|
data: {
|
|
27689
29493
|
profileIdA,
|
|
@@ -27712,8 +29516,8 @@ var init_what_if_merge_profiles = __esm({
|
|
|
27712
29516
|
});
|
|
27713
29517
|
|
|
27714
29518
|
// ../mcp/dist/src/tools/what-if-remove-picklist-value.js
|
|
27715
|
-
import { z as
|
|
27716
|
-
var
|
|
29519
|
+
import { z as z112 } from "zod";
|
|
29520
|
+
var CUSTOM_FIELD_PREFIX13, PICKLIST_TYPES2, PICKLIST_VALUE_COVERAGE, DISCLOSURE16, coverageCaveatFor5, whatIfRemovePicklistValueInputSchema, buildValueNeedles, containsAnyNeedle, extractHaystackTexts, classifyCategory2, buildExplanation5, findValueInConditionalContexts, aggregateVerdict7, whatIfRemovePicklistValueHandler;
|
|
27717
29521
|
var init_what_if_remove_picklist_value = __esm({
|
|
27718
29522
|
"../mcp/dist/src/tools/what-if-remove-picklist-value.js"() {
|
|
27719
29523
|
"use strict";
|
|
@@ -27721,7 +29525,7 @@ var init_what_if_remove_picklist_value = __esm({
|
|
|
27721
29525
|
init_src();
|
|
27722
29526
|
init_src2();
|
|
27723
29527
|
init_field_properties();
|
|
27724
|
-
|
|
29528
|
+
CUSTOM_FIELD_PREFIX13 = "CustomField:";
|
|
27725
29529
|
PICKLIST_TYPES2 = /* @__PURE__ */ new Set([
|
|
27726
29530
|
"Picklist",
|
|
27727
29531
|
"MultiselectPicklist"
|
|
@@ -27739,8 +29543,8 @@ var init_what_if_remove_picklist_value = __esm({
|
|
|
27739
29543
|
"ListView",
|
|
27740
29544
|
"FlexiPage"
|
|
27741
29545
|
];
|
|
27742
|
-
|
|
27743
|
-
|
|
29546
|
+
DISCLOSURE16 = "Apex code referencing the picklist value as a string literal is recognized only for static literals. Variable-based picklist comparisons (`if (account.Industry__c == myVar)`), dynamic SOQL strings, and reflective field access via `obj.get('FieldName')` are invisible to the recognizer; review dynamic comparisons separately before removing the value.";
|
|
29547
|
+
coverageCaveatFor5 = (ctx) => {
|
|
27744
29548
|
const coverage = summarizeCoverage(ctx.manifest, PICKLIST_VALUE_COVERAGE);
|
|
27745
29549
|
if (coverage.status === "complete")
|
|
27746
29550
|
return void 0;
|
|
@@ -27751,9 +29555,9 @@ var init_what_if_remove_picklist_value = __esm({
|
|
|
27751
29555
|
message: `Picklist-value removal impact is incomplete because the vault lacks coverage for: ${missingCoverage.join(", ")}. Absence of references in those families means "not checked", not "safe".`
|
|
27752
29556
|
};
|
|
27753
29557
|
};
|
|
27754
|
-
whatIfRemovePicklistValueInputSchema =
|
|
27755
|
-
fieldId:
|
|
27756
|
-
value:
|
|
29558
|
+
whatIfRemovePicklistValueInputSchema = z112.object({
|
|
29559
|
+
fieldId: z112.string().min(1),
|
|
29560
|
+
value: z112.string().min(1)
|
|
27757
29561
|
});
|
|
27758
29562
|
buildValueNeedles = (value) => [
|
|
27759
29563
|
`'${value}'`,
|
|
@@ -27852,10 +29656,10 @@ var init_what_if_remove_picklist_value = __esm({
|
|
|
27852
29656
|
return "risky";
|
|
27853
29657
|
};
|
|
27854
29658
|
whatIfRemovePicklistValueHandler = async (ctx, input2) => {
|
|
27855
|
-
if (!input2.fieldId.startsWith(
|
|
29659
|
+
if (!input2.fieldId.startsWith(CUSTOM_FIELD_PREFIX13)) {
|
|
27856
29660
|
return err({
|
|
27857
29661
|
kind: "invalid-query",
|
|
27858
|
-
message: `fieldId must start with '${
|
|
29662
|
+
message: `fieldId must start with '${CUSTOM_FIELD_PREFIX13}'; got '${input2.fieldId}'`,
|
|
27859
29663
|
path: "fieldId"
|
|
27860
29664
|
});
|
|
27861
29665
|
}
|
|
@@ -27934,7 +29738,7 @@ var init_what_if_remove_picklist_value = __esm({
|
|
|
27934
29738
|
}
|
|
27935
29739
|
const sortedImpacts = [...impactsById.values()].sort((a, b) => a.componentId < b.componentId ? -1 : a.componentId > b.componentId ? 1 : 0);
|
|
27936
29740
|
const compatibility = sortedImpacts.length === 0 ? "review" : "breaking";
|
|
27937
|
-
const coverageCaveat =
|
|
29741
|
+
const coverageCaveat = coverageCaveatFor5(ctx);
|
|
27938
29742
|
const rawVerdict = aggregateVerdict7(sortedImpacts);
|
|
27939
29743
|
const verdict = rawVerdict === "safe" && coverageCaveat !== void 0 ? "review" : rawVerdict;
|
|
27940
29744
|
return ok({
|
|
@@ -27955,11 +29759,11 @@ var init_what_if_remove_picklist_value = __esm({
|
|
|
27955
29759
|
...coverageCaveat !== void 0 ? { missingCoverage: coverageCaveat.missingCoverage } : {}
|
|
27956
29760
|
},
|
|
27957
29761
|
limitations: [
|
|
27958
|
-
|
|
29762
|
+
DISCLOSURE16,
|
|
27959
29763
|
...coverageCaveat !== void 0 ? [coverageCaveat.message] : []
|
|
27960
29764
|
]
|
|
27961
29765
|
},
|
|
27962
|
-
disclosure:
|
|
29766
|
+
disclosure: DISCLOSURE16
|
|
27963
29767
|
},
|
|
27964
29768
|
vaultState: {
|
|
27965
29769
|
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
@@ -27971,8 +29775,8 @@ var init_what_if_remove_picklist_value = __esm({
|
|
|
27971
29775
|
});
|
|
27972
29776
|
|
|
27973
29777
|
// ../mcp/dist/src/tools/what-if-split-profile.js
|
|
27974
|
-
import { z as
|
|
27975
|
-
var PROFILE_PREFIX2, PERMISSION_SET_PREFIX, SPLIT_DEFAULT_LIMIT, SPLIT_MAX_LIMIT,
|
|
29778
|
+
import { z as z113 } from "zod";
|
|
29779
|
+
var PROFILE_PREFIX2, PERMISSION_SET_PREFIX, SPLIT_DEFAULT_LIMIT, SPLIT_MAX_LIMIT, DISCLOSURE17, whatIfSplitProfileInputSchema, tokenize4, makeCandidate, resolveTargets, overlapCount, keywordMatch, domainClusterMatch, assignGrant, splitGrants, fetchProfile2, sortAssignments, sortUnassigned, whatIfSplitProfileHandler;
|
|
27976
29780
|
var init_what_if_split_profile = __esm({
|
|
27977
29781
|
"../mcp/dist/src/tools/what-if-split-profile.js"() {
|
|
27978
29782
|
"use strict";
|
|
@@ -27982,12 +29786,12 @@ var init_what_if_split_profile = __esm({
|
|
|
27982
29786
|
PERMISSION_SET_PREFIX = "PermissionSet:";
|
|
27983
29787
|
SPLIT_DEFAULT_LIMIT = 120;
|
|
27984
29788
|
SPLIT_MAX_LIMIT = 2e3;
|
|
27985
|
-
|
|
27986
|
-
whatIfSplitProfileInputSchema =
|
|
27987
|
-
profileId:
|
|
27988
|
-
targetPermSets:
|
|
27989
|
-
limit:
|
|
27990
|
-
offset:
|
|
29789
|
+
DISCLOSURE17 = "v2.3 split clustering is approximate; the greedy keyword-match heuristic is fail-conservative. Review every assignment before applying \u2014 grants the heuristic could not place are surfaced in unassignedSettings rather than forced into an inappropriate target. Layout assignments, tab visibilities, and record-type visibilities are not split (Profile-only settings in the Salesforce metadata model).";
|
|
29790
|
+
whatIfSplitProfileInputSchema = z113.object({
|
|
29791
|
+
profileId: z113.string().min(1),
|
|
29792
|
+
targetPermSets: z113.array(z113.string().min(1)).min(1),
|
|
29793
|
+
limit: z113.number().int().min(1).max(SPLIT_MAX_LIMIT).optional(),
|
|
29794
|
+
offset: z113.number().int().min(0).optional()
|
|
27991
29795
|
});
|
|
27992
29796
|
tokenize4 = (raw) => {
|
|
27993
29797
|
const spaced = raw.replace(/[._\-/]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2");
|
|
@@ -28248,7 +30052,7 @@ var init_what_if_split_profile = __esm({
|
|
|
28248
30052
|
const page = assignments.slice(offset, offset + limit);
|
|
28249
30053
|
const hasMore = offset + page.length < assignments.length;
|
|
28250
30054
|
const truncated = hasMore || offset > 0;
|
|
28251
|
-
const disclosure = truncated ? `${
|
|
30055
|
+
const disclosure = truncated ? `${DISCLOSURE17} Returning assignments ${offset}\u2013${offset + page.length} of ${assignments.length} (page size ${limit}); summary.byTarget holds the COMPLETE per-target counts. Page through the remaining grants with offset/limit.` : DISCLOSURE17;
|
|
28252
30056
|
return ok({
|
|
28253
30057
|
data: {
|
|
28254
30058
|
profileId,
|
|
@@ -28276,7 +30080,7 @@ var init_what_if_split_profile = __esm({
|
|
|
28276
30080
|
});
|
|
28277
30081
|
|
|
28278
30082
|
// ../mcp/dist/src/tools/why-cant-user-see-record.js
|
|
28279
|
-
import { z as
|
|
30083
|
+
import { z as z114 } from "zod";
|
|
28280
30084
|
var OWD_VISIBLE, OWD_RESTRICTED, ALL_INTERNAL_USERS_GROUP_ID, ROLE_HIERARCHY_MAX_DEPTH, whyCantUserSeeRecordInputSchema, step2, evaluateOWD, grantsReadOrBetter, evaluatePermissionGrants, walkRoleHierarchy, evaluateRoleHierarchy, buildUserMembership, fetchSharingRules, ownerRuleMatches, evaluateOwnerSharingRules, evaluateCriteriaSharingRules, listObjectChildRules, evaluateRestrictionRules, evaluateScopingRules, evaluatePermissionSetGroups, UNKNOWN_TAIL, aggregateVerdict8, PROFILE_PREFIX3, PERMISSION_SET_PREFIX2, ROLE_PREFIX, GROUP_PREFIX, QUEUE_PREFIX, coerceGroupId, coerceUserContext, whyCantUserSeeRecordHandler;
|
|
28281
30085
|
var init_why_cant_user_see_record = __esm({
|
|
28282
30086
|
"../mcp/dist/src/tools/why-cant-user-see-record.js"() {
|
|
@@ -28297,13 +30101,13 @@ var init_why_cant_user_see_record = __esm({
|
|
|
28297
30101
|
]);
|
|
28298
30102
|
ALL_INTERNAL_USERS_GROUP_ID = "Group:AllInternalUsers";
|
|
28299
30103
|
ROLE_HIERARCHY_MAX_DEPTH = 100;
|
|
28300
|
-
whyCantUserSeeRecordInputSchema =
|
|
28301
|
-
componentId:
|
|
28302
|
-
userContext:
|
|
28303
|
-
profileId:
|
|
28304
|
-
permissionSetIds:
|
|
28305
|
-
roleId:
|
|
28306
|
-
groupIds:
|
|
30104
|
+
whyCantUserSeeRecordInputSchema = z114.object({
|
|
30105
|
+
componentId: z114.string().min(1),
|
|
30106
|
+
userContext: z114.object({
|
|
30107
|
+
profileId: z114.string().min(1).optional(),
|
|
30108
|
+
permissionSetIds: z114.array(z114.string().min(1)).optional(),
|
|
30109
|
+
roleId: z114.string().min(1).optional(),
|
|
30110
|
+
groupIds: z114.array(z114.string().min(1)).optional()
|
|
28307
30111
|
}).refine((uc) => uc.profileId !== void 0 || uc.permissionSetIds !== void 0 && uc.permissionSetIds.length > 0 || uc.roleId !== void 0 || uc.groupIds !== void 0 && uc.groupIds.length > 0, {
|
|
28308
30112
|
message: "userContext must supply at least one of: profileId, permissionSetIds, roleId, groupIds"
|
|
28309
30113
|
})
|
|
@@ -28673,17 +30477,17 @@ var init_why_cant_user_see_record = __esm({
|
|
|
28673
30477
|
});
|
|
28674
30478
|
|
|
28675
30479
|
// ../mcp/dist/src/tools/why-field-changed.js
|
|
28676
|
-
import { z as
|
|
28677
|
-
var
|
|
30480
|
+
import { z as z115 } from "zod";
|
|
30481
|
+
var CUSTOM_FIELD_PREFIX14, DISCLOSURE18, whyFieldChangedInputSchema, surfaceFirstCondition3, surfaceTriggerEvent, buildWriter, whyFieldChangedHandler;
|
|
28678
30482
|
var init_why_field_changed = __esm({
|
|
28679
30483
|
"../mcp/dist/src/tools/why-field-changed.js"() {
|
|
28680
30484
|
"use strict";
|
|
28681
30485
|
init_dist();
|
|
28682
30486
|
init_src();
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
whyFieldChangedInputSchema =
|
|
28686
|
-
fieldId:
|
|
30487
|
+
CUSTOM_FIELD_PREFIX14 = "CustomField:";
|
|
30488
|
+
DISCLOSURE18 = "v2.0e composes the documented Salesforce order-of-execution instantiated against THIS org's extracted automation. Conditions ARE listed but NOT EVALUATED \u2014 the tool does not know whether this particular record satisfies them at runtime. Manual sharing, sharing sets, account teams, and Apex callouts after save are out of scope.";
|
|
30489
|
+
whyFieldChangedInputSchema = z115.object({
|
|
30490
|
+
fieldId: z115.string().min(1)
|
|
28687
30491
|
});
|
|
28688
30492
|
surfaceFirstCondition3 = async (ctx, writerId) => {
|
|
28689
30493
|
const edgesResult = await listEdges(ctx.graph, writerId, {
|
|
@@ -28733,10 +30537,10 @@ var init_why_field_changed = __esm({
|
|
|
28733
30537
|
return ok(triggerEvent === void 0 ? withCondition : { ...withCondition, triggerEvent });
|
|
28734
30538
|
};
|
|
28735
30539
|
whyFieldChangedHandler = async (ctx, input2) => {
|
|
28736
|
-
if (!input2.fieldId.startsWith(
|
|
30540
|
+
if (!input2.fieldId.startsWith(CUSTOM_FIELD_PREFIX14)) {
|
|
28737
30541
|
return err({
|
|
28738
30542
|
kind: "invalid-query",
|
|
28739
|
-
message: `fieldId must start with '${
|
|
30543
|
+
message: `fieldId must start with '${CUSTOM_FIELD_PREFIX14}'; got '${input2.fieldId}'`,
|
|
28740
30544
|
path: "fieldId"
|
|
28741
30545
|
});
|
|
28742
30546
|
}
|
|
@@ -28796,7 +30600,7 @@ var init_why_field_changed = __esm({
|
|
|
28796
30600
|
fieldId,
|
|
28797
30601
|
writers: sortedWriters,
|
|
28798
30602
|
summary: { declaredCount, heuristicCount },
|
|
28799
|
-
disclosure:
|
|
30603
|
+
disclosure: DISCLOSURE18
|
|
28800
30604
|
},
|
|
28801
30605
|
vaultState: {
|
|
28802
30606
|
sourceTreeHash: ctx.manifest.sourceTreeHash,
|
|
@@ -28817,7 +30621,7 @@ __export(tools_exports, {
|
|
|
28817
30621
|
registerTools: () => registerTools
|
|
28818
30622
|
});
|
|
28819
30623
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
28820
|
-
var SEARCH_COMPONENTS_INPUT_SCHEMA, CAPABILITIES_INPUT_SCHEMA, ORG_PULSE_INPUT_SCHEMA, FLEET_FIND_INPUT_SCHEMA, RESOLVE_INPUT_SCHEMA, GET_COMPONENT_INPUT_SCHEMA, GET_EDGES_INPUT_SCHEMA, LIST_COMPONENTS_INPUT_SCHEMA, GET_SUBGRAPH_INPUT_SCHEMA, SEARCH_APEX_SOURCE_INPUT_SCHEMA, SEARCH_FLOW_METADATA_INPUT_SCHEMA, NAMING_CONVENTION_REPORT_INPUT_SCHEMA, GET_MANIFEST_INPUT_SCHEMA, COVERAGE_REPORT_INPUT_SCHEMA, BASELINE_ACKNOWLEDGE_INPUT_SCHEMA, BASELINE_STATUS_INPUT_SCHEMA, TREND_INPUT_SCHEMA, CHURN_INPUT_SCHEMA, LIVE_ENABLED_PROPERTY, LIVE_DESCRIBE_INPUT_SCHEMA, LIVE_COUNT_INPUT_SCHEMA, LIVE_SAMPLE_INPUT_SCHEMA, LIVE_FIELD_POPULATION_INPUT_SCHEMA, LIVE_ORG_LIMITS_INPUT_SCHEMA, LIVE_INACTIVE_USERS_INPUT_SCHEMA, LIVE_LICENSE_USAGE_INPUT_SCHEMA, LIVE_GROUP_COUNT_INPUT_SCHEMA, LIVE_STALE_RECORDS_INPUT_SCHEMA, LIVE_RECENT_ACTIVITY_INPUT_SCHEMA, LIVE_AGGREGATE_INPUT_SCHEMA, LIVE_DUPLICATE_CHECK_INPUT_SCHEMA, LIVE_OWNER_BREAKDOWN_INPUT_SCHEMA, LIVE_STORAGE_BY_OBJECT_INPUT_SCHEMA, LIVE_REPORT_USAGE_INPUT_SCHEMA, LIVE_FOLDER_ACCESS_INPUT_SCHEMA, LIVE_EMAIL_TEMPLATE_USAGE_INPUT_SCHEMA, LIVE_ORG_HEALTH_INPUT_SCHEMA, LIVE_CONSENT_INPUT_SCHEMA, ROUTE_QUESTION_INPUT_SCHEMA, SYNTHESIS_INPUT_SCHEMA, HEALTH_CHECK_INPUT_SCHEMA, GET_IMPACT_INPUT_SCHEMA, FIND_FORMULA_REFERENCES_INPUT_SCHEMA, FIND_APEX_USAGES_INPUT_SCHEMA, FIND_CODE_USAGES_INPUT_SCHEMA, WHY_CANT_USER_SEE_RECORD_INPUT_SCHEMA, LAYOUT_FOR_USER_INPUT_SCHEMA, INTEGRATION_MAP_INPUT_SCHEMA, EVENT_SUBSCRIBERS_INPUT_SCHEMA, LOOKUP_RECORD_INPUT_SCHEMA, EXPLAIN_FIELD_INPUT_SCHEMA, SAFE_TO_DELETE_FIELD_INPUT_SCHEMA, UNUSED_COMPONENTS_INPUT_SCHEMA, DIFF_SNAPSHOTS_INPUT_SCHEMA, COMPARE_COMPONENTS_INPUT_SCHEMA, PII_INVENTORY_INPUT_SCHEMA, FIELD_ACCESS_AUDIT_INPUT_SCHEMA, FIELD_360_INPUT_SCHEMA, FIELD_LINEAGE_INPUT_SCHEMA, ORG_OVERVIEW_INPUT_SCHEMA, DOMAIN_CLUSTERS_INPUT_SCHEMA, CHANGED_SINCE_INPUT_SCHEMA, LAST_MODIFIED_INPUT_SCHEMA, WHAT_HAPPENS_ON_SAVE_INPUT_SCHEMA, WHY_FIELD_CHANGED_INPUT_SCHEMA, ORDER_OF_EXECUTION_INPUT_SCHEMA, EXPLAIN_FLOW_INPUT_SCHEMA, EXPLAIN_APEX_METHOD_INPUT_SCHEMA, EXPLAIN_FORMULA_INPUT_SCHEMA, UNUSED_FIELDS_DEEP_INPUT_SCHEMA, PROCESS_BUILDER_MIGRATION_CANDIDATES_INPUT_SCHEMA, UNASSIGNED_PERMISSION_SETS_INPUT_SCHEMA, EMPTY_QUEUES_AND_GROUPS_INPUT_SCHEMA, TECH_DEBT_SCORE_INPUT_SCHEMA, CODE_QUALITY_AUDIT_INPUT_SCHEMA, GOVERNOR_LIMIT_RISKS_INPUT_SCHEMA, FIND_HARDCODED_VALUES_INPUT_SCHEMA, CRUD_FLS_AUDIT_INPUT_SCHEMA, TEST_COVERAGE_GAPS_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_TYPE_INPUT_SCHEMA, WHAT_IF_REMOVE_PICKLIST_VALUE_INPUT_SCHEMA, WHAT_IF_MAKE_FIELD_REQUIRED_INPUT_SCHEMA, WHAT_IF_DEACTIVATE_FLOW_INPUT_SCHEMA, WHAT_IF_DISABLE_TRIGGER_INPUT_SCHEMA, WHAT_IF_CHANGE_METHOD_SIGNATURE_INPUT_SCHEMA, WHAT_IF_MERGE_PROFILES_INPUT_SCHEMA, WHAT_IF_SPLIT_PROFILE_INPUT_SCHEMA, GENERATE_DATA_DICTIONARY_INPUT_SCHEMA, GENERATE_ADMIN_HANDBOOK_INPUT_SCHEMA, GENERATE_ARCHITECTURE_OVERVIEW_INPUT_SCHEMA, GENERATE_SHARING_SUMMARY_INPUT_SCHEMA, GENERATE_COMPLIANCE_REPORT_INPUT_SCHEMA, GENERATE_ONBOARDING_DOC_INPUT_SCHEMA, CALL_GRAPH_INPUT_SCHEMA, DOWNSTREAM_EFFECTS_INPUT_SCHEMA, TEST_COVERAGE_FOR_METHOD_INPUT_SCHEMA, MEANINGFUL_TEST_AUDIT_INPUT_SCHEMA, METHOD_REACHABILITY_INPUT_SCHEMA, TESTS_FOR_CHANGE_INPUT_SCHEMA, PACKAGE_IMPACT_INPUT_SCHEMA, CDC_SUBSCRIBERS_INPUT_SCHEMA, ASYNC_CHAIN_DEPTH_INPUT_SCHEMA, SCHEDULED_JOB_CATALOG_INPUT_SCHEMA, OUTBOUND_MESSAGE_CATALOG_INPUT_SCHEMA, ENDPOINT_CATALOG_INPUT_SCHEMA, FIELD_MEANING_INPUT_SCHEMA, DISAMBIGUATE_CONCEPTS_INPUT_SCHEMA, FIELD_PROVENANCE_INPUT_SCHEMA, FIND_FIELD_ANYWHERE_INPUT_SCHEMA, FIND_SEMANTIC_FIELD_INPUT_SCHEMA, FIND_HARDCODED_VALUES_ANYWHERE_INPUT_SCHEMA, FIND_CLONE_PATTERNS_INPUT_SCHEMA, FIND_DEAD_CODE_INPUT_SCHEMA, CPQ_RULE_CHAIN_INPUT_SCHEMA, CPQ_QUOTE_TEMPLATE_BREAKDOWN_INPUT_SCHEMA, CPQ_DEPENDENCY_MAP_INPUT_SCHEMA, COMPARE_VAULTS_INPUT_SCHEMA, COMPARE_OBJECT_ACROSS_VAULTS_INPUT_SCHEMA, COMPARE_PROFILE_ACROSS_VAULTS_INPUT_SCHEMA, FIELD_MAPPING_BETWEEN_OBJECTS_INPUT_SCHEMA, INTEGRATION_PROCEDURE_CHAIN_INPUT_SCHEMA, OMNISCRIPT_FLOW_INPUT_SCHEMA, OMNIUICARD_WIDGET_BREAKDOWN_INPUT_SCHEMA, DATATRANSFORM_FIELD_MAP_INPUT_SCHEMA, DECISION_TABLE_BROWSE_INPUT_SCHEMA, FIND_DEPENDENCY_CYCLES_INPUT_SCHEMA, APEX_TEST_COVERAGE_INPUT_SCHEMA, AUTOMATION_BUILD_ADVISOR_INPUT_SCHEMA, APEX_BUILD_ADVISOR_INPUT_SCHEMA, FIELD_CHANGE_ADVISOR_INPUT_SCHEMA, LIVE_DRIFT_CHECK_INPUT_SCHEMA, ORG_HISTORY_INPUT_SCHEMA, V01_TOOLS, KNOWN_TOOL_NAMES, dispatchTool, runTool, registerTools, MAX_RESPONSE_BYTES, jsonResult;
|
|
30624
|
+
var SEARCH_COMPONENTS_INPUT_SCHEMA, CAPABILITIES_INPUT_SCHEMA, SYNTHESIZE_ANSWER_INPUT_SCHEMA, ORG_PULSE_INPUT_SCHEMA, FLEET_FIND_INPUT_SCHEMA, RESOLVE_INPUT_SCHEMA, GET_COMPONENT_INPUT_SCHEMA, GET_EDGES_INPUT_SCHEMA, LIST_COMPONENTS_INPUT_SCHEMA, GET_SUBGRAPH_INPUT_SCHEMA, SEARCH_APEX_SOURCE_INPUT_SCHEMA, SEARCH_FLOW_METADATA_INPUT_SCHEMA, NAMING_CONVENTION_REPORT_INPUT_SCHEMA, GET_MANIFEST_INPUT_SCHEMA, COVERAGE_REPORT_INPUT_SCHEMA, BASELINE_ACKNOWLEDGE_INPUT_SCHEMA, BASELINE_STATUS_INPUT_SCHEMA, TREND_INPUT_SCHEMA, CHURN_INPUT_SCHEMA, LIVE_ENABLED_PROPERTY, LIVE_DESCRIBE_INPUT_SCHEMA, LIVE_COUNT_INPUT_SCHEMA, LIVE_SAMPLE_INPUT_SCHEMA, LIVE_FIELD_POPULATION_INPUT_SCHEMA, LIVE_ORG_LIMITS_INPUT_SCHEMA, LIVE_INACTIVE_USERS_INPUT_SCHEMA, LIVE_LICENSE_USAGE_INPUT_SCHEMA, LIVE_GROUP_COUNT_INPUT_SCHEMA, LIVE_STALE_RECORDS_INPUT_SCHEMA, LIVE_RECENT_ACTIVITY_INPUT_SCHEMA, LIVE_AGGREGATE_INPUT_SCHEMA, LIVE_DUPLICATE_CHECK_INPUT_SCHEMA, LIVE_OWNER_BREAKDOWN_INPUT_SCHEMA, LIVE_STORAGE_BY_OBJECT_INPUT_SCHEMA, LIVE_REPORT_USAGE_INPUT_SCHEMA, LIVE_FOLDER_ACCESS_INPUT_SCHEMA, LIVE_EMAIL_TEMPLATE_USAGE_INPUT_SCHEMA, LIVE_ORG_HEALTH_INPUT_SCHEMA, LIVE_CONSENT_INPUT_SCHEMA, ROUTE_QUESTION_INPUT_SCHEMA, SYNTHESIS_INPUT_SCHEMA, HEALTH_CHECK_INPUT_SCHEMA, GET_IMPACT_INPUT_SCHEMA, FIND_FORMULA_REFERENCES_INPUT_SCHEMA, FIND_APEX_USAGES_INPUT_SCHEMA, FIND_CODE_USAGES_INPUT_SCHEMA, WHY_CANT_USER_SEE_RECORD_INPUT_SCHEMA, LAYOUT_FOR_USER_INPUT_SCHEMA, INTEGRATION_MAP_INPUT_SCHEMA, EVENT_SUBSCRIBERS_INPUT_SCHEMA, LOOKUP_RECORD_INPUT_SCHEMA, GUIDANCE_INPUT_SCHEMA, EXPLAIN_FIELD_INPUT_SCHEMA, SAFE_TO_DELETE_FIELD_INPUT_SCHEMA, UNUSED_COMPONENTS_INPUT_SCHEMA, DIFF_SNAPSHOTS_INPUT_SCHEMA, COMPARE_COMPONENTS_INPUT_SCHEMA, PII_INVENTORY_INPUT_SCHEMA, FIELD_ACCESS_AUDIT_INPUT_SCHEMA, FIELD_360_INPUT_SCHEMA, FIELD_LINEAGE_INPUT_SCHEMA, ORG_OVERVIEW_INPUT_SCHEMA, DOMAIN_CLUSTERS_INPUT_SCHEMA, CHANGED_SINCE_INPUT_SCHEMA, LAST_MODIFIED_INPUT_SCHEMA, WHAT_HAPPENS_ON_SAVE_INPUT_SCHEMA, WHY_FIELD_CHANGED_INPUT_SCHEMA, ORDER_OF_EXECUTION_INPUT_SCHEMA, EXPLAIN_FLOW_INPUT_SCHEMA, EXPLAIN_APEX_METHOD_INPUT_SCHEMA, EXPLAIN_FORMULA_INPUT_SCHEMA, UNUSED_FIELDS_DEEP_INPUT_SCHEMA, PROCESS_BUILDER_MIGRATION_CANDIDATES_INPUT_SCHEMA, UNASSIGNED_PERMISSION_SETS_INPUT_SCHEMA, EMPTY_QUEUES_AND_GROUPS_INPUT_SCHEMA, TECH_DEBT_SCORE_INPUT_SCHEMA, CODE_QUALITY_AUDIT_INPUT_SCHEMA, GOVERNOR_LIMIT_RISKS_INPUT_SCHEMA, FIND_HARDCODED_VALUES_INPUT_SCHEMA, CRUD_FLS_AUDIT_INPUT_SCHEMA, TEST_COVERAGE_GAPS_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_VALUE_INPUT_SCHEMA, VALUE_CHANGE_AUDIT_INPUT_SCHEMA, WHAT_IF_CHANGE_FIELD_TYPE_INPUT_SCHEMA, WHAT_IF_REMOVE_PICKLIST_VALUE_INPUT_SCHEMA, WHAT_IF_MAKE_FIELD_REQUIRED_INPUT_SCHEMA, WHAT_IF_DEACTIVATE_FLOW_INPUT_SCHEMA, WHAT_IF_DISABLE_TRIGGER_INPUT_SCHEMA, WHAT_IF_CHANGE_METHOD_SIGNATURE_INPUT_SCHEMA, WHAT_IF_MERGE_PROFILES_INPUT_SCHEMA, WHAT_IF_SPLIT_PROFILE_INPUT_SCHEMA, GENERATE_DATA_DICTIONARY_INPUT_SCHEMA, GENERATE_ADMIN_HANDBOOK_INPUT_SCHEMA, GENERATE_ARCHITECTURE_OVERVIEW_INPUT_SCHEMA, GENERATE_SHARING_SUMMARY_INPUT_SCHEMA, GENERATE_COMPLIANCE_REPORT_INPUT_SCHEMA, GENERATE_ONBOARDING_DOC_INPUT_SCHEMA, CALL_GRAPH_INPUT_SCHEMA, DOWNSTREAM_EFFECTS_INPUT_SCHEMA, TEST_COVERAGE_FOR_METHOD_INPUT_SCHEMA, MEANINGFUL_TEST_AUDIT_INPUT_SCHEMA, METHOD_REACHABILITY_INPUT_SCHEMA, TESTS_FOR_CHANGE_INPUT_SCHEMA, PACKAGE_IMPACT_INPUT_SCHEMA, CDC_SUBSCRIBERS_INPUT_SCHEMA, ASYNC_CHAIN_DEPTH_INPUT_SCHEMA, SCHEDULED_JOB_CATALOG_INPUT_SCHEMA, OUTBOUND_MESSAGE_CATALOG_INPUT_SCHEMA, ENDPOINT_CATALOG_INPUT_SCHEMA, FIELD_MEANING_INPUT_SCHEMA, DISAMBIGUATE_CONCEPTS_INPUT_SCHEMA, FIELD_PROVENANCE_INPUT_SCHEMA, FIND_FIELD_ANYWHERE_INPUT_SCHEMA, FIND_SEMANTIC_FIELD_INPUT_SCHEMA, FIND_HARDCODED_VALUES_ANYWHERE_INPUT_SCHEMA, FIND_CLONE_PATTERNS_INPUT_SCHEMA, FIND_DEAD_CODE_INPUT_SCHEMA, CPQ_RULE_CHAIN_INPUT_SCHEMA, CPQ_QUOTE_TEMPLATE_BREAKDOWN_INPUT_SCHEMA, CPQ_DEPENDENCY_MAP_INPUT_SCHEMA, COMPARE_VAULTS_INPUT_SCHEMA, COMPARE_OBJECT_ACROSS_VAULTS_INPUT_SCHEMA, COMPARE_PROFILE_ACROSS_VAULTS_INPUT_SCHEMA, FIELD_MAPPING_BETWEEN_OBJECTS_INPUT_SCHEMA, INTEGRATION_PROCEDURE_CHAIN_INPUT_SCHEMA, OMNISCRIPT_FLOW_INPUT_SCHEMA, OMNIUICARD_WIDGET_BREAKDOWN_INPUT_SCHEMA, DATATRANSFORM_FIELD_MAP_INPUT_SCHEMA, DECISION_TABLE_BROWSE_INPUT_SCHEMA, FIND_DEPENDENCY_CYCLES_INPUT_SCHEMA, APEX_TEST_COVERAGE_INPUT_SCHEMA, AUTOMATION_BUILD_ADVISOR_INPUT_SCHEMA, APEX_BUILD_ADVISOR_INPUT_SCHEMA, FIELD_CHANGE_ADVISOR_INPUT_SCHEMA, LIVE_DRIFT_CHECK_INPUT_SCHEMA, ORG_HISTORY_INPUT_SCHEMA, V01_TOOLS, KNOWN_TOOL_NAMES, dispatchTool, runTool, registerTools, MAX_RESPONSE_BYTES, jsonResult;
|
|
28821
30625
|
var init_tools = __esm({
|
|
28822
30626
|
"../mcp/dist/src/tools/index.js"() {
|
|
28823
30627
|
"use strict";
|
|
@@ -28884,6 +30688,7 @@ var init_tools = __esm({
|
|
|
28884
30688
|
init_get_impact();
|
|
28885
30689
|
init_get_subgraph();
|
|
28886
30690
|
init_governor_limit_risks();
|
|
30691
|
+
init_guidance();
|
|
28887
30692
|
init_health_check();
|
|
28888
30693
|
init_integration_map();
|
|
28889
30694
|
init_integration_procedure_chain();
|
|
@@ -28916,6 +30721,7 @@ var init_tools = __esm({
|
|
|
28916
30721
|
init_search_flow_metadata();
|
|
28917
30722
|
init_snapshot_trend();
|
|
28918
30723
|
init_synthesis_reports();
|
|
30724
|
+
init_synthesize_answer();
|
|
28919
30725
|
init_tech_debt_score();
|
|
28920
30726
|
init_test_coverage_for_method();
|
|
28921
30727
|
init_test_coverage_gaps();
|
|
@@ -28923,8 +30729,10 @@ var init_tools = __esm({
|
|
|
28923
30729
|
init_unassigned_permission_sets();
|
|
28924
30730
|
init_unused_components();
|
|
28925
30731
|
init_unused_fields_deep();
|
|
30732
|
+
init_value_change_audit();
|
|
28926
30733
|
init_what_happens_on_save();
|
|
28927
30734
|
init_what_if_change_field_type();
|
|
30735
|
+
init_what_if_change_field_value();
|
|
28928
30736
|
init_what_if_change_method_signature();
|
|
28929
30737
|
init_what_if_deactivate_flow();
|
|
28930
30738
|
init_what_if_disable_trigger();
|
|
@@ -28947,6 +30755,14 @@ var init_tools = __esm({
|
|
|
28947
30755
|
type: "object",
|
|
28948
30756
|
properties: {}
|
|
28949
30757
|
});
|
|
30758
|
+
SYNTHESIZE_ANSWER_INPUT_SCHEMA = Object.freeze({
|
|
30759
|
+
type: "object",
|
|
30760
|
+
properties: {
|
|
30761
|
+
input: {},
|
|
30762
|
+
question: { type: "string" },
|
|
30763
|
+
draft: { type: "string" }
|
|
30764
|
+
}
|
|
30765
|
+
});
|
|
28950
30766
|
ORG_PULSE_INPUT_SCHEMA = Object.freeze({
|
|
28951
30767
|
type: "object",
|
|
28952
30768
|
properties: {
|
|
@@ -29467,8 +31283,7 @@ var init_tools = __esm({
|
|
|
29467
31283
|
properties: {
|
|
29468
31284
|
eventId: { type: "string", minLength: 1 },
|
|
29469
31285
|
limit: { type: "integer", minimum: 1, maximum: 500 }
|
|
29470
|
-
}
|
|
29471
|
-
required: ["eventId"]
|
|
31286
|
+
}
|
|
29472
31287
|
});
|
|
29473
31288
|
LOOKUP_RECORD_INPUT_SCHEMA = Object.freeze({
|
|
29474
31289
|
type: "object",
|
|
@@ -29477,6 +31292,12 @@ var init_tools = __esm({
|
|
|
29477
31292
|
},
|
|
29478
31293
|
required: ["recordId"]
|
|
29479
31294
|
});
|
|
31295
|
+
GUIDANCE_INPUT_SCHEMA = Object.freeze({
|
|
31296
|
+
type: "object",
|
|
31297
|
+
properties: {
|
|
31298
|
+
topic: { type: "string", minLength: 1 }
|
|
31299
|
+
}
|
|
31300
|
+
});
|
|
29480
31301
|
EXPLAIN_FIELD_INPUT_SCHEMA = Object.freeze({
|
|
29481
31302
|
type: "object",
|
|
29482
31303
|
properties: {
|
|
@@ -29584,7 +31405,8 @@ var init_tools = __esm({
|
|
|
29584
31405
|
type: "string",
|
|
29585
31406
|
enum: ["identifier", "contact", "financial", "health", "all"]
|
|
29586
31407
|
},
|
|
29587
|
-
limit: { type: "integer", minimum: 1, maximum: 500 }
|
|
31408
|
+
limit: { type: "integer", minimum: 1, maximum: 500 },
|
|
31409
|
+
offset: { type: "integer", minimum: 0 }
|
|
29588
31410
|
}
|
|
29589
31411
|
});
|
|
29590
31412
|
FIELD_ACCESS_AUDIT_INPUT_SCHEMA = Object.freeze({
|
|
@@ -29874,7 +31696,8 @@ var init_tools = __esm({
|
|
|
29874
31696
|
CRUD_FLS_AUDIT_INPUT_SCHEMA = Object.freeze({
|
|
29875
31697
|
type: "object",
|
|
29876
31698
|
properties: {
|
|
29877
|
-
limit: { type: "integer", minimum: 1, maximum: 500 }
|
|
31699
|
+
limit: { type: "integer", minimum: 1, maximum: 500 },
|
|
31700
|
+
offset: { type: "integer", minimum: 0 }
|
|
29878
31701
|
}
|
|
29879
31702
|
});
|
|
29880
31703
|
TEST_COVERAGE_GAPS_INPUT_SCHEMA = Object.freeze({
|
|
@@ -29884,9 +31707,30 @@ var init_tools = __esm({
|
|
|
29884
31707
|
type: "array",
|
|
29885
31708
|
items: { type: "string", minLength: 1 },
|
|
29886
31709
|
maxItems: 500
|
|
29887
|
-
}
|
|
31710
|
+
},
|
|
31711
|
+
limit: { type: "integer", minimum: 1, maximum: 500 },
|
|
31712
|
+
offset: { type: "integer", minimum: 0 }
|
|
29888
31713
|
}
|
|
29889
31714
|
});
|
|
31715
|
+
WHAT_IF_CHANGE_FIELD_VALUE_INPUT_SCHEMA = Object.freeze({
|
|
31716
|
+
type: "object",
|
|
31717
|
+
properties: {
|
|
31718
|
+
fieldId: { type: "string", minLength: 1 },
|
|
31719
|
+
newValue: { type: "string" }
|
|
31720
|
+
},
|
|
31721
|
+
required: ["fieldId"],
|
|
31722
|
+
additionalProperties: false
|
|
31723
|
+
});
|
|
31724
|
+
VALUE_CHANGE_AUDIT_INPUT_SCHEMA = Object.freeze({
|
|
31725
|
+
type: "object",
|
|
31726
|
+
properties: {
|
|
31727
|
+
object: { type: "string", minLength: 1 },
|
|
31728
|
+
fields: { type: "array", items: { type: "string" } },
|
|
31729
|
+
verbosity: { type: "string", enum: ["summary", "detail"] }
|
|
31730
|
+
},
|
|
31731
|
+
required: ["object"],
|
|
31732
|
+
additionalProperties: false
|
|
31733
|
+
});
|
|
29890
31734
|
WHAT_IF_CHANGE_FIELD_TYPE_INPUT_SCHEMA = Object.freeze({
|
|
29891
31735
|
type: "object",
|
|
29892
31736
|
properties: {
|
|
@@ -30369,9 +32213,14 @@ var init_tools = __esm({
|
|
|
30369
32213
|
description: 'Product self-description: what this knowledge base can answer. Returns a categorized capability map (with example natural-language questions per area), the live registered-tool count, the recommended conversational pattern (call sfi.resolve first; ask a clarifying question on ambiguous; offer /sfi-refresh or stop on none), the three slash commands, and the v0.1 read-only/offline boundary. Takes no arguments. Call when the user asks "what can you do / what can I ask?" or to orient a fresh session.',
|
|
30370
32214
|
inputSchema: CAPABILITIES_INPUT_SCHEMA
|
|
30371
32215
|
},
|
|
32216
|
+
{
|
|
32217
|
+
name: "sfi.synthesize_answer",
|
|
32218
|
+
description: "Answer-layer grounding pass: turns the JSON returned by prior sfi.* tool call(s) into a structured, citation-grounded answer skeleton \u2014 `summary`, `bullets` (headline facts extracted from the input), `citations` (ONLY canonical ids present in the input, parsed to type + apiName), and `caveats` (honesty/limitation strings carried verbatim). Pass the source tool output as `input` (any JSON), optionally the user `question` (echoed into the summary), and optionally a `draft` narrative \u2014 when given, `hallucinatedIds` lists canonical ids in the draft that do NOT appear in the source so they can be removed before answering. Pure transform: reads ONLY `input`, never the graph or live org, so it can never add a fact it was not handed. Prose wording stays with the caller; this guarantees grounding, not sentences.",
|
|
32219
|
+
inputSchema: SYNTHESIZE_ANSWER_INPUT_SCHEMA
|
|
32220
|
+
},
|
|
30372
32221
|
{
|
|
30373
32222
|
name: "sfi.route_question",
|
|
30374
|
-
description: "Front-door router: map a plain-language question to the plane that answers it (vault | live | hybrid | unknown) and the ordered sfi.* tools to call \u2014 so the user never types a tool name. Read-only; it suggests a route, it does not answer. Tells you when to sfi.resolve a named component first, whether the opt-in live plane is required, and \u2014 when the question hits a capability we lack \u2014 returns an honest 'unknown'/gap and logs it for the backlog instead of fabricating. Call this first on a vague/broad question to decide which tool(s) to run.",
|
|
32223
|
+
description: "Front-door router: map a plain-language question to the plane that answers it (vault | live | hybrid | unknown) and the ordered sfi.* tools to call \u2014 so the user never types a tool name. Read-only; it suggests a route, it does not answer. Tells you when to sfi.resolve a named component first, whether the opt-in live plane is required, surfaces `suggestedArgs` (heuristic per-intent hints \u2014 e.g. `event: 'update'` for a save-order question so you can call `what_happens_on_save` without guessing the DML event), and \u2014 when the question hits a capability we lack \u2014 returns an honest 'unknown'/gap and logs it for the backlog instead of fabricating. Call this first on a vague/broad question to decide which tool(s) to run.",
|
|
30375
32224
|
inputSchema: ROUTE_QUESTION_INPUT_SCHEMA
|
|
30376
32225
|
},
|
|
30377
32226
|
{
|
|
@@ -30556,7 +32405,7 @@ var init_tools = __esm({
|
|
|
30556
32405
|
},
|
|
30557
32406
|
{
|
|
30558
32407
|
name: "sfi.permission_risk_report",
|
|
30559
|
-
description: "Ranked permission
|
|
32408
|
+
description: "Ranked permission-risk report, leading with OVER-PRIVILEGE read straight from the extracted profile / permission-set metadata: every Profile or PermissionSet that grants a god-mode or administrative system permission (Modify All Data / View All Data = critical; Author Apex, Customize Application, Manage Users, Manage Profiles/PermSets, Modify Metadata, Manage Sharing, Manage Roles, password/login policies = high) OR object-level View All / Modify All, surfaced as ONE aggregated finding per grantor (severity = the worst signal; system perms + a per-grantor count of objects escalated). PermissionSetGroups are analysed too: a PSG's effective god-mode is aggregated from its MEMBER permission sets (so a user who gets Modify All Data via a group is caught), with the muting permission set noted but not subtracted (v1 honesty boundary). A `privilege` block rosters the `modifyAllDataGrantors` / `viewAllDataGrantors` (profiles, permission sets, AND groups) and the `overPrivilegedGrantorCount`. Also rolls in unassigned permission sets and CRUD/FLS audit totals. Answers 'who has god mode / Modify All / View All / who is an admin / who is over-permissioned'. Read-only, declared confidence (literal metadata flags, not heuristics); `limit` (default 50) caps the findings.",
|
|
30560
32409
|
inputSchema: SYNTHESIS_INPUT_SCHEMA
|
|
30561
32410
|
},
|
|
30562
32411
|
{
|
|
@@ -30596,9 +32445,14 @@ var init_tools = __esm({
|
|
|
30596
32445
|
},
|
|
30597
32446
|
{
|
|
30598
32447
|
name: "sfi.event_subscribers",
|
|
30599
|
-
description:
|
|
32448
|
+
description: 'Given a Platform Event id (`CustomObject:{ApiName}__e`), list every subscriber (ApexTrigger, ApexClass, Flow) that emits an incoming `listensTo` edge into the event. OMIT `eventId` for CATALOG mode: every Platform Event in the org with its subscriber count (`events[]`) \u2014 answers "what platform events does this org publish?" (then `subscribers` is `[]` and `eventApiName` is `null`). Single-event mode returns each subscriber\'s identity, the emitting extractor, and edge-level subscription metadata. Honest empty list when no subscribers exist; `invalid-query` when a supplied id is not a Platform Event canonical form.',
|
|
30600
32449
|
inputSchema: EVENT_SUBSCRIBERS_INPUT_SCHEMA
|
|
30601
32450
|
},
|
|
32451
|
+
{
|
|
32452
|
+
name: "sfi.guidance",
|
|
32453
|
+
description: "General Salesforce best-practice guidance \u2014 the `knowledge` plane for greenfield / New-Org questions that have NO org-specific answer (Flow vs Apex, order of execution, governor limits, async Apex, trigger frameworks, bulkification, Apex testing, callouts, SFDX, unlocked packages, profiles vs permission sets, OWD/sharing, standard vs custom objects, naming, sandboxes). With `topic` (a key like `flow-vs-apex`, or a phrase that loose-matches) it returns a curated summary plus links to official Salesforce docs; without `topic` it lists available topics. Explicitly NOT specific to this org (see `disclosure`) \u2014 it points to authoritative docs and never fabricates vault data.",
|
|
32454
|
+
inputSchema: GUIDANCE_INPUT_SCHEMA
|
|
32455
|
+
},
|
|
30602
32456
|
{
|
|
30603
32457
|
name: "sfi.find_code_usages",
|
|
30604
32458
|
description: "List the code source files (ApexClass, ApexTrigger, LightningComponentBundle, AuraDefinitionBundle, VisualforcePage, VisualforceComponent) that read, write, call, or reference a component. Strict superset of `sfi.find_apex_usages`: same Apex-source coverage plus the v1.4 frontend tier. Filters incoming `readsFrom`/`writesTo`/`callsApex`/`references` edges to those originating from one of the six code node types; optional `nodeTypes` narrows to a single producer (e.g., `['LightningComponentBundle']` for LWC-only). LWC apex-import callsApex edges are `declared`; LWC field reads, Aura field accesses, and VF field touches are `heuristic`; VF controller/extension references are `declared`.",
|
|
@@ -30616,7 +32470,7 @@ var init_tools = __esm({
|
|
|
30616
32470
|
},
|
|
30617
32471
|
{
|
|
30618
32472
|
name: "sfi.safe_to_delete_field",
|
|
30619
|
-
description: "Given a CustomField canonical id (`CustomField:{Object}.{Field}`), composes every incoming dependency edge into a confidence-weighted deletion verdict: `safe` (no incoming edges), `risky` (heuristic-confidence Apex/LWC references that need spot-checking), `blocking` (declared Flow/ValidationRule/Layout/formula dependencies the platform will refuse to drop),
|
|
32473
|
+
description: "Given a CustomField canonical id (`CustomField:{Object}.{Field}`), composes every incoming dependency edge into a confidence-weighted deletion verdict: `safe` (no incoming edges), `risky` (heuristic-confidence Apex/LWC references that need spot-checking), `blocking` (declared Flow/ValidationRule/Layout/formula dependencies the platform will refuse to drop), `unknown` (only unrecognised edges), or `review` (NOT proven safe \u2014 incomplete coverage, OR a standard / managed-package field with no node of its own but referenced by edges: it is reviewed from those edges with a not-modeled caveat instead of returning component-not-found; B12). Each category in the `reasoning` array carries its referrer count, up to 5 example referrers (full list via `sfi.get_impact`), and a per-category note explaining the honesty boundary. Does NOT consult the Tooling API for runtime dependency confirmation (deferred to v1.7+ `dependsOnFromApi` enrichment).",
|
|
30620
32474
|
inputSchema: SAFE_TO_DELETE_FIELD_INPUT_SCHEMA
|
|
30621
32475
|
},
|
|
30622
32476
|
{
|
|
@@ -30649,6 +32503,16 @@ var init_tools = __esm({
|
|
|
30649
32503
|
description: "Decision-support tool: before changing a field, see the whole blast radius in one briefing. Given `fieldId`, synthesises `makeRequired` (verdict + create-path impact count from what_if_make_field_required), `deletion` (verdict + blocking/risky dependency counts from safe_to_delete_field), and \u2014 when `newType` is given \u2014 `changeType` (compatibility + verdict + reference count from what_if_change_field_type), plus combined `recommendations`. Does NOT change anything (backend knowledge layer). Honesty axis: inherits the composed tools' boundaries \u2014 dataflow into Apex insert/update and dynamic/reflective field access are invisible, so verdicts mean 'investigate', not guarantees. `component-not-found` when the fieldId is not a CustomField in the vault.",
|
|
30650
32504
|
inputSchema: FIELD_CHANGE_ADVISOR_INPUT_SCHEMA
|
|
30651
32505
|
},
|
|
32506
|
+
{
|
|
32507
|
+
name: "sfi.what_if_change_field_value",
|
|
32508
|
+
description: "Value-change impact (Data Steward / Identity & Integration lens): given a CustomField `fieldId`, what breaks if its stored VALUE changes \u2014 NOT its schema (use what_if_change_field_type for type/required/delete). Returns impact buckets (identity / integration-key / uniqueness / automation / save-pipeline / display), an overall severity, honesty-surface disclosures, and recommended pre-change checks. Identity / key / uniqueness verdicts come from the field's own metadata (externalId / unique / idLookup, identity catalog) \u2014 so a value change is flagged even on a field with ZERO references (e.g. a SAML federation key). Derived fields (formula / roll-up / auto-number) return mutable:false and re-route to their source. Honesty axis: the vault cannot see external upsert systems, the IdP side of SSO, or dynamic / managed-package code; automation buckets surface declarative value-literal couplings (the value a rule compares this field to); Apex literal comparisons remain invisible. Optional `newValue` adds a targeted collision/acceptance check. `component-not-found` when the fieldId is not a CustomField in the vault.",
|
|
32509
|
+
inputSchema: WHAT_IF_CHANGE_FIELD_VALUE_INPUT_SCHEMA
|
|
32510
|
+
},
|
|
32511
|
+
{
|
|
32512
|
+
name: "sfi.value_change_audit",
|
|
32513
|
+
description: "Batch value-change audit (Data Steward lens): given an `object` and optionally a list of `fields`, risk-ranks the impact of changing each field's stored VALUE \u2014 the portfolio version of what_if_change_field_value. WITHOUT `fields`, auto-detects the value-sensitive fields on the object (upsert keys via externalId/unique/idLookup, identity-catalog fields, name-lexicon matches). Each row carries an overall severity, role, top impact reasons, confidence, and disclosure count; `verbosity:'detail'` inlines full buckets. Returns a severity summary + global disclosures; unknown explicit fields come back in `notFound`. This answers 'tell me if changing any of these has an impact on {object}'. Honesty axis: auto-detect can miss a value-sensitive field carrying none of those signals; per-row blast radius inherits what_if_change_field_value's boundaries (external upsert systems, IdP side of SSO, dynamic/managed-package code invisible).",
|
|
32514
|
+
inputSchema: VALUE_CHANGE_AUDIT_INPUT_SCHEMA
|
|
32515
|
+
},
|
|
30652
32516
|
{
|
|
30653
32517
|
name: "sfi.live_drift_check",
|
|
30654
32518
|
description: "Offline\u2194live contradiction detection (requires the opt-in live plane). For `objectApiName`, compares the fields the vault recorded at the last refresh against a LIVE read-only describe and reports `onlyInVault` (fields in the snapshot the live org no longer returns \u2014 deleted/renamed/permission-hidden since refresh; the high-signal STALE indicator), `onlyInLiveCustom` (custom fields added live since refresh, filtered to `__`-suffixed to avoid standard-field noise), `inSync`, and a plain-language `interpretation`. The only check that uses BOTH planes at once; never mutates the org. Honesty axis: the vault models extracted custom fields + standard object definitions (not standard fields), so onlyInVault is the trustworthy drift signal. Pass `liveEnabled:true` or set SFI_LIVE_PLANE_ENABLED.",
|
|
@@ -30681,12 +32545,12 @@ var init_tools = __esm({
|
|
|
30681
32545
|
},
|
|
30682
32546
|
{
|
|
30683
32547
|
name: "sfi.pii_inventory",
|
|
30684
|
-
description: "Enumerate every CustomField in the vault, classify each with the v2.0d `pii-detection` recognizer (which inspects API name, declared data type, and description text), then emit a structured inventory. Filter by `classification` (`'pii' | 'sensitive' | 'all'`) and/or `category` (`'identifier' | 'contact' | 'financial' | 'health' | 'all'`); both default to `'all'`. Each emitted field carries its classification, category, data type, description, and a plain-English `reason` naming the rule that fired. `summary` reports the full per-classification and per-category counts across the matched set; `truncated` flips true when
|
|
32548
|
+
description: "Enumerate every CustomField in the vault, classify each with the v2.0d `pii-detection` recognizer (which inspects API name, declared data type, and description text), then emit a structured inventory. Filter by `classification` (`'pii' | 'sensitive' | 'all'`) and/or `category` (`'identifier' | 'contact' | 'financial' | 'health' | 'all'`); both default to `'all'`. Each emitted field carries its classification, category, data type, description, and a plain-English `reason` naming the rule that fired. `summary` reports the full per-classification and per-category counts across the matched set. The response is paginated: `limit` (default 200, max 500) and `offset` (default 0) page through the inventory, and a per-response ~38 KB byte budget trims the `fields` slice further when a page would exceed it, so the result never trips the global ~45 KB MCP response limit; `truncated` flips true when more matching fields remain, with `nextOffset` carrying the cursor to advance (plus a `note` when a page was byte-trimmed). The recognizer is heuristic \u2014 a field with no name-token match and no description signal classifies as `public` even if it stores PII at runtime; `EncryptedText`-typed fields ALWAYS classify as `sensitive` because the encryption type IS the declaration.",
|
|
30685
32549
|
inputSchema: PII_INVENTORY_INPUT_SCHEMA
|
|
30686
32550
|
},
|
|
30687
32551
|
{
|
|
30688
32552
|
name: "sfi.field_access_audit",
|
|
30689
|
-
description: "Given a CustomField canonical id (`CustomField:{Object}.{Field}`), cross-walk every Profile and PermissionSet that grants access to the field via incoming `grantedBy` edges. `grants` carries one entry per (Profile or PermissionSet, permission level) pair where permission is `'read'` / `'edit'` / `'unknown'` (the last meaning the older extractor did not populate the per-flag axis). `summary` reports the unfiltered counts split four ways (profilesWithRead, profilesWithEdit, permSetsWithRead, permSetsWithEdit). `viaApexAccess` enumerates ApexClass / ApexTrigger nodes with incoming `readsFrom` / `writesTo` edges to the field \u2014 a user with execute permission on one of those classes may access the field through that code path even when the metadata-grant audit reports no direct grant. Optional `permissionType` (`'read' | 'edit' | 'all'`, default `'all'`) narrows the emitted `grants` array. Honesty axis: this is the v2.0d.0 permission-grant-level audit; criteria-based and account-team sharing rules are deferred to v2.0d.1. Invalid prefix surfaces as `invalid-query
|
|
32553
|
+
description: "Given a CustomField canonical id (`CustomField:{Object}.{Field}`), cross-walk every Profile and PermissionSet that grants access to the field via incoming `grantedBy` edges. `grants` carries one entry per (Profile or PermissionSet, permission level) pair where permission is `'read'` / `'edit'` / `'unknown'` (the last meaning the older extractor did not populate the per-flag axis). `summary` reports the unfiltered counts split four ways (profilesWithRead, profilesWithEdit, permSetsWithRead, permSetsWithEdit). `viaApexAccess` enumerates ApexClass / ApexTrigger nodes with incoming `readsFrom` / `writesTo` edges to the field \u2014 a user with execute permission on one of those classes may access the field through that code path even when the metadata-grant audit reports no direct grant. Optional `permissionType` (`'read' | 'edit' | 'all'`, default `'all'`) narrows the emitted `grants` array. A standard or managed-package field with no node of its own but referenced by fieldPermissions / Apex edges is still audited from those edges with `notModeled: true` + a `notModeledNote` (grants are accurate; data type / formula are unavailable and PII is inferred from the field name) \u2014 only an id with no node AND no inbound references is `component-not-found` (B12). Honesty axis: this is the v2.0d.0 permission-grant-level audit; criteria-based and account-team sharing rules are deferred to v2.0d.1. Invalid prefix surfaces as `invalid-query`.",
|
|
30690
32554
|
inputSchema: FIELD_ACCESS_AUDIT_INPUT_SCHEMA
|
|
30691
32555
|
},
|
|
30692
32556
|
{
|
|
@@ -30781,12 +32645,12 @@ var init_tools = __esm({
|
|
|
30781
32645
|
},
|
|
30782
32646
|
{
|
|
30783
32647
|
name: "sfi.crud_fls_audit",
|
|
30784
|
-
description: "v2.1 R3 CRUD/FLS enforcement audit. Walks every ApexClass / ApexTrigger node's `properties.qualityIssues[]`, narrows to the two CRUD/FLS rules (`missing-crud-check`, `missing-fls-check`), groups findings by class, and surfaces the verbatim Q80 disclosure naming the HIGH false-positive rate inherited from ApexQualitySemantics.md \xA7\xA7 6-7. Each class entry carries its identity and a per-finding list (rule / severity / location / explanation). `totalFindingCount` / `byRule` report the FULL pre-slice counts. `limit` defaults to 100 (max 500); the
|
|
32648
|
+
description: "v2.1 R3 CRUD/FLS enforcement audit. Walks every ApexClass / ApexTrigger node's `properties.qualityIssues[]`, narrows to the two CRUD/FLS rules (`missing-crud-check`, `missing-fls-check`), groups findings by class, and surfaces the verbatim Q80 disclosure naming the HIGH false-positive rate inherited from ApexQualitySemantics.md \xA7\xA7 6-7. Each class entry carries its identity and a per-finding list (rule / severity / location / explanation). `totalFindingCount` / `byRule` report the FULL pre-slice counts. The class list is paginated: `limit` defaults to 100 (max 500) and `offset` (default 0) page over CLASSES, and a per-response ~36 KB byte budget trims the page further when a page would exceed it, so the result never trips the global ~45 KB MCP response limit; `truncated` flips true when more classes remain, with `nextOffset` to advance (plus a `note` when byte-trimmed, and a per-class `findingsTruncated` flag in the rare case one class's findings alone overflow). Honesty axis (verbatim, surfaced in `boundaries[]` when at least one finding qualifies): the Q80 false-positive disclosure \u2014 'custom security utility methods are invisible to the recognizer; this finding may be a false positive if your org uses a helper like SecurityUtils.canCreate(account)' \u2014 is the load-bearing honesty surface for this tool. Also surfaced: cross-method dataflow is invisible; dynamic SOQL strings (Database.query) are stripped before pattern passes.",
|
|
30785
32649
|
inputSchema: CRUD_FLS_AUDIT_INPUT_SCHEMA
|
|
30786
32650
|
},
|
|
30787
32651
|
{
|
|
30788
32652
|
name: "sfi.test_coverage_gaps",
|
|
30789
|
-
description: "v2.1 R3 test-coverage-gap surface. Combines three signals: (1) `properties.isTest === true` identifies test classes (excluded from the scan), (2) BFS over incoming `callsApex` edges (capped at depth 3) collects the test classes reaching each non-test class, (3) `qualityIssues[]` `fake-assertion` findings on those test classes mark meaninglessly-covered classes. Classifies each non-test ApexClass into one of three coverage statuses \u2014 `uncovered` (no test reaches it within depth 3), `fake-coverage` (covered, but EVERY covering test has fake-assertion findings), `low-quality-coverage` (covered, but SOME covering test has fake-assertion findings). Each gap entry carries `componentId` / `apiName` / `coverageStatus` / `coveringTestClassIds[]` / `fakeAssertions[]` / `recommendedAction`. `byStatus` reports the per-status counts. Optional `classFilter[]` narrows the scan to specific ApexClass ids (capped at 500). Honesty axis (verbatim, surfaced in `boundaries[]` when at least one gap qualifies): the meaningful-assertion heuristic recognizes `System.assertEquals(expected, actual)` with distinct tokens; assertions via helper methods or framework wrappers are invisible. Reachability via `callsApex` does NOT cover dynamic dispatch. BFS is capped at depth 3.",
|
|
32653
|
+
description: "v2.1 R3 test-coverage-gap surface. Combines three signals: (1) `properties.isTest === true` identifies test classes (excluded from the scan), (2) BFS over incoming `callsApex` edges (capped at depth 3) collects the test classes reaching each non-test class, (3) `qualityIssues[]` `fake-assertion` findings on those test classes mark meaninglessly-covered classes. Classifies each non-test ApexClass into one of three coverage statuses \u2014 `uncovered` (no test reaches it within depth 3), `fake-coverage` (covered, but EVERY covering test has fake-assertion findings), `low-quality-coverage` (covered, but SOME covering test has fake-assertion findings). Each gap entry carries `componentId` / `apiName` / `coverageStatus` / `coveringTestClassIds[]` / `fakeAssertions[]` / `recommendedAction`. `byStatus` reports the per-status counts. The gap list is paginated: `limit` (default 200, max 500) and `offset` (default 0) page over gap entries, and a per-response ~38 KB byte budget trims the page further when a page would exceed it, so the result never trips the global ~45 KB MCP response limit; `truncated` flips true when more gaps remain, with `nextOffset` to advance (plus a `note` when byte-trimmed). Optional `classFilter[]` narrows the scan to specific ApexClass ids (capped at 500). Honesty axis (verbatim, surfaced in `boundaries[]` when at least one gap qualifies): the meaningful-assertion heuristic recognizes `System.assertEquals(expected, actual)` with distinct tokens; assertions via helper methods or framework wrappers are invisible. Reachability via `callsApex` does NOT cover dynamic dispatch. BFS is capped at depth 3.",
|
|
30790
32654
|
inputSchema: TEST_COVERAGE_GAPS_INPUT_SCHEMA
|
|
30791
32655
|
},
|
|
30792
32656
|
{
|
|
@@ -31116,6 +32980,10 @@ var init_tools = __esm({
|
|
|
31116
32980
|
return runTool(ctx, args, resolveInputSchema, resolveHandler);
|
|
31117
32981
|
case "sfi.capabilities":
|
|
31118
32982
|
return runTool(ctx, args, capabilitiesInputSchema, capabilitiesHandler);
|
|
32983
|
+
case "sfi.guidance":
|
|
32984
|
+
return runTool(ctx, args, guidanceInputSchema, guidanceHandler);
|
|
32985
|
+
case "sfi.synthesize_answer":
|
|
32986
|
+
return runTool(ctx, args, synthesizeAnswerInputSchema, synthesizeAnswerHandler);
|
|
31119
32987
|
case "sfi.org_pulse":
|
|
31120
32988
|
return runTool(ctx, args, orgPulseInputSchema, orgPulseHandler);
|
|
31121
32989
|
case "sfi.fleet_find":
|
|
@@ -31286,6 +33154,10 @@ var init_tools = __esm({
|
|
|
31286
33154
|
return runTool(ctx, args, testCoverageGapsInputSchema, testCoverageGapsHandler);
|
|
31287
33155
|
case "sfi.what_if_change_field_type":
|
|
31288
33156
|
return runTool(ctx, args, whatIfChangeFieldTypeInputSchema, whatIfChangeFieldTypeHandler);
|
|
33157
|
+
case "sfi.what_if_change_field_value":
|
|
33158
|
+
return runTool(ctx, args, whatIfChangeFieldValueInputSchema, whatIfChangeFieldValueHandler);
|
|
33159
|
+
case "sfi.value_change_audit":
|
|
33160
|
+
return runTool(ctx, args, valueChangeAuditInputSchema, valueChangeAuditHandler);
|
|
31289
33161
|
case "sfi.what_if_remove_picklist_value":
|
|
31290
33162
|
return runTool(ctx, args, whatIfRemovePicklistValueInputSchema, whatIfRemovePicklistValueHandler);
|
|
31291
33163
|
case "sfi.what_if_make_field_required":
|
|
@@ -37347,11 +39219,30 @@ var extractEnterpriseMetadata = async (path, config) => {
|
|
|
37347
39219
|
apiName = deriveComponentApiName(path, config.suffix);
|
|
37348
39220
|
}
|
|
37349
39221
|
const fieldRefs = extractFieldRefs(text.value, parentObjectApiName);
|
|
39222
|
+
const nodeId = `${config.type}:${apiName}`;
|
|
39223
|
+
const childRefEdges = [];
|
|
39224
|
+
const childRefSummary = {};
|
|
39225
|
+
for (const spec of config.childRefs ?? []) {
|
|
39226
|
+
const values = [...new Set(extractXmlValues(text.value, spec.element))].sort();
|
|
39227
|
+
if (values.length > 0)
|
|
39228
|
+
childRefSummary[spec.element] = values;
|
|
39229
|
+
for (const value of values) {
|
|
39230
|
+
childRefEdges.push({
|
|
39231
|
+
fromId: nodeId,
|
|
39232
|
+
toId: `${spec.toType}:${value}`,
|
|
39233
|
+
edgeType: "references",
|
|
39234
|
+
confidence: "declared",
|
|
39235
|
+
source: EXTRACTOR_SOURCE10,
|
|
39236
|
+
properties: { referenceKind: spec.referenceKind }
|
|
39237
|
+
});
|
|
39238
|
+
}
|
|
39239
|
+
}
|
|
37350
39240
|
const node = makeNode(config.type, apiName, path, parentObjectApiName === null ? null : `CustomObject:${parentObjectApiName}`, {
|
|
37351
39241
|
fieldRefs,
|
|
37352
|
-
rawReferenceCount: fieldRefs.length
|
|
39242
|
+
rawReferenceCount: fieldRefs.length,
|
|
39243
|
+
...childRefSummary
|
|
37353
39244
|
});
|
|
37354
|
-
const
|
|
39245
|
+
const fieldRefEdges = fieldRefs.map((fieldId) => ({
|
|
37355
39246
|
fromId: node.id,
|
|
37356
39247
|
toId: fieldId,
|
|
37357
39248
|
edgeType: "references",
|
|
@@ -37359,7 +39250,7 @@ var extractEnterpriseMetadata = async (path, config) => {
|
|
|
37359
39250
|
source: EXTRACTOR_SOURCE10,
|
|
37360
39251
|
properties: { referenceKind: "fieldRef" }
|
|
37361
39252
|
}));
|
|
37362
|
-
return ok({ nodes: [node], edges });
|
|
39253
|
+
return ok({ nodes: [node], edges: [...fieldRefEdges, ...childRefEdges] });
|
|
37363
39254
|
};
|
|
37364
39255
|
var extractReport = (path) => extractEnterpriseMetadata(path, { type: "Report", suffix: ".report-meta.xml" });
|
|
37365
39256
|
var extractDashboard = (path) => extractEnterpriseMetadata(path, { type: "Dashboard", suffix: ".dashboard-meta.xml" });
|
|
@@ -37372,7 +39263,22 @@ var extractReportType = (path) => extractEnterpriseMetadata(path, { type: "Repor
|
|
|
37372
39263
|
var extractFlexiPage = (path) => extractEnterpriseMetadata(path, { type: "FlexiPage", suffix: ".flexipage-meta.xml" });
|
|
37373
39264
|
var extractPermissionSetGroup = (path) => extractEnterpriseMetadata(path, {
|
|
37374
39265
|
type: "PermissionSetGroup",
|
|
37375
|
-
suffix: ".permissionsetgroup-meta.xml"
|
|
39266
|
+
suffix: ".permissionsetgroup-meta.xml",
|
|
39267
|
+
// A PSG's effective permissions are the UNION of its member permission
|
|
39268
|
+
// sets' grants, minus the muting permission set's. Capture both so the
|
|
39269
|
+
// permission analysis can flow god-mode / object grants through the group.
|
|
39270
|
+
childRefs: [
|
|
39271
|
+
{
|
|
39272
|
+
element: "permissionSets",
|
|
39273
|
+
toType: "PermissionSet",
|
|
39274
|
+
referenceKind: "permissionSetGroupMember"
|
|
39275
|
+
},
|
|
39276
|
+
{
|
|
39277
|
+
element: "mutingPermissionSets",
|
|
39278
|
+
toType: "MutingPermissionSet",
|
|
39279
|
+
referenceKind: "mutingPermissionSet"
|
|
39280
|
+
}
|
|
39281
|
+
]
|
|
37376
39282
|
});
|
|
37377
39283
|
var extractMutingPermissionSet = (path) => extractEnterpriseMetadata(path, {
|
|
37378
39284
|
type: "MutingPermissionSet",
|
|
@@ -43257,10 +45163,14 @@ var extractRole = async (path) => {
|
|
|
43257
45163
|
return ok({ nodes: [node], edges });
|
|
43258
45164
|
};
|
|
43259
45165
|
|
|
45166
|
+
// ../extractors/dist/src/saml-sso-config.js
|
|
45167
|
+
init_dist();
|
|
45168
|
+
import { XMLParser as XMLParser53, XMLValidator as XMLValidator52 } from "fast-xml-parser";
|
|
45169
|
+
|
|
43260
45170
|
// ../extractors/dist/src/sharing-rules.js
|
|
43261
45171
|
init_dist();
|
|
43262
45172
|
import { readFile as readFile66 } from "node:fs/promises";
|
|
43263
|
-
import { XMLParser as
|
|
45173
|
+
import { XMLParser as XMLParser54, XMLValidator as XMLValidator53 } from "fast-xml-parser";
|
|
43264
45174
|
var SHARING_RULES_FILE_SUFFIX = ".sharingRules-meta.xml";
|
|
43265
45175
|
var ROOT_ELEMENT52 = "SharingRules";
|
|
43266
45176
|
var EXTRACTOR_SOURCE28 = "sharing-rule-extractor";
|
|
@@ -43321,7 +45231,7 @@ var readAndValidateXml48 = async (path) => {
|
|
|
43321
45231
|
cause
|
|
43322
45232
|
});
|
|
43323
45233
|
}
|
|
43324
|
-
const validation =
|
|
45234
|
+
const validation = XMLValidator53.validate(xmlText);
|
|
43325
45235
|
if (validation !== true) {
|
|
43326
45236
|
return err({ kind: "parse-error", path, message: validation.err.msg });
|
|
43327
45237
|
}
|
|
@@ -43511,7 +45421,7 @@ var extractSharingRules = async (path) => {
|
|
|
43511
45421
|
const xmlResult = await readAndValidateXml48(path);
|
|
43512
45422
|
if (!xmlResult.ok)
|
|
43513
45423
|
return xmlResult;
|
|
43514
|
-
const parser = new
|
|
45424
|
+
const parser = new XMLParser54({
|
|
43515
45425
|
ignoreAttributes: true,
|
|
43516
45426
|
parseTagValue: false,
|
|
43517
45427
|
trimValues: true,
|
|
@@ -43558,7 +45468,7 @@ var extractSharingRules = async (path) => {
|
|
|
43558
45468
|
// ../extractors/dist/src/static-resource.js
|
|
43559
45469
|
init_dist();
|
|
43560
45470
|
import { readFile as readFile67 } from "node:fs/promises";
|
|
43561
|
-
import { XMLParser as
|
|
45471
|
+
import { XMLParser as XMLParser55, XMLValidator as XMLValidator54 } from "fast-xml-parser";
|
|
43562
45472
|
var STATIC_RESOURCE_FILE_SUFFIX = ".resource-meta.xml";
|
|
43563
45473
|
var ROOT_ELEMENT53 = "StaticResource";
|
|
43564
45474
|
var REQUIRED_ELEMENTS24 = ["cacheControl"];
|
|
@@ -43583,7 +45493,7 @@ var readAndValidateXml49 = async (path) => {
|
|
|
43583
45493
|
cause
|
|
43584
45494
|
});
|
|
43585
45495
|
}
|
|
43586
|
-
const validation =
|
|
45496
|
+
const validation = XMLValidator54.validate(xmlText);
|
|
43587
45497
|
if (validation !== true) {
|
|
43588
45498
|
return err({ kind: "parse-error", path, message: validation.err.msg });
|
|
43589
45499
|
}
|
|
@@ -43614,7 +45524,7 @@ var extractStaticResource = async (path) => {
|
|
|
43614
45524
|
const xmlResult = await readAndValidateXml49(path);
|
|
43615
45525
|
if (!xmlResult.ok)
|
|
43616
45526
|
return xmlResult;
|
|
43617
|
-
const parser = new
|
|
45527
|
+
const parser = new XMLParser55({
|
|
43618
45528
|
ignoreAttributes: true,
|
|
43619
45529
|
parseTagValue: false,
|
|
43620
45530
|
trimValues: true,
|
|
@@ -43668,7 +45578,7 @@ var extractStaticResource = async (path) => {
|
|
|
43668
45578
|
init_dist();
|
|
43669
45579
|
import { readFile as readFile68 } from "node:fs/promises";
|
|
43670
45580
|
import { basename as basename10, dirname as dirname13 } from "node:path";
|
|
43671
|
-
import { XMLParser as
|
|
45581
|
+
import { XMLParser as XMLParser56, XMLValidator as XMLValidator55 } from "fast-xml-parser";
|
|
43672
45582
|
var VALIDATION_RULE_FILE_SUFFIX = ".validationRule-meta.xml";
|
|
43673
45583
|
var ROOT_ELEMENT54 = "ValidationRule";
|
|
43674
45584
|
var VALIDATION_RULES_DIR_NAME = "validationRules";
|
|
@@ -43703,7 +45613,7 @@ var readAndValidateXml50 = async (path) => {
|
|
|
43703
45613
|
cause
|
|
43704
45614
|
});
|
|
43705
45615
|
}
|
|
43706
|
-
const validation =
|
|
45616
|
+
const validation = XMLValidator55.validate(xmlText);
|
|
43707
45617
|
if (validation !== true) {
|
|
43708
45618
|
return err({ kind: "parse-error", path, message: validation.err.msg });
|
|
43709
45619
|
}
|
|
@@ -43751,7 +45661,7 @@ var extractValidationRule = async (path) => {
|
|
|
43751
45661
|
const xmlResult = await readAndValidateXml50(path);
|
|
43752
45662
|
if (!xmlResult.ok)
|
|
43753
45663
|
return xmlResult;
|
|
43754
|
-
const parser = new
|
|
45664
|
+
const parser = new XMLParser56({
|
|
43755
45665
|
ignoreAttributes: true,
|
|
43756
45666
|
parseTagValue: false,
|
|
43757
45667
|
trimValues: true,
|
|
@@ -43822,7 +45732,7 @@ var extractValidationRule = async (path) => {
|
|
|
43822
45732
|
init_dist();
|
|
43823
45733
|
init_src3();
|
|
43824
45734
|
import { readFile as readFile69 } from "node:fs/promises";
|
|
43825
|
-
import { XMLParser as
|
|
45735
|
+
import { XMLParser as XMLParser57, XMLValidator as XMLValidator56 } from "fast-xml-parser";
|
|
43826
45736
|
var COMPONENT_FILE_SUFFIX = ".component";
|
|
43827
45737
|
var META_FILE_EXT3 = "-meta.xml";
|
|
43828
45738
|
var ROOT_ELEMENT55 = "ApexComponent";
|
|
@@ -43848,14 +45758,14 @@ var readAndValidateXml51 = async (path, missingMessage) => {
|
|
|
43848
45758
|
cause
|
|
43849
45759
|
});
|
|
43850
45760
|
}
|
|
43851
|
-
const validation =
|
|
45761
|
+
const validation = XMLValidator56.validate(xmlText);
|
|
43852
45762
|
if (validation !== true) {
|
|
43853
45763
|
return err({ kind: "parse-error", path, message: validation.err.msg });
|
|
43854
45764
|
}
|
|
43855
45765
|
return ok(xmlText);
|
|
43856
45766
|
};
|
|
43857
45767
|
var parseMetaXml5 = (xmlText, path) => {
|
|
43858
|
-
const parser = new
|
|
45768
|
+
const parser = new XMLParser57({
|
|
43859
45769
|
ignoreAttributes: true,
|
|
43860
45770
|
parseTagValue: false,
|
|
43861
45771
|
trimValues: true,
|
|
@@ -44063,7 +45973,7 @@ var extractVisualforceComponent = async (path) => {
|
|
|
44063
45973
|
init_dist();
|
|
44064
45974
|
init_src3();
|
|
44065
45975
|
import { readFile as readFile70 } from "node:fs/promises";
|
|
44066
|
-
import { XMLParser as
|
|
45976
|
+
import { XMLParser as XMLParser58, XMLValidator as XMLValidator57 } from "fast-xml-parser";
|
|
44067
45977
|
var PAGE_FILE_SUFFIX = ".page";
|
|
44068
45978
|
var META_FILE_EXT4 = "-meta.xml";
|
|
44069
45979
|
var ROOT_ELEMENT56 = "ApexPage";
|
|
@@ -44090,14 +46000,14 @@ var readAndValidateXml52 = async (path, missingMessage) => {
|
|
|
44090
46000
|
cause
|
|
44091
46001
|
});
|
|
44092
46002
|
}
|
|
44093
|
-
const validation =
|
|
46003
|
+
const validation = XMLValidator57.validate(xmlText);
|
|
44094
46004
|
if (validation !== true) {
|
|
44095
46005
|
return err({ kind: "parse-error", path, message: validation.err.msg });
|
|
44096
46006
|
}
|
|
44097
46007
|
return ok(xmlText);
|
|
44098
46008
|
};
|
|
44099
46009
|
var parseMetaXml6 = (xmlText, path) => {
|
|
44100
|
-
const parser = new
|
|
46010
|
+
const parser = new XMLParser58({
|
|
44101
46011
|
ignoreAttributes: true,
|
|
44102
46012
|
parseTagValue: false,
|
|
44103
46013
|
trimValues: true,
|
|
@@ -44311,7 +46221,7 @@ var extractVisualforcePage = async (path) => {
|
|
|
44311
46221
|
init_dist();
|
|
44312
46222
|
import { readFile as readFile71 } from "node:fs/promises";
|
|
44313
46223
|
import { basename as basename11, dirname as dirname14 } from "node:path";
|
|
44314
|
-
import { XMLParser as
|
|
46224
|
+
import { XMLParser as XMLParser59, XMLValidator as XMLValidator58 } from "fast-xml-parser";
|
|
44315
46225
|
var FILE_SUFFIX5 = ".webLink-meta.xml";
|
|
44316
46226
|
var ROOT_ELEMENT57 = "WebLink";
|
|
44317
46227
|
var DIR_NAME4 = "webLinks";
|
|
@@ -44349,7 +46259,7 @@ var readAndValidateXml53 = async (path) => {
|
|
|
44349
46259
|
cause
|
|
44350
46260
|
});
|
|
44351
46261
|
}
|
|
44352
|
-
const validation =
|
|
46262
|
+
const validation = XMLValidator58.validate(xmlText);
|
|
44353
46263
|
if (validation !== true) {
|
|
44354
46264
|
return err({ kind: "parse-error", path, message: validation.err.msg });
|
|
44355
46265
|
}
|
|
@@ -44376,7 +46286,7 @@ var extractWebLink = async (path) => {
|
|
|
44376
46286
|
const xmlResult = await readAndValidateXml53(path);
|
|
44377
46287
|
if (!xmlResult.ok)
|
|
44378
46288
|
return xmlResult;
|
|
44379
|
-
const parser = new
|
|
46289
|
+
const parser = new XMLParser59({
|
|
44380
46290
|
ignoreAttributes: true,
|
|
44381
46291
|
parseTagValue: false,
|
|
44382
46292
|
trimValues: true,
|
|
@@ -44451,7 +46361,7 @@ var extractWebLink = async (path) => {
|
|
|
44451
46361
|
// ../extractors/dist/src/workflow-rule.js
|
|
44452
46362
|
init_dist();
|
|
44453
46363
|
import { readFile as readFile72 } from "node:fs/promises";
|
|
44454
|
-
import { XMLParser as
|
|
46364
|
+
import { XMLParser as XMLParser60, XMLValidator as XMLValidator59 } from "fast-xml-parser";
|
|
44455
46365
|
var WORKFLOW_FILE_SUFFIX = ".workflow-meta.xml";
|
|
44456
46366
|
var ROOT_ELEMENT58 = "Workflow";
|
|
44457
46367
|
var EXTRACTOR_SOURCE29 = "workflow-rule-extractor";
|
|
@@ -44527,7 +46437,7 @@ var readAndValidateXml54 = async (path) => {
|
|
|
44527
46437
|
cause
|
|
44528
46438
|
});
|
|
44529
46439
|
}
|
|
44530
|
-
const validation =
|
|
46440
|
+
const validation = XMLValidator59.validate(xmlText);
|
|
44531
46441
|
if (validation !== true) {
|
|
44532
46442
|
return err({ kind: "parse-error", path, message: validation.err.msg });
|
|
44533
46443
|
}
|
|
@@ -44841,7 +46751,7 @@ var extractWorkflowRule = async (path) => {
|
|
|
44841
46751
|
const xmlResult = await readAndValidateXml54(path);
|
|
44842
46752
|
if (!xmlResult.ok)
|
|
44843
46753
|
return xmlResult;
|
|
44844
|
-
const parser = new
|
|
46754
|
+
const parser = new XMLParser60({
|
|
44845
46755
|
ignoreAttributes: true,
|
|
44846
46756
|
parseTagValue: false,
|
|
44847
46757
|
trimValues: true,
|
|
@@ -46840,7 +48750,7 @@ var registerStatusCommand = (program) => {
|
|
|
46840
48750
|
// dist/src/program.js
|
|
46841
48751
|
var readVersion = () => {
|
|
46842
48752
|
if (true)
|
|
46843
|
-
return "0.1.
|
|
48753
|
+
return "0.1.5";
|
|
46844
48754
|
const pkgUrl = new URL("../../package.json", import.meta.url);
|
|
46845
48755
|
const raw = readFileSync2(fileURLToPath(pkgUrl), "utf8");
|
|
46846
48756
|
const parsed = JSON.parse(raw);
|