@cyclonedx/cdxgen 12.3.0 → 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 (121) hide show
  1. package/README.md +15 -5
  2. package/bin/audit.js +7 -0
  3. package/bin/cdxgen.js +241 -81
  4. package/bin/repl.js +138 -0
  5. package/data/rules/ai-agent-governance.yaml +249 -0
  6. package/data/rules/dependency-sources.yaml +41 -0
  7. package/data/rules/mcp-servers.yaml +304 -0
  8. package/data/rules/package-integrity.yaml +123 -0
  9. package/lib/audit/index.js +353 -29
  10. package/lib/audit/index.poku.js +247 -7
  11. package/lib/audit/reporters.js +26 -0
  12. package/lib/audit/scoring.js +262 -13
  13. package/lib/audit/scoring.poku.js +179 -0
  14. package/lib/audit/targets.js +391 -2
  15. package/lib/audit/targets.poku.js +416 -3
  16. package/lib/cli/index.js +588 -45
  17. package/lib/cli/index.poku.js +735 -1
  18. package/lib/evinser/evinser.js +8 -5
  19. package/lib/helpers/agentFormulationParser.js +318 -0
  20. package/lib/helpers/aiInventory.js +262 -0
  21. package/lib/helpers/aiInventory.poku.js +111 -0
  22. package/lib/helpers/analyzer.js +1769 -0
  23. package/lib/helpers/analyzer.poku.js +284 -3
  24. package/lib/helpers/auditCategories.js +76 -0
  25. package/lib/helpers/ciParsers/githubActions.js +140 -16
  26. package/lib/helpers/ciParsers/githubActions.poku.js +110 -0
  27. package/lib/helpers/communityAiConfigParser.js +672 -0
  28. package/lib/helpers/communityAiConfigParser.poku.js +63 -0
  29. package/lib/helpers/depsUtils.js +108 -0
  30. package/lib/helpers/depsUtils.poku.js +72 -1
  31. package/lib/helpers/display.js +325 -3
  32. package/lib/helpers/display.poku.js +301 -0
  33. package/lib/helpers/formulationParsers.js +28 -0
  34. package/lib/helpers/formulationParsers.poku.js +504 -1
  35. package/lib/helpers/jsonLike.js +102 -0
  36. package/lib/helpers/jsonLike.poku.js +34 -0
  37. package/lib/helpers/mcp.js +248 -0
  38. package/lib/helpers/mcp.poku.js +101 -0
  39. package/lib/helpers/mcpConfigParser.js +656 -0
  40. package/lib/helpers/mcpConfigParser.poku.js +126 -0
  41. package/lib/helpers/mcpDiscovery.js +84 -0
  42. package/lib/helpers/mcpDiscovery.poku.js +21 -0
  43. package/lib/helpers/protobom.js +3 -3
  44. package/lib/helpers/provenanceUtils.js +29 -4
  45. package/lib/helpers/provenanceUtils.poku.js +29 -3
  46. package/lib/helpers/registryProvenance.js +210 -0
  47. package/lib/helpers/registryProvenance.poku.js +144 -0
  48. package/lib/helpers/rustFormulationParser.js +330 -0
  49. package/lib/helpers/source.js +21 -2
  50. package/lib/helpers/source.poku.js +38 -0
  51. package/lib/helpers/utils.js +1331 -83
  52. package/lib/helpers/utils.poku.js +599 -188
  53. package/lib/helpers/vsixutils.js +12 -4
  54. package/lib/helpers/vsixutils.poku.js +34 -0
  55. package/lib/managers/binary.js +36 -12
  56. package/lib/managers/binary.poku.js +68 -0
  57. package/lib/managers/docker.js +59 -9
  58. package/lib/managers/docker.poku.js +61 -0
  59. package/lib/managers/piptree.js +12 -7
  60. package/lib/managers/piptree.poku.js +44 -0
  61. package/lib/stages/postgen/annotator.js +2 -1
  62. package/lib/stages/postgen/annotator.poku.js +15 -0
  63. package/lib/stages/postgen/auditBom.js +20 -6
  64. package/lib/stages/postgen/auditBom.poku.js +694 -1
  65. package/lib/stages/postgen/postgen.js +262 -11
  66. package/lib/stages/postgen/postgen.poku.js +306 -2
  67. package/lib/stages/postgen/ruleEngine.js +49 -1
  68. package/lib/stages/postgen/spdxConverter.poku.js +70 -0
  69. package/lib/stages/pregen/pregen.js +6 -4
  70. package/package.json +1 -1
  71. package/types/bin/repl.d.ts.map +1 -1
  72. package/types/lib/audit/index.d.ts.map +1 -1
  73. package/types/lib/audit/reporters.d.ts.map +1 -1
  74. package/types/lib/audit/scoring.d.ts.map +1 -1
  75. package/types/lib/audit/targets.d.ts +12 -0
  76. package/types/lib/audit/targets.d.ts.map +1 -1
  77. package/types/lib/cli/index.d.ts +2 -8
  78. package/types/lib/cli/index.d.ts.map +1 -1
  79. package/types/lib/evinser/evinser.d.ts.map +1 -1
  80. package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
  81. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
  82. package/types/lib/helpers/aiInventory.d.ts +23 -0
  83. package/types/lib/helpers/aiInventory.d.ts.map +1 -0
  84. package/types/lib/helpers/analyzer.d.ts +10 -0
  85. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  86. package/types/lib/helpers/auditCategories.d.ts +12 -0
  87. package/types/lib/helpers/auditCategories.d.ts.map +1 -0
  88. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  89. package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
  90. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
  91. package/types/lib/helpers/depsUtils.d.ts +8 -0
  92. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  93. package/types/lib/helpers/display.d.ts +17 -1
  94. package/types/lib/helpers/display.d.ts.map +1 -1
  95. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  96. package/types/lib/helpers/jsonLike.d.ts +4 -0
  97. package/types/lib/helpers/jsonLike.d.ts.map +1 -0
  98. package/types/lib/helpers/mcp.d.ts +29 -0
  99. package/types/lib/helpers/mcp.d.ts.map +1 -0
  100. package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
  101. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
  102. package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
  103. package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
  104. package/types/lib/helpers/provenanceUtils.d.ts +5 -3
  105. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -1
  106. package/types/lib/helpers/registryProvenance.d.ts +9 -0
  107. package/types/lib/helpers/registryProvenance.d.ts.map +1 -1
  108. package/types/lib/helpers/rustFormulationParser.d.ts +17 -0
  109. package/types/lib/helpers/rustFormulationParser.d.ts.map +1 -0
  110. package/types/lib/helpers/source.d.ts.map +1 -1
  111. package/types/lib/helpers/utils.d.ts +31 -1
  112. package/types/lib/helpers/utils.d.ts.map +1 -1
  113. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  114. package/types/lib/managers/binary.d.ts.map +1 -1
  115. package/types/lib/managers/docker.d.ts.map +1 -1
  116. package/types/lib/managers/piptree.d.ts.map +1 -1
  117. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  118. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  119. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  120. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  121. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
