@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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { LedgerLoad, MachineBudget } from './machine-capacity'
|
|
2
|
+
|
|
3
|
+
// The ledger's admission arithmetic, kept apart from the markers `core-budget.ts` reads and writes so
|
|
4
|
+
// that file stays under its line ceiling as the budget grew a memory side (joshuafolkken/kit#3371).
|
|
5
|
+
// Everything here is a pure function of the ledger it is handed.
|
|
6
|
+
|
|
7
|
+
const MS_PER_SECOND = 1000
|
|
8
|
+
// **How long an admitted claim's tool takes to reach its declared peak.** The machine reading holds only
|
|
9
|
+
// what a tool has already allocated, so a claim admitted moments ago is not in it yet: adding its
|
|
10
|
+
// declared memory back would admit the next claim into memory about to be taken — the concurrent start
|
|
11
|
+
// this ledger exists to stop. Until the window passes, the claim's memory counts only in the FIFO walk; a
|
|
12
|
+
// tool partway up is then counted twice, which costs a slower admission, never a swap. Its cores are in
|
|
13
|
+
// the reading from the start, so the window does not apply to them.
|
|
14
|
+
const RAMP_UP_MS = 30_000
|
|
15
|
+
|
|
16
|
+
// What admission reads from a marker. `memory_mb` and `admitted_at` are optional because a marker an
|
|
17
|
+
// older `josh` wrote has neither: it declares no memory, and it counts as load from outside the ledger.
|
|
18
|
+
interface LedgerReservation {
|
|
19
|
+
weight: number
|
|
20
|
+
claimed_at: number
|
|
21
|
+
memory_mb?: number | undefined
|
|
22
|
+
admitted_at?: number | undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface LedgerEntry {
|
|
26
|
+
// The marker's filename, which is its identity in the ledger and the tiebreak when two reservations
|
|
27
|
+
// share a claim time.
|
|
28
|
+
key: string
|
|
29
|
+
reservation: LedgerReservation
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// A claim admitted at the wait cap rather than by room in the budget — the run went ahead past the
|
|
33
|
+
// budget, and the gate's summary says so.
|
|
34
|
+
interface Overflow {
|
|
35
|
+
waited_ms: number
|
|
36
|
+
ledger: LedgerLoad
|
|
37
|
+
budget: MachineBudget
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface Admission {
|
|
41
|
+
overflow: Overflow | undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Claim order, with the filename as the tiebreak so every reader agrees on the same ledger order
|
|
45
|
+
// however the wall clock rounded two near-simultaneous claims.
|
|
46
|
+
function compare_claim(left: LedgerEntry, right: LedgerEntry): number {
|
|
47
|
+
const by_time = left.reservation.claimed_at - right.reservation.claimed_at
|
|
48
|
+
|
|
49
|
+
return by_time === 0 ? left.key.localeCompare(right.key) : by_time
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function add_load(totals: LedgerLoad, reservation: LedgerReservation): void {
|
|
53
|
+
totals.cores += reservation.weight
|
|
54
|
+
totals.memory_mb += reservation.memory_mb ?? 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function exceeds(totals: LedgerLoad, budget: MachineBudget): boolean {
|
|
58
|
+
return totals.cores > budget.cores || totals.memory_mb > budget.memory_mb
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// **The keys admitted under FIFO.** The head always runs — a single check heavier than the whole
|
|
62
|
+
// machine must not wait for room that will never come — and each later reservation runs while the
|
|
63
|
+
// cumulative weight ahead of it still fits the budget. The first that does not fit stops the walk, so a
|
|
64
|
+
// place is never jumped: admission is in claim order, which is what removes the start-time asymmetry.
|
|
65
|
+
// Memory is walked the same way, so a claim whose tool would not fit in free memory waits like one whose
|
|
66
|
+
// cores would not fit (joshuafolkken/kit#3371).
|
|
67
|
+
function admitted_keys(
|
|
68
|
+
reservations: ReadonlyArray<LedgerEntry>,
|
|
69
|
+
budget: number,
|
|
70
|
+
memory_budget = Infinity,
|
|
71
|
+
): Set<string> {
|
|
72
|
+
const limit = { cores: budget, memory_mb: memory_budget }
|
|
73
|
+
const totals = { cores: 0, memory_mb: 0 }
|
|
74
|
+
const admitted = new Set<string>()
|
|
75
|
+
|
|
76
|
+
for (const [index, entry] of reservations.toSorted(compare_claim).entries()) {
|
|
77
|
+
add_load(totals, entry.reservation)
|
|
78
|
+
|
|
79
|
+
if (index !== 0 && exceeds(totals, limit)) break
|
|
80
|
+
|
|
81
|
+
admitted.add(entry.key)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return admitted
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function is_admitted(
|
|
88
|
+
key: string,
|
|
89
|
+
reservations: ReadonlyArray<LedgerEntry>,
|
|
90
|
+
budget: MachineBudget,
|
|
91
|
+
): boolean {
|
|
92
|
+
return admitted_keys(reservations, budget.cores, budget.memory_mb).has(key)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sum_load(reservations: ReadonlyArray<LedgerEntry>): LedgerLoad {
|
|
96
|
+
const load = { cores: 0, memory_mb: 0 }
|
|
97
|
+
|
|
98
|
+
for (const entry of reservations) add_load(load, entry.reservation)
|
|
99
|
+
|
|
100
|
+
return load
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function is_ramped(reservation: LedgerReservation, now: number): boolean {
|
|
104
|
+
const { admitted_at } = reservation
|
|
105
|
+
|
|
106
|
+
return admitted_at !== undefined && now - admitted_at >= RAMP_UP_MS
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// **The load the ledger already accounts for.** CPU use shows in the reading the moment a tool starts,
|
|
110
|
+
// so every admitted claim's cores count at once; memory grows as the tool allocates, so only the claims
|
|
111
|
+
// admitted long enough ago to be in the reading count their memory.
|
|
112
|
+
function ledger_load(reservations: ReadonlyArray<LedgerEntry>, now: number): LedgerLoad {
|
|
113
|
+
const admitted = reservations.filter((entry) => entry.reservation.admitted_at !== undefined)
|
|
114
|
+
const ramped = admitted.filter((entry) => is_ramped(entry.reservation, now))
|
|
115
|
+
|
|
116
|
+
return { cores: sum_load(admitted).cores, memory_mb: sum_load(ramped).memory_mb }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function total_weight(reservations: ReadonlyArray<LedgerEntry>): number {
|
|
120
|
+
return sum_load(reservations).cores
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The weight actually running at once — what the reproduction test asserts stays within the budget.
|
|
124
|
+
function admitted_load(reservations: ReadonlyArray<LedgerEntry>, budget: number): number {
|
|
125
|
+
const admitted = admitted_keys(reservations, budget)
|
|
126
|
+
|
|
127
|
+
return total_weight(reservations.filter((entry) => admitted.has(entry.key)))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// The memory side of an overflow, so a claim held back by memory is not reported as a core shortfall.
|
|
131
|
+
// A budget that read no memory set no memory limit, and says nothing.
|
|
132
|
+
function describe_memory(ledger: LedgerLoad, budget: MachineBudget): string {
|
|
133
|
+
if (!Number.isFinite(budget.memory_mb)) return ''
|
|
134
|
+
|
|
135
|
+
return ` and ${String(Math.round(ledger.memory_mb))} MB of memory against ${String(Math.round(budget.memory_mb))} MB`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The summary line for a claim admitted past the budget, or undefined for one that fitted.
|
|
139
|
+
function describe_overflow(admission: Admission): string | undefined {
|
|
140
|
+
const { overflow } = admission
|
|
141
|
+
|
|
142
|
+
if (overflow === undefined) return undefined
|
|
143
|
+
|
|
144
|
+
const { ledger, budget } = overflow
|
|
145
|
+
const waited = String(Math.round(overflow.waited_ms / MS_PER_SECOND))
|
|
146
|
+
const cores = `the ledger held ${String(ledger.cores)} cores against a budget of ${String(budget.cores)}`
|
|
147
|
+
|
|
148
|
+
return `started past the core budget after ${waited}s — ${cores}${describe_memory(ledger, budget)}`
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const core_admission = {
|
|
152
|
+
RAMP_UP_MS,
|
|
153
|
+
admitted_keys,
|
|
154
|
+
admitted_load,
|
|
155
|
+
describe_overflow,
|
|
156
|
+
is_admitted,
|
|
157
|
+
ledger_load,
|
|
158
|
+
sum_load,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export type { Admission, LedgerEntry, Overflow }
|
|
162
|
+
export { core_admission }
|
|
@@ -6,7 +6,10 @@ import { PLATFORM_TEMP_ROOT } from '#scripts/josh/platform-temporary'
|
|
|
6
6
|
import { process_identity } from '#scripts/josh/process-identity'
|
|
7
7
|
import { process_owner_schema } from '#scripts/josh/process-owner'
|
|
8
8
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
9
|
+
import { unit_worker_share } from '#scripts/test/unit-worker-share'
|
|
9
10
|
import { z } from 'zod'
|
|
11
|
+
import { core_admission, type Admission, type LedgerEntry, type Overflow } from './core-admission'
|
|
12
|
+
import { machine_capacity, type MachineBudget, type MachineReading } from './machine-capacity'
|
|
10
13
|
|
|
11
14
|
// A machine-wide weighted core budget with admission control (joshuafolkken/kit#2351).
|
|
12
15
|
//
|
|
@@ -33,6 +36,11 @@ import { z } from 'zod'
|
|
|
33
36
|
// killed outright leaves its marker behind, and a ledger that trusted the file alone would fill forever
|
|
34
37
|
// and admit nothing. The pid-and-start-time pair a marker carries is the same identity the unit-run
|
|
35
38
|
// marker keeps, swept on read the moment its process is gone.
|
|
39
|
+
//
|
|
40
|
+
// **The budget is what the machine has free, in cores and in memory** (joshuafolkken/kit#3371). The
|
|
41
|
+
// core count stays the ceiling, but load from outside the ledger is subtracted from it, and a claim
|
|
42
|
+
// waits while the memory its tool holds is not free — `machine-capacity.ts` reads both. A claim marks
|
|
43
|
+
// its marker admitted once it runs, so the reading's load is split into the ledger's and the rest.
|
|
36
44
|
|
|
37
45
|
const RESERVED_PREFIX = 'josh-core-reserved-'
|
|
38
46
|
const RESERVED_SUFFIX = '.json'
|
|
@@ -46,22 +54,60 @@ const WAIT_CAP_MS = 120_000
|
|
|
46
54
|
// The head of the ledger is always admitted, so the smallest budget worth planning against is one core.
|
|
47
55
|
const MIN_BUDGET = 1
|
|
48
56
|
|
|
57
|
+
// **The cores each heavy tool occupies, declared once for both places that reserve them**
|
|
58
|
+
// (joshuafolkken/kit#3345). `gate-plan.ts`'s checks and the `josh` commands that run the same tools
|
|
59
|
+
// directly read these, so a direct `josh lint` claims what the gate's lint claims. The lint, type check
|
|
60
|
+
// and spell check numbers are `gate-plan.ts`'s measured table, floored. The whole-tree eslint scan
|
|
61
|
+
// behind `josh lines` and `josh refactor:scan` runs in one process: measured cold on the 11-core
|
|
62
|
+
// machine on 2026-10-06, 113s of CPU over 98s of wall, so it gets one core.
|
|
63
|
+
const CORE_WEIGHTS = {
|
|
64
|
+
lint: 2,
|
|
65
|
+
type_check: 1,
|
|
66
|
+
spell_check: 1,
|
|
67
|
+
eslint_scan: 1,
|
|
68
|
+
} as const
|
|
69
|
+
|
|
70
|
+
// **The memory each heavy tool holds, declared beside its cores** (joshuafolkken/kit#3371): peak
|
|
71
|
+
// resident size in MB, rounded up. Measured with `/usr/bin/time -l` on the 11-core / 18 GB machine on
|
|
72
|
+
// 2026-10-07: the type check 1,244 MB, the spell check 330 MB, and one unit worker 319 MB over 54 test
|
|
73
|
+
// files. Typed eslint was 1,207 MB over one directory and was observed at 1.6–2.4 GB per whole-tree
|
|
74
|
+
// `eslint .` with three lanes running the same day, so lint and the whole-tree scan take that peak.
|
|
75
|
+
const MEMORY_MB = {
|
|
76
|
+
lint: 2400,
|
|
77
|
+
type_check: 1300,
|
|
78
|
+
spell_check: 350,
|
|
79
|
+
eslint_scan: 2400,
|
|
80
|
+
unit_worker: 350,
|
|
81
|
+
} as const
|
|
82
|
+
|
|
83
|
+
// **The environment mark that says a parent already holds this process's cores**
|
|
84
|
+
// (joshuafolkken/kit#3345). `josh gate` reserves per check and then spawns `pnpm josh lint`. Without
|
|
85
|
+
// the mark, that child's own dispatch would claim a second place for the cores its parent already
|
|
86
|
+
// holds. Every holder sets it, and every child inherits it through the environment and claims nothing.
|
|
87
|
+
const HELD_KEY = 'JOSH_CORE_RESERVED'
|
|
88
|
+
const HELD_VALUE = '1'
|
|
89
|
+
|
|
90
|
+
// `memory_mb` and `admitted_at` are optional so a marker an older `josh` wrote still reads: it holds no
|
|
91
|
+
// declared memory, and it counts as load from outside the ledger until it is gone.
|
|
49
92
|
const reservation_schema = process_owner_schema.extend({
|
|
50
93
|
weight: z.number(),
|
|
51
94
|
claimed_at: z.number(),
|
|
95
|
+
memory_mb: z.number().optional(),
|
|
96
|
+
admitted_at: z.number().optional(),
|
|
52
97
|
})
|
|
53
98
|
|
|
54
99
|
type Reservation = z.infer<typeof reservation_schema>
|
|
55
100
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
key: string
|
|
101
|
+
type ReservationFields = Pick<Reservation, 'weight' | 'claimed_at' | 'memory_mb' | 'admitted_at'>
|
|
102
|
+
|
|
103
|
+
interface LiveReservation extends LedgerEntry {
|
|
60
104
|
reservation: Reservation
|
|
61
105
|
}
|
|
62
106
|
|
|
63
107
|
interface ReserveOptions {
|
|
64
108
|
budget?: number
|
|
109
|
+
memory_mb?: number | undefined
|
|
110
|
+
read_machine?: () => Promise<MachineReading>
|
|
65
111
|
wait_cap_ms?: number
|
|
66
112
|
poll_interval_ms?: number
|
|
67
113
|
directory?: string
|
|
@@ -71,6 +117,7 @@ interface ReserveOptions {
|
|
|
71
117
|
|
|
72
118
|
interface Handle {
|
|
73
119
|
target: string
|
|
120
|
+
admission: Admission
|
|
74
121
|
}
|
|
75
122
|
|
|
76
123
|
function marker_name(key: string): string {
|
|
@@ -120,10 +167,13 @@ function live_reservation(source: string): Reservation | undefined {
|
|
|
120
167
|
}
|
|
121
168
|
|
|
122
169
|
// An unreadable temp directory answers "no other reservation", the direction that admits rather than
|
|
123
|
-
// stalls a run on a guess.
|
|
170
|
+
// stalls a run on a guess. The suffix leaves out `replace_stamp`'s in-flight temporary copy of a marker,
|
|
171
|
+
// which would otherwise count one place twice.
|
|
124
172
|
function marker_files(directory: string): Array<string> {
|
|
125
173
|
try {
|
|
126
|
-
return readdirSync(directory).filter(
|
|
174
|
+
return readdirSync(directory).filter(
|
|
175
|
+
(name) => name.startsWith(RESERVED_PREFIX) && name.endsWith(RESERVED_SUFFIX),
|
|
176
|
+
)
|
|
127
177
|
} catch {
|
|
128
178
|
return []
|
|
129
179
|
}
|
|
@@ -133,54 +183,10 @@ function live_reservations(directory: string = PLATFORM_TEMP_ROOT): Array<LiveRe
|
|
|
133
183
|
return marker_files(directory).flatMap((name) => {
|
|
134
184
|
const reservation = live_reservation(path.join(directory, name))
|
|
135
185
|
|
|
136
|
-
return reservation === undefined ? [] :
|
|
186
|
+
return reservation === undefined ? [] : { key: name, reservation }
|
|
137
187
|
})
|
|
138
188
|
}
|
|
139
189
|
|
|
140
|
-
// Claim order, with the filename as the tiebreak so every reader agrees on the same ledger order
|
|
141
|
-
// however the wall clock rounded two near-simultaneous claims.
|
|
142
|
-
function compare_claim(left: LiveReservation, right: LiveReservation): number {
|
|
143
|
-
const by_time = left.reservation.claimed_at - right.reservation.claimed_at
|
|
144
|
-
|
|
145
|
-
return by_time === 0 ? left.key.localeCompare(right.key) : by_time
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// **The keys admitted under FIFO.** The head always runs — a single check heavier than the whole
|
|
149
|
-
// machine must not wait for room that will never come — and each later reservation runs while the
|
|
150
|
-
// cumulative weight ahead of it still fits the budget. The first that does not fit stops the walk, so a
|
|
151
|
-
// place is never jumped: admission is in claim order, which is what removes the start-time asymmetry.
|
|
152
|
-
function admitted_keys(reservations: ReadonlyArray<LiveReservation>, budget: number): Set<string> {
|
|
153
|
-
const admitted = new Set<string>()
|
|
154
|
-
let cumulative = 0
|
|
155
|
-
|
|
156
|
-
for (const [index, entry] of reservations.toSorted(compare_claim).entries()) {
|
|
157
|
-
cumulative += entry.reservation.weight
|
|
158
|
-
|
|
159
|
-
if (index !== 0 && cumulative > budget) break
|
|
160
|
-
|
|
161
|
-
admitted.add(entry.key)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return admitted
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function is_admitted(
|
|
168
|
-
key: string,
|
|
169
|
-
reservations: ReadonlyArray<LiveReservation>,
|
|
170
|
-
budget: number,
|
|
171
|
-
): boolean {
|
|
172
|
-
return admitted_keys(reservations, budget).has(key)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// The weight actually running at once — what the reproduction test asserts stays within the budget.
|
|
176
|
-
function admitted_load(reservations: ReadonlyArray<LiveReservation>, budget: number): number {
|
|
177
|
-
const admitted = admitted_keys(reservations, budget)
|
|
178
|
-
|
|
179
|
-
return reservations
|
|
180
|
-
.filter((entry) => admitted.has(entry.key))
|
|
181
|
-
.reduce((total, entry) => total + entry.reservation.weight, 0)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
190
|
// `Math.max` propagates `NaN` rather than clamping it, so an unusable budget is caught before it could
|
|
185
191
|
// make every comparison false and admit everything — the hole `gate-plan.ts` closes for the same reason.
|
|
186
192
|
function normalize_budget(budget: number): number {
|
|
@@ -193,23 +199,30 @@ async function default_sleep(ms: number): Promise<void> {
|
|
|
193
199
|
})
|
|
194
200
|
}
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
// **Replaced atomically, never unlinked and recreated**: the admitted rewrite lands on a marker other
|
|
203
|
+
// lanes are polling, and a reader that hit the gap between an unlink and its create would see the ledger
|
|
204
|
+
// without this place and admit past it.
|
|
205
|
+
function write_reservation(target: string, fields: ReservationFields): void {
|
|
206
|
+
stamp_file.replace_stamp(target, { ...process_identity.own_fields(), ...fields })
|
|
198
207
|
}
|
|
199
208
|
|
|
200
209
|
// The clock and cadence a wait runs on, resolved apart from the ledger fields so `reserve` stays under
|
|
201
|
-
// the complexity limit — each default is a branch
|
|
210
|
+
// the complexity limit — each default is a branch.
|
|
202
211
|
interface TimingConfig {
|
|
203
212
|
now: () => number
|
|
204
213
|
sleep: (ms: number) => Promise<void>
|
|
205
214
|
poll_interval_ms: number
|
|
215
|
+
wait_cap_ms: number
|
|
206
216
|
}
|
|
207
217
|
|
|
208
218
|
interface WaitContext extends TimingConfig {
|
|
219
|
+
read_machine: () => Promise<MachineReading>
|
|
209
220
|
key: string
|
|
221
|
+
target: string
|
|
210
222
|
directory: string
|
|
211
223
|
budget: number
|
|
212
224
|
deadline: number
|
|
225
|
+
fields: ReservationFields
|
|
213
226
|
}
|
|
214
227
|
|
|
215
228
|
function resolve_timing(options: ReserveOptions): TimingConfig {
|
|
@@ -217,39 +230,96 @@ function resolve_timing(options: ReserveOptions): TimingConfig {
|
|
|
217
230
|
now: options.now ?? Date.now,
|
|
218
231
|
sleep: options.sleep ?? default_sleep,
|
|
219
232
|
poll_interval_ms: options.poll_interval_ms ?? POLL_INTERVAL_MS,
|
|
233
|
+
wait_cap_ms: options.wait_cap_ms ?? WAIT_CAP_MS,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// **A claim alone in the ledger reads nothing from the machine**: the head is admitted whatever the
|
|
238
|
+
// budget says, so a solo run pays no sampling window and starts exactly as it did before.
|
|
239
|
+
async function current_budget(
|
|
240
|
+
context: WaitContext,
|
|
241
|
+
reservations: ReadonlyArray<LiveReservation>,
|
|
242
|
+
): Promise<MachineBudget> {
|
|
243
|
+
if (reservations.length <= 1) return { cores: context.budget, memory_mb: Infinity }
|
|
244
|
+
|
|
245
|
+
const reading = await context.read_machine()
|
|
246
|
+
const ledger = core_admission.ledger_load(reservations, context.now())
|
|
247
|
+
const budget = machine_capacity.machine_budget(context.budget, reading, ledger)
|
|
248
|
+
|
|
249
|
+
return { ...budget, cores: normalize_budget(budget.cores) }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// The claim is admitted: its marker records when, which moves its load from "load outside the ledger"
|
|
253
|
+
// to "load the ledger holds" for every later reader — its cores at once, its memory once its tool has
|
|
254
|
+
// had time to ramp up.
|
|
255
|
+
function admit(context: WaitContext, overflow?: Overflow): Admission {
|
|
256
|
+
write_reservation(context.target, { ...context.fields, admitted_at: context.now() })
|
|
257
|
+
|
|
258
|
+
return { overflow }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function overflow_of(
|
|
262
|
+
context: WaitContext,
|
|
263
|
+
reservations: ReadonlyArray<LiveReservation>,
|
|
264
|
+
budget: MachineBudget,
|
|
265
|
+
): Overflow {
|
|
266
|
+
return {
|
|
267
|
+
waited_ms: context.now() - context.fields.claimed_at,
|
|
268
|
+
ledger: core_admission.sum_load(reservations),
|
|
269
|
+
budget,
|
|
220
270
|
}
|
|
221
271
|
}
|
|
222
272
|
|
|
273
|
+
async function poll_admission(context: WaitContext): Promise<Admission | undefined> {
|
|
274
|
+
const reservations = live_reservations(context.directory)
|
|
275
|
+
const budget = await current_budget(context, reservations)
|
|
276
|
+
|
|
277
|
+
if (core_admission.is_admitted(context.key, reservations, budget)) return admit(context)
|
|
278
|
+
|
|
279
|
+
if (context.now() >= context.deadline) {
|
|
280
|
+
return admit(context, overflow_of(context, reservations, budget))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return undefined
|
|
284
|
+
}
|
|
285
|
+
|
|
223
286
|
// Poll the ledger until this reservation is admitted, or until the wait cap admits it at minimum width.
|
|
224
287
|
// A solo run is admitted on the first read — its own claim is the ledger's head — so the loop body never
|
|
225
288
|
// runs and the wait costs nothing.
|
|
226
|
-
async function await_admission(context: WaitContext): Promise<
|
|
227
|
-
|
|
228
|
-
if (context.now() >= context.deadline) return
|
|
289
|
+
async function await_admission(context: WaitContext): Promise<Admission> {
|
|
290
|
+
let admission = await poll_admission(context)
|
|
229
291
|
|
|
292
|
+
while (admission === undefined) {
|
|
230
293
|
// eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
|
|
231
294
|
await context.sleep(context.poll_interval_ms)
|
|
295
|
+
// eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
|
|
296
|
+
admission = await poll_admission(context)
|
|
232
297
|
}
|
|
298
|
+
|
|
299
|
+
return admission
|
|
233
300
|
}
|
|
234
301
|
|
|
235
302
|
async function reserve(weight: number, options: ReserveOptions = {}): Promise<Handle> {
|
|
236
303
|
const timing = resolve_timing(options)
|
|
237
304
|
const directory = options.directory ?? PLATFORM_TEMP_ROOT
|
|
238
|
-
const budget = normalize_budget(options.budget ?? availableParallelism())
|
|
239
305
|
const claimed_at = timing.now()
|
|
240
306
|
const name = marker_name(randomUUID())
|
|
241
307
|
const target = path.join(directory, name)
|
|
308
|
+
const fields = { weight, claimed_at, memory_mb: options.memory_mb }
|
|
242
309
|
|
|
243
|
-
write_reservation(target,
|
|
244
|
-
await await_admission({
|
|
310
|
+
write_reservation(target, fields)
|
|
311
|
+
const admission = await await_admission({
|
|
245
312
|
...timing,
|
|
246
313
|
key: name,
|
|
314
|
+
target,
|
|
247
315
|
directory,
|
|
248
|
-
budget,
|
|
249
|
-
deadline: claimed_at +
|
|
316
|
+
budget: normalize_budget(options.budget ?? availableParallelism()),
|
|
317
|
+
deadline: claimed_at + timing.wait_cap_ms,
|
|
318
|
+
fields,
|
|
319
|
+
read_machine: options.read_machine ?? machine_capacity.read_machine,
|
|
250
320
|
})
|
|
251
321
|
|
|
252
|
-
return { target }
|
|
322
|
+
return { target, admission }
|
|
253
323
|
}
|
|
254
324
|
|
|
255
325
|
function release(handle: Handle): void {
|
|
@@ -257,30 +327,82 @@ function release(handle: Handle): void {
|
|
|
257
327
|
}
|
|
258
328
|
|
|
259
329
|
// The whole reservation lifetime around a step: claim a place, wait for admission, run, and free the
|
|
260
|
-
// place on any exit. `finally`, so a step that threw still releases its cores.
|
|
330
|
+
// place on any exit. `finally`, so a step that threw still releases its cores. The step is told how it was
|
|
331
|
+
// admitted, so the gate can report one that started past the budget.
|
|
261
332
|
async function with_core_reservation<T>(
|
|
262
333
|
weight: number,
|
|
263
|
-
run: () => Promise<T>,
|
|
334
|
+
run: (admission: Admission) => Promise<T>,
|
|
264
335
|
options: ReserveOptions = {},
|
|
265
336
|
): Promise<T> {
|
|
266
337
|
const handle = await reserve(weight, options)
|
|
267
338
|
|
|
268
339
|
try {
|
|
269
|
-
return await run()
|
|
340
|
+
return await run(handle.admission)
|
|
270
341
|
} finally {
|
|
271
342
|
release(handle)
|
|
272
343
|
}
|
|
273
344
|
}
|
|
274
345
|
|
|
346
|
+
function is_held(): boolean {
|
|
347
|
+
return process.env[HELD_KEY] === HELD_VALUE
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// A function of its own so the restoring write does not sit after an `await`, where
|
|
351
|
+
// `require-atomic-updates` reads any write to `process` as a possible race — the reason `josh.ts`
|
|
352
|
+
// gives `record_exit_code`.
|
|
353
|
+
function set_held_mark(value: string): void {
|
|
354
|
+
process.env[HELD_KEY] = value
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Marks this process, and every child it spawns, as running inside a held reservation. The previous
|
|
358
|
+
// value comes back afterwards, so a nested holder never clears the mark its outer holder set.
|
|
359
|
+
async function with_held_mark<T>(run: () => Promise<T>): Promise<T> {
|
|
360
|
+
const previous = process.env[HELD_KEY] ?? ''
|
|
361
|
+
|
|
362
|
+
set_held_mark(HELD_VALUE)
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
return await run()
|
|
366
|
+
} finally {
|
|
367
|
+
set_held_mark(previous)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// **A command's reservation: the gate's claim, unless a parent already holds the cores**
|
|
372
|
+
// (joshuafolkken/kit#3345). `josh-logic.ts` calls this around every command that declares a weight.
|
|
373
|
+
// Each command then only declares its weight, and no command carries a copy of the claim, run and
|
|
374
|
+
// release steps. A solo run is admitted on the first read, so a run with room on the machine starts
|
|
375
|
+
// with no wait. A command inside a unit suite claims nothing either, for the reason `reserved-run.ts`
|
|
376
|
+
// gives: the suite's own reservation already covers it, and a place claimed there would wait on it.
|
|
377
|
+
async function with_command_reservation<T>(
|
|
378
|
+
weight: number,
|
|
379
|
+
run: () => Promise<T>,
|
|
380
|
+
options: ReserveOptions = {},
|
|
381
|
+
): Promise<T> {
|
|
382
|
+
if (is_held() || unit_worker_share.is_nested_run()) return await run()
|
|
383
|
+
|
|
384
|
+
return await with_core_reservation(weight, async () => await with_held_mark(run), options)
|
|
385
|
+
}
|
|
386
|
+
|
|
275
387
|
const core_budget = {
|
|
388
|
+
CORE_WEIGHTS,
|
|
389
|
+
HELD_KEY,
|
|
390
|
+
HELD_VALUE,
|
|
391
|
+
MEMORY_MB,
|
|
276
392
|
RESERVED_PREFIX,
|
|
277
|
-
admitted_keys,
|
|
278
|
-
admitted_load,
|
|
393
|
+
admitted_keys: core_admission.admitted_keys,
|
|
394
|
+
admitted_load: core_admission.admitted_load,
|
|
395
|
+
describe_overflow: core_admission.describe_overflow,
|
|
396
|
+
is_held,
|
|
279
397
|
live_reservations,
|
|
280
398
|
release,
|
|
281
399
|
reserve,
|
|
400
|
+
with_command_reservation,
|
|
282
401
|
with_core_reservation,
|
|
402
|
+
with_held_mark,
|
|
283
403
|
}
|
|
284
404
|
|
|
285
405
|
export type { LiveReservation, Reservation, ReserveOptions }
|
|
286
406
|
export { core_budget }
|
|
407
|
+
|
|
408
|
+
export { type Admission } from './core-admission'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { availableParallelism } from 'node:os'
|
|
2
2
|
import { josh_verdict } from '#scripts/josh/josh-verdict'
|
|
3
3
|
import { unit_worker_share } from '#scripts/test/unit-worker-share'
|
|
4
|
+
import { core_budget } from './core-budget'
|
|
4
5
|
|
|
5
6
|
// How many of the gate's checks run at once, and how wide the one elastic check may fan out —
|
|
6
7
|
// both derived from the machine rather than fixed at four (joshuafolkken/kit#1258).
|
|
@@ -35,6 +36,9 @@ interface GateCheck {
|
|
|
35
36
|
// The unit suite reserves nothing: it sizes its own pool from the machine, so it is capped
|
|
36
37
|
// instead of reserved, and counting it here would reserve cores against itself.
|
|
37
38
|
reserved_cores: number
|
|
39
|
+
// The memory this check holds while it runs, `core_budget.MEMORY_MB`'s measured peak; none declared is
|
|
40
|
+
// none waited for (joshuafolkken/kit#3371). The unit suite's is per worker, so it is derived instead.
|
|
41
|
+
memory_mb?: number
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
const LINT_LABEL = 'lint'
|
|
@@ -61,15 +65,41 @@ const WARNING_CHECKER_LABELS: ReadonlyArray<string> = [LINT_LABEL, TYPE_CHECK_LA
|
|
|
61
65
|
// check rather than one appended after the unit suite is what keeps the plan's shape intact: the
|
|
62
66
|
// `--no-unit` set stays "the whole gate minus the unit suite", and a CI runner — which has no recorded
|
|
63
67
|
// transcripts — runs it as a trivial green rather than skipping it.
|
|
68
|
+
//
|
|
69
|
+
// **The three reserving weights are `core_budget.CORE_WEIGHTS`** (joshuafolkken/kit#3345), the same
|
|
70
|
+
// numbers the `josh lint`, `josh check` and `josh cspell:dot` commands claim when run directly.
|
|
71
|
+
//
|
|
72
|
+
// **The two zero weights were measured again for joshuafolkken/kit#3345, and they stay zero.** These are
|
|
73
|
+
// warm runs on the 11-core machine on 2026-10-06. `behavior` took 0.6s of wall for 0.7s of CPU, which
|
|
74
|
+
// is node starting up. `exports:unused` took 5.5s of wall for 9.0s of CPU, about 1.6 cores. Floored,
|
|
75
|
+
// that would be one core, but a reservation lasts the whole gate's plan, not the check's five seconds.
|
|
76
|
+
// It would lower `RESERVED_CORES` from 4 to 5 and cut the solo unit cap from 7 workers to 6 for the
|
|
77
|
+
// full length of the unit suite, to cover a burst that ends before lint does. Nothing has measured
|
|
78
|
+
// that trade, so the measured shape stays.
|
|
64
79
|
const STATIC_CHECKS: ReadonlyArray<GateCheck> = [
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
{
|
|
81
|
+
label: LINT_LABEL,
|
|
82
|
+
target: 'lint',
|
|
83
|
+
reserved_cores: core_budget.CORE_WEIGHTS.lint,
|
|
84
|
+
memory_mb: core_budget.MEMORY_MB.lint,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: TYPE_CHECK_LABEL,
|
|
88
|
+
target: 'check',
|
|
89
|
+
reserved_cores: core_budget.CORE_WEIGHTS.type_check,
|
|
90
|
+
memory_mb: core_budget.MEMORY_MB.type_check,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: 'cspell',
|
|
94
|
+
target: 'cspell:dot',
|
|
95
|
+
reserved_cores: core_budget.CORE_WEIGHTS.spell_check,
|
|
96
|
+
memory_mb: core_budget.MEMORY_MB.spell_check,
|
|
97
|
+
},
|
|
68
98
|
{ label: BEHAVIOR_LABEL, target: BEHAVIOR_LABEL, reserved_cores: 0 },
|
|
69
99
|
// **The unused-member check reserves no core** (joshuafolkken/kit#2987): it builds one TypeScript
|
|
70
|
-
// program and walks it
|
|
71
|
-
//
|
|
72
|
-
//
|
|
100
|
+
// program and walks it for about 5s, and it is over well before lint and the type check. A
|
|
101
|
+
// reservation would only take a place from the four-core CI runner's fan-out and a worker from the
|
|
102
|
+
// unit suite for the whole gate.
|
|
73
103
|
{ label: 'exports', target: 'exports:unused', reserved_cores: 0 },
|
|
74
104
|
]
|
|
75
105
|
|
|
@@ -242,6 +272,11 @@ function resolve_unit_worker_cap(
|
|
|
242
272
|
return available_cores - RESERVED_CORES
|
|
243
273
|
}
|
|
244
274
|
|
|
275
|
+
// The unit suite's weight when nothing caps its pool: the cores left once the static checks have theirs.
|
|
276
|
+
function solo_unit_weight(available_cores: number): number {
|
|
277
|
+
return Math.max(0, available_cores - RESERVED_CORES)
|
|
278
|
+
}
|
|
279
|
+
|
|
245
280
|
// The cores a check reserves from the machine-wide budget while it runs (joshuafolkken/kit#2351). The
|
|
246
281
|
// static checks declare their measured reservation; the unit suite declares the pool it will open,
|
|
247
282
|
// which `unit_worker_cap` has already sized against the runs in flight. **This is the same table, read
|
|
@@ -256,7 +291,42 @@ function resolve_unit_worker_cap(
|
|
|
256
291
|
function check_weight(check: GateCheck, plan: GatePlan, available_cores: number): number {
|
|
257
292
|
if (check.label !== UNIT_LABEL) return check.reserved_cores
|
|
258
293
|
|
|
259
|
-
return plan.unit_worker_cap ??
|
|
294
|
+
return plan.unit_worker_cap ?? solo_unit_weight(available_cores)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function unit_memory(workers: number): number {
|
|
298
|
+
return workers * core_budget.MEMORY_MB.unit_worker
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// The memory a check holds (joshuafolkken/kit#3371): its declared figure, or for the unit suite one
|
|
302
|
+
// worker's peak for each core it reserves — its weight is its worker count.
|
|
303
|
+
function check_memory(check: GateCheck, plan: GatePlan, available_cores: number): number {
|
|
304
|
+
if (check.label !== UNIT_LABEL) return check.memory_mb ?? 0
|
|
305
|
+
|
|
306
|
+
return unit_memory(check_weight(check, plan, available_cores))
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// The cores a unit run started directly reserves (joshuafolkken/kit#3345): the share it will pass to
|
|
310
|
+
// vitest while other runs are live, and otherwise the gate's own solo unit weight. **Not the whole
|
|
311
|
+
// machine**, though a lone vitest sizes its own pool: FIFO admission lets a claim of every core in only
|
|
312
|
+
// once the ledger is empty, so a one-file `josh test:related` would wait out every other lane's gate
|
|
313
|
+
// and hold every later claim behind it. One weight for the suite however it is started. **Floored at one
|
|
314
|
+
// worker**: on a machine the static checks fill, a four-core CI runner among them, the gate's solo
|
|
315
|
+
// weight is zero because its siblings hold every core — a direct run has no siblings, and vitest still
|
|
316
|
+
// occupies a core.
|
|
317
|
+
function direct_unit_weight(
|
|
318
|
+
available_cores: number = availableParallelism(),
|
|
319
|
+
live_runs: number = unit_worker_share.live_run_count(),
|
|
320
|
+
): number {
|
|
321
|
+
const weight =
|
|
322
|
+
unit_worker_share.current_share(available_cores, live_runs) ?? solo_unit_weight(available_cores)
|
|
323
|
+
|
|
324
|
+
return Math.max(unit_worker_share.MIN_WORKERS, weight)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// What a unit run started directly holds: one worker's peak for each worker `direct_unit_weight` gives it.
|
|
328
|
+
function direct_unit_memory(): number {
|
|
329
|
+
return unit_memory(direct_unit_weight())
|
|
260
330
|
}
|
|
261
331
|
|
|
262
332
|
// `availableParallelism()` rather than `cpus().length`: it reports what this process may actually
|
|
@@ -344,7 +414,10 @@ const gate_plan = {
|
|
|
344
414
|
TYPE_CHECK_LABEL,
|
|
345
415
|
UNIT_LABEL,
|
|
346
416
|
WARNING_CHECKER_LABELS,
|
|
417
|
+
check_memory,
|
|
347
418
|
check_weight,
|
|
419
|
+
direct_unit_memory,
|
|
420
|
+
direct_unit_weight,
|
|
348
421
|
format_gate_plan,
|
|
349
422
|
format_machine,
|
|
350
423
|
has_unit_check,
|