@cdxgen/cdxgen 13.5.0 → 13.7.0

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 (380) hide show
  1. package/README.md +17 -10
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +260 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +28 -3
  6. package/bin/repl.js +47 -3
  7. package/bin/sign.js +211 -34
  8. package/bin/tracebom.js +18 -6
  9. package/bin/validate.js +24 -3
  10. package/bin/verify.js +113 -5
  11. package/data/behavior-taxonomy.schema.json +1491 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
  13. package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
  14. package/data/license-aliases.json +24 -2
  15. package/data/pqc-migration.json +239 -0
  16. package/data/remediations.json +176 -0
  17. package/data/rules/ai-bom.yaml +1 -1
  18. package/data/rules/brotli-decoder.yaml +107 -0
  19. package/data/rules/cbom-security.yaml +4 -3
  20. package/data/rules/package-integrity.yaml +28 -0
  21. package/data/rules/vscode-extensions.yaml +75 -0
  22. package/data/spdx-license-list.json +121 -7
  23. package/data/spdx-licenses.json +69 -0
  24. package/data/spdx.schema.json +845 -2
  25. package/lib/audit/index.js +25 -3
  26. package/lib/cli/bomAssembly.js +172 -4
  27. package/lib/cli/cliOptions.js +121 -17
  28. package/lib/cli/index.js +720 -902
  29. package/lib/cli/jsBom.js +156 -64
  30. package/lib/cli/jvmBom.js +602 -86
  31. package/lib/cli/managedBom.js +161 -19
  32. package/lib/cli/nativeBom.js +52 -5
  33. package/lib/core/boundedFile.js +344 -0
  34. package/lib/core/brotliProbe.js +182 -0
  35. package/lib/core/env.js +34 -0
  36. package/lib/core/fs.js +138 -3
  37. package/lib/core/globPatterns.js +218 -0
  38. package/lib/core/httpClient.js +98 -1
  39. package/lib/core/lockPaths.js +38 -0
  40. package/lib/core/paths.js +24 -1
  41. package/lib/core/processTree.js +100 -8
  42. package/lib/core/projectConfig.js +332 -25
  43. package/lib/core/toolRegistry.js +184 -0
  44. package/lib/ecosystems/cppEvidence.js +263 -106
  45. package/lib/ecosystems/cppIncludeResolver.js +16 -5
  46. package/lib/ecosystems/denoutils.js +2 -2
  47. package/lib/ecosystems/ecosystems.js +25 -10
  48. package/lib/ecosystems/gemHostProbe.js +301 -0
  49. package/lib/ecosystems/golem.js +3 -8
  50. package/lib/ecosystems/gradleutils.js +1909 -229
  51. package/lib/ecosystems/jsEvidence.js +90 -38
  52. package/lib/ecosystems/kosi.js +3 -5
  53. package/lib/ecosystems/npmVersionResolution.js +376 -0
  54. package/lib/ecosystems/parsers-bazel.js +107 -6
  55. package/lib/ecosystems/parsers-js.js +505 -22
  56. package/lib/ecosystems/parsers-misc.js +209 -20
  57. package/lib/ecosystems/parsers-python.js +178 -30
  58. package/lib/ecosystems/parsers-rust.js +117 -15
  59. package/lib/ecosystems/pythonutils.js +128 -2
  60. package/lib/ecosystems/remote/huggingface.js +22 -0
  61. package/lib/ecosystems/rubyutils.js +538 -31
  62. package/lib/ecosystems/rusi.js +2 -4
  63. package/lib/ecosystems/scalacliutils.js +2 -0
  64. package/lib/ecosystems/scalasem.js +798 -152
  65. package/lib/ecosystems/vendoredCode.js +237 -3
  66. package/lib/ecosystems/venvProbe.js +360 -0
  67. package/lib/evinser/evinser.js +536 -71
  68. package/lib/evinser/scalasem.js +5 -1
  69. package/lib/helpers/agenticToolutils.js +525 -94
  70. package/lib/helpers/bomSigner.js +43 -9
  71. package/lib/helpers/jssSigner.js +1276 -0
  72. package/lib/helpers/vsixutils.js +1574 -315
  73. package/lib/inventory/agentBlueprint.js +193 -0
  74. package/lib/inventory/agentToolNames.js +145 -0
  75. package/lib/inventory/aiCollector.js +153 -7
  76. package/lib/inventory/aiInventory.js +44 -8
  77. package/lib/inventory/aiProviders.js +324 -0
  78. package/lib/inventory/atomUtils.js +77 -0
  79. package/lib/inventory/bomUtils.js +8 -1
  80. package/lib/inventory/brotliDecoder.js +142 -0
  81. package/lib/inventory/buildToolRateLimit.js +1 -1
  82. package/lib/inventory/cbomutils.js +56 -7
  83. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  84. package/lib/inventory/citations.js +31 -2
  85. package/lib/inventory/communityAiConfigParser.js +978 -80
  86. package/lib/inventory/cyclonedx20.js +152 -0
  87. package/lib/inventory/cyclonedx20Common.js +286 -0
  88. package/lib/inventory/cyclonedx20Derived.js +159 -0
  89. package/lib/inventory/cyclonedx20Parties.js +552 -0
  90. package/lib/inventory/cyclonedx20Read.js +768 -0
  91. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  92. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  93. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  94. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  95. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  96. package/lib/inventory/cyclonedx20Render.js +766 -0
  97. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  98. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  99. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  100. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  101. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  102. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  103. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  104. package/lib/inventory/cyclonedx20Tables.js +594 -0
  105. package/lib/inventory/deps.js +141 -13
  106. package/lib/inventory/dosai.js +375 -6
  107. package/lib/inventory/dosaiParsers.js +3 -6
  108. package/lib/inventory/dotnetRuntime.js +367 -0
  109. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  110. package/lib/inventory/envcontext.js +436 -48
  111. package/lib/inventory/evidenceUtils.js +13 -1
  112. package/lib/inventory/fetchBatch.js +1 -1
  113. package/lib/inventory/formulationParsers.js +124 -14
  114. package/lib/inventory/gtfobins.js +13 -0
  115. package/lib/inventory/hbom.js +45 -6
  116. package/lib/inventory/hbomLoader.js +166 -0
  117. package/lib/inventory/hostRuntimeProbe.js +686 -0
  118. package/lib/inventory/jvmLocalRepos.js +9 -2
  119. package/lib/inventory/jvmToolEnv.js +29 -0
  120. package/lib/inventory/licenseEnhancer.js +12 -0
  121. package/lib/inventory/mavenCentralMirror.js +534 -0
  122. package/lib/inventory/mcpConfigParser.js +5 -0
  123. package/lib/inventory/nodeBuildInfo.js +218 -0
  124. package/lib/inventory/nodeHostProbe.js +202 -0
  125. package/lib/inventory/occurrenceKey.js +90 -0
  126. package/lib/inventory/osPackageResolver.js +182 -0
  127. package/lib/inventory/phpBuildInfo.js +10 -59
  128. package/lib/inventory/phpHostProbe.js +32 -488
  129. package/lib/inventory/pqcMigration.js +426 -0
  130. package/lib/inventory/rubyBuildInfo.js +972 -0
  131. package/lib/inventory/rubyHostProbe.js +218 -0
  132. package/lib/inventory/rustFormulationParser.js +21 -10
  133. package/lib/inventory/scalaCoords.js +4 -3
  134. package/lib/inventory/scalaDedupe.js +440 -0
  135. package/lib/inventory/source.js +114 -8
  136. package/lib/managers/binary.js +337 -3
  137. package/lib/managers/docker.js +79 -0
  138. package/lib/parsers/huggingfaceManifest.js +9 -1
  139. package/lib/parsers/huggingfaceUtils.js +18 -0
  140. package/lib/parsers/kiroPermissions.js +166 -0
  141. package/lib/parsers/largeJson.js +28 -11
  142. package/lib/parsers/vscodeExtensionId.js +183 -0
  143. package/lib/server/server.js +48 -35
  144. package/lib/stages/postgen/postgen.js +41 -1
  145. package/lib/stages/postgen/ruleEngine.js +13 -5
  146. package/lib/stages/postgen/specVersionCompat.js +76 -0
  147. package/lib/stages/pregen/envAudit.js +1 -0
  148. package/lib/stages/pregen/pregen.js +114 -57
  149. package/lib/validator/bomValidator.js +14 -2
  150. package/lib/validator/complianceEngine.js +30 -3
  151. package/lib/validator/complianceRules.js +1416 -4
  152. package/lib/validator/generated/validate-1.6.mjs +1 -1
  153. package/lib/validator/generated/validate-1.7.mjs +1 -1
  154. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  155. package/lib/validator/index.js +49 -8
  156. package/lib/validator/reporters/json.js +6 -0
  157. package/package.json +11 -10
  158. package/types/bin/repl.d.ts.map +1 -1
  159. package/types/lib/audit/index.d.ts +6 -2
  160. package/types/lib/audit/index.d.ts.map +1 -1
  161. package/types/lib/cli/bomAssembly.d.ts +29 -1
  162. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  163. package/types/lib/cli/cliOptions.d.ts +6 -0
  164. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  165. package/types/lib/cli/index.d.ts.map +1 -1
  166. package/types/lib/cli/jsBom.d.ts +3 -18
  167. package/types/lib/cli/jsBom.d.ts.map +1 -1
  168. package/types/lib/cli/jvmBom.d.ts +15 -0
  169. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  170. package/types/lib/cli/managedBom.d.ts.map +1 -1
  171. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  172. package/types/lib/core/boundedFile.d.ts +100 -0
  173. package/types/lib/core/boundedFile.d.ts.map +1 -0
  174. package/types/lib/core/brotliProbe.d.ts +37 -0
  175. package/types/lib/core/brotliProbe.d.ts.map +1 -0
  176. package/types/lib/core/env.d.ts +14 -0
  177. package/types/lib/core/env.d.ts.map +1 -1
  178. package/types/lib/core/fs.d.ts +4 -0
  179. package/types/lib/core/fs.d.ts.map +1 -1
  180. package/types/lib/core/globPatterns.d.ts +70 -0
  181. package/types/lib/core/globPatterns.d.ts.map +1 -0
  182. package/types/lib/core/httpClient.d.ts +24 -0
  183. package/types/lib/core/httpClient.d.ts.map +1 -1
  184. package/types/lib/core/lockPaths.d.ts +19 -0
  185. package/types/lib/core/lockPaths.d.ts.map +1 -0
  186. package/types/lib/core/paths.d.ts +9 -0
  187. package/types/lib/core/paths.d.ts.map +1 -1
  188. package/types/lib/core/processTree.d.ts +17 -4
  189. package/types/lib/core/processTree.d.ts.map +1 -1
  190. package/types/lib/core/projectConfig.d.ts +47 -8
  191. package/types/lib/core/projectConfig.d.ts.map +1 -1
  192. package/types/lib/core/toolRegistry.d.ts +36 -0
  193. package/types/lib/core/toolRegistry.d.ts.map +1 -0
  194. package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
  195. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  196. package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
  197. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
  198. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  199. package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
  200. package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
  201. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  202. package/types/lib/ecosystems/gradleutils.d.ts +294 -20
  203. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  205. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  206. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  207. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  208. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  209. package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
  210. package/types/lib/ecosystems/parsers-js.d.ts +17 -20
  211. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  212. package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
  213. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  214. package/types/lib/ecosystems/parsers-python.d.ts +10 -0
  215. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  216. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  217. package/types/lib/ecosystems/pythonutils.d.ts +19 -1
  218. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  219. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  220. package/types/lib/ecosystems/rubyutils.d.ts +144 -12
  221. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  222. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  223. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  224. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  225. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  226. package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
  227. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
  228. package/types/lib/ecosystems/venvProbe.d.ts +40 -0
  229. package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
  230. package/types/lib/evinser/evinser.d.ts +120 -13
  231. package/types/lib/evinser/evinser.d.ts.map +1 -1
  232. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  233. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  234. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  235. package/types/lib/helpers/bomSigner.d.ts +20 -3
  236. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  237. package/types/lib/helpers/jssSigner.d.ts +288 -0
  238. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  239. package/types/lib/helpers/vsixutils.d.ts +303 -19
  240. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  241. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  242. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  243. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  244. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  245. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  246. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  247. package/types/lib/inventory/aiProviders.d.ts +85 -0
  248. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  249. package/types/lib/inventory/atomUtils.d.ts +19 -0
  250. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  251. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  252. package/types/lib/inventory/brotliDecoder.d.ts +58 -0
  253. package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
  254. package/types/lib/inventory/cbomutils.d.ts +17 -0
  255. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  256. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  257. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  258. package/types/lib/inventory/citations.d.ts +5 -0
  259. package/types/lib/inventory/citations.d.ts.map +1 -1
  260. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  261. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  262. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  263. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  264. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  265. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  266. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  267. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  268. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  269. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  270. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  271. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  272. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  273. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  274. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  275. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  276. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  277. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  278. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  279. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  280. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  281. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  282. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  283. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  284. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  285. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  286. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  287. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  288. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  289. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  290. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  291. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  292. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  293. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  294. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  295. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  296. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  297. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  298. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  299. package/types/lib/inventory/deps.d.ts +35 -2
  300. package/types/lib/inventory/deps.d.ts.map +1 -1
  301. package/types/lib/inventory/dosai.d.ts +36 -1
  302. package/types/lib/inventory/dosai.d.ts.map +1 -1
  303. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  304. package/types/lib/inventory/dotnetRuntime.d.ts +91 -0
  305. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
  306. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  307. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  308. package/types/lib/inventory/envcontext.d.ts +74 -12
  309. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  310. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  311. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  312. package/types/lib/inventory/gtfobins.d.ts.map +1 -1
  313. package/types/lib/inventory/hbom.d.ts +11 -0
  314. package/types/lib/inventory/hbom.d.ts.map +1 -1
  315. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  316. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  317. package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
  318. package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
  319. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  320. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  321. package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
  322. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  323. package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
  324. package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
  325. package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
  326. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  327. package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
  328. package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
  329. package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
  330. package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
  331. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  332. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  333. package/types/lib/inventory/osPackageResolver.d.ts +38 -0
  334. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  335. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
  336. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
  337. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  338. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  339. package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
  340. package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
  341. package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
  342. package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
  343. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  344. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  345. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  346. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  347. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  348. package/types/lib/inventory/source.d.ts +25 -0
  349. package/types/lib/inventory/source.d.ts.map +1 -1
  350. package/types/lib/managers/binary.d.ts.map +1 -1
  351. package/types/lib/managers/docker.d.ts +12 -0
  352. package/types/lib/managers/docker.d.ts.map +1 -1
  353. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  354. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  355. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  356. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  357. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  358. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  359. package/types/lib/parsers/largeJson.d.ts +4 -3
  360. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  361. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  362. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  363. package/types/lib/server/server.d.ts.map +1 -1
  364. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  365. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  366. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  367. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  368. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  369. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  370. package/types/lib/stages/pregen/pregen.d.ts +12 -8
  371. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  372. package/types/lib/validator/bomValidator.d.ts +5 -1
  373. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  374. package/types/lib/validator/complianceEngine.d.ts +4 -4
  375. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  376. package/types/lib/validator/complianceRules.d.ts +14 -2
  377. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  378. package/types/lib/validator/index.d.ts +10 -0
  379. package/types/lib/validator/index.d.ts.map +1 -1
  380. package/types/lib/validator/reporters/json.d.ts.map +1 -1
