@complior/engine 0.9.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 (594) hide show
  1. package/.well-known/ai-compliance.json +16 -0
  2. package/COMPLIANCE.md +64 -0
  3. package/data/data-integrity.test.ts +75 -0
  4. package/data/eval/eval-mappings.json +33 -0
  5. package/data/llm/model-pricing.json +15 -0
  6. package/data/llm/model-routing.json +36 -0
  7. package/data/onboarding/risk-profile.json +17 -0
  8. package/data/regulations/eu-ai-act/README.md +245 -0
  9. package/data/regulations/eu-ai-act/applicability-tree.json +160 -0
  10. package/data/regulations/eu-ai-act/cross-mapping.json +175 -0
  11. package/data/regulations/eu-ai-act/localization.json +186 -0
  12. package/data/regulations/eu-ai-act/obligations.json +3981 -0
  13. package/data/regulations/eu-ai-act/regulation-meta.json +482 -0
  14. package/data/regulations/eu-ai-act/scoring.json +342 -0
  15. package/data/regulations/eu-ai-act/technical-requirements.json +2590 -0
  16. package/data/regulations/eu-ai-act/timeline.json +160 -0
  17. package/data/regulations/jurisdictions/at.json +15 -0
  18. package/data/regulations/jurisdictions/be.json +15 -0
  19. package/data/regulations/jurisdictions/bg.json +15 -0
  20. package/data/regulations/jurisdictions/cy.json +15 -0
  21. package/data/regulations/jurisdictions/cz.json +15 -0
  22. package/data/regulations/jurisdictions/de.json +15 -0
  23. package/data/regulations/jurisdictions/dk.json +15 -0
  24. package/data/regulations/jurisdictions/ee.json +15 -0
  25. package/data/regulations/jurisdictions/es.json +15 -0
  26. package/data/regulations/jurisdictions/fi.json +15 -0
  27. package/data/regulations/jurisdictions/fr.json +15 -0
  28. package/data/regulations/jurisdictions/gr.json +15 -0
  29. package/data/regulations/jurisdictions/hr.json +15 -0
  30. package/data/regulations/jurisdictions/hu.json +15 -0
  31. package/data/regulations/jurisdictions/ie.json +15 -0
  32. package/data/regulations/jurisdictions/is.json +15 -0
  33. package/data/regulations/jurisdictions/it.json +15 -0
  34. package/data/regulations/jurisdictions/li.json +15 -0
  35. package/data/regulations/jurisdictions/lt.json +15 -0
  36. package/data/regulations/jurisdictions/lu.json +15 -0
  37. package/data/regulations/jurisdictions/lv.json +15 -0
  38. package/data/regulations/jurisdictions/mt.json +15 -0
  39. package/data/regulations/jurisdictions/nl.json +15 -0
  40. package/data/regulations/jurisdictions/no.json +15 -0
  41. package/data/regulations/jurisdictions/pl.json +15 -0
  42. package/data/regulations/jurisdictions/pt.json +15 -0
  43. package/data/regulations/jurisdictions/ro.json +15 -0
  44. package/data/regulations/jurisdictions/se.json +15 -0
  45. package/data/regulations/jurisdictions/si.json +15 -0
  46. package/data/regulations/jurisdictions/sk.json +15 -0
  47. package/data/scanner/check-id-categories.json +81 -0
  48. package/data/scanner/confidence-params.json +16 -0
  49. package/data/scanner/limits.json +4 -0
  50. package/data/schemas/http-contract-sample.json +79 -0
  51. package/data/schemas/http-contract.json +144 -0
  52. package/data/semgrep-rules/bare-call.yaml +37 -0
  53. package/data/semgrep-rules/injection.yaml +73 -0
  54. package/data/semgrep-rules/missing-error-handling.yaml +58 -0
  55. package/data/semgrep-rules/unsafe-deser.yaml +65 -0
  56. package/data/templates/eu-ai-act/ai-literacy.md +184 -0
  57. package/data/templates/eu-ai-act/art5-screening.md +131 -0
  58. package/data/templates/eu-ai-act/data-governance.md +145 -0
  59. package/data/templates/eu-ai-act/declaration-of-conformity.md +161 -0
  60. package/data/templates/eu-ai-act/fria.md +127 -0
  61. package/data/templates/eu-ai-act/gpai-systemic-risk.md +150 -0
  62. package/data/templates/eu-ai-act/gpai-transparency.md +166 -0
  63. package/data/templates/eu-ai-act/incident-report.md +188 -0
  64. package/data/templates/eu-ai-act/instructions-for-use.md +202 -0
  65. package/data/templates/eu-ai-act/monitoring-policy.md +110 -0
  66. package/data/templates/eu-ai-act/qms.md +180 -0
  67. package/data/templates/eu-ai-act/risk-management-system.md +123 -0
  68. package/data/templates/eu-ai-act/technical-documentation.md +287 -0
  69. package/data/templates/eu-ai-act/worker-notification.md +143 -0
  70. package/data/templates/policies/biometrics-ai-policy.md +214 -0
  71. package/data/templates/policies/critical-infra-ai-policy.md +228 -0
  72. package/data/templates/policies/education-ai-policy.md +184 -0
  73. package/data/templates/policies/finance-ai-policy.md +191 -0
  74. package/data/templates/policies/healthcare-ai-policy.md +197 -0
  75. package/data/templates/policies/hr-ai-policy.md +178 -0
  76. package/data/templates/policies/legal-ai-policy.md +189 -0
  77. package/data/templates/policies/migration-ai-policy.md +239 -0
  78. package/engine.log +7 -0
  79. package/package.json +74 -0
  80. package/src/composition-root.ts +791 -0
  81. package/src/data/eval/conformity-tests.test.ts +122 -0
  82. package/src/data/eval/ct-1-transparency.ts +106 -0
  83. package/src/data/eval/ct-10-gpai.ts +25 -0
  84. package/src/data/eval/ct-11-industry.ts +42 -0
  85. package/src/data/eval/ct-2-oversight.ts +41 -0
  86. package/src/data/eval/ct-3-explanation.ts +14 -0
  87. package/src/data/eval/ct-4-bias.ts +83 -0
  88. package/src/data/eval/ct-5-accuracy.ts +41 -0
  89. package/src/data/eval/ct-6-robustness.ts +81 -0
  90. package/src/data/eval/ct-7-prohibited.ts +52 -0
  91. package/src/data/eval/ct-8-logging.ts +68 -0
  92. package/src/data/eval/ct-9-risk-awareness.ts +33 -0
  93. package/src/data/eval/deterministic-evaluator.ts +120 -0
  94. package/src/data/eval/index.ts +55 -0
  95. package/src/data/eval/judge-prompts.ts +146 -0
  96. package/src/data/eval/llm-judged-tests.ts +279 -0
  97. package/src/data/eval/llm-tests.test.ts +83 -0
  98. package/src/data/eval/remediation/ct-1-transparency.ts +91 -0
  99. package/src/data/eval/remediation/ct-10-gpai.ts +94 -0
  100. package/src/data/eval/remediation/ct-11-industry.ts +94 -0
  101. package/src/data/eval/remediation/ct-2-oversight.ts +71 -0
  102. package/src/data/eval/remediation/ct-3-explanation.ts +70 -0
  103. package/src/data/eval/remediation/ct-4-bias.ts +70 -0
  104. package/src/data/eval/remediation/ct-5-accuracy.ts +70 -0
  105. package/src/data/eval/remediation/ct-6-robustness.ts +70 -0
  106. package/src/data/eval/remediation/ct-7-prohibited.ts +94 -0
  107. package/src/data/eval/remediation/ct-8-logging.ts +94 -0
  108. package/src/data/eval/remediation/ct-9-risk-awareness.ts +94 -0
  109. package/src/data/eval/remediation/index.ts +89 -0
  110. package/src/data/eval/remediation/owasp-art5.ts +15 -0
  111. package/src/data/eval/remediation/owasp-llm01.ts +72 -0
  112. package/src/data/eval/remediation/owasp-llm02.ts +72 -0
  113. package/src/data/eval/remediation/owasp-llm03.ts +15 -0
  114. package/src/data/eval/remediation/owasp-llm04.ts +15 -0
  115. package/src/data/eval/remediation/owasp-llm05.ts +15 -0
  116. package/src/data/eval/remediation/owasp-llm06.ts +15 -0
  117. package/src/data/eval/remediation/owasp-llm07.ts +15 -0
  118. package/src/data/eval/remediation/owasp-llm08.ts +15 -0
  119. package/src/data/eval/remediation/owasp-llm09.ts +15 -0
  120. package/src/data/eval/remediation/owasp-llm10.ts +15 -0
  121. package/src/data/eval/remediation/remediation.test.ts +229 -0
  122. package/src/data/eval/remediation/test-mapping.ts +290 -0
  123. package/src/data/eval/security-rubrics.ts +381 -0
  124. package/src/data/finding-explanations.json +453 -0
  125. package/src/data/industry-patterns.ts +161 -0
  126. package/src/data/registry-cards.ts +368 -0
  127. package/src/data/regulation/index.ts +5 -0
  128. package/src/data/regulation/jurisdiction-data.test.ts +73 -0
  129. package/src/data/regulation/jurisdiction-data.ts +65 -0
  130. package/src/data/regulation/regulation-data.ts +19 -0
  131. package/src/data/regulation/regulation-loader.test.ts +107 -0
  132. package/src/data/regulation/regulation-loader.ts +56 -0
  133. package/src/data/scanner-constants.ts +46 -0
  134. package/src/data/schemas/schemas-core.ts +140 -0
  135. package/src/data/schemas/schemas-supplementary.ts +211 -0
  136. package/src/data/schemas/schemas.ts +28 -0
  137. package/src/data/security/attack-probes.test.ts +62 -0
  138. package/src/data/security/attack-probes.ts +496 -0
  139. package/src/data/security/eu-ai-act-security.ts +40 -0
  140. package/src/data/security/index.ts +19 -0
  141. package/src/data/security/mitre-atlas.test.ts +43 -0
  142. package/src/data/security/mitre-atlas.ts +93 -0
  143. package/src/data/security/nist-ai-rmf.ts +43 -0
  144. package/src/data/security/owasp-llm-top10.test.ts +60 -0
  145. package/src/data/security/owasp-llm-top10.ts +138 -0
  146. package/src/data/template-registry.ts +53 -0
  147. package/src/data/tool-versions.json +22 -0
  148. package/src/domain/audit/audit-package.test.ts +152 -0
  149. package/src/domain/audit/audit-package.ts +166 -0
  150. package/src/domain/audit/audit-trail.test.ts +121 -0
  151. package/src/domain/audit/audit-trail.ts +174 -0
  152. package/src/domain/audit/index.ts +8 -0
  153. package/src/domain/audit/permissions-matrix.test.ts +136 -0
  154. package/src/domain/audit/permissions-matrix.ts +121 -0
  155. package/src/domain/certification/adversarial/bias-tests.ts +95 -0
  156. package/src/domain/certification/adversarial/evaluators.ts +304 -0
  157. package/src/domain/certification/adversarial/index.ts +11 -0
  158. package/src/domain/certification/adversarial/prompt-injection.ts +103 -0
  159. package/src/domain/certification/adversarial/safety-boundary.ts +132 -0
  160. package/src/domain/certification/aiuc1-readiness.test.ts +236 -0
  161. package/src/domain/certification/aiuc1-readiness.ts +298 -0
  162. package/src/domain/certification/aiuc1-requirements.ts +235 -0
  163. package/src/domain/certification/index.ts +10 -0
  164. package/src/domain/certification/redteam-runner.test.ts +97 -0
  165. package/src/domain/certification/redteam-runner.ts +205 -0
  166. package/src/domain/certification/test-runner.test.ts +232 -0
  167. package/src/domain/certification/test-runner.ts +289 -0
  168. package/src/domain/cost/cost-estimator.test.ts +187 -0
  169. package/src/domain/cost/cost-estimator.ts +133 -0
  170. package/src/domain/disclaimer.test.ts +52 -0
  171. package/src/domain/disclaimer.ts +39 -0
  172. package/src/domain/documents/ai-enricher.test.ts +120 -0
  173. package/src/domain/documents/ai-enricher.ts +159 -0
  174. package/src/domain/documents/document-generator.test.ts +318 -0
  175. package/src/domain/documents/document-generator.ts +239 -0
  176. package/src/domain/documents/index.ts +9 -0
  177. package/src/domain/documents/passport-helpers.ts +25 -0
  178. package/src/domain/documents/policy-generator.test.ts +252 -0
  179. package/src/domain/documents/policy-generator.ts +94 -0
  180. package/src/domain/documents/worker-notification-generator.test.ts +162 -0
  181. package/src/domain/documents/worker-notification-generator.ts +141 -0
  182. package/src/domain/eval/adapters/adapter-port.ts +94 -0
  183. package/src/domain/eval/adapters/adapters.test.ts +303 -0
  184. package/src/domain/eval/adapters/anthropic-adapter.ts +57 -0
  185. package/src/domain/eval/adapters/auto-detect.ts +104 -0
  186. package/src/domain/eval/adapters/create-chat-adapter.ts +106 -0
  187. package/src/domain/eval/adapters/custom-adapter.ts +74 -0
  188. package/src/domain/eval/adapters/http-adapter.ts +66 -0
  189. package/src/domain/eval/adapters/index.ts +7 -0
  190. package/src/domain/eval/adapters/ollama-adapter.ts +48 -0
  191. package/src/domain/eval/adapters/openai-adapter.ts +58 -0
  192. package/src/domain/eval/adapters/with-timeout.ts +25 -0
  193. package/src/domain/eval/conformity-score.test.ts +161 -0
  194. package/src/domain/eval/conformity-score.ts +135 -0
  195. package/src/domain/eval/eval-constants.ts +55 -0
  196. package/src/domain/eval/eval-evidence.test.ts +85 -0
  197. package/src/domain/eval/eval-evidence.ts +103 -0
  198. package/src/domain/eval/eval-fix-generator.test.ts +421 -0
  199. package/src/domain/eval/eval-fix-generator.ts +205 -0
  200. package/src/domain/eval/eval-passport.test.ts +82 -0
  201. package/src/domain/eval/eval-passport.ts +89 -0
  202. package/src/domain/eval/eval-remediation-report.test.ts +682 -0
  203. package/src/domain/eval/eval-remediation-report.ts +170 -0
  204. package/src/domain/eval/eval-report.ts +108 -0
  205. package/src/domain/eval/eval-runner.test.ts +609 -0
  206. package/src/domain/eval/eval-runner.ts +593 -0
  207. package/src/domain/eval/eval-to-findings.test.ts +293 -0
  208. package/src/domain/eval/eval-to-findings.ts +83 -0
  209. package/src/domain/eval/index.ts +31 -0
  210. package/src/domain/eval/llm-judge.test.ts +139 -0
  211. package/src/domain/eval/llm-judge.ts +168 -0
  212. package/src/domain/eval/remediation-types.ts +90 -0
  213. package/src/domain/eval/security-integration.test.ts +196 -0
  214. package/src/domain/eval/security-integration.ts +136 -0
  215. package/src/domain/eval/types.test.ts +173 -0
  216. package/src/domain/eval/types.ts +244 -0
  217. package/src/domain/eval/verdict-utils.ts +45 -0
  218. package/src/domain/fixer/create-fixer.ts +101 -0
  219. package/src/domain/fixer/diff.ts +70 -0
  220. package/src/domain/fixer/fix-history.ts +23 -0
  221. package/src/domain/fixer/fixer.test.ts +306 -0
  222. package/src/domain/fixer/index.ts +9 -0
  223. package/src/domain/fixer/strategies/bandit-fix.ts +61 -0
  224. package/src/domain/fixer/strategies/bias-testing.ts +49 -0
  225. package/src/domain/fixer/strategies/ci-compliance.ts +57 -0
  226. package/src/domain/fixer/strategies/content-marking.ts +45 -0
  227. package/src/domain/fixer/strategies/cve-upgrade.ts +66 -0
  228. package/src/domain/fixer/strategies/data-governance.ts +65 -0
  229. package/src/domain/fixer/strategies/disclosure.ts +69 -0
  230. package/src/domain/fixer/strategies/doc-code-sync.ts +53 -0
  231. package/src/domain/fixer/strategies/documentation.ts +59 -0
  232. package/src/domain/fixer/strategies/error-handler.ts +63 -0
  233. package/src/domain/fixer/strategies/hitl-gate.ts +67 -0
  234. package/src/domain/fixer/strategies/index.ts +61 -0
  235. package/src/domain/fixer/strategies/kill-switch-test.ts +85 -0
  236. package/src/domain/fixer/strategies/kill-switch.ts +53 -0
  237. package/src/domain/fixer/strategies/license-fix.ts +57 -0
  238. package/src/domain/fixer/strategies/log-retention.ts +40 -0
  239. package/src/domain/fixer/strategies/logging.ts +59 -0
  240. package/src/domain/fixer/strategies/metadata.ts +45 -0
  241. package/src/domain/fixer/strategies/permission-guard.ts +84 -0
  242. package/src/domain/fixer/strategies/record-keeping.ts +69 -0
  243. package/src/domain/fixer/strategies/secret-rotation.ts +52 -0
  244. package/src/domain/fixer/strategies.test.ts +341 -0
  245. package/src/domain/fixer/template-engine.test.ts +64 -0
  246. package/src/domain/fixer/template-engine.ts +38 -0
  247. package/src/domain/fixer/types.ts +88 -0
  248. package/src/domain/frameworks/aiuc1-framework.test.ts +159 -0
  249. package/src/domain/frameworks/aiuc1-framework.ts +126 -0
  250. package/src/domain/frameworks/collect-foundation-metrics.test.ts +96 -0
  251. package/src/domain/frameworks/collect-foundation-metrics.ts +34 -0
  252. package/src/domain/frameworks/eu-ai-act-framework.test.ts +117 -0
  253. package/src/domain/frameworks/eu-ai-act-framework.ts +100 -0
  254. package/src/domain/frameworks/framework-registry.test.ts +91 -0
  255. package/src/domain/frameworks/framework-registry.ts +38 -0
  256. package/src/domain/frameworks/index.ts +8 -0
  257. package/src/domain/frameworks/mitre-atlas-framework.test.ts +53 -0
  258. package/src/domain/frameworks/mitre-atlas-framework.ts +53 -0
  259. package/src/domain/frameworks/owasp-llm-framework.test.ts +77 -0
  260. package/src/domain/frameworks/owasp-llm-framework.ts +54 -0
  261. package/src/domain/frameworks/score-plugin-framework.ts +117 -0
  262. package/src/domain/fria/fria-generator.test.ts +273 -0
  263. package/src/domain/fria/fria-generator.ts +366 -0
  264. package/src/domain/import/promptfoo-importer.test.ts +103 -0
  265. package/src/domain/import/promptfoo-importer.ts +151 -0
  266. package/src/domain/onboarding/guided-onboarding.test.ts +144 -0
  267. package/src/domain/onboarding/guided-onboarding.ts +135 -0
  268. package/src/domain/passport/builder/domain-mapper.ts +9 -0
  269. package/src/domain/passport/builder/manifest-builder.test.ts +546 -0
  270. package/src/domain/passport/builder/manifest-builder.ts +535 -0
  271. package/src/domain/passport/builder/manifest-diff.test.ts +105 -0
  272. package/src/domain/passport/builder/manifest-diff.ts +89 -0
  273. package/src/domain/passport/builder/manifest-files.ts +17 -0
  274. package/src/domain/passport/crypto-signer.test.ts +93 -0
  275. package/src/domain/passport/crypto-signer.ts +157 -0
  276. package/src/domain/passport/discovery/agent-discovery.test.ts +296 -0
  277. package/src/domain/passport/discovery/agent-discovery.ts +325 -0
  278. package/src/domain/passport/discovery/autonomy-analyzer.test.ts +141 -0
  279. package/src/domain/passport/discovery/autonomy-analyzer.ts +113 -0
  280. package/src/domain/passport/discovery/permission-scanner.test.ts +191 -0
  281. package/src/domain/passport/discovery/permission-scanner.ts +414 -0
  282. package/src/domain/passport/export/a2a-mapper.ts +75 -0
  283. package/src/domain/passport/export/aiuc1-mapper.ts +126 -0
  284. package/src/domain/passport/export/export.test.ts +207 -0
  285. package/src/domain/passport/export/index.ts +41 -0
  286. package/src/domain/passport/export/nist-mapper.ts +227 -0
  287. package/src/domain/passport/import/a2a-importer.test.ts +133 -0
  288. package/src/domain/passport/import/a2a-importer.ts +156 -0
  289. package/src/domain/passport/import/index.ts +2 -0
  290. package/src/domain/passport/index.ts +32 -0
  291. package/src/domain/passport/obligation-field-map.test.ts +113 -0
  292. package/src/domain/passport/obligation-field-map.ts +117 -0
  293. package/src/domain/passport/passport-validator.test.ts +156 -0
  294. package/src/domain/passport/passport-validator.ts +126 -0
  295. package/src/domain/passport/scan-to-compliance.test.ts +336 -0
  296. package/src/domain/passport/scan-to-compliance.ts +166 -0
  297. package/src/domain/passport/test-generator.test.ts +93 -0
  298. package/src/domain/passport/test-generator.ts +136 -0
  299. package/src/domain/proxy/index.ts +11 -0
  300. package/src/domain/proxy/json-rpc.test.ts +72 -0
  301. package/src/domain/proxy/json-rpc.ts +53 -0
  302. package/src/domain/proxy/policy-engine.test.ts +259 -0
  303. package/src/domain/proxy/policy-engine.ts +137 -0
  304. package/src/domain/proxy/proxy-bridge.ts +125 -0
  305. package/src/domain/proxy/proxy-interceptor.test.ts +184 -0
  306. package/src/domain/proxy/proxy-interceptor.ts +120 -0
  307. package/src/domain/proxy/proxy-types.ts +35 -0
  308. package/src/domain/registry/compute-agent-score.test.ts +279 -0
  309. package/src/domain/registry/compute-agent-score.ts +162 -0
  310. package/src/domain/reporter/audit-report.test.ts +87 -0
  311. package/src/domain/reporter/audit-report.ts +116 -0
  312. package/src/domain/reporter/badge-generator.test.ts +54 -0
  313. package/src/domain/reporter/badge-generator.ts +40 -0
  314. package/src/domain/reporter/compliance-md.ts +45 -0
  315. package/src/domain/reporter/index.ts +7 -0
  316. package/src/domain/reporter/pdf-renderer.ts +282 -0
  317. package/src/domain/reporter/share.test.ts +92 -0
  318. package/src/domain/reporter/share.ts +80 -0
  319. package/src/domain/scanner/ast/swc-analyzer.test.ts +49 -0
  320. package/src/domain/scanner/ast/swc-analyzer.ts +124 -0
  321. package/src/domain/scanner/attestations.ts +97 -0
  322. package/src/domain/scanner/checks/ai-disclosure.test.ts +90 -0
  323. package/src/domain/scanner/checks/ai-disclosure.ts +54 -0
  324. package/src/domain/scanner/checks/ai-literacy.ts +163 -0
  325. package/src/domain/scanner/checks/behavioral-constraints.test.ts +167 -0
  326. package/src/domain/scanner/checks/behavioral-constraints.ts +86 -0
  327. package/src/domain/scanner/checks/compliance-metadata.ts +63 -0
  328. package/src/domain/scanner/checks/content-marking.ts +74 -0
  329. package/src/domain/scanner/checks/dep-deep-scan.test.ts +318 -0
  330. package/src/domain/scanner/checks/dep-deep-scan.ts +137 -0
  331. package/src/domain/scanner/checks/documentation.test.ts +88 -0
  332. package/src/domain/scanner/checks/documentation.ts +79 -0
  333. package/src/domain/scanner/checks/git-history.test.ts +120 -0
  334. package/src/domain/scanner/checks/git-history.ts +163 -0
  335. package/src/domain/scanner/checks/gpai-systemic-risk.test.ts +84 -0
  336. package/src/domain/scanner/checks/gpai-systemic-risk.ts +98 -0
  337. package/src/domain/scanner/checks/gpai-transparency.ts +94 -0
  338. package/src/domain/scanner/checks/index.ts +28 -0
  339. package/src/domain/scanner/checks/industry/index.ts +40 -0
  340. package/src/domain/scanner/checks/industry/industry.test.ts +287 -0
  341. package/src/domain/scanner/checks/interaction-logging.test.ts +113 -0
  342. package/src/domain/scanner/checks/interaction-logging.ts +142 -0
  343. package/src/domain/scanner/checks/nhi-scanner.test.ts +158 -0
  344. package/src/domain/scanner/checks/nhi-scanner.ts +78 -0
  345. package/src/domain/scanner/checks/passport-completeness.test.ts +127 -0
  346. package/src/domain/scanner/checks/passport-completeness.ts +82 -0
  347. package/src/domain/scanner/checks/passport-presence.test.ts +56 -0
  348. package/src/domain/scanner/checks/passport-presence.ts +78 -0
  349. package/src/domain/scanner/checks/pattern-check-factory.ts +70 -0
  350. package/src/domain/scanner/checks/permission-scanner.test.ts +279 -0
  351. package/src/domain/scanner/checks/permission-scanner.ts +90 -0
  352. package/src/domain/scanner/checks/presence-check-factory.test.ts +124 -0
  353. package/src/domain/scanner/checks/presence-check-factory.ts +275 -0
  354. package/src/domain/scanner/compliance-diff.test.ts +165 -0
  355. package/src/domain/scanner/compliance-diff.ts +138 -0
  356. package/src/domain/scanner/confidence.test.ts +235 -0
  357. package/src/domain/scanner/confidence.ts +156 -0
  358. package/src/domain/scanner/constants.ts +13 -0
  359. package/src/domain/scanner/create-scanner.ts +573 -0
  360. package/src/domain/scanner/cross-layer.test.ts +372 -0
  361. package/src/domain/scanner/cross-layer.ts +232 -0
  362. package/src/domain/scanner/data/ai-packages.ts +82 -0
  363. package/src/domain/scanner/debt-calculator.test.ts +89 -0
  364. package/src/domain/scanner/debt-calculator.ts +111 -0
  365. package/src/domain/scanner/drift.test.ts +191 -0
  366. package/src/domain/scanner/drift.ts +73 -0
  367. package/src/domain/scanner/evidence-store.test.ts +207 -0
  368. package/src/domain/scanner/evidence-store.ts +195 -0
  369. package/src/domain/scanner/evidence.test.ts +104 -0
  370. package/src/domain/scanner/evidence.ts +71 -0
  371. package/src/domain/scanner/external/bandit-runner.test.ts +45 -0
  372. package/src/domain/scanner/external/bandit-runner.ts +90 -0
  373. package/src/domain/scanner/external/checks.ts +321 -0
  374. package/src/domain/scanner/external/dedup.test.ts +79 -0
  375. package/src/domain/scanner/external/dedup.ts +94 -0
  376. package/src/domain/scanner/external/detect-secrets-runner.test.ts +58 -0
  377. package/src/domain/scanner/external/detect-secrets-runner.ts +81 -0
  378. package/src/domain/scanner/external/external-scanner.test.ts +221 -0
  379. package/src/domain/scanner/external/external-scanner.ts +36 -0
  380. package/src/domain/scanner/external/finding-mapper.test.ts +95 -0
  381. package/src/domain/scanner/external/finding-mapper.ts +138 -0
  382. package/src/domain/scanner/external/index.ts +15 -0
  383. package/src/domain/scanner/external/mappings.ts +93 -0
  384. package/src/domain/scanner/external/modelscan-runner.test.ts +35 -0
  385. package/src/domain/scanner/external/modelscan-runner.ts +101 -0
  386. package/src/domain/scanner/external/path-utils.ts +8 -0
  387. package/src/domain/scanner/external/runner-port.ts +45 -0
  388. package/src/domain/scanner/external/semgrep-runner.test.ts +52 -0
  389. package/src/domain/scanner/external/semgrep-runner.ts +94 -0
  390. package/src/domain/scanner/external/types.ts +32 -0
  391. package/src/domain/scanner/finding-attribution.test.ts +444 -0
  392. package/src/domain/scanner/finding-attribution.ts +195 -0
  393. package/src/domain/scanner/finding-explainer.test.ts +157 -0
  394. package/src/domain/scanner/finding-explainer.ts +73 -0
  395. package/src/domain/scanner/fix-diff-builder.test.ts +272 -0
  396. package/src/domain/scanner/fix-diff-builder.ts +477 -0
  397. package/src/domain/scanner/import-graph.test.ts +162 -0
  398. package/src/domain/scanner/import-graph.ts +198 -0
  399. package/src/domain/scanner/languages/adapter.test.ts +105 -0
  400. package/src/domain/scanner/languages/adapter.ts +239 -0
  401. package/src/domain/scanner/layers/index.ts +24 -0
  402. package/src/domain/scanner/layers/layer1-files.ts +54 -0
  403. package/src/domain/scanner/layers/layer2-docs.test.ts +1207 -0
  404. package/src/domain/scanner/layers/layer2-docs.ts +297 -0
  405. package/src/domain/scanner/layers/layer2-parsing.ts +217 -0
  406. package/src/domain/scanner/layers/layer3-config.test.ts +187 -0
  407. package/src/domain/scanner/layers/layer3-config.ts +279 -0
  408. package/src/domain/scanner/layers/layer3-parsers.ts +73 -0
  409. package/src/domain/scanner/layers/layer4-patterns.test.ts +397 -0
  410. package/src/domain/scanner/layers/layer4-patterns.ts +216 -0
  411. package/src/domain/scanner/layers/layer5-docs.test.ts +99 -0
  412. package/src/domain/scanner/layers/layer5-docs.ts +250 -0
  413. package/src/domain/scanner/layers/layer5-llm.test.ts +146 -0
  414. package/src/domain/scanner/layers/layer5-llm.ts +262 -0
  415. package/src/domain/scanner/layers/layer5-targeted.test.ts +93 -0
  416. package/src/domain/scanner/layers/layer5-targeted.ts +233 -0
  417. package/src/domain/scanner/layers/lockfile-parsers.test.ts +320 -0
  418. package/src/domain/scanner/layers/lockfile-parsers.ts +184 -0
  419. package/src/domain/scanner/regulation-version.test.ts +54 -0
  420. package/src/domain/scanner/regulation-version.ts +23 -0
  421. package/src/domain/scanner/role-filter.test.ts +116 -0
  422. package/src/domain/scanner/role-filter.ts +51 -0
  423. package/src/domain/scanner/rules/banned-packages-data.ts +553 -0
  424. package/src/domain/scanner/rules/banned-packages-sdk.ts +65 -0
  425. package/src/domain/scanner/rules/banned-packages.test.ts +249 -0
  426. package/src/domain/scanner/rules/banned-packages.ts +55 -0
  427. package/src/domain/scanner/rules/comment-filter.test.ts +115 -0
  428. package/src/domain/scanner/rules/comment-filter.ts +297 -0
  429. package/src/domain/scanner/rules/index.ts +9 -0
  430. package/src/domain/scanner/rules/nhi-patterns.test.ts +128 -0
  431. package/src/domain/scanner/rules/nhi-patterns.ts +60 -0
  432. package/src/domain/scanner/rules/pattern-rules.ts +1152 -0
  433. package/src/domain/scanner/sbom.test.ts +136 -0
  434. package/src/domain/scanner/sbom.ts +103 -0
  435. package/src/domain/scanner/scan-cache.test.ts +136 -0
  436. package/src/domain/scanner/scan-cache.ts +115 -0
  437. package/src/domain/scanner/scanner.test.ts +125 -0
  438. package/src/domain/scanner/score-calculator.test.ts +363 -0
  439. package/src/domain/scanner/score-calculator.ts +189 -0
  440. package/src/domain/scanner/security-score.test.ts +107 -0
  441. package/src/domain/scanner/security-score.ts +116 -0
  442. package/src/domain/scanner/source-filter.ts +24 -0
  443. package/src/domain/scanner/validators.ts +223 -0
  444. package/src/domain/shared/compliance-constants.ts +48 -0
  445. package/src/domain/shared/disclosure-patterns.ts +16 -0
  446. package/src/domain/shared/index.ts +6 -0
  447. package/src/domain/shared/parse-dependencies.ts +21 -0
  448. package/src/domain/supply-chain/dependency-analyzer.ts +138 -0
  449. package/src/domain/supply-chain/index.ts +3 -0
  450. package/src/domain/supply-chain/supply-chain.test.ts +211 -0
  451. package/src/domain/supply-chain/types.ts +32 -0
  452. package/src/domain/whatif/config-fixer.ts +187 -0
  453. package/src/domain/whatif/index.ts +6 -0
  454. package/src/domain/whatif/scenario-engine.ts +121 -0
  455. package/src/domain/whatif/simulate-actions.test.ts +161 -0
  456. package/src/domain/whatif/simulate-actions.ts +114 -0
  457. package/src/domain/whatif/whatif.test.ts +135 -0
  458. package/src/e2e/gaps-e2e.test.ts +259 -0
  459. package/src/e2e/smoke.test.ts +101 -0
  460. package/src/hooks/hooks-export.test.ts +81 -0
  461. package/src/hooks/installer.ts +113 -0
  462. package/src/http/cors.test.ts +38 -0
  463. package/src/http/create-router.ts +259 -0
  464. package/src/http/routes/agent.route.ts +380 -0
  465. package/src/http/routes/audit.route.ts +66 -0
  466. package/src/http/routes/badge.route.ts +23 -0
  467. package/src/http/routes/cert.route.ts +66 -0
  468. package/src/http/routes/chat.route.ts +228 -0
  469. package/src/http/routes/cost.route.ts +33 -0
  470. package/src/http/routes/debt.route.ts +29 -0
  471. package/src/http/routes/disclaimer.route.ts +64 -0
  472. package/src/http/routes/eval.route.ts +161 -0
  473. package/src/http/routes/events.route.test.ts +108 -0
  474. package/src/http/routes/events.route.ts +71 -0
  475. package/src/http/routes/external-scan.route.ts +24 -0
  476. package/src/http/routes/file.route.ts +54 -0
  477. package/src/http/routes/fix.route.ts +219 -0
  478. package/src/http/routes/frameworks.route.test.ts +66 -0
  479. package/src/http/routes/frameworks.route.ts +36 -0
  480. package/src/http/routes/git.route.ts +27 -0
  481. package/src/http/routes/guided-onboarding.route.ts +65 -0
  482. package/src/http/routes/import.route.ts +64 -0
  483. package/src/http/routes/jurisdiction.route.ts +22 -0
  484. package/src/http/routes/obligations.route.test.ts +122 -0
  485. package/src/http/routes/obligations.route.ts +110 -0
  486. package/src/http/routes/onboarding.route.ts +53 -0
  487. package/src/http/routes/provider.route.ts +42 -0
  488. package/src/http/routes/proxy.route.ts +40 -0
  489. package/src/http/routes/redteam.route.ts +84 -0
  490. package/src/http/routes/report.route.ts +29 -0
  491. package/src/http/routes/scan.route.ts +104 -0
  492. package/src/http/routes/share.route.ts +44 -0
  493. package/src/http/routes/shell.route.ts +27 -0
  494. package/src/http/routes/status.route.ts +66 -0
  495. package/src/http/routes/supply-chain.route.ts +121 -0
  496. package/src/http/routes/sync.route.ts +328 -0
  497. package/src/http/routes/tools.route.ts +29 -0
  498. package/src/http/routes/whatif.route.ts +96 -0
  499. package/src/http/utils/validation.ts +31 -0
  500. package/src/index.ts +1 -0
  501. package/src/infra/bundle-fetcher.ts +77 -0
  502. package/src/infra/cache-storage.ts +34 -0
  503. package/src/infra/event-bus.ts +31 -0
  504. package/src/infra/file-collector.ts +61 -0
  505. package/src/infra/file-ops-adapter.ts +95 -0
  506. package/src/infra/file-watcher.test.ts +90 -0
  507. package/src/infra/file-watcher.ts +106 -0
  508. package/src/infra/git-adapter.ts +93 -0
  509. package/src/infra/git-history-adapter.ts +41 -0
  510. package/src/infra/headless-browser.ts +178 -0
  511. package/src/infra/llm-adapter.test.ts +83 -0
  512. package/src/infra/llm-adapter.ts +86 -0
  513. package/src/infra/logger.ts +27 -0
  514. package/src/infra/project-config.test.ts +74 -0
  515. package/src/infra/project-config.ts +35 -0
  516. package/src/infra/rate-limiter.test.ts +36 -0
  517. package/src/infra/rate-limiter.ts +34 -0
  518. package/src/infra/retry.ts +46 -0
  519. package/src/infra/saas-client.ts +123 -0
  520. package/src/infra/search-adapter.ts +113 -0
  521. package/src/infra/shell-adapter.ts +68 -0
  522. package/src/infra/tool-manager.test.ts +99 -0
  523. package/src/infra/tool-manager.ts +197 -0
  524. package/src/llm/agents/agent-modes.test.ts +44 -0
  525. package/src/llm/agents/modes.ts +68 -0
  526. package/src/llm/routing/cost-routing.test.ts +37 -0
  527. package/src/llm/routing/cost-tracker.ts +74 -0
  528. package/src/llm/routing/model-routing.test.ts +79 -0
  529. package/src/llm/routing/model-routing.ts +38 -0
  530. package/src/llm/routing/pricing.ts +19 -0
  531. package/src/llm/sse-protocol.ts +77 -0
  532. package/src/llm/tool-definitions.ts +83 -0
  533. package/src/llm/tool-executors.ts +80 -0
  534. package/src/llm/tools/types.ts +13 -0
  535. package/src/mcp/create-mcp-stack.ts +82 -0
  536. package/src/mcp/handlers.ts +245 -0
  537. package/src/mcp/index.ts +28 -0
  538. package/src/mcp/mcp-server.test.ts +80 -0
  539. package/src/mcp/server.ts +79 -0
  540. package/src/mcp/tools.ts +48 -0
  541. package/src/onboarding/auto-detect.ts +164 -0
  542. package/src/onboarding/onboarding.test.ts +89 -0
  543. package/src/onboarding/profile.ts +169 -0
  544. package/src/onboarding/questions.ts +112 -0
  545. package/src/onboarding/wizard.ts +66 -0
  546. package/src/output/github-issue.ts +32 -0
  547. package/src/output/json-output.ts +67 -0
  548. package/src/ports/browser.port.ts +23 -0
  549. package/src/ports/events.port.ts +28 -0
  550. package/src/ports/llm.port.ts +23 -0
  551. package/src/ports/logger.port.ts +6 -0
  552. package/src/ports/process.port.ts +6 -0
  553. package/src/ports/scanner.port.ts +15 -0
  554. package/src/server.ts +134 -0
  555. package/src/services/badge-service.ts +67 -0
  556. package/src/services/chat-service.test.ts +162 -0
  557. package/src/services/chat-service.ts +152 -0
  558. package/src/services/cost-service.ts +52 -0
  559. package/src/services/debt-service.ts +65 -0
  560. package/src/services/eval-integration.test.ts +132 -0
  561. package/src/services/eval-service.test.ts +373 -0
  562. package/src/services/eval-service.ts +463 -0
  563. package/src/services/external-scan-service.ts +60 -0
  564. package/src/services/file-service.ts +37 -0
  565. package/src/services/fix-service.test.ts +470 -0
  566. package/src/services/fix-service.ts +648 -0
  567. package/src/services/framework-service.test.ts +159 -0
  568. package/src/services/framework-service.ts +67 -0
  569. package/src/services/onboarding-service.ts +165 -0
  570. package/src/services/passport-audit.ts +244 -0
  571. package/src/services/passport-documents.ts +258 -0
  572. package/src/services/passport-service-utils.ts +72 -0
  573. package/src/services/passport-service.test.ts +251 -0
  574. package/src/services/passport-service.ts +339 -0
  575. package/src/services/proxy-service.ts +81 -0
  576. package/src/services/report-service.ts +72 -0
  577. package/src/services/scan-service.test.ts +470 -0
  578. package/src/services/scan-service.ts +335 -0
  579. package/src/services/share-service.ts +108 -0
  580. package/src/services/shared/backup.ts +23 -0
  581. package/src/services/status-service.ts +38 -0
  582. package/src/services/undo-service.test.ts +190 -0
  583. package/src/services/undo-service.ts +144 -0
  584. package/src/test-helpers/factories.ts +116 -0
  585. package/src/types/common.schemas.ts +147 -0
  586. package/src/types/common.types.ts +292 -0
  587. package/src/types/contract.test.ts +217 -0
  588. package/src/types/errors.ts +52 -0
  589. package/src/types/framework.types.ts +87 -0
  590. package/src/types/passport-schemas.ts +241 -0
  591. package/src/types/passport.types.ts +296 -0
  592. package/src/version.ts +1 -0
  593. package/tsconfig.json +20 -0
  594. package/vitest.config.ts +9 -0
