sf-intelligence 0.1.24 → 0.1.26

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/README.md CHANGED
@@ -75,14 +75,14 @@ guessing.
75
75
  ## Documentation
76
76
 
77
77
  Full guides, capabilities, the tool catalog, and configuration:
78
- **https://salesforce-intelligence.pages.dev**
78
+ **https://sfi.auditforce.cloud**
79
79
 
80
- - [Getting started](https://salesforce-intelligence.pages.dev/getting-started.html)
81
- - [Capabilities](https://salesforce-intelligence.pages.dev/capabilities.html) · [All tools](https://salesforce-intelligence.pages.dev/tools.html)
82
- - [Configuration](https://salesforce-intelligence.pages.dev/configuration.html) · [FAQ](https://salesforce-intelligence.pages.dev/faq.html)
83
- - [Quality & trust](https://salesforce-intelligence.pages.dev/trust.html)
80
+ - [Getting started](https://sfi.auditforce.cloud/getting-started.html)
81
+ - [Capabilities](https://sfi.auditforce.cloud/capabilities.html) · [All tools](https://sfi.auditforce.cloud/tools.html)
82
+ - [Configuration](https://sfi.auditforce.cloud/configuration.html) · [FAQ](https://sfi.auditforce.cloud/faq.html)
83
+ - [Quality & trust](https://sfi.auditforce.cloud/trust.html)
84
84
 
85
85
  ## License
86
86
 
87
87
  MIT + Commons Clause — see the `LICENSE` file shipped in this package, or
88
- <https://salesforce-intelligence.pages.dev/licensing.html>.
88
+ <https://sfi.auditforce.cloud/licensing.html>.
package/dist/index.js CHANGED
@@ -964,6 +964,11 @@ var init_queries = __esm({
964
964
  if (options?.recordTriggered === true) {
965
965
  out += ` AND json_extract_string(properties_json, '$.triggerType') LIKE 'Record%'`;
966
966
  }
967
+ if (options?.descriptionPresence === "present") {
968
+ out += ` AND coalesce(json_extract_string(properties_json, '$.description'), '') <> ''`;
969
+ } else if (options?.descriptionPresence === "absent") {
970
+ out += ` AND coalesce(json_extract_string(properties_json, '$.description'), '') = ''`;
971
+ }
967
972
  return out;
968
973
  };
969
974
  parseProperties = (raw) => {
@@ -13056,8 +13061,22 @@ var init_enterprise_metadata = __esm({
13056
13061
  });
13057
13062
  return ok({ nodes: [node], edges: [...fieldRefEdges, ...childRefEdges, ...visibleToEdges] });
13058
13063
  };
13059
- extractReport = (path) => extractEnterpriseMetadata(path, { type: "Report", suffix: ".report-meta.xml" });
13060
- extractDashboard = (path) => extractEnterpriseMetadata(path, { type: "Dashboard", suffix: ".dashboard-meta.xml" });
13064
+ extractReport = (path) => extractEnterpriseMetadata(path, {
13065
+ type: "Report",
13066
+ suffix: ".report-meta.xml",
13067
+ // Reports carry a top-level <description> in source. Capture it so
13068
+ // "which reports have no description" is answerable and get_component
13069
+ // can surface the report's stated purpose. Omitted when absent — the
13070
+ // "extracted, none present" signal (vs a not-modeled type).
13071
+ extraProperties: ["description"]
13072
+ });
13073
+ extractDashboard = (path) => extractEnterpriseMetadata(path, {
13074
+ type: "Dashboard",
13075
+ suffix: ".dashboard-meta.xml",
13076
+ // Dashboards carry a top-level <description>. Same capture rationale as
13077
+ // Report — omitted when absent.
13078
+ extraProperties: ["description"]
13079
+ });
13061
13080
  extractListView = (path) => extractEnterpriseMetadata(path, {
13062
13081
  type: "ListView",
13063
13082
  suffix: ".listView-meta.xml",
@@ -13072,7 +13091,11 @@ var init_enterprise_metadata = __esm({
13072
13091
  // Daily Summer '22") that distinguishes versioned clones from each other.
13073
13092
  // Without this, makeNode always sets label: null and get_component has no
13074
13093
  // human-readable display name to surface in vault Markdown.
13075
- labelXmlElement: "label"
13094
+ labelXmlElement: "label",
13095
+ // ReportType XML carries a top-level <description> (nearly universal in
13096
+ // source). Capture it so custom report types disclose their purpose and
13097
+ // are queryable via missingDescription. Omitted when absent.
13098
+ extraProperties: ["description"]
13076
13099
  });
13077
13100
  extractCustomPermission = (path) => extractEnterpriseMetadata(path, { type: "CustomPermission", suffix: ".customPermission-meta.xml" });
13078
13101
  FLEXIPAGE_PAGE_TYPES = /* @__PURE__ */ new Set([
@@ -13149,6 +13172,10 @@ var init_enterprise_metadata = __esm({
13149
13172
  extractPermissionSetGroup = (path) => extractEnterpriseMetadata(path, {
13150
13173
  type: "PermissionSetGroup",
13151
13174
  suffix: ".permissionsetgroup-meta.xml",
13175
+ // PSGs carry a top-level <description>. Capture it so the group's stated
13176
+ // purpose is surfaced and queryable via missingDescription. Omitted when
13177
+ // absent.
13178
+ extraProperties: ["description"],
13152
13179
  // A PSG's effective permissions are the UNION of its member permission
13153
13180
  // sets' grants, minus the muting permission set's. Capture both so the
13154
13181
  // permission analysis can flow god-mode / object grants through the group.
@@ -52665,7 +52692,19 @@ var init_list_components = __esm({
52665
52692
  /** Flow metadata: exact match on `properties.status` (e.g. Active). */
52666
52693
  status: z90.string().min(1).optional(),
52667
52694
  /** Flow metadata: keep only record-triggered flows (`triggerType` starts with Record). */
52668
- recordTriggered: coercedOptionalBoolean
52695
+ recordTriggered: coercedOptionalBoolean,
52696
+ /**
52697
+ * Keep only components that LACK a description — `properties.description` is
52698
+ * absent, null, or empty. Answers "which reports/objects/permission-sets have
52699
+ * no description". Only trustworthy for a type whose extractor captures
52700
+ * description; a type that carries no `<description>` in Salesforce source
52701
+ * (ListView, CustomPermission, MutingPermissionSet, CustomMetadata records)
52702
+ * will match ALL of its nodes — that means "this type has no description in
52703
+ * source", not "the org failed to fill them in".
52704
+ */
52705
+ missingDescription: coercedOptionalBoolean,
52706
+ /** Keep only components that HAVE a non-empty `properties.description`. */
52707
+ hasDescription: coercedOptionalBoolean
52669
52708
  });
52670
52709
  listComponentsHandler = async (ctx, input2) => {
52671
52710
  if (input2.type === void 0) {
@@ -52674,6 +52713,13 @@ var init_list_components = __esm({
52674
52713
  message: "type is required for v0.1"
52675
52714
  });
52676
52715
  }
52716
+ if (input2.missingDescription === true && input2.hasDescription === true) {
52717
+ return err({
52718
+ kind: "invalid-query",
52719
+ message: "missingDescription and hasDescription are mutually exclusive \u2014 pass at most one."
52720
+ });
52721
+ }
52722
+ const descriptionPresence = input2.missingDescription === true ? "absent" : input2.hasDescription === true ? "present" : void 0;
52677
52723
  const limit = input2.limit ?? LIST_DEFAULT_LIMIT2;
52678
52724
  const recordTriggered = input2.recordTriggered === true;
52679
52725
  const fingerprint = argsFingerprint({
@@ -52682,6 +52728,7 @@ var init_list_components = __esm({
52682
52728
  ...input2.triggerObject !== void 0 ? { triggerObject: input2.triggerObject } : {},
52683
52729
  ...input2.status !== void 0 ? { status: input2.status } : {},
52684
52730
  ...recordTriggered ? { recordTriggered: true } : {},
52731
+ ...descriptionPresence !== void 0 ? { descriptionPresence } : {},
52685
52732
  ...Object.fromEntries(APEX_BOOLEAN_FILTERS.flatMap((k2) => input2[k2] !== void 0 ? [[k2, input2[k2]]] : []))
52686
52733
  });
52687
52734
  let offset = input2.offset ?? 0;
@@ -52712,7 +52759,8 @@ var init_list_components = __esm({
52712
52759
  ...input2.parentId !== void 0 ? { parentId: input2.parentId } : {},
52713
52760
  ...hasPropertyFilter ? { propertyEquals } : {},
52714
52761
  ...hasStringPropertyFilter ? { propertyStringEquals } : {},
52715
- ...recordTriggered ? { recordTriggered: true } : {}
52762
+ ...recordTriggered ? { recordTriggered: true } : {},
52763
+ ...descriptionPresence !== void 0 ? { descriptionPresence } : {}
52716
52764
  };
52717
52765
  const queryResult = await listNodesByType(ctx.graph, input2.type, {
52718
52766
  limit,
@@ -52745,7 +52793,7 @@ var init_list_components = __esm({
52745
52793
  const propertiesSlimmed = kept.some((n2) => n2.properties?.["propertiesTruncated"] === true);
52746
52794
  const hasMore = pageNodes.length === limit || trimmed;
52747
52795
  let retrievalHint;
52748
- if (offset === 0 && pageNodes.length === 0 && !hasPropertyFilter && !hasStringPropertyFilter && !recordTriggered) {
52796
+ if (offset === 0 && pageNodes.length === 0 && !hasPropertyFilter && !hasStringPropertyFilter && !recordTriggered && descriptionPresence === void 0) {
52749
52797
  const cov = summarizeCoverage(ctx.manifest, [input2.type]);
52750
52798
  if (cov.notModeledTypes.includes(input2.type)) {
52751
52799
  retrievalHint = `No \`${input2.type}\` in the vault \u2014 this type is NOT modeled by the current build, so its absence means "not analyzed", never "none in the org".`;
@@ -69830,7 +69878,11 @@ var init_tools = __esm({
69830
69878
  hasFutureMethod: { type: "boolean" },
69831
69879
  hasInvocableMethod: { type: "boolean" },
69832
69880
  hasAuraEnabledMethod: { type: "boolean" },
69833
- isTest: { type: "boolean" }
69881
+ isTest: { type: "boolean" },
69882
+ // Description-presence filters. Answer "which <type> have no description".
69883
+ // Only meaningful for a type whose extractor captures `<description>`.
69884
+ missingDescription: { type: "boolean" },
69885
+ hasDescription: { type: "boolean" }
69834
69886
  }
69835
69887
  });
69836
69888
  GET_SUBGRAPH_INPUT_SCHEMA = Object.freeze({
@@ -71698,7 +71750,7 @@ var init_tools = __esm({
71698
71750
  },
71699
71751
  {
71700
71752
  name: "sfi.list_components",
71701
- description: 'List components of a given type (optionally narrowed by parentId), sorted by id. Paginated via limit/offset; `hasMore` hints at additional pages (a truncated page returns a `nextCursor` to resume). Grant-heavy rows (Profile / PermissionSet, whose nodes carry tens of KB of declarative grants) are slimmed to scalar properties \u2014 each such row is marked `properties.propertiesTruncated: true` and the page carries a top-level `propertiesSlimmed: true` \u2014 so the whole inventory fits per page; fetch full detail per component via sfi.get_component. For `type: \'ApexClass\'`, optional boolean filters list interface/async/API implementers at the DB layer (correct pagination, not a post-filtered page): `isBatchable` / `isQueueable` / `isSchedulable` / `isRestResource` / `hasFutureMethod` / `hasInvocableMethod` / `hasAuraEnabledMethod` / `isTest` \u2014 e.g. `{ type: \'ApexClass\', isBatchable: true }` returns every Batchable class. When manifest coverage for the requested `type` is not `complete`, a structured `coverageCaveat` flags the inventory as potentially incomplete (scoped refresh, errored retrieve, not modeled) \u2014 including on non-empty pages. When the FIRST page is empty, a `retrievalHint` (FRESH-02) says WHY \u2014 "none in the org" (retrieved, none found) vs "not retrieved" (a scoped refresh skipped the type \u2014 run /sfi-refresh) vs "not modeled" \u2014 so an empty list is never a silent `[]` read as "the org has none". (The hint is suppressed when a boolean filter is active, since an empty filtered result is not a coverage gap.)',
71753
+ description: 'List components of a given type (optionally narrowed by parentId), sorted by id. Paginated via limit/offset; `hasMore` hints at additional pages (a truncated page returns a `nextCursor` to resume). Grant-heavy rows (Profile / PermissionSet, whose nodes carry tens of KB of declarative grants) are slimmed to scalar properties \u2014 each such row is marked `properties.propertiesTruncated: true` and the page carries a top-level `propertiesSlimmed: true` \u2014 so the whole inventory fits per page; fetch full detail per component via sfi.get_component. For `type: \'ApexClass\'`, optional boolean filters list interface/async/API implementers at the DB layer (correct pagination, not a post-filtered page): `isBatchable` / `isQueueable` / `isSchedulable` / `isRestResource` / `hasFutureMethod` / `hasInvocableMethod` / `hasAuraEnabledMethod` / `isTest` \u2014 e.g. `{ type: \'ApexClass\', isBatchable: true }` returns every Batchable class. `missingDescription: true` (or `hasDescription: true`) filters to components that lack (or carry) a non-empty `properties.description`, with `totalCount` as the authoritative tally \u2014 only meaningful for a type whose extractor captures a source `<description>` (a type that carries none in source will match ALL of its nodes, meaning "no description in source", not "left blank"). When manifest coverage for the requested `type` is not `complete`, a structured `coverageCaveat` flags the inventory as potentially incomplete (scoped refresh, errored retrieve, not modeled) \u2014 including on non-empty pages. When the FIRST page is empty, a `retrievalHint` (FRESH-02) says WHY \u2014 "none in the org" (retrieved, none found) vs "not retrieved" (a scoped refresh skipped the type \u2014 run /sfi-refresh) vs "not modeled" \u2014 so an empty list is never a silent `[]` read as "the org has none". (The hint is suppressed when a boolean filter is active, since an empty filtered result is not a coverage gap.)',
71702
71754
  inputSchema: LIST_COMPONENTS_INPUT_SCHEMA
71703
71755
  },
71704
71756
  {
@@ -74635,7 +74687,7 @@ var init_package_version = __esm({
74635
74687
  "use strict";
74636
74688
  readCliPackageVersion = () => {
74637
74689
  if (true)
74638
- return "0.1.24";
74690
+ return "0.1.26";
74639
74691
  for (const rel of ["../package.json", "../../package.json"]) {
74640
74692
  try {
74641
74693
  const raw = readFileSync3(fileURLToPath(new URL(rel, import.meta.url)), "utf8");
@@ -144559,7 +144611,7 @@ var makeShutdownOnce = (ctx) => {
144559
144611
 
144560
144612
  // dist/src/commands/demo.js
144561
144613
  init_refresh();
144562
- var buildVersion = () => true ? "0.1.24" : "dev";
144614
+ var buildVersion = () => true ? "0.1.26" : "dev";
144563
144615
  var SHUTDOWN_SIGNALS2 = ["SIGINT", "SIGTERM"];
144564
144616
  var resolveDemoSource = () => {
144565
144617
  let dir = dirname21(fileURLToPath2(import.meta.url));
@@ -145544,7 +145596,7 @@ init_vault_git();
145544
145596
  init_watch();
145545
145597
  var readVersion = () => {
145546
145598
  if (true)
145547
- return "0.1.24";
145599
+ return "0.1.26";
145548
145600
  const pkgUrl = new URL("../../package.json", import.meta.url);
145549
145601
  const raw = readFileSync6(fileURLToPath3(pkgUrl), "utf8");
145550
145602
  const parsed = JSON.parse(raw);
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "sf-intelligence",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Offline-first, MCP-first knowledge base for a Salesforce org. Ask about your org's metadata, dependencies, permissions, and automation — grounded in real retrieved metadata. Ships the sfi CLI and an MCP server.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
- "homepage": "https://salesforce-intelligence.pages.dev",
6
+ "homepage": "https://sfi.auditforce.cloud",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/PranavNagrecha/Salesforce-Intelligence.git"
@@ -56,11 +56,6 @@
56
56
  "server.json",
57
57
  "demo-source"
58
58
  ],
59
- "scripts": {
60
- "build": "tsc --build && node build.mjs",
61
- "test": "vitest run",
62
- "prepublishOnly": "node ../../scripts/prepublish-check.mjs"
63
- },
64
59
  "dependencies": {
65
60
  "@duckdb/node-api": "1.5.3-r.2",
66
61
  "@inquirer/prompts": "^7.0.0",
@@ -70,17 +65,21 @@
70
65
  "zod": "^3.23.0"
71
66
  },
72
67
  "devDependencies": {
73
- "@sf-intelligence/contracts": "workspace:*",
74
- "@sf-intelligence/core": "workspace:*",
75
- "@sf-intelligence/extractors": "workspace:*",
76
- "@sf-intelligence/graph": "workspace:*",
77
- "@sf-intelligence/mcp": "workspace:*",
78
- "@sf-intelligence/parsers": "workspace:*",
79
- "@sf-intelligence/patterns": "workspace:*",
80
- "@sf-intelligence/renderers": "workspace:*",
81
- "@sf-intelligence/tooling-api": "workspace:*",
82
- "@sf-intelligence/vault": "workspace:*",
83
68
  "esbuild": "^0.28.0",
84
- "vitest": "^1.6.0"
69
+ "vitest": "^1.6.0",
70
+ "@sf-intelligence/contracts": "0.1.0",
71
+ "@sf-intelligence/mcp": "0.1.0",
72
+ "@sf-intelligence/extractors": "0.1.0",
73
+ "@sf-intelligence/tooling-api": "0.1.0",
74
+ "@sf-intelligence/parsers": "0.1.0",
75
+ "@sf-intelligence/vault": "0.1.0",
76
+ "@sf-intelligence/patterns": "0.1.0",
77
+ "@sf-intelligence/renderers": "0.1.0",
78
+ "@sf-intelligence/core": "0.1.0",
79
+ "@sf-intelligence/graph": "0.1.0"
80
+ },
81
+ "scripts": {
82
+ "build": "tsc --build && node build.mjs",
83
+ "test": "vitest run"
85
84
  }
86
- }
85
+ }
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "url": "https://github.com/PranavNagrecha/Salesforce-Intelligence",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.1.24",
10
+ "version": "0.1.25",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "sf-intelligence",
16
- "version": "0.1.24",
16
+ "version": "0.1.25",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },