@joshuafolkken/kit 1.1784.0 → 1.2116.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/.claude/agents/reviewer-verification.md +19 -0
- package/.claude/agents/reviewer.md +19 -0
- package/.claude/settings.json +5 -6
- package/.claude/skills/dependency-update/SKILL.md +9 -10
- package/.claude/skills/workflow-commands/SKILL.md +6 -6
- package/.claude/skills/workflow-commands/background-commands.md +8 -5
- package/.claude/skills/workflow-commands/backlogrun-child.md +3 -1
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-park.md +5 -2
- package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -3
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
- package/.claude/skills/workflow-commands/backlogrun.md +1 -1
- package/.claude/skills/workflow-commands/chain-rule.md +41 -32
- package/.claude/skills/workflow-commands/delegation.md +6 -6
- package/.claude/skills/workflow-commands/entry-sequence.md +2 -2
- package/.claude/skills/workflow-commands/followup.md +7 -5
- package/.claude/skills/workflow-commands/fullrun-steps.md +6 -6
- package/.claude/skills/workflow-commands/fullrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +9 -4
- package/.claude/skills/workflow-commands/issue-fold-existing.md +2 -1
- package/.claude/skills/workflow-commands/kickoff.md +2 -2
- package/.claude/skills/workflow-commands/observation-filing.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +6 -4
- package/.claude/skills/workflow-commands/pre-gate-cut.md +9 -7
- package/.claude/skills/workflow-commands/prerequisite.md +3 -2
- package/.claude/skills/workflow-commands/retrospective.md +2 -2
- package/.claude/skills/workflow-commands/split-assessment.md +7 -5
- package/.codex/hooks.json +5 -5
- package/.github/actions/setup-pnpm/action.yml +9 -16
- package/.github/dependabot.yml +16 -0
- package/.github/workflows/auto-tag.yml +45 -0
- package/.github/workflows/ci.yml +155 -15
- package/.github/workflows/dependabot-auto-merge.yml +63 -4
- package/.github/workflows/production.yml +8 -0
- package/.github/workflows/publish.yml +60 -45
- package/.vscode/settings.json +19 -0
- package/CLAUDE.md +5 -12
- package/SECURITY.md +2 -2
- package/dist/CLAUDE.md +5 -12
- package/dist/commands/document-read-cli.js +2 -1
- package/dist/commands/document-section-main.js +2 -1
- package/dist/commands/read-set-cli.js +194 -134
- package/dist/commands/run-prep-main.js +9694 -10173
- package/dist/commands/run-step-cli.js +22138 -18285
- package/dist/hooks/audit-provision.js +23 -5
- package/dist/hooks/chunk-3GKE653K.js +6204 -0
- package/dist/hooks/chunk-45CJJOO7.js +1243 -0
- package/dist/hooks/{chunk-CWMBGKYW.js → chunk-5PM2JRGV.js} +2153 -856
- package/dist/hooks/{chunk-WR2KQAYZ.js → chunk-7SIUNQXK.js} +1207 -1470
- package/dist/hooks/{chunk-O2YBIDSI.js → chunk-CQD4LJE6.js} +1682 -1130
- package/dist/hooks/{chunk-VWWCFGWK.js → chunk-OPSRGWFU.js} +1 -1
- package/dist/hooks/{chunk-XGBNSWEI.js → chunk-UJ3FPMMM.js} +132 -49
- package/dist/hooks/chunk-VVNYJWEI.js +30 -0
- package/dist/hooks/chunk-Z6DQJKH3.js +2806 -0
- package/dist/hooks/codex-hook-adapter.js +8 -8
- package/dist/hooks/format-edited.js +4 -5
- package/dist/hooks/inputs.json +386 -360
- package/dist/hooks/pretool-guard.js +7 -6
- package/dist/hooks/session-lang.js +101 -258
- package/dist/hooks/stop-guard.js +1744 -2576
- package/dist/josh.js +7133 -6368
- package/dist/version/index.js +161 -1
- package/eslint/rules/comment-why-only.js +108 -0
- package/package.json +16 -9
- package/prompts/coding-standards.md +1 -1
- package/prompts/collaboration-workflow/file-edits.md +14 -11
- package/prompts/collaboration-workflow/issue-citation.md +0 -4
- package/prompts/collaboration-workflow/issue-template.md +40 -21
- package/prompts/collaboration-workflow/lane-instructions.md +3 -1
- package/prompts/collaboration-workflow/measurement.md +8 -1
- package/prompts/collaboration-workflow/operating-rules.md +11 -2
- package/prompts/collaboration-workflow/output-bounds.md +6 -2
- package/prompts/collaboration-workflow/plan-comment.md +1 -1
- package/prompts/collaboration-workflow/principles.md +9 -0
- package/prompts/collaboration-workflow/residency.md +3 -2
- package/prompts/collaboration-workflow/rule-delivery.md +13 -1
- package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
- package/prompts/collaboration-workflow/wip-cap.md +3 -1
- package/prompts/review-rubric.md +8 -0
- package/prompts/review.md +31 -22
- package/prompts/testing-guide.md +1 -0
- package/scripts/agent/agent-argv.ts +14 -16
- package/scripts/agent/agent-provider.ts +138 -0
- package/scripts/agent/agent-role-profile.ts +108 -156
- package/scripts/agent/agent-safe-text.ts +20 -5
- package/scripts/agent/api-usage-limit.ts +65 -0
- package/scripts/agent/claude-agent-argv.ts +19 -9
- package/scripts/agent/codex-agent-argv.ts +118 -22
- package/scripts/agent/usage-limit-reset.ts +179 -0
- package/scripts/auth/claude-token-output.ts +2 -2
- package/scripts/auto-ok/auto-ok-author.ts +53 -0
- package/scripts/auto-ok/auto-ok-cli.ts +20 -9
- package/scripts/backlog/backlog-defect-priority.ts +1 -1
- package/scripts/backlog/backlog-drive-cli.ts +6 -1
- package/scripts/backlog/backlog-drive-hold.ts +3 -3
- package/scripts/backlog/backlog-drive-idle.ts +10 -0
- package/scripts/backlog/backlog-drive-launch-pass.ts +90 -0
- package/scripts/backlog/backlog-drive-launch.ts +8 -2
- package/scripts/backlog/backlog-drive-named-offer.ts +73 -30
- package/scripts/backlog/backlog-drive-named.ts +28 -7
- package/scripts/backlog/backlog-drive-paused.ts +5 -4
- package/scripts/backlog/backlog-drive-restore.ts +11 -3
- package/scripts/backlog/backlog-drive.ts +21 -60
- package/scripts/backlog/backlog-gate-scope.ts +61 -0
- package/scripts/backlog/backlog-measure-close.ts +112 -0
- package/scripts/backlog/backlog-next.ts +39 -49
- package/scripts/backlog/backlog-occupied.ts +2 -2
- package/scripts/backlog/backlog-overlap.ts +42 -8
- package/scripts/backlog/backlog-plan-cli.ts +4 -1
- package/scripts/backlog/backlog-plan-comment.ts +81 -0
- package/scripts/backlog/backlog-plan-read.ts +19 -6
- package/scripts/backlog/backlog-pool.ts +50 -12
- package/scripts/backlog/backlog-rank.ts +60 -13
- package/scripts/backlog/backlog-stalled-detect.ts +2 -1
- package/scripts/backlog/backlog-waves.ts +17 -2
- package/scripts/backlog/backlogrun-launch.ts +7 -4
- package/scripts/cost-runtime/context-cut-payback.ts +27 -25
- package/scripts/cost-runtime/cost-cli.ts +37 -16
- package/scripts/cost-runtime/cost-pricing.ts +9 -0
- package/scripts/cost-runtime/cost-verdict.ts +4 -1
- package/scripts/delegation/delegation-policy.ts +2 -2
- package/scripts/delegation/duplicate-read-guard.ts +1 -0
- package/scripts/delegation/investigation-guard.ts +14 -2
- package/scripts/delegation/investigation-reads.ts +45 -23
- package/scripts/dependabot/dependabot-cooldown.ts +3 -2
- package/scripts/discovery/repo-party.ts +80 -20
- package/scripts/discovery/session-owner-pin.ts +51 -0
- package/scripts/document/capped-print.ts +1 -1
- package/scripts/document/command-reference-line-budget.ts +42 -0
- package/scripts/document/document-byte-budget.ts +17 -7
- package/scripts/document/document-checks.ts +20 -0
- package/scripts/document/document-ratchet-check.ts +101 -0
- package/scripts/document/document-section.ts +1 -0
- package/scripts/document/entry-read-budget.ts +7 -3
- package/scripts/document/issue-citation-budget.ts +94 -0
- package/scripts/epic/epic-cli-argv.ts +2 -2
- package/scripts/epic/epic-fetch.ts +4 -2
- package/scripts/epic/epic-graph.ts +7 -2
- package/scripts/epic/epic-issue.ts +3 -1
- package/scripts/epic/epic-rank.ts +5 -0
- package/scripts/exports/unused-members-check.ts +62 -0
- package/scripts/exports/unused-members-cli.ts +6 -51
- package/scripts/followup/followup-ci-path.ts +67 -0
- package/scripts/followup/followup-coderabbit-retrigger.ts +261 -0
- package/scripts/followup/followup-coderabbit-wait.ts +13 -10
- package/scripts/followup/followup-failed-tests.ts +160 -0
- package/scripts/followup/followup-job-needs.ts +100 -0
- package/scripts/followup/followup-job-rerun.ts +303 -0
- package/scripts/followup/followup-main-move.ts +113 -0
- package/scripts/followup/git-followup-finish.ts +43 -11
- package/scripts/followup/git-followup-pending.ts +3 -3
- package/scripts/followup/git-followup-stages.ts +41 -11
- package/scripts/followup/git-pr-followup-checks.ts +55 -16
- package/scripts/followup/git-pr-followup-wrapup.ts +24 -5
- package/scripts/followup/git-pr-followup.ts +62 -27
- package/scripts/fresh-machine/fresh-machine-script.ts +5 -9
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +2 -2
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +2 -2
- package/scripts/gate/busy-average.ts +34 -2
- package/scripts/gate/check-related-pinned.ts +43 -0
- package/scripts/gate/check-related-reuse.ts +64 -0
- package/scripts/gate/check-related-section.ts +57 -0
- package/scripts/gate/check-related-unplanned.ts +34 -0
- package/scripts/gate/check-related.ts +265 -0
- package/scripts/gate/command-memory.ts +1 -1
- package/scripts/gate/core-admission.ts +24 -6
- package/scripts/gate/core-budget-timing.ts +20 -6
- package/scripts/gate/core-budget.ts +40 -11
- package/scripts/gate/core-wait-record.ts +3 -2
- package/scripts/gate/core-wait.ts +25 -2
- package/scripts/gate/core-weights.ts +15 -7
- package/scripts/gate/gate-labels.ts +46 -0
- package/scripts/gate/gate-ledger.ts +14 -6
- package/scripts/gate/gate-plan.ts +103 -58
- package/scripts/gate/gate-report.ts +9 -7
- package/scripts/gate/gate-run-steps.ts +26 -3
- package/scripts/gate/gate-skip.ts +45 -6
- package/scripts/gate/gate-static-group.ts +60 -0
- package/scripts/gate/gate-steps.ts +2 -1
- package/scripts/gate/gate-tree.ts +4 -2
- package/scripts/gate/hook-gate-reuse.ts +19 -2
- package/scripts/gate/metrics-totals.ts +59 -0
- package/scripts/gate/scoped-green.ts +68 -15
- package/scripts/gate/since-green.ts +14 -6
- package/scripts/gate/structural-checks.ts +96 -0
- package/scripts/gate/verification-gate.ts +51 -16
- package/scripts/gate/verification-inputs.ts +21 -6
- package/scripts/gate/verification-tail.ts +52 -0
- package/scripts/gh/gh-author-association.ts +23 -2
- package/scripts/gh/git-ai-review-scan.ts +1 -1
- package/scripts/gh/git-gh-api-path.ts +12 -3
- package/scripts/gh/git-gh-comments-unreadable.ts +36 -0
- package/scripts/gh/git-gh-exec.ts +32 -2
- package/scripts/gh/git-gh-issue-read.ts +4 -2
- package/scripts/gh/git-gh-issue-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +8 -2
- package/scripts/gh/git-gh-issue.ts +12 -5
- package/scripts/gh/git-gh-pr-auto-merge.ts +13 -6
- package/scripts/gh/git-gh-pr-graphql-state.ts +56 -0
- package/scripts/gh/git-gh-pr-read.ts +74 -13
- package/scripts/gh/git-gh-pr-rest.ts +3 -1
- package/scripts/gh/git-gh-pr-snapshot.ts +22 -7
- package/scripts/gh/git-gh-pr-too-large.ts +20 -0
- package/scripts/gh/git-gh-pr.ts +21 -7
- package/scripts/gh/git-gh-retry.ts +141 -0
- package/scripts/gh/git-pr-ai-review.ts +15 -6
- package/scripts/gh/git-pr-checks-eval.ts +3 -0
- package/scripts/gh/git-pr-checks.ts +5 -5
- package/scripts/git/base-tree.ts +30 -0
- package/scripts/git/change-base.ts +4 -10
- package/scripts/git/changed-paths-sync.ts +63 -0
- package/scripts/git/changed-paths.ts +16 -1
- package/scripts/git/git-common-directory.ts +11 -1
- package/scripts/git/git-diff-reads.ts +66 -20
- package/scripts/git/git-error.ts +9 -2
- package/scripts/git/git-schemas.ts +13 -0
- package/scripts/git/git-worktree.ts +13 -0
- package/scripts/git/gone-branch.ts +4 -1
- package/scripts/git/main-merge-budget-conflict.ts +89 -0
- package/scripts/git/main-merge-guard.ts +55 -18
- package/scripts/git/main-merge-regenerate.ts +59 -25
- package/scripts/git/main-merge.ts +53 -5
- package/scripts/git/no-base-checkout.ts +127 -0
- package/scripts/git/stash/git-stash.ts +11 -2
- package/scripts/git/stash/stash-push-cli.ts +10 -0
- package/scripts/hooks/batch-guard.ts +40 -21
- package/scripts/hooks/edited-cspell.ts +37 -11
- package/scripts/hooks/format-edited-file.ts +22 -2
- package/scripts/hooks/lane-plan-guard.ts +50 -0
- package/scripts/hooks/pre-commit-lint-switch.ts +9 -0
- package/scripts/hooks/pre-commit-lint.ts +14 -1
- package/scripts/hooks/pre-push-related-switch.ts +10 -0
- package/scripts/hooks/pre-push-unit.ts +76 -10
- package/scripts/hooks/pretool-guard.ts +2 -0
- package/scripts/hooks/reserved-run.ts +14 -1
- package/scripts/hooks/run-hook.sh +23 -8
- package/scripts/hooks/step-zero-notice.ts +6 -1
- package/scripts/hooks/stop-actions.ts +3 -15
- package/scripts/hooks/stop-guard.ts +7 -6
- package/scripts/init/distributed-paths.ts +1 -0
- package/scripts/init/hook-command-rewrite.ts +5 -1
- package/scripts/init/hook-launch.ts +4 -2
- package/scripts/issue/aggregation-split.ts +52 -0
- package/scripts/issue/defect-origin-line.ts +19 -1
- package/scripts/issue/defect-rate-cli.ts +5 -102
- package/scripts/issue/defect-report.ts +4 -4
- package/scripts/issue/effect-audit-cli.ts +178 -0
- package/scripts/issue/effect-audit.ts +185 -0
- package/scripts/issue/expected-effect-lint.ts +64 -9
- package/scripts/issue/git-next-issues.ts +5 -0
- package/scripts/issue/issue-body-or-comment.ts +38 -4
- package/scripts/issue/issue-cite-cli.ts +1 -3
- package/scripts/issue/issue-classification.ts +1 -0
- package/scripts/issue/issue-closing-pr.ts +8 -1
- package/scripts/issue/issue-evidence-exit.ts +4 -2
- package/scripts/issue/issue-file-cap.ts +58 -0
- package/scripts/issue/issue-file-cli.ts +111 -38
- package/scripts/issue/issue-file-deferral.ts +52 -0
- package/scripts/issue/issue-file-fold.ts +29 -10
- package/scripts/issue/issue-file-interrupt.ts +85 -0
- package/scripts/issue/issue-file-lane-hint.ts +30 -0
- package/scripts/issue/issue-file.ts +43 -6
- package/scripts/issue/issue-labels.ts +19 -2
- package/scripts/issue/issue-lint-cli.ts +27 -2
- package/scripts/issue/issue-measured-cli.ts +13 -35
- package/scripts/issue/issue-measured-close.ts +58 -0
- package/scripts/issue/issue-not-before-label.ts +2 -2
- package/scripts/issue/issue-not-before.ts +4 -1
- package/scripts/issue/issue-priority-cli.ts +60 -0
- package/scripts/issue/issue-priority-raise.ts +55 -0
- package/scripts/issue/issue-priority.ts +174 -43
- package/scripts/issue/issue-rank.ts +21 -4
- package/scripts/issue/issue-read-cli.ts +32 -6
- package/scripts/issue/issue-read.ts +37 -12
- package/scripts/issue/issue-sample-gate.ts +95 -74
- package/scripts/issue/issue-sample-prime.ts +127 -0
- package/scripts/issue/issue-sample-required.ts +57 -0
- package/scripts/issue/issue-sample-source.ts +12 -11
- package/scripts/issue/issue-search.ts +124 -0
- package/scripts/issue/issue-state.ts +13 -1
- package/scripts/issue/issue-template-section.ts +43 -0
- package/scripts/issue/issue-watch-diff.ts +31 -1
- package/scripts/issue/issue-watch-hook.ts +11 -2
- package/scripts/issue/issue-watch.ts +55 -12
- package/scripts/issue/markdown-section.ts +1 -0
- package/scripts/issue/measure-declaration.ts +121 -0
- package/scripts/issue/measure-rerun-cli.ts +7 -70
- package/scripts/issue/plan-consumers-section.ts +22 -1
- package/scripts/issue/reproduction-measure.ts +3 -1
- package/scripts/issue/size-estimate.ts +90 -0
- package/scripts/issue/trusted-measure.ts +84 -0
- package/scripts/josh/command-usage.ts +3 -2
- package/scripts/josh/durable-state-migrate.ts +105 -0
- package/scripts/josh/durable-state.ts +112 -0
- package/scripts/josh/josh-aliases.ts +95 -0
- package/scripts/josh/josh-command-map.ts +1 -82
- package/scripts/josh/josh-commands-agent-hooks.ts +6 -6
- package/scripts/josh/josh-commands-ai.ts +5 -5
- package/scripts/josh/josh-commands-development.ts +59 -58
- package/scripts/josh/josh-commands-issue-write.ts +23 -4
- package/scripts/josh/josh-commands-issue.ts +32 -0
- package/scripts/josh/josh-commands-lane.ts +87 -4
- package/scripts/josh/josh-commands-lint.ts +16 -3
- package/scripts/josh/josh-commands-maintenance.ts +15 -0
- package/scripts/josh/josh-commands-review.ts +41 -6
- package/scripts/josh/josh-commands-ship.ts +56 -8
- package/scripts/josh/josh-commands-test-check.ts +80 -0
- package/scripts/josh/josh-commands-workflow.ts +1 -1
- package/scripts/josh/josh-environment-file.ts +43 -3
- package/scripts/josh/josh-in-process.ts +2 -2
- package/scripts/josh/josh-logic.ts +5 -3
- package/scripts/josh/josh-run.ts +19 -1
- package/scripts/josh/run-record-path.ts +15 -0
- package/scripts/josh/stamp-file.ts +11 -2
- package/scripts/lane/lane-adopt-cli.ts +98 -0
- package/scripts/lane/lane-adopt.ts +208 -0
- package/scripts/lane/lane-await.ts +5 -8
- package/scripts/lane/lane-baseline.ts +123 -0
- package/scripts/lane/lane-breakdown-cli.ts +64 -0
- package/scripts/lane/lane-breakdown-implement.ts +170 -0
- package/scripts/lane/lane-breakdown-laps.ts +107 -0
- package/scripts/lane/lane-breakdown-pinned.ts +76 -0
- package/scripts/lane/lane-breakdown-text.ts +145 -0
- package/scripts/lane/lane-breakdown.ts +239 -0
- package/scripts/lane/lane-check-stats.ts +29 -8
- package/scripts/lane/lane-child-invocation.ts +9 -2
- package/scripts/lane/lane-child-mark-keys.ts +12 -0
- package/scripts/lane/lane-child-marker.ts +20 -1
- package/scripts/lane/lane-ci-path-stats.ts +73 -0
- package/scripts/lane/lane-claim-stats.ts +84 -0
- package/scripts/lane/lane-close.ts +8 -3
- package/scripts/lane/lane-dispatch-log.ts +3 -3
- package/scripts/lane/lane-dispatch.ts +2 -2
- package/scripts/lane/lane-gap-stats.ts +7 -6
- package/scripts/lane/lane-guard-policy.ts +7 -1
- package/scripts/lane/lane-implement-stats.ts +207 -0
- package/scripts/lane/lane-implement-subagents.ts +90 -0
- package/scripts/lane/lane-investigation-switch.ts +16 -0
- package/scripts/lane/lane-ledger-kind.ts +31 -0
- package/scripts/lane/lane-ledger-schema-ship.ts +49 -0
- package/scripts/lane/lane-ledger-schema.ts +119 -36
- package/scripts/lane/lane-ledger-write.ts +257 -0
- package/scripts/lane/lane-ledger.ts +31 -177
- package/scripts/lane/lane-load.ts +1 -1
- package/scripts/lane/lane-measure-cli.ts +176 -59
- package/scripts/lane/lane-net-read.ts +44 -0
- package/scripts/lane/lane-net-time.ts +212 -0
- package/scripts/lane/lane-open.ts +16 -7
- package/scripts/lane/lane-output.ts +2 -4
- package/scripts/lane/lane-overrun-prompt.ts +147 -0
- package/scripts/lane/lane-overrun.ts +274 -0
- package/scripts/lane/lane-paths.ts +24 -1
- package/scripts/lane/lane-pause-cli.ts +4 -1
- package/scripts/lane/lane-paused-spans.ts +97 -0
- package/scripts/lane/lane-plan-check.ts +207 -0
- package/scripts/lane/lane-plan-cli.ts +41 -0
- package/scripts/lane/lane-plan.ts +144 -0
- package/scripts/lane/lane-registry.ts +14 -1
- package/scripts/lane/lane-relaunch.ts +1 -1
- package/scripts/lane/lane-round-one-stats.ts +51 -0
- package/scripts/lane/lane-sampler.ts +23 -4
- package/scripts/lane/lane-session-action.ts +93 -18
- package/scripts/lane/lane-session-broker.ts +49 -21
- package/scripts/lane/lane-session-reread-main.ts +14 -0
- package/scripts/lane/lane-session-reread.ts +51 -0
- package/scripts/lane/lane-ship-lap-stats.ts +74 -0
- package/scripts/lane/lane-sleep.ts +58 -0
- package/scripts/lane/lane-spent.ts +1 -1
- package/scripts/lane/lane-split-stats.ts +28 -0
- package/scripts/lane/lane-stage-stats.ts +13 -1
- package/scripts/lane/lane-state.ts +41 -9
- package/scripts/lane/lane-stats-tables.ts +58 -0
- package/scripts/lane/lane-stats.ts +21 -5
- package/scripts/lane/lane-stop-stats.ts +51 -19
- package/scripts/lane/lane-tail-stats.ts +32 -0
- package/scripts/lane/lane-transcript-calls.ts +67 -10
- package/scripts/lane/lane-transcript-stats.ts +44 -12
- package/scripts/lane/lane-value-cell.ts +72 -0
- package/scripts/lane/lane-value.ts +118 -0
- package/scripts/lane/lane-withdraw-closed.ts +124 -0
- package/scripts/lane/lane-withdraw.ts +108 -31
- package/scripts/lane/lane-yield.ts +85 -0
- package/scripts/lane/openai-lane-ship.ts +12 -11
- package/scripts/lane/openai-lane-supervisor-decision.ts +6 -8
- package/scripts/lane/openai-lane-supervisor-owner.ts +5 -8
- package/scripts/lane/openai-lane-supervisor.ts +5 -4
- package/scripts/lib/poll.ts +38 -9
- package/scripts/lib/settled-together.ts +10 -0
- package/scripts/lib/shell-quote.ts +21 -0
- package/scripts/lib/token-shape.ts +111 -0
- package/scripts/lint/eslint-daemon-sources.ts +79 -0
- package/scripts/lint/eslint-daemon.ts +45 -6
- package/scripts/lint/eslint-warm-cli.ts +7 -0
- package/scripts/lint/eslint-warm.ts +66 -0
- package/scripts/lint/import-graph.ts +141 -11
- package/scripts/lint/lint-gate-scope.ts +2 -2
- package/scripts/lint/lint-parallel.ts +54 -6
- package/scripts/lint/lint-related.ts +154 -33
- package/scripts/lint/rename-cli.ts +179 -0
- package/scripts/lint/rename-identifier.ts +192 -0
- package/scripts/lint/type-only-imports.ts +58 -0
- package/scripts/notify/telegram-notify.ts +12 -3
- package/scripts/observations/observation-record.ts +42 -16
- package/scripts/observations/observations-flush.ts +32 -9
- package/scripts/overrides/overrides-check.ts +9 -1
- package/scripts/release/release-approval-prune.ts +7 -1
- package/scripts/repo/required-checks-logic.ts +4 -1
- package/scripts/repo/ruleset-check.ts +4 -3
- package/scripts/report/markdown-shape.ts +28 -0
- package/scripts/report/report-format-reference.ts +15 -1
- package/scripts/report/report-labels.ts +12 -1
- package/scripts/report/report-lint-child-summary.ts +94 -0
- package/scripts/report/report-lint-cli.ts +26 -7
- package/scripts/report/report-lint.ts +2 -20
- package/scripts/review/prose-stale-cli.ts +88 -0
- package/scripts/review/prose-stale-scan.ts +152 -0
- package/scripts/review/review-attest.ts +73 -23
- package/scripts/review/review-brief-cli.ts +68 -19
- package/scripts/review/review-brief-paths.ts +22 -0
- package/scripts/review/review-brief.ts +14 -7
- package/scripts/review/review-comment-carry.ts +47 -0
- package/scripts/review/review-comment-round.ts +85 -22
- package/scripts/review/review-finding-ledger.ts +109 -7
- package/scripts/review/review-finding-text.ts +43 -0
- package/scripts/review/review-interaction.ts +11 -0
- package/scripts/review/review-lane-handover.ts +68 -0
- package/scripts/review/review-lint-cli.ts +57 -0
- package/scripts/review/review-lint-findings.ts +92 -0
- package/scripts/review/review-lint-sections.ts +80 -0
- package/scripts/review/review-lint.ts +221 -0
- package/scripts/review/review-prior-findings.ts +102 -0
- package/scripts/review/review-readers-scan.ts +4 -2
- package/scripts/review/review-record-cli.ts +177 -54
- package/scripts/review/review-record-code-round.ts +55 -0
- package/scripts/review/review-record-comment.ts +60 -0
- package/scripts/review/review-record-interaction.ts +35 -0
- package/scripts/review/review-record-pinned.ts +108 -0
- package/scripts/review/review-record.ts +25 -1
- package/scripts/review/review-reviewed-tree.ts +307 -0
- package/scripts/review/review-round-one-closure.ts +62 -0
- package/scripts/review/review-round-reuse.ts +106 -0
- package/scripts/review/review-round2-cli.ts +91 -29
- package/scripts/review/review-round2-skip-comment.ts +55 -0
- package/scripts/review/review-round2.ts +24 -8
- package/scripts/review/review-stamps.ts +7 -3
- package/scripts/review/review-stop-log.ts +20 -6
- package/scripts/review/review-stops-cli.ts +2 -1
- package/scripts/review/review-submit-cli.ts +101 -0
- package/scripts/rules/agent-model.ts +1 -1
- package/scripts/rules/bash-triggers.ts +3 -3
- package/scripts/rules/concise-reply.ts +88 -0
- package/scripts/rules/credential-body.ts +77 -0
- package/scripts/rules/decision-oracle.ts +1 -1
- package/scripts/rules/delivered-rules.ts +36 -17
- package/scripts/rules/destructive-command.ts +3 -11
- package/scripts/rules/direct-issue-comment.ts +34 -12
- package/scripts/rules/direct-issue-read.ts +191 -0
- package/scripts/rules/direct-pr-create.ts +4 -24
- package/scripts/rules/early-heartbeat.ts +2 -0
- package/scripts/rules/edit-tool.ts +27 -0
- package/scripts/rules/file-edit-rows.ts +17 -0
- package/scripts/rules/file-rewrite.ts +146 -0
- package/scripts/rules/firing-point.ts +3 -2
- package/scripts/rules/gh-api.ts +128 -7
- package/scripts/rules/gh-repo-selector.ts +84 -0
- package/scripts/rules/git-force.ts +2 -2
- package/scripts/rules/guard-remedy.ts +14 -5
- package/scripts/rules/implementation-cut.ts +61 -28
- package/scripts/rules/investigator-foreground.ts +231 -0
- package/scripts/rules/issue-citation.ts +15 -60
- package/scripts/rules/issue-comments.ts +9 -4
- package/scripts/rules/josh-git-bare.ts +1 -1
- package/scripts/rules/lane-background.ts +41 -13
- package/scripts/rules/lane-direct-tool.ts +107 -9
- package/scripts/rules/lane-interactive-ask.ts +4 -1
- package/scripts/rules/lane-park.ts +4 -2
- package/scripts/rules/lane-spilled-read.ts +90 -0
- package/scripts/rules/lane-split-park.ts +1 -1
- package/scripts/rules/lane-switch-main.ts +4 -4
- package/scripts/rules/last-prompt.ts +20 -47
- package/scripts/rules/oracle-consulted.ts +1 -1
- package/scripts/rules/permission-guards.ts +5 -1
- package/scripts/rules/piped-verification-shapes.ts +110 -0
- package/scripts/rules/piped-verification.ts +67 -17
- package/scripts/rules/poll-loop.ts +72 -9
- package/scripts/rules/pr-merge.ts +5 -5
- package/scripts/rules/pre-gate-cut.ts +3 -3
- package/scripts/rules/protected-files.ts +88 -24
- package/scripts/rules/reply-language.ts +3 -17
- package/scripts/rules/reply-prose.ts +34 -0
- package/scripts/rules/repo-admin.ts +132 -0
- package/scripts/rules/reviewer-foreground.ts +88 -0
- package/scripts/rules/round-one-check-related.ts +120 -0
- package/scripts/rules/rule-body-guard.ts +5 -15
- package/scripts/rules/rule-value-cache.ts +53 -5
- package/scripts/rules/rule-value-candidates.ts +84 -0
- package/scripts/rules/rule-value-cli.ts +59 -11
- package/scripts/rules/rule-value.ts +17 -4
- package/scripts/rules/run-tail-rule.ts +1 -1
- package/scripts/rules/sed-in-place.ts +196 -0
- package/scripts/rules/shell-segments.ts +10 -2
- package/scripts/rules/stop-rule-rows.ts +1 -13
- package/scripts/rules/stop-rules.ts +33 -58
- package/scripts/rules/test-declared-commit.ts +1 -1
- package/scripts/rules/third-party-write.ts +110 -23
- package/scripts/rules/timed-rerun.ts +1 -1
- package/scripts/rules/turn-filings.ts +90 -0
- package/scripts/rules/worktree-guard.ts +22 -7
- package/scripts/run/board/run-board-ending-reason.ts +6 -2
- package/scripts/run/board/run-board-fit.ts +24 -1
- package/scripts/run/board/run-board-found.ts +134 -0
- package/scripts/run/board/run-board-header.ts +8 -0
- package/scripts/run/board/run-board-labels.ts +11 -1
- package/scripts/run/board/run-board-lane-time.ts +1 -0
- package/scripts/run/board/run-board-link.ts +15 -2
- package/scripts/run/board/run-board-notes.ts +7 -1
- package/scripts/run/board/run-board-operation.ts +4 -2
- package/scripts/run/board/run-board-phase.ts +2 -5
- package/scripts/run/board/run-board-plan.ts +1 -1
- package/scripts/run/board/run-board-read.ts +46 -1
- package/scripts/run/board/run-board-reason.ts +20 -8
- package/scripts/run/board/run-board-render-checks.ts +3 -2
- package/scripts/run/board/run-board-render-drawn.ts +11 -0
- package/scripts/run/board/run-board-render-found.ts +157 -0
- package/scripts/run/board/run-board-render-lead.ts +9 -0
- package/scripts/run/board/run-board-render-legend.ts +19 -0
- package/scripts/run/board/run-board-render-tail.ts +1 -1
- package/scripts/run/board/run-board-render.ts +82 -18
- package/scripts/run/board/run-board-spin.ts +1 -3
- package/scripts/run/board/run-board-status.ts +2 -0
- package/scripts/run/board/run-board-tick.ts +39 -10
- package/scripts/run/carry/run-carry-ended.ts +3 -2
- package/scripts/run/carry/run-carry.ts +3 -2
- package/scripts/run/cut/run-cut-cli.ts +33 -133
- package/scripts/run/cut/run-cut-digest.ts +152 -0
- package/scripts/run/cut/run-cut-handoff.ts +52 -8
- package/scripts/run/cut/run-cut-report.ts +24 -3
- package/scripts/run/cut/run-cut-resume.ts +129 -0
- package/scripts/run/cut/run-cut-review.ts +105 -0
- package/scripts/run/cut/run-cut.ts +18 -7
- package/scripts/run/detached-launch.ts +1 -1
- package/scripts/run/entry/run-entry-cli.ts +88 -19
- package/scripts/run/entry/run-entry-spill.ts +83 -0
- package/scripts/run/entry/run-entry-stop.ts +5 -1
- package/scripts/run/entry/run-entry-trust.ts +91 -0
- package/scripts/run/event/run-event-baseline.ts +56 -0
- package/scripts/run/event/run-event-filed.ts +9 -3
- package/scripts/run/event/run-event-overrun.ts +197 -0
- package/scripts/run/event/run-event-render.ts +4 -0
- package/scripts/run/event/run-event-stream.ts +25 -3
- package/scripts/run/hold/run-stop-notify.ts +3 -8
- package/scripts/run/merge/run-merge-cap.ts +57 -0
- package/scripts/run/merge/run-merge-cli.ts +66 -23
- package/scripts/run/merge/run-merge-outcome.ts +2 -0
- package/scripts/run/merge/run-merge-ship.ts +13 -3
- package/scripts/run/merge/run-merge-signals.ts +40 -23
- package/scripts/run/merge/run-merge-steps.ts +56 -36
- package/scripts/run/merge/run-merge-usage-limit.ts +53 -0
- package/scripts/run/merge/run-merge-withdrawn.ts +53 -0
- package/scripts/run/merge/run-merge.ts +41 -14
- package/scripts/run/review/run-review-cli.ts +306 -20
- package/scripts/run/review/run-review-early-push.ts +121 -0
- package/scripts/run/review/run-review-join-read.ts +94 -0
- package/scripts/run/review/run-review-pinned-accepted.ts +126 -0
- package/scripts/run/review/run-review-pinned-join.ts +216 -0
- package/scripts/run/review/run-review-pinned-steps.ts +383 -0
- package/scripts/run/review/run-review-pinned-tree.ts +37 -0
- package/scripts/run/review/run-review-pinned.ts +53 -0
- package/scripts/run/review/run-review-steps.ts +70 -34
- package/scripts/run/review/run-review.ts +26 -6
- package/scripts/run/run-parent-cut-hook.ts +3 -4
- package/scripts/run/run-prep.ts +1 -0
- package/scripts/run/run-stage-read.ts +2 -6
- package/scripts/run/run-step-cli.ts +17 -5
- package/scripts/run/run-step-lane-plan.ts +27 -0
- package/scripts/run/run-step-section.ts +26 -7
- package/scripts/run/run-step.ts +5 -2
- package/scripts/run/ship/run-ship-args.ts +19 -0
- package/scripts/run/ship/run-ship-brokered-review.ts +106 -0
- package/scripts/run/ship/run-ship-cli.ts +131 -39
- package/scripts/run/ship/run-ship-conflict-probe.ts +59 -0
- package/scripts/run/ship/run-ship-detach.ts +10 -4
- package/scripts/run/ship/run-ship-handover.ts +106 -0
- package/scripts/run/ship/run-ship-infra-retry.ts +16 -1
- package/scripts/run/ship/run-ship-lap.ts +63 -0
- package/scripts/run/ship/run-ship-next.ts +27 -26
- package/scripts/run/ship/run-ship-overlap.ts +8 -3
- package/scripts/run/ship/run-ship-place.ts +9 -2
- package/scripts/run/ship/run-ship-planned.ts +92 -0
- package/scripts/run/ship/run-ship-pre-detach-pinned.ts +197 -0
- package/scripts/run/ship/run-ship-pre-detach-timed.ts +7 -8
- package/scripts/run/ship/run-ship-pre-detach.ts +77 -23
- package/scripts/run/ship/run-ship-preflight.ts +16 -4
- package/scripts/run/ship/run-ship-probe.ts +23 -9
- package/scripts/run/ship/run-ship-relaunch-cap.ts +43 -7
- package/scripts/run/ship/run-ship-relaunch.ts +51 -0
- package/scripts/run/ship/run-ship-return.ts +103 -29
- package/scripts/run/ship/run-ship-review-reason.ts +24 -0
- package/scripts/run/ship/run-ship-review-steps.ts +230 -59
- package/scripts/run/ship/run-ship-review.ts +171 -51
- package/scripts/run/ship/run-ship-reviewer.ts +138 -42
- package/scripts/run/ship/run-ship-scoped.ts +39 -10
- package/scripts/run/ship/run-ship-stage.ts +49 -13
- package/scripts/run/ship/run-ship-steps.ts +24 -4
- package/scripts/run/ship/run-ship-stop-cause.ts +43 -2
- package/scripts/run/ship/run-ship-stop-context.ts +50 -13
- package/scripts/run/ship/run-ship-sync.ts +171 -30
- package/scripts/run/ship/run-ship-synced-scope.ts +178 -0
- package/scripts/run/ship/run-ship-wait-cli.ts +145 -0
- package/scripts/run/ship/run-ship-waiter.ts +114 -0
- package/scripts/run/ship/run-ship.ts +16 -2
- package/scripts/run/wake/run-wake-caffeinate.ts +48 -0
- package/scripts/run/wake/run-wake-cli.ts +6 -113
- package/scripts/run/wake/run-wake-code.ts +53 -11
- package/scripts/run/wake/run-wake-driver.ts +5 -3
- package/scripts/run/wake/run-wake-finish.ts +137 -0
- package/scripts/run/wake/run-wake-loop.ts +5 -3
- package/scripts/run/wake/run-wake-ports.ts +11 -3
- package/scripts/run/wake/run-wake-session.ts +2 -0
- package/scripts/run/wake/run-wake.ts +3 -2
- package/scripts/safe-chain/ci-installer-pin.ts +49 -8
- package/scripts/safe-chain/project-config.ts +3 -2
- package/scripts/security/secretlint-scan.ts +42 -9
- package/scripts/security/security-audit-provision-main.ts +9 -1
- package/scripts/security/security-audit-provision.ts +1 -1
- package/scripts/security/security-audit.ts +21 -1
- package/scripts/security/sonar-file.ts +10 -5
- package/scripts/split/split-assess.ts +12 -3
- package/scripts/split/split-early-notice.ts +116 -0
- package/scripts/split/split-measure.ts +20 -2
- package/scripts/split/split-resolution.ts +107 -0
- package/scripts/test/direct-importers.ts +185 -0
- package/scripts/test/pure-files-entries.ts +83 -0
- package/scripts/test/related-heavy-tests.ts +81 -0
- package/scripts/test/test-no-base.ts +88 -0
- package/scripts/test/test-pinned-cache-logic.ts +363 -0
- package/scripts/test/test-pinned-cache.ts +337 -0
- package/scripts/test/test-pinned-emit.ts +49 -0
- package/scripts/test/test-pinned-functions.ts +116 -0
- package/scripts/test/test-pinned-ledger.ts +80 -0
- package/scripts/test/test-pinned-logic.ts +208 -20
- package/scripts/test/test-pinned-main.ts +84 -18
- package/scripts/test/test-pinned-pool.ts +73 -0
- package/scripts/test/test-pinned-probe.ts +254 -0
- package/scripts/test/test-pinned-prune-logic.ts +118 -0
- package/scripts/test/test-pinned-reach.ts +194 -0
- package/scripts/test/test-pinned-record.ts +196 -0
- package/scripts/test/test-pinned-rewire.ts +148 -0
- package/scripts/test/test-pinned-spawn.ts +81 -0
- package/scripts/test/test-pinned-stage.ts +70 -0
- package/scripts/test/test-pinned.ts +258 -47
- package/scripts/test/test-reach-cli.ts +61 -0
- package/scripts/test/test-read-map.ts +35 -11
- package/scripts/test/test-read-recording.ts +17 -5
- package/scripts/test/test-read-trace.ts +26 -8
- package/scripts/test/test-red-logic.ts +46 -7
- package/scripts/test/test-red.ts +34 -14
- package/scripts/test/test-related-claim.ts +37 -0
- package/scripts/test/test-related-main.ts +4 -1
- package/scripts/test/test-related-moved.ts +91 -0
- package/scripts/test/test-related-record.ts +43 -23
- package/scripts/test/test-related-scope.ts +99 -34
- package/scripts/test/test-related-synced.ts +135 -0
- package/scripts/test/test-related-touched.ts +84 -0
- package/scripts/test/test-related.ts +167 -83
- package/scripts/test/test-unit-guard.ts +28 -5
- package/scripts/test/unit-durations.ts +41 -0
- package/scripts/test/unit-worker-share.ts +5 -4
- package/scripts/test/vitest-cache-directory.ts +19 -0
- package/scripts/test/vitest-cli-excludes.ts +14 -0
- package/scripts/test/vitest-failure-summary.ts +111 -0
- package/scripts/test/whole-suite-files.ts +12 -1
- package/scripts/time-runtime/time-batch-guard.ts +42 -4
- package/scripts/time-runtime/time-bundle-call.ts +1 -1
- package/scripts/time-runtime/time-bundles.ts +36 -5
- package/scripts/time-runtime/time-markers.ts +2 -2
- package/scripts/time-runtime/time-search-call.ts +38 -0
- package/scripts/time-runtime/time-shell.ts +1 -1
- package/scripts/time-runtime/time-transcript-line.ts +17 -5
- package/scripts/version/development-engines-verdict.ts +124 -0
- package/scripts/version/latest-corepack.ts +2 -0
- package/templates/gitignore +2 -0
- package/templates/workflows/ci.yml +41 -0
- package/dist/hooks/chunk-AZX4FGQ3.js +0 -207
- package/dist/hooks/chunk-PT5R6UJD.js +0 -7131
- package/dist/hooks/chunk-TUIFS5E7.js +0 -72
- package/dist/hooks/chunk-YCDOIR3I.js +0 -3456
- package/scripts/cost-runtime/context-cut-threshold.ts +0 -11
- package/scripts/rules/filing-cap.ts +0 -139
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
2
|
+
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
3
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
// How a stopped detached ship goes back to the lane session that launched it, rather than to a fresh
|
|
7
|
+
// one that re-reads the diff and the findings (joshuafolkken/kit#4711).
|
|
8
|
+
//
|
|
9
|
+
// **The session proves it is alive by waiting**: `josh ship:wait` refreshes a heartbeat on every poll,
|
|
10
|
+
// and keeps it for one session gap after a chunk ends `pending`, so the session can re-issue the wait
|
|
11
|
+
// without the supervisor reading it as gone. A wait that ends for good removes it.
|
|
12
|
+
//
|
|
13
|
+
// **One side decides who answers a stop, by an exclusive create**: the supervisor writes the stop note
|
|
14
|
+
// and waits for the waiter to take it; past the gap it withdraws it and relaunches as before. Taking and
|
|
15
|
+
// withdrawing are the same `create_stamp` on one decision record, so exactly one of them wins and a stop
|
|
16
|
+
// is never answered twice nor dropped.
|
|
17
|
+
|
|
18
|
+
const HEARTBEAT_PREFIX = 'josh-ship-waiter-'
|
|
19
|
+
const HANDOFF_PREFIX = 'josh-ship-handoff-'
|
|
20
|
+
const DECISION_PREFIX = 'josh-ship-handoff-taken-'
|
|
21
|
+
const KEY_SEPARATOR = '-'
|
|
22
|
+
const TAKEN = 'taken'
|
|
23
|
+
const WITHDRAWN = 'withdrawn'
|
|
24
|
+
// Long enough for a session to answer a `pending` chunk with the next wait, short enough that a session
|
|
25
|
+
// which ended delays its fresh successor by no more than this.
|
|
26
|
+
const SESSION_GAP_MS = 120_000
|
|
27
|
+
const OFFER_POLL_MS = 1000
|
|
28
|
+
|
|
29
|
+
const heartbeat_schema = z.object({ at: z.number() })
|
|
30
|
+
const offer_schema = z.object({ at: z.number(), note: z.string() })
|
|
31
|
+
|
|
32
|
+
function path_of(prefix: string, repository: string, issue: string): string {
|
|
33
|
+
return stamp_file.stamp_path(`${prefix}${issue}${KEY_SEPARATOR}`, repository)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function beat(repository: string, issue: string, at: number = Date.now()): void {
|
|
37
|
+
stamp_file.replace_stamp(path_of(HEARTBEAT_PREFIX, repository, issue), { at })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function leave(repository: string, issue: string): void {
|
|
41
|
+
stamp_file.remove_stamp(path_of(HEARTBEAT_PREFIX, repository, issue))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function is_waited(repository: string, issue: string, now: number = Date.now()): boolean {
|
|
45
|
+
const raw = stamp_file.read_stamp_text(path_of(HEARTBEAT_PREFIX, repository, issue))
|
|
46
|
+
const heartbeat = raw === undefined ? undefined : json_value.parse_with(raw, heartbeat_schema)
|
|
47
|
+
|
|
48
|
+
return heartbeat !== undefined && now - heartbeat.at < SESSION_GAP_MS
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function decide(repository: string, issue: string, decision: string): boolean {
|
|
52
|
+
return stamp_file.create_stamp(path_of(DECISION_PREFIX, repository, issue), decision)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function clear_offer(repository: string, issue: string): void {
|
|
56
|
+
stamp_file.remove_stamp(path_of(HANDOFF_PREFIX, repository, issue))
|
|
57
|
+
stamp_file.remove_stamp(path_of(DECISION_PREFIX, repository, issue))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// An offer is never held past the session gap, so an older one was left by a supervisor that died
|
|
61
|
+
// mid-offer and belongs to an earlier attempt, not to the ship this session waits on.
|
|
62
|
+
function offered_note(repository: string, issue: string, now: number): string | undefined {
|
|
63
|
+
const raw = stamp_file.read_stamp_text(path_of(HANDOFF_PREFIX, repository, issue))
|
|
64
|
+
const offered = raw === undefined ? undefined : json_value.parse_with(raw, offer_schema)
|
|
65
|
+
|
|
66
|
+
return offered !== undefined && now - offered.at < SESSION_GAP_MS ? offered.note : undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The waiter's side: the stop note, once this call won it; `undefined` where none is offered or the
|
|
70
|
+
// supervisor already withdrew it.
|
|
71
|
+
function take(repository: string, issue: string, now: number = Date.now()): string | undefined {
|
|
72
|
+
const note = offered_note(repository, issue, now)
|
|
73
|
+
|
|
74
|
+
if (note === undefined || !decide(repository, issue, TAKEN)) return undefined
|
|
75
|
+
|
|
76
|
+
stamp_file.remove_stamp(path_of(HANDOFF_PREFIX, repository, issue))
|
|
77
|
+
|
|
78
|
+
return note
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function await_taken(repository: string, issue: string): Promise<boolean> {
|
|
82
|
+
const decision = path_of(DECISION_PREFIX, repository, issue)
|
|
83
|
+
|
|
84
|
+
for (let waited = 0; waited < SESSION_GAP_MS; waited += OFFER_POLL_MS) {
|
|
85
|
+
if (stamp_file.read_stamp_text(decision) !== undefined) return true
|
|
86
|
+
|
|
87
|
+
// eslint-disable-next-line no-await-in-loop -- polling: each read waits on the waiter's next poll
|
|
88
|
+
await sleep(OFFER_POLL_MS)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return !decide(repository, issue, WITHDRAWN)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The supervisor's side: whether a waiting session took `note`. A session that is not waiting is not
|
|
95
|
+
// offered anything, so a lane whose child ended relaunches with no delay.
|
|
96
|
+
async function offer(repository: string, issue: string, note: string): Promise<boolean> {
|
|
97
|
+
if (!is_waited(repository, issue)) return false
|
|
98
|
+
|
|
99
|
+
clear_offer(repository, issue)
|
|
100
|
+
// Renamed into place whole: a waiter polling between an exclusive create and its write would read an
|
|
101
|
+
// empty note, win the decision on it and drop the stop.
|
|
102
|
+
stamp_file.replace_stamp(path_of(HANDOFF_PREFIX, repository, issue), { at: Date.now(), note })
|
|
103
|
+
const is_taken = await await_taken(repository, issue)
|
|
104
|
+
|
|
105
|
+
// The decision stays until the next offer clears it: a waiter that read the note just before the
|
|
106
|
+
// withdrawal would otherwise win a fresh decision record and answer the stop a second time.
|
|
107
|
+
stamp_file.remove_stamp(path_of(HANDOFF_PREFIX, repository, issue))
|
|
108
|
+
|
|
109
|
+
return is_taken
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const run_ship_waiter = { SESSION_GAP_MS, beat, is_waited, leave, offer, take }
|
|
113
|
+
|
|
114
|
+
export { run_ship_waiter }
|
|
@@ -37,10 +37,23 @@ type ShipSection = Section & {
|
|
|
37
37
|
preconditions?: ReadonlyArray<string>
|
|
38
38
|
// The cause a stopped review round tagged its stop with, recorded with the stop.
|
|
39
39
|
cause?: Cause
|
|
40
|
+
// The stage found nothing to do, and why it ran or was skipped — both read onto its event line.
|
|
41
|
+
is_skipped?: boolean
|
|
42
|
+
reason?: string
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
//
|
|
43
|
-
|
|
45
|
+
// The skip and the reason a stage's event line carries, only where the stage gave them.
|
|
46
|
+
function mark_of(result: StepResult): Pick<ShipSection, 'is_skipped' | 'reason'> {
|
|
47
|
+
const { is_skipped, reason } = result
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...(is_skipped !== undefined && { is_skipped }),
|
|
51
|
+
...(reason !== undefined && { reason }),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// A stage's result under its header, carrying the conflicts, the preconditions, the cause and the mark
|
|
56
|
+
// only where it has them.
|
|
44
57
|
function section_of(header: string, result: StepResult): ShipSection {
|
|
45
58
|
const { conflicts, preconditions, cause } = result
|
|
46
59
|
|
|
@@ -52,6 +65,7 @@ function section_of(header: string, result: StepResult): ShipSection {
|
|
|
52
65
|
...(conflicts !== undefined && { conflicts }),
|
|
53
66
|
...(preconditions !== undefined && { preconditions }),
|
|
54
67
|
...(cause !== undefined && { cause }),
|
|
68
|
+
...mark_of(result),
|
|
55
69
|
}
|
|
56
70
|
}
|
|
57
71
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawn, type SpawnOptions } from 'node:child_process'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
// **The host stays awake while a supervisor runs** (#5173). `lane-sleep.ts` takes host sleep out of a
|
|
5
|
+
// lane's net time but cannot prevent it, and an idle sleep stops every lane, CI wait and relaunch at
|
|
6
|
+
// once. `caffeinate -w <pid>` holds the assertion for exactly the supervisor's lifetime: a `--stop`, a
|
|
7
|
+
// crash or a reload's exit ends it with no bookkeeping, and the successor of a reload starts its own
|
|
8
|
+
// from this same call, so the hand-off needs no re-attach. `-i` blocks idle sleep, `-s` system sleep —
|
|
9
|
+
// lid-close sleep included, but only on AC power (`docs/how-to/run-backlog.md`).
|
|
10
|
+
const CAFFEINATE_PATH = '/usr/bin/caffeinate'
|
|
11
|
+
const CAFFEINATE_FLAGS = ['-i', '-s', '-w']
|
|
12
|
+
|
|
13
|
+
interface Started {
|
|
14
|
+
on: (event: 'error', listener: () => void) => unknown
|
|
15
|
+
unref: () => unknown
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Spawner = (command: string, args: Array<string>, options: SpawnOptions) => Started
|
|
19
|
+
|
|
20
|
+
function argv(pid: number): Array<string> {
|
|
21
|
+
return [...CAFFEINATE_FLAGS, String(pid)]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// **A host without `caffeinate` (Linux, CI) runs the supervisor as before** — nothing is started and
|
|
25
|
+
// nothing is printed; a spawn that fails late is swallowed for the same reason.
|
|
26
|
+
function keep_awake(
|
|
27
|
+
pid: number,
|
|
28
|
+
is_present: (path: string) => boolean = existsSync,
|
|
29
|
+
spawner: Spawner = spawn,
|
|
30
|
+
): void {
|
|
31
|
+
if (!is_present(CAFFEINATE_PATH)) return
|
|
32
|
+
|
|
33
|
+
const child = spawner(CAFFEINATE_PATH, argv(pid), { detached: true, stdio: 'ignore' })
|
|
34
|
+
|
|
35
|
+
child.on('error', () => {
|
|
36
|
+
// the supervisor runs without the assertion, as on a host without caffeinate
|
|
37
|
+
})
|
|
38
|
+
child.unref()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const run_wake_caffeinate = {
|
|
42
|
+
CAFFEINATE_PATH,
|
|
43
|
+
argv,
|
|
44
|
+
keep_awake,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type { Spawner, Started }
|
|
48
|
+
export { run_wake_caffeinate }
|
|
@@ -4,15 +4,14 @@ import { backlog_budget } from '#scripts/backlog/backlog-budget'
|
|
|
4
4
|
import { lane_session_broker } from '#scripts/lane/lane-session-broker'
|
|
5
5
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
6
6
|
import { MS_PER_SECOND } from '#scripts/lib/timeouts'
|
|
7
|
-
import { telegram_notify } from '#scripts/notify/telegram-notify'
|
|
8
7
|
import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
|
|
9
8
|
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
10
9
|
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
11
|
-
import {
|
|
12
|
-
import { run_wake, type WakeStopReason, type WakeTidyResult } from './run-wake'
|
|
10
|
+
import { run_wake, type WakeTidyResult } from './run-wake'
|
|
13
11
|
import { run_wake_await } from './run-wake-await'
|
|
14
12
|
import { run_wake_code } from './run-wake-code'
|
|
15
13
|
import { run_wake_describe, type WakeContext } from './run-wake-describe'
|
|
14
|
+
import { run_wake_finish } from './run-wake-finish'
|
|
16
15
|
import { run_wake_loop, type LoopStop } from './run-wake-loop'
|
|
17
16
|
import { run_wake_ports } from './run-wake-ports'
|
|
18
17
|
import { run_wake_session, type LaunchResult } from './run-wake-session'
|
|
@@ -54,29 +53,6 @@ const NONE_VERDICT = 'none'
|
|
|
54
53
|
const UNKNOWN_VERDICT = 'unknown'
|
|
55
54
|
const FAILED_VERDICT = 'failed'
|
|
56
55
|
|
|
57
|
-
// The command rather than one entry point: the supervisor continues a `queue` as readily as a
|
|
58
|
-
// `backlogrun`, and a warning that named the wrong one would send the reader looking at a run that is
|
|
59
|
-
// not the one that stopped.
|
|
60
|
-
const WARNING_TITLE = 'run:wake supervisor'
|
|
61
|
-
const WARNING_RECOVERY = `Check the wake command, then restart with \`pnpm josh run:wake --start\`.`
|
|
62
|
-
// **The stop reasons that reach a person, and what each one tells them.** A reason mapped to
|
|
63
|
-
// `undefined` is one where nothing went wrong: `ended` is the run finishing through `run:carry --end`,
|
|
64
|
-
// `stopped` is a person's own `--stop` or a supervisor being superseded, and a warning channel that
|
|
65
|
-
// fired on those is one that stops being read.
|
|
66
|
-
//
|
|
67
|
-
// **`unreadable` warns**, because it ends the supervisor while a carry record is still sitting there
|
|
68
|
-
// handed off, so the run would otherwise be left asleep with nothing anywhere saying so.
|
|
69
|
-
// **Every reason is listed, and the silent two are listed as `undefined`.** A partial map would let a
|
|
70
|
-
// fifth stop reason compile straight past this and reach nobody, which is the defect class this
|
|
71
|
-
// section exists to close.
|
|
72
|
-
const STOP_BODIES: Record<WakeStopReason, string | undefined> = {
|
|
73
|
-
ended: undefined,
|
|
74
|
-
stopped: undefined,
|
|
75
|
-
reload: undefined,
|
|
76
|
-
failed: 'The supervisor could not continue the run and has stopped.',
|
|
77
|
-
unreadable:
|
|
78
|
-
'The carry record could not be read, so the supervisor stopped rather than guess at it.',
|
|
79
|
-
}
|
|
80
56
|
const NO_PROFILE_NOTE = 'the scheduler profile could not be resolved'
|
|
81
57
|
const RELOAD_FAILED_NOTE =
|
|
82
58
|
"The supervisor's code changed, but the supervisor to replace it did not start"
|
|
@@ -151,87 +127,6 @@ function refuse_without_carry(kind: CarryRead['kind']): number {
|
|
|
151
127
|
return report(NONE_VERDICT, FAILURE_EXIT_CODE)
|
|
152
128
|
}
|
|
153
129
|
|
|
154
|
-
// The log is named in the warning rather than only in `--list`, because the person reading a warning
|
|
155
|
-
// at three in the morning is the one who needs to know where to look next.
|
|
156
|
-
function log_hint(context: WakeContext): string {
|
|
157
|
-
return `The output of every session this supervisor started is in ${context.log_target}.`
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// The log hint goes ahead of the note: the Telegram length limit cuts the tail, and a long failure note
|
|
161
|
-
// (stdout tail plus stderr tail) would otherwise take the one line that says where to look next.
|
|
162
|
-
function warning_body(stop: LoopStop, body: string, context: WakeContext): string {
|
|
163
|
-
return [body, log_hint(context), stop.note].filter(Boolean).join('\n')
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Whether the warning reached a person — the answer `record_failure` leaves its marker on.
|
|
167
|
-
async function warn_of_stop(stop: LoopStop, body: string, context: WakeContext): Promise<boolean> {
|
|
168
|
-
return await telegram_notify.warn({
|
|
169
|
-
issue_title: WARNING_TITLE,
|
|
170
|
-
body: warning_body(stop, body, context),
|
|
171
|
-
recovery: WARNING_RECOVERY,
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// **A failed supervisor that leaves the run stranded says so on the stream, with its cause.** Nothing
|
|
176
|
-
// else writes there when the supervisor gives up, so a reader of the events would see the last merge
|
|
177
|
-
// and a run that looks as if it were still watching. The cause is the note's first line — the
|
|
178
|
-
// driver's own `error …` — because the rest is a stderr tail the log already holds.
|
|
179
|
-
//
|
|
180
|
-
// **Only a strand, and only one a person was told of.** A `failed` stop is not always a strand: a
|
|
181
|
-
// `run:carry --end` under a live driver fails it too, and that run ended cleanly. And the event is the
|
|
182
|
-
// marker `run_stranded_detect` holds its own notification back on, so it is left only when the warning
|
|
183
|
-
// was sent — after a failed send the stop-time detector still finds no marker, and notifies.
|
|
184
|
-
function is_strand_told(stop: LoopStop, watched: WatchedRun, is_warned: boolean): boolean {
|
|
185
|
-
if (!is_warned || stop.reason !== run_wake_loop.FAILED_REASON) return false
|
|
186
|
-
|
|
187
|
-
return run_stranded_detect.is_left_stranded(watched)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async function record_failure(
|
|
191
|
-
stop: LoopStop,
|
|
192
|
-
watched: WatchedRun,
|
|
193
|
-
is_warned: boolean,
|
|
194
|
-
): Promise<void> {
|
|
195
|
-
if (!is_strand_told(stop, watched, is_warned)) return
|
|
196
|
-
|
|
197
|
-
const [cause = ''] = (stop.note ?? '').split('\n', 1)
|
|
198
|
-
|
|
199
|
-
await run_event_stream_emit.emit(
|
|
200
|
-
run_event_stream.EVENT_KIND.STRANDED,
|
|
201
|
-
`The supervisor failed and stopped watching the run — ${cause}`,
|
|
202
|
-
)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// **Only a failed wake exits non-zero; `unreadable` warns but exits zero.** The
|
|
206
|
-
// verdict-and-exit-code table in `docs/josh-commands-run.md` → `run:wake` is a contract callers branch on,
|
|
207
|
-
// and what that one needs is the notification, not a different exit code.
|
|
208
|
-
function exit_code_of(reason: WakeStopReason): number {
|
|
209
|
-
return reason === run_wake_loop.FAILED_REASON ? FAILURE_EXIT_CODE : SUCCESS_EXIT_CODE
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// A failure that reached nobody is the defect, so the Telegram goes out before the verdict is printed.
|
|
213
|
-
// Which stops reach a person, and what each one says — `undefined` for the two where nothing went
|
|
214
|
-
// wrong. It is a function rather than a bare lookup so the decision can be asserted for every reason
|
|
215
|
-
// the loop can end on.
|
|
216
|
-
function stop_body(reason: WakeStopReason): string | undefined {
|
|
217
|
-
return STOP_BODIES[reason]
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// `started_at` is the run this supervisor took on — what `record_failure` tells an ended run by once
|
|
221
|
-
// the carry record that held it no longer reads.
|
|
222
|
-
async function finish(stop: LoopStop, context: WakeContext, started_at: string): Promise<number> {
|
|
223
|
-
const body = stop_body(stop.reason)
|
|
224
|
-
|
|
225
|
-
if (body === undefined) return report(stop.reason)
|
|
226
|
-
|
|
227
|
-
console.error(stop.note ?? body)
|
|
228
|
-
const is_warned = await warn_of_stop(stop, body, context)
|
|
229
|
-
|
|
230
|
-
await record_failure(stop, { ...context, started_at }, is_warned)
|
|
231
|
-
|
|
232
|
-
return report(stop.reason, exit_code_of(stop.reason))
|
|
233
|
-
}
|
|
234
|
-
|
|
235
130
|
function launch_supervisor(
|
|
236
131
|
context: WakeContext,
|
|
237
132
|
interval: string | undefined,
|
|
@@ -375,7 +270,7 @@ async function supervised(
|
|
|
375
270
|
profile: AgentProfile,
|
|
376
271
|
): Promise<LoopStop> {
|
|
377
272
|
const source = run_wake_code.source_of(context.worktree, SCRIPT_PATH)
|
|
378
|
-
const is_stale = code_check(await run_wake_code.watch(source))
|
|
273
|
+
const is_stale = code_check(await run_wake_code.watch(source, lane_session_broker.take_refusal))
|
|
379
274
|
const ports = run_wake_ports.ports_for(context, profile, is_stale)
|
|
380
275
|
const stop = await run_wake_supervise.supervise({
|
|
381
276
|
context,
|
|
@@ -405,9 +300,11 @@ async function loop(context: WakeContext, interval_ms: number): Promise<number>
|
|
|
405
300
|
if (claimed.kind === 'running') return report(RUNNING_VERDICT)
|
|
406
301
|
if (claimed.kind === 'refused') return refuse_without_carry(claimed.carry_kind)
|
|
407
302
|
|
|
303
|
+
run_wake_session.keep_awake(process.pid)
|
|
304
|
+
|
|
408
305
|
const stop = await supervised(context, interval_ms, claimed.wake.profile ?? profile)
|
|
409
306
|
|
|
410
|
-
return await finish(stop, context, claimed.carry.started_at)
|
|
307
|
+
return await run_wake_finish.finish(stop, context, claimed.carry.started_at)
|
|
411
308
|
}
|
|
412
309
|
|
|
413
310
|
// **The verdict tracks whether the supervisor is actually running, not merely whether a record is
|
|
@@ -467,16 +364,12 @@ const run_wake_cli = {
|
|
|
467
364
|
SUPERVISING_VERDICT,
|
|
468
365
|
UNKNOWN_VERDICT,
|
|
469
366
|
USAGE,
|
|
470
|
-
finish,
|
|
471
367
|
RELOAD_FAILED_NOTE,
|
|
472
|
-
record_failure,
|
|
473
368
|
reload,
|
|
474
369
|
restart_supervisor,
|
|
475
370
|
run,
|
|
476
371
|
start_for_launch,
|
|
477
|
-
stop_body,
|
|
478
372
|
supervised,
|
|
479
|
-
warning_body,
|
|
480
373
|
}
|
|
481
374
|
|
|
482
375
|
export { run_wake_cli }
|
|
@@ -23,31 +23,73 @@ interface CodeSource {
|
|
|
23
23
|
interface CodeState {
|
|
24
24
|
head: string | undefined
|
|
25
25
|
loaded: string | undefined
|
|
26
|
+
// A suspicion taken while the closure could not be hashed, kept for the next pass.
|
|
27
|
+
is_suspect: boolean
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
// **A hash that cannot be computed is never stale**: a supervisor that cannot tell keeps running, as
|
|
29
|
-
// it did before this check existed, rather than restarting on every pass.
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
// it did before this check existed, rather than restarting on every pass. **A suspected supervisor whose
|
|
32
|
+
// start left no baseline is stale** (joshuafolkken/kit#4714): the broker refused a request, and without
|
|
33
|
+
// a baseline nothing else could ever tell it differs, so the successor reads one afresh.
|
|
34
|
+
function is_changed(
|
|
35
|
+
loaded: string | undefined,
|
|
36
|
+
current: string | undefined,
|
|
37
|
+
is_suspected: boolean,
|
|
38
|
+
): boolean {
|
|
39
|
+
if (current === undefined) return false
|
|
40
|
+
if (loaded === undefined) return is_suspected
|
|
32
41
|
|
|
33
42
|
return loaded !== current
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
// **A move and a suspicion are consumed only once they were hashed**, so a closure that could not be
|
|
46
|
+
// read at them is read again at the next pass (joshuafolkken/kit#4714).
|
|
47
|
+
function consume(
|
|
48
|
+
state: CodeState,
|
|
49
|
+
read: { head: string | undefined; is_suspect: boolean },
|
|
50
|
+
current: string | undefined,
|
|
51
|
+
): void {
|
|
52
|
+
if (current === undefined) {
|
|
53
|
+
state.is_suspect = read.is_suspect
|
|
54
|
+
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
state.head = read.head
|
|
59
|
+
state.is_suspect = false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// **A suspicion re-hashes an unmoved commit** (joshuafolkken/kit#4714).
|
|
63
|
+
async function is_stale(
|
|
64
|
+
state: CodeState,
|
|
65
|
+
source: CodeSource,
|
|
66
|
+
is_suspected: () => boolean,
|
|
67
|
+
): Promise<boolean> {
|
|
37
68
|
const head = await source.head()
|
|
38
|
-
|
|
69
|
+
const is_suspect = is_suspected() || state.is_suspect
|
|
70
|
+
if (!is_suspect && head === state.head) return false
|
|
71
|
+
|
|
72
|
+
const current = await source.closure()
|
|
39
73
|
|
|
40
|
-
state
|
|
74
|
+
consume(state, { head, is_suspect }, current)
|
|
41
75
|
|
|
42
|
-
return is_changed(state.loaded,
|
|
76
|
+
return is_changed(state.loaded, current, is_suspect)
|
|
43
77
|
}
|
|
44
78
|
|
|
45
79
|
// The baseline is read when the watch starts — at the supervisor's start, as close to the moment its
|
|
46
|
-
// modules were loaded as a reading can be.
|
|
47
|
-
|
|
48
|
-
|
|
80
|
+
// modules were loaded as a reading can be. `is_suspected` answers, and clears, whether something has
|
|
81
|
+
// seen this code differ from main's since it was last asked (`lane-session-broker.ts`).
|
|
82
|
+
async function watch(
|
|
83
|
+
source: CodeSource,
|
|
84
|
+
is_suspected: () => boolean = () => false,
|
|
85
|
+
): Promise<() => Promise<boolean>> {
|
|
86
|
+
const state: CodeState = {
|
|
87
|
+
head: await source.head(),
|
|
88
|
+
loaded: await source.closure(),
|
|
89
|
+
is_suspect: false,
|
|
90
|
+
}
|
|
49
91
|
|
|
50
|
-
return async () => await is_stale(state, source)
|
|
92
|
+
return async () => await is_stale(state, source, is_suspected)
|
|
51
93
|
}
|
|
52
94
|
|
|
53
95
|
async function read_head(): Promise<string | undefined> {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
|
|
2
|
+
import { MS_PER_MINUTE } from '#scripts/lib/timeouts'
|
|
2
3
|
import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
|
|
3
4
|
import { run_event_setup } from '#scripts/run/event/run-event-setup'
|
|
4
5
|
import { run_invocation } from '#scripts/run/run-invocation'
|
|
@@ -14,7 +15,8 @@ const ERROR_PREFIX = 'error '
|
|
|
14
15
|
// **The driver is bounded so the supervisor reaches its pass boundary between dispatches**
|
|
15
16
|
// (joshuafolkken/kit#4509): unbounded, `backlog:drive` dispatches, collects and merges lanes in its own
|
|
16
17
|
// loop until a judgment hand-off, and the code check at the pass boundary would wait for that.
|
|
17
|
-
const WINDOW_MINUTES =
|
|
18
|
+
const WINDOW_MINUTES = 5
|
|
19
|
+
const WINDOW_MS = WINDOW_MINUTES * MS_PER_MINUTE
|
|
18
20
|
const WINDOW_VERDICT = 'window'
|
|
19
21
|
|
|
20
22
|
function driver_args(invocation: string): ReadonlyArray<string> | undefined {
|
|
@@ -27,7 +29,7 @@ function driver_args(invocation: string): ReadonlyArray<string> | undefined {
|
|
|
27
29
|
'--owner',
|
|
28
30
|
String(process.pid),
|
|
29
31
|
'--window',
|
|
30
|
-
WINDOW_MINUTES,
|
|
32
|
+
String(WINDOW_MINUTES),
|
|
31
33
|
...rebuilt.split(' ').slice(1 + named_count),
|
|
32
34
|
]
|
|
33
35
|
}
|
|
@@ -222,4 +224,4 @@ async function drive(target: string, wait: Pause = pause): Promise<DriveResult>
|
|
|
222
224
|
)
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
export const run_wake_driver = { RERUN_LIMIT, drive, driver_args, driver_result }
|
|
227
|
+
export const run_wake_driver = { RERUN_LIMIT, WINDOW_MS, drive, driver_args, driver_result }
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { telegram_notify } from '#scripts/notify/telegram-notify'
|
|
2
|
+
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
3
|
+
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
4
|
+
import { run_stranded_detect, type WatchedRun } from '#scripts/run/run-stranded-detect'
|
|
5
|
+
import type { WakeStopReason } from './run-wake'
|
|
6
|
+
import type { WakeContext } from './run-wake-describe'
|
|
7
|
+
import { run_wake_loop, type LoopStop } from './run-wake-loop'
|
|
8
|
+
|
|
9
|
+
// How a `run:wake --loop` supervisor ends: which stops reach a person, what the warning says, the
|
|
10
|
+
// `stranded` event it leaves, and the verdict and exit code it ends on. The CLI that runs the loop is
|
|
11
|
+
// `run-wake-cli.ts`.
|
|
12
|
+
|
|
13
|
+
const SUCCESS_EXIT_CODE = 0
|
|
14
|
+
const FAILURE_EXIT_CODE = 1
|
|
15
|
+
|
|
16
|
+
// The command rather than one entry point: the supervisor continues a `queue` as readily as a
|
|
17
|
+
// `backlogrun`, and a warning that named the wrong one would send the reader looking at a run that is
|
|
18
|
+
// not the one that stopped.
|
|
19
|
+
const WARNING_TITLE = 'run:wake supervisor'
|
|
20
|
+
const WARNING_RECOVERY = `Check the wake command, then restart with \`pnpm josh run:wake --start\`.`
|
|
21
|
+
// **The stop reasons that reach a person, and what each one tells them.** A reason mapped to
|
|
22
|
+
// `undefined` is one where nothing went wrong: `ended` is the run finishing through `run:carry --end`,
|
|
23
|
+
// `stopped` is a person's own `--stop` or a supervisor being superseded, and a warning channel that
|
|
24
|
+
// fired on those is one that stops being read.
|
|
25
|
+
//
|
|
26
|
+
// **`unreadable` warns**, because it ends the supervisor while a carry record is still sitting there
|
|
27
|
+
// handed off, so the run would otherwise be left asleep with nothing anywhere saying so.
|
|
28
|
+
// **Every reason is listed, and the silent two are listed as `undefined`.** A partial map would let a
|
|
29
|
+
// fifth stop reason compile straight past this and reach nobody, which is the defect class this
|
|
30
|
+
// section exists to close.
|
|
31
|
+
const STOP_BODIES: Record<WakeStopReason, string | undefined> = {
|
|
32
|
+
ended: undefined,
|
|
33
|
+
stopped: undefined,
|
|
34
|
+
reload: undefined,
|
|
35
|
+
failed: 'The supervisor could not continue the run and has stopped.',
|
|
36
|
+
unreadable:
|
|
37
|
+
'The carry record could not be read, so the supervisor stopped rather than guess at it.',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The log is named in the warning rather than only in `--list`, because the person reading a warning
|
|
41
|
+
// at three in the morning is the one who needs to know where to look next.
|
|
42
|
+
function log_hint(context: WakeContext): string {
|
|
43
|
+
return `The output of every session this supervisor started is in ${context.log_target}.`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The log hint goes ahead of the note: the Telegram length limit cuts the tail, and a long failure note
|
|
47
|
+
// (stdout tail plus stderr tail) would otherwise take the one line that says where to look next.
|
|
48
|
+
function warning_body(stop: LoopStop, body: string, context: WakeContext): string {
|
|
49
|
+
return [body, log_hint(context), stop.note].filter(Boolean).join('\n')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Whether the warning reached a person — the answer `record_failure` leaves its marker on.
|
|
53
|
+
async function warn_of_stop(stop: LoopStop, body: string, context: WakeContext): Promise<boolean> {
|
|
54
|
+
return await telegram_notify.warn({
|
|
55
|
+
issue_title: WARNING_TITLE,
|
|
56
|
+
body: warning_body(stop, body, context),
|
|
57
|
+
recovery: WARNING_RECOVERY,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// **A failed supervisor that leaves the run stranded says so on the stream, with its cause.** Nothing
|
|
62
|
+
// else writes there when the supervisor gives up, so a reader of the events would see the last merge
|
|
63
|
+
// and a run that looks as if it were still watching. The cause is the note's first line — the
|
|
64
|
+
// driver's own `error …` — because the rest is a stderr tail the log already holds.
|
|
65
|
+
//
|
|
66
|
+
// **Only a strand, and only one a person was told of.** A `failed` stop is not always a strand: a
|
|
67
|
+
// `run:carry --end` under a live driver fails it too, and that run ended cleanly. And the event is the
|
|
68
|
+
// marker `run_stranded_detect` holds its own notification back on, so it is left only when the warning
|
|
69
|
+
// was sent — after a failed send the stop-time detector still finds no marker, and notifies.
|
|
70
|
+
function is_strand_told(stop: LoopStop, watched: WatchedRun, is_warned: boolean): boolean {
|
|
71
|
+
if (!is_warned || stop.reason !== run_wake_loop.FAILED_REASON) return false
|
|
72
|
+
|
|
73
|
+
return run_stranded_detect.is_left_stranded(watched)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function record_failure(
|
|
77
|
+
stop: LoopStop,
|
|
78
|
+
watched: WatchedRun,
|
|
79
|
+
is_warned: boolean,
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
if (!is_strand_told(stop, watched, is_warned)) return
|
|
82
|
+
|
|
83
|
+
const [cause = ''] = (stop.note ?? '').split('\n', 1)
|
|
84
|
+
|
|
85
|
+
await run_event_stream_emit.emit(
|
|
86
|
+
run_event_stream.EVENT_KIND.STRANDED,
|
|
87
|
+
`The supervisor failed and stopped watching the run — ${cause}`,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// **Only a failed wake exits non-zero; `unreadable` warns but exits zero.** The
|
|
92
|
+
// verdict-and-exit-code table in `docs/josh-commands-run.md` → `run:wake` is a contract callers branch on,
|
|
93
|
+
// and what that one needs is the notification, not a different exit code.
|
|
94
|
+
function exit_code_of(reason: WakeStopReason): number {
|
|
95
|
+
return reason === run_wake_loop.FAILED_REASON ? FAILURE_EXIT_CODE : SUCCESS_EXIT_CODE
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Which stops reach a person, and what each one says — `undefined` for the two where nothing went
|
|
99
|
+
// wrong. It is a function rather than a bare lookup so the decision can be asserted for every reason
|
|
100
|
+
// the loop can end on.
|
|
101
|
+
function stop_body(reason: WakeStopReason): string | undefined {
|
|
102
|
+
return STOP_BODIES[reason]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// `started_at` is the run this supervisor took on — what `record_failure` tells an ended run by once
|
|
106
|
+
// the carry record that held it no longer reads.
|
|
107
|
+
async function warn_and_record(
|
|
108
|
+
stop: LoopStop,
|
|
109
|
+
body: string,
|
|
110
|
+
context: WakeContext,
|
|
111
|
+
started_at: string,
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
console.error(stop.note ?? body)
|
|
114
|
+
const is_warned = await warn_of_stop(stop, body, context)
|
|
115
|
+
|
|
116
|
+
await record_failure(stop, { ...context, started_at }, is_warned)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A failure that reached nobody is the defect, so the Telegram goes out before the verdict is printed.
|
|
120
|
+
async function finish(stop: LoopStop, context: WakeContext, started_at: string): Promise<number> {
|
|
121
|
+
const body = stop_body(stop.reason)
|
|
122
|
+
|
|
123
|
+
if (body !== undefined) await warn_and_record(stop, body, context, started_at)
|
|
124
|
+
|
|
125
|
+
console.info(stop.reason)
|
|
126
|
+
|
|
127
|
+
return exit_code_of(stop.reason)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const run_wake_finish = {
|
|
131
|
+
finish,
|
|
132
|
+
record_failure,
|
|
133
|
+
stop_body,
|
|
134
|
+
warning_body,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { run_wake_finish }
|
|
@@ -50,7 +50,8 @@ interface LoopPorts {
|
|
|
50
50
|
wake: (invocation: string, session_id: string, material: string) => LaunchResult
|
|
51
51
|
sleep: (milliseconds: number) => Promise<void>
|
|
52
52
|
now: () => Date
|
|
53
|
-
// Closes again a lane a process outlived, once the process is gone (`lane_close.retry_closes`)
|
|
53
|
+
// Closes again a lane a process outlived, once the process is gone (`lane_close.retry_closes`), and
|
|
54
|
+
// withdraws a lane whose issue was closed as not planned (`lane_withdraw_closed.withdraw_closed`).
|
|
54
55
|
retry_closes: () => Promise<void>
|
|
55
56
|
// Whether the code this process loaded differs from the code on disk now (`run-wake-code.ts`).
|
|
56
57
|
is_code_stale: () => Promise<boolean>
|
|
@@ -159,8 +160,9 @@ async function decide_pass(wake: RunWake, ports: LoopPorts): Promise<WakeDecisio
|
|
|
159
160
|
return run_wake.decide({ ...input, has_work: await ports.has_work(input.read) })
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
// A lane whose close a process outlived ends once the process is gone (joshuafolkken/kit#4300)
|
|
163
|
-
//
|
|
163
|
+
// A lane whose close a process outlived ends once the process is gone (joshuafolkken/kit#4300), and
|
|
164
|
+
// a lane whose issue was closed as not planned is withdrawn (joshuafolkken/kit#4915). A failed retry
|
|
165
|
+
// is the next pass's to try again, never a reason to stop the run.
|
|
164
166
|
async function retry_closes(ports: LoopPorts): Promise<void> {
|
|
165
167
|
try {
|
|
166
168
|
await ports.retry_closes()
|