@cyclonedx/cdxgen 12.3.1 → 12.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/bin/cdxgen.js +1 -2
  2. package/data/rules/ai-agent-governance.yaml +43 -0
  3. package/data/rules/mcp-servers.yaml +36 -2
  4. package/lib/cli/index.js +295 -17
  5. package/lib/cli/index.poku.js +296 -1
  6. package/lib/helpers/agentFormulationParser.js +4 -1
  7. package/lib/helpers/aiInventory.js +262 -0
  8. package/lib/helpers/aiInventory.poku.js +111 -0
  9. package/lib/helpers/analyzer.js +375 -45
  10. package/lib/helpers/analyzer.poku.js +50 -0
  11. package/lib/helpers/auditCategories.js +76 -0
  12. package/lib/helpers/display.js +5 -2
  13. package/lib/helpers/display.poku.js +25 -0
  14. package/lib/helpers/formulationParsers.js +26 -6
  15. package/lib/helpers/jsonLike.js +21 -20
  16. package/lib/helpers/jsonLike.poku.js +34 -0
  17. package/lib/helpers/mcpConfigParser.js +11 -11
  18. package/lib/helpers/mcpConfigParser.poku.js +67 -0
  19. package/lib/helpers/mcpDiscovery.js +13 -23
  20. package/lib/helpers/mcpDiscovery.poku.js +21 -0
  21. package/lib/helpers/utils.js +2 -1
  22. package/lib/helpers/utils.poku.js +19 -1
  23. package/lib/stages/postgen/annotator.js +2 -1
  24. package/lib/stages/postgen/annotator.poku.js +15 -0
  25. package/lib/stages/postgen/auditBom.js +12 -6
  26. package/lib/stages/postgen/auditBom.poku.js +111 -4
  27. package/lib/stages/postgen/postgen.js +229 -6
  28. package/lib/stages/postgen/postgen.poku.js +180 -0
  29. package/package.json +1 -1
  30. package/types/lib/cli/index.d.ts +1 -0
  31. package/types/lib/cli/index.d.ts.map +1 -1
  32. package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
  33. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
  34. package/types/lib/helpers/aiInventory.d.ts +23 -0
  35. package/types/lib/helpers/aiInventory.d.ts.map +1 -0
  36. package/types/lib/helpers/analyzer.d.ts +5 -0
  37. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  38. package/types/lib/helpers/auditCategories.d.ts +12 -0
  39. package/types/lib/helpers/auditCategories.d.ts.map +1 -0
  40. package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
  41. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
  42. package/types/lib/helpers/display.d.ts.map +1 -1
  43. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  44. package/types/lib/helpers/jsonLike.d.ts +4 -0
  45. package/types/lib/helpers/jsonLike.d.ts.map +1 -0
  46. package/types/lib/helpers/mcp.d.ts +29 -0
  47. package/types/lib/helpers/mcp.d.ts.map +1 -0
  48. package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
  49. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
  50. package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
  51. package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
  52. package/types/lib/helpers/utils.d.ts +2 -0
  53. package/types/lib/helpers/utils.d.ts.map +1 -1
  54. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  55. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  56. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
package/bin/cdxgen.js CHANGED
@@ -462,9 +462,8 @@ const args = _yargs
462
462
  })
463
463
  .option("tlp-classification", {
464
464
  description:
465
- 'Traffic Light Protocol (TLP) is a classification system for identifying the potential risk associated with artefact, including whether it is subject to certain types of legal, financial, or technical threats. Refer to [https://www.first.org/tlp/](https://www.first.org/tlp/) for further information.\nThe default classification is "CLEAR"',
465
+ "Traffic Light Protocol (TLP) is a classification system for identifying the potential risk associated with an artefact, including whether it is subject to certain types of legal, financial, or technical threats. Refer to [https://www.first.org/tlp/](https://www.first.org/tlp/) for further information.",
466
466
  choices: ["CLEAR", "GREEN", "AMBER", "AMBER_AND_STRICT", "RED"],
467
- default: "CLEAR",
468
467
  hidden: true,
469
468
  })
