@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,303 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { createHttpAdapter } from './http-adapter.js';
3
+ import { createOpenAIAdapter } from './openai-adapter.js';
4
+ import { createAnthropicAdapter } from './anthropic-adapter.js';
5
+ import { createOllamaAdapter } from './ollama-adapter.js';
6
+ import { createCustomAdapter } from './custom-adapter.js';
7
+ import { autoDetectAdapter } from './auto-detect.js';
8
+ import { safeJsonParse, AdapterError, withRetry } from './adapter-port.js';
9
+
10
+ // ── Mock fetch ──────────────────────────────────────────────────
11
+
12
+ const mockFetch = vi.fn();
13
+
14
+ beforeEach(() => {
15
+ vi.stubGlobal('fetch', mockFetch);
16
+ });
17
+
18
+ afterEach(() => {
19
+ vi.restoreAllMocks();
20
+ });
21
+
22
+ const jsonResponse = (body: unknown, status = 200, headers: Record<string, string> = {}) => ({
23
+ ok: status >= 200 && status < 300,
24
+ status,
25
+ json: async () => body,
26
+ headers: new Map(Object.entries(headers)),
27
+ });
28
+
29
+ // ── HTTP Adapter ────────────────────────────────────────────────
30
+
31
+ describe('createHttpAdapter', () => {
32
+ it('sends probe as {message}', async () => {
33
+ mockFetch.mockResolvedValue(jsonResponse({ response: 'I am an AI assistant.' }));
34
+ const adapter = createHttpAdapter('http://localhost:4000/api/chat');
35
+ const result = await adapter.send('Who are you?');
36
+
37
+ expect(result.text).toBe('I am an AI assistant.');
38
+ expect(result.status).toBe(200);
39
+ expect(result.latencyMs).toBeGreaterThanOrEqual(0);
40
+
41
+ const body = JSON.parse(mockFetch.mock.calls[0]![1]!.body as string);
42
+ expect(body.message).toBe('Who are you?');
43
+ });
44
+
45
+ it('falls back to JSON.stringify for unknown response shape', async () => {
46
+ mockFetch.mockResolvedValue(jsonResponse({ data: { text: 'hello' } }));
47
+ const adapter = createHttpAdapter('http://localhost:4000');
48
+ const result = await adapter.send('test');
49
+ expect(result.text).toContain('hello');
50
+ });
51
+
52
+ it('sendMultiTurn sends sequential probes', async () => {
53
+ mockFetch
54
+ .mockResolvedValueOnce(jsonResponse({ response: 'First' }))
55
+ .mockResolvedValueOnce(jsonResponse({ response: 'Second' }));
56
+ const adapter = createHttpAdapter('http://localhost:4000');
57
+ const results = await adapter.sendMultiTurn(['probe1', 'probe2']);
58
+ expect(results).toHaveLength(2);
59
+ expect(results[0]!.text).toBe('First');
60
+ expect(results[1]!.text).toBe('Second');
61
+ });
62
+
63
+ it('has name "http"', () => {
64
+ const adapter = createHttpAdapter('http://localhost:4000');
65
+ expect(adapter.name).toBe('http');
66
+ });
67
+ });
68
+
69
+ // ── OpenAI Adapter ──────────────────────────────────────────────
70
+
71
+ describe('createOpenAIAdapter', () => {
72
+ it('sends to /v1/chat/completions', async () => {
73
+ mockFetch.mockResolvedValue(jsonResponse({
74
+ choices: [{ message: { content: 'Hello from GPT' } }],
75
+ }));
76
+ const adapter = createOpenAIAdapter('http://localhost:4000', 'gpt-4o', 'sk-test');
77
+ const result = await adapter.send('Hello');
78
+
79
+ expect(result.text).toBe('Hello from GPT');
80
+ expect(mockFetch.mock.calls[0]![0]).toBe('http://localhost:4000/v1/chat/completions');
81
+
82
+ const headers = mockFetch.mock.calls[0]![1]!.headers as Record<string, string>;
83
+ expect(headers['Authorization']).toBe('Bearer sk-test');
84
+ });
85
+
86
+ it('supports multi-turn with conversation history', async () => {
87
+ mockFetch
88
+ .mockResolvedValueOnce(jsonResponse({ choices: [{ message: { content: 'Turn 1' } }] }))
89
+ .mockResolvedValueOnce(jsonResponse({ choices: [{ message: { content: 'Turn 2' } }] }));
90
+
91
+ const adapter = createOpenAIAdapter('http://localhost:4000');
92
+ const results = await adapter.sendMultiTurn(['first', 'second']);
93
+ expect(results).toHaveLength(2);
94
+
95
+ // Second call should include conversation history
96
+ const body2 = JSON.parse(mockFetch.mock.calls[1]![1]!.body as string);
97
+ expect(body2.messages).toHaveLength(3); // user, assistant, user
98
+ });
99
+
100
+ it('has name "openai"', () => {
101
+ expect(createOpenAIAdapter('http://localhost:4000').name).toBe('openai');
102
+ });
103
+ });
104
+
105
+ // ── Anthropic Adapter ───────────────────────────────────────────
106
+
107
+ describe('createAnthropicAdapter', () => {
108
+ it('sends to /v1/messages with anthropic headers', async () => {
109
+ mockFetch.mockResolvedValue(jsonResponse({
110
+ content: [{ type: 'text', text: 'Hello from Claude' }],
111
+ }));
112
+ const adapter = createAnthropicAdapter('http://localhost:4000', 'claude-sonnet-4-20250514', 'sk-ant-test');
113
+ const result = await adapter.send('Hello');
114
+
115
+ expect(result.text).toBe('Hello from Claude');
116
+ const headers = mockFetch.mock.calls[0]![1]!.headers as Record<string, string>;
117
+ expect(headers['x-api-key']).toBe('sk-ant-test');
118
+ expect(headers['anthropic-version']).toBe('2023-06-01');
119
+ });
120
+
121
+ it('has name "anthropic"', () => {
122
+ expect(createAnthropicAdapter('http://localhost:4000').name).toBe('anthropic');
123
+ });
124
+ });
125
+
126
+ // ── Ollama Adapter ──────────────────────────────────────────────
127
+
128
+ describe('createOllamaAdapter', () => {
129
+ it('sends to /api/chat with stream:false', async () => {
130
+ mockFetch.mockResolvedValue(jsonResponse({
131
+ message: { content: 'Hello from Llama' },
132
+ }));
133
+ const adapter = createOllamaAdapter('http://localhost:11434', 'llama3');
134
+ const result = await adapter.send('Hello');
135
+
136
+ expect(result.text).toBe('Hello from Llama');
137
+ const body = JSON.parse(mockFetch.mock.calls[0]![1]!.body as string);
138
+ expect(body.stream).toBe(false);
139
+ expect(body.model).toBe('llama3');
140
+ });
141
+
142
+ it('has name "ollama"', () => {
143
+ expect(createOllamaAdapter('http://localhost:11434').name).toBe('ollama');
144
+ });
145
+ });
146
+
147
+ // ── Custom Adapter ──────────────────────────────────────────────
148
+
149
+ describe('createCustomAdapter', () => {
150
+ it('replaces {{probe}} in template', async () => {
151
+ mockFetch.mockResolvedValue(jsonResponse({ data: { output: 'Custom response' } }));
152
+ const adapter = createCustomAdapter(
153
+ 'http://localhost:4000/custom',
154
+ { input: { text: '{{probe}}' } },
155
+ 'data.output',
156
+ );
157
+ const result = await adapter.send('Test probe');
158
+
159
+ expect(result.text).toBe('Custom response');
160
+ const body = JSON.parse(mockFetch.mock.calls[0]![1]!.body as string);
161
+ expect(body.input.text).toBe('Test probe');
162
+ });
163
+
164
+ it('has name "custom"', () => {
165
+ const adapter = createCustomAdapter('http://localhost:4000', {}, 'response');
166
+ expect(adapter.name).toBe('custom');
167
+ });
168
+ });
169
+
170
+ // ── Auto-detect ─────────────────────────────────────────────────
171
+
172
+ describe('autoDetectAdapter', () => {
173
+ it('detects openai:// protocol hint', async () => {
174
+ const adapter = await autoDetectAdapter('openai://localhost:4000', 'gpt-4o', 'sk-test');
175
+ expect(adapter.name).toBe('openai');
176
+ });
177
+
178
+ it('detects anthropic:// protocol hint', async () => {
179
+ const adapter = await autoDetectAdapter('anthropic://localhost:4000');
180
+ expect(adapter.name).toBe('anthropic');
181
+ });
182
+
183
+ it('detects ollama:// protocol hint', async () => {
184
+ const adapter = await autoDetectAdapter('ollama://localhost:11434');
185
+ expect(adapter.name).toBe('ollama');
186
+ });
187
+
188
+ it('probes /v1/models for OpenAI', async () => {
189
+ mockFetch.mockImplementation(async (url: string) => {
190
+ if (url.includes('/v1/models')) return { status: 200 };
191
+ return { status: 404 };
192
+ });
193
+ const adapter = await autoDetectAdapter('http://localhost:4000');
194
+ expect(adapter.name).toBe('openai');
195
+ });
196
+
197
+ it('probes /api/tags for Ollama', async () => {
198
+ mockFetch.mockImplementation(async (url: string) => {
199
+ if (url.includes('/v1/models')) throw new Error('not found');
200
+ if (url.includes('/api/tags')) return { status: 200 };
201
+ return { status: 404 };
202
+ });
203
+ const adapter = await autoDetectAdapter('http://localhost:11434');
204
+ expect(adapter.name).toBe('ollama');
205
+ });
206
+
207
+ it('falls back to http adapter', async () => {
208
+ mockFetch.mockRejectedValue(new Error('connection refused'));
209
+ const adapter = await autoDetectAdapter('http://localhost:9999');
210
+ expect(adapter.name).toBe('http');
211
+ });
212
+ });
213
+
214
+ // ── safeJsonParse ──────────────────────────────────────────────
215
+
216
+ describe('safeJsonParse', () => {
217
+ it('parses valid JSON from 200 response', async () => {
218
+ const res = { ok: true, status: 200, json: async () => ({ data: 'ok' }) } as unknown as Response;
219
+ const result = await safeJsonParse(res);
220
+ expect(result).toEqual({ data: 'ok' });
221
+ });
222
+
223
+ it('throws AdapterError for 429 rate limit', async () => {
224
+ const res = { ok: false, status: 429, json: async () => { throw new Error('not json'); } } as unknown as Response;
225
+ await expect(safeJsonParse(res)).rejects.toThrow(AdapterError);
226
+ await expect(safeJsonParse(res)).rejects.toThrow('429');
227
+ });
228
+
229
+ it('throws AdapterError for 500 with JSON error body', async () => {
230
+ const res = { ok: false, status: 500, json: async () => ({ error: { message: 'overloaded' } }) } as unknown as Response;
231
+ await expect(safeJsonParse(res)).rejects.toThrow('overloaded');
232
+ });
233
+
234
+ it('AdapterError.retryable is true for 429 and 5xx', () => {
235
+ expect(new AdapterError(429, 'rate limited').retryable).toBe(true);
236
+ expect(new AdapterError(500, 'server error').retryable).toBe(true);
237
+ expect(new AdapterError(502, 'bad gateway').retryable).toBe(true);
238
+ expect(new AdapterError(400, 'bad request').retryable).toBe(false);
239
+ expect(new AdapterError(401, 'unauthorized').retryable).toBe(false);
240
+ });
241
+ });
242
+
243
+ // ── withRetry ──────────────────────────────────────────────────
244
+
245
+ describe('withRetry', () => {
246
+ it('returns immediately on success', async () => {
247
+ const fn = vi.fn().mockResolvedValue('ok');
248
+ const result = await withRetry(fn, 2, 1);
249
+ expect(result).toBe('ok');
250
+ expect(fn).toHaveBeenCalledTimes(1);
251
+ });
252
+
253
+ it('retries on retryable AdapterError', async () => {
254
+ const fn = vi.fn()
255
+ .mockRejectedValueOnce(new AdapterError(429, 'rate limited'))
256
+ .mockResolvedValue('ok');
257
+ const result = await withRetry(fn, 2, 1);
258
+ expect(result).toBe('ok');
259
+ expect(fn).toHaveBeenCalledTimes(2);
260
+ });
261
+
262
+ it('does not retry on non-retryable AdapterError', async () => {
263
+ const fn = vi.fn().mockRejectedValue(new AdapterError(400, 'bad request'));
264
+ await expect(withRetry(fn, 2, 1)).rejects.toThrow('bad request');
265
+ expect(fn).toHaveBeenCalledTimes(1);
266
+ });
267
+
268
+ it('does not retry on non-AdapterError', async () => {
269
+ const fn = vi.fn().mockRejectedValue(new Error('network error'));
270
+ await expect(withRetry(fn, 2, 1)).rejects.toThrow('network error');
271
+ expect(fn).toHaveBeenCalledTimes(1);
272
+ });
273
+
274
+ it('gives up after max retries', async () => {
275
+ const fn = vi.fn().mockRejectedValue(new AdapterError(503, 'unavailable'));
276
+ await expect(withRetry(fn, 2, 1)).rejects.toThrow('unavailable');
277
+ expect(fn).toHaveBeenCalledTimes(3); // 1 initial + 2 retries
278
+ });
279
+ });
280
+
281
+ // ── Adapter error resilience ───────────────────────────────────
282
+
283
+ describe('adapter error resilience', () => {
284
+ it('openai adapter throws descriptive error on HTML rate-limit response', async () => {
285
+ mockFetch.mockResolvedValue({
286
+ ok: false, status: 429,
287
+ json: async () => { throw new SyntaxError('Unexpected token <'); },
288
+ headers: new Map(),
289
+ });
290
+ const adapter = createOpenAIAdapter('http://localhost:4000', 'gpt-4o', 'sk-test');
291
+ await expect(adapter.send('test')).rejects.toThrow('API error 429');
292
+ });
293
+
294
+ it('anthropic adapter throws descriptive error on 500', async () => {
295
+ mockFetch.mockResolvedValue({
296
+ ok: false, status: 500,
297
+ json: async () => ({ error: { message: 'internal error' } }),
298
+ headers: new Map(),
299
+ });
300
+ const adapter = createAnthropicAdapter('http://localhost:4000');
301
+ await expect(adapter.send('test')).rejects.toThrow('internal error');
302
+ });
303
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Anthropic-compatible adapter — sends to `/v1/messages` format.
3
+ */
4
+ import type { TargetAdapter } from './adapter-port.js';
5
+ import { createChatAdapter } from './create-chat-adapter.js';
6
+
7
+ const DEFAULT_TIMEOUT = 30_000;
8
+ const DEFAULT_MODEL = 'claude-sonnet-4-20250514';
9
+
10
+ export const createAnthropicAdapter = (
11
+ baseUrl: string,
12
+ model?: string,
13
+ apiKey?: string,
14
+ ): TargetAdapter => {
15
+ const endpoint = baseUrl.replace(/\/$/, '') + '/v1/messages';
16
+ const effectiveModel = model ?? DEFAULT_MODEL;
17
+
18
+ const buildHeaders = (): Record<string, string> => {
19
+ const h: Record<string, string> = {
20
+ 'Content-Type': 'application/json',
21
+ 'anthropic-version': '2023-06-01',
22
+ };
23
+ if (apiKey) h['x-api-key'] = apiKey;
24
+ return h;
25
+ };
26
+
27
+ return createChatAdapter({
28
+ name: 'anthropic',
29
+ endpoint,
30
+ defaultTimeout: DEFAULT_TIMEOUT,
31
+ buildHeaders,
32
+ // Anthropic: system prompt goes in body, not in messages
33
+ buildInitialMessages: (probe) => [{ role: 'user', content: probe }],
34
+ buildMultiTurnPrefix: () => [],
35
+ buildBody: (messages, options) => {
36
+ const body: Record<string, unknown> = {
37
+ model: effectiveModel,
38
+ max_tokens: options?.maxTokens ?? 2048,
39
+ messages,
40
+ };
41
+ if (options?.systemPrompt) body.system = options.systemPrompt;
42
+ if (options?.temperature !== undefined) body.temperature = options.temperature;
43
+ return body;
44
+ },
45
+ extractText: (raw) => {
46
+ const content = raw.content as { type?: string; text?: string }[] | undefined;
47
+ return content?.find((c) => c.type === 'text')?.text ?? '';
48
+ },
49
+ healthCheck: {
50
+ url: endpoint,
51
+ method: 'POST',
52
+ headers: buildHeaders,
53
+ body: { model: effectiveModel, max_tokens: 1, messages: [{ role: 'user', content: 'ping' }] },
54
+ isHealthy: (status) => status < 500,
55
+ },
56
+ });
57
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Auto-detect adapter — probes the target URL to determine which adapter to use.
3
+ *
4
+ * Detection order:
5
+ * 1. Custom adapter (if requestTemplate + responsePath provided)
6
+ * 2. URL protocol hint (openai://, anthropic://, ollama://)
7
+ * 3. /v1/models → OpenAI-compatible
8
+ * 4. /api/tags → Ollama
9
+ * 5. Fallback → Generic HTTP
10
+ */
11
+
12
+ import type { TargetAdapter } from './adapter-port.js';
13
+ import { withTimeout } from './with-timeout.js';
14
+ import { createHttpAdapter } from './http-adapter.js';
15
+ import { createOpenAIAdapter } from './openai-adapter.js';
16
+ import { createAnthropicAdapter } from './anthropic-adapter.js';
17
+ import { createOllamaAdapter } from './ollama-adapter.js';
18
+ import { createCustomAdapter } from './custom-adapter.js';
19
+
20
+ export interface AutoDetectOptions {
21
+ readonly model?: string;
22
+ readonly apiKey?: string;
23
+ readonly requestTemplate?: string;
24
+ readonly responsePath?: string;
25
+ readonly headers?: string;
26
+ }
27
+
28
+ /** Parse protocol-hinted URLs like openai://localhost:4000 → http://localhost:4000 */
29
+ const parseProtocolHint = (url: string): { protocol: string; httpUrl: string } | null => {
30
+ const match = url.match(/^(openai|anthropic|ollama):\/\/(.+)/);
31
+ if (!match) return null;
32
+ const protocol = match[1]!;
33
+ const rest = match[2]!;
34
+ const httpUrl = rest.startsWith('http') ? rest : `http://${rest}`;
35
+ return { protocol, httpUrl };
36
+ };
37
+
38
+ const tryFetch = async (url: string, timeout = 3000): Promise<boolean> => {
39
+ try {
40
+ return await withTimeout(async (signal) => {
41
+ const res = await fetch(url, { signal });
42
+ return res.status === 200;
43
+ }, timeout);
44
+ } catch {
45
+ return false;
46
+ }
47
+ };
48
+
49
+ export const autoDetectAdapter = async (
50
+ url: string,
51
+ modelOrOpts?: string | AutoDetectOptions,
52
+ apiKey?: string,
53
+ ): Promise<TargetAdapter> => {
54
+ // Normalize overloaded params
55
+ const opts: AutoDetectOptions = typeof modelOrOpts === 'string'
56
+ ? { model: modelOrOpts, apiKey }
57
+ : modelOrOpts ?? {};
58
+ const model = opts.model;
59
+ const key = opts.apiKey ?? apiKey;
60
+
61
+ // 0. Custom adapter (explicit template)
62
+ if (opts.requestTemplate && opts.responsePath) {
63
+ let template: Record<string, unknown>;
64
+ try {
65
+ template = JSON.parse(opts.requestTemplate);
66
+ } catch {
67
+ throw new Error(`Invalid --request-template JSON: ${opts.requestTemplate}`);
68
+ }
69
+ let customHeaders: Record<string, string> | undefined;
70
+ if (opts.headers) {
71
+ try {
72
+ customHeaders = JSON.parse(opts.headers);
73
+ } catch {
74
+ throw new Error(`Invalid --headers JSON: ${opts.headers}`);
75
+ }
76
+ }
77
+ return createCustomAdapter(url, template, opts.responsePath, customHeaders);
78
+ }
79
+
80
+ // 1. Check protocol hints
81
+ const hint = parseProtocolHint(url);
82
+ if (hint) {
83
+ switch (hint.protocol) {
84
+ case 'openai': return createOpenAIAdapter(hint.httpUrl, model, key);
85
+ case 'anthropic': return createAnthropicAdapter(hint.httpUrl, model, key);
86
+ case 'ollama': return createOllamaAdapter(hint.httpUrl, model);
87
+ }
88
+ }
89
+
90
+ const baseUrl = url.replace(/\/$/, '');
91
+
92
+ // 2. Probe /v1/models (OpenAI-compatible)
93
+ if (await tryFetch(`${baseUrl}/v1/models`)) {
94
+ return createOpenAIAdapter(baseUrl, model, key);
95
+ }
96
+
97
+ // 3. Probe /api/tags (Ollama)
98
+ if (await tryFetch(`${baseUrl}/api/tags`)) {
99
+ return createOllamaAdapter(baseUrl, model);
100
+ }
101
+
102
+ // 4. Fallback to generic HTTP
103
+ return createHttpAdapter(url);
104
+ };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Factory for chat-based eval adapters (OpenAI, Anthropic, Ollama).
3
+ * Eliminates duplicated send/sendMultiTurn/checkHealth boilerplate.
4
+ * Each adapter provides only: body builder, response parser, health config.
5
+ */
6
+ import type { TargetAdapter, TargetResponse, ProbeOptions } from './adapter-port.js';
7
+ import { safeJsonParse, withRetry } from './adapter-port.js';
8
+ import { withTimeout, extractResponseHeaders } from './with-timeout.js';
9
+
10
+ interface ChatMessage {
11
+ readonly role: string;
12
+ readonly content: string;
13
+ }
14
+
15
+ interface HealthCheckConfig {
16
+ readonly url: string;
17
+ readonly method: 'GET' | 'POST';
18
+ readonly headers?: () => Record<string, string>;
19
+ readonly body?: unknown;
20
+ readonly isHealthy: (status: number) => boolean;
21
+ }
22
+
23
+ export interface ChatAdapterConfig {
24
+ readonly name: string;
25
+ readonly endpoint: string;
26
+ readonly defaultTimeout: number;
27
+ readonly buildHeaders: () => Record<string, string>;
28
+ /** Build request body from conversation messages + options. */
29
+ readonly buildBody: (messages: readonly ChatMessage[], options?: ProbeOptions) => unknown;
30
+ /** Extract response text from parsed JSON. */
31
+ readonly extractText: (raw: Record<string, unknown>) => string;
32
+ /** Build initial message list for single send (handles system prompt placement). */
33
+ readonly buildInitialMessages: (probe: string, options?: ProbeOptions) => ChatMessage[];
34
+ /** Build initial message list for multi-turn (conversation prefix). */
35
+ readonly buildMultiTurnPrefix: (options?: ProbeOptions) => ChatMessage[];
36
+ readonly healthCheck: HealthCheckConfig;
37
+ }
38
+
39
+ export const createChatAdapter = (config: ChatAdapterConfig): TargetAdapter => {
40
+ const { endpoint, defaultTimeout, buildHeaders, buildBody, extractText, buildInitialMessages, buildMultiTurnPrefix, healthCheck } = config;
41
+
42
+ const send = async (probe: string, options?: ProbeOptions): Promise<TargetResponse> => {
43
+ const start = Date.now();
44
+ const timeout = options?.timeout ?? defaultTimeout;
45
+ const messages = buildInitialMessages(probe, options);
46
+
47
+ return withRetry(async () => withTimeout(async (signal) => {
48
+ const res = await fetch(endpoint, {
49
+ method: 'POST',
50
+ headers: buildHeaders(),
51
+ body: JSON.stringify(buildBody(messages, options)),
52
+ signal,
53
+ });
54
+ const latencyMs = Date.now() - start;
55
+ const raw = await safeJsonParse(res);
56
+ const text = extractText(raw);
57
+ return { text, status: res.status, headers: extractResponseHeaders(res), latencyMs, raw };
58
+ }, timeout));
59
+ };
60
+
61
+ const sendMultiTurn = async (probes: readonly string[], options?: ProbeOptions): Promise<readonly TargetResponse[]> => {
62
+ const results: TargetResponse[] = [];
63
+ const messages: ChatMessage[] = buildMultiTurnPrefix(options);
64
+
65
+ for (const probe of probes) {
66
+ messages.push({ role: 'user', content: probe });
67
+ const start = Date.now();
68
+ const timeout = options?.timeout ?? defaultTimeout;
69
+
70
+ const result = await withRetry(async () => withTimeout(async (signal) => {
71
+ const res = await fetch(endpoint, {
72
+ method: 'POST',
73
+ headers: buildHeaders(),
74
+ body: JSON.stringify(buildBody([...messages], options)),
75
+ signal,
76
+ });
77
+ const latencyMs = Date.now() - start;
78
+ const raw = await safeJsonParse(res);
79
+ const text = extractText(raw);
80
+ return { text, status: res.status, headers: extractResponseHeaders(res), latencyMs, raw };
81
+ }, timeout));
82
+ messages.push({ role: 'assistant', content: result.text });
83
+ results.push(result);
84
+ }
85
+ return results;
86
+ };
87
+
88
+ const checkHealth = async (): Promise<boolean> => {
89
+ try {
90
+ return await withTimeout(async (signal) => {
91
+ const fetchOpts: RequestInit = { method: healthCheck.method, signal };
92
+ if (healthCheck.headers) fetchOpts.headers = healthCheck.headers();
93
+ if (healthCheck.body) {
94
+ fetchOpts.body = JSON.stringify(healthCheck.body);
95
+ fetchOpts.headers = { ...fetchOpts.headers as Record<string, string>, 'Content-Type': 'application/json' };
96
+ }
97
+ const res = await fetch(healthCheck.url, fetchOpts);
98
+ return healthCheck.isHealthy(res.status);
99
+ }, 5000);
100
+ } catch {
101
+ return false;
102
+ }
103
+ };
104
+
105
+ return Object.freeze({ send, sendMultiTurn, checkHealth, name: config.name });
106
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Custom template-based adapter — sends probes via configurable JSON template
3
+ * and extracts responses via a dot-path.
4
+ */
5
+
6
+ import type { TargetAdapter, TargetResponse, ProbeOptions } from './adapter-port.js';
7
+ import { safeJsonParse, withRetry } from './adapter-port.js';
8
+ import { withTimeout, extractResponseHeaders } from './with-timeout.js';
9
+
10
+ const DEFAULT_TIMEOUT = 30_000;
11
+
12
+ /** Resolve a dot-separated path on an object (e.g. "data.choices.0.text"). */
13
+ const resolvePath = (obj: unknown, path: string): unknown => {
14
+ let current = obj;
15
+ for (const key of path.split('.')) {
16
+ if (current == null || typeof current !== 'object') return undefined;
17
+ current = (current as Record<string, unknown>)[key];
18
+ }
19
+ return current;
20
+ };
21
+
22
+ export const createCustomAdapter = (
23
+ url: string,
24
+ template: Record<string, unknown>,
25
+ responsePath: string,
26
+ headers?: Record<string, string>,
27
+ ): TargetAdapter => {
28
+
29
+ const send = async (probe: string, options?: ProbeOptions): Promise<TargetResponse> => {
30
+ const start = Date.now();
31
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
32
+
33
+ // Replace {{probe}} placeholder in template
34
+ const body = JSON.parse(
35
+ JSON.stringify(template).replace(/\{\{probe\}\}/g, probe.replace(/"/g, '\\"')),
36
+ );
37
+
38
+ return withRetry(async () => withTimeout(async (signal) => {
39
+ const res = await fetch(url, {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json', ...headers },
42
+ body: JSON.stringify(body),
43
+ signal,
44
+ });
45
+ const latencyMs = Date.now() - start;
46
+ const raw = await safeJsonParse(res);
47
+ const resolved = resolvePath(raw, responsePath);
48
+ const text = typeof resolved === 'string' ? resolved : JSON.stringify(resolved ?? '');
49
+
50
+ return { text, status: res.status, headers: extractResponseHeaders(res), latencyMs, raw };
51
+ }, timeout));
52
+ };
53
+
54
+ const sendMultiTurn = async (probes: readonly string[], options?: ProbeOptions): Promise<readonly TargetResponse[]> => {
55
+ const results: TargetResponse[] = [];
56
+ for (const probe of probes) {
57
+ results.push(await send(probe, options));
58
+ }
59
+ return results;
60
+ };
61
+
62
+ const checkHealth = async (): Promise<boolean> => {
63
+ try {
64
+ return await withTimeout(async (signal) => {
65
+ const res = await fetch(url, { method: 'GET', signal });
66
+ return res.status < 500;
67
+ }, 5000);
68
+ } catch {
69
+ return false;
70
+ }
71
+ };
72
+
73
+ return Object.freeze({ send, sendMultiTurn, checkHealth, name: 'custom' });
74
+ };