toolroll 0.5.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/LICENSE +21 -0
- package/README.md +1113 -0
- package/THIRD_PARTY_NOTICES.md +449 -0
- package/dist/accent-colors.d.ts +48 -0
- package/dist/accent-colors.js +122 -0
- package/dist/action-ledger.d.ts +26 -0
- package/dist/action-ledger.js +75 -0
- package/dist/agent-fence.d.ts +42 -0
- package/dist/agent-fence.js +183 -0
- package/dist/agentconfig.d.ts +216 -0
- package/dist/agentconfig.js +454 -0
- package/dist/api-tokens.d.ts +16 -0
- package/dist/api-tokens.js +27 -0
- package/dist/approval-policy.d.ts +69 -0
- package/dist/approval-policy.js +137 -0
- package/dist/approval-rules-ui.d.ts +25 -0
- package/dist/approval-rules-ui.js +39 -0
- package/dist/assignment-adapters.d.ts +180 -0
- package/dist/assignment-adapters.js +239 -0
- package/dist/assignment-brief.d.ts +71 -0
- package/dist/assignment-brief.js +129 -0
- package/dist/assignment-delivery.d.ts +56 -0
- package/dist/assignment-delivery.js +160 -0
- package/dist/assignment-presentation.d.ts +26 -0
- package/dist/assignment-presentation.js +80 -0
- package/dist/assignment-status.d.ts +62 -0
- package/dist/assignment-status.js +152 -0
- package/dist/assignment-ui.d.ts +71 -0
- package/dist/assignment-ui.js +103 -0
- package/dist/assignment.d.ts +222 -0
- package/dist/assignment.js +399 -0
- package/dist/attest.d.ts +56 -0
- package/dist/attest.js +153 -0
- package/dist/backend.d.ts +97 -0
- package/dist/backend.js +166 -0
- package/dist/backup-ui.d.ts +22 -0
- package/dist/backup-ui.js +59 -0
- package/dist/backup.d.ts +84 -0
- package/dist/backup.js +421 -0
- package/dist/beads.d.ts +34 -0
- package/dist/beads.js +135 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +22 -0
- package/dist/board.d.ts +150 -0
- package/dist/board.js +210 -0
- package/dist/boot-identity.d.ts +63 -0
- package/dist/boot-identity.js +99 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
- package/dist/browser/workspace.css +4 -0
- package/dist/browser/workspace.js +225 -0
- package/dist/browser-crew.d.ts +11 -0
- package/dist/browser-crew.js +50 -0
- package/dist/browser-shell.d.ts +8 -0
- package/dist/browser-shell.js +60 -0
- package/dist/browser-workspace.d.ts +780 -0
- package/dist/browser-workspace.js +47 -0
- package/dist/budget-alerts.d.ts +19 -0
- package/dist/budget-alerts.js +46 -0
- package/dist/builder.d.ts +370 -0
- package/dist/builder.js +3262 -0
- package/dist/capscan.d.ts +35 -0
- package/dist/capscan.js +113 -0
- package/dist/chat-acceptance.d.ts +71 -0
- package/dist/chat-acceptance.js +120 -0
- package/dist/chat-actions.d.ts +265 -0
- package/dist/chat-actions.js +1273 -0
- package/dist/chat-channel.d.ts +109 -0
- package/dist/chat-channel.js +652 -0
- package/dist/chat-continuity.d.ts +32 -0
- package/dist/chat-continuity.js +317 -0
- package/dist/chat-controls.d.ts +113 -0
- package/dist/chat-controls.js +53 -0
- package/dist/chat-delivery-state.d.ts +159 -0
- package/dist/chat-delivery-state.js +309 -0
- package/dist/chat-delivery.d.ts +38 -0
- package/dist/chat-delivery.js +693 -0
- package/dist/chat-display.d.ts +1 -0
- package/dist/chat-display.js +18 -0
- package/dist/chat-evidence.d.ts +123 -0
- package/dist/chat-evidence.js +207 -0
- package/dist/chat-flow.d.ts +44 -0
- package/dist/chat-flow.js +215 -0
- package/dist/chat-inbox.d.ts +57 -0
- package/dist/chat-inbox.js +123 -0
- package/dist/chat-polish.d.ts +16 -0
- package/dist/chat-polish.js +135 -0
- package/dist/chat-review.d.ts +46 -0
- package/dist/chat-review.js +98 -0
- package/dist/chat-rooms.d.ts +123 -0
- package/dist/chat-rooms.js +216 -0
- package/dist/chat-task-actions.d.ts +44 -0
- package/dist/chat-task-actions.js +79 -0
- package/dist/check-progress.d.ts +51 -0
- package/dist/check-progress.js +290 -0
- package/dist/child-database.d.ts +12 -0
- package/dist/child-database.js +30 -0
- package/dist/claim.d.ts +688 -0
- package/dist/claim.js +1740 -0
- package/dist/cli.d.ts +137 -0
- package/dist/cli.js +1461 -0
- package/dist/codex-limits.d.ts +15 -0
- package/dist/codex-limits.js +145 -0
- package/dist/coding-context.d.ts +55 -0
- package/dist/coding-context.js +92 -0
- package/dist/coding-handoff.d.ts +57 -0
- package/dist/coding-handoff.js +277 -0
- package/dist/coding-provider.d.ts +72 -0
- package/dist/coding-provider.js +424 -0
- package/dist/coding-shipping-ui.d.ts +4 -0
- package/dist/coding-shipping-ui.js +12 -0
- package/dist/coding-types.d.ts +62 -0
- package/dist/coding-types.js +1 -0
- package/dist/coding-ui.d.ts +17 -0
- package/dist/coding-ui.js +364 -0
- package/dist/coding-update.d.ts +15 -0
- package/dist/coding-update.js +149 -0
- package/dist/coding-workspace.d.ts +124 -0
- package/dist/coding-workspace.js +768 -0
- package/dist/container-state.d.ts +5 -0
- package/dist/container-state.js +62 -0
- package/dist/containment.d.ts +197 -0
- package/dist/containment.js +559 -0
- package/dist/contest.d.ts +318 -0
- package/dist/contest.js +753 -0
- package/dist/control-setup.d.ts +35 -0
- package/dist/control-setup.js +40 -0
- package/dist/control-ui.d.ts +29 -0
- package/dist/control-ui.js +53 -0
- package/dist/controller-service.d.ts +1 -0
- package/dist/controller-service.js +19 -0
- package/dist/controller-supervisor.d.ts +30 -0
- package/dist/controller-supervisor.js +104 -0
- package/dist/converse.d.ts +304 -0
- package/dist/converse.js +849 -0
- package/dist/coordinator-proposals.d.ts +29 -0
- package/dist/coordinator-proposals.js +137 -0
- package/dist/coordinator.d.ts +186 -0
- package/dist/coordinator.js +447 -0
- package/dist/credentials-ui.d.ts +25 -0
- package/dist/credentials-ui.js +48 -0
- package/dist/daemon.d.ts +209 -0
- package/dist/daemon.js +604 -0
- package/dist/decision.d.ts +120 -0
- package/dist/decision.js +388 -0
- package/dist/demo.d.ts +55 -0
- package/dist/demo.js +992 -0
- package/dist/desktop-access.d.ts +28 -0
- package/dist/desktop-access.js +88 -0
- package/dist/desktop-bundle.d.ts +16 -0
- package/dist/desktop-bundle.js +74 -0
- package/dist/desktop-host.d.ts +54 -0
- package/dist/desktop-host.js +508 -0
- package/dist/desktop-update-gate.d.ts +12 -0
- package/dist/desktop-update-gate.js +91 -0
- package/dist/desktop-update-recovery.d.ts +14 -0
- package/dist/desktop-update-recovery.js +243 -0
- package/dist/desktop-update.d.ts +110 -0
- package/dist/desktop-update.js +740 -0
- package/dist/discord-api.d.ts +20 -0
- package/dist/discord-api.js +138 -0
- package/dist/discord-chat.d.ts +16 -0
- package/dist/discord-chat.js +381 -0
- package/dist/discord-settings.d.ts +7 -0
- package/dist/discord-settings.js +75 -0
- package/dist/discord.d.ts +10 -0
- package/dist/discord.js +169 -0
- package/dist/discover.d.ts +75 -0
- package/dist/discover.js +150 -0
- package/dist/dispatch.d.ts +100 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispose.d.ts +161 -0
- package/dist/dispose.js +803 -0
- package/dist/email-settings.d.ts +34 -0
- package/dist/email-settings.js +50 -0
- package/dist/envelope.d.ts +47 -0
- package/dist/envelope.js +105 -0
- package/dist/evidence-pack.d.ts +185 -0
- package/dist/evidence-pack.js +258 -0
- package/dist/evidence.d.ts +345 -0
- package/dist/evidence.js +782 -0
- package/dist/exec.d.ts +285 -0
- package/dist/exec.js +1841 -0
- package/dist/exhaustion.d.ts +85 -0
- package/dist/exhaustion.js +141 -0
- package/dist/export-ui.d.ts +4 -0
- package/dist/export-ui.js +17 -0
- package/dist/export.d.ts +48 -0
- package/dist/export.js +305 -0
- package/dist/flow-actions.d.ts +77 -0
- package/dist/flow-actions.js +203 -0
- package/dist/flow-code.d.ts +50 -0
- package/dist/flow-code.js +159 -0
- package/dist/flow-draft.d.ts +38 -0
- package/dist/flow-draft.js +80 -0
- package/dist/flow-engine.d.ts +66 -0
- package/dist/flow-engine.js +416 -0
- package/dist/flow-insights.d.ts +95 -0
- package/dist/flow-insights.js +149 -0
- package/dist/flow-live.d.ts +24 -0
- package/dist/flow-live.js +150 -0
- package/dist/flow-people.d.ts +24 -0
- package/dist/flow-people.js +91 -0
- package/dist/flow-replies.d.ts +48 -0
- package/dist/flow-replies.js +113 -0
- package/dist/flow-scripts.d.ts +38 -0
- package/dist/flow-scripts.js +73 -0
- package/dist/flow-secrets.d.ts +13 -0
- package/dist/flow-secrets.js +45 -0
- package/dist/flow-sort.d.ts +82 -0
- package/dist/flow-sort.js +153 -0
- package/dist/flow-steps.d.ts +40 -0
- package/dist/flow-steps.js +408 -0
- package/dist/flow-triggers.d.ts +244 -0
- package/dist/flow-triggers.js +959 -0
- package/dist/flows-ui.d.ts +20 -0
- package/dist/flows-ui.js +175 -0
- package/dist/flows.d.ts +229 -0
- package/dist/flows.js +968 -0
- package/dist/fonts.d.ts +19 -0
- package/dist/fonts.js +19 -0
- package/dist/gaps.d.ts +35 -0
- package/dist/gaps.js +101 -0
- package/dist/gate-failure.d.ts +40 -0
- package/dist/gate-failure.js +66 -0
- package/dist/git.d.ts +54 -0
- package/dist/git.js +94 -0
- package/dist/google-mail.d.ts +58 -0
- package/dist/google-mail.js +151 -0
- package/dist/grant.d.ts +136 -0
- package/dist/grant.js +238 -0
- package/dist/graph.d.ts +164 -0
- package/dist/graph.js +383 -0
- package/dist/guides.d.ts +22 -0
- package/dist/guides.js +363 -0
- package/dist/held.d.ts +150 -0
- package/dist/held.js +799 -0
- package/dist/invoke.d.ts +112 -0
- package/dist/invoke.js +780 -0
- package/dist/issues.d.ts +41 -0
- package/dist/issues.js +131 -0
- package/dist/job-object-helper.ps1 +252 -0
- package/dist/jsonl-discriminants.d.ts +11 -0
- package/dist/jsonl-discriminants.js +93 -0
- package/dist/keys.d.ts +104 -0
- package/dist/keys.js +229 -0
- package/dist/kits-ui.d.ts +17 -0
- package/dist/kits-ui.js +46 -0
- package/dist/kits.d.ts +86 -0
- package/dist/kits.js +215 -0
- package/dist/knowledge-cli.d.ts +101 -0
- package/dist/knowledge-cli.js +92 -0
- package/dist/knowledge-ui.d.ts +24 -0
- package/dist/knowledge-ui.js +40 -0
- package/dist/lead-context.d.ts +8 -0
- package/dist/lead-context.js +56 -0
- package/dist/lead-follow.d.ts +22 -0
- package/dist/lead-follow.js +167 -0
- package/dist/lead-status.d.ts +75 -0
- package/dist/lead-status.js +285 -0
- package/dist/ledger-chain.d.ts +46 -0
- package/dist/ledger-chain.js +187 -0
- package/dist/ledger-csv.d.ts +4 -0
- package/dist/ledger-csv.js +11 -0
- package/dist/ledger-view.d.ts +24 -0
- package/dist/ledger-view.js +58 -0
- package/dist/limits-ui.d.ts +26 -0
- package/dist/limits-ui.js +67 -0
- package/dist/link.d.ts +72 -0
- package/dist/link.js +217 -0
- package/dist/live.d.ts +96 -0
- package/dist/live.js +366 -0
- package/dist/liveness.d.ts +30 -0
- package/dist/liveness.js +42 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +25 -0
- package/dist/mailbox.d.ts +57 -0
- package/dist/mailbox.js +150 -0
- package/dist/maintenance.d.ts +11 -0
- package/dist/maintenance.js +35 -0
- package/dist/mate-cli.d.ts +52 -0
- package/dist/mate-cli.js +345 -0
- package/dist/mate-contract.d.ts +10 -0
- package/dist/mate-contract.js +30 -0
- package/dist/mate-doors.d.ts +69 -0
- package/dist/mate-doors.js +548 -0
- package/dist/mate-progress.d.ts +29 -0
- package/dist/mate-progress.js +121 -0
- package/dist/mate-tools.d.ts +94 -0
- package/dist/mate-tools.js +1878 -0
- package/dist/mate.d.ts +92 -0
- package/dist/mate.js +435 -0
- package/dist/mcp-connect.d.ts +103 -0
- package/dist/mcp-connect.js +252 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.js +651 -0
- package/dist/memory-cli.d.ts +466 -0
- package/dist/memory-cli.js +111 -0
- package/dist/memory-pass.d.ts +160 -0
- package/dist/memory-pass.js +409 -0
- package/dist/metrics.d.ts +3 -0
- package/dist/metrics.js +56 -0
- package/dist/mobile-viewport.d.ts +3 -0
- package/dist/mobile-viewport.js +37 -0
- package/dist/model-catalog.d.ts +118 -0
- package/dist/model-catalog.js +376 -0
- package/dist/models-cli.d.ts +102 -0
- package/dist/models-cli.js +66 -0
- package/dist/models-ui.d.ts +34 -0
- package/dist/models-ui.js +68 -0
- package/dist/modes.d.ts +80 -0
- package/dist/modes.js +182 -0
- package/dist/monitoring-settings.d.ts +44 -0
- package/dist/monitoring-settings.js +173 -0
- package/dist/monitoring-ui.d.ts +20 -0
- package/dist/monitoring-ui.js +51 -0
- package/dist/monitoring.d.ts +34 -0
- package/dist/monitoring.js +221 -0
- package/dist/names.d.ts +67 -0
- package/dist/names.js +102 -0
- package/dist/observations.d.ts +40 -0
- package/dist/observations.js +211 -0
- package/dist/oidc.d.ts +71 -0
- package/dist/oidc.js +182 -0
- package/dist/onboard.d.ts +108 -0
- package/dist/onboard.js +325 -0
- package/dist/openrouter-models.d.ts +30 -0
- package/dist/openrouter-models.js +120 -0
- package/dist/operate.d.ts +118 -0
- package/dist/operate.js +11321 -0
- package/dist/peek-cli.d.ts +97 -0
- package/dist/peek-cli.js +215 -0
- package/dist/peek.d.ts +134 -0
- package/dist/peek.js +578 -0
- package/dist/phase-routing.d.ts +307 -0
- package/dist/phase-routing.js +658 -0
- package/dist/plan-auto.d.ts +14 -0
- package/dist/plan-auto.js +92 -0
- package/dist/plan.d.ts +186 -0
- package/dist/plan.js +401 -0
- package/dist/planner-source.d.ts +251 -0
- package/dist/planner-source.js +460 -0
- package/dist/planner.d.ts +75 -0
- package/dist/planner.js +992 -0
- package/dist/policy-ui.d.ts +20 -0
- package/dist/policy-ui.js +41 -0
- package/dist/policy.d.ts +111 -0
- package/dist/policy.js +231 -0
- package/dist/prepared-evidence.d.ts +16 -0
- package/dist/prepared-evidence.js +96 -0
- package/dist/pricing.d.ts +45 -0
- package/dist/pricing.js +77 -0
- package/dist/principal.d.ts +42 -0
- package/dist/principal.js +82 -0
- package/dist/probe.d.ts +46 -0
- package/dist/probe.js +79 -0
- package/dist/process-custody.d.ts +17 -0
- package/dist/process-custody.js +91 -0
- package/dist/process-liveness.d.ts +10 -0
- package/dist/process-liveness.js +81 -0
- package/dist/process-recovery-anchor.d.ts +75 -0
- package/dist/process-recovery-anchor.js +188 -0
- package/dist/process-recovery-coalition.d.ts +25 -0
- package/dist/process-recovery-coalition.js +105 -0
- package/dist/process-recovery-eligibility.d.ts +178 -0
- package/dist/process-recovery-eligibility.js +170 -0
- package/dist/process-recovery-native.d.ts +172 -0
- package/dist/process-recovery-native.js +592 -0
- package/dist/process-recovery-provenance.d.ts +163 -0
- package/dist/process-recovery-provenance.js +292 -0
- package/dist/process-recovery-services.d.ts +56 -0
- package/dist/process-recovery-services.js +191 -0
- package/dist/process-recovery-settlement.d.ts +32 -0
- package/dist/process-recovery-settlement.js +132 -0
- package/dist/process-recovery.d.ts +41 -0
- package/dist/process-recovery.js +91 -0
- package/dist/process-tree.d.ts +41 -0
- package/dist/process-tree.js +264 -0
- package/dist/project-access.d.ts +14 -0
- package/dist/project-access.js +34 -0
- package/dist/project-cli.d.ts +18 -0
- package/dist/project-cli.js +104 -0
- package/dist/project-delete-ui.d.ts +22 -0
- package/dist/project-delete-ui.js +48 -0
- package/dist/project-delete.d.ts +55 -0
- package/dist/project-delete.js +413 -0
- package/dist/project-knowledge.d.ts +111 -0
- package/dist/project-knowledge.js +241 -0
- package/dist/project-learning.d.ts +100 -0
- package/dist/project-learning.js +438 -0
- package/dist/project-memory.d.ts +81 -0
- package/dist/project-memory.js +264 -0
- package/dist/project-skills.d.ts +152 -0
- package/dist/project-skills.js +660 -0
- package/dist/project-tools.d.ts +219 -0
- package/dist/project-tools.js +796 -0
- package/dist/project.d.ts +61 -0
- package/dist/project.js +125 -0
- package/dist/prompt.d.ts +22 -0
- package/dist/prompt.js +72 -0
- package/dist/proof.d.ts +513 -0
- package/dist/proof.js +1140 -0
- package/dist/proposal.d.ts +82 -0
- package/dist/proposal.js +210 -0
- package/dist/provider-connection.d.ts +22 -0
- package/dist/provider-connection.js +98 -0
- package/dist/provider-limits.d.ts +35 -0
- package/dist/provider-limits.js +95 -0
- package/dist/provider.d.ts +279 -0
- package/dist/provider.js +935 -0
- package/dist/publish.d.ts +107 -0
- package/dist/publish.js +650 -0
- package/dist/pulls.d.ts +118 -0
- package/dist/pulls.js +240 -0
- package/dist/push.d.ts +95 -0
- package/dist/push.js +353 -0
- package/dist/quality.d.ts +8 -0
- package/dist/quality.js +6 -0
- package/dist/recipe-ui.d.ts +18 -0
- package/dist/recipe-ui.js +152 -0
- package/dist/recipes.d.ts +81 -0
- package/dist/recipes.js +332 -0
- package/dist/remote.d.ts +67 -0
- package/dist/remote.js +122 -0
- package/dist/render.d.ts +64 -0
- package/dist/render.js +587 -0
- package/dist/report-summary.d.ts +13 -0
- package/dist/report-summary.js +15 -0
- package/dist/repos.d.ts +65 -0
- package/dist/repos.js +277 -0
- package/dist/repository-context-ui.d.ts +2 -0
- package/dist/repository-context-ui.js +10 -0
- package/dist/repository-context.d.ts +67 -0
- package/dist/repository-context.js +376 -0
- package/dist/restart-certification.d.ts +134 -0
- package/dist/restart-certification.js +226 -0
- package/dist/result-actions.d.ts +28 -0
- package/dist/result-actions.js +270 -0
- package/dist/result-completion.d.ts +18 -0
- package/dist/result-completion.js +66 -0
- package/dist/result-review.d.ts +213 -0
- package/dist/result-review.js +430 -0
- package/dist/retention-ui.d.ts +12 -0
- package/dist/retention-ui.js +39 -0
- package/dist/retention.d.ts +72 -0
- package/dist/retention.js +288 -0
- package/dist/review-context.d.ts +243 -0
- package/dist/review-context.js +1043 -0
- package/dist/review-evidence.d.ts +33 -0
- package/dist/review-evidence.js +56 -0
- package/dist/reviewer.d.ts +97 -0
- package/dist/reviewer.js +202 -0
- package/dist/routine.d.ts +228 -0
- package/dist/routine.js +764 -0
- package/dist/runner.d.ts +266 -0
- package/dist/runner.js +399 -0
- package/dist/scan.d.ts +32 -0
- package/dist/scan.js +92 -0
- package/dist/scope.d.ts +695 -0
- package/dist/scope.js +1176 -0
- package/dist/scout-report.d.ts +42 -0
- package/dist/scout-report.js +108 -0
- package/dist/scout.d.ts +69 -0
- package/dist/scout.js +351 -0
- package/dist/serve.d.ts +313 -0
- package/dist/serve.js +21776 -0
- package/dist/session-brief.d.ts +8 -0
- package/dist/session-brief.js +24 -0
- package/dist/session-cli.d.ts +13 -0
- package/dist/session-cli.js +284 -0
- package/dist/session-contract.d.ts +183 -0
- package/dist/session-contract.js +122 -0
- package/dist/session-http.d.ts +19 -0
- package/dist/session-http.js +154 -0
- package/dist/session-server.d.ts +12 -0
- package/dist/session-server.js +37 -0
- package/dist/session-service.d.ts +22 -0
- package/dist/session-service.js +157 -0
- package/dist/setup-guide.d.ts +39 -0
- package/dist/setup-guide.js +93 -0
- package/dist/sign-in-guard.d.ts +45 -0
- package/dist/sign-in-guard.js +74 -0
- package/dist/skills-ui.d.ts +11 -0
- package/dist/skills-ui.js +58 -0
- package/dist/skills.d.ts +97 -0
- package/dist/skills.js +276 -0
- package/dist/slack-api.d.ts +55 -0
- package/dist/slack-api.js +229 -0
- package/dist/slack-chat.d.ts +28 -0
- package/dist/slack-chat.js +413 -0
- package/dist/slack-settings.d.ts +7 -0
- package/dist/slack-settings.js +75 -0
- package/dist/slack-state.d.ts +13 -0
- package/dist/slack-state.js +9 -0
- package/dist/slack.d.ts +13 -0
- package/dist/slack.js +167 -0
- package/dist/spend-ui.d.ts +28 -0
- package/dist/spend-ui.js +95 -0
- package/dist/spend.d.ts +142 -0
- package/dist/spend.js +313 -0
- package/dist/sqlite-runtime.d.ts +3 -0
- package/dist/sqlite-runtime.js +6 -0
- package/dist/sso-settings.d.ts +33 -0
- package/dist/sso-settings.js +103 -0
- package/dist/sso-ui.d.ts +22 -0
- package/dist/sso-ui.js +39 -0
- package/dist/storage.d.ts +15 -0
- package/dist/storage.js +84 -0
- package/dist/store.d.ts +7754 -0
- package/dist/store.js +23279 -0
- package/dist/structured-output.d.ts +58 -0
- package/dist/structured-output.js +103 -0
- package/dist/style-asset.d.ts +7 -0
- package/dist/style-asset.js +41 -0
- package/dist/subscription-chat.d.ts +46 -0
- package/dist/subscription-chat.js +275 -0
- package/dist/summary.d.ts +33 -0
- package/dist/summary.js +71 -0
- package/dist/supervisor.mjs +297 -0
- package/dist/surface.d.ts +63 -0
- package/dist/surface.js +352 -0
- package/dist/sync.d.ts +94 -0
- package/dist/sync.js +279 -0
- package/dist/task-composer.d.ts +13 -0
- package/dist/task-composer.js +52 -0
- package/dist/task-control.d.ts +165 -0
- package/dist/task-control.js +214 -0
- package/dist/task-outcome-cli.d.ts +10 -0
- package/dist/task-outcome-cli.js +92 -0
- package/dist/task-text.d.ts +30 -0
- package/dist/task-text.js +41 -0
- package/dist/team-cli.d.ts +15 -0
- package/dist/team-cli.js +618 -0
- package/dist/team-contract.d.ts +92 -0
- package/dist/team-contract.js +1 -0
- package/dist/team-http.d.ts +17 -0
- package/dist/team-http.js +144 -0
- package/dist/team-leads.d.ts +81 -0
- package/dist/team-leads.js +565 -0
- package/dist/team-runtime.d.ts +27 -0
- package/dist/team-runtime.js +264 -0
- package/dist/team-ui.d.ts +3 -0
- package/dist/team-ui.js +9 -0
- package/dist/team-updates.d.ts +8 -0
- package/dist/team-updates.js +57 -0
- package/dist/teammate-admin.d.ts +53 -0
- package/dist/teammate-admin.js +111 -0
- package/dist/teammate-desk.d.ts +60 -0
- package/dist/teammate-desk.js +154 -0
- package/dist/teammate-memory.d.ts +64 -0
- package/dist/teammate-memory.js +196 -0
- package/dist/teammate-question.d.ts +59 -0
- package/dist/teammate-question.js +175 -0
- package/dist/teammate-tools.d.ts +108 -0
- package/dist/teammate-tools.js +271 -0
- package/dist/teammate-week.d.ts +68 -0
- package/dist/teammate-week.js +132 -0
- package/dist/teammate-work.d.ts +76 -0
- package/dist/teammate-work.js +280 -0
- package/dist/teammates-ui.d.ts +20 -0
- package/dist/teammates-ui.js +166 -0
- package/dist/teammates.d.ts +180 -0
- package/dist/teammates.js +253 -0
- package/dist/teams-api.d.ts +33 -0
- package/dist/teams-api.js +204 -0
- package/dist/teams-chat.d.ts +26 -0
- package/dist/teams-chat.js +215 -0
- package/dist/teams-settings.d.ts +12 -0
- package/dist/teams-settings.js +57 -0
- package/dist/teams.d.ts +21 -0
- package/dist/teams.js +124 -0
- package/dist/telegram-flow.d.ts +49 -0
- package/dist/telegram-flow.js +110 -0
- package/dist/telegram-mate.d.ts +137 -0
- package/dist/telegram-mate.js +707 -0
- package/dist/telegram-progress.d.ts +14 -0
- package/dist/telegram-progress.js +129 -0
- package/dist/telegram-settings.d.ts +9 -0
- package/dist/telegram-settings.js +36 -0
- package/dist/telegram-status.d.ts +75 -0
- package/dist/telegram-status.js +265 -0
- package/dist/telegram-team.d.ts +85 -0
- package/dist/telegram-team.js +359 -0
- package/dist/telegram.d.ts +230 -0
- package/dist/telegram.js +1931 -0
- package/dist/templates.d.ts +65 -0
- package/dist/templates.js +118 -0
- package/dist/tool-launcher.d.ts +1 -0
- package/dist/tool-launcher.js +101 -0
- package/dist/tools-ui.d.ts +30 -0
- package/dist/tools-ui.js +40 -0
- package/dist/transitions-recipes.d.ts +1 -0
- package/dist/transitions-recipes.js +203 -0
- package/dist/tree-proof.d.ts +34 -0
- package/dist/tree-proof.js +58 -0
- package/dist/verification-evidence.d.ts +44 -0
- package/dist/verification-evidence.js +238 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/dist/webhooks.d.ts +91 -0
- package/dist/webhooks.js +283 -0
- package/dist/work-index.d.ts +68 -0
- package/dist/work-index.js +384 -0
- package/dist/work-summary.d.ts +57 -0
- package/dist/work-summary.js +116 -0
- package/dist/workspace-motion.d.ts +4 -0
- package/dist/workspace-motion.js +133 -0
- package/dist/workspace-revision.d.ts +19 -0
- package/dist/workspace-revision.js +92 -0
- package/dist/workspace-ui.d.ts +204 -0
- package/dist/workspace-ui.js +411 -0
- package/dist/worktree-notices.d.ts +13 -0
- package/dist/worktree-notices.js +17 -0
- package/dist/worktree.d.ts +249 -0
- package/dist/worktree.js +720 -0
- package/package.json +122 -0
- package/scripts/canary-assertions.mjs +42 -0
- package/scripts/crash-canary.mjs +253 -0
- package/scripts/fixtures/crash-process.mjs +74 -0
- package/scripts/fixtures/pilot-scenarios.mjs +85 -0
- package/scripts/fixtures/restart-service.mjs +27 -0
- package/scripts/launchd-certification.mjs +143 -0
- package/scripts/pilot.mjs +121 -0
- package/scripts/proof-preflight.mjs +140 -0
- package/scripts/provider-canary.mjs +379 -0
- package/scripts/recovery-canary.mjs +135 -0
- package/scripts/restart-certification.mjs +98 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Store } from "./store.js";
|
|
2
|
+
import { type AcceptanceCriterion } from "./scope.js";
|
|
3
|
+
export declare const RECIPE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS workflow_recipe (\n id TEXT NOT NULL, revision INTEGER NOT NULL CHECK(revision > 0),\n repo TEXT NOT NULL, document TEXT NOT NULL, digest TEXT NOT NULL,\n author TEXT NOT NULL, created_at TEXT NOT NULL,\n PRIMARY KEY(id, revision)\n);\nCREATE INDEX IF NOT EXISTS workflow_recipe_project ON workflow_recipe(repo, id, revision DESC);\nCREATE TRIGGER IF NOT EXISTS workflow_recipe_no_update BEFORE UPDATE ON workflow_recipe\nBEGIN SELECT RAISE(ABORT, 'recipe revisions are immutable'); END;\nCREATE TRIGGER IF NOT EXISTS workflow_recipe_no_delete BEFORE DELETE ON workflow_recipe\nBEGIN SELECT RAISE(ABORT, 'recipe revisions are immutable'); END;\nCREATE TABLE IF NOT EXISTS workflow_preview (\n token TEXT PRIMARY KEY, actor TEXT NOT NULL, repo TEXT NOT NULL,\n document TEXT NOT NULL, digest TEXT NOT NULL, source TEXT NOT NULL,\n created_at TEXT NOT NULL, expires_at TEXT NOT NULL,\n task_id TEXT REFERENCES task(id), routine_id INTEGER REFERENCES routine(id),\n saved_id TEXT, saved_revision INTEGER,\n CHECK(task_id IS NULL OR routine_id IS NULL),\n FOREIGN KEY(saved_id, saved_revision) REFERENCES workflow_recipe(id, revision)\n);\nCREATE INDEX IF NOT EXISTS workflow_preview_project ON workflow_preview(repo, created_at DESC);\nCREATE INDEX IF NOT EXISTS workflow_preview_source ON workflow_preview(repo, source, created_at DESC)\nWHERE task_id IS NOT NULL OR routine_id IS NOT NULL;\n";
|
|
4
|
+
type RecipeFields = {
|
|
5
|
+
format: "standing-orders-recipe";
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
goal: string;
|
|
9
|
+
outOfScope: string | null;
|
|
10
|
+
touches: string[];
|
|
11
|
+
acceptance: AcceptanceCriterion[];
|
|
12
|
+
planning: "auto" | "required" | "skip";
|
|
13
|
+
deliverable: "branch" | "report";
|
|
14
|
+
schedule: string | null;
|
|
15
|
+
costCeilingUsd: number | null;
|
|
16
|
+
};
|
|
17
|
+
export type RecipeInput = {
|
|
18
|
+
key: string;
|
|
19
|
+
label: string;
|
|
20
|
+
defaultValue: string | null;
|
|
21
|
+
};
|
|
22
|
+
export type RecipeDocument = RecipeFields & ({
|
|
23
|
+
version: 1;
|
|
24
|
+
inputs?: never;
|
|
25
|
+
} | {
|
|
26
|
+
version: 2;
|
|
27
|
+
inputs: RecipeInput[];
|
|
28
|
+
});
|
|
29
|
+
export declare const RECIPE_INPUT_LIMIT = 8;
|
|
30
|
+
export type RecipeAnswers = ReadonlyMap<string, string>;
|
|
31
|
+
export type Recipe = {
|
|
32
|
+
id: string;
|
|
33
|
+
revision: number;
|
|
34
|
+
repo: string | null;
|
|
35
|
+
document: RecipeDocument;
|
|
36
|
+
digest: string;
|
|
37
|
+
author: string;
|
|
38
|
+
};
|
|
39
|
+
export type WorkflowPreview = {
|
|
40
|
+
token: string;
|
|
41
|
+
actor: string;
|
|
42
|
+
repo: string;
|
|
43
|
+
document: RecipeDocument;
|
|
44
|
+
digest: string;
|
|
45
|
+
source: string;
|
|
46
|
+
expiresAt: string;
|
|
47
|
+
taskId: string | null;
|
|
48
|
+
routineId: number | null;
|
|
49
|
+
savedId: string | null;
|
|
50
|
+
savedRevision: number | null;
|
|
51
|
+
};
|
|
52
|
+
export declare class RecipeError extends Error {
|
|
53
|
+
readonly status: number;
|
|
54
|
+
constructor(message: string, status?: number);
|
|
55
|
+
}
|
|
56
|
+
/** Reject unknown fields, including approval/provider settings, on import. */
|
|
57
|
+
export declare function parseRecipe(input: unknown): RecipeDocument;
|
|
58
|
+
export declare function importRecipe(text: string): RecipeDocument;
|
|
59
|
+
export declare const recipeDigest: (document: RecipeDocument) => string;
|
|
60
|
+
export declare const exportRecipe: (document: RecipeDocument) => string;
|
|
61
|
+
export declare function resolveRecipe(document: RecipeDocument, answers: RecipeAnswers): RecipeDocument & {
|
|
62
|
+
version: 1;
|
|
63
|
+
};
|
|
64
|
+
/** Only the stored definition supplies scope; form answers cannot replace it. */
|
|
65
|
+
export declare function prepareRecipeRun(store: Store, actor: string, repo: string, id: string, revision: number, answers: RecipeAnswers, now: Date): WorkflowPreview;
|
|
66
|
+
export declare function starterRecipes(): Recipe[];
|
|
67
|
+
export declare function savedRecipes(store: Store, actor: string, repo: string): Recipe[];
|
|
68
|
+
export declare function findRecipe(store: Store, actor: string, repo: string | null, id: string, revision?: number): Recipe | null;
|
|
69
|
+
export declare function createWorkflowPreview(store: Store, actor: string, repo: string, document: RecipeDocument, source: string, now: Date): WorkflowPreview;
|
|
70
|
+
export declare function workflowPreview(store: Store, actor: string, repo: string, token: string): WorkflowPreview | null;
|
|
71
|
+
export declare function launchWorkflow(store: Store, actor: string, repo: string, token: string, now: Date, operatorFiling: boolean): {
|
|
72
|
+
taskId: string | null;
|
|
73
|
+
routineId: number | null;
|
|
74
|
+
};
|
|
75
|
+
export declare function saveWorkflowRecipe(store: Store, actor: string, repo: string, token: string, now: Date): Recipe;
|
|
76
|
+
export declare function workflowSteps(document: RecipeDocument): {
|
|
77
|
+
title: string;
|
|
78
|
+
detail: string;
|
|
79
|
+
}[];
|
|
80
|
+
export declare function recipeScheduleWords(document: RecipeDocument, now: Date): string;
|
|
81
|
+
export {};
|
package/dist/recipes.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/** Reusable work definitions. Recipes carry scope, never credentials or grants.
|
|
2
|
+
* A preview freezes the exact copy to file; its durable receipt makes a retry
|
|
3
|
+
* after a lost response return the same task/routine, even after a restart. */
|
|
4
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
+
import { TEMPLATES } from "./templates.js";
|
|
6
|
+
import { hasDisguisedText, hasForbiddenControls } from "./decision.js";
|
|
7
|
+
import { parseAcceptanceCriteria } from "./scope.js";
|
|
8
|
+
import { validateTaskText, fileTaskProposal, fileRoutineProposal } from "./proposal.js";
|
|
9
|
+
import { parseSchedule, firstFireAt, describeSchedule, validateRoutineTerms } from "./routine.js";
|
|
10
|
+
import { applyModeToNewFiling } from "./plan-auto.js";
|
|
11
|
+
import { canonicalProject } from "./project.js";
|
|
12
|
+
import { resolve } from "node:path";
|
|
13
|
+
export const RECIPE_SCHEMA = `
|
|
14
|
+
CREATE TABLE IF NOT EXISTS workflow_recipe (
|
|
15
|
+
id TEXT NOT NULL, revision INTEGER NOT NULL CHECK(revision > 0),
|
|
16
|
+
repo TEXT NOT NULL, document TEXT NOT NULL, digest TEXT NOT NULL,
|
|
17
|
+
author TEXT NOT NULL, created_at TEXT NOT NULL,
|
|
18
|
+
PRIMARY KEY(id, revision)
|
|
19
|
+
);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS workflow_recipe_project ON workflow_recipe(repo, id, revision DESC);
|
|
21
|
+
CREATE TRIGGER IF NOT EXISTS workflow_recipe_no_update BEFORE UPDATE ON workflow_recipe
|
|
22
|
+
BEGIN SELECT RAISE(ABORT, 'recipe revisions are immutable'); END;
|
|
23
|
+
CREATE TRIGGER IF NOT EXISTS workflow_recipe_no_delete BEFORE DELETE ON workflow_recipe
|
|
24
|
+
BEGIN SELECT RAISE(ABORT, 'recipe revisions are immutable'); END;
|
|
25
|
+
CREATE TABLE IF NOT EXISTS workflow_preview (
|
|
26
|
+
token TEXT PRIMARY KEY, actor TEXT NOT NULL, repo TEXT NOT NULL,
|
|
27
|
+
document TEXT NOT NULL, digest TEXT NOT NULL, source TEXT NOT NULL,
|
|
28
|
+
created_at TEXT NOT NULL, expires_at TEXT NOT NULL,
|
|
29
|
+
task_id TEXT REFERENCES task(id), routine_id INTEGER REFERENCES routine(id),
|
|
30
|
+
saved_id TEXT, saved_revision INTEGER,
|
|
31
|
+
CHECK(task_id IS NULL OR routine_id IS NULL),
|
|
32
|
+
FOREIGN KEY(saved_id, saved_revision) REFERENCES workflow_recipe(id, revision)
|
|
33
|
+
);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS workflow_preview_project ON workflow_preview(repo, created_at DESC);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS workflow_preview_source ON workflow_preview(repo, source, created_at DESC)
|
|
36
|
+
WHERE task_id IS NOT NULL OR routine_id IS NOT NULL;
|
|
37
|
+
`;
|
|
38
|
+
export const RECIPE_INPUT_LIMIT = 8;
|
|
39
|
+
export class RecipeError extends Error {
|
|
40
|
+
status;
|
|
41
|
+
constructor(message, status = 400) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.status = status;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const keys = ["format", "version", "name", "description", "goal", "outOfScope", "touches", "acceptance", "planning", "deliverable", "schedule", "costCeilingUsd"];
|
|
47
|
+
const honest = (text) => !hasForbiddenControls(text) && !hasDisguisedText(text);
|
|
48
|
+
const recipeProject = (repo) => canonicalProject(repo) ?? resolve(repo);
|
|
49
|
+
/** Reject unknown fields, including approval/provider settings, on import. */
|
|
50
|
+
export function parseRecipe(input) {
|
|
51
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
52
|
+
throw new RecipeError("Choose a recipe document.");
|
|
53
|
+
if (Buffer.byteLength(JSON.stringify(input), "utf8") > 32_768)
|
|
54
|
+
throw new RecipeError("A recipe must be at most 32 KB.");
|
|
55
|
+
const raw = input;
|
|
56
|
+
if (raw.format !== "standing-orders-recipe" || (raw.version !== 1 && raw.version !== 2))
|
|
57
|
+
throw new RecipeError("This recipe format/version is not supported.");
|
|
58
|
+
const expected = raw.version === 2 ? [...keys, "inputs"] : keys;
|
|
59
|
+
if (Object.keys(raw).some(key => !expected.includes(key)) || expected.some(key => !Object.hasOwn(raw, key)))
|
|
60
|
+
throw new RecipeError("Recipe fields do not match this version. Recipes contain work definitions, not permissions, credentials, or agent settings.");
|
|
61
|
+
if (typeof raw.name !== "string" || typeof raw.description !== "string" || typeof raw.goal !== "string" || (raw.outOfScope !== null && typeof raw.outOfScope !== "string") || !Array.isArray(raw.touches) || raw.touches.some(path => typeof path !== "string"))
|
|
62
|
+
throw new RecipeError("Name, goal, exclusions, and allowed paths must be readable text.");
|
|
63
|
+
if (raw.description.length > 400 || !honest(raw.description))
|
|
64
|
+
throw new RecipeError("Keep the description within 400 characters, without hidden text.");
|
|
65
|
+
const bad = validateTaskText({ title: raw.name, goal: raw.goal, outOfScope: raw.outOfScope, touches: raw.touches });
|
|
66
|
+
if (bad !== null)
|
|
67
|
+
throw new RecipeError(bad.message);
|
|
68
|
+
if (!["auto", "required", "skip"].includes(String(raw.planning)) || !["branch", "report"].includes(String(raw.deliverable)))
|
|
69
|
+
throw new RecipeError("Choose a supported planning and result type.");
|
|
70
|
+
if (raw.schedule !== null && (typeof raw.schedule !== "string" || parseSchedule(raw.schedule) === null))
|
|
71
|
+
throw new RecipeError("Choose a valid schedule.");
|
|
72
|
+
if (raw.costCeilingUsd !== null && (typeof raw.costCeilingUsd !== "number" || !Number.isFinite(raw.costCeilingUsd) || raw.costCeilingUsd <= 0))
|
|
73
|
+
throw new RecipeError("The weekly budget must be a positive dollar amount.");
|
|
74
|
+
if (raw.schedule === null && raw.costCeilingUsd !== null)
|
|
75
|
+
throw new RecipeError("A weekly budget applies to a repeating workflow. One-time work uses the project's task budget.");
|
|
76
|
+
if (raw.deliverable === "report" && raw.schedule !== null)
|
|
77
|
+
throw new RecipeError("Report recipes run once. Repeating workflows currently produce code changes.");
|
|
78
|
+
if (raw.schedule !== null && raw.planning === "required")
|
|
79
|
+
throw new RecipeError("Plan-first recipes run once. Repeating workflows reuse their approved scope; choose direct execution or run once.");
|
|
80
|
+
if (!Array.isArray(raw.acceptance) || raw.acceptance.some(one => typeof one !== "object" || one === null || Object.keys(one).some(key => !["id", "statement", "how", "evidence"].includes(key))))
|
|
81
|
+
throw new RecipeError("Success checks have unsupported fields.");
|
|
82
|
+
const parsed = parseAcceptanceCriteria(raw.acceptance);
|
|
83
|
+
if (parsed.problems.length > 0 || parsed.criteria.length === 0)
|
|
84
|
+
throw new RecipeError(parsed.problems.map(one => one.message).join("; ") || "Add at least one success check.");
|
|
85
|
+
const fields = { format: "standing-orders-recipe", name: raw.name.trim(), description: raw.description.trim(), goal: raw.goal.trim(), outOfScope: raw.outOfScope?.trim() || null,
|
|
86
|
+
touches: [...raw.touches], acceptance: parsed.criteria, planning: raw.planning, deliverable: raw.deliverable, schedule: raw.schedule, costCeilingUsd: raw.costCeilingUsd };
|
|
87
|
+
const document = raw.version === 1 ? { ...fields, version: 1 } : { ...fields, version: 2, inputs: parseRecipeInputs(raw.inputs) };
|
|
88
|
+
// Keep v1's canonical key order stable: existing immutable digests must survive.
|
|
89
|
+
const ordered = { format: document.format, version: document.version, name: document.name, description: document.description, goal: document.goal,
|
|
90
|
+
outOfScope: document.outOfScope, touches: document.touches, acceptance: document.acceptance, planning: document.planning, deliverable: document.deliverable,
|
|
91
|
+
schedule: document.schedule, costCeilingUsd: document.costCeilingUsd, ...(document.version === 2 ? { inputs: document.inputs } : {}) };
|
|
92
|
+
if (ordered.version === 2)
|
|
93
|
+
validateInputReferences(ordered);
|
|
94
|
+
if (document.schedule !== null) {
|
|
95
|
+
const problems = validateRoutineTerms({ repo: "/recipe-preview", ...document, requirements: [], schedule: document.schedule, singleFlight: true });
|
|
96
|
+
if (problems.length)
|
|
97
|
+
throw new RecipeError(problems.map(one => `${one.field}: ${one.problem}`).join("; "));
|
|
98
|
+
}
|
|
99
|
+
return ordered;
|
|
100
|
+
}
|
|
101
|
+
export function importRecipe(text) {
|
|
102
|
+
if (Buffer.byteLength(text, "utf8") > 32_768)
|
|
103
|
+
throw new RecipeError("A recipe must be at most 32 KB.");
|
|
104
|
+
let value;
|
|
105
|
+
try {
|
|
106
|
+
value = JSON.parse(text);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new RecipeError("That is not valid recipe JSON.");
|
|
110
|
+
}
|
|
111
|
+
return parseRecipe(value);
|
|
112
|
+
}
|
|
113
|
+
export const recipeDigest = (document) => createHash("sha256").update(JSON.stringify(document)).digest("hex");
|
|
114
|
+
export const exportRecipe = (document) => JSON.stringify(parseRecipe(document), null, 2) + "\n";
|
|
115
|
+
/** Inputs are literal work text, never a program, path grant, or shell fragment. */
|
|
116
|
+
function parseRecipeInputs(raw) {
|
|
117
|
+
if (!Array.isArray(raw) || raw.length < 1 || raw.length > RECIPE_INPUT_LIMIT)
|
|
118
|
+
throw new RecipeError("Add between 1 and 8 questions, or remove the empty questions to save a fixed recipe.");
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
return raw.map(input => {
|
|
121
|
+
if (input === null || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some(key => !["key", "label", "defaultValue"].includes(key)))
|
|
122
|
+
throw new RecipeError("Questions contain only a key, label, and optional default answer.");
|
|
123
|
+
const { key, label, defaultValue } = input;
|
|
124
|
+
if (typeof key !== "string" || !/^[a-z][a-z0-9_]{0,31}$/.test(key) || ["constructor", "prototype"].includes(key) || seen.has(key))
|
|
125
|
+
throw new RecipeError("Give each question a unique key: lowercase letters, numbers, or underscores, starting with a letter (up to 32 characters).");
|
|
126
|
+
if (typeof label !== "string" || !label.trim() || label.length > 80 || !honest(label))
|
|
127
|
+
throw new RecipeError("Give each question a readable label of up to 80 characters.");
|
|
128
|
+
if (defaultValue !== null && (typeof defaultValue !== "string" || defaultValue.length > 500 || !honest(defaultValue) || /\{\{|\}\}/.test(defaultValue)))
|
|
129
|
+
throw new RecipeError("Default answers must be plain text up to 500 characters, without input placeholders.");
|
|
130
|
+
seen.add(key);
|
|
131
|
+
return { key, label: label.trim(), defaultValue: typeof defaultValue === "string" ? defaultValue.trim() || null : null };
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const inputToken = /\{\{\s*([a-z][a-z0-9_]{0,31})\s*\}\}/g;
|
|
135
|
+
function workTexts(document) {
|
|
136
|
+
return [document.name, document.description, document.goal, document.outOfScope ?? "", ...document.acceptance.map(one => one.statement)];
|
|
137
|
+
}
|
|
138
|
+
function validateInputReferences(document) {
|
|
139
|
+
const keys = new Set(document.inputs.map(input => input.key)), used = new Set();
|
|
140
|
+
for (const text of workTexts(document)) {
|
|
141
|
+
const rest = text.replace(inputToken, (_, key) => {
|
|
142
|
+
if (!keys.has(key))
|
|
143
|
+
throw new RecipeError(`Add a question for {{${key}}}, or remove that placeholder.`);
|
|
144
|
+
used.add(key);
|
|
145
|
+
return "";
|
|
146
|
+
});
|
|
147
|
+
if (/\{\{|\}\}/.test(rest))
|
|
148
|
+
throw new RecipeError("Use placeholders like {{module}}, matching a question key.");
|
|
149
|
+
}
|
|
150
|
+
if ([...document.touches, ...document.acceptance.map(one => one.how ?? "")].some(text => /\{\{|\}\}/.test(text)))
|
|
151
|
+
throw new RecipeError("Use inputs in instructions and success checks. Allowed paths and check commands must stay fixed.");
|
|
152
|
+
for (const key of keys)
|
|
153
|
+
if (!used.has(key))
|
|
154
|
+
throw new RecipeError(`Use {{${key}}} in your instructions or success checks so its answer affects the work.`);
|
|
155
|
+
}
|
|
156
|
+
export function resolveRecipe(document, answers) {
|
|
157
|
+
const parsed = parseRecipe(document);
|
|
158
|
+
const inputs = parsed.version === 2 ? parsed.inputs : [];
|
|
159
|
+
const known = new Set(inputs.map(input => input.key));
|
|
160
|
+
for (const key of answers.keys())
|
|
161
|
+
if (!known.has(key))
|
|
162
|
+
throw new RecipeError("This recipe does not ask for that input. Reload it before continuing.");
|
|
163
|
+
const values = new Map();
|
|
164
|
+
for (const input of inputs) {
|
|
165
|
+
const value = (answers.has(input.key) ? answers.get(input.key) : input.defaultValue ?? "").trim();
|
|
166
|
+
if (!value)
|
|
167
|
+
throw new RecipeError(`Answer “${input.label}” before previewing the work.`);
|
|
168
|
+
if (value.length > 500 || !honest(value) || /\{\{|\}\}/.test(value))
|
|
169
|
+
throw new RecipeError(`“${input.label}” must be plain text up to 500 characters, without input placeholders.`);
|
|
170
|
+
values.set(input.key, value);
|
|
171
|
+
}
|
|
172
|
+
if (parsed.version === 1)
|
|
173
|
+
return parsed;
|
|
174
|
+
const expand = (text) => text.replace(inputToken, (_, key) => values.get(key));
|
|
175
|
+
const { inputs: _inputs, ...fields } = parsed;
|
|
176
|
+
return parseRecipe({ ...fields, version: 1, name: expand(parsed.name), description: expand(parsed.description), goal: expand(parsed.goal), outOfScope: parsed.outOfScope === null ? null : expand(parsed.outOfScope),
|
|
177
|
+
acceptance: parsed.acceptance.map(one => ({ ...one, statement: expand(one.statement) })) });
|
|
178
|
+
}
|
|
179
|
+
/** Only the stored definition supplies scope; form answers cannot replace it. */
|
|
180
|
+
export function prepareRecipeRun(store, actor, repo, id, revision, answers, now) {
|
|
181
|
+
repo = recipeProject(repo);
|
|
182
|
+
return store.transact(() => {
|
|
183
|
+
access(store, actor, repo, true);
|
|
184
|
+
const recipe = findRecipe(store, actor, repo, id, revision);
|
|
185
|
+
if (recipe === null || recipe.repo === null)
|
|
186
|
+
throw new RecipeError("This recipe is not available in this project.", 404);
|
|
187
|
+
return createWorkflowPreview(store, actor, repo, resolveRecipe(recipe.document, answers), recipe.id, now);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const titles = { "nightly-deps": "Keep dependencies current", "test-coverage": "Test one overlooked module", "docs-drift": "Keep documentation accurate", "lint-sweep": "Clean up lint and types" };
|
|
191
|
+
export function starterRecipes() {
|
|
192
|
+
const recipes = TEMPLATES.filter(one => one.kind !== "recipe").map(one => {
|
|
193
|
+
const document = parseRecipe({ format: "standing-orders-recipe", version: 1, name: titles[one.name] ?? one.name, description: one.purpose, goal: one.goal, outOfScope: one.outOfScope, touches: one.touches, acceptance: one.acceptance,
|
|
194
|
+
planning: "skip", deliverable: "branch", schedule: one.kind === "routine" ? one.schedule : null, costCeilingUsd: null });
|
|
195
|
+
return { id: one.name, revision: 1, repo: null, document, digest: recipeDigest(document), author: "Toolroll" };
|
|
196
|
+
});
|
|
197
|
+
for (const [id, document] of [
|
|
198
|
+
["small-feature", { name: "Ship a small feature", description: "Turn one clear outcome into a plan, a reviewed change, and evidence.", goal: "Implement one small feature. Describe the desired behavior here, including who uses it and what success looks like.", outOfScope: "No unrelated refactoring, dependency upgrades, deployment, or changes to existing public behavior.", planning: "required", deliverable: "branch", acceptance: [{ id: "c1", statement: "The requested behavior works and relevant regression tests pass.", how: null, evidence: ["check", "changed-path"] }] }],
|
|
199
|
+
["project-tour", { name: "Understand this project", description: "A useful first run: get a source-backed report before changing anything.", goal: "Map the project's entry points, major components, build and test commands, and one bounded improvement worth doing next. Cite the source paths behind each claim and distinguish confirmed facts from unknowns.", outOfScope: "No code or configuration changes, dependency installation, or publication.", planning: "skip", deliverable: "report", acceptance: [{ id: "c1", statement: "The report explains the entry points, components, build/test commands, and next improvement with source references and explicit unknowns.", how: null, evidence: ["manual-review"] }] }],
|
|
200
|
+
]) {
|
|
201
|
+
const parsed = parseRecipe({ format: "standing-orders-recipe", version: 1, ...document, touches: [], schedule: null, costCeilingUsd: null });
|
|
202
|
+
recipes.unshift({ id, revision: 1, repo: null, document: parsed, digest: recipeDigest(parsed), author: "Toolroll" });
|
|
203
|
+
}
|
|
204
|
+
return recipes;
|
|
205
|
+
}
|
|
206
|
+
function access(store, actor, repo, write = false) {
|
|
207
|
+
if (!store.accountCanAccess(actor, repo) || (write && store.accountOf(actor)?.role !== "approver"))
|
|
208
|
+
throw new RecipeError("This project is outside your current access.", 403);
|
|
209
|
+
}
|
|
210
|
+
function readRecipe(row) {
|
|
211
|
+
const document = importRecipe(String(row.document));
|
|
212
|
+
if (recipeDigest(document) !== row.digest)
|
|
213
|
+
throw new RecipeError("The saved recipe does not match its recorded version.", 409);
|
|
214
|
+
return { id: String(row.id), revision: Number(row.revision), repo: String(row.repo), document, digest: String(row.digest), author: String(row.author) };
|
|
215
|
+
}
|
|
216
|
+
export function savedRecipes(store, actor, repo) {
|
|
217
|
+
repo = recipeProject(repo);
|
|
218
|
+
access(store, actor, repo);
|
|
219
|
+
return store.handle.prepare(`SELECT r.* FROM workflow_recipe r WHERE repo=? AND revision=(SELECT MAX(revision) FROM workflow_recipe WHERE id=r.id) ORDER BY (SELECT MAX(p.created_at) FROM workflow_preview p WHERE p.repo=r.repo AND p.source=r.id AND (p.task_id IS NOT NULL OR p.routine_id IS NOT NULL)) DESC, created_at DESC, id LIMIT 100`).all(repo).map(readRecipe);
|
|
220
|
+
}
|
|
221
|
+
export function findRecipe(store, actor, repo, id, revision) {
|
|
222
|
+
const starter = starterRecipes().find(one => one.id === id);
|
|
223
|
+
if (starter !== undefined)
|
|
224
|
+
return revision === undefined || revision === starter.revision ? starter : null;
|
|
225
|
+
if (repo === null)
|
|
226
|
+
return null;
|
|
227
|
+
repo = recipeProject(repo);
|
|
228
|
+
access(store, actor, repo);
|
|
229
|
+
const row = revision === undefined ? store.handle.prepare("SELECT * FROM workflow_recipe WHERE repo=? AND id=? ORDER BY revision DESC LIMIT 1").get(repo, id)
|
|
230
|
+
: store.handle.prepare("SELECT * FROM workflow_recipe WHERE repo=? AND id=? AND revision=?").get(repo, id, revision);
|
|
231
|
+
return row === undefined ? null : readRecipe(row);
|
|
232
|
+
}
|
|
233
|
+
export function createWorkflowPreview(store, actor, repo, document, source, now) {
|
|
234
|
+
repo = recipeProject(repo);
|
|
235
|
+
const parsed = parseRecipe(document);
|
|
236
|
+
if (!/^[a-z0-9-]{1,48}$/.test(source))
|
|
237
|
+
throw new RecipeError("Unknown recipe source.");
|
|
238
|
+
return store.transact(() => {
|
|
239
|
+
access(store, actor, repo, true);
|
|
240
|
+
store.handle.prepare("DELETE FROM workflow_preview WHERE actor=? AND expires_at<? AND task_id IS NULL AND routine_id IS NULL AND saved_id IS NULL").run(actor, now.toISOString());
|
|
241
|
+
const count = store.handle.prepare("SELECT COUNT(*) n FROM workflow_preview WHERE actor=? AND expires_at>? AND task_id IS NULL AND routine_id IS NULL AND saved_id IS NULL").get(actor, now.toISOString());
|
|
242
|
+
if (Number(count?.n) >= 30)
|
|
243
|
+
throw new RecipeError("You have 30 open previews. Use one or let it expire before making another.", 429);
|
|
244
|
+
const token = randomBytes(16).toString("hex");
|
|
245
|
+
store.handle.prepare("INSERT INTO workflow_preview(token,actor,repo,document,digest,source,created_at,expires_at) VALUES (?,?,?,?,?,?,?,?)")
|
|
246
|
+
.run(token, actor, repo, JSON.stringify(parsed), recipeDigest(parsed), source, now.toISOString(), new Date(now.getTime() + 30 * 60_000).toISOString());
|
|
247
|
+
return workflowPreview(store, actor, repo, token);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
export function workflowPreview(store, actor, repo, token) {
|
|
251
|
+
repo = recipeProject(repo);
|
|
252
|
+
access(store, actor, repo);
|
|
253
|
+
const row = store.handle.prepare("SELECT * FROM workflow_preview WHERE token=? AND actor=? AND repo=?").get(token, actor, repo);
|
|
254
|
+
if (row === undefined)
|
|
255
|
+
return null;
|
|
256
|
+
const document = importRecipe(String(row.document));
|
|
257
|
+
if (recipeDigest(document) !== row.digest)
|
|
258
|
+
throw new RecipeError("This preview has changed. Make a fresh preview.", 409);
|
|
259
|
+
return { token, actor, repo, document, digest: String(row.digest), source: String(row.source), expiresAt: String(row.expires_at), taskId: row.task_id, routineId: row.routine_id, savedId: row.saved_id, savedRevision: row.saved_revision };
|
|
260
|
+
}
|
|
261
|
+
function currentPreview(store, actor, repo, token) {
|
|
262
|
+
access(store, actor, repo, true);
|
|
263
|
+
const preview = workflowPreview(store, actor, repo, token);
|
|
264
|
+
if (preview === null)
|
|
265
|
+
throw new RecipeError("No preview in this project. Preview the workflow again.", 404);
|
|
266
|
+
return preview;
|
|
267
|
+
}
|
|
268
|
+
export function launchWorkflow(store, actor, repo, token, now, operatorFiling) {
|
|
269
|
+
repo = recipeProject(repo);
|
|
270
|
+
return store.transact(() => {
|
|
271
|
+
const preview = currentPreview(store, actor, repo, token);
|
|
272
|
+
if (preview.taskId !== null || preview.routineId !== null)
|
|
273
|
+
return { taskId: preview.taskId, routineId: preview.routineId };
|
|
274
|
+
if (preview.expiresAt <= now.toISOString())
|
|
275
|
+
throw new RecipeError("This preview expired. Preview it again before creating work.", 409);
|
|
276
|
+
const d = preview.document;
|
|
277
|
+
if (d.version === 2)
|
|
278
|
+
throw new RecipeError("Answer the recipe questions and preview the filled-in work before creating it.", 409);
|
|
279
|
+
const provenance = `recipe:${preview.savedId ?? preview.source}`;
|
|
280
|
+
let taskId = null, routineId = null;
|
|
281
|
+
if (d.schedule === null) {
|
|
282
|
+
const made = fileTaskProposal(store, { id: `workflow-${token}`, title: d.name, repo, goal: d.goal, outOfScope: d.outOfScope, touches: d.touches, acceptance: d.acceptance, planning: d.planning, deliverable: d.deliverable, filedVia: provenance, filedBy: { name: actor, kind: "person" }, admittedRepos: [repo] }, now);
|
|
283
|
+
if (!made.ok)
|
|
284
|
+
throw new RecipeError(made.message, made.reason === "backlog-full" ? 429 : 400);
|
|
285
|
+
taskId = made.id;
|
|
286
|
+
if (operatorFiling)
|
|
287
|
+
applyModeToNewFiling(store, taskId, actor, now);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
const slug = d.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 32) || "workflow";
|
|
291
|
+
const made = fileRoutineProposal(store, { name: `${slug}-${token.slice(0, 8)}`, repo, goal: d.goal, outOfScope: d.outOfScope, touches: d.touches, acceptance: d.acceptance, requirements: [], schedule: d.schedule, costCeilingUsd: d.costCeilingUsd, filedVia: provenance, createdBy: actor, admittedRepos: [repo] }, now);
|
|
292
|
+
if (!made.ok)
|
|
293
|
+
throw new RecipeError(made.message);
|
|
294
|
+
routineId = made.id;
|
|
295
|
+
}
|
|
296
|
+
store.handle.prepare("UPDATE workflow_preview SET task_id=?,routine_id=? WHERE token=?").run(taskId, routineId, token);
|
|
297
|
+
store.recordAction({ at: now.toISOString(), actor, repo, taskId, runId: null, action: "workflow created", outcome: routineId === null ? "task" : "routine", source: "work" });
|
|
298
|
+
return { taskId, routineId };
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
export function saveWorkflowRecipe(store, actor, repo, token, now) {
|
|
302
|
+
repo = recipeProject(repo);
|
|
303
|
+
return store.transact(() => {
|
|
304
|
+
const preview = currentPreview(store, actor, repo, token);
|
|
305
|
+
if (preview.savedId !== null)
|
|
306
|
+
return findRecipe(store, actor, repo, preview.savedId, preview.savedRevision);
|
|
307
|
+
if (preview.expiresAt <= now.toISOString())
|
|
308
|
+
throw new RecipeError("This preview expired. Preview it again before saving.", 409);
|
|
309
|
+
const id = randomBytes(12).toString("hex");
|
|
310
|
+
store.handle.prepare("INSERT INTO workflow_recipe(id,revision,repo,document,digest,author,created_at) VALUES (?,1,?,?,?,?,?)").run(id, repo, JSON.stringify(preview.document), preview.digest, actor, now.toISOString());
|
|
311
|
+
store.handle.prepare("UPDATE workflow_preview SET saved_id=?,saved_revision=1 WHERE token=?").run(id, token);
|
|
312
|
+
store.recordAction({ at: now.toISOString(), actor, repo, taskId: null, runId: null, action: "recipe saved", outcome: "saved", source: "work" });
|
|
313
|
+
return findRecipe(store, actor, repo, id, 1);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
export function workflowSteps(document) {
|
|
317
|
+
return [
|
|
318
|
+
{ title: "Authorize", detail: document.schedule === null ? "Your project policy applies. Without a matching policy, review and approve the scope first." : "Approve the repeating scope and exact agents once. New recipes start inactive." },
|
|
319
|
+
...(document.deliverable === "report" ? [{ title: "Investigate", detail: "Inspect the project and return a source-backed report." }] : [
|
|
320
|
+
...(document.schedule === null && document.planning !== "skip" ? [{ title: "Plan", detail: document.planning === "required" ? "Inspect the repository and plan before building." : "Plan first when the scope calls for repository discovery." }] : []),
|
|
321
|
+
{ title: "Build", detail: "Work in an isolated branch within the agreed scope." },
|
|
322
|
+
{ title: "Verify", detail: "Check each success criterion and retain its evidence." },
|
|
323
|
+
]),
|
|
324
|
+
{ title: "Open result", detail: "See the report or change, its checks, and unresolved questions. Agent review follows your project policy; publishing requires separate authority." },
|
|
325
|
+
];
|
|
326
|
+
}
|
|
327
|
+
export function recipeScheduleWords(document, now) {
|
|
328
|
+
if (document.schedule === null)
|
|
329
|
+
return "Once, after its approval and readiness gates pass";
|
|
330
|
+
const schedule = parseSchedule(document.schedule);
|
|
331
|
+
return `${describeSchedule(schedule)}. If approved now, first due ${firstFireAt(schedule, now)}. Missed slots run once; unfinished work prevents overlap.`;
|
|
332
|
+
}
|
package/dist/remote.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half of "in flight" that is not on this machine.
|
|
3
|
+
*
|
|
4
|
+
* Branches come off the local filesystem in milliseconds. Pull requests and
|
|
5
|
+
* issues are a network round trip per repository, and that difference is the
|
|
6
|
+
* whole reason this is a separate module with a budget attached rather than
|
|
7
|
+
* three more lines inside discovery.
|
|
8
|
+
*
|
|
9
|
+
* An earlier draft kept these behind their own command precisely because of
|
|
10
|
+
* the cost, and the milestone still asks for one command that shows every
|
|
11
|
+
* branch, pull request, and issue in flight. Both concerns are real, so this
|
|
12
|
+
* follows the rule the rest of the tool already uses for expensive reads:
|
|
13
|
+
* price it, bound it, and **say what you withheld**. Twenty-four repositories
|
|
14
|
+
* on hotel wifi will not hang; they will come back with what fitted in the
|
|
15
|
+
* budget and a line naming what did not.
|
|
16
|
+
*
|
|
17
|
+
* The budget is for the whole phase, not per repository. A per-call timeout
|
|
18
|
+
* bounds the worst case at repos × timeout, which for a machine full of work
|
|
19
|
+
* is not a bound anybody would recognise as one.
|
|
20
|
+
*/
|
|
21
|
+
import { type Pull } from "./pulls.js";
|
|
22
|
+
import { type BackendTask, type Runner } from "./backend.js";
|
|
23
|
+
export type RemoteState = {
|
|
24
|
+
pulls: Pull[];
|
|
25
|
+
issues: BackendTask[];
|
|
26
|
+
/**
|
|
27
|
+
* Whether each read actually succeeded.
|
|
28
|
+
*
|
|
29
|
+
* An empty list means nothing without these. A repository with issues
|
|
30
|
+
* switched off, one whose token expired, and one that genuinely has no open
|
|
31
|
+
* issues all produce `issues: []`, and only the first two are a reason to
|
|
32
|
+
* stop trusting the number above it.
|
|
33
|
+
*/
|
|
34
|
+
pullsRead: boolean;
|
|
35
|
+
issuesRead: boolean;
|
|
36
|
+
/** What could not be read, in words. */
|
|
37
|
+
problems: string[];
|
|
38
|
+
/** True when the budget ran out before this repository was asked. */
|
|
39
|
+
skipped: boolean;
|
|
40
|
+
};
|
|
41
|
+
export type RemoteOptions = {
|
|
42
|
+
runner?: Runner;
|
|
43
|
+
/** Total wall clock for the whole phase, not per repository. */
|
|
44
|
+
budgetMs?: number;
|
|
45
|
+
concurrency?: number;
|
|
46
|
+
now?: () => number;
|
|
47
|
+
issueLimit?: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Long enough for a dozen repositories on an ordinary connection, short enough
|
|
51
|
+
* that somebody who typed a command still feels like it answered.
|
|
52
|
+
*/
|
|
53
|
+
export declare const DEFAULT_BUDGET_MS = 15000;
|
|
54
|
+
/** GitHub is the bottleneck here, not the local machine. */
|
|
55
|
+
export declare const DEFAULT_REMOTE_CONCURRENCY = 6;
|
|
56
|
+
export declare const EMPTY: RemoteState;
|
|
57
|
+
/**
|
|
58
|
+
* Read pull requests and issues for each repository, within one budget.
|
|
59
|
+
*
|
|
60
|
+
* Only repositories that look like they are on a remote are asked; a local-only
|
|
61
|
+
* repo has no pull requests by definition, and spending part of a shared budget
|
|
62
|
+
* to be told so would come out of the repositories that do.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readRemote(repos: readonly {
|
|
65
|
+
path: string;
|
|
66
|
+
remoteUrl: string | null;
|
|
67
|
+
}[], options?: RemoteOptions): Promise<Map<string, RemoteState>>;
|
package/dist/remote.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half of "in flight" that is not on this machine.
|
|
3
|
+
*
|
|
4
|
+
* Branches come off the local filesystem in milliseconds. Pull requests and
|
|
5
|
+
* issues are a network round trip per repository, and that difference is the
|
|
6
|
+
* whole reason this is a separate module with a budget attached rather than
|
|
7
|
+
* three more lines inside discovery.
|
|
8
|
+
*
|
|
9
|
+
* An earlier draft kept these behind their own command precisely because of
|
|
10
|
+
* the cost, and the milestone still asks for one command that shows every
|
|
11
|
+
* branch, pull request, and issue in flight. Both concerns are real, so this
|
|
12
|
+
* follows the rule the rest of the tool already uses for expensive reads:
|
|
13
|
+
* price it, bound it, and **say what you withheld**. Twenty-four repositories
|
|
14
|
+
* on hotel wifi will not hang; they will come back with what fitted in the
|
|
15
|
+
* budget and a line naming what did not.
|
|
16
|
+
*
|
|
17
|
+
* The budget is for the whole phase, not per repository. A per-call timeout
|
|
18
|
+
* bounds the worst case at repos × timeout, which for a machine full of work
|
|
19
|
+
* is not a bound anybody would recognise as one.
|
|
20
|
+
*/
|
|
21
|
+
import { readPulls } from "./pulls.js";
|
|
22
|
+
import { githubIssues } from "./issues.js";
|
|
23
|
+
import {} from "./backend.js";
|
|
24
|
+
/**
|
|
25
|
+
* Long enough for a dozen repositories on an ordinary connection, short enough
|
|
26
|
+
* that somebody who typed a command still feels like it answered.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_BUDGET_MS = 15_000;
|
|
29
|
+
/** GitHub is the bottleneck here, not the local machine. */
|
|
30
|
+
export const DEFAULT_REMOTE_CONCURRENCY = 6;
|
|
31
|
+
export const EMPTY = {
|
|
32
|
+
pulls: [],
|
|
33
|
+
issues: [],
|
|
34
|
+
pullsRead: false,
|
|
35
|
+
issuesRead: false,
|
|
36
|
+
problems: [],
|
|
37
|
+
skipped: false,
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Read pull requests and issues for each repository, within one budget.
|
|
41
|
+
*
|
|
42
|
+
* Only repositories that look like they are on a remote are asked; a local-only
|
|
43
|
+
* repo has no pull requests by definition, and spending part of a shared budget
|
|
44
|
+
* to be told so would come out of the repositories that do.
|
|
45
|
+
*/
|
|
46
|
+
export async function readRemote(repos, options = {}) {
|
|
47
|
+
const { runner, budgetMs = DEFAULT_BUDGET_MS, concurrency = DEFAULT_REMOTE_CONCURRENCY, now = () => Date.now(), issueLimit, } = options;
|
|
48
|
+
const state = new Map();
|
|
49
|
+
const wanted = repos.filter(repo => repo.remoteUrl !== null);
|
|
50
|
+
for (const repo of repos)
|
|
51
|
+
state.set(repo.path, { ...EMPTY });
|
|
52
|
+
if (wanted.length === 0)
|
|
53
|
+
return state;
|
|
54
|
+
const deadline = now() + budgetMs;
|
|
55
|
+
let next = 0;
|
|
56
|
+
const consume = async () => {
|
|
57
|
+
while (next < wanted.length) {
|
|
58
|
+
const repo = wanted[next++];
|
|
59
|
+
if (repo === undefined)
|
|
60
|
+
return;
|
|
61
|
+
// Checked before each repository rather than after: once the budget is
|
|
62
|
+
// spent, every remaining repository is marked and none of them waits.
|
|
63
|
+
const remaining = deadline - now();
|
|
64
|
+
if (remaining <= 0) {
|
|
65
|
+
state.set(repo.path, { ...EMPTY, skipped: true });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
// The per-call timeout is clamped to what is left of the budget. A
|
|
70
|
+
// deadline checked only before starting is not a bound: with six lanes
|
|
71
|
+
// and a twenty-second call timeout, six repositories begun a moment
|
|
72
|
+
// before the budget expires run twenty seconds past it. Handing each
|
|
73
|
+
// call the remaining time makes the budget mean what it says.
|
|
74
|
+
state.set(repo.path, await readOne(repo.path, runner, issueLimit, remaining));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
// A runner that throws rather than returning a failure must not end a
|
|
78
|
+
// report covering everything else — the same rule discovery follows.
|
|
79
|
+
state.set(repo.path, {
|
|
80
|
+
...EMPTY,
|
|
81
|
+
problems: [`could not read ${repo.path}: ${describe(error)}`],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(concurrency, wanted.length)) }, consume));
|
|
87
|
+
return state;
|
|
88
|
+
}
|
|
89
|
+
async function readOne(path, runner, issueLimit, timeoutMs) {
|
|
90
|
+
const issues = githubIssues({
|
|
91
|
+
repo: path,
|
|
92
|
+
timeoutMs,
|
|
93
|
+
...(runner === undefined ? {} : { runner }),
|
|
94
|
+
...(issueLimit === undefined ? {} : { limit: issueLimit }),
|
|
95
|
+
});
|
|
96
|
+
// Both round trips at once: they are independent, and doing them in sequence
|
|
97
|
+
// would double the time this repository costs the shared budget.
|
|
98
|
+
const [pulls, listed] = await Promise.all([
|
|
99
|
+
readPulls(path, { timeoutMs, ...(runner === undefined ? {} : { runner }) }),
|
|
100
|
+
issues.listReady(),
|
|
101
|
+
]);
|
|
102
|
+
const problems = [...pulls.problems];
|
|
103
|
+
// Every failed read is said out loud. An earlier version swallowed the
|
|
104
|
+
// `unreachable` ones as though they meant "issues are switched off here",
|
|
105
|
+
// but that bucket also holds an expired token, a rate limit, and a dropped
|
|
106
|
+
// connection — and reporting any of those as `issues: []` turns "we could
|
|
107
|
+
// not look" into "there is nothing there", which is the one thing this
|
|
108
|
+
// report must never do.
|
|
109
|
+
if (!listed.ok)
|
|
110
|
+
problems.push(`could not read issues: ${listed.message}`);
|
|
111
|
+
return {
|
|
112
|
+
pulls: pulls.pulls,
|
|
113
|
+
issues: listed.ok ? listed.value : [],
|
|
114
|
+
pullsRead: pulls.problems.length === 0,
|
|
115
|
+
issuesRead: listed.ok,
|
|
116
|
+
problems,
|
|
117
|
+
skipped: false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function describe(error) {
|
|
121
|
+
return error instanceof Error ? error.message : String(error);
|
|
122
|
+
}
|