470
469
  .option("env-audit", {
@@ -204,3 +204,46 @@
204
204
  "hiddenMcpUrls": $prop($, 'cdx:agent:hiddenMcpUrls'),
205
205
  "providerNames": $prop($, 'cdx:agent:providerNames')
206
206
  }
207
+
208
+ - id: AGT-007
209
+ name: "AI agent or skill file is included in a build or post-build SBOM"
210
+ description: "Shipped AI instruction and skill files deserve explicit review because they can alter developer tooling, release-time automation, and downstream runtime behavior."
211
+ severity: medium
212
+ category: ai-agent
213
+ standards:
214
+ owasp-ai-top-10:
215
+ - "LLM05: Supply Chain Vulnerabilities"
216
+ - "LLM08: Excessive Agency"
217
+ nist-ai-rmf:
218
+ - "Govern"
219
+ - "Map"
220
+ nist-ssdf:
221
+ - "Review build and release instructions before distribution"
222
+ condition: |
223
+ components[
224
+ (
225
+ $prop($, 'cdx:agent:inventorySource') = 'agent-file'
226
+ or $prop($, 'cdx:agent:inventorySource') = 'community-config'
227
+ )
228
+ and (
229
+ $prop($, 'cdx:file:kind') = 'skill-file'
230
+ or $prop($, 'cdx:file:kind') = 'agent-instructions'
231
+ or $prop($, 'cdx:file:kind') = 'copilot-instructions'
232
+ or $prop($, 'cdx:file:kind') = 'copilot-setup-workflow'
233
+ or $prop($, 'cdx:file:kind') = 'ai-agent-file'
234
+ )
235
+ and $count($$.metadata.lifecycles[phase = 'build' or phase = 'post-build']) > 0
236
+ ]
237
+ location: |
238
+ {
239
+ "bomRef": $."bom-ref",
240
+ "file": $prop($, 'SrcFile')
241
+ }
242
+ message: "AI instruction or skill file '{{ name }}' is included in a build/post-build SBOM"
243
+ mitigation: "If the file must ship, keep the BOM review-friendly with '--bom-audit --bom-audit-categories ai-agent' and consider '--tlp-classification AMBER'. If you want a package-only BOM, rerun with '--exclude-type ai-skill'."
244
+ evidence: |
245
+ {
246
+ "inventorySource": $prop($, 'cdx:agent:inventorySource'),
247
+ "fileKind": $prop($, 'cdx:file:kind'),
248
+ "providerNames": $prop($, 'cdx:agent:providerNames')
249
+ }
@@ -191,8 +191,9 @@
191
191
  {
192
192
  "configFormat": $prop($, 'cdx:mcp:configFormat'),
193
193
  "configKey": $prop($, 'cdx:mcp:configKey'),
194
- "credentialExposureFields": $prop($, 'cdx:mcp:credentialExposureFields'),
195
- "credentialRiskIndicators": $prop($, 'cdx:mcp:credentialRiskIndicators')
194
+ "credentialExposureFieldCount": $prop($, 'cdx:mcp:credentialExposureFieldCount'),
195
+ "credentialIndicatorCount": $prop($, 'cdx:mcp:credentialIndicatorCount'),
196
+ "credentialReferenceCount": $prop($, 'cdx:mcp:credentialReferenceCount')
196
197
  }
197
198
 
198
199
  - id: MCP-006
@@ -268,3 +269,36 @@
268
269
  "trustProfile": $prop($, 'cdx:mcp:trustProfile'),
269
270
  "authPosture": $prop($, 'cdx:mcp:authPosture')
270
271
  }
272
+
273
+ - id: MCP-008
274
+ name: "MCP configuration file is included in a build or post-build SBOM"
275
+ description: "Committed MCP client configuration files can carry trust, auth, and distribution sensitivity even when they are not actively used during the current scan."
276
+ severity: medium
277
+ category: mcp-server
278
+ standards:
279
+ owasp-ai-top-10:
280
+ - "LLM07: Insecure Plugin Design"
281
+ - "LLM08: Excessive Agency"
282
+ nist-ai-rmf:
283
+ - "Govern"
284
+ - "Map"
285
+ nist-ssdf:
286
+ - "Review configured AI control surfaces before release"
287
+ condition: |
288
+ components[
289
+ $prop($, 'cdx:file:kind') = 'mcp-config'
290
+ and $count($$.metadata.lifecycles[phase = 'build' or phase = 'post-build']) > 0
291
+ ]
292
+ location: |
293
+ {
294
+ "bomRef": $."bom-ref",
295
+ "file": $prop($, 'SrcFile')
296
+ }
297
+ message: "MCP config file '{{ name }}' is included in a build/post-build SBOM"
298
+ mitigation: "Review the config with '--bom-audit --bom-audit-categories mcp-server', consider '--tlp-classification AMBER' before sharing the BOM broadly, or rerun with '--exclude-type mcp' if config artifacts should be omitted."
299
+ evidence: |
300
+ {
301
+ "configFormat": $prop($, 'cdx:mcp:configFormat'),
302
+ "configuredServiceCount": $prop($, 'cdx:mcp:configuredServiceCount'),
303
+ "configuredServiceNames": $prop($, 'cdx:mcp:configuredServiceNames')
304
+ }
package/lib/cli/index.js CHANGED
@@ -18,10 +18,24 @@ import { parse } from "ssri";
18
18
  import { v4 as uuidv4 } from "uuid";
