@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,297 @@
1
+ import type { CheckResult } from '../../../types/common.types.js';
2
+ import type { DocQualityLevel } from '../../../types/passport.types.js';
3
+ import type { ScanContext } from '../../../ports/scanner.port.js';
4
+ import { DOCUMENT_VALIDATORS } from '../validators.js';
5
+ import {
6
+ parseMarkdownHeadings,
7
+ headingMatches,
8
+ extractSectionContents,
9
+ extractGroupedSectionContent,
10
+ measureSemanticDepth,
11
+ hasAiReviewMarker,
12
+ } from './layer2-parsing.js';
13
+
14
+ // Re-export for backward compatibility
15
+ export { measureSectionDepth, measureSemanticDepth } from './layer2-parsing.js';
16
+ export type { SectionDepth, SemanticDepth } from './layer2-parsing.js';
17
+
18
+ // --- Types ---
19
+
20
+ export interface ValidatorSection {
21
+ readonly title: string;
22
+ readonly required: boolean;
23
+ }
24
+
25
+ export interface DocumentValidator {
26
+ readonly document: string;
27
+ readonly obligation: string;
28
+ readonly article: string;
29
+ readonly file_patterns: readonly string[];
30
+ readonly required_sections: readonly ValidatorSection[];
31
+ }
32
+
33
+ export type L2Status = 'VALID' | 'PARTIAL' | 'SHALLOW' | 'EMPTY';
34
+
35
+ export interface L2CheckResult {
36
+ readonly obligationId: string;
37
+ readonly article: string;
38
+ readonly document: string;
39
+ readonly status: L2Status;
40
+ readonly foundSections: readonly string[];
41
+ readonly missingSections: readonly string[];
42
+ readonly totalRequired: number;
43
+ readonly matchedRequired: number;
44
+ readonly shallowSections?: readonly string[];
45
+ readonly sectionFeedback?: readonly string[]; // per-section actionable feedback
46
+ readonly completenessScore?: number; // 0-100 overall document quality
47
+ readonly docQuality: DocQualityLevel;
48
+ }
49
+
50
+ // --- Validator Loading ---
51
+
52
+ export const loadValidators = (): readonly DocumentValidator[] => DOCUMENT_VALIDATORS;
53
+
54
+ /** Marker injected by Complior fix into auto-generated scaffold files. */
55
+ const SCAFFOLD_MARKER = '<!-- COMPLIOR:SCAFFOLD -->';
56
+ const hasScaffoldMarker = (content: string): boolean => content.includes(SCAFFOLD_MARKER);
57
+
58
+ /** Derive doc quality from L2 status + AI review marker presence + scaffold marker. */
59
+ const classifyDocQuality = (status: L2Status, content: string): DocQualityLevel =>
60
+ hasAiReviewMarker(content)
61
+ ? 'reviewed'
62
+ : hasScaffoldMarker(content)
63
+ ? 'scaffold'
64
+ : (status === 'SHALLOW' || status === 'EMPTY' || status === 'PARTIAL') ? 'scaffold' : 'draft';
65
+
66
+ // --- L2 Check Logic ---
67
+
68
+ const findMatchingFile = (
69
+ validator: DocumentValidator,
70
+ files: readonly { readonly relativePath: string; readonly content: string }[],
71
+ ): { readonly relativePath: string; readonly content: string } | undefined => {
72
+ for (const file of files) {
73
+ const filename = file.relativePath.split('/').pop() ?? '';
74
+ for (const pattern of validator.file_patterns) {
75
+ if (filename.toLowerCase() === pattern.toLowerCase()) {
76
+ return file;
77
+ }
78
+ }
79
+ }
80
+ return undefined;
81
+ };
82
+
83
+ export const validateDocument = (
84
+ validator: DocumentValidator,
85
+ content: string,
86
+ ): L2CheckResult => {
87
+ const obligationId = validator.obligation;
88
+ const headings = parseMarkdownHeadings(content);
89
+ const requiredSections = validator.required_sections.filter((s) => s.required);
90
+
91
+ if (content.trim().length === 0 || headings.length === 0) {
92
+ return {
93
+ obligationId,
94
+ article: validator.article,
95
+ document: validator.document,
96
+ status: 'EMPTY',
97
+ foundSections: [],
98
+ missingSections: requiredSections.map((s) => s.title),
99
+ totalRequired: requiredSections.length,
100
+ matchedRequired: 0,
101
+ docQuality: classifyDocQuality('EMPTY', content),
102
+ };
103
+ }
104
+
105
+ const found: string[] = [];
106
+ const missing: string[] = [];
107
+
108
+ for (const section of requiredSections) {
109
+ const matched = headings.some((h) => headingMatches(h, section.title));
110
+ if (matched) {
111
+ found.push(section.title);
112
+ } else {
113
+ missing.push(section.title);
114
+ }
115
+ }
116
+
117
+ // If all headings present, check content depth for SHALLOW detection
118
+ if (missing.length === 0 && found.length > 0) {
119
+ const sectionContents = extractSectionContents(content);
120
+ const shallowSections: string[] = [];
121
+ const feedbackItems: string[] = [];
122
+ let totalQualityScore = 0;
123
+ let scoredSections = 0;
124
+
125
+ for (const sectionTitle of found) {
126
+ // Find matching heading in extracted contents
127
+ const matchedHeading = [...sectionContents.keys()].find((h) =>
128
+ headingMatches(h, sectionTitle),
129
+ );
130
+ if (matchedHeading !== undefined) {
131
+ // Use grouped extraction to include child sub-headings (### under ##)
132
+ const sectionContent = extractGroupedSectionContent(content, sectionTitle);
133
+ const semantic = measureSemanticDepth(sectionContent, sectionTitle);
134
+ if (semantic.isShallow) {
135
+ shallowSections.push(sectionTitle);
136
+ }
137
+ if (semantic.feedback && !semantic.feedback.endsWith(': adequate')) {
138
+ feedbackItems.push(semantic.feedback);
139
+ }
140
+ totalQualityScore += semantic.qualityScore;
141
+ scoredSections++;
142
+ }
143
+ }
144
+
145
+ const shallowRatio = found.length > 0 ? shallowSections.length / found.length : 0;
146
+ const completenessScore = scoredSections > 0
147
+ ? Math.round(totalQualityScore / scoredSections)
148
+ : 0;
149
+ const status: L2Status = shallowRatio > 0.5
150
+ ? 'SHALLOW'
151
+ : completenessScore < 50
152
+ ? 'PARTIAL'
153
+ : 'VALID';
154
+
155
+ const docQuality = classifyDocQuality(status, content);
156
+
157
+ return {
158
+ obligationId,
159
+ article: validator.article,
160
+ document: validator.document,
161
+ status,
162
+ foundSections: found,
163
+ missingSections: missing,
164
+ totalRequired: requiredSections.length,
165
+ matchedRequired: found.length,
166
+ shallowSections: shallowSections.length > 0 ? shallowSections : undefined,
167
+ sectionFeedback: feedbackItems.length > 0 ? feedbackItems : undefined,
168
+ completenessScore,
169
+ docQuality,
170
+ };
171
+ }
172
+
173
+ const status: L2Status = missing.length === 0
174
+ ? 'VALID'
175
+ : found.length === 0
176
+ ? 'EMPTY'
177
+ : 'PARTIAL';
178
+
179
+ const docQuality = classifyDocQuality(status, content);
180
+
181
+ return {
182
+ obligationId,
183
+ article: validator.article,
184
+ document: validator.document,
185
+ status,
186
+ foundSections: found,
187
+ missingSections: missing,
188
+ totalRequired: requiredSections.length,
189
+ matchedRequired: found.length,
190
+ docQuality,
191
+ };
192
+ };
193
+
194
+ // --- Scanner Integration ---
195
+
196
+ export const runLayer2 = (ctx: ScanContext): readonly L2CheckResult[] => {
197
+ const validators = loadValidators();
198
+ const results: L2CheckResult[] = [];
199
+
200
+ for (const validator of validators) {
201
+ const file = findMatchingFile(validator, ctx.files);
202
+ if (file === undefined) continue; // L1 didn't find it — L2 skips
203
+
204
+ results.push(validateDocument(validator, file.content));
205
+ }
206
+
207
+ return results;
208
+ };
209
+
210
+ export const layer2ToCheckResults = (l2Results: readonly L2CheckResult[]): readonly CheckResult[] => {
211
+ return l2Results.map((r): CheckResult => {
212
+ if (r.status === 'VALID') {
213
+ // Scaffold docs should never pass — they need manual editing
214
+ if (r.docQuality === 'scaffold') {
215
+ return {
216
+ type: 'fail',
217
+ checkId: `l2-${r.document}`,
218
+ message: `${r.article}: ${r.document} — document is scaffold/template quality, fill placeholder fields to upgrade`,
219
+ severity: 'low',
220
+ obligationId: r.obligationId,
221
+ articleReference: r.article,
222
+ fix: `Edit ${r.document} — replace [bracketed] placeholders with actual data`,
223
+ docQuality: r.docQuality,
224
+ };
225
+ }
226
+ return {
227
+ type: 'pass',
228
+ checkId: `l2-${r.document}`,
229
+ message: `${r.article}: ${r.document} — all ${r.totalRequired} required sections present`,
230
+ };
231
+ }
232
+
233
+ if (r.status === 'SHALLOW') {
234
+ const shallowList = r.shallowSections?.join(', ') ?? 'multiple sections';
235
+ const feedbackSuffix = r.sectionFeedback && r.sectionFeedback.length > 0
236
+ ? `. Suggestions: ${r.sectionFeedback.join('. ')}`
237
+ : '';
238
+ const scoreSuffix = r.completenessScore !== undefined
239
+ ? ` (quality: ${r.completenessScore}/100)`
240
+ : '';
241
+ return {
242
+ type: 'fail',
243
+ checkId: `l2-${r.document}`,
244
+ message: `${r.article}: ${r.document} — headings present but content is shallow in: ${shallowList}${scoreSuffix}`,
245
+ severity: 'medium',
246
+ obligationId: r.obligationId,
247
+ articleReference: r.article,
248
+ fix: `Expand shallow sections in ${r.document} with details (dates, specifics, lists): ${shallowList}${feedbackSuffix}`,
249
+ };
250
+ }
251
+
252
+ if (r.status === 'PARTIAL') {
253
+ // Two PARTIAL triggers: missing sections OR low content quality
254
+ if (r.missingSections.length > 0) {
255
+ return {
256
+ type: 'fail',
257
+ checkId: `l2-${r.document}`,
258
+ message: `${r.article}: ${r.document} — missing sections: ${r.missingSections.join(', ')} (${r.matchedRequired}/${r.totalRequired})`,
259
+ severity: 'medium',
260
+ obligationId: r.obligationId,
261
+ articleReference: r.article,
262
+ fix: `Add missing sections to ${r.document}: ${r.missingSections.join(', ')}`,
263
+ };
264
+ }
265
+ // All headings present but content quality < 50
266
+ const scoreSuffix = r.completenessScore !== undefined
267
+ ? ` (quality: ${r.completenessScore}/100)`
268
+ : '';
269
+ const feedbackSuffix = r.sectionFeedback && r.sectionFeedback.length > 0
270
+ ? `. Suggestions: ${r.sectionFeedback.join('. ')}`
271
+ : '';
272
+ const scaffoldHint = r.docQuality === 'scaffold'
273
+ ? '. Remove <!-- COMPLIOR:SCAFFOLD --> comment after editing to upgrade to draft quality'
274
+ : '';
275
+ return {
276
+ type: 'fail',
277
+ checkId: `l2-${r.document}`,
278
+ message: `${r.article}: ${r.document} — all sections present but content needs enrichment${scoreSuffix}`,
279
+ severity: 'low',
280
+ obligationId: r.obligationId,
281
+ articleReference: r.article,
282
+ fix: `Enrich content in ${r.document} — add specifics, dates, tables${feedbackSuffix}${scaffoldHint}`,
283
+ };
284
+ }
285
+
286
+ // EMPTY
287
+ return {
288
+ type: 'fail',
289
+ checkId: `l2-${r.document}`,
290
+ message: `${r.article}: ${r.document} — document is empty or has no headings (0/${r.totalRequired} required sections)`,
291
+ severity: 'high',
292
+ obligationId: r.obligationId,
293
+ articleReference: r.article,
294
+ fix: `Populate ${r.document} with required sections: ${r.missingSections.join(', ')}`,
295
+ };
296
+ });
297
+ };
@@ -0,0 +1,217 @@
1
+ export interface SectionDepth {
2
+ readonly wordCount: number;
3
+ readonly sentenceCount: number;
4
+ readonly hasLists: boolean;
5
+ readonly hasTables: boolean;
6
+ readonly hasSpecifics: boolean;
7
+ readonly isShallow: boolean;
8
+ }
9
+
10
+ const HEADING_REGEX = /^#{1,4}\s+(.+)$/gm;
11
+
12
+ export const parseMarkdownHeadings = (content: string): readonly string[] => {
13
+ const headings: string[] = [];
14
+ let match: RegExpExecArray | null;
15
+ while ((match = HEADING_REGEX.exec(content)) !== null) {
16
+ headings.push(match[1].trim());
17
+ }
18
+ return headings;
19
+ };
20
+
21
+ export const normalize = (text: string): string =>
22
+ text.toLowerCase().replace(/[\s_-]+/g, ' ').trim();
23
+
24
+ export const headingMatches = (heading: string, sectionTitle: string): boolean =>
25
+ normalize(heading).includes(normalize(sectionTitle));
26
+
27
+ const LIST_REGEX = /^[\s]*[-*•]\s+|^\s*\d+\.\s+/m;
28
+ const TABLE_REGEX = /\|.*\|.*\|/;
29
+ const SPECIFICS_REGEX = /\b\d{4}[-/]\d{2}[-/]\d{2}\b|\b\d+%|\b\d+\.\d+\b|€|Art\.\s*\d+/;
30
+
31
+ export const extractSectionContents = (content: string): ReadonlyMap<string, string> => {
32
+ const sections = new Map<string, string>();
33
+ const lines = content.split('\n');
34
+ let currentHeading: string | null = null;
35
+ let currentContent: string[] = [];
36
+
37
+ for (const line of lines) {
38
+ const headingMatch = /^#{1,4}\s+(.+)$/.exec(line);
39
+ if (headingMatch) {
40
+ if (currentHeading !== null) {
41
+ sections.set(currentHeading, currentContent.join('\n'));
42
+ }
43
+ currentHeading = headingMatch[1].trim();
44
+ currentContent = [];
45
+ } else if (currentHeading !== null) {
46
+ currentContent.push(line);
47
+ }
48
+ }
49
+
50
+ if (currentHeading !== null) {
51
+ sections.set(currentHeading, currentContent.join('\n'));
52
+ }
53
+
54
+ return sections;
55
+ };
56
+
57
+ /**
58
+ * Extract content for a target heading, including all child sub-headings
59
+ * until the next heading of equal or higher level.
60
+ * Example: for "## System Elements", aggregates ### 2.1, ### 2.2, etc.
61
+ */
62
+ export const extractGroupedSectionContent = (
63
+ content: string,
64
+ targetHeading: string,
65
+ ): string => {
66
+ const lines = content.split('\n');
67
+ let capturing = false;
68
+ let targetLevel = 0;
69
+ const captured: string[] = [];
70
+
71
+ for (const line of lines) {
72
+ const match = /^(#{1,4})\s+(.+)$/.exec(line);
73
+ if (match) {
74
+ const level = match[1].length;
75
+ const heading = match[2].trim();
76
+ if (!capturing && headingMatches(heading, targetHeading)) {
77
+ capturing = true;
78
+ targetLevel = level;
79
+ continue;
80
+ }
81
+ if (capturing && level <= targetLevel) {
82
+ break; // next sibling or parent — stop
83
+ }
84
+ // child heading — include its content
85
+ }
86
+ if (capturing) {
87
+ captured.push(line);
88
+ }
89
+ }
90
+
91
+ return captured.join('\n');
92
+ };
93
+
94
+ // --- Semantic Depth (E-12 enhancement) ---
95
+
96
+ export interface SemanticDepth extends SectionDepth {
97
+ readonly hasNumericMetrics: boolean; // percentages, counts, thresholds
98
+ readonly hasLegalReferences: boolean; // "Art. X", "GDPR", "ISO"
99
+ readonly hasDateReferences: boolean; // dates, deadlines, "annually"
100
+ readonly hasActionItems: boolean; // "must", "shall", "required to"
101
+ readonly hasMeasurableTargets: boolean; // "99.9%", ">95%", "within 24 hours"
102
+ readonly placeholderCount: number; // [TODO], [Name], [Company] etc.
103
+ readonly qualityScore: number; // 0-100 per section
104
+ readonly feedback: string; // actionable recommendation
105
+ }
106
+
107
+ const NUMERIC_METRICS_REGEX = /\b\d+(\.\d+)?%|\b\d+(\.\d+)?\s*(ms|seconds?|minutes?|hours?|days?|requests?|errors?|users?)|\b[<>≥≤]=?\s*\d+/;
108
+ const LEGAL_REF_REGEX = /\bArt\.\s*\d+|\bGDPR\b|\bISO\s*\d+|\bNIST\b|\bAIUC|EU\s*AI\s*Act|Regulation\s*\(EU\)/i;
109
+ const DATE_REF_REGEX = /\b\d{4}[-/]\d{2}[-/]\d{2}\b|\b(annually|quarterly|monthly|weekly|bi-annual|semi-annual)\b|\b(Q[1-4]\s*20\d{2}|H[12]\s*20\d{2})\b/i;
110
+ const ACTION_REGEX = /\b(must|shall|required to|obliged to|needs? to|will implement|will ensure)\b/i;
111
+ const MEASURABLE_REGEX = /\b\d+(\.\d+)?%|\bwithin\s+\d+\s*(hours?|days?|minutes?)|\b(SLA|KPI|SLO|threshold|target|benchmark)\b/i;
112
+
113
+ /**
114
+ * Detect bracketed placeholder patterns like [Name], [TODO: ...], [Company Name].
115
+ * Excludes markdown links [text](url) via negative lookahead, and checkboxes [x]/[ ] via min length.
116
+ */
117
+ const PLACEHOLDER_BRACKET_REGEX = /\[[^\]]{2,60}\](?!\()/g;
118
+
119
+ export const measureSemanticDepth = (content: string, sectionTitle: string): SemanticDepth => {
120
+ const base = measureSectionDepth(content);
121
+ const trimmed = content.trim();
122
+
123
+ const hasNumericMetrics = NUMERIC_METRICS_REGEX.test(trimmed);
124
+ const hasLegalReferences = LEGAL_REF_REGEX.test(trimmed);
125
+ const hasDateReferences = DATE_REF_REGEX.test(trimmed);
126
+ const hasActionItems = ACTION_REGEX.test(trimmed);
127
+ const hasMeasurableTargets = MEASURABLE_REGEX.test(trimmed);
128
+
129
+ // Placeholder detection: [Name], [TODO: Company], [H/M/L/N], etc.
130
+ const placeholderMatches = trimmed.match(PLACEHOLDER_BRACKET_REGEX) ?? [];
131
+ const placeholderCount = placeholderMatches.filter(m =>
132
+ !/^\[[ xX]\]$/.test(m) && // not checkbox
133
+ !/^\[!\w/.test(m), // not image alt start
134
+ ).length;
135
+
136
+ // Quality score: weighted sum of semantic signals
137
+ let qualityScore = 0;
138
+ if (base.wordCount >= 50) qualityScore += 20;
139
+ else if (base.wordCount >= 20) qualityScore += 10;
140
+ if (base.hasLists) qualityScore += 10;
141
+ if (base.hasTables) qualityScore += 10;
142
+ if (base.hasSpecifics) qualityScore += 10;
143
+ if (hasNumericMetrics) qualityScore += 15;
144
+ if (hasLegalReferences) qualityScore += 10;
145
+ if (hasDateReferences) qualityScore += 5;
146
+ if (hasActionItems) qualityScore += 10;
147
+ if (hasMeasurableTargets) qualityScore += 10;
148
+
149
+ // Placeholder penalty: scaffold documents have many [TODO], [Name], etc.
150
+ if (placeholderCount > 0) {
151
+ qualityScore -= placeholderCount * 10;
152
+ if (placeholderCount >= 3) qualityScore -= 20; // heavy scaffold penalty
153
+ }
154
+ qualityScore = Math.max(0, qualityScore);
155
+
156
+ // Generate feedback
157
+ const suggestions: string[] = [];
158
+ if (placeholderCount > 0) suggestions.push(`Fill ${placeholderCount} placeholder(s) — replace [bracketed] fields with actual data`);
159
+ if (!hasNumericMetrics) suggestions.push('Add numeric metrics (accuracy %, response time, thresholds)');
160
+ if (!hasLegalReferences) suggestions.push('Add legal references (Art. numbers, standards)');
161
+ if (!hasDateReferences) suggestions.push('Add timeline (dates, review frequency)');
162
+ if (!hasMeasurableTargets) suggestions.push('Add measurable targets (KPIs, SLAs)');
163
+ if (base.wordCount < 50) suggestions.push(`Expand content (currently ${base.wordCount} words, minimum 50 recommended)`);
164
+ if (!base.hasLists && !base.hasTables) suggestions.push('Add structured content (lists or tables)');
165
+
166
+ const feedback = suggestions.length > 0
167
+ ? `Section "${sectionTitle}": ${suggestions.join('; ')}`
168
+ : `Section "${sectionTitle}": adequate`;
169
+
170
+ // Override isShallow based on semantic quality
171
+ const isShallow = qualityScore < 30;
172
+
173
+ return {
174
+ ...base,
175
+ isShallow,
176
+ hasNumericMetrics,
177
+ hasLegalReferences,
178
+ hasDateReferences,
179
+ hasActionItems,
180
+ hasMeasurableTargets,
181
+ placeholderCount,
182
+ qualityScore,
183
+ feedback,
184
+ };
185
+ };
186
+
187
+ // --- AI Review Marker Detection ---
188
+
189
+ export const AI_REVIEW_MARKER_REGEX = /<!--\s*complior:reviewed\s+(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s*-->/;
190
+
191
+ export const hasAiReviewMarker = (content: string): boolean =>
192
+ AI_REVIEW_MARKER_REGEX.test(content);
193
+
194
+ export const extractReviewDate = (content: string): string | undefined =>
195
+ content.match(AI_REVIEW_MARKER_REGEX)?.[1];
196
+
197
+ // --- Section Depth ---
198
+
199
+ export const measureSectionDepth = (content: string): SectionDepth => {
200
+ const trimmed = content.trim();
201
+ const words = trimmed.split(/\s+/).filter((w) => w.length > 0);
202
+ const sentences = trimmed.split(/[.!?]+/).filter((s) => s.trim().length > 0);
203
+ const hasLists = LIST_REGEX.test(trimmed);
204
+ const hasTables = TABLE_REGEX.test(trimmed);
205
+ const hasSpecifics = SPECIFICS_REGEX.test(trimmed);
206
+
207
+ const isShallow = words.length < 50 && !hasLists && !hasTables && !hasSpecifics;
208
+
209
+ return {
210
+ wordCount: words.length,
211
+ sentenceCount: sentences.length,
212
+ hasLists,
213
+ hasTables,
214
+ hasSpecifics,
215
+ isShallow,
216
+ };
217
+ };
@@ -0,0 +1,187 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { runLayer3, layer3ToCheckResults } from './layer3-config.js';
3
+ import { createScanFile, createScanCtx } from '../../../test-helpers/factories.js';
4
+
5
+ describe('runLayer3', () => {
6
+ it('detects AI SDK in package.json (npm project)', () => {
7
+ const ctx = createScanCtx([
8
+ createScanFile('package.json', JSON.stringify({
9
+ dependencies: {
10
+ 'openai': '^4.56.0',
11
+ 'express': '^4.18.0',
12
+ },
13
+ })),
14
+ ]);
15
+
16
+ const results = runLayer3(ctx);
17
+ const sdkFindings = results.filter((r) => r.type === 'ai-sdk-detected');
18
+
19
+ expect(sdkFindings.length).toBeGreaterThan(0);
20
+ expect(sdkFindings.some((r) => r.message.includes('OpenAI'))).toBe(true);
21
+ });
22
+
23
+ it('detects prohibited package in requirements.txt (pip project)', () => {
24
+ const ctx = createScanCtx([
25
+ createScanFile('requirements.txt', `anthropic>=0.7.0
26
+ deepface==1.0.2
27
+ flask>=2.0.0
28
+ `),
29
+ ]);
30
+
31
+ const results = runLayer3(ctx);
32
+ const banned = results.filter((r) => r.type === 'banned-package');
33
+ const sdks = results.filter((r) => r.type === 'ai-sdk-detected');
34
+
35
+ expect(banned).toHaveLength(1);
36
+ expect(banned[0].status).toBe('PROHIBITED');
37
+ expect(banned[0].packageName).toBe('deepface');
38
+ expect(banned[0].article).toBe('Art. 5(1)(f)');
39
+ expect(banned[0].message).toContain('Art. 5 REVIEW:');
40
+ expect(banned[0].message).toContain('Verify:');
41
+ expect(banned[0].bannedPackage).toBeDefined();
42
+ expect(banned[0].bannedPackage?.prohibitedWhen).toContain('workplace or educational');
43
+
44
+ expect(sdks.some((r) => r.message.includes('Anthropic'))).toBe(true);
45
+ });
46
+
47
+ it('detects AI SDK in go.mod (Go project)', () => {
48
+ const ctx = createScanCtx([
49
+ createScanFile('go.mod', `module myapp
50
+
51
+ go 1.21
52
+
53
+ require (
54
+ github.com/sashabaranov/go-openai v1.20.0
55
+ github.com/gin-gonic/gin v1.9.1
56
+ )
57
+ `),
58
+ ]);
59
+
60
+ const results = runLayer3(ctx);
61
+ const sdkFindings = results.filter((r) => r.type === 'ai-sdk-detected');
62
+
63
+ expect(sdkFindings).toHaveLength(1);
64
+ expect(sdkFindings[0].message).toContain('OpenAI');
65
+ });
66
+
67
+ it('aggregates mixed dependencies (npm + pip + .env)', () => {
68
+ const ctx = createScanCtx([
69
+ createScanFile('package.json', JSON.stringify({
70
+ dependencies: { 'openai': '^4.0.0' },
71
+ })),
72
+ createScanFile('requirements.txt', 'anthropic>=0.7.0\n'),
73
+ createScanFile('.env', 'OPENAI_API_KEY=sk-xxx\nLOG_LEVEL=info\nSENTRY_DSN=https://xxx\n'),
74
+ ]);
75
+
76
+ const results = runLayer3(ctx);
77
+ const sdkFindings = results.filter((r) => r.type === 'ai-sdk-detected');
78
+ const envFindings = results.filter((r) => r.type === 'env-config');
79
+
80
+ // At least 2 AI SDKs (openai from npm, anthropic from pip)
81
+ expect(sdkFindings.length).toBeGreaterThanOrEqual(2);
82
+
83
+ // .env has API key, LOG_LEVEL, and SENTRY_DSN — all OK
84
+ const okEnvs = envFindings.filter((r) => r.status === 'OK');
85
+ expect(okEnvs.length).toBeGreaterThan(0);
86
+ });
87
+
88
+ it('detects log retention in docker-compose.override.yml', () => {
89
+ const ctx = createScanCtx([
90
+ createScanFile('docker-compose.override.yml', `services:
91
+ app:
92
+ logging:
93
+ driver: json-file
94
+ options:
95
+ max-size: "50m"
96
+ max-file: "60"
97
+ `),
98
+ ]);
99
+
100
+ const results = runLayer3(ctx);
101
+ const retention = results.filter((r) => r.type === 'log-retention');
102
+
103
+ expect(retention).toHaveLength(1);
104
+ expect(retention[0].status).toBe('OK');
105
+ });
106
+
107
+ it('skips bias-testing warning when bias-testing config file present', () => {
108
+ const ctx = createScanCtx([
109
+ createScanFile('package.json', JSON.stringify({
110
+ dependencies: { 'openai': '^4.56.0' },
111
+ })),
112
+ createScanFile('bias-testing.config.json', '{ "enabled": true }'),
113
+ ]);
114
+
115
+ const results = runLayer3(ctx);
116
+ const biasFindings = results.filter((r) => r.type === 'missing-bias-testing');
117
+
118
+ expect(biasFindings).toHaveLength(0);
119
+ });
120
+
121
+ it('returns no AI SDK findings for non-AI project', () => {
122
+ const ctx = createScanCtx([
123
+ createScanFile('package.json', JSON.stringify({
124
+ dependencies: {
125
+ 'express': '^4.18.0',
126
+ 'react': '^18.2.0',
127
+ 'lodash': '^4.17.0',
128
+ },
129
+ })),
130
+ ]);
131
+
132
+ const results = runLayer3(ctx);
133
+ const sdkFindings = results.filter((r) => r.type === 'ai-sdk-detected');
134
+ const bannedFindings = results.filter((r) => r.type === 'banned-package');
135
+
136
+ expect(sdkFindings).toHaveLength(0);
137
+ expect(bannedFindings).toHaveLength(0);
138
+ });
139
+ });
140
+
141
+ describe('layer3ToCheckResults', () => {
142
+ it('converts PROHIBITED finding to critical fail with contextual fix', () => {
143
+ const results = layer3ToCheckResults([{
144
+ type: 'banned-package',
145
+ status: 'PROHIBITED',
146
+ message: 'Art. 5 REVIEW: "deepface" detected — Emotion recognition. Prohibited under Art. 5(1)(f) when: Infers emotions in workplace or educational settings, except for medical or safety purposes. Verify: Is this used to detect emotions of employees or students? (Medical/safety use is exempt)',
147
+ obligationId: 'eu-ai-act-OBL-002',
148
+ article: 'Art. 5(1)(f)',
149
+ packageName: 'deepface',
150
+ ecosystem: 'pip',
151
+ penalty: '€35M or 7% turnover',
152
+ bannedPackage: {
153
+ name: 'deepface',
154
+ ecosystem: 'pip',
155
+ reason: 'Emotion recognition',
156
+ obligationId: 'eu-ai-act-OBL-002',
157
+ article: 'Art. 5(1)(f)',
158
+ penalty: '€35M or 7% turnover',
159
+ prohibitedWhen: 'Infers emotions in workplace or educational settings, except for medical or safety purposes',
160
+ verifyMessage: 'Is this used to detect emotions of employees or students? (Medical/safety use is exempt)',
161
+ },
162
+ }]);
163
+
164
+ expect(results).toHaveLength(1);
165
+ expect(results[0].type).toBe('fail');
166
+ if (results[0].type === 'fail') {
167
+ expect(results[0].severity).toBe('critical');
168
+ expect(results[0].message).toContain('Art. 5 REVIEW:');
169
+ expect(results[0].message).toContain('Verify:');
170
+ expect(results[0].fix).toContain('Verify your use case');
171
+ expect(results[0].fix).toContain('Document your use case to confirm compliance');
172
+ }
173
+ });
174
+
175
+ it('converts OK finding to pass', () => {
176
+ const results = layer3ToCheckResults([{
177
+ type: 'ai-sdk-detected',
178
+ status: 'OK',
179
+ message: 'AI SDK detected: OpenAI (openai@4.56.0) in npm',
180
+ packageName: 'openai',
181
+ ecosystem: 'npm',
182
+ }]);
183
+
184
+ expect(results).toHaveLength(1);
185
+ expect(results[0].type).toBe('pass');
186
+ });
187
+ });