@joshuafolkken/kit 1.1178.0 → 1.1240.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/settings.json +2 -2
- package/.claude/skills/dependency-update/SKILL.md +4 -8
- package/.claude/skills/epic-commands/SKILL.md +2 -2
- package/.claude/skills/epic-commands/epic-bundle.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +2 -4
- package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +7 -3
- package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
- package/.claude/skills/workflow-commands/backlogrun-progress.md +9 -4
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
- package/.claude/skills/workflow-commands/chain-rule.md +1 -1
- package/.claude/skills/workflow-commands/delegation.md +2 -2
- package/.claude/skills/workflow-commands/followup.md +10 -0
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-scout.md +1 -1
- package/.claude/skills/workflow-commands/kickoff.md +2 -1
- package/.claude/skills/workflow-commands/latest-gate.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
- package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
- package/.claude/skills/workflow-commands/prerequisite.md +2 -2
- package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
- package/.codex/hooks.json +2 -2
- package/.cursorrules +6 -41
- package/.github/actions/setup-node/action.yml +1 -1
- package/.github/workflows/auto-tag.yml +3 -4
- package/.github/workflows/ci.yml +32 -31
- package/.github/workflows/dependabot-auto-merge.yml +13 -6
- package/.github/workflows/publish.yml +50 -4
- package/.github/workflows/sonar-qube.yml +3 -0
- package/.pnpmfile.mjs +4 -3
- package/.prettierignore +8 -2
- package/CLAUDE.md +18 -20
- package/README.md +44 -38
- package/SECURITY.md +15 -1
- package/dist/CLAUDE.md +18 -20
- package/dist/commands/document-section-cli.js +734 -0
- package/dist/hooks/audit-provision.js +346 -0
- package/dist/hooks/batch-guard.js +5 -4
- package/dist/hooks/{chunk-G6KRCKEC.js → chunk-4TZVB44A.js} +496 -378
- package/dist/hooks/chunk-5VNDSNG3.js +12 -0
- package/dist/hooks/chunk-CDY4NFQG.js +15 -0
- package/dist/hooks/chunk-ELCN5LCH.js +135 -0
- package/dist/hooks/{chunk-VQLLNFNT.js → chunk-F3ELQ2AF.js} +3 -3
- package/dist/hooks/{chunk-I4XSL4TT.js → chunk-GPTFMK5E.js} +1 -1
- package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
- package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-KHGB5S3Y.js} +7 -6
- package/dist/hooks/{chunk-GDS5N2KB.js → chunk-KHX4U3BG.js} +735 -683
- package/dist/hooks/{chunk-VESKRQOF.js → chunk-KRBERBBI.js} +3 -3
- package/dist/hooks/{chunk-DO6NWWHU.js → chunk-QVSWU24O.js} +4 -4
- package/dist/hooks/{chunk-S6X6WYKR.js → chunk-WK2XSHSI.js} +1643 -923
- package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-XZYKEM6J.js} +25 -10
- package/dist/hooks/{chunk-32GQTL4F.js → chunk-ZQGD4GBY.js} +4 -2
- package/dist/hooks/codex-hook-adapter.js +14 -12
- package/dist/hooks/duplicate-read-guard.js +6 -5
- package/dist/hooks/format-edited.js +4 -3
- package/dist/hooks/inputs.json +203 -179
- package/dist/hooks/investigation-guard.js +6 -5
- package/dist/hooks/pretool-guard.js +12 -10
- package/dist/hooks/session-lang.js +2 -4
- package/dist/hooks/stop-guard.js +66 -72
- package/dist/josh.js +1088 -78
- package/dist/version/index.d.ts +7 -2
- package/dist/version/index.js +152 -75
- package/eslint/base.js +23 -1
- package/eslint/rules/code-quality.js +3 -0
- package/eslint/rules/early-return-one-liner.js +98 -0
- package/eslint/rules/unicorn.js +16 -1
- package/package.json +7 -6
- package/pnpm-workspace.yaml +9 -4
- package/prettier/basic.d.ts +3 -1
- package/prettier/index.d.ts +3 -1
- package/prettier/static.d.ts +3 -1
- package/prompts/coding-standards.md +1 -1
- package/prompts/collaboration-workflow/file-edits.md +4 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/issue-template.md +1 -0
- package/prompts/collaboration-workflow/operating-rules.md +17 -9
- package/prompts/collaboration-workflow/overview.md +2 -2
- package/prompts/collaboration-workflow/principles.md +14 -12
- package/prompts/collaboration-workflow/report-format.md +1 -1
- package/prompts/collaboration-workflow/residency.md +1 -1
- package/prompts/collaboration-workflow/rule-delivery.md +6 -6
- package/prompts/collaboration-workflow/shell-body.md +6 -6
- package/prompts/collaboration-workflow/turn-batching.md +1 -1
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
- package/prompts/collaboration-workflow/wip-cap.md +10 -10
- package/prompts/review-rubric.md +1 -2
- package/prompts/review.md +1 -1
- package/scripts/agent/agent-headless.ts +29 -0
- package/scripts/agent/agent-launch-environment.ts +6 -2
- package/scripts/agent/codex-agent-argv.ts +2 -1
- package/scripts/auto-ok/auto-ok-cli.ts +1 -1
- package/scripts/backlog/backlog-arrival.ts +3 -3
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-args.ts +135 -0
- package/scripts/backlog/backlog-drive-cli.ts +11 -137
- package/scripts/backlog/backlog-drive-launch.ts +39 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
- package/scripts/backlog/backlog-drive-named.ts +3 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
- package/scripts/backlog/backlog-drive-owner.ts +1 -1
- package/scripts/backlog/backlog-drive-restore.ts +1 -1
- package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
- package/scripts/backlog/backlog-drive.ts +1 -1
- package/scripts/backlog/backlog-next.ts +16 -5
- package/scripts/backlog/backlog-offer-cli.ts +6 -6
- package/scripts/backlog/backlog-offer.ts +1 -1
- package/scripts/backlog/backlog-plan-cli.ts +1 -1
- package/scripts/backlog/backlog-pool.ts +57 -11
- package/scripts/backlog/backlog-ready.ts +1 -1
- package/scripts/backlog/backlog-scope.ts +1 -1
- package/scripts/backlog/backlog-stalled-detect.ts +2 -2
- package/scripts/backlog/backlog-stalled.ts +1 -1
- package/scripts/build/build-codex-hooks.ts +3 -1
- package/scripts/build/build-commands.ts +66 -0
- package/scripts/build/build-hooks.ts +2 -1
- package/scripts/build/build-library.ts +12 -3
- package/scripts/build/build.ts +70 -0
- package/scripts/build/local-bin.ts +3 -13
- package/scripts/delegation/investigation-reads.ts +3 -1
- package/scripts/discovery/repo-origin.ts +5 -5
- package/scripts/doctor/doctor-consumer.ts +2 -2
- package/scripts/doctor/doctor-io.ts +2 -3
- package/scripts/document/capped-print-part.ts +30 -0
- package/scripts/document/capped-print.ts +3 -4
- package/scripts/document/document-byte-budget.ts +11 -2
- package/scripts/document/document-scan.ts +117 -0
- package/scripts/document/document-section.ts +143 -16
- package/scripts/document/entry-read-budget.ts +5 -3
- package/scripts/document/entry-read-set.ts +10 -89
- package/scripts/document/read-set-cost.ts +98 -0
- package/scripts/epic/epic-add-body.ts +1 -0
- package/scripts/epic/epic-audit-checks.ts +1 -1
- package/scripts/epic/epic-audit-rationale.ts +4 -3
- package/scripts/epic/epic-bundle-cli.ts +14 -3
- package/scripts/epic/epic-bundle-poll.ts +41 -0
- package/scripts/epic/epic-busy.ts +1 -1
- package/scripts/epic/epic-candidate-confirm.ts +3 -2
- package/scripts/epic/epic-classify.ts +1 -1
- package/scripts/epic/epic-cli-argv.ts +78 -46
- package/scripts/epic/epic-cli-remove.ts +11 -19
- package/scripts/epic/epic-cli.ts +74 -73
- package/scripts/epic/epic-close-comment.ts +1 -1
- package/scripts/epic/epic-close.ts +1 -1
- package/scripts/epic/epic-cross-repo.ts +5 -5
- package/scripts/epic/epic-graph.ts +1 -1
- package/scripts/epic/epic-index.ts +1 -1
- package/scripts/epic/epic-issue.ts +2 -6
- package/scripts/epic/epic-lane-offer.ts +2 -2
- package/scripts/epic/epic-next.ts +29 -42
- package/scripts/epic/epic-solo-stale.ts +1 -1
- package/scripts/epic/epic-triage.ts +8 -4
- package/scripts/epic/epic-validate.ts +1 -1
- package/scripts/followup/git-followup-finish.ts +2 -2
- package/scripts/followup/git-followup-label.ts +2 -2
- package/scripts/followup/git-followup-merged.ts +2 -2
- package/scripts/followup/git-pr-followup-checks.ts +142 -0
- package/scripts/followup/git-pr-followup.ts +4 -129
- package/scripts/gate/core-admission.ts +162 -0
- package/scripts/gate/core-budget.ts +190 -68
- package/scripts/gate/gate-plan.ts +80 -7
- package/scripts/gate/gate-report.ts +28 -9
- package/scripts/gate/gate-skip.ts +38 -7
- package/scripts/gate/hook-gate-reuse.ts +12 -10
- package/scripts/gate/machine-capacity.ts +175 -0
- package/scripts/gate/pre-commit-type-check.ts +4 -1
- package/scripts/gate/project-checks.ts +6 -9
- package/scripts/gate/type-check-step.ts +2 -3
- package/scripts/gate/verification-gate.ts +34 -10
- package/scripts/gh/git-gh-api-path.ts +2 -2
- package/scripts/gh/git-gh-issue-rest.ts +3 -3
- package/scripts/gh/git-gh-issue-write.ts +53 -7
- package/scripts/gh/git-gh-pr-read.ts +64 -19
- package/scripts/gh/git-gh-pr-rest.ts +1 -1
- package/scripts/gh/git-gh-pr.ts +5 -1
- package/scripts/gh/git-pr-ai-review.ts +1 -1
- package/scripts/gh/git-pr-checks-eval.ts +1 -3
- package/scripts/gh/git-pr-checks-parse.ts +1 -1
- package/scripts/gh/git-pr-checks-watch.ts +6 -5
- package/scripts/gh/git-pr-checks.ts +7 -5
- package/scripts/gh/git-pr-coderabbit.ts +1 -1
- package/scripts/gh/git-pr-error.ts +1 -3
- package/scripts/gh/git-pr.ts +9 -13
- package/scripts/gh/github-issue-url.ts +2 -2
- package/scripts/git/constants.ts +2 -6
- package/scripts/git/git-command.ts +20 -218
- package/scripts/git/git-diff-reads.ts +225 -0
- package/scripts/git/git-error.ts +4 -12
- package/scripts/git/git-pre-push-hook.ts +139 -0
- package/scripts/git/git-prompt.ts +2 -6
- package/scripts/git/git-spawn-sync.ts +3 -1
- package/scripts/git/git-spawn.ts +1 -1
- package/scripts/git/git-staging.ts +4 -6
- package/scripts/git/git-status.ts +3 -9
- package/scripts/hooks/codex-hook-adapter.ts +1 -1
- package/scripts/hooks/edited-cspell.ts +2 -4
- package/scripts/hooks/format-edited-file.ts +3 -12
- package/scripts/hooks/pre-push-unit.ts +31 -9
- package/scripts/hooks/reserved-run.ts +2 -2
- package/scripts/hooks/run-hook.sh +5 -4
- package/scripts/hooks/stop-guard.ts +5 -3
- package/scripts/init/init-actions.ts +0 -5
- package/scripts/init/init-ai-copy.ts +13 -2
- package/scripts/init/init-logic-json-merge.ts +19 -0
- package/scripts/init/init-logic-secretlint.ts +7 -7
- package/scripts/init/init-logic-workspace.ts +41 -2
- package/scripts/init/init-logic.ts +16 -26
- package/scripts/init/init.ts +10 -16
- package/scripts/init/initial-commit.ts +4 -4
- package/scripts/init/kit-development-versions.ts +40 -0
- package/scripts/init/kit-setup-state.ts +2 -2
- package/scripts/init/package-manager-pin.ts +35 -0
- package/scripts/init/start-exec.ts +68 -19
- package/scripts/init/start-setup-pr.ts +9 -9
- package/scripts/init/start-steps.ts +6 -10
- package/scripts/issue/defect-rate-cli.ts +1 -1
- package/scripts/issue/git-next-issues.ts +1 -1
- package/scripts/issue/issue-auto-ok.ts +32 -7
- package/scripts/issue/issue-backlinks-cli.ts +2 -2
- package/scripts/issue/issue-backlinks.ts +9 -9
- package/scripts/issue/issue-comment-cli.ts +19 -13
- package/scripts/issue/issue-file-cli.ts +72 -22
- package/scripts/issue/issue-file.ts +29 -1
- package/scripts/issue/issue-labels.ts +13 -0
- package/scripts/issue/issue-release-cli.ts +155 -0
- package/scripts/issue/issue-release.ts +58 -0
- package/scripts/issue/issue-scout-closed.ts +1 -1
- package/scripts/issue/issue-state-cli.ts +30 -74
- package/scripts/josh/josh-command-map.ts +2 -0
- package/scripts/josh/josh-command-types.ts +14 -0
- package/scripts/josh/josh-commands-ai.ts +4 -4
- package/scripts/josh/josh-commands-backlog.ts +4 -1
- package/scripts/josh/josh-commands-development.ts +18 -0
- package/scripts/josh/josh-commands-document.ts +1 -0
- package/scripts/josh/josh-commands-issue.ts +8 -1
- package/scripts/josh/josh-commands-lane.ts +24 -1
- package/scripts/josh/josh-commands-maintenance.ts +6 -6
- package/scripts/josh/josh-commands-run.ts +12 -7
- package/scripts/josh/josh-in-process.ts +28 -9
- package/scripts/josh/josh-logic.ts +34 -15
- package/scripts/josh/process-identity.ts +1 -3
- package/scripts/josh/session-language-cli.ts +3 -4
- package/scripts/lane/lane-await.ts +5 -1
- package/scripts/lane/lane-child-invocation.ts +2 -2
- package/scripts/lane/lane-cli.ts +12 -4
- package/scripts/lane/lane-dispatch.ts +14 -10
- package/scripts/lane/lane-environment.ts +5 -1
- package/scripts/lane/lane-handoff.ts +1 -1
- package/scripts/lane/lane-install.ts +2 -13
- package/scripts/lane/lane-launch-cli.ts +15 -1
- package/scripts/lane/lane-ledger.ts +145 -0
- package/scripts/lane/lane-leftover.ts +44 -30
- package/scripts/lane/lane-load.ts +108 -0
- package/scripts/lane/lane-measure-cli.ts +149 -0
- package/scripts/lane/lane-occupancy.ts +3 -0
- package/scripts/lane/lane-open.ts +5 -5
- package/scripts/lane/lane-paths.ts +13 -0
- package/scripts/lane/lane-reap.ts +1 -3
- package/scripts/lane/lane-registry.ts +11 -0
- package/scripts/lane/lane-relaunch.ts +1 -1
- package/scripts/lane/lane-stats.ts +224 -0
- package/scripts/lane/lane-stray.ts +140 -0
- package/scripts/lane/lane-vacant.ts +36 -0
- package/scripts/lane/openai-lane-ship.ts +2 -2
- package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
- package/scripts/lane/openai-lane-supervisor.ts +7 -165
- package/scripts/lane/openai-review-broker.ts +2 -4
- package/scripts/lib/ancestor-directories.ts +35 -0
- package/scripts/lib/animation-helpers.ts +2 -2
- package/scripts/lib/buffered-process.ts +3 -7
- package/scripts/lib/cli-flags.ts +71 -1
- package/scripts/lib/spawn-exit.ts +2 -2
- package/scripts/lib/timeouts.ts +35 -2
- package/scripts/lines/line-budget.ts +4 -6
- package/scripts/notify/telegram-test.ts +6 -0
- package/scripts/observations/observation-ledger-line.ts +6 -6
- package/scripts/observations/observation-ledger-migrate.ts +55 -4
- package/scripts/observations/observation-ledger.ts +26 -7
- package/scripts/overrides/lockfile-overrides.ts +1 -1
- package/scripts/overrides/overrides-logic.ts +1 -1
- package/scripts/propagate/propagate-git.ts +1 -1
- package/scripts/propagate/propagate-publish.ts +2 -2
- package/scripts/propagate/propagate-steps.ts +20 -15
- package/scripts/propagate/propagate.ts +36 -27
- package/scripts/refactor/import-graph.ts +18 -1
- package/scripts/refactor/refactor-lint.ts +4 -4
- package/scripts/release/github-release-environment.ts +1 -0
- package/scripts/release/github-release-runs.ts +115 -0
- package/scripts/release/github-release.ts +13 -67
- package/scripts/repo/repository-labels.ts +2 -2
- package/scripts/repo/required-checks-logic.ts +1 -3
- package/scripts/review/live-evidence.ts +2 -4
- package/scripts/review/review-brief-cli.ts +1 -1
- package/scripts/review/review-finding-ledger.ts +1 -1
- package/scripts/review/review-findings-cli.ts +1 -3
- package/scripts/rules/decision-oracle-batch.ts +128 -0
- package/scripts/rules/decision-oracle-tokens.ts +21 -0
- package/scripts/rules/decision-oracle.ts +8 -120
- package/scripts/rules/delivered-rules.ts +26 -182
- package/scripts/rules/direct-filing.ts +1 -1
- package/scripts/rules/early-heartbeat.ts +3 -3
- package/scripts/rules/git-argv.ts +1 -1
- package/scripts/rules/git-force.ts +1 -1
- package/scripts/rules/implementation-cut.ts +2 -2
- package/scripts/rules/index-guard.ts +1 -1
- package/scripts/rules/issue-comments.ts +118 -0
- package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
- package/scripts/rules/lane-background.ts +2 -2
- package/scripts/rules/lane-park.ts +18 -0
- package/scripts/rules/lane-split-park.ts +128 -0
- package/scripts/rules/oracle-firing.ts +1 -1
- package/scripts/rules/poll-loop.ts +1 -1
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/rules/protected-files.ts +59 -4
- package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
- package/scripts/rules/shell-body-trigger.ts +1 -1
- package/scripts/rules/stop-rules.ts +18 -7
- package/scripts/rules/test-declared-commit.ts +4 -4
- package/scripts/rules/wip-cap.ts +63 -0
- package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
- package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
- package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
- package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
- package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
- package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +18 -5
- package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
- package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
- package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
- package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +1 -0
- package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
- package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
- package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +20 -2
- package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
- package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
- package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
- package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
- package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
- package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
- package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
- package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
- package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
- package/scripts/run/run-ending.ts +2 -2
- package/scripts/run/run-halfrun-resume.ts +6 -1
- package/scripts/run/run-headless.ts +1 -1
- package/scripts/run/run-invocation.ts +85 -84
- package/scripts/run/run-liveness.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +1 -1
- package/scripts/run/run-preflight.ts +6 -6
- package/scripts/run/run-prep-cli.ts +1 -1
- package/scripts/run/run-prrun-resume.ts +1 -1
- package/scripts/run/run-report-cli.ts +4 -4
- package/scripts/run/run-report.ts +2 -2
- package/scripts/run/run-status-cli.ts +1 -1
- package/scripts/run/run-step-cli.ts +3 -3
- package/scripts/run/run-step.ts +7 -4
- package/scripts/run/run-stop-notify.ts +1 -1
- package/scripts/run/run-stranded-detect.ts +4 -4
- package/scripts/run/run-stranded.ts +2 -2
- package/scripts/run/run-watcher-guard-cli.ts +1 -1
- package/scripts/run/run-watcher-guard.ts +5 -3
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
- package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
- package/scripts/run/ship/run-ship-lock.ts +50 -0
- package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
- package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
- package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
- package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
- package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
- package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
- package/scripts/run/ship/run-ship-stop-text.ts +13 -0
- package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
- package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
- package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +25 -11
- package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +58 -7
- package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
- package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
- package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +13 -15
- package/scripts/run/wake/run-wake-restart.ts +58 -0
- package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
- package/scripts/run/{run-wake.ts → wake/run-wake.ts} +16 -65
- package/scripts/safe-chain/preinstall-command.ts +20 -21
- package/scripts/safe-chain/project-config.ts +19 -15
- package/scripts/sync/sync-ai-files.ts +1 -1
- package/scripts/sync/sync-configs.ts +2 -1
- package/scripts/sync/sync.ts +15 -2
- package/scripts/test/unit-worker-share.ts +1 -0
- package/scripts/time-runtime/time-background.ts +5 -1
- package/scripts/time-runtime/time-instant.ts +3 -1
- package/scripts/time-runtime/time-spans.ts +6 -139
- package/scripts/time-runtime/time-tool-call.ts +160 -0
- package/scripts/version/effective-upstream.ts +2 -20
- package/scripts/version/latest-corepack.ts +124 -93
- package/scripts/version/latest-regression.ts +1 -1
- package/scripts/version/latest-update.ts +4 -4
- package/scripts/version/npm-registry.ts +20 -5
- package/scripts/version/publishable-range-check.ts +2 -2
- package/scripts/version/publishable-range.ts +1 -1
- package/scripts/version/release-age.ts +48 -38
- package/scripts/version/release-hold.ts +1 -1
- package/scripts/version/safe-chain-version-update.ts +40 -0
- package/scripts/version/upgrade-shell-command.ts +48 -8
- package/scripts/version/version-check-logic.ts +23 -14
- package/scripts/version/version-commands.ts +23 -11
- package/scripts/version/version-remote.ts +28 -13
- package/templates/pnpm-workspace.basic.yaml +4 -0
- package/templates/workflows/dependabot-auto-merge.yml +5 -5
- package/templates/workflows/github-release.yml +6 -0
- package/scripts/build/build-config-merge.ts +0 -27
- package/scripts/build/build-managed-marker.ts +0 -27
- package/scripts/build/build-self-sync-guard.ts +0 -27
- package/scripts/build/build-version.ts +0 -26
- package/scripts/version/preinstall-version-update.ts +0 -86
- /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
- /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
- /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
- /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
- /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
- /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
- /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
- /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
- /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
- /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
- /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
- /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
- /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
- /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
- /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
- /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
- /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
- /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
- /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
- /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
- /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
- /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
- /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
- /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
- /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
|
@@ -9,26 +9,29 @@ import { git_gh_issue_list, MAX_SCANNED } from '#scripts/gh/git-gh-issue-list'
|
|
|
9
9
|
import { git_gh_issue_write } from '#scripts/gh/git-gh-issue-write'
|
|
10
10
|
import { github_issue_url } from '#scripts/gh/github-issue-url'
|
|
11
11
|
import { error_text } from '#scripts/lib/error-message'
|
|
12
|
+
import type { PollOptions } from '#scripts/lib/poll'
|
|
12
13
|
import { repository_labels } from '#scripts/repo/repository-labels'
|
|
13
14
|
import { issue_auto_ok } from './issue-auto-ok'
|
|
14
15
|
import { issue_file, type FileArguments } from './issue-file'
|
|
15
16
|
import { issue_lint_cli } from './issue-lint-cli'
|
|
17
|
+
import { issue_release_cli } from './issue-release-cli'
|
|
16
18
|
import { issue_scout_cli } from './issue-scout-cli'
|
|
17
19
|
import { issue_wip } from './issue-wip'
|
|
18
20
|
|
|
19
21
|
// `josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>]…
|
|
20
|
-
// [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok]` — file an Issue
|
|
21
|
-
// filing step run in order (joshuafolkken/kit#2808): the third-party refusal, the body
|
|
22
|
-
// `## Origin` check for another repository, the
|
|
23
|
-
//
|
|
24
|
-
// (joshuafolkken/kit#
|
|
22
|
+
// [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok] [--release]` — file an Issue
|
|
23
|
+
// with every filing step run in order (joshuafolkken/kit#2808): the third-party refusal, the body
|
|
24
|
+
// lint, the `## Origin` check for another repository, the `auto-ok` decision (joshuafolkken/kit#3213)
|
|
25
|
+
// with the run label it owes (joshuafolkken/kit#3313), the WIP cap count (joshuafolkken/kit#3181), the
|
|
26
|
+
// duplicate scout, the missing workflow labels created (joshuafolkken/kit#3176), the create call
|
|
27
|
+
// carrying every label, the release link on `--release` (joshuafolkken/kit#3360), and `epic:bundle`.
|
|
25
28
|
// A direct `gh api …/issues` filing is refused by the `direct-filing` delivered rule and pointed here.
|
|
26
29
|
|
|
27
30
|
const SUCCESS_EXIT_CODE = 0
|
|
28
31
|
const FAILURE_EXIT_CODE = 1
|
|
29
32
|
const ARGV_OFFSET = 2
|
|
30
33
|
const USAGE =
|
|
31
|
-
'Usage: josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <tier-a|split|interrupt|review-cap>] [--label <name>]… [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok]'
|
|
34
|
+
'Usage: josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <tier-a|split|interrupt|review-cap>] [--label <name>]… [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok] [--release]'
|
|
32
35
|
const UNKNOWN_REPO_MESSAGE =
|
|
33
36
|
'Could not read this repository from `git remote`, so the filing has no repository to compare against — check `gh auth status`.'
|
|
34
37
|
const THIRD_PARTY_MESSAGE =
|
|
@@ -36,6 +39,9 @@ const THIRD_PARTY_MESSAGE =
|
|
|
36
39
|
// `GH_REPO` is the variable `gh` reads `{owner}/{repo}` from, so setting it points every listing the
|
|
37
40
|
// scout and `epic:bundle` make at the target repository rather than at this checkout.
|
|
38
41
|
const GH_REPO_VARIABLE = 'GH_REPO'
|
|
42
|
+
// The open listing `epic:bundle` reads trails the create call, so the issue just filed is looked for
|
|
43
|
+
// again for up to ten seconds before the placement is given up (joshuafolkken/kit#3332).
|
|
44
|
+
const FRESH_ISSUE_POLL: PollOptions = { attempts: 6, interval_ms: 2000 }
|
|
39
45
|
|
|
40
46
|
interface Filing {
|
|
41
47
|
args: FileArguments
|
|
@@ -130,22 +136,35 @@ async function is_scout_clear(filing: Filing): Promise<boolean> {
|
|
|
130
136
|
return open.length === 0
|
|
131
137
|
}
|
|
132
138
|
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
async function create(filing: Filing): Promise<string | undefined> {
|
|
137
|
-
repository_labels.ensure_labels(filing.target)
|
|
139
|
+
// Every label the create call carries, or `undefined` when an `auto-ok` filing names neither run label
|
|
140
|
+
// (joshuafolkken/kit#3313). Decided before the WIP count and the scout, so a refusal costs no listing.
|
|
141
|
+
async function labels_of(filing: Filing): Promise<ReadonlyArray<string> | undefined> {
|
|
138
142
|
const auto_ok = await issue_auto_ok.resolve(
|
|
139
143
|
filing.args.is_auto_ok_opted_out,
|
|
140
144
|
filing.target,
|
|
141
145
|
filing.current,
|
|
146
|
+
filing.args.labels,
|
|
142
147
|
)
|
|
143
148
|
|
|
144
149
|
console.info(issue_auto_ok.line_of(auto_ok))
|
|
150
|
+
const labels = issue_file.labels_of(filing.args, filing.body, auto_ok.is_applied)
|
|
151
|
+
const problem = issue_file.triage_problem(labels)
|
|
152
|
+
|
|
153
|
+
if (problem === undefined) return labels
|
|
154
|
+
console.error(`✖ ${problem}`)
|
|
155
|
+
|
|
156
|
+
return undefined
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// `undefined` when the create call failed. `gh`'s standard error is captured into the thrown error
|
|
160
|
+
// rather than printed, so it is printed here — otherwise the filing fails with no reason given. A label
|
|
161
|
+
// the create applies is provisioned first, so it never arrives with a generated color.
|
|
162
|
+
async function create(filing: Filing, labels: ReadonlyArray<string>): Promise<string | undefined> {
|
|
163
|
+
repository_labels.ensure_labels(filing.target)
|
|
145
164
|
const request = git_gh_issue_write.issue_create_request({
|
|
146
165
|
title: filing.args.title,
|
|
147
166
|
body: filing.body,
|
|
148
|
-
labels
|
|
167
|
+
labels,
|
|
149
168
|
repo: filing.target,
|
|
150
169
|
})
|
|
151
170
|
|
|
@@ -160,12 +179,32 @@ async function create(filing: Filing): Promise<string | undefined> {
|
|
|
160
179
|
}
|
|
161
180
|
}
|
|
162
181
|
|
|
182
|
+
function issue_number_of(url: string): number {
|
|
183
|
+
return Number(github_issue_url.parse(url)?.issue_number)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// `--release`: the filed Issue blocks the target's release Issue (joshuafolkken/kit#3360). Reported,
|
|
187
|
+
// never failed, for the reason `place` gives. The re-run hint names the target through `GH_REPO`:
|
|
188
|
+
// `issue:release` links in the repository `gh` resolves, which is not the target on a `--repo` filing.
|
|
189
|
+
async function link_release(url: string, filing: Filing): Promise<void> {
|
|
190
|
+
if (!filing.args.is_release) return
|
|
191
|
+
const issue_number = issue_number_of(url)
|
|
192
|
+
const is_linked =
|
|
193
|
+
Number.isSafeInteger(issue_number) &&
|
|
194
|
+
(await issue_release_cli.link(issue_number, filing.target))
|
|
195
|
+
|
|
196
|
+
if (is_linked) return
|
|
197
|
+
console.error(
|
|
198
|
+
`⚠ the release link did not complete for ${url} — run \`${GH_REPO_VARIABLE}=${filing.target} pnpm josh issue:release <N>\`.`,
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
163
202
|
// The Issue exists once `create` returns, so a failed placement is reported rather than failed: a
|
|
164
203
|
// non-zero exit would read as "not filed" and invite the second filing the scout exists to stop.
|
|
165
204
|
async function place(url: string, target: string): Promise<void> {
|
|
166
|
-
const issue_number =
|
|
205
|
+
const issue_number = issue_number_of(url)
|
|
167
206
|
const exit_code = Number.isSafeInteger(issue_number)
|
|
168
|
-
? await epic_bundle_cli.report_for(issue_number, target)
|
|
207
|
+
? await epic_bundle_cli.report_for(issue_number, target, FRESH_ISSUE_POLL)
|
|
169
208
|
: FAILURE_EXIT_CODE
|
|
170
209
|
|
|
171
210
|
if (exit_code !== SUCCESS_EXIT_CODE) {
|
|
@@ -173,25 +212,36 @@ async function place(url: string, target: string): Promise<void> {
|
|
|
173
212
|
}
|
|
174
213
|
}
|
|
175
214
|
|
|
176
|
-
|
|
215
|
+
function is_admitted(filing: Filing): boolean {
|
|
177
216
|
const refusals = refusals_of(filing)
|
|
178
217
|
|
|
179
|
-
if (refusals.length
|
|
180
|
-
|
|
218
|
+
if (refusals.length === 0) return true
|
|
219
|
+
console.error(refusals.join('\n'))
|
|
181
220
|
|
|
182
|
-
|
|
183
|
-
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
184
223
|
|
|
185
|
-
|
|
186
|
-
const url = await create(filing)
|
|
224
|
+
async function send(filing: Filing, labels: ReadonlyArray<string>): Promise<number> {
|
|
225
|
+
const url = await create(filing, labels)
|
|
187
226
|
|
|
188
227
|
if (url === undefined) return FAILURE_EXIT_CODE
|
|
189
228
|
console.info(url)
|
|
229
|
+
await link_release(url, filing)
|
|
190
230
|
await place(url, filing.target)
|
|
191
231
|
|
|
192
232
|
return SUCCESS_EXIT_CODE
|
|
193
233
|
}
|
|
194
234
|
|
|
235
|
+
async function file(filing: Filing): Promise<number> {
|
|
236
|
+
if (!is_admitted(filing)) return FAILURE_EXIT_CODE
|
|
237
|
+
const labels = await labels_of(filing)
|
|
238
|
+
|
|
239
|
+
if (labels === undefined) return FAILURE_EXIT_CODE
|
|
240
|
+
if (!(await is_wip_clear(filing)) || !(await is_scout_clear(filing))) return FAILURE_EXIT_CODE
|
|
241
|
+
|
|
242
|
+
return await send(filing, labels)
|
|
243
|
+
}
|
|
244
|
+
|
|
195
245
|
async function filing_of(args: FileArguments): Promise<Filing | string> {
|
|
196
246
|
const body = await read_body(args.body_file)
|
|
197
247
|
|
|
@@ -228,7 +278,7 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
|
|
|
228
278
|
process.exitCode = await run(argv)
|
|
229
279
|
}
|
|
230
280
|
|
|
231
|
-
const issue_file_cli = { THIRD_PARTY_MESSAGE, run }
|
|
281
|
+
const issue_file_cli = { FRESH_ISSUE_POLL, THIRD_PARTY_MESSAGE, run }
|
|
232
282
|
|
|
233
283
|
if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
|
|
234
284
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { parseArgs } from 'node:util'
|
|
2
2
|
import { epic_issue } from '#scripts/epic/epic-issue'
|
|
3
|
+
import { epic_triage } from '#scripts/epic/epic-triage'
|
|
3
4
|
import { github_issue_url } from '#scripts/gh/github-issue-url'
|
|
4
5
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
6
|
import { issue_backlinks } from './issue-backlinks'
|
|
@@ -9,6 +10,8 @@ import {
|
|
|
9
10
|
DEPTH_LABEL_ORDER,
|
|
10
11
|
FILING_ROUTE_LABELS,
|
|
11
12
|
has_label_name,
|
|
13
|
+
RUN_LANE_LABEL,
|
|
14
|
+
RUN_SOLO_LABEL,
|
|
12
15
|
} from './issue-labels'
|
|
13
16
|
import { markdown_section } from './markdown-section'
|
|
14
17
|
|
|
@@ -40,6 +43,9 @@ interface FileArguments {
|
|
|
40
43
|
is_over_cap: boolean
|
|
41
44
|
// `--no-auto-ok`: the Issue needs a person's judgement, so `auto-ok` is not applied (`issue-auto-ok.ts`).
|
|
42
45
|
is_auto_ok_opted_out: boolean
|
|
46
|
+
// `--release`: the change reaches a consumer only once published, so the Issue is linked to the
|
|
47
|
+
// repository's release Issue as a blocker (`issue-release-cli.ts`).
|
|
48
|
+
is_release: boolean
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
const OPTIONS = {
|
|
@@ -51,6 +57,7 @@ const OPTIONS = {
|
|
|
51
57
|
distinct: { type: 'string', multiple: true },
|
|
52
58
|
'over-cap': { type: 'boolean' },
|
|
53
59
|
'no-auto-ok': { type: 'boolean' },
|
|
60
|
+
release: { type: 'boolean' },
|
|
54
61
|
} as const
|
|
55
62
|
|
|
56
63
|
// The label a `--depth` / `--route` value names, or `undefined` when it names none — read against the
|
|
@@ -120,6 +127,7 @@ function arguments_of(values: ParsedValues, title: string | undefined): FileArgu
|
|
|
120
127
|
repo: values.repo,
|
|
121
128
|
is_over_cap: values['over-cap'] === true,
|
|
122
129
|
is_auto_ok_opted_out: values['no-auto-ok'] === true,
|
|
130
|
+
is_release: values.release === true,
|
|
123
131
|
}
|
|
124
132
|
}
|
|
125
133
|
|
|
@@ -152,6 +160,19 @@ function labels_of(args: FileArguments, body: string, is_auto_ok = false): Reado
|
|
|
152
160
|
return [...labels, AUTO_OK_LABEL]
|
|
153
161
|
}
|
|
154
162
|
|
|
163
|
+
// Why an `auto-ok` filing with neither `run:lane` nor `run:solo` is refused, or `undefined` when it is
|
|
164
|
+
// not (joshuafolkken/kit#3313). The offer paths withhold every candidate while one opted-in Issue is
|
|
165
|
+
// untriaged, so a run's own filing without the judgement stopped every lane. No default is applied:
|
|
166
|
+
// `run:solo` is the answer that keeps a verification defect from running beside others, and only a
|
|
167
|
+
// reading of the Issue gives it (`backlogrun-lanes.md`).
|
|
168
|
+
function triage_problem(labels: ReadonlyArray<string>): string | undefined {
|
|
169
|
+
const is_owed = has_label_name(labels, AUTO_OK_LABEL) && !epic_triage.has_triage_label(labels)
|
|
170
|
+
|
|
171
|
+
if (!is_owed) return undefined
|
|
172
|
+
|
|
173
|
+
return `an \`${AUTO_OK_LABEL}\` filing needs \`--label ${RUN_LANE_LABEL}\` or \`--label ${RUN_SOLO_LABEL}\` — \`${RUN_SOLO_LABEL}\` only for a defect in kit's own verification that makes unrelated PRs answer wrongly on main now (\`backlogrun-lanes.md\`), else \`${RUN_LANE_LABEL}\``
|
|
174
|
+
}
|
|
175
|
+
|
|
155
176
|
function is_same_repository(target: string, current: string): boolean {
|
|
156
177
|
return target.toLowerCase() === current.toLowerCase()
|
|
157
178
|
}
|
|
@@ -202,7 +223,14 @@ function unacknowledged(
|
|
|
202
223
|
return candidates.filter((candidate) => !distinct.includes(candidate))
|
|
203
224
|
}
|
|
204
225
|
|
|
205
|
-
const issue_file = {
|
|
226
|
+
const issue_file = {
|
|
227
|
+
parse,
|
|
228
|
+
labels_of,
|
|
229
|
+
origin_problem,
|
|
230
|
+
triage_problem,
|
|
231
|
+
unacknowledged,
|
|
232
|
+
is_same_repository,
|
|
233
|
+
}
|
|
206
234
|
|
|
207
235
|
export type { FileArguments }
|
|
208
236
|
export { issue_file }
|
|
@@ -65,6 +65,12 @@ const NEEDS_HUMAN_REVIEW_LABEL = 'needs-human-review'
|
|
|
65
65
|
// takes it off, because taking it off asserts the work is *not* done, which is the same claim in
|
|
66
66
|
// reverse and is no more a run's to make.
|
|
67
67
|
const ALREADY_DONE_LABEL = 'already-done'
|
|
68
|
+
// Marks the Issue that tracks a repository's next release (joshuafolkken/kit#3360). An Issue whose
|
|
69
|
+
// change reaches a consumer only once published is linked to it as a `blocked_by` blocker
|
|
70
|
+
// (`josh issue:release`), so "merged but not yet released" is visible in the backlog. A run applies
|
|
71
|
+
// it; **`auto-ok` never rides on it** — `issue-auto-ok.ts` refuses the default for it on every path,
|
|
72
|
+
// since a release is Tier C — and only a person opts it into unattended execution.
|
|
73
|
+
const RELEASE_LABEL = 'release'
|
|
68
74
|
|
|
69
75
|
// The four labels that mean an open issue must not be handed to a run as it stands: an `epic`
|
|
70
76
|
// tracks a batch and is never run directly (its children are), `in-progress` is already claimed by
|
|
@@ -228,6 +234,11 @@ const WORKFLOW_LABELS: ReadonlyArray<LabelDefinition> = [
|
|
|
228
234
|
color: 'e99695',
|
|
229
235
|
description: 'Offered first in a backlogrun; only a person removes it',
|
|
230
236
|
},
|
|
237
|
+
{
|
|
238
|
+
name: RELEASE_LABEL,
|
|
239
|
+
color: 'fef2c0',
|
|
240
|
+
description: 'Tracks the next release; its blockers are the merged work it publishes',
|
|
241
|
+
},
|
|
231
242
|
...FILING_ROUTE_LABELS,
|
|
232
243
|
...DEPTH_LABELS,
|
|
233
244
|
]
|
|
@@ -340,6 +351,7 @@ const ALL_LABELS: ReadonlySet<string> = new Set([
|
|
|
340
351
|
NEEDS_HUMAN_REVIEW_LABEL,
|
|
341
352
|
ALREADY_DONE_LABEL,
|
|
342
353
|
PRIORITY_HIGH_LABEL,
|
|
354
|
+
RELEASE_LABEL,
|
|
343
355
|
INTERRUPT_ROUTE_LABEL,
|
|
344
356
|
REVIEW_CAP_ROUTE_LABEL,
|
|
345
357
|
REVIEW_ROUND2_SKIPPED_LABEL,
|
|
@@ -386,6 +398,7 @@ export {
|
|
|
386
398
|
PRIORITY_HIGH_LABEL,
|
|
387
399
|
RELEASE_CLASSIFICATION_LABELS,
|
|
388
400
|
RELEASE_CLASSIFICATION_NAMES,
|
|
401
|
+
RELEASE_LABEL,
|
|
389
402
|
REPOSITORY_LABELS,
|
|
390
403
|
REVIEW_CAP_ROUTE_LABEL,
|
|
391
404
|
REVIEW_ROUND2_SKIPPED_LABEL,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
4
|
+
import { git_gh_exec } from '#scripts/gh/git-gh-exec'
|
|
5
|
+
import { git_gh_issue_list, MAX_SCANNED } from '#scripts/gh/git-gh-issue-list'
|
|
6
|
+
import { git_gh_issue_write } from '#scripts/gh/git-gh-issue-write'
|
|
7
|
+
import { github_issue_url } from '#scripts/gh/github-issue-url'
|
|
8
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
9
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
10
|
+
import { repository_labels } from '#scripts/repo/repository-labels'
|
|
11
|
+
import { RELEASE_LABEL } from './issue-labels'
|
|
12
|
+
import { issue_release } from './issue-release'
|
|
13
|
+
|
|
14
|
+
// `josh issue:release <N>` — link Issue `<N>` to its repository's release Issue as a `blocked_by`
|
|
15
|
+
// blocker, filing the release Issue when none is open (joshuafolkken/kit#3360). `josh issue:file
|
|
16
|
+
// --release` calls the same `link` once its Issue exists, so a new and an existing Issue are linked
|
|
17
|
+
// by one body of code.
|
|
18
|
+
//
|
|
19
|
+
// The relation is written through `issue_add_blocked_by`, which addresses the repository `gh`
|
|
20
|
+
// resolves — the current one, or the target a cross-repository `issue:file` has pointed `GH_REPO` at.
|
|
21
|
+
|
|
22
|
+
const SUCCESS_EXIT_CODE = 0
|
|
23
|
+
const FAILURE_EXIT_CODE = 1
|
|
24
|
+
const ARGV_OFFSET = 2
|
|
25
|
+
const ISSUE_NUMBER_PATTERN = /^[1-9]\d*$/u
|
|
26
|
+
const USAGE = 'Usage: josh issue:release <issue-number>'
|
|
27
|
+
const NONE = 'none'
|
|
28
|
+
const UNKNOWN_REPO_MESSAGE =
|
|
29
|
+
'Could not read this repository from `git remote` — check `gh auth status`.'
|
|
30
|
+
|
|
31
|
+
interface ReleaseIssue {
|
|
32
|
+
issue_number: number
|
|
33
|
+
// Filed by this call, rather than found open.
|
|
34
|
+
is_filed: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// `undefined` when the listing is unreadable, `NONE` when it reads and names no open release Issue.
|
|
38
|
+
async function find_open(target: string): Promise<number | typeof NONE | undefined> {
|
|
39
|
+
const outcome = await git_gh_issue_list.issue_list({
|
|
40
|
+
json_fields: 'number',
|
|
41
|
+
limit: MAX_SCANNED,
|
|
42
|
+
label: RELEASE_LABEL,
|
|
43
|
+
repo: target,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (outcome.json === undefined) return undefined
|
|
47
|
+
|
|
48
|
+
return issue_release.open_release_of(outcome.json) ?? NONE
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The new release Issue's number, or `undefined` when the create call failed. Never `auto-ok`: the
|
|
52
|
+
// labels are `RELEASE_LABELS`, decided here rather than by `issue_auto_ok`, which refuses the label too.
|
|
53
|
+
async function file_release(target: string): Promise<number | undefined> {
|
|
54
|
+
repository_labels.ensure_labels(target)
|
|
55
|
+
const request = git_gh_issue_write.issue_create_request({
|
|
56
|
+
title: issue_release.title_of(target),
|
|
57
|
+
body: issue_release.body_of(target),
|
|
58
|
+
labels: issue_release.RELEASE_LABELS,
|
|
59
|
+
repo: target,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const url = await git_gh_exec.exec_gh_api(request)
|
|
64
|
+
const issue_number = Number(github_issue_url.parse(url)?.issue_number) || undefined
|
|
65
|
+
|
|
66
|
+
if (issue_number === undefined) console.error(`✖ the release number is unreadable: ${url}`)
|
|
67
|
+
|
|
68
|
+
return issue_number
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`✖ the release Issue could not be filed: ${error_text.message_of(error)}`)
|
|
71
|
+
|
|
72
|
+
return undefined
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// An unreadable listing files nothing: a second release Issue beside one the listing failed to show
|
|
77
|
+
// would split the blockers between two releases.
|
|
78
|
+
async function release_of(target: string): Promise<ReleaseIssue | undefined> {
|
|
79
|
+
const open = await find_open(target)
|
|
80
|
+
|
|
81
|
+
if (open === undefined) {
|
|
82
|
+
console.error(`✖ the open ${RELEASE_LABEL} Issues of ${target} could not be listed`)
|
|
83
|
+
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (open !== NONE) return { issue_number: open, is_filed: false }
|
|
88
|
+
const filed = await file_release(target)
|
|
89
|
+
|
|
90
|
+
return filed === undefined ? undefined : { issue_number: filed, is_filed: true }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function linked_line(release: ReleaseIssue, issue_number: number): string {
|
|
94
|
+
const how = release.is_filed ? 'filed' : 'open'
|
|
95
|
+
|
|
96
|
+
return `release: #${String(release.issue_number)} (${how}) is blocked by #${String(issue_number)}`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Whether Issue `issue_number` of `target` now blocks the release Issue. Every outcome is printed.
|
|
100
|
+
async function link(issue_number: number, target: string): Promise<boolean> {
|
|
101
|
+
const release = await release_of(target)
|
|
102
|
+
|
|
103
|
+
if (release === undefined) return false
|
|
104
|
+
const blocked = String(release.issue_number)
|
|
105
|
+
const is_linked = await git_gh_issue_write.issue_add_blocked_by(blocked, String(issue_number))
|
|
106
|
+
|
|
107
|
+
if (is_linked) console.info(linked_line(release, issue_number))
|
|
108
|
+
else console.error(`✖ #${String(issue_number)} could not be recorded as a blocker of #${blocked}`)
|
|
109
|
+
|
|
110
|
+
return is_linked
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// An unknown flag parses to no positionals, so it refuses the call like a missing number.
|
|
114
|
+
function positionals_of(argv: ReadonlyArray<string>): ReadonlyArray<string> {
|
|
115
|
+
return cli_flags.arguments_of(argv, {})?.positionals ?? []
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The number is the one positional: a missing, malformed or second one refuses the whole call.
|
|
119
|
+
function issue_number_of(argv: ReadonlyArray<string>): number | undefined {
|
|
120
|
+
const [issue_number, ...rest] = positionals_of(argv)
|
|
121
|
+
const is_single = rest.length === 0 && issue_number !== undefined
|
|
122
|
+
|
|
123
|
+
return is_single && ISSUE_NUMBER_PATTERN.test(issue_number) ? Number(issue_number) : undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
127
|
+
const issue_number = issue_number_of(argv)
|
|
128
|
+
|
|
129
|
+
if (issue_number === undefined) {
|
|
130
|
+
console.error(USAGE)
|
|
131
|
+
|
|
132
|
+
return FAILURE_EXIT_CODE
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const current = await git_gh_command.repo_get_name_with_owner()
|
|
136
|
+
|
|
137
|
+
if (current === undefined) {
|
|
138
|
+
console.error(UNKNOWN_REPO_MESSAGE)
|
|
139
|
+
|
|
140
|
+
return FAILURE_EXIT_CODE
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return (await link(issue_number, current)) ? SUCCESS_EXIT_CODE : FAILURE_EXIT_CODE
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// `process.exitCode` rather than `process.exit()`, so the report on standard output drains first.
|
|
147
|
+
async function main(argv: ReadonlyArray<string>): Promise<void> {
|
|
148
|
+
process.exitCode = await run(argv)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const issue_release_cli = { link, run }
|
|
152
|
+
|
|
153
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
|
|
154
|
+
|
|
155
|
+
export { issue_release_cli }
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DEPTH_0_LABEL, RELEASE_LABEL } from './issue-labels'
|
|
2
|
+
|
|
3
|
+
// The decisions behind `josh issue:release` (joshuafolkken/kit#3360), kept apart from the command
|
|
4
|
+
// that reads the network so each is a pure answer a test can pin. An Issue whose change reaches a
|
|
5
|
+
// consumer only once published used to surface the release only at the end of `followup`'s report;
|
|
6
|
+
// it is now linked to the repository's release Issue, which it blocks until it merges.
|
|
7
|
+
|
|
8
|
+
interface ListingRow {
|
|
9
|
+
number: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function is_listing_row(value: unknown): value is ListingRow {
|
|
13
|
+
if (typeof value !== 'object' || value === null) return false
|
|
14
|
+
|
|
15
|
+
return Number.isSafeInteger(Reflect.get(value, 'number'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// The open release Issue a listing of `release`-labelled Issues names, or `undefined` when it names
|
|
19
|
+
// none or is unreadable. One release publishes every merge since the last, so a repository holds one
|
|
20
|
+
// release Issue at a time; were a person to open a second, the oldest is the one the next release
|
|
21
|
+
// closes, so it is the one every new blocker joins.
|
|
22
|
+
function open_release_of(json: string): number | undefined {
|
|
23
|
+
try {
|
|
24
|
+
const parsed: unknown = JSON.parse(json)
|
|
25
|
+
const rows = Array.isArray(parsed) ? parsed.filter((row) => is_listing_row(row)) : []
|
|
26
|
+
|
|
27
|
+
return rows.length === 0 ? undefined : Math.min(...rows.map((row) => row.number))
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function title_of(repo: string): string {
|
|
34
|
+
return `Release ${repo} (next version)`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Plain English with no classification declaration: the Issue is not filed through `issue:file`, and
|
|
38
|
+
// its labels are `RELEASE_LABELS` rather than read off the body.
|
|
39
|
+
function body_of(repo: string): string {
|
|
40
|
+
return [
|
|
41
|
+
`Tracks the next release of ${repo}.`,
|
|
42
|
+
'',
|
|
43
|
+
'Every Issue this one is blocked by changes something a consumer can use only once it is published. Once they have merged, a person runs `pnpm josh release` in the primary checkout.',
|
|
44
|
+
'',
|
|
45
|
+
'Close this Issue once that release is published: the next blocker files a new one, so an open release Issue names only unreleased work.',
|
|
46
|
+
'',
|
|
47
|
+
'`auto-ok` is never applied to this Issue by a run; a person may apply it.',
|
|
48
|
+
'',
|
|
49
|
+
].join('\n')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The release label and a depth: the subject of a release is what a consumer touches.
|
|
53
|
+
// Never `auto-ok`.
|
|
54
|
+
const RELEASE_LABELS: ReadonlyArray<string> = [RELEASE_LABEL, DEPTH_0_LABEL]
|
|
55
|
+
|
|
56
|
+
const issue_release = { RELEASE_LABELS, open_release_of, title_of, body_of }
|
|
57
|
+
|
|
58
|
+
export { issue_release }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
2
|
+
import { issue_label_schema } from '#scripts/git/git-schemas'
|
|
2
3
|
import { PAGE_CEILING_CAUSE } from '#scripts/git/listing-cutoff'
|
|
3
4
|
import { parse_json_array_or_undefined } from '#scripts/git/parse-json-array'
|
|
4
|
-
import { issue_label_schema } from '#scripts/git/schemas'
|
|
5
5
|
import { z } from 'zod'
|
|
6
6
|
import { EPIC_LABEL, has_any_label } from './issue-labels'
|
|
7
7
|
import type { ScoutIssue } from './issue-scout'
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
4
4
|
import { bounded_pool } from '#scripts/lib/bounded-pool'
|
|
5
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
6
|
import { issue_report_failures, type ReadFailureKind } from './issue-report-failures'
|
|
6
7
|
import { issue_state, type IssueState } from './issue-state'
|
|
7
8
|
|
|
@@ -28,9 +29,6 @@ import { issue_state, type IssueState } from './issue-state'
|
|
|
28
29
|
|
|
29
30
|
const FAILURE_EXIT_CODE = 1
|
|
30
31
|
const ARGV_OFFSET = 2
|
|
31
|
-
const REPO_FLAG = '--repo'
|
|
32
|
-
const FLAG_PREFIX = '-'
|
|
33
|
-
const INLINE_REPO_FLAG = `${REPO_FLAG}=`
|
|
34
32
|
const ISSUE_NUMBER_PATTERN = /^[1-9]\d*$/u
|
|
35
33
|
const USAGE = 'Usage: josh issue:state <issue-number> [<issue-number> ...] [--repo <owner/repo>]'
|
|
36
34
|
// A blank line between the blocks of a multi-number report, so a person sees where one issue ends
|
|
@@ -60,85 +58,43 @@ interface IssueReport {
|
|
|
60
58
|
result: StateRead
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
function read_inline_repo(argv: ReadonlyArray<string>): RepoFlag | undefined {
|
|
79
|
-
const inline = argv.find((argument) => argument.startsWith(INLINE_REPO_FLAG))
|
|
80
|
-
|
|
81
|
-
if (inline === undefined) return undefined
|
|
82
|
-
|
|
83
|
-
const repo = inline.slice(INLINE_REPO_FLAG.length)
|
|
84
|
-
|
|
85
|
-
if (repo.length === 0) return { kind: 'incomplete' }
|
|
86
|
-
|
|
87
|
-
return { kind: 'named', repo, consumed: [argv.indexOf(inline)] }
|
|
61
|
+
// The read goes through `cli_flags`, both spellings gh itself accepts included (`--repo owner/repo`,
|
|
62
|
+
// `--repo=owner/repo`), and is strict (joshuafolkken/kit#3261). An unrecognized flag refuses the
|
|
63
|
+
// call: `--rep=owner/repo` used to be discarded for starting with a dash, so the call fell back to
|
|
64
|
+
// the session's repository and printed a confident state for a *different* repository's issue of
|
|
65
|
+
// that number (joshuafolkken/kit#1355).
|
|
66
|
+
const OPTIONS = { repo: { type: 'string', multiple: true } } as const
|
|
67
|
+
|
|
68
|
+
// `absent` and "given but with nothing usable" are different answers. Falling back to the session's
|
|
69
|
+
// repository on the second — an empty value, or `--repo` given twice — would print a confident state
|
|
70
|
+
// for a *different* issue of the same number, which is the exact misread `--repo` exists to prevent.
|
|
71
|
+
function read_repo(given: ReadonlyArray<string> | undefined): { repo?: string } | undefined {
|
|
72
|
+
if (given === undefined) return {}
|
|
73
|
+
const [repo, ...rest] = given
|
|
74
|
+
|
|
75
|
+
return repo !== undefined && rest.length === 0 && repo.length > 0 ? { repo } : undefined
|
|
88
76
|
}
|
|
89
77
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (repo === undefined || repo.startsWith(FLAG_PREFIX)) return { kind: 'incomplete' }
|
|
99
|
-
|
|
100
|
-
return { kind: 'named', repo, consumed: [index, value_index] }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function read_repo_flag(argv: ReadonlyArray<string>): RepoFlag {
|
|
104
|
-
return read_inline_repo(argv) ?? read_separate_repo(argv)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// The argv positions the repository flag occupied, and nothing else. Everything outside this set was
|
|
108
|
-
// offered as an issue number, whatever it looks like — so a flag nobody recognizes reaches the check
|
|
109
|
-
// below and refuses the call instead of vanishing from it (joshuafolkken/kit#1355).
|
|
110
|
-
function consumed_indices(repo_flag: RepoFlag): ReadonlySet<number> {
|
|
111
|
-
return new Set(repo_flag.kind === 'named' ? repo_flag.consumed : [])
|
|
78
|
+
// A token that is not a number refuses the whole invocation rather than being dropped. Dropping it
|
|
79
|
+
// answers fewer numbers than were asked for and still exits zero — and with one number left, the
|
|
80
|
+
// surviving block prints in the single-number shape, so nothing in the output says a number went
|
|
81
|
+
// unanswered. `#1262` copied out of a `diag` table is exactly that token.
|
|
82
|
+
function is_issue_numbers(positionals: ReadonlyArray<string>): boolean {
|
|
83
|
+
return (
|
|
84
|
+
positionals.length > 0 && positionals.every((argument) => ISSUE_NUMBER_PATTERN.test(argument))
|
|
85
|
+
)
|
|
112
86
|
}
|
|
113
87
|
|
|
114
88
|
function parse_request(argv: ReadonlyArray<string>): StateRequest | undefined {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// issue number — and nothing else is removed here. Every remaining token is one the caller offered
|
|
121
|
-
// as an issue number, whatever it looks like.
|
|
122
|
-
const consumed = consumed_indices(repo_flag)
|
|
123
|
-
const rest = argv.filter((_argument, index) => !consumed.has(index))
|
|
124
|
-
const numbers = rest.filter((argument) => ISSUE_NUMBER_PATTERN.test(argument))
|
|
125
|
-
|
|
126
|
-
// A token that is not a number refuses the whole invocation rather than being dropped. Dropping
|
|
127
|
-
// it answers fewer numbers than were asked for and still exits zero — and with one number left,
|
|
128
|
-
// the surviving block prints in the single-number shape, so nothing in the output says a number
|
|
129
|
-
// went unanswered. `#1262` copied out of a `diag` table is exactly that token.
|
|
130
|
-
//
|
|
131
|
-
// An unrecognized flag reaches this same check rather than a lenient one of its own
|
|
132
|
-
// (joshuafolkken/kit#1355). `--rep=owner/repo` used to be discarded for starting with a dash, so
|
|
133
|
-
// the call fell back to the session's repository and printed a confident state for a *different*
|
|
134
|
-
// repository's issue of that number — the misread `incomplete` already exists to prevent.
|
|
135
|
-
if (numbers.length === 0 || numbers.length !== rest.length) return undefined
|
|
89
|
+
const parsed = cli_flags.arguments_of(argv, OPTIONS)
|
|
90
|
+
if (parsed === undefined) return undefined
|
|
91
|
+
const repo = read_repo(parsed.values.repo)
|
|
92
|
+
|
|
93
|
+
if (repo === undefined || !is_issue_numbers(parsed.positionals)) return undefined
|
|
136
94
|
|
|
137
95
|
// In the order they were typed, with a repeat dropped: repeating a number would spend a second
|
|
138
96
|
// read on an answer already in hand, and print a second block a caller counting rows counts twice.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return repo_flag.kind === 'named' ? { issue_numbers, repo: repo_flag.repo } : { issue_numbers }
|
|
97
|
+
return { issue_numbers: [...new Set(parsed.positionals)], ...repo }
|
|
142
98
|
}
|
|
143
99
|
|
|
144
100
|
// One number's read, reduced to what the report needs and nothing printed yet. Separating the two
|