@joshuafolkken/kit 1.1178.0 → 1.1229.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 +6 -2
- package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
- package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
- 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 +5 -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 +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 +48 -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 +26 -19
- 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-S6X6WYKR.js → chunk-55HHG5PX.js} +1210 -711
- package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-5VMKCCC4.js} +25 -10
- package/dist/hooks/chunk-5VNDSNG3.js +12 -0
- package/dist/hooks/{chunk-32GQTL4F.js → chunk-6HJJXRBX.js} +4 -2
- package/dist/hooks/chunk-CDY4NFQG.js +15 -0
- package/dist/hooks/chunk-ELCN5LCH.js +135 -0
- package/dist/hooks/{chunk-VESKRQOF.js → chunk-F56MFS4B.js} +3 -3
- package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-FIVDHQGT.js} +7 -6
- package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
- package/dist/hooks/{chunk-G6KRCKEC.js → chunk-JUDKDCDP.js} +496 -378
- package/dist/hooks/{chunk-GDS5N2KB.js → chunk-NV2JRZZO.js} +718 -681
- package/dist/hooks/{chunk-VQLLNFNT.js → chunk-RNWHN5BP.js} +3 -3
- package/dist/hooks/{chunk-DO6NWWHU.js → chunk-TQNPDZNH.js} +4 -4
- package/dist/hooks/{chunk-I4XSL4TT.js → chunk-VC3PZPZO.js} +1 -1
- 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 +217 -195
- 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 -58
- package/dist/josh.js +868 -74
- 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/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-budget.ts +69 -1
- package/scripts/gate/gate-plan.ts +47 -7
- package/scripts/gate/gate-skip.ts +38 -7
- package/scripts/gate/hook-gate-reuse.ts +12 -10
- 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 +27 -8
- 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 +5 -15
- 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/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 +1 -1
- 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 +45 -18
- package/scripts/issue/issue-file.ts +24 -1
- 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 +11 -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 +10 -0
- package/scripts/josh/josh-commands-document.ts +1 -0
- package/scripts/josh/josh-commands-lane.ts +22 -0
- package/scripts/josh/josh-commands-maintenance.ts +4 -4
- package/scripts/josh/josh-commands-run.ts +12 -7
- package/scripts/josh/josh-in-process.ts +28 -9
- package/scripts/josh/josh-logic.ts +33 -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 +3 -3
- 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 +1 -1
- 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-reap.ts +1 -3
- package/scripts/lane/lane-relaunch.ts +1 -1
- package/scripts/lane/lane-stats.ts +224 -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/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} +2 -2
- 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-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} +16 -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 +2 -2
- 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} +23 -8
- package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
- 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} +8 -2
- 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} +1 -1
- 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 +17 -17
- 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 +2 -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-render.ts → event/run-event-render.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,142 @@
|
|
|
1
|
+
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
2
|
+
import { git_gh_helpers } from '#scripts/gh/git-gh-helpers'
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_STABLE_READS,
|
|
5
|
+
git_pr_checks,
|
|
6
|
+
WATCH_CONFIRMED_STABLE_READS,
|
|
7
|
+
} from '#scripts/gh/git-pr-checks'
|
|
8
|
+
import { is_coderabbit_check } from '#scripts/gh/git-pr-checks-eval'
|
|
9
|
+
import { CHECK_STATUS_PASS, type PrStateSnapshot } from '#scripts/gh/git-pr-checks-parse'
|
|
10
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
11
|
+
|
|
12
|
+
// The check wait and the notes read off its snapshot — split out of `git-pr-followup.ts` when it
|
|
13
|
+
// reached its line limit (joshuafolkken/kit#3264). `git-pr-followup` re-exports each name it always
|
|
14
|
+
// exported, so the move changed no call site and no suite that imports from it.
|
|
15
|
+
|
|
16
|
+
// Named so a test can pin the note without restating it (joshuafolkken/kit#999).
|
|
17
|
+
const WATCH_FAILED_NOTE = 'pr checks --watch failed; falling through to polling'
|
|
18
|
+
|
|
19
|
+
// The watch is a look ahead, not a gate. It fails when **any** check has failed — CodeRabbit
|
|
20
|
+
// included — and letting that escape ended the run before `evaluate_pr_state` could apply kit#753's
|
|
21
|
+
// CodeRabbit exemption at all. It only ever bit where every check finished inside the two-minute
|
|
22
|
+
// window, so a repository with a slow E2E never saw it and a fast one always would
|
|
23
|
+
// (joshuafolkken/kit#999). What the watch is has since changed — `gh pr checks --watch` went through
|
|
24
|
+
// GraphQL, so joshuafolkken/kit#1028 replaced it with the same poll loop bounded to two minutes —
|
|
25
|
+
// but its place here has not.
|
|
26
|
+
//
|
|
27
|
+
// Falling through costs nothing: whether the merge may proceed is decided in one place below, and a
|
|
28
|
+
// genuinely failing non-CodeRabbit check still ends the wait on the first poll by kit#990's
|
|
29
|
+
// fast-fail rather than by that failure.
|
|
30
|
+
// The watch fails for two different reasons — "a check failed" and "no checks reported on this
|
|
31
|
+
// branch" — and its own error does not say which. The pull request itself can: a failed check leaves
|
|
32
|
+
// a rollup, and a branch with no checks leaves it empty. Falling through on the empty case would
|
|
33
|
+
// trade a failure reported in seconds for the whole budget spent waiting on a required check that is
|
|
34
|
+
// missing rather than pending, so that one is rethrown and the old behavior kept.
|
|
35
|
+
//
|
|
36
|
+
// **Read from the raw payload, not from `parse_pr_state_snapshot`.** That parser never throws: a
|
|
37
|
+
// malformed answer or a schema mismatch degrades to `rollup: []`, which is indistinguishable there
|
|
38
|
+
// from a branch that genuinely has no checks. Rethrowing on it would put an unreadable answer back
|
|
39
|
+
// on the path this whole change exists to remove. So the question asked here is the narrow one —
|
|
40
|
+
// *is this definitely an empty rollup* — and every other outcome falls through.
|
|
41
|
+
function reads_as_empty_rollup(raw_json: string): boolean {
|
|
42
|
+
const parsed = json_value.parse_or_undefined(raw_json)
|
|
43
|
+
|
|
44
|
+
if (typeof parsed !== 'object' || parsed === null || !('statusCheckRollup' in parsed)) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { statusCheckRollup: rollup } = parsed
|
|
49
|
+
|
|
50
|
+
return Array.isArray(rollup) && rollup.length === 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The read throws rather than answering `undefined`, so the `catch` is its only failure path.
|
|
54
|
+
//
|
|
55
|
+
// The **checks** half, not the whole snapshot: the question here is about `statusCheckRollup` alone,
|
|
56
|
+
// and reading the review listing to answer it paged a whole conversation for nothing
|
|
57
|
+
// (joshuafolkken/kit#1043).
|
|
58
|
+
async function has_no_checks(branch_name: string): Promise<boolean> {
|
|
59
|
+
try {
|
|
60
|
+
const checks = await git_gh_command.pr_get_checks_snapshot(branch_name)
|
|
61
|
+
|
|
62
|
+
return reads_as_empty_rollup(checks.snapshot_json)
|
|
63
|
+
} catch {
|
|
64
|
+
// The read refines the swallow; it is not a gate of its own. An answer it cannot get is not
|
|
65
|
+
// evidence of anything, so prefer falling through — the poll below reads the same state.
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function handle_watch_failure(branch_name: string, error: unknown): Promise<void> {
|
|
71
|
+
if (await has_no_checks(branch_name)) throw error
|
|
72
|
+
|
|
73
|
+
// Swallowed, but never silently: the reason a run stopped early used to be this line, so it
|
|
74
|
+
// stays visible even though it no longer decides anything.
|
|
75
|
+
console.info(`⚠️ ${WATCH_FAILED_NOTE}: ${git_gh_helpers.get_error_message_with_stderr(error)}`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// **Returns whether the watch confirmed completion** (joshuafolkken/kit#2029): `true` only when
|
|
79
|
+
// `pr_checks_watch` saw every check finish, `false` when it timed out with checks still pending or
|
|
80
|
+
// fell through to polling on a swallowed failure. `run_checks` reads it to decide how many stable poll
|
|
81
|
+
// reads the wait still needs. A timed-out watch (`timed_out: true`) never spanned the pending→settled
|
|
82
|
+
// window, so it must keep the full stable-read window rather than claim the confirmation. A rethrown
|
|
83
|
+
// failure — the empty-rollup case — never reaches a caller, so it needs no answer here.
|
|
84
|
+
async function watch_before_polling(branch_name: string): Promise<boolean> {
|
|
85
|
+
console.info('')
|
|
86
|
+
console.info('📊 Watching PR checks...')
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const result = await git_gh_command.pr_checks_watch(branch_name)
|
|
90
|
+
|
|
91
|
+
return !result.timed_out
|
|
92
|
+
} catch (error) {
|
|
93
|
+
await handle_watch_failure(branch_name, error)
|
|
94
|
+
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// **A confirmed watch lowers the poll's stable-read requirement to one** (joshuafolkken/kit#2029). The
|
|
100
|
+
// watch is the first confirmation that every check settled, so the poll that follows need only agree
|
|
101
|
+
// once rather than twice — which removes the extra interval `followup` spent re-proving a settled run.
|
|
102
|
+
// A skipped or fallen-through watch keeps `DEFAULT_STABLE_READS`, and a check that turned red is still
|
|
103
|
+
// caught: the poll evaluates the full merge gate and throws on failure before any stable read counts.
|
|
104
|
+
async function run_checks(input: {
|
|
105
|
+
branch_name: string
|
|
106
|
+
is_skip_watch: boolean
|
|
107
|
+
}): Promise<PrStateSnapshot> {
|
|
108
|
+
const is_watch_confirmed = !input.is_skip_watch && (await watch_before_polling(input.branch_name))
|
|
109
|
+
const stable_reads = is_watch_confirmed ? WATCH_CONFIRMED_STABLE_READS : DEFAULT_STABLE_READS
|
|
110
|
+
|
|
111
|
+
return await git_pr_checks.wait_for_pr_success(input.branch_name, stable_reads)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Temporary (kit#753): record every CodeRabbit check that was not passing when the merge gate
|
|
115
|
+
// opened, so a merge shipped without CodeRabbit review stays auditable.
|
|
116
|
+
function read_coderabbit_skip_notes(snapshot: PrStateSnapshot): Array<string> {
|
|
117
|
+
return snapshot.rollup
|
|
118
|
+
.filter((check) => is_coderabbit_check(check.name))
|
|
119
|
+
.filter((check) => check.status !== CHECK_STATUS_PASS)
|
|
120
|
+
.map(
|
|
121
|
+
(check) =>
|
|
122
|
+
`CodeRabbit check skipped (kit#753): ${check.name} was ${check.status} at merge time`,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The skip is printed where the run is being watched, not only carried into the Telegram body it
|
|
127
|
+
// ends up in. A merge that went ahead while CodeRabbit had not come back is the one thing kit#753's
|
|
128
|
+
// exemption trades away, and it used to be invisible in `followup`'s own output
|
|
129
|
+
// (joshuafolkken/kit#1217).
|
|
130
|
+
function log_skip_notes(notes: ReadonlyArray<string>): void {
|
|
131
|
+
for (const note of notes) {
|
|
132
|
+
console.info(`⏭ ${note}`)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const git_pr_followup_checks = {
|
|
137
|
+
run_checks,
|
|
138
|
+
read_coderabbit_skip_notes,
|
|
139
|
+
log_skip_notes,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { git_pr_followup_checks, WATCH_FAILED_NOTE }
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
2
|
-
import { git_gh_helpers } from '#scripts/gh/git-gh-helpers'
|
|
3
2
|
import { git_pr_ai_review, type TelegramContext } from '#scripts/gh/git-pr-ai-review'
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_STABLE_READS,
|
|
6
|
-
git_pr_checks,
|
|
7
|
-
WATCH_CONFIRMED_STABLE_READS,
|
|
8
|
-
} from '#scripts/gh/git-pr-checks'
|
|
9
|
-
import { is_coderabbit_check } from '#scripts/gh/git-pr-checks-eval'
|
|
10
|
-
import { CHECK_STATUS_PASS, type PrStateSnapshot } from '#scripts/gh/git-pr-checks-parse'
|
|
11
3
|
import { git_pr_coderabbit } from '#scripts/gh/git-pr-coderabbit'
|
|
12
4
|
import { git_pr_managed_config } from '#scripts/gh/git-pr-managed-config'
|
|
13
5
|
import { github_issue_url } from '#scripts/gh/github-issue-url'
|
|
14
6
|
import { git_closes_keyword } from '#scripts/git/git-closes-keyword'
|
|
15
|
-
import { json_value } from '#scripts/lib/json-value'
|
|
16
7
|
import type { GitNotifyConfig } from '#scripts/notify/git-notify'
|
|
17
8
|
import {
|
|
18
9
|
telegram_notify,
|
|
@@ -22,12 +13,12 @@ import {
|
|
|
22
13
|
import { git_followup_merged } from './git-followup-merged'
|
|
23
14
|
import { git_followup_pending } from './git-followup-pending'
|
|
24
15
|
import { git_followup_stages, type StageLog } from './git-followup-stages'
|
|
16
|
+
import { git_pr_followup_checks } from './git-pr-followup-checks'
|
|
25
17
|
import { git_pr_followup_wrapup } from './git-pr-followup-wrapup'
|
|
26
18
|
|
|
27
19
|
const { STAGE, lap } = git_followup_stages
|
|
20
|
+
const { run_checks, read_coderabbit_skip_notes, log_skip_notes } = git_pr_followup_checks
|
|
28
21
|
|
|
29
|
-
// Named so a test can pin the note without restating it (joshuafolkken/kit#999).
|
|
30
|
-
const WATCH_FAILED_NOTE = 'pr checks --watch failed; falling through to polling'
|
|
31
22
|
const REPO_NAME_SEPARATOR = '/'
|
|
32
23
|
|
|
33
24
|
function parse_repo_name(name_with_owner: string | undefined): string | undefined {
|
|
@@ -107,123 +98,6 @@ async function warn_if_missing_closes(branch_name: string): Promise<string | und
|
|
|
107
98
|
return undefined
|
|
108
99
|
}
|
|
109
100
|
|
|
110
|
-
// The watch is a look ahead, not a gate. It fails when **any** check has failed — CodeRabbit
|
|
111
|
-
// included — and letting that escape ended the run before `evaluate_pr_state` could apply kit#753's
|
|
112
|
-
// CodeRabbit exemption at all. It only ever bit where every check finished inside the two-minute
|
|
113
|
-
// window, so a repository with a slow E2E never saw it and a fast one always would
|
|
114
|
-
// (joshuafolkken/kit#999). What the watch is has since changed — `gh pr checks --watch` went through
|
|
115
|
-
// GraphQL, so joshuafolkken/kit#1028 replaced it with the same poll loop bounded to two minutes —
|
|
116
|
-
// but its place here has not.
|
|
117
|
-
//
|
|
118
|
-
// Falling through costs nothing: whether the merge may proceed is decided in one place below, and a
|
|
119
|
-
// genuinely failing non-CodeRabbit check still ends the wait on the first poll by kit#990's
|
|
120
|
-
// fast-fail rather than by that failure.
|
|
121
|
-
// The watch fails for two different reasons — "a check failed" and "no checks reported on this
|
|
122
|
-
// branch" — and its own error does not say which. The pull request itself can: a failed check leaves
|
|
123
|
-
// a rollup, and a branch with no checks leaves it empty. Falling through on the empty case would
|
|
124
|
-
// trade a failure reported in seconds for the whole budget spent waiting on a required check that is
|
|
125
|
-
// missing rather than pending, so that one is rethrown and the old behavior kept.
|
|
126
|
-
//
|
|
127
|
-
// **Read from the raw payload, not from `parse_pr_state_snapshot`.** That parser never throws: a
|
|
128
|
-
// malformed answer or a schema mismatch degrades to `rollup: []`, which is indistinguishable there
|
|
129
|
-
// from a branch that genuinely has no checks. Rethrowing on it would put an unreadable answer back
|
|
130
|
-
// on the path this whole change exists to remove. So the question asked here is the narrow one —
|
|
131
|
-
// *is this definitely an empty rollup* — and every other outcome falls through.
|
|
132
|
-
function reads_as_empty_rollup(raw_json: string): boolean {
|
|
133
|
-
const parsed = json_value.parse_or_undefined(raw_json)
|
|
134
|
-
|
|
135
|
-
if (typeof parsed !== 'object' || parsed === null || !('statusCheckRollup' in parsed)) {
|
|
136
|
-
return false
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const { statusCheckRollup: rollup } = parsed
|
|
140
|
-
|
|
141
|
-
return Array.isArray(rollup) && rollup.length === 0
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// The read throws rather than answering `undefined`, so the `catch` is its only failure path.
|
|
145
|
-
//
|
|
146
|
-
// The **checks** half, not the whole snapshot: the question here is about `statusCheckRollup` alone,
|
|
147
|
-
// and reading the review listing to answer it paged a whole conversation for nothing
|
|
148
|
-
// (joshuafolkken/kit#1043).
|
|
149
|
-
async function has_no_checks(branch_name: string): Promise<boolean> {
|
|
150
|
-
try {
|
|
151
|
-
const checks = await git_gh_command.pr_get_checks_snapshot(branch_name)
|
|
152
|
-
|
|
153
|
-
return reads_as_empty_rollup(checks.snapshot_json)
|
|
154
|
-
} catch {
|
|
155
|
-
// The read refines the swallow; it is not a gate of its own. An answer it cannot get is not
|
|
156
|
-
// evidence of anything, so prefer falling through — the poll below reads the same state.
|
|
157
|
-
return false
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function handle_watch_failure(branch_name: string, error: unknown): Promise<void> {
|
|
162
|
-
if (await has_no_checks(branch_name)) throw error
|
|
163
|
-
|
|
164
|
-
// Swallowed, but never silently: the reason a run stopped early used to be this line, so it
|
|
165
|
-
// stays visible even though it no longer decides anything.
|
|
166
|
-
console.info(`⚠️ ${WATCH_FAILED_NOTE}: ${git_gh_helpers.get_error_message_with_stderr(error)}`)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// **Returns whether the watch confirmed completion** (joshuafolkken/kit#2029): `true` only when
|
|
170
|
-
// `pr_checks_watch` saw every check finish, `false` when it timed out with checks still pending or
|
|
171
|
-
// fell through to polling on a swallowed failure. `run_checks` reads it to decide how many stable poll
|
|
172
|
-
// reads the wait still needs. A timed-out watch (`timed_out: true`) never spanned the pending→settled
|
|
173
|
-
// window, so it must keep the full stable-read window rather than claim the confirmation. A rethrown
|
|
174
|
-
// failure — the empty-rollup case — never reaches a caller, so it needs no answer here.
|
|
175
|
-
async function watch_before_polling(branch_name: string): Promise<boolean> {
|
|
176
|
-
console.info('')
|
|
177
|
-
console.info('📊 Watching PR checks...')
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
const result = await git_gh_command.pr_checks_watch(branch_name)
|
|
181
|
-
|
|
182
|
-
return !result.timed_out
|
|
183
|
-
} catch (error) {
|
|
184
|
-
await handle_watch_failure(branch_name, error)
|
|
185
|
-
|
|
186
|
-
return false
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// **A confirmed watch lowers the poll's stable-read requirement to one** (joshuafolkken/kit#2029). The
|
|
191
|
-
// watch is the first confirmation that every check settled, so the poll that follows need only agree
|
|
192
|
-
// once rather than twice — which removes the extra interval `followup` spent re-proving a settled run.
|
|
193
|
-
// A skipped or fallen-through watch keeps `DEFAULT_STABLE_READS`, and a check that turned red is still
|
|
194
|
-
// caught: the poll evaluates the full merge gate and throws on failure before any stable read counts.
|
|
195
|
-
async function run_checks(input: {
|
|
196
|
-
branch_name: string
|
|
197
|
-
is_skip_watch: boolean
|
|
198
|
-
}): Promise<PrStateSnapshot> {
|
|
199
|
-
const is_watch_confirmed = !input.is_skip_watch && (await watch_before_polling(input.branch_name))
|
|
200
|
-
const stable_reads = is_watch_confirmed ? WATCH_CONFIRMED_STABLE_READS : DEFAULT_STABLE_READS
|
|
201
|
-
|
|
202
|
-
return await git_pr_checks.wait_for_pr_success(input.branch_name, stable_reads)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Temporary (kit#753): record every CodeRabbit check that was not passing when the merge gate
|
|
206
|
-
// opened, so a merge shipped without CodeRabbit review stays auditable.
|
|
207
|
-
function read_coderabbit_skip_notes(snapshot: PrStateSnapshot): Array<string> {
|
|
208
|
-
return snapshot.rollup
|
|
209
|
-
.filter((check) => is_coderabbit_check(check.name))
|
|
210
|
-
.filter((check) => check.status !== CHECK_STATUS_PASS)
|
|
211
|
-
.map(
|
|
212
|
-
(check) =>
|
|
213
|
-
`CodeRabbit check skipped (kit#753): ${check.name} was ${check.status} at merge time`,
|
|
214
|
-
)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// The skip is printed where the run is being watched, not only carried into the Telegram body it
|
|
218
|
-
// ends up in. A merge that went ahead while CodeRabbit had not come back is the one thing kit#753's
|
|
219
|
-
// exemption trades away, and it used to be invisible in `followup`'s own output
|
|
220
|
-
// (joshuafolkken/kit#1217).
|
|
221
|
-
function log_skip_notes(notes: ReadonlyArray<string>): void {
|
|
222
|
-
for (const note of notes) {
|
|
223
|
-
console.info(`⏭ ${note}`)
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
101
|
// **The one read of the four that can depend on another.** Without an issue number on the command
|
|
228
102
|
// line it is the pull request body that names it (joshuafolkken/kit#1539), so the title read waits
|
|
229
103
|
// for exactly that one and for nothing else. With a number in hand the `??` short-circuits and the
|
|
@@ -451,7 +325,6 @@ const git_pr_followup = {
|
|
|
451
325
|
|
|
452
326
|
export {
|
|
453
327
|
git_pr_followup,
|
|
454
|
-
WATCH_FAILED_NOTE,
|
|
455
328
|
run_checks,
|
|
456
329
|
build_issue_url,
|
|
457
330
|
parse_repo_name,
|
|
@@ -464,3 +337,5 @@ export {
|
|
|
464
337
|
}
|
|
465
338
|
export type { FollowupInput }
|
|
466
339
|
export type { TelegramContext } from '#scripts/gh/git-pr-ai-review'
|
|
340
|
+
|
|
341
|
+
export { WATCH_FAILED_NOTE } from './git-pr-followup-checks'
|
|
@@ -6,6 +6,7 @@ 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'
|
|
10
11
|
|
|
11
12
|
// A machine-wide weighted core budget with admission control (joshuafolkken/kit#2351).
|
|
@@ -46,6 +47,26 @@ const WAIT_CAP_MS = 120_000
|
|
|
46
47
|
// The head of the ledger is always admitted, so the smallest budget worth planning against is one core.
|
|
47
48
|
const MIN_BUDGET = 1
|
|
48
49
|
|
|
50
|
+
// **The cores each heavy tool occupies, declared once for both places that reserve them**
|
|
51
|
+
// (joshuafolkken/kit#3345). `gate-plan.ts`'s checks and the `josh` commands that run the same tools
|
|
52
|
+
// directly read these, so a direct `josh lint` claims what the gate's lint claims. The lint, type check
|
|
53
|
+
// and spell check numbers are `gate-plan.ts`'s measured table, floored. The whole-tree eslint scan
|
|
54
|
+
// behind `josh lines` and `josh refactor:scan` runs in one process: measured cold on the 11-core
|
|
55
|
+
// machine on 2026-10-06, 113s of CPU over 98s of wall, so it gets one core.
|
|
56
|
+
const CORE_WEIGHTS = {
|
|
57
|
+
lint: 2,
|
|
58
|
+
type_check: 1,
|
|
59
|
+
spell_check: 1,
|
|
60
|
+
eslint_scan: 1,
|
|
61
|
+
} as const
|
|
62
|
+
|
|
63
|
+
// **The environment mark that says a parent already holds this process's cores**
|
|
64
|
+
// (joshuafolkken/kit#3345). `josh gate` reserves per check and then spawns `pnpm josh lint`. Without
|
|
65
|
+
// the mark, that child's own dispatch would claim a second place for the cores its parent already
|
|
66
|
+
// holds. Every holder sets it, and every child inherits it through the environment and claims nothing.
|
|
67
|
+
const HELD_KEY = 'JOSH_CORE_RESERVED'
|
|
68
|
+
const HELD_VALUE = '1'
|
|
69
|
+
|
|
49
70
|
const reservation_schema = process_owner_schema.extend({
|
|
50
71
|
weight: z.number(),
|
|
51
72
|
claimed_at: z.number(),
|
|
@@ -133,7 +154,7 @@ function live_reservations(directory: string = PLATFORM_TEMP_ROOT): Array<LiveRe
|
|
|
133
154
|
return marker_files(directory).flatMap((name) => {
|
|
134
155
|
const reservation = live_reservation(path.join(directory, name))
|
|
135
156
|
|
|
136
|
-
return reservation === undefined ? [] :
|
|
157
|
+
return reservation === undefined ? [] : { key: name, reservation }
|
|
137
158
|
})
|
|
138
159
|
}
|
|
139
160
|
|
|
@@ -272,14 +293,61 @@ async function with_core_reservation<T>(
|
|
|
272
293
|
}
|
|
273
294
|
}
|
|
274
295
|
|
|
296
|
+
function is_held(): boolean {
|
|
297
|
+
return process.env[HELD_KEY] === HELD_VALUE
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// A function of its own so the restoring write does not sit after an `await`, where
|
|
301
|
+
// `require-atomic-updates` reads any write to `process` as a possible race — the reason `josh.ts`
|
|
302
|
+
// gives `record_exit_code`.
|
|
303
|
+
function set_held_mark(value: string): void {
|
|
304
|
+
process.env[HELD_KEY] = value
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Marks this process, and every child it spawns, as running inside a held reservation. The previous
|
|
308
|
+
// value comes back afterwards, so a nested holder never clears the mark its outer holder set.
|
|
309
|
+
async function with_held_mark<T>(run: () => Promise<T>): Promise<T> {
|
|
310
|
+
const previous = process.env[HELD_KEY] ?? ''
|
|
311
|
+
|
|
312
|
+
set_held_mark(HELD_VALUE)
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
return await run()
|
|
316
|
+
} finally {
|
|
317
|
+
set_held_mark(previous)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// **A command's reservation: the gate's claim, unless a parent already holds the cores**
|
|
322
|
+
// (joshuafolkken/kit#3345). `josh-logic.ts` calls this around every command that declares a weight.
|
|
323
|
+
// Each command then only declares its weight, and no command carries a copy of the claim, run and
|
|
324
|
+
// release steps. A solo run is admitted on the first read, so a run with room on the machine starts
|
|
325
|
+
// with no wait. A command inside a unit suite claims nothing either, for the reason `reserved-run.ts`
|
|
326
|
+
// gives: the suite's own reservation already covers it, and a place claimed there would wait on it.
|
|
327
|
+
async function with_command_reservation<T>(
|
|
328
|
+
weight: number,
|
|
329
|
+
run: () => Promise<T>,
|
|
330
|
+
options: ReserveOptions = {},
|
|
331
|
+
): Promise<T> {
|
|
332
|
+
if (is_held() || unit_worker_share.is_nested_run()) return await run()
|
|
333
|
+
|
|
334
|
+
return await with_core_reservation(weight, async () => await with_held_mark(run), options)
|
|
335
|
+
}
|
|
336
|
+
|
|
275
337
|
const core_budget = {
|
|
338
|
+
CORE_WEIGHTS,
|
|
339
|
+
HELD_KEY,
|
|
340
|
+
HELD_VALUE,
|
|
276
341
|
RESERVED_PREFIX,
|
|
277
342
|
admitted_keys,
|
|
278
343
|
admitted_load,
|
|
344
|
+
is_held,
|
|
279
345
|
live_reservations,
|
|
280
346
|
release,
|
|
281
347
|
reserve,
|
|
348
|
+
with_command_reservation,
|
|
282
349
|
with_core_reservation,
|
|
350
|
+
with_held_mark,
|
|
283
351
|
}
|
|
284
352
|
|
|
285
353
|
export type { LiveReservation, Reservation, ReserveOptions }
|
|
@@ -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).
|
|
@@ -61,15 +62,30 @@ const WARNING_CHECKER_LABELS: ReadonlyArray<string> = [LINT_LABEL, TYPE_CHECK_LA
|
|
|
61
62
|
// check rather than one appended after the unit suite is what keeps the plan's shape intact: the
|
|
62
63
|
// `--no-unit` set stays "the whole gate minus the unit suite", and a CI runner — which has no recorded
|
|
63
64
|
// transcripts — runs it as a trivial green rather than skipping it.
|
|
65
|
+
//
|
|
66
|
+
// **The three reserving weights are `core_budget.CORE_WEIGHTS`** (joshuafolkken/kit#3345), the same
|
|
67
|
+
// numbers the `josh lint`, `josh check` and `josh cspell:dot` commands claim when run directly.
|
|
68
|
+
//
|
|
69
|
+
// **The two zero weights were measured again for joshuafolkken/kit#3345, and they stay zero.** These are
|
|
70
|
+
// warm runs on the 11-core machine on 2026-10-06. `behavior` took 0.6s of wall for 0.7s of CPU, which
|
|
71
|
+
// is node starting up. `exports:unused` took 5.5s of wall for 9.0s of CPU, about 1.6 cores. Floored,
|
|
72
|
+
// that would be one core, but a reservation lasts the whole gate's plan, not the check's five seconds.
|
|
73
|
+
// It would lower `RESERVED_CORES` from 4 to 5 and cut the solo unit cap from 7 workers to 6 for the
|
|
74
|
+
// full length of the unit suite, to cover a burst that ends before lint does. Nothing has measured
|
|
75
|
+
// that trade, so the measured shape stays.
|
|
64
76
|
const STATIC_CHECKS: ReadonlyArray<GateCheck> = [
|
|
65
|
-
{ label: LINT_LABEL, target: 'lint', reserved_cores:
|
|
66
|
-
{
|
|
67
|
-
|
|
77
|
+
{ label: LINT_LABEL, target: 'lint', reserved_cores: core_budget.CORE_WEIGHTS.lint },
|
|
78
|
+
{
|
|
79
|
+
label: TYPE_CHECK_LABEL,
|
|
80
|
+
target: 'check',
|
|
81
|
+
reserved_cores: core_budget.CORE_WEIGHTS.type_check,
|
|
82
|
+
},
|
|
83
|
+
{ label: 'cspell', target: 'cspell:dot', reserved_cores: core_budget.CORE_WEIGHTS.spell_check },
|
|
68
84
|
{ label: BEHAVIOR_LABEL, target: BEHAVIOR_LABEL, reserved_cores: 0 },
|
|
69
85
|
// **The unused-member check reserves no core** (joshuafolkken/kit#2987): it builds one TypeScript
|
|
70
|
-
// program and walks it
|
|
71
|
-
//
|
|
72
|
-
//
|
|
86
|
+
// program and walks it for about 5s, and it is over well before lint and the type check. A
|
|
87
|
+
// reservation would only take a place from the four-core CI runner's fan-out and a worker from the
|
|
88
|
+
// unit suite for the whole gate.
|
|
73
89
|
{ label: 'exports', target: 'exports:unused', reserved_cores: 0 },
|
|
74
90
|
]
|
|
75
91
|
|
|
@@ -242,6 +258,11 @@ function resolve_unit_worker_cap(
|
|
|
242
258
|
return available_cores - RESERVED_CORES
|
|
243
259
|
}
|
|
244
260
|
|
|
261
|
+
// The unit suite's weight when nothing caps its pool: the cores left once the static checks have theirs.
|
|
262
|
+
function solo_unit_weight(available_cores: number): number {
|
|
263
|
+
return Math.max(0, available_cores - RESERVED_CORES)
|
|
264
|
+
}
|
|
265
|
+
|
|
245
266
|
// The cores a check reserves from the machine-wide budget while it runs (joshuafolkken/kit#2351). The
|
|
246
267
|
// static checks declare their measured reservation; the unit suite declares the pool it will open,
|
|
247
268
|
// which `unit_worker_cap` has already sized against the runs in flight. **This is the same table, read
|
|
@@ -256,7 +277,25 @@ function resolve_unit_worker_cap(
|
|
|
256
277
|
function check_weight(check: GateCheck, plan: GatePlan, available_cores: number): number {
|
|
257
278
|
if (check.label !== UNIT_LABEL) return check.reserved_cores
|
|
258
279
|
|
|
259
|
-
return plan.unit_worker_cap ??
|
|
280
|
+
return plan.unit_worker_cap ?? solo_unit_weight(available_cores)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// The cores a unit run started directly reserves (joshuafolkken/kit#3345): the share it will pass to
|
|
284
|
+
// vitest while other runs are live, and otherwise the gate's own solo unit weight. **Not the whole
|
|
285
|
+
// machine**, though a lone vitest sizes its own pool: FIFO admission lets a claim of every core in only
|
|
286
|
+
// once the ledger is empty, so a one-file `josh test:related` would wait out every other lane's gate
|
|
287
|
+
// and hold every later claim behind it. One weight for the suite however it is started. **Floored at one
|
|
288
|
+
// worker**: on a machine the static checks fill, a four-core CI runner among them, the gate's solo
|
|
289
|
+
// weight is zero because its siblings hold every core — a direct run has no siblings, and vitest still
|
|
290
|
+
// occupies a core.
|
|
291
|
+
function direct_unit_weight(
|
|
292
|
+
available_cores: number = availableParallelism(),
|
|
293
|
+
live_runs: number = unit_worker_share.live_run_count(),
|
|
294
|
+
): number {
|
|
295
|
+
const weight =
|
|
296
|
+
unit_worker_share.current_share(available_cores, live_runs) ?? solo_unit_weight(available_cores)
|
|
297
|
+
|
|
298
|
+
return Math.max(unit_worker_share.MIN_WORKERS, weight)
|
|
260
299
|
}
|
|
261
300
|
|
|
262
301
|
// `availableParallelism()` rather than `cpus().length`: it reports what this process may actually
|
|
@@ -345,6 +384,7 @@ const gate_plan = {
|
|
|
345
384
|
UNIT_LABEL,
|
|
346
385
|
WARNING_CHECKER_LABELS,
|
|
347
386
|
check_weight,
|
|
387
|
+
direct_unit_weight,
|
|
348
388
|
format_gate_plan,
|
|
349
389
|
format_machine,
|
|
350
390
|
has_unit_check,
|
|
@@ -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 }
|
|
@@ -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
|