@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { git_stash } from '#scripts/git/stash/git-stash'
|
|
2
|
+
import { lane_launch_cli, type LaunchOutcome } from '#scripts/lane/lane-launch-cli'
|
|
3
|
+
import { backlog_drive_owner } from './backlog-drive-owner'
|
|
4
|
+
|
|
5
|
+
// The driver's lane launch (joshuafolkken/kit#3272). The parent that ran `josh latest` in the primary
|
|
6
|
+
// checkout leaves its rewritten lock file in a stash under this message — `backlogrun-lanes.md` →
|
|
7
|
+
// "Once per repository, before the first lane opens" — and the lane that pops it carries the update
|
|
8
|
+
// into its pull request. **The stash itself is the "first lane" marker**: each launch asks whether it
|
|
9
|
+
// is still on the stack, and the pop that `lane:launch --stash` runs consumes it, so the first launch
|
|
10
|
+
// after the push takes it and every later one finds nothing. A restarted driver needs no memory of
|
|
11
|
+
// which lane was first. Two entries under the message are a state a person resolves, so neither is
|
|
12
|
+
// guessed at — the launch goes ahead without one, as `stash:pop` would refuse it anyway. That skip is
|
|
13
|
+
// said on stderr, because `stash:pop` refusing would have been heard and a silent skip would let the
|
|
14
|
+
// update never reach a pull request.
|
|
15
|
+
const LATEST_STASH_MESSAGE = 'backlogrun: josh latest before lanes'
|
|
16
|
+
|
|
17
|
+
async function latest_stash(): Promise<string | undefined> {
|
|
18
|
+
const selection = git_stash.select(await git_stash.list(), LATEST_STASH_MESSAGE)
|
|
19
|
+
|
|
20
|
+
if (selection.kind === 'ambiguous') {
|
|
21
|
+
console.error(
|
|
22
|
+
`Launching without the "${LATEST_STASH_MESSAGE}" stash: ${selection.selectors.join(', ')} all match — keep one.`,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return selection.kind === 'match' ? LATEST_STASH_MESSAGE : undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function launch(issue: string, owner: string): Promise<LaunchOutcome['kind']> {
|
|
30
|
+
await backlog_drive_owner.assert_current(owner)
|
|
31
|
+
|
|
32
|
+
const outcome = await lane_launch_cli.launch_lane({ issue, stash: await latest_stash() })
|
|
33
|
+
|
|
34
|
+
return outcome.kind
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const backlog_drive_launch = { LATEST_STASH_MESSAGE, launch }
|
|
38
|
+
|
|
39
|
+
export { backlog_drive_launch }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { EPIC_LABEL, has_label_name, NEEDS_DECISION_LABEL } from '#scripts/issue/issue-labels'
|
|
2
2
|
import type { IssueState } from '#scripts/issue/issue-state'
|
|
3
3
|
import { issue_state_cli } from '#scripts/issue/issue-state-cli'
|
|
4
|
-
import type { RunCarry } from '#scripts/run/run-carry'
|
|
5
|
-
import type { MergeResult } from '#scripts/run/run-merge-cli'
|
|
4
|
+
import type { RunCarry } from '#scripts/run/carry/run-carry'
|
|
5
|
+
import type { MergeResult } from '#scripts/run/merge/run-merge-cli'
|
|
6
6
|
import { backlog_budget } from './backlog-budget'
|
|
7
7
|
import type { DriveState, OfferRead } from './backlog-drive'
|
|
8
8
|
import { backlog_drive_named } from './backlog-drive-named'
|
|
@@ -58,9 +58,7 @@ function settled_outcome(state: IssueState): MergeResult['outcome'] | undefined
|
|
|
58
58
|
async function classify(issue: string, named: OfferRead, owner: string): Promise<OfferRead> {
|
|
59
59
|
const result = await issue_state_cli.read_issue(issue)
|
|
60
60
|
|
|
61
|
-
if (result.kind !== 'state') {
|
|
62
|
-
return { verdict: 'issue-state', issues: [], retries: NO_RETRIES }
|
|
63
|
-
}
|
|
61
|
+
if (result.kind !== 'state') return { verdict: 'issue-state', issues: [], retries: NO_RETRIES }
|
|
64
62
|
|
|
65
63
|
const outcome = settled_outcome(result.state)
|
|
66
64
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { has_label_name, NEEDS_DECISION_LABEL } from '#scripts/issue/issue-labels'
|
|
2
2
|
import { issue_state_cli } from '#scripts/issue/issue-state-cli'
|
|
3
|
-
import { run_carry, type RunCarry } from '#scripts/run/run-carry'
|
|
4
|
-
import type { RunEvent } from '#scripts/run/run-event-stream'
|
|
5
|
-
import type { MergeResult } from '#scripts/run/run-merge-cli'
|
|
3
|
+
import { run_carry, type RunCarry } from '#scripts/run/carry/run-carry'
|
|
4
|
+
import type { RunEvent } from '#scripts/run/event/run-event-stream'
|
|
5
|
+
import type { MergeResult } from '#scripts/run/merge/run-merge-cli'
|
|
6
6
|
import type { DriveState, OfferRead } from './backlog-drive'
|
|
7
7
|
import { backlog_drive_restore } from './backlog-drive-restore'
|
|
8
8
|
import { backlog_named } from './backlog-named'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { doctor_consumer } from '#scripts/doctor/doctor-consumer'
|
|
2
2
|
import { find_package_directory } from '#scripts/josh/josh-logic'
|
|
3
|
-
import type { RunCarry } from '#scripts/run/run-carry'
|
|
3
|
+
import type { RunCarry } from '#scripts/run/carry/run-carry'
|
|
4
4
|
import { run_retrospective } from '#scripts/run/run-retrospective'
|
|
5
5
|
|
|
6
6
|
// Whether a drain owes the retrospective, read through the owed rule `run:step` reads, so a drain it
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// without a process, a network or a clock.
|
|
18
18
|
|
|
19
19
|
import type { LaunchOutcome } from '#scripts/lane/lane-launch-cli'
|
|
20
|
-
import type { MergeResult } from '#scripts/run/run-merge-cli'
|
|
20
|
+
import type { MergeResult } from '#scripts/run/merge/run-merge-cli'
|
|
21
21
|
|
|
22
22
|
const RUN_VERDICT = 'run'
|
|
23
23
|
const STOP_VERDICT = 'stop'
|
|
@@ -16,7 +16,8 @@ import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
|
16
16
|
import { issue_citation } from '#scripts/rules/issue-citation'
|
|
17
17
|
import { backlog_defect_priority } from './backlog-defect-priority'
|
|
18
18
|
import { backlog_pool } from './backlog-pool'
|
|
19
|
-
import { backlog_rank } from './backlog-rank'
|
|
19
|
+
import { backlog_rank, type GateScope } from './backlog-rank'
|
|
20
|
+
import { backlog_restructure } from './backlog-restructure'
|
|
20
21
|
|
|
21
22
|
// `josh backlog:next` — what the whole opted-in backlog may run next (joshuafolkken/kit#1630).
|
|
22
23
|
//
|
|
@@ -145,6 +146,19 @@ function report(result: EpicNextResult, context: PoolContext): number {
|
|
|
145
146
|
return SUCCESS_EXIT_CODE
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
// The settled set the views were classified against, so a standalone row and an epic child weigh the
|
|
150
|
+
// same blocker the same way.
|
|
151
|
+
function running_of(
|
|
152
|
+
views: ReadonlyArray<EpicView>,
|
|
153
|
+
graphs: ReadonlyArray<ReadonlyArray<EpicChild>>,
|
|
154
|
+
context: PoolContext,
|
|
155
|
+
): ReadonlySet<string> {
|
|
156
|
+
return (
|
|
157
|
+
views[0]?.running ??
|
|
158
|
+
backlog_pool.running_set(graphs, context.opted_in.issues, context.repo, context.tracking.index)
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
148
162
|
// The two halves as one result. The epic half is already classified by `epic:next`; the standalone
|
|
149
163
|
// half is classified here; the verdict is decided from the merge, so a backlog with a runnable epic
|
|
150
164
|
// child and nothing else says `run` exactly as one with a runnable standalone issue does.
|
|
@@ -160,10 +174,7 @@ function combine(views: ReadonlyArray<EpicView>, context: PoolContext): EpicNext
|
|
|
160
174
|
tracked: epic_index.withheld_children(context.tracking.index, context.opted_in.issues),
|
|
161
175
|
exclude: context.exclude,
|
|
162
176
|
repo: context.repo,
|
|
163
|
-
|
|
164
|
-
// the same blocker the same way.
|
|
165
|
-
running:
|
|
166
|
-
views[0]?.running ?? backlog_pool.running_set(graphs, context.opted_in.issues, context.repo),
|
|
177
|
+
running: running_of(views, graphs, context),
|
|
167
178
|
})
|
|
168
179
|
|
|
169
180
|
// The same checkout map `epic:next` hands `build_result`. Without it every bundle heading reads
|
|
@@ -193,6 +204,7 @@ function settled_views(
|
|
|
193
204
|
views[0]?.running ?? running,
|
|
194
205
|
context.opted_in.issues,
|
|
195
206
|
context.repo,
|
|
207
|
+
context.tracking.index,
|
|
196
208
|
)
|
|
197
209
|
|
|
198
210
|
return settled.size === running.size ? views : settled_views(reads, settled, context)
|
|
@@ -211,7 +223,7 @@ function views_from(reads: ReadonlyArray<EpicRead>, context: PoolContext): Reado
|
|
|
211
223
|
|
|
212
224
|
return settled_views(
|
|
213
225
|
reads,
|
|
214
|
-
backlog_pool.running_set(graphs, context.opted_in.issues, context.repo),
|
|
226
|
+
backlog_pool.running_set(graphs, context.opted_in.issues, context.repo, context.tracking.index),
|
|
215
227
|
context,
|
|
216
228
|
)
|
|
217
229
|
}
|
|
@@ -278,7 +290,7 @@ function report_retry(context: PoolContext): number {
|
|
|
278
290
|
return SUCCESS_EXIT_CODE
|
|
279
291
|
}
|
|
280
292
|
|
|
281
|
-
// The rows the offer's cap bounds — the standalone half
|
|
293
|
+
// The rows the offer's cap bounds — the standalone half.
|
|
282
294
|
function standalone_keys(context: PoolContext): ReadonlySet<string> {
|
|
283
295
|
return backlog_pool.standalone_keys(context.opted_in.issues, {
|
|
284
296
|
tracked: epic_index.withheld_children(context.tracking.index, context.opted_in.issues),
|
|
@@ -287,6 +299,16 @@ function standalone_keys(context: PoolContext): ReadonlySet<string> {
|
|
|
287
299
|
})
|
|
288
300
|
}
|
|
289
301
|
|
|
302
|
+
// The running lanes still carry the pickup label, so the opted-in listing holds their bodies too —
|
|
303
|
+
// which is what lets a candidate be held back from a restructure a running lane already makes
|
|
304
|
+
// (joshuafolkken/kit#3221). `backlog:plan --waves` reads the same scope, so its first wave is this offer.
|
|
305
|
+
function gate_scope(context: PoolContext): GateScope {
|
|
306
|
+
return {
|
|
307
|
+
standalone: standalone_keys(context),
|
|
308
|
+
declared: backlog_restructure.declared_of(context.opted_in.issues),
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
290
312
|
// The `run:solo` gate is applied here rather than in `resolve`, because `backlog:plan` shares
|
|
291
313
|
// `resolve` and a plan made before the run lists everything (joshuafolkken/kit#2776). The holders are
|
|
292
314
|
// read only on a `run` answer — the one answer the gate can change. The cap follows the gate
|
|
@@ -302,7 +324,7 @@ async function gate_solo(result: EpicNextResult, context: PoolContext): Promise<
|
|
|
302
324
|
|
|
303
325
|
if (notice !== undefined) console.error(notice)
|
|
304
326
|
|
|
305
|
-
const gated = backlog_rank.gate(result, read, repo,
|
|
327
|
+
const gated = backlog_rank.gate(result, read, repo, gate_scope(context))
|
|
306
328
|
|
|
307
329
|
if (gated.notice !== undefined) console.error(gated.notice)
|
|
308
330
|
|
|
@@ -418,7 +440,7 @@ const backlog_next = {
|
|
|
418
440
|
READ_FAILURES,
|
|
419
441
|
VERDICT_TOKENS,
|
|
420
442
|
resolve,
|
|
421
|
-
|
|
443
|
+
gate_scope,
|
|
422
444
|
context_of,
|
|
423
445
|
run,
|
|
424
446
|
}
|
|
@@ -3,8 +3,8 @@ import { fileURLToPath } from 'node:url'
|
|
|
3
3
|
import { josh_command } from '#scripts/josh/josh-run'
|
|
4
4
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
5
|
import { rule_value_cache } from '#scripts/rules/rule-value-cache'
|
|
6
|
-
import { run_event_stream } from '#scripts/run/run-event-stream'
|
|
7
|
-
import { run_event_stream_emit } from '#scripts/run/run-event-stream-emit'
|
|
6
|
+
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
7
|
+
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
8
8
|
import { z } from 'zod'
|
|
9
9
|
import { backlog_offer, type OfferAnswer } from './backlog-offer'
|
|
10
10
|
|
|
@@ -74,8 +74,8 @@ function to_count(raw: string | undefined): number | undefined {
|
|
|
74
74
|
|
|
75
75
|
// A count flag that was given but unreadable makes the invocation unreadable rather than defaulted —
|
|
76
76
|
// the contract `backlog:budget` follows for its own counts.
|
|
77
|
-
function count_of(raw: string | undefined
|
|
78
|
-
return raw === undefined ?
|
|
77
|
+
function count_of(raw: string | undefined): number | undefined {
|
|
78
|
+
return raw === undefined ? DEFAULT_COUNT : to_count(raw)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
interface OfferCounts {
|
|
@@ -86,8 +86,8 @@ interface OfferCounts {
|
|
|
86
86
|
// The two counts the mapping needs: `--running` decides `wait`, `--retries` decides `retry`. Either
|
|
87
87
|
// given but unreadable refuses the whole invocation.
|
|
88
88
|
function counts_of(values: ParsedValues): OfferCounts | undefined {
|
|
89
|
-
const running = count_of(cli_flags.string_of(values.running)
|
|
90
|
-
const retries = count_of(cli_flags.string_of(values.retries)
|
|
89
|
+
const running = count_of(cli_flags.string_of(values.running))
|
|
90
|
+
const retries = count_of(cli_flags.string_of(values.retries))
|
|
91
91
|
|
|
92
92
|
if (running === undefined || retries === undefined) return undefined
|
|
93
93
|
|
|
@@ -5,7 +5,7 @@ import { backlog_next } from './backlog-next'
|
|
|
5
5
|
// answered — its stdout tokens and its exit code — into the one word `backlog:budget` is then asked
|
|
6
6
|
// with, plus the issue numbers to start and the consecutive-retry count carried to the next ask.
|
|
7
7
|
//
|
|
8
|
-
// **The mapping is `docs/josh-commands-
|
|
8
|
+
// **The mapping is `docs/josh-commands-backlog.md` → "`josh backlog:offer`", held here so it cannot
|
|
9
9
|
// drift.** It fixes which `backlog:next` answer becomes which `backlog:budget` word, and it carries two
|
|
10
10
|
// context branches an agent once applied by hand every iteration: `wait` is `blocked` while this run
|
|
11
11
|
// has children in flight and `exhausted` when it has none, and `retry` is `blocked` until the third
|
|
@@ -4,12 +4,13 @@ import { auto_ok_cli } from '#scripts/auto-ok/auto-ok-cli'
|
|
|
4
4
|
import { epic_index } from '#scripts/epic/epic-index'
|
|
5
5
|
import type { EpicNextResult } from '#scripts/epic/epic-report'
|
|
6
6
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
7
|
+
import { open_issue_schema, type OpenIssueData } from '#scripts/git/git-schemas'
|
|
7
8
|
import { read_json_listing } from '#scripts/git/parse-json-array'
|
|
8
|
-
import { open_issue_schema, type OpenIssueData } from '#scripts/git/schemas'
|
|
9
9
|
import { run_invocation } from '#scripts/run/run-invocation'
|
|
10
10
|
import { backlog_named } from './backlog-named'
|
|
11
11
|
import { backlog_next, type OptedIn } from './backlog-next'
|
|
12
12
|
import { backlog_plan, type NamedPlan, type PlanContext } from './backlog-plan'
|
|
13
|
+
import type { GateScope } from './backlog-rank'
|
|
13
14
|
import { backlog_scope } from './backlog-scope'
|
|
14
15
|
import { backlog_waves } from './backlog-waves'
|
|
15
16
|
|
|
@@ -104,9 +105,9 @@ interface Plan {
|
|
|
104
105
|
// past the listing cap. Carrying the *whole* index here instead would move that misreport rather
|
|
105
106
|
// than remove it: the sentence would name an epic that is withholding nothing.
|
|
106
107
|
tracked: ReadonlyMap<number, number>
|
|
107
|
-
// The rows the offer's cap bounds, so `--waves` cuts each
|
|
108
|
-
// (joshuafolkken/kit#2928).
|
|
109
|
-
|
|
108
|
+
// The rows the offer's cap bounds and the paths each issue restructures, so `--waves` cuts each
|
|
109
|
+
// wave where `backlog:next` does (joshuafolkken/kit#2928, joshuafolkken/kit#3221).
|
|
110
|
+
scope: GateScope
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
// The listing plus whether it was cut. Carried together because the rows alone cannot say whether
|
|
@@ -141,7 +142,7 @@ async function classify(
|
|
|
141
142
|
repo: context.repo,
|
|
142
143
|
exclude,
|
|
143
144
|
tracked: epic_index.withheld_children(context.tracking.index, context.opted_in.issues),
|
|
144
|
-
|
|
145
|
+
scope: backlog_next.gate_scope(context),
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
@@ -172,7 +173,7 @@ function print_plan(plan: Plan, listing: OpenListing, view: PlanView): void {
|
|
|
172
173
|
const context = context_of(plan, listing)
|
|
173
174
|
|
|
174
175
|
if (view.is_waves) {
|
|
175
|
-
console.info(backlog_waves.format_waves(plan.result, context, plan.
|
|
176
|
+
console.info(backlog_waves.format_waves(plan.result, context, plan.scope))
|
|
176
177
|
|
|
177
178
|
return
|
|
178
179
|
}
|
|
@@ -5,7 +5,7 @@ import { epic_index } from '#scripts/epic/epic-index'
|
|
|
5
5
|
import { epic_issue } from '#scripts/epic/epic-issue'
|
|
6
6
|
import type { EpicView } from '#scripts/epic/epic-next-views'
|
|
7
7
|
import { epic_outside_blocker } from '#scripts/epic/epic-outside-blocker'
|
|
8
|
-
import type { OpenIssueData } from '#scripts/git/schemas'
|
|
8
|
+
import type { OpenIssueData } from '#scripts/git/git-schemas'
|
|
9
9
|
import { git_next_issues } from '#scripts/issue/git-next-issues'
|
|
10
10
|
import {
|
|
11
11
|
ALREADY_DONE_LABEL,
|
|
@@ -37,6 +37,9 @@ const DECISION_LABELS: ReadonlySet<string> = new Set([NEEDS_DECISION_LABEL, ALRE
|
|
|
37
37
|
// `tracked` is the narrowed set `epic_index.withheld_children` builds, not every child an epic
|
|
38
38
|
// tracks (joshuafolkken/kit#1668) — the narrowing itself is defined there, once, for this half and
|
|
39
39
|
// `auto-ok:next` alike.
|
|
40
|
+
// Child number to every open epic whose task list names it — `epic_index.build_tracking_index`.
|
|
41
|
+
type TrackingIndex = ReadonlyMap<number, ReadonlyArray<number>>
|
|
42
|
+
|
|
40
43
|
// The part of the context that decides which rows the standalone half considers at all.
|
|
41
44
|
type ConsideredContext = Pick<StandaloneContext, 'tracked' | 'exclude' | 'repo'>
|
|
42
45
|
|
|
@@ -69,24 +72,64 @@ function needs_person(issue: OpenIssueData, context: StandaloneContext): boolean
|
|
|
69
72
|
)
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
// Each open epic's key, read off the tracking index — child number to the epics listing it.
|
|
76
|
+
function epic_keys_of(tracking: TrackingIndex, repo: string): ReadonlySet<string> {
|
|
77
|
+
return new Set([...tracking.values()].flat().map((number) => epic_graph.key_of({ repo, number })))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The set with every epic that tracks a member added, nested epics included (joshuafolkken/kit#3289).
|
|
81
|
+
// An epic is a container rather than work, so it is never run itself — but it closes once its children
|
|
82
|
+
// do, so a row blocked by it waits on time exactly when one of those children is in this run.
|
|
83
|
+
function with_tracking_epics(
|
|
84
|
+
running: ReadonlySet<string>,
|
|
85
|
+
tracking: TrackingIndex,
|
|
86
|
+
repo: string,
|
|
87
|
+
): ReadonlySet<string> {
|
|
88
|
+
const tracking_epics = [...tracking]
|
|
89
|
+
.filter(([child]) => running.has(epic_graph.key_of({ repo, number: child })))
|
|
90
|
+
.flatMap(([, epics]) => epics.map((number) => epic_graph.key_of({ repo, number })))
|
|
91
|
+
const grown = new Set([...running, ...tracking_epics])
|
|
92
|
+
|
|
93
|
+
return grown.size === running.size ? running : with_tracking_epics(grown, tracking, repo)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The epics re-derived from the members alone, so an epic whose last running child was just taken out
|
|
97
|
+
// leaves with it rather than lingering from an earlier pass.
|
|
98
|
+
function rooted(
|
|
99
|
+
running: ReadonlySet<string>,
|
|
100
|
+
tracking: TrackingIndex,
|
|
101
|
+
repo: string,
|
|
102
|
+
): ReadonlySet<string> {
|
|
103
|
+
const epics = epic_keys_of(tracking, repo)
|
|
104
|
+
const members = new Set([...running].filter((key) => !epics.has(key)))
|
|
105
|
+
|
|
106
|
+
return with_tracking_epics(members, tracking, repo)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function is_stuck(issue: OpenIssueData, running: ReadonlySet<string>, repo: string): boolean {
|
|
110
|
+
return has_any_label(issue.labels, DECISION_LABELS) || waits_outside(issue, running, repo)
|
|
111
|
+
}
|
|
112
|
+
|
|
72
113
|
// The running set with every standalone row a person has to resolve taken out, repeated until a pass
|
|
73
114
|
// removes nothing — a row waiting on such a row waits on that person too, however long the chain.
|
|
74
115
|
function settle_standalone(
|
|
75
116
|
running: ReadonlySet<string>,
|
|
76
117
|
issues: ReadonlyArray<OpenIssueData>,
|
|
77
118
|
repo: string,
|
|
119
|
+
tracking: TrackingIndex,
|
|
78
120
|
): ReadonlySet<string> {
|
|
79
121
|
const settled = new Set(running)
|
|
80
122
|
|
|
81
|
-
// An epic row is never
|
|
123
|
+
// An epic row is never a standalone row, so deleting its key here never happens; `rooted` decides it.
|
|
82
124
|
for (const issue of issues) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (is_stuck) settled.delete(epic_graph.key_of({ repo, number: issue.number }))
|
|
125
|
+
if (is_stuck(issue, running, repo)) {
|
|
126
|
+
settled.delete(epic_graph.key_of({ repo, number: issue.number }))
|
|
127
|
+
}
|
|
87
128
|
}
|
|
88
129
|
|
|
89
|
-
|
|
130
|
+
const next = rooted(settled, tracking, repo)
|
|
131
|
+
|
|
132
|
+
return next.size === running.size ? next : settle_standalone(next, issues, repo, tracking)
|
|
90
133
|
}
|
|
91
134
|
|
|
92
135
|
// An opted-in row as the graph's node type. Both halves of the pool are then the same shape, so one
|
|
@@ -182,18 +225,21 @@ function classify_standalone(
|
|
|
182
225
|
// (joshuafolkken/kit#1943). A child is opted in through its epic's `auto-ok` even where it carries none
|
|
183
226
|
// of its own, which is why the set is built from the graphs that were read rather than from labels. A
|
|
184
227
|
// standalone row a person has to resolve is left out, so a row waiting on it waits for that person too;
|
|
185
|
-
// an epic child in the same state is taken out by `epic_next_views.settle_views`.
|
|
228
|
+
// an epic child in the same state is taken out by `epic_next_views.settle_views`. An open epic with a
|
|
229
|
+
// child in the set is in it too, so a row blocked by that epic waits rather than needs a person. A
|
|
230
|
+
// closed child is left out: it runs nothing, and kept in the set it would hold its epic there.
|
|
186
231
|
function running_set(
|
|
187
232
|
graphs: ReadonlyArray<ReadonlyArray<EpicChild>>,
|
|
188
233
|
issues: ReadonlyArray<OpenIssueData>,
|
|
189
234
|
repo: string,
|
|
235
|
+
tracking: TrackingIndex,
|
|
190
236
|
): ReadonlySet<string> {
|
|
191
237
|
const running = epic_outside_blocker.running_keys([
|
|
192
|
-
...graphs.flat(),
|
|
238
|
+
...graphs.flat().filter((child) => child.state === 'OPEN'),
|
|
193
239
|
...to_children(standalone_rows(issues), repo),
|
|
194
240
|
])
|
|
195
241
|
|
|
196
|
-
return settle_standalone(running, issues, repo)
|
|
242
|
+
return settle_standalone(rooted(running, tracking, repo), issues, repo, tracking)
|
|
197
243
|
}
|
|
198
244
|
|
|
199
245
|
// A cycle no single epic can see, because its links cross from one graph into another. Each epic's own
|
|
@@ -336,4 +382,4 @@ const backlog_pool = {
|
|
|
336
382
|
}
|
|
337
383
|
|
|
338
384
|
export { backlog_pool }
|
|
339
|
-
export type { ConsideredContext, StandaloneContext }
|
|
385
|
+
export type { ConsideredContext, StandaloneContext, TrackingIndex }
|
|
@@ -4,6 +4,7 @@ import { epic_rank } from '#scripts/epic/epic-rank'
|
|
|
4
4
|
import type { EpicNextResult } from '#scripts/epic/epic-report'
|
|
5
5
|
import { epic_solo, type SoloGate, type SoloSelection } from '#scripts/epic/epic-solo'
|
|
6
6
|
import { git_next_issues } from '#scripts/issue/git-next-issues'
|
|
7
|
+
import { backlog_restructure, type Declared } from './backlog-restructure'
|
|
7
8
|
|
|
8
9
|
// What the backlog offers, in the order it is decided (joshuafolkken/kit#2928): rank, then the
|
|
9
10
|
// `run:solo` gate, then the cap. `backlog:next` and `backlog:plan --waves` both call `select`, so the
|
|
@@ -78,29 +79,37 @@ interface OfferInput {
|
|
|
78
79
|
repo: string
|
|
79
80
|
// The keys of the standalone rows — the ones the cap bounds.
|
|
80
81
|
standalone: ReadonlySet<string>
|
|
82
|
+
// The paths each issue restructures; two candidates claiming one are not offered together
|
|
83
|
+
// (joshuafolkken/kit#3221). Absent, nothing is separated.
|
|
84
|
+
declared?: Declared
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
// The restructure separation runs before the cap, for the reason the gate does: the cap only ever
|
|
88
|
+
// removes what would otherwise be offered.
|
|
83
89
|
function select(input: OfferInput): SoloSelection {
|
|
84
90
|
const ranked = rank(input.candidates, input.pool)
|
|
91
|
+
const { read, repo } = input
|
|
92
|
+
const gated = epic_solo.select(ranked, read, repo)
|
|
93
|
+
const separated = backlog_restructure.separate(gated, input.declared ?? new Map(), { read, repo })
|
|
85
94
|
|
|
86
|
-
return cap(
|
|
95
|
+
return cap(separated, input.standalone)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface GateScope {
|
|
99
|
+
standalone: ReadonlySet<string>
|
|
100
|
+
declared: Declared
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
// `backlog:next`'s gate: `epic_solo.gate` with this selection in place of its bare one.
|
|
90
|
-
function gate(
|
|
91
|
-
result: EpicNextResult,
|
|
92
|
-
read: BusyRead,
|
|
93
|
-
repo: string,
|
|
94
|
-
standalone: ReadonlySet<string>,
|
|
95
|
-
): SoloGate {
|
|
104
|
+
function gate(result: EpicNextResult, read: BusyRead, repo: string, scope: GateScope): SoloGate {
|
|
96
105
|
const pool = everything_in(result)
|
|
97
106
|
|
|
98
107
|
return epic_solo.gate(result, read, repo, (candidates) =>
|
|
99
|
-
select({ candidates, pool, read, repo,
|
|
108
|
+
select({ candidates, pool, read, repo, ...scope }),
|
|
100
109
|
)
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
const backlog_rank = { everything_in, gate, rank_result, select }
|
|
104
113
|
|
|
105
114
|
export { backlog_rank }
|
|
106
|
-
export type { OfferInput }
|
|
115
|
+
export type { GateScope, OfferInput }
|
|
@@ -3,7 +3,7 @@ import { issue_cite } from '#scripts/issue/issue-cite'
|
|
|
3
3
|
import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
|
|
4
4
|
import { lane_capacity } from '#scripts/lane/lane-capacity'
|
|
5
5
|
import { lane_registry } from '#scripts/lane/lane-registry'
|
|
6
|
-
import type { RunCarry } from '#scripts/run/run-carry'
|
|
6
|
+
import type { RunCarry } from '#scripts/run/carry/run-carry'
|
|
7
7
|
import { run_headless } from '#scripts/run/run-headless'
|
|
8
8
|
import { run_invocation } from '#scripts/run/run-invocation'
|
|
9
9
|
import { backlog_stalled } from './backlog-stalled'
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { BusyRead } from '#scripts/epic/epic-busy'
|
|
2
|
+
import type { EpicChild } from '#scripts/epic/epic-graph'
|
|
3
|
+
import type { SoloSelection } from '#scripts/epic/epic-solo'
|
|
4
|
+
|
|
5
|
+
// Which candidates may not run side by side because both restructure the same file
|
|
6
|
+
// (joshuafolkken/kit#3221). The condition is deliberately narrow: plain overlap is not serialized,
|
|
7
|
+
// because every resume record of the 2026-10-05 backlog run was resolved mechanically by merging
|
|
8
|
+
// main. What git cannot resolve on its own is a rename or modify/delete conflict — two lanes that
|
|
9
|
+
// each move, split, rename or delete the same file — so only a path both bodies name on a line with
|
|
10
|
+
// such a verb holds the second one back. `remove` is left out: issue bodies use it for lines and
|
|
11
|
+
// options far more often than for files.
|
|
12
|
+
const RESTRUCTURE_WORDS: ReadonlySet<string> = new Set([
|
|
13
|
+
'move',
|
|
14
|
+
'moved',
|
|
15
|
+
'moves',
|
|
16
|
+
'moving',
|
|
17
|
+
'rename',
|
|
18
|
+
'renamed',
|
|
19
|
+
'renames',
|
|
20
|
+
'renaming',
|
|
21
|
+
'split',
|
|
22
|
+
'splits',
|
|
23
|
+
'splitting',
|
|
24
|
+
'delete',
|
|
25
|
+
'deleted',
|
|
26
|
+
'deletes',
|
|
27
|
+
'deleting',
|
|
28
|
+
])
|
|
29
|
+
const RESTRUCTURE_PHRASES: ReadonlyArray<string> = ['移動', '分割', '削除', 'リネーム', '改名']
|
|
30
|
+
const WORD = /[a-z]+/giu
|
|
31
|
+
const CODE_SPAN = /`([^`\s]+)`/gu
|
|
32
|
+
const LINE_SUFFIX = /:\d+(?::\d+)?$/u
|
|
33
|
+
const FILE_EXTENSION = /\.[a-z]\w*$/iu
|
|
34
|
+
|
|
35
|
+
// The restructured paths each issue declares, by issue number.
|
|
36
|
+
type Declared = ReadonlyMap<number, ReadonlySet<string>>
|
|
37
|
+
|
|
38
|
+
interface DeclaringIssue {
|
|
39
|
+
number: number
|
|
40
|
+
body?: string | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function is_path(token: string): boolean {
|
|
44
|
+
return token.includes('/') || FILE_EXTENSION.test(token)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function paths_on(line: string): Array<string> {
|
|
48
|
+
return [...line.matchAll(CODE_SPAN)]
|
|
49
|
+
.map((match) => (match[1] ?? '').replace(LINE_SUFFIX, ''))
|
|
50
|
+
.filter((token) => is_path(token))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function is_restructuring(line: string): boolean {
|
|
54
|
+
const words = line.match(WORD) ?? []
|
|
55
|
+
|
|
56
|
+
if (words.some((word) => RESTRUCTURE_WORDS.has(word.toLowerCase()))) return true
|
|
57
|
+
|
|
58
|
+
return RESTRUCTURE_PHRASES.some((phrase) => line.includes(phrase))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The backticked paths on the lines of `body` that carry a restructuring verb.
|
|
62
|
+
function restructured_paths(body: string): ReadonlySet<string> {
|
|
63
|
+
const lines = body.split('\n').filter((line) => is_restructuring(line))
|
|
64
|
+
|
|
65
|
+
return new Set(lines.flatMap((line) => paths_on(line)))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function declared_of(issues: ReadonlyArray<DeclaringIssue>): Declared {
|
|
69
|
+
return new Map(issues.map((issue) => [issue.number, restructured_paths(issue.body ?? '')]))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface SeparateContext {
|
|
73
|
+
read: BusyRead
|
|
74
|
+
repo: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The path each running lane restructures, mapped to the issue that holds it.
|
|
78
|
+
function running_claims(declared: Declared, read: BusyRead): Map<string, number> {
|
|
79
|
+
const holders = read.kind === 'busy' ? read.issues.map((issue) => issue.number) : []
|
|
80
|
+
const pairs = holders.flatMap((number) =>
|
|
81
|
+
[...(declared.get(number) ?? [])].map((path): [string, number] => [path, number]),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return new Map(pairs)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function paths_of(child: EpicChild, declared: Declared, repo: string): ReadonlySet<string> {
|
|
88
|
+
if (child.repo !== repo) return new Set()
|
|
89
|
+
|
|
90
|
+
return declared.get(child.number) ?? new Set()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function clash_note(child: EpicChild, path: string, holder: number): string {
|
|
94
|
+
return `#${String(child.number)} waits: it and #${String(holder)} both restructure \`${path}\`.`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface Walk {
|
|
98
|
+
claims: Map<string, number>
|
|
99
|
+
offered: Array<EpicChild>
|
|
100
|
+
withheld: Array<EpicChild>
|
|
101
|
+
notes: Array<string>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function visit(walk: Walk, child: EpicChild, paths: ReadonlySet<string>): void {
|
|
105
|
+
const clash = [...paths].find((path) => walk.claims.has(path))
|
|
106
|
+
|
|
107
|
+
if (clash === undefined) {
|
|
108
|
+
walk.offered.push(child)
|
|
109
|
+
for (const path of paths) walk.claims.set(path, child.number)
|
|
110
|
+
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
walk.withheld.push(child)
|
|
115
|
+
walk.notes.push(clash_note(child, clash, walk.claims.get(clash) ?? child.number))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function merged_notice(
|
|
119
|
+
notice: string | undefined,
|
|
120
|
+
notes: ReadonlyArray<string>,
|
|
121
|
+
): string | undefined {
|
|
122
|
+
const parts = notice === undefined ? notes : [notice, ...notes]
|
|
123
|
+
|
|
124
|
+
return parts.length === 0 ? undefined : parts.join('\n')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// `selection` with every offered candidate that restructures a path a running lane or an earlier
|
|
128
|
+
// candidate already restructures moved to `withheld`, in offer order.
|
|
129
|
+
function separate(
|
|
130
|
+
selection: SoloSelection,
|
|
131
|
+
declared: Declared,
|
|
132
|
+
context: SeparateContext,
|
|
133
|
+
): SoloSelection {
|
|
134
|
+
const walk: Walk = {
|
|
135
|
+
claims: running_claims(declared, context.read),
|
|
136
|
+
offered: [],
|
|
137
|
+
withheld: [],
|
|
138
|
+
notes: [],
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const child of selection.offered) visit(walk, child, paths_of(child, declared, context.repo))
|
|
142
|
+
|
|
143
|
+
const notice = merged_notice(selection.notice, walk.notes)
|
|
144
|
+
const withheld = [...walk.withheld, ...selection.withheld]
|
|
145
|
+
|
|
146
|
+
return { offered: walk.offered, withheld, ...(notice !== undefined && { notice }) }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const backlog_restructure = { declared_of, restructured_paths, separate }
|
|
150
|
+
|
|
151
|
+
export { backlog_restructure }
|
|
152
|
+
export type { Declared }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EpicNextResult } from '#scripts/epic/epic-report'
|
|
2
|
-
import type { OpenIssueData } from '#scripts/git/schemas'
|
|
2
|
+
import type { OpenIssueData } from '#scripts/git/git-schemas'
|
|
3
3
|
import { AUTO_OK_LABEL, EPIC_LABEL, has_any_label } from '#scripts/issue/issue-labels'
|
|
4
4
|
|
|
5
5
|
// Which open issues the backlog will not run, and why (joshuafolkken/kit#1652).
|