@joshuafolkken/kit 1.1166.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 +9 -5
- package/.claude/skills/workflow-commands/backlogrun-park.md +29 -9
- 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 +24 -15
- 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 +10 -5
- 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 +47 -33
- 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-DYAVAM6S.js → chunk-55HHG5PX.js} +1405 -631
- package/dist/hooks/{chunk-A6P7PN7P.js → chunk-5VMKCCC4.js} +25 -10
- package/dist/hooks/chunk-5VNDSNG3.js +12 -0
- package/dist/hooks/{chunk-5KTCMCOT.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-ENA7P5FB.js → chunk-F56MFS4B.js} +3 -3
- package/dist/hooks/{chunk-ANJ2WGFD.js → chunk-FIVDHQGT.js} +7 -6
- package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
- package/dist/hooks/{chunk-GU5MATVE.js → chunk-JUDKDCDP.js} +499 -389
- package/dist/hooks/{chunk-ZDZ555YH.js → chunk-NV2JRZZO.js} +743 -909
- package/dist/hooks/{chunk-V2BTOZHT.js → chunk-RNWHN5BP.js} +3 -3
- package/dist/hooks/{chunk-URJA6XY4.js → chunk-TQNPDZNH.js} +4 -4
- package/dist/hooks/{chunk-GYNGP7HI.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 +232 -210
- 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 +81 -59
- package/dist/josh.js +872 -75
- 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 +31 -9
- package/scripts/backlog/backlog-offer-cli.ts +6 -6
- package/scripts/backlog/backlog-offer.ts +1 -1
- package/scripts/backlog/backlog-plan-cli.ts +7 -6
- package/scripts/backlog/backlog-pool.ts +57 -11
- package/scripts/backlog/backlog-rank.ts +18 -9
- package/scripts/backlog/backlog-ready.ts +1 -1
- package/scripts/backlog/backlog-restructure.ts +152 -0
- 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/backlog/backlog-waves.ts +15 -17
- 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/cost-runtime/context-cut-threshold.ts +1 -9
- package/scripts/cost-runtime/cost-verdict.ts +15 -28
- 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-followup-stages.ts +9 -0
- package/scripts/followup/git-pr-followup-checks.ts +142 -0
- package/scripts/followup/git-pr-followup.ts +5 -130
- 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-issue.ts +5 -3
- package/scripts/gh/git-gh-pr-read.ts +68 -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 +9 -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 +18 -6
- 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 +12 -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/{schemas.ts → git-schemas.ts} +2 -0
- 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/git/main-merge.ts +57 -21
- 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 +11 -1
- 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 +35 -3
- 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-notify.ts +26 -1
- 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 +3 -3
- 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} +12 -6
- 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} +3 -3
- package/scripts/run/{run-cut-report.ts → cut/run-cut-report.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} +29 -8
- 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-next.ts → ship/run-ship-next.ts} +13 -0
- package/scripts/run/ship/run-ship-pre-detach.ts +66 -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} +10 -6
- 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-stage.ts → ship/run-ship-stage.ts} +13 -0
- package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +15 -8
- package/scripts/run/ship/run-ship-stop-text.ts +13 -0
- package/scripts/run/ship/run-ship-sync.ts +128 -0
- package/scripts/run/{run-ship.ts → ship/run-ship.ts} +6 -0
- 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} +31 -9
- 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} +99 -17
- 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/test-red-logic.ts +17 -2
- package/scripts/test/test-red.ts +8 -7
- 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/time-runtime/time-transcript-line.ts +27 -1
- 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/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-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-review.ts → ship/run-ship-review.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
|
@@ -6,6 +6,7 @@ import { PLATFORM_TEMP_ROOT } from '#scripts/josh/platform-temporary'
|
|
|
6
6
|
import { process_identity } from '#scripts/josh/process-identity'
|
|
7
7
|
import { process_owner_schema } from '#scripts/josh/process-owner'
|
|
8
8
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
9
|
+
import { unit_worker_share } from '#scripts/test/unit-worker-share'
|
|
9
10
|
import { z } from 'zod'
|
|
10
11
|
|
|
11
12
|
// A machine-wide weighted core budget with admission control (joshuafolkken/kit#2351).
|
|
@@ -46,6 +47,26 @@ const WAIT_CAP_MS = 120_000
|
|
|
46
47
|
// The head of the ledger is always admitted, so the smallest budget worth planning against is one core.
|
|
47
48
|
const MIN_BUDGET = 1
|
|
48
49
|
|
|
50
|
+
// **The cores each heavy tool occupies, declared once for both places that reserve them**
|
|
51
|
+
// (joshuafolkken/kit#3345). `gate-plan.ts`'s checks and the `josh` commands that run the same tools
|
|
52
|
+
// directly read these, so a direct `josh lint` claims what the gate's lint claims. The lint, type check
|
|
53
|
+
// and spell check numbers are `gate-plan.ts`'s measured table, floored. The whole-tree eslint scan
|
|
54
|
+
// behind `josh lines` and `josh refactor:scan` runs in one process: measured cold on the 11-core
|
|
55
|
+
// machine on 2026-10-06, 113s of CPU over 98s of wall, so it gets one core.
|
|
56
|
+
const CORE_WEIGHTS = {
|
|
57
|
+
lint: 2,
|
|
58
|
+
type_check: 1,
|
|
59
|
+
spell_check: 1,
|
|
60
|
+
eslint_scan: 1,
|
|
61
|
+
} as const
|
|
62
|
+
|
|
63
|
+
// **The environment mark that says a parent already holds this process's cores**
|
|
64
|
+
// (joshuafolkken/kit#3345). `josh gate` reserves per check and then spawns `pnpm josh lint`. Without
|
|
65
|
+
// the mark, that child's own dispatch would claim a second place for the cores its parent already
|
|
66
|
+
// holds. Every holder sets it, and every child inherits it through the environment and claims nothing.
|
|
67
|
+
const HELD_KEY = 'JOSH_CORE_RESERVED'
|
|
68
|
+
const HELD_VALUE = '1'
|
|
69
|
+
|
|
49
70
|
const reservation_schema = process_owner_schema.extend({
|
|
50
71
|
weight: z.number(),
|
|
51
72
|
claimed_at: z.number(),
|
|
@@ -133,7 +154,7 @@ function live_reservations(directory: string = PLATFORM_TEMP_ROOT): Array<LiveRe
|
|
|
133
154
|
return marker_files(directory).flatMap((name) => {
|
|
134
155
|
const reservation = live_reservation(path.join(directory, name))
|
|
135
156
|
|
|
136
|
-
return reservation === undefined ? [] :
|
|
157
|
+
return reservation === undefined ? [] : { key: name, reservation }
|
|
137
158
|
})
|
|
138
159
|
}
|
|
139
160
|
|
|
@@ -272,14 +293,61 @@ async function with_core_reservation<T>(
|
|
|
272
293
|
}
|
|
273
294
|
}
|
|
274
295
|
|
|
296
|
+
function is_held(): boolean {
|
|
297
|
+
return process.env[HELD_KEY] === HELD_VALUE
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// A function of its own so the restoring write does not sit after an `await`, where
|
|
301
|
+
// `require-atomic-updates` reads any write to `process` as a possible race — the reason `josh.ts`
|
|
302
|
+
// gives `record_exit_code`.
|
|
303
|
+
function set_held_mark(value: string): void {
|
|
304
|
+
process.env[HELD_KEY] = value
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Marks this process, and every child it spawns, as running inside a held reservation. The previous
|
|
308
|
+
// value comes back afterwards, so a nested holder never clears the mark its outer holder set.
|
|
309
|
+
async function with_held_mark<T>(run: () => Promise<T>): Promise<T> {
|
|
310
|
+
const previous = process.env[HELD_KEY] ?? ''
|
|
311
|
+
|
|
312
|
+
set_held_mark(HELD_VALUE)
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
return await run()
|
|
316
|
+
} finally {
|
|
317
|
+
set_held_mark(previous)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// **A command's reservation: the gate's claim, unless a parent already holds the cores**
|
|
322
|
+
// (joshuafolkken/kit#3345). `josh-logic.ts` calls this around every command that declares a weight.
|
|
323
|
+
// Each command then only declares its weight, and no command carries a copy of the claim, run and
|
|
324
|
+
// release steps. A solo run is admitted on the first read, so a run with room on the machine starts
|
|
325
|
+
// with no wait. A command inside a unit suite claims nothing either, for the reason `reserved-run.ts`
|
|
326
|
+
// gives: the suite's own reservation already covers it, and a place claimed there would wait on it.
|
|
327
|
+
async function with_command_reservation<T>(
|
|
328
|
+
weight: number,
|
|
329
|
+
run: () => Promise<T>,
|
|
330
|
+
options: ReserveOptions = {},
|
|
331
|
+
): Promise<T> {
|
|
332
|
+
if (is_held() || unit_worker_share.is_nested_run()) return await run()
|
|
333
|
+
|
|
334
|
+
return await with_core_reservation(weight, async () => await with_held_mark(run), options)
|
|
335
|
+
}
|
|
336
|
+
|
|
275
337
|
const core_budget = {
|
|
338
|
+
CORE_WEIGHTS,
|
|
339
|
+
HELD_KEY,
|
|
340
|
+
HELD_VALUE,
|
|
276
341
|
RESERVED_PREFIX,
|
|
277
342
|
admitted_keys,
|
|
278
343
|
admitted_load,
|
|
344
|
+
is_held,
|
|
279
345
|
live_reservations,
|
|
280
346
|
release,
|
|
281
347
|
reserve,
|
|
348
|
+
with_command_reservation,
|
|
282
349
|
with_core_reservation,
|
|
350
|
+
with_held_mark,
|
|
283
351
|
}
|
|
284
352
|
|
|
285
353
|
export type { LiveReservation, Reservation, ReserveOptions }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { availableParallelism } from 'node:os'
|
|
2
2
|
import { josh_verdict } from '#scripts/josh/josh-verdict'
|
|
3
3
|
import { unit_worker_share } from '#scripts/test/unit-worker-share'
|
|
4
|
+
import { core_budget } from './core-budget'
|
|
4
5
|
|
|
5
6
|
// How many of the gate's checks run at once, and how wide the one elastic check may fan out —
|
|
6
7
|
// both derived from the machine rather than fixed at four (joshuafolkken/kit#1258).
|
|
@@ -61,15 +62,30 @@ const WARNING_CHECKER_LABELS: ReadonlyArray<string> = [LINT_LABEL, TYPE_CHECK_LA
|
|
|
61
62
|
// check rather than one appended after the unit suite is what keeps the plan's shape intact: the
|
|
62
63
|
// `--no-unit` set stays "the whole gate minus the unit suite", and a CI runner — which has no recorded
|
|
63
64
|
// transcripts — runs it as a trivial green rather than skipping it.
|
|
65
|
+
//
|
|
66
|
+
// **The three reserving weights are `core_budget.CORE_WEIGHTS`** (joshuafolkken/kit#3345), the same
|
|
67
|
+
// numbers the `josh lint`, `josh check` and `josh cspell:dot` commands claim when run directly.
|
|
68
|
+
//
|
|
69
|
+
// **The two zero weights were measured again for joshuafolkken/kit#3345, and they stay zero.** These are
|
|
70
|
+
// warm runs on the 11-core machine on 2026-10-06. `behavior` took 0.6s of wall for 0.7s of CPU, which
|
|
71
|
+
// is node starting up. `exports:unused` took 5.5s of wall for 9.0s of CPU, about 1.6 cores. Floored,
|
|
72
|
+
// that would be one core, but a reservation lasts the whole gate's plan, not the check's five seconds.
|
|
73
|
+
// It would lower `RESERVED_CORES` from 4 to 5 and cut the solo unit cap from 7 workers to 6 for the
|
|
74
|
+
// full length of the unit suite, to cover a burst that ends before lint does. Nothing has measured
|
|
75
|
+
// that trade, so the measured shape stays.
|
|
64
76
|
const STATIC_CHECKS: ReadonlyArray<GateCheck> = [
|
|
65
|
-
{ label: LINT_LABEL, target: 'lint', reserved_cores:
|
|
66
|
-
{
|
|
67
|
-
|
|
77
|
+
{ label: LINT_LABEL, target: 'lint', reserved_cores: core_budget.CORE_WEIGHTS.lint },
|
|
78
|
+
{
|
|
79
|
+
label: TYPE_CHECK_LABEL,
|
|
80
|
+
target: 'check',
|
|
81
|
+
reserved_cores: core_budget.CORE_WEIGHTS.type_check,
|
|
82
|
+
},
|
|
83
|
+
{ label: 'cspell', target: 'cspell:dot', reserved_cores: core_budget.CORE_WEIGHTS.spell_check },
|
|
68
84
|
{ label: BEHAVIOR_LABEL, target: BEHAVIOR_LABEL, reserved_cores: 0 },
|
|
69
85
|
// **The unused-member check reserves no core** (joshuafolkken/kit#2987): it builds one TypeScript
|
|
70
|
-
// program and walks it
|
|
71
|
-
//
|
|
72
|
-
//
|
|
86
|
+
// program and walks it for about 5s, and it is over well before lint and the type check. A
|
|
87
|
+
// reservation would only take a place from the four-core CI runner's fan-out and a worker from the
|
|
88
|
+
// unit suite for the whole gate.
|
|
73
89
|
{ label: 'exports', target: 'exports:unused', reserved_cores: 0 },
|
|
74
90
|
]
|
|
75
91
|
|
|
@@ -242,6 +258,11 @@ function resolve_unit_worker_cap(
|
|
|
242
258
|
return available_cores - RESERVED_CORES
|
|
243
259
|
}
|
|
244
260
|
|
|
261
|
+
// The unit suite's weight when nothing caps its pool: the cores left once the static checks have theirs.
|
|
262
|
+
function solo_unit_weight(available_cores: number): number {
|
|
263
|
+
return Math.max(0, available_cores - RESERVED_CORES)
|
|
264
|
+
}
|
|
265
|
+
|
|
245
266
|
// The cores a check reserves from the machine-wide budget while it runs (joshuafolkken/kit#2351). The
|
|
246
267
|
// static checks declare their measured reservation; the unit suite declares the pool it will open,
|
|
247
268
|
// which `unit_worker_cap` has already sized against the runs in flight. **This is the same table, read
|
|
@@ -256,7 +277,25 @@ function resolve_unit_worker_cap(
|
|
|
256
277
|
function check_weight(check: GateCheck, plan: GatePlan, available_cores: number): number {
|
|
257
278
|
if (check.label !== UNIT_LABEL) return check.reserved_cores
|
|
258
279
|
|
|
259
|
-
return plan.unit_worker_cap ??
|
|
280
|
+
return plan.unit_worker_cap ?? solo_unit_weight(available_cores)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// The cores a unit run started directly reserves (joshuafolkken/kit#3345): the share it will pass to
|
|
284
|
+
// vitest while other runs are live, and otherwise the gate's own solo unit weight. **Not the whole
|
|
285
|
+
// machine**, though a lone vitest sizes its own pool: FIFO admission lets a claim of every core in only
|
|
286
|
+
// once the ledger is empty, so a one-file `josh test:related` would wait out every other lane's gate
|
|
287
|
+
// and hold every later claim behind it. One weight for the suite however it is started. **Floored at one
|
|
288
|
+
// worker**: on a machine the static checks fill, a four-core CI runner among them, the gate's solo
|
|
289
|
+
// weight is zero because its siblings hold every core — a direct run has no siblings, and vitest still
|
|
290
|
+
// occupies a core.
|
|
291
|
+
function direct_unit_weight(
|
|
292
|
+
available_cores: number = availableParallelism(),
|
|
293
|
+
live_runs: number = unit_worker_share.live_run_count(),
|
|
294
|
+
): number {
|
|
295
|
+
const weight =
|
|
296
|
+
unit_worker_share.current_share(available_cores, live_runs) ?? solo_unit_weight(available_cores)
|
|
297
|
+
|
|
298
|
+
return Math.max(unit_worker_share.MIN_WORKERS, weight)
|
|
260
299
|
}
|
|
261
300
|
|
|
262
301
|
// `availableParallelism()` rather than `cpus().length`: it reports what this process may actually
|
|
@@ -345,6 +384,7 @@ const gate_plan = {
|
|
|
345
384
|
UNIT_LABEL,
|
|
346
385
|
WARNING_CHECKER_LABELS,
|
|
347
386
|
check_weight,
|
|
387
|
+
direct_unit_weight,
|
|
348
388
|
format_gate_plan,
|
|
349
389
|
format_machine,
|
|
350
390
|
has_unit_check,
|
|
@@ -60,18 +60,42 @@ const FORCE_FLAG = '--force'
|
|
|
60
60
|
//
|
|
61
61
|
// A `base` that could not be read is `undefined`, and an older record carries none: both compare
|
|
62
62
|
// unequal to anything, so the gate runs its checks. No base means no reuse, never reuse without one.
|
|
63
|
-
|
|
63
|
+
//
|
|
64
|
+
// **A refusal carries its reason** (joshuafolkken/kit#3307): a pre-push hook that fell back to the
|
|
65
|
+
// whole unit suite said nothing about why, so a merge base moved by ship's main sync and a lock file
|
|
66
|
+
// rewritten by the hook's own install each cost minutes before anyone could tell them apart.
|
|
67
|
+
type GateReuse = { stamp: FileMapStamp } | { miss: string }
|
|
68
|
+
|
|
69
|
+
function base_moved(recorded: string | undefined, base: string): string {
|
|
70
|
+
return `the merge base moved since the green gate (${recorded ?? 'none'} → ${base})`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function gate_reuse(
|
|
64
74
|
tree: Record<string, string>,
|
|
65
75
|
base: string | undefined,
|
|
66
76
|
source?: string,
|
|
67
|
-
):
|
|
68
|
-
if (base === undefined
|
|
77
|
+
): GateReuse {
|
|
78
|
+
if (base === undefined) return { miss: 'the merge base could not be read' }
|
|
79
|
+
if (Object.keys(tree).length === 0) return { miss: 'no file differs from the merge base' }
|
|
69
80
|
|
|
70
81
|
const stamp = review_brief.matching_stamp(review_stamps.gate_stamp.read(source), tree)
|
|
71
82
|
|
|
72
|
-
if (stamp
|
|
83
|
+
if (stamp === undefined) return { miss: 'no green gate record matches the changed files' }
|
|
84
|
+
if (stamp.base !== base) return { miss: base_moved(stamp.base, base) }
|
|
85
|
+
|
|
86
|
+
return { stamp }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function stamp_of(reuse: GateReuse): FileMapStamp | undefined {
|
|
90
|
+
return 'stamp' in reuse ? reuse.stamp : undefined
|
|
91
|
+
}
|
|
73
92
|
|
|
74
|
-
|
|
93
|
+
function reusable_green_gate(
|
|
94
|
+
tree: Record<string, string>,
|
|
95
|
+
base: string | undefined,
|
|
96
|
+
source?: string,
|
|
97
|
+
): FileMapStamp | undefined {
|
|
98
|
+
return stamp_of(gate_reuse(tree, base, source))
|
|
75
99
|
}
|
|
76
100
|
|
|
77
101
|
// **The sentence claims the result, and never merely the omission.** "verification skipped" on its
|
|
@@ -116,8 +140,15 @@ function format_skip(taken_at: string): string {
|
|
|
116
140
|
})
|
|
117
141
|
}
|
|
118
142
|
|
|
119
|
-
const gate_skip = {
|
|
143
|
+
const gate_skip = {
|
|
144
|
+
FORCE_FLAG,
|
|
145
|
+
format_reuse_notice,
|
|
146
|
+
format_skip,
|
|
147
|
+
gate_reuse,
|
|
148
|
+
reusable_green_gate,
|
|
149
|
+
stamp_of,
|
|
150
|
+
}
|
|
120
151
|
|
|
121
|
-
export type { ReuseNotice }
|
|
152
|
+
export type { GateReuse, ReuseNotice }
|
|
122
153
|
|
|
123
154
|
export { gate_skip }
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { git_command } from '#scripts/git/git-command'
|
|
2
|
-
import type { FileMapStamp } from '#scripts/josh/file-map-stamp'
|
|
3
2
|
import { observation_ledger } from '#scripts/observations/observation-ledger'
|
|
4
|
-
import { gate_skip } from './gate-skip'
|
|
3
|
+
import { gate_skip, type GateReuse } from './gate-skip'
|
|
5
4
|
import type { GateTree } from './gate-tree'
|
|
6
5
|
|
|
7
6
|
// What the two git hooks share when they decline to re-run a check `pnpm josh gate` already passed on
|
|
@@ -94,6 +93,8 @@ interface HookReuse {
|
|
|
94
93
|
// The hook-specific narrowing: whether what this git operation carries is the tree the record
|
|
95
94
|
// describes. `is_worktree_clean` for a push, `is_index_matching_worktree` for a commit.
|
|
96
95
|
is_tree_carried: boolean
|
|
96
|
+
// The reason a refusal on that narrowing reports, in the hook's own words (joshuafolkken/kit#3307).
|
|
97
|
+
carry_miss: string
|
|
97
98
|
extra_arguments: ReadonlyArray<string>
|
|
98
99
|
force_env: string
|
|
99
100
|
// The record to read, so a test can plant one without overwriting the record a live run relies on.
|
|
@@ -102,27 +103,28 @@ interface HookReuse {
|
|
|
102
103
|
source?: string | undefined
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
// The stamp rather than a boolean, for the same reason `gate_skip.
|
|
106
|
-
//
|
|
107
|
-
// printing.
|
|
106
|
+
// The stamp rather than a boolean, for the same reason `gate_skip.gate_reuse` hands one back: the
|
|
107
|
+
// caller prints `taken_at`, and a record that does not describe this tree has no timestamp worth
|
|
108
|
+
// printing. A refusal carries its reason instead (joshuafolkken/kit#3307).
|
|
108
109
|
//
|
|
109
110
|
// **Any argument at all refuses the reuse**, not only the force flag. A caller who narrowed the run
|
|
110
111
|
// asked for that run rather than for a recorded result about a whole tree, and a hook's own line passes
|
|
111
112
|
// none, so this costs it nothing.
|
|
112
|
-
function
|
|
113
|
-
if (input.extra_arguments.length > 0
|
|
114
|
-
if (
|
|
113
|
+
function hook_reuse(input: HookReuse): GateReuse {
|
|
114
|
+
if (input.extra_arguments.length > 0) return { miss: 'arguments were passed to the hook' }
|
|
115
|
+
if (is_force_requested(input.force_env)) return { miss: `${input.force_env} is set` }
|
|
116
|
+
if (!input.is_tree_carried) return { miss: input.carry_miss }
|
|
115
117
|
|
|
116
|
-
return gate_skip.
|
|
118
|
+
return gate_skip.gate_reuse(input.tree.files, input.tree.base, input.source)
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
const hook_gate_reuse = {
|
|
120
122
|
GATE_REUSING_TARGETS,
|
|
123
|
+
hook_reuse,
|
|
121
124
|
is_force_requested,
|
|
122
125
|
is_index_matching_worktree,
|
|
123
126
|
is_worktree_clean,
|
|
124
127
|
read_status_lines,
|
|
125
|
-
reusable_green_hook,
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
export type { HookReuse }
|
|
@@ -96,14 +96,17 @@ async function reusable_green_commit(
|
|
|
96
96
|
source?: string,
|
|
97
97
|
start_directory: string = process.cwd(),
|
|
98
98
|
): Promise<FileMapStamp | undefined> {
|
|
99
|
-
const
|
|
99
|
+
const reuse = hook_gate_reuse.hook_reuse({
|
|
100
100
|
tree,
|
|
101
101
|
is_tree_carried: tree.is_index_matching_worktree,
|
|
102
|
+
carry_miss: 'the index differs from the working tree',
|
|
102
103
|
extra_arguments,
|
|
103
104
|
force_env: FORCE_ENV,
|
|
104
105
|
source,
|
|
105
106
|
})
|
|
106
107
|
|
|
108
|
+
const reusable = gate_skip.stamp_of(reuse)
|
|
109
|
+
|
|
107
110
|
if (reusable === undefined) return undefined
|
|
108
111
|
|
|
109
112
|
return (await is_recorded_check_this_check(start_directory)) ? reusable : undefined
|
|
@@ -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 }
|
|
@@ -26,8 +26,10 @@ const SUMMARY_FIELDS = 'number,title,labels,createdAt'
|
|
|
26
26
|
// `SUMMARY_FIELDS` on purpose: a listing carries no `blockedBy`, so the field is answered from each
|
|
27
27
|
// row's own dependencies endpoint, and asking for it on the shared list would put that request
|
|
28
28
|
// behind the next-issues display too — where a refused one becomes a failure with no message at all,
|
|
29
|
-
// since the listing swallows the error (joshuafolkken/kit#996, joshuafolkken/kit#1025).
|
|
30
|
-
|
|
29
|
+
// since the listing swallows the error (joshuafolkken/kit#996, joshuafolkken/kit#1025). `body` costs
|
|
30
|
+
// no request — the REST listing row already carries it — and is what the backlog offer reads for
|
|
31
|
+
// the restructures each issue declares (joshuafolkken/kit#3221).
|
|
32
|
+
const PICKUP_FIELDS = `${SUMMARY_FIELDS},blockedBy,body`
|
|
31
33
|
|
|
32
34
|
// Every listing below goes through the one REST invocation in `git-gh-issue-list.ts`. It kept the
|
|
33
35
|
// `json_fields` / `limit` contract it had as a `gh issue list` wrapper, so these callers changed
|
|
@@ -111,7 +113,7 @@ async function issue_list_by_label(label: string, limit: number): Promise<IssueL
|
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
// The same filter with the fields the `auto-ok` pickup needs (joshuafolkken/kit#906): it orders by
|
|
114
|
-
// `createdAt
|
|
116
|
+
// `createdAt`, re-checks `labels` client-side and reads each body for its declared restructures. A
|
|
115
117
|
// label that does not exist in the repository is not an error — the listing comes back empty, which
|
|
116
118
|
// is exactly "nobody has opted anything in".
|
|
117
119
|
// `json_fields` overrides the pickup's list. Its only caller is the probe that runs *after* a failed
|