@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,123 @@
1
+ import { createLogger } from './logger.js';
2
+
3
+ const log = createLogger('saas-client');
4
+
5
+ export interface SyncPassportPayload {
6
+ readonly name: string;
7
+ readonly vendorName?: string;
8
+ readonly vendorUrl?: string;
9
+ readonly description?: string;
10
+ readonly purpose?: string;
11
+ readonly domain?: string;
12
+ readonly riskLevel?: string;
13
+ readonly slug?: string;
14
+ readonly detectionPatterns?: unknown;
15
+ readonly versions?: unknown;
16
+ readonly autonomyLevel?: string;
17
+ readonly framework?: string;
18
+ readonly modelProvider?: string;
19
+ readonly modelId?: string;
20
+ readonly dataResidency?: string;
21
+ readonly lifecycleStatus?: string;
22
+ readonly compliorScore?: number;
23
+ readonly manifestVersion?: string;
24
+ readonly signature?: unknown;
25
+ readonly extendedFields?: unknown;
26
+ }
27
+
28
+ export interface SyncScanPayload {
29
+ readonly projectPath: string;
30
+ readonly score?: number;
31
+ readonly findings?: readonly { severity: string; message: string; tool?: string }[];
32
+ readonly toolsDetected: readonly { name: string; version?: string; vendor?: string; category?: string }[];
33
+ }
34
+
35
+ export interface SyncDocPayload {
36
+ readonly type: string;
37
+ readonly title: string;
38
+ readonly content: string;
39
+ readonly obligationId?: string;
40
+ readonly toolSlug?: string;
41
+ }
42
+
43
+ export interface SaasClient {
44
+ readonly syncPassport: (token: string, payload: SyncPassportPayload) => Promise<Record<string, unknown>>;
45
+ readonly syncScan: (token: string, payload: SyncScanPayload) => Promise<Record<string, unknown>>;
46
+ readonly syncDocuments: (token: string, documents: readonly SyncDocPayload[]) => Promise<Record<string, unknown>>;
47
+ readonly syncFria: (token: string, payload: Record<string, unknown>) => Promise<Record<string, unknown>>;
48
+ readonly syncStatus: (token: string) => Promise<Record<string, unknown>>;
49
+ readonly fetchDataBundle: (etag?: string) => Promise<{ data: Record<string, unknown> | null; etag?: string }>;
50
+ readonly syncAudit: (token: string, entries: readonly Record<string, unknown>[]) => Promise<Record<string, unknown>>;
51
+ readonly syncEvidence: (token: string, summary: Record<string, unknown>) => Promise<Record<string, unknown>>;
52
+ readonly syncRegistry: (token: string, entries: readonly Record<string, unknown>[]) => Promise<Record<string, unknown>>;
53
+ }
54
+
55
+ export const createSaasClient = (baseUrl: string): SaasClient => {
56
+ const url = baseUrl.replace(/\/$/, '');
57
+
58
+ const postJson = async (endpoint: string, token: string, body: unknown): Promise<Record<string, unknown>> => {
59
+ log.debug(`POST ${endpoint}`);
60
+ const resp = await fetch(`${url}${endpoint}`, {
61
+ method: 'POST',
62
+ headers: {
63
+ 'Content-Type': 'application/json',
64
+ 'Authorization': `Bearer ${token}`,
65
+ },
66
+ body: JSON.stringify(body),
67
+ signal: AbortSignal.timeout(30_000),
68
+ });
69
+ if (!resp.ok) {
70
+ const text = await resp.text().catch(() => '');
71
+ throw new Error(`SaaS ${endpoint} failed (${resp.status}): ${text}`);
72
+ }
73
+ const data: Record<string, unknown> = await resp.json();
74
+ return data;
75
+ };
76
+
77
+ const getJson = async (endpoint: string, token: string): Promise<Record<string, unknown>> => {
78
+ log.debug(`GET ${endpoint}`);
79
+ const resp = await fetch(`${url}${endpoint}`, {
80
+ method: 'GET',
81
+ headers: { 'Authorization': `Bearer ${token}` },
82
+ signal: AbortSignal.timeout(30_000),
83
+ });
84
+ if (!resp.ok) {
85
+ const text = await resp.text().catch(() => '');
86
+ throw new Error(`SaaS ${endpoint} failed (${resp.status}): ${text}`);
87
+ }
88
+ const data: Record<string, unknown> = await resp.json();
89
+ return data;
90
+ };
91
+
92
+ return Object.freeze({
93
+ syncPassport: (token: string, payload: SyncPassportPayload) => postJson('/api/sync/passport', token, payload),
94
+ syncScan: (token: string, payload: SyncScanPayload) => postJson('/api/sync/scan', token, payload),
95
+ syncDocuments: (token: string, documents: readonly SyncDocPayload[]) => postJson('/api/sync/documents', token, { documents }),
96
+ syncFria: (token: string, payload: Record<string, unknown>) => postJson('/api/sync/fria', token, payload),
97
+ syncStatus: (token: string) => getJson('/api/sync/status', token),
98
+ syncAudit: (token: string, entries: readonly Record<string, unknown>[]) => postJson('/api/sync/audit', token, { entries }),
99
+ syncEvidence: (token: string, summary: Record<string, unknown>) => postJson('/api/sync/evidence', token, summary),
100
+ syncRegistry: (token: string, entries: readonly Record<string, unknown>[]) => postJson('/api/sync/registry', token, { entries }),
101
+ fetchDataBundle: async (etag?: string) => {
102
+ log.debug('Fetching data bundle');
103
+ const headers: Record<string, string> = {};
104
+ if (etag) headers['If-None-Match'] = etag;
105
+
106
+ const resp = await fetch(`${url}/v1/data/bundle`, {
107
+ method: 'GET',
108
+ headers,
109
+ signal: AbortSignal.timeout(30_000),
110
+ });
111
+
112
+ if (resp.status === 304) {
113
+ return { data: null, etag };
114
+ }
115
+ if (!resp.ok) {
116
+ throw new Error(`Data bundle fetch failed (${resp.status})`);
117
+ }
118
+ const newEtag = resp.headers.get('etag') ?? undefined;
119
+ const data: Record<string, unknown> = await resp.json();
120
+ return { data, etag: newEtag };
121
+ },
122
+ });
123
+ };
@@ -0,0 +1,113 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { readdir, readFile } from 'node:fs/promises';
4
+ import { join, extname } from 'node:path';
5
+ // search utilities
6
+
7
+ const execFileAsync = promisify(execFile);
8
+
9
+ export interface SearchResult {
10
+ readonly file: string;
11
+ readonly line: number;
12
+ readonly content: string;
13
+ }
14
+
15
+ const ripgrepSearch = async (
16
+ pattern: string,
17
+ searchPath: string,
18
+ ): Promise<readonly SearchResult[]> => {
19
+ try {
20
+ const { stdout } = await execFileAsync('rg', [
21
+ '--json',
22
+ '--max-count', '50',
23
+ '--max-filesize', '1M',
24
+ '-g', '!node_modules',
25
+ '-g', '!.git',
26
+ '-g', '!dist',
27
+ pattern,
28
+ searchPath,
29
+ ], { timeout: 10_000, maxBuffer: 5 * 1024 * 1024 });
30
+
31
+ const results: SearchResult[] = [];
32
+
33
+ for (const line of stdout.split('\n')) {
34
+ if (line.trim() === '') continue;
35
+ try {
36
+ const isRec = (v: unknown): v is Record<string, unknown> =>
37
+ typeof v === 'object' && v !== null;
38
+ const raw: unknown = JSON.parse(line);
39
+ if (!isRec(raw) || raw['type'] !== 'match') continue;
40
+ const data = isRec(raw['data']) ? raw['data'] : {};
41
+ const pathObj = isRec(data['path']) ? data['path'] : {};
42
+ const linesObj = isRec(data['lines']) ? data['lines'] : {};
43
+ results.push({
44
+ file: typeof pathObj['text'] === 'string' ? pathObj['text'] : '',
45
+ line: typeof data['line_number'] === 'number' ? data['line_number'] : 0,
46
+ content: typeof linesObj['text'] === 'string' ? linesObj['text'].trim() : '',
47
+ });
48
+ } catch {
49
+ // skip malformed JSON lines
50
+ }
51
+ }
52
+
53
+ return results;
54
+ } catch {
55
+ return [];
56
+ }
57
+ };
58
+
59
+ const SEARCH_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.md', '.json', '.html']);
60
+
61
+ const nativeSearch = async (
62
+ pattern: string,
63
+ searchPath: string,
64
+ ): Promise<readonly SearchResult[]> => {
65
+ const results: SearchResult[] = [];
66
+ const regex = new RegExp(pattern, 'gi');
67
+
68
+ const walk = async (dir: string, depth: number): Promise<void> => {
69
+ if (depth > 8 || results.length > 50) return;
70
+
71
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
72
+
73
+ for (const entry of entries) {
74
+ if (results.length > 50) break;
75
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist') continue;
76
+
77
+ const fullPath = join(dir, entry.name);
78
+
79
+ if (entry.isDirectory()) {
80
+ await walk(fullPath, depth + 1);
81
+ } else if (entry.isFile() && SEARCH_EXTENSIONS.has(extname(entry.name))) {
82
+ try {
83
+ const content = await readFile(fullPath, 'utf-8');
84
+ const lines = content.split('\n');
85
+ for (let i = 0; i < lines.length; i++) {
86
+ if (regex.test(lines[i] ?? '')) {
87
+ results.push({ file: fullPath, line: i + 1, content: (lines[i] ?? '').trim() });
88
+ }
89
+ regex.lastIndex = 0;
90
+ }
91
+ } catch {
92
+ // skip unreadable files
93
+ }
94
+ }
95
+ }
96
+ };
97
+
98
+ await walk(searchPath, 0);
99
+ return results;
100
+ };
101
+
102
+ export const search = async (
103
+ pattern: string,
104
+ searchPath: string,
105
+ ): Promise<readonly SearchResult[]> => {
106
+ // Try ripgrep first, fall back to native
107
+ const rgResults = await ripgrepSearch(pattern, searchPath);
108
+ if (rgResults.length > 0) {
109
+ return rgResults;
110
+ }
111
+
112
+ return nativeSearch(pattern, searchPath);
113
+ };
@@ -0,0 +1,68 @@
1
+ import { exec } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { ToolError } from '../types/errors.js';
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ const ALLOWED_COMMANDS = new Set([
8
+ 'bun', 'npm', 'npx', 'node', 'tsc', 'eslint', 'prettier',
9
+ 'git', 'cargo', 'rustc', 'rustfmt', 'clippy',
10
+ 'ls', 'cat', 'head', 'tail', 'wc', 'find', 'grep', 'rg',
11
+ 'echo', 'pwd', 'which', 'env', 'mkdir', 'cp', 'mv', 'touch',
12
+ ]);
13
+
14
+ const DEFAULT_TIMEOUT = 30_000;
15
+ const MAX_TIMEOUT = 30_000;
16
+
17
+ export interface CommandResult {
18
+ readonly stdout: string;
19
+ readonly stderr: string;
20
+ readonly exitCode: number;
21
+ }
22
+
23
+ const extractFirstToken = (command: string): string => {
24
+ const trimmed = command.trim();
25
+ // Handle env var prefixes like "FOO=bar cmd"
26
+ const withoutEnvVars = trimmed.replace(/^(?:\w+=\S+\s+)+/, '');
27
+ const firstToken = withoutEnvVars.split(/[\s;|&]/)[0] ?? '';
28
+ // Handle paths like /usr/bin/git → git
29
+ return firstToken.split('/').pop() ?? '';
30
+ };
31
+
32
+ export const runCommand = async (
33
+ command: string,
34
+ cwd?: string,
35
+ timeout?: number,
36
+ ): Promise<CommandResult> => {
37
+ const token = extractFirstToken(command);
38
+
39
+ if (!ALLOWED_COMMANDS.has(token)) {
40
+ throw new ToolError(`Command not allowed: "${token}". Allowed: ${[...ALLOWED_COMMANDS].join(', ')}`);
41
+ }
42
+
43
+ const effectiveTimeout = Math.min(timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
44
+
45
+ try {
46
+ const { stdout, stderr } = await execAsync(command, {
47
+ cwd,
48
+ timeout: effectiveTimeout,
49
+ maxBuffer: 5 * 1024 * 1024,
50
+ env: { ...process.env, NODE_ENV: 'development' },
51
+ });
52
+
53
+ return { stdout, stderr, exitCode: 0 };
54
+ } catch (error: unknown) {
55
+ if (error !== null && typeof error === 'object' && 'killed' in error && error.killed) {
56
+ throw new ToolError(`Command timed out after ${effectiveTimeout}ms`);
57
+ }
58
+
59
+ if (error !== null && typeof error === 'object') {
60
+ const stdout = 'stdout' in error && typeof error.stdout === 'string' ? error.stdout : '';
61
+ const stderr = 'stderr' in error && typeof error.stderr === 'string' ? error.stderr : '';
62
+ const code = 'code' in error && typeof error.code === 'number' ? error.code : 1;
63
+ return { stdout, stderr, exitCode: code };
64
+ }
65
+
66
+ return { stdout: '', stderr: String(error), exitCode: 1 };
67
+ }
68
+ };
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { createToolManager, type ProcessRunner } from './tool-manager.js';
3
+
4
+ const createMockRunner = (responses: Record<string, { stdout: string; stderr: string; exitCode: number }>): ProcessRunner => {
5
+ return async (cmd: string, args: readonly string[]) => {
6
+ const key = `${cmd} ${args.join(' ')}`;
7
+ // Find matching response by prefix
8
+ for (const [pattern, response] of Object.entries(responses)) {
9
+ if (key.startsWith(pattern) || key.includes(pattern)) {
10
+ return response;
11
+ }
12
+ }
13
+ return { stdout: '', stderr: 'command not found', exitCode: 127 };
14
+ };
15
+ };
16
+
17
+ describe('createToolManager', () => {
18
+ it('reports uv not available when uv --version fails', async () => {
19
+ const runner = createMockRunner({
20
+ 'uv --version': { stdout: '', stderr: 'not found', exitCode: 127 },
21
+ });
22
+ const mgr = createToolManager('/tmp/tools', runner);
23
+ expect(await mgr.isUvAvailable()).toBe(false);
24
+ });
25
+
26
+ it('reports uv available when uv --version succeeds', async () => {
27
+ const runner = createMockRunner({
28
+ 'uv --version': { stdout: 'uv 0.5.10\n', stderr: '', exitCode: 0 },
29
+ });
30
+ const mgr = createToolManager('/tmp/tools', runner);
31
+ expect(await mgr.isUvAvailable()).toBe(true);
32
+ });
33
+
34
+ it('returns error status for all tools when uv is not available', async () => {
35
+ const runner = createMockRunner({
36
+ 'uv --version': { stdout: '', stderr: 'not found', exitCode: 127 },
37
+ });
38
+ const mgr = createToolManager('/tmp/tools', runner);
39
+ const statuses = await mgr.ensureTools(['semgrep', 'bandit']);
40
+ expect(statuses).toHaveLength(2);
41
+ expect(statuses[0]!.installed).toBe(false);
42
+ expect(statuses[0]!.error).toContain('uv not available');
43
+ expect(statuses[1]!.installed).toBe(false);
44
+ });
45
+
46
+ it('detects already installed tool', async () => {
47
+ const runner = createMockRunner({
48
+ 'uv --version': { stdout: 'uv 0.5.10\n', stderr: '', exitCode: 0 },
49
+ 'uv tool run semgrep --version': { stdout: '1.67.0\n', stderr: '', exitCode: 0 },
50
+ 'uv tool run -- which semgrep': { stdout: '/home/user/.local/bin/semgrep\n', stderr: '', exitCode: 0 },
51
+ });
52
+ const mgr = createToolManager('/tmp/tools', runner);
53
+ const statuses = await mgr.ensureTools(['semgrep']);
54
+ expect(statuses).toHaveLength(1);
55
+ expect(statuses[0]!.installed).toBe(true);
56
+ expect(statuses[0]!.version).toBe('1.67.0');
57
+ });
58
+
59
+ it('installs tool when not already installed', async () => {
60
+ let installCalled = false;
61
+ const runner: ProcessRunner = async (cmd, args) => {
62
+ const key = `${cmd} ${args.join(' ')}`;
63
+ if (key === 'uv --version') return { stdout: 'uv 0.5.10\n', stderr: '', exitCode: 0 };
64
+ if (key.includes('tool run semgrep --version') && !installCalled) return { stdout: '', stderr: 'not installed', exitCode: 1 };
65
+ if (key.includes('tool install semgrep==')) {
66
+ installCalled = true;
67
+ return { stdout: 'Installed semgrep\n', stderr: '', exitCode: 0 };
68
+ }
69
+ if (key.includes('tool run semgrep --version') && installCalled) return { stdout: '1.67.0\n', stderr: '', exitCode: 0 };
70
+ if (key.includes('which semgrep')) return { stdout: '/home/user/.local/bin/semgrep\n', stderr: '', exitCode: 0 };
71
+ return { stdout: '', stderr: 'unknown command', exitCode: 1 };
72
+ };
73
+ const mgr = createToolManager('/tmp/tools', runner);
74
+ const statuses = await mgr.ensureTools(['semgrep']);
75
+ expect(statuses).toHaveLength(1);
76
+ expect(statuses[0]!.installed).toBe(true);
77
+ expect(installCalled).toBe(true);
78
+ });
79
+
80
+ it('getToolPath returns null for unknown tools', () => {
81
+ const runner = createMockRunner({});
82
+ const mgr = createToolManager('/tmp/tools', runner);
83
+ expect(mgr.getToolPath('nonexistent')).toBeNull();
84
+ });
85
+
86
+ it('getToolStatus returns status for all known tools', async () => {
87
+ const runner = createMockRunner({
88
+ 'uv --version': { stdout: 'uv 0.5.10\n', stderr: '', exitCode: 0 },
89
+ 'uv tool run': { stdout: '', stderr: 'not installed', exitCode: 1 },
90
+ });
91
+ const mgr = createToolManager('/tmp/tools', runner);
92
+ const statuses = await mgr.getToolStatus();
93
+ expect(statuses.length).toBeGreaterThanOrEqual(4);
94
+ for (const s of statuses) {
95
+ expect(s.name).toBeTruthy();
96
+ expect(s.expectedVersion).toBeTruthy();
97
+ }
98
+ });
99
+ });
@@ -0,0 +1,197 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { resolve } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { mkdir, readFile } from 'node:fs/promises';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ // Load pinned tool versions at module level
8
+ const TOOL_VERSIONS_PATH = resolve(
9
+ fileURLToPath(import.meta.url), '..', '..', 'data', 'tool-versions.json',
10
+ );
11
+
12
+ export interface ToolVersionEntry {
13
+ readonly package: string;
14
+ readonly version: string;
15
+ readonly description: string;
16
+ }
17
+
18
+ export interface ToolStatus {
19
+ readonly name: string;
20
+ readonly installed: boolean;
21
+ readonly version?: string;
22
+ readonly expectedVersion: string;
23
+ readonly path?: string;
24
+ readonly error?: string;
25
+ }
26
+
27
+ // Re-export ProcessRunner from port for backward compatibility
28
+ export type { ProcessRunner } from '../ports/process.port.js';
29
+
30
+ export interface ToolManager {
31
+ readonly ensureTools: (tools: readonly string[]) => Promise<readonly ToolStatus[]>;
32
+ readonly getToolStatus: () => Promise<readonly ToolStatus[]>;
33
+ readonly updateTools: () => Promise<readonly ToolStatus[]>;
34
+ readonly getToolPath: (name: string) => string | null;
35
+ readonly isUvAvailable: () => Promise<boolean>;
36
+ }
37
+
38
+ /** Default tools dir — user-level, shared across projects, complior-scoped. */
39
+ export const DEFAULT_TOOLS_DIR = resolve(homedir(), '.complior', 'tools');
40
+
41
+ /** Create a process runner that passes UV_TOOL_DIR so uv installs to ~/.complior/tools/. */
42
+ const createUvProcessRunner = (toolsDir: string): ProcessRunner => (cmd, args, options) => {
43
+ return new Promise((resolve) => {
44
+ execFile(cmd, [...args], {
45
+ timeout: options?.timeout ?? 60_000,
46
+ maxBuffer: 10 * 1024 * 1024,
47
+ env: { ...process.env, UV_TOOL_DIR: toolsDir },
48
+ }, (error, stdout, stderr) => {
49
+ resolve({
50
+ stdout: stdout ?? '',
51
+ stderr: stderr ?? '',
52
+ exitCode: error ? (error as NodeJS.ErrnoException & { code?: number }).code === 'ENOENT' ? 127 : (error as { code?: number }).code ?? 1 : 0,
53
+ });
54
+ });
55
+ });
56
+ };
57
+
58
+ export const createToolManager = (
59
+ toolsDir?: string,
60
+ runProcess?: ProcessRunner,
61
+ ): ToolManager => {
62
+ const dir = toolsDir ?? DEFAULT_TOOLS_DIR;
63
+ const run = runProcess ?? createUvProcessRunner(dir);
64
+ const installedPaths = new Map<string, string>();
65
+ let toolVersions: Record<string, ToolVersionEntry> | null = null;
66
+
67
+ const loadVersions = async (): Promise<Record<string, ToolVersionEntry>> => {
68
+ if (toolVersions) return toolVersions;
69
+ const raw = await readFile(TOOL_VERSIONS_PATH, 'utf-8');
70
+ toolVersions = JSON.parse(raw) as Record<string, ToolVersionEntry>;
71
+ return toolVersions;
72
+ };
73
+
74
+ const isUvAvailable = async (): Promise<boolean> => {
75
+ const result = await run('uv', ['--version'], { timeout: 5000 });
76
+ return result.exitCode === 0;
77
+ };
78
+
79
+ const checkInstalled = async (name: string, versions: Record<string, ToolVersionEntry>): Promise<ToolStatus> => {
80
+ const entry = versions[name];
81
+ if (!entry) {
82
+ return { name, installed: false, expectedVersion: 'unknown', error: `Unknown tool: ${name}` };
83
+ }
84
+
85
+ // Check if uv tool is installed — `uv tool run <pkg> --version`
86
+ const result = await run('uv', ['tool', 'run', entry.package, '--version'], { timeout: 15_000 });
87
+ if (result.exitCode === 0) {
88
+ // Extract semver from potentially verbose output (e.g. "bandit 1.7.8\n python version = ...")
89
+ const semverMatch = result.stdout.match(/(\d+\.\d+\.\d+)/);
90
+ const version = semverMatch?.[1] ?? result.stdout.trim().split('\n')[0]?.trim() ?? '';
91
+ // Try to find the tool path
92
+ const whichResult = await run('uv', ['tool', 'run', '--', 'which', entry.package], { timeout: 5000 });
93
+ const toolPath = whichResult.exitCode === 0 ? whichResult.stdout.trim() : undefined;
94
+ if (toolPath) installedPaths.set(name, toolPath);
95
+ return { name, installed: true, version, expectedVersion: entry.version, path: toolPath };
96
+ }
97
+
98
+ return { name, installed: false, expectedVersion: entry.version };
99
+ };
100
+
101
+ const installTool = async (name: string, versions: Record<string, ToolVersionEntry>): Promise<ToolStatus> => {
102
+ const entry = versions[name];
103
+ if (!entry) {
104
+ return { name, installed: false, expectedVersion: 'unknown', error: `Unknown tool: ${name}` };
105
+ }
106
+
107
+ // Ensure tools directory exists
108
+ await mkdir(dir, { recursive: true });
109
+
110
+ // Install via uv tool install
111
+ const result = await run('uv', [
112
+ 'tool', 'install',
113
+ `${entry.package}==${entry.version}`,
114
+ ], { timeout: 120_000 });
115
+
116
+ if (result.exitCode !== 0) {
117
+ // Check if already installed (uv returns non-zero if already installed)
118
+ if (result.stderr.includes('already installed') || result.stderr.includes('is already available')) {
119
+ return checkInstalled(name, versions);
120
+ }
121
+ return {
122
+ name,
123
+ installed: false,
124
+ expectedVersion: entry.version,
125
+ error: `Install failed: ${result.stderr.slice(0, 200)}`,
126
+ };
127
+ }
128
+
129
+ return checkInstalled(name, versions);
130
+ };
131
+
132
+ const ensureTools = async (tools: readonly string[]): Promise<readonly ToolStatus[]> => {
133
+ const versions = await loadVersions();
134
+
135
+ // Check if uv is available
136
+ const uvOk = await isUvAvailable();
137
+ if (!uvOk) {
138
+ return tools.map((name) => ({
139
+ name,
140
+ installed: false,
141
+ expectedVersion: versions[name]?.version ?? 'unknown',
142
+ error: 'uv not available — install with: curl -LsSf https://astral.sh/uv/install.sh | sh',
143
+ }));
144
+ }
145
+
146
+ const results: ToolStatus[] = [];
147
+ for (const name of tools) {
148
+ const status = await checkInstalled(name, versions);
149
+ if (status.installed) {
150
+ results.push(status);
151
+ } else {
152
+ results.push(await installTool(name, versions));
153
+ }
154
+ }
155
+ return results;
156
+ };
157
+
158
+ const getToolStatus = async (): Promise<readonly ToolStatus[]> => {
159
+ const versions = await loadVersions();
160
+ const uvOk = await isUvAvailable();
161
+ if (!uvOk) {
162
+ return Object.keys(versions).map((name) => ({
163
+ name,
164
+ installed: false,
165
+ expectedVersion: versions[name]!.version,
166
+ error: 'uv not available',
167
+ }));
168
+ }
169
+
170
+ return Promise.all(
171
+ Object.keys(versions).map((name) => checkInstalled(name, versions)),
172
+ );
173
+ };
174
+
175
+ const updateTools = async (): Promise<readonly ToolStatus[]> => {
176
+ const versions = await loadVersions();
177
+ const uvOk = await isUvAvailable();
178
+ if (!uvOk) {
179
+ return Object.keys(versions).map((name) => ({
180
+ name,
181
+ installed: false,
182
+ expectedVersion: versions[name]!.version,
183
+ error: 'uv not available',
184
+ }));
185
+ }
186
+
187
+ return Promise.all(
188
+ Object.keys(versions).map((name) => installTool(name, versions)),
189
+ );
190
+ };
191
+
192
+ const getToolPath = (name: string): string | null => {
193
+ return installedPaths.get(name) ?? null;
194
+ };
195
+
196
+ return Object.freeze({ ensureTools, getToolStatus, updateTools, getToolPath, isUvAvailable });
197
+ };
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getAgentConfig, getAllModes, nextMode } from './modes.js';
3
+
4
+ describe('Agent Modes', () => {
5
+ it('provides 4 modes', () => {
6
+ expect(getAllModes()).toEqual(['build', 'comply', 'audit', 'learn']);
7
+ });
8
+
9
+ it('build mode enables writes', () => {
10
+ const config = getAgentConfig('build');
11
+ expect(config.writeEnabled).toBe(true);
12
+ expect(config.label).toBe('BUILD');
13
+ });
14
+
15
+ it('comply mode disables writes', () => {
16
+ const config = getAgentConfig('comply');
17
+ expect(config.writeEnabled).toBe(false);
18
+ });
19
+
20
+ it('audit mode disables writes', () => {
21
+ const config = getAgentConfig('audit');
22
+ expect(config.writeEnabled).toBe(false);
23
+ });
24
+
25
+ it('learn mode disables writes', () => {
26
+ const config = getAgentConfig('learn');
27
+ expect(config.writeEnabled).toBe(false);
28
+ });
29
+
30
+ it('all modes have disclaimer in system prompt', () => {
31
+ for (const mode of getAllModes()) {
32
+ const config = getAgentConfig(mode);
33
+ expect(config.systemPrompt).toContain('not a legal advisor');
34
+ expect(config.systemPrompt).toContain('NEVER');
35
+ }
36
+ });
37
+
38
+ it('nextMode cycles through modes', () => {
39
+ expect(nextMode('build')).toBe('comply');
40
+ expect(nextMode('comply')).toBe('audit');
41
+ expect(nextMode('audit')).toBe('learn');
42
+ expect(nextMode('learn')).toBe('build');
43
+ });
44
+ });