package/lib/cli/index.js CHANGED
@@ -3,13 +3,9 @@ import {
3
3
  accessSync,
4
4
  constants,
5
5
  lstatSync,
6
- mkdtempSync,
7
6
  readdirSync,
8
7
  readFileSync,
9
- rmSync,
10
8
  statSync,
11
- unlinkSync,
12
- writeFileSync,
13
9
  } from "node:fs";
14
10
  import { platform as _platform, arch, homedir } from "node:os";
15
11
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
@@ -22,7 +18,24 @@ import { parse } from "ssri";
22
18
  import { v4 as uuidv4 } from "uuid";
23
19
  import { parse as loadYaml } from "yaml";
24
20
 
25
- import { findJSImportsExports } from "../helpers/analyzer.js";
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";
33
+ import {
34
+ detectMcpInventory,
35
+ detectPythonMcpInventory,
36
+ findJSImportsExports,
37
+ } from "../helpers/analyzer.js";
38
+ import { expandBomAuditCategories } from "../helpers/auditCategories.js";
26
39
  import { parseCaxaMetadata } from "../helpers/caxa.js";
27
40
  import {
28
41
  CHROME_EXTENSION_PURL_TYPE,
@@ -30,9 +43,17 @@ import {
30
43
  collectInstalledChromeExtensions,
31
44
  discoverChromiumExtensionDirs,
32
45
  } from "../helpers/chromextutils.js";
33
- import { mergeDependencies, trimComponents } from "../helpers/depsUtils.js";
46
+ import {
47
+ mergeDependencies,
48
+ mergeServices,
49
+ trimComponents,
50
+ } from "../helpers/depsUtils.js";
34
51
  import { GIT_COMMAND } from "../helpers/envcontext.js";
35
52
  import { thoughtLog } from "../helpers/logger.js";
53
+ import {
54
+ classifyMcpReference,
55
+ enrichComponentWithMcpMetadata,
56
+ } from "../helpers/mcp.js";
36
57
  import { isPyLockFile } from "../helpers/pylockutils.js";
37
58
  import {
38
59
  buildDependencyTrackBomPayload,
@@ -71,6 +92,7 @@ import {
71
92
  generatePixiLockFile,
72
93
  getAllFiles,
73
94
  getCppModules,
95
+ getCratesMetadata,
74
96
  getGradleCommand,
75
97
  getLicenses,
76
98
  getMavenCommand,
@@ -87,6 +109,7 @@ import {
87
109
  getTmpDir,
88
110
  hasAnyProjectType,
89
111
  includeMavenTestScope,
112
+ isDryRun,
90
113
  isFeatureEnabled,
91
114
  isMac,
92
115
  isPackageManagerAllowed,
@@ -105,6 +128,7 @@ import {
105
128
  parseCabalData,
106
129
  parseCargoData,
107
130
  parseCargoDependencyData,
131
+ parseCargoManifestDependencyData,
108
132
  parseCargoTomlData,
109
133
  parseCljDep,
110
134
  parseCloudBuildData,
@@ -173,13 +197,23 @@ import {
173
197
  parseYarnWorkspace,
174
198
  readZipEntry,
175
199
  recomputeScope,
200
+ recordActivity,
201
+ resetActivityContext,
176
202
  SWIFT_CMD,
177
203
  safeExistsSync,
178
204
  safeMkdirSync,
205
+ safeMkdtempSync,
206
+ safeRmSync,
179
207
  safeSpawnSync,
208
+ safeUnlinkSync,
209
+ safeWriteSync,
210
+ setActivityContext,
180
211
  shouldFetchLicense,
181
212
  splitOutputByGradleProjects,
182
213
  } from "../helpers/utils.js";
214
+
215
+ export { summarizeAiInventory } from "../helpers/aiInventory.js";
216
+
183
217
  import {
184
218
  cleanupTempDir,
185
219
  collectInstalledExtensions,
@@ -254,6 +288,16 @@ const GRADLE_INIT_SCRIPT = resolve(
254
288
  const SBT_CACHE_DIR =
255
289
  process.env.SBT_CACHE_DIR || join(homedir(), ".ivy2", "cache");
256
290
 
291
+ function getCargoCacheDir() {
292
+ return process.env.CARGO_CACHE_DIR
293
+ ? resolve(process.env.CARGO_CACHE_DIR)
294
+ : resolve(
295
+ process.env.CARGO_HOME || join(homedir(), ".cargo"),
296
+ "registry",
297
+ "cache",
298
+ );
299
+ }
300
+
257
301
  // CycloneDX Hash pattern
258
302
  const HASH_PATTERN =
259
303
  "^([a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128})$";
@@ -1040,6 +1084,7 @@ function addComponent(
1040
1084
  if (pkg.components) {
1041
1085
  component.components = pkg.components;
1042
1086
  }
1087
+ component = enrichComponentWithMcpMetadata(component);
1043
1088
  const compMapKey = component.purl || component["bom-ref"];
1044
1089
  // Issue: 1353. We need to keep merging the properties
1045
1090
  if (compMap[compMapKey]) {
@@ -1241,6 +1286,23 @@ function addComponentHash(alg, digest, component) {
1241
1286
  * @returns {Object} BOM with namespace mapping
1242
1287
  */
1243
1288
  const buildBomNSData = (options, pkgInfo, ptype, context) => {
1289
+ // Many create*Bom call sites provide only a source directory (`src`) when
1290
+ // there is no single manifest/lock file to report, so activity records must
1291
+ // fall back to that directory to keep the target populated.
1292
+ const sourcePath =
1293
+ context?.srcDir || context?.src || options.path || options.filePath;
1294
+ const activityProjectType =
1295
+ context?.projectType ||
1296
+ (Array.isArray(options.projectType)
1297
+ ? options.projectType.length === 1
1298
+ ? options.projectType[0]
1299
+ : undefined
1300
+ : options.projectType);
1301
+ setActivityContext({
1302
+ packageType: ptype,
1303
+ sourcePath,
1304
+ ...(activityProjectType ? { projectType: activityProjectType } : {}),
1305
+ });
1244
1306
  const bomNSData = {
1245
1307
  bomJson: undefined,
1246
1308
  bomJsonFiles: undefined,
@@ -1255,6 +1317,7 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
1255
1317
  }
1256
1318
  const nsMapping = context.nsMapping || {};
1257
1319
  const dependencies = context.dependencies || [];
1320
+ const services = context.services || [];
1258
1321
  const parentComponent =
1259
1322
  determineParentComponent(options) || context.parentComponent;
1260
1323
  const metadata = addMetadata(parentComponent, options, context);
@@ -1270,6 +1333,9 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
1270
1333
  components,
1271
1334
  dependencies,
1272
1335
  };
1336
+ if (services.length) {
1337
+ jsonTpl.services = mergeServices([], services);
1338
+ }
1273
1339
  bomNSData.bomJson = jsonTpl;
1274
1340
  bomNSData.nsMapping = nsMapping;
1275
1341
  bomNSData.dependencies = dependencies;
@@ -1280,6 +1346,13 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
1280
1346
  bomNSData.formulationList = context.formulationList;
1281
1347
  }
1282
1348
  }
1349
+ recordActivity({
1350
+ kind: "read",
1351
+ reason: `Collected ${ptype || "generic"} component metadata.`,
1352
+ status: components?.length || parentComponent ? "completed" : "failed",
1353
+ target: context?.filename || sourcePath,
1354
+ });
1355
+ resetActivityContext();
1283
1356
  return bomNSData;
1284
1357
  };
1285
1358
 
@@ -1342,7 +1415,7 @@ export async function createJarBom(path, options) {
1342
1415
  jarFiles = jarFiles.concat(hpiFiles);
1343
1416
  }
1344
1417
  for (const jar of jarFiles) {
1345
- const tempDir = mkdtempSync(join(getTmpDir(), "jar-deps-"));
1418
+ const tempDir = safeMkdtempSync(join(getTmpDir(), "jar-deps-"));
1346
1419
  if (DEBUG_MODE) {
1347
1420
  console.log(`Parsing ${jar}`);
1348
1421
  }
@@ -1354,8 +1427,8 @@ export async function createJarBom(path, options) {
1354
1427
  pkgList = await getMvnMetadata(pkgList);
1355
1428
  }
1356
1429
  // Clean up
1357
- if (tempDir?.startsWith(getTmpDir()) && rmSync) {
1358
- rmSync(tempDir, { recursive: true, force: true });
1430
+ if (tempDir?.startsWith(getTmpDir())) {
1431
+ safeRmSync(tempDir, { recursive: true, force: true });
1359
1432
  }
1360
1433
  }
1361
1434
  pkgList = pkgList.concat(await convertJarNSToPackages(nsMapping));
@@ -1384,7 +1457,7 @@ export function createAndroidBom(path, options) {
1384
1457
  * @returns {Object|undefined} BOM object
1385
1458
  */
1386
1459
  export function createBinaryBom(path, options) {
1387
- const tempDir = mkdtempSync(join(getTmpDir(), "blint-tmp-"));
1460
+ const tempDir = safeMkdtempSync(join(getTmpDir(), "blint-tmp-"));
1388
1461
  const binaryBomFile = join(tempDir, "bom.json");
1389
1462
  getBinaryBom(path, binaryBomFile, options.deep);
1390
1463
  if (safeExistsSync(binaryBomFile)) {
@@ -1425,16 +1498,16 @@ export async function createJavaBom(path, options) {
1425
1498
  if (DEBUG_MODE) {
1426
1499
  console.log(`Retrieving packages from ${path}`);
1427
1500
  }
1428
- const tempDir = mkdtempSync(join(getTmpDir(), "war-deps-"));
1501
+ const tempDir = safeMkdtempSync(join(getTmpDir(), "war-deps-"));
1429
1502
  jarNSMapping = await collectJarNS(tempDir);
1430
1503
  pkgList = await extractJarArchive(path, tempDir, jarNSMapping);
1431
1504
  if (pkgList.length) {
1432
1505
  pkgList = await getMvnMetadata(pkgList);
1433
1506
  }
1434
1507
  // Clean up
1435
- if (tempDir?.startsWith(getTmpDir()) && rmSync) {
1508
+ if (tempDir?.startsWith(getTmpDir())) {
1436
1509
  console.log(`Cleaning up ${tempDir}`);
1437
- rmSync(tempDir, { recursive: true, force: true });
1510
+ safeRmSync(tempDir, { recursive: true, force: true });
1438
1511
  }
1439
1512
  } else {
1440
1513
  console.log(`${path} doesn't exist`);
@@ -1788,7 +1861,7 @@ export async function createJavaBom(path, options) {
1788
1861
  }
1789
1862
  }
1790
1863
  if (!DEBUG_MODE) {
1791
- unlinkSync(tempMvnTree);
1864
+ safeUnlinkSync(tempMvnTree);
1792
1865
  }
1793
1866
  }
1794
1867
  }
@@ -2329,7 +2402,7 @@ export async function createJavaBom(path, options) {
2329
2402
  `${options.multiProject ? "**/" : ""}build.sbt.lock`,
2330
2403
  options,
2331
2404
  );
2332
- const tempCacheDir = mkdtempSync(join(getTmpDir(), "sbt-cache-"));
2405
+ const tempCacheDir = safeMkdtempSync(join(getTmpDir(), "sbt-cache-"));
2333
2406
  safeMkdirSync(tempCacheDir, { recursive: true });
2334
2407
  if (
2335
2408
  sbtProjects?.length &&
@@ -2373,8 +2446,8 @@ export async function createJavaBom(path, options) {
2373
2446
  const useSlashSyntax = !sbtVersion || gte(sbtVersion, "1.5.0");
2374
2447
  const isDependencyTreeBuiltIn =
2375
2448
  sbtVersion != null && gte(sbtVersion, "1.4.0");
2376
- const tempDir = mkdtempSync(join(getTmpDir(), "cdxsbt-"));
2377
- const tempSbtgDir = mkdtempSync(join(getTmpDir(), "cdxsbtg-"));
2449
+ const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxsbt-"));
2450
+ const tempSbtgDir = safeMkdtempSync(join(getTmpDir(), "cdxsbtg-"));
2378
2451
  safeMkdirSync(tempSbtgDir, { recursive: true });
2379
2452
  // Create temporary plugins file
2380
2453
  const tempSbtPlugins = join(tempSbtgDir, "dep-plugins.sbt");
@@ -2388,7 +2461,7 @@ export async function createJavaBom(path, options) {
2388
2461
  console.log("Using addDependencyTreePlugin as the custom plugin");
2389
2462
  }
2390
2463
  }
2391
- writeFileSync(tempSbtPlugins, sbtPluginDefinition);
2464
+ safeWriteSync(tempSbtPlugins, sbtPluginDefinition);
2392
2465
  let sbtExtraArgs = "";
2393
2466
  const env = { ...process.env };
2394
2467
  // We need to collect the jars from the cache
@@ -2477,7 +2550,7 @@ export async function createJavaBom(path, options) {
2477
2550
  }
2478
2551
 
2479
2552
  // Cleanup
2480
- unlinkSync(tempSbtPlugins);
2553
+ safeUnlinkSync(tempSbtPlugins);
2481
2554
  } // else
2482
2555
 
2483
2556
  if (DEBUG_MODE) {
@@ -2510,7 +2583,7 @@ export async function createJavaBom(path, options) {
2510
2583
  }
2511
2584
  }
2512
2585
  if (tempCacheDir?.startsWith(getTmpDir())) {
2513
- rmSync(tempCacheDir, {
2586
+ safeRmSync(tempCacheDir, {
2514
2587
  recursive: true,
2515
2588
  force: true,
2516
2589
  });
@@ -2661,6 +2734,130 @@ export async function createJavaBom(path, options) {
2661
2734
  * @param {Object} options Parse options from the cli
2662
2735
  * @returns {Promise<Object>} Promise resolving to BOM object
2663
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
+
2758
+ function shouldDetectMcpInventory(options, allImports = {}) {
2759
+ if (hasAnyProjectType(["mcp"], options, false)) {
2760
+ return true;
2761
+ }
2762
+ const auditCategories = expandBomAuditCategories(options?.bomAuditCategories);
2763
+ if (
2764
+ auditCategories.some((category) =>
2765
+ ["mcp-server", "ai-agent"].includes(category),
2766
+ )
2767
+ ) {
2768
+ return true;
2769
+ }
2770
+ return Object.keys(allImports).some((importName) => {
2771
+ const classification = classifyMcpReference(importName);
2772
+ return classification.isMcp;
2773
+ });
2774
+ }
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
+
2664
2861
  export async function createNodejsBom(path, options) {
2665
2862
  let pkgList = [];
2666
2863
  let manifestFiles = [];
@@ -2668,6 +2865,15 @@ export async function createNodejsBom(path, options) {
2668
2865
  let parentComponent = {};
2669
2866
  const parentSubComponents = [];
2670
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: [] };
2671
2877
  // Docker mode requires special handling
2672
2878
  if (hasAnyProjectType(["docker", "oci", "container", "os"], options, false)) {
2673
2879
  const pkgJsonFiles = getAllFiles(path, "**/package.json", options);
@@ -2679,16 +2885,33 @@ export async function createNodejsBom(path, options) {
2679
2885
  pkgList = pkgList.concat(dlist);
2680
2886
  }
2681
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
+ }
2682
2898
  return buildBomNSData(options, pkgList, "npm", {
2683
2899
  allImports: {},
2684
2900
  src: path,
2685
2901
  filename: "package.json",
2902
+ dependencies: mergeDependencies(
2903
+ [],
2904
+ aiInventory.dependencies,
2905
+ parentComponent,
2906
+ ),
2686
2907
  parentComponent,
2908
+ services: aiInventory.services,
2687
2909
  });
2688
2910
  }
2689
2911
  }
2690
2912
  let allImports = {};
2691
2913
  let allExports = {};
2914
+ let mcpInventory = {};
2692
2915
  if (
2693
2916
  !hasAnyProjectType(["docker", "oci", "container", "os"], options, false) &&
2694
2917
  !options.noBabel
@@ -2701,6 +2924,43 @@ export async function createNodejsBom(path, options) {
2701
2924
  const retData = await findJSImportsExports(path, options.deep);
2702
2925
  allImports = retData.allImports;
2703
2926
  allExports = retData.allExports;
2927
+ if (shouldDetectMcpInventory(options, allImports)) {
2928
+ mcpInventory = detectMcpInventory(path, options.deep);
2929
+ }
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
+ });
2704
2964
  }
2705
2965
  let yarnLockFiles = getAllFiles(
2706
2966
  path,
@@ -3582,11 +3842,47 @@ export async function createNodejsBom(path, options) {
3582
3842
  options.deep,
3583
3843
  );
3584
3844
  }
3845
+ if (mcpInventory.components?.length) {
3846
+ pkgList = trimComponents(pkgList.concat(mcpInventory.components));
3847
+ }
3848
+ if (mcpInventory.dependencies?.length) {
3849
+ dependencies = mergeDependencies(
3850
+ dependencies,
3851
+ mcpInventory.dependencies,
3852
+ parentComponent,
3853
+ );
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
+ }
3585
3880
  return buildBomNSData(options, pkgList, "npm", {
3586
3881
  src: path,
3587
3882
  filename: manifestFiles.join(", "),
3588
3883
  dependencies,
3589
3884
  parentComponent,
3885
+ services: inventoryServices,
3590
3886
  });
3591
3887
  }
3592
3888
 
@@ -3765,7 +4061,17 @@ export async function createPythonBom(path, options) {
3765
4061
  let dependencies = [];
3766
4062
  let pkgList = [];
3767
4063
  let formulationList = [];
3768
- const tempDir = mkdtempSync(join(getTmpDir(), "cdxgen-venv-"));
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: [] };
4074
+ const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxgen-venv-"));
3769
4075
  let parentComponent = createDefaultParentComponent(path, "pypi", options);
3770
4076
  // We are checking only the root here for pipenv
3771
4077
  const pipenvMode = safeExistsSync(join(path, "Pipfile"));
@@ -3984,7 +4290,7 @@ export async function createPythonBom(path, options) {
3984
4290
  tempDir,
3985
4291
  `exported-${basename(basePath)}-reqs.txt`,
3986
4292
  );
3987
- writeFileSync(tmpReqFile, exportedReqs);
4293
+ safeWriteSync(tmpReqFile, exportedReqs);
3988
4294
  const dlist = await parseReqFile(tmpReqFile, false);
3989
4295
  if (dlist?.length) {
3990
4296
  pkgList = pkgList.concat(dlist);
@@ -4256,6 +4562,24 @@ export async function createPythonBom(path, options) {
4256
4562
  pkgList = pkgList.concat(dlist);
4257
4563
  }
4258
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);
4259
4583
  // Check and complete the dependency tree
4260
4584
  if (
4261
4585
  isFeatureEnabled(options, "safe-pip-install") &&
@@ -4296,14 +4620,38 @@ export async function createPythonBom(path, options) {
4296
4620
  }
4297
4621
  }
4298
4622
  // Clean up
4299
- if (tempDir?.startsWith(getTmpDir()) && rmSync) {
4300
- rmSync(tempDir, { recursive: true, force: true });
4623
+ if (tempDir?.startsWith(getTmpDir())) {
4624
+ safeRmSync(tempDir, { recursive: true, force: true });
4301
4625
  }
4302
4626
  // Re-compute the component scope
4303
4627
  pkgList = recomputeScope(pkgList, dependencies);
4304
4628
  if (shouldFetchLicense()) {
4305
4629
  pkgList = await getPyMetadata(pkgList, false);
4306
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
+ );
4307
4655
  return buildBomNSData(options, pkgList, "pypi", {
4308
4656
  allImports,
4309
4657
  src: path,
@@ -4311,6 +4659,7 @@ export async function createPythonBom(path, options) {
4311
4659
  dependencies,
4312
4660
  parentComponent,
4313
4661
  formulationList,
4662
+ services: inventoryServices,
4314
4663
  });
4315
4664
  }
4316
4665
 
@@ -4828,7 +5177,9 @@ export async function createRustBom(path, options) {
4828
5177
  if (DEBUG_MODE) {
4829
5178
  console.log(`Parsing ${f}`);
4830
5179
  }
4831
- const dlist = await parseCargoTomlData(f, cargoLockMode, pkgFilesMap);
5180
+ const dlist = await parseCargoTomlData(f, cargoLockMode, pkgFilesMap, {
5181
+ includeWorkspaceMembers: !options.multiProject,
5182
+ });
4832
5183
  if (dlist?.length) {
4833
5184
  if (!cargoLockMode) {
4834
5185
  pkgList = pkgList.concat(dlist);
@@ -4873,6 +5224,18 @@ export async function createRustBom(path, options) {
4873
5224
  }
4874
5225
  }
4875
5226
  }
5227
+ for (const f of cargoFiles) {
5228
+ const manifestDependencyList = parseCargoManifestDependencyData(f, {
5229
+ includeWorkspaceMembers: !options.multiProject,
5230
+ });
5231
+ if (manifestDependencyList?.length) {
5232
+ dependencyTree = mergeDependencies(
5233
+ dependencyTree,
5234
+ manifestDependencyList,
5235
+ parentComponent,
5236
+ );
5237
+ }
5238
+ }
4876
5239
  return buildBomNSData(options, pkgList, "cargo", {
4877
5240
  src: path,
4878
5241
  filename: cargoLockFiles.join(", "),
@@ -4881,6 +5244,95 @@ export async function createRustBom(path, options) {
4881
5244
  });
4882
5245
  }
4883
5246
 
5247
+ function buildCargoCacheComponent(crateFile) {
5248
+ const crateFileName = basename(crateFile, ".crate");
5249
+ const nameVersionMatch = crateFileName.match(/^(.+)-([0-9][A-Za-z0-9.+-]*)$/);
5250
+ if (!nameVersionMatch) {
5251
+ return undefined;
5252
+ }
5253
+ const [, name, version] = nameVersionMatch;
5254
+ const purl = new PackageURL(
5255
+ "cargo",
5256
+ "",
5257
+ name,
5258
+ version,
5259
+ null,
5260
+ null,
5261
+ ).toString();
5262
+ return {
5263
+ "bom-ref": decodeURIComponent(purl),
5264
+ group: "",
5265
+ name,
5266
+ properties: [
5267
+ {
5268
+ name: "SrcFile",
5269
+ value: crateFile,
5270
+ },
5271
+ {
5272
+ name: "cdx:cargo:cacheSource",
5273
+ value: "registry-cache",
5274
+ },
5275
+ ],
5276
+ purl,
5277
+ type: "library",
5278
+ version,
5279
+ };
5280
+ }
5281
+
5282
+ async function enrichCargoCacheComponent(crateFile, component) {
5283
+ if (!component) {
5284
+ return undefined;
5285
+ }
5286
+ try {
5287
+ component.hashes = [
5288
+ {
5289
+ alg: "SHA-256",
5290
+ content: await checksumFile("sha256", crateFile),
5291
+ },
5292
+ ];
5293
+ } catch {
5294
+ // continue without hashes
5295
+ }
5296
+ component.evidence = {
5297
+ identity: {
5298
+ field: "purl",
5299
+ confidence: 0.5,
5300
+ methods: [
5301
+ {
5302
+ technique: "filename",
5303
+ confidence: 0.5,
5304
+ value: crateFile,
5305
+ },
5306
+ ],
5307
+ },
5308
+ };
5309
+ return component;
5310
+ }
5311
+
5312
+ async function createCargoCacheBom(path, options) {
5313
+ const parentComponent = createDefaultParentComponent(path, "cargo", options);
5314
+ const crateFiles = path.endsWith(".crate")
5315
+ ? [resolve(path)]
5316
+ : getAllFiles(path, "**/*.crate", options);
5317
+ let pkgList = [];
5318
+ for (const crateFile of crateFiles) {
5319
+ const component = await enrichCargoCacheComponent(
5320
+ crateFile,
5321
+ buildCargoCacheComponent(crateFile),
5322
+ );
5323
+ if (component) {
5324
+ pkgList.push(component);
5325
+ }
5326
+ }
5327
+ if (pkgList.length && shouldFetchLicense()) {
5328
+ pkgList = await getCratesMetadata(pkgList);
5329
+ }
5330
+ return buildBomNSData(options, pkgList, "cargo", {
5331
+ src: path,
5332
+ parentComponent,
5333
+ });
5334
+ }
5335
+
4884
5336
  /**
4885
5337
  * Function to create bom string for Dart projects
4886
5338
  *
@@ -5483,7 +5935,7 @@ export async function createJenkinsBom(path, options) {
5483
5935
  `${options.multiProject ? "**/" : ""}*.hpi`,
5484
5936
  options,
5485
5937
  );
5486
- const tempDir = mkdtempSync(join(getTmpDir(), "hpi-deps-"));
5938
+ const tempDir = safeMkdtempSync(join(getTmpDir(), "hpi-deps-"));
5487
5939
  if (hpiFiles.length) {
5488
5940
  for (const f of hpiFiles) {
5489
5941
  if (DEBUG_MODE) {
@@ -5508,9 +5960,9 @@ export async function createJenkinsBom(path, options) {
5508
5960
  }
5509
5961
  }
5510
5962
  // Clean up
5511
- if (tempDir?.startsWith(getTmpDir()) && rmSync) {
5963
+ if (tempDir?.startsWith(getTmpDir())) {
5512
5964
  console.log(`Cleaning up ${tempDir}`);
5513
- rmSync(tempDir, { recursive: true, force: true });
5965
+ safeRmSync(tempDir, { recursive: true, force: true });
5514
5966
  }
5515
5967
  return buildBomNSData(options, pkgList, "maven", {
5516
5968
  src: path,
@@ -6281,7 +6733,7 @@ export async function createContainerSpecLikeBom(path, options) {
6281
6733
  },
6282
6734
  ];
6283
6735
  }
6284
- services = services.concat(servlist);
6736
+ services = mergeServices(services, servlist);
6285
6737
  }
6286
6738
  }
6287
6739
  }
@@ -6303,7 +6755,7 @@ export async function createContainerSpecLikeBom(path, options) {
6303
6755
  console.log(`Parsing ${f}`);
6304
6756
  }
6305
6757
  const servlist = parsePrivadoFile(f);
6306
- services = services.concat(servlist);
6758
+ services = mergeServices(services, servlist);
6307
6759
  if (servlist.length) {
6308
6760
  const aservice = servlist[0];
6309
6761
  if (aservice.data) {
@@ -6353,7 +6805,7 @@ export async function createContainerSpecLikeBom(path, options) {
6353
6805
  );
6354
6806
  }
6355
6807
  if (mbomData.bomJson.services) {
6356
- services = services.concat(mbomData.bomJson.services);
6808
+ services = mergeServices(services, mbomData.bomJson.services);
6357
6809
  }
6358
6810
  }
6359
6811
  if (DEBUG_MODE) {
@@ -6363,7 +6815,7 @@ export async function createContainerSpecLikeBom(path, options) {
6363
6815
  }
6364
6816
  }
6365
6817
  }
6366
- options.services = services;
6818
+ options.services = mergeServices([], services);
6367
6819
  options.ociSpecs = ociSpecs;
6368
6820
  return dedupeBom(options, components, parentComponent, dependencies);
6369
6821
  }
@@ -6763,7 +7215,10 @@ export async function createRubyBom(path, options) {
6763
7215
  }
6764
7216
  // Clean up
6765
7217
  if (process.env?.CDXGEN_GEM_HOME?.startsWith(getTmpDir())) {
6766
- rmSync(process.env.CDXGEN_GEM_HOME, { recursive: true, force: true });
7218
+ safeRmSync(process.env.CDXGEN_GEM_HOME, {
7219
+ recursive: true,
7220
+ force: true,
7221
+ });
6767
7222
  }
6768
7223
  } else {
6769
7224
  if (process.env.CDXGEN_GEM_HOME) {
@@ -7578,6 +8033,12 @@ export async function createMultiXBom(pathList, options) {
7578
8033
  let formulationList = [];
7579
8034
  let parentComponent = determineParentComponent(options) || {};
7580
8035
  let parentSubComponents = [];
8036
+ const setProjectTypeActivityContext = (projectType, sourcePath) => {
8037
+ setActivityContext({
8038
+ projectType,
8039
+ sourcePath,
8040
+ });
8041
+ };
7581
8042
  options.createMultiXBom = true;
7582
8043
  // Convert single path to an array
7583
8044
  if (!Array.isArray(pathList)) {
@@ -7657,27 +8118,58 @@ export async function createMultiXBom(pathList, options) {
7657
8118
  }
7658
8119
  }
7659
8120
  for (const path of pathList) {
8121
+ setActivityContext({
8122
+ projectType: Array.isArray(options.projectType)
8123
+ ? options.projectType.join(",")
8124
+ : options.projectType,
8125
+ sourcePath: path,
8126
+ });
8127
+ recordActivity({
8128
+ kind: "read",
8129
+ reason:
8130
+ "Scanning project path for supported languages and package managers.",
8131
+ status: "completed",
8132
+ target: path,
8133
+ });
7660
8134
  if (DEBUG_MODE) {
7661
8135
  console.log("Scanning", path);
7662
8136
  }
7663
8137
  if (pathList.length > 2) {
7664
8138
  thoughtLog(`Let's thoroughly check the path ${path}.`);
7665
8139
  }
7666
- // Node.js
7667
- 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);
7668
8143
  if (!hasAnyProjectType(["oci"], options, false)) {
7669
- thoughtLog(
7670
- "**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
7671
- );
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
+ }
7672
8157
  }
8158
+ setProjectTypeActivityContext(exactAiInventoryType || "js", path);
7673
8159
  bomData = await createNodejsBom(path, options);
7674
8160
  if (bomData?.bomJson?.components?.length) {
7675
- thoughtLog(
7676
- `I found ${bomData.bomJson.components.length} npm packages. Let's keep looking.`,
7677
- );
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
+ }
7678
8170
  if (DEBUG_MODE) {
7679
8171
  console.log(
7680
- `Found ${bomData.bomJson.components.length} npm packages at ${path}`,
8172
+ `Found ${bomData.bomJson.components.length} ${exactAiInventoryType || "npm"} components at ${path}`,
7681
8173
  );
7682
8174
  }
7683
8175
  components = components.concat(bomData.bomJson.components);
@@ -7685,6 +8177,12 @@ export async function createMultiXBom(pathList, options) {
7685
8177
  dependencies,
7686
8178
  bomData.bomJson.dependencies,
7687
8179
  );
8180
+ if (bomData?.bomJson?.services?.length) {
8181
+ options.services = mergeServices(
8182
+ options.services || [],
8183
+ bomData.bomJson.services,
8184
+ );
8185
+ }
7688
8186
  if (
7689
8187
  bomData.parentComponent &&
7690
8188
  Object.keys(bomData.parentComponent).length
@@ -7707,6 +8205,7 @@ export async function createMultiXBom(pathList, options) {
7707
8205
  "**JAVA**: Looking for Java projects (e.g., Maven, Gradle, SBT). I hope all configurations—from Java version to individual build settings—are correctly aligned.",
7708
8206
  );
7709
8207
  }
8208
+ setProjectTypeActivityContext("java", path);
7710
8209
  bomData = await createJavaBom(path, options);
7711
8210
  if (bomData?.bomJson?.components?.length) {
7712
8211
  thoughtLog(
@@ -7764,6 +8263,7 @@ export async function createMultiXBom(pathList, options) {
7764
8263
  "I'm running in a non-container environment. Let's hope the correct build tools are available ✌️.",
7765
8264
  );
7766
8265
  }
8266
+ setProjectTypeActivityContext("py", path);
7767
8267
  bomData = await createPythonBom(path, options);
7768
8268
  if (bomData?.bomJson?.components?.length) {
7769
8269
  thoughtLog(
@@ -7796,6 +8296,7 @@ export async function createMultiXBom(pathList, options) {
7796
8296
  "**GO**: Looking for go projects. I need to be cautious about purl namespaces and potential failures with the 'go list' command.",
7797
8297
  );
7798
8298
  }
8299
+ setProjectTypeActivityContext("go", path);
7799
8300
  bomData = await createGoBom(path, options);
7800
8301
  if (bomData?.bomJson?.components?.length) {
7801
8302
  thoughtLog(`I found ${bomData.bomJson.components.length} go packages.`);
@@ -7823,6 +8324,7 @@ export async function createMultiXBom(pathList, options) {
7823
8324
  "**RUST**: Let's search for Cargo/Rust projects. Should I warn the user that we don't support Cargo 'features' and native dependencies, which may lead to both false positives and false negatives? 🤔?",
7824
8325
  );
7825
8326
  }
8327
+ setProjectTypeActivityContext("rust", path);
7826
8328
  bomData = await createRustBom(path, options);
7827
8329
  if (bomData?.bomJson?.components?.length) {
7828
8330
  thoughtLog(
@@ -7859,6 +8361,7 @@ export async function createMultiXBom(pathList, options) {
7859
8361
  "**PHP**: About to search for Composer-based projects. I hope lock files are available; otherwise, the 'composer install' command might fail for various reasons.",
7860
8362
  );
7861
8363
  }
8364
+ setProjectTypeActivityContext("php", path);
7862
8365
  bomData = createPHPBom(path, options);
7863
8366
  if (bomData?.bomJson?.components?.length) {
7864
8367
  thoughtLog(
@@ -7895,6 +8398,7 @@ export async function createMultiXBom(pathList, options) {
7895
8398
  "**RUBY**: Are there any Ruby projects in this path? There's only one way to know.",
7896
8399
  );
7897
8400
  }
8401
+ setProjectTypeActivityContext("ruby", path);
7898
8402
  bomData = await createRubyBom(path, options);
7899
8403
  if (bomData?.bomJson?.components?.length) {
7900
8404
  thoughtLog(
@@ -7930,6 +8434,7 @@ export async function createMultiXBom(pathList, options) {
7930
8434
  if (!hasAnyProjectType(["oci"], options, false)) {
7931
8435
  thoughtLog("**CSHARP**: What about csharp and fsharp projects?");
7932
8436
  }
8437
+ setProjectTypeActivityContext("csharp", path);
7933
8438
  bomData = await createCsharpBom(path, options);
7934
8439
  if (bomData?.bomJson?.components?.length) {
7935
8440
  thoughtLog(
@@ -7966,6 +8471,7 @@ export async function createMultiXBom(pathList, options) {
7966
8471
  "**DART**: Looking for Dart projects. These are rare ones. Should I inform the user that they can pass the types argument via the command-line to speed things up?",
7967
8472
  );
7968
8473
  }
8474
+ setProjectTypeActivityContext("dart", path);
7969
8475
  bomData = await createDartBom(path, options);
7970
8476
  if (bomData?.bomJson?.components?.length) {
7971
8477
  thoughtLog(
@@ -7995,6 +8501,7 @@ export async function createMultiXBom(pathList, options) {
7995
8501
  "**HASKELL**: Looking for Haskell projects. They're rarely encountered.",
7996
8502
  );
7997
8503
  }
8504
+ setProjectTypeActivityContext("haskell", path);
7998
8505
  bomData = createHaskellBom(path, options);
7999
8506
  if (bomData?.bomJson?.components?.length) {
8000
8507
  thoughtLog(
@@ -8024,6 +8531,7 @@ export async function createMultiXBom(pathList, options) {
8024
8531
  "**ELIXIR**: Looking for Elixir projects—they're quite rare as well.",
8025
8532
  );
8026
8533
  }
8534
+ setProjectTypeActivityContext("elixir", path);
8027
8535
  bomData = createElixirBom(path, options);
8028
8536
  if (bomData?.bomJson?.components?.length) {
8029
8537
  thoughtLog(
@@ -8053,6 +8561,7 @@ export async function createMultiXBom(pathList, options) {
8053
8561
  "**C/C++**: Looking for C/C++ projects. Should I warn the user that the generated SBOM might have low accuracy and contain errors?",
8054
8562
  );
8055
8563
  }
8564
+ setProjectTypeActivityContext("c", path);
8056
8565
  bomData = createCppBom(path, options);
8057
8566
  if (bomData?.bomJson?.components?.length) {
8058
8567
  thoughtLog(
@@ -8082,6 +8591,7 @@ export async function createMultiXBom(pathList, options) {
8082
8591
  "**CLOJURE**: Looking for Clojure projects. Should I warn the user that the purl namespace 'clojars' isn't widely supported by tools like Dependency-Track?",
8083
8592
  );
8084
8593
  }
8594
+ setProjectTypeActivityContext("clojure", path);
8085
8595
  bomData = createClojureBom(path, options);
8086
8596
  if (bomData?.bomJson?.components?.length) {
8087
8597
  thoughtLog(
@@ -8111,6 +8621,7 @@ export async function createMultiXBom(pathList, options) {
8111
8621
  "**GITHUB**: Looking for any github packages and workflows.",
8112
8622
  );
8113
8623
  }
8624
+ setProjectTypeActivityContext("github", path);
8114
8625
  bomData = createGitHubBom(path, options);
8115
8626
  if (bomData?.bomJson?.components?.length) {
8116
8627
  thoughtLog(
@@ -8140,6 +8651,7 @@ export async function createMultiXBom(pathList, options) {
8140
8651
  "**CLOUDBUILD**: Let's check for CloudBuild configuration files that include package dependencies.",
8141
8652
  );
8142
8653
  }
8654
+ setProjectTypeActivityContext("cloudbuild", path);
8143
8655
  bomData = createCloudBuildBom(path, options);
8144
8656
  if (bomData?.bomJson?.components?.length) {
8145
8657
  thoughtLog(
@@ -8169,6 +8681,7 @@ export async function createMultiXBom(pathList, options) {
8169
8681
  "**SWIFT**: Now checking for Swift projects. We don't support CocoaPods, Objective-C, or pure Xcode projects, so the SBOM will be incomplete.",
8170
8682
  );
8171
8683
  }
8684
+ setProjectTypeActivityContext("swift", path);
8172
8685
  bomData = await createSwiftBom(path, options);
8173
8686
  if (bomData?.bomJson?.components?.length) {
8174
8687
  thoughtLog(
@@ -8198,6 +8711,7 @@ export async function createMultiXBom(pathList, options) {
8198
8711
  "**JAR**: Let's check for any bundled jar/war/ear files to improve the SBOM accuracy.",
8199
8712
  );
8200
8713
  }
8714
+ setProjectTypeActivityContext("jar", path);
8201
8715
  bomData = await createJarBom(path, options);
8202
8716
  if (bomData?.bomJson?.components?.length) {
8203
8717
  thoughtLog(
@@ -8222,6 +8736,7 @@ export async function createMultiXBom(pathList, options) {
8222
8736
  }
8223
8737
  }
8224
8738
  if (hasAnyProjectType(["oci", "cocoa"], options)) {
8739
+ setProjectTypeActivityContext("cocoa", path);
8225
8740
  bomData = await createCocoaBom(path, options);
8226
8741
  if (bomData?.bomJson?.components?.length) {
8227
8742
  if (DEBUG_MODE) {
@@ -8240,6 +8755,7 @@ export async function createMultiXBom(pathList, options) {
8240
8755
  }
8241
8756
  }
8242
8757
  if (hasAnyProjectType(["oci", "nix"], options)) {
8758
+ setProjectTypeActivityContext("nix", path);
8243
8759
  bomData = await createNixBom(path, options);
8244
8760
  if (bomData?.bomJson?.components?.length) {
8245
8761
  if (DEBUG_MODE) {
@@ -8261,6 +8777,7 @@ export async function createMultiXBom(pathList, options) {
8261
8777
  }
8262
8778
  }
8263
8779
  if (hasAnyProjectType(["caxa"], options)) {
8780
+ setProjectTypeActivityContext("caxa", path);
8264
8781
  bomData = await createCaxaBom(path, options);
8265
8782
  if (bomData?.bomJson?.components?.length) {
8266
8783
  if (DEBUG_MODE) {
@@ -8282,6 +8799,7 @@ export async function createMultiXBom(pathList, options) {
8282
8799
  }
8283
8800
  }
8284
8801
  if (hasAnyProjectType(["vscode-extension"], options)) {
8802
+ setProjectTypeActivityContext("vscode-extension", path);
8285
8803
  bomData = await createVscodeExtensionBom(path, options);
8286
8804
  if (bomData?.bomJson?.components?.length) {
8287
8805
  if (DEBUG_MODE) {
@@ -8321,6 +8839,7 @@ export async function createMultiXBom(pathList, options) {
8321
8839
  if (!isExplicitChromeExtensionPath) {
8322
8840
  options.__didScanChromeExtensions = true;
8323
8841
  }
8842
+ setProjectTypeActivityContext("chrome-extension", path);
8324
8843
  bomData = await createChromeExtensionBom(path, options);
8325
8844
  if (bomData?.bomJson?.components?.length) {
8326
8845
  if (DEBUG_MODE) {
@@ -8349,6 +8868,7 @@ export async function createMultiXBom(pathList, options) {
8349
8868
  "**CBOM**: Wait, the user wants me to look for cryptographic assets. Let's check thoroughly.",
8350
8869
  );
8351
8870
  }
8871
+ setProjectTypeActivityContext("cbom", path);
8352
8872
  bomData = await createCryptoCertsBom(path, options);
8353
8873
  if (bomData?.bomJson?.components?.length) {
8354
8874
  thoughtLog(
@@ -8370,6 +8890,7 @@ export async function createMultiXBom(pathList, options) {
8370
8890
  !options.lastWorkingDir.includes("/opt/") &&
8371
8891
  !options.lastWorkingDir.includes("/home/")
8372
8892
  ) {
8893
+ setProjectTypeActivityContext("jar", options.lastWorkingDir);
8373
8894
  bomData = createJarBom(options.lastWorkingDir, options);
8374
8895
  if (bomData?.bomJson?.components?.length) {
8375
8896
  if (DEBUG_MODE) {
@@ -8435,9 +8956,7 @@ export async function createMultiXBom(pathList, options) {
8435
8956
  // some cleanup, but not complete
8436
8957
  for (const path of pathList) {
8437
8958
  if (path.startsWith(join(getTmpDir(), "docker-images-"))) {
8438
- if (rmSync) {
8439
- rmSync(path, { recursive: true, force: true });
8440
- }
8959
+ safeRmSync(path, { recursive: true, force: true });
8441
8960
  }
8442
8961
  }
8443
8962
  const multiResult = dedupeBom(
@@ -8956,6 +9475,10 @@ export async function createBom(path, options) {
8956
9475
  projectType = ["java"];
8957
9476
  }
8958
9477
  if (projectType.length > 1) {
9478
+ setActivityContext({
9479
+ projectType: projectType.join(","),
9480
+ sourcePath: path,
9481
+ });
8959
9482
  thoughtLog(
8960
9483
  `The user has specified multiple project types: ${projectType.join(", ")}. Let's focus on the types one at a time.`,
8961
9484
  );
@@ -8963,6 +9486,7 @@ export async function createBom(path, options) {
8963
9486
  return await createMultiXBom(path, options);
8964
9487
  }
8965
9488
  if (projectType.length === 1) {
9489
+ setActivityContext({ projectType: projectType[0], sourcePath: path });
8966
9490
  if (hasAnyProjectType(["oci"], options, false)) {
8967
9491
  thoughtLog(
8968
9492
  "Okay, we're generating an SBOM for the OCI type. We'll need a compatible tool like Docker, Podman, or Nerdctl, along with the binary plugins.",
@@ -8986,6 +9510,12 @@ export async function createBom(path, options) {
8986
9510
  ) {
8987
9511
  return await createNodejsBom(path, options);
8988
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
+ }
8989
9519
  if (
8990
9520
  PROJECT_TYPE_ALIASES["py"].includes(projectType[0]) ||
8991
9521
  projectType?.[0]?.startsWith("python")
@@ -8998,6 +9528,9 @@ export async function createBom(path, options) {
8998
9528
  if (PROJECT_TYPE_ALIASES["rust"].includes(projectType[0])) {
8999
9529
  return await createRustBom(path, options);
9000
9530
  }
9531
+ if (PROJECT_TYPE_ALIASES["cargo-cache"].includes(projectType[0])) {
9532
+ return await createCargoCacheBom(getCargoCacheDir(), options);
9533
+ }
9001
9534
  if (PROJECT_TYPE_ALIASES["php"].includes(projectType[0])) {
9002
9535
  return createPHPBom(path, options);
9003
9536
  }
@@ -9108,6 +9641,16 @@ export async function createBom(path, options) {
9108
9641
  * @throws {Error} if the request fails
9109
9642
  */
9110
9643
  export async function submitBom(args, bomContents) {
9644
+ if (isDryRun) {
9645
+ recordActivity({
9646
+ kind: "network",
9647
+ reason:
9648
+ "Dry run mode blocks Dependency-Track submission and reports the request instead.",
9649
+ status: "blocked",
9650
+ target: getDependencyTrackBomUrl(args.serverUrl),
9651
+ });
9652
+ return undefined;
9653
+ }
9111
9654
  const serverUrl = getDependencyTrackBomUrl(args.serverUrl);
9112
9655
  const bomPayload = buildDependencyTrackBomPayload(args, bomContents);
9113
9656
  if (!bomPayload) {