@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
|
@@ -25,6 +25,8 @@ interface GateStepResult extends BufferedProcessResult {
|
|
|
25
25
|
// What was actually run. The type check's command is resolved per project, so a failure on the
|
|
26
26
|
// `check` step is only reproducible if the header names the command rather than the label.
|
|
27
27
|
command: string
|
|
28
|
+
// Set when the check started past the machine-wide budget at the wait cap (joshuafolkken/kit#3371).
|
|
29
|
+
budget_note?: string | undefined
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
function is_gate_step_failed(result: GateStepResult): boolean {
|
|
@@ -141,25 +143,42 @@ function format_failure_actions(failed: ReadonlyArray<GateStepResult>): string {
|
|
|
141
143
|
return `${GATE_COMMAND} — next:\n${lines.join('\n')}\n`
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
// **A check that started past the core budget is said in the summary, not only in the log**
|
|
147
|
+
// (joshuafolkken/kit#3371). The wait cap admits a check that waited two minutes whatever the budget says,
|
|
148
|
+
// so a gate that ran over-subscribed would otherwise look exactly like one that fitted. The icon is
|
|
149
|
+
// local rather than in `status-icons.ts` because nothing reads this line back.
|
|
150
|
+
const WARN_ICON = '⚠'
|
|
151
|
+
|
|
152
|
+
function format_budget_notes(results: ReadonlyArray<GateStepResult>): string {
|
|
153
|
+
const lines = results.flatMap((result) =>
|
|
154
|
+
result.budget_note === undefined
|
|
155
|
+
? []
|
|
156
|
+
: ` ${WARN_ICON} ${result.label} — ${result.budget_note}`,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return lines.length === 0 ? '' : `${lines.join('\n')}\n`
|
|
160
|
+
}
|
|
161
|
+
|
|
144
162
|
// The total is wall-clock for the whole command, not the sum of the four — they run concurrently,
|
|
145
163
|
// so a sum would report about three times what the caller waited.
|
|
146
164
|
//
|
|
147
165
|
// The failure actions and the log path are printed with the summary and immediately **above** the
|
|
148
166
|
// verdict line, which stays last for the reason `josh-verdict.ts` gives (joshuafolkken/kit#1227).
|
|
149
167
|
function print_gate_summary(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
step_count: string,
|
|
168
|
+
results: ReadonlyArray<GateStepResult>,
|
|
169
|
+
report: GateReport,
|
|
153
170
|
log_path?: string,
|
|
154
171
|
): void {
|
|
155
|
-
const
|
|
172
|
+
const failed = results.filter((result) => is_gate_step_failed(result))
|
|
173
|
+
const total = format_seconds(report.elapsed_ms)
|
|
156
174
|
const verdict =
|
|
157
175
|
failed.length === 0
|
|
158
|
-
? josh_verdict.format_gate_passed(step_count, total)
|
|
176
|
+
? josh_verdict.format_gate_passed(report.step_count, total)
|
|
159
177
|
: josh_verdict.format_gate_failed(failed.map((result) => result.label).join(', '), total)
|
|
178
|
+
const notes = format_budget_notes(results)
|
|
160
179
|
|
|
161
180
|
process.stdout.write(
|
|
162
|
-
`\n${format_failure_actions(failed)}${gate_log.format_log_notice(log_path)}${verdict}\n`,
|
|
181
|
+
`\n${notes}${format_failure_actions(failed)}${gate_log.format_log_notice(log_path)}${verdict}\n`,
|
|
163
182
|
)
|
|
164
183
|
}
|
|
165
184
|
|
|
@@ -183,15 +202,15 @@ function report_gate_steps(
|
|
|
183
202
|
header: gate_step_header(result),
|
|
184
203
|
output: result.output,
|
|
185
204
|
}))
|
|
186
|
-
const failed = results.filter((result) => is_gate_step_failed(result))
|
|
187
205
|
const written_log = gate_log.write_gate_log(entries, report.log_path)
|
|
188
206
|
|
|
189
|
-
print_gate_summary(
|
|
207
|
+
print_gate_summary(results, report, written_log)
|
|
190
208
|
|
|
191
|
-
return
|
|
209
|
+
return results.filter((result) => is_gate_step_failed(result)).map((result) => result.label)
|
|
192
210
|
}
|
|
193
211
|
|
|
194
212
|
const gate_report = {
|
|
213
|
+
format_budget_notes,
|
|
195
214
|
format_failure_actions,
|
|
196
215
|
has_checker_warning,
|
|
197
216
|
is_gate_step_failed,
|
|
@@ -60,18 +60,42 @@ const FORCE_FLAG = '--force'
|
|
|
60
60
|
//
|
|
61
61
|
// A `base` that could not be read is `undefined`, and an older record carries none: both compare
|
|
62
62
|
// unequal to anything, so the gate runs its checks. No base means no reuse, never reuse without one.
|
|
63
|
-
|
|
63
|
+
//
|
|
64
|
+
// **A refusal carries its reason** (joshuafolkken/kit#3307): a pre-push hook that fell back to the
|
|
65
|
+
// whole unit suite said nothing about why, so a merge base moved by ship's main sync and a lock file
|
|
66
|
+
// rewritten by the hook's own install each cost minutes before anyone could tell them apart.
|
|
67
|
+
type GateReuse = { stamp: FileMapStamp } | { miss: string }
|
|
68
|
+
|
|
69
|
+
function base_moved(recorded: string | undefined, base: string): string {
|
|
70
|
+
return `the merge base moved since the green gate (${recorded ?? 'none'} → ${base})`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function gate_reuse(
|
|
64
74
|
tree: Record<string, string>,
|
|
65
75
|
base: string | undefined,
|
|
66
76
|
source?: string,
|
|
67
|
-
):
|
|
68
|
-
if (base === undefined
|
|
77
|
+
): GateReuse {
|
|
78
|
+
if (base === undefined) return { miss: 'the merge base could not be read' }
|
|
79
|
+
if (Object.keys(tree).length === 0) return { miss: 'no file differs from the merge base' }
|
|
69
80
|
|
|
70
81
|
const stamp = review_brief.matching_stamp(review_stamps.gate_stamp.read(source), tree)
|
|
71
82
|
|
|
72
|
-
if (stamp
|
|
83
|
+
if (stamp === undefined) return { miss: 'no green gate record matches the changed files' }
|
|
84
|
+
if (stamp.base !== base) return { miss: base_moved(stamp.base, base) }
|
|
85
|
+
|
|
86
|
+
return { stamp }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function stamp_of(reuse: GateReuse): FileMapStamp | undefined {
|
|
90
|
+
return 'stamp' in reuse ? reuse.stamp : undefined
|
|
91
|
+
}
|
|
73
92
|
|
|
74
|
-
|
|
93
|
+
function reusable_green_gate(
|
|
94
|
+
tree: Record<string, string>,
|
|
95
|
+
base: string | undefined,
|
|
96
|
+
source?: string,
|
|
97
|
+
): FileMapStamp | undefined {
|
|
98
|
+
return stamp_of(gate_reuse(tree, base, source))
|
|
75
99
|
}
|
|
76
100
|
|
|
77
101
|
// **The sentence claims the result, and never merely the omission.** "verification skipped" on its
|
|
@@ -116,8 +140,15 @@ function format_skip(taken_at: string): string {
|
|
|
116
140
|
})
|
|
117
141
|
}
|
|
118
142
|
|
|
119
|
-
const gate_skip = {
|
|
143
|
+
const gate_skip = {
|
|
144
|
+
FORCE_FLAG,
|
|
145
|
+
format_reuse_notice,
|
|
146
|
+
format_skip,
|
|
147
|
+
gate_reuse,
|
|
148
|
+
reusable_green_gate,
|
|
149
|
+
stamp_of,
|
|
150
|
+
}
|
|
120
151
|
|
|
121
|
-
export type { ReuseNotice }
|
|
152
|
+
export type { GateReuse, ReuseNotice }
|
|
122
153
|
|
|
123
154
|
export { gate_skip }
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { git_command } from '#scripts/git/git-command'
|
|
2
|
-
import type { FileMapStamp } from '#scripts/josh/file-map-stamp'
|
|
3
2
|
import { observation_ledger } from '#scripts/observations/observation-ledger'
|
|
4
|
-
import { gate_skip } from './gate-skip'
|
|
3
|
+
import { gate_skip, type GateReuse } from './gate-skip'
|
|
5
4
|
import type { GateTree } from './gate-tree'
|
|
6
5
|
|
|
7
6
|
// What the two git hooks share when they decline to re-run a check `pnpm josh gate` already passed on
|
|
@@ -94,6 +93,8 @@ interface HookReuse {
|
|
|
94
93
|
// The hook-specific narrowing: whether what this git operation carries is the tree the record
|
|
95
94
|
// describes. `is_worktree_clean` for a push, `is_index_matching_worktree` for a commit.
|
|
96
95
|
is_tree_carried: boolean
|
|
96
|
+
// The reason a refusal on that narrowing reports, in the hook's own words (joshuafolkken/kit#3307).
|
|
97
|
+
carry_miss: string
|
|
97
98
|
extra_arguments: ReadonlyArray<string>
|
|
98
99
|
force_env: string
|
|
99
100
|
// The record to read, so a test can plant one without overwriting the record a live run relies on.
|
|
@@ -102,27 +103,28 @@ interface HookReuse {
|
|
|
102
103
|
source?: string | undefined
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
// The stamp rather than a boolean, for the same reason `gate_skip.
|
|
106
|
-
//
|
|
107
|
-
// printing.
|
|
106
|
+
// The stamp rather than a boolean, for the same reason `gate_skip.gate_reuse` hands one back: the
|
|
107
|
+
// caller prints `taken_at`, and a record that does not describe this tree has no timestamp worth
|
|
108
|
+
// printing. A refusal carries its reason instead (joshuafolkken/kit#3307).
|
|
108
109
|
//
|
|
109
110
|
// **Any argument at all refuses the reuse**, not only the force flag. A caller who narrowed the run
|
|
110
111
|
// asked for that run rather than for a recorded result about a whole tree, and a hook's own line passes
|
|
111
112
|
// none, so this costs it nothing.
|
|
112
|
-
function
|
|
113
|
-
if (input.extra_arguments.length > 0
|
|
114
|
-
if (
|
|
113
|
+
function hook_reuse(input: HookReuse): GateReuse {
|
|
114
|
+
if (input.extra_arguments.length > 0) return { miss: 'arguments were passed to the hook' }
|
|
115
|
+
if (is_force_requested(input.force_env)) return { miss: `${input.force_env} is set` }
|
|
116
|
+
if (!input.is_tree_carried) return { miss: input.carry_miss }
|
|
115
117
|
|
|
116
|
-
return gate_skip.
|
|
118
|
+
return gate_skip.gate_reuse(input.tree.files, input.tree.base, input.source)
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
const hook_gate_reuse = {
|
|
120
122
|
GATE_REUSING_TARGETS,
|
|
123
|
+
hook_reuse,
|
|
121
124
|
is_force_requested,
|
|
122
125
|
is_index_matching_worktree,
|
|
123
126
|
is_worktree_clean,
|
|
124
127
|
read_status_lines,
|
|
125
|
-
reusable_green_hook,
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
export type { HookReuse }
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { availableParallelism, cpus, totalmem } from 'node:os'
|
|
3
|
+
import { execa } from 'execa'
|
|
4
|
+
|
|
5
|
+
// What the machine has free right now, read for the core budget's admission (joshuafolkken/kit#3371).
|
|
6
|
+
//
|
|
7
|
+
// **The budget used to be the core count, and that count is not what is free.** The ledger in
|
|
8
|
+
// `core-budget.ts` admits against `availableParallelism()`, so the load it never sees — Claude sessions,
|
|
9
|
+
// node processes that do not pass through `josh`, the window server — ran on top of a budget already
|
|
10
|
+
// spent. On 2026-10-07, three lanes on the 11-core / 18 GB machine reached a load average of 16.8 with
|
|
11
|
+
// 31% of the CPU in the kernel, compressing and swapping 4.4 GB of memory the ledger never counted.
|
|
12
|
+
//
|
|
13
|
+
// **This is a closed loop, not the open-loop formula joshuafolkken/kit#1637 rejected.** That proposal
|
|
14
|
+
// derived a share from the core count up front; this reads what is busy and free at the moment of
|
|
15
|
+
// admission and subtracts only the part the ledger does not already account for.
|
|
16
|
+
//
|
|
17
|
+
// **The busy cores are the CPU-time difference over a short window, not the load average.** The load
|
|
18
|
+
// average trails by a minute, so a gate started just after another one finished would see the finished
|
|
19
|
+
// one's load as external and wait out the decay. `os.cpus()` is one read with no process spawned.
|
|
20
|
+
//
|
|
21
|
+
// **A reading that fails answers undefined, and undefined is the old behavior.** A CI runner or a
|
|
22
|
+
// container — where the host's CPU times are not this process's quota — budgets by the core count and
|
|
23
|
+
// admits without looking at memory, exactly as before.
|
|
24
|
+
|
|
25
|
+
// The window the busy cores are measured over: long enough that one scheduler tick does not dominate it,
|
|
26
|
+
// short enough that a solo admission is not visibly delayed.
|
|
27
|
+
const SAMPLE_WINDOW_MS = 250
|
|
28
|
+
// **The background a developer machine always carries, which the gate's weights were measured beside.**
|
|
29
|
+
// On 2026-10-07 the 11-core machine idled at about 1.5 cores outside `josh` — Claude sessions 0.55, the
|
|
30
|
+
// window server 0.47, a browser 0.5. The solo gate's weights sum to exactly the core count and were
|
|
31
|
+
// measured with that load present, so only what exceeds it is load the budget must give way to;
|
|
32
|
+
// counting the baseline would defer a solo gate's unit suite on every run.
|
|
33
|
+
const BASELINE_CORES = 2
|
|
34
|
+
const BYTES_PER_MB = 1_048_576
|
|
35
|
+
const KB_PER_MB = 1024
|
|
36
|
+
const PERCENT = 100
|
|
37
|
+
const MEMINFO_PATH = '/proc/meminfo'
|
|
38
|
+
const LINUX_AVAILABLE = /^MemAvailable:\s+(?<kb>\d+) kB/mu
|
|
39
|
+
// The kernel's own memory-pressure figure: the percentage of memory still available, compressible and
|
|
40
|
+
// purgeable pages included — what `os.freemem()` leaves out on macOS, where it reads near zero on a
|
|
41
|
+
// machine with gigabytes to spare.
|
|
42
|
+
const PRESSURE_ARGUMENTS = ['-n', 'kern.memorystatus_level']
|
|
43
|
+
const PRESSURE_LEVEL = /^\s*(?<level>\d+)\s*$/u
|
|
44
|
+
|
|
45
|
+
interface MachineReading {
|
|
46
|
+
busy_cores: number | undefined
|
|
47
|
+
available_mb: number | undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// What the ledger's admitted reservations declare — the part of the machine's load already accounted for.
|
|
51
|
+
interface LedgerLoad {
|
|
52
|
+
cores: number
|
|
53
|
+
memory_mb: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface MachineBudget {
|
|
57
|
+
cores: number
|
|
58
|
+
memory_mb: number
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface CpuTimes {
|
|
62
|
+
busy: number
|
|
63
|
+
total: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function cpu_times(): CpuTimes {
|
|
67
|
+
let busy = 0
|
|
68
|
+
let idle = 0
|
|
69
|
+
|
|
70
|
+
for (const { times } of cpus()) {
|
|
71
|
+
busy += times.user + times.nice + times.sys + times.irq
|
|
72
|
+
idle += times.idle
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { busy, total: busy + idle }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function busy_cores_between(before: CpuTimes, after: CpuTimes, cores: number): number | undefined {
|
|
79
|
+
const total = after.total - before.total
|
|
80
|
+
|
|
81
|
+
if (total <= 0) return undefined
|
|
82
|
+
|
|
83
|
+
return ((after.busy - before.busy) / total) * cores
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// `kern.memorystatus_level` → `58`: the share of physical memory still available, as a percentage.
|
|
87
|
+
function parse_darwin_pressure(output: string, total_bytes: number): number | undefined {
|
|
88
|
+
const level = PRESSURE_LEVEL.exec(output)?.groups?.['level']
|
|
89
|
+
|
|
90
|
+
if (level === undefined) return undefined
|
|
91
|
+
|
|
92
|
+
return (Number(level) / PERCENT) * (total_bytes / BYTES_PER_MB)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parse_linux_available(meminfo: string): number | undefined {
|
|
96
|
+
const kb = LINUX_AVAILABLE.exec(meminfo)?.groups?.['kb']
|
|
97
|
+
|
|
98
|
+
return kb === undefined ? undefined : Number(kb) / KB_PER_MB
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function read_available_mb(
|
|
102
|
+
platform: NodeJS.Platform = process.platform,
|
|
103
|
+
): Promise<number | undefined> {
|
|
104
|
+
try {
|
|
105
|
+
if (platform === 'darwin') {
|
|
106
|
+
const { stdout } = await execa('sysctl', PRESSURE_ARGUMENTS)
|
|
107
|
+
|
|
108
|
+
return parse_darwin_pressure(stdout, totalmem())
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return platform === 'linux'
|
|
112
|
+
? parse_linux_available(await readFile(MEMINFO_PATH, 'utf8'))
|
|
113
|
+
: undefined
|
|
114
|
+
} catch {
|
|
115
|
+
return undefined
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A CPU quota narrower than the host makes the host-wide CPU times the wrong machine, so the busy cores
|
|
120
|
+
// are read only where the quota and the host agree.
|
|
121
|
+
function is_unquoted(): boolean {
|
|
122
|
+
return cpus().length > 0 && cpus().length === availableParallelism()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function default_sleep(ms: number): Promise<void> {
|
|
126
|
+
await new Promise<void>((resolve) => {
|
|
127
|
+
setTimeout(resolve, ms)
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function read_machine(
|
|
132
|
+
sleep: (ms: number) => Promise<void> = default_sleep,
|
|
133
|
+
): Promise<MachineReading> {
|
|
134
|
+
const before = cpu_times()
|
|
135
|
+
const [available_mb] = await Promise.all([read_available_mb(), sleep(SAMPLE_WINDOW_MS)])
|
|
136
|
+
const busy_cores = is_unquoted()
|
|
137
|
+
? busy_cores_between(before, cpu_times(), cpus().length)
|
|
138
|
+
: undefined
|
|
139
|
+
|
|
140
|
+
return { busy_cores, available_mb }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// The busy cores the ledger does not account for, beyond the baseline the weights were measured beside,
|
|
144
|
+
// in whole cores — the budget is in whole cores, and a fraction of a core is not a core to give up.
|
|
145
|
+
function external_cores(busy_cores: number | undefined, ledger_cores: number): number {
|
|
146
|
+
if (busy_cores === undefined) return 0
|
|
147
|
+
|
|
148
|
+
return Math.floor(Math.max(0, busy_cores - ledger_cores - BASELINE_CORES))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// **The budget the ledger admits against: what is free plus what the ledger itself already holds.**
|
|
152
|
+
// The admitted reservations' load is inside the reading, and the admission walk counts their declared
|
|
153
|
+
// weight again, so it is added back — what is subtracted is only the load from outside the ledger. The
|
|
154
|
+
// caller passes every admitted claim's cores but only the memory of claims past
|
|
155
|
+
// `core_admission.RAMP_UP_MS`, whose tools have had time to allocate.
|
|
156
|
+
// Memory that could not be read is no limit at all, the behavior before memory was read.
|
|
157
|
+
function machine_budget(cores: number, reading: MachineReading, ledger: LedgerLoad): MachineBudget {
|
|
158
|
+
return {
|
|
159
|
+
cores: cores - external_cores(reading.busy_cores, ledger.cores),
|
|
160
|
+
memory_mb:
|
|
161
|
+
reading.available_mb === undefined ? Infinity : reading.available_mb + ledger.memory_mb,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const machine_capacity = {
|
|
166
|
+
BASELINE_CORES,
|
|
167
|
+
busy_cores_between,
|
|
168
|
+
machine_budget,
|
|
169
|
+
parse_darwin_pressure,
|
|
170
|
+
parse_linux_available,
|
|
171
|
+
read_machine,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type { LedgerLoad, MachineBudget, MachineReading }
|
|
175
|
+
export { machine_capacity }
|
|
@@ -96,14 +96,17 @@ async function reusable_green_commit(
|
|
|
96
96
|
source?: string,
|
|
97
97
|
start_directory: string = process.cwd(),
|
|
98
98
|
): Promise<FileMapStamp | undefined> {
|
|
99
|
-
const
|
|
99
|
+
const reuse = hook_gate_reuse.hook_reuse({
|
|
100
100
|
tree,
|
|
101
101
|
is_tree_carried: tree.is_index_matching_worktree,
|
|
102
|
+
carry_miss: 'the index differs from the working tree',
|
|
102
103
|
extra_arguments,
|
|
103
104
|
force_env: FORCE_ENV,
|
|
104
105
|
source,
|
|
105
106
|
})
|
|
106
107
|
|
|
108
|
+
const reusable = gate_skip.stamp_of(reuse)
|
|
109
|
+
|
|
107
110
|
if (reusable === undefined) return undefined
|
|
108
111
|
|
|
109
112
|
return (await is_recorded_check_this_check(start_directory)) ? reusable : undefined
|
|
@@ -2,6 +2,7 @@ import { existsSync, globSync, readFileSync } from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { find_local_bin_upwards } from '#scripts/build/local-bin'
|
|
4
4
|
import { project_profile } from '#scripts/init/project-profile'
|
|
5
|
+
import { ancestor_directories } from '#scripts/lib/ancestor-directories'
|
|
5
6
|
import { json_value } from '#scripts/lib/json-value'
|
|
6
7
|
import { SKIP_MARKER } from '#scripts/test/skip-marker'
|
|
7
8
|
import { z } from 'zod'
|
|
@@ -43,15 +44,11 @@ const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies']
|
|
|
43
44
|
const object_schema = z.record(z.string(), z.unknown())
|
|
44
45
|
|
|
45
46
|
function project_root(directory: string): string {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
current = parent
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return current
|
|
47
|
+
return (
|
|
48
|
+
ancestor_directories.nearest(directory, (candidate) =>
|
|
49
|
+
existsSync(path.join(candidate, PACKAGE_JSON)),
|
|
50
|
+
) ?? directory
|
|
51
|
+
)
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
function is_ignored(entry: string): boolean {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { find_local_bin_upwards } from '#scripts/build/local-bin'
|
|
2
|
+
import { COMMAND_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
2
3
|
import { execa } from 'execa'
|
|
3
4
|
|
|
4
5
|
// joshuafolkken/kit#934: `josh gate`'s type-check step used to be `tsc --noEmit` for every project.
|
|
@@ -28,8 +29,6 @@ const TOOLKIT_BINS: ReadonlyArray<string> = ['josh-app', 'josh-game']
|
|
|
28
29
|
// a bracketed warning printed first would otherwise be read as the command list.
|
|
29
30
|
const USAGE_COMMANDS_PATTERN = /Usage:[^<>\n]*<([^<>\n]+)>/u
|
|
30
31
|
const COMMAND_SEPARATOR = '|'
|
|
31
|
-
// A toolkit that prints its usage answers in milliseconds; anything slower is hung.
|
|
32
|
-
const USAGE_TIMEOUT_MS = 30_000
|
|
33
32
|
|
|
34
33
|
function parse_usage_commands(output: string): ReadonlyArray<string> {
|
|
35
34
|
const group = USAGE_COMMANDS_PATTERN.exec(output)?.[1]
|
|
@@ -46,7 +45,7 @@ async function read_toolkit_commands(bin_path: string): Promise<ReadonlyArray<st
|
|
|
46
45
|
const result = await execa(bin_path, [], {
|
|
47
46
|
all: true,
|
|
48
47
|
reject: false,
|
|
49
|
-
timeout:
|
|
48
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
50
49
|
})
|
|
51
50
|
|
|
52
51
|
return parse_usage_commands(result.all)
|
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
4
4
|
import type { FileMapStamp } from '#scripts/josh/file-map-stamp'
|
|
5
5
|
import { GATE_COMMAND } from '#scripts/josh/josh-command-types'
|
|
6
6
|
import { composite_arguments, USAGE_ERROR_EXIT_CODE } from '#scripts/josh/josh-composite-arguments'
|
|
7
|
+
import { lane_ledger } from '#scripts/lane/lane-ledger'
|
|
7
8
|
import { bounded_pool } from '#scripts/lib/bounded-pool'
|
|
8
9
|
import { buffered_process, FAIL_EXIT_CODE } from '#scripts/lib/buffered-process'
|
|
9
10
|
import { review_stamps } from '#scripts/review/review-stamps'
|
|
@@ -166,7 +167,7 @@ interface BudgetContext {
|
|
|
166
167
|
function default_budget(): BudgetContext {
|
|
167
168
|
return {
|
|
168
169
|
available_cores: availableParallelism(),
|
|
169
|
-
is_reserved: !unit_worker_share.is_nested_run(),
|
|
170
|
+
is_reserved: !unit_worker_share.is_nested_run() && !core_budget.is_held(),
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
173
|
|
|
@@ -176,6 +177,7 @@ function default_budget(): BudgetContext {
|
|
|
176
177
|
interface ReservedStep {
|
|
177
178
|
step: GateStep
|
|
178
179
|
weight: number
|
|
180
|
+
memory_mb: number
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
async function build_reserved_steps(
|
|
@@ -187,6 +189,7 @@ async function build_reserved_steps(
|
|
|
187
189
|
plan.checks.map(async (check) => ({
|
|
188
190
|
step: await build_gate_step(check, start_directory, plan),
|
|
189
191
|
weight: gate_plan.check_weight(check, plan, available_cores),
|
|
192
|
+
memory_mb: gate_plan.check_memory(check, plan, available_cores),
|
|
190
193
|
})),
|
|
191
194
|
)
|
|
192
195
|
}
|
|
@@ -204,8 +207,11 @@ async function run_reserved_step(
|
|
|
204
207
|
|
|
205
208
|
return await core_budget.with_core_reservation(
|
|
206
209
|
reserved.weight,
|
|
207
|
-
async () =>
|
|
208
|
-
|
|
210
|
+
async (admission) => ({
|
|
211
|
+
...(await run_gate_step(reserved.step)),
|
|
212
|
+
budget_note: core_budget.describe_overflow(admission),
|
|
213
|
+
}),
|
|
214
|
+
{ budget: budget.available_cores, memory_mb: reserved.memory_mb },
|
|
209
215
|
)
|
|
210
216
|
}
|
|
211
217
|
|
|
@@ -222,10 +228,17 @@ async function run_gate_steps(
|
|
|
222
228
|
): Promise<ReadonlyArray<GateStepResult>> {
|
|
223
229
|
const reserved = await build_reserved_steps(process.cwd(), plan, budget.available_cores)
|
|
224
230
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
231
|
+
// **The checks run under the held mark, so a check's own dispatch claims nothing**
|
|
232
|
+
// (joshuafolkken/kit#3345). Each check is a `pnpm josh <target>` child, and a target that declares
|
|
233
|
+
// a weight would otherwise claim a second place for the cores reserved for it here. A nested gate
|
|
234
|
+
// sets the mark too, since its checks are covered by the outer gate's reservation.
|
|
235
|
+
return await core_budget.with_held_mark(
|
|
236
|
+
async () =>
|
|
237
|
+
await bounded_pool.bounded_map(
|
|
238
|
+
reserved,
|
|
239
|
+
plan.concurrency,
|
|
240
|
+
async (entry) => await run_reserved_step(entry, budget),
|
|
241
|
+
),
|
|
229
242
|
)
|
|
230
243
|
}
|
|
231
244
|
|
|
@@ -299,6 +312,9 @@ interface GateOptions {
|
|
|
299
312
|
// The scoped records' paths, overridable so a test can plant a green pair rather than depend on the
|
|
300
313
|
// surrounding checkout's — the reason `scoped-green.ts` takes a `source` per stamp.
|
|
301
314
|
scoped_sources?: ScopedSources
|
|
315
|
+
// The lane ledger a finished gate's duration is appended to (joshuafolkken/kit#3355). Only the CLI
|
|
316
|
+
// entry resolves it, so a suite driving the gate never writes a measurement of checks that never ran.
|
|
317
|
+
ledger_path?: string | undefined
|
|
302
318
|
}
|
|
303
319
|
|
|
304
320
|
// **The gate refuses to start when the scoped pair has not been green on this tree**
|
|
@@ -339,14 +355,20 @@ async function settle_gate(
|
|
|
339
355
|
tree: GateTree,
|
|
340
356
|
options: GateOptions & { started_at: number },
|
|
341
357
|
): Promise<number> {
|
|
358
|
+
const elapsed_ms = performance.now() - options.started_at
|
|
342
359
|
const failed_labels = gate_report.report_gate_steps(results, {
|
|
343
360
|
is_verbose: options.is_verbose ?? false,
|
|
344
361
|
log_path: options.log_path,
|
|
345
|
-
elapsed_ms
|
|
362
|
+
elapsed_ms,
|
|
346
363
|
step_count: String(plan.checks.length),
|
|
347
364
|
})
|
|
365
|
+
const is_passed = failed_labels.length === 0
|
|
348
366
|
|
|
349
|
-
if (
|
|
367
|
+
if (options.ledger_path !== undefined) {
|
|
368
|
+
await lane_ledger.record_gate(options.ledger_path, elapsed_ms, is_passed)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!is_passed) return FAIL_EXIT_CODE
|
|
350
372
|
|
|
351
373
|
await record_whole_gate(plan, results, tree, options)
|
|
352
374
|
|
|
@@ -474,7 +496,9 @@ async function run_gate_command(
|
|
|
474
496
|
// summary, written last, is the first thing lost. Setting the code lets the writes drain and the
|
|
475
497
|
// process end on its own, which it can, since every child has already exited by here.
|
|
476
498
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
477
|
-
process.exitCode = await run_gate_command(process.argv.slice(FIRST_ARGUMENT_INDEX)
|
|
499
|
+
process.exitCode = await run_gate_command(process.argv.slice(FIRST_ARGUMENT_INDEX), {
|
|
500
|
+
ledger_path: await lane_ledger.target(),
|
|
501
|
+
})
|
|
478
502
|
}
|
|
479
503
|
|
|
480
504
|
const verification_gate = {
|
|
@@ -14,8 +14,8 @@ const FULL_PAGE_QUERY = '?per_page=100'
|
|
|
14
14
|
// `gh api` expands `{owner}` and `{repo}` from the current repository, so the unqualified form needs
|
|
15
15
|
// no extra lookup to name a path. An explicit `repo` names another repository — the form a
|
|
16
16
|
// cross-repository reference takes.
|
|
17
|
-
function repo_api_path(repo
|
|
18
|
-
return `repos/${repo
|
|
17
|
+
function repo_api_path(repo = '{owner}/{repo}'): string {
|
|
18
|
+
return `repos/${repo}`
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// The issue collection — what a listing pages through and what a creation posts to. One segment,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { blocking_issue_schema } from '#scripts/git/git-schemas'
|
|
1
2
|
import {
|
|
2
3
|
parse_json_array_or_undefined,
|
|
3
4
|
parse_json_object_safe,
|
|
4
5
|
} from '#scripts/git/parse-json-array'
|
|
5
|
-
import { blocking_issue_schema } from '#scripts/git/schemas'
|
|
6
6
|
import type { IssueReference } from '#scripts/issue/issue-reference'
|
|
7
7
|
import { z } from 'zod'
|
|
8
8
|
import { MERGED_STATE, to_gh_state } from './git-gh-rest-state'
|
|
@@ -163,8 +163,8 @@ function empty_blocked_by(): BlockedBy {
|
|
|
163
163
|
// repository and names a different issue there (joshuafolkken/kit#1126). Undefined when the field is
|
|
164
164
|
// absent or shaped otherwise; the caller then falls back to the repository it is reading in, which is
|
|
165
165
|
// what an unqualified relation has always meant.
|
|
166
|
-
function repo_of_url(repository_url: string | undefined): string | undefined {
|
|
167
|
-
const match = REPOSITORY_URL.exec(repository_url
|
|
166
|
+
function repo_of_url(repository_url: string | undefined = ''): string | undefined {
|
|
167
|
+
const match = REPOSITORY_URL.exec(repository_url)
|
|
168
168
|
|
|
169
169
|
return match?.[REPOSITORY_URL_GROUP]
|
|
170
170
|
}
|