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
package/dist/tools/self-host.js
CHANGED
|
@@ -14,6 +14,7 @@ import { hostedPrograms } from "./self-host-processes.js";
|
|
|
14
14
|
import { workflowBundleLoader } from "./self-host-workflows.js";
|
|
15
15
|
import { adoptRuntimeDirectory } from "./self-host-runtime.js";
|
|
16
16
|
import { parseSchedulesSetting } from "../src/workflow-schedule.js";
|
|
17
|
+
import { checkSubmission } from "../src/submission.js";
|
|
17
18
|
const product = resolve(import.meta.dirname, "..");
|
|
18
19
|
const flags = process.argv.slice(2);
|
|
19
20
|
function flag(name) {
|
|
@@ -89,7 +90,18 @@ function queueLanes() {
|
|
|
89
90
|
throw new Error("The daemon did not start: queue-lanes-needs-queue");
|
|
90
91
|
return Number(value);
|
|
91
92
|
}
|
|
92
|
-
function
|
|
93
|
+
function queueSpendLimit() {
|
|
94
|
+
const value = environment["CLOVING_QUEUE_DAILY_SPEND_USD"];
|
|
95
|
+
if (value === undefined)
|
|
96
|
+
return undefined;
|
|
97
|
+
const limit = Number(value);
|
|
98
|
+
if (!Number.isFinite(limit) || limit <= 0)
|
|
99
|
+
throw new Error("The daemon did not start: invalid-queue-spend-limit");
|
|
100
|
+
if (environment["CLOVING_QUEUE_SECONDS"] === undefined)
|
|
101
|
+
throw new Error("The daemon did not start: queue-spend-limit-needs-queue");
|
|
102
|
+
return limit;
|
|
103
|
+
}
|
|
104
|
+
function queueInterval() {
|
|
93
105
|
const value = environment["CLOVING_QUEUE_SECONDS"];
|
|
94
106
|
if (value === undefined)
|
|
95
107
|
return undefined;
|
|
@@ -99,10 +111,6 @@ function queueInterval(commands) {
|
|
|
99
111
|
seconds < 10 ||
|
|
100
112
|
seconds > 3600)
|
|
101
113
|
throw new Error("The daemon did not start: invalid-queue-seconds");
|
|
102
|
-
if (!commands.nextCommand?.length)
|
|
103
|
-
throw new Error("The daemon did not start: queue-needs-next-command");
|
|
104
|
-
if (!commands.ticketCommand?.length)
|
|
105
|
-
throw new Error("The daemon did not start: queue-needs-ticket-command");
|
|
106
114
|
return seconds * 1000;
|
|
107
115
|
}
|
|
108
116
|
function listed(name) {
|
|
@@ -118,6 +126,12 @@ function source(file) {
|
|
|
118
126
|
return JSON.stringify(pathToFileURL(join(product, file.replace(/\.ts$/, extname(import.meta.filename)))).href);
|
|
119
127
|
}
|
|
120
128
|
async function launchers(commands, workflows) {
|
|
129
|
+
const queueEnabled = commands.queueMilliseconds !== undefined &&
|
|
130
|
+
workflows.declarations.some((declaration) => declaration.queue === true);
|
|
131
|
+
if (queueEnabled && !commands.nextCommand?.length)
|
|
132
|
+
throw new Error("The daemon did not start: queue-needs-next-command");
|
|
133
|
+
if (queueEnabled && !commands.ticketCommand?.length)
|
|
134
|
+
throw new Error("The daemon did not start: queue-needs-ticket-command");
|
|
121
135
|
const settings = {
|
|
122
136
|
...commands,
|
|
123
137
|
directory,
|
|
@@ -144,6 +158,22 @@ process.once("SIGINT", stop);
|
|
|
144
158
|
process.once("SIGTERM", stop);
|
|
145
159
|
`);
|
|
146
160
|
}
|
|
161
|
+
function runCommand(declaration) {
|
|
162
|
+
const { workflowId, input } = declaration;
|
|
163
|
+
for (const [field, value, argument] of [
|
|
164
|
+
["ticket", "ticket-id", "--ticket <ticket-id>"],
|
|
165
|
+
["prompt", "task text", '--prompt "<text>"'],
|
|
166
|
+
]) {
|
|
167
|
+
const checked = checkSubmission(input, {
|
|
168
|
+
workflowId,
|
|
169
|
+
source: field,
|
|
170
|
+
submission: { [field]: value },
|
|
171
|
+
});
|
|
172
|
+
if (checked.kind === "valid")
|
|
173
|
+
return ` ${client} run ${workflowId} ${argument}`;
|
|
174
|
+
}
|
|
175
|
+
return ` ${client} run ${workflowId} --input <json-file>`;
|
|
176
|
+
}
|
|
147
177
|
function guide(workflows) {
|
|
148
178
|
return [
|
|
149
179
|
"Cloving runs. Stop it with Control-C.",
|
|
@@ -154,10 +184,9 @@ function guide(workflows) {
|
|
|
154
184
|
...Object.entries(configuration.sources).map(([field, source]) => `${field}: ${source}`),
|
|
155
185
|
...workflows.workflowRefusals.map((refusal) => `Refused workflow ${refusal.scope} ${refusal.label}: ${refusal.codes.join(", ")}`),
|
|
156
186
|
"Commands for a second terminal:",
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
` ${client} run develop-prd --prompt "<the idea>"`,
|
|
187
|
+
...(workflows.declarations.length === 0
|
|
188
|
+
? ["No workflow. Run: cloving init"]
|
|
189
|
+
: workflows.declarations.map(runCommand)),
|
|
161
190
|
` ${client} status`,
|
|
162
191
|
` ${client} questions`,
|
|
163
192
|
` ${client} watch`,
|
|
@@ -187,8 +216,9 @@ const harness = configuration.harness ?? {};
|
|
|
187
216
|
const command = turnHarness({ worker, workerCommands, harness }).command;
|
|
188
217
|
const nextCommand = listed("CLOVING_NEXT_COMMAND");
|
|
189
218
|
const ticketCommand = listed("CLOVING_TICKET_COMMAND");
|
|
219
|
+
const queueDailySpendLimitUsd = queueSpendLimit();
|
|
190
220
|
const lanes = queueLanes();
|
|
191
|
-
const queueMilliseconds = queueInterval(
|
|
221
|
+
const queueMilliseconds = queueInterval();
|
|
192
222
|
const manifests = await preparedProject({
|
|
193
223
|
product,
|
|
194
224
|
project,
|
|
@@ -213,6 +243,7 @@ await launchers({
|
|
|
213
243
|
nextCommand,
|
|
214
244
|
queueMilliseconds,
|
|
215
245
|
queueLanes: lanes,
|
|
246
|
+
queueDailySpendLimitUsd,
|
|
216
247
|
resetQueue: flags.includes("--reset-queue"),
|
|
217
248
|
notifyCommand: listed("CLOVING_NOTIFY_COMMAND"),
|
|
218
249
|
githubCommand: listed("CLOVING_GH_COMMAND"),
|
|
@@ -221,6 +252,7 @@ await launchers({
|
|
|
221
252
|
privateTexts: listed("CLOVING_PRIVATE_TEXTS"),
|
|
222
253
|
}, manifests);
|
|
223
254
|
process.exitCode = await hostedPrograms({
|
|
255
|
+
environment,
|
|
224
256
|
project,
|
|
225
257
|
product,
|
|
226
258
|
runtime,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function templateCommand(argumentsList: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { absoluteWorkflowFolder } from "../src/project-settings.js";
|
|
6
|
+
import { record } from "../src/protocol.js";
|
|
7
|
+
import { isCandidateDeclaration } from "../src/workflow-candidate.js";
|
|
8
|
+
import { projectConfiguration, projectRoot } from "./project-configuration.js";
|
|
9
|
+
import { copyTemplate } from "./template-copy.js";
|
|
10
|
+
import { exportedFunctions, readTemplate, templateList, templateRoot, } from "./template-files.js";
|
|
11
|
+
import { userLocation } from "./workflow-folders.js";
|
|
12
|
+
const usage = "Usage: cloving templates list [--json]\n cloving templates show <name> [--json]\n cloving templates copy <name> [--id <workflow-id>] [--json] [--repository <directory>]\n";
|
|
13
|
+
function invocation(argumentsList) {
|
|
14
|
+
const { values, positionals } = parseArgs({
|
|
15
|
+
args: [...argumentsList],
|
|
16
|
+
allowPositionals: true,
|
|
17
|
+
options: {
|
|
18
|
+
json: { type: "boolean" },
|
|
19
|
+
id: { type: "string" },
|
|
20
|
+
repository: { type: "string" },
|
|
21
|
+
help: { type: "boolean", short: "h" },
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
const [command, name, ...extra] = positionals;
|
|
25
|
+
if (values.help === true)
|
|
26
|
+
return { ...values, command: "help", name: "" };
|
|
27
|
+
if (!["list", "show", "copy"].includes(command ?? ""))
|
|
28
|
+
throw new Error("unknown-command");
|
|
29
|
+
if (extra.length > 0 || (command === "list") !== (name === undefined))
|
|
30
|
+
throw new Error("unexpected-argument");
|
|
31
|
+
if (command !== "copy" && (values.id !== undefined || values.repository))
|
|
32
|
+
throw new Error("unexpected-option");
|
|
33
|
+
return { ...values, command, name: name ?? "" };
|
|
34
|
+
}
|
|
35
|
+
export async function templateCommand(argumentsList) {
|
|
36
|
+
let selected;
|
|
37
|
+
try {
|
|
38
|
+
selected = invocation(argumentsList);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
process.stderr.write("invalid-invocation: " + usage);
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
44
|
+
if (selected.help) {
|
|
45
|
+
process.stdout.write(usage);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const root = templateRoot();
|
|
50
|
+
const json = selected.json === true;
|
|
51
|
+
if (selected.command === "list")
|
|
52
|
+
return await listed(root, { json });
|
|
53
|
+
if (selected.command === "show")
|
|
54
|
+
return await shown(root, { name: selected.name, json });
|
|
55
|
+
return await copied(root, {
|
|
56
|
+
name: selected.name,
|
|
57
|
+
id: selected.id ?? selected.name,
|
|
58
|
+
json,
|
|
59
|
+
repository: selected.repository ?? process.cwd(),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
process.stderr.write((error instanceof Error ? error.message : "template-command-failed") +
|
|
64
|
+
"\n");
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function listed(root, input) {
|
|
69
|
+
const templates = await templateList(root);
|
|
70
|
+
if (input.json)
|
|
71
|
+
process.stdout.write(JSON.stringify(templates) + "\n");
|
|
72
|
+
else
|
|
73
|
+
for (const template of templates)
|
|
74
|
+
process.stdout.write(`${template.name}: ${template.summary} (${String(template.points)} decision points)\n`);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
async function shown(root, input) {
|
|
78
|
+
const template = await readTemplate(root, input.name);
|
|
79
|
+
if (input.json) {
|
|
80
|
+
process.stdout.write(JSON.stringify({
|
|
81
|
+
template: template.description,
|
|
82
|
+
files: template.files,
|
|
83
|
+
}) + "\n");
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
process.stdout.write(`${template.name}: ${String(template.description["summary"])}\n${pointText(template.points)}Files:\n${template.files.map((file) => ` ${file}\n`).join("")}`);
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
async function copied(root, input) {
|
|
90
|
+
const repository = await projectRoot(input.repository);
|
|
91
|
+
const folder = join(repository, ".cloving", "workflows");
|
|
92
|
+
const folders = [folder, await userFolder(repository)];
|
|
93
|
+
const packageRoot = dirname(root);
|
|
94
|
+
const takenNames = await exportedFunctions([
|
|
95
|
+
join(packageRoot, "workflows"),
|
|
96
|
+
join(packageRoot, "dist", "workflows"),
|
|
97
|
+
...folders,
|
|
98
|
+
]);
|
|
99
|
+
const result = await copyTemplate({ ...input, root, folder, takenNames });
|
|
100
|
+
const queued = (await Promise.all(folders.map(queuedIn))).flat().sort();
|
|
101
|
+
if (input.json)
|
|
102
|
+
process.stdout.write(JSON.stringify({
|
|
103
|
+
workflowId: input.id,
|
|
104
|
+
template: input.name,
|
|
105
|
+
files: result.files,
|
|
106
|
+
queueWorkflows: queued,
|
|
107
|
+
}) + "\n");
|
|
108
|
+
else
|
|
109
|
+
process.stdout.write(`Copied the template ${input.name} to .cloving/workflows as the workflow ${input.id}.\nDecision points:\n${pointText(result.template.points)}Next: cloving workflows test ${input.id}\n`);
|
|
110
|
+
if (queued.length > 1)
|
|
111
|
+
process.stderr.write(`queue-workflow-conflict: ${queued.join(", ")} have "queue": true. Select "a person" for the select point of one copy: remove workflow.queue from its declaration.\n`);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
function pointText(points) {
|
|
115
|
+
return points
|
|
116
|
+
.map((value) => {
|
|
117
|
+
const point = record(value);
|
|
118
|
+
const options = point?.["options"];
|
|
119
|
+
const lines = (Array.isArray(options) ? options : []).map((option, index) => {
|
|
120
|
+
const fields = record(option);
|
|
121
|
+
return ` - ${String(fields?.["id"])}: ${String(fields?.["label"])}${index === 0 ? " (default)" : ""}\n`;
|
|
122
|
+
});
|
|
123
|
+
return ` ${String(point?.["id"])}: ${String(point?.["question"])}\n${lines.join("")}`;
|
|
124
|
+
})
|
|
125
|
+
.join("");
|
|
126
|
+
}
|
|
127
|
+
async function userFolder(repository) {
|
|
128
|
+
const settings = await projectConfiguration(repository, process.env);
|
|
129
|
+
const user = userLocation({
|
|
130
|
+
home: homedir(),
|
|
131
|
+
xdgConfigHome: process.env["XDG_CONFIG_HOME"],
|
|
132
|
+
userFolder: absoluteWorkflowFolder(settings.environment["CLOVING_WORKFLOW_FOLDER"]),
|
|
133
|
+
});
|
|
134
|
+
return join(user.root, ...user.parts);
|
|
135
|
+
}
|
|
136
|
+
async function queuedIn(folder) {
|
|
137
|
+
const names = await readdir(folder).catch(() => []);
|
|
138
|
+
const ids = await Promise.all(names
|
|
139
|
+
.filter((name) => name.endsWith(".declaration.json"))
|
|
140
|
+
.map((name) => queuedId(folder, name)));
|
|
141
|
+
return ids.flatMap((id) => (id === undefined ? [] : [id]));
|
|
142
|
+
}
|
|
143
|
+
async function queuedId(folder, name) {
|
|
144
|
+
const text = await readFile(join(folder, name), "utf8").catch(() => "");
|
|
145
|
+
let declaration;
|
|
146
|
+
try {
|
|
147
|
+
declaration = JSON.parse(text);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
if (!isCandidateDeclaration(declaration) ||
|
|
153
|
+
declaration.workflow.queue !== true)
|
|
154
|
+
return undefined;
|
|
155
|
+
const source = join(folder, name.replace(/\.declaration\.json$/, ".ts"));
|
|
156
|
+
const entry = await stat(source).catch(() => undefined);
|
|
157
|
+
return entry?.isFile() === true ? declaration.workflow.workflowId : undefined;
|
|
158
|
+
}
|
|
159
|
+
if (process.argv[1] === import.meta.filename)
|
|
160
|
+
process.exitCode = await templateCommand(process.argv.slice(2));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Template } from "./template-files.ts";
|
|
2
|
+
export declare function copyTemplate(input: {
|
|
3
|
+
readonly root: string;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly folder: string;
|
|
7
|
+
readonly takenNames: ReadonlySet<string>;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
readonly template: Template;
|
|
10
|
+
readonly files: readonly string[];
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { link, mkdir, mkdtemp, readFile, rm, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { record } from "../src/protocol.js";
|
|
5
|
+
import { readTemplate, templateName } from "./template-files.js";
|
|
6
|
+
export async function copyTemplate(input) {
|
|
7
|
+
if (!templateName.test(input.id) || input.id.length > 64)
|
|
8
|
+
throw new Error(`invalid-workflow-id: ${input.id}`);
|
|
9
|
+
const template = await readTemplate(input.root, input.name);
|
|
10
|
+
const sources = await Promise.all(template.files.map(async (file) => ({
|
|
11
|
+
file,
|
|
12
|
+
text: await readFile(join(input.root, input.name, file), "utf8"),
|
|
13
|
+
})));
|
|
14
|
+
const renaming = functionRenaming(sources, input);
|
|
15
|
+
const plan = sources.map(({ file, text }) => ({
|
|
16
|
+
target: input.id + file.slice(input.name.length),
|
|
17
|
+
content: copiedContent({ file, text }, renaming),
|
|
18
|
+
}));
|
|
19
|
+
await writePlan(input.folder, plan);
|
|
20
|
+
return { template, files: plan.map((item) => item.target) };
|
|
21
|
+
}
|
|
22
|
+
function camelName(id) {
|
|
23
|
+
return id.replace(/-([a-z0-9])/g, (_, letter) => letter.toUpperCase());
|
|
24
|
+
}
|
|
25
|
+
function functionRenaming(sources, input) {
|
|
26
|
+
const from = camelName(input.name);
|
|
27
|
+
const names = new Set(sources
|
|
28
|
+
.filter(({ file }) => file.endsWith(".ts"))
|
|
29
|
+
.flatMap(({ text }) => text.match(/[A-Za-z_$][\w$]*/g) ?? []));
|
|
30
|
+
for (const name of input.takenNames)
|
|
31
|
+
names.add(name);
|
|
32
|
+
let to = camelName(input.id);
|
|
33
|
+
while (to !== from && (names.has(to) || reservedWord(to)))
|
|
34
|
+
to += "Workflow";
|
|
35
|
+
return { template: input.name, id: input.id, from, to };
|
|
36
|
+
}
|
|
37
|
+
function reservedWord(name) {
|
|
38
|
+
const scanner = ts.createScanner(ts.ScriptTarget.Latest, true);
|
|
39
|
+
scanner.setText(name);
|
|
40
|
+
const kind = scanner.scan();
|
|
41
|
+
return ((kind >= ts.SyntaxKind.FirstReservedWord &&
|
|
42
|
+
kind <= ts.SyntaxKind.LastFutureReservedWord) ||
|
|
43
|
+
["await", "eval", "arguments"].includes(name));
|
|
44
|
+
}
|
|
45
|
+
function copiedContent(source, renaming) {
|
|
46
|
+
const { file, text } = source;
|
|
47
|
+
if (file.endsWith(".declaration.json"))
|
|
48
|
+
return copiedDeclaration(text, renaming);
|
|
49
|
+
if (!file.endsWith(".ts") || renaming.id === renaming.template)
|
|
50
|
+
return text;
|
|
51
|
+
return renamedSource(source, renaming);
|
|
52
|
+
}
|
|
53
|
+
function copiedDeclaration(text, renaming) {
|
|
54
|
+
const declaration = record(JSON.parse(text));
|
|
55
|
+
const workflow = record(declaration?.["workflow"]);
|
|
56
|
+
if (declaration === undefined || workflow === undefined)
|
|
57
|
+
throw new Error(`invalid-template: ${renaming.template}`);
|
|
58
|
+
return (JSON.stringify({
|
|
59
|
+
template: renaming.template,
|
|
60
|
+
...declaration,
|
|
61
|
+
workflow: { ...workflow, workflowId: renaming.id },
|
|
62
|
+
}, null, 2) + "\n");
|
|
63
|
+
}
|
|
64
|
+
function renamedSource(input, renaming) {
|
|
65
|
+
const { file, text } = input;
|
|
66
|
+
const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
|
|
67
|
+
const edits = [];
|
|
68
|
+
const prefix = new RegExp(`^(\\.\\.?/)${renaming.template}(?=[./])`);
|
|
69
|
+
const visit = (node) => {
|
|
70
|
+
if (ts.isIdentifier(node) && node.text === renaming.from)
|
|
71
|
+
edits.push({ start: node.getStart(), end: node.end, text: renaming.to });
|
|
72
|
+
if (ts.isStringLiteral(node) &&
|
|
73
|
+
(ts.isImportDeclaration(node.parent) ||
|
|
74
|
+
ts.isExportDeclaration(node.parent)) &&
|
|
75
|
+
prefix.test(node.text))
|
|
76
|
+
edits.push({
|
|
77
|
+
start: node.getStart() + 1,
|
|
78
|
+
end: node.end - 1,
|
|
79
|
+
text: node.text.replace(prefix, `$1${renaming.id}`),
|
|
80
|
+
});
|
|
81
|
+
ts.forEachChild(node, visit);
|
|
82
|
+
};
|
|
83
|
+
visit(source);
|
|
84
|
+
return edits
|
|
85
|
+
.sort((left, right) => right.start - left.start)
|
|
86
|
+
.reduce((current, edit) => current.slice(0, edit.start) + edit.text + current.slice(edit.end), text);
|
|
87
|
+
}
|
|
88
|
+
async function writePlan(folder, plan) {
|
|
89
|
+
await mkdir(folder, { recursive: true });
|
|
90
|
+
const staging = await mkdtemp(join(dirname(folder), ".template-copy-"));
|
|
91
|
+
const created = [];
|
|
92
|
+
try {
|
|
93
|
+
for (const item of plan) {
|
|
94
|
+
await mkdir(dirname(join(staging, item.target)), { recursive: true });
|
|
95
|
+
await writeFile(join(staging, item.target), item.content);
|
|
96
|
+
}
|
|
97
|
+
for (const item of plan)
|
|
98
|
+
await placed({ folder, staging, item }, created);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
for (const path of created.reverse())
|
|
102
|
+
await rm(path, { recursive: true, force: true });
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await rm(staging, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function placed(input, created) {
|
|
110
|
+
const parts = input.item.target.split("/");
|
|
111
|
+
for (let depth = 1; depth < parts.length; depth += 1) {
|
|
112
|
+
const directory = join(input.folder, ...parts.slice(0, depth));
|
|
113
|
+
if (created.includes(directory))
|
|
114
|
+
continue;
|
|
115
|
+
await mkdir(directory).catch((error) => {
|
|
116
|
+
throw present(error, parts.slice(0, depth).join("/"));
|
|
117
|
+
});
|
|
118
|
+
created.push(directory);
|
|
119
|
+
}
|
|
120
|
+
const target = join(input.folder, input.item.target);
|
|
121
|
+
await link(join(input.staging, input.item.target), target).catch((error) => {
|
|
122
|
+
throw present(error, input.item.target);
|
|
123
|
+
});
|
|
124
|
+
created.push(target);
|
|
125
|
+
}
|
|
126
|
+
function present(error, target) {
|
|
127
|
+
return error instanceof Error && "code" in error && error.code === "EEXIST"
|
|
128
|
+
? new Error(`workflow-exists: ${target}`)
|
|
129
|
+
: error;
|
|
130
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Fields } from "../src/protocol.ts";
|
|
2
|
+
export interface TemplateSummary {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly summary: string;
|
|
5
|
+
readonly points: number;
|
|
6
|
+
}
|
|
7
|
+
export interface Template {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly description: Fields;
|
|
10
|
+
readonly points: readonly unknown[];
|
|
11
|
+
readonly files: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export declare const templateName: RegExp;
|
|
14
|
+
export declare function templateRoot(start?: string): string;
|
|
15
|
+
export declare function templateList(root: string): Promise<readonly TemplateSummary[]>;
|
|
16
|
+
export declare function readTemplate(root: string, name: string): Promise<Template>;
|
|
17
|
+
export declare function exportedFunctions(folders: readonly string[]): Promise<ReadonlySet<string>>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { record } from "../src/protocol.js";
|
|
5
|
+
export const templateName = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
6
|
+
const descriptionFiles = ["template.json", "README.md"];
|
|
7
|
+
export function templateRoot(start = import.meta.dirname) {
|
|
8
|
+
let directory = start;
|
|
9
|
+
while (!existsSync(join(directory, "package.json"))) {
|
|
10
|
+
const parent = dirname(directory);
|
|
11
|
+
if (parent === directory)
|
|
12
|
+
throw new Error("templates-not-found");
|
|
13
|
+
directory = parent;
|
|
14
|
+
}
|
|
15
|
+
return join(directory, "templates");
|
|
16
|
+
}
|
|
17
|
+
export async function templateList(root) {
|
|
18
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
19
|
+
const names = entries
|
|
20
|
+
.filter((entry) => entry.isDirectory() && templateName.test(entry.name))
|
|
21
|
+
.map((entry) => entry.name)
|
|
22
|
+
.sort();
|
|
23
|
+
const templates = await Promise.all(names.map((name) => readTemplate(root, name).catch(() => undefined)));
|
|
24
|
+
return templates.flatMap((template) => template === undefined
|
|
25
|
+
? []
|
|
26
|
+
: [
|
|
27
|
+
{
|
|
28
|
+
name: template.name,
|
|
29
|
+
summary: String(template.description["summary"]),
|
|
30
|
+
points: template.points.length,
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
export async function readTemplate(root, name) {
|
|
35
|
+
if (!templateName.test(name))
|
|
36
|
+
throw new Error(`unknown-template: ${name}`);
|
|
37
|
+
const folder = join(root, name);
|
|
38
|
+
const text = await readFile(join(folder, "template.json"), "utf8").catch(() => {
|
|
39
|
+
throw new Error(`unknown-template: ${name}`);
|
|
40
|
+
});
|
|
41
|
+
const description = record(JSON.parse(text));
|
|
42
|
+
const points = description?.["points"];
|
|
43
|
+
if (description === undefined ||
|
|
44
|
+
typeof description["summary"] !== "string" ||
|
|
45
|
+
!Array.isArray(points))
|
|
46
|
+
throw new Error(`invalid-template: ${name}`);
|
|
47
|
+
return { name, description, points, files: await workflowFiles(folder) };
|
|
48
|
+
}
|
|
49
|
+
export async function exportedFunctions(folders) {
|
|
50
|
+
const names = new Set();
|
|
51
|
+
for (const folder of folders) {
|
|
52
|
+
const files = await readdir(folder).catch(() => []);
|
|
53
|
+
for (const file of files.filter((name) => /^[^.].*(?<!\.test|\.d)\.[jt]s$/.test(name))) {
|
|
54
|
+
const text = await readFile(join(folder, file), "utf8").catch(() => "");
|
|
55
|
+
for (const match of text.matchAll(/export\s+(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/g))
|
|
56
|
+
names.add(String(match[1]));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return names;
|
|
60
|
+
}
|
|
61
|
+
async function workflowFiles(folder) {
|
|
62
|
+
const entries = await readdir(folder, {
|
|
63
|
+
recursive: true,
|
|
64
|
+
withFileTypes: true,
|
|
65
|
+
});
|
|
66
|
+
return entries
|
|
67
|
+
.filter((entry) => entry.isFile())
|
|
68
|
+
.map((entry) => join(entry.parentPath, entry.name)
|
|
69
|
+
.slice(folder.length + 1)
|
|
70
|
+
.split("\\")
|
|
71
|
+
.join("/"))
|
|
72
|
+
.filter((file) => !descriptionFiles.includes(file))
|
|
73
|
+
.sort();
|
|
74
|
+
}
|
|
@@ -153,6 +153,7 @@ export async function retainWorkflowBundle(input) {
|
|
|
153
153
|
}
|
|
154
154
|
const temporary = await mkdtemp(join(input.directory, ".pending-"));
|
|
155
155
|
try {
|
|
156
|
+
await mkdir(join(temporary, "workflows"), { mode: 0o700 });
|
|
156
157
|
for (const { name, bytes } of files) {
|
|
157
158
|
const path = join(temporary, name);
|
|
158
159
|
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { protectedCall, helperCall, } from "./workflow-candidate-syntax.js";
|
|
2
|
+
import { protectedCall, commandWaitCall, helperCall, } from "./workflow-candidate-syntax.js";
|
|
3
3
|
import { propertyName } from "./workflow-candidate-fields.js";
|
|
4
4
|
import { referenceSymbol } from "./workflow-source-effects.js";
|
|
5
5
|
const zero = { knownCallCount: 0, knownUnitTotal: 0 };
|
|
@@ -218,7 +218,7 @@ function nodeTotals(node, input) {
|
|
|
218
218
|
if (local && ts.isFunctionLike(node))
|
|
219
219
|
return local;
|
|
220
220
|
let total = local ??
|
|
221
|
-
(protectedCall(node, input.symbols)
|
|
221
|
+
(protectedCall(node, input.symbols) && !commandWaitCall(node, input.symbols)
|
|
222
222
|
? { knownCallCount: 1, knownUnitTotal: units(node, input.symbols) }
|
|
223
223
|
: zero);
|
|
224
224
|
ts.forEachChild(node, (child) => {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export function workflowShape(source) {
|
|
3
|
+
const exported = source.statements.filter((node) => ts.isFunctionDeclaration(node) &&
|
|
4
|
+
(ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
|
|
5
|
+
const candidate = exported[0];
|
|
6
|
+
if (exported.length !== 1 ||
|
|
7
|
+
!candidate ||
|
|
8
|
+
!ts.isFunctionDeclaration(candidate))
|
|
9
|
+
return false;
|
|
10
|
+
if (!workflowFunction(candidate))
|
|
11
|
+
return false;
|
|
12
|
+
const directives = [];
|
|
13
|
+
function visit(node) {
|
|
14
|
+
if (ts.isStringLiteral(node) && node.text === "use workflow")
|
|
15
|
+
directives.push(node);
|
|
16
|
+
ts.forEachChild(node, visit);
|
|
17
|
+
}
|
|
18
|
+
visit(source);
|
|
19
|
+
return directives.length === 1;
|
|
20
|
+
}
|
|
21
|
+
function workflowFunction(candidate) {
|
|
22
|
+
if (!candidate.name || !candidate.body || candidate.asteriskToken)
|
|
23
|
+
return false;
|
|
24
|
+
const modifiers = ts.getModifiers(candidate) ?? [];
|
|
25
|
+
if (!modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ||
|
|
26
|
+
modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword))
|
|
27
|
+
return false;
|
|
28
|
+
const first = candidate.body.statements[0];
|
|
29
|
+
return first !== undefined && workflowDirective(first);
|
|
30
|
+
}
|
|
31
|
+
function workflowDirective(node) {
|
|
32
|
+
return (ts.isExpressionStatement(node) &&
|
|
33
|
+
ts.isStringLiteral(node.expression) &&
|
|
34
|
+
node.expression.text === "use workflow");
|
|
35
|
+
}
|
|
36
|
+
function unwrappedInitializer(node) {
|
|
37
|
+
if (ts.isParenthesizedExpression(node) ||
|
|
38
|
+
ts.isAsExpression(node) ||
|
|
39
|
+
ts.isSatisfiesExpression(node) ||
|
|
40
|
+
ts.isTypeAssertionExpression(node))
|
|
41
|
+
return node.expression;
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function passiveCompound(node) {
|
|
45
|
+
if (ts.isArrayLiteralExpression(node))
|
|
46
|
+
return node.elements.every((element) => ts.isOmittedExpression(element) || passiveInitializer(element));
|
|
47
|
+
if (ts.isObjectLiteralExpression(node))
|
|
48
|
+
return node.properties.every((property) => ts.isPropertyAssignment(property) &&
|
|
49
|
+
!ts.isComputedPropertyName(property.name) &&
|
|
50
|
+
passiveInitializer(property.initializer));
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
function passiveInitializer(node) {
|
|
54
|
+
if (!node)
|
|
55
|
+
return false;
|
|
56
|
+
if (ts.isLiteralExpression(node))
|
|
57
|
+
return true;
|
|
58
|
+
if ([
|
|
59
|
+
ts.SyntaxKind.TrueKeyword,
|
|
60
|
+
ts.SyntaxKind.FalseKeyword,
|
|
61
|
+
ts.SyntaxKind.NullKeyword,
|
|
62
|
+
].includes(node.kind))
|
|
63
|
+
return true;
|
|
64
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node))
|
|
65
|
+
return true;
|
|
66
|
+
if (ts.isPrefixUnaryExpression(node))
|
|
67
|
+
return ([ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken].includes(node.operator) && ts.isNumericLiteral(node.operand));
|
|
68
|
+
const inner = unwrappedInitializer(node);
|
|
69
|
+
return inner ? passiveInitializer(inner) : passiveCompound(node);
|
|
70
|
+
}
|
|
71
|
+
function passiveStatement(node, kind) {
|
|
72
|
+
if (ts.isVariableStatement(node))
|
|
73
|
+
return ((node.declarationList.flags & ts.NodeFlags.Const) !== 0 &&
|
|
74
|
+
node.declarationList.declarations.every((declaration) => (kind === "helper" ||
|
|
75
|
+
(ts.getCombinedModifierFlags(declaration) &
|
|
76
|
+
ts.ModifierFlags.Export) ===
|
|
77
|
+
0) &&
|
|
78
|
+
ts.isIdentifier(declaration.name) &&
|
|
79
|
+
passiveInitializer(declaration.initializer)));
|
|
80
|
+
if (ts.isExpressionStatement(node))
|
|
81
|
+
return ts.isStringLiteral(node.expression);
|
|
82
|
+
return (ts.isImportDeclaration(node) ||
|
|
83
|
+
ts.isTypeAliasDeclaration(node) ||
|
|
84
|
+
ts.isInterfaceDeclaration(node) ||
|
|
85
|
+
ts.isFunctionDeclaration(node) ||
|
|
86
|
+
ts.isEmptyStatement(node));
|
|
87
|
+
}
|
|
88
|
+
export function passiveCandidateModule(source, kind = "workflow") {
|
|
89
|
+
return source.statements.every((node) => passiveStatement(node, kind));
|
|
90
|
+
}
|