@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
|
@@ -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
|
|
|
@@ -222,10 +223,17 @@ async function run_gate_steps(
|
|
|
222
223
|
): Promise<ReadonlyArray<GateStepResult>> {
|
|
223
224
|
const reserved = await build_reserved_steps(process.cwd(), plan, budget.available_cores)
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
// **The checks run under the held mark, so a check's own dispatch claims nothing**
|
|
227
|
+
// (joshuafolkken/kit#3345). Each check is a `pnpm josh <target>` child, and a target that declares
|
|
228
|
+
// a weight would otherwise claim a second place for the cores reserved for it here. A nested gate
|
|
229
|
+
// sets the mark too, since its checks are covered by the outer gate's reservation.
|
|
230
|
+
return await core_budget.with_held_mark(
|
|
231
|
+
async () =>
|
|
232
|
+
await bounded_pool.bounded_map(
|
|
233
|
+
reserved,
|
|
234
|
+
plan.concurrency,
|
|
235
|
+
async (entry) => await run_reserved_step(entry, budget),
|
|
236
|
+
),
|
|
229
237
|
)
|
|
230
238
|
}
|
|
231
239
|
|
|
@@ -299,6 +307,9 @@ interface GateOptions {
|
|
|
299
307
|
// The scoped records' paths, overridable so a test can plant a green pair rather than depend on the
|
|
300
308
|
// surrounding checkout's — the reason `scoped-green.ts` takes a `source` per stamp.
|
|
301
309
|
scoped_sources?: ScopedSources
|
|
310
|
+
// The lane ledger a finished gate's duration is appended to (joshuafolkken/kit#3355). Only the CLI
|
|
311
|
+
// entry resolves it, so a suite driving the gate never writes a measurement of checks that never ran.
|
|
312
|
+
ledger_path?: string | undefined
|
|
302
313
|
}
|
|
303
314
|
|
|
304
315
|
// **The gate refuses to start when the scoped pair has not been green on this tree**
|
|
@@ -339,14 +350,20 @@ async function settle_gate(
|
|
|
339
350
|
tree: GateTree,
|
|
340
351
|
options: GateOptions & { started_at: number },
|
|
341
352
|
): Promise<number> {
|
|
353
|
+
const elapsed_ms = performance.now() - options.started_at
|
|
342
354
|
const failed_labels = gate_report.report_gate_steps(results, {
|
|
343
355
|
is_verbose: options.is_verbose ?? false,
|
|
344
356
|
log_path: options.log_path,
|
|
345
|
-
elapsed_ms
|
|
357
|
+
elapsed_ms,
|
|
346
358
|
step_count: String(plan.checks.length),
|
|
347
359
|
})
|
|
360
|
+
const is_passed = failed_labels.length === 0
|
|
348
361
|
|
|
349
|
-
if (
|
|
362
|
+
if (options.ledger_path !== undefined) {
|
|
363
|
+
await lane_ledger.record_gate(options.ledger_path, elapsed_ms, is_passed)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!is_passed) return FAIL_EXIT_CODE
|
|
350
367
|
|
|
351
368
|
await record_whole_gate(plan, results, tree, options)
|
|
352
369
|
|
|
@@ -474,7 +491,9 @@ async function run_gate_command(
|
|
|
474
491
|
// summary, written last, is the first thing lost. Setting the code lets the writes drain and the
|
|
475
492
|
// process end on its own, which it can, since every child has already exited by here.
|
|
476
493
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
477
|
-
process.exitCode = await run_gate_command(process.argv.slice(FIRST_ARGUMENT_INDEX)
|
|
494
|
+
process.exitCode = await run_gate_command(process.argv.slice(FIRST_ARGUMENT_INDEX), {
|
|
495
|
+
ledger_path: await lane_ledger.target(),
|
|
496
|
+
})
|
|
478
497
|
}
|
|
479
498
|
|
|
480
499
|
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
|
}
|
|
@@ -29,6 +29,7 @@ const DELETE_METHOD = 'DELETE'
|
|
|
29
29
|
// object, so the same value is unwrapped out of it rather than the shape being changed.
|
|
30
30
|
const HTML_URL_FILTER = '.html_url'
|
|
31
31
|
const ID_FILTER = '.id'
|
|
32
|
+
const LABEL_NAMES_FILTER = '.[].name'
|
|
32
33
|
|
|
33
34
|
const LABELS_SEGMENT = '/labels'
|
|
34
35
|
const COMMENTS_SEGMENT = '/comments'
|
|
@@ -182,17 +183,60 @@ async function issue_create_with_label(input: {
|
|
|
182
183
|
)
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
// The outcome of applying one label, with the swallowed failure's gist kept for a caller that has to
|
|
187
|
+
// say why it refused (joshuafolkken/kit#3312).
|
|
188
|
+
type LabelWrite = { is_applied: true } | { is_applied: false; reason: string }
|
|
189
|
+
|
|
190
|
+
// The issue's label names, read back after a failed write. A read that itself fails answers
|
|
191
|
+
// `undefined`, which the caller treats as "not applied" — the write's own error is the reason then.
|
|
192
|
+
async function read_label_names(issue_number: string): Promise<Array<string> | undefined> {
|
|
193
|
+
try {
|
|
194
|
+
const raw = await git_gh_exec.exec_gh_api({
|
|
195
|
+
path: `${git_gh_api_path.issue_api_path(issue_number)}${LABELS_SEGMENT}`,
|
|
196
|
+
should_paginate: true,
|
|
197
|
+
jq_filter: LABEL_NAMES_FILTER,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return raw.split('\n').map((name) => name.trim())
|
|
201
|
+
} catch (error) {
|
|
202
|
+
error_text.trace_swallowed('git_gh_issue_write.read_label_names', error)
|
|
203
|
+
|
|
204
|
+
return undefined
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// **A failed answer is not a failed write.** The POST is idempotent, and a timeout, a non-zero exit
|
|
209
|
+
// after the write or a secondary rate limit can each throw after the label landed — which refused a
|
|
210
|
+
// lane dispatch whose label was in fact applied (joshuafolkken/kit#3312). So a throw is confirmed by
|
|
211
|
+
// reading the labels back, and only a label that is really absent answers `is_applied: false`.
|
|
212
|
+
async function issue_apply_label(issue_number: string, label: string): Promise<LabelWrite> {
|
|
213
|
+
const labels_path = `${git_gh_api_path.issue_api_path(issue_number)}${LABELS_SEGMENT}`
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
await git_gh_exec.exec_gh_api({ path: labels_path, body: JSON.stringify({ labels: [label] }) })
|
|
217
|
+
|
|
218
|
+
return { is_applied: true }
|
|
219
|
+
} catch (error) {
|
|
220
|
+
const names = await read_label_names(issue_number)
|
|
221
|
+
|
|
222
|
+
if (names?.includes(label) === true) return { is_applied: true }
|
|
223
|
+
error_text.trace_swallowed('git_gh_issue_write.issue_apply_label', error)
|
|
224
|
+
|
|
225
|
+
// The first line is the gist: gh follows it with the response body, which a one-line refusal
|
|
226
|
+
// does not need.
|
|
227
|
+
const [gist = ''] = error_text.message_of(error).split('\n', 1)
|
|
228
|
+
|
|
229
|
+
return { is_applied: false, reason: gist }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
185
233
|
// Applied after the body edit so a failure leaves an issue with the epic sections and no label,
|
|
186
234
|
// which `epic:check` reports — rather than a labelled issue with nothing to track. The caller checks
|
|
187
235
|
// the return: the label is what the auto-close filters on (joshuafolkken/kit#865).
|
|
188
236
|
async function issue_add_label(issue_number: string, label: string): Promise<boolean> {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
path: `${git_gh_api_path.issue_api_path(issue_number)}${LABELS_SEGMENT}`,
|
|
193
|
-
body: JSON.stringify({ labels: [label] }),
|
|
194
|
-
}),
|
|
195
|
-
)
|
|
237
|
+
const write = await issue_apply_label(issue_number, label)
|
|
238
|
+
|
|
239
|
+
return write.is_applied
|
|
196
240
|
}
|
|
197
241
|
|
|
198
242
|
// The counterpart to the addition above, for a run taking its own marker back off
|
|
@@ -279,6 +323,7 @@ const git_gh_issue_write = {
|
|
|
279
323
|
label_ensure,
|
|
280
324
|
issue_create_request,
|
|
281
325
|
issue_create_with_label,
|
|
326
|
+
issue_apply_label,
|
|
282
327
|
issue_add_label,
|
|
283
328
|
issue_remove_label,
|
|
284
329
|
issue_add_blocked_by,
|
|
@@ -286,3 +331,4 @@ const git_gh_issue_write = {
|
|
|
286
331
|
}
|
|
287
332
|
|
|
288
333
|
export { git_gh_issue_write }
|
|
334
|
+
export type { LabelWrite }
|
|
@@ -89,9 +89,24 @@ interface PullMergeState {
|
|
|
89
89
|
type PullNumberRead =
|
|
90
90
|
{ kind: 'read'; pr_number: number } | { kind: 'missing' } | { kind: 'unreadable'; cause: unknown }
|
|
91
91
|
|
|
92
|
+
// The branch-keyed detail read, in the same three answers. `missing` and `unreadable` are the
|
|
93
|
+
// resolution's own; `unreadable` also covers a resolved number whose detail read failed, which every
|
|
94
|
+
// branch-keyed reader used to fold into "no pull request" (joshuafolkken/kit#3263).
|
|
95
|
+
type PullRead = Exclude<PullNumberRead, { kind: 'read' }> | { kind: 'read'; pull: RestPull }
|
|
96
|
+
|
|
92
97
|
// The lookups still in flight, keyed the same way the resolved numbers are.
|
|
93
98
|
const pending_pr_number_by_branch = new Map<string, Promise<PullNumberRead>>()
|
|
94
99
|
|
|
100
|
+
// The detail reads, keyed by number and remembered for the life of the command
|
|
101
|
+
// (joshuafolkken/kit#3263). `josh followup` asks `pr_get_body` and `pr_get_url` in one tick — the
|
|
102
|
+
// same `GET /pulls/{N}` each time. The promise is stored, so readers in one tick share the request as
|
|
103
|
+
// well as its answer; a read that fails is dropped and re-tried, the rule the number memo holds.
|
|
104
|
+
// **Every write to a pull request clears it** through `forget_pr_numbers`, and the two state readers
|
|
105
|
+
// never read it: `pr_get_merge_state` answers where the pull request stands *now*, which `josh ship`
|
|
106
|
+
// asks again after every merge it pushes, and `pr_view` decides whether `josh git -y` writes onto the
|
|
107
|
+
// pull request — after a commit and a push its preflight read would otherwise outlast.
|
|
108
|
+
const pull_by_number = new Map<number, Promise<RestPull>>()
|
|
109
|
+
|
|
95
110
|
async function fetch_pr_number_read(branch_name: string): Promise<PullNumberRead> {
|
|
96
111
|
try {
|
|
97
112
|
const pr_number = await fetch_pr_number(branch_name)
|
|
@@ -130,10 +145,11 @@ async function start_pr_number_read(branch_name: string): Promise<PullNumberRead
|
|
|
130
145
|
}
|
|
131
146
|
}
|
|
132
147
|
|
|
133
|
-
// Defined after the
|
|
148
|
+
// Defined after the maps it clears, so the memos are declared before anything reaches them.
|
|
134
149
|
function forget_pr_numbers(): void {
|
|
135
150
|
pr_number_by_branch.clear()
|
|
136
151
|
pending_pr_number_by_branch.clear()
|
|
152
|
+
pull_by_number.clear()
|
|
137
153
|
}
|
|
138
154
|
|
|
139
155
|
async function read_pr_number(branch_name: string): Promise<PullNumberRead> {
|
|
@@ -144,9 +160,9 @@ async function read_pr_number(branch_name: string): Promise<PullNumberRead> {
|
|
|
144
160
|
}
|
|
145
161
|
|
|
146
162
|
// `undefined` covers both "this branch has no pull request" and "the lookup failed", which is the
|
|
147
|
-
// distinction `gh pr view` never made either —
|
|
148
|
-
//
|
|
149
|
-
//
|
|
163
|
+
// distinction `gh pr view` never made either — the *display* reads here fold them together on
|
|
164
|
+
// purpose: the comment readers answer `undefined` either way. The callers that act on the absence
|
|
165
|
+
// rather than displaying it — `require_pr_number`, `pr_exists` and `pr_view` — opt out.
|
|
150
166
|
async function resolve_pr_number(branch_name: string): Promise<number | undefined> {
|
|
151
167
|
const read = await read_pr_number(branch_name)
|
|
152
168
|
|
|
@@ -186,19 +202,43 @@ async function read_pull(pr_number: number): Promise<RestPull> {
|
|
|
186
202
|
return git_gh_pr_rest.parse_rest_pull(json)
|
|
187
203
|
}
|
|
188
204
|
|
|
205
|
+
// The identity check keeps a failure from evicting a read that `forget_pr_numbers` already replaced.
|
|
206
|
+
async function read_remembered_pull(pr_number: number): Promise<RestPull> {
|
|
207
|
+
const pending = pull_by_number.get(pr_number) ?? read_pull(pr_number)
|
|
208
|
+
|
|
209
|
+
pull_by_number.set(pr_number, pending)
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
return await pending
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (pull_by_number.get(pr_number) === pending) pull_by_number.delete(pr_number)
|
|
215
|
+
throw error
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
189
219
|
// One pull request named by its branch: the resolution, then the detail read that carries
|
|
190
220
|
// `mergeable` and `mergeable_state`, which the lookup listing does not.
|
|
191
|
-
async function read_pull_of_branch(
|
|
192
|
-
|
|
193
|
-
|
|
221
|
+
async function read_pull_of_branch(
|
|
222
|
+
branch_name: string,
|
|
223
|
+
read: (pr_number: number) => Promise<RestPull> = read_remembered_pull,
|
|
224
|
+
): Promise<PullRead> {
|
|
225
|
+
const number_read = await read_pr_number(branch_name)
|
|
226
|
+
if (number_read.kind !== 'read') return number_read
|
|
194
227
|
|
|
195
228
|
try {
|
|
196
|
-
return await
|
|
197
|
-
} catch {
|
|
198
|
-
return
|
|
229
|
+
return { kind: 'read', pull: await read(number_read.pr_number) }
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return { kind: 'unreadable', cause: error }
|
|
199
232
|
}
|
|
200
233
|
}
|
|
201
234
|
|
|
235
|
+
// The display readers' fold: "no pull request" and "nothing could be read" are one empty answer.
|
|
236
|
+
async function read_pull_or_undefined(branch_name: string): Promise<RestPull | undefined> {
|
|
237
|
+
const read = await read_pull_of_branch(branch_name)
|
|
238
|
+
|
|
239
|
+
return read.kind === 'read' ? read.pull : undefined
|
|
240
|
+
}
|
|
241
|
+
|
|
202
242
|
// **`false` means the branch has no pull request, and nothing else.** This is the second half of
|
|
203
243
|
// joshuafolkken/kit#1048's distinction, on the caller that needs it most: `pr_exists` is not read for
|
|
204
244
|
// display, it decides whether `git-pr.ts` **opens** a pull request. Folding an unreadable lookup into
|
|
@@ -218,7 +258,7 @@ async function pr_get_number(branch_name: string): Promise<number | undefined> {
|
|
|
218
258
|
}
|
|
219
259
|
|
|
220
260
|
async function pr_get_url(branch_name: string): Promise<string | undefined> {
|
|
221
|
-
const pull = await
|
|
261
|
+
const pull = await read_pull_or_undefined(branch_name)
|
|
222
262
|
if (pull?.html_url === undefined) return undefined
|
|
223
263
|
|
|
224
264
|
return git_gh_helpers.parse_pr_state_string(pull.html_url)
|
|
@@ -229,9 +269,11 @@ async function pr_get_url(branch_name: string): Promise<string | undefined> {
|
|
|
229
269
|
// made on GitHub moves without touching the local branch. `undefined` covers both "no pull request"
|
|
230
270
|
// and "nothing could be read": `followup` then takes its ordinary path, whose merge step settles a
|
|
231
271
|
// pull request that did merge after all (`pr_merge`), so an unread answer costs a wait, not a wrong tail.
|
|
272
|
+
// Read past the detail memo: `josh ship` asks it again after each merge it pushes (joshuafolkken/kit#3263).
|
|
232
273
|
async function pr_get_merge_state(branch_name: string): Promise<PullMergeState | undefined> {
|
|
233
|
-
const
|
|
234
|
-
if (
|
|
274
|
+
const read = await read_pull_of_branch(branch_name, read_pull)
|
|
275
|
+
if (read.kind !== 'read') return undefined
|
|
276
|
+
const { pull } = read
|
|
235
277
|
|
|
236
278
|
return {
|
|
237
279
|
is_merged: git_gh_pr_rest.is_merged(pull),
|
|
@@ -244,19 +286,22 @@ async function pr_get_merge_state(branch_name: string): Promise<PullMergeState |
|
|
|
244
286
|
// REST answers JSON null for a pull request with no body where `gh --json body` answered an empty
|
|
245
287
|
// string, and both used to arrive here as the empty answer this folds to `undefined`.
|
|
246
288
|
async function pr_get_body(branch_name: string): Promise<string | undefined> {
|
|
247
|
-
const pull = await
|
|
289
|
+
const pull = await read_pull_or_undefined(branch_name)
|
|
248
290
|
const body = pull?.body
|
|
249
291
|
|
|
250
292
|
return typeof body === 'string' && body.length > 0 ? body : undefined
|
|
251
293
|
}
|
|
252
294
|
|
|
253
|
-
// The empty string means "
|
|
254
|
-
//
|
|
295
|
+
// The empty string means "this branch has no pull request", and nothing else. A read that failed
|
|
296
|
+
// throws, carrying gh's reason as the `cause`: `git-pr.ts` decides from this state whether the
|
|
297
|
+
// branch's pull request merged, and a rate limit answered as "no state" sent a merged pull request
|
|
298
|
+
// down the open one's path (joshuafolkken/kit#3263). Read past the detail memo, like the merge state.
|
|
255
299
|
async function pr_view(branch_name: string): Promise<string> {
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
300
|
+
const read = await read_pull_of_branch(branch_name, read_pull)
|
|
301
|
+
if (read.kind === 'unreadable') throw to_unreadable_error(branch_name, read.cause)
|
|
302
|
+
if (read.kind === 'missing') return ''
|
|
258
303
|
|
|
259
|
-
return JSON.stringify(git_gh_pr_rest.to_pr_info(pull))
|
|
304
|
+
return JSON.stringify(git_gh_pr_rest.to_pr_info(read.pull))
|
|
260
305
|
}
|
|
261
306
|
|
|
262
307
|
// `undefined` when the listing could not be read — a failed request, or a PR whose number could not
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { rest_comment_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 { rest_comment_schema } from '#scripts/git/schemas'
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
import { MERGED_STATE, to_gh_state } from './git-gh-rest-state'
|
|
8
8
|
|
package/scripts/gh/git-gh-pr.ts
CHANGED
|
@@ -152,10 +152,12 @@ async function pr_comment(branch_name: string, body: string): Promise<string> {
|
|
|
152
152
|
|
|
153
153
|
// Replaces the body of the branch's open pull request. `git -y` opens a pull request once, so a
|
|
154
154
|
// body supplied on a later run — the live-execution evidence `followup` gates the merge on
|
|
155
|
-
// (joshuafolkken/kit#2446) — reaches an already-open one only through this write.
|
|
155
|
+
// (joshuafolkken/kit#2446) — reaches an already-open one only through this write. The remembered
|
|
156
|
+
// detail read holds the old body, so it is cleared first (joshuafolkken/kit#3263).
|
|
156
157
|
async function pr_update_body(branch_name: string, body: string): Promise<void> {
|
|
157
158
|
const pr_number = await require_pr_number(branch_name)
|
|
158
159
|
|
|
160
|
+
forget_pr_numbers()
|
|
159
161
|
await git_gh_exec.exec_gh_api({
|
|
160
162
|
path: git_gh_api_path.pull_api_path(String(pr_number)),
|
|
161
163
|
method: PATCH_METHOD,
|
|
@@ -163,7 +165,9 @@ async function pr_update_body(branch_name: string, body: string): Promise<void>
|
|
|
163
165
|
})
|
|
164
166
|
}
|
|
165
167
|
|
|
168
|
+
// A merge moves the remembered detail read's state, so it is cleared first (joshuafolkken/kit#3263).
|
|
166
169
|
async function put_merge(pr_number: number): Promise<void> {
|
|
170
|
+
forget_pr_numbers()
|
|
167
171
|
await git_gh_exec.exec_gh_api({
|
|
168
172
|
path: git_gh_api_path.pull_merge_api_path(String(pr_number)),
|
|
169
173
|
method: PUT_METHOD,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cspell:words coderabbit coderabbitai
|
|
2
|
+
import { ai_review_pull_comment_schema } from '#scripts/git/git-schemas'
|
|
2
3
|
import { parse_json_array_or_undefined } from '#scripts/git/parse-json-array'
|
|
3
|
-
import { ai_review_pull_comment_schema } from '#scripts/git/schemas'
|
|
4
4
|
import {
|
|
5
5
|
classify_ai_review_comments,
|
|
6
6
|
type ClassifiedFinding,
|
|
@@ -191,9 +191,7 @@ function evaluate_pr_state(snapshot: PrStateSnapshot): PrEvaluation {
|
|
|
191
191
|
|
|
192
192
|
if (failure !== undefined) return failure
|
|
193
193
|
|
|
194
|
-
if (is_mergeable_state(snapshot) && is_every_required_passing(statuses))
|
|
195
|
-
return 'success'
|
|
196
|
-
}
|
|
194
|
+
if (is_mergeable_state(snapshot) && is_every_required_passing(statuses)) return 'success'
|
|
197
195
|
|
|
198
196
|
return 'pending'
|
|
199
197
|
}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
SECONDS_TO_MS,
|
|
8
8
|
wait_for_pr_success,
|
|
9
9
|
type PrStateEvaluator,
|
|
10
|
-
type ReviewDecisionPredicate,
|
|
11
10
|
} from './git-pr-checks'
|
|
12
11
|
import { is_coderabbit_check, is_required_check, type PrEvaluation } from './git-pr-checks-eval'
|
|
13
12
|
import {
|
|
@@ -116,12 +115,14 @@ function describe_checks_failure(snapshot: PrStateSnapshot): string {
|
|
|
116
115
|
// **The watch never reads `review_decision`**, so the poll must not spend a request fetching it. It
|
|
117
116
|
// is the same difference `evaluate_checks_settled` is written for, said to the fetcher instead of to
|
|
118
117
|
// the verdict: three requests a poll rather than four (joshuafolkken/kit#1043).
|
|
119
|
-
|
|
118
|
+
function should_never_read_review_decision(): boolean {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
120
121
|
|
|
121
122
|
const CHECKS_SETTLED_EVALUATOR: PrStateEvaluator = {
|
|
122
123
|
evaluate: evaluate_checks_settled,
|
|
123
124
|
describe: describe_checks_failure,
|
|
124
|
-
should_read_review_decision:
|
|
125
|
+
should_read_review_decision: should_never_read_review_decision,
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
async function watch_until_settled(branch_name: string): Promise<void> {
|
|
@@ -145,7 +146,7 @@ async function watch_until_settled(branch_name: string): Promise<void> {
|
|
|
145
146
|
// from failing on the first read: two minutes is the grace, and an empty rollup at the end of them
|
|
146
147
|
// is the answer `gh` gave immediately.
|
|
147
148
|
async function fail_when_no_checks(branch_name: string): Promise<void> {
|
|
148
|
-
const snapshot = await default_fetch_pr_state(branch_name,
|
|
149
|
+
const snapshot = await default_fetch_pr_state(branch_name, should_never_read_review_decision)
|
|
149
150
|
if (snapshot.rollup.length === 0) throw new Error(NO_CHECKS_MESSAGE)
|
|
150
151
|
}
|
|
151
152
|
|
|
@@ -173,7 +174,7 @@ export {
|
|
|
173
174
|
git_pr_checks_watch,
|
|
174
175
|
fail_when_no_checks,
|
|
175
176
|
CHECKS_SETTLED_EVALUATOR,
|
|
176
|
-
|
|
177
|
+
should_never_read_review_decision,
|
|
177
178
|
evaluate_checks_settled,
|
|
178
179
|
is_awaited_check,
|
|
179
180
|
describe_checks_failure,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { package_name_schema } from '#scripts/git/schemas'
|
|
1
|
+
import { package_name_schema } from '#scripts/git/git-schemas'
|
|
2
2
|
import { poll } from '#scripts/lib/poll'
|
|
3
3
|
import { git_gh_pr_auto_merge } from './git-gh-pr-auto-merge'
|
|
4
4
|
import { git_gh_pr_snapshot } from './git-gh-pr-snapshot'
|
|
@@ -87,7 +87,9 @@ type ReviewDecisionPredicate = (snapshot: PrStateSnapshot) => boolean
|
|
|
87
87
|
|
|
88
88
|
// The conservative answer, and the one every evaluator that does not say gets: read it every poll,
|
|
89
89
|
// which is what all of them did before the predicate existed.
|
|
90
|
-
|
|
90
|
+
function should_always_read_review_decision(): boolean {
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
91
93
|
|
|
92
94
|
type PrStateFetcher = (
|
|
93
95
|
branch_name: string,
|
|
@@ -199,7 +201,7 @@ async function read_pr_state(
|
|
|
199
201
|
try {
|
|
200
202
|
const snapshot = await input.options.fetcher(
|
|
201
203
|
input.options.branch_name,
|
|
202
|
-
evaluator.should_read_review_decision ??
|
|
204
|
+
evaluator.should_read_review_decision ?? should_always_read_review_decision,
|
|
203
205
|
)
|
|
204
206
|
|
|
205
207
|
return { kind: 'read', snapshot }
|
|
@@ -297,7 +299,7 @@ async function wait_for_pr_success(options: WaitForPrSuccessOptions): Promise<Pr
|
|
|
297
299
|
// (joshuafolkken/kit#1043).
|
|
298
300
|
async function default_fetch_pr_state(
|
|
299
301
|
branch_name: string,
|
|
300
|
-
should_read_review_decision: ReviewDecisionPredicate =
|
|
302
|
+
should_read_review_decision: ReviewDecisionPredicate = should_always_read_review_decision,
|
|
301
303
|
): Promise<PrStateSnapshot> {
|
|
302
304
|
const checks = await git_gh_pr_snapshot.pr_get_checks_snapshot(branch_name)
|
|
303
305
|
const snapshot = parse_pr_state_snapshot(checks.snapshot_json)
|
|
@@ -385,7 +387,7 @@ export {
|
|
|
385
387
|
is_pr_checks_timeout,
|
|
386
388
|
default_fetch_pr_state,
|
|
387
389
|
MERGE_GATE_EVALUATOR,
|
|
388
|
-
|
|
390
|
+
should_always_read_review_decision,
|
|
389
391
|
}
|
|
390
392
|
export type { MergeProgress, PrStateFetcher, PrStateEvaluator, ReviewDecisionPredicate }
|
|
391
393
|
export {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { pull_comment_schema } from '#scripts/git/git-schemas'
|
|
1
2
|
import { parse_json_array_or_undefined } from '#scripts/git/parse-json-array'
|
|
2
|
-
import { pull_comment_schema } from '#scripts/git/schemas'
|
|
3
3
|
import { git_gh_command } from './git-gh-command'
|
|
4
4
|
import { has_ignore_reason } from './git-pr-ai-review'
|
|
5
5
|
|
|
@@ -8,9 +8,7 @@ function has_error_cause(error: Error): boolean {
|
|
|
8
8
|
|
|
9
9
|
function is_pr_already_exists_error(error: unknown): boolean {
|
|
10
10
|
if (error instanceof Error) {
|
|
11
|
-
if (is_pr_already_exists_message(error.message))
|
|
12
|
-
return true
|
|
13
|
-
}
|
|
11
|
+
if (is_pr_already_exists_message(error.message)) return true
|
|
14
12
|
|
|
15
13
|
return has_error_cause(error)
|
|
16
14
|
}
|
package/scripts/gh/git-pr.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { pr_classification, type ReleaseClassification } from '#scripts/ci/pr-classification'
|
|
2
2
|
import type { IssueInfo } from '#scripts/git/git-issue'
|
|
3
|
-
import { pr_info_schema } from '#scripts/git/schemas'
|
|
3
|
+
import { pr_info_schema } from '#scripts/git/git-schemas'
|
|
4
4
|
import { animation_helpers, type AnimationOptions } from '#scripts/lib/animation-helpers'
|
|
5
5
|
import { git_gh_command } from './git-gh-command'
|
|
6
6
|
import { git_pr_error } from './git-pr-error'
|
|
@@ -98,18 +98,14 @@ function parse_pr_state(pr_info_json: string): string | undefined {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
// A read that failed throws rather than answering `undefined`: "no state" is read as "not merged",
|
|
102
|
+
// which would write onto a pull request that already merged (joshuafolkken/kit#3263).
|
|
103
|
+
async function get_pr_state(branch_name: string): Promise<string | undefined> {
|
|
104
|
+
const pr_info_json = await git_gh_command.pr_view(branch_name)
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
return undefined
|
|
107
|
-
}
|
|
106
|
+
if (pr_info_json.length === 0) return undefined
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
} catch {
|
|
111
|
-
return undefined
|
|
112
|
-
}
|
|
108
|
+
return parse_pr_state(pr_info_json)
|
|
113
109
|
}
|
|
114
110
|
|
|
115
111
|
function is_pr_state_merged(pr_state: string | undefined): boolean {
|
|
@@ -137,7 +133,7 @@ async function handle_existing_pr(
|
|
|
137
133
|
branch_name: string,
|
|
138
134
|
options: { label: ReleaseClassification; should_replace_body: boolean },
|
|
139
135
|
): Promise<void> {
|
|
140
|
-
const pr_state_result = await
|
|
136
|
+
const pr_state_result = await get_pr_state(branch_name)
|
|
141
137
|
|
|
142
138
|
if (is_pr_state_merged(pr_state_result)) {
|
|
143
139
|
git_pr_messages.display_merged_pr_message()
|
|
@@ -181,7 +177,7 @@ function build_body(issue_info: IssueInfo, extra_body?: string): string {
|
|
|
181
177
|
|
|
182
178
|
async function existing_pr_label(branch_name: string): Promise<ReleaseClassification | undefined> {
|
|
183
179
|
if (!(await git_gh_command.pr_exists(branch_name))) return undefined
|
|
184
|
-
if (is_pr_state_merged(await
|
|
180
|
+
if (is_pr_state_merged(await get_pr_state(branch_name))) return undefined
|
|
185
181
|
|
|
186
182
|
return await git_gh_command.pr_get_classification(branch_name)
|
|
187
183
|
}
|
|
@@ -75,8 +75,8 @@ function build_target(match: RegExpExecArray): ParsedUrl {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function parse_with(pattern: RegExp, url: string | undefined): ParsedUrl | undefined {
|
|
79
|
-
const match = pattern.exec(url
|
|
78
|
+
function parse_with(pattern: RegExp, url: string | undefined = ''): ParsedUrl | undefined {
|
|
79
|
+
const match = pattern.exec(url)
|
|
80
80
|
if (match === null) return undefined
|
|
81
81
|
|
|
82
82
|
return build_target(match)
|