@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,335 @@
1
+ import { randomUUID, createHash } from 'node:crypto';
2
+ import type { ScanResult, Finding, AgentSummary, Role } from '../types/common.types.js';
3
+ import type { ScanContext } from '../ports/scanner.port.js';
4
+ import type { EventBusPort } from '../ports/events.port.js';
5
+ import type { Scanner } from '../domain/scanner/create-scanner.js';
6
+ import { detectDrift } from '../domain/scanner/drift.js';
7
+ import { generateSbom, type CycloneDxBom } from '../domain/scanner/sbom.js';
8
+ import { parseDepsFromContext } from '../domain/shared/parse-dependencies.js';
9
+ import { discoverAgents } from '../domain/passport/discovery/agent-discovery.js';
10
+ import { attributeFindings, expandPerAgentFindings, type AgentInfo } from '../domain/scanner/finding-attribution.js';
11
+ import { buildImportGraph } from '../domain/scanner/import-graph.js';
12
+ import type { EvidenceStore } from '../domain/scanner/evidence-store.js';
13
+ import { createEvidence } from '../domain/scanner/evidence.js';
14
+ import type { AuditStore } from '../domain/audit/audit-trail.js';
15
+ import { computeComplianceDiff, formatDiffMarkdown, type ComplianceDiff } from '../domain/scanner/compliance-diff.js';
16
+ import { loadCustomBannedPackages } from '../domain/scanner/rules/banned-packages.js';
17
+ import { filterFindingsByRole } from '../domain/scanner/role-filter.js';
18
+ import type { ScanCache } from '../domain/scanner/scan-cache.js';
19
+
20
+ export interface ScanServiceDeps {
21
+ readonly scanner: Scanner;
22
+ readonly collectFiles: (projectPath: string) => Promise<ScanContext>;
23
+ readonly events: EventBusPort;
24
+ readonly getLastScanResult: () => ScanResult | null;
25
+ readonly setLastScanResult: (result: ScanResult) => void;
26
+ readonly evidenceStore?: EvidenceStore;
27
+ readonly auditStore?: AuditStore;
28
+ /** E-11: Per-file scan cache (SHA-256 + mtime). Persisted to .complior/cache/. */
29
+ readonly scanCache?: ScanCache;
30
+ /** Optional passport service for per-agent finding enrichment + post-scan update. */
31
+ readonly passportService?: {
32
+ readonly listPassports: (path?: string) => Promise<readonly { name: string; source_files?: readonly string[] }[]>;
33
+ readonly updatePassportsAfterScan?: (result: ScanResult, projectPath?: string) => Promise<void>;
34
+ readonly initPassport?: (projectPath?: string) => Promise<{ manifests: readonly unknown[]; savedPaths: readonly string[]; skipped: readonly string[] }>;
35
+ };
36
+ /** Project role from onboarding profile. Injected via composition-root. */
37
+ readonly getProjectRole?: (projectPath: string) => Promise<Role>;
38
+ }
39
+
40
+ /** E-11: Compute a fast project-level hash from all file contents. */
41
+ const computeProjectHash = (ctx: ScanContext): string => {
42
+ const hash = createHash('sha256');
43
+ // Sort for determinism, then hash path + content
44
+ const sorted = [...ctx.files].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
45
+ for (const f of sorted) {
46
+ hash.update(f.relativePath);
47
+ hash.update(f.content);
48
+ }
49
+ return hash.digest('hex');
50
+ };
51
+
52
+ /** US-S05-34: Result of a compliance diff scan. */
53
+ export interface ScanDiffResult extends ComplianceDiff {
54
+ readonly markdown?: string;
55
+ }
56
+
57
+
58
+ /** Recalculate score after role filtering (some fails → skip). */
59
+ const recalcScore = (findings: readonly Finding[], original: ScanResult['score']): ScanResult['score'] => {
60
+ const passed = findings.filter(f => f.type === 'pass').length;
61
+ const failed = findings.filter(f => f.type === 'fail').length;
62
+ const skipped = findings.filter(f => f.type === 'skip').length;
63
+ const applicable = passed + failed;
64
+ const totalScore = applicable === 0 ? 100 : Math.round((passed / applicable) * 100);
65
+ return {
66
+ ...original,
67
+ totalScore,
68
+ zone: totalScore >= 80 ? 'green' : totalScore >= 50 ? 'yellow' : 'red',
69
+ passedChecks: passed,
70
+ failedChecks: failed,
71
+ skippedChecks: skipped,
72
+ totalChecks: findings.length,
73
+ };
74
+ };
75
+
76
+ /** Enrich scan findings with agentId from passport source_files mapping. */
77
+ const enrichWithAgentIds = async (
78
+ result: ScanResult,
79
+ projectPath: string,
80
+ passportService?: ScanServiceDeps['passportService'],
81
+ ctx?: ScanContext,
82
+ ): Promise<ScanResult> => {
83
+ if (!passportService && !ctx) return result;
84
+
85
+ let passports: { name: string; source_files?: readonly string[] }[] = [];
86
+
87
+ if (passportService) {
88
+ try { passports = [...await passportService.listPassports(projectPath)]; }
89
+ catch { /* continue to fallback */ }
90
+ }
91
+
92
+ // Fallback: no persisted passports → auto-discover from code patterns
93
+ if (passports.length === 0 && ctx) {
94
+ const discovered = discoverAgents(ctx, parseDepsFromContext(ctx));
95
+ if (discovered.length === 0) return result;
96
+ passports = discovered.map(a => ({ name: a.name, source_files: a.sourceFiles }));
97
+ }
98
+
99
+ if (passports.length === 0) return result;
100
+
101
+ // Map passports to AgentInfo for attribution
102
+ const agents: AgentInfo[] = passports.map(p => ({
103
+ name: p.name,
104
+ sourceFiles: p.source_files ?? [],
105
+ }));
106
+
107
+ // Build import graph from scan context for graph-based attribution
108
+ const importGraph = ctx ? buildImportGraph(ctx.files) : null;
109
+
110
+ const enrichedFindings: readonly Finding[] = importGraph
111
+ ? attributeFindings(result.findings, agents, importGraph)
112
+ : result.findings.map(f => agents.length === 1 ? { ...f, agentId: agents[0].name } : f);
113
+
114
+ // Per-agent document requirements: each agent gets its own doc-presence findings
115
+ const expandedFindings = expandPerAgentFindings(enrichedFindings, agents);
116
+
117
+ // Per-agent summaries — include ALL passports (even those with 0 findings)
118
+ const byAgent = new Map<string, Finding[]>();
119
+ for (const f of expandedFindings) {
120
+ if (!f.agentId) continue;
121
+ if (!byAgent.has(f.agentId)) byAgent.set(f.agentId, []);
122
+ byAgent.get(f.agentId)!.push(f);
123
+ }
124
+
125
+ const agentSummaries: AgentSummary[] = passports.map(p => {
126
+ const findings = byAgent.get(p.name) ?? [];
127
+ return {
128
+ agentId: p.name,
129
+ agentName: p.name,
130
+ findingCount: findings.filter(f => f.type === 'fail').length,
131
+ criticalCount: findings.filter(f => f.severity === 'critical').length,
132
+ highCount: findings.filter(f => f.severity === 'high').length,
133
+ fileCount: new Set(findings.map(f => f.file).filter(Boolean)).size,
134
+ };
135
+ });
136
+
137
+ return {
138
+ ...result,
139
+ findings: expandedFindings,
140
+ agentSummaries,
141
+ };
142
+ };
143
+
144
+ export const createScanService = (deps: ScanServiceDeps) => {
145
+ const { scanner, collectFiles, events, setLastScanResult } = deps;
146
+
147
+ // E-11: In-memory project-level scan cache (hash of all files → ScanResult)
148
+ let cachedProjectHash: string | null = null;
149
+ let cachedResult: ScanResult | null = null;
150
+
151
+ /** Synchronous post-scan: auto-discover agents + update passports before returning result. */
152
+ const syncPassportUpdate = async (result: ScanResult, projectPath: string): Promise<void> => {
153
+ if (!deps.passportService?.updatePassportsAfterScan) return;
154
+ try {
155
+ // 1. Auto-discover new agents (idempotent — skips existing)
156
+ if (deps.passportService.initPassport) {
157
+ await deps.passportService.initPassport(projectPath).catch(() => ({ manifests: [], savedPaths: [], skipped: [] }));
158
+ }
159
+ // 2. Update scores on ALL passports
160
+ await deps.passportService.updatePassportsAfterScan(result, projectPath);
161
+ } catch { /* non-fatal */ }
162
+ };
163
+
164
+ /** Apply role filtering to a scan result (pure, no caching side effects). */
165
+ const applyRoleFilter = async (scanResult: ScanResult, projectPath: string): Promise<ScanResult> => {
166
+ const projectRole = deps.getProjectRole
167
+ ? await deps.getProjectRole(projectPath)
168
+ : 'both' as Role;
169
+ const filtered = filterFindingsByRole(scanResult.findings, projectRole);
170
+ if (filtered === scanResult.findings) return scanResult;
171
+ return { ...scanResult, findings: filtered, score: recalcScore(filtered, scanResult.score) };
172
+ };
173
+
174
+ const scan = async (projectPath: string): Promise<ScanResult> => {
175
+ events.emit('scan.started', { projectPath });
176
+
177
+ const previousResult = deps.getLastScanResult();
178
+ await loadCustomBannedPackages(projectPath);
179
+ const ctx = await collectFiles(projectPath);
180
+
181
+ // E-11: Check if project content unchanged since last scan
182
+ const projectHash = computeProjectHash(ctx);
183
+ if (cachedProjectHash === projectHash && cachedResult !== null) {
184
+ // Content identical — apply role filter (role may have changed) and return
185
+ const result = await applyRoleFilter({
186
+ ...cachedResult,
187
+ scannedAt: new Date().toISOString(),
188
+ }, projectPath);
189
+ events.emit('scan.completed', { result });
190
+ return result;
191
+ }
192
+
193
+ const rawResult = scanner.scan(ctx);
194
+ const enriched = await enrichWithAgentIds(rawResult, projectPath, deps.passportService, ctx);
195
+
196
+ // E-11: Update in-memory cache with pre-filter result (role-independent)
197
+ cachedProjectHash = projectHash;
198
+ cachedResult = enriched;
199
+
200
+ // Apply role-based filtering after caching
201
+ const result = await applyRoleFilter(enriched, projectPath);
202
+
203
+ // E-11: Persist file-level cache to disk (survives daemon restarts)
204
+ if (deps.scanCache) {
205
+ for (const file of ctx.files) {
206
+ deps.scanCache.set(file.relativePath, file.content, 0, [], 'L4');
207
+ }
208
+ deps.scanCache.save();
209
+ }
210
+
211
+ setLastScanResult(result);
212
+ events.emit('scan.completed', { result });
213
+
214
+ // US-S05-14: Record scan completion in audit trail
215
+ if (deps.auditStore) {
216
+ await deps.auditStore.append('scan.completed', {
217
+ score: result.score.totalScore,
218
+ zone: result.score.zone,
219
+ findings: result.findings.length,
220
+ });
221
+ }
222
+
223
+ // C.R20: Persist scan summary evidence to chain (not individual findings)
224
+ if (deps.evidenceStore) {
225
+ const scanId = randomUUID();
226
+ const uniqueCheckIds = [...new Set(result.findings.map(f => f.checkId))];
227
+ const summaryEvidence = createEvidence(
228
+ `scan-${scanId}`,
229
+ 'scan-summary',
230
+ 'pattern-match',
231
+ {
232
+ snippet: JSON.stringify({
233
+ score: result.score.totalScore,
234
+ zone: result.score.zone,
235
+ findings: result.findings.length,
236
+ checks: uniqueCheckIds.length,
237
+ }),
238
+ },
239
+ );
240
+ await deps.evidenceStore.append([summaryEvidence], scanId);
241
+ }
242
+
243
+ // Drift detection: compare with previous scan
244
+ if (previousResult !== null) {
245
+ const drift = detectDrift(result, previousResult);
246
+ if (drift.hasDrift) {
247
+ events.emit('scan.drift', { drift });
248
+ }
249
+ }
250
+
251
+ // Synchronous passport update (ensures passport is current before HTTP response)
252
+ await syncPassportUpdate(result, projectPath);
253
+
254
+ return result;
255
+ };
256
+
257
+ const scanDeep = async (projectPath: string): Promise<ScanResult> => {
258
+ if (scanner.scanDeep === undefined) {
259
+ return scan(projectPath);
260
+ }
261
+
262
+ events.emit('scan.started', { projectPath });
263
+
264
+ const ctx = await collectFiles(projectPath);
265
+
266
+ // Build file contents map for L5 analysis
267
+ const fileContents = new Map<string, string>();
268
+ for (const file of ctx.files) {
269
+ fileContents.set(file.relativePath, file.content);
270
+ }
271
+
272
+ const rawResult = await scanner.scanDeep(ctx, fileContents);
273
+ const enriched = await enrichWithAgentIds(rawResult, projectPath, deps.passportService, ctx);
274
+ const result = await applyRoleFilter(enriched, projectPath);
275
+
276
+ setLastScanResult(result);
277
+ events.emit('scan.completed', { result });
278
+ await syncPassportUpdate(result, projectPath);
279
+
280
+ return result;
281
+ };
282
+
283
+ const getSbom = async (projectPath: string): Promise<CycloneDxBom> => {
284
+ const ctx = await collectFiles(projectPath);
285
+ return generateSbom(parseDepsFromContext(ctx));
286
+ };
287
+
288
+ /** US-S05-34: Compliance Diff — run scan and compare against baseline. */
289
+ const scanDiff = async (
290
+ projectPath: string,
291
+ changedFiles?: readonly string[],
292
+ options?: { readonly markdown?: boolean },
293
+ ): Promise<ScanDiffResult> => {
294
+ // 1. Get baseline (previous scan result, if any)
295
+ const baseline = deps.getLastScanResult();
296
+
297
+ // 2. Run fresh scan
298
+ const current = await scan(projectPath);
299
+
300
+ // 3. Compute diff using pure domain function
301
+ const diff = computeComplianceDiff(baseline, current, changedFiles);
302
+
303
+ // 4. Generate markdown if requested
304
+ const markdown = options?.markdown ? formatDiffMarkdown(diff) : undefined;
305
+
306
+ return Object.freeze({ ...diff, markdown });
307
+ };
308
+
309
+ const scanTier2 = async (projectPath: string): Promise<ScanResult> => {
310
+ if (scanner.scanTier2 === undefined) {
311
+ return scan(projectPath);
312
+ }
313
+
314
+ events.emit('scan.started', { projectPath });
315
+
316
+ await loadCustomBannedPackages(projectPath);
317
+ const ctx = await collectFiles(projectPath);
318
+ const rawResult = await scanner.scanTier2(ctx);
319
+ const enriched = await enrichWithAgentIds(rawResult, projectPath, deps.passportService, ctx);
320
+ const result = await applyRoleFilter(enriched, projectPath);
321
+
322
+ setLastScanResult(result);
323
+ events.emit('scan.completed', { result });
324
+ await syncPassportUpdate(result, projectPath);
325
+
326
+ return result;
327
+ };
328
+
329
+ /** Alias for scanDeep — L5 LLM analysis. */
330
+ const scanLlm = scanDeep;
331
+
332
+ return Object.freeze({ scan, scanDeep, scanTier2, scanLlm, getSbom, scanDiff });
333
+ };
334
+
335
+ export type ScanService = ReturnType<typeof createScanService>;
@@ -0,0 +1,108 @@
1
+ import { mkdir, writeFile, readFile, readdir } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { z } from 'zod';
4
+ import type { ScanResult } from '../types/common.types.js';
5
+ import type { EventBusPort } from '../ports/events.port.js';
6
+ import { createSharePayload } from '../domain/reporter/share.js';
7
+ import type { SharePayload } from '../domain/reporter/share.js';
8
+
9
+ const ShareFindingSchema = z.object({
10
+ obligationId: z.string(),
11
+ article: z.string(),
12
+ severity: z.string(),
13
+ title: z.string(),
14
+ recommendation: z.string(),
15
+ });
16
+
17
+ const SharePayloadSchema = z.object({
18
+ id: z.string(),
19
+ createdAt: z.string(),
20
+ expiresAt: z.string(),
21
+ score: z.number(),
22
+ jurisdiction: z.string(),
23
+ findingsCount: z.object({
24
+ critical: z.number(),
25
+ high: z.number(),
26
+ medium: z.number(),
27
+ low: z.number(),
28
+ }),
29
+ topFindings: z.array(ShareFindingSchema),
30
+ scanType: z.enum(['code', 'external']),
31
+ compliorVersion: z.string(),
32
+ });
33
+
34
+ export interface ShareServiceDeps {
35
+ readonly events: EventBusPort;
36
+ readonly getProjectPath: () => string;
37
+ readonly getLastScanResult: () => ScanResult | null;
38
+ readonly getVersion: () => string;
39
+ }
40
+
41
+ export const createShareService = (deps: ShareServiceDeps) => {
42
+ const { events, getProjectPath, getLastScanResult, getVersion } = deps;
43
+
44
+ const getShareDir = () => resolve(getProjectPath(), '.complior', 'shares');
45
+
46
+ const createShare = async (options?: {
47
+ readonly jurisdiction?: string;
48
+ readonly scanType?: 'code' | 'external';
49
+ readonly expirationDays?: number;
50
+ }): Promise<SharePayload> => {
51
+ const scanResult = getLastScanResult();
52
+ if (!scanResult) {
53
+ throw new Error('No scan result available. Run a scan first.');
54
+ }
55
+
56
+ const payload = createSharePayload(scanResult, getVersion(), options);
57
+
58
+ const dir = getShareDir();
59
+ await mkdir(dir, { recursive: true });
60
+ await writeFile(resolve(dir, `${payload.id}.json`), JSON.stringify(payload, null, 2));
61
+
62
+ events.emit('scan.completed', { result: scanResult });
63
+ return payload;
64
+ };
65
+
66
+ const getShare = async (id: string): Promise<SharePayload | null> => {
67
+ try {
68
+ const raw = await readFile(resolve(getShareDir(), `${id}.json`), 'utf-8');
69
+ const payload = SharePayloadSchema.parse(JSON.parse(raw));
70
+ if (new Date(payload.expiresAt) < new Date()) {
71
+ return null;
72
+ }
73
+ return payload;
74
+ } catch {
75
+ return null;
76
+ }
77
+ };
78
+
79
+ const listShares = async (): Promise<readonly SharePayload[]> => {
80
+ try {
81
+ const dir = getShareDir();
82
+ const files = await readdir(dir);
83
+ const now = new Date();
84
+ const results: SharePayload[] = [];
85
+
86
+ for (const file of files) {
87
+ if (!file.endsWith('.json')) continue;
88
+ try {
89
+ const raw = await readFile(resolve(dir, file), 'utf-8');
90
+ const payload = SharePayloadSchema.parse(JSON.parse(raw));
91
+ if (new Date(payload.expiresAt) >= now) {
92
+ results.push(payload);
93
+ }
94
+ } catch {
95
+ // skip corrupt files
96
+ }
97
+ }
98
+
99
+ return results.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
100
+ } catch {
101
+ return [];
102
+ }
103
+ };
104
+
105
+ return Object.freeze({ createShare, getShare, listShares });
106
+ };
107
+
108
+ export type ShareService = ReturnType<typeof createShareService>;
@@ -0,0 +1,23 @@
1
+ import { resolve } from 'node:path';
2
+ import { mkdir, copyFile } from 'node:fs/promises';
3
+
4
+ /**
5
+ * Create a timestamped backup of a project file.
6
+ * Returns the backup path. If the source file doesn't exist (e.g. a create action),
7
+ * the backup is silently skipped but the path is still returned for undo tracking.
8
+ */
9
+ export const backupFile = async (
10
+ filePath: string,
11
+ projectPath: string,
12
+ ): Promise<string> => {
13
+ const backupDir = resolve(projectPath, '.complior', 'backups');
14
+ await mkdir(backupDir, { recursive: true });
15
+ const timestamp = Date.now();
16
+ const backupPath = resolve(backupDir, `${timestamp}-${filePath.replace(/[\\/]/g, '_')}`);
17
+ try {
18
+ await copyFile(resolve(projectPath, filePath), backupPath);
19
+ } catch {
20
+ // File doesn't exist yet (create action) — no backup needed
21
+ }
22
+ return backupPath;
23
+ };
@@ -0,0 +1,38 @@
1
+ import type { EngineStatus, ScanResult } from '../types/common.types.js';
2
+
3
+ export interface StatusServiceDeps {
4
+ readonly getVersion: () => string;
5
+ readonly getMode: () => string;
6
+ readonly getStartedAt: () => number;
7
+ readonly getLastScanResult: () => ScanResult | null;
8
+ }
9
+
10
+ export const createStatusService = (deps: StatusServiceDeps) => {
11
+ const { getVersion, getMode, getStartedAt, getLastScanResult } = deps;
12
+
13
+ const getStatus = (): EngineStatus => {
14
+ const lastScan = getLastScanResult();
15
+
16
+ return {
17
+ ready: true,
18
+ version: getVersion(),
19
+ mode: getMode(),
20
+ uptime: Date.now() - getStartedAt(),
21
+ lastScan: lastScan
22
+ ? {
23
+ score: lastScan.score.totalScore,
24
+ zone: lastScan.score.zone,
25
+ findingsCount: lastScan.findings.length,
26
+ criticalCount: lastScan.findings.filter(
27
+ (f) => f.severity === 'critical',
28
+ ).length,
29
+ timestamp: lastScan.scannedAt,
30
+ }
31
+ : undefined,
32
+ };
33
+ };
34
+
35
+ return Object.freeze({ getStatus });
36
+ };
37
+
38
+ export type StatusService = ReturnType<typeof createStatusService>;
@@ -0,0 +1,190 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { resolve } from 'node:path';
3
+ import { createUndoService } from './undo-service.js';
4
+ import type { FixPlan, FixResult } from '../domain/fixer/types.js';
5
+ import { createMockScanResult, createMockFinding } from '../test-helpers/factories.js';
6
+
7
+ const PROJECT_PATH = '/tmp/test-undo';
8
+ const HISTORY_PATH = resolve(PROJECT_PATH, '.complior', 'history.json');
9
+
10
+ // In-memory fs simulation
11
+ let files: Map<string, string>;
12
+
13
+ vi.mock('node:fs/promises', () => ({
14
+ readFile: vi.fn(async (path: string) => {
15
+ const content = files.get(path);
16
+ if (content === undefined) throw new Error(`ENOENT: ${path}`);
17
+ return content;
18
+ }),
19
+ writeFile: vi.fn(async (path: string, content: string) => {
20
+ files.set(path, content);
21
+ }),
22
+ mkdir: vi.fn(async () => {}),
23
+ copyFile: vi.fn(async (src: string, dest: string) => {
24
+ const content = files.get(src);
25
+ if (content === undefined) throw new Error(`ENOENT: ${src}`);
26
+ files.set(dest, content);
27
+ }),
28
+ unlink: vi.fn(async (path: string) => {
29
+ files.delete(path);
30
+ }),
31
+ }));
32
+
33
+ const createMockPlan = (overrides?: Partial<FixPlan>): FixPlan => ({
34
+ obligationId: 'OBL-015',
35
+ checkId: 'ai-disclosure',
36
+ article: 'Art. 52',
37
+ fixType: 'template_generation',
38
+ framework: 'Next.js',
39
+ actions: [{ type: 'create', path: 'ai-disclosure.md', content: '# AI', description: 'Create' }],
40
+ diff: '+# AI',
41
+ scoreImpact: 5,
42
+ commitMessage: 'feat: add AI disclosure',
43
+ description: 'Add disclosure',
44
+ ...overrides,
45
+ });
46
+
47
+ const createMockResult = (overrides?: Partial<FixResult>): FixResult => ({
48
+ plan: createMockPlan(),
49
+ applied: true,
50
+ scoreBefore: 60,
51
+ scoreAfter: 70,
52
+ backedUpFiles: ['/tmp/test-undo/.complior/backups/123-ai-disclosure.md'],
53
+ ...overrides,
54
+ });
55
+
56
+ describe('undo-service', () => {
57
+ let currentScore: number;
58
+
59
+ beforeEach(() => {
60
+ files = new Map();
61
+ currentScore = 70;
62
+ });
63
+
64
+ const createTestService = () => {
65
+ const events = { on: vi.fn(), off: vi.fn(), emit: vi.fn() };
66
+ const scanService = {
67
+ scan: vi.fn().mockImplementation(async () =>
68
+ createMockScanResult({
69
+ score: {
70
+ totalScore: currentScore,
71
+ zone: currentScore >= 80 ? 'green' : 'yellow',
72
+ categoryScores: [],
73
+ criticalCapApplied: false,
74
+ totalChecks: 10,
75
+ passedChecks: 7,
76
+ failedChecks: 2,
77
+ skippedChecks: 1,
78
+ },
79
+ findings: [
80
+ createMockFinding({ checkId: 'ai-disclosure', type: currentScore <= 60 ? 'fail' : 'pass' }),
81
+ ],
82
+ }),
83
+ ),
84
+ };
85
+
86
+ const service = createUndoService({
87
+ events,
88
+ scanService,
89
+ getProjectPath: () => PROJECT_PATH,
90
+ getHistoryPath: () => HISTORY_PATH,
91
+ getLastScanResult: () => createMockScanResult({
92
+ score: {
93
+ totalScore: currentScore,
94
+ zone: currentScore >= 80 ? 'green' : 'yellow',
95
+ categoryScores: [],
96
+ criticalCapApplied: false,
97
+ totalChecks: 10,
98
+ passedChecks: 7,
99
+ failedChecks: 2,
100
+ skippedChecks: 1,
101
+ },
102
+ }),
103
+ });
104
+
105
+ return { service, events, scanService };
106
+ };
107
+
108
+ it('records fix then undoes it, restoring file', async () => {
109
+ const { service, events, scanService } = createTestService();
110
+ const plan = createMockPlan();
111
+ const result = createMockResult();
112
+
113
+ // Set up backup file
114
+ files.set(result.backedUpFiles[0]!, 'original content');
115
+
116
+ await service.recordFix(result, plan);
117
+
118
+ // After fix, current score is 70. Undo will re-scan and get 60.
119
+ scanService.scan.mockImplementationOnce(async () => {
120
+ currentScore = 60;
121
+ return createMockScanResult({
122
+ score: {
123
+ totalScore: 60,
124
+ zone: 'yellow',
125
+ categoryScores: [],
126
+ criticalCapApplied: false,
127
+ totalChecks: 10,
128
+ passedChecks: 6,
129
+ failedChecks: 3,
130
+ skippedChecks: 1,
131
+ },
132
+ findings: [createMockFinding({ checkId: 'ai-disclosure', type: 'fail' })],
133
+ });
134
+ });
135
+ const validation = await service.undoLast();
136
+
137
+ expect(validation.checkId).toBe('ai-disclosure');
138
+ expect(validation.scoreDelta).toBe(-10); // 60 - 70
139
+ expect(events.emit).toHaveBeenCalledWith('fix.undone', {
140
+ checkId: 'ai-disclosure',
141
+ restoredFiles: ['ai-disclosure.md'],
142
+ });
143
+ });
144
+
145
+ it('undoes by specific ID when multiple fixes exist', async () => {
146
+ const { service } = createTestService();
147
+
148
+ // Record 3 fixes
149
+ await service.recordFix(
150
+ createMockResult({ scoreBefore: 50, scoreAfter: 55 }),
151
+ createMockPlan({ checkId: 'check-1', obligationId: 'OBL-001' }),
152
+ );
153
+ await service.recordFix(
154
+ createMockResult({ scoreBefore: 55, scoreAfter: 60, backedUpFiles: ['/tmp/test-undo/.complior/backups/456-file2.md'] }),
155
+ createMockPlan({ checkId: 'check-2', obligationId: 'OBL-002', actions: [{ type: 'edit', path: 'file2.md', oldContent: 'old', newContent: 'new', description: 'Edit' }] }),
156
+ );
157
+ await service.recordFix(
158
+ createMockResult({ scoreBefore: 60, scoreAfter: 65 }),
159
+ createMockPlan({ checkId: 'check-3', obligationId: 'OBL-003' }),
160
+ );
161
+
162
+ // Set up backup for fix #2
163
+ files.set('/tmp/test-undo/.complior/backups/456-file2.md', 'backup of file2');
164
+
165
+ currentScore = 58;
166
+ const validation = await service.undoById(2);
167
+
168
+ expect(validation.checkId).toBe('check-2');
169
+ // Should have restored file2.md from backup
170
+ expect(files.get(resolve(PROJECT_PATH, 'file2.md'))).toBe('backup of file2');
171
+ });
172
+
173
+ it('persists history with correct statuses after apply and undo', async () => {
174
+ const { service } = createTestService();
175
+
176
+ await service.recordFix(createMockResult(), createMockPlan());
177
+
178
+ let history = await service.getHistory();
179
+ expect(history.fixes).toHaveLength(1);
180
+ expect(history.fixes[0]!.status).toBe('applied');
181
+
182
+ currentScore = 60;
183
+ // Need backup file for create undo (delete)
184
+ await service.undoLast();
185
+
186
+ history = await service.getHistory();
187
+ expect(history.fixes).toHaveLength(1);
188
+ expect(history.fixes[0]!.status).toBe('undone');
189
+ });
190
+ });