@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
|
@@ -4,6 +4,7 @@ import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
|
4
4
|
import { cutoff_of, type ScanCutoff } from '#scripts/git/listing-cutoff'
|
|
5
5
|
import { parse_json_array_or_undefined } from '#scripts/git/parse-json-array'
|
|
6
6
|
import { bounded_pool } from '#scripts/lib/bounded-pool'
|
|
7
|
+
import type { PollOptions } from '#scripts/lib/poll'
|
|
7
8
|
import { issue_citation } from '#scripts/rules/issue-citation'
|
|
8
9
|
import { z } from 'zod'
|
|
9
10
|
import {
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from './epic-bundle'
|
|
15
16
|
import { epic_bundle_evidence } from './epic-bundle-evidence'
|
|
16
17
|
import { epic_bundle_gaps } from './epic-bundle-gaps'
|
|
18
|
+
import { epic_bundle_poll } from './epic-bundle-poll'
|
|
17
19
|
import { epic_bundle_referenced, type ReferencedContext } from './epic-bundle-referenced'
|
|
18
20
|
import { epic_index, epic_schema, type FetchedEpics } from './epic-index'
|
|
19
21
|
import { epic_issue } from './epic-issue'
|
|
@@ -393,9 +395,18 @@ async function report_widened(
|
|
|
393
395
|
return report_decision(subject, widened, repo)
|
|
394
396
|
}
|
|
395
397
|
|
|
396
|
-
// The recommendation for one issue, from the open backlog around it.
|
|
397
|
-
|
|
398
|
-
|
|
398
|
+
// The recommendation for one issue, from the open backlog around it. `options` is the poll a caller
|
|
399
|
+
// holding a just-filed issue passes, since the listing trails the create call (`epic-bundle-poll.ts`).
|
|
400
|
+
async function report_for(
|
|
401
|
+
issue_number: number,
|
|
402
|
+
repo: string,
|
|
403
|
+
options?: PollOptions,
|
|
404
|
+
): Promise<number> {
|
|
405
|
+
const backlog = await epic_bundle_poll.read_until_listed(
|
|
406
|
+
async () => await read_backlog(repo),
|
|
407
|
+
issue_number,
|
|
408
|
+
options,
|
|
409
|
+
)
|
|
399
410
|
|
|
400
411
|
if (!backlog.is_readable) {
|
|
401
412
|
console.error(unreadable_backlog_message(backlog))
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { poll, type PollOptions } from '#scripts/lib/poll'
|
|
2
|
+
|
|
3
|
+
// **The open listing trails the create call** (joshuafolkken/kit#3332): `issue:file` asks
|
|
4
|
+
// `epic:bundle` about the issue it has just filed, and the list endpoint had not shown it yet on every
|
|
5
|
+
// filing of one run, so the placement was skipped each time. A caller that knows the issue is fresh
|
|
6
|
+
// passes a poll and the listing is read again until it shows the issue. An unreadable listing is
|
|
7
|
+
// final, since reading it again is not what would change it.
|
|
8
|
+
|
|
9
|
+
// A number typed at the command line names an issue that exists already, so it is read once.
|
|
10
|
+
const SINGLE_READ: PollOptions = { attempts: 1, interval_ms: 0 }
|
|
11
|
+
|
|
12
|
+
interface Listing {
|
|
13
|
+
is_readable: boolean
|
|
14
|
+
issues: ReadonlyArray<{ number: number }>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function is_settled(listing: Listing, issue_number: number): boolean {
|
|
18
|
+
return !listing.is_readable || listing.issues.some((issue) => issue.number === issue_number)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function read_until_listed<T extends Listing>(
|
|
22
|
+
read: () => Promise<T>,
|
|
23
|
+
issue_number: number,
|
|
24
|
+
options: PollOptions = SINGLE_READ,
|
|
25
|
+
): Promise<T> {
|
|
26
|
+
const reads: Array<T> = []
|
|
27
|
+
|
|
28
|
+
await poll.poll_until(async () => {
|
|
29
|
+
const listing = await read()
|
|
30
|
+
|
|
31
|
+
reads.push(listing)
|
|
32
|
+
|
|
33
|
+
return is_settled(listing, issue_number)
|
|
34
|
+
}, options)
|
|
35
|
+
|
|
36
|
+
return reads.at(-1) ?? (await read())
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const epic_bundle_poll = { read_until_listed }
|
|
40
|
+
|
|
41
|
+
export { epic_bundle_poll }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
2
|
+
import { open_issue_schema, type OpenIssueData } from '#scripts/git/git-schemas'
|
|
2
3
|
import { read_json_listing } from '#scripts/git/parse-json-array'
|
|
3
|
-
import { open_issue_schema, type OpenIssueData } from '#scripts/git/schemas'
|
|
4
4
|
import {
|
|
5
5
|
ALREADY_DONE_LABEL,
|
|
6
6
|
has_any_label,
|
|
@@ -70,11 +70,12 @@ function is_same_blockers(
|
|
|
70
70
|
left: ReadonlyArray<IssueReference>,
|
|
71
71
|
right: ReadonlyArray<IssueReference>,
|
|
72
72
|
): boolean {
|
|
73
|
-
|
|
74
|
-
blockers
|
|
73
|
+
function sorted(blockers: ReadonlyArray<IssueReference>): string {
|
|
74
|
+
return blockers
|
|
75
75
|
.map((blocker) => epic_graph.key_of(blocker))
|
|
76
76
|
.toSorted((first, second) => first.localeCompare(second))
|
|
77
77
|
.join(',')
|
|
78
|
+
}
|
|
78
79
|
|
|
79
80
|
return sorted(left) === sorted(right)
|
|
80
81
|
}
|
|
@@ -217,7 +217,7 @@ function is_ready(node: string, context: ClassifyContext): boolean {
|
|
|
217
217
|
function fill_memo(children: ReadonlyArray<EpicChild>, context: ClassifyContext): void {
|
|
218
218
|
const pending = new Set(children.map((child) => epic_graph.key_of(child)))
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
function peel(): number {
|
|
221
221
|
const ready = [...pending].filter((node) => is_ready(node, context))
|
|
222
222
|
|
|
223
223
|
for (const node of ready) {
|
|
@@ -1,76 +1,108 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ParseArgsOptionsConfig } from 'node:util'
|
|
2
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
3
|
+
|
|
4
|
+
// The argument reading every `josh epic` form shares, on top of `cli_flags` (joshuafolkken/kit#3261).
|
|
2
5
|
//
|
|
3
|
-
// They were private to `epic-cli.ts` until `--remove` needed the same
|
|
6
|
+
// They were private to `epic-cli.ts` until `--remove` needed the same readers (joshuafolkken/kit#1712),
|
|
4
7
|
// at which point the choice was to copy them or to name them. Copying is what `CLAUDE.md` prohibits,
|
|
5
8
|
// and the failure it would produce is specific: two readers disagreeing about what counts as a flag
|
|
6
9
|
// value would have one form silently take a `--decision-file` path for an issue number.
|
|
10
|
+
//
|
|
11
|
+
// **Each form names its own flags, and the read is strict.** Which flags consume the argument after
|
|
12
|
+
// them is per form: `--before` takes a value only under `--add`, and treating it as one everywhere had
|
|
13
|
+
// `josh epic "T" 101 102 --after 103` silently drop #103 (joshuafolkken/kit#890). A flag the form does
|
|
14
|
+
// not know refuses the invocation — a mistyped flag would otherwise leave its value positional, so it
|
|
15
|
+
// becomes an issue number and the edit silently lands somewhere else.
|
|
7
16
|
|
|
8
|
-
const FLAG_PREFIX = '--'
|
|
9
17
|
const ISSUE_NUMBER_PATTERN = /^[1-9]\d*$/u
|
|
18
|
+
// Every value flag is read as a list so a repeat is visible rather than resolved to one of the two:
|
|
19
|
+
// two `--decision-file` paths name two records, and two `--before` targets name two places.
|
|
20
|
+
const VALUE_OPTION = { type: 'string', multiple: true } as const
|
|
21
|
+
const SWITCH_OPTION = { type: 'boolean' } as const
|
|
22
|
+
const SINGLE = 1
|
|
23
|
+
|
|
24
|
+
type ParsedValue = string | boolean | Array<string | boolean> | undefined
|
|
25
|
+
|
|
26
|
+
interface EpicArgv {
|
|
27
|
+
positionals: Array<string>
|
|
28
|
+
values: Readonly<Record<string, ParsedValue>>
|
|
29
|
+
}
|
|
10
30
|
|
|
11
|
-
|
|
12
|
-
|
|
31
|
+
interface FormFlags {
|
|
32
|
+
switches: ReadonlyArray<string>
|
|
33
|
+
value_flags?: ReadonlyArray<string>
|
|
13
34
|
}
|
|
14
35
|
|
|
15
|
-
|
|
16
|
-
const index = argv.indexOf(flag)
|
|
17
|
-
if (index === -1) return undefined
|
|
36
|
+
type OptionEntry = [string, ParseArgsOptionsConfig[string]]
|
|
18
37
|
|
|
19
|
-
|
|
38
|
+
function options_of(form: FormFlags): ParseArgsOptionsConfig {
|
|
39
|
+
const entries: Array<OptionEntry> = [
|
|
40
|
+
...form.switches.map((flag): OptionEntry => [cli_flags.option_name(flag), SWITCH_OPTION]),
|
|
41
|
+
...(form.value_flags ?? []).map((flag): OptionEntry => [
|
|
42
|
+
cli_flags.option_name(flag),
|
|
43
|
+
VALUE_OPTION,
|
|
44
|
+
]),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
return Object.fromEntries(entries)
|
|
20
48
|
}
|
|
21
49
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
): boolean {
|
|
29
|
-
return value_flags.has(argv[index - 1] ?? '')
|
|
50
|
+
// Every value a flag was given, in order; empty when it was absent.
|
|
51
|
+
function values_of_flag(parsed: EpicArgv, flag: string): ReadonlyArray<string> {
|
|
52
|
+
const given = parsed.values[cli_flags.option_name(flag)]
|
|
53
|
+
if (!Array.isArray(given)) return []
|
|
54
|
+
|
|
55
|
+
return given.filter((value): value is string => typeof value === 'string')
|
|
30
56
|
}
|
|
31
57
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// silently drop #103 from the new epic instead of ignoring an unknown flag (joshuafolkken/kit#890).
|
|
35
|
-
function to_positional_arguments(
|
|
36
|
-
argv: ReadonlyArray<string>,
|
|
37
|
-
value_flags: ReadonlySet<string>,
|
|
38
|
-
): Array<string> {
|
|
39
|
-
return argv.filter(
|
|
40
|
-
(argument, index) => !is_flag(argument) && !is_flag_value(argv, index, value_flags),
|
|
41
|
-
)
|
|
58
|
+
function count_flag(parsed: EpicArgv, flag: string): number {
|
|
59
|
+
return values_of_flag(parsed, flag).length
|
|
42
60
|
}
|
|
43
61
|
|
|
44
|
-
function
|
|
45
|
-
return
|
|
62
|
+
function is_repeated(parsed: EpicArgv, flag: string): boolean {
|
|
63
|
+
return count_flag(parsed, flag) > SINGLE
|
|
46
64
|
}
|
|
47
65
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
function is_value_unusable(argv: ReadonlyArray<string>, flag: string): boolean {
|
|
54
|
-
if (!argv.includes(flag)) return false
|
|
55
|
-
if (count_flag(argv, flag) > 1) return true
|
|
56
|
-
const value = read_flag_value(argv, flag)
|
|
66
|
+
// The form's arguments, or `undefined` when a flag is unknown, a value is missing, or a value flag was
|
|
67
|
+
// given twice — a repeat is refused rather than silently taking one of the two.
|
|
68
|
+
function read_form(argv: ReadonlyArray<string>, form: FormFlags): EpicArgv | undefined {
|
|
69
|
+
const parsed = cli_flags.arguments_of(argv, options_of(form))
|
|
70
|
+
if (parsed === undefined) return undefined
|
|
57
71
|
|
|
58
|
-
return
|
|
72
|
+
return (form.value_flags ?? []).some((flag) => is_repeated(parsed, flag)) ? undefined : parsed
|
|
59
73
|
}
|
|
60
74
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
// The form's positional arguments, or none when the read refused the invocation.
|
|
76
|
+
function positionals_of(argv: ReadonlyArray<string>, form: FormFlags): Array<string> {
|
|
77
|
+
return read_form(argv, form)?.positionals ?? []
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function read_flag_value(parsed: EpicArgv, flag: string): string | undefined {
|
|
81
|
+
return values_of_flag(parsed, flag)[0]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function is_switch_set(parsed: EpicArgv, flag: string): boolean {
|
|
85
|
+
return parsed.values[cli_flags.option_name(flag)] === true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A value-taking flag given without a usable value, or given twice. **Refused rather than read as "none
|
|
89
|
+
// was asked for"** — `--decision-file` is passed precisely because the record has to exist, so a shell
|
|
90
|
+
// that ate the path would otherwise land the edit, write no record, post no comment and exit 0: success
|
|
91
|
+
// reported for half the job. Repeated, it names two records (joshuafolkken/kit#1350).
|
|
92
|
+
function is_value_unusable(argv: ReadonlyArray<string>, flag: string): boolean {
|
|
93
|
+
return (
|
|
94
|
+
cli_flags.given_values(argv, flag).length > SINGLE || cli_flags.is_value_unusable(argv, flag)
|
|
95
|
+
)
|
|
66
96
|
}
|
|
67
97
|
|
|
68
98
|
const epic_cli_argv = {
|
|
99
|
+
read_form,
|
|
100
|
+
positionals_of,
|
|
69
101
|
read_flag_value,
|
|
70
|
-
|
|
102
|
+
is_switch_set,
|
|
71
103
|
count_flag,
|
|
72
104
|
is_value_unusable,
|
|
73
|
-
has_unknown_flag,
|
|
74
105
|
}
|
|
75
106
|
|
|
76
107
|
export { epic_cli_argv, ISSUE_NUMBER_PATTERN }
|
|
108
|
+
export type { EpicArgv, FormFlags }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { epic_cli_argv, ISSUE_NUMBER_PATTERN } from './epic-cli-argv'
|
|
1
|
+
import { epic_cli_argv, ISSUE_NUMBER_PATTERN, type EpicArgv, type FormFlags } from './epic-cli-argv'
|
|
2
2
|
|
|
3
3
|
// `josh epic --remove <E> <M> <N> …`'s argument rules (joshuafolkken/kit#1712).
|
|
4
4
|
//
|
|
@@ -10,8 +10,7 @@ const REMOVE_FLAG = '--remove'
|
|
|
10
10
|
const DECISION_FLAG = '--decision-file'
|
|
11
11
|
// `--remove` takes no positioning flag: there is nothing for a removal to be relative to, and the
|
|
12
12
|
// path itself says which orders go.
|
|
13
|
-
const
|
|
14
|
-
const REMOVE_KNOWN_FLAGS: ReadonlySet<string> = new Set([REMOVE_FLAG, DECISION_FLAG])
|
|
13
|
+
const REMOVE_FORM: FormFlags = { switches: [REMOVE_FLAG], value_flags: [DECISION_FLAG] }
|
|
15
14
|
// The shortest path that names an order at all: `<M> <N>`.
|
|
16
15
|
const MINIMUM_PATH_LENGTH = 2
|
|
17
16
|
|
|
@@ -36,30 +35,23 @@ function to_path_numbers(raw_path: ReadonlyArray<string>): Array<number> | undef
|
|
|
36
35
|
return raw_path.length < MINIMUM_PATH_LENGTH ? undefined : raw_path.map(Number)
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
epic_cli_argv.has_unknown_flag(argv, REMOVE_KNOWN_FLAGS) ||
|
|
43
|
-
epic_cli_argv.is_value_unusable(argv, DECISION_FLAG)
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function read_subject(
|
|
48
|
-
argv: ReadonlyArray<string>,
|
|
49
|
-
): { epic_number: number; path: Array<number> } | undefined {
|
|
50
|
-
const [raw_epic, ...raw_path] = epic_cli_argv.to_positional_arguments(argv, REMOVE_VALUE_FLAGS)
|
|
38
|
+
function read_subject(parsed: EpicArgv): { epic_number: number; path: Array<number> } | undefined {
|
|
39
|
+
const [raw_epic, ...raw_path] = parsed.positionals
|
|
51
40
|
if (raw_epic === undefined || !ISSUE_NUMBER_PATTERN.test(raw_epic)) return undefined
|
|
52
41
|
const path = to_path_numbers(raw_path)
|
|
53
42
|
|
|
54
43
|
return path === undefined ? undefined : { epic_number: Number(raw_epic), path }
|
|
55
44
|
}
|
|
56
45
|
|
|
46
|
+
// An unknown flag, or a `--decision-file` a shell ate, makes the whole invocation unreadable whatever
|
|
47
|
+
// the positional arguments say.
|
|
57
48
|
function parse_remove_arguments(argv: ReadonlyArray<string>): RemoveArguments | undefined {
|
|
58
|
-
if (
|
|
59
|
-
const
|
|
60
|
-
|
|
49
|
+
if (epic_cli_argv.is_value_unusable(argv, DECISION_FLAG)) return undefined
|
|
50
|
+
const parsed = epic_cli_argv.read_form(argv, REMOVE_FORM)
|
|
51
|
+
const subject = parsed === undefined ? undefined : read_subject(parsed)
|
|
52
|
+
if (parsed === undefined || subject === undefined) return undefined
|
|
61
53
|
|
|
62
|
-
return { ...subject, decision_path: epic_cli_argv.read_flag_value(
|
|
54
|
+
return { ...subject, decision_path: epic_cli_argv.read_flag_value(parsed, DECISION_FLAG) }
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
const epic_cli_remove = {
|
package/scripts/epic/epic-cli.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cli_body } from '#scripts/josh/cli-body'
|
|
2
2
|
import type { InsertKind, InsertPosition } from './epic-chains'
|
|
3
|
-
import { epic_cli_argv, ISSUE_NUMBER_PATTERN } from './epic-cli-argv'
|
|
3
|
+
import { epic_cli_argv, ISSUE_NUMBER_PATTERN, type EpicArgv, type FormFlags } from './epic-cli-argv'
|
|
4
4
|
import { epic_cli_remove } from './epic-cli-remove'
|
|
5
5
|
import { epic_parse, type ExternalChild } from './epic-parse'
|
|
6
6
|
|
|
@@ -27,25 +27,22 @@ const ORDER_AFTER_FLAG = '--order-after'
|
|
|
27
27
|
// from a file, or from stdin as `-`. The text is a judgement, so the caller writes it; what the command
|
|
28
28
|
// contributes is placing it in the epic's `## Decisions` and on each child (joshuafolkken/kit#1350).
|
|
29
29
|
const DECISION_FLAG = '--decision-file'
|
|
30
|
-
// Which flags consume the argument after them — per
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
]
|
|
39
|
-
const
|
|
40
|
-
ADD_FLAG,
|
|
41
|
-
BEFORE_FLAG,
|
|
42
|
-
|
|
43
|
-
ORDER_BEFORE_FLAG,
|
|
44
|
-
ORDER_AFTER_FLAG,
|
|
45
|
-
DECISION_FLAG,
|
|
46
|
-
])
|
|
30
|
+
// Which flags each form knows, and which of them consume the argument after them — per form, for the
|
|
31
|
+
// reason `epic-cli-argv.ts` gives.
|
|
32
|
+
const CREATE_FORM: FormFlags = {
|
|
33
|
+
switches: [ORDERED_FLAG],
|
|
34
|
+
value_flags: [RATIONALE_FLAG, ORIGIN_FLAG],
|
|
35
|
+
}
|
|
36
|
+
const PROMOTE_FORM: FormFlags = { ...CREATE_FORM, switches: [PROMOTE_FLAG, ORDERED_FLAG] }
|
|
37
|
+
const RECONCILE_FORM: FormFlags = { switches: [RECONCILE_FLAG] }
|
|
38
|
+
const CHECK_FORM: FormFlags = { switches: [] }
|
|
39
|
+
const ADD_FORM: FormFlags = {
|
|
40
|
+
switches: [ADD_FLAG],
|
|
41
|
+
value_flags: [BEFORE_FLAG, AFTER_FLAG, ORDER_BEFORE_FLAG, ORDER_AFTER_FLAG, DECISION_FLAG],
|
|
42
|
+
}
|
|
47
43
|
|
|
48
|
-
const { count_flag, is_value_unusable, read_flag_value } =
|
|
44
|
+
const { count_flag, is_switch_set, is_value_unusable, positionals_of, read_flag_value, read_form } =
|
|
45
|
+
epic_cli_argv
|
|
49
46
|
|
|
50
47
|
interface CreateArguments {
|
|
51
48
|
title: string
|
|
@@ -65,13 +62,6 @@ interface PromoteArguments {
|
|
|
65
62
|
origin?: string | undefined
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
function to_positional_arguments(
|
|
69
|
-
argv: ReadonlyArray<string>,
|
|
70
|
-
value_flags: ReadonlySet<string> = VALUE_FLAGS,
|
|
71
|
-
): Array<string> {
|
|
72
|
-
return epic_cli_argv.to_positional_arguments(argv, value_flags)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
65
|
// Deduplicated because a repeated number would render a duplicate task-list row, and with
|
|
76
66
|
// `--ordered` would ask GitHub to make an issue block itself.
|
|
77
67
|
function to_child_numbers(raw_children: ReadonlyArray<string>): Array<number> {
|
|
@@ -83,22 +73,33 @@ function to_child_numbers(raw_children: ReadonlyArray<string>): Array<number> {
|
|
|
83
73
|
return [...new Set(numbers)]
|
|
84
74
|
}
|
|
85
75
|
|
|
86
|
-
// The
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
// The flags a creation and a promotion share, read the same way for both.
|
|
77
|
+
function read_batch_flags(
|
|
78
|
+
parsed: EpicArgv,
|
|
79
|
+
): Pick<CreateArguments, 'is_ordered' | 'rationale_path' | 'origin'> {
|
|
80
|
+
return {
|
|
81
|
+
is_ordered: is_switch_set(parsed, ORDERED_FLAG),
|
|
82
|
+
rationale_path: read_flag_value(parsed, RATIONALE_FLAG),
|
|
83
|
+
origin: read_flag_value(parsed, ORIGIN_FLAG),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function to_create_arguments(parsed: EpicArgv): CreateArguments | undefined {
|
|
88
|
+
const [title, ...raw_children] = parsed.positionals
|
|
90
89
|
if (title === undefined || title.length === 0) return undefined
|
|
91
90
|
|
|
92
91
|
const children = to_child_numbers(raw_children)
|
|
93
92
|
if (children.length === 0) return undefined
|
|
94
93
|
|
|
95
|
-
return {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
return { title, children, ...read_batch_flags(parsed) }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The title comes first so the children can stay a bare list of numbers, which is how they are
|
|
98
|
+
// written down when the split is made.
|
|
99
|
+
function parse_create_arguments(argv: ReadonlyArray<string>): CreateArguments | undefined {
|
|
100
|
+
const parsed = read_form(argv, CREATE_FORM)
|
|
101
|
+
|
|
102
|
+
return parsed === undefined ? undefined : to_create_arguments(parsed)
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
// Whether the invocation is a promotion rather than a creation.
|
|
@@ -118,7 +119,7 @@ interface ReconcileArguments {
|
|
|
118
119
|
// The epic to reconcile. The number after `--reconcile` is the only subject; there are no children
|
|
119
120
|
// and no position — the repair reads what the epic already records rather than being told an order.
|
|
120
121
|
function parse_reconcile_arguments(argv: ReadonlyArray<string>): ReconcileArguments | undefined {
|
|
121
|
-
const [raw_epic] =
|
|
122
|
+
const [raw_epic] = positionals_of(argv, RECONCILE_FORM)
|
|
122
123
|
if (raw_epic === undefined || !ISSUE_NUMBER_PATTERN.test(raw_epic)) return undefined
|
|
123
124
|
|
|
124
125
|
return { epic_number: Number(raw_epic) }
|
|
@@ -127,20 +128,20 @@ function parse_reconcile_arguments(argv: ReadonlyArray<string>): ReconcileArgume
|
|
|
127
128
|
// The promoted issue and its children. The number after `--promote` is the epic; everything else
|
|
128
129
|
// positional is a child. Refused when the epic would also be listed as its own child, which would
|
|
129
130
|
// have it block itself under `--ordered`.
|
|
130
|
-
function
|
|
131
|
-
const [raw_epic, ...raw_children] =
|
|
131
|
+
function to_promote_arguments(parsed: EpicArgv): PromoteArguments | undefined {
|
|
132
|
+
const [raw_epic, ...raw_children] = parsed.positionals
|
|
132
133
|
if (raw_epic === undefined || !ISSUE_NUMBER_PATTERN.test(raw_epic)) return undefined
|
|
133
134
|
const epic_number = Number(raw_epic)
|
|
134
135
|
const children = to_child_numbers(raw_children).filter((child) => child !== epic_number)
|
|
135
136
|
if (children.length === 0) return undefined
|
|
136
137
|
|
|
137
|
-
return {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
return { epic_number, children, ...read_batch_flags(parsed) }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parse_promote_arguments(argv: ReadonlyArray<string>): PromoteArguments | undefined {
|
|
142
|
+
const parsed = read_form(argv, PROMOTE_FORM)
|
|
143
|
+
|
|
144
|
+
return parsed === undefined ? undefined : to_promote_arguments(parsed)
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
// `--add <E> <N...> [--before <M> | --after <M> | --order-before <M> | --order-after <M>]`: the epic
|
|
@@ -195,36 +196,33 @@ const POSITION_FLAGS: ReadonlyArray<PositionFlag> = [
|
|
|
195
196
|
// of each direction, or a dependency-writing flag beside an order-only one. `read_flag_value` would
|
|
196
197
|
// answer with the first, which is a silent choice rather than a refusal — and here the silent choice
|
|
197
198
|
// would decide whether a `blocked-by` is written at all.
|
|
198
|
-
function
|
|
199
|
-
return POSITION_FLAGS.reduce((total, entry) => total + count_flag(
|
|
199
|
+
function count_position_flags(parsed: EpicArgv): number {
|
|
200
|
+
return POSITION_FLAGS.reduce((total, entry) => total + count_flag(parsed, entry.flag), 0)
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
interface PositionTarget extends PositionFlag {
|
|
203
204
|
raw: string
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
function to_position_target(
|
|
207
|
-
|
|
208
|
-
entry: PositionFlag,
|
|
209
|
-
): PositionTarget | undefined {
|
|
210
|
-
const raw = read_flag_value(argv, entry.flag)
|
|
207
|
+
function to_position_target(parsed: EpicArgv, entry: PositionFlag): PositionTarget | undefined {
|
|
208
|
+
const raw = read_flag_value(parsed, entry.flag)
|
|
211
209
|
|
|
212
210
|
return raw === undefined ? undefined : { ...entry, raw }
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
function read_position_target(
|
|
216
|
-
if (
|
|
213
|
+
function read_position_target(parsed: EpicArgv): PositionTarget | undefined {
|
|
214
|
+
if (count_position_flags(parsed) > 1) return undefined
|
|
217
215
|
|
|
218
|
-
return POSITION_FLAGS.map((entry) => to_position_target(
|
|
216
|
+
return POSITION_FLAGS.map((entry) => to_position_target(parsed, entry)).find(
|
|
219
217
|
(found) => found !== undefined,
|
|
220
218
|
)
|
|
221
219
|
}
|
|
222
220
|
|
|
223
221
|
// A target that is not an issue number is refused for the same reason two flags are: guessing would
|
|
224
222
|
// insert somewhere.
|
|
225
|
-
function parse_position(
|
|
226
|
-
const has_flag =
|
|
227
|
-
const target = read_position_target(
|
|
223
|
+
function parse_position(parsed: EpicArgv): PositionOutcome {
|
|
224
|
+
const has_flag = count_position_flags(parsed) > 0
|
|
225
|
+
const target = read_position_target(parsed)
|
|
228
226
|
if (target === undefined) return has_flag ? REFUSED_POSITION : NO_POSITION
|
|
229
227
|
if (!ISSUE_NUMBER_PATTERN.test(target.raw)) return REFUSED_POSITION
|
|
230
228
|
|
|
@@ -235,10 +233,6 @@ function parse_position(argv: ReadonlyArray<string>): PositionOutcome {
|
|
|
235
233
|
}
|
|
236
234
|
}
|
|
237
235
|
|
|
238
|
-
function has_unknown_flag(argv: ReadonlyArray<string>): boolean {
|
|
239
|
-
return epic_cli_argv.has_unknown_flag(argv, ADD_KNOWN_FLAGS)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
236
|
// Whether *this* is why the insertion could not be read, so the refusal can say so. Without it the
|
|
243
237
|
// caller prints the generic requirements line, and a shell that expanded `--decision-file $REC` to
|
|
244
238
|
// nothing is answered with "an epic number and at least one child issue number are required" — advice
|
|
@@ -248,9 +242,9 @@ function is_decision_path_unusable(argv: ReadonlyArray<string>): boolean {
|
|
|
248
242
|
}
|
|
249
243
|
|
|
250
244
|
function read_add_subject(
|
|
251
|
-
|
|
245
|
+
parsed: EpicArgv,
|
|
252
246
|
): { epic_number: number; children: Array<number> } | undefined {
|
|
253
|
-
const [raw_epic, ...raw_children] =
|
|
247
|
+
const [raw_epic, ...raw_children] = parsed.positionals
|
|
254
248
|
if (raw_epic === undefined || !ISSUE_NUMBER_PATTERN.test(raw_epic)) return undefined
|
|
255
249
|
const epic_number = Number(raw_epic)
|
|
256
250
|
const children = to_child_numbers(raw_children).filter((child) => child !== epic_number)
|
|
@@ -258,21 +252,28 @@ function read_add_subject(
|
|
|
258
252
|
return children.length === 0 ? undefined : { epic_number, children }
|
|
259
253
|
}
|
|
260
254
|
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
const subject = read_add_subject(argv)
|
|
255
|
+
function to_add_arguments(parsed: EpicArgv): AddArguments | undefined {
|
|
256
|
+
const subject = read_add_subject(parsed)
|
|
264
257
|
if (subject === undefined) return undefined
|
|
265
|
-
const outcome = parse_position(
|
|
258
|
+
const outcome = parse_position(parsed)
|
|
266
259
|
if (outcome.is_refused) return undefined
|
|
267
260
|
|
|
268
261
|
return {
|
|
269
262
|
...subject,
|
|
270
263
|
position: outcome.position,
|
|
271
264
|
is_order_only: outcome.is_order_only,
|
|
272
|
-
decision_path: read_flag_value(
|
|
265
|
+
decision_path: read_flag_value(parsed, DECISION_FLAG),
|
|
273
266
|
}
|
|
274
267
|
}
|
|
275
268
|
|
|
269
|
+
// An unknown flag is refused by the strict read: a mistyped positioning flag would otherwise leave its
|
|
270
|
+
// value positional, so it becomes a child and the edit lands somewhere else (joshuafolkken/kit#890).
|
|
271
|
+
function parse_add_arguments(argv: ReadonlyArray<string>): AddArguments | undefined {
|
|
272
|
+
const parsed = is_decision_path_unusable(argv) ? undefined : read_form(argv, ADD_FORM)
|
|
273
|
+
|
|
274
|
+
return parsed === undefined ? undefined : to_add_arguments(parsed)
|
|
275
|
+
}
|
|
276
|
+
|
|
276
277
|
// The one refusal `--add` has to explain rather than merely report. `into owner/repo#N` is a legal
|
|
277
278
|
// thing for a person to type, and this command cannot serve it: it reads and edits issues in the
|
|
278
279
|
// repository it is run from. Falling through to the usage line would read as "that form does not
|
|
@@ -288,7 +289,7 @@ interface CrossRepoAddTarget {
|
|
|
288
289
|
// problem, and
|
|
289
290
|
// a suggestion built from it would drop or mangle what the person typed.
|
|
290
291
|
function find_cross_repo_add_target(argv: ReadonlyArray<string>): CrossRepoAddTarget | undefined {
|
|
291
|
-
const [raw_epic] =
|
|
292
|
+
const [raw_epic] = positionals_of(argv, ADD_FORM)
|
|
292
293
|
if (raw_epic === undefined) return undefined
|
|
293
294
|
const epic = epic_parse.parse_external_reference(raw_epic)
|
|
294
295
|
if (epic === undefined) return undefined
|
|
@@ -351,7 +352,7 @@ function format_cross_repo_refusal(found: CrossRepoAddTarget): string {
|
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
function parse_check_argument(argv: ReadonlyArray<string>): number | undefined {
|
|
354
|
-
const [raw] =
|
|
355
|
+
const [raw] = positionals_of(argv, CHECK_FORM)
|
|
355
356
|
if (raw === undefined || !ISSUE_NUMBER_PATTERN.test(raw)) return undefined
|
|
356
357
|
|
|
357
358
|
return Number(raw)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
2
|
+
import { rest_comment_schema, type RestCommentData } from '#scripts/git/git-schemas'
|
|
2
3
|
import { read_json_listing } from '#scripts/git/parse-json-array'
|
|
3
|
-
import { rest_comment_schema, type RestCommentData } from '#scripts/git/schemas'
|
|
4
4
|
import { epic_parse, type ExternalChild } from './epic-parse'
|
|
5
5
|
|
|
6
6
|
// The comment the epic auto-close announces itself with, and how a run recognizes the one a previous
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
2
|
+
import { epic_child_schema, epic_issue_schema, type EpicChildData } from '#scripts/git/git-schemas'
|
|
2
3
|
import { cutoff_cause, cutoff_of, type ScanCutoff } from '#scripts/git/listing-cutoff'
|
|
3
4
|
import {
|
|
4
5
|
parse_json_array_or_undefined,
|
|
5
6
|
parse_json_object_safe,
|
|
6
7
|
} from '#scripts/git/parse-json-array'
|
|
7
|
-
import { epic_child_schema, epic_issue_schema, type EpicChildData } from '#scripts/git/schemas'
|
|
8
8
|
import { EPIC_LABEL } from '#scripts/issue/issue-labels'
|
|
9
9
|
import { error_text } from '#scripts/lib/error-message'
|
|
10
10
|
import { epic_close_comment } from './epic-close-comment'
|