cloving 1.3.0 → 1.4.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 +235 -206
- package/dist/src/agent-harness-shell.d.ts +2 -1
- package/dist/src/agent-harness-shell.js +8 -5
- package/dist/src/authority/command-wait-checks.d.ts +8 -0
- package/dist/src/authority/command-wait-checks.js +88 -0
- package/dist/src/authority/command-wait-results.d.ts +15 -0
- package/dist/src/authority/command-wait-results.js +73 -0
- package/dist/src/authority/command-wait-view.d.ts +21 -0
- package/dist/src/authority/command-wait-view.js +34 -0
- package/dist/src/authority/command-waits.d.ts +39 -0
- package/dist/src/authority/command-waits.js +97 -0
- package/dist/src/authority/commands.js +22 -1
- package/dist/src/authority/dispatch-decision.d.ts +8 -0
- package/dist/src/authority/dispatch-decision.js +26 -1
- package/dist/src/authority/effect-requests.js +6 -3
- package/dist/src/authority/effect-results.js +4 -2
- package/dist/src/authority/history-state.d.ts +2 -0
- package/dist/src/authority/history-state.js +13 -1
- package/dist/src/authority/instants.d.ts +1 -0
- package/dist/src/authority/instants.js +21 -0
- package/dist/src/authority/reduction-state.d.ts +2 -0
- package/dist/src/authority/reduction-state.js +5 -0
- package/dist/src/authority/reduction.js +24 -0
- package/dist/src/authority/status-input.d.ts +2 -0
- package/dist/src/authority/status.d.ts +2 -0
- package/dist/src/authority/status.js +1 -0
- package/dist/src/authority/stop-sequence.d.ts +1 -0
- package/dist/src/authority/stop-sequence.js +2 -1
- package/dist/src/authority/transitions.d.ts +6 -4
- package/dist/src/authority/transitions.js +7 -0
- package/dist/src/broker/actor/authority-queries.d.ts +2 -1
- package/dist/src/broker/actor/authority-queries.js +14 -5
- package/dist/src/broker/actor/authority-receipts.js +1 -1
- package/dist/src/broker/actor/authority-settings.js +2 -2
- package/dist/src/broker/actor/authority-upgrades.d.ts +19 -2
- package/dist/src/broker/actor/authority-upgrades.js +7 -2
- package/dist/src/broker/actor/delivery-intents.js +2 -0
- package/dist/src/broker/actor/effect-delivery.d.ts +1 -1
- package/dist/src/broker/actor/effect-delivery.js +15 -2
- package/dist/src/broker/actor/effect-plan.js +11 -2
- package/dist/src/broker/actor/effect-queries.d.ts +4 -0
- package/dist/src/broker/actor/effect-queries.js +9 -1
- package/dist/src/broker/actor/effect-turn.js +2 -0
- package/dist/src/broker/actor/stop-plan.js +9 -3
- package/dist/src/broker/actor/stop-turn.d.ts +2 -0
- package/dist/src/broker/actor/stop-turn.js +24 -11
- package/dist/src/broker/actor/turn-draft.d.ts +1 -0
- package/dist/src/broker/actor/turn-draft.js +4 -0
- package/dist/src/broker/actor/turn-state.js +5 -1
- package/dist/src/broker/authority-admission.js +60 -0
- package/dist/src/broker/authority-restore.d.ts +2 -0
- package/dist/src/broker/authority-restore.js +35 -1
- package/dist/src/broker/command-wait-restore.d.ts +31 -0
- package/dist/src/broker/command-wait-restore.js +86 -0
- package/dist/src/broker/decision-restore.d.ts +1 -1
- package/dist/src/broker/decision-restore.js +1 -0
- package/dist/src/broker/effect-upgrade.d.ts +2 -0
- package/dist/src/broker/effect-upgrade.js +8 -2
- package/dist/src/cli/arguments.d.ts +13 -0
- package/dist/src/cli/arguments.js +42 -3
- package/dist/src/cli/chat-stores.d.ts +2 -0
- package/dist/src/cli/chat-stores.js +113 -0
- package/dist/src/cli/client.d.ts +4 -1
- package/dist/src/cli/client.js +9 -3
- package/dist/src/cli/command-requests.d.ts +17 -0
- package/dist/src/cli/command-requests.js +43 -0
- package/dist/src/cli/command-waits.d.ts +9 -0
- package/dist/src/cli/command-waits.js +51 -0
- package/dist/src/cli/commands.js +14 -41
- package/dist/src/cli/config-file.d.ts +2 -2
- package/dist/src/cli/config-file.js +4 -2
- package/dist/src/cli/doctor-workflows.d.ts +5 -2
- package/dist/src/cli/doctor-workflows.js +12 -7
- package/dist/src/cli/doctor.js +6 -9
- package/dist/src/cli/harness-probes.d.ts +2 -0
- package/dist/src/cli/harness-probes.js +1 -1
- package/dist/src/cli/harness-processes.d.ts +6 -0
- package/dist/src/cli/harness-processes.js +36 -0
- package/dist/src/cli/init-command.d.ts +9 -0
- package/dist/src/cli/init-command.js +52 -0
- package/dist/src/cli/init-prompt-command.d.ts +10 -0
- package/dist/src/cli/init-prompt-command.js +145 -0
- package/dist/src/cli/init-prompt.d.ts +35 -0
- package/dist/src/cli/init-prompt.js +74 -0
- package/dist/src/cli/init-selection.d.ts +13 -0
- package/dist/src/cli/init-selection.js +75 -0
- package/dist/src/cli/main.d.ts +2 -0
- package/dist/src/cli/main.js +37 -7
- package/dist/src/cli/mcp-server.d.ts +1 -0
- package/dist/src/cli/mcp-server.js +156 -0
- package/dist/src/cli/mcp-tools.d.ts +15 -0
- package/dist/src/cli/mcp-tools.js +80 -0
- package/dist/src/cli/render.d.ts +1 -1
- package/dist/src/cli/render.js +17 -30
- package/dist/src/cli/report-ci-jobs.d.ts +23 -0
- package/dist/src/cli/report-ci-jobs.js +103 -0
- package/dist/src/cli/report-ci-usage.d.ts +9 -0
- package/dist/src/cli/report-ci-usage.js +185 -0
- package/dist/src/cli/report-ci.d.ts +13 -0
- package/dist/src/cli/report-ci.js +74 -0
- package/dist/src/cli/report-closures.d.ts +6 -0
- package/dist/src/cli/report-closures.js +97 -0
- package/dist/src/cli/report-flow.d.ts +7 -0
- package/dist/src/cli/report-flow.js +122 -0
- package/dist/src/cli/report-github-reader.d.ts +29 -0
- package/dist/src/cli/report-github-reader.js +157 -0
- package/dist/src/cli/report-github.d.ts +15 -8
- package/dist/src/cli/report-github.js +131 -150
- package/dist/src/cli/report-operator.d.ts +3 -0
- package/dist/src/cli/report-operator.js +40 -0
- package/dist/src/cli/report-ready.d.ts +3 -0
- package/dist/src/cli/report-ready.js +96 -0
- package/dist/src/cli/report-text.d.ts +9 -0
- package/dist/src/cli/report-text.js +33 -0
- package/dist/src/cli/report.d.ts +1 -0
- package/dist/src/cli/report.js +104 -27
- package/dist/src/cli/runtime-arguments.d.ts +2 -1
- package/dist/src/cli/runtime-arguments.js +7 -2
- package/dist/src/cli/schedule-arguments.d.ts +14 -0
- package/dist/src/cli/schedule-arguments.js +26 -0
- package/dist/src/cli/schedule-render.d.ts +22 -0
- package/dist/src/cli/schedule-render.js +73 -0
- package/dist/src/cli/terminal-text.d.ts +3 -0
- package/dist/src/cli/terminal-text.js +28 -0
- package/dist/src/cli/usage-render.d.ts +1 -0
- package/dist/src/cli/usage-render.js +28 -1
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +50 -7
- package/dist/src/cli/watch-actions.js +2 -2
- package/dist/src/cli/watch-board-view.js +19 -2
- package/dist/src/cli/watch-board.d.ts +4 -2
- package/dist/src/cli/watch-board.js +53 -43
- package/dist/src/cli/watch-card-forms.d.ts +1 -1
- package/dist/src/cli/watch-card-forms.js +11 -10
- package/dist/src/cli/watch-client.d.ts +1 -1
- package/dist/src/cli/watch-client.js +2 -11
- package/dist/src/cli/watch-header-view.d.ts +0 -5
- package/dist/src/cli/watch-header-view.js +8 -20
- package/dist/src/cli/watch-model.d.ts +4 -1
- package/dist/src/cli/watch-model.js +17 -11
- package/dist/src/cli/watch-page.js +12 -14
- package/dist/src/cli/watch-phases.d.ts +12 -3
- package/dist/src/cli/watch-phases.js +68 -11
- package/dist/src/cli/watch-pr-board.js +1 -1
- package/dist/src/cli/watch-pr-command.d.ts +1 -0
- package/dist/src/cli/watch-pull-request.js +18 -8
- package/dist/src/cli/watch-reads.d.ts +2 -0
- package/dist/src/cli/watch-reads.js +17 -16
- package/dist/src/cli/watch-render.d.ts +14 -0
- package/dist/src/cli/watch-run-input.d.ts +0 -1
- package/dist/src/cli/watch-run-input.js +0 -13
- package/dist/src/cli/watch-server.js +6 -6
- package/dist/src/cli/watch-view.d.ts +0 -3
- package/dist/src/cli/watch-view.js +13 -13
- package/dist/src/cli.js +59 -5
- package/dist/src/command-wait.d.ts +4 -0
- package/dist/src/command-wait.js +45 -0
- package/dist/src/configuration-change.d.ts +7 -0
- package/dist/src/configuration-change.js +14 -0
- package/dist/src/configuration-schema.d.ts +9 -0
- package/dist/src/configuration-schema.js +6 -2
- package/dist/src/daemon/attachment-file.d.ts +6 -0
- package/dist/src/daemon/attachment-file.js +86 -0
- package/dist/src/daemon/attachments.d.ts +14 -0
- package/dist/src/daemon/attachments.js +17 -0
- package/dist/src/daemon/claude-recovery.d.ts +50 -0
- package/dist/src/daemon/claude-recovery.js +204 -0
- package/dist/src/daemon/claude-stream.d.ts +27 -0
- package/dist/src/daemon/claude-stream.js +63 -0
- package/dist/src/daemon/claude-turn.d.ts +11 -25
- package/dist/src/daemon/claude-turn.js +64 -81
- package/dist/src/daemon/claude-worker.d.ts +2 -1
- package/dist/src/daemon/claude-worker.js +124 -122
- package/dist/src/daemon/daemon-adapters.d.ts +2 -0
- package/dist/src/daemon/daemon-adapters.js +26 -2
- package/dist/src/daemon/daemon-loops.d.ts +3 -1
- package/dist/src/daemon/daemon-loops.js +3 -1
- package/dist/src/daemon/daemon.js +14 -6
- package/dist/src/daemon/github-attach.d.ts +2 -0
- package/dist/src/daemon/github-attach.js +149 -0
- package/dist/src/daemon/github-ci-filters.d.ts +6 -0
- package/dist/src/daemon/github-ci-filters.js +138 -0
- package/dist/src/daemon/github-ci-workflows.d.ts +10 -0
- package/dist/src/daemon/github-ci-workflows.js +148 -0
- package/dist/src/daemon/github-ci.js +22 -4
- package/dist/src/daemon/github-issue-methods.d.ts +2 -0
- package/dist/src/daemon/github-issue-methods.js +37 -2
- package/dist/src/daemon/github-merge-call.d.ts +2 -1
- package/dist/src/daemon/github-merge-call.js +10 -3
- package/dist/src/daemon/github-merge.js +52 -24
- package/dist/src/daemon/github-methods.js +12 -2
- package/dist/src/daemon/github-operation.d.ts +4 -0
- package/dist/src/daemon/github-operation.js +6 -0
- package/dist/src/daemon/github-worker.d.ts +4 -0
- package/dist/src/daemon/github-worker.js +6 -0
- package/dist/src/daemon/issue-queue.d.ts +1 -1
- package/dist/src/daemon/issue-queue.js +16 -11
- package/dist/src/daemon/notify-events.d.ts +6 -0
- package/dist/src/daemon/notify-events.js +84 -0
- package/dist/src/daemon/notify-journal.js +1 -1
- package/dist/src/daemon/notify-message.d.ts +4 -1
- package/dist/src/daemon/notify-message.js +36 -0
- package/dist/src/daemon/notify.d.ts +1 -1
- package/dist/src/daemon/notify.js +5 -77
- package/dist/src/daemon/operation-journal.d.ts +1 -0
- package/dist/src/daemon/queue-status.d.ts +9 -3
- package/dist/src/daemon/queue-status.js +17 -4
- package/dist/src/daemon/resume-index-lock.d.ts +13 -0
- package/dist/src/daemon/resume-index-lock.js +160 -0
- package/dist/src/daemon/resume-worktree.d.ts +7 -0
- package/dist/src/daemon/resume-worktree.js +29 -0
- package/dist/src/daemon/retrying-turn.d.ts +2 -12
- package/dist/src/daemon/retrying-turn.js +98 -172
- package/dist/src/daemon/schedule-control.d.ts +26 -0
- package/dist/src/daemon/schedule-control.js +110 -0
- package/dist/src/daemon/schedule-journal.d.ts +2 -15
- package/dist/src/daemon/schedule-journal.js +67 -4
- package/dist/src/daemon/ticket-registration.d.ts +2 -7
- package/dist/src/daemon/ticket-registration.js +15 -81
- package/dist/src/daemon/turn-attempt-journal.d.ts +38 -0
- package/dist/src/daemon/turn-attempt-journal.js +136 -0
- package/dist/src/daemon/turn-attempts.d.ts +47 -0
- package/dist/src/daemon/turn-attempts.js +166 -0
- package/dist/src/daemon/turn-report.d.ts +8 -1
- package/dist/src/daemon/turn-report.js +39 -0
- package/dist/src/daemon/work-tree.d.ts +9 -0
- package/dist/src/daemon/work-tree.js +20 -0
- package/dist/src/daemon/worker-profile.d.ts +8 -0
- package/dist/src/daemon/worker-profile.js +40 -0
- package/dist/src/daemon/workflow-schedules.d.ts +3 -0
- package/dist/src/daemon/workflow-schedules.js +98 -45
- package/dist/src/daemon/worktree-processes.d.ts +9 -0
- package/dist/src/daemon/worktree-processes.js +151 -0
- package/dist/src/doctor-report.d.ts +3 -5
- package/dist/src/doctor-report.js +6 -13
- package/dist/src/harness-start.d.ts +15 -0
- package/dist/src/harness-start.js +37 -0
- package/dist/src/host/command-wait.d.ts +38 -0
- package/dist/src/host/command-wait.js +115 -0
- package/dist/src/host/start-delivery.d.ts +5 -7
- package/dist/src/host/start-delivery.js +3 -15
- package/dist/src/host/step-bridge-host.d.ts +2 -0
- package/dist/src/host/step-bridge-host.js +21 -0
- package/dist/src/operation-identity.d.ts +1 -0
- package/dist/src/operation-identity.js +4 -0
- package/dist/src/private-config.schema.json +40 -0
- package/dist/src/project-config.schema.json +40 -0
- package/dist/src/project-settings.d.ts +2 -2
- package/dist/src/project-settings.js +3 -1
- package/dist/src/queue-spend.d.ts +4 -0
- package/dist/src/queue-spend.js +21 -0
- package/dist/src/relay/relay-service.d.ts +3 -0
- package/dist/src/relay/relay-service.js +5 -3
- package/dist/src/report-ci.d.ts +164 -0
- package/dist/src/report-ci.js +32 -0
- package/dist/src/report-effects.d.ts +15 -0
- package/dist/src/report-effects.js +212 -0
- package/dist/src/report-flow-types.d.ts +38 -0
- package/dist/src/report-flow-types.js +7 -0
- package/dist/src/report-flow.d.ts +43 -0
- package/dist/src/report-flow.js +193 -0
- package/dist/src/report-operator.d.ts +67 -0
- package/dist/src/report-operator.js +25 -0
- package/dist/src/report-quality.d.ts +221 -0
- package/dist/src/report-quality.js +94 -0
- package/dist/src/report-ready.d.ts +23 -0
- package/dist/src/report-ready.js +117 -0
- package/dist/src/report-statistics.d.ts +2 -1
- package/dist/src/report-statistics.js +40 -4
- package/dist/src/report.d.ts +588 -47
- package/dist/src/report.js +156 -41
- package/dist/src/schedule-progress.d.ts +33 -0
- package/dist/src/schedule-progress.js +45 -0
- package/dist/src/service/active-tickets.js +6 -6
- package/dist/src/service/command-waits.d.ts +34 -0
- package/dist/src/service/command-waits.js +86 -0
- package/dist/src/service/expiry.js +2 -0
- package/dist/src/service/gateway.js +3 -1
- package/dist/src/service/queue-spend.d.ts +6 -0
- package/dist/src/service/queue-spend.js +68 -0
- package/dist/src/service/replies.d.ts +8 -1
- package/dist/src/service/replies.js +11 -0
- package/dist/src/service/run-handlers.js +22 -2
- package/dist/src/service/run-metadata.d.ts +15 -1
- package/dist/src/service/run-metadata.js +93 -8
- package/dist/src/service/service.d.ts +4 -0
- package/dist/src/service/service.js +8 -8
- package/dist/src/service/submission-handler.js +18 -4
- package/dist/src/service/worker-port.js +9 -1
- package/dist/src/service/worker-totals.d.ts +2 -0
- package/dist/src/service/worker-totals.js +1 -1
- package/dist/src/step-bridge.d.ts +20 -0
- package/dist/src/steps/command-wait.d.ts +12 -0
- package/dist/src/steps/command-wait.js +26 -0
- package/dist/src/steps/open-decision-wait.d.ts +2 -0
- package/dist/src/steps/{decision-gate.js → open-decision-wait.js} +0 -7
- package/dist/src/steps/review-by-person.js +4 -1
- package/dist/src/steps/wait-for-command.d.ts +3 -0
- package/dist/src/steps/wait-for-command.js +17 -0
- package/dist/src/steps/wait-for-decision.js +1 -1
- package/dist/src/storage/command-wait-rows.d.ts +30 -0
- package/dist/src/storage/command-wait-rows.js +96 -0
- package/dist/src/storage/decision-snapshots.d.ts +8 -1
- package/dist/src/storage/decision-snapshots.js +15 -1
- package/dist/src/storage/domain-changes.d.ts +2 -0
- package/dist/src/storage/domain-changes.js +3 -0
- package/dist/src/storage/domain-schema.d.ts +13 -1
- package/dist/src/storage/domain-schema.js +31 -4
- package/dist/src/storage/effect-schema.d.ts +1 -0
- package/dist/src/storage/effect-schema.js +5 -0
- package/dist/src/testing-requests.d.ts +15 -0
- package/dist/src/testing-requests.js +93 -0
- package/dist/src/testing.d.ts +4 -10
- package/dist/src/testing.js +33 -86
- package/dist/src/worker-usage.d.ts +1 -0
- package/dist/src/worker-usage.js +3 -0
- package/dist/src/workflow-candidate.d.ts +1 -0
- package/dist/src/workflow-candidate.js +12 -1
- package/dist/src/workflow-contracts.d.ts +1 -0
- package/dist/src/workflow-contracts.js +9 -2
- package/dist/src/workflow-discovery.d.ts +2 -13
- package/dist/src/workflow-discovery.js +1 -13
- package/dist/src/workflow-refusals.js +0 -1
- package/dist/src/workflows.d.ts +1 -1
- package/dist/src/workflows.js +1 -1
- package/dist/tools/red-green-command.d.ts +1 -0
- package/dist/tools/red-green-command.js +174 -0
- package/dist/tools/red-green-process.d.ts +10 -0
- package/dist/tools/red-green-process.js +59 -0
- package/dist/tools/self-host-processes.d.ts +2 -0
- package/dist/tools/self-host-processes.js +32 -13
- package/dist/tools/self-host-project.js +13 -78
- package/dist/tools/self-host-runtime.js +1 -1
- package/dist/tools/self-host.js +42 -10
- package/dist/tools/template-command.d.ts +1 -0
- package/dist/tools/template-command.js +160 -0
- package/dist/tools/template-copy.d.ts +11 -0
- package/dist/tools/template-copy.js +130 -0
- package/dist/tools/template-files.d.ts +17 -0
- package/dist/tools/template-files.js +74 -0
- package/dist/tools/workflow-bundles.js +1 -0
- package/dist/tools/workflow-candidate-counts.js +2 -2
- package/dist/tools/workflow-candidate-shape.d.ts +3 -0
- package/dist/tools/workflow-candidate-shape.js +90 -0
- package/dist/tools/workflow-candidate-source.js +15 -1
- package/dist/tools/workflow-candidate-syntax.d.ts +2 -2
- package/dist/tools/workflow-candidate-syntax.js +24 -94
- package/dist/tools/workflow-command.js +1 -2
- package/dist/tools/workflow-folders.d.ts +2 -3
- package/dist/tools/workflow-folders.js +1 -1
- package/package.json +16 -8
- package/templates/develop/README.md +12 -0
- package/templates/develop/develop/flow-tests.ts +37 -0
- package/templates/develop/develop/pull.ts +5 -1
- package/templates/develop/develop/refusal-tests.ts +77 -0
- package/templates/develop/develop/turn.ts +2 -1
- package/templates/idea-to-issue/README.md +27 -0
- package/templates/idea-to-issue/idea-to-issue/effects.ts +52 -0
- package/templates/idea-to-issue/idea-to-issue/leave.ts +10 -0
- package/templates/idea-to-issue/idea-to-issue/next.ts +29 -0
- package/templates/idea-to-issue/idea-to-issue/post-person.ts +18 -0
- package/templates/idea-to-issue/idea-to-issue/post.ts +50 -0
- package/templates/idea-to-issue/idea-to-issue/queue.ts +27 -0
- package/templates/idea-to-issue/idea-to-issue/review.ts +42 -0
- package/templates/idea-to-issue/idea-to-issue/scenarios.ts +50 -0
- package/templates/idea-to-issue/idea-to-issue/turn.ts +40 -0
- package/templates/idea-to-issue/idea-to-issue.declaration.json +16 -0
- package/templates/idea-to-issue/idea-to-issue.test.ts +79 -0
- package/templates/idea-to-issue/idea-to-issue.ts +49 -0
- package/templates/idea-to-issue/template.json +81 -0
- package/templates/review-day/README.md +97 -0
- package/templates/review-day/review-day/critique.ts +58 -0
- package/templates/review-day/review-day/decision.ts +29 -0
- package/templates/review-day/review-day/facts.ts +69 -0
- package/templates/review-day/review-day/label.ts +29 -0
- package/templates/review-day/review-day/next.ts +28 -0
- package/templates/review-day/review-day/post.ts +27 -0
- package/templates/review-day/review-day/report.ts +98 -0
- package/templates/review-day/review-day/rules.ts +12 -0
- package/templates/review-day/review-day/scenarios.ts +76 -0
- package/templates/review-day/review-day/turn.ts +73 -0
- package/templates/review-day/review-day.declaration.json +16 -0
- package/templates/review-day/review-day.test.ts +362 -0
- package/templates/review-day/review-day.ts +37 -0
- package/templates/review-day/template.json +56 -0
- package/dist/src/cli/watch-start-view.d.ts +0 -18
- package/dist/src/cli/watch-start-view.js +0 -77
- package/dist/src/steps/decision-gate.d.ts +0 -3
- package/dist/workflows/develop-prd.d.ts +0 -7
- package/dist/workflows/develop-prd.js +0 -165
- package/dist/workflows/develop-ticket.d.ts +0 -9
- package/dist/workflows/develop-ticket.js +0 -207
- package/dist/workflows/issue-labels.d.ts +0 -16
- package/dist/workflows/issue-labels.js +0 -70
- package/dist/workflows/ticket-prompts.d.ts +0 -35
- package/dist/workflows/ticket-prompts.js +0 -147
|
@@ -3,6 +3,7 @@ import { issueQueue } from "./issue-queue.js";
|
|
|
3
3
|
import { notifyRuns } from "./notify.js";
|
|
4
4
|
export async function daemonLoops(settings) {
|
|
5
5
|
const schedules = await workflowSchedules(settings);
|
|
6
|
+
settings.scheduleConnection?.connect(schedules.request);
|
|
6
7
|
try {
|
|
7
8
|
const notifier = settings.notifyCommand === undefined
|
|
8
9
|
? undefined
|
|
@@ -14,7 +15,8 @@ export async function daemonLoops(settings) {
|
|
|
14
15
|
? {}
|
|
15
16
|
: { intervalMilliseconds: settings.notifyMilliseconds }),
|
|
16
17
|
});
|
|
17
|
-
const queue = settings.queueMilliseconds === undefined
|
|
18
|
+
const queue = settings.queueMilliseconds === undefined ||
|
|
19
|
+
settings.queueWorkflowId === undefined
|
|
18
20
|
? undefined
|
|
19
21
|
: issueQueue({
|
|
20
22
|
directory: settings.directory,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { adapters, recoverCommands, } from "./daemon-adapters.js";
|
|
1
|
+
import { adapters, recoverCommands, recoverClaude, } from "./daemon-adapters.js";
|
|
2
2
|
import { mkdir, realpath } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { registerStepBridge, sdkStepCall, stepBridge, } from "../host/step-bridge-host.js";
|
|
@@ -12,8 +12,9 @@ import { commandEffects } from "./command-journal.js";
|
|
|
12
12
|
import { worldDirectory } from "./host-environment.js";
|
|
13
13
|
import { pinnedWorkflowHandler } from "./workflow-replay.js";
|
|
14
14
|
import { scheduledDeclarations } from "./workflow-schedules.js";
|
|
15
|
+
import { scheduleConnection } from "./schedule-control.js";
|
|
15
16
|
import { daemonLoops } from "./daemon-loops.js";
|
|
16
|
-
import { ticketRegistrations, ticketRegistrationTable,
|
|
17
|
+
import { ticketRegistrations, ticketRegistrationTable, } from "./ticket-registration.js";
|
|
17
18
|
export const turnLeaseMilliseconds = 4 * 3_600_000;
|
|
18
19
|
export const loopMilliseconds = 250;
|
|
19
20
|
export const bridgeTiming = {
|
|
@@ -138,7 +139,12 @@ async function openDaemon(settings) {
|
|
|
138
139
|
const { worker, github, runs, handoff, commands } = resources;
|
|
139
140
|
await mkdir(join(settings.directory, "state"), { recursive: true });
|
|
140
141
|
await mkdir(settings.runtimeDirectory, { recursive: true, mode: 0o700 });
|
|
142
|
+
const schedules = scheduleConnection();
|
|
141
143
|
const opening = await startService({
|
|
144
|
+
schedule: schedules.request,
|
|
145
|
+
...(settings.queueDailySpendLimitUsd === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: { queueDailySpendLimitUsd: settings.queueDailySpendLimitUsd }),
|
|
142
148
|
workerHarness: worker.harness,
|
|
143
149
|
commandEffects: commandEffects(join(settings.directory, "commands")),
|
|
144
150
|
stateDirectory: join(settings.directory, "state"),
|
|
@@ -164,7 +170,7 @@ async function openDaemon(settings) {
|
|
|
164
170
|
await closeWorkers(resources);
|
|
165
171
|
return opening;
|
|
166
172
|
}
|
|
167
|
-
const running = { ...resources, service: opening.service };
|
|
173
|
+
const running = { ...resources, service: opening.service, schedules };
|
|
168
174
|
await recoverWorkers(running, given.promise);
|
|
169
175
|
return {
|
|
170
176
|
kind: "started",
|
|
@@ -173,7 +179,9 @@ async function openDaemon(settings) {
|
|
|
173
179
|
}
|
|
174
180
|
async function recoverWorkers(resources, ports) {
|
|
175
181
|
try {
|
|
176
|
-
|
|
182
|
+
const host = await ports;
|
|
183
|
+
await recoverCommands(resources.commands, host);
|
|
184
|
+
await recoverClaude(resources.worker, host);
|
|
177
185
|
}
|
|
178
186
|
catch (error) {
|
|
179
187
|
await Promise.allSettled([
|
|
@@ -195,6 +203,7 @@ async function runningDaemon(settings, resources) {
|
|
|
195
203
|
await github.scan();
|
|
196
204
|
const loops = await daemonLoops({
|
|
197
205
|
...settings,
|
|
206
|
+
scheduleConnection: resources.schedules,
|
|
198
207
|
submit: service.worker.submit,
|
|
199
208
|
queueWorkflowId: queueWorkflowId(settings),
|
|
200
209
|
}).catch(async (error) => {
|
|
@@ -212,6 +221,5 @@ async function runningDaemon(settings, resources) {
|
|
|
212
221
|
};
|
|
213
222
|
}
|
|
214
223
|
function queueWorkflowId(settings) {
|
|
215
|
-
return
|
|
216
|
-
?.workflowId ?? issueWorkflow);
|
|
224
|
+
return settings.declarations?.find((declaration) => declaration.queue === true)?.workflowId;
|
|
217
225
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { copiedAttachment, openedAttachment } from "./attachment-file.js";
|
|
4
|
+
import { attachmentFolder } from "./attachments.js";
|
|
5
|
+
import { repositoryName } from "./github-issue-methods.js";
|
|
6
|
+
import { issuePrivacy, operatorPrivateTexts } from "./github-issue-privacy.js";
|
|
7
|
+
import { field, repositoryPath, text, } from "./github-operation.js";
|
|
8
|
+
import { githubRead } from "./github-retry.js";
|
|
9
|
+
function isList(value) {
|
|
10
|
+
return Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function validName(name) {
|
|
13
|
+
return /^[\w-][\w.-]*\.(?:mp4|webm|png)$/.test(name) && !name.includes("..");
|
|
14
|
+
}
|
|
15
|
+
function validRelease(release) {
|
|
16
|
+
return /^[a-z][a-z0-9-]{0,63}$/.test(release) && !/^v\d/.test(release);
|
|
17
|
+
}
|
|
18
|
+
function attachmentOf(operation) {
|
|
19
|
+
const number = operation.payload["number"];
|
|
20
|
+
const name = text(operation, "name");
|
|
21
|
+
const release = text(operation, "release");
|
|
22
|
+
const comment = text(operation, "comment");
|
|
23
|
+
if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 1)
|
|
24
|
+
throw new Error("invalid-payload");
|
|
25
|
+
if (!validName(name) || !validRelease(release) || comment.length > 4000)
|
|
26
|
+
throw new Error("invalid-payload");
|
|
27
|
+
return { number, name, release, comment };
|
|
28
|
+
}
|
|
29
|
+
function attachedFile(operation, name) {
|
|
30
|
+
return openedAttachment({
|
|
31
|
+
folder: attachmentFolder({
|
|
32
|
+
worktrees: operation.settings.worktrees,
|
|
33
|
+
target: operation.target,
|
|
34
|
+
}),
|
|
35
|
+
name,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function pages(operation, path) {
|
|
39
|
+
const reply = JSON.parse(await githubRead(operation, ["api", "--paginate", "--slurp", path]));
|
|
40
|
+
if (!isList(reply))
|
|
41
|
+
throw new Error("github-call-failed");
|
|
42
|
+
return reply.flatMap((page) => {
|
|
43
|
+
if (!isList(page))
|
|
44
|
+
throw new Error("github-call-failed");
|
|
45
|
+
return page;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function releaseOf(operation, tag) {
|
|
49
|
+
const releases = await pages(operation, `${repositoryPath}/releases?per_page=100`);
|
|
50
|
+
return releases.find((item) => field(item, "tag_name") === tag);
|
|
51
|
+
}
|
|
52
|
+
async function ensuredRelease(operation, release) {
|
|
53
|
+
if ((await releaseOf(operation, release)) !== undefined)
|
|
54
|
+
return;
|
|
55
|
+
try {
|
|
56
|
+
await operation.calls.gh([
|
|
57
|
+
"release",
|
|
58
|
+
"create",
|
|
59
|
+
release,
|
|
60
|
+
"--prerelease",
|
|
61
|
+
"--latest=false",
|
|
62
|
+
"--title",
|
|
63
|
+
"Pull request demos",
|
|
64
|
+
"--notes",
|
|
65
|
+
"Demo videos of pull requests. Cloving uploads them.",
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if ((await releaseOf(operation, release)) === undefined)
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function uploaded(operation, attachment) {
|
|
74
|
+
const { release } = attachment;
|
|
75
|
+
await ensuredRelease(operation, release);
|
|
76
|
+
const asset = `pr-${String(attachment.number)}-${attachment.name}`;
|
|
77
|
+
const copy = join(operation.folder, asset);
|
|
78
|
+
try {
|
|
79
|
+
await copiedAttachment(attachment.file, copy);
|
|
80
|
+
await operation.calls.gh(["release", "upload", release, copy, "--clobber"]);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await rm(copy, { force: true });
|
|
84
|
+
}
|
|
85
|
+
const assets = field(await releaseOf(operation, release), "assets");
|
|
86
|
+
const found = isList(assets)
|
|
87
|
+
? assets.find((item) => field(item, "name") === asset)
|
|
88
|
+
: undefined;
|
|
89
|
+
const url = field(found, "browser_download_url");
|
|
90
|
+
if (typeof url !== "string" || !url.startsWith("https://"))
|
|
91
|
+
throw new Error("github-call-failed");
|
|
92
|
+
return url;
|
|
93
|
+
}
|
|
94
|
+
async function ownLogin(operation) {
|
|
95
|
+
const user = JSON.parse(await githubRead(operation, ["api", "user"]));
|
|
96
|
+
const login = field(user, "login");
|
|
97
|
+
if (typeof login !== "string" || login === "")
|
|
98
|
+
throw new Error("github-call-failed");
|
|
99
|
+
return login;
|
|
100
|
+
}
|
|
101
|
+
async function commented(operation, attachment) {
|
|
102
|
+
const { number, name, url } = attachment;
|
|
103
|
+
const marker = `<!-- cloving-attachment:${String(number)}:${name} -->`;
|
|
104
|
+
const body = `${attachment.comment}\n\n[${name}](${url})\n\n${marker}`;
|
|
105
|
+
const comments = await pages(operation, `${repositoryPath}/issues/${String(number)}/comments?per_page=100`);
|
|
106
|
+
const login = await ownLogin(operation);
|
|
107
|
+
const known = comments.find((item) => {
|
|
108
|
+
const existing = field(item, "body");
|
|
109
|
+
return (typeof existing === "string" &&
|
|
110
|
+
existing.includes(marker) &&
|
|
111
|
+
field(field(item, "user"), "login") === login);
|
|
112
|
+
});
|
|
113
|
+
const id = field(known, "id");
|
|
114
|
+
if (typeof id === "number") {
|
|
115
|
+
await operation.calls.gh([
|
|
116
|
+
"api",
|
|
117
|
+
"-X",
|
|
118
|
+
"PATCH",
|
|
119
|
+
`${repositoryPath}/issues/comments/${String(id)}`,
|
|
120
|
+
"-f",
|
|
121
|
+
`body=${body}`,
|
|
122
|
+
]);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await operation.calls.gh([
|
|
126
|
+
"api",
|
|
127
|
+
`${repositoryPath}/issues/${String(number)}/comments`,
|
|
128
|
+
"-f",
|
|
129
|
+
`body=${body}`,
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
export async function attachAsset(operation) {
|
|
133
|
+
const attachment = attachmentOf(operation);
|
|
134
|
+
operatorPrivateTexts(operation.settings);
|
|
135
|
+
const repository = repositoryName(await operation.calls.gh(["repo", "view", "--json", "nameWithOwner"]));
|
|
136
|
+
issuePrivacy(attachment.comment, {
|
|
137
|
+
settings: operation.settings,
|
|
138
|
+
repository,
|
|
139
|
+
});
|
|
140
|
+
const file = await attachedFile(operation, attachment.name);
|
|
141
|
+
try {
|
|
142
|
+
const url = await uploaded(operation, { ...attachment, file });
|
|
143
|
+
await commented(operation, { ...attachment, url });
|
|
144
|
+
return { number: attachment.number, url };
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
await file.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function triggerKind(trigger: unknown): "push" | "event";
|
|
2
|
+
export declare function pullRequestTrigger(source: string): unknown;
|
|
3
|
+
export declare function workflowApplies(trigger: unknown, context: {
|
|
4
|
+
readonly branch: string;
|
|
5
|
+
readonly paths: readonly string[];
|
|
6
|
+
}): boolean;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { parse } from "yaml";
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
export function triggerKind(trigger) {
|
|
6
|
+
if (!isRecord(trigger))
|
|
7
|
+
return "push";
|
|
8
|
+
const types = patterns(trigger["types"]);
|
|
9
|
+
return types === undefined || types.includes("synchronize")
|
|
10
|
+
? "push"
|
|
11
|
+
: "event";
|
|
12
|
+
}
|
|
13
|
+
export function pullRequestTrigger(source) {
|
|
14
|
+
try {
|
|
15
|
+
const document = parse(source, { merge: true });
|
|
16
|
+
if (!isRecord(document) || !("on" in document))
|
|
17
|
+
return true;
|
|
18
|
+
const events = document["on"];
|
|
19
|
+
if (typeof events === "string")
|
|
20
|
+
return events === "pull_request";
|
|
21
|
+
if (Array.isArray(events))
|
|
22
|
+
return events.includes("pull_request");
|
|
23
|
+
if (isRecord(events))
|
|
24
|
+
return "pull_request" in events ? events["pull_request"] : false;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function patternToken(token) {
|
|
32
|
+
if (token === "**/")
|
|
33
|
+
return { kind: "directory" };
|
|
34
|
+
if (token === "*" || token === "**")
|
|
35
|
+
return {
|
|
36
|
+
kind: "character",
|
|
37
|
+
matcher: token === "*" ? /[^/]/ : /[\s\S]/,
|
|
38
|
+
optional: true,
|
|
39
|
+
repeat: true,
|
|
40
|
+
};
|
|
41
|
+
const literal = token.startsWith("\\") ? token.slice(1) : token;
|
|
42
|
+
const source = token.startsWith("[")
|
|
43
|
+
? token
|
|
44
|
+
: literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45
|
+
return {
|
|
46
|
+
kind: "character",
|
|
47
|
+
matcher: new RegExp(`^${source}$`),
|
|
48
|
+
optional: false,
|
|
49
|
+
repeat: false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function patternTokens(source) {
|
|
53
|
+
const tokens = [];
|
|
54
|
+
for (const raw of source.match(/\*\*\/|\*\*|\*|\[[a-zA-Z0-9-]+\]|\\[\s\S]|[\s\S]/g) ?? []) {
|
|
55
|
+
if (raw !== "?" && raw !== "+") {
|
|
56
|
+
tokens.push(patternToken(raw));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const previous = tokens.pop();
|
|
60
|
+
if (previous?.kind !== "character" || previous.optional || previous.repeat)
|
|
61
|
+
throw new Error("invalid-workflow-filter");
|
|
62
|
+
tokens.push({ ...previous, optional: raw === "?", repeat: raw === "+" });
|
|
63
|
+
}
|
|
64
|
+
return tokens;
|
|
65
|
+
}
|
|
66
|
+
function advanceDirectory({ value, previous, }) {
|
|
67
|
+
let reachable = false;
|
|
68
|
+
return Array.from({ length: value.length + 1 }, (_, index) => {
|
|
69
|
+
const matches = previous[index] === true || (reachable && value[index - 1] === "/");
|
|
70
|
+
reachable ||= previous[index] === true;
|
|
71
|
+
return matches;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function advancePattern(token, input) {
|
|
75
|
+
const { value, previous } = input;
|
|
76
|
+
const next = [];
|
|
77
|
+
for (let index = 0; index <= value.length; index += 1) {
|
|
78
|
+
const consumes = index > 0 && token.matcher.test(value[index - 1] ?? "");
|
|
79
|
+
next[index] =
|
|
80
|
+
(token.optional && previous[index] === true) ||
|
|
81
|
+
(consumes &&
|
|
82
|
+
(previous[index - 1] === true ||
|
|
83
|
+
(token.repeat && next[index - 1] === true)));
|
|
84
|
+
}
|
|
85
|
+
return next;
|
|
86
|
+
}
|
|
87
|
+
function matchesPattern(value, source) {
|
|
88
|
+
let previous = [true];
|
|
89
|
+
for (const token of patternTokens(source))
|
|
90
|
+
previous =
|
|
91
|
+
token.kind === "directory"
|
|
92
|
+
? advanceDirectory({ value, previous })
|
|
93
|
+
: advancePattern(token, { value, previous });
|
|
94
|
+
return previous[value.length] === true;
|
|
95
|
+
}
|
|
96
|
+
function selected(value, patterns) {
|
|
97
|
+
let included = false;
|
|
98
|
+
for (const pattern of patterns) {
|
|
99
|
+
const negative = pattern.startsWith("!");
|
|
100
|
+
const source = negative ? pattern.slice(1) : pattern;
|
|
101
|
+
try {
|
|
102
|
+
if (matchesPattern(value, source))
|
|
103
|
+
included = !negative;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return included;
|
|
110
|
+
}
|
|
111
|
+
function patterns(value) {
|
|
112
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
113
|
+
return undefined;
|
|
114
|
+
return value.every((item) => typeof item === "string")
|
|
115
|
+
? value
|
|
116
|
+
: undefined;
|
|
117
|
+
}
|
|
118
|
+
function allows(values, filters) {
|
|
119
|
+
const include = patterns(filters.include);
|
|
120
|
+
const ignore = patterns(filters.ignore);
|
|
121
|
+
if (include !== undefined &&
|
|
122
|
+
!values.some((value) => selected(value, include) !== false))
|
|
123
|
+
return false;
|
|
124
|
+
return (ignore === undefined ||
|
|
125
|
+
values.some((value) => selected(value, ignore) !== true));
|
|
126
|
+
}
|
|
127
|
+
export function workflowApplies(trigger, context) {
|
|
128
|
+
if (!isRecord(trigger))
|
|
129
|
+
return trigger !== false;
|
|
130
|
+
return (allows([context.branch], {
|
|
131
|
+
include: trigger["branches"],
|
|
132
|
+
ignore: trigger["branches-ignore"],
|
|
133
|
+
}) &&
|
|
134
|
+
allows(context.paths, {
|
|
135
|
+
include: trigger["paths"],
|
|
136
|
+
ignore: trigger["paths-ignore"],
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Operation } from "./github-operation.ts";
|
|
2
|
+
export interface ExpectedWorkflow {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly kind: "push" | "event";
|
|
5
|
+
}
|
|
6
|
+
export declare function pullRequestWorkflows(operation: Operation, commit: string): Promise<readonly ExpectedWorkflow[]>;
|
|
7
|
+
export declare function pendingWorkflows(operation: Operation, input: {
|
|
8
|
+
readonly commit: string;
|
|
9
|
+
readonly workflows: readonly ExpectedWorkflow[];
|
|
10
|
+
}): Promise<readonly string[]>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { pullRequestTrigger, triggerKind, workflowApplies, } from "./github-ci-filters.js";
|
|
2
|
+
import { field, repositoryPath } from "./github-operation.js";
|
|
3
|
+
import { githubRead } from "./github-retry.js";
|
|
4
|
+
async function pullBase(operation) {
|
|
5
|
+
const pull = JSON.parse(await githubRead(operation, [
|
|
6
|
+
"api",
|
|
7
|
+
`${repositoryPath}/pulls/${String(operation.payload["number"])}`,
|
|
8
|
+
]));
|
|
9
|
+
if (typeof pull !== "object" || pull === null || !("base" in pull))
|
|
10
|
+
throw new Error("github-call-failed");
|
|
11
|
+
const baseData = pull.base;
|
|
12
|
+
if (typeof baseData !== "object" || baseData === null)
|
|
13
|
+
throw new Error("github-call-failed");
|
|
14
|
+
const base = field(baseData, "sha");
|
|
15
|
+
const branch = field(baseData, "ref");
|
|
16
|
+
if (typeof base !== "string" ||
|
|
17
|
+
!/^[0-9a-f]{40,64}$/.test(base) ||
|
|
18
|
+
typeof branch !== "string")
|
|
19
|
+
throw new Error("github-call-failed");
|
|
20
|
+
return { base, branch };
|
|
21
|
+
}
|
|
22
|
+
async function appliesToPull(operation, input) {
|
|
23
|
+
const { commit, trigger } = input;
|
|
24
|
+
if (trigger === false)
|
|
25
|
+
return false;
|
|
26
|
+
if (typeof trigger !== "object" || trigger === null)
|
|
27
|
+
return true;
|
|
28
|
+
if (!["paths", "paths-ignore", "branches", "branches-ignore"].some((key) => Object.hasOwn(trigger, key)))
|
|
29
|
+
return true;
|
|
30
|
+
const { base, branch } = await pullBase(operation);
|
|
31
|
+
await operation.calls.git(operation.settings.repository, [
|
|
32
|
+
"fetch",
|
|
33
|
+
"--quiet",
|
|
34
|
+
"--no-tags",
|
|
35
|
+
"origin",
|
|
36
|
+
base,
|
|
37
|
+
]);
|
|
38
|
+
const paths = await operation.calls.git(operation.settings.repository, [
|
|
39
|
+
"diff",
|
|
40
|
+
"--name-only",
|
|
41
|
+
"-z",
|
|
42
|
+
`${base}...${commit}`,
|
|
43
|
+
]);
|
|
44
|
+
return workflowApplies(trigger, {
|
|
45
|
+
branch,
|
|
46
|
+
paths: paths.split("\0").filter((path) => path !== ""),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export async function pullRequestWorkflows(operation, commit) {
|
|
50
|
+
const read = (parts) => operation.calls.git(operation.settings.repository, parts);
|
|
51
|
+
const paths = await read([
|
|
52
|
+
"ls-tree",
|
|
53
|
+
"-r",
|
|
54
|
+
"--name-only",
|
|
55
|
+
"-z",
|
|
56
|
+
commit,
|
|
57
|
+
"--",
|
|
58
|
+
".github/workflows",
|
|
59
|
+
]);
|
|
60
|
+
const workflows = [];
|
|
61
|
+
for (const path of paths.split("\0")) {
|
|
62
|
+
if (!/^\.github\/workflows\/[^/]+\.ya?ml$/.test(path))
|
|
63
|
+
continue;
|
|
64
|
+
const trigger = pullRequestTrigger(await read(["show", `${commit}:${path}`]));
|
|
65
|
+
if (await appliesToPull(operation, { commit, trigger }))
|
|
66
|
+
workflows.push({ path, kind: triggerKind(trigger) });
|
|
67
|
+
}
|
|
68
|
+
if (workflows.length === 0)
|
|
69
|
+
return workflows;
|
|
70
|
+
const inactive = await inactiveWorkflows(operation);
|
|
71
|
+
return workflows.filter((workflow) => !inactive.has(workflow.path));
|
|
72
|
+
}
|
|
73
|
+
function isList(value) {
|
|
74
|
+
return Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
async function actionPages(operation, request) {
|
|
77
|
+
const pages = JSON.parse(await githubRead(operation, ["api", "--paginate", "--slurp", request.path]));
|
|
78
|
+
if (!isList(pages) || pages.length === 0)
|
|
79
|
+
throw new Error("github-call-failed");
|
|
80
|
+
return pages.flatMap((page) => {
|
|
81
|
+
if (typeof page !== "object" || page === null)
|
|
82
|
+
throw new Error("github-call-failed");
|
|
83
|
+
const items = field(page, request.key);
|
|
84
|
+
if (!isList(items))
|
|
85
|
+
throw new Error("github-call-failed");
|
|
86
|
+
return items;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function inactiveWorkflows(operation) {
|
|
90
|
+
const workflows = await actionPages(operation, {
|
|
91
|
+
path: `${repositoryPath}/actions/workflows?per_page=100`,
|
|
92
|
+
key: "workflows",
|
|
93
|
+
});
|
|
94
|
+
const inactive = new Set();
|
|
95
|
+
for (const workflow of workflows) {
|
|
96
|
+
if (typeof workflow !== "object" || workflow === null)
|
|
97
|
+
throw new Error("github-call-failed");
|
|
98
|
+
const path = field(workflow, "path");
|
|
99
|
+
const state = field(workflow, "state");
|
|
100
|
+
if (typeof path !== "string" || typeof state !== "string")
|
|
101
|
+
throw new Error("github-call-failed");
|
|
102
|
+
if (state !== "active")
|
|
103
|
+
inactive.add(path);
|
|
104
|
+
}
|
|
105
|
+
return inactive;
|
|
106
|
+
}
|
|
107
|
+
function isWorkflowRun(value) {
|
|
108
|
+
if (typeof value !== "object" || value === null)
|
|
109
|
+
return false;
|
|
110
|
+
const conclusion = field(value, "conclusion");
|
|
111
|
+
return (typeof field(value, "id") === "number" &&
|
|
112
|
+
["path", "head_sha", "event", "status"].every((key) => typeof field(value, key) === "string") &&
|
|
113
|
+
(conclusion === null || typeof conclusion === "string"));
|
|
114
|
+
}
|
|
115
|
+
async function workflowRuns(operation, commit) {
|
|
116
|
+
const runs = await actionPages(operation, {
|
|
117
|
+
path: `${repositoryPath}/actions/runs?head_sha=${commit}&event=pull_request&per_page=100`,
|
|
118
|
+
key: "workflow_runs",
|
|
119
|
+
});
|
|
120
|
+
if (!runs.every(isWorkflowRun))
|
|
121
|
+
throw new Error("github-call-failed");
|
|
122
|
+
return runs;
|
|
123
|
+
}
|
|
124
|
+
export async function pendingWorkflows(operation, input) {
|
|
125
|
+
if (input.workflows.length === 0)
|
|
126
|
+
return [];
|
|
127
|
+
const paths = input.workflows.map((workflow) => workflow.path);
|
|
128
|
+
const latest = new Map();
|
|
129
|
+
for (const run of await workflowRuns(operation, input.commit)) {
|
|
130
|
+
if (run.head_sha !== input.commit || run.event !== "pull_request")
|
|
131
|
+
continue;
|
|
132
|
+
const path = paths
|
|
133
|
+
.filter((expected) => run.path === expected || run.path.startsWith(`${expected}@`))
|
|
134
|
+
.sort((left, right) => right.length - left.length)[0];
|
|
135
|
+
if (path === undefined)
|
|
136
|
+
continue;
|
|
137
|
+
if (run.id > (latest.get(path)?.id ?? -1))
|
|
138
|
+
latest.set(path, run);
|
|
139
|
+
}
|
|
140
|
+
const pending = input.workflows.filter((workflow) => {
|
|
141
|
+
const run = latest.get(workflow.path);
|
|
142
|
+
if (run === undefined)
|
|
143
|
+
return workflow.kind === "push";
|
|
144
|
+
return (run.status !== "completed" ||
|
|
145
|
+
!["success", "neutral", "skipped"].includes(run.conclusion ?? ""));
|
|
146
|
+
});
|
|
147
|
+
return pending.length === 0 ? [] : ["waiting for GitHub Actions checks"];
|
|
148
|
+
}
|
|
@@ -2,6 +2,7 @@ import { settingsEnvironment } from "../cli/project-settings.js";
|
|
|
2
2
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
3
|
import { githubText } from "./github-calls.js";
|
|
4
4
|
import { githubRead } from "./github-retry.js";
|
|
5
|
+
import { pendingWorkflows, pullRequestWorkflows, } from "./github-ci-workflows.js";
|
|
5
6
|
import { ciWait, workflowAttempt, attemptChecks, rerunCi, attemptOutput, } from "./github-ci-rerun.js";
|
|
6
7
|
import { field, githubTiming, repositoryPath, } from "./github-operation.js";
|
|
7
8
|
function isCheckRun(value) {
|
|
@@ -203,16 +204,29 @@ async function ciFailureOutput(operation, input) {
|
|
|
203
204
|
current,
|
|
204
205
|
]);
|
|
205
206
|
}
|
|
206
|
-
|
|
207
|
-
const
|
|
207
|
+
async function ciSettings(operation, commit) {
|
|
208
|
+
const timing = {
|
|
208
209
|
...githubTiming,
|
|
209
210
|
ciGraceMilliseconds: 60_000,
|
|
210
211
|
ciLimitMilliseconds: 5_400_000,
|
|
211
212
|
...operation.settings.timing,
|
|
212
213
|
};
|
|
214
|
+
const required = requiredChecks(operation);
|
|
215
|
+
const workflows = required === undefined ? await pullRequestWorkflows(operation, commit) : [];
|
|
216
|
+
return {
|
|
217
|
+
required,
|
|
218
|
+
workflows,
|
|
219
|
+
poll: timing.pollMilliseconds,
|
|
220
|
+
limit: timing.ciLimitMilliseconds,
|
|
221
|
+
grace: workflows.some((workflow) => workflow.kind === "push")
|
|
222
|
+
? Infinity
|
|
223
|
+
: timing.ciGraceMilliseconds,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
export async function waitForCi(operation, commit) {
|
|
213
227
|
const wait = await ciWait(operation, commit);
|
|
214
228
|
const started = wait.startedAt;
|
|
215
|
-
const required =
|
|
229
|
+
const { required, workflows, grace, poll, limit } = await ciSettings(operation, commit);
|
|
216
230
|
const names = new Set(required);
|
|
217
231
|
for (;;) {
|
|
218
232
|
const checks = latestRuns(await checkRuns(operation, commit), required);
|
|
@@ -232,7 +246,11 @@ export async function waitForCi(operation, commit) {
|
|
|
232
246
|
failed: retried.failures,
|
|
233
247
|
}),
|
|
234
248
|
};
|
|
235
|
-
const pending = [
|
|
249
|
+
const pending = [
|
|
250
|
+
...pendingChecks(checks, names),
|
|
251
|
+
...retried.pending,
|
|
252
|
+
...(await pendingWorkflows(operation, { commit, workflows })),
|
|
253
|
+
];
|
|
236
254
|
if (pending.length === 0 &&
|
|
237
255
|
(required !== undefined ||
|
|
238
256
|
names.size > 0 ||
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { type Operation } from "./github-operation.ts";
|
|
2
|
+
export declare function repositoryName(reply: string): string;
|
|
3
|
+
export declare function captureDraft(operation: Operation): Promise<unknown>;
|
|
2
4
|
export declare function createIssue(operation: Operation): Promise<unknown>;
|
|
3
5
|
export declare function discard(operation: Operation): Promise<unknown>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { lstat, readFile } from "node:fs/promises";
|
|
3
4
|
import { basename, join } from "node:path";
|
|
4
5
|
import { existingTree, removeWorktree, repositoryPath, text, } from "./github-operation.js";
|
|
@@ -30,7 +31,7 @@ function issuePage(line) {
|
|
|
30
31
|
throw new Error("github-call-failed");
|
|
31
32
|
return page.map((issue) => issueOf(issue));
|
|
32
33
|
}
|
|
33
|
-
function repositoryName(reply) {
|
|
34
|
+
export function repositoryName(reply) {
|
|
34
35
|
const repository = JSON.parse(reply);
|
|
35
36
|
if (typeof repository !== "object" ||
|
|
36
37
|
repository === null ||
|
|
@@ -50,6 +51,28 @@ async function draftOf(operation) {
|
|
|
50
51
|
throw new Error("draft-not-available");
|
|
51
52
|
return readFile(path, "utf8");
|
|
52
53
|
}
|
|
54
|
+
function draftDigest(text) {
|
|
55
|
+
return "sha256:" + createHash("sha256").update(text).digest("hex");
|
|
56
|
+
}
|
|
57
|
+
export async function captureDraft(operation) {
|
|
58
|
+
const text = await draftOf(operation);
|
|
59
|
+
const capture = { text, digest: draftDigest(text) };
|
|
60
|
+
if (Buffer.byteLength(JSON.stringify(capture)) > 65_536)
|
|
61
|
+
throw new Error("draft-too-large");
|
|
62
|
+
return capture;
|
|
63
|
+
}
|
|
64
|
+
function checkDraftDigest(operation, draft) {
|
|
65
|
+
if (!Object.hasOwn(operation.payload, "expectedDigests"))
|
|
66
|
+
return;
|
|
67
|
+
const digests = operation.payload["expectedDigests"];
|
|
68
|
+
if (!Array.isArray(digests) ||
|
|
69
|
+
digests.length === 0 ||
|
|
70
|
+
!digests.every((digest) => typeof digest === "string" && /^sha256:[0-9a-f]{64}$/.test(digest)))
|
|
71
|
+
throw new Error("invalid-payload");
|
|
72
|
+
const current = draftDigest(draft);
|
|
73
|
+
if (!digests.every((digest) => digest === current))
|
|
74
|
+
throw new Error("draft-changed");
|
|
75
|
+
}
|
|
53
76
|
async function knownIssue(operation, marker) {
|
|
54
77
|
const output = await operation.calls.gh([
|
|
55
78
|
"api",
|
|
@@ -62,7 +85,10 @@ async function knownIssue(operation, marker) {
|
|
|
62
85
|
}
|
|
63
86
|
export async function createIssue(operation) {
|
|
64
87
|
operatorPrivateTexts(operation.settings);
|
|
65
|
-
const draft =
|
|
88
|
+
const draft = Object.hasOwn(operation.payload, "text")
|
|
89
|
+
? reportText(operation)
|
|
90
|
+
: await draftOf(operation);
|
|
91
|
+
checkDraftDigest(operation, draft);
|
|
66
92
|
const [heading = "", ...lines] = draft.split("\n");
|
|
67
93
|
if (!heading.startsWith("# ") || heading.slice(2).trim() === "")
|
|
68
94
|
throw new Error("invalid-draft");
|
|
@@ -89,6 +115,15 @@ export async function createIssue(operation) {
|
|
|
89
115
|
const url = printed.trim().split("\n").at(-1) ?? "";
|
|
90
116
|
return issueFromUrl(url);
|
|
91
117
|
}
|
|
118
|
+
function reportText(operation) {
|
|
119
|
+
if (Object.hasOwn(operation.payload, "draft") ||
|
|
120
|
+
Object.hasOwn(operation.payload, "expectedDigests"))
|
|
121
|
+
throw new Error("invalid-payload");
|
|
122
|
+
const report = text(operation, "text");
|
|
123
|
+
if (Buffer.byteLength(report) > 65536)
|
|
124
|
+
throw new Error("draft-too-large");
|
|
125
|
+
return report;
|
|
126
|
+
}
|
|
92
127
|
export async function discard(operation) {
|
|
93
128
|
const place = treePlace({
|
|
94
129
|
worktrees: operation.settings.worktrees,
|