@@ -0,0 +1,444 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { buildFileOwnership, attributeFindings, expandPerAgentFindings, PER_AGENT_DOC_CHECK_IDS, type AgentInfo } from './finding-attribution.js';
3
+ import { buildImportGraph } from './import-graph.js';
4
+ import type { FileInfo } from '../../ports/scanner.port.js';
5
+ import type { Finding } from '../../types/common.types.js';
6
+
7
+ const makeFile = (path: string, content: string): FileInfo => ({
8
+ path: `/project/${path}`,
9
+ relativePath: path,
10
+ content,
11
+ extension: '.' + (path.split('.').pop() ?? 'ts'),
12
+ });
13
+
14
+ const makeFinding = (overrides: Partial<Finding> = {}): Finding => ({
15
+ checkId: 'test-check',
16
+ type: 'fail',
17
+ message: 'test finding',
18
+ severity: 'medium',
19
+ ...overrides,
20
+ });
21
+
22
+ describe('buildFileOwnership', () => {
23
+ it('assigns files reachable by one agent only', () => {
24
+ const files = [
25
+ makeFile('agent-a/index.ts', `import { helper } from './helper.js';`),
26
+ makeFile('agent-a/helper.ts', `export const helper = 1;`),
27
+ makeFile('agent-b/main.ts', `export const b = 2;`),
28
+ ];
29
+ const graph = buildImportGraph(files);
30
+ const agents: AgentInfo[] = [
31
+ { name: 'agent-a', sourceFiles: ['agent-a/index.ts'] },
32
+ { name: 'agent-b', sourceFiles: ['agent-b/main.ts'] },
33
+ ];
34
+
35
+ const ownership = buildFileOwnership(agents, graph);
36
+
37
+ expect(ownership.fileToOwner.get('agent-a/index.ts')).toBe('agent-a');
38
+ expect(ownership.fileToOwner.get('agent-a/helper.ts')).toBe('agent-a');
39
+ expect(ownership.fileToOwner.get('agent-b/main.ts')).toBe('agent-b');
40
+ expect(ownership.sharedFiles.size).toBe(0);
41
+ });
42
+
43
+ it('marks files reachable by 2+ agents as shared', () => {
44
+ const files = [
45
+ makeFile('agent-a/index.ts', `import { util } from '../lib/util.js';`),
46
+ makeFile('agent-b/main.ts', `import { util } from '../lib/util.js';`),
47
+ makeFile('lib/util.ts', `export const util = 1;`),
48
+ ];
49
+ const graph = buildImportGraph(files);
50
+ const agents: AgentInfo[] = [
51
+ { name: 'agent-a', sourceFiles: ['agent-a/index.ts'] },
52
+ { name: 'agent-b', sourceFiles: ['agent-b/main.ts'] },
53
+ ];
54
+
55
+ const ownership = buildFileOwnership(agents, graph);
56
+
57
+ expect(ownership.sharedFiles.has('lib/util.ts')).toBe(true);
58
+ expect(ownership.fileToOwner.has('lib/util.ts')).toBe(false);
59
+ });
60
+ });
61
+
62
+ describe('attributeFindings', () => {
63
+ // Test 1: Single agent — all findings attributed
64
+ it('attributes all findings to sole agent', () => {
65
+ const files = [makeFile('src/app.ts', `export const x = 1;`)];
66
+ const graph = buildImportGraph(files);
67
+ const agents: AgentInfo[] = [{ name: 'my-agent', sourceFiles: ['src/app.ts'] }];
68
+ const findings = [
69
+ makeFinding({ file: 'src/app.ts' }),
70
+ makeFinding({ file: 'other.ts' }),
71
+ makeFinding(), // no file
72
+ ];
73
+
74
+ const result = attributeFindings(findings, agents, graph);
75
+
76
+ expect(result).toHaveLength(3);
77
+ expect(result.every(f => f.agentId === 'my-agent')).toBe(true);
78
+ });
79
+
80
+ // Test 2: Two agents, separate dirs — correct attribution
81
+ it('attributes findings to correct agents by import graph', () => {
82
+ const files = [
83
+ makeFile('agent-a/index.ts', `import { h } from './helper.js';`),
84
+ makeFile('agent-a/helper.ts', `export const h = 1;`),
85
+ makeFile('agent-b/main.ts', `import { u } from './utils.js';`),
86
+ makeFile('agent-b/utils.ts', `export const u = 2;`),
87
+ ];
88
+ const graph = buildImportGraph(files);
89
+ const agents: AgentInfo[] = [
90
+ { name: 'agent-a', sourceFiles: ['agent-a/index.ts'] },
91
+ { name: 'agent-b', sourceFiles: ['agent-b/main.ts'] },
92
+ ];
93
+ const findings = [
94
+ makeFinding({ checkId: 'c1', file: 'agent-a/helper.ts' }),
95
+ makeFinding({ checkId: 'c2', file: 'agent-b/utils.ts' }),
96
+ ];
97
+
98
+ const result = attributeFindings(findings, agents, graph);
99
+
100
+ expect(result[0].agentId).toBe('agent-a');
101
+ expect(result[1].agentId).toBe('agent-b');
102
+ });
103
+
104
+ // Test 3: Shared utility imported by both agents → project-level
105
+ it('leaves shared utility findings as project-level', () => {
106
+ const files = [
107
+ makeFile('agent-a/index.ts', `import { util } from '../lib/shared.js';`),
108
+ makeFile('agent-b/main.ts', `import { util } from '../lib/shared.js';`),
109
+ makeFile('lib/shared.ts', `export const util = 1;`),
110
+ ];
111
+ const graph = buildImportGraph(files);
112
+ const agents: AgentInfo[] = [
113
+ { name: 'agent-a', sourceFiles: ['agent-a/index.ts'] },
114
+ { name: 'agent-b', sourceFiles: ['agent-b/main.ts'] },
115
+ ];
116
+ const findings = [makeFinding({ file: 'lib/shared.ts' })];
117
+
118
+ const result = attributeFindings(findings, agents, graph);
119
+
120
+ expect(result[0].agentId).toBeUndefined();
121
+ });
122
+
123
+ // Test 4: Transitive imports (3 levels deep)
124
+ it('follows transitive imports through 3 levels', () => {
125
+ const files = [
126
+ makeFile('src/entry.ts', `import { mid } from './mid.js';`),
127
+ makeFile('src/mid.ts', `import { deep } from './deep.js';`),
128
+ makeFile('src/deep.ts', `export const deep = 1;`),
129
+ ];
130
+ const graph = buildImportGraph(files);
131
+ const agents: AgentInfo[] = [
132
+ { name: 'agent-x', sourceFiles: ['src/entry.ts'] },
133
+ { name: 'agent-y', sourceFiles: [] },
134
+ ];
135
+ const findings = [
136
+ makeFinding({ checkId: 'c1', file: 'src/mid.ts' }),
137
+ makeFinding({ checkId: 'c2', file: 'src/deep.ts' }),
138
+ ];
139
+
140
+ const result = attributeFindings(findings, agents, graph);
141
+
142
+ expect(result[0].agentId).toBe('agent-x');
143
+ expect(result[1].agentId).toBe('agent-x');
144
+ });
145
+
146
+ // Test 5: Finding without file (L1) in multi-agent → project-level
147
+ it('leaves L1 findings without file as project-level in multi-agent', () => {
148
+ const files = [
149
+ makeFile('a/index.ts', `export const a = 1;`),
150
+ makeFile('b/main.ts', `export const b = 2;`),
151
+ ];
152
+ const graph = buildImportGraph(files);
153
+ const agents: AgentInfo[] = [
154
+ { name: 'agent-a', sourceFiles: ['a/index.ts'] },
155
+ { name: 'agent-b', sourceFiles: ['b/main.ts'] },
156
+ ];
157
+ const findings = [makeFinding({ file: undefined })];
158
+
159
+ const result = attributeFindings(findings, agents, graph);
160
+
161
+ expect(result[0].agentId).toBeUndefined();
162
+ });
163
+
164
+ // Test 6: Directory prefix fallback (doc file under agent dir)
165
+ it('uses directory prefix fallback for non-code files', () => {
166
+ const files = [
167
+ makeFile('agents/bot-a/src/index.ts', `export const a = 1;`),
168
+ makeFile('agents/bot-a/config.ts', `export const c = 1;`),
169
+ makeFile('agents/bot-b/src/main.ts', `export const b = 2;`),
170
+ makeFile('agents/bot-b/config.ts', `export const c = 2;`),
171
+ ];
172
+ const graph = buildImportGraph(files);
173
+ const agents: AgentInfo[] = [
174
+ { name: 'bot-a', sourceFiles: ['agents/bot-a/src/index.ts', 'agents/bot-a/config.ts'] },
175
+ { name: 'bot-b', sourceFiles: ['agents/bot-b/src/main.ts', 'agents/bot-b/config.ts'] },
176
+ ];
177
+ // README under bot-a's directory, not in import graph
178
+ const findings = [makeFinding({ file: 'agents/bot-a/docs/README.md' })];
179
+
180
+ const result = attributeFindings(findings, agents, graph);
181
+
182
+ expect(result[0].agentId).toBe('bot-a');
183
+ });
184
+
185
+ // Test 7: Root-level config (package.json) → project-level
186
+ it('leaves root-level configs as project-level', () => {
187
+ const files = [
188
+ makeFile('src/a.ts', `export const a = 1;`),
189
+ makeFile('src/b.ts', `export const b = 2;`),
190
+ ];
191
+ const graph = buildImportGraph(files);
192
+ const agents: AgentInfo[] = [
193
+ { name: 'agent-a', sourceFiles: ['src/a.ts'] },
194
+ { name: 'agent-b', sourceFiles: ['src/b.ts'] },
195
+ ];
196
+ const findings = [makeFinding({ file: 'package.json' })];
197
+
198
+ const result = attributeFindings(findings, agents, graph);
199
+
200
+ expect(result[0].agentId).toBeUndefined();
201
+ });
202
+
203
+ // Test 8: Empty agents array → no attribution
204
+ it('returns findings unchanged when no agents', () => {
205
+ const graph = buildImportGraph([]);
206
+ const findings = [makeFinding({ file: 'src/app.ts' })];
207
+
208
+ const result = attributeFindings(findings, [], graph);
209
+
210
+ expect(result[0].agentId).toBeUndefined();
211
+ });
212
+
213
+ // Test 9: Circular imports — handled, no infinite loop
214
+ it('handles circular imports without infinite loop', () => {
215
+ const files = [
216
+ makeFile('src/a.ts', `import { b } from './b.js';`),
217
+ makeFile('src/b.ts', `import { a } from './a.js';`),
218
+ ];
219
+ const graph = buildImportGraph(files);
220
+ const agents: AgentInfo[] = [
221
+ { name: 'circle-agent', sourceFiles: ['src/a.ts'] },
222
+ { name: 'other', sourceFiles: [] },
223
+ ];
224
+ const findings = [
225
+ makeFinding({ file: 'src/a.ts' }),
226
+ makeFinding({ file: 'src/b.ts' }),
227
+ ];
228
+
229
+ const result = attributeFindings(findings, agents, graph);
230
+
231
+ expect(result[0].agentId).toBe('circle-agent');
232
+ expect(result[1].agentId).toBe('circle-agent');
233
+ });
234
+
235
+ // Test 10: Graph ownership beats directory prefix
236
+ it('prefers graph ownership over directory prefix', () => {
237
+ const files = [
238
+ makeFile('agent-a/index.ts', `import { helper } from '../lib/helper.js';`),
239
+ makeFile('lib/helper.ts', `export const helper = 1;`),
240
+ makeFile('agent-b/main.ts', `export const b = 2;`),
241
+ ];
242
+ const graph = buildImportGraph(files);
243
+ const agents: AgentInfo[] = [
244
+ { name: 'agent-a', sourceFiles: ['agent-a/index.ts'] },
245
+ { name: 'agent-b', sourceFiles: ['agent-b/main.ts'] },
246
+ ];
247
+ // lib/helper.ts is only reachable from agent-a via import graph
248
+ // It's NOT under agent-b's directory prefix
249
+ const findings = [makeFinding({ file: 'lib/helper.ts' })];
250
+
251
+ const result = attributeFindings(findings, agents, graph);
252
+
253
+ // Graph ownership: agent-a imports lib/helper → owned by agent-a
254
+ expect(result[0].agentId).toBe('agent-a');
255
+ });
256
+ });
257
+
258
+ describe('E2E: full multi-agent attribution pipeline', () => {
259
+ it('attributes findings correctly across complex multi-agent project', () => {
260
+ // Simulate a real multi-agent project:
261
+ // - agent-chat: imports openai, has chat service + helper
262
+ // - agent-embed: imports @ai-sdk/openai, has embed pipeline + processor
263
+ // - shared lib/logger used by both
264
+ // - config files under each agent dir
265
+ // - root-level package.json
266
+ const files = [
267
+ // Agent Chat subtree
268
+ makeFile('agents/chat/src/index.ts',
269
+ `import { OpenAI } from 'openai';\nimport { format } from './format.js';\nimport { log } from '../../lib/logger.js';`),
270
+ makeFile('agents/chat/src/format.ts',
271
+ `export const format = (msg: string) => msg.trim();`),
272
+ // Agent Embed subtree
273
+ makeFile('agents/embed/src/main.ts',
274
+ `import { embed } from '@ai-sdk/openai';\nimport { process } from './processor.js';\nimport { log } from '../../lib/logger.js';`),
275
+ makeFile('agents/embed/src/processor.ts',
276
+ `export const process = (data: string[]) => data;`),
277
+ // Shared lib — imported by both
278
+ makeFile('lib/logger.ts',
279
+ `export const log = (msg: string) => console.log(msg);`),
280
+ // Root config
281
+ makeFile('package.json', `{"name":"multi-agent-project"}`),
282
+ ];
283
+
284
+ const graph = buildImportGraph(files);
285
+ const agents: AgentInfo[] = [
286
+ { name: 'chat-agent', sourceFiles: ['agents/chat/src/index.ts'] },
287
+ { name: 'embed-agent', sourceFiles: ['agents/embed/src/main.ts'] },
288
+ ];
289
+
290
+ const findings = [
291
+ // Finding on chat's direct file
292
+ makeFinding({ checkId: 'sdk-no-disclosure', file: 'agents/chat/src/index.ts', severity: 'high' }),
293
+ // Finding on chat's transitively-imported helper
294
+ makeFinding({ checkId: 'missing-error-handling', file: 'agents/chat/src/format.ts', severity: 'medium' }),
295
+ // Finding on embed's direct file
296
+ makeFinding({ checkId: 'sdk-no-disclosure', file: 'agents/embed/src/main.ts', severity: 'high' }),
297
+ // Finding on embed's transitively-imported processor
298
+ makeFinding({ checkId: 'missing-validation', file: 'agents/embed/src/processor.ts', severity: 'medium' }),
299
+ // Finding on shared logger — project-level
300
+ makeFinding({ checkId: 'logging-no-retention', file: 'lib/logger.ts', severity: 'low' }),
301
+ // Finding on root package.json — project-level
302
+ makeFinding({ checkId: 'banned-package', file: 'package.json', severity: 'critical' }),
303
+ // L1 finding without file — project-level
304
+ makeFinding({ checkId: 'missing-fria', file: undefined, severity: 'high' }),
305
+ ];
306
+
307
+ const result = attributeFindings(findings, agents, graph);
308
+
309
+ // Chat agent owns its files
310
+ expect(result[0].agentId).toBe('chat-agent'); // index.ts
311
+ expect(result[1].agentId).toBe('chat-agent'); // format.ts (transitive)
312
+
313
+ // Embed agent owns its files
314
+ expect(result[2].agentId).toBe('embed-agent'); // main.ts
315
+ expect(result[3].agentId).toBe('embed-agent'); // processor.ts (transitive)
316
+
317
+ // Shared logger → project-level (no agentId)
318
+ expect(result[4].agentId).toBeUndefined();
319
+
320
+ // Root config → project-level (no agentId)
321
+ expect(result[5].agentId).toBeUndefined();
322
+
323
+ // L1 finding without file → project-level
324
+ expect(result[6].agentId).toBeUndefined();
325
+ });
326
+
327
+ it('attributeFindings is pure — does not mutate input', () => {
328
+ const files = [
329
+ makeFile('src/app.ts', `import { OpenAI } from 'openai';`),
330
+ ];
331
+ const graph = buildImportGraph(files);
332
+ const agents: AgentInfo[] = [{ name: 'test-agent', sourceFiles: ['src/app.ts'] }];
333
+ const original = makeFinding({ file: 'src/app.ts' });
334
+ const findings = [original];
335
+
336
+ const result = attributeFindings(findings, agents, graph);
337
+
338
+ // Original finding not mutated
339
+ expect(original.agentId).toBeUndefined();
340
+ // Result has new agentId
341
+ expect(result[0].agentId).toBe('test-agent');
342
+ // Different object reference
343
+ expect(result[0]).not.toBe(original);
344
+ });
345
+
346
+ it('buildFileOwnership returns frozen immutable result', () => {
347
+ const files = [
348
+ makeFile('src/a.ts', `export const a = 1;`),
349
+ ];
350
+ const graph = buildImportGraph(files);
351
+ const agents: AgentInfo[] = [{ name: 'agent', sourceFiles: ['src/a.ts'] }];
352
+
353
+ const ownership = buildFileOwnership(agents, graph);
354
+
355
+ expect(Object.isFrozen(ownership)).toBe(true);
356
+ });
357
+ });
358
+
359
+ describe('expandPerAgentFindings', () => {
360
+ const agents: AgentInfo[] = [
361
+ { name: 'agent-x', sourceFiles: ['src/x.ts'] },
362
+ { name: 'agent-y', sourceFiles: ['src/y.ts'] },
363
+ ];
364
+
365
+ it('single agent → no expansion', () => {
366
+ const singleAgent: AgentInfo[] = [{ name: 'sole', sourceFiles: ['src/app.ts'] }];
367
+ const findings = [
368
+ makeFinding({ checkId: 'fria', type: 'fail' }),
369
+ makeFinding({ checkId: 'qms', type: 'fail' }),
370
+ ];
371
+
372
+ const result = expandPerAgentFindings(findings, singleAgent);
373
+
374
+ expect(result).toBe(findings); // same reference, no-op
375
+ });
376
+
377
+ it('multi-agent, FRIA fail → expanded to N per-agent fails', () => {
378
+ const findings = [
379
+ makeFinding({ checkId: 'fria', type: 'fail', message: 'No FRIA found' }),
380
+ ];
381
+
382
+ const result = expandPerAgentFindings(findings, agents);
383
+
384
+ expect(result).toHaveLength(2);
385
+ expect(result[0].agentId).toBe('agent-x');
386
+ expect(result[0].checkId).toBe('fria');
387
+ expect(result[1].agentId).toBe('agent-y');
388
+ expect(result[1].checkId).toBe('fria');
389
+ });
390
+
391
+ it('multi-agent, FRIA pass → expanded to per-agent passes', () => {
392
+ const findings = [
393
+ makeFinding({ checkId: 'fria', type: 'pass', message: 'FRIA found' }),
394
+ ];
395
+
396
+ const result = expandPerAgentFindings(findings, agents);
397
+
398
+ expect(result).toHaveLength(2);
399
+ expect(result[0]).toMatchObject({ checkId: 'fria', type: 'pass', agentId: 'agent-x' });
400
+ expect(result[1]).toMatchObject({ checkId: 'fria', type: 'pass', agentId: 'agent-y' });
401
+ });
402
+
403
+ it('multi-agent, mixed per-agent + project-only checks', () => {
404
+ const findings = [
405
+ makeFinding({ checkId: 'fria', type: 'fail' }),
406
+ makeFinding({ checkId: 'risk-management', type: 'fail' }),
407
+ makeFinding({ checkId: 'qms', type: 'fail' }), // project-level, not expanded
408
+ makeFinding({ checkId: 'technical-documentation', type: 'pass' }), // pass, expanded per-agent
409
+ makeFinding({ checkId: 'ai-literacy', type: 'fail' }), // project-level
410
+ ];
411
+
412
+ const result = expandPerAgentFindings(findings, agents);
413
+
414
+ // fria fail → 2, risk-management fail → 2, qms → 1, tech-doc pass → 2, ai-literacy → 1
415
+ expect(result).toHaveLength(8);
416
+
417
+ // First 2: fria expanded
418
+ expect(result[0]).toMatchObject({ checkId: 'fria', agentId: 'agent-x' });
419
+ expect(result[1]).toMatchObject({ checkId: 'fria', agentId: 'agent-y' });
420
+
421
+ // Next 2: risk-management expanded
422
+ expect(result[2]).toMatchObject({ checkId: 'risk-management', agentId: 'agent-x' });
423
+ expect(result[3]).toMatchObject({ checkId: 'risk-management', agentId: 'agent-y' });
424
+
425
+ // qms stays project-level
426
+ expect(result[4]).toMatchObject({ checkId: 'qms' });
427
+ expect(result[4].agentId).toBeUndefined();
428
+
429
+ // tech-doc pass expanded per-agent (each agent gets credit)
430
+ expect(result[5]).toMatchObject({ checkId: 'technical-documentation', type: 'pass', agentId: 'agent-x' });
431
+ expect(result[6]).toMatchObject({ checkId: 'technical-documentation', type: 'pass', agentId: 'agent-y' });
432
+
433
+ // ai-literacy stays project-level
434
+ expect(result[7]).toMatchObject({ checkId: 'ai-literacy' });
435
+ expect(result[7].agentId).toBeUndefined();
436
+ });
437
+
438
+ it('PER_AGENT_DOC_CHECK_IDS contains all 7 per-agent checks', () => {
439
+ expect(PER_AGENT_DOC_CHECK_IDS.size).toBe(7);
440
+ expect(PER_AGENT_DOC_CHECK_IDS.has('fria')).toBe(true);
441
+ expect(PER_AGENT_DOC_CHECK_IDS.has('data-governance')).toBe(true);
442
+ expect(PER_AGENT_DOC_CHECK_IDS.has('qms')).toBe(false);
443
+ });
444
+ });
@@ -0,0 +1,195 @@
1
+ import type { Finding } from '../../types/common.types.js';
2
+ import type { ImportGraph } from './import-graph.js';
3
+
4
+ export interface AgentInfo {
5
+ readonly name: string;
6
+ readonly sourceFiles: readonly string[];
7
+ }
8
+
9
+ export interface FileOwnershipMap {
10
+ readonly fileToOwner: ReadonlyMap<string, string>; // file → sole agent
11
+ readonly sharedFiles: ReadonlySet<string>; // files reachable by 2+ agents
12
+ }
13
+
14
+ /**
15
+ * BFS forward from each agent's sourceFiles through the import graph.
16
+ * Files reachable by exactly 1 agent → owned by that agent.
17
+ * Files reachable by 2+ agents → shared (project-level).
18
+ */
19
+ export const buildFileOwnership = (
20
+ agents: readonly AgentInfo[],
21
+ importGraph: ImportGraph,
22
+ ): FileOwnershipMap => {
23
+ // reachable[file] = set of agent names that can reach it
24
+ const reachableBy = new Map<string, Set<string>>();
25
+
26
+ for (const agent of agents) {
27
+ const visited = new Set<string>();
28
+ const queue = [...agent.sourceFiles];
29
+
30
+ // Seed: mark all sourceFiles as visited
31
+ for (const sf of agent.sourceFiles) {
32
+ visited.add(sf);
33
+ }
34
+
35
+ while (queue.length > 0) {
36
+ const current = queue.shift()!;
37
+
38
+ // Record reachability
39
+ if (!reachableBy.has(current)) reachableBy.set(current, new Set());
40
+ reachableBy.get(current)!.add(agent.name);
41
+
42
+ // BFS forward through imports
43
+ const node = importGraph.nodes.get(current);
44
+ if (!node) continue;
45
+ for (const imp of node.imports) {
46
+ if (!visited.has(imp)) {
47
+ visited.add(imp);
48
+ queue.push(imp);
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ const fileToOwner = new Map<string, string>();
55
+ const sharedFiles = new Set<string>();
56
+
57
+ for (const [file, owners] of reachableBy) {
58
+ if (owners.size === 1) {
59
+ fileToOwner.set(file, owners.values().next().value!);
60
+ } else {
61
+ sharedFiles.add(file);
62
+ }
63
+ }
64
+
65
+ return Object.freeze({ fileToOwner, sharedFiles });
66
+ };
67
+
68
+ /**
69
+ * Compute common directory prefix of an agent's sourceFiles.
70
+ * Returns '' if no common prefix.
71
+ */
72
+ const computeAgentRoot = (sourceFiles: readonly string[]): string => {
73
+ const dirs = sourceFiles
74
+ .map(f => {
75
+ const idx = f.lastIndexOf('/');
76
+ return idx >= 0 ? f.substring(0, idx + 1) : '';
77
+ })
78
+ .filter(Boolean);
79
+
80
+ if (dirs.length === 0) return '';
81
+
82
+ let common = dirs[0];
83
+ for (const d of dirs) {
84
+ while (common && !d.startsWith(common)) {
85
+ const trimmed = common.substring(0, common.length - 1);
86
+ const slash = trimmed.lastIndexOf('/');
87
+ common = slash >= 0 ? trimmed.substring(0, slash + 1) : '';
88
+ }
89
+ }
90
+ return common;
91
+ };
92
+
93
+ /**
94
+ * Build sorted (longest first) agent root directories for prefix fallback.
95
+ */
96
+ const buildAgentRoots = (
97
+ agents: readonly AgentInfo[],
98
+ ): readonly { readonly name: string; readonly root: string }[] => {
99
+ const roots: { name: string; root: string }[] = [];
100
+ for (const agent of agents) {
101
+ const root = computeAgentRoot(agent.sourceFiles);
102
+ if (root) roots.push({ name: agent.name, root });
103
+ }
104
+ // Longest root first → most specific match wins
105
+ roots.sort((a, b) => b.root.length - a.root.length);
106
+ return roots;
107
+ };
108
+
109
+ /** Check IDs that require per-agent documents in multi-agent projects (EU AI Act per-AI-system). */
110
+ export const PER_AGENT_DOC_CHECK_IDS: ReadonlySet<string> = new Set([
111
+ 'fria',
112
+ 'risk-management',
113
+ 'technical-documentation',
114
+ 'declaration-of-conformity',
115
+ 'art5-screening',
116
+ 'instructions-for-use',
117
+ 'data-governance',
118
+ ]);
119
+
120
+ /**
121
+ * In multi-agent projects, expand project-level document-presence findings
122
+ * into per-agent findings. Each agent needs its own compliance documents,
123
+ * and each agent should receive credit for existing project-level docs.
124
+ *
125
+ * - agents.length ≤ 1 → no-op (single agent already gets all findings)
126
+ * - PASS/FAIL/SKIP on per-agent checkId → N findings, one per agent with agentId
127
+ */
128
+ export const expandPerAgentFindings = (
129
+ findings: readonly Finding[],
130
+ agents: readonly AgentInfo[],
131
+ ): readonly Finding[] => {
132
+ if (agents.length <= 1) return findings;
133
+
134
+ const result: Finding[] = [];
135
+
136
+ for (const f of findings) {
137
+ if (!PER_AGENT_DOC_CHECK_IDS.has(f.checkId)) {
138
+ result.push(f);
139
+ continue;
140
+ }
141
+
142
+ // Expand doc findings to all agents — both passes and fails
143
+ for (const agent of agents) {
144
+ result.push({ ...f, agentId: agent.name });
145
+ }
146
+ }
147
+
148
+ return result;
149
+ };
150
+
151
+ /**
152
+ * Attribute findings to agents using import-graph ownership + directory prefix fallback.
153
+ *
154
+ * Algorithm:
155
+ * - 0 agents → return as-is
156
+ * - 1 agent → all findings → that agent (fast path)
157
+ * - N agents → graph-based ownership, then directory prefix fallback for non-code files
158
+ */
159
+ export const attributeFindings = (
160
+ findings: readonly Finding[],
161
+ agents: readonly AgentInfo[],
162
+ importGraph: ImportGraph,
163
+ ): readonly Finding[] => {
164
+ if (agents.length === 0) return findings;
165
+
166
+ // Single agent → all findings belong to it
167
+ if (agents.length === 1) {
168
+ const sole = agents[0].name;
169
+ return findings.map(f => ({ ...f, agentId: sole }));
170
+ }
171
+
172
+ // Multi-agent: graph-based ownership + directory prefix fallback
173
+ const ownership = buildFileOwnership(agents, importGraph);
174
+ const agentRoots = buildAgentRoots(agents);
175
+
176
+ return findings.map(f => {
177
+ // 1. No file (L1) → project-level
178
+ if (!f.file) return f;
179
+
180
+ // 2. Graph-based: sole owner
181
+ const owner = ownership.fileToOwner.get(f.file);
182
+ if (owner) return { ...f, agentId: owner };
183
+
184
+ // 3. Shared file (reachable by 2+ agents) → project-level
185
+ if (ownership.sharedFiles.has(f.file)) return f;
186
+
187
+ // 4. Directory prefix fallback (for docs, configs not in import graph)
188
+ for (const { name, root } of agentRoots) {
189
+ if (f.file.startsWith(root)) return { ...f, agentId: name };
190
+ }
191
+
192
+ // 5. Root-level / unattributable → project-level
193
+ return f;
194
+ });
195
+ };