@@ -1,11 +1,16 @@
1
- import { readFileSync } from "node:fs";
1
+ import { readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, dirname, extname, join } from "node:path";
3
3
 
4
4
  import { parse as loadYaml } from "yaml";
5
5
 
6
6
  import { safeExistsSync } from "../core/fs.js";
7
7
  import { sanitizeBomPropertyValue } from "../core/propertySanitizer.js";
8
+ import {
9
+ kiroPermissionSummaryEntries,
10
+ summarizeKiroPermissions,
11
+ } from "../parsers/kiroPermissions.js";
8
12
  import { isAgentSkillSpecValid } from "./agentSkillSpec.js";
13
+ import { summarizeAgentToolList } from "./agentToolNames.js";
9
14
  import { parseJsonLike } from "./jsonLike.js";
10
15
  import {
11
16
  credentialIndicatorsForText,
@@ -54,6 +59,33 @@ const COMMUNITY_AI_PATTERNS = [
54
59
  "**/.nanocoder/commands/**/*.md",
55
60
  ".nanocoder/skills/**/*.md",
56
61
  "**/.nanocoder/skills/**/*.md",
62
+ ".kiro/steering/**/*.md",
63
+ "**/.kiro/steering/**/*.md",
64
+ ".kiro/specs/**/*.md",
65
+ "**/.kiro/specs/**/*.md",
66
+ ".kiro/skills/*/SKILL.md",
67
+ "**/.kiro/skills/*/SKILL.md",
68
+ ".kiro/agents/*.json",
69
+ "**/.kiro/agents/*.json",
70
+ ".kiro/agents/*.md",
71
+ "**/.kiro/agents/*.md",
72
+ ".kiro/settings/permissions.yaml",
73
+ "**/.kiro/settings/permissions.yaml",
74
+ ".kiro/settings/cli.json",
75
+ "**/.kiro/settings/cli.json",
76
+ ".kiro/hooks/*.{json,sh,bash,zsh,ps1}",
77
+ "**/.kiro/hooks/*.{json,sh,bash,zsh,ps1}",
78
+ ".kiro/workflows/*.{yaml,yml,json}",
79
+ "**/.kiro/workflows/*.{yaml,yml,json}",
80
+ // Kiro keeps the same content outside .kiro in repositories that install
81
+ // its CLI configuration at a project root (steering documents, settings,
82
+ // and hooks). Only the settings files are matched as candidates; the
83
+ // steering and hook files are picked up from the validated Kiro root in
84
+ // parse(), so a plain docs steering/ folder never matches anything.
85
+ "settings/permissions.yaml",
86
+ "**/settings/permissions.yaml",
87
+ "settings/cli.json",
88
+ "**/settings/cli.json",
57
89
  "langgraph.json",
58
90
  "**/langgraph.json",
59
91
  "agents.py",
@@ -119,6 +151,119 @@ function parseFrontmatter(raw) {
119
151
  return { body: match[2].trim(), metadata };
120
152
  }
121
153
 
154
+ function parseYamlSafe(raw) {
155
+ try {
156
+ const doc = loadYaml(raw);
157
+ return doc && typeof doc === "object" && !Array.isArray(doc) ? doc : {};
158
+ } catch {
159
+ return {};
160
+ }
161
+ }
162
+
163
+ function parseJsonLikeSafe(raw) {
164
+ try {
165
+ const value = parseJsonLike(raw);
166
+ return value && typeof value === "object" && !Array.isArray(value)
167
+ ? value
168
+ : undefined;
169
+ } catch {
170
+ return undefined;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Whether a parsed permissions document holds at least one rule entry that
176
+ * names both a capability and an effect, the shape that sets a Kiro V3
177
+ * permission file apart from any other settings/permissions.yaml.
178
+ *
179
+ * @param {Object} doc Parsed YAML document
180
+ * @returns {boolean} True when the document looks like Kiro permission rules
181
+ */
182
+ function hasKiroPermissionRulesShape(doc) {
183
+ return (
184
+ Array.isArray(doc?.rules) &&
185
+ doc.rules.some(
186
+ (rule) =>
187
+ rule !== null &&
188
+ typeof rule === "object" &&
189
+ Object.hasOwn(rule, "capability") &&
190
+ Object.hasOwn(rule, "effect"),
191
+ )
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Whether a parsed cli.json carries at least one `chat.`-prefixed key, the
197
+ * shape only the Kiro CLI writes to its settings file.
198
+ *
199
+ * @param {Object|undefined} settings Parsed cli.json content
200
+ * @returns {boolean} True when a chat-prefixed key is present
201
+ */
202
+ function hasKiroChatKeys(settings) {
203
+ return (
204
+ Boolean(settings) &&
205
+ Object.keys(settings).some((key) => key.startsWith("chat."))
206
+ );
207
+ }
208
+
209
+ /**
210
+ * Decide whether a directory is a Kiro configuration root of the layout that
211
+ * keeps steering, settings, and hooks at the project root instead of under
212
+ * .kiro. The check reads the two settings files directly, so a directory that
213
+ * merely contains a docs steering/ folder never qualifies.
214
+ *
215
+ * @param {string} rootDir Candidate root directory
216
+ * @returns {boolean} True when the directory holds Kiro settings
217
+ */
218
+ function isKiroConfigurationRoot(rootDir) {
219
+ const permissionsFile = join(rootDir, "settings", "permissions.yaml");
220
+ if (
221
+ safeExistsSync(permissionsFile) &&
222
+ hasKiroPermissionRulesShape(
223
+ parseYamlSafe(readFileSync(permissionsFile, "utf-8")),
224
+ )
225
+ ) {
226
+ return true;
227
+ }
228
+ const cliFile = join(rootDir, "settings", "cli.json");
229
+ return (
230
+ safeExistsSync(cliFile) &&
231
+ hasKiroChatKeys(parseJsonLikeSafe(readFileSync(cliFile, "utf-8")))
232
+ );
233
+ }
234
+
235
+ /**
236
+ * List the files directly inside a directory, or an empty list when the
237
+ * directory is missing or unreadable.
238
+ *
239
+ * @param {string} dirPath Directory to list
240
+ * @returns {string[]} File names
241
+ */
242
+ function listFileNames(dirPath) {
243
+ try {
244
+ return readdirSync(dirPath, { withFileTypes: true })
245
+ .filter((entry) => entry.isFile())
246
+ .map((entry) => entry.name);
247
+ } catch {
248
+ return [];
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Whether a lower-cased path sits in a named directory either at the start of
254
+ * a root-relative path or after a separator, so both `steering/x.md` and
255
+ * `<root>/steering/x.md` match while `my-steering/x.md` does not.
256
+ *
257
+ * @param {string} lowerPath Normalized lower-cased path
258
+ * @param {string} dirName Directory name
259
+ * @returns {boolean} True when the path is inside the named directory
260
+ */
261
+ function inNamedDir(lowerPath, dirName) {
262
+ return (
263
+ lowerPath.startsWith(`${dirName}/`) || lowerPath.includes(`/${dirName}/`)
264
+ );
265
+ }
266
+
122
267
  function frameworkForFile(filePath, raw = "") {
123
268
  const lowerPath = normalizeFilePath(filePath).toLowerCase();
124
269
  if (
@@ -131,6 +276,24 @@ function frameworkForFile(filePath, raw = "") {
131
276
  if (lowerPath.includes("/.nanocoder/") || lowerPath.endsWith(".mcp.json")) {
132
277
  return "nanocoder";
133
278
  }
279
+ if (lowerPath.includes("/.kiro/")) {
280
+ return "kiro";
281
+ }
282
+ // Kiro keeps its configuration outside .kiro in some repositories. The
283
+ // settings files below only name the framework when their content matches
284
+ // the Kiro shapes, so a plain settings folder stays community-ai.
285
+ if (
286
+ lowerPath.endsWith("/settings/permissions.yaml") &&
287
+ hasKiroPermissionRulesShape(parseYamlSafe(raw))
288
+ ) {
289
+ return "kiro";
290
+ }
291
+ if (
292
+ lowerPath.endsWith("/settings/cli.json") &&
293
+ hasKiroChatKeys(parseJsonLikeSafe(raw))
294
+ ) {
295
+ return "kiro";
296
+ }
134
297
  if (lowerPath.includes("/.cursor/rules/")) {
135
298
  return "cursor";
136
299
  }
@@ -431,15 +594,11 @@ function parseClaudeSubagent(filePath, raw) {
431
594
  metadata?.description,
432
595
  );
433
596
  addUniqueProperty(component.properties, "cdx:agent:model", metadata?.model);
434
- if (Array.isArray(metadata?.tools)) {
435
- addUniqueProperty(
436
- component.properties,
437
- "cdx:agent:tools",
438
- metadata.tools.join(","),
439
- );
440
- } else if (typeof metadata?.tools === "string") {
441
- addUniqueProperty(component.properties, "cdx:agent:tools", metadata.tools);
442
- }
597
+ addUniqueProperty(
598
+ component.properties,
599
+ "cdx:agent:tools",
600
+ summarizeAgentToolList(metadata?.tools),
601
+ );
443
602
  maybeAddFileSignals(component.properties, filePath, raw);
444
603
  return [component];
445
604
  }
@@ -472,20 +631,370 @@ function parseAgentMarkdown(filePath, raw) {
472
631
  addUniqueProperty(
473
632
  component.properties,
474
633
  "cdx:agent:tools",
475
- metadata.tools.join(","),
634
+ summarizeAgentToolList(metadata.tools),
476
635
  );
477
636
  }
478
637
  if (Array.isArray(metadata?.disallowedTools)) {
479
638
  addUniqueProperty(
480
639
  component.properties,
481
640
  "cdx:agent:disallowedTools",
482
- metadata.disallowedTools.join(","),
641
+ summarizeAgentToolList(metadata.disallowedTools),
483
642
  );
484
643
  }
485
644
  maybeAddFileSignals(component.properties, filePath, raw);
486
645
  return [component];
487
646
  }
488
647
 
648
+ /**
649
+ * Add the summarized V3 permission posture of a Kiro agent or settings
650
+ * document to a component property list, using the `cdx:agent:` namespace of
651
+ * the project scan.
652
+ *
653
+ * @param {Object[]} properties Component property list to extend
654
+ * @param {Object} permissionDoc Parsed `permissions` object or YAML document
655
+ */
656
+ function addKiroPermissionProperties(properties, permissionDoc) {
657
+ for (const [suffix, value] of Object.entries(
658
+ kiroPermissionSummaryEntries(summarizeKiroPermissions(permissionDoc)),
659
+ )) {
660
+ addUniqueProperty(properties, `cdx:agent:${suffix}`, value);
661
+ }
662
+ }
663
+
664
+ /**
665
+ * Parse a Kiro V3 agent definition (`.kiro/agents/*.json`) into an
666
+ * agent-definition component. Only the name, the model, redacted counts of
667
+ * MCP servers and tools, the permission posture, and the integer upgrade
668
+ * revision are recorded; the prompt, resource values, toolsSettings, and the
669
+ * upgrade fingerprint or source are never copied.
670
+ *
671
+ * @param {string} filePath Agent definition path
672
+ * @param {string} raw File contents
673
+ * @returns {Object[]} Component list (one agent component or empty)
674
+ */
675
+ function parseKiroAgentJson(filePath, raw) {
676
+ if (basename(filePath).toLowerCase().endsWith(".example")) {
677
+ return [];
678
+ }
679
+ const agent = parseJsonLikeSafe(raw);
680
+ if (!agent || typeof agent.name !== "string" || !agent.name.trim()) {
681
+ return [];
682
+ }
683
+ const component = createComponent(
684
+ filePath,
685
+ frameworkForFile(filePath, raw),
686
+ "agent-definition",
687
+ agent.name,
688
+ );
689
+ addUniqueProperty(component.properties, "cdx:agent:role", "kiro-agent");
690
+ if (typeof agent.model === "string") {
691
+ addUniqueProperty(component.properties, "cdx:agent:model", agent.model);
692
+ }
693
+ if (agent.mcpServers && typeof agent.mcpServers === "object") {
694
+ addUniqueProperty(
695
+ component.properties,
696
+ "cdx:agent:mcpServerCount",
697
+ Object.keys(agent.mcpServers).length,
698
+ );
699
+ }
700
+ if (Array.isArray(agent.tools)) {
701
+ addUniqueProperty(
702
+ component.properties,
703
+ "cdx:agent:toolCount",
704
+ agent.tools.length,
705
+ );
706
+ }
707
+ if (Array.isArray(agent.allowedTools)) {
708
+ addUniqueProperty(
709
+ component.properties,
710
+ "cdx:agent:allowedToolCount",
711
+ agent.allowedTools.length,
712
+ );
713
+ }
714
+ if (agent.permissions && typeof agent.permissions === "object") {
715
+ addKiroPermissionProperties(component.properties, agent.permissions);
716
+ }
717
+ if (Number.isInteger(agent.kiroUpgrade?.revision)) {
718
+ addUniqueProperty(
719
+ component.properties,
720
+ "cdx:agent:upgradeRevision",
721
+ agent.kiroUpgrade.revision,
722
+ );
723
+ }
724
+ maybeAddFileSignals(component.properties, filePath, raw);
725
+ return [component];
726
+ }
727
+
728
+ /**
729
+ * Parse a Kiro permission policy (`permissions.yaml`) into a file component
730
+ * carrying the summarized rule posture. Match patterns and other rule text
731
+ * are never copied.
732
+ *
733
+ * @param {string} filePath Permissions file path
734
+ * @param {string} raw File contents
735
+ * @returns {Object[]} Component list
736
+ */
737
+ function parseKiroPermissionPolicy(filePath, raw, framework) {
738
+ const component = createComponent(
739
+ filePath,
740
+ framework || frameworkForFile(filePath, raw),
741
+ "permission-policy",
742
+ basename(filePath),
743
+ "file",
744
+ );
745
+ addKiroPermissionProperties(component.properties, parseYamlSafe(raw));
746
+ maybeAddFileSignals(component.properties, filePath, raw);
747
+ return [component];
748
+ }
749
+
750
+ // Hook triggers and action types of the Kiro hook schema shared by the CLI
751
+ // and the IDE, kept as allowlisted enums so a manifest never contributes free
752
+ // text. Matcher values, command strings, and prompts are never emitted.
753
+ const KIRO_HOOK_EVENTS = new Map(
754
+ [
755
+ "PostFileCreate",
756
+ "PostFileDelete",
757
+ "PostFileSave",
758
+ "PostTaskExec",
759
+ "PostToolUse",
760
+ "PreTaskExec",
761
+ "PreToolUse",
762
+ "SessionStart",
763
+ "Stop",
764
+ "UserPromptSubmit",
765
+ ].map((event) => [event.toLowerCase(), event]),
766
+ );
767
+ const KIRO_HOOK_ACTION_TYPES = new Map(
768
+ ["agent", "command"].map((actionType) => [
769
+ actionType.toLowerCase(),
770
+ actionType,
771
+ ]),
772
+ );
773
+
774
+ /**
775
+ * Parse a Kiro hook file (`.kiro/hooks/*.json` and hook scripts) into a file
776
+ * component. JSON manifests contribute the allowlisted lifecycle events,
777
+ * allowlisted action types, and a hook count; scripts contribute the file
778
+ * signals only.
779
+ *
780
+ * @param {string} filePath Hook file path
781
+ * @param {string} raw File contents
782
+ * @returns {Object[]} Component list
783
+ */
784
+ function parseKiroHookFile(filePath, raw, framework) {
785
+ const component = createComponent(
786
+ filePath,
787
+ framework || frameworkForFile(filePath, raw),
788
+ "hook-file",
789
+ basename(filePath),
790
+ "file",
791
+ );
792
+ if (basename(filePath).toLowerCase().endsWith(".json")) {
793
+ const manifest = parseJsonLikeSafe(raw);
794
+ const hooks = Array.isArray(manifest?.hooks)
795
+ ? manifest.hooks.filter(
796
+ (hook) => hook !== null && typeof hook === "object",
797
+ )
798
+ : [];
799
+ addUniqueProperty(
800
+ component.properties,
801
+ "cdx:agent:hookCount",
802
+ hooks.length,
803
+ );
804
+ const events = new Set();
805
+ const actionTypes = new Set();
806
+ for (const hook of hooks) {
807
+ if (typeof hook.trigger === "string") {
808
+ const event = KIRO_HOOK_EVENTS.get(hook.trigger.toLowerCase());
809
+ if (event) {
810
+ events.add(event);
811
+ }
812
+ }
813
+ if (typeof hook.action?.type === "string") {
814
+ const actionType = KIRO_HOOK_ACTION_TYPES.get(
815
+ hook.action.type.toLowerCase(),
816
+ );
817
+ if (actionType) {
818
+ actionTypes.add(actionType);
819
+ }
820
+ }
821
+ }
822
+ addUniqueProperty(
823
+ component.properties,
824
+ "cdx:agent:hookEvents",
825
+ [...events].sort().join(","),
826
+ );
827
+ addUniqueProperty(
828
+ component.properties,
829
+ "cdx:agent:hookActionTypes",
830
+ [...actionTypes].sort().join(","),
831
+ );
832
+ }
833
+ maybeAddFileSignals(component.properties, filePath, raw);
834
+ return [component];
835
+ }
836
+
837
+ /**
838
+ * Parse a Kiro workflow file (`.kiro/workflows/*`) into a file component.
839
+ * Workflow contents are never copied.
840
+ *
841
+ * @param {string} filePath Workflow file path
842
+ * @param {string} raw File contents
843
+ * @returns {Object[]} Component list
844
+ */
845
+ function parseKiroWorkflowFile(filePath, raw, framework) {
846
+ const component = createComponent(
847
+ filePath,
848
+ framework || frameworkForFile(filePath, raw),
849
+ "workflow-file",
850
+ basename(filePath),
851
+ "file",
852
+ );
853
+ maybeAddFileSignals(component.properties, filePath, raw);
854
+ return [component];
855
+ }
856
+
857
+ /**
858
+ * Parse a Kiro CLI settings file (`settings/cli.json`) into a file component.
859
+ * Only the default agent name and a count of boolean feature flags are
860
+ * recorded; every other value stays out of the BOM.
861
+ *
862
+ * @param {string} filePath Settings file path
863
+ * @param {string} raw File contents
864
+ * @returns {Object[]} Component list
865
+ */
866
+ function parseKiroSettings(filePath, raw, framework) {
867
+ const settings = parseJsonLikeSafe(raw);
868
+ if (!settings) {
869
+ return [];
870
+ }
871
+ const component = createComponent(
872
+ filePath,
873
+ framework || frameworkForFile(filePath, raw),
874
+ "settings",
875
+ basename(filePath),
876
+ "file",
877
+ );
878
+ if (typeof settings["chat.defaultAgent"] === "string") {
879
+ addUniqueProperty(
880
+ component.properties,
881
+ "cdx:agent:defaultAgent",
882
+ settings["chat.defaultAgent"],
883
+ );
884
+ }
885
+ const booleanFlagCount = Object.keys(settings).filter(
886
+ (key) => typeof settings[key] === "boolean",
887
+ ).length;
888
+ if (booleanFlagCount) {
889
+ addUniqueProperty(
890
+ component.properties,
891
+ "cdx:agent:booleanFeatureFlagCount",
892
+ booleanFlagCount,
893
+ );
894
+ }
895
+ maybeAddFileSignals(component.properties, filePath, raw);
896
+ return [component];
897
+ }
898
+
899
+ /**
900
+ * Route one Kiro file (under `.kiro` or in a validated Kiro configuration
901
+ * root) to its parser. Files outside these shapes return no components. A
902
+ * validated configuration root names its framework explicitly, because the
903
+ * individual file content of a steering or hook document cannot identify it.
904
+ *
905
+ * @param {string} filePath File path
906
+ * @param {string} raw File contents
907
+ * @param {string} [framework] Framework override for validated root files
908
+ * @returns {Object[]} Component list
909
+ */
910
+ function parseKiroLayoutFile(filePath, raw, framework) {
911
+ const resolvedFramework = framework || frameworkForFile(filePath, raw);
912
+ const lowerPath = normalizeFilePath(filePath).toLowerCase();
913
+ if (lowerPath.endsWith("/skill.md")) {
914
+ return parseSkillFile(filePath, raw);
915
+ }
916
+ if (lowerPath.endsWith(".json") && inNamedDir(lowerPath, "agents")) {
917
+ return parseKiroAgentJson(filePath, raw);
918
+ }
919
+ if (inNamedDir(lowerPath, "agents")) {
920
+ return parseAgentMarkdown(filePath, raw);
921
+ }
922
+ if (lowerPath.endsWith(".md") && inNamedDir(lowerPath, "specs")) {
923
+ return parseInstructionMarkdown(
924
+ filePath,
925
+ raw,
926
+ "agent-spec",
927
+ resolvedFramework,
928
+ );
929
+ }
930
+ if (lowerPath.endsWith(".md") && inNamedDir(lowerPath, "steering")) {
931
+ return parseInstructionMarkdown(
932
+ filePath,
933
+ raw,
934
+ undefined,
935
+ resolvedFramework,
936
+ );
937
+ }
938
+ if (lowerPath.endsWith("/permissions.yaml")) {
939
+ return parseKiroPermissionPolicy(filePath, raw, resolvedFramework);
940
+ }
941
+ if (lowerPath.endsWith("/cli.json")) {
942
+ return parseKiroSettings(filePath, raw, resolvedFramework);
943
+ }
944
+ if (inNamedDir(lowerPath, "hooks")) {
945
+ return parseKiroHookFile(filePath, raw, resolvedFramework);
946
+ }
947
+ if (inNamedDir(lowerPath, "workflows")) {
948
+ return parseKiroWorkflowFile(filePath, raw, resolvedFramework);
949
+ }
950
+ return [];
951
+ }
952
+
953
+ /**
954
+ * Pick up the steering, settings, and hook files of a validated Kiro
955
+ * configuration root. Files the parser already received are skipped, so a
956
+ * settings file claimed by its own glob is never parsed twice.
957
+ *
958
+ * @param {string} rootDir Validated Kiro configuration root
959
+ * @param {Set<string>} inputKeys Lower-cased paths already being parsed
960
+ * @param {Object[]} components Component list to append to
961
+ */
962
+ function expandKiroConfigurationRoot(rootDir, inputKeys, components) {
963
+ const candidates = [
964
+ ...listFileNames(join(rootDir, "settings")).map((name) =>
965
+ join(rootDir, "settings", name),
966
+ ),
967
+ ...listFileNames(join(rootDir, "steering")).map((name) =>
968
+ join(rootDir, "steering", name),
969
+ ),
970
+ ...listFileNames(join(rootDir, "hooks")).map((name) =>
971
+ join(rootDir, "hooks", name),
972
+ ),
973
+ ];
974
+ for (const candidate of candidates) {
975
+ const lowerCandidate = normalizeFilePath(candidate).toLowerCase();
976
+ if (inputKeys.has(lowerCandidate)) {
977
+ continue;
978
+ }
979
+ const isLayoutFile =
980
+ lowerCandidate.endsWith("/settings/permissions.yaml") ||
981
+ lowerCandidate.endsWith("/settings/cli.json") ||
982
+ (lowerCandidate.endsWith(".md") &&
983
+ inNamedDir(lowerCandidate, "steering")) ||
984
+ (lowerCandidate.endsWith(".json") && inNamedDir(lowerCandidate, "hooks"));
985
+ if (!isLayoutFile) {
986
+ continue;
987
+ }
988
+ let raw;
989
+ try {
990
+ raw = readFileSync(candidate, "utf-8");
991
+ } catch {
992
+ continue;
993
+ }
994
+ components.push(...parseKiroLayoutFile(candidate, raw, "kiro"));
995
+ }
996
+ }
997
+
489
998
  function parseNanocoderCommand(filePath, raw) {
490
999
  const { metadata } = parseFrontmatter(raw);
491
1000
  const component = createComponent(
@@ -535,8 +1044,8 @@ function parseNanocoderCommand(filePath, raw) {
535
1044
  return [component];
536
1045
  }
537
1046
 
538
- function parseInstructionMarkdown(filePath, raw) {
539
- const framework = frameworkForFile(filePath, raw);
1047
+ function parseInstructionMarkdown(filePath, raw, kindOverride, framework) {
1048
+ const resolvedFramework = framework || frameworkForFile(filePath, raw);
540
1049
  const { body, metadata } = parseFrontmatter(raw);
541
1050
  const lowerPath = normalizeFilePath(filePath).toLowerCase();
542
1051
  const headingMatch = body.match(/^#\s+(.+)$/mu);
@@ -547,12 +1056,20 @@ function parseInstructionMarkdown(filePath, raw) {
547
1056
  if (!name || !body.trim()) {
548
1057
  return [];
549
1058
  }
550
- const kind = lowerPath.includes("/commands/")
551
- ? "custom-command"
552
- : lowerPath.includes("/skills/")
553
- ? "skill-file"
554
- : "agent-instructions";
555
- const component = createComponent(filePath, framework, kind, name, "file");
1059
+ const kind =
1060
+ kindOverride ||
1061
+ (lowerPath.includes("/commands/")
1062
+ ? "custom-command"
1063
+ : lowerPath.includes("/skills/")
1064
+ ? "skill-file"
1065
+ : "agent-instructions");
1066
+ const component = createComponent(
1067
+ filePath,
1068
+ resolvedFramework,
1069
+ kind,
1070
+ name,
1071
+ "file",
1072
+ );
556
1073
  addUniqueProperty(
557
1074
  component.properties,
558
1075
  "cdx:agent:description",
@@ -772,18 +1289,405 @@ function parseCrewAiYaml(filePath, raw) {
772
1289
  return components;
773
1290
  }
774
1291
 
775
- function captureQuotedValue(block, key) {
776
- const patterns = [
777
- new RegExp(`${key}\\s*=\\s*["'\`]([^"'\`]+)["'\`]`, "u"),
778
- new RegExp(`${key}\\s*=\\s*dedent\\([^\\n]*?["'\`]([\\s\\S]*?)["'\`]`, "u"),
779
- ];
780
- for (const pattern of patterns) {
781
- const match = block.match(pattern);
782
- if (match?.[1]) {
783
- return match[1].replace(/\s+/gu, " ").trim();
1292
+ // Keyword arguments of a CrewAI Agent(...) or Task(...) call that cdxgen
1293
+ // records, with the property each one becomes.
1294
+ const CREWAI_TEXT_ARGUMENTS = [
1295
+ ["role", "cdx:crewai:role"],
1296
+ ["goal", "cdx:crewai:goal"],
1297
+ ["backstory", "cdx:crewai:backstory"],
1298
+ ["description", "cdx:crewai:description"],
1299
+ ["expected_output", "cdx:crewai:expectedOutput"],
1300
+ ];
1301
+
1302
+ function isPythonBlank(character) {
1303
+ return (
1304
+ character === " " ||
1305
+ character === "\t" ||
1306
+ character === "\r" ||
1307
+ character === "\f" ||
1308
+ character === "\n"
1309
+ );
1310
+ }
1311
+
1312
+ function isPythonNameCharacter(character) {
1313
+ return (
1314
+ character !== undefined &&
1315
+ ((character >= "a" && character <= "z") ||
1316
+ (character >= "A" && character <= "Z") ||
1317
+ (character >= "0" && character <= "9") ||
1318
+ character === "_")
1319
+ );
1320
+ }
1321
+
1322
+ /**
1323
+ * Find the end of the Python string literal whose opening quote is at
1324
+ * `index`. A single-quoted literal also ends at a newline, where Python
1325
+ * would reject it.
1326
+ *
1327
+ * @param {string} text Python source
1328
+ * @param {number} index offset of the opening quote
1329
+ * @param {number} limit offset the literal may not reach past
1330
+ * @returns {{contentStart: number, contentEnd: number, end: number}} offsets
1331
+ * of the literal's content and of the first character after it
1332
+ */
1333
+ function pythonStringSpan(text, index, limit) {
1334
+ const quote = text[index];
1335
+ const delimiter = text.startsWith(quote.repeat(3), index)
1336
+ ? quote.repeat(3)
1337
+ : quote;
1338
+ const contentStart = index + delimiter.length;
1339
+ let cursor = contentStart;
1340
+ while (cursor < limit) {
1341
+ if (text[cursor] === "\\") {
1342
+ cursor += 2;
1343
+ continue;
1344
+ }
1345
+ if (delimiter.length === 1 && text[cursor] === "\n") {
1346
+ return { contentEnd: cursor, contentStart, end: cursor };
1347
+ }
1348
+ if (text.startsWith(delimiter, cursor)) {
1349
+ return {
1350
+ contentEnd: cursor,
1351
+ contentStart,
1352
+ end: cursor + delimiter.length,
1353
+ };
1354
+ }
1355
+ cursor += 1;
1356
+ }
1357
+ return { contentEnd: limit, contentStart, end: limit };
1358
+ }
1359
+
1360
+ /**
1361
+ * Split Python source into logical lines: a statement together with the
1362
+ * lines it continues onto inside brackets, a triple-quoted string or after a
1363
+ * backslash. Blank and comment-only lines are left out. Each character is
1364
+ * read once, so the cost is linear in the size of the source.
1365
+ *
1366
+ * @param {string} raw Python source
1367
+ * @returns {{start: number, end: number, indent: number}[]} offsets of each
1368
+ * logical line's first non-blank character and of its end, and the width
1369
+ * of its indentation
1370
+ */
1371
+ function pythonLogicalLines(raw) {
1372
+ const lines = [];
1373
+ let depth = 0;
1374
+ let current;
1375
+ let cursor = 0;
1376
+ while (cursor < raw.length) {
1377
+ if (!current) {
1378
+ let indent = 0;
1379
+ while (raw[cursor] === " " || raw[cursor] === "\t") {
1380
+ indent += 1;
1381
+ cursor += 1;
1382
+ }
1383
+ const first = raw[cursor];
1384
+ if (first === undefined) {
1385
+ break;
1386
+ }
1387
+ if (first === "\n" || first === "\r" || first === "#") {
1388
+ const newline = raw.indexOf("\n", cursor);
1389
+ cursor = newline === -1 ? raw.length : newline + 1;
1390
+ continue;
1391
+ }
1392
+ current = { end: raw.length, indent, start: cursor };
1393
+ depth = 0;
1394
+ }
1395
+ const character = raw[cursor];
1396
+ if (character === '"' || character === "'") {
1397
+ cursor = pythonStringSpan(raw, cursor, raw.length).end;
1398
+ continue;
1399
+ }
1400
+ if (character === "#") {
1401
+ const newline = raw.indexOf("\n", cursor);
1402
+ cursor = newline === -1 ? raw.length : newline;
1403
+ continue;
1404
+ }
1405
+ if (character === "\\") {
1406
+ cursor += 2;
1407
+ continue;
1408
+ }
1409
+ if (character === "(" || character === "[" || character === "{") {
1410
+ depth += 1;
1411
+ } else if (character === ")" || character === "]" || character === "}") {
1412
+ depth = Math.max(0, depth - 1);
1413
+ } else if (character === "\n" && depth === 0) {
1414
+ current.end = cursor;
1415
+ lines.push(current);
1416
+ current = undefined;
1417
+ }
1418
+ cursor += 1;
1419
+ }
1420
+ if (current) {
1421
+ lines.push(current);
1422
+ }
1423
+ return lines;
1424
+ }
1425
+
1426
+ function skipPythonSpaces(raw, cursor, end) {
1427
+ let position = cursor;
1428
+ while (position < end && (raw[position] === " " || raw[position] === "\t")) {
1429
+ position += 1;
1430
+ }
1431
+ return position;
1432
+ }
1433
+
1434
+ /**
1435
+ * Read the function name from a logical line that starts with `def name(`
1436
+ * or `async def name(`.
1437
+ *
1438
+ * @param {string} raw Python source
1439
+ * @param {{start: number, end: number}} line logical line
1440
+ * @returns {string|undefined} function name
1441
+ */
1442
+ function pythonFunctionName(raw, line) {
1443
+ let cursor = line.start;
1444
+ if (raw.startsWith("async", cursor) && isPythonBlank(raw[cursor + 5])) {
1445
+ cursor = skipPythonSpaces(raw, cursor + 5, line.end);
1446
+ }
1447
+ if (!raw.startsWith("def", cursor) || !isPythonBlank(raw[cursor + 3])) {
1448
+ return undefined;
1449
+ }
1450
+ cursor = skipPythonSpaces(raw, cursor + 3, line.end);
1451
+ const nameStart = cursor;
1452
+ while (cursor < line.end && isPythonNameCharacter(raw[cursor])) {
1453
+ cursor += 1;
1454
+ }
1455
+ if (cursor === nameStart) {
1456
+ return undefined;
1457
+ }
1458
+ const name = raw.slice(nameStart, cursor);
1459
+ cursor = skipPythonSpaces(raw, cursor, line.end);
1460
+ return raw[cursor] === "(" ? name : undefined;
1461
+ }
1462
+
1463
+ /**
1464
+ * Find the opening parenthesis of `return <constructor>(` at the start of a
1465
+ * logical line.
1466
+ *
1467
+ * @param {string} raw Python source
1468
+ * @param {{start: number, end: number}} line logical line
1469
+ * @param {string} constructorName Agent or Task
1470
+ * @returns {number} offset of the parenthesis, or -1
1471
+ */
1472
+ function returnedCallStart(raw, line, constructorName) {
1473
+ if (!raw.startsWith("return", line.start)) {
1474
+ return -1;
1475
+ }
1476
+ let cursor = line.start + 6;
1477
+ if (!isPythonBlank(raw[cursor])) {
1478
+ return -1;
1479
+ }
1480
+ cursor = skipPythonSpaces(raw, cursor, line.end);
1481
+ if (!raw.startsWith(constructorName, cursor)) {
1482
+ return -1;
1483
+ }
1484
+ cursor = skipPythonSpaces(raw, cursor + constructorName.length, line.end);
1485
+ return raw[cursor] === "(" ? cursor : -1;
1486
+ }
1487
+
1488
+ /**
1489
+ * Read the arguments of a call, from just after its opening parenthesis to
1490
+ * the matching closing one, or to `limit` when it never closes.
1491
+ *
1492
+ * @param {string} raw Python source
1493
+ * @param {number} openParen offset of the opening parenthesis
1494
+ * @param {number} limit end of the logical line holding the call
1495
+ * @returns {string} argument text
1496
+ */
1497
+ function pythonCallArguments(raw, openParen, limit) {
1498
+ let depth = 1;
1499
+ let cursor = openParen + 1;
1500
+ while (cursor < limit) {
1501
+ const character = raw[cursor];
1502
+ if (character === '"' || character === "'") {
1503
+ cursor = pythonStringSpan(raw, cursor, limit).end;
1504
+ continue;
1505
+ }
1506
+ if (character === "(" || character === "[" || character === "{") {
1507
+ depth += 1;
1508
+ } else if (character === ")" || character === "]" || character === "}") {
1509
+ depth -= 1;
1510
+ if (depth === 0) {
1511
+ return raw.slice(openParen + 1, cursor);
1512
+ }
1513
+ }
1514
+ cursor += 1;
1515
+ }
1516
+ return raw.slice(openParen + 1, limit);
1517
+ }
1518
+
1519
+ /**
1520
+ * Split text on the commas that are outside brackets and strings.
1521
+ *
1522
+ * @param {string} text argument or list text
1523
+ * @returns {string[]} trimmed, non-empty parts
1524
+ */
1525
+ function splitPythonTopLevel(text) {
1526
+ const parts = [];
1527
+ let depth = 0;
1528
+ let partStart = 0;
1529
+ let cursor = 0;
1530
+ while (cursor < text.length) {
1531
+ const character = text[cursor];
1532
+ if (character === '"' || character === "'") {
1533
+ cursor = pythonStringSpan(text, cursor, text.length).end;
1534
+ continue;
1535
+ }
1536
+ if (character === "(" || character === "[" || character === "{") {
1537
+ depth += 1;
1538
+ } else if (character === ")" || character === "]" || character === "}") {
1539
+ depth = Math.max(0, depth - 1);
1540
+ } else if (character === "," && depth === 0) {
1541
+ parts.push(text.slice(partStart, cursor));
1542
+ partStart = cursor + 1;
1543
+ }
1544
+ cursor += 1;
1545
+ }
1546
+ parts.push(text.slice(partStart));
1547
+ return parts.map((part) => part.trim()).filter(Boolean);
1548
+ }
1549
+
1550
+ /**
1551
+ * Map each `name=value` keyword argument of a call to its value text.
1552
+ *
1553
+ * @param {string} argumentText text between the call's parentheses
1554
+ * @returns {Map<string, string>} keyword argument values, first one wins
1555
+ */
1556
+ function pythonKeywordArguments(argumentText) {
1557
+ const keywordArguments = new Map();
1558
+ for (const argument of splitPythonTopLevel(argumentText)) {
1559
+ const equals = argument.indexOf("=");
1560
+ if (equals <= 0 || argument[equals + 1] === "=") {
1561
+ continue;
1562
+ }
1563
+ const name = argument.slice(0, equals).trim();
1564
+ if (
1565
+ !name ||
1566
+ ![...name].every(isPythonNameCharacter) ||
1567
+ keywordArguments.has(name)
1568
+ ) {
1569
+ continue;
1570
+ }
1571
+ keywordArguments.set(name, argument.slice(equals + 1).trim());
1572
+ }
1573
+ return keywordArguments;
1574
+ }
1575
+
1576
+ function unwrapPythonCall(value, prefixes) {
1577
+ for (const prefix of prefixes) {
1578
+ if (value.startsWith(prefix) && value.endsWith(")")) {
1579
+ return value.slice(prefix.length, -1).trim();
1580
+ }
1581
+ }
1582
+ return value;
1583
+ }
1584
+
1585
+ /**
1586
+ * Read the text of a keyword argument whose value is a string literal, a run
1587
+ * of adjacent literals, or either of those inside parentheses or
1588
+ * `dedent(...)`. Other values, such as names or calls, give undefined.
1589
+ *
1590
+ * @param {string|undefined} value keyword argument value text
1591
+ * @returns {string|undefined} literal text with whitespace collapsed
1592
+ */
1593
+ function pythonStringValue(value) {
1594
+ if (!value) {
1595
+ return undefined;
1596
+ }
1597
+ let text = unwrapPythonCall(value, ["textwrap.dedent(", "dedent("]);
1598
+ if (text.startsWith("(") && text.endsWith(")")) {
1599
+ text = text.slice(1, -1).trim();
1600
+ }
1601
+ const parts = [];
1602
+ let cursor = 0;
1603
+ while (cursor < text.length) {
1604
+ while (cursor < text.length && isPythonBlank(text[cursor])) {
1605
+ cursor += 1;
1606
+ }
1607
+ let quoteAt = cursor;
1608
+ while (quoteAt - cursor < 2 && "rRbBuUfF".includes(text[quoteAt] || "-")) {
1609
+ quoteAt += 1;
1610
+ }
1611
+ if (text[quoteAt] !== '"' && text[quoteAt] !== "'") {
1612
+ break;
1613
+ }
1614
+ const isRaw = text.slice(cursor, quoteAt).toLowerCase().includes("r");
1615
+ const span = pythonStringSpan(text, quoteAt, text.length);
1616
+ const content = text.slice(span.contentStart, span.contentEnd);
1617
+ parts.push(
1618
+ isRaw
1619
+ ? content
1620
+ : content.replace(/\\(.)/gsu, (_match, escaped) =>
1621
+ escaped === "n" || escaped === "t" || escaped === "r"
1622
+ ? " "
1623
+ : escaped,
1624
+ ),
1625
+ );
1626
+ cursor = span.end;
1627
+ }
1628
+ const joined = parts.join("").replace(/\s+/gu, " ").trim();
1629
+ return joined || undefined;
1630
+ }
1631
+
1632
+ /**
1633
+ * Read a list literal of tools as a comma-separated string.
1634
+ *
1635
+ * @param {string|undefined} value keyword argument value text
1636
+ * @returns {string|undefined} tool expressions without whitespace
1637
+ */
1638
+ function pythonListValue(value) {
1639
+ if (!value?.startsWith("[") || !value.endsWith("]")) {
1640
+ return undefined;
1641
+ }
1642
+ const items = splitPythonTopLevel(value.slice(1, -1)).map((item) =>
1643
+ item.replace(/\s+/gu, ""),
1644
+ );
1645
+ return items.length ? items.join(",") : undefined;
1646
+ }
1647
+
1648
+ /**
1649
+ * Find the functions in a CrewAI Python module that return an Agent or a
1650
+ * Task, with the arguments of that call. A function's body is the run of
1651
+ * logical lines indented deeper than its `def`, and the first matching
1652
+ * return statement in it belongs to the innermost function. The source is
1653
+ * read in one pass and each call's arguments only within their own logical
1654
+ * line, so crafted input cannot make the work grow faster than its size.
1655
+ *
1656
+ * @param {string} raw Python source
1657
+ * @param {string} constructorName Agent or Task
1658
+ * @returns {{name: string, argumentText: string}[]} returned calls in order
1659
+ */
1660
+ function crewAiReturnedCalls(raw, constructorName) {
1661
+ const openFunctions = [];
1662
+ const calls = [];
1663
+ for (const line of pythonLogicalLines(raw)) {
1664
+ while (openFunctions.length && openFunctions.at(-1).indent >= line.indent) {
1665
+ openFunctions.pop();
784
1666
  }
1667
+ const functionName = pythonFunctionName(raw, line);
1668
+ if (functionName) {
1669
+ openFunctions.push({
1670
+ indent: line.indent,
1671
+ name: functionName,
1672
+ returned: false,
1673
+ });
1674
+ continue;
1675
+ }
1676
+ const innermost = openFunctions.at(-1);
1677
+ if (!innermost || innermost.returned) {
1678
+ continue;
1679
+ }
1680
+ const openParen = returnedCallStart(raw, line, constructorName);
1681
+ if (openParen === -1) {
1682
+ continue;
1683
+ }
1684
+ innermost.returned = true;
1685
+ calls.push({
1686
+ argumentText: pythonCallArguments(raw, openParen, line.end),
1687
+ name: innermost.name,
1688
+ });
785
1689
  }
786
- return undefined;
1690
+ return calls;
787
1691
  }
788
1692
 
789
1693
  function parseCrewAiPython(filePath, raw) {
@@ -792,61 +1696,25 @@ function parseCrewAiPython(filePath, raw) {
792
1696
  }
793
1697
  const isTaskFile = /\bfrom\s+crewai\s+import\s+Task\b/u.test(raw);
794
1698
  const constructorName = isTaskFile ? "Task" : "Agent";
795
- const matches = [
796
- ...raw.matchAll(
797
- new RegExp(
798
- `def\\s+([A-Za-z0-9_]+)\\s*\\([^)]*\\):[\\s\\S]*?return\\s+${constructorName}\\(([\\s\\S]*?)\\n\\s*\\)`,
799
- "gu",
800
- ),
801
- ),
802
- ];
1699
+ const kind = isTaskFile ? "crew-task" : "crew-agent";
803
1700
  const components = [];
804
- for (const match of matches) {
805
- const component = createComponent(
806
- filePath,
807
- "crewai",
808
- isTaskFile ? "crew-task" : "crew-agent",
809
- match[1],
810
- );
811
- addUniqueProperty(
812
- component.properties,
813
- "cdx:agent:role",
814
- isTaskFile ? "crew-task" : "crew-agent",
815
- );
816
- addUniqueProperty(
817
- component.properties,
818
- "cdx:crewai:role",
819
- captureQuotedValue(match[2], "role"),
820
- );
821
- addUniqueProperty(
822
- component.properties,
823
- "cdx:crewai:goal",
824
- captureQuotedValue(match[2], "goal"),
825
- );
826
- addUniqueProperty(
827
- component.properties,
828
- "cdx:crewai:backstory",
829
- captureQuotedValue(match[2], "backstory"),
830
- );
831
- addUniqueProperty(
832
- component.properties,
833
- "cdx:crewai:description",
834
- captureQuotedValue(match[2], "description"),
835
- );
836
- addUniqueProperty(
837
- component.properties,
838
- "cdx:crewai:expectedOutput",
839
- captureQuotedValue(match[2], "expected_output"),
840
- );
841
- const toolsMatch = match[2].match(/tools\s*=\s*\[([^\]]+)\]/u);
842
- if (toolsMatch?.[1]) {
1701
+ for (const call of crewAiReturnedCalls(raw, constructorName)) {
1702
+ const component = createComponent(filePath, "crewai", kind, call.name);
1703
+ addUniqueProperty(component.properties, "cdx:agent:role", kind);
1704
+ const keywordArguments = pythonKeywordArguments(call.argumentText);
1705
+ for (const [argumentName, propertyName] of CREWAI_TEXT_ARGUMENTS) {
843
1706
  addUniqueProperty(
844
1707
  component.properties,
845
- "cdx:crewai:tools",
846
- toolsMatch[1].replace(/\s+/gu, ""),
1708
+ propertyName,
1709
+ pythonStringValue(keywordArguments.get(argumentName)),
847
1710
  );
848
1711
  }
849
- maybeAddFileSignals(component.properties, filePath, match[2]);
1712
+ addUniqueProperty(
1713
+ component.properties,
1714
+ "cdx:crewai:tools",
1715
+ pythonListValue(keywordArguments.get("tools")),
1716
+ );
1717
+ maybeAddFileSignals(component.properties, filePath, call.argumentText);
850
1718
  components.push(component);
851
1719
  }
852
1720
  return components;
@@ -870,6 +1738,12 @@ export const communityAiConfigParser = {
870
1738
  parse(files, _options = {}) {
871
1739
  const components = [];
872
1740
  const services = [];
1741
+ const inputKeys = new Set(
1742
+ (files || []).map((filePath) =>
1743
+ normalizeFilePath(filePath).toLowerCase(),
1744
+ ),
1745
+ );
1746
+ const expandedKiroRoots = new Set();
873
1747
  for (const filePath of [...new Set(files || [])]) {
874
1748
  let raw;
875
1749
  try {
@@ -878,6 +1752,30 @@ export const communityAiConfigParser = {
878
1752
  continue;
879
1753
  }
880
1754
  const lowerPath = normalizeFilePath(filePath).toLowerCase();
1755
+ // Kiro files under .kiro are all handled by the Kiro parsers, so the
1756
+ // generic community branches below never see them.
1757
+ if (lowerPath.includes("/.kiro/")) {
1758
+ components.push(...parseKiroLayoutFile(filePath, raw));
1759
+ continue;
1760
+ }
1761
+ // A settings file outside .kiro is a Kiro configuration root candidate.
1762
+ // The root is validated against the Kiro content shapes, and only a
1763
+ // validated root contributes its steering, settings, and hook files.
1764
+ if (
1765
+ lowerPath.endsWith("/settings/permissions.yaml") ||
1766
+ lowerPath.endsWith("/settings/cli.json")
1767
+ ) {
1768
+ const rootDir = dirname(dirname(filePath));
1769
+ const rootKey = normalizeFilePath(rootDir).toLowerCase();
1770
+ if (isKiroConfigurationRoot(rootDir)) {
1771
+ components.push(...parseKiroLayoutFile(filePath, raw));
1772
+ if (!expandedKiroRoots.has(rootKey)) {
1773
+ expandedKiroRoots.add(rootKey);
1774
+ expandKiroConfigurationRoot(rootDir, inputKeys, components);
1775
+ }
1776
+ }
1777
+ continue;
1778
+ }
881
1779
  if (
882
1780
  lowerPath.endsWith("opencode.json") ||
883
1781
  lowerPath.endsWith("opencode.jsonc")