cloving 1.1.0 → 1.3.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/README.md +584 -144
- package/assets/watch-fonts/fira-code-OFL.md +93 -0
- package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-OFL.md +93 -0
- package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
- package/assets/watch-fonts/manifest.json +112 -0
- package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
- package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
- package/dist/src/agent-harness-shell.d.ts +12 -0
- package/dist/src/agent-harness-shell.js +90 -0
- package/dist/src/agent-harness-versions.d.ts +8 -0
- package/dist/src/agent-harness-versions.js +49 -0
- package/dist/src/agent-harnesses.d.ts +4 -0
- package/dist/src/agent-harnesses.js +13 -0
- package/dist/src/authority/commands.d.ts +2 -0
- package/dist/src/authority/commands.js +49 -8
- package/dist/src/authority/dispatch-decision.d.ts +1 -0
- package/dist/src/authority/dispatch-decision.js +7 -4
- package/dist/src/authority/events.js +2 -2
- package/dist/src/authority/gate-reduction.d.ts +1 -0
- package/dist/src/authority/gate-reduction.js +18 -3
- package/dist/src/authority/gates.d.ts +2 -1
- package/dist/src/authority/gates.js +5 -1
- package/dist/src/authority/handoff-chain.d.ts +24 -0
- package/dist/src/authority/handoff-chain.js +46 -0
- package/dist/src/authority/operations.js +1 -1
- package/dist/src/authority/reconciliation.d.ts +1 -0
- package/dist/src/authority/reconciliation.js +18 -11
- package/dist/src/authority/reduction.d.ts +1 -1
- package/dist/src/authority/reduction.js +6 -1
- package/dist/src/authority/status-input.d.ts +1 -1
- package/dist/src/authority/status.d.ts +1 -0
- package/dist/src/authority/status.js +27 -3
- package/dist/src/authority/transitions.d.ts +5 -5
- package/dist/src/authority/transitions.js +9 -0
- package/dist/src/broker/actor/authority-delivery.js +1 -0
- package/dist/src/broker/actor/authority-queries.d.ts +11 -1
- package/dist/src/broker/actor/authority-queries.js +67 -3
- package/dist/src/broker/actor/authority-receipts.js +11 -1
- package/dist/src/broker/actor/authority-session.js +2 -2
- package/dist/src/broker/actor/authority-settings.js +4 -0
- package/dist/src/broker/actor/delivery-intents.d.ts +3 -1
- package/dist/src/broker/actor/delivery-intents.js +4 -4
- package/dist/src/broker/actor/effect-delivery.js +13 -1
- package/dist/src/broker/actor/effect-queries.d.ts +1 -0
- package/dist/src/broker/actor/effect-queries.js +7 -0
- package/dist/src/broker/actor/pin-check.js +1 -1
- package/dist/src/broker/actor/stop-plan.js +1 -1
- package/dist/src/broker/actor/turn-state.js +1 -1
- package/dist/src/broker/authority-admission.js +26 -5
- package/dist/src/broker/authority-restore.js +1 -0
- package/dist/src/broker/decision-restore.js +4 -4
- package/dist/src/broker/socket-address.d.ts +3 -1
- package/dist/src/broker/socket-address.js +2 -2
- package/dist/src/cli/arguments.d.ts +11 -13
- package/dist/src/cli/arguments.js +37 -107
- package/dist/src/cli/command-effects.d.ts +7 -0
- package/dist/src/cli/command-effects.js +39 -0
- package/dist/src/cli/commands.js +11 -4
- package/dist/src/cli/config-arguments.d.ts +11 -0
- package/dist/src/cli/config-arguments.js +59 -0
- package/dist/src/cli/config-command.d.ts +2 -0
- package/dist/src/cli/config-command.js +105 -0
- package/dist/src/cli/config-file.d.ts +3 -0
- package/dist/src/cli/config-file.js +50 -0
- package/dist/src/cli/config-lock.d.ts +7 -0
- package/dist/src/cli/config-lock.js +61 -0
- package/dist/src/cli/config-notice.d.ts +5 -0
- package/dist/src/cli/config-notice.js +46 -0
- package/dist/src/cli/doctor-github.d.ts +9 -0
- package/dist/src/cli/doctor-github.js +138 -0
- package/dist/src/cli/doctor-harness.d.ts +13 -0
- package/dist/src/cli/doctor-harness.js +242 -0
- package/dist/src/cli/doctor-local.d.ts +18 -0
- package/dist/src/cli/doctor-local.js +77 -0
- package/dist/src/cli/doctor-runtime.d.ts +15 -0
- package/dist/src/cli/doctor-runtime.js +176 -0
- package/dist/src/cli/doctor-service.d.ts +1 -0
- package/dist/src/cli/doctor-service.js +11 -0
- package/dist/src/cli/doctor-workflows.d.ts +3 -0
- package/dist/src/cli/doctor-workflows.js +45 -0
- package/dist/src/cli/doctor.d.ts +15 -0
- package/dist/src/cli/doctor.js +190 -0
- package/dist/src/cli/github-ci-progress.d.ts +8 -0
- package/dist/src/cli/github-ci-progress.js +83 -0
- package/dist/src/cli/github-retry.d.ts +6 -0
- package/dist/src/cli/github-retry.js +47 -0
- package/dist/src/cli/harness-probes.d.ts +22 -0
- package/dist/src/cli/harness-probes.js +183 -0
- package/dist/src/cli/main.d.ts +7 -1
- package/dist/src/cli/main.js +39 -61
- package/dist/src/cli/phone-arguments.d.ts +14 -0
- package/dist/src/cli/phone-arguments.js +37 -0
- package/dist/src/cli/phone-client.d.ts +2 -0
- package/dist/src/cli/phone-client.js +32 -0
- package/dist/src/cli/phone-command.d.ts +13 -0
- package/dist/src/cli/phone-command.js +101 -0
- package/dist/src/cli/probe-command.d.ts +36 -0
- package/dist/src/cli/probe-command.js +59 -0
- package/dist/src/cli/project-settings.d.ts +4 -0
- package/dist/src/cli/project-settings.js +33 -0
- package/dist/src/cli/render.d.ts +3 -1
- package/dist/src/cli/render.js +102 -39
- package/dist/src/cli/report-github.d.ts +18 -0
- package/dist/src/cli/report-github.js +200 -0
- package/dist/src/cli/report.d.ts +17 -0
- package/dist/src/cli/report.js +192 -0
- package/dist/src/cli/repository-arguments.d.ts +5 -0
- package/dist/src/cli/repository-arguments.js +34 -0
- package/dist/src/cli/runtime-arguments.d.ts +4 -0
- package/dist/src/cli/runtime-arguments.js +15 -0
- package/dist/src/cli/runtime-directory.d.ts +6 -2
- package/dist/src/cli/runtime-directory.js +72 -14
- package/dist/src/cli/toolchain.d.ts +2 -0
- package/dist/src/cli/toolchain.js +22 -2
- package/dist/src/cli/usage-render.d.ts +5 -0
- package/dist/src/cli/usage-render.js +40 -1
- package/dist/src/cli/usage.d.ts +3 -3
- package/dist/src/cli/usage.js +74 -45
- package/dist/src/cli/watch-actions.d.ts +2 -0
- package/dist/src/cli/watch-actions.js +4 -4
- package/dist/src/cli/watch-board-view.d.ts +12 -1
- package/dist/src/cli/watch-board-view.js +64 -12
- package/dist/src/cli/watch-board.d.ts +7 -1
- package/dist/src/cli/watch-board.js +61 -16
- package/dist/src/cli/watch-card-forms.js +2 -2
- package/dist/src/cli/watch-client.d.ts +1 -1
- package/dist/src/cli/watch-client.js +12 -3
- package/dist/src/cli/watch-header-view.d.ts +27 -0
- package/dist/src/cli/watch-header-view.js +116 -0
- package/dist/src/cli/watch-model.d.ts +10 -4
- package/dist/src/cli/watch-model.js +49 -59
- package/dist/src/cli/watch-page.js +67 -14
- package/dist/src/cli/watch-phases.js +3 -3
- package/dist/src/cli/watch-phone-client.d.ts +1 -0
- package/dist/src/cli/watch-phone-client.js +84 -0
- package/dist/src/cli/watch-phone-view.d.ts +56 -0
- package/dist/src/cli/watch-phone-view.js +113 -0
- package/dist/src/cli/watch-phone.d.ts +5 -0
- package/dist/src/cli/watch-phone.js +53 -0
- package/dist/src/cli/watch-pr-command.d.ts +5 -2
- package/dist/src/cli/watch-pr-command.js +41 -4
- package/dist/src/cli/watch-pr-identity.d.ts +14 -2
- package/dist/src/cli/watch-pr-identity.js +25 -0
- package/dist/src/cli/watch-pr-observer.d.ts +1 -0
- package/dist/src/cli/watch-pr-observer.js +48 -6
- package/dist/src/cli/watch-pull-request.d.ts +12 -0
- package/dist/src/cli/watch-pull-request.js +134 -0
- package/dist/src/cli/watch-reads.d.ts +7 -1
- package/dist/src/cli/watch-reads.js +42 -20
- package/dist/src/cli/watch-render.d.ts +23 -2
- package/dist/src/cli/watch-render.js +2 -1
- package/dist/src/cli/watch-server.js +56 -7
- package/dist/src/cli/watch-turn.d.ts +4 -1
- package/dist/src/cli/watch-turn.js +38 -2
- package/dist/src/cli/watch-usage.js +5 -1
- package/dist/src/cli/watch-view.d.ts +10 -17
- package/dist/src/cli/watch-view.js +33 -77
- package/dist/src/cli.js +106 -23
- package/dist/src/command-settings.d.ts +16 -0
- package/dist/src/command-settings.js +42 -0
- package/dist/src/configuration-change.d.ts +11 -0
- package/dist/src/configuration-change.js +62 -0
- package/dist/src/configuration-display.d.ts +21 -0
- package/dist/src/configuration-display.js +69 -0
- package/dist/src/configuration-schema.d.ts +49 -0
- package/dist/src/configuration-schema.js +159 -0
- package/dist/src/daemon/claude-turn.d.ts +20 -5
- package/dist/src/daemon/claude-turn.js +116 -53
- package/dist/src/daemon/claude-worker.d.ts +3 -5
- package/dist/src/daemon/claude-worker.js +64 -80
- package/dist/src/daemon/command-journal.d.ts +5 -0
- package/dist/src/daemon/command-journal.js +50 -0
- package/dist/src/daemon/command-process.d.ts +28 -0
- package/dist/src/daemon/command-process.js +164 -0
- package/dist/src/daemon/command-worker.d.ts +19 -0
- package/dist/src/daemon/command-worker.js +182 -0
- package/dist/src/daemon/cursor-stream.d.ts +4 -0
- package/dist/src/daemon/cursor-stream.js +154 -0
- package/dist/src/daemon/daemon-adapters.d.ts +58 -0
- package/dist/src/daemon/daemon-adapters.js +102 -0
- package/dist/src/daemon/daemon-loops.d.ts +14 -0
- package/dist/src/daemon/daemon-loops.js +39 -0
- package/dist/src/daemon/daemon.d.ts +3 -30
- package/dist/src/daemon/daemon.js +132 -110
- package/dist/src/daemon/declared-input.d.ts +3 -0
- package/dist/src/daemon/declared-input.js +9 -0
- package/dist/src/daemon/delivery-router.js +7 -1
- package/dist/src/daemon/gemini-stream.d.ts +4 -0
- package/dist/src/daemon/gemini-stream.js +129 -0
- package/dist/src/daemon/github-calls.d.ts +4 -0
- package/dist/src/daemon/github-calls.js +16 -7
- package/dist/src/daemon/github-ci-rerun.d.ts +26 -0
- package/dist/src/daemon/github-ci-rerun.js +92 -0
- package/dist/src/daemon/github-ci.d.ts +6 -0
- package/dist/src/daemon/github-ci.js +251 -0
- package/dist/src/daemon/github-merge-call.d.ts +16 -0
- package/dist/src/daemon/github-merge-call.js +73 -0
- package/dist/src/daemon/github-merge.d.ts +3 -5
- package/dist/src/daemon/github-merge.js +59 -98
- package/dist/src/daemon/github-methods.d.ts +28 -0
- package/dist/src/daemon/github-methods.js +90 -16
- package/dist/src/daemon/github-operation.d.ts +3 -0
- package/dist/src/daemon/github-retry.d.ts +6 -0
- package/dist/src/daemon/github-retry.js +13 -0
- package/dist/src/daemon/github-ticket-status.js +27 -1
- package/dist/src/daemon/github-worker.js +2 -1
- package/dist/src/daemon/handoff-worker.d.ts +10 -0
- package/dist/src/daemon/handoff-worker.js +232 -0
- package/dist/src/daemon/issue-queue.d.ts +2 -0
- package/dist/src/daemon/issue-queue.js +92 -63
- package/dist/src/daemon/notify-journal.d.ts +18 -0
- package/dist/src/daemon/notify-journal.js +108 -0
- package/dist/src/daemon/notify-message.js +2 -1
- package/dist/src/daemon/notify.js +177 -37
- package/dist/src/daemon/opencode-stream.d.ts +5 -0
- package/dist/src/daemon/opencode-stream.js +87 -0
- package/dist/src/daemon/queue-status.d.ts +4 -0
- package/dist/src/daemon/queue-status.js +38 -0
- package/dist/src/daemon/retry-worktree.d.ts +18 -0
- package/dist/src/daemon/retry-worktree.js +139 -0
- package/dist/src/daemon/retrying-turn.d.ts +5 -0
- package/dist/src/daemon/retrying-turn.js +145 -21
- package/dist/src/daemon/schedule-journal.d.ts +19 -0
- package/dist/src/daemon/schedule-journal.js +57 -0
- package/dist/src/daemon/text-output.d.ts +18 -0
- package/dist/src/daemon/text-output.js +44 -0
- package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
- package/dist/src/daemon/ticket-dependency-waits.js +60 -0
- package/dist/src/daemon/ticket-registration.d.ts +10 -8
- package/dist/src/daemon/ticket-registration.js +146 -47
- package/dist/src/daemon/ticket-source.d.ts +4 -1
- package/dist/src/daemon/ticket-source.js +8 -2
- package/dist/src/daemon/turn-command.d.ts +10 -0
- package/dist/src/daemon/turn-command.js +33 -0
- package/dist/src/daemon/turn-report.d.ts +18 -0
- package/dist/src/daemon/turn-report.js +79 -0
- package/dist/src/daemon/work-tree.d.ts +2 -1
- package/dist/src/daemon/work-tree.js +15 -12
- package/dist/src/daemon/workflow-generations.d.ts +32 -0
- package/dist/src/daemon/workflow-generations.js +57 -0
- package/dist/src/daemon/workflow-replay.d.ts +16 -0
- package/dist/src/daemon/workflow-replay.js +58 -0
- package/dist/src/daemon/workflow-schedules.d.ts +27 -0
- package/dist/src/daemon/workflow-schedules.js +185 -0
- package/dist/src/decision-visibility.d.ts +2 -0
- package/dist/src/decision-visibility.js +10 -0
- package/dist/src/decision-wait.d.ts +1 -0
- package/dist/src/decision-wait.js +3 -0
- package/dist/src/delivery-backoff.d.ts +1 -0
- package/dist/src/delivery-backoff.js +3 -0
- package/dist/src/doctor-report.d.ts +24 -0
- package/dist/src/doctor-report.js +70 -0
- package/dist/src/execution-pin.d.ts +9 -0
- package/dist/src/execution-pin.js +86 -2
- package/dist/src/grok-result.d.ts +12 -0
- package/dist/src/grok-result.js +61 -0
- package/dist/src/harness-defaults.d.ts +35 -0
- package/dist/src/harness-defaults.js +121 -0
- package/dist/src/host/decision-resume.d.ts +35 -0
- package/dist/src/host/decision-resume.js +86 -0
- package/dist/src/host/gate-bridge.d.ts +48 -0
- package/dist/src/host/gate-bridge.js +124 -0
- package/dist/src/host/run-wait.d.ts +26 -0
- package/dist/src/host/run-wait.js +37 -0
- package/dist/src/host/start-delivery.d.ts +3 -2
- package/dist/src/host/start-delivery.js +11 -4
- package/dist/src/host/step-bridge-host.d.ts +6 -19
- package/dist/src/host/step-bridge-host.js +57 -55
- package/dist/src/host/world-reconciliation.d.ts +1 -0
- package/dist/src/host/world-reconciliation.js +3 -0
- package/dist/src/host/world-runs.js +5 -0
- package/dist/src/issue-identity.d.ts +6 -0
- package/dist/src/issue-identity.js +23 -0
- package/dist/src/merge-request.d.ts +19 -0
- package/dist/src/merge-request.js +32 -0
- package/dist/src/phone/canonical-json.d.ts +1 -0
- package/dist/src/phone/canonical-json.js +16 -0
- package/dist/src/phone/phone-link.d.ts +7 -0
- package/dist/src/phone/phone-link.js +21 -0
- package/dist/src/phone/qr-data.d.ts +8 -0
- package/dist/src/phone/qr-data.js +133 -0
- package/dist/src/phone/qr-matrix.d.ts +6 -0
- package/dist/src/phone/qr-matrix.js +175 -0
- package/dist/src/phone/qr-penalty.d.ts +3 -0
- package/dist/src/phone/qr-penalty.js +67 -0
- package/dist/src/phone/qr-picture.d.ts +5 -0
- package/dist/src/phone/qr-picture.js +24 -0
- package/dist/src/private-config.schema.json +263 -0
- package/dist/src/project-config.schema.json +65 -0
- package/dist/src/project-settings.d.ts +19 -0
- package/dist/src/project-settings.js +131 -0
- package/dist/src/relay/answer-check.d.ts +24 -0
- package/dist/src/relay/answer-check.js +145 -0
- package/dist/src/relay/answer-inbox.d.ts +15 -0
- package/dist/src/relay/answer-inbox.js +173 -0
- package/dist/src/relay/daemon-keys.d.ts +16 -0
- package/dist/src/relay/daemon-keys.js +75 -0
- package/dist/src/relay/decision-check.d.ts +3 -0
- package/dist/src/relay/decision-check.js +77 -0
- package/dist/src/relay/decision-close.d.ts +6 -0
- package/dist/src/relay/decision-close.js +72 -0
- package/dist/src/relay/decision-envelopes.d.ts +29 -0
- package/dist/src/relay/decision-envelopes.js +93 -0
- package/dist/src/relay/decision-watch.d.ts +14 -0
- package/dist/src/relay/decision-watch.js +68 -0
- package/dist/src/relay/envelope-post.d.ts +8 -0
- package/dist/src/relay/envelope-post.js +71 -0
- package/dist/src/relay/envelope.d.ts +28 -0
- package/dist/src/relay/envelope.js +53 -0
- package/dist/src/relay/hpke.d.ts +25 -0
- package/dist/src/relay/hpke.js +108 -0
- package/dist/src/relay/pairing-checks.d.ts +42 -0
- package/dist/src/relay/pairing-checks.js +172 -0
- package/dist/src/relay/pairing-follow.d.ts +8 -0
- package/dist/src/relay/pairing-follow.js +95 -0
- package/dist/src/relay/pairing-session.d.ts +29 -0
- package/dist/src/relay/pairing-session.js +87 -0
- package/dist/src/relay/phone-delivery.d.ts +51 -0
- package/dist/src/relay/phone-delivery.js +139 -0
- package/dist/src/relay/phone-devices.d.ts +36 -0
- package/dist/src/relay/phone-devices.js +46 -0
- package/dist/src/relay/phone-inbox.d.ts +18 -0
- package/dist/src/relay/phone-inbox.js +17 -0
- package/dist/src/relay/phone-operations.d.ts +18 -0
- package/dist/src/relay/phone-operations.js +121 -0
- package/dist/src/relay/phone-outbox.d.ts +21 -0
- package/dist/src/relay/phone-outbox.js +25 -0
- package/dist/src/relay/phone-socket.d.ts +6 -0
- package/dist/src/relay/phone-socket.js +63 -0
- package/dist/src/relay/relay-control.d.ts +7 -0
- package/dist/src/relay/relay-control.js +24 -0
- package/dist/src/relay/relay-files.d.ts +2 -0
- package/dist/src/relay/relay-files.js +23 -0
- package/dist/src/relay/relay-heartbeat.d.ts +17 -0
- package/dist/src/relay/relay-heartbeat.js +81 -0
- package/dist/src/relay/relay-http.d.ts +14 -0
- package/dist/src/relay/relay-http.js +63 -0
- package/dist/src/relay/relay-registration.d.ts +15 -0
- package/dist/src/relay/relay-registration.js +63 -0
- package/dist/src/relay/relay-service.d.ts +25 -0
- package/dist/src/relay/relay-service.js +216 -0
- package/dist/src/relay/relay-store.d.ts +48 -0
- package/dist/src/relay/relay-store.js +131 -0
- package/dist/src/relay/sealed-post.d.ts +25 -0
- package/dist/src/relay/sealed-post.js +52 -0
- package/dist/src/relay.d.ts +1 -0
- package/dist/src/relay.js +8 -0
- package/dist/src/report-statistics.d.ts +50 -0
- package/dist/src/report-statistics.js +70 -0
- package/dist/src/report.d.ts +171 -0
- package/dist/src/report.js +119 -0
- package/dist/src/repository-identity.d.ts +4 -0
- package/dist/src/repository-identity.js +4 -0
- package/dist/src/review-subject.d.ts +41 -0
- package/dist/src/review-subject.js +91 -0
- package/dist/src/run-wait.d.ts +23 -0
- package/dist/src/run-wait.js +20 -0
- package/dist/src/service/active-tickets.d.ts +4 -0
- package/dist/src/service/active-tickets.js +49 -0
- package/dist/src/service/command-effects.d.ts +4 -0
- package/dist/src/service/command-effects.js +16 -0
- package/dist/src/service/decision-handler.d.ts +4 -0
- package/dist/src/service/decision-handler.js +39 -4
- package/dist/src/service/gateway.js +18 -3
- package/dist/src/service/queue-runs.d.ts +3 -0
- package/dist/src/service/queue-runs.js +39 -0
- package/dist/src/service/registry.d.ts +22 -3
- package/dist/src/service/registry.js +51 -8
- package/dist/src/service/replies.d.ts +10 -0
- package/dist/src/service/replies.js +12 -0
- package/dist/src/service/run-handlers.js +45 -54
- package/dist/src/service/run-metadata.d.ts +8 -0
- package/dist/src/service/run-metadata.js +53 -0
- package/dist/src/service/run-waits.d.ts +3 -0
- package/dist/src/service/run-waits.js +41 -0
- package/dist/src/service/service.d.ts +21 -2
- package/dist/src/service/service.js +23 -5
- package/dist/src/service/submission-handler.js +70 -19
- package/dist/src/service/worker-port.d.ts +1 -0
- package/dist/src/service/worker-totals.js +10 -1
- package/dist/src/step-bridge.d.ts +32 -0
- package/dist/src/steps/decision-gate.d.ts +2 -1
- package/dist/src/steps/decision-gate.js +7 -0
- package/dist/src/steps/handoff.d.ts +2 -0
- package/dist/src/steps/handoff.js +13 -0
- package/dist/src/steps/merge-by-person.d.ts +2 -0
- package/dist/src/steps/merge-by-person.js +18 -0
- package/dist/src/steps/merge-when-green.d.ts +2 -0
- package/dist/src/steps/merge-when-green.js +62 -0
- package/dist/src/steps/review-by-agent.d.ts +5 -0
- package/dist/src/steps/review-by-agent.js +124 -0
- package/dist/src/steps/review-by-person.d.ts +4 -0
- package/dist/src/steps/review-by-person.js +16 -0
- package/dist/src/steps/review-subject.d.ts +2 -0
- package/dist/src/steps/review-subject.js +11 -0
- package/dist/src/steps/run-command.d.ts +2 -0
- package/dist/src/steps/run-command.js +15 -0
- package/dist/src/steps/sleep.d.ts +3 -0
- package/dist/src/steps/sleep.js +19 -0
- package/dist/src/steps/wait-for-decision.d.ts +2 -0
- package/dist/src/steps/wait-for-decision.js +71 -0
- package/dist/src/steps/wait-for-run.d.ts +5 -0
- package/dist/src/steps/wait-for-run.js +9 -0
- package/dist/src/storage/decision-snapshots.d.ts +1 -0
- package/dist/src/storage/decision-snapshots.js +7 -1
- package/dist/src/submission.d.ts +4 -0
- package/dist/src/submission.js +6 -2
- package/dist/src/testing.d.ts +29 -0
- package/dist/src/testing.js +264 -0
- package/dist/src/ticket-dependencies.d.ts +1 -0
- package/dist/src/ticket-dependencies.js +15 -0
- package/dist/src/ticket-status-stream.d.ts +9 -0
- package/dist/src/ticket-status-stream.js +94 -0
- package/dist/src/ticket-status.js +7 -4
- package/dist/src/token-prices.d.ts +14 -4
- package/dist/src/token-prices.js +56 -11
- package/dist/src/turn-profile.d.ts +1 -0
- package/dist/src/turn-profile.js +8 -1
- package/dist/src/worker-usage.d.ts +25 -0
- package/dist/src/worker-usage.js +58 -1
- package/dist/src/workflow-admission.d.ts +6 -0
- package/dist/src/workflow-candidate.d.ts +7 -2
- package/dist/src/workflow-candidate.js +48 -5
- package/dist/src/workflow-contracts.d.ts +2 -0
- package/dist/src/workflow-contracts.js +8 -3
- package/dist/src/workflow-declaration.d.ts +31 -2
- package/dist/src/workflow-declaration.js +140 -16
- package/dist/src/workflow-discovery.d.ts +19 -0
- package/dist/src/workflow-discovery.js +36 -0
- package/dist/src/workflow-refusals.d.ts +6 -0
- package/dist/src/workflow-refusals.js +42 -0
- package/dist/src/workflow-schedule.d.ts +22 -0
- package/dist/src/workflow-schedule.js +53 -0
- package/dist/src/workflows.d.ts +12 -1
- package/dist/src/workflows.js +9 -0
- package/dist/tools/admit-workflow.d.ts +14 -0
- package/dist/tools/admit-workflow.js +107 -0
- package/dist/tools/doctor-adoption.d.ts +1 -0
- package/dist/tools/doctor-adoption.js +11 -0
- package/dist/tools/project-configuration.d.ts +4 -0
- package/dist/tools/project-configuration.js +47 -0
- package/dist/tools/self-host-processes.d.ts +1 -0
- package/dist/tools/self-host-processes.js +33 -22
- package/dist/tools/self-host-project.d.ts +21 -0
- package/dist/tools/self-host-project.js +224 -0
- package/dist/tools/self-host-runtime.d.ts +16 -0
- package/dist/tools/self-host-runtime.js +60 -0
- package/dist/tools/self-host-workflows.d.ts +1 -0
- package/dist/tools/self-host-workflows.js +36 -1
- package/dist/tools/self-host.js +106 -135
- package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
- package/dist/tools/workflow-bundle-dependencies.js +93 -0
- package/dist/tools/workflow-bundles.d.ts +20 -0
- package/dist/tools/workflow-bundles.js +173 -0
- package/dist/tools/workflow-candidate-counts.d.ts +12 -0
- package/dist/tools/workflow-candidate-counts.js +266 -0
- package/dist/tools/workflow-candidate-fields.d.ts +5 -0
- package/dist/tools/workflow-candidate-fields.js +24 -0
- package/dist/tools/workflow-candidate-helper-counts.d.ts +10 -0
- package/dist/tools/workflow-candidate-helper-counts.js +129 -0
- package/dist/tools/workflow-candidate-program.d.ts +13 -0
- package/dist/tools/workflow-candidate-program.js +106 -0
- package/dist/tools/workflow-candidate-source.d.ts +6 -0
- package/dist/tools/workflow-candidate-source.js +169 -0
- package/dist/tools/workflow-candidate-syntax.d.ts +21 -0
- package/dist/tools/workflow-candidate-syntax.js +263 -0
- package/dist/tools/workflow-command.d.ts +1 -0
- package/dist/tools/workflow-command.js +143 -0
- package/dist/tools/workflow-folder-capture.d.ts +18 -0
- package/dist/tools/workflow-folder-capture.js +261 -0
- package/dist/tools/workflow-folders.d.ts +49 -0
- package/dist/tools/workflow-folders.js +203 -0
- package/dist/tools/workflow-source-effects.d.ts +10 -0
- package/dist/tools/workflow-source-effects.js +213 -0
- package/dist/tools/workflow-test-hook.d.ts +7 -0
- package/dist/tools/workflow-test-hook.js +16 -0
- package/dist/tools/workflow-test-loader.d.ts +1 -0
- package/dist/tools/workflow-test-loader.js +10 -0
- package/dist/workflows/develop-prd.js +71 -3
- package/dist/workflows/develop-ticket.js +6 -1
- package/dist/workflows/ticket-prompts.js +17 -3
- package/package.json +66 -6
- package/templates/develop/README.md +78 -0
- package/templates/develop/develop/accept.ts +11 -0
- package/templates/develop/develop/flow-tests.ts +357 -0
- package/templates/develop/develop/local.ts +40 -0
- package/templates/develop/develop/option-tests.ts +174 -0
- package/templates/develop/develop/partial-choice.ts +29 -0
- package/templates/develop/develop/partial.ts +28 -0
- package/templates/develop/develop/pull.ts +152 -0
- package/templates/develop/develop/queue-partial.ts +39 -0
- package/templates/develop/develop/refusal-tests.ts +174 -0
- package/templates/develop/develop/report.ts +156 -0
- package/templates/develop/develop/round.ts +40 -0
- package/templates/develop/develop/scenarios.ts +87 -0
- package/templates/develop/develop/turn.ts +67 -0
- package/templates/develop/develop.declaration.json +24 -0
- package/templates/develop/develop.test.ts +287 -0
- package/templates/develop/develop.ts +30 -0
- package/templates/develop/template.json +153 -0
- package/dist/src/chats/analysis-batches.d.ts +0 -24
- package/dist/src/chats/analysis-batches.js +0 -83
- package/dist/src/chats/analysis-contract.d.ts +0 -80
- package/dist/src/chats/analysis-contract.js +0 -16
- package/dist/src/chats/analysis-data.d.ts +0 -3
- package/dist/src/chats/analysis-data.js +0 -85
- package/dist/src/chats/analysis-input.d.ts +0 -12
- package/dist/src/chats/analysis-input.js +0 -83
- package/dist/src/chats/analysis-links.d.ts +0 -2
- package/dist/src/chats/analysis-links.js +0 -25
- package/dist/src/chats/analysis-prompt.d.ts +0 -10
- package/dist/src/chats/analysis-prompt.js +0 -16
- package/dist/src/chats/analysis-proposal.d.ts +0 -2
- package/dist/src/chats/analysis-proposal.js +0 -32
- package/dist/src/chats/analysis-provenance.d.ts +0 -4
- package/dist/src/chats/analysis-provenance.js +0 -99
- package/dist/src/chats/analysis-shape.d.ts +0 -3
- package/dist/src/chats/analysis-shape.js +0 -228
- package/dist/src/chats/analysis.d.ts +0 -3
- package/dist/src/chats/analysis.js +0 -42
- package/dist/src/chats/corpus-input.d.ts +0 -5
- package/dist/src/chats/corpus-input.js +0 -288
- package/dist/src/chats/destination.d.ts +0 -4
- package/dist/src/chats/destination.js +0 -96
- package/dist/src/chats/import.d.ts +0 -15
- package/dist/src/chats/import.js +0 -243
- package/dist/src/chats/input-file.d.ts +0 -3
- package/dist/src/chats/input-file.js +0 -32
- package/dist/src/chats/normalize.d.ts +0 -29
- package/dist/src/chats/normalize.js +0 -155
- package/dist/src/chats/previous-import.d.ts +0 -5
- package/dist/src/chats/previous-import.js +0 -34
- package/dist/src/chats/reader.d.ts +0 -21
- package/dist/src/chats/reader.js +0 -118
- package/dist/src/chats/redact-import.d.ts +0 -14
- package/dist/src/chats/redact-import.js +0 -141
- package/dist/src/chats/redaction.d.ts +0 -41
- package/dist/src/chats/redaction.js +0 -161
- package/dist/src/chats/revision-input.d.ts +0 -3
- package/dist/src/chats/revision-input.js +0 -180
- package/dist/src/chats/revision.d.ts +0 -48
- package/dist/src/chats/revision.js +0 -94
- package/dist/src/chats/selection.d.ts +0 -25
- package/dist/src/chats/selection.js +0 -109
- package/dist/src/chats/source.d.ts +0 -23
- package/dist/src/chats/source.js +0 -135
- package/dist/src/chats/structure.d.ts +0 -57
- package/dist/src/chats/structure.js +0 -216
- package/dist/src/daemon/analysis-launch.d.ts +0 -5
- package/dist/src/daemon/analysis-launch.js +0 -81
- package/dist/src/daemon/analysis-token.d.ts +0 -6
- package/dist/src/daemon/analysis-token.js +0 -88
- package/dist/src/daemon/analysis-worker.d.ts +0 -22
- package/dist/src/daemon/analysis-worker.js +0 -219
- package/dist/src/daemon/chats-files.d.ts +0 -15
- package/dist/src/daemon/chats-files.js +0 -113
- package/dist/src/daemon/chats-worker.d.ts +0 -12
- package/dist/src/daemon/chats-worker.js +0 -200
- package/dist/workflows/analyze-chats.d.ts +0 -10
- package/dist/workflows/analyze-chats.declaration.json +0 -9
- package/dist/workflows/analyze-chats.js +0 -73
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { absoluteWorkflowFolder } from "../src/project-settings.js";
|
|
2
|
+
import { projectConfiguration, projectRoot } from "./project-configuration.js";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { lstat, mkdtemp, rm } from "node:fs/promises";
|
|
5
|
+
import { homedir, tmpdir } from "node:os";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
import { workflowTestLoader } from "./workflow-test-loader.js";
|
|
9
|
+
import { preparedProject } from "./self-host-project.js";
|
|
10
|
+
const usage = "Usage: cloving workflows list [--json] [--repository <directory>]\n cloving workflows test [<id>] [--repository <directory>]\n";
|
|
11
|
+
function invocation(argumentsList) {
|
|
12
|
+
const { values, positionals } = parseArgs({
|
|
13
|
+
args: [...argumentsList],
|
|
14
|
+
allowPositionals: true,
|
|
15
|
+
options: {
|
|
16
|
+
json: { type: "boolean" },
|
|
17
|
+
repository: { type: "string" },
|
|
18
|
+
help: { type: "boolean", short: "h" },
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const [command, id, ...extra] = positionals;
|
|
22
|
+
if (extra.length > 0 || (command === "list" && id !== undefined))
|
|
23
|
+
throw new Error("unexpected-argument");
|
|
24
|
+
if (command !== "list" && command !== "test")
|
|
25
|
+
throw new Error("unknown-command");
|
|
26
|
+
if (command === "test" && values.json)
|
|
27
|
+
throw new Error("unexpected-option");
|
|
28
|
+
return { ...values, command, id };
|
|
29
|
+
}
|
|
30
|
+
export async function workflowCommand(argumentsList) {
|
|
31
|
+
let selected;
|
|
32
|
+
try {
|
|
33
|
+
selected = invocation(argumentsList);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
process.stderr.write("invalid-invocation: " + usage);
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
if (selected.help) {
|
|
40
|
+
process.stdout.write(usage);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
const temporary = await mkdtemp(join(tmpdir(), "cloving-workflows-"));
|
|
44
|
+
try {
|
|
45
|
+
const repository = resolve(selected.repository ?? process.cwd());
|
|
46
|
+
const result = await preparedProject({
|
|
47
|
+
product: resolve(import.meta.dirname, ".."),
|
|
48
|
+
project: join(temporary, "project"),
|
|
49
|
+
repository,
|
|
50
|
+
home: homedir(),
|
|
51
|
+
xdgConfigHome: process.env["XDG_CONFIG_HOME"],
|
|
52
|
+
userFolder: absoluteWorkflowFolder((await projectConfiguration(await projectRoot(repository), process.env))
|
|
53
|
+
.environment["CLOVING_WORKFLOW_FOLDER"]),
|
|
54
|
+
});
|
|
55
|
+
if (selected.command === "test")
|
|
56
|
+
return await testWorkflows(result.workflows, {
|
|
57
|
+
id: selected.id,
|
|
58
|
+
repository,
|
|
59
|
+
});
|
|
60
|
+
presentWorkflows(result.workflows, { json: selected.json === true });
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
process.stderr.write("workflow-inspection-failed: " +
|
|
65
|
+
(error instanceof Error ? error.message : "unavailable") +
|
|
66
|
+
"\n");
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await rm(temporary, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function presentWorkflows(workflows, options) {
|
|
74
|
+
if (options.json) {
|
|
75
|
+
process.stdout.write(JSON.stringify(workflows) + "\n");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const workflow of workflows)
|
|
79
|
+
process.stdout.write(`${workflow.workflowId ?? "unknown"} (${workflow.scope}, ${workflow.admitted ? "admitted" : "refused: " + workflow.codes.join(", ")}) ${JSON.stringify(workflow.file)}\n`);
|
|
80
|
+
}
|
|
81
|
+
async function testWorkflows(workflows, options) {
|
|
82
|
+
const { id, repository } = options;
|
|
83
|
+
const selected = workflows.filter((workflow) => workflow.scope !== "bundled" &&
|
|
84
|
+
(id === undefined || workflow.workflowId === id));
|
|
85
|
+
if (id !== undefined &&
|
|
86
|
+
!workflows.some((workflow) => workflow.workflowId === id)) {
|
|
87
|
+
process.stderr.write("unknown-workflow: " + id + "\n");
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
const files = [];
|
|
91
|
+
for (const workflow of selected) {
|
|
92
|
+
const file = workflow.file.replace(/\.ts$/, ".test.ts");
|
|
93
|
+
const exists = await lstat(file).catch((error) => {
|
|
94
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
95
|
+
return undefined;
|
|
96
|
+
throw error;
|
|
97
|
+
});
|
|
98
|
+
if (exists?.isFile())
|
|
99
|
+
files.push(file);
|
|
100
|
+
}
|
|
101
|
+
if (files.length === 0) {
|
|
102
|
+
process.stderr.write("workflow-test-missing: " + (id ?? "no project or user tests") + "\n");
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
return await runTests(files, repository);
|
|
106
|
+
}
|
|
107
|
+
function runTests(files, repository) {
|
|
108
|
+
return new Promise((settle, reject) => {
|
|
109
|
+
const child = spawn(process.execPath, [
|
|
110
|
+
"--experimental-strip-types",
|
|
111
|
+
"--import",
|
|
112
|
+
workflowTestLoader(),
|
|
113
|
+
"--test",
|
|
114
|
+
...files,
|
|
115
|
+
], {
|
|
116
|
+
cwd: repository,
|
|
117
|
+
stdio: "inherit",
|
|
118
|
+
env: { ...process.env, NODE_TEST_CONTEXT: undefined },
|
|
119
|
+
});
|
|
120
|
+
const interrupt = () => {
|
|
121
|
+
child.kill("SIGINT");
|
|
122
|
+
};
|
|
123
|
+
const terminate = () => {
|
|
124
|
+
child.kill("SIGTERM");
|
|
125
|
+
};
|
|
126
|
+
process.on("SIGINT", interrupt);
|
|
127
|
+
process.on("SIGTERM", terminate);
|
|
128
|
+
const cleanup = () => {
|
|
129
|
+
process.off("SIGINT", interrupt);
|
|
130
|
+
process.off("SIGTERM", terminate);
|
|
131
|
+
};
|
|
132
|
+
child.once("error", (error) => {
|
|
133
|
+
cleanup();
|
|
134
|
+
reject(error);
|
|
135
|
+
});
|
|
136
|
+
child.once("close", (code) => {
|
|
137
|
+
cleanup();
|
|
138
|
+
settle(code ?? 1);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (process.argv[1] === import.meta.filename)
|
|
143
|
+
process.exitCode = await workflowCommand(process.argv.slice(2));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
export type WorkflowCaptureCode = "unsafe-workflow-path" | "workflow-read-failed" | "workflow-source-changed" | "unsupported-source" | "invalid-declaration";
|
|
3
|
+
export interface WorkflowFolder {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly components: readonly {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly stats: BigIntStats;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
export declare function inspectWorkflowFolder(root: string, parts: readonly string[]): Promise<WorkflowFolder>;
|
|
11
|
+
export declare function captureWorkflowCandidate(folder: WorkflowFolder, name: string): Promise<{
|
|
12
|
+
source: string | undefined;
|
|
13
|
+
declaration: string | undefined;
|
|
14
|
+
helpers: Record<string, string>;
|
|
15
|
+
codes: WorkflowCaptureCode[];
|
|
16
|
+
}>;
|
|
17
|
+
export declare function workflowCaptureFailure(error: unknown): WorkflowCaptureCode;
|
|
18
|
+
export declare function workflowFolderEntries(folder: WorkflowFolder): Promise<string[]>;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat, open, readdir, realpath, } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
import { workflowHelperPath } from "../src/workflow-candidate.js";
|
|
6
|
+
function contained(root, path) {
|
|
7
|
+
const suffix = relative(root, path);
|
|
8
|
+
return (!isAbsolute(suffix) && suffix !== ".." && !suffix.startsWith(`..${sep}`));
|
|
9
|
+
}
|
|
10
|
+
function safeComponent(name) {
|
|
11
|
+
return (name.length > 0 &&
|
|
12
|
+
name !== "." &&
|
|
13
|
+
name !== ".." &&
|
|
14
|
+
!/[/\\:]/.test(name) &&
|
|
15
|
+
!name.startsWith("~"));
|
|
16
|
+
}
|
|
17
|
+
export async function inspectWorkflowFolder(root, parts) {
|
|
18
|
+
const canonical = await realpath(root);
|
|
19
|
+
if (parts.some((part) => !safeComponent(part)))
|
|
20
|
+
throw new Error("unsafe-workflow-path");
|
|
21
|
+
const components = [];
|
|
22
|
+
let path = canonical;
|
|
23
|
+
for (const part of ["", ...parts]) {
|
|
24
|
+
path = join(path, part);
|
|
25
|
+
const stats = await lstat(path, { bigint: true }).catch(async (error) => {
|
|
26
|
+
await verifyFolder({ path, components });
|
|
27
|
+
throw error;
|
|
28
|
+
});
|
|
29
|
+
if (stats.isSymbolicLink())
|
|
30
|
+
throw new Error("unsafe-workflow-path");
|
|
31
|
+
if (!stats.isDirectory())
|
|
32
|
+
throw new Error("workflow-read-failed");
|
|
33
|
+
components.push({ path, stats });
|
|
34
|
+
}
|
|
35
|
+
const resolved = await realpath(path).catch(changedPathError);
|
|
36
|
+
const folder = { path, components };
|
|
37
|
+
await verifyFolder(folder);
|
|
38
|
+
if (!contained(canonical, resolved))
|
|
39
|
+
throw new Error("unsafe-workflow-path");
|
|
40
|
+
return folder;
|
|
41
|
+
}
|
|
42
|
+
function sameIdentity(left, right) {
|
|
43
|
+
return (left.dev === right.dev && left.ino === right.ino && left.mode === right.mode);
|
|
44
|
+
}
|
|
45
|
+
function sameSnapshot(left, right) {
|
|
46
|
+
return (sameIdentity(left, right) &&
|
|
47
|
+
left.size === right.size &&
|
|
48
|
+
left.mtimeNs === right.mtimeNs &&
|
|
49
|
+
left.ctimeNs === right.ctimeNs);
|
|
50
|
+
}
|
|
51
|
+
async function verifyFolder(folder) {
|
|
52
|
+
for (const component of folder.components) {
|
|
53
|
+
const compare = component.path === folder.path ? sameSnapshot : sameIdentity;
|
|
54
|
+
if (!compare(component.stats, await capturedStats(component.path)))
|
|
55
|
+
throw new Error("workflow-source-changed");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function capturedStats(path) {
|
|
59
|
+
return lstat(path, { bigint: true }).catch(changedPathError);
|
|
60
|
+
}
|
|
61
|
+
function changedPathError(error) {
|
|
62
|
+
if (error instanceof Error &&
|
|
63
|
+
"code" in error &&
|
|
64
|
+
(error.code === "ENOENT" ||
|
|
65
|
+
error.code === "ENOTDIR" ||
|
|
66
|
+
error.code === "ELOOP"))
|
|
67
|
+
throw new Error("workflow-source-changed");
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
async function openSource(folder, name) {
|
|
71
|
+
const flags = constants.O_RDONLY | constants.O_NONBLOCK;
|
|
72
|
+
if (process.platform === "darwin")
|
|
73
|
+
return open(join(folder.path, name), flags | 0x20000000);
|
|
74
|
+
if (process.platform !== "linux")
|
|
75
|
+
throw new Error("workflow-read-failed");
|
|
76
|
+
const handles = [];
|
|
77
|
+
try {
|
|
78
|
+
let path = folder.components[0]?.path;
|
|
79
|
+
if (!path)
|
|
80
|
+
throw new Error("workflow-read-failed");
|
|
81
|
+
for (const component of folder.components) {
|
|
82
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
83
|
+
handles.push(handle);
|
|
84
|
+
if (!sameIdentity(component.stats, await handle.stat({ bigint: true })))
|
|
85
|
+
throw new Error("workflow-source-changed");
|
|
86
|
+
const next = folder.components[handles.length];
|
|
87
|
+
path = `/proc/self/fd/${String(handle.fd)}/${next ? relative(component.path, next.path) : name}`;
|
|
88
|
+
}
|
|
89
|
+
return await open(path, flags | constants.O_NOFOLLOW);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
for (const handle of handles.reverse())
|
|
93
|
+
await handle.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function inspectFile(folder, name) {
|
|
97
|
+
const parts = name.split("/");
|
|
98
|
+
const fileName = parts.pop();
|
|
99
|
+
if (!fileName || parts.some((part) => !safeComponent(part)))
|
|
100
|
+
throw new Error("unsafe-workflow-path");
|
|
101
|
+
let parent = folder;
|
|
102
|
+
for (const part of parts) {
|
|
103
|
+
const path = join(parent.path, part);
|
|
104
|
+
const stats = await lstat(path, { bigint: true });
|
|
105
|
+
if (!stats.isDirectory())
|
|
106
|
+
throw new Error("unsafe-workflow-path");
|
|
107
|
+
parent = { path, components: [...parent.components, { path, stats }] };
|
|
108
|
+
}
|
|
109
|
+
await verifyFolder(parent);
|
|
110
|
+
const path = join(folder.path, name);
|
|
111
|
+
const stats = await lstat(path, { bigint: true });
|
|
112
|
+
if (!stats.isFile())
|
|
113
|
+
throw new Error("unsafe-workflow-path");
|
|
114
|
+
const resolved = await realpath(path).catch(changedPathError);
|
|
115
|
+
if (!sameSnapshot(stats, await capturedStats(path)))
|
|
116
|
+
throw new Error("workflow-source-changed");
|
|
117
|
+
if (!contained(folder.path, resolved))
|
|
118
|
+
throw new Error("unsafe-workflow-path");
|
|
119
|
+
return { path, name, stats, folder: parent, fileName };
|
|
120
|
+
}
|
|
121
|
+
async function captureFile(folder, file) {
|
|
122
|
+
const { stats } = file;
|
|
123
|
+
await verifyFolder(folder);
|
|
124
|
+
const handle = await openSource(file.folder, file.fileName).catch(changedPathError);
|
|
125
|
+
try {
|
|
126
|
+
if (!sameSnapshot(stats, await handle.stat({ bigint: true })))
|
|
127
|
+
throw new Error("workflow-source-changed");
|
|
128
|
+
const bytes = await handle.readFile();
|
|
129
|
+
if (!sameSnapshot(stats, await handle.stat({ bigint: true })) ||
|
|
130
|
+
BigInt(bytes.length) !== stats.size)
|
|
131
|
+
throw new Error("workflow-source-changed");
|
|
132
|
+
return { ...file, bytes };
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
await handle.close();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function captureFiles(folder, names) {
|
|
139
|
+
const inspected = [];
|
|
140
|
+
const captured = [];
|
|
141
|
+
const codes = [];
|
|
142
|
+
for (const name of names) {
|
|
143
|
+
try {
|
|
144
|
+
inspected.push(await inspectFile(folder, name));
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
codes.push(workflowCaptureFailure(error));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const file of inspected) {
|
|
151
|
+
try {
|
|
152
|
+
captured.push(await captureFile(folder, file));
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
codes.push(workflowCaptureFailure(error));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const file of captured) {
|
|
159
|
+
await verifyFolder(file.folder);
|
|
160
|
+
if (!sameSnapshot(file.stats, await capturedStats(file.path)))
|
|
161
|
+
throw new Error("workflow-source-changed");
|
|
162
|
+
}
|
|
163
|
+
return { captured, codes };
|
|
164
|
+
}
|
|
165
|
+
async function captureImports(folder, snapshot) {
|
|
166
|
+
const { captured, codes } = snapshot;
|
|
167
|
+
const helpers = {};
|
|
168
|
+
for (const source of captured) {
|
|
169
|
+
if (!source.name.endsWith(".ts"))
|
|
170
|
+
continue;
|
|
171
|
+
for (const imported of ts.preProcessFile(source.bytes.toString("utf8"))
|
|
172
|
+
.importedFiles) {
|
|
173
|
+
const name = workflowHelperPath(source.name, imported.fileName);
|
|
174
|
+
if (!name || captured.some((file) => file.name === name))
|
|
175
|
+
continue;
|
|
176
|
+
const result = await captureFiles(folder, [name]);
|
|
177
|
+
codes.push(...result.codes);
|
|
178
|
+
captured.push(...result.captured);
|
|
179
|
+
const text = decodedFile({ bytes: result.captured[0]?.bytes, code: "unsupported-source" }, codes);
|
|
180
|
+
if (text !== undefined)
|
|
181
|
+
helpers[name] = text;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const file of captured) {
|
|
185
|
+
await verifyFolder(file.folder);
|
|
186
|
+
if (!sameSnapshot(file.stats, await capturedStats(file.path)))
|
|
187
|
+
throw new Error("workflow-source-changed");
|
|
188
|
+
}
|
|
189
|
+
return helpers;
|
|
190
|
+
}
|
|
191
|
+
function decodedFile(input, codes) {
|
|
192
|
+
if (input.bytes === undefined)
|
|
193
|
+
return undefined;
|
|
194
|
+
const text = input.bytes.toString("utf8");
|
|
195
|
+
if (input.bytes.equals(Buffer.from(text)))
|
|
196
|
+
return text;
|
|
197
|
+
codes.push(input.code);
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
export async function captureWorkflowCandidate(folder, name) {
|
|
201
|
+
if (!safeComponent(name) || !name.endsWith(".ts") || name.endsWith(".d.ts"))
|
|
202
|
+
throw new Error("unsafe-workflow-path");
|
|
203
|
+
await verifyFolder(folder);
|
|
204
|
+
const declarationName = name.slice(0, -3) + ".declaration.json";
|
|
205
|
+
const { captured, codes } = await captureFiles(folder, [
|
|
206
|
+
name,
|
|
207
|
+
declarationName,
|
|
208
|
+
]);
|
|
209
|
+
const helpers = await captureImports(folder, { captured, codes });
|
|
210
|
+
await verifyFolder(folder);
|
|
211
|
+
const source = decodedFile({
|
|
212
|
+
bytes: captured.find((file) => file.name === name)?.bytes,
|
|
213
|
+
code: "unsupported-source",
|
|
214
|
+
}, codes);
|
|
215
|
+
const declaration = decodedFile({
|
|
216
|
+
bytes: captured.find((file) => file.name === declarationName)?.bytes,
|
|
217
|
+
code: "invalid-declaration",
|
|
218
|
+
}, codes);
|
|
219
|
+
return {
|
|
220
|
+
source,
|
|
221
|
+
declaration,
|
|
222
|
+
helpers,
|
|
223
|
+
codes: [
|
|
224
|
+
...new Set(codes.includes("unsafe-workflow-path")
|
|
225
|
+
? codes.filter((code) => code !== "workflow-read-failed")
|
|
226
|
+
: codes),
|
|
227
|
+
].sort(),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
export function workflowCaptureFailure(error) {
|
|
231
|
+
const code = error instanceof Error && "code" in error ? error.code : undefined;
|
|
232
|
+
if (code === "ELOOP")
|
|
233
|
+
return "unsafe-workflow-path";
|
|
234
|
+
const message = error instanceof Error ? error.message : "";
|
|
235
|
+
if (message === "unsafe-workflow-path" ||
|
|
236
|
+
message === "workflow-source-changed" ||
|
|
237
|
+
message === "unsupported-source" ||
|
|
238
|
+
message === "invalid-declaration")
|
|
239
|
+
return message;
|
|
240
|
+
return "workflow-read-failed";
|
|
241
|
+
}
|
|
242
|
+
async function descriptorEntries(folder) {
|
|
243
|
+
const handle = await openSource(folder, ".").catch(changedPathError);
|
|
244
|
+
try {
|
|
245
|
+
const expected = folder.components.at(-1)?.stats;
|
|
246
|
+
if (!expected ||
|
|
247
|
+
!sameIdentity(expected, await handle.stat({ bigint: true })))
|
|
248
|
+
throw new Error("workflow-source-changed");
|
|
249
|
+
return await readdir(`/proc/self/fd/${String(handle.fd)}`);
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
await handle.close();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export async function workflowFolderEntries(folder) {
|
|
256
|
+
const entries = process.platform === "darwin"
|
|
257
|
+
? await readdir(`/.nofollow${folder.path}`).catch(changedPathError)
|
|
258
|
+
: await descriptorEntries(folder);
|
|
259
|
+
await verifyFolder(folder);
|
|
260
|
+
return entries;
|
|
261
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { WorkflowDeclaration } from "../src/workflow-declaration.ts";
|
|
2
|
+
import { type CandidateDeclaration, type WorkflowCandidateCode } from "../src/workflow-candidate.ts";
|
|
3
|
+
import { type WorkflowConflictCode, type WorkflowReservation } from "../src/workflow-discovery.ts";
|
|
4
|
+
import type { CandidatePackageOptions } from "./workflow-candidate-program.ts";
|
|
5
|
+
import { type WorkflowCaptureCode } from "./workflow-folder-capture.ts";
|
|
6
|
+
export interface WorkflowFolderInput {
|
|
7
|
+
readonly repository: string;
|
|
8
|
+
readonly home: string;
|
|
9
|
+
readonly xdgConfigHome?: string | undefined;
|
|
10
|
+
readonly userFolder?: string | undefined;
|
|
11
|
+
readonly bundled: readonly WorkflowReservation[];
|
|
12
|
+
}
|
|
13
|
+
interface WorkflowLabel {
|
|
14
|
+
readonly scope: "project" | "user";
|
|
15
|
+
readonly label: string;
|
|
16
|
+
}
|
|
17
|
+
interface WorkflowSnapshot extends WorkflowLabel {
|
|
18
|
+
readonly baseName: string;
|
|
19
|
+
readonly source: string;
|
|
20
|
+
readonly declaration: string;
|
|
21
|
+
readonly helpers: Readonly<Record<string, string>>;
|
|
22
|
+
}
|
|
23
|
+
export interface WorkflowFolderCandidate extends WorkflowSnapshot {
|
|
24
|
+
readonly repository: string;
|
|
25
|
+
readonly artifactDigest: string;
|
|
26
|
+
}
|
|
27
|
+
export interface WorkflowFolderRefusal extends WorkflowLabel {
|
|
28
|
+
readonly codes: readonly (WorkflowCandidateCode | WorkflowCaptureCode | WorkflowConflictCode | "compile-failed")[];
|
|
29
|
+
}
|
|
30
|
+
export interface WorkflowListing {
|
|
31
|
+
readonly scope: "bundled" | "project" | "user";
|
|
32
|
+
readonly workflowId: string | null;
|
|
33
|
+
readonly file: string;
|
|
34
|
+
readonly admitted: boolean;
|
|
35
|
+
readonly codes: readonly string[];
|
|
36
|
+
readonly declaration?: CandidateDeclaration | WorkflowDeclaration;
|
|
37
|
+
}
|
|
38
|
+
export interface WorkflowFolderResult {
|
|
39
|
+
readonly workflows: readonly WorkflowListing[];
|
|
40
|
+
readonly repository: string;
|
|
41
|
+
readonly candidates: readonly WorkflowFolderCandidate[];
|
|
42
|
+
readonly refusals: readonly WorkflowFolderRefusal[];
|
|
43
|
+
}
|
|
44
|
+
export declare function userLocation(input: Pick<WorkflowFolderInput, "home" | "xdgConfigHome" | "userFolder">): {
|
|
45
|
+
readonly root: string;
|
|
46
|
+
readonly parts: readonly string[];
|
|
47
|
+
};
|
|
48
|
+
export declare function loadWorkflowFolders(input: WorkflowFolderInput, installed?: CandidatePackageOptions): Promise<WorkflowFolderResult>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { absoluteWorkflowFolder } from "../src/project-settings.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { realpath } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
import { isCandidateDeclaration, } from "../src/workflow-candidate.js";
|
|
7
|
+
import { record } from "../src/protocol.js";
|
|
8
|
+
import { workflowConflicts, } from "../src/workflow-discovery.js";
|
|
9
|
+
import { admitWorkflowCandidate } from "./admit-workflow.js";
|
|
10
|
+
import { captureWorkflowCandidate, inspectWorkflowFolder, workflowCaptureFailure, workflowFolderEntries, } from "./workflow-folder-capture.js";
|
|
11
|
+
function fileLabel(name) {
|
|
12
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.ts$/.test(name)
|
|
13
|
+
? name
|
|
14
|
+
: "file-" + createHash("sha256").update(name).digest("hex").slice(0, 16);
|
|
15
|
+
}
|
|
16
|
+
function parsedDeclaration(text) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(text);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function identity(entry) {
|
|
25
|
+
const snapshot = entry.kind === "captured" ? entry.snapshot : entry;
|
|
26
|
+
const source = ts.createSourceFile("candidate.ts", snapshot.source ?? "", ts.ScriptTarget.Latest, true);
|
|
27
|
+
const parsed = parsedDeclaration(snapshot.declaration ?? "");
|
|
28
|
+
const declaration = record(record(parsed)?.["workflow"]);
|
|
29
|
+
const workflowId = declaration?.["workflowId"];
|
|
30
|
+
return {
|
|
31
|
+
queue: isCandidateDeclaration(parsed) && parsed.workflow.queue === true,
|
|
32
|
+
baseName: snapshot.baseName,
|
|
33
|
+
workflowId: typeof workflowId === "string" ? workflowId : undefined,
|
|
34
|
+
functionNames: source.statements
|
|
35
|
+
.filter(ts.isFunctionDeclaration)
|
|
36
|
+
.filter((node) => (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0)
|
|
37
|
+
.flatMap((node) => (node.name ? [node.name.text] : [])),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function compareLabels(left, right) {
|
|
41
|
+
const a = `${left.scope}\0${left.label}`;
|
|
42
|
+
const b = `${right.scope}\0${right.label}`;
|
|
43
|
+
return a < b ? -1 : Number(a > b);
|
|
44
|
+
}
|
|
45
|
+
async function scanFolder(location, refusals) {
|
|
46
|
+
let folder;
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
folder = await inspectWorkflowFolder(location.root, location.parts);
|
|
50
|
+
entries = await workflowFolderEntries(folder);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
54
|
+
refusals.push({
|
|
55
|
+
scope: location.scope,
|
|
56
|
+
label: "workflows/",
|
|
57
|
+
codes: [workflowCaptureFailure(error)],
|
|
58
|
+
});
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const snapshots = [];
|
|
62
|
+
for (const name of entries
|
|
63
|
+
.filter((name) => name.endsWith(".ts") &&
|
|
64
|
+
!name.endsWith(".d.ts") &&
|
|
65
|
+
!name.endsWith(".test.ts"))
|
|
66
|
+
.sort()) {
|
|
67
|
+
snapshots.push(await captureEntry(folder, {
|
|
68
|
+
scope: location.scope,
|
|
69
|
+
label: fileLabel(name),
|
|
70
|
+
name,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
return snapshots;
|
|
74
|
+
}
|
|
75
|
+
async function captureEntry(folder, file) {
|
|
76
|
+
const label = {
|
|
77
|
+
scope: file.scope,
|
|
78
|
+
label: file.label,
|
|
79
|
+
baseName: file.name.slice(0, -3),
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
const capture = await captureWorkflowCandidate(folder, file.name);
|
|
83
|
+
if (capture.source === undefined ||
|
|
84
|
+
capture.declaration === undefined ||
|
|
85
|
+
capture.codes.length > 0)
|
|
86
|
+
return { kind: "refused", ...label, ...capture };
|
|
87
|
+
return {
|
|
88
|
+
kind: "captured",
|
|
89
|
+
snapshot: {
|
|
90
|
+
...label,
|
|
91
|
+
source: capture.source,
|
|
92
|
+
declaration: capture.declaration,
|
|
93
|
+
helpers: capture.helpers,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
kind: "refused",
|
|
100
|
+
...label,
|
|
101
|
+
codes: [workflowCaptureFailure(error)],
|
|
102
|
+
source: undefined,
|
|
103
|
+
declaration: undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function userLocation(input) {
|
|
108
|
+
const folder = absoluteWorkflowFolder(input.userFolder);
|
|
109
|
+
if (folder !== undefined)
|
|
110
|
+
return { root: dirname(folder), parts: [basename(folder)] };
|
|
111
|
+
const configuration = input.xdgConfigHome && isAbsolute(input.xdgConfigHome)
|
|
112
|
+
? input.xdgConfigHome
|
|
113
|
+
: join(input.home, ".config");
|
|
114
|
+
return { root: configuration, parts: ["cloving", "workflows"] };
|
|
115
|
+
}
|
|
116
|
+
async function scanFolders(input, refusals) {
|
|
117
|
+
const repository = await realpath(input.repository);
|
|
118
|
+
const snapshots = [
|
|
119
|
+
...(await scanFolder({ scope: "project", root: repository, parts: [".cloving", "workflows"] }, refusals)),
|
|
120
|
+
...(await scanFolder({ scope: "user", ...userLocation(input) }, refusals)),
|
|
121
|
+
];
|
|
122
|
+
return { repository, snapshots };
|
|
123
|
+
}
|
|
124
|
+
function workflowFolder(input, scope) {
|
|
125
|
+
if (scope === "project")
|
|
126
|
+
return join(input.repository, ".cloving", "workflows");
|
|
127
|
+
const { root, parts } = userLocation(input);
|
|
128
|
+
return join(root, ...parts);
|
|
129
|
+
}
|
|
130
|
+
function folderListings(input, found) {
|
|
131
|
+
const { repository, snapshots, refusals } = found;
|
|
132
|
+
const workflows = snapshots.map((entry) => {
|
|
133
|
+
const snapshot = entry.kind === "captured" ? entry.snapshot : entry;
|
|
134
|
+
const declaration = parsedDeclaration(snapshot.declaration ?? "");
|
|
135
|
+
const refusal = refusals.find((item) => item.scope === snapshot.scope && item.label === snapshot.label);
|
|
136
|
+
const folder = workflowFolder({ ...input, repository }, snapshot.scope);
|
|
137
|
+
return {
|
|
138
|
+
scope: snapshot.scope,
|
|
139
|
+
workflowId: isCandidateDeclaration(declaration)
|
|
140
|
+
? declaration.workflow.workflowId
|
|
141
|
+
: null,
|
|
142
|
+
file: join(folder, snapshot.baseName + ".ts"),
|
|
143
|
+
admitted: refusal === undefined,
|
|
144
|
+
codes: refusal?.codes ?? [],
|
|
145
|
+
...(isCandidateDeclaration(declaration) ? { declaration } : {}),
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
return [
|
|
149
|
+
...workflows,
|
|
150
|
+
...refusals
|
|
151
|
+
.filter((refusal) => refusal.label === "workflows/")
|
|
152
|
+
.map((refusal) => ({
|
|
153
|
+
scope: refusal.scope,
|
|
154
|
+
workflowId: null,
|
|
155
|
+
file: workflowFolder({ ...input, repository }, refusal.scope),
|
|
156
|
+
admitted: false,
|
|
157
|
+
codes: refusal.codes,
|
|
158
|
+
})),
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
export async function loadWorkflowFolders(input, installed = {}) {
|
|
162
|
+
const candidates = [];
|
|
163
|
+
const refusals = [];
|
|
164
|
+
const { repository, snapshots } = await scanFolders(input, refusals);
|
|
165
|
+
const conflicts = workflowConflicts(snapshots.map(identity), input.bundled);
|
|
166
|
+
for (const [index, entry] of snapshots.entries()) {
|
|
167
|
+
const label = entry.kind === "captured" ? entry.snapshot : entry;
|
|
168
|
+
const codes = [
|
|
169
|
+
...new Set([
|
|
170
|
+
...(entry.kind === "refused" ? entry.codes : []),
|
|
171
|
+
...(conflicts[index] ?? []),
|
|
172
|
+
]),
|
|
173
|
+
].sort();
|
|
174
|
+
if (entry.kind === "refused" || codes.length > 0) {
|
|
175
|
+
refusals.push({ scope: label.scope, label: label.label, codes });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const { snapshot } = entry;
|
|
179
|
+
const result = await admitWorkflowCandidate({
|
|
180
|
+
source: snapshot.source,
|
|
181
|
+
declaration: parsedDeclaration(snapshot.declaration),
|
|
182
|
+
helpers: snapshot.helpers,
|
|
183
|
+
}, installed);
|
|
184
|
+
if (result.kind === "refused")
|
|
185
|
+
refusals.push({
|
|
186
|
+
scope: snapshot.scope,
|
|
187
|
+
label: snapshot.label,
|
|
188
|
+
codes: result.codes,
|
|
189
|
+
});
|
|
190
|
+
else
|
|
191
|
+
candidates.push({
|
|
192
|
+
...snapshot,
|
|
193
|
+
repository,
|
|
194
|
+
artifactDigest: result.artifactDigest,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
workflows: folderListings(input, { repository, snapshots, refusals }),
|
|
199
|
+
repository,
|
|
200
|
+
candidates: candidates.sort(compareLabels),
|
|
201
|
+
refusals: refusals.sort(compareLabels),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export interface ReferenceContext {
|
|
3
|
+
readonly parent: ts.Node | undefined;
|
|
4
|
+
readonly checker: ts.TypeChecker;
|
|
5
|
+
readonly profile?: "candidate";
|
|
6
|
+
}
|
|
7
|
+
export declare function referenceSymbol(node: ts.Identifier, context: ReferenceContext): ts.Symbol | undefined;
|
|
8
|
+
export declare function typeOnlyEffectNode(node: ts.Node): boolean;
|
|
9
|
+
export declare function restrictedEffect(node: ts.Node, context: ReferenceContext): boolean;
|
|
10
|
+
export declare function sourceEffectNodes(source: ts.SourceFile, context: Omit<ReferenceContext, "parent">): readonly ts.Node[];
|