@joshuafolkken/kit 1.1178.0 → 1.1240.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/.claude-plugin/plugin.json +1 -1
- package/.claude/settings.json +2 -2
- package/.claude/skills/dependency-update/SKILL.md +4 -8
- package/.claude/skills/epic-commands/SKILL.md +2 -2
- package/.claude/skills/epic-commands/epic-bundle.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +2 -4
- package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +7 -3
- package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
- package/.claude/skills/workflow-commands/backlogrun-progress.md +9 -4
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
- package/.claude/skills/workflow-commands/chain-rule.md +1 -1
- package/.claude/skills/workflow-commands/delegation.md +2 -2
- package/.claude/skills/workflow-commands/followup.md +10 -0
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-scout.md +1 -1
- package/.claude/skills/workflow-commands/kickoff.md +2 -1
- package/.claude/skills/workflow-commands/latest-gate.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
- package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
- package/.claude/skills/workflow-commands/prerequisite.md +2 -2
- package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
- package/.codex/hooks.json +2 -2
- package/.cursorrules +6 -41
- package/.github/actions/setup-node/action.yml +1 -1
- package/.github/workflows/auto-tag.yml +3 -4
- package/.github/workflows/ci.yml +32 -31
- package/.github/workflows/dependabot-auto-merge.yml +13 -6
- package/.github/workflows/publish.yml +50 -4
- package/.github/workflows/sonar-qube.yml +3 -0
- package/.pnpmfile.mjs +4 -3
- package/.prettierignore +8 -2
- package/CLAUDE.md +18 -20
- package/README.md +44 -38
- package/SECURITY.md +15 -1
- package/dist/CLAUDE.md +18 -20
- package/dist/commands/document-section-cli.js +734 -0
- package/dist/hooks/audit-provision.js +346 -0
- package/dist/hooks/batch-guard.js +5 -4
- package/dist/hooks/{chunk-G6KRCKEC.js → chunk-4TZVB44A.js} +496 -378
- package/dist/hooks/chunk-5VNDSNG3.js +12 -0
- package/dist/hooks/chunk-CDY4NFQG.js +15 -0
- package/dist/hooks/chunk-ELCN5LCH.js +135 -0
- package/dist/hooks/{chunk-VQLLNFNT.js → chunk-F3ELQ2AF.js} +3 -3
- package/dist/hooks/{chunk-I4XSL4TT.js → chunk-GPTFMK5E.js} +1 -1
- package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
- package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-KHGB5S3Y.js} +7 -6
- package/dist/hooks/{chunk-GDS5N2KB.js → chunk-KHX4U3BG.js} +735 -683
- package/dist/hooks/{chunk-VESKRQOF.js → chunk-KRBERBBI.js} +3 -3
- package/dist/hooks/{chunk-DO6NWWHU.js → chunk-QVSWU24O.js} +4 -4
- package/dist/hooks/{chunk-S6X6WYKR.js → chunk-WK2XSHSI.js} +1643 -923
- package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-XZYKEM6J.js} +25 -10
- package/dist/hooks/{chunk-32GQTL4F.js → chunk-ZQGD4GBY.js} +4 -2
- package/dist/hooks/codex-hook-adapter.js +14 -12
- package/dist/hooks/duplicate-read-guard.js +6 -5
- package/dist/hooks/format-edited.js +4 -3
- package/dist/hooks/inputs.json +203 -179
- package/dist/hooks/investigation-guard.js +6 -5
- package/dist/hooks/pretool-guard.js +12 -10
- package/dist/hooks/session-lang.js +2 -4
- package/dist/hooks/stop-guard.js +66 -72
- package/dist/josh.js +1088 -78
- package/dist/version/index.d.ts +7 -2
- package/dist/version/index.js +152 -75
- package/eslint/base.js +23 -1
- package/eslint/rules/code-quality.js +3 -0
- package/eslint/rules/early-return-one-liner.js +98 -0
- package/eslint/rules/unicorn.js +16 -1
- package/package.json +7 -6
- package/pnpm-workspace.yaml +9 -4
- package/prettier/basic.d.ts +3 -1
- package/prettier/index.d.ts +3 -1
- package/prettier/static.d.ts +3 -1
- package/prompts/coding-standards.md +1 -1
- package/prompts/collaboration-workflow/file-edits.md +4 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/issue-template.md +1 -0
- package/prompts/collaboration-workflow/operating-rules.md +17 -9
- package/prompts/collaboration-workflow/overview.md +2 -2
- package/prompts/collaboration-workflow/principles.md +14 -12
- package/prompts/collaboration-workflow/report-format.md +1 -1
- package/prompts/collaboration-workflow/residency.md +1 -1
- package/prompts/collaboration-workflow/rule-delivery.md +6 -6
- package/prompts/collaboration-workflow/shell-body.md +6 -6
- package/prompts/collaboration-workflow/turn-batching.md +1 -1
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
- package/prompts/collaboration-workflow/wip-cap.md +10 -10
- package/prompts/review-rubric.md +1 -2
- package/prompts/review.md +1 -1
- package/scripts/agent/agent-headless.ts +29 -0
- package/scripts/agent/agent-launch-environment.ts +6 -2
- package/scripts/agent/codex-agent-argv.ts +2 -1
- package/scripts/auto-ok/auto-ok-cli.ts +1 -1
- package/scripts/backlog/backlog-arrival.ts +3 -3
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-args.ts +135 -0
- package/scripts/backlog/backlog-drive-cli.ts +11 -137
- package/scripts/backlog/backlog-drive-launch.ts +39 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
- package/scripts/backlog/backlog-drive-named.ts +3 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
- package/scripts/backlog/backlog-drive-owner.ts +1 -1
- package/scripts/backlog/backlog-drive-restore.ts +1 -1
- package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
- package/scripts/backlog/backlog-drive.ts +1 -1
- package/scripts/backlog/backlog-next.ts +16 -5
- package/scripts/backlog/backlog-offer-cli.ts +6 -6
- package/scripts/backlog/backlog-offer.ts +1 -1
- package/scripts/backlog/backlog-plan-cli.ts +1 -1
- package/scripts/backlog/backlog-pool.ts +57 -11
- package/scripts/backlog/backlog-ready.ts +1 -1
- package/scripts/backlog/backlog-scope.ts +1 -1
- package/scripts/backlog/backlog-stalled-detect.ts +2 -2
- package/scripts/backlog/backlog-stalled.ts +1 -1
- package/scripts/build/build-codex-hooks.ts +3 -1
- package/scripts/build/build-commands.ts +66 -0
- package/scripts/build/build-hooks.ts +2 -1
- package/scripts/build/build-library.ts +12 -3
- package/scripts/build/build.ts +70 -0
- package/scripts/build/local-bin.ts +3 -13
- package/scripts/delegation/investigation-reads.ts +3 -1
- package/scripts/discovery/repo-origin.ts +5 -5
- package/scripts/doctor/doctor-consumer.ts +2 -2
- package/scripts/doctor/doctor-io.ts +2 -3
- package/scripts/document/capped-print-part.ts +30 -0
- package/scripts/document/capped-print.ts +3 -4
- package/scripts/document/document-byte-budget.ts +11 -2
- package/scripts/document/document-scan.ts +117 -0
- package/scripts/document/document-section.ts +143 -16
- package/scripts/document/entry-read-budget.ts +5 -3
- package/scripts/document/entry-read-set.ts +10 -89
- package/scripts/document/read-set-cost.ts +98 -0
- package/scripts/epic/epic-add-body.ts +1 -0
- package/scripts/epic/epic-audit-checks.ts +1 -1
- package/scripts/epic/epic-audit-rationale.ts +4 -3
- package/scripts/epic/epic-bundle-cli.ts +14 -3
- package/scripts/epic/epic-bundle-poll.ts +41 -0
- package/scripts/epic/epic-busy.ts +1 -1
- package/scripts/epic/epic-candidate-confirm.ts +3 -2
- package/scripts/epic/epic-classify.ts +1 -1
- package/scripts/epic/epic-cli-argv.ts +78 -46
- package/scripts/epic/epic-cli-remove.ts +11 -19
- package/scripts/epic/epic-cli.ts +74 -73
- package/scripts/epic/epic-close-comment.ts +1 -1
- package/scripts/epic/epic-close.ts +1 -1
- package/scripts/epic/epic-cross-repo.ts +5 -5
- package/scripts/epic/epic-graph.ts +1 -1
- package/scripts/epic/epic-index.ts +1 -1
- package/scripts/epic/epic-issue.ts +2 -6
- package/scripts/epic/epic-lane-offer.ts +2 -2
- package/scripts/epic/epic-next.ts +29 -42
- package/scripts/epic/epic-solo-stale.ts +1 -1
- package/scripts/epic/epic-triage.ts +8 -4
- package/scripts/epic/epic-validate.ts +1 -1
- package/scripts/followup/git-followup-finish.ts +2 -2
- package/scripts/followup/git-followup-label.ts +2 -2
- package/scripts/followup/git-followup-merged.ts +2 -2
- package/scripts/followup/git-pr-followup-checks.ts +142 -0
- package/scripts/followup/git-pr-followup.ts +4 -129
- package/scripts/gate/core-admission.ts +162 -0
- package/scripts/gate/core-budget.ts +190 -68
- package/scripts/gate/gate-plan.ts +80 -7
- package/scripts/gate/gate-report.ts +28 -9
- package/scripts/gate/gate-skip.ts +38 -7
- package/scripts/gate/hook-gate-reuse.ts +12 -10
- package/scripts/gate/machine-capacity.ts +175 -0
- package/scripts/gate/pre-commit-type-check.ts +4 -1
- package/scripts/gate/project-checks.ts +6 -9
- package/scripts/gate/type-check-step.ts +2 -3
- package/scripts/gate/verification-gate.ts +34 -10
- package/scripts/gh/git-gh-api-path.ts +2 -2
- package/scripts/gh/git-gh-issue-rest.ts +3 -3
- package/scripts/gh/git-gh-issue-write.ts +53 -7
- package/scripts/gh/git-gh-pr-read.ts +64 -19
- package/scripts/gh/git-gh-pr-rest.ts +1 -1
- package/scripts/gh/git-gh-pr.ts +5 -1
- package/scripts/gh/git-pr-ai-review.ts +1 -1
- package/scripts/gh/git-pr-checks-eval.ts +1 -3
- package/scripts/gh/git-pr-checks-parse.ts +1 -1
- package/scripts/gh/git-pr-checks-watch.ts +6 -5
- package/scripts/gh/git-pr-checks.ts +7 -5
- package/scripts/gh/git-pr-coderabbit.ts +1 -1
- package/scripts/gh/git-pr-error.ts +1 -3
- package/scripts/gh/git-pr.ts +9 -13
- package/scripts/gh/github-issue-url.ts +2 -2
- package/scripts/git/constants.ts +2 -6
- package/scripts/git/git-command.ts +20 -218
- package/scripts/git/git-diff-reads.ts +225 -0
- package/scripts/git/git-error.ts +4 -12
- package/scripts/git/git-pre-push-hook.ts +139 -0
- package/scripts/git/git-prompt.ts +2 -6
- package/scripts/git/git-spawn-sync.ts +3 -1
- package/scripts/git/git-spawn.ts +1 -1
- package/scripts/git/git-staging.ts +4 -6
- package/scripts/git/git-status.ts +3 -9
- package/scripts/hooks/codex-hook-adapter.ts +1 -1
- package/scripts/hooks/edited-cspell.ts +2 -4
- package/scripts/hooks/format-edited-file.ts +3 -12
- package/scripts/hooks/pre-push-unit.ts +31 -9
- package/scripts/hooks/reserved-run.ts +2 -2
- package/scripts/hooks/run-hook.sh +5 -4
- package/scripts/hooks/stop-guard.ts +5 -3
- package/scripts/init/init-actions.ts +0 -5
- package/scripts/init/init-ai-copy.ts +13 -2
- package/scripts/init/init-logic-json-merge.ts +19 -0
- package/scripts/init/init-logic-secretlint.ts +7 -7
- package/scripts/init/init-logic-workspace.ts +41 -2
- package/scripts/init/init-logic.ts +16 -26
- package/scripts/init/init.ts +10 -16
- package/scripts/init/initial-commit.ts +4 -4
- package/scripts/init/kit-development-versions.ts +40 -0
- package/scripts/init/kit-setup-state.ts +2 -2
- package/scripts/init/package-manager-pin.ts +35 -0
- package/scripts/init/start-exec.ts +68 -19
- package/scripts/init/start-setup-pr.ts +9 -9
- package/scripts/init/start-steps.ts +6 -10
- package/scripts/issue/defect-rate-cli.ts +1 -1
- package/scripts/issue/git-next-issues.ts +1 -1
- package/scripts/issue/issue-auto-ok.ts +32 -7
- package/scripts/issue/issue-backlinks-cli.ts +2 -2
- package/scripts/issue/issue-backlinks.ts +9 -9
- package/scripts/issue/issue-comment-cli.ts +19 -13
- package/scripts/issue/issue-file-cli.ts +72 -22
- package/scripts/issue/issue-file.ts +29 -1
- package/scripts/issue/issue-labels.ts +13 -0
- package/scripts/issue/issue-release-cli.ts +155 -0
- package/scripts/issue/issue-release.ts +58 -0
- package/scripts/issue/issue-scout-closed.ts +1 -1
- package/scripts/issue/issue-state-cli.ts +30 -74
- package/scripts/josh/josh-command-map.ts +2 -0
- package/scripts/josh/josh-command-types.ts +14 -0
- package/scripts/josh/josh-commands-ai.ts +4 -4
- package/scripts/josh/josh-commands-backlog.ts +4 -1
- package/scripts/josh/josh-commands-development.ts +18 -0
- package/scripts/josh/josh-commands-document.ts +1 -0
- package/scripts/josh/josh-commands-issue.ts +8 -1
- package/scripts/josh/josh-commands-lane.ts +24 -1
- package/scripts/josh/josh-commands-maintenance.ts +6 -6
- package/scripts/josh/josh-commands-run.ts +12 -7
- package/scripts/josh/josh-in-process.ts +28 -9
- package/scripts/josh/josh-logic.ts +34 -15
- package/scripts/josh/process-identity.ts +1 -3
- package/scripts/josh/session-language-cli.ts +3 -4
- package/scripts/lane/lane-await.ts +5 -1
- package/scripts/lane/lane-child-invocation.ts +2 -2
- package/scripts/lane/lane-cli.ts +12 -4
- package/scripts/lane/lane-dispatch.ts +14 -10
- package/scripts/lane/lane-environment.ts +5 -1
- package/scripts/lane/lane-handoff.ts +1 -1
- package/scripts/lane/lane-install.ts +2 -13
- package/scripts/lane/lane-launch-cli.ts +15 -1
- package/scripts/lane/lane-ledger.ts +145 -0
- package/scripts/lane/lane-leftover.ts +44 -30
- package/scripts/lane/lane-load.ts +108 -0
- package/scripts/lane/lane-measure-cli.ts +149 -0
- package/scripts/lane/lane-occupancy.ts +3 -0
- package/scripts/lane/lane-open.ts +5 -5
- package/scripts/lane/lane-paths.ts +13 -0
- package/scripts/lane/lane-reap.ts +1 -3
- package/scripts/lane/lane-registry.ts +11 -0
- package/scripts/lane/lane-relaunch.ts +1 -1
- package/scripts/lane/lane-stats.ts +224 -0
- package/scripts/lane/lane-stray.ts +140 -0
- package/scripts/lane/lane-vacant.ts +36 -0
- package/scripts/lane/openai-lane-ship.ts +2 -2
- package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
- package/scripts/lane/openai-lane-supervisor.ts +7 -165
- package/scripts/lane/openai-review-broker.ts +2 -4
- package/scripts/lib/ancestor-directories.ts +35 -0
- package/scripts/lib/animation-helpers.ts +2 -2
- package/scripts/lib/buffered-process.ts +3 -7
- package/scripts/lib/cli-flags.ts +71 -1
- package/scripts/lib/spawn-exit.ts +2 -2
- package/scripts/lib/timeouts.ts +35 -2
- package/scripts/lines/line-budget.ts +4 -6
- package/scripts/notify/telegram-test.ts +6 -0
- package/scripts/observations/observation-ledger-line.ts +6 -6
- package/scripts/observations/observation-ledger-migrate.ts +55 -4
- package/scripts/observations/observation-ledger.ts +26 -7
- package/scripts/overrides/lockfile-overrides.ts +1 -1
- package/scripts/overrides/overrides-logic.ts +1 -1
- package/scripts/propagate/propagate-git.ts +1 -1
- package/scripts/propagate/propagate-publish.ts +2 -2
- package/scripts/propagate/propagate-steps.ts +20 -15
- package/scripts/propagate/propagate.ts +36 -27
- package/scripts/refactor/import-graph.ts +18 -1
- package/scripts/refactor/refactor-lint.ts +4 -4
- package/scripts/release/github-release-environment.ts +1 -0
- package/scripts/release/github-release-runs.ts +115 -0
- package/scripts/release/github-release.ts +13 -67
- package/scripts/repo/repository-labels.ts +2 -2
- package/scripts/repo/required-checks-logic.ts +1 -3
- package/scripts/review/live-evidence.ts +2 -4
- package/scripts/review/review-brief-cli.ts +1 -1
- package/scripts/review/review-finding-ledger.ts +1 -1
- package/scripts/review/review-findings-cli.ts +1 -3
- package/scripts/rules/decision-oracle-batch.ts +128 -0
- package/scripts/rules/decision-oracle-tokens.ts +21 -0
- package/scripts/rules/decision-oracle.ts +8 -120
- package/scripts/rules/delivered-rules.ts +26 -182
- package/scripts/rules/direct-filing.ts +1 -1
- package/scripts/rules/early-heartbeat.ts +3 -3
- package/scripts/rules/git-argv.ts +1 -1
- package/scripts/rules/git-force.ts +1 -1
- package/scripts/rules/implementation-cut.ts +2 -2
- package/scripts/rules/index-guard.ts +1 -1
- package/scripts/rules/issue-comments.ts +118 -0
- package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
- package/scripts/rules/lane-background.ts +2 -2
- package/scripts/rules/lane-park.ts +18 -0
- package/scripts/rules/lane-split-park.ts +128 -0
- package/scripts/rules/oracle-firing.ts +1 -1
- package/scripts/rules/poll-loop.ts +1 -1
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/rules/protected-files.ts +59 -4
- package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
- package/scripts/rules/shell-body-trigger.ts +1 -1
- package/scripts/rules/stop-rules.ts +18 -7
- package/scripts/rules/test-declared-commit.ts +4 -4
- package/scripts/rules/wip-cap.ts +63 -0
- package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
- package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
- package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
- package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
- package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
- package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +18 -5
- package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
- package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
- package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
- package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +1 -0
- package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
- package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
- package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +20 -2
- package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
- package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
- package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
- package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
- package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
- package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
- package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
- package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
- package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
- package/scripts/run/run-ending.ts +2 -2
- package/scripts/run/run-halfrun-resume.ts +6 -1
- package/scripts/run/run-headless.ts +1 -1
- package/scripts/run/run-invocation.ts +85 -84
- package/scripts/run/run-liveness.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +1 -1
- package/scripts/run/run-preflight.ts +6 -6
- package/scripts/run/run-prep-cli.ts +1 -1
- package/scripts/run/run-prrun-resume.ts +1 -1
- package/scripts/run/run-report-cli.ts +4 -4
- package/scripts/run/run-report.ts +2 -2
- package/scripts/run/run-status-cli.ts +1 -1
- package/scripts/run/run-step-cli.ts +3 -3
- package/scripts/run/run-step.ts +7 -4
- package/scripts/run/run-stop-notify.ts +1 -1
- package/scripts/run/run-stranded-detect.ts +4 -4
- package/scripts/run/run-stranded.ts +2 -2
- package/scripts/run/run-watcher-guard-cli.ts +1 -1
- package/scripts/run/run-watcher-guard.ts +5 -3
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
- package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
- package/scripts/run/ship/run-ship-lock.ts +50 -0
- package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
- package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
- package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
- package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
- package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
- package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
- package/scripts/run/ship/run-ship-stop-text.ts +13 -0
- package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
- package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
- package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +25 -11
- package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +58 -7
- package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
- package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
- package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +13 -15
- package/scripts/run/wake/run-wake-restart.ts +58 -0
- package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
- package/scripts/run/{run-wake.ts → wake/run-wake.ts} +16 -65
- package/scripts/safe-chain/preinstall-command.ts +20 -21
- package/scripts/safe-chain/project-config.ts +19 -15
- package/scripts/sync/sync-ai-files.ts +1 -1
- package/scripts/sync/sync-configs.ts +2 -1
- package/scripts/sync/sync.ts +15 -2
- package/scripts/test/unit-worker-share.ts +1 -0
- package/scripts/time-runtime/time-background.ts +5 -1
- package/scripts/time-runtime/time-instant.ts +3 -1
- package/scripts/time-runtime/time-spans.ts +6 -139
- package/scripts/time-runtime/time-tool-call.ts +160 -0
- package/scripts/version/effective-upstream.ts +2 -20
- package/scripts/version/latest-corepack.ts +124 -93
- package/scripts/version/latest-regression.ts +1 -1
- package/scripts/version/latest-update.ts +4 -4
- package/scripts/version/npm-registry.ts +20 -5
- package/scripts/version/publishable-range-check.ts +2 -2
- package/scripts/version/publishable-range.ts +1 -1
- package/scripts/version/release-age.ts +48 -38
- package/scripts/version/release-hold.ts +1 -1
- package/scripts/version/safe-chain-version-update.ts +40 -0
- package/scripts/version/upgrade-shell-command.ts +48 -8
- package/scripts/version/version-check-logic.ts +23 -14
- package/scripts/version/version-commands.ts +23 -11
- package/scripts/version/version-remote.ts +28 -13
- package/templates/pnpm-workspace.basic.yaml +4 -0
- package/templates/workflows/dependabot-auto-merge.yml +5 -5
- package/templates/workflows/github-release.yml +6 -0
- package/scripts/build/build-config-merge.ts +0 -27
- package/scripts/build/build-managed-marker.ts +0 -27
- package/scripts/build/build-self-sync-guard.ts +0 -27
- package/scripts/build/build-version.ts +0 -26
- package/scripts/version/preinstall-version-update.ts +0 -86
- /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
- /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
- /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
- /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
- /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
- /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
- /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
- /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
- /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
- /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
- /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
- /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
- /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
- /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
- /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
- /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
- /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
- /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
- /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
- /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
- /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
- /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
- /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
- /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
- /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
|
@@ -6,7 +6,7 @@ import { stamp_file } from '#scripts/josh/stamp-file'
|
|
|
6
6
|
import { lane_child_marker } from '#scripts/lane/lane-child-marker'
|
|
7
7
|
import { openai_review_broker } from '#scripts/lane/openai-review-broker'
|
|
8
8
|
import { review_record } from '#scripts/review/review-record'
|
|
9
|
-
import { detached_launch } from '
|
|
9
|
+
import { detached_launch } from '#scripts/run/detached-launch'
|
|
10
10
|
import { run_ship_review, type RoundOutcome, type ScoredVerdict } from './run-ship-review'
|
|
11
11
|
import { run_ship_scoped, type Phase } from './run-ship-scoped'
|
|
12
12
|
|
|
@@ -59,8 +59,8 @@ function failure(out: string): JoshResult {
|
|
|
59
59
|
return { code: FAILURE_EXIT_CODE, out }
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function paths(
|
|
63
|
-
return openai_review_broker.review_paths(
|
|
62
|
+
function paths(): { brief: string; findings: string; log: string } {
|
|
63
|
+
return openai_review_broker.review_paths(PROJECT_ROOT)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
async function josh(argv: ReadonlyArray<string>): Promise<JoshResult> {
|
|
@@ -39,7 +39,16 @@ async function scoped_pair(): Promise<JoshResult> {
|
|
|
39
39
|
)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
// The scoped pair and then `josh gate` — the gate stage, and the followup's gate over a merged tree
|
|
43
|
+
// before its push (joshuafolkken/kit#3307), so both leave the record the pre-push hook reuses.
|
|
44
|
+
async function scoped_gate(): Promise<JoshResult> {
|
|
45
|
+
return await run_phases([
|
|
46
|
+
scoped_pair,
|
|
47
|
+
async () => await josh_command.josh_run(['gate'], should_forward_stderr),
|
|
48
|
+
])
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const run_ship_scoped = { run_phases, scoped_gate, scoped_pair }
|
|
43
52
|
|
|
44
53
|
export type { Phase }
|
|
45
54
|
export { run_ship_scoped }
|
|
@@ -48,22 +48,20 @@ const PREFLIGHT_STEP: Step = {
|
|
|
48
48
|
// with no green record (joshuafolkken/kit#2946). The commit step carries the `--skip-*` flags a resumed
|
|
49
49
|
// ship needs, so an existing commit or push is never made twice.
|
|
50
50
|
const COMMIT_STEPS: ReadonlyArray<Step> = [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await run_ship_scoped.run_phases([
|
|
56
|
-
run_ship_scoped.scoped_pair,
|
|
57
|
-
async () => await josh(['gate']),
|
|
58
|
-
]),
|
|
59
|
-
},
|
|
60
|
-
// joshuafolkken/kit#3221: the default branch merged again right before the commit, so the pull
|
|
61
|
-
// request opens on a branch that is current; a clean merge goes on without waking anyone.
|
|
51
|
+
// joshuafolkken/kit#3221: the default branch merged again before the commit, so the pull request
|
|
52
|
+
// opens on a branch that is current; a clean merge goes on without waking anyone. It runs before the
|
|
53
|
+
// gate (joshuafolkken/kit#3307): a merge after it moved the merge base the gate's record pins, so the
|
|
54
|
+
// pre-push hook could never reuse that record and re-ran the whole unit suite on every merged ship.
|
|
62
55
|
{
|
|
63
56
|
stage: STAGE.SYNC,
|
|
64
57
|
header: run_ship.SYNC_HEADER,
|
|
65
58
|
run: async () => await run_ship_sync.sync_stage(),
|
|
66
59
|
},
|
|
60
|
+
{
|
|
61
|
+
stage: STAGE.GATE,
|
|
62
|
+
header: run_ship.GATE_HEADER,
|
|
63
|
+
run: async () => await run_ship_scoped.scoped_gate(),
|
|
64
|
+
},
|
|
67
65
|
{
|
|
68
66
|
stage: STAGE.COMMIT,
|
|
69
67
|
header: run_ship.COMMIT_HEADER,
|
|
@@ -87,7 +85,8 @@ const MERGE_STEPS: ReadonlyArray<Step> = [
|
|
|
87
85
|
]
|
|
88
86
|
|
|
89
87
|
// `--review` (joshuafolkken/kit#2427) puts the supervised round-1 review in front of the gate: it
|
|
90
|
-
// launches the gate itself, so the gate stage that follows reuses that tree's green record
|
|
88
|
+
// launches the gate itself, so the gate stage that follows reuses that tree's green record unless the
|
|
89
|
+
// sync stage between them merged the default branch and changed the tree. It also
|
|
91
90
|
// puts the round-2 pass between the commit and the followup (joshuafolkken/kit#2489), so the PR opens
|
|
92
91
|
// between the rounds and round 2 runs beside CI — a no-op when round 1 left no fix delta.
|
|
93
92
|
const REVIEW_STEP: Step = {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The text a `josh ship` supervisor records when a stage stops, written and read in one place so the
|
|
2
|
+
// retrospective's per-stage count never drifts from the writer (joshuafolkken/kit#3245).
|
|
3
|
+
const STOP_PATTERN = /^#\d+ (\S+) failed — /u
|
|
4
|
+
|
|
5
|
+
function format(issue: string, reason: string): string {
|
|
6
|
+
return `#${issue} ${reason} failed — pnpm josh ship --log ${issue}`
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function reason_of(text: string): string | undefined {
|
|
10
|
+
return STOP_PATTERN.exec(text)?.[1]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const run_ship_stop_text = { format, reason_of }
|
|
@@ -26,6 +26,7 @@ const MAX_MERGE_RETRIES = 2
|
|
|
26
26
|
const CONFLICT_NOTE = 'merging the default branch left unmerged paths:'
|
|
27
27
|
const DEFERRED_PREFIX = 'merge deferred to the followup: '
|
|
28
28
|
const CURRENT_SUFFIX = ' brings nothing in'
|
|
29
|
+
const MERGED_PREFIX = 'merged '
|
|
29
30
|
|
|
30
31
|
// A stage's result, plus the paths a stopped merge left unmerged — carried to the stop prompt.
|
|
31
32
|
interface StepResult extends JoshResult {
|
|
@@ -52,21 +53,24 @@ function conflict_result(files: ReadonlyArray<string>, lead: string): StepResult
|
|
|
52
53
|
return { code: FAILURE_EXIT_CODE, out, conflicts: files }
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
function sync_note(outcome: Exclude<MergeOutcome, { kind: 'conflict' }>): string {
|
|
57
|
+
if (outcome.kind === 'refused') return `${DEFERRED_PREFIX}${outcome.message}`
|
|
58
|
+
|
|
59
|
+
return outcome.kind === 'current'
|
|
60
|
+
? `${outcome.branch}${CURRENT_SUFFIX}`
|
|
61
|
+
: `${MERGED_PREFIX}${outcome.branch}`
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
// A refusal does not stop the commit: the guard refuses only uncommitted work the default branch also
|
|
56
65
|
// touches, and once that work is committed the followup merges again on a clean tree. A merge that changed
|
|
57
|
-
// the tree
|
|
66
|
+
// the tree is checked by the gate stage that follows (joshuafolkken/kit#3307), so the commit never carries
|
|
67
|
+
// an unchecked merge and the gate's record names the merge base the push carries.
|
|
58
68
|
async function sync_stage(): Promise<StepResult> {
|
|
59
69
|
const outcome = await merge_outcome()
|
|
60
70
|
|
|
61
71
|
if (outcome.kind === 'conflict') return conflict_result(outcome.files, '')
|
|
62
|
-
if (outcome.kind === 'merged') return await run_ship_scoped.scoped_pair()
|
|
63
|
-
|
|
64
|
-
const out =
|
|
65
|
-
outcome.kind === 'current'
|
|
66
|
-
? `${outcome.branch}${CURRENT_SUFFIX}`
|
|
67
|
-
: `${DEFERRED_PREFIX}${outcome.message}`
|
|
68
72
|
|
|
69
|
-
return { code: SUCCESS_EXIT_CODE, out }
|
|
73
|
+
return { code: SUCCESS_EXIT_CODE, out: sync_note(outcome) }
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
// Any read that fails answers "not conflicting": the followup failure then stands as it was.
|
|
@@ -80,6 +84,18 @@ async function is_conflicting(): Promise<boolean> {
|
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
// The merged tree is gated before it is pushed (joshuafolkken/kit#3307): the push then carries the tree
|
|
88
|
+
// the gate's record describes, so the pre-push hook reuses it rather than re-running the unit suite.
|
|
89
|
+
async function push_merged(title: string): Promise<StepResult | undefined> {
|
|
90
|
+
const gate = await run_ship_scoped.scoped_gate()
|
|
91
|
+
|
|
92
|
+
if (gate.code !== SUCCESS_EXIT_CODE) return gate
|
|
93
|
+
|
|
94
|
+
const push = await josh(['git', '-y', '--skip-commit', '--skip-pr', title])
|
|
95
|
+
|
|
96
|
+
return push.code === SUCCESS_EXIT_CODE ? undefined : push
|
|
97
|
+
}
|
|
98
|
+
|
|
83
99
|
// The result to stop on, or `undefined` once a clean merge is pushed and the followup can wait again.
|
|
84
100
|
async function merge_again(title: string, failed: JoshResult): Promise<StepResult | undefined> {
|
|
85
101
|
if (!(await is_conflicting())) return failed
|
|
@@ -89,9 +105,7 @@ async function merge_again(title: string, failed: JoshResult): Promise<StepResul
|
|
|
89
105
|
if (outcome.kind === 'conflict') return conflict_result(outcome.files, failed.out)
|
|
90
106
|
if (outcome.kind !== 'merged') return failed
|
|
91
107
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return push.code === SUCCESS_EXIT_CODE ? undefined : push
|
|
108
|
+
return await push_merged(title)
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
async function followup_stage(
|
|
@@ -3,7 +3,7 @@ import { git_stash } from '#scripts/git/stash/git-stash'
|
|
|
3
3
|
import { lane_close } from '#scripts/lane/lane-close'
|
|
4
4
|
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
5
5
|
import { error_text } from '#scripts/lib/error-message'
|
|
6
|
-
import { run_hold } from '
|
|
6
|
+
import { run_hold } from '#scripts/run/hold/run-hold'
|
|
7
7
|
import { run_tidy, type Outcome, type Verdict } from './run-tidy'
|
|
8
8
|
|
|
9
9
|
// The lane half of `josh run:tidy` (joshuafolkken/kit#2701): each lane whose issue merged, read for
|
|
@@ -4,15 +4,17 @@ import path from 'node:path'
|
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import type { AgentProfile } from '#scripts/agent/agent-role-profile'
|
|
6
6
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
7
|
+
import { poll } from '#scripts/lib/poll'
|
|
7
8
|
import { telegram_notify } from '#scripts/notify/telegram-notify'
|
|
8
|
-
import { run_carry, type CarryRead } from '
|
|
9
|
-
import { run_event_stream } from '
|
|
10
|
-
import { run_headless } from '
|
|
9
|
+
import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
|
|
10
|
+
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
11
|
+
import { run_headless } from '#scripts/run/run-headless'
|
|
11
12
|
import { run_wake, type RunWake, type WakeStopReason, type WakeTidyResult } from './run-wake'
|
|
12
13
|
import { run_wake_describe, type WakeContext } from './run-wake-describe'
|
|
13
14
|
import { run_wake_driver } from './run-wake-driver'
|
|
14
15
|
import { run_wake_handoff } from './run-wake-handoff'
|
|
15
16
|
import { run_wake_loop, type LoopPorts, type LoopStop } from './run-wake-loop'
|
|
17
|
+
import { run_wake_restart, type RestartPorts } from './run-wake-restart'
|
|
16
18
|
import { run_wake_session, type LaunchResult } from './run-wake-session'
|
|
17
19
|
|
|
18
20
|
// `josh run:wake --start | --list | --stop | --loop` — the supervisor that continues a cut
|
|
@@ -72,9 +74,8 @@ const WARNING_RECOVERY = `Check the wake command, then restart with \`pnpm josh
|
|
|
72
74
|
//
|
|
73
75
|
// **`expired` and `unreadable` were silent until joshuafolkken/kit#1746, and that was the defect
|
|
74
76
|
// rather than the design.** Both end the supervisor while a carry record is still sitting there handed
|
|
75
|
-
// off, so the run is left asleep with nothing anywhere saying so —
|
|
76
|
-
//
|
|
77
|
-
// then only the `failed` reason was wired to the notification.
|
|
77
|
+
// off, so the run is left asleep with nothing anywhere saying so — a silent overnight failure, while
|
|
78
|
+
// only the `failed` reason was wired to the notification.
|
|
78
79
|
// **Every reason is listed, and the silent two are listed as `undefined`.** A partial map would let a
|
|
79
80
|
// sixth stop reason compile straight past this and reach nobody, which is the defect class this
|
|
80
81
|
// section exists to close.
|
|
@@ -309,6 +310,22 @@ function tidy_up(target: string): void {
|
|
|
309
310
|
if (run_wake.tidy_own_wake(target) === SUPERSEDED_RESULT) note_to_stderr(SUPERSEDED_NOTE)
|
|
310
311
|
}
|
|
311
312
|
|
|
313
|
+
function restart_ports(context: WakeContext, ports: LoopPorts, interval_ms: number): RestartPorts {
|
|
314
|
+
return {
|
|
315
|
+
pass: async () => await run_wake_loop.run_loop(context.wake_target, ports, interval_ms),
|
|
316
|
+
is_resumable: () => run_carry.read_carry(context.carry_target).kind === 'carried',
|
|
317
|
+
pause: async () => {
|
|
318
|
+
await poll.sleep(interval_ms)
|
|
319
|
+
},
|
|
320
|
+
note: note_to_stderr,
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// A failed pass is restarted while the carry record is still resumable (`run-wake-restart.ts`), so
|
|
325
|
+
// only a failure past the restart bound reaches `finish` and its warning (joshuafolkken/kit#3332).
|
|
326
|
+
// **The record is claimed once and held across every restart**, so a `--stop` landing in the pause
|
|
327
|
+
// between two passes still finds it, signals this process, and the next pass reads the removal as the
|
|
328
|
+
// person's stop rather than claiming a fresh record and resuming.
|
|
312
329
|
async function loop(context: WakeContext, interval_ms: number): Promise<number> {
|
|
313
330
|
const read = run_carry.read_carry(context.carry_target)
|
|
314
331
|
|
|
@@ -321,11 +338,8 @@ async function loop(context: WakeContext, interval_ms: number): Promise<number>
|
|
|
321
338
|
|
|
322
339
|
if (claimed === undefined) return report(RUNNING_VERDICT)
|
|
323
340
|
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
ports_for(context, claimed.profile ?? profile),
|
|
327
|
-
interval_ms,
|
|
328
|
-
)
|
|
341
|
+
const ports = ports_for(context, claimed.profile ?? profile)
|
|
342
|
+
const stop = await run_wake_restart.supervise(restart_ports(context, ports, interval_ms))
|
|
329
343
|
|
|
330
344
|
tidy_up(context.wake_target)
|
|
331
345
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { agent_role_profile } from '#scripts/agent/agent-role-profile'
|
|
2
2
|
import { gh_spawn } from '#scripts/gh/gh-spawn'
|
|
3
3
|
import { issue_citation } from '#scripts/rules/issue-citation'
|
|
4
|
-
import { run_carry, type CarryRead } from '
|
|
5
|
-
import { run_event_stream } from '
|
|
6
|
-
import { run_liveness } from '
|
|
7
|
-
import { run_wake, type RunWake } from './run-wake'
|
|
4
|
+
import { run_carry, type CarryRead, type RunCarry } from '#scripts/run/carry/run-carry'
|
|
5
|
+
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
6
|
+
import { run_liveness } from '#scripts/run/run-liveness'
|
|
7
|
+
import { run_wake, type RunWake, type WakeDecision, type WakeDecisionInput } from './run-wake'
|
|
8
8
|
|
|
9
9
|
// The `--list` presentation of a wake record (joshuafolkken/kit#2407 split it out of `run-wake-cli.ts`,
|
|
10
10
|
// which had reached its line limit). What the command *does* stays in the CLI; how a supervisor's
|
|
@@ -13,6 +13,7 @@ import { run_wake, type RunWake } from './run-wake'
|
|
|
13
13
|
const UNKNOWN_CUTS = 'an unreadable number of'
|
|
14
14
|
const REPO_LOOKUP_TIMEOUT_MS = 10_000
|
|
15
15
|
const STOP_COMMAND = 'pnpm josh run:wake --stop'
|
|
16
|
+
const UNREADABLE_TIME = 'an unreadable time'
|
|
16
17
|
|
|
17
18
|
// The three paths every verb needs: the carry record it reads, its own record, and the work tree a
|
|
18
19
|
// woken session runs in. The work tree is the common git directory's parent, which is the *primary*
|
|
@@ -49,6 +50,55 @@ function outstanding_line(wake: RunWake): string | undefined {
|
|
|
49
50
|
return `${String(wake.attempts)} launch(es) outstanding for the current cut, none claimed yet`
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
// **Why the supervisor is not launching, and until when** (joshuafolkken/kit#3363). A supervisor that
|
|
54
|
+
// waits writes nothing to its log, so before this `--list` showed a stalled run and a waiting one
|
|
55
|
+
// alike. The state is `run_wake.decide`'s own answer rather than a second reading of the record: an
|
|
56
|
+
// `idle_since` mark is the last pass having found no work, so it is fed back in as `has_work: false`.
|
|
57
|
+
function wait_input(wake: RunWake, read: CarryRead, now: Date): WakeDecisionInput {
|
|
58
|
+
return {
|
|
59
|
+
read,
|
|
60
|
+
woke_at: wake.woke_at,
|
|
61
|
+
attempts: wake.attempts ?? 0,
|
|
62
|
+
is_owner_live: read.kind === 'carried' && run_carry.is_owner_live(read.carry, now),
|
|
63
|
+
has_work: wake.idle_since === undefined ? undefined : false,
|
|
64
|
+
idle_since: wake.idle_since,
|
|
65
|
+
now,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function deadline(marked_at: string | undefined, limit_ms: number): string {
|
|
70
|
+
const marked = Date.parse(marked_at ?? '')
|
|
71
|
+
|
|
72
|
+
return Number.isNaN(marked) ? UNREADABLE_TIME : new Date(marked + limit_ms).toISOString()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A live owner that is this supervisor is its own driver at work; any other is a session spending the
|
|
76
|
+
// budget, which has no deadline of its own short of the carry record's whole-run expiry.
|
|
77
|
+
function owner_wait(wake: RunWake, carry: RunCarry): string {
|
|
78
|
+
if (carry.owner_pid === wake.pid) {
|
|
79
|
+
return 'waiting: the driver holds the carry record and is running'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const expiry = deadline(carry.started_at, run_carry.CARRY_MAX_AGE_MS)
|
|
83
|
+
|
|
84
|
+
return `waiting: process ${String(carry.owner_pid)} holds the carry record; the driver starts at its next cut or once it exits, and the record expires at ${expiry}`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const WAIT_LINES: Partial<Record<WakeDecision['kind'], (wake: RunWake) => string>> = {
|
|
88
|
+
pending: (wake) =>
|
|
89
|
+
`waiting: the woken session has until ${deadline(wake.woke_at, run_wake.WAKE_GRACE_MS)} to claim the carry record`,
|
|
90
|
+
idle: (wake) =>
|
|
91
|
+
`waiting: no runnable work; the driver starts anyway at ${deadline(wake.idle_since, run_wake.IDLE_CEILING_MS)}`,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function wait_line(wake: RunWake, read: CarryRead, now: Date): string | undefined {
|
|
95
|
+
const { kind } = run_wake.decide(wait_input(wake, read, now))
|
|
96
|
+
|
|
97
|
+
if (kind === 'wait' && read.kind === 'carried') return owner_wait(wake, read.carry)
|
|
98
|
+
|
|
99
|
+
return WAIT_LINES[kind]?.(wake)
|
|
100
|
+
}
|
|
101
|
+
|
|
52
102
|
// The run's stream, read here because a headless parent's progress reaches its own transcript alone —
|
|
53
103
|
// after a cut, `--list` is the person's one window onto it (joshuafolkken/kit#1910). Two lines: the
|
|
54
104
|
// newest event (the degenerate last-event read of the stream the watch pane follows, so `--list` and
|
|
@@ -73,16 +123,17 @@ function stream_lines(context: WakeContext): Array<string> {
|
|
|
73
123
|
// **The outstanding line beside it is what makes a shortfall readable**: the count is observed at a
|
|
74
124
|
// poll, so it lags a claim by up to one interval, and launches still outstanding are what say whether
|
|
75
125
|
// a missing wake is one not yet seen or one that never arrived.
|
|
76
|
-
function describe_wake(wake: RunWake, context: WakeContext): string {
|
|
126
|
+
function describe_wake(wake: RunWake, context: WakeContext, now: Date = new Date()): string {
|
|
77
127
|
const live = run_wake.is_supervisor_live(wake) ? 'running' : 'not running'
|
|
78
|
-
const
|
|
128
|
+
const read = run_carry.read_carry(context.carry_target)
|
|
79
129
|
|
|
80
130
|
return [
|
|
81
131
|
`invocation: ${wake.invocation}`,
|
|
82
132
|
`profile: ${wake.profile === undefined ? 'unrecorded' : agent_role_profile.describe(wake.profile)}`,
|
|
83
133
|
`supervisor: process ${String(wake.pid)} (${live}), watching since ${wake.started_at}`,
|
|
84
|
-
`woke ${String(wake.woke)} session(s) across ${
|
|
134
|
+
`woke ${String(wake.woke)} session(s) across ${carry_cuts(read)} cut(s)`,
|
|
85
135
|
outstanding_line(wake),
|
|
136
|
+
wait_line(wake, read, now),
|
|
86
137
|
run_liveness.describe_agent_state(context.log_target),
|
|
87
138
|
`output: ${context.log_target}`,
|
|
88
139
|
// How progress is seen without asking after the cut: the run's stream, followed from here
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
|
|
2
|
-
import { run_carry, type CarryRead } from '
|
|
3
|
-
import { run_invocation } from '
|
|
2
|
+
import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
|
|
3
|
+
import { run_invocation } from '#scripts/run/run-invocation'
|
|
4
4
|
import type { DriveResult } from './run-wake-loop'
|
|
5
5
|
|
|
6
6
|
const SUCCESS = 0
|
|
@@ -114,7 +114,12 @@ function failure_note(result: JoshResult): string {
|
|
|
114
114
|
return [cause_of(result.out), details_tail(result.err ?? '')].filter(Boolean).join('\n')
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function read_result(
|
|
117
|
+
function read_result(
|
|
118
|
+
result: JoshResult | undefined,
|
|
119
|
+
target: string,
|
|
120
|
+
invocation: string,
|
|
121
|
+
): DriveResult {
|
|
122
|
+
if (result === undefined) return { kind: 'released' }
|
|
118
123
|
if (result.code !== SUCCESS) return { kind: 'failed', note: failure_note(result) }
|
|
119
124
|
|
|
120
125
|
return driver_result(result.out, run_carry.read_carry(target), invocation, result.err)
|
|
@@ -128,7 +133,51 @@ function claim_record(target: string, invocation: string): boolean {
|
|
|
128
133
|
return run_carry.adopt_carry(target, current.carry, run_carry.owner_of(process.pid)) !== undefined
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
|
|
136
|
+
// **A verdict with nothing to judge is re-driven here, never handed to a session** (joshuafolkken/kit#3245).
|
|
137
|
+
// A woken session met with `window`, `merge busy` or `merge retry` only cut and handed the loop back, and
|
|
138
|
+
// the supervisor then ran the very same driver again — a model round trip, at a cache write each, that
|
|
139
|
+
// decided nothing. The record is already this process's, so the rerun needs no fresh claim; the limit
|
|
140
|
+
// keeps a driver that never settles from holding the supervisor past its own checks.
|
|
141
|
+
const RERUN_VERDICTS: ReadonlyArray<string> = ['window', 'merge busy', 'merge retry']
|
|
142
|
+
const RERUN_LIMIT = 10
|
|
143
|
+
const RERUN_PAUSE_MS = 60_000
|
|
144
|
+
|
|
145
|
+
type Pause = (ms: number) => Promise<void>
|
|
146
|
+
|
|
147
|
+
async function pause(ms: number): Promise<void> {
|
|
148
|
+
await new Promise((resolve) => {
|
|
149
|
+
setTimeout(resolve, ms)
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function is_mechanical(result: JoshResult): boolean {
|
|
154
|
+
const verdict = result.out.split('\n', 1)[FIRST_LINE] ?? ''
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
result.code === SUCCESS &&
|
|
158
|
+
RERUN_VERDICTS.some((entry) => verdict === entry || verdict.startsWith(`${entry} `))
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// A record that expired or was removed during the pause answers `undefined`: its stale verdict must not
|
|
163
|
+
// reach a session, so the supervisor's own next pass re-reads the record and ends the run as `expired`
|
|
164
|
+
// or `stopped`; a rerun would only fail on it and report the run as `failed`.
|
|
165
|
+
async function run_driver(
|
|
166
|
+
target: string,
|
|
167
|
+
args: ReadonlyArray<string>,
|
|
168
|
+
wait: Pause,
|
|
169
|
+
remaining: number,
|
|
170
|
+
): Promise<JoshResult | undefined> {
|
|
171
|
+
const result = await josh_command.josh_run(args, true)
|
|
172
|
+
if (remaining === 0 || !is_mechanical(result)) return result
|
|
173
|
+
|
|
174
|
+
await wait(RERUN_PAUSE_MS)
|
|
175
|
+
if (run_carry.read_carry(target).kind !== 'carried') return undefined
|
|
176
|
+
|
|
177
|
+
return await run_driver(target, args, wait, remaining - 1)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function drive(target: string, wait: Pause = pause): Promise<DriveResult> {
|
|
132
181
|
const read = run_carry.read_carry(target)
|
|
133
182
|
if (read.kind !== 'carried') return { kind: 'failed', note: 'The carry record changed.' }
|
|
134
183
|
|
|
@@ -139,9 +188,11 @@ async function drive(target: string): Promise<DriveResult> {
|
|
|
139
188
|
return { kind: 'failed', note: 'The driver could not claim the carry record.' }
|
|
140
189
|
}
|
|
141
190
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
191
|
+
return read_result(
|
|
192
|
+
await run_driver(target, args, wait, RERUN_LIMIT),
|
|
193
|
+
target,
|
|
194
|
+
read.carry.invocation,
|
|
195
|
+
)
|
|
145
196
|
}
|
|
146
197
|
|
|
147
|
-
export const run_wake_driver = { drive, driver_args, driver_result }
|
|
198
|
+
export const run_wake_driver = { RERUN_LIMIT, drive, driver_args, driver_result }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
2
|
-
import { run_carry } from '
|
|
2
|
+
import { run_carry } from '#scripts/run/carry/run-carry'
|
|
3
3
|
|
|
4
4
|
// The supervisor's recovery hand-off (joshuafolkken/kit#2437). A session that claimed the carry record
|
|
5
5
|
// and then died without `run:carry --cut` leaves it `carried`, owned by a gone process, with no
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CarryRead } from '
|
|
1
|
+
import type { CarryRead } from '#scripts/run/carry/run-carry'
|
|
2
2
|
import {
|
|
3
3
|
run_wake,
|
|
4
4
|
type RunWake,
|
|
@@ -35,9 +35,8 @@ interface LoopPorts {
|
|
|
35
35
|
// The carry record, re-read every pass rather than cached: it is written by the parent loop of a
|
|
36
36
|
// different session, which is the whole point of reading it here.
|
|
37
37
|
read_carry: () => CarryRead
|
|
38
|
-
// Whether the process named on the carry record is still running
|
|
39
|
-
//
|
|
40
|
-
// out the second — `run-wake.ts` → `WakeDecisionInput.is_owner_live`.
|
|
38
|
+
// Whether the process named on the carry record is still running, which tells a session at work
|
|
39
|
+
// from a crashed one — `run-wake.ts` → `WakeDecisionInput.is_owner_live`.
|
|
41
40
|
is_owner_live: (read: CarryRead) => boolean
|
|
42
41
|
// A compatibility probe for the wake decision. Production lets the resident driver inspect work,
|
|
43
42
|
// including an empty idle watch, without starting an AI session.
|
|
@@ -56,8 +55,13 @@ interface LoopPorts {
|
|
|
56
55
|
now: () => Date
|
|
57
56
|
}
|
|
58
57
|
|
|
58
|
+
// `released`: the carry record ended while the driver paused, so there is nothing to hand on and the
|
|
59
|
+
// next pass decides from the record how the run ends.
|
|
59
60
|
type DriveResult =
|
|
60
|
-
|
|
61
|
+
| { kind: 'finished' }
|
|
62
|
+
| { kind: 'released' }
|
|
63
|
+
| { kind: 'judgment'; material: string }
|
|
64
|
+
| { kind: 'failed'; note: string }
|
|
61
65
|
|
|
62
66
|
interface LoopStop {
|
|
63
67
|
reason: WakeStopReason
|
|
@@ -89,6 +93,7 @@ async function wake_step(wake: RunWake, ports: LoopPorts): Promise<StepOutcome>
|
|
|
89
93
|
const driven = await ports.drive()
|
|
90
94
|
if (driven.kind === 'finished') return stopped('ended')
|
|
91
95
|
if (driven.kind === 'failed') return stopped(FAILED_REASON, driven.note)
|
|
96
|
+
if (driven.kind === 'released') return { kind: 'continue', wake }
|
|
92
97
|
|
|
93
98
|
const session_id = ports.new_session_id()
|
|
94
99
|
|
|
@@ -104,17 +109,11 @@ async function wake_step(wake: RunWake, ports: LoopPorts): Promise<StepOutcome>
|
|
|
104
109
|
// A `wait` means the woken session has claimed the carry record, so the wake mark is cleared and the
|
|
105
110
|
// next cut starts the grace window afresh — **and it is the only pass on which `woke` may grow**
|
|
106
111
|
// (joshuafolkken/kit#1746), because arriving here is the supervisor observing that a record it was
|
|
107
|
-
// waiting on has been taken over. A `
|
|
108
|
-
//
|
|
109
|
-
// that predecessor does. A `pending` keeps what is there, because the window it is measured from is
|
|
110
|
-
// still open.
|
|
112
|
+
// waiting on has been taken over. A `pending` keeps what is there, because the window it is measured
|
|
113
|
+
// from is still open.
|
|
111
114
|
function continue_step(wake: RunWake, decision: WakeDecision, ports: LoopPorts): StepOutcome {
|
|
112
115
|
if (decision.kind === 'wait') return { kind: 'continue', wake: run_wake.count_claim(wake) }
|
|
113
116
|
|
|
114
|
-
if (decision.kind === 'hold') {
|
|
115
|
-
return { kind: 'continue', wake: run_wake.mark_wait(wake, ports.now()) }
|
|
116
|
-
}
|
|
117
|
-
|
|
118
117
|
if (decision.kind === 'idle') {
|
|
119
118
|
return { kind: 'continue', wake: run_wake.mark_idle(wake, ports.now()) }
|
|
120
119
|
}
|
|
@@ -140,7 +139,6 @@ function decision_input(wake: RunWake, ports: LoopPorts): WakeDecisionInput {
|
|
|
140
139
|
woke_at: wake.woke_at,
|
|
141
140
|
attempts: wake.attempts ?? NO_ATTEMPTS,
|
|
142
141
|
is_owner_live: ports.is_owner_live(read),
|
|
143
|
-
held_at: wake.held_at,
|
|
144
142
|
has_work: undefined,
|
|
145
143
|
idle_since: wake.idle_since,
|
|
146
144
|
now: ports.now(),
|
|
@@ -149,7 +147,7 @@ function decision_input(wake: RunWake, ports: LoopPorts): WakeDecisionInput {
|
|
|
149
147
|
|
|
150
148
|
// **Work is asked about only where the answer could change the decision** (joshuafolkken/kit#2417):
|
|
151
149
|
// on a pass the record alone would launch from. Every other pass — the in-flight wait, the pending
|
|
152
|
-
// grace window
|
|
150
|
+
// grace window — decides from the record as before and costs no backlog read.
|
|
153
151
|
async function decide_pass(wake: RunWake, ports: LoopPorts): Promise<WakeDecision> {
|
|
154
152
|
const input = decision_input(wake, ports)
|
|
155
153
|
const decision = run_wake.decide(input)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { run_wake_loop, type LoopStop } from './run-wake-loop'
|
|
2
|
+
|
|
3
|
+
// **A supervisor that fails while the run is still resumable restarts itself** (joshuafolkken/kit#3332).
|
|
4
|
+
// A `backlog:drive` that exited on a transient `main:sync` failure ended the supervisor as `failed`,
|
|
5
|
+
// and the run then sat for forty minutes with a valid carry record, waiting for one `run:wake --start`
|
|
6
|
+
// that only a person was left to type — joshuafolkken/kit#3297's "resume it yourself" rule binds an AI
|
|
7
|
+
// session, and once the supervisor is gone there is none to read it.
|
|
8
|
+
//
|
|
9
|
+
// **Bounded, and only the bound reaches a person.** A failure that is not transient fails again on
|
|
10
|
+
// every pass, so the restarts stop at `RESTART_LIMIT` and the last failure is handed back for the
|
|
11
|
+
// ordinary warning, its note saying how many restarts went before it.
|
|
12
|
+
|
|
13
|
+
const RESTART_LIMIT = 3
|
|
14
|
+
|
|
15
|
+
interface RestartPorts {
|
|
16
|
+
// One run of the loop over the record the caller claimed once and holds across every restart, so a
|
|
17
|
+
// `--stop` landing in the pause is read by the next pass as the person's stop.
|
|
18
|
+
pass: () => Promise<LoopStop>
|
|
19
|
+
// Whether the carry record can still be continued — `carried`, neither `expired` nor gone.
|
|
20
|
+
is_resumable: () => boolean
|
|
21
|
+
pause: () => Promise<void>
|
|
22
|
+
note: (text: string) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function is_restartable(stop: LoopStop, restarts: number, ports: RestartPorts): boolean {
|
|
26
|
+
return (
|
|
27
|
+
stop.reason === run_wake_loop.FAILED_REASON && restarts < RESTART_LIMIT && ports.is_resumable()
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function restart_note(stop: LoopStop, restarts: number): string {
|
|
32
|
+
return `the supervisor failed (${stop.note ?? 'no note'}); restarting it, ${String(restarts + 1)} of ${String(RESTART_LIMIT)}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function with_restart_count(stop: LoopStop, restarts: number): LoopStop {
|
|
36
|
+
if (restarts === 0 || stop.reason !== run_wake_loop.FAILED_REASON) return stop
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
...stop,
|
|
40
|
+
note: [stop.note, `after ${String(restarts)} automatic restarts`].filter(Boolean).join('\n'),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function supervise(ports: RestartPorts, restarts = 0): Promise<LoopStop> {
|
|
45
|
+
const stop = await ports.pass()
|
|
46
|
+
|
|
47
|
+
if (!is_restartable(stop, restarts, ports)) return with_restart_count(stop, restarts)
|
|
48
|
+
|
|
49
|
+
ports.note(restart_note(stop, restarts))
|
|
50
|
+
await ports.pause()
|
|
51
|
+
|
|
52
|
+
return await supervise(ports, restarts + 1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const run_wake_restart = { RESTART_LIMIT, supervise }
|
|
56
|
+
|
|
57
|
+
export { run_wake_restart }
|
|
58
|
+
export type { RestartPorts }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { agent_argv, type AgentArgvResult } from '#scripts/agent/agent-argv'
|
|
2
2
|
import { agent_role_profile, type AgentProfile } from '#scripts/agent/agent-role-profile'
|
|
3
3
|
import { claude_agent_argv } from '#scripts/agent/claude-agent-argv'
|
|
4
|
-
import { detached_launch, type LaunchArgv } from '
|
|
5
|
-
import { run_invocation } from '
|
|
4
|
+
import { detached_launch, type LaunchArgv } from '#scripts/run/detached-launch'
|
|
5
|
+
import { run_invocation } from '#scripts/run/run-invocation'
|
|
6
6
|
|
|
7
7
|
// How the supervisor starts things: the next agent session, and — at `--start` — its own detached
|
|
8
8
|
// self. Both go through one `launch`, because the two differ only in what is being run
|
|
@@ -159,5 +159,5 @@ const run_wake_session = {
|
|
|
159
159
|
wake_argv,
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
export type { LaunchRequest, LaunchResult } from '
|
|
162
|
+
export type { LaunchRequest, LaunchResult } from '#scripts/run/detached-launch'
|
|
163
163
|
export { run_wake_session }
|