@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,648 @@
1
+ import { resolve, dirname } from 'node:path';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { backupFile } from './shared/backup.js';
5
+ import type { Finding, ScanResult } from '../types/common.types.js';
6
+ import type { EventBusPort } from '../ports/events.port.js';
7
+ import type { Fixer } from '../domain/fixer/create-fixer.js';
8
+ import type { FixPlan, FixResult, FixValidation } from '../domain/fixer/types.js';
9
+ import type { ScanService } from './scan-service.js';
10
+ import type { UndoService } from './undo-service.js';
11
+ import type { EvidenceStore } from '../domain/scanner/evidence-store.js';
12
+ import { createEvidence } from '../domain/scanner/evidence.js';
13
+ import type { AgentPassport } from '../types/passport.types.js';
14
+ import { generateDocument, TEMPLATE_FILE_MAP, type DocType, type DocResult } from '../domain/documents/document-generator.js';
15
+ import { enrichDocumentWithAI, detectWeakSections } from '../domain/documents/ai-enricher.js';
16
+ import type { LlmPort } from '../ports/llm.port.js';
17
+
18
+ /** Resolve [PASSPORT:field] tokens from agent passport data. */
19
+ function resolvePassportPlaceholders(content: string, passport: AgentPassport): string {
20
+ const fields: Record<string, string | undefined> = {
21
+ 'display_name': passport.display_name ?? passport.name,
22
+ 'name': passport.name,
23
+ 'description': passport.description,
24
+ 'owner.team': passport.owner?.team,
25
+ 'owner.contact': passport.owner?.contact,
26
+ 'owner.responsible_person': passport.owner?.responsible_person,
27
+ 'model.provider': passport.model?.provider,
28
+ 'model.model_id': passport.model?.model_id,
29
+ 'risk_class': passport.compliance?.eu_ai_act?.risk_class,
30
+ 'autonomy_level': passport.autonomy_level,
31
+ 'disclosure_text': passport.disclosure?.disclosure_text,
32
+ };
33
+
34
+ return content.replace(/\[PASSPORT:([^\]]+)\]/g, (match, key: string) => {
35
+ return fields[key] ?? match;
36
+ });
37
+ }
38
+
39
+ export interface FixServiceDeps {
40
+ readonly fixer: Fixer;
41
+ readonly scanService: ScanService;
42
+ readonly events: EventBusPort;
43
+ readonly getProjectPath: () => string;
44
+ readonly getLastScanResult: () => ScanResult | null;
45
+ readonly loadTemplate: (templateFile: string) => Promise<string>;
46
+ readonly undoService?: UndoService;
47
+ readonly evidenceStore?: EvidenceStore;
48
+ readonly passportService?: { listPassports: (path?: string) => Promise<readonly AgentPassport[]> };
49
+ readonly llm?: LlmPort;
50
+ }
51
+
52
+ export const createFixService = (deps: FixServiceDeps) => {
53
+ const { fixer, scanService, events, getProjectPath: _getProjectPath, getLastScanResult, loadTemplate, undoService } = deps;
54
+
55
+ const getProjectPath = () => _getProjectPath();
56
+
57
+ const createBackup = (filePath: string): Promise<string> =>
58
+ backupFile(filePath, getProjectPath());
59
+
60
+ const applyAction = async (action: FixPlan['actions'][number], projectPath: string, useAi = false): Promise<readonly string[] | undefined> => {
61
+ const fullPath = resolve(projectPath, action.path);
62
+ await mkdir(dirname(fullPath), { recursive: true });
63
+
64
+ if (action.type === 'create') {
65
+ let content = action.content ?? '';
66
+ let manualFields: readonly string[] | undefined;
67
+ // Resolve template placeholder — pre-fill with passport data when available
68
+ const templateMatch = content.match(/^\[TEMPLATE:(.+)]$/);
69
+ if (templateMatch) {
70
+ const templateFile = templateMatch[1]!;
71
+ const template = await loadTemplate(templateFile);
72
+
73
+ // Reverse-lookup: templateFile → DocType
74
+ const docTypeEntry = (Object.entries(TEMPLATE_FILE_MAP) as [DocType, string][])
75
+ .find(([, file]) => file === templateFile);
76
+
77
+ if (docTypeEntry && deps.passportService) {
78
+ try {
79
+ const passports = await deps.passportService.listPassports(projectPath);
80
+ const passport = passports[0];
81
+ if (passport) {
82
+ // Check if file already exists on disk — enhance existing instead of overwriting
83
+ let existingContent: string | undefined;
84
+ try { existingContent = await readFile(fullPath, 'utf-8'); } catch { /* file doesn't exist */ }
85
+
86
+ if (existingContent && existingContent.trim().length > 0) {
87
+ if (useAi && deps.llm) {
88
+ // Existing doc + --ai: enhance with L2-informed LLM feedback (preserve user edits)
89
+ const weakSections = detectWeakSections(existingContent);
90
+ const baseResult: DocResult = {
91
+ markdown: existingContent,
92
+ docType: docTypeEntry[0],
93
+ prefilledFields: [],
94
+ manualFields: [...weakSections],
95
+ };
96
+ manualFields = weakSections;
97
+
98
+ if (weakSections.length > 0) {
99
+ try {
100
+ const selection = deps.llm.routeModel('document-generation');
101
+ const model = await deps.llm.getModel(selection.provider, selection.modelId);
102
+ const enriched = await enrichDocumentWithAI({ baseResult, manifest: passport, model });
103
+ // Second pass: re-check for remaining weak sections and enrich again
104
+ let finalMarkdown = enriched.markdown;
105
+ const remainingWeak = detectWeakSections(finalMarkdown);
106
+ if (remainingWeak.length > 0) {
107
+ try {
108
+ const secondPass = await enrichDocumentWithAI({
109
+ baseResult: { markdown: finalMarkdown, docType: docTypeEntry[0], prefilledFields: [], manualFields: [...remainingWeak] },
110
+ manifest: passport,
111
+ model,
112
+ });
113
+ finalMarkdown = secondPass.markdown;
114
+ } catch {
115
+ // Keep first-pass result on second-pass failure
116
+ }
117
+ }
118
+ // Remove scaffold marker after successful LLM enrichment — scanner upgrades to 'draft'
119
+ content = finalMarkdown.replace(/^<!-- COMPLIOR:SCAFFOLD -->\n/, '');
120
+ } catch (err) {
121
+ events.emit('log', { level: 'warn', message: `LLM enrichment failed: ${err instanceof Error ? err.message : err}` });
122
+ content = existingContent; // LLM failed — keep existing
123
+ }
124
+ } else {
125
+ content = existingContent; // All sections adequate — no changes needed
126
+ }
127
+ } else {
128
+ // Existing doc without --ai: keep existing content, never overwrite with scaffold
129
+ content = existingContent;
130
+ manualFields = detectWeakSections(existingContent);
131
+ }
132
+ } else {
133
+ // No existing file — generate fresh scaffold
134
+ const baseResult = generateDocument({ manifest: passport, template, docType: docTypeEntry[0] });
135
+ manualFields = baseResult.manualFields;
136
+
137
+ // LLM enrichment for fresh scaffold
138
+ if (useAi && deps.llm && baseResult.manualFields.length > 0) {
139
+ try {
140
+ const selection = deps.llm.routeModel('document-generation');
141
+ const model = await deps.llm.getModel(selection.provider, selection.modelId);
142
+ const enriched = await enrichDocumentWithAI({ baseResult, manifest: passport, model });
143
+ // Second pass: re-check for remaining weak sections and enrich again
144
+ let finalMarkdown = enriched.markdown;
145
+ const remainingWeak = detectWeakSections(finalMarkdown);
146
+ if (remainingWeak.length > 0) {
147
+ try {
148
+ const secondPass = await enrichDocumentWithAI({
149
+ baseResult: { markdown: finalMarkdown, docType: docTypeEntry[0], prefilledFields: [], manualFields: [...remainingWeak] },
150
+ manifest: passport,
151
+ model,
152
+ });
153
+ finalMarkdown = secondPass.markdown;
154
+ } catch {
155
+ // Keep first-pass result on second-pass failure
156
+ }
157
+ }
158
+ // LLM enriched → no scaffold marker (scanner classifies as 'draft')
159
+ content = finalMarkdown;
160
+ } catch (err) {
161
+ events.emit('log', { level: 'warn', message: `LLM enrichment failed: ${err instanceof Error ? err.message : err}` });
162
+ content = `<!-- COMPLIOR:SCAFFOLD -->\n${baseResult.markdown}`;
163
+ }
164
+ } else {
165
+ // No LLM → mark as scaffold
166
+ content = `<!-- COMPLIOR:SCAFFOLD -->\n${baseResult.markdown}`;
167
+ }
168
+ }
169
+ } else {
170
+ content = `<!-- COMPLIOR:SCAFFOLD -->\n${template}`;
171
+ }
172
+ } catch {
173
+ content = `<!-- COMPLIOR:SCAFFOLD -->\n${template}`;
174
+ }
175
+ } else {
176
+ content = `<!-- COMPLIOR:SCAFFOLD -->\n${template}`;
177
+ }
178
+ }
179
+ // Resolve [PASSPORT:*] tokens from agent passport (for non-template content)
180
+ if (content.includes('[PASSPORT:') && deps.passportService) {
181
+ try {
182
+ const passports = await deps.passportService.listPassports(projectPath);
183
+ const passport = passports[0];
184
+ if (passport) {
185
+ content = resolvePassportPlaceholders(content, passport);
186
+ }
187
+ } catch { /* ignore — keep placeholders */ }
188
+ }
189
+ await writeFile(fullPath, content, 'utf-8');
190
+ events.emit('file.changed', { path: fullPath, action: 'create' });
191
+ return manualFields;
192
+ } else if (action.type === 'splice') {
193
+ const content = await readFile(fullPath, 'utf-8');
194
+ const lines = content.split('\n');
195
+ const startIdx = (action.startLine ?? 1) - 1;
196
+ const beforeLines = action.beforeLines ?? [];
197
+ const endIdx = startIdx + beforeLines.length;
198
+
199
+ // Stale diff protection — validate before-lines match (trimmed)
200
+ if (endIdx > lines.length) {
201
+ throw new Error(`Stale diff: line range exceeds file length — re-scan first`);
202
+ }
203
+ for (let i = 0; i < beforeLines.length; i++) {
204
+ if ((lines[startIdx + i] ?? '').trim() !== (beforeLines[i] ?? '').trim()) {
205
+ throw new Error(`Stale diff at line ${(action.startLine ?? 1) + i} — re-scan first`);
206
+ }
207
+ }
208
+
209
+ // Splice: replace before-lines with after-lines
210
+ lines.splice(startIdx, beforeLines.length, ...(action.afterLines ?? []));
211
+
212
+ // Import injection — after last existing import, or at top
213
+ if (action.importLine && !lines.some((l) => l.includes(action.importLine!))) {
214
+ let insertAt = 0;
215
+ for (let i = lines.length - 1; i >= 0; i--) {
216
+ if (lines[i]!.startsWith('import ')) { insertAt = i + 1; break; }
217
+ }
218
+ lines.splice(insertAt, 0, action.importLine);
219
+ }
220
+
221
+ // Write back, preserving trailing newline
222
+ let output = lines.join('\n');
223
+ if (content.endsWith('\n') && !output.endsWith('\n')) output += '\n';
224
+ await writeFile(fullPath, output, 'utf-8');
225
+ events.emit('file.changed', { path: fullPath, action: 'edit' });
226
+ return undefined;
227
+ } else {
228
+ const current = await readFile(fullPath, 'utf-8');
229
+ const updated = current.replace(action.oldContent ?? '', action.newContent ?? '');
230
+ await writeFile(fullPath, updated, 'utf-8');
231
+ events.emit('file.changed', { path: fullPath, action: 'edit' });
232
+ return undefined;
233
+ }
234
+ };
235
+
236
+ const preview = (finding: Finding): FixPlan | null => {
237
+ return fixer.previewFix(finding);
238
+ };
239
+
240
+ const previewAll = (): readonly FixPlan[] => {
241
+ const lastScan = getLastScanResult();
242
+ if (!lastScan) return [];
243
+ return fixer.generateFixes(lastScan.findings);
244
+ };
245
+
246
+ const applyFix = async (plan: FixPlan, useAi = false): Promise<FixResult> => {
247
+ const projectPath = getProjectPath();
248
+ // Normalize: run a basic scan first so scoreBefore matches scoreAfter tier
249
+ const baselineScan = await scanService.scan(projectPath);
250
+ const scoreBefore = baselineScan.score.totalScore;
251
+ const backedUp: string[] = [];
252
+
253
+ try {
254
+ let manualFields: readonly string[] | undefined;
255
+ for (const action of plan.actions) {
256
+ const bk = await createBackup(action.path);
257
+ backedUp.push(bk);
258
+ const mf = await applyAction(action, projectPath, useAi);
259
+ if (mf) manualFields = mf;
260
+ }
261
+ const enrichedPlan = manualFields ? { ...plan, manualFields } : plan;
262
+
263
+ // Re-scan to get updated score
264
+ const newResult = await scanService.scan(projectPath);
265
+ const scoreAfter = newResult.score.totalScore;
266
+
267
+ events.emit('score.updated', { before: scoreBefore, after: scoreAfter });
268
+
269
+ // C.R20: Record fix event in evidence chain
270
+ if (deps.evidenceStore) {
271
+ const evidence = createEvidence(
272
+ plan.checkId,
273
+ 'fix',
274
+ 'fix',
275
+ { file: plan.actions[0]?.path },
276
+ );
277
+ await deps.evidenceStore.append([evidence], randomUUID());
278
+ }
279
+
280
+ const result: FixResult = {
281
+ plan: enrichedPlan,
282
+ applied: true,
283
+ scoreBefore,
284
+ scoreAfter,
285
+ backedUpFiles: backedUp,
286
+ };
287
+
288
+ if (undoService) {
289
+ await undoService.recordFix(result, enrichedPlan);
290
+ }
291
+
292
+ return result;
293
+ } catch (err) {
294
+ return {
295
+ plan,
296
+ applied: false,
297
+ scoreBefore,
298
+ scoreAfter: scoreBefore,
299
+ backedUpFiles: backedUp,
300
+ error: err instanceof Error ? err.message : String(err),
301
+ };
302
+ }
303
+ };
304
+
305
+ const applyAndValidate = async (plan: FixPlan, useAi = false): Promise<FixResult & { validation: FixValidation }> => {
306
+ const lastScan = getLastScanResult();
307
+ const findingBefore = lastScan?.findings.find(
308
+ (f) => f.checkId === plan.checkId && (!plan.obligationId || f.obligationId === plan.obligationId),
309
+ );
310
+ const beforeType = findingBefore?.type ?? 'fail';
311
+
312
+ const result = await applyFix(plan, useAi);
313
+
314
+ const newScan = getLastScanResult();
315
+ const findingAfter = newScan?.findings.find(
316
+ (f) => f.checkId === plan.checkId && (!plan.obligationId || f.obligationId === plan.obligationId),
317
+ );
318
+ const afterType = findingAfter?.type ?? (result.applied ? 'pass' : beforeType);
319
+ const scoreDelta = result.scoreAfter - result.scoreBefore;
320
+
321
+ const validation: FixValidation = {
322
+ checkId: plan.checkId,
323
+ obligationId: plan.obligationId,
324
+ article: plan.article,
325
+ before: beforeType,
326
+ after: afterType,
327
+ scoreDelta,
328
+ totalScore: result.scoreAfter,
329
+ };
330
+
331
+ events.emit('fix.validated', {
332
+ checkId: plan.checkId,
333
+ passed: afterType === 'pass',
334
+ scoreDelta,
335
+ });
336
+
337
+ return { ...result, validation };
338
+ };
339
+
340
+ const applyAllAndValidate = async (useAi = false): Promise<{
341
+ results: readonly (FixResult & { validation: FixValidation })[];
342
+ totalDelta: number;
343
+ }> => {
344
+ const plans = previewAll();
345
+ const results: (FixResult & { validation: FixValidation })[] = [];
346
+ for (const plan of plans) {
347
+ const result = await applyAndValidate(plan, useAi);
348
+ results.push(result);
349
+ }
350
+ const totalDelta = results.reduce((sum, r) => sum + r.validation.scoreDelta, 0);
351
+ return { results, totalDelta };
352
+ };
353
+
354
+ type FixProgressCallback = (event: {
355
+ type: 'applying' | 'applied' | 'failed';
356
+ checkId: string;
357
+ path: string;
358
+ action?: string;
359
+ scoreAfter?: number;
360
+ error?: string;
361
+ }) => Promise<void>;
362
+
363
+ const applyAll = async (useAi = false, overridePath?: string, onProgress?: FixProgressCallback): Promise<readonly FixResult[]> => {
364
+ const plans = fixer.generateFixes(
365
+ getLastScanResult()?.findings ?? [],
366
+ useAi ? { useAi: true } : undefined,
367
+ );
368
+ if (plans.length === 0) return [];
369
+
370
+ const projectPath = overridePath ?? getProjectPath();
371
+ // Normalize: ensure scoreBefore reflects basic scan (same tier as scoreAfter)
372
+ const baselineScan = await scanService.scan(projectPath);
373
+ const scoreBefore = baselineScan.score.totalScore;
374
+ const results: FixResult[] = [];
375
+ const appliedActionKeys = new Set<string>(); // Track path:startLine (splice) or path (create/edit)
376
+
377
+ // Separate splice-only plans (batch per file) from others (apply individually)
378
+ const splicePlans: FixPlan[] = [];
379
+ const otherPlans: FixPlan[] = [];
380
+ for (const plan of plans) {
381
+ if (plan.actions.length === 1 && plan.actions[0]!.type === 'splice') {
382
+ splicePlans.push(plan);
383
+ } else {
384
+ otherPlans.push(plan);
385
+ }
386
+ }
387
+
388
+ // Phase 1a: Apply non-splice plans individually
389
+ for (const plan of otherPlans) {
390
+ const backedUp: string[] = [];
391
+ const actionPath = plan.actions[0]?.path ?? '?';
392
+ const actionType = plan.actions[0]?.type === 'create' ? 'CREATE' : 'MODIFY';
393
+ if (onProgress) {
394
+ await onProgress({ type: 'applying', checkId: plan.checkId, path: actionPath, action: actionType });
395
+ }
396
+ try {
397
+ let manualFields: readonly string[] | undefined;
398
+ for (const action of plan.actions) {
399
+ const bk = await createBackup(action.path);
400
+ backedUp.push(bk);
401
+ const mf = await applyAction(action, projectPath, useAi);
402
+ if (mf) manualFields = mf;
403
+ }
404
+ const enrichedPlan = manualFields ? { ...plan, manualFields } : plan;
405
+ for (const action of plan.actions) {
406
+ appliedActionKeys.add(action.type === 'splice' ? `${action.path}:${action.startLine}` : action.path);
407
+ }
408
+ results.push({ plan: enrichedPlan, applied: true, scoreBefore, scoreAfter: 0, backedUpFiles: backedUp });
409
+ if (onProgress) {
410
+ await onProgress({ type: 'applied', checkId: plan.checkId, path: actionPath });
411
+ }
412
+ } catch (err) {
413
+ const errorMsg = err instanceof Error ? err.message : String(err);
414
+ results.push({
415
+ plan,
416
+ applied: false,
417
+ scoreBefore,
418
+ scoreAfter: scoreBefore,
419
+ backedUpFiles: backedUp,
420
+ error: errorMsg,
421
+ });
422
+ if (onProgress) {
423
+ await onProgress({ type: 'failed', checkId: plan.checkId, path: actionPath, error: errorMsg });
424
+ }
425
+ }
426
+ }
427
+
428
+ // Phase 1b: Batch same-file splices — single read-modify-write per file
429
+ const fileGroups = new Map<string, FixPlan[]>();
430
+ for (const plan of splicePlans) {
431
+ const path = plan.actions[0]!.path;
432
+ if (!fileGroups.has(path)) fileGroups.set(path, []);
433
+ fileGroups.get(path)!.push(plan);
434
+ }
435
+
436
+ for (const [filePath, group] of fileGroups) {
437
+ // Sort bottom-up by startLine so splicing doesn't shift indices for remaining ops
438
+ group.sort((a, b) => (b.actions[0]!.startLine ?? 0) - (a.actions[0]!.startLine ?? 0));
439
+
440
+ const bk = await createBackup(filePath);
441
+ const fullPath = resolve(projectPath, filePath);
442
+
443
+ let content: string;
444
+ try {
445
+ content = await readFile(fullPath, 'utf-8');
446
+ } catch {
447
+ for (const plan of group) {
448
+ results.push({
449
+ plan, applied: false, scoreBefore, scoreAfter: scoreBefore,
450
+ backedUpFiles: [bk], error: `File not found: ${filePath}`,
451
+ });
452
+ }
453
+ continue;
454
+ }
455
+
456
+ const lines = content.split('\n');
457
+ const importLines: string[] = [];
458
+
459
+ // Apply each splice to in-memory lines (bottom-up)
460
+ for (const plan of group) {
461
+ const action = plan.actions[0]!;
462
+ const startIdx = (action.startLine ?? 1) - 1;
463
+ const beforeLines = action.beforeLines ?? [];
464
+
465
+ // Validate before-lines against current in-memory state
466
+ let valid = true;
467
+ if (startIdx + beforeLines.length > lines.length) {
468
+ results.push({
469
+ plan, applied: false, scoreBefore, scoreAfter: scoreBefore,
470
+ backedUpFiles: [bk], error: `Stale diff: line range exceeds file length — re-scan first`,
471
+ });
472
+ valid = false;
473
+ } else {
474
+ for (let i = 0; i < beforeLines.length; i++) {
475
+ if ((lines[startIdx + i] ?? '').trim() !== (beforeLines[i] ?? '').trim()) {
476
+ results.push({
477
+ plan, applied: false, scoreBefore, scoreAfter: scoreBefore,
478
+ backedUpFiles: [bk], error: `Stale diff at line ${(action.startLine ?? 1) + i} — re-scan first`,
479
+ });
480
+ valid = false;
481
+ break;
482
+ }
483
+ }
484
+ }
485
+ if (!valid) continue;
486
+
487
+ if (onProgress) {
488
+ await onProgress({ type: 'applying', checkId: plan.checkId, path: action.path, action: 'MODIFY' });
489
+ }
490
+
491
+ // Splice in-memory
492
+ lines.splice(startIdx, beforeLines.length, ...(action.afterLines ?? []));
493
+ if (action.importLine) importLines.push(action.importLine);
494
+ appliedActionKeys.add(`${action.path}:${action.startLine}`);
495
+ results.push({ plan, applied: true, scoreBefore, scoreAfter: 0, backedUpFiles: [bk] });
496
+
497
+ if (onProgress) {
498
+ await onProgress({ type: 'applied', checkId: plan.checkId, path: action.path });
499
+ }
500
+ }
501
+
502
+ // Inject unique imports after all splices (after last import/from line)
503
+ const uniqueImports = [...new Set(importLines)].filter(
504
+ (imp) => !lines.some((l) => l.includes(imp)),
505
+ );
506
+ if (uniqueImports.length > 0) {
507
+ let insertAt = 0;
508
+ for (let i = lines.length - 1; i >= 0; i--) {
509
+ if (lines[i]!.startsWith('import ') || lines[i]!.startsWith('from ')) {
510
+ insertAt = i + 1;
511
+ break;
512
+ }
513
+ }
514
+ lines.splice(insertAt, 0, ...uniqueImports);
515
+ }
516
+
517
+ // Write file once
518
+ let output = lines.join('\n');
519
+ if (content.endsWith('\n') && !output.endsWith('\n')) output += '\n';
520
+ await writeFile(fullPath, output, 'utf-8');
521
+ events.emit('file.changed', { path: fullPath, action: 'edit' });
522
+ }
523
+
524
+ // Phase 2: Iterative scan+fix (up to 2 extra passes for cascading findings)
525
+ // After fixing line N, the scanner may detect line N+2 which was previously shadowed.
526
+ let scoreAfter = 0;
527
+ for (let pass = 0; pass < 3; pass++) {
528
+ const scanResult = await scanService.scan(projectPath);
529
+ scoreAfter = scanResult.score.totalScore;
530
+
531
+ if (pass >= 2) break; // Last pass is just the final scan
532
+
533
+ // Check for new fixable findings (splice + cascading create/edit)
534
+ const newPlans = previewAll().filter((p) => {
535
+ if (p.actions.length === 0) return false;
536
+ const a = p.actions[0]!;
537
+ const key = a.type === 'splice' ? `${a.path}:${a.startLine}` : a.path;
538
+ return !appliedActionKeys.has(key);
539
+ });
540
+ if (newPlans.length === 0) break;
541
+
542
+ // Apply new splice fixes using the same batched approach
543
+ const spliceCascadePlans = newPlans.filter(p => p.actions.length === 1 && p.actions[0]!.type === 'splice');
544
+ const newFileGroups = new Map<string, FixPlan[]>();
545
+ for (const plan of spliceCascadePlans) {
546
+ const path = plan.actions[0]!.path;
547
+ if (!newFileGroups.has(path)) newFileGroups.set(path, []);
548
+ newFileGroups.get(path)!.push(plan);
549
+ }
550
+
551
+ let anyApplied = false;
552
+ for (const [fp, group] of newFileGroups) {
553
+ group.sort((a, b) => (b.actions[0]!.startLine ?? 0) - (a.actions[0]!.startLine ?? 0));
554
+ const bk = await createBackup(fp);
555
+ const full = resolve(projectPath, fp);
556
+ let content: string;
557
+ try { content = await readFile(full, 'utf-8'); } catch { continue; }
558
+ const lines = content.split('\n');
559
+ const importLines: string[] = [];
560
+
561
+ for (const plan of group) {
562
+ const action = plan.actions[0]!;
563
+ const startIdx = (action.startLine ?? 1) - 1;
564
+ const beforeLines = action.beforeLines ?? [];
565
+ let valid = true;
566
+ if (startIdx + beforeLines.length > lines.length) { valid = false; }
567
+ else {
568
+ for (let i = 0; i < beforeLines.length; i++) {
569
+ if ((lines[startIdx + i] ?? '').trim() !== (beforeLines[i] ?? '').trim()) { valid = false; break; }
570
+ }
571
+ }
572
+ if (!valid) continue;
573
+ lines.splice(startIdx, beforeLines.length, ...(action.afterLines ?? []));
574
+ if (action.importLine) importLines.push(action.importLine);
575
+ appliedActionKeys.add(`${action.path}:${action.startLine}`);
576
+ results.push({ plan, applied: true, scoreBefore, scoreAfter: 0, backedUpFiles: [bk] });
577
+ anyApplied = true;
578
+ }
579
+
580
+ const uniqueImports = [...new Set(importLines)].filter((imp) => !lines.some((l) => l.includes(imp)));
581
+ if (uniqueImports.length > 0) {
582
+ let insertAt = 0;
583
+ for (let i = lines.length - 1; i >= 0; i--) {
584
+ if (lines[i]!.startsWith('import ') || lines[i]!.startsWith('from ')) { insertAt = i + 1; break; }
585
+ }
586
+ lines.splice(insertAt, 0, ...uniqueImports);
587
+ }
588
+ let output = lines.join('\n');
589
+ if (content.endsWith('\n') && !output.endsWith('\n')) output += '\n';
590
+ await writeFile(full, output, 'utf-8');
591
+ events.emit('file.changed', { path: full, action: 'edit' });
592
+ }
593
+ // Apply non-splice cascading actions (create/edit) using existing applyAction helper
594
+ const nonSplicePlans = newPlans.filter(p => p.actions.length > 0 && p.actions[0]!.type !== 'splice');
595
+ for (const plan of nonSplicePlans) {
596
+ try {
597
+ const bk = await createBackup(plan.actions[0]!.path);
598
+ await applyAction(plan.actions[0]!, projectPath);
599
+ appliedActionKeys.add(plan.actions[0]!.path);
600
+ results.push({ plan, applied: true, scoreBefore, scoreAfter: 0, backedUpFiles: [bk] });
601
+ anyApplied = true;
602
+ } catch { /* skip failed cascading fixes */ }
603
+ }
604
+ if (!anyApplied) break;
605
+ }
606
+ events.emit('score.updated', { before: scoreBefore, after: scoreAfter });
607
+
608
+ // Phase 3: Record undo history + evidence for applied fixes
609
+ for (const result of results) {
610
+ if (!result.applied) continue;
611
+ // Patch final score into result (reconstruct to preserve readonly contract)
612
+ Object.assign(result, { scoreAfter });
613
+
614
+ if (undoService) {
615
+ await undoService.recordFix(result, result.plan);
616
+ }
617
+ if (deps.evidenceStore) {
618
+ const evidence = createEvidence(
619
+ result.plan.checkId,
620
+ 'fix',
621
+ 'fix',
622
+ { file: result.plan.actions[0]?.path },
623
+ );
624
+ await deps.evidenceStore.append([evidence], randomUUID());
625
+ }
626
+ }
627
+
628
+ return results;
629
+ };
630
+
631
+ const getUnfixedFindings = (): readonly Finding[] => {
632
+ const lastScan = getLastScanResult();
633
+ if (!lastScan) return [];
634
+ const fixableIds = new Set(previewAll().map((p) => p.checkId));
635
+ return lastScan.findings.filter(
636
+ (f) => f.type === 'fail' && !fixableIds.has(f.checkId),
637
+ );
638
+ };
639
+
640
+ const getCurrentScore = (): number => {
641
+ const lastScan = getLastScanResult();
642
+ return lastScan?.score.totalScore ?? 0;
643
+ };
644
+
645
+ return Object.freeze({ preview, previewAll, applyFix, applyAll, applyAndValidate, applyAllAndValidate, getUnfixedFindings, getCurrentScore });
646
+ };
647
+
648
+ export type FixService = ReturnType<typeof createFixService>;