@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.
- package/.well-known/ai-compliance.json +16 -0
- package/COMPLIANCE.md +64 -0
- package/data/data-integrity.test.ts +75 -0
- package/data/eval/eval-mappings.json +33 -0
- package/data/llm/model-pricing.json +15 -0
- package/data/llm/model-routing.json +36 -0
- package/data/onboarding/risk-profile.json +17 -0
- package/data/regulations/eu-ai-act/README.md +245 -0
- package/data/regulations/eu-ai-act/applicability-tree.json +160 -0
- package/data/regulations/eu-ai-act/cross-mapping.json +175 -0
- package/data/regulations/eu-ai-act/localization.json +186 -0
- package/data/regulations/eu-ai-act/obligations.json +3981 -0
- package/data/regulations/eu-ai-act/regulation-meta.json +482 -0
- package/data/regulations/eu-ai-act/scoring.json +342 -0
- package/data/regulations/eu-ai-act/technical-requirements.json +2590 -0
- package/data/regulations/eu-ai-act/timeline.json +160 -0
- package/data/regulations/jurisdictions/at.json +15 -0
- package/data/regulations/jurisdictions/be.json +15 -0
- package/data/regulations/jurisdictions/bg.json +15 -0
- package/data/regulations/jurisdictions/cy.json +15 -0
- package/data/regulations/jurisdictions/cz.json +15 -0
- package/data/regulations/jurisdictions/de.json +15 -0
- package/data/regulations/jurisdictions/dk.json +15 -0
- package/data/regulations/jurisdictions/ee.json +15 -0
- package/data/regulations/jurisdictions/es.json +15 -0
- package/data/regulations/jurisdictions/fi.json +15 -0
- package/data/regulations/jurisdictions/fr.json +15 -0
- package/data/regulations/jurisdictions/gr.json +15 -0
- package/data/regulations/jurisdictions/hr.json +15 -0
- package/data/regulations/jurisdictions/hu.json +15 -0
- package/data/regulations/jurisdictions/ie.json +15 -0
- package/data/regulations/jurisdictions/is.json +15 -0
- package/data/regulations/jurisdictions/it.json +15 -0
- package/data/regulations/jurisdictions/li.json +15 -0
- package/data/regulations/jurisdictions/lt.json +15 -0
- package/data/regulations/jurisdictions/lu.json +15 -0
- package/data/regulations/jurisdictions/lv.json +15 -0
- package/data/regulations/jurisdictions/mt.json +15 -0
- package/data/regulations/jurisdictions/nl.json +15 -0
- package/data/regulations/jurisdictions/no.json +15 -0
- package/data/regulations/jurisdictions/pl.json +15 -0
- package/data/regulations/jurisdictions/pt.json +15 -0
- package/data/regulations/jurisdictions/ro.json +15 -0
- package/data/regulations/jurisdictions/se.json +15 -0
- package/data/regulations/jurisdictions/si.json +15 -0
- package/data/regulations/jurisdictions/sk.json +15 -0
- package/data/scanner/check-id-categories.json +81 -0
- package/data/scanner/confidence-params.json +16 -0
- package/data/scanner/limits.json +4 -0
- package/data/schemas/http-contract-sample.json +79 -0
- package/data/schemas/http-contract.json +144 -0
- package/data/semgrep-rules/bare-call.yaml +37 -0
- package/data/semgrep-rules/injection.yaml +73 -0
- package/data/semgrep-rules/missing-error-handling.yaml +58 -0
- package/data/semgrep-rules/unsafe-deser.yaml +65 -0
- package/data/templates/eu-ai-act/ai-literacy.md +184 -0
- package/data/templates/eu-ai-act/art5-screening.md +131 -0
- package/data/templates/eu-ai-act/data-governance.md +145 -0
- package/data/templates/eu-ai-act/declaration-of-conformity.md +161 -0
- package/data/templates/eu-ai-act/fria.md +127 -0
- package/data/templates/eu-ai-act/gpai-systemic-risk.md +150 -0
- package/data/templates/eu-ai-act/gpai-transparency.md +166 -0
- package/data/templates/eu-ai-act/incident-report.md +188 -0
- package/data/templates/eu-ai-act/instructions-for-use.md +202 -0
- package/data/templates/eu-ai-act/monitoring-policy.md +110 -0
- package/data/templates/eu-ai-act/qms.md +180 -0
- package/data/templates/eu-ai-act/risk-management-system.md +123 -0
- package/data/templates/eu-ai-act/technical-documentation.md +287 -0
- package/data/templates/eu-ai-act/worker-notification.md +143 -0
- package/data/templates/policies/biometrics-ai-policy.md +214 -0
- package/data/templates/policies/critical-infra-ai-policy.md +228 -0
- package/data/templates/policies/education-ai-policy.md +184 -0
- package/data/templates/policies/finance-ai-policy.md +191 -0
- package/data/templates/policies/healthcare-ai-policy.md +197 -0
- package/data/templates/policies/hr-ai-policy.md +178 -0
- package/data/templates/policies/legal-ai-policy.md +189 -0
- package/data/templates/policies/migration-ai-policy.md +239 -0
- package/engine.log +7 -0
- package/package.json +74 -0
- package/src/composition-root.ts +791 -0
- package/src/data/eval/conformity-tests.test.ts +122 -0
- package/src/data/eval/ct-1-transparency.ts +106 -0
- package/src/data/eval/ct-10-gpai.ts +25 -0
- package/src/data/eval/ct-11-industry.ts +42 -0
- package/src/data/eval/ct-2-oversight.ts +41 -0
- package/src/data/eval/ct-3-explanation.ts +14 -0
- package/src/data/eval/ct-4-bias.ts +83 -0
- package/src/data/eval/ct-5-accuracy.ts +41 -0
- package/src/data/eval/ct-6-robustness.ts +81 -0
- package/src/data/eval/ct-7-prohibited.ts +52 -0
- package/src/data/eval/ct-8-logging.ts +68 -0
- package/src/data/eval/ct-9-risk-awareness.ts +33 -0
- package/src/data/eval/deterministic-evaluator.ts +120 -0
- package/src/data/eval/index.ts +55 -0
- package/src/data/eval/judge-prompts.ts +146 -0
- package/src/data/eval/llm-judged-tests.ts +279 -0
- package/src/data/eval/llm-tests.test.ts +83 -0
- package/src/data/eval/remediation/ct-1-transparency.ts +91 -0
- package/src/data/eval/remediation/ct-10-gpai.ts +94 -0
- package/src/data/eval/remediation/ct-11-industry.ts +94 -0
- package/src/data/eval/remediation/ct-2-oversight.ts +71 -0
- package/src/data/eval/remediation/ct-3-explanation.ts +70 -0
- package/src/data/eval/remediation/ct-4-bias.ts +70 -0
- package/src/data/eval/remediation/ct-5-accuracy.ts +70 -0
- package/src/data/eval/remediation/ct-6-robustness.ts +70 -0
- package/src/data/eval/remediation/ct-7-prohibited.ts +94 -0
- package/src/data/eval/remediation/ct-8-logging.ts +94 -0
- package/src/data/eval/remediation/ct-9-risk-awareness.ts +94 -0
- package/src/data/eval/remediation/index.ts +89 -0
- package/src/data/eval/remediation/owasp-art5.ts +15 -0
- package/src/data/eval/remediation/owasp-llm01.ts +72 -0
- package/src/data/eval/remediation/owasp-llm02.ts +72 -0
- package/src/data/eval/remediation/owasp-llm03.ts +15 -0
- package/src/data/eval/remediation/owasp-llm04.ts +15 -0
- package/src/data/eval/remediation/owasp-llm05.ts +15 -0
- package/src/data/eval/remediation/owasp-llm06.ts +15 -0
- package/src/data/eval/remediation/owasp-llm07.ts +15 -0
- package/src/data/eval/remediation/owasp-llm08.ts +15 -0
- package/src/data/eval/remediation/owasp-llm09.ts +15 -0
- package/src/data/eval/remediation/owasp-llm10.ts +15 -0
- package/src/data/eval/remediation/remediation.test.ts +229 -0
- package/src/data/eval/remediation/test-mapping.ts +290 -0
- package/src/data/eval/security-rubrics.ts +381 -0
- package/src/data/finding-explanations.json +453 -0
- package/src/data/industry-patterns.ts +161 -0
- package/src/data/registry-cards.ts +368 -0
- package/src/data/regulation/index.ts +5 -0
- package/src/data/regulation/jurisdiction-data.test.ts +73 -0
- package/src/data/regulation/jurisdiction-data.ts +65 -0
- package/src/data/regulation/regulation-data.ts +19 -0
- package/src/data/regulation/regulation-loader.test.ts +107 -0
- package/src/data/regulation/regulation-loader.ts +56 -0
- package/src/data/scanner-constants.ts +46 -0
- package/src/data/schemas/schemas-core.ts +140 -0
- package/src/data/schemas/schemas-supplementary.ts +211 -0
- package/src/data/schemas/schemas.ts +28 -0
- package/src/data/security/attack-probes.test.ts +62 -0
- package/src/data/security/attack-probes.ts +496 -0
- package/src/data/security/eu-ai-act-security.ts +40 -0
- package/src/data/security/index.ts +19 -0
- package/src/data/security/mitre-atlas.test.ts +43 -0
- package/src/data/security/mitre-atlas.ts +93 -0
- package/src/data/security/nist-ai-rmf.ts +43 -0
- package/src/data/security/owasp-llm-top10.test.ts +60 -0
- package/src/data/security/owasp-llm-top10.ts +138 -0
- package/src/data/template-registry.ts +53 -0
- package/src/data/tool-versions.json +22 -0
- package/src/domain/audit/audit-package.test.ts +152 -0
- package/src/domain/audit/audit-package.ts +166 -0
- package/src/domain/audit/audit-trail.test.ts +121 -0
- package/src/domain/audit/audit-trail.ts +174 -0
- package/src/domain/audit/index.ts +8 -0
- package/src/domain/audit/permissions-matrix.test.ts +136 -0
- package/src/domain/audit/permissions-matrix.ts +121 -0
- package/src/domain/certification/adversarial/bias-tests.ts +95 -0
- package/src/domain/certification/adversarial/evaluators.ts +304 -0
- package/src/domain/certification/adversarial/index.ts +11 -0
- package/src/domain/certification/adversarial/prompt-injection.ts +103 -0
- package/src/domain/certification/adversarial/safety-boundary.ts +132 -0
- package/src/domain/certification/aiuc1-readiness.test.ts +236 -0
- package/src/domain/certification/aiuc1-readiness.ts +298 -0
- package/src/domain/certification/aiuc1-requirements.ts +235 -0
- package/src/domain/certification/index.ts +10 -0
- package/src/domain/certification/redteam-runner.test.ts +97 -0
- package/src/domain/certification/redteam-runner.ts +205 -0
- package/src/domain/certification/test-runner.test.ts +232 -0
- package/src/domain/certification/test-runner.ts +289 -0
- package/src/domain/cost/cost-estimator.test.ts +187 -0
- package/src/domain/cost/cost-estimator.ts +133 -0
- package/src/domain/disclaimer.test.ts +52 -0
- package/src/domain/disclaimer.ts +39 -0
- package/src/domain/documents/ai-enricher.test.ts +120 -0
- package/src/domain/documents/ai-enricher.ts +159 -0
- package/src/domain/documents/document-generator.test.ts +318 -0
- package/src/domain/documents/document-generator.ts +239 -0
- package/src/domain/documents/index.ts +9 -0
- package/src/domain/documents/passport-helpers.ts +25 -0
- package/src/domain/documents/policy-generator.test.ts +252 -0
- package/src/domain/documents/policy-generator.ts +94 -0
- package/src/domain/documents/worker-notification-generator.test.ts +162 -0
- package/src/domain/documents/worker-notification-generator.ts +141 -0
- package/src/domain/eval/adapters/adapter-port.ts +94 -0
- package/src/domain/eval/adapters/adapters.test.ts +303 -0
- package/src/domain/eval/adapters/anthropic-adapter.ts +57 -0
- package/src/domain/eval/adapters/auto-detect.ts +104 -0
- package/src/domain/eval/adapters/create-chat-adapter.ts +106 -0
- package/src/domain/eval/adapters/custom-adapter.ts +74 -0
- package/src/domain/eval/adapters/http-adapter.ts +66 -0
- package/src/domain/eval/adapters/index.ts +7 -0
- package/src/domain/eval/adapters/ollama-adapter.ts +48 -0
- package/src/domain/eval/adapters/openai-adapter.ts +58 -0
- package/src/domain/eval/adapters/with-timeout.ts +25 -0
- package/src/domain/eval/conformity-score.test.ts +161 -0
- package/src/domain/eval/conformity-score.ts +135 -0
- package/src/domain/eval/eval-constants.ts +55 -0
- package/src/domain/eval/eval-evidence.test.ts +85 -0
- package/src/domain/eval/eval-evidence.ts +103 -0
- package/src/domain/eval/eval-fix-generator.test.ts +421 -0
- package/src/domain/eval/eval-fix-generator.ts +205 -0
- package/src/domain/eval/eval-passport.test.ts +82 -0
- package/src/domain/eval/eval-passport.ts +89 -0
- package/src/domain/eval/eval-remediation-report.test.ts +682 -0
- package/src/domain/eval/eval-remediation-report.ts +170 -0
- package/src/domain/eval/eval-report.ts +108 -0
- package/src/domain/eval/eval-runner.test.ts +609 -0
- package/src/domain/eval/eval-runner.ts +593 -0
- package/src/domain/eval/eval-to-findings.test.ts +293 -0
- package/src/domain/eval/eval-to-findings.ts +83 -0
- package/src/domain/eval/index.ts +31 -0
- package/src/domain/eval/llm-judge.test.ts +139 -0
- package/src/domain/eval/llm-judge.ts +168 -0
- package/src/domain/eval/remediation-types.ts +90 -0
- package/src/domain/eval/security-integration.test.ts +196 -0
- package/src/domain/eval/security-integration.ts +136 -0
- package/src/domain/eval/types.test.ts +173 -0
- package/src/domain/eval/types.ts +244 -0
- package/src/domain/eval/verdict-utils.ts +45 -0
- package/src/domain/fixer/create-fixer.ts +101 -0
- package/src/domain/fixer/diff.ts +70 -0
- package/src/domain/fixer/fix-history.ts +23 -0
- package/src/domain/fixer/fixer.test.ts +306 -0
- package/src/domain/fixer/index.ts +9 -0
- package/src/domain/fixer/strategies/bandit-fix.ts +61 -0
- package/src/domain/fixer/strategies/bias-testing.ts +49 -0
- package/src/domain/fixer/strategies/ci-compliance.ts +57 -0
- package/src/domain/fixer/strategies/content-marking.ts +45 -0
- package/src/domain/fixer/strategies/cve-upgrade.ts +66 -0
- package/src/domain/fixer/strategies/data-governance.ts +65 -0
- package/src/domain/fixer/strategies/disclosure.ts +69 -0
- package/src/domain/fixer/strategies/doc-code-sync.ts +53 -0
- package/src/domain/fixer/strategies/documentation.ts +59 -0
- package/src/domain/fixer/strategies/error-handler.ts +63 -0
- package/src/domain/fixer/strategies/hitl-gate.ts +67 -0
- package/src/domain/fixer/strategies/index.ts +61 -0
- package/src/domain/fixer/strategies/kill-switch-test.ts +85 -0
- package/src/domain/fixer/strategies/kill-switch.ts +53 -0
- package/src/domain/fixer/strategies/license-fix.ts +57 -0
- package/src/domain/fixer/strategies/log-retention.ts +40 -0
- package/src/domain/fixer/strategies/logging.ts +59 -0
- package/src/domain/fixer/strategies/metadata.ts +45 -0
- package/src/domain/fixer/strategies/permission-guard.ts +84 -0
- package/src/domain/fixer/strategies/record-keeping.ts +69 -0
- package/src/domain/fixer/strategies/secret-rotation.ts +52 -0
- package/src/domain/fixer/strategies.test.ts +341 -0
- package/src/domain/fixer/template-engine.test.ts +64 -0
- package/src/domain/fixer/template-engine.ts +38 -0
- package/src/domain/fixer/types.ts +88 -0
- package/src/domain/frameworks/aiuc1-framework.test.ts +159 -0
- package/src/domain/frameworks/aiuc1-framework.ts +126 -0
- package/src/domain/frameworks/collect-foundation-metrics.test.ts +96 -0
- package/src/domain/frameworks/collect-foundation-metrics.ts +34 -0
- package/src/domain/frameworks/eu-ai-act-framework.test.ts +117 -0
- package/src/domain/frameworks/eu-ai-act-framework.ts +100 -0
- package/src/domain/frameworks/framework-registry.test.ts +91 -0
- package/src/domain/frameworks/framework-registry.ts +38 -0
- package/src/domain/frameworks/index.ts +8 -0
- package/src/domain/frameworks/mitre-atlas-framework.test.ts +53 -0
- package/src/domain/frameworks/mitre-atlas-framework.ts +53 -0
- package/src/domain/frameworks/owasp-llm-framework.test.ts +77 -0
- package/src/domain/frameworks/owasp-llm-framework.ts +54 -0
- package/src/domain/frameworks/score-plugin-framework.ts +117 -0
- package/src/domain/fria/fria-generator.test.ts +273 -0
- package/src/domain/fria/fria-generator.ts +366 -0
- package/src/domain/import/promptfoo-importer.test.ts +103 -0
- package/src/domain/import/promptfoo-importer.ts +151 -0
- package/src/domain/onboarding/guided-onboarding.test.ts +144 -0
- package/src/domain/onboarding/guided-onboarding.ts +135 -0
- package/src/domain/passport/builder/domain-mapper.ts +9 -0
- package/src/domain/passport/builder/manifest-builder.test.ts +546 -0
- package/src/domain/passport/builder/manifest-builder.ts +535 -0
- package/src/domain/passport/builder/manifest-diff.test.ts +105 -0
- package/src/domain/passport/builder/manifest-diff.ts +89 -0
- package/src/domain/passport/builder/manifest-files.ts +17 -0
- package/src/domain/passport/crypto-signer.test.ts +93 -0
- package/src/domain/passport/crypto-signer.ts +157 -0
- package/src/domain/passport/discovery/agent-discovery.test.ts +296 -0
- package/src/domain/passport/discovery/agent-discovery.ts +325 -0
- package/src/domain/passport/discovery/autonomy-analyzer.test.ts +141 -0
- package/src/domain/passport/discovery/autonomy-analyzer.ts +113 -0
- package/src/domain/passport/discovery/permission-scanner.test.ts +191 -0
- package/src/domain/passport/discovery/permission-scanner.ts +414 -0
- package/src/domain/passport/export/a2a-mapper.ts +75 -0
- package/src/domain/passport/export/aiuc1-mapper.ts +126 -0
- package/src/domain/passport/export/export.test.ts +207 -0
- package/src/domain/passport/export/index.ts +41 -0
- package/src/domain/passport/export/nist-mapper.ts +227 -0
- package/src/domain/passport/import/a2a-importer.test.ts +133 -0
- package/src/domain/passport/import/a2a-importer.ts +156 -0
- package/src/domain/passport/import/index.ts +2 -0
- package/src/domain/passport/index.ts +32 -0
- package/src/domain/passport/obligation-field-map.test.ts +113 -0
- package/src/domain/passport/obligation-field-map.ts +117 -0
- package/src/domain/passport/passport-validator.test.ts +156 -0
- package/src/domain/passport/passport-validator.ts +126 -0
- package/src/domain/passport/scan-to-compliance.test.ts +336 -0
- package/src/domain/passport/scan-to-compliance.ts +166 -0
- package/src/domain/passport/test-generator.test.ts +93 -0
- package/src/domain/passport/test-generator.ts +136 -0
- package/src/domain/proxy/index.ts +11 -0
- package/src/domain/proxy/json-rpc.test.ts +72 -0
- package/src/domain/proxy/json-rpc.ts +53 -0
- package/src/domain/proxy/policy-engine.test.ts +259 -0
- package/src/domain/proxy/policy-engine.ts +137 -0
- package/src/domain/proxy/proxy-bridge.ts +125 -0
- package/src/domain/proxy/proxy-interceptor.test.ts +184 -0
- package/src/domain/proxy/proxy-interceptor.ts +120 -0
- package/src/domain/proxy/proxy-types.ts +35 -0
- package/src/domain/registry/compute-agent-score.test.ts +279 -0
- package/src/domain/registry/compute-agent-score.ts +162 -0
- package/src/domain/reporter/audit-report.test.ts +87 -0
- package/src/domain/reporter/audit-report.ts +116 -0
- package/src/domain/reporter/badge-generator.test.ts +54 -0
- package/src/domain/reporter/badge-generator.ts +40 -0
- package/src/domain/reporter/compliance-md.ts +45 -0
- package/src/domain/reporter/index.ts +7 -0
- package/src/domain/reporter/pdf-renderer.ts +282 -0
- package/src/domain/reporter/share.test.ts +92 -0
- package/src/domain/reporter/share.ts +80 -0
- package/src/domain/scanner/ast/swc-analyzer.test.ts +49 -0
- package/src/domain/scanner/ast/swc-analyzer.ts +124 -0
- package/src/domain/scanner/attestations.ts +97 -0
- package/src/domain/scanner/checks/ai-disclosure.test.ts +90 -0
- package/src/domain/scanner/checks/ai-disclosure.ts +54 -0
- package/src/domain/scanner/checks/ai-literacy.ts +163 -0
- package/src/domain/scanner/checks/behavioral-constraints.test.ts +167 -0
- package/src/domain/scanner/checks/behavioral-constraints.ts +86 -0
- package/src/domain/scanner/checks/compliance-metadata.ts +63 -0
- package/src/domain/scanner/checks/content-marking.ts +74 -0
- package/src/domain/scanner/checks/dep-deep-scan.test.ts +318 -0
- package/src/domain/scanner/checks/dep-deep-scan.ts +137 -0
- package/src/domain/scanner/checks/documentation.test.ts +88 -0
- package/src/domain/scanner/checks/documentation.ts +79 -0
- package/src/domain/scanner/checks/git-history.test.ts +120 -0
- package/src/domain/scanner/checks/git-history.ts +163 -0
- package/src/domain/scanner/checks/gpai-systemic-risk.test.ts +84 -0
- package/src/domain/scanner/checks/gpai-systemic-risk.ts +98 -0
- package/src/domain/scanner/checks/gpai-transparency.ts +94 -0
- package/src/domain/scanner/checks/index.ts +28 -0
- package/src/domain/scanner/checks/industry/index.ts +40 -0
- package/src/domain/scanner/checks/industry/industry.test.ts +287 -0
- package/src/domain/scanner/checks/interaction-logging.test.ts +113 -0
- package/src/domain/scanner/checks/interaction-logging.ts +142 -0
- package/src/domain/scanner/checks/nhi-scanner.test.ts +158 -0
- package/src/domain/scanner/checks/nhi-scanner.ts +78 -0
- package/src/domain/scanner/checks/passport-completeness.test.ts +127 -0
- package/src/domain/scanner/checks/passport-completeness.ts +82 -0
- package/src/domain/scanner/checks/passport-presence.test.ts +56 -0
- package/src/domain/scanner/checks/passport-presence.ts +78 -0
- package/src/domain/scanner/checks/pattern-check-factory.ts +70 -0
- package/src/domain/scanner/checks/permission-scanner.test.ts +279 -0
- package/src/domain/scanner/checks/permission-scanner.ts +90 -0
- package/src/domain/scanner/checks/presence-check-factory.test.ts +124 -0
- package/src/domain/scanner/checks/presence-check-factory.ts +275 -0
- package/src/domain/scanner/compliance-diff.test.ts +165 -0
- package/src/domain/scanner/compliance-diff.ts +138 -0
- package/src/domain/scanner/confidence.test.ts +235 -0
- package/src/domain/scanner/confidence.ts +156 -0
- package/src/domain/scanner/constants.ts +13 -0
- package/src/domain/scanner/create-scanner.ts +573 -0
- package/src/domain/scanner/cross-layer.test.ts +372 -0
- package/src/domain/scanner/cross-layer.ts +232 -0
- package/src/domain/scanner/data/ai-packages.ts +82 -0
- package/src/domain/scanner/debt-calculator.test.ts +89 -0
- package/src/domain/scanner/debt-calculator.ts +111 -0
- package/src/domain/scanner/drift.test.ts +191 -0
- package/src/domain/scanner/drift.ts +73 -0
- package/src/domain/scanner/evidence-store.test.ts +207 -0
- package/src/domain/scanner/evidence-store.ts +195 -0
- package/src/domain/scanner/evidence.test.ts +104 -0
- package/src/domain/scanner/evidence.ts +71 -0
- package/src/domain/scanner/external/bandit-runner.test.ts +45 -0
- package/src/domain/scanner/external/bandit-runner.ts +90 -0
- package/src/domain/scanner/external/checks.ts +321 -0
- package/src/domain/scanner/external/dedup.test.ts +79 -0
- package/src/domain/scanner/external/dedup.ts +94 -0
- package/src/domain/scanner/external/detect-secrets-runner.test.ts +58 -0
- package/src/domain/scanner/external/detect-secrets-runner.ts +81 -0
- package/src/domain/scanner/external/external-scanner.test.ts +221 -0
- package/src/domain/scanner/external/external-scanner.ts +36 -0
- package/src/domain/scanner/external/finding-mapper.test.ts +95 -0
- package/src/domain/scanner/external/finding-mapper.ts +138 -0
- package/src/domain/scanner/external/index.ts +15 -0
- package/src/domain/scanner/external/mappings.ts +93 -0
- package/src/domain/scanner/external/modelscan-runner.test.ts +35 -0
- package/src/domain/scanner/external/modelscan-runner.ts +101 -0
- package/src/domain/scanner/external/path-utils.ts +8 -0
- package/src/domain/scanner/external/runner-port.ts +45 -0
- package/src/domain/scanner/external/semgrep-runner.test.ts +52 -0
- package/src/domain/scanner/external/semgrep-runner.ts +94 -0
- package/src/domain/scanner/external/types.ts +32 -0
- package/src/domain/scanner/finding-attribution.test.ts +444 -0
- package/src/domain/scanner/finding-attribution.ts +195 -0
- package/src/domain/scanner/finding-explainer.test.ts +157 -0
- package/src/domain/scanner/finding-explainer.ts +73 -0
- package/src/domain/scanner/fix-diff-builder.test.ts +272 -0
- package/src/domain/scanner/fix-diff-builder.ts +477 -0
- package/src/domain/scanner/import-graph.test.ts +162 -0
- package/src/domain/scanner/import-graph.ts +198 -0
- package/src/domain/scanner/languages/adapter.test.ts +105 -0
- package/src/domain/scanner/languages/adapter.ts +239 -0
- package/src/domain/scanner/layers/index.ts +24 -0
- package/src/domain/scanner/layers/layer1-files.ts +54 -0
- package/src/domain/scanner/layers/layer2-docs.test.ts +1207 -0
- package/src/domain/scanner/layers/layer2-docs.ts +297 -0
- package/src/domain/scanner/layers/layer2-parsing.ts +217 -0
- package/src/domain/scanner/layers/layer3-config.test.ts +187 -0
- package/src/domain/scanner/layers/layer3-config.ts +279 -0
- package/src/domain/scanner/layers/layer3-parsers.ts +73 -0
- package/src/domain/scanner/layers/layer4-patterns.test.ts +397 -0
- package/src/domain/scanner/layers/layer4-patterns.ts +216 -0
- package/src/domain/scanner/layers/layer5-docs.test.ts +99 -0
- package/src/domain/scanner/layers/layer5-docs.ts +250 -0
- package/src/domain/scanner/layers/layer5-llm.test.ts +146 -0
- package/src/domain/scanner/layers/layer5-llm.ts +262 -0
- package/src/domain/scanner/layers/layer5-targeted.test.ts +93 -0
- package/src/domain/scanner/layers/layer5-targeted.ts +233 -0
- package/src/domain/scanner/layers/lockfile-parsers.test.ts +320 -0
- package/src/domain/scanner/layers/lockfile-parsers.ts +184 -0
- package/src/domain/scanner/regulation-version.test.ts +54 -0
- package/src/domain/scanner/regulation-version.ts +23 -0
- package/src/domain/scanner/role-filter.test.ts +116 -0
- package/src/domain/scanner/role-filter.ts +51 -0
- package/src/domain/scanner/rules/banned-packages-data.ts +553 -0
- package/src/domain/scanner/rules/banned-packages-sdk.ts +65 -0
- package/src/domain/scanner/rules/banned-packages.test.ts +249 -0
- package/src/domain/scanner/rules/banned-packages.ts +55 -0
- package/src/domain/scanner/rules/comment-filter.test.ts +115 -0
- package/src/domain/scanner/rules/comment-filter.ts +297 -0
- package/src/domain/scanner/rules/index.ts +9 -0
- package/src/domain/scanner/rules/nhi-patterns.test.ts +128 -0
- package/src/domain/scanner/rules/nhi-patterns.ts +60 -0
- package/src/domain/scanner/rules/pattern-rules.ts +1152 -0
- package/src/domain/scanner/sbom.test.ts +136 -0
- package/src/domain/scanner/sbom.ts +103 -0
- package/src/domain/scanner/scan-cache.test.ts +136 -0
- package/src/domain/scanner/scan-cache.ts +115 -0
- package/src/domain/scanner/scanner.test.ts +125 -0
- package/src/domain/scanner/score-calculator.test.ts +363 -0
- package/src/domain/scanner/score-calculator.ts +189 -0
- package/src/domain/scanner/security-score.test.ts +107 -0
- package/src/domain/scanner/security-score.ts +116 -0
- package/src/domain/scanner/source-filter.ts +24 -0
- package/src/domain/scanner/validators.ts +223 -0
- package/src/domain/shared/compliance-constants.ts +48 -0
- package/src/domain/shared/disclosure-patterns.ts +16 -0
- package/src/domain/shared/index.ts +6 -0
- package/src/domain/shared/parse-dependencies.ts +21 -0
- package/src/domain/supply-chain/dependency-analyzer.ts +138 -0
- package/src/domain/supply-chain/index.ts +3 -0
- package/src/domain/supply-chain/supply-chain.test.ts +211 -0
- package/src/domain/supply-chain/types.ts +32 -0
- package/src/domain/whatif/config-fixer.ts +187 -0
- package/src/domain/whatif/index.ts +6 -0
- package/src/domain/whatif/scenario-engine.ts +121 -0
- package/src/domain/whatif/simulate-actions.test.ts +161 -0
- package/src/domain/whatif/simulate-actions.ts +114 -0
- package/src/domain/whatif/whatif.test.ts +135 -0
- package/src/e2e/gaps-e2e.test.ts +259 -0
- package/src/e2e/smoke.test.ts +101 -0
- package/src/hooks/hooks-export.test.ts +81 -0
- package/src/hooks/installer.ts +113 -0
- package/src/http/cors.test.ts +38 -0
- package/src/http/create-router.ts +259 -0
- package/src/http/routes/agent.route.ts +380 -0
- package/src/http/routes/audit.route.ts +66 -0
- package/src/http/routes/badge.route.ts +23 -0
- package/src/http/routes/cert.route.ts +66 -0
- package/src/http/routes/chat.route.ts +228 -0
- package/src/http/routes/cost.route.ts +33 -0
- package/src/http/routes/debt.route.ts +29 -0
- package/src/http/routes/disclaimer.route.ts +64 -0
- package/src/http/routes/eval.route.ts +161 -0
- package/src/http/routes/events.route.test.ts +108 -0
- package/src/http/routes/events.route.ts +71 -0
- package/src/http/routes/external-scan.route.ts +24 -0
- package/src/http/routes/file.route.ts +54 -0
- package/src/http/routes/fix.route.ts +219 -0
- package/src/http/routes/frameworks.route.test.ts +66 -0
- package/src/http/routes/frameworks.route.ts +36 -0
- package/src/http/routes/git.route.ts +27 -0
- package/src/http/routes/guided-onboarding.route.ts +65 -0
- package/src/http/routes/import.route.ts +64 -0
- package/src/http/routes/jurisdiction.route.ts +22 -0
- package/src/http/routes/obligations.route.test.ts +122 -0
- package/src/http/routes/obligations.route.ts +110 -0
- package/src/http/routes/onboarding.route.ts +53 -0
- package/src/http/routes/provider.route.ts +42 -0
- package/src/http/routes/proxy.route.ts +40 -0
- package/src/http/routes/redteam.route.ts +84 -0
- package/src/http/routes/report.route.ts +29 -0
- package/src/http/routes/scan.route.ts +104 -0
- package/src/http/routes/share.route.ts +44 -0
- package/src/http/routes/shell.route.ts +27 -0
- package/src/http/routes/status.route.ts +66 -0
- package/src/http/routes/supply-chain.route.ts +121 -0
- package/src/http/routes/sync.route.ts +328 -0
- package/src/http/routes/tools.route.ts +29 -0
- package/src/http/routes/whatif.route.ts +96 -0
- package/src/http/utils/validation.ts +31 -0
- package/src/index.ts +1 -0
- package/src/infra/bundle-fetcher.ts +77 -0
- package/src/infra/cache-storage.ts +34 -0
- package/src/infra/event-bus.ts +31 -0
- package/src/infra/file-collector.ts +61 -0
- package/src/infra/file-ops-adapter.ts +95 -0
- package/src/infra/file-watcher.test.ts +90 -0
- package/src/infra/file-watcher.ts +106 -0
- package/src/infra/git-adapter.ts +93 -0
- package/src/infra/git-history-adapter.ts +41 -0
- package/src/infra/headless-browser.ts +178 -0
- package/src/infra/llm-adapter.test.ts +83 -0
- package/src/infra/llm-adapter.ts +86 -0
- package/src/infra/logger.ts +27 -0
- package/src/infra/project-config.test.ts +74 -0
- package/src/infra/project-config.ts +35 -0
- package/src/infra/rate-limiter.test.ts +36 -0
- package/src/infra/rate-limiter.ts +34 -0
- package/src/infra/retry.ts +46 -0
- package/src/infra/saas-client.ts +123 -0
- package/src/infra/search-adapter.ts +113 -0
- package/src/infra/shell-adapter.ts +68 -0
- package/src/infra/tool-manager.test.ts +99 -0
- package/src/infra/tool-manager.ts +197 -0
- package/src/llm/agents/agent-modes.test.ts +44 -0
- package/src/llm/agents/modes.ts +68 -0
- package/src/llm/routing/cost-routing.test.ts +37 -0
- package/src/llm/routing/cost-tracker.ts +74 -0
- package/src/llm/routing/model-routing.test.ts +79 -0
- package/src/llm/routing/model-routing.ts +38 -0
- package/src/llm/routing/pricing.ts +19 -0
- package/src/llm/sse-protocol.ts +77 -0
- package/src/llm/tool-definitions.ts +83 -0
- package/src/llm/tool-executors.ts +80 -0
- package/src/llm/tools/types.ts +13 -0
- package/src/mcp/create-mcp-stack.ts +82 -0
- package/src/mcp/handlers.ts +245 -0
- package/src/mcp/index.ts +28 -0
- package/src/mcp/mcp-server.test.ts +80 -0
- package/src/mcp/server.ts +79 -0
- package/src/mcp/tools.ts +48 -0
- package/src/onboarding/auto-detect.ts +164 -0
- package/src/onboarding/onboarding.test.ts +89 -0
- package/src/onboarding/profile.ts +169 -0
- package/src/onboarding/questions.ts +112 -0
- package/src/onboarding/wizard.ts +66 -0
- package/src/output/github-issue.ts +32 -0
- package/src/output/json-output.ts +67 -0
- package/src/ports/browser.port.ts +23 -0
- package/src/ports/events.port.ts +28 -0
- package/src/ports/llm.port.ts +23 -0
- package/src/ports/logger.port.ts +6 -0
- package/src/ports/process.port.ts +6 -0
- package/src/ports/scanner.port.ts +15 -0
- package/src/server.ts +134 -0
- package/src/services/badge-service.ts +67 -0
- package/src/services/chat-service.test.ts +162 -0
- package/src/services/chat-service.ts +152 -0
- package/src/services/cost-service.ts +52 -0
- package/src/services/debt-service.ts +65 -0
- package/src/services/eval-integration.test.ts +132 -0
- package/src/services/eval-service.test.ts +373 -0
- package/src/services/eval-service.ts +463 -0
- package/src/services/external-scan-service.ts +60 -0
- package/src/services/file-service.ts +37 -0
- package/src/services/fix-service.test.ts +470 -0
- package/src/services/fix-service.ts +648 -0
- package/src/services/framework-service.test.ts +159 -0
- package/src/services/framework-service.ts +67 -0
- package/src/services/onboarding-service.ts +165 -0
- package/src/services/passport-audit.ts +244 -0
- package/src/services/passport-documents.ts +258 -0
- package/src/services/passport-service-utils.ts +72 -0
- package/src/services/passport-service.test.ts +251 -0
- package/src/services/passport-service.ts +339 -0
- package/src/services/proxy-service.ts +81 -0
- package/src/services/report-service.ts +72 -0
- package/src/services/scan-service.test.ts +470 -0
- package/src/services/scan-service.ts +335 -0
- package/src/services/share-service.ts +108 -0
- package/src/services/shared/backup.ts +23 -0
- package/src/services/status-service.ts +38 -0
- package/src/services/undo-service.test.ts +190 -0
- package/src/services/undo-service.ts +144 -0
- package/src/test-helpers/factories.ts +116 -0
- package/src/types/common.schemas.ts +147 -0
- package/src/types/common.types.ts +292 -0
- package/src/types/contract.test.ts +217 -0
- package/src/types/errors.ts +52 -0
- package/src/types/framework.types.ts +87 -0
- package/src/types/passport-schemas.ts +241 -0
- package/src/types/passport.types.ts +296 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +9 -0
package/src/server.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { mkdir, writeFile, unlinkSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { serve } from '@hono/node-server';
|
|
4
|
+
import { loadApplication } from './composition-root.js';
|
|
5
|
+
import { createLogger } from './infra/logger.js';
|
|
6
|
+
import { withRetry } from './infra/retry.js';
|
|
7
|
+
import { ENGINE_VERSION } from './version.js';
|
|
8
|
+
|
|
9
|
+
// ── Load user's .complior/.env (API keys for LLM judge, fix --ai, etc.) ────────
|
|
10
|
+
// Users place their API keys in <project>/.complior/.env.
|
|
11
|
+
// We never load the project root .env (it may contain PORT, DATABASE_URL, etc. for the user's own app).
|
|
12
|
+
const loadEnvFile = (filePath: string): void => {
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
15
|
+
for (const line of content.split('\n')) {
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
18
|
+
const eqIdx = trimmed.indexOf('=');
|
|
19
|
+
if (eqIdx < 1) continue;
|
|
20
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
21
|
+
const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
22
|
+
if (process.env[key] === undefined) {
|
|
23
|
+
process.env[key] = val;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// File doesn't exist — expected
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const projectDir = process.env['COMPLIOR_PROJECT_PATH'] ?? process.cwd();
|
|
32
|
+
loadEnvFile(resolve(projectDir, '.complior', '.env'));
|
|
33
|
+
|
|
34
|
+
const log = createLogger('server');
|
|
35
|
+
const PORT = Number(process.env['PORT'] ?? 3099);
|
|
36
|
+
const isMcpMode = process.argv.includes('mcp-server');
|
|
37
|
+
|
|
38
|
+
const pidFilePath = process.env['COMPLIOR_PID_FILE'] ?? '';
|
|
39
|
+
const watchMode = process.env['COMPLIOR_WATCH'] === '1';
|
|
40
|
+
|
|
41
|
+
const writePidFile = (port: number): void => {
|
|
42
|
+
if (!pidFilePath) return;
|
|
43
|
+
const info = JSON.stringify({
|
|
44
|
+
pid: process.pid,
|
|
45
|
+
port,
|
|
46
|
+
started_at: new Date().toISOString(),
|
|
47
|
+
});
|
|
48
|
+
mkdir(dirname(pidFilePath), { recursive: true }, (mkdirErr) => {
|
|
49
|
+
if (mkdirErr) {
|
|
50
|
+
log.warn(`Failed to create PID dir: ${mkdirErr.message}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
writeFile(pidFilePath, info, (writeErr) => {
|
|
54
|
+
if (writeErr) log.warn(`Failed to write PID file: ${writeErr.message}`);
|
|
55
|
+
else log.info(`PID file written: ${pidFilePath}`);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const removePidFile = (): void => {
|
|
61
|
+
if (!pidFilePath) return;
|
|
62
|
+
try {
|
|
63
|
+
unlinkSync(pidFilePath);
|
|
64
|
+
log.info('PID file removed');
|
|
65
|
+
} catch {
|
|
66
|
+
// Already gone or never written — fine
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const startHttp = async (): Promise<void> => {
|
|
71
|
+
log.info('Loading regulation data...');
|
|
72
|
+
const { app, startWatcher } = await loadApplication();
|
|
73
|
+
|
|
74
|
+
const server = serve({ fetch: app.fetch, port: PORT }, () => {
|
|
75
|
+
log.info(`Complior Engine v${ENGINE_VERSION} running on http://127.0.0.1:${PORT}`);
|
|
76
|
+
|
|
77
|
+
// Write PID file after server binds (daemon mode)
|
|
78
|
+
writePidFile(PORT);
|
|
79
|
+
|
|
80
|
+
// Start file watcher: always in daemon watch mode, or legacy default
|
|
81
|
+
if (watchMode) {
|
|
82
|
+
startWatcher();
|
|
83
|
+
log.info('File watcher started (daemon mode)');
|
|
84
|
+
} else {
|
|
85
|
+
// US-S0202: start file watcher for Compliance Gate (legacy default)
|
|
86
|
+
startWatcher();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Background: fetch data bundle from SaaS (5s delay, then every 5min)
|
|
91
|
+
const saasUrl = process.env['PROJECT_API_URL'] ?? '';
|
|
92
|
+
if (saasUrl && process.env['OFFLINE_MODE'] !== '1') {
|
|
93
|
+
const { createBundleFetcher } = await import('./infra/bundle-fetcher.js');
|
|
94
|
+
const cacheDir = process.env['COMPLIOR_CACHE_DIR'] ?? '/tmp/complior-cache';
|
|
95
|
+
const bundleFetcher = createBundleFetcher(saasUrl, cacheDir);
|
|
96
|
+
const fetchBundle = () => withRetry(() => bundleFetcher.fetchIfUpdated()).catch((err: unknown) => { log.warn('Background bundle fetch failed:', err); });
|
|
97
|
+
setTimeout(fetchBundle, 5_000);
|
|
98
|
+
setInterval(fetchBundle, 300_000);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const shutdown = (): void => {
|
|
102
|
+
log.info('Graceful shutdown...');
|
|
103
|
+
removePidFile();
|
|
104
|
+
server.close(() => {
|
|
105
|
+
log.info('Server closed');
|
|
106
|
+
process.exit(0);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
process.on('SIGINT', shutdown);
|
|
111
|
+
process.on('SIGTERM', shutdown);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const startMcp = async (): Promise<void> => {
|
|
115
|
+
const application = await loadApplication();
|
|
116
|
+
const { state, setLastScanResult } = application;
|
|
117
|
+
const { createMcpStack } = await import('./mcp/create-mcp-stack.js');
|
|
118
|
+
|
|
119
|
+
const { mcpServer } = await createMcpStack({
|
|
120
|
+
regulationData: state.regulationData,
|
|
121
|
+
projectPath: state.projectPath,
|
|
122
|
+
getLastScanResult: () => state.lastScanResult,
|
|
123
|
+
setLastScanResult,
|
|
124
|
+
version: state.version,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await mcpServer.start();
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const main = isMcpMode ? startMcp : startHttp;
|
|
131
|
+
main().catch((err: unknown) => {
|
|
132
|
+
log.error(`Failed to start engine${isMcpMode ? ' (MCP mode)' : ''}:`, err);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { resolve, dirname } from 'node:path';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import type { ScanResult } from '../types/common.types.js';
|
|
4
|
+
import type { EventBusPort } from '../ports/events.port.js';
|
|
5
|
+
import { generateBadgeSvg } from '../domain/reporter/badge-generator.js';
|
|
6
|
+
import { generateComplianceMd } from '../domain/reporter/compliance-md.js';
|
|
7
|
+
|
|
8
|
+
export interface BadgeServiceDeps {
|
|
9
|
+
readonly events: EventBusPort;
|
|
10
|
+
readonly getProjectPath: () => string;
|
|
11
|
+
readonly getLastScanResult: () => ScanResult | null;
|
|
12
|
+
readonly getVersion: () => string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const createBadgeService = (deps: BadgeServiceDeps) => {
|
|
16
|
+
const { events, getProjectPath, getLastScanResult, getVersion } = deps;
|
|
17
|
+
|
|
18
|
+
let cachedSvg = '';
|
|
19
|
+
|
|
20
|
+
const generateBadge = async (): Promise<{ svg: string; md: string; embedCode: string }> => {
|
|
21
|
+
const scanResult = getLastScanResult();
|
|
22
|
+
if (!scanResult) {
|
|
23
|
+
throw new Error('No scan result available. Run a scan first.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { score } = scanResult;
|
|
27
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
28
|
+
const svg = generateBadgeSvg(score.totalScore, score.zone, 'EU', date);
|
|
29
|
+
const md = generateComplianceMd(scanResult, getVersion());
|
|
30
|
+
|
|
31
|
+
const projectPath = getProjectPath();
|
|
32
|
+
const badgePath = resolve(projectPath, '.complior', 'badge.svg');
|
|
33
|
+
const mdPath = resolve(projectPath, 'COMPLIANCE.md');
|
|
34
|
+
|
|
35
|
+
await mkdir(dirname(badgePath), { recursive: true });
|
|
36
|
+
await writeFile(badgePath, svg, 'utf-8');
|
|
37
|
+
await writeFile(mdPath, md, 'utf-8');
|
|
38
|
+
|
|
39
|
+
cachedSvg = svg;
|
|
40
|
+
|
|
41
|
+
events.emit('badge.generated', {
|
|
42
|
+
path: badgePath,
|
|
43
|
+
score: score.totalScore,
|
|
44
|
+
zone: score.zone,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const embedCode = ``;
|
|
48
|
+
|
|
49
|
+
return { svg, md, embedCode };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getBadgeSvg = (): string => {
|
|
53
|
+
if (cachedSvg) return cachedSvg;
|
|
54
|
+
|
|
55
|
+
const scanResult = getLastScanResult();
|
|
56
|
+
if (!scanResult) return '';
|
|
57
|
+
|
|
58
|
+
const { score } = scanResult;
|
|
59
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
60
|
+
cachedSvg = generateBadgeSvg(score.totalScore, score.zone, 'EU', date);
|
|
61
|
+
return cachedSvg;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return Object.freeze({ generateBadge, getBadgeSvg });
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type BadgeService = ReturnType<typeof createBadgeService>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import type { CoreMessage } from 'ai';
|
|
6
|
+
import { createChatService, type ChatServiceDeps } from './chat-service.js';
|
|
7
|
+
|
|
8
|
+
const createTestDeps = (overrides: Partial<ChatServiceDeps> = {}): ChatServiceDeps => {
|
|
9
|
+
const history: CoreMessage[] = [];
|
|
10
|
+
return {
|
|
11
|
+
getConversationHistory: () => history,
|
|
12
|
+
appendConversationHistory: (msg) => { history.push(msg); },
|
|
13
|
+
getProjectPath: () => '/test/project',
|
|
14
|
+
getVersion: () => '1.0.0-test',
|
|
15
|
+
getLastScanResult: () => null,
|
|
16
|
+
getRegulationData: () => ({
|
|
17
|
+
obligations: { obligations: [] },
|
|
18
|
+
scoring: undefined,
|
|
19
|
+
}),
|
|
20
|
+
getPassportSummary: async () => null,
|
|
21
|
+
getChatHistoryPath: () => '/tmp/test-chat-history.json',
|
|
22
|
+
...overrides,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('ChatService', () => {
|
|
27
|
+
describe('buildSystemPrompt', () => {
|
|
28
|
+
it('includes deadline', async () => {
|
|
29
|
+
const svc = createChatService(createTestDeps());
|
|
30
|
+
const prompt = await svc.buildSystemPrompt();
|
|
31
|
+
expect(prompt).toContain('EU AI Act full enforcement deadline: 2 August 2026');
|
|
32
|
+
expect(prompt).toMatch(/\d+d left/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('includes passport data when available', async () => {
|
|
36
|
+
const svc = createChatService(createTestDeps({
|
|
37
|
+
getPassportSummary: async () => ({
|
|
38
|
+
name: 'test-agent',
|
|
39
|
+
type: 'chatbot',
|
|
40
|
+
riskClass: 'high',
|
|
41
|
+
autonomyLevel: 'L3',
|
|
42
|
+
completeness: 75,
|
|
43
|
+
}),
|
|
44
|
+
}));
|
|
45
|
+
const prompt = await svc.buildSystemPrompt();
|
|
46
|
+
expect(prompt).toContain('Agent Passport');
|
|
47
|
+
expect(prompt).toContain('Name: test-agent');
|
|
48
|
+
expect(prompt).toContain('Risk class: high');
|
|
49
|
+
expect(prompt).toContain('Autonomy level: L3');
|
|
50
|
+
expect(prompt).toContain('Completeness: 75%');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('includes top findings from last scan', async () => {
|
|
54
|
+
const svc = createChatService(createTestDeps({
|
|
55
|
+
getLastScanResult: () => ({
|
|
56
|
+
score: { totalScore: 42, zone: 'red', breakdown: {} as never },
|
|
57
|
+
filesScanned: 10,
|
|
58
|
+
findings: [
|
|
59
|
+
{ checkId: 'l1-risk', message: 'Missing risk assessment', severity: 'critical', type: 'fail', layer: 'L1', category: 'risk' },
|
|
60
|
+
{ checkId: 'l2-fria', message: 'Incomplete FRIA', severity: 'high', type: 'fail', layer: 'L2', category: 'docs' },
|
|
61
|
+
{ checkId: 'l3-sdk', message: 'No SDK wrapper', severity: 'medium', type: 'fail', layer: 'L3', category: 'code' },
|
|
62
|
+
],
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
} as never),
|
|
65
|
+
}));
|
|
66
|
+
const prompt = await svc.buildSystemPrompt();
|
|
67
|
+
expect(prompt).toContain('[CRITICAL] l1-risk: Missing risk assessment');
|
|
68
|
+
expect(prompt).toContain('[HIGH] l2-fria: Incomplete FRIA');
|
|
69
|
+
// medium severity should not be in top findings
|
|
70
|
+
expect(prompt).not.toContain('l3-sdk');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('skips passport section when unavailable', async () => {
|
|
74
|
+
const svc = createChatService(createTestDeps());
|
|
75
|
+
const prompt = await svc.buildSystemPrompt();
|
|
76
|
+
expect(prompt).not.toContain('Agent Passport');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('loadHistory / saveHistory', () => {
|
|
81
|
+
let tmpDir: string;
|
|
82
|
+
|
|
83
|
+
beforeEach(async () => {
|
|
84
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'chat-test-'));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(async () => {
|
|
88
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('round-trips chat history through disk', async () => {
|
|
92
|
+
const historyPath = join(tmpDir, 'chat-history.json');
|
|
93
|
+
const history: CoreMessage[] = [];
|
|
94
|
+
|
|
95
|
+
const svc = createChatService(createTestDeps({
|
|
96
|
+
getConversationHistory: () => history,
|
|
97
|
+
appendConversationHistory: (msg) => { history.push(msg); },
|
|
98
|
+
getChatHistoryPath: () => historyPath,
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
// Add messages
|
|
102
|
+
svc.appendConversationHistory({ role: 'user', content: 'Hello' });
|
|
103
|
+
svc.appendConversationHistory({ role: 'assistant', content: 'Hi there!' });
|
|
104
|
+
await svc.saveHistory();
|
|
105
|
+
|
|
106
|
+
// Verify file exists
|
|
107
|
+
const raw = await readFile(historyPath, 'utf-8');
|
|
108
|
+
const saved = JSON.parse(raw) as CoreMessage[];
|
|
109
|
+
expect(saved).toHaveLength(2);
|
|
110
|
+
expect(saved[0]!.role).toBe('user');
|
|
111
|
+
expect(saved[1]!.role).toBe('assistant');
|
|
112
|
+
|
|
113
|
+
// Load into a fresh service
|
|
114
|
+
const history2: CoreMessage[] = [];
|
|
115
|
+
const svc2 = createChatService(createTestDeps({
|
|
116
|
+
getConversationHistory: () => history2,
|
|
117
|
+
appendConversationHistory: (msg) => { history2.push(msg); },
|
|
118
|
+
getChatHistoryPath: () => historyPath,
|
|
119
|
+
}));
|
|
120
|
+
await svc2.loadHistory();
|
|
121
|
+
expect(history2).toHaveLength(2);
|
|
122
|
+
expect(history2[0]!.content).toBe('Hello');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('truncates history to 100 messages', async () => {
|
|
126
|
+
const historyPath = join(tmpDir, 'chat-history.json');
|
|
127
|
+
|
|
128
|
+
// Write 120 messages directly
|
|
129
|
+
const messages: CoreMessage[] = Array.from({ length: 120 }, (_, i) => ({
|
|
130
|
+
role: (i % 2 === 0 ? 'user' : 'assistant') as 'user' | 'assistant',
|
|
131
|
+
content: `Message ${i}`,
|
|
132
|
+
}));
|
|
133
|
+
const { writeFile } = await import('node:fs/promises');
|
|
134
|
+
await writeFile(historyPath, JSON.stringify(messages), 'utf-8');
|
|
135
|
+
|
|
136
|
+
const history: CoreMessage[] = [];
|
|
137
|
+
const svc = createChatService(createTestDeps({
|
|
138
|
+
getConversationHistory: () => history,
|
|
139
|
+
appendConversationHistory: (msg) => { history.push(msg); },
|
|
140
|
+
getChatHistoryPath: () => historyPath,
|
|
141
|
+
}));
|
|
142
|
+
await svc.loadHistory();
|
|
143
|
+
|
|
144
|
+
// Should only load last 100
|
|
145
|
+
expect(history).toHaveLength(100);
|
|
146
|
+
expect(history[0]!.content).toBe('Message 20');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('handles missing history file gracefully', async () => {
|
|
150
|
+
const historyPath = join(tmpDir, 'nonexistent.json');
|
|
151
|
+
const history: CoreMessage[] = [];
|
|
152
|
+
|
|
153
|
+
const svc = createChatService(createTestDeps({
|
|
154
|
+
getConversationHistory: () => history,
|
|
155
|
+
appendConversationHistory: (msg) => { history.push(msg); },
|
|
156
|
+
getChatHistoryPath: () => historyPath,
|
|
157
|
+
}));
|
|
158
|
+
await svc.loadHistory();
|
|
159
|
+
expect(history).toHaveLength(0);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createLogger } from '../infra/logger.js';
|
|
5
|
+
import type { CoreMessage } from 'ai';
|
|
6
|
+
import type { ScanResult } from '../types/common.types.js';
|
|
7
|
+
import type { PassportSummary } from '../types/passport.types.js';
|
|
8
|
+
import type { RegulationData } from '../data/regulation/regulation-loader.js';
|
|
9
|
+
import { EU_AI_ACT_DEADLINE } from '../domain/shared/compliance-constants.js';
|
|
10
|
+
|
|
11
|
+
/** Maximum chat messages retained in history (disk + memory). */
|
|
12
|
+
const MAX_HISTORY = 100;
|
|
13
|
+
|
|
14
|
+
/** Maximum top findings shown in system prompt. */
|
|
15
|
+
const MAX_TOP_FINDINGS = 5;
|
|
16
|
+
|
|
17
|
+
/** Zod schema for validating CoreMessage from disk. */
|
|
18
|
+
const CoreMessageSchema = z.object({
|
|
19
|
+
role: z.enum(['user', 'assistant', 'system', 'tool']),
|
|
20
|
+
content: z.unknown(),
|
|
21
|
+
});
|
|
22
|
+
const ChatHistorySchema = z.array(CoreMessageSchema);
|
|
23
|
+
|
|
24
|
+
export interface ChatServiceDeps {
|
|
25
|
+
readonly getConversationHistory: () => CoreMessage[];
|
|
26
|
+
readonly appendConversationHistory: (message: CoreMessage) => void;
|
|
27
|
+
readonly getProjectPath: () => string;
|
|
28
|
+
readonly getVersion: () => string;
|
|
29
|
+
readonly getLastScanResult: () => ScanResult | null;
|
|
30
|
+
readonly getRegulationData: () => RegulationData;
|
|
31
|
+
readonly getPassportSummary: () => Promise<PassportSummary | null>;
|
|
32
|
+
readonly getChatHistoryPath: () => string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const log = createLogger('chat-service');
|
|
36
|
+
|
|
37
|
+
export const createChatService = (deps: ChatServiceDeps) => {
|
|
38
|
+
const {
|
|
39
|
+
getConversationHistory,
|
|
40
|
+
appendConversationHistory,
|
|
41
|
+
getProjectPath,
|
|
42
|
+
getVersion,
|
|
43
|
+
getLastScanResult,
|
|
44
|
+
getRegulationData,
|
|
45
|
+
getPassportSummary,
|
|
46
|
+
getChatHistoryPath,
|
|
47
|
+
} = deps;
|
|
48
|
+
|
|
49
|
+
const buildSystemPrompt = async (): Promise<string> => {
|
|
50
|
+
const parts: string[] = [];
|
|
51
|
+
|
|
52
|
+
parts.push('You are Complior — an EU AI Act compliance assistant and AI coding agent.');
|
|
53
|
+
parts.push('You help developers ensure their AI systems comply with the EU AI Act regulation.');
|
|
54
|
+
parts.push('');
|
|
55
|
+
parts.push('## Capabilities');
|
|
56
|
+
parts.push('You have access to tools for scanning projects, reading/writing files, searching code, running commands, and git operations.');
|
|
57
|
+
parts.push('When asked to analyze or fix compliance issues, use the available tools to inspect the codebase and make changes.');
|
|
58
|
+
parts.push('');
|
|
59
|
+
parts.push('## Guidelines');
|
|
60
|
+
parts.push('- Always think step-by-step before acting');
|
|
61
|
+
parts.push('- Read files before editing them');
|
|
62
|
+
parts.push('- After making changes that affect compliance, run a scan to verify the impact');
|
|
63
|
+
parts.push('- Be precise and concise in explanations');
|
|
64
|
+
parts.push('- Reference specific EU AI Act articles when relevant');
|
|
65
|
+
parts.push('');
|
|
66
|
+
parts.push(`## Current Context`);
|
|
67
|
+
parts.push(`- Project path: ${getProjectPath()}`);
|
|
68
|
+
parts.push(`- Engine version: ${getVersion()}`);
|
|
69
|
+
|
|
70
|
+
// Deadline
|
|
71
|
+
const daysLeft = Math.max(0, Math.ceil((EU_AI_ACT_DEADLINE.getTime() - Date.now()) / (1000 * 60 * 60 * 24)));
|
|
72
|
+
parts.push(`- EU AI Act full enforcement deadline: 2 August 2026 (${daysLeft}d left)`);
|
|
73
|
+
|
|
74
|
+
const lastScan = getLastScanResult();
|
|
75
|
+
if (lastScan) {
|
|
76
|
+
const { totalScore, zone } = lastScan.score;
|
|
77
|
+
parts.push(`- Last scan score: ${totalScore}/100 (${zone} zone)`);
|
|
78
|
+
parts.push(`- Files scanned: ${lastScan.filesScanned}`);
|
|
79
|
+
|
|
80
|
+
// Top critical/high findings
|
|
81
|
+
const topFindings = lastScan.findings
|
|
82
|
+
.filter(f => f.severity === 'critical' || f.severity === 'high')
|
|
83
|
+
.slice(0, MAX_TOP_FINDINGS);
|
|
84
|
+
if (topFindings.length > 0) {
|
|
85
|
+
parts.push('');
|
|
86
|
+
parts.push('## Top Findings');
|
|
87
|
+
for (const f of topFindings) {
|
|
88
|
+
parts.push(`- [${f.severity.toUpperCase()}] ${f.checkId}: ${f.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Passport summary
|
|
94
|
+
try {
|
|
95
|
+
const passport = await getPassportSummary();
|
|
96
|
+
if (passport) {
|
|
97
|
+
parts.push('');
|
|
98
|
+
parts.push('## Agent Passport');
|
|
99
|
+
parts.push(`- Name: ${passport.name}`);
|
|
100
|
+
parts.push(`- Type: ${passport.type}`);
|
|
101
|
+
parts.push(`- Risk class: ${passport.riskClass}`);
|
|
102
|
+
parts.push(`- Autonomy level: ${passport.autonomyLevel}`);
|
|
103
|
+
parts.push(`- Completeness: ${passport.completeness}%`);
|
|
104
|
+
}
|
|
105
|
+
} catch { /* passport unavailable — skip */ }
|
|
106
|
+
|
|
107
|
+
const regulationData = getRegulationData();
|
|
108
|
+
const obligations = regulationData.obligations.obligations;
|
|
109
|
+
if (obligations.length > 0) {
|
|
110
|
+
parts.push('');
|
|
111
|
+
parts.push('## Key EU AI Act Obligations');
|
|
112
|
+
for (const ob of obligations.slice(0, 10)) {
|
|
113
|
+
const riskLevels = ob.applies_to_risk_level.join(', ');
|
|
114
|
+
parts.push(`- ${ob.obligation_id}: ${ob.title} (${riskLevels})`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return parts.join('\n');
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const loadHistory = async (): Promise<void> => {
|
|
122
|
+
try {
|
|
123
|
+
const raw = await readFile(getChatHistoryPath(), 'utf-8');
|
|
124
|
+
const parsed = ChatHistorySchema.safeParse(JSON.parse(raw));
|
|
125
|
+
if (!parsed.success) { log.warn('Invalid chat history on disk:', parsed.error.message); return; }
|
|
126
|
+
const recent = parsed.data.slice(-MAX_HISTORY);
|
|
127
|
+
for (const msg of recent) {
|
|
128
|
+
appendConversationHistory(msg as CoreMessage);
|
|
129
|
+
}
|
|
130
|
+
} catch { /* no history file — expected on first run */ }
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const saveHistory = async (): Promise<void> => {
|
|
134
|
+
const historyPath = getChatHistoryPath();
|
|
135
|
+
const history = getConversationHistory().slice(-MAX_HISTORY);
|
|
136
|
+
try {
|
|
137
|
+
await mkdir(dirname(historyPath), { recursive: true });
|
|
138
|
+
await writeFile(historyPath, JSON.stringify(history), 'utf-8');
|
|
139
|
+
} catch { /* ignore write errors */ }
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return Object.freeze({
|
|
143
|
+
buildSystemPrompt,
|
|
144
|
+
getConversationHistory,
|
|
145
|
+
appendConversationHistory,
|
|
146
|
+
getProjectPath,
|
|
147
|
+
loadHistory,
|
|
148
|
+
saveHistory,
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export type ChatService = ReturnType<typeof createChatService>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-S05-27: Cost Estimation Service
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates cost estimation by gathering scan results, passport completeness,
|
|
5
|
+
* and evidence chain state, then delegates to the pure domain function.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ScanResult } from '../types/common.types.js';
|
|
9
|
+
import type { CostEstimateResult } from '../domain/cost/cost-estimator.js';
|
|
10
|
+
import { computeCostEstimate } from '../domain/cost/cost-estimator.js';
|
|
11
|
+
import { DEFAULT_HOURLY_RATE } from '../domain/shared/compliance-constants.js';
|
|
12
|
+
|
|
13
|
+
export interface CostServiceDeps {
|
|
14
|
+
readonly getLastScanResult: () => ScanResult | null;
|
|
15
|
+
readonly getPassportCompleteness: (
|
|
16
|
+
name?: string,
|
|
17
|
+
) => Promise<{ score: number; friaCompleted: boolean }>;
|
|
18
|
+
readonly getEvidenceValid: () => Promise<boolean>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CostService {
|
|
22
|
+
readonly estimate: (
|
|
23
|
+
hourlyRate?: number,
|
|
24
|
+
agentName?: string,
|
|
25
|
+
) => Promise<CostEstimateResult>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const createCostService = (deps: CostServiceDeps): CostService => {
|
|
29
|
+
const estimate = async (
|
|
30
|
+
hourlyRate = DEFAULT_HOURLY_RATE,
|
|
31
|
+
agentName?: string,
|
|
32
|
+
): Promise<CostEstimateResult> => {
|
|
33
|
+
const scan = deps.getLastScanResult();
|
|
34
|
+
const findings = scan?.findings ?? [];
|
|
35
|
+
const completeness = await deps.getPassportCompleteness(agentName);
|
|
36
|
+
const evidenceValid = await deps.getEvidenceValid();
|
|
37
|
+
|
|
38
|
+
return computeCostEstimate({
|
|
39
|
+
findings: findings.map((f) => ({
|
|
40
|
+
severity: f.severity,
|
|
41
|
+
checkId: f.checkId,
|
|
42
|
+
status: f.type ?? 'fail',
|
|
43
|
+
})),
|
|
44
|
+
passportCompleteness: completeness.score,
|
|
45
|
+
friaCompleted: completeness.friaCompleted,
|
|
46
|
+
evidenceValid,
|
|
47
|
+
hourlyRate,
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return Object.freeze({ estimate });
|
|
52
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-S05-22: Compliance Debt Service
|
|
3
|
+
*
|
|
4
|
+
* Application service that gathers inputs from scan results, passport, and
|
|
5
|
+
* evidence store, then delegates to the pure domain computeDebt function.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ScanResult } from '../types/common.types.js';
|
|
9
|
+
import type { DebtResult } from '../domain/scanner/debt-calculator.js';
|
|
10
|
+
import { computeDebt } from '../domain/scanner/debt-calculator.js';
|
|
11
|
+
|
|
12
|
+
export interface DebtServiceDeps {
|
|
13
|
+
readonly getLastScanResult: () => ScanResult | null;
|
|
14
|
+
readonly getPassportCompleteness: () => Promise<number>;
|
|
15
|
+
readonly getEvidenceFreshness: () => Promise<number>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Extended result with optional trend data (previous debt score). */
|
|
19
|
+
export interface DebtResultWithTrend extends DebtResult {
|
|
20
|
+
readonly previousDebt?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DebtService {
|
|
24
|
+
readonly getDebt: (includeTrend?: boolean) => Promise<DebtResultWithTrend>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const createDebtService = (deps: DebtServiceDeps): DebtService => {
|
|
28
|
+
/** In-memory previous debt score for trend comparison. */
|
|
29
|
+
let previousDebt: number | undefined;
|
|
30
|
+
|
|
31
|
+
const getDebt = async (includeTrend = false): Promise<DebtResultWithTrend> => {
|
|
32
|
+
const scan = deps.getLastScanResult();
|
|
33
|
+
const findings = scan?.findings ?? [];
|
|
34
|
+
const passportCompleteness = await deps.getPassportCompleteness();
|
|
35
|
+
const evidenceFreshness = await deps.getEvidenceFreshness();
|
|
36
|
+
|
|
37
|
+
// Calculate days since last scan
|
|
38
|
+
const daysSinceLastScan = scan?.scannedAt
|
|
39
|
+
? Math.max(0, (Date.now() - new Date(scan.scannedAt).getTime()) / (1000 * 60 * 60 * 24))
|
|
40
|
+
: 30;
|
|
41
|
+
|
|
42
|
+
const result = computeDebt({
|
|
43
|
+
findings: findings.map((f) => ({
|
|
44
|
+
severity: f.severity,
|
|
45
|
+
status: f.type ?? 'fail',
|
|
46
|
+
checkId: f.checkId,
|
|
47
|
+
createdAt: undefined,
|
|
48
|
+
})),
|
|
49
|
+
passportCompleteness,
|
|
50
|
+
evidenceFreshness,
|
|
51
|
+
daysSinceLastScan: Math.round(daysSinceLastScan),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const withTrend: DebtResultWithTrend = includeTrend && previousDebt !== undefined
|
|
55
|
+
? { ...result, previousDebt }
|
|
56
|
+
: result;
|
|
57
|
+
|
|
58
|
+
// Store current for next trend comparison
|
|
59
|
+
previousDebt = result.totalDebt;
|
|
60
|
+
|
|
61
|
+
return withTrend;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return Object.freeze({ getDebt });
|
|
65
|
+
};
|