@vercel/factory 0.0.13 → 0.0.15
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/README.md +307 -1
- package/dist/agent-routes.d.mts +69 -0
- package/dist/agent-routes.mjs +64 -0
- package/dist/agent-routes.mjs.map +1 -0
- package/dist/api-contracts.d.mts +1749 -0
- package/dist/api-contracts.mjs +118 -0
- package/dist/api-contracts.mjs.map +1 -0
- package/dist/api-images.d.mts +30 -0
- package/dist/api-images.mjs +28 -0
- package/dist/api-images.mjs.map +1 -0
- package/dist/api-task-graph.mjs +57 -0
- package/dist/api-task-graph.mjs.map +1 -0
- package/dist/api.d.mts +77 -0
- package/dist/api.mjs +1087 -0
- package/dist/api.mjs.map +1 -0
- package/dist/approval-access.d.mts +39 -0
- package/dist/approval-access.mjs +62 -0
- package/dist/approval-access.mjs.map +1 -0
- package/dist/approval-contracts.d.mts +692 -0
- package/dist/approval-contracts.mjs +27 -0
- package/dist/approval-contracts.mjs.map +1 -0
- package/dist/approval.d.mts +49 -0
- package/dist/approval.mjs +39 -0
- package/dist/approval.mjs.map +1 -0
- package/dist/blob/artifacts.d.mts +37 -0
- package/dist/blob/artifacts.mjs +45 -0
- package/dist/blob/artifacts.mjs.map +1 -0
- package/dist/blob/index.d.mts +44 -8
- package/dist/blob/index.mjs +159 -35
- package/dist/blob/index.mjs.map +1 -1
- package/dist/budget.d.mts +108 -0
- package/dist/budget.mjs +141 -0
- package/dist/budget.mjs.map +1 -0
- package/dist/budgets.d.mts +4 -0
- package/dist/budgets.mjs +4 -0
- package/dist/build-factory.d.mts +6 -4
- package/dist/build-factory.mjs +2 -1
- package/dist/build-factory.mjs.map +1 -1
- package/dist/change-verification/brief.d.mts +9 -0
- package/dist/change-verification/brief.mjs +13 -0
- package/dist/change-verification/brief.mjs.map +1 -0
- package/dist/change-verification/dispatch.d.mts +24 -0
- package/dist/change-verification/dispatch.mjs +45 -0
- package/dist/change-verification/dispatch.mjs.map +1 -0
- package/dist/change-verification/eve-tool.d.mts +60 -0
- package/dist/change-verification/eve-tool.mjs +101 -0
- package/dist/change-verification/eve-tool.mjs.map +1 -0
- package/dist/changes/eve-record-change.d.mts +56 -0
- package/dist/changes/eve-record-change.mjs +97 -0
- package/dist/changes/eve-record-change.mjs.map +1 -0
- package/dist/changes/presentation.d.mts +14 -0
- package/dist/changes.d.mts +23 -0
- package/dist/changes.mjs +29 -0
- package/dist/changes.mjs.map +1 -0
- package/dist/client-events.d.mts +18 -0
- package/dist/client-events.mjs +41 -0
- package/dist/client-events.mjs.map +1 -0
- package/dist/client-stream.d.mts +28 -0
- package/dist/client-stream.mjs +204 -0
- package/dist/client-stream.mjs.map +1 -0
- package/dist/client-transcript.d.mts +41 -0
- package/dist/client-transcript.mjs +229 -0
- package/dist/client-transcript.mjs.map +1 -0
- package/dist/client.d.mts +234 -0
- package/dist/client.mjs +258 -0
- package/dist/client.mjs.map +1 -0
- package/dist/code-review/contracts.d.mts +26 -0
- package/dist/code-review/eve-post-review.d.mts +70 -0
- package/dist/code-review/eve-post-review.mjs +183 -0
- package/dist/code-review/eve-post-review.mjs.map +1 -0
- package/dist/code-review/eve-review-comments.d.mts +64 -0
- package/dist/code-review/eve-review-comments.mjs +159 -0
- package/dist/code-review/eve-review-comments.mjs.map +1 -0
- package/dist/code-review/github-contracts.d.mts +10 -0
- package/dist/code-review/github-reporter.d.mts +44 -0
- package/dist/code-review/github-reporter.mjs +260 -0
- package/dist/code-review/github-reporter.mjs.map +1 -0
- package/dist/code-review/preparation.d.mts +34 -0
- package/dist/code-review/preparation.mjs +23 -0
- package/dist/code-review/preparation.mjs.map +1 -0
- package/dist/code-review/stall.d.mts +17 -0
- package/dist/code-review/stall.mjs +30 -0
- package/dist/code-review/stall.mjs.map +1 -0
- package/dist/code-review.d.mts +6 -0
- package/dist/code-review.mjs +5 -0
- package/dist/communication.d.mts +9 -0
- package/dist/communication.mjs +9 -0
- package/dist/connectors/github.d.mts +26 -0
- package/dist/connectors/github.mjs +55 -0
- package/dist/connectors/github.mjs.map +1 -0
- package/dist/connectors/index.d.mts +35 -2
- package/dist/connectors/index.mjs +4 -26
- package/dist/connectors/index.mjs.map +1 -1
- package/dist/connectors/outbox.d.mts +34 -0
- package/dist/connectors/outbox.mjs +50 -0
- package/dist/connectors/outbox.mjs.map +1 -0
- package/dist/connectors/slack-approval-blocks.mjs +136 -0
- package/dist/connectors/slack-approval-blocks.mjs.map +1 -0
- package/dist/connectors/slack-approvals.d.mts +29 -0
- package/dist/connectors/slack-approvals.mjs +346 -0
- package/dist/connectors/slack-approvals.mjs.map +1 -0
- package/dist/connectors/slack.d.mts +35 -41
- package/dist/connectors/slack.mjs +13 -69
- package/dist/connectors/slack.mjs.map +1 -1
- package/dist/deepsec/eve-tool.d.mts +110 -0
- package/dist/deepsec/eve-tool.mjs +164 -0
- package/dist/deepsec/eve-tool.mjs.map +1 -0
- package/dist/deepsec/finding.d.mts +84 -0
- package/dist/deepsec/finding.mjs +56 -0
- package/dist/deepsec/finding.mjs.map +1 -0
- package/dist/deepsec.d.mts +3 -0
- package/dist/delivery-metrics/metrics.d.mts +93 -0
- package/dist/delivery-metrics/metrics.mjs +200 -0
- package/dist/delivery-metrics/metrics.mjs.map +1 -0
- package/dist/delivery-metrics.d.mts +2 -0
- package/dist/delivery-metrics.mjs +2 -0
- package/dist/dispatch.d.mts +81 -0
- package/dist/dispatch.mjs +140 -0
- package/dist/dispatch.mjs.map +1 -0
- package/dist/errors.d.mts +15 -0
- package/dist/errors.mjs +38 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/eve/index.d.mts +143 -1561
- package/dist/eve/index.mjs +244 -1063
- package/dist/eve/index.mjs.map +1 -1
- package/dist/eve/invoke.d.mts +144 -0
- package/dist/eve/invoke.mjs +241 -0
- package/dist/eve/invoke.mjs.map +1 -0
- package/dist/eve/session-client.d.mts +68 -0
- package/dist/eve/session-client.mjs +131 -0
- package/dist/eve/session-client.mjs.map +1 -0
- package/dist/eve/task-execution.mjs +18 -0
- package/dist/eve/task-execution.mjs.map +1 -0
- package/dist/eve/task-session.d.mts +87 -0
- package/dist/eve/task-session.mjs +203 -0
- package/dist/eve/task-session.mjs.map +1 -0
- package/dist/eve/transcript.d.mts +37 -0
- package/dist/eve/transcript.mjs +65 -0
- package/dist/eve/transcript.mjs.map +1 -0
- package/dist/execution-public.d.mts +8 -0
- package/dist/execution-public.mjs +7 -0
- package/dist/execution.d.mts +75 -0
- package/dist/execution.mjs +153 -0
- package/dist/execution.mjs.map +1 -0
- package/dist/finding-remediation/admission.d.mts +51 -0
- package/dist/finding-remediation/admission.mjs +87 -0
- package/dist/finding-remediation/admission.mjs.map +1 -0
- package/dist/finding-remediation/brief.d.mts +14 -0
- package/dist/finding-remediation/brief.mjs +25 -0
- package/dist/finding-remediation/brief.mjs.map +1 -0
- package/dist/finding-remediation/eve-tool.d.mts +29 -0
- package/dist/finding-remediation/eve-tool.mjs +34 -0
- package/dist/finding-remediation/eve-tool.mjs.map +1 -0
- package/dist/findings/fingerprint.d.mts +14 -0
- package/dist/findings/fingerprint.mjs +28 -0
- package/dist/findings/fingerprint.mjs.map +1 -0
- package/dist/findings.d.mts +6 -0
- package/dist/findings.mjs +6 -0
- package/dist/github-app.d.mts +54 -0
- package/dist/github-app.mjs +121 -0
- package/dist/github-app.mjs.map +1 -0
- package/dist/github-publication.d.mts +198 -0
- package/dist/github-publication.mjs +429 -0
- package/dist/github-publication.mjs.map +1 -0
- package/dist/github-transfer.d.mts +72 -0
- package/dist/github-transfer.mjs +125 -0
- package/dist/github-transfer.mjs.map +1 -0
- package/dist/github.d.mts +406 -2
- package/dist/github.mjs +250 -37
- package/dist/github.mjs.map +1 -1
- package/dist/image-input.d.mts +51 -0
- package/dist/image-input.mjs +63 -0
- package/dist/image-input.mjs.map +1 -0
- package/dist/index.d.mts +14 -314
- package/dist/index.mjs +14 -1124
- package/dist/intake-contracts.d.mts +56 -0
- package/dist/intake-contracts.mjs +33 -0
- package/dist/intake-contracts.mjs.map +1 -0
- package/dist/integrations/deepsec.d.mts +4 -0
- package/dist/integrations/deepsec.mjs +3 -0
- package/dist/integrations/github.d.mts +11 -0
- package/dist/integrations/github.mjs +9 -0
- package/dist/integrations/slack.d.mts +3 -0
- package/dist/integrations/slack.mjs +3 -0
- package/dist/integrations/vercel.d.mts +3 -0
- package/dist/integrations/vercel.mjs +3 -0
- package/dist/merge-resolution/contracts.d.mts +17 -0
- package/dist/merge-resolution/contracts.mjs +19 -0
- package/dist/merge-resolution/contracts.mjs.map +1 -0
- package/dist/merge-resolution/eve-tools.d.mts +46 -0
- package/dist/merge-resolution/eve-tools.mjs +117 -0
- package/dist/merge-resolution/eve-tools.mjs.map +1 -0
- package/dist/multi-repository-changes/coordination.d.mts +49 -0
- package/dist/multi-repository-changes/coordination.mjs +115 -0
- package/dist/multi-repository-changes/coordination.mjs.map +1 -0
- package/dist/planning/cancellation.mjs +82 -0
- package/dist/planning/cancellation.mjs.map +1 -0
- package/dist/planning/configuration.d.mts +23 -0
- package/dist/planning/configuration.mjs +56 -0
- package/dist/planning/configuration.mjs.map +1 -0
- package/dist/planning/contracts.d.mts +136 -0
- package/dist/planning/contracts.mjs +42 -0
- package/dist/planning/contracts.mjs.map +1 -0
- package/dist/planning/dependencies.mjs +44 -0
- package/dist/planning/dependencies.mjs.map +1 -0
- package/dist/planning/effects.mjs +122 -0
- package/dist/planning/effects.mjs.map +1 -0
- package/dist/planning/reconcile.mjs +273 -0
- package/dist/planning/reconcile.mjs.map +1 -0
- package/dist/planning/runtime.d.mts +27 -0
- package/dist/planning/runtime.mjs +25 -0
- package/dist/planning/runtime.mjs.map +1 -0
- package/dist/planning/state.mjs +92 -0
- package/dist/planning/state.mjs.map +1 -0
- package/dist/planning/turns.mjs +262 -0
- package/dist/planning/turns.mjs.map +1 -0
- package/dist/planning/validation.mjs +106 -0
- package/dist/planning/validation.mjs.map +1 -0
- package/dist/postgres/index.d.mts +7 -1
- package/dist/postgres/index.mjs +7 -3
- package/dist/postgres/index.mjs.map +1 -1
- package/dist/presets/software-development/approval.d.mts +14 -0
- package/dist/presets/software-development/approval.mjs +21 -0
- package/dist/presets/software-development/approval.mjs.map +1 -0
- package/dist/presets/software-development/deepsec-brief.d.mts +8 -0
- package/dist/presets/software-development/deepsec-brief.mjs +14 -0
- package/dist/presets/software-development/deepsec-brief.mjs.map +1 -0
- package/dist/presets/software-development/dispatch.d.mts +53 -0
- package/dist/presets/software-development/dispatch.mjs +66 -0
- package/dist/presets/software-development/dispatch.mjs.map +1 -0
- package/dist/presets/software-development/eve-delegation-policy.d.mts +7 -0
- package/dist/presets/software-development/eve-delegation-policy.mjs +34 -0
- package/dist/presets/software-development/eve-delegation-policy.mjs.map +1 -0
- package/dist/presets/software-development/eve.d.mts +18 -0
- package/dist/presets/software-development/eve.mjs +19 -0
- package/dist/presets/software-development/eve.mjs.map +1 -0
- package/dist/presets/software-development/finish-task-policy.d.mts +7 -0
- package/dist/presets/software-development/finish-task-policy.mjs +9 -0
- package/dist/presets/software-development/finish-task-policy.mjs.map +1 -0
- package/dist/presets/software-development/recovery.d.mts +32 -0
- package/dist/presets/software-development/recovery.mjs +120 -0
- package/dist/presets/software-development/recovery.mjs.map +1 -0
- package/dist/presets/software-development/task-communication.d.mts +23 -0
- package/dist/presets/software-development/task-communication.mjs +120 -0
- package/dist/presets/software-development/task-communication.mjs.map +1 -0
- package/dist/presets/software-development.d.mts +9 -0
- package/dist/presets/software-development.mjs +9 -0
- package/dist/pull-requests/contracts.d.mts +72 -0
- package/dist/pull-requests/eve-fetch.d.mts +57 -0
- package/dist/pull-requests/eve-fetch.mjs +151 -0
- package/dist/pull-requests/eve-fetch.mjs.map +1 -0
- package/dist/pull-requests/github-label-manager.d.mts +27 -0
- package/dist/pull-requests/github-label-manager.mjs +40 -0
- package/dist/pull-requests/github-label-manager.mjs.map +1 -0
- package/dist/pull-requests/github-publisher.d.mts +53 -0
- package/dist/pull-requests/github-publisher.mjs +345 -0
- package/dist/pull-requests/github-publisher.mjs.map +1 -0
- package/dist/pull-requests.d.mts +5 -0
- package/dist/pull-requests.mjs +4 -0
- package/dist/receipts.d.mts +4 -0
- package/dist/receipts.mjs +3 -0
- package/dist/repositories.d.mts +18 -0
- package/dist/repositories.mjs +33 -0
- package/dist/repositories.mjs.map +1 -0
- package/dist/sandbox/command.d.mts +18 -0
- package/dist/sandbox/execution-abort.d.mts +16 -0
- package/dist/sandbox/execution-abort.mjs +49 -0
- package/dist/sandbox/execution-abort.mjs.map +1 -0
- package/dist/sandbox/execution-environment.d.mts +25 -0
- package/dist/sandbox/execution-run-ledger.d.mts +115 -0
- package/dist/sandbox/execution-run-ledger.mjs +209 -0
- package/dist/sandbox/execution-run-ledger.mjs.map +1 -0
- package/dist/sandbox/execution.d.mts +54 -0
- package/dist/sandbox/execution.mjs +228 -0
- package/dist/sandbox/execution.mjs.map +1 -0
- package/dist/sandbox/harness-runner.d.mts +79 -0
- package/dist/sandbox/harness-runner.mjs +162 -0
- package/dist/sandbox/harness-runner.mjs.map +1 -0
- package/dist/sandbox/index.d.mts +9 -0
- package/dist/sandbox/index.mjs +7 -0
- package/dist/sandbox/task-environment.d.mts +50 -0
- package/dist/sandbox/task-environment.mjs +140 -0
- package/dist/sandbox/task-environment.mjs.map +1 -0
- package/dist/sandbox/workspace.d.mts +29 -0
- package/dist/sandbox/workspace.mjs +28 -0
- package/dist/sandbox/workspace.mjs.map +1 -0
- package/dist/schema/agent-route.d.mts +22 -0
- package/dist/schema/agent-route.mjs +15 -0
- package/dist/schema/agent-route.mjs.map +1 -0
- package/dist/schema/answer.d.mts +23 -0
- package/dist/schema/answer.mjs +21 -0
- package/dist/schema/answer.mjs.map +1 -0
- package/dist/schema/budget.d.mts +38 -0
- package/dist/schema/budget.mjs +30 -0
- package/dist/schema/budget.mjs.map +1 -0
- package/dist/schema/catalog.mjs +72 -0
- package/dist/schema/catalog.mjs.map +1 -0
- package/dist/schema/change.d.mts +117 -0
- package/dist/schema/change.mjs +68 -0
- package/dist/schema/change.mjs.map +1 -0
- package/dist/schema/common.d.mts +33 -0
- package/dist/{id-DwCihQr4.mjs → schema/common.mjs} +7 -14
- package/dist/schema/common.mjs.map +1 -0
- package/dist/schema/communication.d.mts +52 -0
- package/dist/schema/communication.mjs +35 -0
- package/dist/schema/communication.mjs.map +1 -0
- package/dist/schema/execution.d.mts +101 -0
- package/dist/schema/execution.mjs +86 -0
- package/dist/schema/execution.mjs.map +1 -0
- package/dist/schema/factory-config.d.mts +95 -0
- package/dist/schema/factory-config.mjs +60 -0
- package/dist/schema/factory-config.mjs.map +1 -0
- package/dist/schema/finding.d.mts +125 -0
- package/dist/schema/finding.mjs +67 -0
- package/dist/schema/finding.mjs.map +1 -0
- package/dist/schema/id.d.mts +43 -0
- package/dist/schema/id.mjs +32 -0
- package/dist/schema/id.mjs.map +1 -0
- package/dist/schema/outbox.d.mts +240 -0
- package/dist/schema/outbox.mjs +53 -0
- package/dist/schema/outbox.mjs.map +1 -0
- package/dist/schema/question-route.mjs +16 -0
- package/dist/schema/question-route.mjs.map +1 -0
- package/dist/schema/receipt.d.mts +81 -0
- package/dist/schema/receipt.mjs +94 -0
- package/dist/schema/receipt.mjs.map +1 -0
- package/dist/schema/repository.d.mts +26 -0
- package/dist/schema/repository.mjs +25 -0
- package/dist/schema/repository.mjs.map +1 -0
- package/dist/schema/session.d.mts +135 -0
- package/dist/schema/session.mjs +53 -0
- package/dist/schema/session.mjs.map +1 -0
- package/dist/schema/signal.d.mts +105 -0
- package/dist/schema/signal.mjs +81 -0
- package/dist/schema/signal.mjs.map +1 -0
- package/dist/schema/stored-session.mjs +10 -0
- package/dist/schema/stored-session.mjs.map +1 -0
- package/dist/schema/stored-task.mjs +20 -0
- package/dist/schema/stored-task.mjs.map +1 -0
- package/dist/schema/task-effect.d.mts +39 -0
- package/dist/schema/task-effect.mjs +29 -0
- package/dist/schema/task-effect.mjs.map +1 -0
- package/dist/schema/task-graph.d.mts +3134 -0
- package/dist/schema/task-graph.mjs +97 -0
- package/dist/schema/task-graph.mjs.map +1 -0
- package/dist/schema/task-message.d.mts +254 -0
- package/dist/schema/task-message.mjs +189 -0
- package/dist/schema/task-message.mjs.map +1 -0
- package/dist/schema/task.d.mts +272 -0
- package/dist/schema/task.mjs +199 -0
- package/dist/schema/task.mjs.map +1 -0
- package/dist/schema/transcript.d.mts +170 -0
- package/dist/schema/transcript.mjs +86 -0
- package/dist/schema/transcript.mjs.map +1 -0
- package/dist/schema/transitions.d.mts +50 -0
- package/dist/schema/transitions.mjs +303 -0
- package/dist/schema/transitions.mjs.map +1 -0
- package/dist/schema/work.d.mts +101 -0
- package/dist/schema/work.mjs +102 -0
- package/dist/schema/work.mjs.map +1 -0
- package/dist/signal-triage.d.mts +20 -0
- package/dist/signal-triage.mjs +85 -0
- package/dist/signal-triage.mjs.map +1 -0
- package/dist/signals.d.mts +19 -0
- package/dist/signals.mjs +19 -0
- package/dist/signals.mjs.map +1 -0
- package/dist/stall.d.mts +38 -0
- package/dist/stall.mjs +79 -0
- package/dist/stall.mjs.map +1 -0
- package/dist/storage.d.mts +4 -0
- package/dist/storage.mjs +4 -0
- package/dist/store/driver.d.mts +65 -0
- package/dist/store/driver.mjs +42 -0
- package/dist/store/driver.mjs.map +1 -0
- package/dist/store/engine.d.mts +485 -0
- package/dist/store/engine.mjs +2175 -0
- package/dist/store/engine.mjs.map +1 -0
- package/dist/store/memory.d.mts +7 -0
- package/dist/store/memory.mjs +52 -0
- package/dist/store/memory.mjs.map +1 -0
- package/dist/store/task-effects.d.mts +38 -0
- package/dist/store/task-effects.mjs +118 -0
- package/dist/store/task-effects.mjs.map +1 -0
- package/dist/store/task-graphs.d.mts +60 -0
- package/dist/store/task-graphs.mjs +212 -0
- package/dist/store/task-graphs.mjs.map +1 -0
- package/dist/store/task-messages.d.mts +136 -0
- package/dist/store/task-messages.mjs +475 -0
- package/dist/store/task-messages.mjs.map +1 -0
- package/dist/store/task-work.d.mts +65 -0
- package/dist/store/task-work.mjs +134 -0
- package/dist/store/task-work.mjs.map +1 -0
- package/dist/store/work-notifications.d.mts +19 -0
- package/dist/store/work-notifications.mjs +48 -0
- package/dist/store/work-notifications.mjs.map +1 -0
- package/dist/sweep.d.mts +57 -0
- package/dist/sweep.mjs +143 -0
- package/dist/sweep.mjs.map +1 -0
- package/dist/task-communication.d.mts +35 -0
- package/dist/task-communication.mjs +47 -0
- package/dist/task-communication.mjs.map +1 -0
- package/dist/task-graph-view.d.mts +264 -0
- package/dist/task-graph-view.mjs +17 -0
- package/dist/task-graph-view.mjs.map +1 -0
- package/dist/task-message-processing.d.mts +60 -0
- package/dist/task-message-processing.mjs +200 -0
- package/dist/task-message-processing.mjs.map +1 -0
- package/dist/task-protocols.d.mts +80 -0
- package/dist/task-protocols.mjs +47 -0
- package/dist/task-protocols.mjs.map +1 -0
- package/dist/tasks.d.mts +17 -0
- package/dist/tasks.mjs +17 -0
- package/dist/vercel-git.d.mts +21 -0
- package/dist/vercel-git.mjs +112 -0
- package/dist/vercel-git.mjs.map +1 -0
- package/dist/vercel.d.mts +33 -2
- package/dist/vercel.mjs +48 -3
- package/dist/vercel.mjs.map +1 -1
- package/dist/work-triage/eve-tool.d.mts +29 -0
- package/dist/work-triage/eve-tool.mjs +32 -0
- package/dist/work-triage/eve-tool.mjs.map +1 -0
- package/dist/work-triage/find-existing-work.d.mts +32 -0
- package/dist/work-triage/find-existing-work.mjs +31 -0
- package/dist/work-triage/find-existing-work.mjs.map +1 -0
- package/dist/work-triage/github.d.mts +15 -0
- package/dist/work-triage/github.mjs +14 -0
- package/dist/work-triage/github.mjs.map +1 -0
- package/dist/work-triage.d.mts +3 -0
- package/dist/work-triage.mjs +3 -0
- package/dist/workflows.d.mts +48 -0
- package/dist/workflows.mjs +86 -0
- package/dist/workflows.mjs.map +1 -0
- package/dist/workspace-diff-assembly.d.mts +33 -0
- package/dist/workspace-diff-assembly.mjs +95 -0
- package/dist/workspace-diff-assembly.mjs.map +1 -0
- package/dist/workspace-diff.d.mts +90 -0
- package/dist/workspace-diff.mjs +61 -0
- package/dist/workspace-diff.mjs.map +1 -0
- package/dist/workspace-snapshot.d.mts +26 -0
- package/dist/workspace-snapshot.mjs +80 -0
- package/dist/workspace-snapshot.mjs.map +1 -0
- package/package.json +129 -40
- package/dist/approval-CwhTP1XB.mjs +0 -269
- package/dist/approval-CwhTP1XB.mjs.map +0 -1
- package/dist/change-CzEAoFVE.mjs +0 -620
- package/dist/change-CzEAoFVE.mjs.map +0 -1
- package/dist/dispatch-B77cuPxh.d.mts +0 -713
- package/dist/driver-BJutB0PE.d.mts +0 -34
- package/dist/driver-Bbd25rNM.mjs +0 -23
- package/dist/driver-Bbd25rNM.mjs.map +0 -1
- package/dist/factory-config-BJcjsPKl.mjs +0 -86
- package/dist/factory-config-BJcjsPKl.mjs.map +0 -1
- package/dist/factory-config-D2yU3G0c.d.mts +0 -58
- package/dist/github-DXsxboUL.d.mts +0 -271
- package/dist/id-DwCihQr4.mjs.map +0 -1
- package/dist/index-Cy4jfXh5.d.mts +0 -67
- package/dist/index.mjs.map +0 -1
- package/dist/signal-CHVEmzvI.d.mts +0 -59
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.mjs","names":[],"sources":["../../src/connectors/slack.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n communicationEventSchema,\n type CommunicationAdapter,\n type CommunicationEvent,\n type CommunicationOutcome,\n type NormalizedSignal,\n} from \"./index\";\n\nconst slackOutcomeLabels: Record<CommunicationOutcome, string> = {\n accepted: \"Request accepted\",\n started: \"Work started\",\n succeeded: \"Work completed\",\n failed: \"Work failed\",\n needs_human: \"Human action required\",\n question: \"Question requires an answer\",\n digest: \"Factory digest\",\n rolled_back: \"Change rolled back\",\n budget_warning: \"Factory budget warning\",\n budget_exhausted: \"Factory budget exhausted\",\n message: \"Factory update\",\n};\n\nconst slackDetailFields = [\"title\", \"message\", \"summary\", \"question\", \"requestedAction\", \"reason\", \"url\"] as const;\nconst slackListFields = [\"items\", \"options\"] as const;\nconst slackTextLimit = 12_000;\nconst slackOwnerOutcomes = new Set<CommunicationOutcome>([\n \"digest\",\n \"budget_warning\",\n \"budget_exhausted\",\n]);\n\nfunction escapeSlackText(value: string): string {\n return value.replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\");\n}\n\nfunction slackLink(url: string, label: string): string | null {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n return `<${escapeSlackText(parsed.toString())}|${escapeSlackText(label)}>`;\n } catch {\n return null;\n }\n}\n\nexport interface SlackCommunicationDelivery {\n eventId: string;\n clientMessageId: string;\n text: string;\n target:\n | { type: \"owner\" }\n | { type: \"thread\"; channelId: string; threadTs: string };\n}\n\nexport interface SlackCommunicationAdapter extends CommunicationAdapter {\n readonly channel: \"slack\";\n render(event: CommunicationEvent): string;\n}\n\nexport interface SlackCommunicationAdapterOptions {\n send(delivery: SlackCommunicationDelivery): Promise<void>;\n render?: (event: CommunicationEvent) => string;\n}\n\nexport interface SlackReplyTarget {\n channelId: string;\n threadTs: string;\n}\n\n/** Parse Eve's Slack continuation token into its channel and thread target. */\nexport function parseSlackReplyAddress(address: string): SlackReplyTarget {\n const separator = address.indexOf(\":\");\n const channelId = address.slice(0, separator);\n const threadTs = address.slice(separator + 1);\n if (separator <= 0 || !/^[A-Z][A-Z0-9]+$/.test(channelId) || !/^\\d+\\.\\d+$/.test(threadTs)) {\n throw new Error(`Invalid Slack reply address: ${address}.`);\n }\n return { channelId, threadTs };\n}\n\n/** Render a provider-neutral communication event as Slack mrkdwn. */\nexport function renderSlackCommunicationEvent(input: CommunicationEvent): string {\n const event = communicationEventSchema.parse(input);\n const headline = `*${slackOutcomeLabels[event.outcome]}*`;\n const details = new Map(slackDetailFields.flatMap((field) => {\n const value = event.data[field];\n return typeof value === \"string\" && value.trim() !== \"\" ? [[field, value.trim()] as const] : [];\n }));\n const primaryField = ([\"title\", \"question\", \"requestedAction\"] as const)\n .find((field) => details.has(field));\n const primary = primaryField === undefined ? undefined : details.get(primaryField);\n const url = details.get(\"url\");\n const linkedPrimary = primary !== undefined && url !== undefined ? slackLink(url, primary) : null;\n if (primaryField !== undefined) details.delete(primaryField);\n if (linkedPrimary !== null) details.delete(\"url\");\n\n const detailLines = [...new Set([\n ...(linkedPrimary !== null ? [linkedPrimary] : primary === undefined ? [] : [escapeSlackText(primary)]),\n ...[...details.values()].map(escapeSlackText),\n ])];\n const lists = slackListFields.flatMap((field) => {\n const value = event.data[field];\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) =>\n typeof item === \"string\" && item.trim() !== \"\" ? [`• ${escapeSlackText(item.trim())}`] : []\n );\n });\n const text = [headline, ...detailLines, ...(lists.length === 0 ? [] : [lists.join(\"\\n\")])]\n .join(\"\\n\\n\");\n return text.length <= slackTextLimit ? text : `${text.slice(0, slackTextLimit - 1)}…`;\n}\n\nfunction slackClientMessageId(eventId: string): string {\n const digest = createHash(\"sha256\").update(eventId).digest(\"hex\").slice(0, 32);\n return [\n digest.slice(0, 8),\n digest.slice(8, 12),\n digest.slice(12, 16),\n digest.slice(16, 20),\n digest.slice(20),\n ].join(\"-\");\n}\n\n/** Bind Slack rendering and delivery to the channel-neutral communication contract. */\nexport function createSlackCommunicationAdapter(\n options: SlackCommunicationAdapterOptions,\n): SlackCommunicationAdapter {\n const render = options.render ?? renderSlackCommunicationEvent;\n return Object.freeze({\n channel: \"slack\" as const,\n render,\n async deliver(input: CommunicationEvent): Promise<void> {\n const event = communicationEventSchema.parse(input);\n if (event.replyTo.channel !== \"slack\") {\n throw new Error(`Slack communication adapter cannot deliver to ${event.replyTo.channel}.`);\n }\n const target = slackOwnerOutcomes.has(event.outcome)\n ? { type: \"owner\" as const }\n : { type: \"thread\" as const, ...parseSlackReplyAddress(event.replyTo.address) };\n await options.send({\n eventId: event.eventId,\n clientMessageId: slackClientMessageId(event.eventId),\n text: render(event),\n target,\n });\n },\n });\n}\n\nexport interface SlackMessageEvent {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n replyAddress: string;\n repositorySlugs: readonly [string, ...string[]];\n payload: unknown;\n}\n\nexport interface SlackMessageSignal extends NormalizedSignal<\"slack\"> {\n repositorySlugs: readonly [string, ...string[]];\n message: {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n };\n}\n\n/** Normalize an Eve-verified Slack message after the caller resolves its repositories. */\nexport function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal {\n return {\n source: \"slack\",\n deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,\n repositorySlugs: [\n event.repositorySlugs[0].toLowerCase(),\n ...event.repositorySlugs.slice(1).map((slug) => slug.toLowerCase()),\n ],\n replyTo: { channel: \"slack\", address: event.replyAddress },\n message: {\n teamId: event.teamId,\n channelId: event.channelId,\n messageTs: event.messageTs,\n threadTs: event.threadTs,\n actorId: event.actorId,\n text: event.text,\n },\n payload: event.payload,\n };\n}\n\nexport type SlackRepositorySelection =\n | { status: \"selected\"; repositorySlug: string }\n | { status: \"required\" }\n | { status: \"ambiguous\"; repositorySlugs: readonly string[] };\n\nexport type SlackRepositoriesSelection =\n | { status: \"selected\"; repositorySlugs: readonly [string, ...string[]] }\n | { status: \"required\" };\n\nfunction escapedPattern(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction isDelimitedTextMentioned(text: string, value: string): boolean {\n return new RegExp(\n `(^|[^a-z0-9._/-])${escapedPattern(value)}($|[^a-z0-9._/-])`,\n \"i\",\n ).test(text);\n}\n\nfunction isRepositoryReferenceMentioned(text: string, slug: string): boolean {\n if (isDelimitedTextMentioned(text, slug)) return true;\n return new RegExp(\n `https?://github\\\\.com/${escapedPattern(slug)}(?:\\\\.git)?/?(?=$|[\\\\s?#|>)\\\\],.!])`,\n \"i\",\n ).test(text);\n}\n\nfunction repositoryName(slug: string): string {\n return slug.slice(slug.lastIndexOf(\"/\") + 1);\n}\n\ninterface SlackRepositoryMentions {\n repositories: readonly string[];\n matches: readonly string[];\n ambiguous: readonly string[];\n candidates: readonly string[];\n}\n\nfunction resolveSlackRepositoryMentions(\n text: string,\n repositories: readonly string[],\n): SlackRepositoryMentions {\n const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];\n const entries = normalized.map((slug) => ({ slug, name: repositoryName(slug) }));\n const counts = new Map<string, number>();\n for (const { name } of entries) {\n counts.set(name, (counts.get(name) ?? 0) + 1);\n }\n const mentionedNames = new Set(\n [...counts.keys()].filter((name) => isDelimitedTextMentioned(text, name)),\n );\n const matches = new Set(entries.flatMap(({ slug, name }) =>\n isRepositoryReferenceMentioned(text, slug) || (\n counts.get(name) === 1 && mentionedNames.has(name)\n )\n ? [slug]\n : []\n ));\n const ambiguous = new Set(entries.flatMap(({ slug, name }) =>\n counts.get(name) !== 1 && mentionedNames.has(name) ? [slug] : []\n ));\n\n return {\n repositories: normalized,\n matches: normalized.filter((slug) => matches.has(slug)),\n ambiguous: normalized.filter((slug) => ambiguous.has(slug)),\n candidates: normalized.filter((slug) => matches.has(slug) || ambiguous.has(slug)),\n };\n}\n\n/** Select every named configured repository, preserving configuration order. */\nexport function selectSlackRepositories(\n text: string,\n repositories: readonly string[],\n): SlackRepositoriesSelection {\n const mentions = resolveSlackRepositoryMentions(text, repositories);\n if (mentions.repositories.length === 1) {\n return { status: \"selected\", repositorySlugs: [mentions.repositories[0]!] };\n }\n if (mentions.ambiguous.length > 0) return { status: \"required\" };\n return mentions.matches.length === 0\n ? { status: \"required\" }\n : {\n status: \"selected\",\n repositorySlugs: [mentions.matches[0]!, ...mentions.matches.slice(1)],\n };\n}\n\n/**\n * Select the only configured repository, or match a full slug, unique short\n * name, or GitHub repository URL when a Slack surface fronts multiple repositories.\n */\nexport function selectSlackRepository(\n text: string,\n repositories: readonly string[],\n): SlackRepositorySelection {\n const mentions = resolveSlackRepositoryMentions(text, repositories);\n if (mentions.repositories.length === 1) {\n return { status: \"selected\", repositorySlug: mentions.repositories[0]! };\n }\n if (mentions.candidates.length === 1) {\n return { status: \"selected\", repositorySlug: mentions.candidates[0]! };\n }\n if (mentions.candidates.length > 1) {\n return { status: \"ambiguous\", repositorySlugs: mentions.candidates };\n }\n return { status: \"required\" };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slack.mjs","names":[],"sources":["../../src/connectors/slack.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { MaybePromise } from \"../schema/common\";\nimport { repositorySlugSchema, type RepositorySlug } from \"../schema/repository\";\nexport {\n createSlackApprovals,\n type SlackApprovals,\n type SlackApprovalsOptions,\n type SlackApprovalsStores,\n} from \"./slack-approvals\";\nimport {\n communicationEventSchema,\n type CommunicationAdapter,\n type CommunicationEvent,\n type CommunicationOutcome,\n type NormalizedSignal,\n} from \"./index\";\n\nconst slackOutcomeLabels: Record<CommunicationOutcome, string> = {\n accepted: \"Request accepted\",\n started: \"Work started\",\n succeeded: \"Work completed\",\n failed: \"Work failed\",\n cancelled: \"Work cancelled\",\n needs_human: \"Human action required\",\n question: \"Question requires an answer\",\n digest: \"Factory digest\",\n rolled_back: \"Change rolled back\",\n budget_warning: \"Factory budget warning\",\n budget_exhausted: \"Factory budget exhausted\",\n message: \"Factory update\",\n};\n\nconst slackDetailFields = [\n \"title\",\n \"message\",\n \"summary\",\n \"question\",\n \"requestedAction\",\n \"reference\",\n \"answerLocation\",\n \"reason\",\n \"url\",\n] as const;\nconst slackListFields = [\"items\", \"options\"] as const;\nconst slackTextLimit = 12_000;\nconst slackOwnerOutcomes = new Set<CommunicationOutcome>([\n \"digest\",\n \"budget_warning\",\n \"budget_exhausted\",\n \"needs_human\",\n]);\n\nfunction escapeSlackText(value: string): string {\n return value.replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\");\n}\n\nfunction slackLink(url: string, label: string): string | null {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n return `<${escapeSlackText(parsed.toString())}|${escapeSlackText(label)}>`;\n } catch {\n return null;\n }\n}\n\n/** Fully rendered Slack delivery with a stable idempotency key and resolved destination. */\nexport interface SlackCommunicationDelivery {\n eventId: string;\n clientMessageId: string;\n text: string;\n target: { type: \"owner\" } | { type: \"thread\"; channelId: string; threadTs: string };\n}\n\n/** Slack-specific communication adapter with an inspectable mrkdwn renderer. */\nexport interface SlackCommunicationAdapter extends CommunicationAdapter {\n readonly channel: \"slack\";\n render(event: CommunicationEvent): string;\n}\n\n/** Configures Slack rendering, provider delivery, and durable delivery projection. */\nexport interface SlackCommunicationAdapterOptions {\n send(delivery: SlackCommunicationDelivery): Promise<void>;\n render?: (event: CommunicationEvent) => string;\n /**\n * Runs once send() resolves without throwing, i.e. once Slack has\n * confirmed delivery. Errors propagate so the outbox retries with the same\n * client message ID until both the send and its durable projection succeed.\n */\n onDelivered?: (event: CommunicationEvent) => MaybePromise<void>;\n}\n\n/** The channel and thread extracted from a Slack reply address. */\nexport interface SlackReplyTarget {\n channelId: string;\n threadTs: string;\n}\n\n/** Parse Eve's Slack continuation token into its channel and thread target. */\nexport function parseSlackReplyAddress(address: string): SlackReplyTarget {\n const separator = address.indexOf(\":\");\n const channelId = address.slice(0, separator);\n const threadTs = address.slice(separator + 1);\n if (separator <= 0 || !/^[A-Z][A-Z0-9]+$/.test(channelId) || !/^\\d+\\.\\d+$/.test(threadTs)) {\n throw new Error(`Invalid Slack reply address: ${address}.`);\n }\n return { channelId, threadTs };\n}\n\n/** Render a provider-neutral communication event as Slack mrkdwn. */\nexport function renderSlackCommunicationEvent(input: CommunicationEvent): string {\n const event = communicationEventSchema.parse(input);\n const headline = `*${slackOutcomeLabels[event.outcome]}*`;\n const details = new Map(\n slackDetailFields.flatMap((field) => {\n const value = event.data[field];\n return typeof value === \"string\" && value.trim() !== \"\"\n ? [[field, value.trim()] as const]\n : [];\n }),\n );\n const primaryField = ([\"title\", \"question\", \"requestedAction\"] as const).find((field) =>\n details.has(field),\n );\n const primary = primaryField === undefined ? undefined : details.get(primaryField);\n const url = details.get(\"url\");\n const linkedPrimary = primary !== undefined && url !== undefined ? slackLink(url, primary) : null;\n if (primaryField !== undefined) details.delete(primaryField);\n if (linkedPrimary !== null) details.delete(\"url\");\n\n const detailLines = [\n ...new Set([\n ...(linkedPrimary !== null\n ? [linkedPrimary]\n : primary === undefined\n ? []\n : [escapeSlackText(primary)]),\n ...[...details.values()].map(escapeSlackText),\n ]),\n ];\n const lists = slackListFields.flatMap((field) => {\n const value = event.data[field];\n if (!Array.isArray(value)) return [];\n return value.flatMap((item) =>\n typeof item === \"string\" && item.trim() !== \"\" ? [`• ${escapeSlackText(item.trim())}`] : [],\n );\n });\n // Success messages lead directly with the result; the generic outcome\n // headline is redundant once the work is done and the summary says it all.\n // When a succeeded event carries no other content, fall back to the\n // shared status-label mapping so the message is never blank and stays\n // consistent with how every other outcome renders.\n const hasContent = detailLines.length > 0 || lists.length > 0;\n const headlineLines = event.outcome === \"succeeded\" && hasContent ? [] : [headline];\n const text = [\n ...headlineLines,\n ...detailLines,\n ...(lists.length === 0 ? [] : [lists.join(\"\\n\")]),\n ].join(\"\\n\\n\");\n return text.length <= slackTextLimit ? text : `${text.slice(0, slackTextLimit - 1)}…`;\n}\n\nfunction slackClientMessageId(eventId: string): string {\n const digest = createHash(\"sha256\").update(eventId).digest(\"hex\").slice(0, 32);\n return [\n digest.slice(0, 8),\n digest.slice(8, 12),\n digest.slice(12, 16),\n digest.slice(16, 20),\n digest.slice(20),\n ].join(\"-\");\n}\n\n/** Bind Slack rendering and delivery to the channel-neutral communication contract. */\nexport function createSlackCommunicationAdapter(\n options: SlackCommunicationAdapterOptions,\n): SlackCommunicationAdapter {\n const render = options.render ?? renderSlackCommunicationEvent;\n return Object.freeze({\n channel: \"slack\" as const,\n render,\n async deliver(input: CommunicationEvent): Promise<void> {\n const event = communicationEventSchema.parse(input);\n if (event.replyTo.channel !== \"slack\") {\n throw new Error(`Slack communication adapter cannot deliver to ${event.replyTo.channel}.`);\n }\n const target = slackOwnerOutcomes.has(event.outcome)\n ? { type: \"owner\" as const }\n : { type: \"thread\" as const, ...parseSlackReplyAddress(event.replyTo.address) };\n await options.send({\n eventId: event.eventId,\n clientMessageId: slackClientMessageId(event.eventId),\n text: render(event),\n target,\n });\n await options.onDelivered?.(event);\n },\n });\n}\n\n/** Authenticated Slack message metadata after repository and reply context resolution. */\nexport interface SlackMessageEvent {\n readonly teamId: string;\n readonly channelId: string;\n readonly messageTs: string;\n readonly threadTs: string;\n readonly actorId: string;\n readonly text: string;\n readonly replyAddress: string;\n readonly repositorySlugs: readonly [RepositorySlug, ...RepositorySlug[]];\n readonly payload: unknown;\n}\n\n/** A normalized Slack signal with the authenticated message and repository scope retained. */\nexport interface SlackMessageSignal extends NormalizedSignal<\"slack\"> {\n readonly repositorySlugs: readonly [RepositorySlug, ...RepositorySlug[]];\n readonly message: {\n readonly teamId: string;\n readonly channelId: string;\n readonly messageTs: string;\n readonly threadTs: string;\n readonly actorId: string;\n readonly text: string;\n };\n}\n\n/** Normalize an Eve-verified Slack message after the caller resolves its repositories. */\nexport function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal {\n return {\n source: \"slack\",\n deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,\n repositorySlugs: [\n repositorySlugSchema.parse(event.repositorySlugs[0].toLowerCase()),\n ...event.repositorySlugs\n .slice(1)\n .map((slug) => repositorySlugSchema.parse(slug.toLowerCase())),\n ],\n replyTo: { channel: \"slack\", address: event.replyAddress },\n message: {\n teamId: event.teamId,\n channelId: event.channelId,\n messageTs: event.messageTs,\n threadTs: event.threadTs,\n actorId: event.actorId,\n text: event.text,\n },\n payload: event.payload,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { TaskId } from "../schema/id.mjs";
|
|
2
|
+
import { MillisecondClock } from "../schema/common.mjs";
|
|
3
|
+
import { FactoryStores } from "../store/engine.mjs";
|
|
4
|
+
import { CommandSandbox, CommandSandboxRunResult } from "../sandbox/command.mjs";
|
|
5
|
+
import { Finding } from "../schema/finding.mjs";
|
|
6
|
+
import { AdmitFindingOptions } from "../finding-remediation/admission.mjs";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { ToolContext, ToolDefinition } from "eve/tools";
|
|
9
|
+
//#region src/deepsec/eve-tool.d.ts
|
|
10
|
+
/** Repository, receipt, Signal, and Task capabilities required by the Deepsec scan tool. */
|
|
11
|
+
type DeepsecScanToolStores = Pick<FactoryStores, "receipts" | "repositories" | "signals" | "tasks">;
|
|
12
|
+
/** Deepsec investigation backend selected for one scan. */
|
|
13
|
+
type DeepsecAgent = "claude" | "codex" | "pi";
|
|
14
|
+
/** Reasoning effort accepted by Deepsec's investigation agent. */
|
|
15
|
+
type DeepsecThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
16
|
+
/** Validates the repository and optional candidate-file cap supplied to the Deepsec tool. */
|
|
17
|
+
declare const deepsecScanInputSchema: z.ZodObject<{
|
|
18
|
+
repositoryId: z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>;
|
|
19
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
/** Repository and optional candidate-file cap supplied to one Deepsec scan. */
|
|
22
|
+
type DeepsecScanInput = z.infer<typeof deepsecScanInputSchema>;
|
|
23
|
+
/** Configures credential isolation, execution limits, and finding admission for a deepsec scan. */
|
|
24
|
+
interface DeepsecScanToolOptions<S extends CommandSandbox = CommandSandbox> extends AdmitFindingOptions {
|
|
25
|
+
/** Factory capabilities used to report scan phases and admit findings. */
|
|
26
|
+
stores: DeepsecScanToolStores;
|
|
27
|
+
/**
|
|
28
|
+
* Runs one shell command with the model credential deepsec's gateway route
|
|
29
|
+
* expects, and removes it again before resolving. Supplied by the app, which
|
|
30
|
+
* owns where the credential comes from: on Vercel it is the deployment's own
|
|
31
|
+
* OIDC identity (VERCEL_OIDC_TOKEN), so nothing has to be configured.
|
|
32
|
+
*/
|
|
33
|
+
withModelCredential: (input: DeepsecCredentialCommandInput<S>) => Promise<DeepsecCommandResult>;
|
|
34
|
+
/** deepsec agent backend: claude, codex, or pi. */
|
|
35
|
+
agent?: DeepsecAgent;
|
|
36
|
+
/** Model deepsec's investigation agent uses, through the AI Gateway (e.g. claude-sonnet-4-6). */
|
|
37
|
+
model?: string;
|
|
38
|
+
/** Reasoning effort for the investigation: minimal, low, medium, high, or xhigh. */
|
|
39
|
+
thinkingLevel?: DeepsecThinkingLevel;
|
|
40
|
+
/** Parallel investigation batches. */
|
|
41
|
+
concurrency?: number;
|
|
42
|
+
/** deepsec version to install. */
|
|
43
|
+
version?: string;
|
|
44
|
+
/** Millisecond clock used for receipt elapsed-time reporting. */
|
|
45
|
+
now?: MillisecondClock;
|
|
46
|
+
/** Repository checkout inside the sandbox. Defaults to `repo`. */
|
|
47
|
+
repoPath?: string;
|
|
48
|
+
/** deepsec workspace inside the sandbox. Defaults to `deepsec`. */
|
|
49
|
+
workspacePath?: string;
|
|
50
|
+
/** Shell run before every deepsec command, e.g. to put a Node 24 on PATH. */
|
|
51
|
+
commandPrefix?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Candidate files investigated per run. deepsec keeps state, so successive
|
|
54
|
+
* scans continue where the last one stopped; the cap keeps one run inside
|
|
55
|
+
* the deployment's time limit on large repositories. Defaults to 40.
|
|
56
|
+
*/
|
|
57
|
+
filesPerRun?: number;
|
|
58
|
+
}
|
|
59
|
+
/** Sandbox and trusted command that may temporarily receive a model credential. */
|
|
60
|
+
interface DeepsecCredentialCommandInput<S extends CommandSandbox = CommandSandbox> {
|
|
61
|
+
sandbox: S;
|
|
62
|
+
command: string;
|
|
63
|
+
}
|
|
64
|
+
/** Captured output from one Deepsec sandbox command. */
|
|
65
|
+
type DeepsecCommandResult = CommandSandboxRunResult;
|
|
66
|
+
/** Machine-readable reason a Deepsec scan did not produce findings. */
|
|
67
|
+
type DeepsecScanFailureCode = "repository_not_found" | "scan_failed" | "process_failed" | "invalid_export_json" | "invalid_export_shape";
|
|
68
|
+
/** Bounded Deepsec refusal with a human-readable reason code payload. */
|
|
69
|
+
interface DeepsecScanFailure {
|
|
70
|
+
scanned: false;
|
|
71
|
+
code: DeepsecScanFailureCode;
|
|
72
|
+
reason: string;
|
|
73
|
+
}
|
|
74
|
+
/** One normalized Deepsec finding and its admission outcome. */
|
|
75
|
+
type DeepsecScanFinding = Pick<Finding, "title" | "priority" | "path" | "line"> & ({
|
|
76
|
+
duplicate: true;
|
|
77
|
+
taskId?: never;
|
|
78
|
+
approval?: never;
|
|
79
|
+
} | {
|
|
80
|
+
duplicate?: never;
|
|
81
|
+
taskId: TaskId;
|
|
82
|
+
approval: "required" | "not_required";
|
|
83
|
+
});
|
|
84
|
+
/** Counts and bounded finding summaries returned after a completed Deepsec scan. */
|
|
85
|
+
interface DeepsecScanSuccess {
|
|
86
|
+
scanned: true;
|
|
87
|
+
tool: "deepsec";
|
|
88
|
+
found: number;
|
|
89
|
+
recorded: number;
|
|
90
|
+
duplicates: number;
|
|
91
|
+
awaitingApproval: number;
|
|
92
|
+
findings: readonly DeepsecScanFinding[];
|
|
93
|
+
}
|
|
94
|
+
/** Discriminated result returned by the optional Deepsec scanner tool. */
|
|
95
|
+
type DeepsecScanResult = DeepsecScanFailure | DeepsecScanSuccess;
|
|
96
|
+
/** Eve tool that runs one bounded Deepsec scan and admits its findings. */
|
|
97
|
+
interface DeepsecScanTool extends ToolDefinition<DeepsecScanInput, DeepsecScanResult> {
|
|
98
|
+
execute(input: DeepsecScanInput, ctx: ToolContext): Promise<DeepsecScanResult>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Runs deepsec (vercel-labs/deepsec) against the sandbox checkout and records
|
|
102
|
+
* what it finds through the same path as record_finding, so every deepsec
|
|
103
|
+
* finding becomes a fix task subject to the approval policy. Trusted code
|
|
104
|
+
* owns the command sequence and the credential; the agent chooses when to
|
|
105
|
+
* scan and reports the outcome.
|
|
106
|
+
*/
|
|
107
|
+
declare function createDeepsecScanTool<S extends CommandSandbox>(input: DeepsecScanToolOptions<S>): DeepsecScanTool;
|
|
108
|
+
//#endregion
|
|
109
|
+
export { DeepsecAgent, DeepsecCommandResult, DeepsecCredentialCommandInput, DeepsecScanFailure, DeepsecScanFailureCode, DeepsecScanFinding, DeepsecScanInput, DeepsecScanResult, DeepsecScanSuccess, DeepsecScanTool, DeepsecScanToolOptions, DeepsecScanToolStores, DeepsecThinkingLevel, createDeepsecScanTool, deepsecScanInputSchema };
|
|
110
|
+
//# sourceMappingURL=eve-tool.d.mts.map
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { repositoryIdSchema } from "../schema/id.mjs";
|
|
2
|
+
import { optionalTaskIdForSession } from "../eve/task-session.mjs";
|
|
3
|
+
import { deepsecExportSchema, findingFromDeepsec } from "./finding.mjs";
|
|
4
|
+
import { admitFinding } from "../finding-remediation/admission.mjs";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { defineTool } from "eve/tools";
|
|
7
|
+
//#region src/deepsec/eve-tool.ts
|
|
8
|
+
/** Validates the repository and optional candidate-file cap supplied to the Deepsec tool. */
|
|
9
|
+
const deepsecScanInputSchema = z.object({
|
|
10
|
+
repositoryId: repositoryIdSchema,
|
|
11
|
+
limit: z.number().int().min(1).max(500).optional().describe("Candidate files to investigate this run (default 40; deepsec resumes where it left off next time).")
|
|
12
|
+
});
|
|
13
|
+
const findingsPath = "/tmp/deepsec-findings.json";
|
|
14
|
+
/** Parse "N file(s) with candidates" out of deepsec scan output; null when absent. */
|
|
15
|
+
function candidateCount(output) {
|
|
16
|
+
const match = /(\d+)\s+file\(s\) with candidates/u.exec(output);
|
|
17
|
+
return match?.[1] === void 0 ? null : Number(match[1]);
|
|
18
|
+
}
|
|
19
|
+
/** The last few meaningful lines of a command's output, ANSI stripped, for a receipt. */
|
|
20
|
+
function tail(output, lines = 4) {
|
|
21
|
+
return output.replaceAll(/\u001b\[[0-9;]*m/gu, "").split("\n").map((line) => line.trim()).filter((line) => line !== "").slice(-lines).join(" | ").slice(0, 600);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Runs deepsec (vercel-labs/deepsec) against the sandbox checkout and records
|
|
25
|
+
* what it finds through the same path as record_finding, so every deepsec
|
|
26
|
+
* finding becomes a fix task subject to the approval policy. Trusted code
|
|
27
|
+
* owns the command sequence and the credential; the agent chooses when to
|
|
28
|
+
* scan and reports the outcome.
|
|
29
|
+
*/
|
|
30
|
+
function createDeepsecScanTool(input) {
|
|
31
|
+
const { stores, ...options } = input;
|
|
32
|
+
const repoPath = options.repoPath ?? "repo";
|
|
33
|
+
const workspace = options.workspacePath ?? "deepsec";
|
|
34
|
+
const version = options.version ?? "2.3.8";
|
|
35
|
+
const agent = options.agent ?? "claude";
|
|
36
|
+
const model = options.model ?? "claude-sonnet-4-6";
|
|
37
|
+
const thinkingLevel = options.thinkingLevel ?? "medium";
|
|
38
|
+
const concurrency = options.concurrency ?? 4;
|
|
39
|
+
const prefix = options.commandPrefix === void 0 ? [] : [options.commandPrefix];
|
|
40
|
+
const filesPerRun = options.filesPerRun ?? 40;
|
|
41
|
+
const now = options.now ?? Date.now;
|
|
42
|
+
return defineTool({
|
|
43
|
+
description: "Run deepsec, the AI-powered vulnerability scanner, over the repository checkout and record every finding it reports as a fix task. Takes minutes. Call once per scan; then summarize the result.",
|
|
44
|
+
inputSchema: deepsecScanInputSchema,
|
|
45
|
+
async execute({ repositoryId, limit }, ctx) {
|
|
46
|
+
const repository = await stores.repositories.get(repositoryId);
|
|
47
|
+
if (repository === null) return {
|
|
48
|
+
scanned: false,
|
|
49
|
+
code: "repository_not_found",
|
|
50
|
+
reason: `repository ${repositoryId} does not exist`
|
|
51
|
+
};
|
|
52
|
+
const sandbox = await ctx.getSandbox();
|
|
53
|
+
const projectId = repository.slug.replace(/[^a-z0-9]+/gu, "-");
|
|
54
|
+
const startedAt = now();
|
|
55
|
+
const phase = async (state, reason) => {
|
|
56
|
+
await stores.receipts.append({
|
|
57
|
+
repositoryIds: [repositoryId],
|
|
58
|
+
kind: "deepsec",
|
|
59
|
+
state,
|
|
60
|
+
phase: "security_scan",
|
|
61
|
+
reason: `${reason} (${Math.round((now() - startedAt) / 1e3)}s in)`
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
await phase("started", `deepsec ${version} on ${repository.slug}, ${agent}/${model}`);
|
|
65
|
+
const prepare = await sandbox.run({ command: [
|
|
66
|
+
...prefix,
|
|
67
|
+
"set -e",
|
|
68
|
+
`if [ ! -f ${workspace}/deepsec.config.ts ]; then`,
|
|
69
|
+
` npx -y deepsec@${version} init ./${workspace} ./${repoPath} --id ${projectId} --scaffold-only --headless --yes --skip-install`,
|
|
70
|
+
` (cd ${workspace} && npm install --no-audit --no-fund --loglevel=error)`,
|
|
71
|
+
"fi",
|
|
72
|
+
`cd ${workspace}`,
|
|
73
|
+
`./node_modules/.bin/deepsec scan --project-id ${projectId}`
|
|
74
|
+
].join("\n") });
|
|
75
|
+
if (typeof prepare.exitCode === "number" && prepare.exitCode !== 0) {
|
|
76
|
+
const detail = (prepare.stderr ?? prepare.stdout).trim().slice(-1500);
|
|
77
|
+
await phase("failed", `scan phase failed: ${detail.slice(-300)}`);
|
|
78
|
+
return {
|
|
79
|
+
scanned: false,
|
|
80
|
+
code: "scan_failed",
|
|
81
|
+
reason: `deepsec scan failed: ${detail}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const candidates = candidateCount(prepare.stdout);
|
|
85
|
+
await phase("scanned", `${candidates === null ? "scan output" : `${candidates} files with candidates`}: ${tail(`${prepare.stdout}\n${prepare.stderr ?? ""}`)}`);
|
|
86
|
+
const investigate = await options.withModelCredential({
|
|
87
|
+
sandbox,
|
|
88
|
+
command: [
|
|
89
|
+
...prefix,
|
|
90
|
+
"set -e",
|
|
91
|
+
`cd ${workspace}`,
|
|
92
|
+
`./node_modules/.bin/deepsec process --project-id ${projectId} --agent ${agent} --model ${model} --thinking-level ${thinkingLevel} --concurrency ${concurrency} --limit ${limit ?? filesPerRun}`,
|
|
93
|
+
`./node_modules/.bin/deepsec export --project-id ${projectId} --format json --out ${findingsPath}`
|
|
94
|
+
].join("\n")
|
|
95
|
+
});
|
|
96
|
+
if (typeof investigate.exitCode === "number" && investigate.exitCode !== 0) {
|
|
97
|
+
const detail = (investigate.stderr ?? investigate.stdout).trim().slice(-1500);
|
|
98
|
+
await phase("failed", `process phase failed: ${detail.slice(-300)}`);
|
|
99
|
+
return {
|
|
100
|
+
scanned: false,
|
|
101
|
+
code: "process_failed",
|
|
102
|
+
reason: `deepsec process failed: ${detail}`
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const exported = await sandbox.run({ command: `cat ${findingsPath}` });
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(exported.stdout);
|
|
109
|
+
} catch {
|
|
110
|
+
return {
|
|
111
|
+
scanned: false,
|
|
112
|
+
code: "invalid_export_json",
|
|
113
|
+
reason: "deepsec export was not valid JSON"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const raw = deepsecExportSchema.safeParse(parsed);
|
|
117
|
+
if (!raw.success) return {
|
|
118
|
+
scanned: false,
|
|
119
|
+
code: "invalid_export_shape",
|
|
120
|
+
reason: `deepsec export had an unexpected shape: ${raw.error.issues[0]?.message ?? "unknown"}`
|
|
121
|
+
};
|
|
122
|
+
const results = [];
|
|
123
|
+
const taskId = optionalTaskIdForSession(ctx.session.auth.initiator);
|
|
124
|
+
const scan = taskId === null ? null : await stores.tasks.get(taskId);
|
|
125
|
+
const replyTo = scan?.replyTo.channel === "slack" && scan.repositoryIds.includes(repositoryId) ? scan.replyTo : options.replyTo;
|
|
126
|
+
for (const entry of raw.data) {
|
|
127
|
+
const finding = findingFromDeepsec(entry);
|
|
128
|
+
results.push({
|
|
129
|
+
finding,
|
|
130
|
+
result: await admitFinding(stores, {
|
|
131
|
+
repositoryId,
|
|
132
|
+
finding,
|
|
133
|
+
...options,
|
|
134
|
+
replyTo
|
|
135
|
+
})
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const recorded = results.filter(({ result }) => result.recorded);
|
|
139
|
+
await phase("completed", `${results.length} findings, ${recorded.length} new. process output: ${tail(`${investigate.stdout}\n${investigate.stderr ?? ""}`, 6)}`);
|
|
140
|
+
return {
|
|
141
|
+
scanned: true,
|
|
142
|
+
tool: "deepsec",
|
|
143
|
+
found: results.length,
|
|
144
|
+
recorded: recorded.length,
|
|
145
|
+
duplicates: results.length - recorded.length,
|
|
146
|
+
awaitingApproval: recorded.filter(({ result }) => result.recorded && result.approval === "required").length,
|
|
147
|
+
findings: results.map(({ finding, result }) => ({
|
|
148
|
+
title: finding.title,
|
|
149
|
+
priority: finding.priority,
|
|
150
|
+
path: finding.path,
|
|
151
|
+
line: finding.line,
|
|
152
|
+
...result.recorded ? {
|
|
153
|
+
taskId: result.taskId,
|
|
154
|
+
approval: result.approval
|
|
155
|
+
} : { duplicate: true }
|
|
156
|
+
}))
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { createDeepsecScanTool, deepsecScanInputSchema };
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=eve-tool.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eve-tool.mjs","names":[],"sources":["../../src/deepsec/eve-tool.ts"],"sourcesContent":["import { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport { deepsecExportSchema, findingFromDeepsec } from \"./finding\";\nimport {\n admitFinding,\n type AdmitFindingOptions,\n type AdmitFindingResult,\n} from \"../finding-remediation/admission\";\nimport type { Finding } from \"../schema/finding\";\nimport type { MillisecondClock } from \"../schema/common\";\nimport { repositoryIdSchema, type TaskId } from \"../schema/id\";\nimport type { FactoryStores } from \"../store/engine\";\nimport { optionalTaskIdForSession } from \"../eve/task-session\";\nimport type { CommandSandbox, CommandSandboxRunResult } from \"../sandbox/command\";\n\n/** Repository, receipt, Signal, and Task capabilities required by the Deepsec scan tool. */\nexport type DeepsecScanToolStores = Pick<\n FactoryStores,\n \"receipts\" | \"repositories\" | \"signals\" | \"tasks\"\n>;\n\n/** Deepsec investigation backend selected for one scan. */\nexport type DeepsecAgent = \"claude\" | \"codex\" | \"pi\";\n\n/** Reasoning effort accepted by Deepsec's investigation agent. */\nexport type DeepsecThinkingLevel = \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\n/** Validates the repository and optional candidate-file cap supplied to the Deepsec tool. */\nexport const deepsecScanInputSchema = z.object({\n repositoryId: repositoryIdSchema,\n limit: z\n .number()\n .int()\n .min(1)\n .max(500)\n .optional()\n .describe(\n \"Candidate files to investigate this run (default 40; deepsec resumes where it left off next time).\",\n ),\n});\n\n/** Repository and optional candidate-file cap supplied to one Deepsec scan. */\nexport type DeepsecScanInput = z.infer<typeof deepsecScanInputSchema>;\n\n/** Configures credential isolation, execution limits, and finding admission for a deepsec scan. */\nexport interface DeepsecScanToolOptions<\n S extends CommandSandbox = CommandSandbox,\n> extends AdmitFindingOptions {\n /** Factory capabilities used to report scan phases and admit findings. */\n stores: DeepsecScanToolStores;\n /**\n * Runs one shell command with the model credential deepsec's gateway route\n * expects, and removes it again before resolving. Supplied by the app, which\n * owns where the credential comes from: on Vercel it is the deployment's own\n * OIDC identity (VERCEL_OIDC_TOKEN), so nothing has to be configured.\n */\n withModelCredential: (input: DeepsecCredentialCommandInput<S>) => Promise<DeepsecCommandResult>;\n /** deepsec agent backend: claude, codex, or pi. */\n agent?: DeepsecAgent;\n /** Model deepsec's investigation agent uses, through the AI Gateway (e.g. claude-sonnet-4-6). */\n model?: string;\n /** Reasoning effort for the investigation: minimal, low, medium, high, or xhigh. */\n thinkingLevel?: DeepsecThinkingLevel;\n /** Parallel investigation batches. */\n concurrency?: number;\n /** deepsec version to install. */\n version?: string;\n /** Millisecond clock used for receipt elapsed-time reporting. */\n now?: MillisecondClock;\n /** Repository checkout inside the sandbox. Defaults to `repo`. */\n repoPath?: string;\n /** deepsec workspace inside the sandbox. Defaults to `deepsec`. */\n workspacePath?: string;\n /** Shell run before every deepsec command, e.g. to put a Node 24 on PATH. */\n commandPrefix?: string;\n /**\n * Candidate files investigated per run. deepsec keeps state, so successive\n * scans continue where the last one stopped; the cap keeps one run inside\n * the deployment's time limit on large repositories. Defaults to 40.\n */\n filesPerRun?: number;\n}\n\n/** Sandbox and trusted command that may temporarily receive a model credential. */\nexport interface DeepsecCredentialCommandInput<S extends CommandSandbox = CommandSandbox> {\n sandbox: S;\n command: string;\n}\n\n/** Captured output from one Deepsec sandbox command. */\nexport type DeepsecCommandResult = CommandSandboxRunResult;\n\n/** Machine-readable reason a Deepsec scan did not produce findings. */\nexport type DeepsecScanFailureCode =\n | \"repository_not_found\"\n | \"scan_failed\"\n | \"process_failed\"\n | \"invalid_export_json\"\n | \"invalid_export_shape\";\n\n/** Bounded Deepsec refusal with a human-readable reason code payload. */\nexport interface DeepsecScanFailure {\n scanned: false;\n code: DeepsecScanFailureCode;\n reason: string;\n}\n\n/** One normalized Deepsec finding and its admission outcome. */\nexport type DeepsecScanFinding = Pick<Finding, \"title\" | \"priority\" | \"path\" | \"line\"> &\n (\n | { duplicate: true; taskId?: never; approval?: never }\n | { duplicate?: never; taskId: TaskId; approval: \"required\" | \"not_required\" }\n );\n\n/** Counts and bounded finding summaries returned after a completed Deepsec scan. */\nexport interface DeepsecScanSuccess {\n scanned: true;\n tool: \"deepsec\";\n found: number;\n recorded: number;\n duplicates: number;\n awaitingApproval: number;\n findings: readonly DeepsecScanFinding[];\n}\n\n/** Discriminated result returned by the optional Deepsec scanner tool. */\nexport type DeepsecScanResult = DeepsecScanFailure | DeepsecScanSuccess;\n\n/** Eve tool that runs one bounded Deepsec scan and admits its findings. */\nexport interface DeepsecScanTool extends ToolDefinition<DeepsecScanInput, DeepsecScanResult> {\n execute(input: DeepsecScanInput, ctx: ToolContext): Promise<DeepsecScanResult>;\n}\n\nconst findingsPath = \"/tmp/deepsec-findings.json\";\n\n/** Parse \"N file(s) with candidates\" out of deepsec scan output; null when absent. */\nfunction candidateCount(output: string): number | null {\n const match = /(\\d+)\\s+file\\(s\\) with candidates/u.exec(output);\n return match?.[1] === undefined ? null : Number(match[1]);\n}\n\n/** The last few meaningful lines of a command's output, ANSI stripped, for a receipt. */\nfunction tail(output: string, lines = 4): string {\n // oxlint-disable-next-line no-control-regex -- stripping terminal colour codes\n const ansi = /\\u001b\\[[0-9;]*m/gu;\n return output\n .replaceAll(ansi, \"\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line !== \"\")\n .slice(-lines)\n .join(\" | \")\n .slice(0, 600);\n}\n\n/**\n * Runs deepsec (vercel-labs/deepsec) against the sandbox checkout and records\n * what it finds through the same path as record_finding, so every deepsec\n * finding becomes a fix task subject to the approval policy. Trusted code\n * owns the command sequence and the credential; the agent chooses when to\n * scan and reports the outcome.\n */\nexport function createDeepsecScanTool<S extends CommandSandbox>(\n input: DeepsecScanToolOptions<S>,\n): DeepsecScanTool {\n const { stores, ...options } = input;\n const repoPath = options.repoPath ?? \"repo\";\n const workspace = options.workspacePath ?? \"deepsec\";\n const version = options.version ?? \"2.3.8\";\n const agent = options.agent ?? \"claude\";\n const model = options.model ?? \"claude-sonnet-4-6\";\n const thinkingLevel = options.thinkingLevel ?? \"medium\";\n const concurrency = options.concurrency ?? 4;\n const prefix = options.commandPrefix === undefined ? [] : [options.commandPrefix];\n const filesPerRun = options.filesPerRun ?? 40;\n const now = options.now ?? Date.now;\n\n return defineTool({\n description:\n \"Run deepsec, the AI-powered vulnerability scanner, over the repository checkout and record every finding it reports as a fix task. Takes minutes. Call once per scan; then summarize the result.\",\n inputSchema: deepsecScanInputSchema,\n async execute({ repositoryId, limit }, ctx): Promise<DeepsecScanResult> {\n const repository = await stores.repositories.get(repositoryId);\n if (repository === null) {\n return {\n scanned: false as const,\n code: \"repository_not_found\",\n reason: `repository ${repositoryId} does not exist`,\n };\n }\n const sandbox = (await ctx.getSandbox()) as unknown as S;\n const projectId = repository.slug.replace(/[^a-z0-9]+/gu, \"-\");\n const startedAt = now();\n\n // One receipt per phase, so the ledger answers \"where is the scan\" while\n // deepsec runs and shows what each phase cost afterwards.\n const phase = async (state: string, reason: string): Promise<void> => {\n await stores.receipts.append({\n repositoryIds: [repositoryId],\n kind: \"deepsec\",\n state,\n phase: \"security_scan\",\n reason: `${reason} (${Math.round((now() - startedAt) / 1000)}s in)`,\n });\n };\n await phase(\"started\", `deepsec ${version} on ${repository.slug}, ${agent}/${model}`);\n\n // Workspace: scaffolded once per sandbox, installed from npm. deepsec\n // needs Node 24; the sandbox bootstrap is expected to provide it.\n const prepare = await sandbox.run({\n command: [\n ...prefix,\n \"set -e\",\n `if [ ! -f ${workspace}/deepsec.config.ts ]; then`,\n ` npx -y deepsec@${version} init ./${workspace} ./${repoPath} --id ${projectId} --scaffold-only --headless --yes --skip-install`,\n ` (cd ${workspace} && npm install --no-audit --no-fund --loglevel=error)`,\n \"fi\",\n // deepsec resolves deepsec.config.ts from the current directory, so\n // every deepsec command runs inside the workspace. No --root: it is a\n // one-off override that skips the project's file records, leaving\n // `process` with nothing to investigate.\n `cd ${workspace}`,\n `./node_modules/.bin/deepsec scan --project-id ${projectId}`,\n ].join(\"\\n\"),\n });\n if (typeof prepare.exitCode === \"number\" && prepare.exitCode !== 0) {\n const detail = (prepare.stderr ?? prepare.stdout).trim().slice(-1500);\n await phase(\"failed\", `scan phase failed: ${detail.slice(-300)}`);\n return {\n scanned: false as const,\n code: \"scan_failed\",\n reason: `deepsec scan failed: ${detail}`,\n };\n }\n const candidates = candidateCount(prepare.stdout);\n await phase(\n \"scanned\",\n `${candidates === null ? \"scan output\" : `${candidates} files with candidates`}: ${tail(`${prepare.stdout}\\n${prepare.stderr ?? \"\"}`)}`,\n );\n\n // Investigation talks to the model through the AI Gateway (deepsec's\n // default route); this is the only step that needs a credential.\n const investigate = await options.withModelCredential({\n sandbox,\n command: [\n ...prefix,\n \"set -e\",\n `cd ${workspace}`,\n `./node_modules/.bin/deepsec process --project-id ${projectId} --agent ${agent} --model ${model} --thinking-level ${thinkingLevel} --concurrency ${concurrency} --limit ${limit ?? filesPerRun}`,\n `./node_modules/.bin/deepsec export --project-id ${projectId} --format json --out ${findingsPath}`,\n ].join(\"\\n\"),\n });\n if (typeof investigate.exitCode === \"number\" && investigate.exitCode !== 0) {\n const detail = (investigate.stderr ?? investigate.stdout).trim().slice(-1500);\n await phase(\"failed\", `process phase failed: ${detail.slice(-300)}`);\n return {\n scanned: false as const,\n code: \"process_failed\",\n reason: `deepsec process failed: ${detail}`,\n };\n }\n\n const exported = await sandbox.run({ command: `cat ${findingsPath}` });\n let parsed: unknown;\n try {\n parsed = JSON.parse(exported.stdout);\n } catch {\n return {\n scanned: false as const,\n code: \"invalid_export_json\",\n reason: \"deepsec export was not valid JSON\",\n };\n }\n const raw = deepsecExportSchema.safeParse(parsed);\n if (!raw.success) {\n return {\n scanned: false as const,\n code: \"invalid_export_shape\",\n reason: `deepsec export had an unexpected shape: ${raw.error.issues[0]?.message ?? \"unknown\"}`,\n };\n }\n\n const results: { finding: Finding; result: AdmitFindingResult }[] = [];\n const taskId = optionalTaskIdForSession(ctx.session.auth.initiator);\n const scan = taskId === null ? null : await stores.tasks.get(taskId);\n const replyTo =\n scan?.replyTo.channel === \"slack\" && scan.repositoryIds.includes(repositoryId)\n ? scan.replyTo\n : options.replyTo;\n for (const entry of raw.data) {\n const finding = findingFromDeepsec(entry);\n results.push({\n finding,\n result: await admitFinding(stores, {\n repositoryId,\n finding,\n ...options,\n replyTo,\n }),\n });\n }\n const recorded = results.filter(({ result }) => result.recorded);\n await phase(\n \"completed\",\n `${results.length} findings, ${recorded.length} new. process output: ${tail(`${investigate.stdout}\\n${investigate.stderr ?? \"\"}`, 6)}`,\n );\n return {\n scanned: true as const,\n tool: \"deepsec\",\n found: results.length,\n recorded: recorded.length,\n duplicates: results.length - recorded.length,\n awaitingApproval: recorded.filter(\n ({ result }) => result.recorded && result.approval === \"required\",\n ).length,\n findings: results.map(({ finding, result }) => ({\n title: finding.title,\n priority: finding.priority,\n path: finding.path,\n line: finding.line,\n ...(result.recorded\n ? { taskId: result.taskId, approval: result.approval }\n : { duplicate: true }),\n })),\n };\n },\n });\n}\n"],"mappings":";;;;;;;;AA4BA,MAAa,yBAAyB,EAAE,OAAO;CAC7C,cAAc;CACd,OAAO,EACJ,OAAO,CAAC,CACR,IAAI,CAAC,CACL,IAAI,CAAC,CAAC,CACN,IAAI,GAAG,CAAC,CACR,SAAS,CAAC,CACV,SACC,oGACF;AACJ,CAAC;AA8FD,MAAM,eAAe;;AAGrB,SAAS,eAAe,QAA+B;CACrD,MAAM,QAAQ,qCAAqC,KAAK,MAAM;CAC9D,OAAO,QAAQ,OAAO,KAAA,IAAY,OAAO,OAAO,MAAM,EAAE;AAC1D;;AAGA,SAAS,KAAK,QAAgB,QAAQ,GAAW;CAG/C,OAAO,OACJ,WAAW,sBAAM,EAAE,CAAC,CACpB,MAAM,IAAI,CAAC,CACX,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,QAAQ,SAAS,SAAS,EAAE,CAAC,CAC7B,MAAM,CAAC,KAAK,CAAC,CACb,KAAK,KAAK,CAAC,CACX,MAAM,GAAG,GAAG;AACjB;;;;;;;;AASA,SAAgB,sBACd,OACiB;CACjB,MAAM,EAAE,QAAQ,GAAG,YAAY;CAC/B,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,YAAY,QAAQ,iBAAiB;CAC3C,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,gBAAgB,QAAQ,iBAAiB;CAC/C,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,SAAS,QAAQ,kBAAkB,KAAA,IAAY,CAAC,IAAI,CAAC,QAAQ,aAAa;CAChF,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,MAAM,QAAQ,OAAO,KAAK;CAEhC,OAAO,WAAW;EAChB,aACE;EACF,aAAa;EACb,MAAM,QAAQ,EAAE,cAAc,SAAS,KAAiC;GACtE,MAAM,aAAa,MAAM,OAAO,aAAa,IAAI,YAAY;GAC7D,IAAI,eAAe,MACjB,OAAO;IACL,SAAS;IACT,MAAM;IACN,QAAQ,cAAc,aAAa;GACrC;GAEF,MAAM,UAAW,MAAM,IAAI,WAAW;GACtC,MAAM,YAAY,WAAW,KAAK,QAAQ,gBAAgB,GAAG;GAC7D,MAAM,YAAY,IAAI;GAItB,MAAM,QAAQ,OAAO,OAAe,WAAkC;IACpE,MAAM,OAAO,SAAS,OAAO;KAC3B,eAAe,CAAC,YAAY;KAC5B,MAAM;KACN;KACA,OAAO;KACP,QAAQ,GAAG,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,aAAa,GAAI,EAAE;IAC/D,CAAC;GACH;GACA,MAAM,MAAM,WAAW,WAAW,QAAQ,MAAM,WAAW,KAAK,IAAI,MAAM,GAAG,OAAO;GAIpF,MAAM,UAAU,MAAM,QAAQ,IAAI,EAChC,SAAS;IACP,GAAG;IACH;IACA,aAAa,UAAU;IACvB,oBAAoB,QAAQ,UAAU,UAAU,KAAK,SAAS,QAAQ,UAAU;IAChF,SAAS,UAAU;IACnB;IAKA,MAAM;IACN,iDAAiD;GACnD,CAAC,CAAC,KAAK,IAAI,EACb,CAAC;GACD,IAAI,OAAO,QAAQ,aAAa,YAAY,QAAQ,aAAa,GAAG;IAClE,MAAM,UAAU,QAAQ,UAAU,QAAQ,OAAA,CAAQ,KAAK,CAAC,CAAC,MAAM,KAAK;IACpE,MAAM,MAAM,UAAU,sBAAsB,OAAO,MAAM,IAAI,GAAG;IAChE,OAAO;KACL,SAAS;KACT,MAAM;KACN,QAAQ,wBAAwB;IAClC;GACF;GACA,MAAM,aAAa,eAAe,QAAQ,MAAM;GAChD,MAAM,MACJ,WACA,GAAG,eAAe,OAAO,gBAAgB,GAAG,WAAW,wBAAwB,IAAI,KAAK,GAAG,QAAQ,OAAO,IAAI,QAAQ,UAAU,IAAI,GACtI;GAIA,MAAM,cAAc,MAAM,QAAQ,oBAAoB;IACpD;IACA,SAAS;KACP,GAAG;KACH;KACA,MAAM;KACN,oDAAoD,UAAU,WAAW,MAAM,WAAW,MAAM,oBAAoB,cAAc,iBAAiB,YAAY,WAAW,SAAS;KACnL,mDAAmD,UAAU,uBAAuB;IACtF,CAAC,CAAC,KAAK,IAAI;GACb,CAAC;GACD,IAAI,OAAO,YAAY,aAAa,YAAY,YAAY,aAAa,GAAG;IAC1E,MAAM,UAAU,YAAY,UAAU,YAAY,OAAA,CAAQ,KAAK,CAAC,CAAC,MAAM,KAAK;IAC5E,MAAM,MAAM,UAAU,yBAAyB,OAAO,MAAM,IAAI,GAAG;IACnE,OAAO;KACL,SAAS;KACT,MAAM;KACN,QAAQ,2BAA2B;IACrC;GACF;GAEA,MAAM,WAAW,MAAM,QAAQ,IAAI,EAAE,SAAS,OAAO,eAAe,CAAC;GACrE,IAAI;GACJ,IAAI;IACF,SAAS,KAAK,MAAM,SAAS,MAAM;GACrC,QAAQ;IACN,OAAO;KACL,SAAS;KACT,MAAM;KACN,QAAQ;IACV;GACF;GACA,MAAM,MAAM,oBAAoB,UAAU,MAAM;GAChD,IAAI,CAAC,IAAI,SACP,OAAO;IACL,SAAS;IACT,MAAM;IACN,QAAQ,2CAA2C,IAAI,MAAM,OAAO,EAAE,EAAE,WAAW;GACrF;GAGF,MAAM,UAA8D,CAAC;GACrE,MAAM,SAAS,yBAAyB,IAAI,QAAQ,KAAK,SAAS;GAClE,MAAM,OAAO,WAAW,OAAO,OAAO,MAAM,OAAO,MAAM,IAAI,MAAM;GACnE,MAAM,UACJ,MAAM,QAAQ,YAAY,WAAW,KAAK,cAAc,SAAS,YAAY,IACzE,KAAK,UACL,QAAQ;GACd,KAAK,MAAM,SAAS,IAAI,MAAM;IAC5B,MAAM,UAAU,mBAAmB,KAAK;IACxC,QAAQ,KAAK;KACX;KACA,QAAQ,MAAM,aAAa,QAAQ;MACjC;MACA;MACA,GAAG;MACH;KACF,CAAC;IACH,CAAC;GACH;GACA,MAAM,WAAW,QAAQ,QAAQ,EAAE,aAAa,OAAO,QAAQ;GAC/D,MAAM,MACJ,aACA,GAAG,QAAQ,OAAO,aAAa,SAAS,OAAO,wBAAwB,KAAK,GAAG,YAAY,OAAO,IAAI,YAAY,UAAU,MAAM,CAAC,GACrI;GACA,OAAO;IACL,SAAS;IACT,MAAM;IACN,OAAO,QAAQ;IACf,UAAU,SAAS;IACnB,YAAY,QAAQ,SAAS,SAAS;IACtC,kBAAkB,SAAS,QACxB,EAAE,aAAa,OAAO,YAAY,OAAO,aAAa,UACzD,CAAC,CAAC;IACF,UAAU,QAAQ,KAAK,EAAE,SAAS,cAAc;KAC9C,OAAO,QAAQ;KACf,UAAU,QAAQ;KAClB,MAAM,QAAQ;KACd,MAAM,QAAQ;KACd,GAAI,OAAO,WACP;MAAE,QAAQ,OAAO;MAAQ,UAAU,OAAO;KAAS,IACnD,EAAE,WAAW,KAAK;IACxB,EAAE;GACJ;EACF;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Finding } from "../schema/finding.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/deepsec/finding.d.ts
|
|
4
|
+
/** Deepsec's exported finding shape, as far as Factory depends on it. */
|
|
5
|
+
declare const deepsecFindingSchema: z.ZodReadonly<z.ZodObject<{
|
|
6
|
+
title: z.ZodString;
|
|
7
|
+
description: z.ZodString;
|
|
8
|
+
severity: z.ZodString;
|
|
9
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
10
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
11
|
+
lineNumbers: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodNumber>>>;
|
|
12
|
+
vulnSlug: z.ZodOptional<z.ZodString>;
|
|
13
|
+
confidence: z.ZodOptional<z.ZodString>;
|
|
14
|
+
runId: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, z.core.$loose>>;
|
|
16
|
+
}, z.core.$loose>>;
|
|
17
|
+
/** Deepsec export document accepted by the optional scanner adapter. */
|
|
18
|
+
declare const deepsecExportSchema: z.ZodUnion<readonly [z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodObject<{
|
|
19
|
+
title: z.ZodString;
|
|
20
|
+
description: z.ZodString;
|
|
21
|
+
severity: z.ZodString;
|
|
22
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
23
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
24
|
+
lineNumbers: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodNumber>>>;
|
|
25
|
+
vulnSlug: z.ZodOptional<z.ZodString>;
|
|
26
|
+
confidence: z.ZodOptional<z.ZodString>;
|
|
27
|
+
runId: z.ZodOptional<z.ZodString>;
|
|
28
|
+
}, z.core.$loose>>;
|
|
29
|
+
}, z.core.$loose>>>>, z.ZodPipe<z.ZodObject<{
|
|
30
|
+
findings: z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodObject<{
|
|
31
|
+
title: z.ZodString;
|
|
32
|
+
description: z.ZodString;
|
|
33
|
+
severity: z.ZodString;
|
|
34
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
35
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
36
|
+
lineNumbers: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodNumber>>>;
|
|
37
|
+
vulnSlug: z.ZodOptional<z.ZodString>;
|
|
38
|
+
confidence: z.ZodOptional<z.ZodString>;
|
|
39
|
+
runId: z.ZodOptional<z.ZodString>;
|
|
40
|
+
}, z.core.$loose>>;
|
|
41
|
+
}, z.core.$loose>>>>;
|
|
42
|
+
}, z.core.$loose>, z.ZodTransform<readonly Readonly<{
|
|
43
|
+
[x: string]: unknown;
|
|
44
|
+
title: string;
|
|
45
|
+
description: string;
|
|
46
|
+
severity: string;
|
|
47
|
+
metadata?: {
|
|
48
|
+
[x: string]: unknown;
|
|
49
|
+
filePath?: string | undefined;
|
|
50
|
+
lineNumbers?: readonly number[] | undefined;
|
|
51
|
+
vulnSlug?: string | undefined;
|
|
52
|
+
confidence?: string | undefined;
|
|
53
|
+
runId?: string | undefined;
|
|
54
|
+
} | undefined;
|
|
55
|
+
}>[], {
|
|
56
|
+
[x: string]: unknown;
|
|
57
|
+
findings: readonly Readonly<{
|
|
58
|
+
[x: string]: unknown;
|
|
59
|
+
title: string;
|
|
60
|
+
description: string;
|
|
61
|
+
severity: string;
|
|
62
|
+
metadata?: {
|
|
63
|
+
[x: string]: unknown;
|
|
64
|
+
filePath?: string | undefined;
|
|
65
|
+
lineNumbers?: readonly number[] | undefined;
|
|
66
|
+
vulnSlug?: string | undefined;
|
|
67
|
+
confidence?: string | undefined;
|
|
68
|
+
runId?: string | undefined;
|
|
69
|
+
} | undefined;
|
|
70
|
+
}>[];
|
|
71
|
+
}>>]>;
|
|
72
|
+
/** Provider payload accepted for one Deepsec finding before normalization. */
|
|
73
|
+
type DeepsecFindingInput = z.input<typeof deepsecFindingSchema>;
|
|
74
|
+
/** Parsed Deepsec finding payload used by the normalization adapter. */
|
|
75
|
+
type DeepsecFinding = z.output<typeof deepsecFindingSchema>;
|
|
76
|
+
/** Provider export document accepted before its wrapper is normalized. */
|
|
77
|
+
type DeepsecExportInput = z.input<typeof deepsecExportSchema>;
|
|
78
|
+
/** Parsed list of Deepsec findings produced by the export schema. */
|
|
79
|
+
type DeepsecExport = z.output<typeof deepsecExportSchema>;
|
|
80
|
+
/** Validate and convert one Deepsec finding into Factory's normalized Finding contract. */
|
|
81
|
+
declare function findingFromDeepsec(input: DeepsecFindingInput): Finding;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { DeepsecExport, DeepsecExportInput, DeepsecFinding, DeepsecFindingInput, deepsecExportSchema, deepsecFindingSchema, findingFromDeepsec };
|
|
84
|
+
//# sourceMappingURL=finding.d.mts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { findingSchema } from "../schema/finding.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/deepsec/finding.ts
|
|
4
|
+
/** Deepsec's exported finding shape, as far as Factory depends on it. */
|
|
5
|
+
const deepsecFindingSchema = z.looseObject({
|
|
6
|
+
title: z.string().min(1),
|
|
7
|
+
description: z.string().min(1),
|
|
8
|
+
severity: z.string().min(1),
|
|
9
|
+
metadata: z.looseObject({
|
|
10
|
+
filePath: z.string().min(1).optional(),
|
|
11
|
+
lineNumbers: z.array(z.number().int().min(1)).readonly().optional(),
|
|
12
|
+
vulnSlug: z.string().min(1).optional(),
|
|
13
|
+
confidence: z.string().optional(),
|
|
14
|
+
runId: z.string().optional()
|
|
15
|
+
}).optional()
|
|
16
|
+
}).readonly();
|
|
17
|
+
const deepsecFindingsSchema = z.array(deepsecFindingSchema).readonly();
|
|
18
|
+
/** Deepsec export document accepted by the optional scanner adapter. */
|
|
19
|
+
const deepsecExportSchema = z.union([deepsecFindingsSchema, z.looseObject({ findings: deepsecFindingsSchema }).transform((value) => value.findings)]);
|
|
20
|
+
const priorityBySeverity = {
|
|
21
|
+
CRITICAL: "P0",
|
|
22
|
+
HIGH: "P1",
|
|
23
|
+
MEDIUM: "P2",
|
|
24
|
+
HIGH_BUG: "P2",
|
|
25
|
+
BUG: "P3",
|
|
26
|
+
LOW: "P3"
|
|
27
|
+
};
|
|
28
|
+
/** Validate and convert one Deepsec finding into Factory's normalized Finding contract. */
|
|
29
|
+
function findingFromDeepsec(input) {
|
|
30
|
+
const raw = deepsecFindingSchema.parse(input);
|
|
31
|
+
const meta = raw.metadata ?? {};
|
|
32
|
+
const confidence = (meta.confidence ?? "").toLowerCase();
|
|
33
|
+
const line = meta.lineNumbers?.[0];
|
|
34
|
+
const endLine = meta.lineNumbers === void 0 ? void 0 : Math.max(...meta.lineNumbers);
|
|
35
|
+
return findingSchema.parse({
|
|
36
|
+
title: raw.title.replace(/^\[[A-Z_]+\]\s*/u, "").slice(0, 200),
|
|
37
|
+
body: raw.description.slice(0, 8e3),
|
|
38
|
+
...meta.filePath === void 0 ? {} : { path: meta.filePath.slice(0, 500) },
|
|
39
|
+
...line === void 0 ? {} : { line },
|
|
40
|
+
...endLine === void 0 || endLine === line ? {} : { endLine },
|
|
41
|
+
priority: priorityBySeverity[raw.severity.toUpperCase()] ?? "P3",
|
|
42
|
+
category: "security",
|
|
43
|
+
verification: {
|
|
44
|
+
level: confidence === "high" ? "supported" : "unverified",
|
|
45
|
+
method: "static_analysis",
|
|
46
|
+
summary: `deepsec ${meta.vulnSlug ?? "finding"} with ${confidence || "unknown"} confidence${meta.runId === void 0 ? "" : ` (run ${meta.runId})`}.`,
|
|
47
|
+
limitations: "AI-assisted static analysis of the checkout; not reproduced at runtime."
|
|
48
|
+
},
|
|
49
|
+
tool: "deepsec",
|
|
50
|
+
...meta.vulnSlug === void 0 ? {} : { ruleId: meta.vulnSlug }
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { deepsecExportSchema, deepsecFindingSchema, findingFromDeepsec };
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=finding.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finding.mjs","names":[],"sources":["../../src/deepsec/finding.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { findingSchema, type Finding, type FindingPriority } from \"../schema/finding\";\n\n/** Deepsec's exported finding shape, as far as Factory depends on it. */\nexport const deepsecFindingSchema = z\n .looseObject({\n title: z.string().min(1),\n description: z.string().min(1),\n severity: z.string().min(1),\n metadata: z\n .looseObject({\n filePath: z.string().min(1).optional(),\n lineNumbers: z.array(z.number().int().min(1)).readonly().optional(),\n vulnSlug: z.string().min(1).optional(),\n confidence: z.string().optional(),\n runId: z.string().optional(),\n })\n .optional(),\n })\n .readonly();\n\nconst deepsecFindingsSchema = z.array(deepsecFindingSchema).readonly();\n\n/** Deepsec export document accepted by the optional scanner adapter. */\nexport const deepsecExportSchema = z.union([\n deepsecFindingsSchema,\n z.looseObject({ findings: deepsecFindingsSchema }).transform((value) => value.findings),\n]);\n\n/** Provider payload accepted for one Deepsec finding before normalization. */\nexport type DeepsecFindingInput = z.input<typeof deepsecFindingSchema>;\n\n/** Parsed Deepsec finding payload used by the normalization adapter. */\nexport type DeepsecFinding = z.output<typeof deepsecFindingSchema>;\n\n/** Provider export document accepted before its wrapper is normalized. */\nexport type DeepsecExportInput = z.input<typeof deepsecExportSchema>;\n\n/** Parsed list of Deepsec findings produced by the export schema. */\nexport type DeepsecExport = z.output<typeof deepsecExportSchema>;\n\nconst priorityBySeverity: Record<string, FindingPriority> = {\n CRITICAL: \"P0\",\n HIGH: \"P1\",\n MEDIUM: \"P2\",\n HIGH_BUG: \"P2\",\n BUG: \"P3\",\n LOW: \"P3\",\n};\n\n/** Validate and convert one Deepsec finding into Factory's normalized Finding contract. */\nexport function findingFromDeepsec(input: DeepsecFindingInput): Finding {\n const raw = deepsecFindingSchema.parse(input);\n const meta = raw.metadata ?? {};\n const confidence = (meta.confidence ?? \"\").toLowerCase();\n const line = meta.lineNumbers?.[0];\n const endLine = meta.lineNumbers === undefined ? undefined : Math.max(...meta.lineNumbers);\n return findingSchema.parse({\n title: raw.title.replace(/^\\[[A-Z_]+\\]\\s*/u, \"\").slice(0, 200),\n body: raw.description.slice(0, 8000),\n ...(meta.filePath === undefined ? {} : { path: meta.filePath.slice(0, 500) }),\n ...(line === undefined ? {} : { line }),\n ...(endLine === undefined || endLine === line ? {} : { endLine }),\n priority: priorityBySeverity[raw.severity.toUpperCase()] ?? \"P3\",\n category: \"security\",\n verification: {\n level: confidence === \"high\" ? \"supported\" : \"unverified\",\n method: \"static_analysis\",\n summary: `deepsec ${meta.vulnSlug ?? \"finding\"} with ${confidence || \"unknown\"} confidence${meta.runId === undefined ? \"\" : ` (run ${meta.runId})`}.`,\n limitations: \"AI-assisted static analysis of the checkout; not reproduced at runtime.\",\n },\n tool: \"deepsec\",\n ...(meta.vulnSlug === undefined ? {} : { ruleId: meta.vulnSlug }),\n });\n}\n"],"mappings":";;;;AAIA,MAAa,uBAAuB,EACjC,YAAY;CACX,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAC7B,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAC1B,UAAU,EACP,YAAY;EACX,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;EACrC,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;EAClE,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;EACrC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;EAChC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,CAAC,CAAC,CACD,SAAS;AACd,CAAC,CAAC,CACD,SAAS;AAEZ,MAAM,wBAAwB,EAAE,MAAM,oBAAoB,CAAC,CAAC,SAAS;;AAGrE,MAAa,sBAAsB,EAAE,MAAM,CACzC,uBACA,EAAE,YAAY,EAAE,UAAU,sBAAsB,CAAC,CAAC,CAAC,WAAW,UAAU,MAAM,QAAQ,CACxF,CAAC;AAcD,MAAM,qBAAsD;CAC1D,UAAU;CACV,MAAM;CACN,QAAQ;CACR,UAAU;CACV,KAAK;CACL,KAAK;AACP;;AAGA,SAAgB,mBAAmB,OAAqC;CACtE,MAAM,MAAM,qBAAqB,MAAM,KAAK;CAC5C,MAAM,OAAO,IAAI,YAAY,CAAC;CAC9B,MAAM,cAAc,KAAK,cAAc,GAAA,CAAI,YAAY;CACvD,MAAM,OAAO,KAAK,cAAc;CAChC,MAAM,UAAU,KAAK,gBAAgB,KAAA,IAAY,KAAA,IAAY,KAAK,IAAI,GAAG,KAAK,WAAW;CACzF,OAAO,cAAc,MAAM;EACzB,OAAO,IAAI,MAAM,QAAQ,oBAAoB,EAAE,CAAC,CAAC,MAAM,GAAG,GAAG;EAC7D,MAAM,IAAI,YAAY,MAAM,GAAG,GAAI;EACnC,GAAI,KAAK,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,MAAM,GAAG,GAAG,EAAE;EAC3E,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;EACrC,GAAI,YAAY,KAAA,KAAa,YAAY,OAAO,CAAC,IAAI,EAAE,QAAQ;EAC/D,UAAU,mBAAmB,IAAI,SAAS,YAAY,MAAM;EAC5D,UAAU;EACV,cAAc;GACZ,OAAO,eAAe,SAAS,cAAc;GAC7C,QAAQ;GACR,SAAS,WAAW,KAAK,YAAY,UAAU,QAAQ,cAAc,UAAU,aAAa,KAAK,UAAU,KAAA,IAAY,KAAK,SAAS,KAAK,MAAM,GAAG;GACnJ,aAAa;EACf;EACA,MAAM;EACN,GAAI,KAAK,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,KAAK,SAAS;CACjE,CAAC;AACH"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { DeepsecExport, DeepsecExportInput, DeepsecFinding, DeepsecFindingInput, deepsecExportSchema, deepsecFindingSchema, findingFromDeepsec } from "./deepsec/finding.mjs";
|
|
2
|
+
import { DeepsecAgent, DeepsecCommandResult, DeepsecCredentialCommandInput, DeepsecScanFailure, DeepsecScanFailureCode, DeepsecScanFinding, DeepsecScanInput, DeepsecScanResult, DeepsecScanSuccess, DeepsecScanTool, DeepsecScanToolOptions, DeepsecScanToolStores, DeepsecThinkingLevel, createDeepsecScanTool, deepsecScanInputSchema } from "./deepsec/eve-tool.mjs";
|
|
3
|
+
export { type DeepsecAgent, type DeepsecCommandResult, type DeepsecCredentialCommandInput, type DeepsecExport, type DeepsecExportInput, type DeepsecFinding, type DeepsecFindingInput, type DeepsecScanFailure, type DeepsecScanFailureCode, type DeepsecScanFinding, type DeepsecScanInput, type DeepsecScanResult, type DeepsecScanSuccess, type DeepsecScanTool, type DeepsecScanToolOptions, type DeepsecScanToolStores, type DeepsecThinkingLevel, createDeepsecScanTool, deepsecExportSchema, deepsecFindingSchema, deepsecScanInputSchema, findingFromDeepsec };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Signal } from "../schema/signal.mjs";
|
|
2
|
+
import { Task } from "../schema/task.mjs";
|
|
3
|
+
import { TaskGraph } from "../schema/task-graph.mjs";
|
|
4
|
+
import { Change, ChangeLevel } from "../schema/change.mjs";
|
|
5
|
+
import { Receipt } from "../schema/receipt.mjs";
|
|
6
|
+
//#region src/delivery-metrics/metrics.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* The derived review bucket from the default risk x confidence table. Derived
|
|
9
|
+
* at read time, never stored; "blocked" is a bucket, not a change state.
|
|
10
|
+
*/
|
|
11
|
+
type ChangeReviewBucket = "stamp" | "review" | "blocked";
|
|
12
|
+
/** Risk and confidence inputs used to derive a human-review bucket. */
|
|
13
|
+
interface ChangeReviewFactors {
|
|
14
|
+
risk: ChangeLevel;
|
|
15
|
+
confidence: ChangeLevel;
|
|
16
|
+
}
|
|
17
|
+
/** Derives the default human-review bucket from a change's risk and confidence. */
|
|
18
|
+
declare function changeReviewBucket(input: ChangeReviewFactors): ChangeReviewBucket;
|
|
19
|
+
/** Validated ledger rows used to derive Factory metrics and health. */
|
|
20
|
+
interface DeliveryLedgerRows {
|
|
21
|
+
changes: readonly Change[];
|
|
22
|
+
signals: readonly Signal[];
|
|
23
|
+
receipts: readonly Receipt[];
|
|
24
|
+
tasks: readonly Task[];
|
|
25
|
+
/** Validated graph snapshots keyed by rootTaskId. Missing snapshots make health yellow, never runnable. */
|
|
26
|
+
graphs?: ReadonlyMap<string, TaskGraph>;
|
|
27
|
+
}
|
|
28
|
+
/** Options controlling ledger metric evaluation. */
|
|
29
|
+
interface ComputeLedgerMetricsOptions {
|
|
30
|
+
/** Evaluation time; defaults to the current time. */
|
|
31
|
+
now?: Date;
|
|
32
|
+
/** Days a landed change must hold without a rollback. Default 7. */
|
|
33
|
+
holdDays?: number;
|
|
34
|
+
}
|
|
35
|
+
/** Product metrics derived deterministically from ledger rows. */
|
|
36
|
+
interface DeliveryMetrics {
|
|
37
|
+
/**
|
|
38
|
+
* Changes a human merged that reached deployed (rolled_out for high risk)
|
|
39
|
+
* and did not roll back within the hold window. Includes provisional ones.
|
|
40
|
+
*/
|
|
41
|
+
landed: number;
|
|
42
|
+
/** Landed changes still inside the hold window; the number may drop. */
|
|
43
|
+
provisional: number;
|
|
44
|
+
/** landed / (landed + rolled back + closed after ready). Null when nothing decided. */
|
|
45
|
+
stickRate: number | null;
|
|
46
|
+
/**
|
|
47
|
+
* Not computable from ledger rows: needs a record of human commits pushed
|
|
48
|
+
* to the pull request after ready, which nothing writes today.
|
|
49
|
+
*/
|
|
50
|
+
stampedClean: null;
|
|
51
|
+
/** Median ms from the ready receipt to the merged receipt, per review bucket. */
|
|
52
|
+
humanTimeMs: {
|
|
53
|
+
stamp: number | null;
|
|
54
|
+
review: number | null;
|
|
55
|
+
};
|
|
56
|
+
/** Sum of settled task cost over landed changes / landed. Null until budgets settle. */
|
|
57
|
+
costPerLandedUsd: number | null;
|
|
58
|
+
/** Signals triage turned away plus changes closed in verification. */
|
|
59
|
+
filtered: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The six product metrics from ledger rows alone. One rule governs them: a
|
|
63
|
+
* change counts only after a human merged it and it stayed. Activity that
|
|
64
|
+
* never landed is not a result. Compute over rows already filtered to the
|
|
65
|
+
* period and repositories you are reporting on.
|
|
66
|
+
*/
|
|
67
|
+
declare function computeLedgerMetrics(rows: DeliveryLedgerRows, options?: ComputeLedgerMetricsOptions): DeliveryMetrics;
|
|
68
|
+
/** Overall repository health severity derived from operational evidence. */
|
|
69
|
+
type RepositoryHealthColor = "green" | "yellow" | "red";
|
|
70
|
+
/** Options controlling repository health evaluation. */
|
|
71
|
+
interface ComputeRepositoryHealthOptions {
|
|
72
|
+
now?: Date;
|
|
73
|
+
/** Open-change cap from factory configuration; omitted disables that check. */
|
|
74
|
+
maxOpenChanges?: number;
|
|
75
|
+
/** Minutes an eligible executable task may remain queued before health turns yellow. Default 5. */
|
|
76
|
+
maxQueuedMinutes?: number;
|
|
77
|
+
}
|
|
78
|
+
/** Explainable repository health derived from ledger and graph state. */
|
|
79
|
+
interface RepositoryHealth {
|
|
80
|
+
readonly color: RepositoryHealthColor;
|
|
81
|
+
/** Every rule that fired, so the color is explainable from rows. */
|
|
82
|
+
readonly reasons: readonly string[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Green, yellow, or red from rows, never from a model. Budget threshold and
|
|
86
|
+
* onboarding rules are not evaluated here yet: budget notices live on the
|
|
87
|
+
* budgets collection (not in these rows), and onboarding investigations are
|
|
88
|
+
* not recorded.
|
|
89
|
+
*/
|
|
90
|
+
declare function computeRepositoryHealth(rows: DeliveryLedgerRows, options?: ComputeRepositoryHealthOptions): RepositoryHealth;
|
|
91
|
+
//#endregion
|
|
92
|
+
export { ChangeReviewBucket, ChangeReviewFactors, ComputeLedgerMetricsOptions, ComputeRepositoryHealthOptions, DeliveryLedgerRows, DeliveryMetrics, RepositoryHealth, RepositoryHealthColor, changeReviewBucket, computeLedgerMetrics, computeRepositoryHealth };
|
|
93
|
+
//# sourceMappingURL=metrics.d.mts.map
|