sf-intelligence 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +496 -51
- package/package.json +19 -18
package/dist/index.js
CHANGED
|
@@ -1345,11 +1345,11 @@ var init_resolve_index = __esm({
|
|
|
1345
1345
|
};
|
|
1346
1346
|
cache = /* @__PURE__ */ new WeakMap();
|
|
1347
1347
|
getResolveIndex = async (store, options) => {
|
|
1348
|
-
const
|
|
1349
|
-
if (
|
|
1348
|
+
const cached2 = cache.get(store);
|
|
1349
|
+
if (cached2 !== void 0) {
|
|
1350
1350
|
const [countRow] = await runRows2(store, `SELECT count(*)::INT AS c FROM nodes`, []);
|
|
1351
|
-
if (countRow !== void 0 && Number(countRow["c"]) ===
|
|
1352
|
-
return
|
|
1351
|
+
if (countRow !== void 0 && Number(countRow["c"]) === cached2.nodeCount) {
|
|
1352
|
+
return cached2;
|
|
1353
1353
|
}
|
|
1354
1354
|
}
|
|
1355
1355
|
if (options?.graphDbPath !== void 0) {
|
|
@@ -26946,7 +26946,7 @@ var init_capabilities = __esm({
|
|
|
26946
26946
|
ROUTING_GUIDANCE = {
|
|
26947
26947
|
startHere: "On a vague or broad question, call sfi.route_question first \u2014 it returns the plane (vault | live | hybrid | unknown) and the tools to run. Default to the offline vault. Use sfi.live_* only for record counts, samples, population, describe, org limits, or inactive users \u2014 and only when the org has live consent (sfi.live_consent), SFI_LIVE_PLANE_ENABLED=1, or liveEnabled: true. Before destructive verdicts, call sfi.coverage_report.",
|
|
26948
26948
|
onAmbiguous: "On ambiguous resolution, clarify the component first. If the user wants live data and live is disabled, say so and offer to enable it once with sfi.live_consent { grant: true } (read-only) \u2014 do not guess from the vault.",
|
|
26949
|
-
onNone: "On none, offer /sfi-refresh for metadata gaps. For live-record questions when offline, name sfi.live_count or sfi.live_sample and the consent requirement \u2014 never invent counts.
|
|
26949
|
+
onNone: "On none, offer /sfi-refresh for metadata gaps. For live-record questions when offline, name sfi.live_count or sfi.live_sample and the consent requirement \u2014 never invent counts. When route_question returns toolCandidates (no rule placed the question, or it matched only weakly), follow its `guidance`: those candidates are an advisory shortlist \u2014 pick the right tool(s) from them, resolve any named component, run them, then synthesize. Do NOT say the capability is unbuilt when candidates are offered. Only a true unknown with NO candidates means the capability is not built yet (the gap is logged).",
|
|
26950
26950
|
groundAnswer: "Run the routed tools, then synthesize ONE answer from their output via sfi.synthesize_answer { question, draft }. It returns hallucinatedIds \u2014 any canonical id you wrote that no tool returned. Strip those before answering; cite only ids the tools produced, with their provenance."
|
|
26951
26951
|
};
|
|
26952
26952
|
BOUNDARIES4 = [
|
|
@@ -46749,6 +46749,283 @@ var init_retrieve_blindspot_report = __esm({
|
|
|
46749
46749
|
}
|
|
46750
46750
|
});
|
|
46751
46751
|
|
|
46752
|
+
// ../mcp/dist/src/semantic-funnel.js
|
|
46753
|
+
var STOPWORDS, SYNONYMS, tokenize6, TOOL_KEYWORDS, expand, buildToolDocs, cached, buildIndex, getFunnelIndex, semanticCandidates;
|
|
46754
|
+
var init_semantic_funnel = __esm({
|
|
46755
|
+
"../mcp/dist/src/semantic-funnel.js"() {
|
|
46756
|
+
"use strict";
|
|
46757
|
+
init_capabilities();
|
|
46758
|
+
init_tools();
|
|
46759
|
+
STOPWORDS = /* @__PURE__ */ new Set([
|
|
46760
|
+
"the",
|
|
46761
|
+
"a",
|
|
46762
|
+
"an",
|
|
46763
|
+
"of",
|
|
46764
|
+
"to",
|
|
46765
|
+
"in",
|
|
46766
|
+
"on",
|
|
46767
|
+
"for",
|
|
46768
|
+
"and",
|
|
46769
|
+
"or",
|
|
46770
|
+
"is",
|
|
46771
|
+
"are",
|
|
46772
|
+
"do",
|
|
46773
|
+
"does",
|
|
46774
|
+
"did",
|
|
46775
|
+
"what",
|
|
46776
|
+
"which",
|
|
46777
|
+
"show",
|
|
46778
|
+
"me",
|
|
46779
|
+
"my",
|
|
46780
|
+
"this",
|
|
46781
|
+
"that",
|
|
46782
|
+
"these",
|
|
46783
|
+
"those",
|
|
46784
|
+
"how",
|
|
46785
|
+
"where",
|
|
46786
|
+
"when",
|
|
46787
|
+
"who",
|
|
46788
|
+
"whom",
|
|
46789
|
+
"can",
|
|
46790
|
+
"could",
|
|
46791
|
+
"i",
|
|
46792
|
+
"it",
|
|
46793
|
+
"its",
|
|
46794
|
+
"have",
|
|
46795
|
+
"has",
|
|
46796
|
+
"had",
|
|
46797
|
+
"with",
|
|
46798
|
+
"from",
|
|
46799
|
+
"get",
|
|
46800
|
+
"give",
|
|
46801
|
+
"list",
|
|
46802
|
+
"tell",
|
|
46803
|
+
"about",
|
|
46804
|
+
"all",
|
|
46805
|
+
"any",
|
|
46806
|
+
"our",
|
|
46807
|
+
"we",
|
|
46808
|
+
"us",
|
|
46809
|
+
"you",
|
|
46810
|
+
"your",
|
|
46811
|
+
"be",
|
|
46812
|
+
"been",
|
|
46813
|
+
"there",
|
|
46814
|
+
"here",
|
|
46815
|
+
"into",
|
|
46816
|
+
"at",
|
|
46817
|
+
"by",
|
|
46818
|
+
"as",
|
|
46819
|
+
"so",
|
|
46820
|
+
"if",
|
|
46821
|
+
"then",
|
|
46822
|
+
"will"
|
|
46823
|
+
]);
|
|
46824
|
+
SYNONYMS = {
|
|
46825
|
+
// permissions / access / sharing
|
|
46826
|
+
access: ["permission", "permissions", "sharing", "visibility", "visible", "crud", "grant"],
|
|
46827
|
+
permission: ["access", "permissions", "grant", "profile", "permissionset", "effective"],
|
|
46828
|
+
permissions: ["permission", "access", "effective", "profile"],
|
|
46829
|
+
see: ["access", "visibility", "visible", "sharing", "view"],
|
|
46830
|
+
visible: ["access", "visibility", "see", "sharing"],
|
|
46831
|
+
create: ["access", "crud", "object", "insert"],
|
|
46832
|
+
read: ["access", "crud", "view"],
|
|
46833
|
+
edit: ["access", "crud", "modify", "write", "update", "editable"],
|
|
46834
|
+
modify: ["edit", "write", "change", "access"],
|
|
46835
|
+
delete: ["access", "crud", "remove", "deletion", "deletable"],
|
|
46836
|
+
remove: ["delete", "deletion"],
|
|
46837
|
+
admin: ["permission", "permissions", "modify", "view", "risk", "effective"],
|
|
46838
|
+
profile: ["permission", "permissions", "permissionset", "effective"],
|
|
46839
|
+
// usage / dependency / impact
|
|
46840
|
+
references: ["usage", "usages", "used", "reference", "depends", "uses", "find"],
|
|
46841
|
+
reference: ["usage", "usages", "references", "used"],
|
|
46842
|
+
referenced: ["usage", "usages", "references", "used"],
|
|
46843
|
+
used: ["usage", "usages", "references", "uses", "find"],
|
|
46844
|
+
uses: ["usage", "usages", "used"],
|
|
46845
|
+
depends: ["dependency", "usage", "usages", "impact", "depend", "references"],
|
|
46846
|
+
depend: ["dependency", "usage", "depends", "impact"],
|
|
46847
|
+
break: ["impact", "depend", "dependency", "breaks", "affected"],
|
|
46848
|
+
// schema / enumeration
|
|
46849
|
+
objects: ["object", "sobject", "components", "schema", "list"],
|
|
46850
|
+
object: ["sobject", "table", "entity", "components"],
|
|
46851
|
+
fields: ["field", "columns", "components", "list"],
|
|
46852
|
+
list: ["components", "enumerate", "inventory"],
|
|
46853
|
+
relationship: ["lookup", "child", "parent", "schema", "related"],
|
|
46854
|
+
child: ["lookup", "relationship", "parent", "components"],
|
|
46855
|
+
// counts / live records
|
|
46856
|
+
how: ["count", "many", "number"],
|
|
46857
|
+
many: ["count", "number"],
|
|
46858
|
+
show: ["sample", "records"],
|
|
46859
|
+
sample: ["records", "show"],
|
|
46860
|
+
// change / audit / freshness
|
|
46861
|
+
changed: ["modified", "change", "history", "audit"],
|
|
46862
|
+
change: ["modified", "history", "changed"],
|
|
46863
|
+
fresh: ["health", "stale", "freshness", "vault", "current"],
|
|
46864
|
+
stale: ["fresh", "freshness", "health", "outdated", "current"],
|
|
46865
|
+
inactive: ["active", "disabled", "draft", "obsolete"],
|
|
46866
|
+
// meaning / docs / misc
|
|
46867
|
+
help: ["meaning", "explain", "description", "text"],
|
|
46868
|
+
meaning: ["explain", "field", "help"],
|
|
46869
|
+
doc: ["document", "documentation", "handbook", "overview"],
|
|
46870
|
+
compare: ["diff", "difference", "across"],
|
|
46871
|
+
pii: ["sensitive", "personal", "compliance", "privacy"],
|
|
46872
|
+
run: ["execute", "fire", "trigger", "runs", "execution"],
|
|
46873
|
+
user: ["profile", "permissionset", "member"],
|
|
46874
|
+
field: ["column", "attribute"],
|
|
46875
|
+
// Intent verbs (generalize across phrasings, unlike tool-specific keywords).
|
|
46876
|
+
touches: ["impact", "depend", "dependency", "affected", "usage", "references", "uses"],
|
|
46877
|
+
relies: ["depend", "dependency", "impact", "usage", "references"],
|
|
46878
|
+
falls: ["break", "breaks", "impact", "affected"],
|
|
46879
|
+
dump: ["list", "inventory", "enumerate", "components"],
|
|
46880
|
+
inventory: ["list", "enumerate", "components", "all"],
|
|
46881
|
+
exist: ["list", "count", "inventory", "many"],
|
|
46882
|
+
blow: ["governor", "limit", "risk", "exceed"],
|
|
46883
|
+
populated: ["population", "filled", "fill", "usage"],
|
|
46884
|
+
required: ["mandatory", "needed", "blank", "null"],
|
|
46885
|
+
walk: ["explain", "trace", "describe", "order", "execution"]
|
|
46886
|
+
};
|
|
46887
|
+
tokenize6 = (text) => {
|
|
46888
|
+
const raw = text.toLowerCase().replace(/[‘’ʼ']/g, "'").replace(/[^a-z0-9]+/g, " ").split(/\s+/).filter((t2) => t2.length > 1 && !STOPWORDS.has(t2));
|
|
46889
|
+
const out = [];
|
|
46890
|
+
for (const t2 of raw) {
|
|
46891
|
+
out.push(t2);
|
|
46892
|
+
if (t2.length > 3 && t2.endsWith("s") && !t2.endsWith("ss"))
|
|
46893
|
+
out.push(t2.slice(0, -1));
|
|
46894
|
+
}
|
|
46895
|
+
return out;
|
|
46896
|
+
};
|
|
46897
|
+
TOOL_KEYWORDS = {
|
|
46898
|
+
"sfi.list_components": "list inventory enumerate catalog all what do we have how many exist objects fields flows classes triggers profiles permission sets layouts record types validation rules approval processes reports dashboards omniscripts custom standard relationship child parent inactive active picklist values",
|
|
46899
|
+
"sfi.capabilities": "what can you do help capabilities what can i ask how do i use",
|
|
46900
|
+
"sfi.automation_risk_report": "objects more than one multiple triggers per object trigger quality automation risk",
|
|
46901
|
+
"sfi.live_group_count": "how many users assigned to profile membership group count",
|
|
46902
|
+
"sfi.annotations": "who owns owner steward responsible curated note",
|
|
46903
|
+
"sfi.last_modified": "who changed when modified last edited touched",
|
|
46904
|
+
"sfi.search_flow_metadata": "find flows matching named sync search flow by name",
|
|
46905
|
+
"sfi.who_can_access_object": "who can access object which profiles can read create edit delete",
|
|
46906
|
+
"sfi.live_sample": "show me sample example first few records rows give me",
|
|
46907
|
+
"sfi.governor_limit_risks": "queries soql governor limit large data volume bulk dml",
|
|
46908
|
+
"sfi.what_if_change_method_signature": "change method signature parameter argument breaks",
|
|
46909
|
+
"sfi.layout_for_user": "which layout does the profile user see page on object",
|
|
46910
|
+
"sfi.integration_map": "api limits at risk integration volume callout capacity external",
|
|
46911
|
+
"sfi.find_apex_usages": "which flows invoke call use apex classes methods from",
|
|
46912
|
+
"sfi.live_folder_access": "who can access report dashboard folder pipeline see view shared"
|
|
46913
|
+
};
|
|
46914
|
+
expand = (tokens) => {
|
|
46915
|
+
const out = [...tokens];
|
|
46916
|
+
for (const t2 of tokens) {
|
|
46917
|
+
const syn = SYNONYMS[t2];
|
|
46918
|
+
if (syn !== void 0)
|
|
46919
|
+
out.push(...syn);
|
|
46920
|
+
}
|
|
46921
|
+
return out;
|
|
46922
|
+
};
|
|
46923
|
+
buildToolDocs = () => {
|
|
46924
|
+
const docs = /* @__PURE__ */ new Map();
|
|
46925
|
+
for (const tool of V01_TOOLS) {
|
|
46926
|
+
const nameWords = tool.name.replace(/^sfi\./, "").replace(/_/g, " ");
|
|
46927
|
+
const keywords = TOOL_KEYWORDS[tool.name] ?? "";
|
|
46928
|
+
docs.set(tool.name, `${nameWords} ${nameWords} ${tool.description} ${keywords}`);
|
|
46929
|
+
}
|
|
46930
|
+
for (const cat of CATEGORIES) {
|
|
46931
|
+
const catText = ` ${cat.title} ${cat.description} ${cat.exampleQuestions.join(" ")}`;
|
|
46932
|
+
for (const toolName of cat.tools) {
|
|
46933
|
+
const prev = docs.get(toolName);
|
|
46934
|
+
if (prev !== void 0)
|
|
46935
|
+
docs.set(toolName, prev + catText);
|
|
46936
|
+
}
|
|
46937
|
+
}
|
|
46938
|
+
return docs;
|
|
46939
|
+
};
|
|
46940
|
+
cached = null;
|
|
46941
|
+
buildIndex = () => {
|
|
46942
|
+
const docs = buildToolDocs();
|
|
46943
|
+
const toolCategory = /* @__PURE__ */ new Map();
|
|
46944
|
+
for (const cat of CATEGORIES) {
|
|
46945
|
+
for (const tool of cat.tools)
|
|
46946
|
+
if (!toolCategory.has(tool))
|
|
46947
|
+
toolCategory.set(tool, cat.id);
|
|
46948
|
+
}
|
|
46949
|
+
const docTokens = /* @__PURE__ */ new Map();
|
|
46950
|
+
const df = /* @__PURE__ */ new Map();
|
|
46951
|
+
for (const [tool, doc] of docs) {
|
|
46952
|
+
const toks = tokenize6(doc);
|
|
46953
|
+
docTokens.set(tool, toks);
|
|
46954
|
+
for (const term of new Set(toks))
|
|
46955
|
+
df.set(term, (df.get(term) ?? 0) + 1);
|
|
46956
|
+
}
|
|
46957
|
+
const n2 = docs.size;
|
|
46958
|
+
const idf = /* @__PURE__ */ new Map();
|
|
46959
|
+
for (const [term, d2] of df)
|
|
46960
|
+
idf.set(term, Math.log((n2 + 1) / (d2 + 1)) + 1);
|
|
46961
|
+
const vectors = /* @__PURE__ */ new Map();
|
|
46962
|
+
for (const [tool, toks] of docTokens) {
|
|
46963
|
+
if (toks.length === 0) {
|
|
46964
|
+
vectors.set(tool, /* @__PURE__ */ new Map());
|
|
46965
|
+
continue;
|
|
46966
|
+
}
|
|
46967
|
+
const tf = /* @__PURE__ */ new Map();
|
|
46968
|
+
for (const t2 of toks)
|
|
46969
|
+
tf.set(t2, (tf.get(t2) ?? 0) + 1);
|
|
46970
|
+
const vec = /* @__PURE__ */ new Map();
|
|
46971
|
+
let norm = 0;
|
|
46972
|
+
for (const [term, f2] of tf) {
|
|
46973
|
+
const w2 = f2 / toks.length * (idf.get(term) ?? 0);
|
|
46974
|
+
vec.set(term, w2);
|
|
46975
|
+
norm += w2 * w2;
|
|
46976
|
+
}
|
|
46977
|
+
norm = Math.sqrt(norm) || 1;
|
|
46978
|
+
for (const [term, w2] of vec)
|
|
46979
|
+
vec.set(term, w2 / norm);
|
|
46980
|
+
vectors.set(tool, vec);
|
|
46981
|
+
}
|
|
46982
|
+
return { vectors, idf, toolCategory };
|
|
46983
|
+
};
|
|
46984
|
+
getFunnelIndex = () => cached ??= buildIndex();
|
|
46985
|
+
semanticCandidates = (question, k2 = 8) => {
|
|
46986
|
+
const idx = getFunnelIndex();
|
|
46987
|
+
const qTokens = expand(tokenize6(question));
|
|
46988
|
+
if (qTokens.length === 0)
|
|
46989
|
+
return [];
|
|
46990
|
+
const qtf = /* @__PURE__ */ new Map();
|
|
46991
|
+
for (const t2 of qTokens)
|
|
46992
|
+
qtf.set(t2, (qtf.get(t2) ?? 0) + 1);
|
|
46993
|
+
const qvec = /* @__PURE__ */ new Map();
|
|
46994
|
+
let qnorm = 0;
|
|
46995
|
+
for (const [term, f2] of qtf) {
|
|
46996
|
+
const w2 = f2 / qTokens.length * (idx.idf.get(term) ?? 0);
|
|
46997
|
+
if (w2 > 0) {
|
|
46998
|
+
qvec.set(term, w2);
|
|
46999
|
+
qnorm += w2 * w2;
|
|
47000
|
+
}
|
|
47001
|
+
}
|
|
47002
|
+
qnorm = Math.sqrt(qnorm) || 1;
|
|
47003
|
+
if (qvec.size === 0)
|
|
47004
|
+
return [];
|
|
47005
|
+
const scored = [];
|
|
47006
|
+
for (const [tool, vec] of idx.vectors) {
|
|
47007
|
+
let dot = 0;
|
|
47008
|
+
const [small, large] = qvec.size <= vec.size ? [qvec, vec] : [vec, qvec];
|
|
47009
|
+
for (const [term, w2] of small) {
|
|
47010
|
+
const o2 = large.get(term);
|
|
47011
|
+
if (o2 !== void 0)
|
|
47012
|
+
dot += w2 * o2;
|
|
47013
|
+
}
|
|
47014
|
+
if (dot <= 0)
|
|
47015
|
+
continue;
|
|
47016
|
+
const score = dot / qnorm;
|
|
47017
|
+
scored.push({
|
|
47018
|
+
tool,
|
|
47019
|
+
score: Math.round(score * 1e3) / 1e3,
|
|
47020
|
+
category: idx.toolCategory.get(tool) ?? null
|
|
47021
|
+
});
|
|
47022
|
+
}
|
|
47023
|
+
scored.sort((a2, b2) => b2.score - a2.score || a2.tool.localeCompare(b2.tool));
|
|
47024
|
+
return scored.slice(0, k2);
|
|
47025
|
+
};
|
|
47026
|
+
}
|
|
47027
|
+
});
|
|
47028
|
+
|
|
46752
47029
|
// ../mcp/dist/src/tools/tool-profile.js
|
|
46753
47030
|
var CORE_PROFILE_TOOLS, toolProfile;
|
|
46754
47031
|
var init_tool_profile = __esm({
|
|
@@ -46781,7 +47058,7 @@ var init_tool_profile = __esm({
|
|
|
46781
47058
|
// ../mcp/dist/src/tools/route-question.js
|
|
46782
47059
|
import { createHash as createHash6 } from "node:crypto";
|
|
46783
47060
|
import { z as z113 } from "zod";
|
|
46784
|
-
var RESOLVE_FALLBACK_MAX_TOKENS, tryResolveFallback, routeQuestionInputSchema, routeTrust, clarificationIdFor, selectedEntityArgsForRoute, extractEntityQuery, inferEntityTypes, refineEntityResolution, applyGlossaryAliases, entityAmbiguityRequiresClarification, splitCompoundQuestion, mixedInventoryAndStoragePlan, routeQuestionHandler;
|
|
47061
|
+
var RESOLVE_FALLBACK_MAX_TOKENS, tryResolveFallback, routeQuestionInputSchema, routeTrust, clarificationIdFor, selectedEntityArgsForRoute, extractEntityQuery, inferEntityTypes, refineEntityResolution, applyGlossaryAliases, entityAmbiguityRequiresClarification, splitCompoundQuestion, mixedInventoryAndStoragePlan, PLAN_FAMILY, ASSESSMENT_FAMILY, rerankForMode, guidanceForMode, routerMode, routeQuestionHandler;
|
|
46785
47062
|
var init_route_question = __esm({
|
|
46786
47063
|
"../mcp/dist/src/tools/route-question.js"() {
|
|
46787
47064
|
"use strict";
|
|
@@ -46789,6 +47066,7 @@ var init_route_question = __esm({
|
|
|
46789
47066
|
init_src();
|
|
46790
47067
|
init_answer_render();
|
|
46791
47068
|
init_intent_router();
|
|
47069
|
+
init_semantic_funnel();
|
|
46792
47070
|
init_resolve2();
|
|
46793
47071
|
init_tool_profile();
|
|
46794
47072
|
RESOLVE_FALLBACK_MAX_TOKENS = 3;
|
|
@@ -46844,7 +47122,16 @@ var init_route_question = __esm({
|
|
|
46844
47122
|
clarificationResponse: z113.object({
|
|
46845
47123
|
clarificationId: z113.string().min(1),
|
|
46846
47124
|
selection: z113.string().min(1)
|
|
46847
|
-
}).optional()
|
|
47125
|
+
}).optional(),
|
|
47126
|
+
/**
|
|
47127
|
+
* CAE-04 output mode — shapes how the host LLM should answer and which candidate
|
|
47128
|
+
* tools to favor. 'ask' = a quick grounded answer (the default behavior);
|
|
47129
|
+
* 'plan' = an ordered change plan (favors the what_if_* / impact tools);
|
|
47130
|
+
* 'assessment' = a full evaluation (favors the *_risk_report / readiness /
|
|
47131
|
+
* coverage tools). When set, toolCandidates + a mode-specific guidance line are
|
|
47132
|
+
* always attached, regardless of the deterministic route's confidence.
|
|
47133
|
+
*/
|
|
47134
|
+
mode: z113.enum(["ask", "plan", "assessment"]).optional()
|
|
46848
47135
|
});
|
|
46849
47136
|
routeTrust = () => ({
|
|
46850
47137
|
provenance: "offline_snapshot",
|
|
@@ -46852,7 +47139,7 @@ var init_route_question = __esm({
|
|
|
46852
47139
|
freshness: {},
|
|
46853
47140
|
completeness: { status: "complete" },
|
|
46854
47141
|
limitations: [
|
|
46855
|
-
"
|
|
47142
|
+
"In the default hybrid mode the meaning-ranked toolCandidates are PRIMARY and the host LLM decides which to run; the deterministic route is a non-authoritative hint, not the answer (SFI_ROUTER_MODE=offline makes the route authoritative for no-LLM hosts). A short phrase that merely names a real vault component is rescued to sfi.resolve so that path also consults the vault. Resolve any named component and confirm the plane before trusting an answer."
|
|
46856
47143
|
]
|
|
46857
47144
|
});
|
|
46858
47145
|
clarificationIdFor = (sourceTreeHash, route) => {
|
|
@@ -47034,6 +47321,30 @@ var init_route_question = __esm({
|
|
|
47034
47321
|
}
|
|
47035
47322
|
];
|
|
47036
47323
|
};
|
|
47324
|
+
PLAN_FAMILY = /^sfi\.(what_if_|get_impact|safe_to_delete|downstream_effects|tests_for_change|field_lineage)/;
|
|
47325
|
+
ASSESSMENT_FAMILY = /(_risk_report$|^sfi\.release_readiness|^sfi\.promotion_readiness|^sfi\.coverage_report|^sfi\.tech_debt_score|^sfi\.governor_limit_risks|^sfi\.crud_fls_audit)/;
|
|
47326
|
+
rerankForMode = (cands, mode) => {
|
|
47327
|
+
if (mode === void 0 || mode === "ask")
|
|
47328
|
+
return cands.slice(0, 8);
|
|
47329
|
+
const fam = mode === "plan" ? PLAN_FAMILY : ASSESSMENT_FAMILY;
|
|
47330
|
+
const lead = cands.filter((c2) => fam.test(c2.tool));
|
|
47331
|
+
const rest = cands.filter((c2) => !fam.test(c2.tool));
|
|
47332
|
+
return [...lead, ...rest].slice(0, 8);
|
|
47333
|
+
};
|
|
47334
|
+
guidanceForMode = (mode) => {
|
|
47335
|
+
const tail = " The candidates are an advisory shortlist, not a route \u2014 YOU pick. Resolve any named component first, ground the final answer with sfi.synthesize_answer, and never answer from a tool name alone.";
|
|
47336
|
+
switch (mode) {
|
|
47337
|
+
case "plan":
|
|
47338
|
+
return "PLAN mode: produce an ORDERED change plan. Favor the what_if_* / get_impact / safe_to_delete candidates, sequence them by dependency, and present numbered steps, each with its risk." + tail;
|
|
47339
|
+
case "assessment":
|
|
47340
|
+
return "ASSESSMENT mode: produce a full EVALUATION. Favor the *_risk_report / readiness / coverage candidates, run them, and present findings with severity + recommended actions." + tail;
|
|
47341
|
+
case "ask":
|
|
47342
|
+
return "ASK mode: answer concisely. Pick the candidate(s) that most directly answer, run them, and synthesize ONE short grounded answer." + tail;
|
|
47343
|
+
default:
|
|
47344
|
+
return "These toolCandidates are the meaning-ranked shortlist \u2014 they are PRIMARY and YOU decide. A deterministic `route` is also attached, but only as a HINT (a suggested tool order plus any resolved entity ids / suggestedArgs) \u2014 never follow it blindly. Plan: read the question \u2192 resolve any named component with sfi.resolve \u2192 pick the tool(s) to run from the candidates (sequence them if compound) \u2192 run them \u2192 ground the answer with sfi.synthesize_answer. Never answer from a tool name alone.";
|
|
47345
|
+
}
|
|
47346
|
+
};
|
|
47347
|
+
routerMode = () => (process.env.SFI_ROUTER_MODE ?? "").trim().toLowerCase() === "offline" ? "offline" : "hybrid";
|
|
47037
47348
|
routeQuestionHandler = async (ctx, input2) => {
|
|
47038
47349
|
let route = classifyQuestion(input2.question);
|
|
47039
47350
|
if (route.plane === "unknown" && route.intent === "unrouted") {
|
|
@@ -47213,6 +47524,9 @@ var init_route_question = __esm({
|
|
|
47213
47524
|
const args = i2 === primaryIdx ? route.suggestedArgs ?? {} : {};
|
|
47214
47525
|
return CORE_PROFILE_TOOLS.has(tool) ? { tool, args } : { tool: "sfi.run_analysis", args: { name: tool, args } };
|
|
47215
47526
|
}) : void 0;
|
|
47527
|
+
const wantCandidates = routerMode() === "hybrid";
|
|
47528
|
+
const toolCandidates = wantCandidates ? rerankForMode(semanticCandidates(input2.question, input2.mode !== void 0 ? 12 : 8), input2.mode) : [];
|
|
47529
|
+
const guidance = toolCandidates.length > 0 ? guidanceForMode(input2.mode) : void 0;
|
|
47216
47530
|
return ok({
|
|
47217
47531
|
data: {
|
|
47218
47532
|
route,
|
|
@@ -47220,8 +47534,12 @@ var init_route_question = __esm({
|
|
|
47220
47534
|
...entityEvidence !== void 0 ? { entityEvidence } : {},
|
|
47221
47535
|
...clarificationResolution !== void 0 ? { clarificationResolution } : {},
|
|
47222
47536
|
gapLogged: logged !== null,
|
|
47223
|
-
rendered: renderRouteMarkdown(route)
|
|
47537
|
+
rendered: toolCandidates.length > 0 ? `${renderRouteMarkdown(route)}
|
|
47538
|
+
|
|
47539
|
+
**Candidate tools (the shortlist \u2014 ranked by meaning; the route above is only a hint). You pick, then run:** ${toolCandidates.map((c2) => c2.tool).join(", ")}` : renderRouteMarkdown(route),
|
|
47224
47540
|
trust: routeTrust(),
|
|
47541
|
+
...toolCandidates.length > 0 ? { toolCandidates } : {},
|
|
47542
|
+
...guidance !== void 0 ? { guidance } : {},
|
|
47225
47543
|
...invoke !== void 0 ? { invoke } : {}
|
|
47226
47544
|
},
|
|
47227
47545
|
vaultState: {
|
|
@@ -50293,9 +50611,9 @@ var init_tests_for_change = __esm({
|
|
|
50293
50611
|
}
|
|
50294
50612
|
const nodeCache = /* @__PURE__ */ new Map();
|
|
50295
50613
|
const loadNode2 = async (id) => {
|
|
50296
|
-
const
|
|
50297
|
-
if (
|
|
50298
|
-
return ok(
|
|
50614
|
+
const cached2 = nodeCache.get(id);
|
|
50615
|
+
if (cached2 !== void 0)
|
|
50616
|
+
return ok(cached2);
|
|
50299
50617
|
const r2 = await getNodeById(ctx.graph, id);
|
|
50300
50618
|
if (!r2.ok) {
|
|
50301
50619
|
return err({ kind: "internal", message: `graph query failed: ${r2.error.message}` });
|
|
@@ -53191,7 +53509,7 @@ var init_what_if_remove_picklist_value = __esm({
|
|
|
53191
53509
|
|
|
53192
53510
|
// ../mcp/dist/src/tools/what-if-split-profile.js
|
|
53193
53511
|
import { z as z138 } from "zod";
|
|
53194
|
-
var PROFILE_PREFIX2, PERMISSION_SET_PREFIX, SPLIT_DEFAULT_LIMIT, SPLIT_MAX_LIMIT, DISCLOSURE18, whatIfSplitProfileInputSchema,
|
|
53512
|
+
var PROFILE_PREFIX2, PERMISSION_SET_PREFIX, SPLIT_DEFAULT_LIMIT, SPLIT_MAX_LIMIT, DISCLOSURE18, whatIfSplitProfileInputSchema, tokenize7, makeCandidate, resolveTargets, overlapCount, keywordMatch, domainClusterMatch, assignGrant, splitGrants, fetchProfile2, sortAssignments, sortUnassigned, whatIfSplitProfileHandler;
|
|
53195
53513
|
var init_what_if_split_profile = __esm({
|
|
53196
53514
|
"../mcp/dist/src/tools/what-if-split-profile.js"() {
|
|
53197
53515
|
"use strict";
|
|
@@ -53209,7 +53527,7 @@ var init_what_if_split_profile = __esm({
|
|
|
53209
53527
|
limit: z138.number().int().min(1).max(SPLIT_MAX_LIMIT).optional(),
|
|
53210
53528
|
offset: z138.number().int().min(0).optional()
|
|
53211
53529
|
});
|
|
53212
|
-
|
|
53530
|
+
tokenize7 = (raw) => {
|
|
53213
53531
|
const spaced = raw.replace(/[._\-/]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2");
|
|
53214
53532
|
return spaced.split(/\s+/).map((t2) => t2.toLowerCase().trim()).filter((t2) => t2.length > 0 && t2 !== "c" && t2 !== "__c");
|
|
53215
53533
|
};
|
|
@@ -53218,7 +53536,7 @@ var init_what_if_split_profile = __esm({
|
|
|
53218
53536
|
return {
|
|
53219
53537
|
id,
|
|
53220
53538
|
apiName: stripped,
|
|
53221
|
-
tokens: new Set(
|
|
53539
|
+
tokens: new Set(tokenize7(stripped))
|
|
53222
53540
|
};
|
|
53223
53541
|
};
|
|
53224
53542
|
resolveTargets = async (ctx, targetIds) => {
|
|
@@ -53265,7 +53583,7 @@ var init_what_if_split_profile = __esm({
|
|
|
53265
53583
|
return count;
|
|
53266
53584
|
};
|
|
53267
53585
|
keywordMatch = (settingId, candidates) => {
|
|
53268
|
-
const settingTokens = new Set(
|
|
53586
|
+
const settingTokens = new Set(tokenize7(settingId));
|
|
53269
53587
|
if (settingTokens.size === 0)
|
|
53270
53588
|
return null;
|
|
53271
53589
|
let best = null;
|
|
@@ -53289,7 +53607,7 @@ var init_what_if_split_profile = __esm({
|
|
|
53289
53607
|
}
|
|
53290
53608
|
if (parent === null || parent.length === 0)
|
|
53291
53609
|
return null;
|
|
53292
|
-
const parentTokens = new Set(
|
|
53610
|
+
const parentTokens = new Set(tokenize7(parent));
|
|
53293
53611
|
if (parentTokens.size === 0)
|
|
53294
53612
|
return null;
|
|
53295
53613
|
for (const candidate of candidates) {
|
|
@@ -56554,7 +56872,7 @@ var init_tools = __esm({
|
|
|
56554
56872
|
},
|
|
56555
56873
|
{
|
|
56556
56874
|
name: "sfi.route_question",
|
|
56557
|
-
description: "Front-door router:
|
|
56875
|
+
description: "Front-door router: for a plain-language question, surface a meaning-ranked shortlist of the sfi.* tools that can answer it \u2014 your host LLM picks which to run \u2014 plus the plane it belongs to (vault | live | hybrid | unknown), so the user never types a tool name. Read-only; it advises, it does not answer. Compound questions carry step ids and `dependsOn` edges: independent steps may run in parallel; a `then`-linked step waits for its prerequisite. On ambiguity it fails closed with `executionBlocked`, a clarification id, and offered options; resume deterministically by calling again with the exact same question plus `clarificationResponse: { clarificationId, selection }`. Stale ids and invented selections are rejected. 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; under `SFI_TOOL_PROFILE=core` the response also carries `invoke`: the routed tools as EXECUTABLE calls (core tools direct, everything else as the byte-identical `sfi.run_analysis` gateway envelope, suggestedArgs threaded) (a short phrase that merely NAMES a real vault component, with no question, is instead routed to sfi.resolve rather than 'unknown'). In the default hybrid mode the meaning-ranked `toolCandidates` are PRIMARY: every routable question carries the shortlist (offline TF-IDF over the capability map, no neural model, no network) plus a `guidance` line stating the loop YOU own \u2014 read the candidates \u2192 resolve any named component \u2192 pick/sequence the tool(s) \u2192 run them \u2192 ground via sfi.synthesize_answer. YOU decide which to run; the deterministic `route` rides along only as a non-authoritative HINT (suggested tool order + any resolved entity / suggestedArgs). Set `SFI_ROUTER_MODE=offline` for a deterministic, no-LLM route (Design A) where the route is authoritative and candidates are omitted \u2014 for CI / air-gapped hosts. An optional `mode` ('ask' | 'plan' | 'assessment') tailors the guidance and reranks the candidates toward that mode's family \u2014 'plan' favors the what_if_* / impact tools (an ordered change plan), 'assessment' favors the *_risk_report / readiness / coverage tools (a full evaluation), 'ask' is a quick grounded answer. Call this first on a vague/broad question to decide which tool(s) to run.",
|
|
56558
56876
|
inputSchema: ROUTE_QUESTION_INPUT_SCHEMA
|
|
56559
56877
|
},
|
|
56560
56878
|
{
|
|
@@ -58026,7 +58344,7 @@ var init_server = __esm({
|
|
|
58026
58344
|
|
|
58027
58345
|
How to use it well:
|
|
58028
58346
|
- To orient a fresh session, or to answer "what can you do / what can I ask?", call \`sfi.capabilities\` (no arguments). It returns the categorized capability map, the live tool count, and the recommended conversational pattern.
|
|
58029
|
-
- For a vague, broad, or compound question ("how many Accounts", "who can edit SSN", "which reports are useless", "what runs on save"), call \`sfi.route_question\` FIRST.
|
|
58347
|
+
- For a vague, broad, or compound question ("how many Accounts", "who can edit SSN", "which reports are useless", "what runs on save"), call \`sfi.route_question\` FIRST. In the default hybrid mode it returns \`toolCandidates\` \u2014 a meaning-ranked shortlist of tools \u2014 as the PRIMARY output, plus a \`guidance\` line: YOU read the candidates, resolve any named component, pick/sequence the tool(s), run them, and ground via \`sfi.synthesize_answer\`. The deterministic \`route\` (plane, ordered tools, dependency-aware plan) rides along as a non-authoritative HINT \u2014 use it to inform your pick, not as a command. If \`executionBlocked\` is true, STOP and ask \`route.clarification.question\` before running any routed tool; resume only with the exact offered \`clarificationId\` + selection, never an invented option. When neither the candidates nor the route place a question, tell the user the capability is not built rather than guessing. (A no-LLM host can set \`SFI_ROUTER_MODE=offline\` to make the deterministic route authoritative and omit candidates.)
|
|
58030
58348
|
- When the user names a component informally ("the email field", "the payment object", a typo), call \`sfi.resolve\` FIRST. It returns ranked candidates with a disposition: exact | ambiguous | none. Never guess a canonical id from memory. On \`ambiguous\`, ask the user to pick from the candidates; on \`none\`, offer \`/sfi-refresh\` (the vault may be stale) or stop \u2014 never fabricate a match.
|
|
58031
58349
|
- Every org artifact you name must come from an \`sfi.*\` tool call and be cited with its canonical id (e.g. \`CustomField:Account.Industry__c\`). Disclose provenance per claim: \`offline_snapshot\` for vault answers, \`live_org\` for live answers (stamp the as-of time from \`trust.freshness\`), \`hybrid\` when you fuse both \u2014 never let a live count imply the vault proved something, or vice-versa.
|
|
58032
58350
|
- Answers are only as fresh as the last refresh; if \`sfi.health_check\` reports stale or missing, tell the user to run \`/sfi-refresh\`. Record-level data (counts, samples, field population, org limits, inactive users) is LIVE, read-only, and queried at call time. The live plane is opt-in PER ORG: enable it once with \`sfi.live_consent { grant: true }\` (persists across sessions; still strictly read-only) \u2014 or SFI_LIVE_PLANE_ENABLED=1, or \`liveEnabled: true\` for a single call. If it is disabled, say so and offer to enable it; never infer record values from the vault.`;
|
|
@@ -58243,6 +58561,7 @@ var init_src7 = __esm({
|
|
|
58243
58561
|
init_server();
|
|
58244
58562
|
init_serve_http();
|
|
58245
58563
|
init_tools();
|
|
58564
|
+
init_semantic_funnel();
|
|
58246
58565
|
}
|
|
58247
58566
|
});
|
|
58248
58567
|
|
|
@@ -59448,7 +59767,7 @@ var init_package_version = __esm({
|
|
|
59448
59767
|
"use strict";
|
|
59449
59768
|
readCliPackageVersion = () => {
|
|
59450
59769
|
if (true)
|
|
59451
|
-
return "0.1.
|
|
59770
|
+
return "0.1.12";
|
|
59452
59771
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
59453
59772
|
try {
|
|
59454
59773
|
const raw = readFileSync3(fileURLToPath(new URL(rel, import.meta.url)), "utf8");
|
|
@@ -126240,6 +126559,7 @@ __export(refresh_exports, {
|
|
|
126240
126559
|
buildFolderedReportManifest: () => buildFolderedReportManifest,
|
|
126241
126560
|
buildRefreshPulse: () => buildRefreshPulse,
|
|
126242
126561
|
classifyForDemandRetrieve: () => classifyForDemandRetrieve,
|
|
126562
|
+
classifyRetrieveError: () => classifyRetrieveError,
|
|
126243
126563
|
computeChangeSummary: () => computeChangeSummary,
|
|
126244
126564
|
countLandedReportMembers: () => countLandedReportMembers,
|
|
126245
126565
|
formatDemandRetrieveSummary: () => formatDemandRetrieveSummary,
|
|
@@ -126250,17 +126570,20 @@ __export(refresh_exports, {
|
|
|
126250
126570
|
markDemandQueueDrains: () => markDemandQueueDrains,
|
|
126251
126571
|
objectsToExpandManifest: () => objectsToExpandManifest,
|
|
126252
126572
|
registerRefreshCommand: () => registerRefreshCommand,
|
|
126573
|
+
retrieveWithFallback: () => retrieveWithFallback,
|
|
126253
126574
|
runDemandRetrieve: () => runDemandRetrieve,
|
|
126254
126575
|
runRefresh: () => runRefresh,
|
|
126255
126576
|
runSfRetrieveSmartReports: () => runSfRetrieveSmartReports,
|
|
126256
|
-
selectManifestTypes: () => selectManifestTypes
|
|
126577
|
+
selectManifestTypes: () => selectManifestTypes,
|
|
126578
|
+
splitTypeBatch: () => splitTypeBatch,
|
|
126579
|
+
summarizeRetrieveFailures: () => summarizeRetrieveFailures
|
|
126257
126580
|
});
|
|
126258
126581
|
import { exec } from "node:child_process";
|
|
126259
126582
|
import { appendFile as appendFile4, mkdir as mkdir12, readdir as readdir6, readFile as readFile79, rename as rename6, rm as rm5, stat as stat10, writeFile as writeFile11 } from "node:fs/promises";
|
|
126260
126583
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
126261
126584
|
import { join as join26, resolve as resolve4 } from "node:path";
|
|
126262
126585
|
import { promisify as promisify4 } from "node:util";
|
|
126263
|
-
var execAsync, SF_API_VERSION2, EMPTY_COUNTS, EMPTY_SKIPPED, failed, PACKAGE_VERSION, extractCachePath, loadExtractCache, saveExtractCache, applyApexAstEdges, runRefresh, preserveFactsForSideBuild, aggregateFailuresByType, buildCoverageEntries2, decoratePendingCoverage, decorateReportsCapCoverage, countGraphTotals, fullRebuild, importGraph, persistResolveIndexBestEffort, runWithOpenGraph, appendRefreshHistory, appendRiskScores, writeOnboardingDoc, writeOrgCard, diffCounts, sumCounts, metricCounts, signedDelta, findAnnotationOrphans, buildRefreshPulse, computeChangeSummary, TOOLING_API_ENRICHED_TYPES, runToolingApiEnrichment, loadVaultConfig, METADATA_API_NAME2, toApiName, SF_MAX_BUFFER, STANDARD_OBJECTS_TO_MODEL, manifestMembersForType, appendStandardObjectDescribeFields, AUTOMATION_EDGE_TYPES, objectsToExpandManifest, buildPackageXml, getOrgSupportedTypes, selectManifestTypes, runSfRetrieve, computeReconciledTypes, runSfRetrieveObjects, buildFolderedReportManifest, REPORT_META_SUFFIXES, REPORT_DIR_MARKERS, countLandedReportMembers, collectReportMetaFiles, runSfRetrieveFolderedReports, reportsCap, runSfRetrieveSmartReports, formatFailure, formatBlock, formatChangeSummary, formatRefreshSummary, formatReportsCapSummary, SKIPPED_TOP_N, KNOWN_UNMODELED_SKIP_DIRS, formatSkippedWarning, formatToolingApiSummary, demandCoverageStatusOf, DEMAND_REFUSAL_REASON, classifyForDemandRetrieve, markDemandQueueDrains, runDemandRetrieve, formatDemandRetrieveSummary, registerRefreshCommand;
|
|
126586
|
+
var execAsync, SF_API_VERSION2, EMPTY_COUNTS, EMPTY_SKIPPED, failed, PACKAGE_VERSION, extractCachePath, loadExtractCache, saveExtractCache, applyApexAstEdges, runRefresh, preserveFactsForSideBuild, aggregateFailuresByType, buildCoverageEntries2, decoratePendingCoverage, decorateReportsCapCoverage, countGraphTotals, fullRebuild, importGraph, persistResolveIndexBestEffort, runWithOpenGraph, appendRefreshHistory, appendRiskScores, writeOnboardingDoc, writeOrgCard, diffCounts, sumCounts, metricCounts, signedDelta, findAnnotationOrphans, buildRefreshPulse, computeChangeSummary, TOOLING_API_ENRICHED_TYPES, runToolingApiEnrichment, loadVaultConfig, METADATA_API_NAME2, toApiName, SF_MAX_BUFFER, STANDARD_OBJECTS_TO_MODEL, manifestMembersForType, appendStandardObjectDescribeFields, AUTOMATION_EDGE_TYPES, objectsToExpandManifest, buildPackageXml, getOrgSupportedTypes, selectManifestTypes, classifyRetrieveError, splitTypeBatch, salientErrorLine, summarizeRetrieveFailures, retrieveWithFallback, retrieveTypeBatch, runSfRetrieve, computeReconciledTypes, runSfRetrieveObjects, buildFolderedReportManifest, REPORT_META_SUFFIXES, REPORT_DIR_MARKERS, countLandedReportMembers, collectReportMetaFiles, runSfRetrieveFolderedReports, reportsCap, runSfRetrieveSmartReports, formatFailure, formatBlock, formatChangeSummary, formatRefreshSummary, formatReportsCapSummary, SKIPPED_TOP_N, KNOWN_UNMODELED_SKIP_DIRS, formatSkippedWarning, formatToolingApiSummary, demandCoverageStatusOf, DEMAND_REFUSAL_REASON, classifyForDemandRetrieve, markDemandQueueDrains, runDemandRetrieve, formatDemandRetrieveSummary, registerRefreshCommand;
|
|
126264
126587
|
var init_refresh = __esm({
|
|
126265
126588
|
"dist/src/commands/refresh.js"() {
|
|
126266
126589
|
"use strict";
|
|
@@ -126437,6 +126760,7 @@ var init_refresh = __esm({
|
|
|
126437
126760
|
});
|
|
126438
126761
|
let pullManifestTypes = null;
|
|
126439
126762
|
let sourceReconcileDeleted = 0;
|
|
126763
|
+
let retrieveFailures = [];
|
|
126440
126764
|
if (!opts.noPull) {
|
|
126441
126765
|
const priorManifest = await loadManifest(configResult.value.vaultRoot);
|
|
126442
126766
|
const priorComponentCount = priorManifest.ok ? Object.values(priorManifest.value.components ?? {}).reduce((a2, b2) => a2 + (typeof b2 === "number" ? b2 : 0), 0) : null;
|
|
@@ -126453,9 +126777,13 @@ var init_refresh = __esm({
|
|
|
126453
126777
|
return failed(started, pulled.error, []);
|
|
126454
126778
|
pullManifestTypes = pulled.value.manifestTypes;
|
|
126455
126779
|
sourceReconcileDeleted = pulled.value.deletedCount;
|
|
126780
|
+
retrieveFailures = pulled.value.failures;
|
|
126456
126781
|
if (sourceReconcileDeleted > 0) {
|
|
126457
126782
|
progress(`Reconciled source: removed ${sourceReconcileDeleted} stale file(s) deleted in the org.`);
|
|
126458
126783
|
}
|
|
126784
|
+
if (retrieveFailures.length > 0) {
|
|
126785
|
+
progress(`Partial retrieve: ${retrieveFailures.length} metadata type(s) failed and were skipped \u2014 the vault is built from what landed. ${summarizeRetrieveFailures(retrieveFailures)}`);
|
|
126786
|
+
}
|
|
126459
126787
|
}
|
|
126460
126788
|
progress("Extracting components from retrieved source...");
|
|
126461
126789
|
const prevCache = opts.incremental ? await loadExtractCache(paths.meta) : void 0;
|
|
@@ -126464,10 +126792,10 @@ var init_refresh = __esm({
|
|
|
126464
126792
|
progress(`Incremental: reused ${walked.reusedCount} unchanged file(s) from cache.`);
|
|
126465
126793
|
}
|
|
126466
126794
|
if (!opts.noPull && (requestedTypes === null || requestedTypes.has("CustomObject"))) {
|
|
126467
|
-
const
|
|
126468
|
-
if (
|
|
126469
|
-
progress(`Auto-expanding retrieve: ${
|
|
126470
|
-
const pulled2 = await runSfRetrieveObjects(targetOrg, paths.source,
|
|
126795
|
+
const expand2 = objectsToExpandManifest(walked.results);
|
|
126796
|
+
if (expand2.length > 0) {
|
|
126797
|
+
progress(`Auto-expanding retrieve: ${expand2.length} object(s) your automation references but the wildcard excluded...`);
|
|
126798
|
+
const pulled2 = await runSfRetrieveObjects(targetOrg, paths.source, expand2);
|
|
126471
126799
|
if (pulled2.ok) {
|
|
126472
126800
|
walked = await walkAndExtract(paths.source, requestedTypes, prevCache);
|
|
126473
126801
|
} else {
|
|
@@ -126552,6 +126880,7 @@ var init_refresh = __esm({
|
|
|
126552
126880
|
opts,
|
|
126553
126881
|
requestedTypes,
|
|
126554
126882
|
snapshotOnRefresh: configResult.value.snapshotOnRefresh,
|
|
126883
|
+
retrieveFailures,
|
|
126555
126884
|
...reconciledTypes !== null ? { reconciledTypes } : {},
|
|
126556
126885
|
...apexAstStats !== void 0 ? { apexAstStats } : {},
|
|
126557
126886
|
...reportsCapStats !== void 0 ? { reportsCapStats } : {}
|
|
@@ -126805,11 +127134,12 @@ var init_refresh = __esm({
|
|
|
126805
127134
|
return failed(started, `countGraphTotals: ${counted.error}`, walked.failures, EMPTY_COUNTS, walked.skippedDirectories);
|
|
126806
127135
|
}
|
|
126807
127136
|
return {
|
|
126808
|
-
status: walked.failures.length === 0 ? "success" : "partial",
|
|
127137
|
+
status: walked.failures.length === 0 && args.retrieveFailures.length === 0 ? "success" : "partial",
|
|
126809
127138
|
counts: counted.value,
|
|
126810
127139
|
skippedDirectories: walked.skippedDirectories,
|
|
126811
127140
|
errors: walked.failures,
|
|
126812
127141
|
durationMs: Date.now() - started,
|
|
127142
|
+
...args.retrieveFailures.length > 0 ? { retrieveFailures: args.retrieveFailures } : {},
|
|
126813
127143
|
...toolingApiSummary !== void 0 ? { toolingApi: toolingApiSummary } : {},
|
|
126814
127144
|
...args.reportsCapStats !== void 0 ? { reportsCap: args.reportsCapStats } : {}
|
|
126815
127145
|
};
|
|
@@ -126954,11 +127284,12 @@ var init_refresh = __esm({
|
|
|
126954
127284
|
}
|
|
126955
127285
|
}
|
|
126956
127286
|
return {
|
|
126957
|
-
status: walked.failures.length === 0 ? "success" : "partial",
|
|
127287
|
+
status: walked.failures.length === 0 && args.retrieveFailures.length === 0 ? "success" : "partial",
|
|
126958
127288
|
counts,
|
|
126959
127289
|
skippedDirectories: walked.skippedDirectories,
|
|
126960
127290
|
errors: walked.failures,
|
|
126961
127291
|
durationMs: Date.now() - started,
|
|
127292
|
+
...args.retrieveFailures.length > 0 ? { retrieveFailures: args.retrieveFailures } : {},
|
|
126962
127293
|
...changeSummary !== void 0 ? { changeSummary } : {},
|
|
126963
127294
|
...pulse !== void 0 ? { pulse } : {},
|
|
126964
127295
|
...toolingApiSummary !== void 0 ? { toolingApi: toolingApiSummary } : {},
|
|
@@ -127380,6 +127711,86 @@ ${card.body}
|
|
|
127380
127711
|
dropped: candidates.filter((type) => !orgTypes.has(toApiName(type)))
|
|
127381
127712
|
};
|
|
127382
127713
|
};
|
|
127714
|
+
classifyRetrieveError = (message) => {
|
|
127715
|
+
const GLOBAL_SIGNALS = [
|
|
127716
|
+
/no authorization|not authorized|no auth information|expired access\/refresh token|invalid[_ ]grant/i,
|
|
127717
|
+
/org ?login|sfdx[_ ]?login|requires? you to (?:re-?)?authenticate|session expired/i,
|
|
127718
|
+
/does not contain a valid salesforce dx project/i,
|
|
127719
|
+
/no default (?:environment|org)|no target-?org|no org configured|requires a target org/i
|
|
127720
|
+
];
|
|
127721
|
+
return GLOBAL_SIGNALS.some((re2) => re2.test(message)) ? "global" : "per-type";
|
|
127722
|
+
};
|
|
127723
|
+
splitTypeBatch = (types) => {
|
|
127724
|
+
const mid = Math.floor(types.length / 2);
|
|
127725
|
+
return [types.slice(0, mid), types.slice(mid)];
|
|
127726
|
+
};
|
|
127727
|
+
salientErrorLine = (error) => {
|
|
127728
|
+
const lines = error.split("\n").map((l2) => l2.trim()).filter((l2) => l2.length > 0);
|
|
127729
|
+
const named = lines.find((l2) => /^Error\b|Error \(|^ERROR\b|UnsafeFilepathError|INVALID_TYPE/.test(l2));
|
|
127730
|
+
if (named !== void 0)
|
|
127731
|
+
return named;
|
|
127732
|
+
const nonWrapper = lines.find((l2) => !l2.startsWith("Command failed:"));
|
|
127733
|
+
return nonWrapper ?? lines[0] ?? error.trim();
|
|
127734
|
+
};
|
|
127735
|
+
summarizeRetrieveFailures = (failures) => {
|
|
127736
|
+
if (failures.length === 0)
|
|
127737
|
+
return "";
|
|
127738
|
+
const uniqueReasons = new Set(failures.map((f2) => salientErrorLine(f2.error)));
|
|
127739
|
+
if (uniqueReasons.size === 1) {
|
|
127740
|
+
const [reason] = [...uniqueReasons];
|
|
127741
|
+
const [first] = failures;
|
|
127742
|
+
return failures.length === 1 && first !== void 0 ? `${first.type}: ${reason}` : reason ?? "";
|
|
127743
|
+
}
|
|
127744
|
+
return failures.map((f2) => `${f2.type} (${salientErrorLine(f2.error)})`).join("; ");
|
|
127745
|
+
};
|
|
127746
|
+
retrieveWithFallback = async (allTypes, retrieveBatch) => {
|
|
127747
|
+
const succeeded = [];
|
|
127748
|
+
const failures = [];
|
|
127749
|
+
let deletedCount = 0;
|
|
127750
|
+
const attempt = async (types) => {
|
|
127751
|
+
if (types.length === 0)
|
|
127752
|
+
return;
|
|
127753
|
+
const result = await retrieveBatch(types);
|
|
127754
|
+
if (result.ok) {
|
|
127755
|
+
succeeded.push(...types);
|
|
127756
|
+
deletedCount += result.value.deletedCount;
|
|
127757
|
+
return;
|
|
127758
|
+
}
|
|
127759
|
+
if (types.length === 1 || classifyRetrieveError(result.error) === "global") {
|
|
127760
|
+
for (const type of types)
|
|
127761
|
+
failures.push({ type, error: result.error });
|
|
127762
|
+
return;
|
|
127763
|
+
}
|
|
127764
|
+
const [left, right] = splitTypeBatch(types);
|
|
127765
|
+
await attempt(left);
|
|
127766
|
+
await attempt(right);
|
|
127767
|
+
};
|
|
127768
|
+
await attempt(allTypes);
|
|
127769
|
+
return { succeeded, deletedCount, failures };
|
|
127770
|
+
};
|
|
127771
|
+
retrieveTypeBatch = async (targetOrg, sourceDir, types) => {
|
|
127772
|
+
const stamp = `${Date.now()}-${types.length}-${types[0] ?? "batch"}`;
|
|
127773
|
+
const manifestPath = join26(tmpdir2(), `sfi-refresh-package-${stamp}.xml`);
|
|
127774
|
+
const projectDir = join26(tmpdir2(), `sfi-retrieve-${stamp}`);
|
|
127775
|
+
const pkgDir = join26(projectDir, "force-app");
|
|
127776
|
+
await mkdir12(pkgDir, { recursive: true });
|
|
127777
|
+
await writeFile11(join26(projectDir, "sfdx-project.json"), `{"packageDirectories":[{"path":"force-app","default":true}],"sourceApiVersion":"${SF_API_VERSION2}"}
|
|
127778
|
+
`, "utf8");
|
|
127779
|
+
await writeFile11(manifestPath, buildPackageXml(types), "utf8");
|
|
127780
|
+
try {
|
|
127781
|
+
await execAsync(`sf project retrieve start --manifest "${manifestPath}" --target-org "${targetOrg}"`, { maxBuffer: SF_MAX_BUFFER, cwd: projectDir });
|
|
127782
|
+
const reconcile = await reconcileSourceDeletions(sourceDir, pkgDir, new Set(types));
|
|
127783
|
+
await syncAuthoritativeRetrieveIntoSource(sourceDir, pkgDir);
|
|
127784
|
+
return ok({ deletedCount: reconcile.deletedCount });
|
|
127785
|
+
} catch (cause) {
|
|
127786
|
+
return err(cause instanceof Error ? cause.message : String(cause));
|
|
127787
|
+
} finally {
|
|
127788
|
+
await rm5(manifestPath, { force: true }).catch(() => {
|
|
127789
|
+
});
|
|
127790
|
+
await rm5(projectDir, { recursive: true, force: true }).catch(() => {
|
|
127791
|
+
});
|
|
127792
|
+
}
|
|
127793
|
+
};
|
|
127383
127794
|
runSfRetrieve = async (targetOrg, sourceDir, requestedTypes) => {
|
|
127384
127795
|
const orgTypes = await getOrgSupportedTypes(targetOrg);
|
|
127385
127796
|
if (orgTypes === null) {
|
|
@@ -127395,22 +127806,15 @@ ${card.body}
|
|
|
127395
127806
|
if (manifestTypes.length === 0) {
|
|
127396
127807
|
return err(`No retrievable metadata types for ${targetOrg} after intersecting with the org's describe.`);
|
|
127397
127808
|
}
|
|
127398
|
-
const
|
|
127399
|
-
|
|
127400
|
-
|
|
127401
|
-
try {
|
|
127402
|
-
await execAsync(`sf project retrieve start --manifest "${manifestPath}" --target-org "${targetOrg}" --output-dir "${retrieveDir}"`, { maxBuffer: SF_MAX_BUFFER });
|
|
127403
|
-
const reconcile = await reconcileSourceDeletions(sourceDir, retrieveDir, new Set(manifestTypes));
|
|
127404
|
-
await syncAuthoritativeRetrieveIntoSource(sourceDir, retrieveDir);
|
|
127405
|
-
return ok({ manifestTypes, deletedCount: reconcile.deletedCount });
|
|
127406
|
-
} catch (cause) {
|
|
127407
|
-
return err(`sf project retrieve failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
127408
|
-
} finally {
|
|
127409
|
-
await rm5(manifestPath, { force: true }).catch(() => {
|
|
127410
|
-
});
|
|
127411
|
-
await rm5(retrieveDir, { recursive: true, force: true }).catch(() => {
|
|
127412
|
-
});
|
|
127809
|
+
const outcome = await retrieveWithFallback(manifestTypes, (types) => retrieveTypeBatch(targetOrg, sourceDir, types));
|
|
127810
|
+
if (outcome.succeeded.length === 0) {
|
|
127811
|
+
return err(`sf project retrieve failed: ${summarizeRetrieveFailures(outcome.failures)}`);
|
|
127413
127812
|
}
|
|
127813
|
+
return ok({
|
|
127814
|
+
manifestTypes: outcome.succeeded,
|
|
127815
|
+
deletedCount: outcome.deletedCount,
|
|
127816
|
+
failures: outcome.failures
|
|
127817
|
+
});
|
|
127414
127818
|
};
|
|
127415
127819
|
computeReconciledTypes = (pullManifestTypes, sourceRoot, failures) => {
|
|
127416
127820
|
if (pullManifestTypes === null || pullManifestTypes.length === 0)
|
|
@@ -127699,6 +128103,9 @@ ${typesXml}
|
|
|
127699
128103
|
if (result.errors.length > 0) {
|
|
127700
128104
|
lines.push("", `Errors (${result.errors.length}):`, ...result.errors.map(formatFailure));
|
|
127701
128105
|
}
|
|
128106
|
+
if (result.retrieveFailures !== void 0 && result.retrieveFailures.length > 0) {
|
|
128107
|
+
lines.push("", `Partial retrieve \u2014 ${result.retrieveFailures.length} metadata type(s) skipped (the org may have rejected them; the vault is built from what landed):`, ...result.retrieveFailures.map((f2) => ` ${f2.type}: ${salientErrorLine(f2.error)}`));
|
|
128108
|
+
}
|
|
127702
128109
|
if (result.fatalError !== void 0) {
|
|
127703
128110
|
lines.push("", `Fatal: ${result.fatalError}`);
|
|
127704
128111
|
}
|
|
@@ -128960,6 +129367,14 @@ var registerStatusCommand = (program) => {
|
|
|
128960
129367
|
// dist/src/commands/doctor.js
|
|
128961
129368
|
var execAsync2 = promisify5(exec2);
|
|
128962
129369
|
var DEFAULT_VAULT_ROOT2 = "org-kb";
|
|
129370
|
+
var parseSfCliVersion = (versionLine) => {
|
|
129371
|
+
const m2 = /(\d+)\.(\d+)\.(\d+)/.exec(versionLine);
|
|
129372
|
+
if (m2 === null)
|
|
129373
|
+
return null;
|
|
129374
|
+
return [Number(m2[1]), Number(m2[2]), Number(m2[3])];
|
|
129375
|
+
};
|
|
129376
|
+
var isLegacySfdxToolbelt = (versionLine) => /sfdx-cli\//i.test(versionLine);
|
|
129377
|
+
var formatVersion = (v2) => v2.join(".");
|
|
128963
129378
|
var summarizeRouteGaps = async (logFile) => {
|
|
128964
129379
|
let raw;
|
|
128965
129380
|
try {
|
|
@@ -129031,6 +129446,15 @@ var runDoctor = async (opts) => {
|
|
|
129031
129446
|
}
|
|
129032
129447
|
}
|
|
129033
129448
|
}
|
|
129449
|
+
if (sfDetail !== null && isLegacySfdxToolbelt(sfDetail)) {
|
|
129450
|
+
const parsed = parseSfCliVersion(sfDetail);
|
|
129451
|
+
checks.push({
|
|
129452
|
+
name: "Salesforce CLI version",
|
|
129453
|
+
status: "warn",
|
|
129454
|
+
detail: `legacy sfdx-cli${parsed !== null ? ` ${formatVersion(parsed)}` : ""} detected \u2014 it predates \`sf project retrieve start\` (used by refresh) and cannot drive a vault refresh`,
|
|
129455
|
+
fix: "Install the modern unified CLI: npm install --global @salesforce/cli@latest"
|
|
129456
|
+
});
|
|
129457
|
+
}
|
|
129034
129458
|
if (sfOnPath) {
|
|
129035
129459
|
checks.push({ name: "Salesforce CLI", status: "pass", detail: sfDetail ?? "installed" });
|
|
129036
129460
|
} else if (sfDetail !== null) {
|
|
@@ -129578,11 +130002,20 @@ init_dist();
|
|
|
129578
130002
|
init_src7();
|
|
129579
130003
|
init_src2();
|
|
129580
130004
|
import { execFile as execFile4 } from "node:child_process";
|
|
129581
|
-
import { stat as stat14 } from "node:fs/promises";
|
|
130005
|
+
import { readFile as readFile83, stat as stat14 } from "node:fs/promises";
|
|
129582
130006
|
import { resolve as resolve10 } from "node:path";
|
|
129583
130007
|
import { promisify as promisify7 } from "node:util";
|
|
129584
130008
|
var DEFAULT_VAULT_ROOT4 = "org-kb";
|
|
129585
130009
|
var SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
130010
|
+
var readBoundOrg = async (configPath) => {
|
|
130011
|
+
try {
|
|
130012
|
+
const raw = await readFile83(configPath, "utf8");
|
|
130013
|
+
const parsed = JSON.parse(raw);
|
|
130014
|
+
return typeof parsed.targetOrg === "string" ? parsed.targetOrg : null;
|
|
130015
|
+
} catch {
|
|
130016
|
+
return null;
|
|
130017
|
+
}
|
|
130018
|
+
};
|
|
129586
130019
|
var nodeExecFile3 = promisify7(execFile4);
|
|
129587
130020
|
var defaultListOrgs = async () => {
|
|
129588
130021
|
try {
|
|
@@ -129608,11 +130041,12 @@ var defaultListOrgs = async () => {
|
|
|
129608
130041
|
}
|
|
129609
130042
|
};
|
|
129610
130043
|
var prepareMcp = async (opts) => {
|
|
129611
|
-
const vaultRoot = resolve10(opts.cwd, DEFAULT_VAULT_ROOT4);
|
|
130044
|
+
const vaultRoot = opts.vaultRoot !== void 0 ? resolve10(opts.cwd, opts.vaultRoot) : resolve10(opts.cwd, DEFAULT_VAULT_ROOT4);
|
|
129612
130045
|
const paths = vaultPaths(vaultRoot);
|
|
129613
130046
|
if (!await pathExists6(paths.config)) {
|
|
129614
130047
|
const orgs = await (opts.listOrgs ?? defaultListOrgs)();
|
|
129615
|
-
const
|
|
130048
|
+
const where = opts.vaultRoot !== void 0 ? ` at ${vaultRoot}` : "";
|
|
130049
|
+
const base = `No vault${where}. Run \`sfi init\` followed by \`sfi refresh\`, or point \`sfi mcp --vault <path>\` at an existing org-kb.`;
|
|
129616
130050
|
const hint = orgs.length > 0 ? ` You are authenticated to ${orgs.length} org(s): ${orgs.slice(0, 8).join(", ")}${orgs.length > 8 ? ", \u2026" : ""}. Run \`sfi init\` to pick the one you want and build its knowledge base \u2014 live answers attach to a vault's org, so the product never guesses which of your orgs to query.` : "";
|
|
129617
130051
|
return err({ kind: "no-vault", message: base + hint });
|
|
129618
130052
|
}
|
|
@@ -129620,7 +130054,13 @@ var prepareMcp = async (opts) => {
|
|
|
129620
130054
|
if (!ctxResult.ok) {
|
|
129621
130055
|
return err({ kind: "buildContext-failed", message: ctxResult.error.message });
|
|
129622
130056
|
}
|
|
129623
|
-
|
|
130057
|
+
const targetOrg = await readBoundOrg(paths.config);
|
|
130058
|
+
return ok({
|
|
130059
|
+
ctx: ctxResult.value,
|
|
130060
|
+
server: createServer(ctxResult.value),
|
|
130061
|
+
vaultRoot,
|
|
130062
|
+
targetOrg
|
|
130063
|
+
});
|
|
129624
130064
|
};
|
|
129625
130065
|
var pathExists6 = async (path) => {
|
|
129626
130066
|
try {
|
|
@@ -129632,14 +130072,19 @@ var pathExists6 = async (path) => {
|
|
|
129632
130072
|
};
|
|
129633
130073
|
var isEnoent5 = (cause) => typeof cause === "object" && cause !== null && "code" in cause && cause.code === "ENOENT";
|
|
129634
130074
|
var registerMcpCommand = (program) => {
|
|
129635
|
-
program.command("mcp").description("Run the MCP server backing the org-kb vault").action(async () => {
|
|
129636
|
-
const prepared = await prepareMcp({
|
|
130075
|
+
program.command("mcp").description("Run the MCP server backing the org-kb vault").option("--vault <path>", "Serve a specific org-kb vault instead of ./org-kb (bind a project to the right org)").action(async (cmdOpts) => {
|
|
130076
|
+
const prepared = await prepareMcp({
|
|
130077
|
+
cwd: process.cwd(),
|
|
130078
|
+
...cmdOpts.vault !== void 0 ? { vaultRoot: cmdOpts.vault } : {}
|
|
130079
|
+
});
|
|
129637
130080
|
if (!prepared.ok) {
|
|
129638
130081
|
process.stderr.write(`sfi mcp: ${prepared.error.message}
|
|
129639
130082
|
`);
|
|
129640
130083
|
process.exit(1);
|
|
129641
130084
|
}
|
|
129642
|
-
const { ctx, server } = prepared.value;
|
|
130085
|
+
const { ctx, server, vaultRoot, targetOrg } = prepared.value;
|
|
130086
|
+
process.stderr.write(`sfi mcp: serving vault ${vaultRoot}${targetOrg !== null ? ` (org: ${targetOrg})` : " (no targetOrg in config)"}
|
|
130087
|
+
`);
|
|
129643
130088
|
const shutdownOnce = makeShutdownOnce(ctx);
|
|
129644
130089
|
for (const signal of SHUTDOWN_SIGNALS) {
|
|
129645
130090
|
process.on(signal, () => {
|
|
@@ -129916,7 +130361,7 @@ init_vault_git();
|
|
|
129916
130361
|
init_watch();
|
|
129917
130362
|
var readVersion = () => {
|
|
129918
130363
|
if (true)
|
|
129919
|
-
return "0.1.
|
|
130364
|
+
return "0.1.12";
|
|
129920
130365
|
const pkgUrl = new URL("../../package.json", import.meta.url);
|
|
129921
130366
|
const raw = readFileSync5(fileURLToPath2(pkgUrl), "utf8");
|
|
129922
130367
|
const parsed = JSON.parse(raw);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sf-intelligence",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Offline-first, MCP-first knowledge base for a Salesforce org. Ask about your org's metadata, dependencies, permissions, and automation
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"description": "Offline-first, MCP-first knowledge base for a Salesforce org. Ask about your org's metadata, dependencies, permissions, and automation \u2014 grounded in real retrieved metadata. Ships the sfi CLI and an MCP server.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://salesforce-intelligence.pages.dev",
|
|
7
7
|
"keywords": [
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"bin",
|
|
37
37
|
"README.md"
|
|
38
38
|
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc --build && node build.mjs",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"prepublishOnly": "node ../../scripts/prepublish-check.mjs"
|
|
43
|
+
},
|
|
39
44
|
"dependencies": {
|
|
40
45
|
"@duckdb/node-api": "1.5.3-r.2",
|
|
41
46
|
"@inquirer/prompts": "^7.0.0",
|
|
@@ -45,21 +50,17 @@
|
|
|
45
50
|
"zod": "^3.23.0"
|
|
46
51
|
},
|
|
47
52
|
"devDependencies": {
|
|
53
|
+
"@sf-intelligence/contracts": "workspace:*",
|
|
54
|
+
"@sf-intelligence/core": "workspace:*",
|
|
55
|
+
"@sf-intelligence/extractors": "workspace:*",
|
|
56
|
+
"@sf-intelligence/graph": "workspace:*",
|
|
57
|
+
"@sf-intelligence/mcp": "workspace:*",
|
|
58
|
+
"@sf-intelligence/parsers": "workspace:*",
|
|
59
|
+
"@sf-intelligence/patterns": "workspace:*",
|
|
60
|
+
"@sf-intelligence/renderers": "workspace:*",
|
|
61
|
+
"@sf-intelligence/tooling-api": "workspace:*",
|
|
62
|
+
"@sf-intelligence/vault": "workspace:*",
|
|
48
63
|
"esbuild": "^0.28.0",
|
|
49
|
-
"vitest": "^1.6.0"
|
|
50
|
-
"@sf-intelligence/contracts": "0.1.0",
|
|
51
|
-
"@sf-intelligence/core": "0.1.0",
|
|
52
|
-
"@sf-intelligence/extractors": "0.1.0",
|
|
53
|
-
"@sf-intelligence/mcp": "0.1.0",
|
|
54
|
-
"@sf-intelligence/parsers": "0.1.0",
|
|
55
|
-
"@sf-intelligence/renderers": "0.1.0",
|
|
56
|
-
"@sf-intelligence/tooling-api": "0.1.0",
|
|
57
|
-
"@sf-intelligence/vault": "0.1.0",
|
|
58
|
-
"@sf-intelligence/patterns": "0.1.0",
|
|
59
|
-
"@sf-intelligence/graph": "0.1.0"
|
|
60
|
-
},
|
|
61
|
-
"scripts": {
|
|
62
|
-
"build": "tsc --build && node build.mjs",
|
|
63
|
-
"test": "vitest run"
|
|
64
|
+
"vitest": "^1.6.0"
|
|
64
65
|
}
|
|
65
|
-
}
|
|
66
|
+
}
|