19
19
  import { parse as loadYaml } from "yaml";
20
20
 
21
+ import {
22
+ AI_INSTRUCTION_FILE_KINDS,
23
+ AI_INVENTORY_PROJECT_TYPES,
24
+ AI_SKILL_FILE_KIND,
25
+ collectAiInventory,
26
+ filterInventoryDependencies,
27
+ filterInventorySubjectsByTypes,
28
+ inventoryPropertyValue,
29
+ MCP_CONFIG_FILE_KIND,
30
+ optionIncludesAiInventoryProjectType,
31
+ summarizeAiInventory,
32
+ } from "../helpers/aiInventory.js";
21
33
  import {
22
34
  detectMcpInventory,
35
+ detectPythonMcpInventory,
23
36
  findJSImportsExports,
24
37
  } from "../helpers/analyzer.js";
38
+ import { expandBomAuditCategories } from "../helpers/auditCategories.js";
25
39
  import { parseCaxaMetadata } from "../helpers/caxa.js";
26
40
  import {
27
41
  CHROME_EXTENSION_PURL_TYPE,
@@ -197,6 +211,9 @@ import {
197
211
  shouldFetchLicense,
198
212
  splitOutputByGradleProjects,
199
213
  } from "../helpers/utils.js";
214
+
215
+ export { summarizeAiInventory } from "../helpers/aiInventory.js";
216
+
200
217
  import {
201
218
  cleanupTempDir,
202
219
  collectInstalledExtensions,
@@ -2717,16 +2734,32 @@ export async function createJavaBom(path, options) {
2717
2734
  * @param {Object} options Parse options from the cli
2718
2735
  * @returns {Promise<Object>} Promise resolving to BOM object
2719
2736
  */
2737
+ function getRequestedAiInventoryTypes(options) {
2738
+ return AI_INVENTORY_PROJECT_TYPES.filter((type) =>
2739
+ optionIncludesAiInventoryProjectType(options?.projectType, type),
2740
+ );
2741
+ }
2742
+
2743
+ function getExcludedAiInventoryTypes(options) {
2744
+ return AI_INVENTORY_PROJECT_TYPES.filter((type) =>
2745
+ optionIncludesAiInventoryProjectType(options?.excludeType, type),
2746
+ );
2747
+ }
2748
+
2749
+ function getExactAiInventoryType(options) {
2750
+ const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2751
+ return requestedAiInventoryTypes.length === 1 &&
2752
+ Array.isArray(options?.projectType) &&
2753
+ options.projectType.length === 1
2754
+ ? requestedAiInventoryTypes[0]
2755
+ : undefined;
2756
+ }
2757
+
2720
2758
  function shouldDetectMcpInventory(options, allImports = {}) {
2721
2759
  if (hasAnyProjectType(["mcp"], options, false)) {
2722
2760
  return true;
2723
2761
  }
2724
- const auditCategories = Array.isArray(options?.bomAuditCategories)
2725
- ? options.bomAuditCategories
2726
- : String(options?.bomAuditCategories || "")
2727
- .split(",")
2728
- .map((category) => category.trim())
2729
- .filter(Boolean);
2762
+ const auditCategories = expandBomAuditCategories(options?.bomAuditCategories);
2730
2763
  if (
2731
2764
  auditCategories.some((category) =>
2732
2765
  ["mcp-server", "ai-agent"].includes(category),
@@ -2740,6 +2773,91 @@ function shouldDetectMcpInventory(options, allImports = {}) {
2740
2773
  });
2741
2774
  }
2742
2775
 
2776
+ function summarizeAiInventoryNames(subjects, discoveryPath, kindSet) {
2777
+ return [
2778
+ ...new Set(
2779
+ (subjects || [])
2780
+ .filter((subject) =>
2781
+ kindSet.has(inventoryPropertyValue(subject, "cdx:file:kind")),
2782
+ )
2783
+ .map((subject) => inventoryPropertyValue(subject, "SrcFile"))
2784
+ .filter(Boolean)
2785
+ .map(
2786
+ (filePath) => relative(discoveryPath, filePath) || basename(filePath),
2787
+ ),
2788
+ ),
2789
+ ].sort();
2790
+ }
2791
+
2792
+ function summarizeAiInventoryServiceNames(services) {
2793
+ return [
2794
+ ...new Set(
2795
+ (services || []).map((service) => service?.name).filter(Boolean),
2796
+ ),
2797
+ ].sort();
2798
+ }
2799
+
2800
+ function formatAiInventorySummaryLine(label, count, nameList) {
2801
+ return ` ${label.padEnd(20)} ${count}${nameList.length ? ` (${nameList.join(", ")})` : ""}`;
2802
+ }
2803
+
2804
+ function emitAiInventorySummary(aiInventory, discoveryPath) {
2805
+ const summary = summarizeAiInventory(aiInventory);
2806
+ const totalInventory =
2807
+ summary.instructionCount +
2808
+ summary.skillCount +
2809
+ summary.mcpConfigCount +
2810
+ summary.mcpServiceCount;
2811
+ if (!totalInventory) {
2812
+ return;
2813
+ }
2814
+ const instructionNames = summarizeAiInventoryNames(
2815
+ aiInventory.components,
2816
+ discoveryPath,
2817
+ AI_INSTRUCTION_FILE_KINDS,
2818
+ );
2819
+ const skillNames = summarizeAiInventoryNames(
2820
+ aiInventory.components,
2821
+ discoveryPath,
2822
+ new Set([AI_SKILL_FILE_KIND]),
2823
+ );
2824
+ const mcpConfigNames = summarizeAiInventoryNames(
2825
+ aiInventory.components,
2826
+ discoveryPath,
2827
+ new Set([MCP_CONFIG_FILE_KIND]),
2828
+ );
2829
+ const mcpServiceNames = summarizeAiInventoryServiceNames(
2830
+ aiInventory.services,
2831
+ );
2832
+ console.warn(
2833
+ [
2834
+ "AI Inventory Summary:",
2835
+ formatAiInventorySummaryLine(
2836
+ "AI instruction files:",
2837
+ summary.instructionCount,
2838
+ instructionNames,
2839
+ ),
2840
+ formatAiInventorySummaryLine(
2841
+ "Skill files:",
2842
+ summary.skillCount,
2843
+ skillNames,
2844
+ ),
2845
+ formatAiInventorySummaryLine(
2846
+ "MCP configs:",
2847
+ summary.mcpConfigCount,
2848
+ mcpConfigNames,
2849
+ ),
2850
+ formatAiInventorySummaryLine(
2851
+ "MCP services:",
2852
+ summary.mcpServiceCount,
2853
+ mcpServiceNames,
2854
+ ),
2855
+ "",
2856
+ "Run --bom-audit --bom-audit-categories ai-inventory to audit these surfaces.",
2857
+ ].join("\n"),
2858
+ );
2859
+ }
2860
+
2743
2861
  export async function createNodejsBom(path, options) {
2744
2862
  let pkgList = [];
2745
2863
  let manifestFiles = [];
@@ -2747,6 +2865,15 @@ export async function createNodejsBom(path, options) {
2747
2865
  let parentComponent = {};
2748
2866
  const parentSubComponents = [];
2749
2867
  let ppurl = "";
2868
+ const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2869
+ const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
2870
+ const exactAiInventoryType = getExactAiInventoryType(options);
2871
+ const includedAiInventoryTypes = exactAiInventoryType
2872
+ ? requestedAiInventoryTypes
2873
+ : AI_INVENTORY_PROJECT_TYPES.filter(
2874
+ (type) => !excludedAiInventoryTypes.includes(type),
2875
+ );
2876
+ let aiInventory = { components: [], dependencies: [], services: [] };
2750
2877
  // Docker mode requires special handling
2751
2878
  if (hasAnyProjectType(["docker", "oci", "container", "os"], options, false)) {
2752
2879
  const pkgJsonFiles = getAllFiles(path, "**/package.json", options);
@@ -2758,11 +2885,27 @@ export async function createNodejsBom(path, options) {
2758
2885
  pkgList = pkgList.concat(dlist);
2759
2886
  }
2760
2887
  }
2888
+ if (includedAiInventoryTypes.length) {
2889
+ aiInventory = collectAiInventory(
2890
+ path,
2891
+ options,
2892
+ includedAiInventoryTypes,
2893
+ );
2894
+ }
2895
+ if (aiInventory.components?.length) {
2896
+ pkgList = trimComponents(pkgList.concat(aiInventory.components));
2897
+ }
2761
2898
  return buildBomNSData(options, pkgList, "npm", {
2762
2899
  allImports: {},
2763
2900
  src: path,
2764
2901
  filename: "package.json",
2902
+ dependencies: mergeDependencies(
2903
+ [],
2904
+ aiInventory.dependencies,
2905
+ parentComponent,
2906
+ ),
2765
2907
  parentComponent,
2908
+ services: aiInventory.services,
2766
2909
  });
2767
2910
  }
2768
2911
  }
@@ -2785,6 +2928,40 @@ export async function createNodejsBom(path, options) {
2785
2928
  mcpInventory = detectMcpInventory(path, options.deep);
2786
2929
  }
2787
2930
  }
2931
+ if (includedAiInventoryTypes.length) {
2932
+ aiInventory = collectAiInventory(path, options, includedAiInventoryTypes);
2933
+ }
2934
+ if (excludedAiInventoryTypes.includes("mcp")) {
2935
+ mcpInventory = { components: [], dependencies: [], services: [] };
2936
+ }
2937
+ const aiInventorySummary = summarizeAiInventory(aiInventory);
2938
+ if (!exactAiInventoryType) {
2939
+ if (aiInventorySummary.instructionCount || aiInventorySummary.skillCount) {
2940
+ thoughtLog(
2941
+ `I found ${aiInventorySummary.instructionCount + aiInventorySummary.skillCount} AI skill/instruction file component(s). Use '--exclude-type ai-skill' for a package-only BOM, or '--bom-audit --bom-audit-categories ai-inventory' for review-friendly reporting.`,
2942
+ );
2943
+ }
2944
+ if (aiInventorySummary.mcpConfigCount) {
2945
+ thoughtLog(
2946
+ `I found ${aiInventorySummary.mcpConfigCount} MCP config component(s). Use '--exclude-type mcp' to drop them, or '--bom-audit --bom-audit-categories ai-inventory --tlp-classification AMBER' to keep and flag them.`,
2947
+ );
2948
+ }
2949
+ emitAiInventorySummary(aiInventory, path);
2950
+ }
2951
+ if (exactAiInventoryType === "ai-skill") {
2952
+ const exactComponents = trimComponents([...(aiInventory.components || [])]);
2953
+ const exactDependencies = mergeDependencies([], aiInventory.dependencies);
2954
+ const exactServices = mergeServices([], aiInventory.services);
2955
+ parentComponent = createDefaultParentComponent(path, "generic", options);
2956
+ return buildBomNSData(options, exactComponents, "generic", {
2957
+ dependencies: exactDependencies,
2958
+ filename: path,
2959
+ parentComponent,
2960
+ projectType: exactAiInventoryType,
2961
+ services: exactServices,
2962
+ src: path,
2963
+ });
2964
+ }
2788
2965
  let yarnLockFiles = getAllFiles(
2789
2966
  path,
2790
2967
  `${options.multiProject ? "**/" : ""}yarn.lock`,
@@ -3675,12 +3852,37 @@ export async function createNodejsBom(path, options) {
3675
3852
  parentComponent,
3676
3853
  );
3677
3854
  }
3855
+ if (aiInventory.components?.length) {
3856
+ pkgList = trimComponents(pkgList.concat(aiInventory.components));
3857
+ }
3858
+ if (aiInventory.dependencies?.length) {
3859
+ dependencies = mergeDependencies(
3860
+ dependencies,
3861
+ aiInventory.dependencies,
3862
+ parentComponent,
3863
+ );
3864
+ }
3865
+ const inventoryServices = mergeServices(
3866
+ mergeServices([], mcpInventory.services || []),
3867
+ aiInventory.services || [],
3868
+ );
3869
+ if (exactAiInventoryType === "mcp") {
3870
+ pkgList = trimComponents(filterInventorySubjectsByTypes(pkgList, ["mcp"]));
3871
+ dependencies = filterInventoryDependencies(
3872
+ dependencies,
3873
+ pkgList,
3874
+ inventoryServices,
3875
+ );
3876
+ if (!parentComponent || !Object.keys(parentComponent).length) {
3877
+ parentComponent = createDefaultParentComponent(path, "generic", options);
3878
+ }
3879
+ }
3678
3880
  return buildBomNSData(options, pkgList, "npm", {
3679
3881
  src: path,
3680
3882
  filename: manifestFiles.join(", "),
3681
3883
  dependencies,
3682
3884
  parentComponent,
3683
- services: mergeServices([], mcpInventory.services || []),
3885
+ services: inventoryServices,
3684
3886
  });
3685
3887
  }
3686
3888
 
@@ -3859,6 +4061,16 @@ export async function createPythonBom(path, options) {
3859
4061
  let dependencies = [];
3860
4062
  let pkgList = [];
3861
4063
  let formulationList = [];
4064
+ const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
4065
+ const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
4066
+ const includedAiInventoryTypes = AI_INVENTORY_PROJECT_TYPES.filter(
4067
+ (type) =>
4068
+ (!requestedAiInventoryTypes.length ||
4069
+ requestedAiInventoryTypes.includes(type)) &&
4070
+ !excludedAiInventoryTypes.includes(type),
4071
+ );
4072
+ let aiInventory = { components: [], dependencies: [], services: [] };
4073
+ let mcpInventory = { components: [], dependencies: [], services: [] };
3862
4074
  const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxgen-venv-"));
3863
4075
  let parentComponent = createDefaultParentComponent(path, "pypi", options);
3864
4076
  // We are checking only the root here for pipenv
@@ -4350,6 +4562,24 @@ export async function createPythonBom(path, options) {
4350
4562
  pkgList = pkgList.concat(dlist);
4351
4563
  }
4352
4564
  }
4565
+ if (includedAiInventoryTypes.length) {
4566
+ aiInventory = collectAiInventory(path, options, includedAiInventoryTypes);
4567
+ if (includedAiInventoryTypes.includes("mcp")) {
4568
+ mcpInventory = detectPythonMcpInventory(path, options.deep);
4569
+ }
4570
+ }
4571
+ const aiInventorySummary = summarizeAiInventory(aiInventory);
4572
+ if (aiInventorySummary.instructionCount || aiInventorySummary.skillCount) {
4573
+ thoughtLog(
4574
+ `I found ${aiInventorySummary.instructionCount + aiInventorySummary.skillCount} AI skill/instruction file component(s). Use '--exclude-type ai-skill' for a package-only BOM, or '--bom-audit --bom-audit-categories ai-inventory' for review-friendly reporting.`,
4575
+ );
4576
+ }
4577
+ if (aiInventorySummary.mcpConfigCount) {
4578
+ thoughtLog(
4579
+ `I found ${aiInventorySummary.mcpConfigCount} MCP config component(s). Use '--exclude-type mcp' to drop them, or '--bom-audit --bom-audit-categories ai-inventory --tlp-classification AMBER' to keep and flag them.`,
4580
+ );
4581
+ }
4582
+ emitAiInventorySummary(aiInventory, path);
4353
4583
  // Check and complete the dependency tree
4354
4584
  if (
4355
4585
  isFeatureEnabled(options, "safe-pip-install") &&
@@ -4398,6 +4628,30 @@ export async function createPythonBom(path, options) {
4398
4628
  if (shouldFetchLicense()) {
4399
4629
  pkgList = await getPyMetadata(pkgList, false);
4400
4630
  }
4631
+ if (mcpInventory.components?.length) {
4632
+ pkgList = trimComponents(pkgList.concat(mcpInventory.components));
4633
+ }
4634
+ if (mcpInventory.dependencies?.length) {
4635
+ dependencies = mergeDependencies(
4636
+ dependencies,
4637
+ mcpInventory.dependencies,
4638
+ parentComponent,
4639
+ );
4640
+ }
4641
+ if (aiInventory.components?.length) {
4642
+ pkgList = trimComponents(pkgList.concat(aiInventory.components));
4643
+ }
4644
+ if (aiInventory.dependencies?.length) {
4645
+ dependencies = mergeDependencies(
4646
+ dependencies,
4647
+ aiInventory.dependencies,
4648
+ parentComponent,
4649
+ );
4650
+ }
4651
+ const inventoryServices = mergeServices(
4652
+ mergeServices([], mcpInventory.services || []),
4653
+ aiInventory.services || [],
4654
+ );
4401
4655
  return buildBomNSData(options, pkgList, "pypi", {
4402
4656
  allImports,
4403
4657
  src: path,
@@ -4405,6 +4659,7 @@ export async function createPythonBom(path, options) {
4405
4659
  dependencies,
4406
4660
  parentComponent,
4407
4661
  formulationList,
4662
+ services: inventoryServices,
4408
4663
  });
4409
4664
  }
4410
4665
 
@@ -7882,22 +8137,39 @@ export async function createMultiXBom(pathList, options) {
7882
8137
  if (pathList.length > 2) {
7883
8138
  thoughtLog(`Let's thoroughly check the path ${path}.`);
7884
8139
  }
7885
- // Node.js
7886
- if (hasAnyProjectType(["oci", "js"], options)) {
8140
+ // Node.js and AI inventory
8141
+ if (hasAnyProjectType(["oci", "js", "mcp", "ai-skill"], options)) {
8142
+ const exactAiInventoryType = getExactAiInventoryType(options);
7887
8143
  if (!hasAnyProjectType(["oci"], options, false)) {
7888
- thoughtLog(
7889
- "**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
7890
- );
8144
+ if (exactAiInventoryType === "mcp") {
8145
+ thoughtLog(
8146
+ "**MCP**: Looking for MCP services, MCP configs, and related AI control-plane artifacts.",
8147
+ );
8148
+ } else if (exactAiInventoryType === "ai-skill") {
8149
+ thoughtLog(
8150
+ "**AI-SKILL**: Looking for AI instruction, skill, and agent-definition files that can influence build or release flows.",
8151
+ );
8152
+ } else {
8153
+ thoughtLog(
8154
+ "**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
8155
+ );
8156
+ }
7891
8157
  }
7892
- setProjectTypeActivityContext("js", path);
8158
+ setProjectTypeActivityContext(exactAiInventoryType || "js", path);
7893
8159
  bomData = await createNodejsBom(path, options);
7894
8160
  if (bomData?.bomJson?.components?.length) {
7895
- thoughtLog(
7896
- `I found ${bomData.bomJson.components.length} npm packages. Let's keep looking.`,
7897
- );
8161
+ if (exactAiInventoryType) {
8162
+ thoughtLog(
8163
+ `I found ${bomData.bomJson.components.length} ${exactAiInventoryType} component(s). Let's keep looking.`,
8164
+ );
8165
+ } else {
8166
+ thoughtLog(
8167
+ `I found ${bomData.bomJson.components.length} npm packages. Let's keep looking.`,
8168
+ );
8169
+ }
7898
8170
  if (DEBUG_MODE) {
7899
8171
  console.log(
7900
- `Found ${bomData.bomJson.components.length} npm packages at ${path}`,
8172
+ `Found ${bomData.bomJson.components.length} ${exactAiInventoryType || "npm"} components at ${path}`,
7901
8173
  );
7902
8174
  }
7903
8175
  components = components.concat(bomData.bomJson.components);
@@ -9238,6 +9510,12 @@ export async function createBom(path, options) {
9238
9510
  ) {
9239
9511
  return await createNodejsBom(path, options);
9240
9512
  }
9513
+ if (PROJECT_TYPE_ALIASES["mcp"].includes(projectType[0])) {
9514
+ return await createNodejsBom(path, options);
9515
+ }
9516
+ if (PROJECT_TYPE_ALIASES["ai-skill"].includes(projectType[0])) {
9517
+ return await createNodejsBom(path, options);
9518
+ }
9241
9519
  if (
9242
9520
  PROJECT_TYPE_ALIASES["py"].includes(projectType[0]) ||
9243
9521
  projectType?.[0]?.startsWith("python")