@joshuafolkken/kit 1.1178.0 → 1.1229.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/settings.json +2 -2
- package/.claude/skills/dependency-update/SKILL.md +4 -8
- package/.claude/skills/epic-commands/SKILL.md +2 -2
- package/.claude/skills/epic-commands/epic-bundle.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +2 -4
- package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +6 -2
- package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
- package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
- package/.claude/skills/workflow-commands/chain-rule.md +1 -1
- package/.claude/skills/workflow-commands/delegation.md +2 -2
- package/.claude/skills/workflow-commands/followup.md +5 -0
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-scout.md +1 -1
- package/.claude/skills/workflow-commands/kickoff.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
- package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
- package/.claude/skills/workflow-commands/prerequisite.md +2 -2
- package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
- package/.codex/hooks.json +2 -2
- package/.cursorrules +6 -41
- package/.github/actions/setup-node/action.yml +1 -1
- package/.github/workflows/auto-tag.yml +3 -4
- package/.github/workflows/ci.yml +32 -31
- package/.github/workflows/dependabot-auto-merge.yml +13 -6
- package/.github/workflows/publish.yml +48 -4
- package/.github/workflows/sonar-qube.yml +3 -0
- package/.pnpmfile.mjs +4 -3
- package/.prettierignore +8 -2
- package/CLAUDE.md +18 -20
- package/README.md +26 -19
- package/SECURITY.md +15 -1
- package/dist/CLAUDE.md +18 -20
- package/dist/commands/document-section-cli.js +734 -0
- package/dist/hooks/audit-provision.js +346 -0
- package/dist/hooks/batch-guard.js +5 -4
- package/dist/hooks/{chunk-S6X6WYKR.js → chunk-55HHG5PX.js} +1210 -711
- package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-5VMKCCC4.js} +25 -10
- package/dist/hooks/chunk-5VNDSNG3.js +12 -0
- package/dist/hooks/{chunk-32GQTL4F.js → chunk-6HJJXRBX.js} +4 -2
- package/dist/hooks/chunk-CDY4NFQG.js +15 -0
- package/dist/hooks/chunk-ELCN5LCH.js +135 -0
- package/dist/hooks/{chunk-VESKRQOF.js → chunk-F56MFS4B.js} +3 -3
- package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-FIVDHQGT.js} +7 -6
- package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
- package/dist/hooks/{chunk-G6KRCKEC.js → chunk-JUDKDCDP.js} +496 -378
- package/dist/hooks/{chunk-GDS5N2KB.js → chunk-NV2JRZZO.js} +718 -681
- package/dist/hooks/{chunk-VQLLNFNT.js → chunk-RNWHN5BP.js} +3 -3
- package/dist/hooks/{chunk-DO6NWWHU.js → chunk-TQNPDZNH.js} +4 -4
- package/dist/hooks/{chunk-I4XSL4TT.js → chunk-VC3PZPZO.js} +1 -1
- package/dist/hooks/codex-hook-adapter.js +14 -12
- package/dist/hooks/duplicate-read-guard.js +6 -5
- package/dist/hooks/format-edited.js +4 -3
- package/dist/hooks/inputs.json +217 -195
- package/dist/hooks/investigation-guard.js +6 -5
- package/dist/hooks/pretool-guard.js +12 -10
- package/dist/hooks/session-lang.js +2 -4
- package/dist/hooks/stop-guard.js +66 -58
- package/dist/josh.js +868 -74
- package/dist/version/index.d.ts +7 -2
- package/dist/version/index.js +152 -75
- package/eslint/base.js +23 -1
- package/eslint/rules/code-quality.js +3 -0
- package/eslint/rules/early-return-one-liner.js +98 -0
- package/eslint/rules/unicorn.js +16 -1
- package/package.json +7 -6
- package/pnpm-workspace.yaml +9 -4
- package/prettier/basic.d.ts +3 -1
- package/prettier/index.d.ts +3 -1
- package/prettier/static.d.ts +3 -1
- package/prompts/coding-standards.md +1 -1
- package/prompts/collaboration-workflow/file-edits.md +4 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/operating-rules.md +17 -9
- package/prompts/collaboration-workflow/overview.md +2 -2
- package/prompts/collaboration-workflow/principles.md +14 -12
- package/prompts/collaboration-workflow/report-format.md +1 -1
- package/prompts/collaboration-workflow/residency.md +1 -1
- package/prompts/collaboration-workflow/rule-delivery.md +6 -6
- package/prompts/collaboration-workflow/shell-body.md +6 -6
- package/prompts/collaboration-workflow/turn-batching.md +1 -1
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
- package/prompts/collaboration-workflow/wip-cap.md +10 -10
- package/prompts/review-rubric.md +1 -2
- package/prompts/review.md +1 -1
- package/scripts/agent/agent-headless.ts +29 -0
- package/scripts/agent/agent-launch-environment.ts +6 -2
- package/scripts/agent/codex-agent-argv.ts +2 -1
- package/scripts/auto-ok/auto-ok-cli.ts +1 -1
- package/scripts/backlog/backlog-arrival.ts +3 -3
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-args.ts +135 -0
- package/scripts/backlog/backlog-drive-cli.ts +11 -137
- package/scripts/backlog/backlog-drive-launch.ts +39 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
- package/scripts/backlog/backlog-drive-named.ts +3 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
- package/scripts/backlog/backlog-drive-owner.ts +1 -1
- package/scripts/backlog/backlog-drive-restore.ts +1 -1
- package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
- package/scripts/backlog/backlog-drive.ts +1 -1
- package/scripts/backlog/backlog-next.ts +16 -5
- package/scripts/backlog/backlog-offer-cli.ts +6 -6
- package/scripts/backlog/backlog-offer.ts +1 -1
- package/scripts/backlog/backlog-plan-cli.ts +1 -1
- package/scripts/backlog/backlog-pool.ts +57 -11
- package/scripts/backlog/backlog-ready.ts +1 -1
- package/scripts/backlog/backlog-scope.ts +1 -1
- package/scripts/backlog/backlog-stalled-detect.ts +2 -2
- package/scripts/backlog/backlog-stalled.ts +1 -1
- package/scripts/build/build-codex-hooks.ts +3 -1
- package/scripts/build/build-commands.ts +66 -0
- package/scripts/build/build-hooks.ts +2 -1
- package/scripts/build/build-library.ts +12 -3
- package/scripts/build/build.ts +70 -0
- package/scripts/build/local-bin.ts +3 -13
- package/scripts/delegation/investigation-reads.ts +3 -1
- package/scripts/discovery/repo-origin.ts +5 -5
- package/scripts/doctor/doctor-consumer.ts +2 -2
- package/scripts/doctor/doctor-io.ts +2 -3
- package/scripts/document/capped-print-part.ts +30 -0
- package/scripts/document/capped-print.ts +3 -4
- package/scripts/document/document-byte-budget.ts +11 -2
- package/scripts/document/document-scan.ts +117 -0
- package/scripts/document/document-section.ts +143 -16
- package/scripts/document/entry-read-budget.ts +5 -3
- package/scripts/document/entry-read-set.ts +10 -89
- package/scripts/document/read-set-cost.ts +98 -0
- package/scripts/epic/epic-add-body.ts +1 -0
- package/scripts/epic/epic-audit-checks.ts +1 -1
- package/scripts/epic/epic-audit-rationale.ts +4 -3
- package/scripts/epic/epic-bundle-cli.ts +14 -3
- package/scripts/epic/epic-bundle-poll.ts +41 -0
- package/scripts/epic/epic-busy.ts +1 -1
- package/scripts/epic/epic-candidate-confirm.ts +3 -2
- package/scripts/epic/epic-classify.ts +1 -1
- package/scripts/epic/epic-cli-argv.ts +78 -46
- package/scripts/epic/epic-cli-remove.ts +11 -19
- package/scripts/epic/epic-cli.ts +74 -73
- package/scripts/epic/epic-close-comment.ts +1 -1
- package/scripts/epic/epic-close.ts +1 -1
- package/scripts/epic/epic-cross-repo.ts +5 -5
- package/scripts/epic/epic-graph.ts +1 -1
- package/scripts/epic/epic-index.ts +1 -1
- package/scripts/epic/epic-issue.ts +2 -6
- package/scripts/epic/epic-lane-offer.ts +2 -2
- package/scripts/epic/epic-next.ts +29 -42
- package/scripts/epic/epic-solo-stale.ts +1 -1
- package/scripts/epic/epic-triage.ts +8 -4
- package/scripts/epic/epic-validate.ts +1 -1
- package/scripts/followup/git-followup-finish.ts +2 -2
- package/scripts/followup/git-followup-label.ts +2 -2
- package/scripts/followup/git-followup-merged.ts +2 -2
- package/scripts/followup/git-pr-followup-checks.ts +142 -0
- package/scripts/followup/git-pr-followup.ts +4 -129
- package/scripts/gate/core-budget.ts +69 -1
- package/scripts/gate/gate-plan.ts +47 -7
- package/scripts/gate/gate-skip.ts +38 -7
- package/scripts/gate/hook-gate-reuse.ts +12 -10
- package/scripts/gate/pre-commit-type-check.ts +4 -1
- package/scripts/gate/project-checks.ts +6 -9
- package/scripts/gate/type-check-step.ts +2 -3
- package/scripts/gate/verification-gate.ts +27 -8
- package/scripts/gh/git-gh-api-path.ts +2 -2
- package/scripts/gh/git-gh-issue-rest.ts +3 -3
- package/scripts/gh/git-gh-issue-write.ts +53 -7
- package/scripts/gh/git-gh-pr-read.ts +64 -19
- package/scripts/gh/git-gh-pr-rest.ts +1 -1
- package/scripts/gh/git-gh-pr.ts +5 -1
- package/scripts/gh/git-pr-ai-review.ts +1 -1
- package/scripts/gh/git-pr-checks-eval.ts +1 -3
- package/scripts/gh/git-pr-checks-parse.ts +1 -1
- package/scripts/gh/git-pr-checks-watch.ts +6 -5
- package/scripts/gh/git-pr-checks.ts +7 -5
- package/scripts/gh/git-pr-coderabbit.ts +1 -1
- package/scripts/gh/git-pr-error.ts +1 -3
- package/scripts/gh/git-pr.ts +9 -13
- package/scripts/gh/github-issue-url.ts +2 -2
- package/scripts/git/constants.ts +2 -6
- package/scripts/git/git-command.ts +20 -218
- package/scripts/git/git-diff-reads.ts +225 -0
- package/scripts/git/git-error.ts +4 -12
- package/scripts/git/git-pre-push-hook.ts +139 -0
- package/scripts/git/git-prompt.ts +2 -6
- package/scripts/git/git-spawn-sync.ts +3 -1
- package/scripts/git/git-spawn.ts +1 -1
- package/scripts/git/git-staging.ts +4 -6
- package/scripts/git/git-status.ts +3 -9
- package/scripts/hooks/codex-hook-adapter.ts +1 -1
- package/scripts/hooks/edited-cspell.ts +2 -4
- package/scripts/hooks/format-edited-file.ts +3 -12
- package/scripts/hooks/pre-push-unit.ts +31 -9
- package/scripts/hooks/reserved-run.ts +2 -2
- package/scripts/hooks/run-hook.sh +5 -4
- package/scripts/hooks/stop-guard.ts +5 -3
- package/scripts/init/init-actions.ts +0 -5
- package/scripts/init/init-ai-copy.ts +13 -2
- package/scripts/init/init-logic-json-merge.ts +19 -0
- package/scripts/init/init-logic-secretlint.ts +7 -7
- package/scripts/init/init-logic-workspace.ts +41 -2
- package/scripts/init/init-logic.ts +16 -26
- package/scripts/init/init.ts +5 -15
- package/scripts/init/initial-commit.ts +4 -4
- package/scripts/init/kit-development-versions.ts +40 -0
- package/scripts/init/kit-setup-state.ts +2 -2
- package/scripts/init/start-exec.ts +68 -19
- package/scripts/init/start-setup-pr.ts +9 -9
- package/scripts/init/start-steps.ts +6 -10
- package/scripts/issue/defect-rate-cli.ts +1 -1
- package/scripts/issue/git-next-issues.ts +1 -1
- package/scripts/issue/issue-auto-ok.ts +1 -1
- package/scripts/issue/issue-backlinks-cli.ts +2 -2
- package/scripts/issue/issue-backlinks.ts +9 -9
- package/scripts/issue/issue-comment-cli.ts +19 -13
- package/scripts/issue/issue-file-cli.ts +45 -18
- package/scripts/issue/issue-file.ts +24 -1
- package/scripts/issue/issue-scout-closed.ts +1 -1
- package/scripts/issue/issue-state-cli.ts +30 -74
- package/scripts/josh/josh-command-map.ts +2 -0
- package/scripts/josh/josh-command-types.ts +11 -0
- package/scripts/josh/josh-commands-ai.ts +4 -4
- package/scripts/josh/josh-commands-backlog.ts +4 -1
- package/scripts/josh/josh-commands-development.ts +10 -0
- package/scripts/josh/josh-commands-document.ts +1 -0
- package/scripts/josh/josh-commands-lane.ts +22 -0
- package/scripts/josh/josh-commands-maintenance.ts +4 -4
- package/scripts/josh/josh-commands-run.ts +12 -7
- package/scripts/josh/josh-in-process.ts +28 -9
- package/scripts/josh/josh-logic.ts +33 -15
- package/scripts/josh/process-identity.ts +1 -3
- package/scripts/josh/session-language-cli.ts +3 -4
- package/scripts/lane/lane-await.ts +5 -1
- package/scripts/lane/lane-child-invocation.ts +2 -2
- package/scripts/lane/lane-cli.ts +3 -3
- package/scripts/lane/lane-dispatch.ts +14 -10
- package/scripts/lane/lane-environment.ts +5 -1
- package/scripts/lane/lane-handoff.ts +1 -1
- package/scripts/lane/lane-install.ts +2 -13
- package/scripts/lane/lane-launch-cli.ts +15 -1
- package/scripts/lane/lane-ledger.ts +145 -0
- package/scripts/lane/lane-leftover.ts +1 -1
- package/scripts/lane/lane-load.ts +108 -0
- package/scripts/lane/lane-measure-cli.ts +149 -0
- package/scripts/lane/lane-occupancy.ts +3 -0
- package/scripts/lane/lane-reap.ts +1 -3
- package/scripts/lane/lane-relaunch.ts +1 -1
- package/scripts/lane/lane-stats.ts +224 -0
- package/scripts/lane/lane-vacant.ts +36 -0
- package/scripts/lane/openai-lane-ship.ts +2 -2
- package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
- package/scripts/lane/openai-lane-supervisor.ts +7 -165
- package/scripts/lane/openai-review-broker.ts +2 -4
- package/scripts/lib/ancestor-directories.ts +35 -0
- package/scripts/lib/animation-helpers.ts +2 -2
- package/scripts/lib/buffered-process.ts +3 -7
- package/scripts/lib/cli-flags.ts +71 -1
- package/scripts/lib/spawn-exit.ts +2 -2
- package/scripts/lib/timeouts.ts +35 -2
- package/scripts/lines/line-budget.ts +4 -6
- package/scripts/notify/telegram-test.ts +6 -0
- package/scripts/observations/observation-ledger-line.ts +6 -6
- package/scripts/observations/observation-ledger-migrate.ts +55 -4
- package/scripts/observations/observation-ledger.ts +26 -7
- package/scripts/overrides/lockfile-overrides.ts +1 -1
- package/scripts/overrides/overrides-logic.ts +1 -1
- package/scripts/propagate/propagate-git.ts +1 -1
- package/scripts/propagate/propagate-publish.ts +2 -2
- package/scripts/propagate/propagate-steps.ts +20 -15
- package/scripts/propagate/propagate.ts +36 -27
- package/scripts/refactor/import-graph.ts +18 -1
- package/scripts/refactor/refactor-lint.ts +4 -4
- package/scripts/repo/repository-labels.ts +2 -2
- package/scripts/repo/required-checks-logic.ts +1 -3
- package/scripts/review/live-evidence.ts +2 -4
- package/scripts/review/review-brief-cli.ts +1 -1
- package/scripts/review/review-finding-ledger.ts +1 -1
- package/scripts/review/review-findings-cli.ts +1 -3
- package/scripts/rules/decision-oracle-batch.ts +128 -0
- package/scripts/rules/decision-oracle-tokens.ts +21 -0
- package/scripts/rules/decision-oracle.ts +8 -120
- package/scripts/rules/delivered-rules.ts +26 -182
- package/scripts/rules/direct-filing.ts +1 -1
- package/scripts/rules/early-heartbeat.ts +3 -3
- package/scripts/rules/git-argv.ts +1 -1
- package/scripts/rules/git-force.ts +1 -1
- package/scripts/rules/implementation-cut.ts +2 -2
- package/scripts/rules/index-guard.ts +1 -1
- package/scripts/rules/issue-comments.ts +118 -0
- package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
- package/scripts/rules/lane-background.ts +2 -2
- package/scripts/rules/lane-park.ts +18 -0
- package/scripts/rules/lane-split-park.ts +128 -0
- package/scripts/rules/oracle-firing.ts +1 -1
- package/scripts/rules/poll-loop.ts +1 -1
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/rules/protected-files.ts +59 -4
- package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
- package/scripts/rules/shell-body-trigger.ts +1 -1
- package/scripts/rules/stop-rules.ts +18 -7
- package/scripts/rules/test-declared-commit.ts +4 -4
- package/scripts/rules/wip-cap.ts +63 -0
- package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
- package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
- package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
- package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
- package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
- package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +2 -2
- package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
- package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
- package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
- package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
- package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
- package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
- package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
- package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
- package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
- package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
- package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
- package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
- package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
- package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
- package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
- package/scripts/run/run-ending.ts +2 -2
- package/scripts/run/run-halfrun-resume.ts +6 -1
- package/scripts/run/run-headless.ts +1 -1
- package/scripts/run/run-invocation.ts +85 -84
- package/scripts/run/run-liveness.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +1 -1
- package/scripts/run/run-preflight.ts +6 -6
- package/scripts/run/run-prep-cli.ts +1 -1
- package/scripts/run/run-prrun-resume.ts +1 -1
- package/scripts/run/run-report-cli.ts +4 -4
- package/scripts/run/run-report.ts +2 -2
- package/scripts/run/run-status-cli.ts +1 -1
- package/scripts/run/run-step-cli.ts +3 -3
- package/scripts/run/run-step.ts +2 -2
- package/scripts/run/run-stop-notify.ts +1 -1
- package/scripts/run/run-stranded-detect.ts +4 -4
- package/scripts/run/run-stranded.ts +2 -2
- package/scripts/run/run-watcher-guard-cli.ts +1 -1
- package/scripts/run/run-watcher-guard.ts +5 -3
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
- package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
- package/scripts/run/ship/run-ship-lock.ts +50 -0
- package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
- package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
- package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
- package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
- package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
- package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
- package/scripts/run/ship/run-ship-stop-text.ts +13 -0
- package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
- package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
- package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +23 -8
- package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
- package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
- package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
- package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
- package/scripts/run/wake/run-wake-restart.ts +58 -0
- package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
- package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
- package/scripts/safe-chain/preinstall-command.ts +20 -21
- package/scripts/safe-chain/project-config.ts +19 -15
- package/scripts/sync/sync-ai-files.ts +1 -1
- package/scripts/sync/sync-configs.ts +2 -1
- package/scripts/sync/sync.ts +15 -2
- package/scripts/test/unit-worker-share.ts +1 -0
- package/scripts/time-runtime/time-background.ts +5 -1
- package/scripts/time-runtime/time-instant.ts +3 -1
- package/scripts/time-runtime/time-spans.ts +6 -139
- package/scripts/time-runtime/time-tool-call.ts +160 -0
- package/scripts/version/effective-upstream.ts +2 -20
- package/scripts/version/latest-corepack.ts +17 -17
- package/scripts/version/latest-regression.ts +1 -1
- package/scripts/version/latest-update.ts +4 -4
- package/scripts/version/npm-registry.ts +20 -5
- package/scripts/version/publishable-range-check.ts +2 -2
- package/scripts/version/publishable-range.ts +1 -1
- package/scripts/version/release-age.ts +48 -38
- package/scripts/version/release-hold.ts +1 -1
- package/scripts/version/safe-chain-version-update.ts +40 -0
- package/scripts/version/upgrade-shell-command.ts +48 -8
- package/scripts/version/version-check-logic.ts +23 -14
- package/scripts/version/version-commands.ts +23 -11
- package/scripts/version/version-remote.ts +28 -13
- package/templates/pnpm-workspace.basic.yaml +4 -0
- package/templates/workflows/dependabot-auto-merge.yml +5 -5
- package/templates/workflows/github-release.yml +2 -0
- package/scripts/build/build-config-merge.ts +0 -27
- package/scripts/build/build-managed-marker.ts +0 -27
- package/scripts/build/build-self-sync-guard.ts +0 -27
- package/scripts/build/build-version.ts +0 -26
- package/scripts/version/preinstall-version-update.ts +0 -86
- /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
- /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
- /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
- /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
- /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
- /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
- /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
- /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
- /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
- /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
- /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
- /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
- /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
- /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
- /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
- /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
- /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
- /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
- /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
- /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
- /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
- /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
- /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
- /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
- /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
- /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
|
@@ -14,20 +14,20 @@
|
|
|
14
14
|
|
|
15
15
|
import { existsSync } from 'node:fs'
|
|
16
16
|
import path from 'node:path'
|
|
17
|
-
import { cost_tokens } from '#scripts/cost-runtime/cost-tokens'
|
|
18
17
|
import { PACKAGE_DIR } from '#scripts/init/init-paths'
|
|
19
18
|
import { bash_output_cap_reader } from './bash-output-cap'
|
|
20
|
-
import { document_section
|
|
19
|
+
import { document_section } from './document-section'
|
|
20
|
+
import { read_set_cost, type Cost } from './read-set-cost'
|
|
21
21
|
import { read_set_point_of_use } from './read-set-point-of-use'
|
|
22
22
|
|
|
23
23
|
const { POINT_OF_USE_FILES, POINT_OF_USE_BY_ENTRY, is_point_of_use, point_of_use_files } =
|
|
24
24
|
read_set_point_of_use
|
|
25
|
+
const { cost_of, scoped_file_cost, total } = read_set_cost
|
|
25
26
|
|
|
26
27
|
const SKILL_DIRECTORY = path.join('.claude', 'skills', 'workflow-commands')
|
|
27
28
|
const SKILL_FILE = 'SKILL.md'
|
|
28
29
|
const TABLE_SECTION = '1. Which file to read'
|
|
29
30
|
const NOTHING = 0
|
|
30
|
-
const ONE_LINE = 1
|
|
31
31
|
|
|
32
32
|
// The point-of-use classification — which documents an entry names but reads only at a later step —
|
|
33
33
|
// is its own concern, split into `read-set-point-of-use.ts` in joshuafolkken/kit#2289. This file
|
|
@@ -54,11 +54,6 @@ interface SectionReference {
|
|
|
54
54
|
heading: string
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
interface Cost {
|
|
58
|
-
bytes: number
|
|
59
|
-
tokens: number
|
|
60
|
-
}
|
|
61
|
-
|
|
62
57
|
interface SectionCost extends SectionReference {
|
|
63
58
|
cost: Cost
|
|
64
59
|
is_resolved: boolean
|
|
@@ -297,21 +292,6 @@ function read_set(root: string, entry: string): ReadSet {
|
|
|
297
292
|
return { entry, files, sections: sections_for(root, entry, files) }
|
|
298
293
|
}
|
|
299
294
|
|
|
300
|
-
function cost_of(text: string): Cost {
|
|
301
|
-
return { bytes: Buffer.byteLength(text, 'utf8'), tokens: cost_tokens.estimate(text) }
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function total(costs: ReadonlyArray<Cost>): Cost {
|
|
305
|
-
const running = { bytes: NOTHING, tokens: NOTHING }
|
|
306
|
-
|
|
307
|
-
for (const cost of costs) {
|
|
308
|
-
running.bytes += cost.bytes
|
|
309
|
-
running.tokens += cost.tokens
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return running
|
|
313
|
-
}
|
|
314
|
-
|
|
315
295
|
function section_cost(root: string, reference: SectionReference): SectionCost {
|
|
316
296
|
const found = document_section.section(read_document(root, reference.file), reference.heading)
|
|
317
297
|
|
|
@@ -334,74 +314,13 @@ function headings_for(sections: ReadonlyArray<SectionReference>, file: string):
|
|
|
334
314
|
return sections.filter((reference) => reference.file === file).map((one) => one.heading)
|
|
335
315
|
}
|
|
336
316
|
|
|
337
|
-
|
|
338
|
-
for (let index = found.first_line; index <= found.last_line; index += ONE_LINE) charged.add(index)
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// `undefined` the moment one heading does not resolve, because that reference is charged at its whole
|
|
342
|
-
// file and there is then nothing to union.
|
|
343
|
-
function charged_lines(markdown: string, headings: ReadonlyArray<string>): Set<number> | undefined {
|
|
344
|
-
const charged = new Set<number>()
|
|
345
|
-
|
|
346
|
-
for (const heading of headings) {
|
|
347
|
-
const found = document_section.section(markdown, heading)
|
|
348
|
-
|
|
349
|
-
if (found === undefined) return undefined
|
|
350
|
-
|
|
351
|
-
add_range(charged, found)
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return charged
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// **The union's charged lines are split into contiguous runs, and each run is costed on its own
|
|
358
|
-
// natural text.** Joining non-adjacent lines with `\n` into one string fabricates a token boundary at
|
|
359
|
-
// every gap the union skipped, so the estimate could drift a token *above* the per-reference sum even
|
|
360
|
-
// with no real overlap — a false negative saving the measurement must never print
|
|
361
|
-
// (joshuafolkken/kit#1934). A run is a maximal stretch of consecutive charged lines, which is exactly
|
|
362
|
-
// the contiguous text a reader of that section actually reads, so summing the runs both mirrors the
|
|
363
|
-
// real read and keeps `scoped` at or below the per-reference sum by construction.
|
|
364
|
-
function extend_or_start(runs: Array<Array<number>>, index: number): void {
|
|
365
|
-
const current = runs.at(-1)
|
|
366
|
-
|
|
367
|
-
if (current?.at(-1) === index - ONE_LINE) current.push(index)
|
|
368
|
-
else runs.push([index])
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function contiguous_runs(charged: ReadonlySet<number>): Array<Array<number>> {
|
|
372
|
-
const runs: Array<Array<number>> = []
|
|
373
|
-
const ascending = [...charged].toSorted((left, right) => left - right)
|
|
374
|
-
|
|
375
|
-
for (const index of ascending) extend_or_start(runs, index)
|
|
376
|
-
|
|
377
|
-
return runs
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function run_text(lines: ReadonlyArray<string>, run: ReadonlyArray<number>): string {
|
|
381
|
-
return run.map((index) => lines[index] ?? '').join('\n')
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// **Charged per file over the union of the lines its references cover, never per reference.**
|
|
385
|
-
// Summing the references instead double-counts the two ways one file can be cited twice — two
|
|
386
|
-
// unresolved headings each charged at the whole file, and a `##` section cited beside one of its own
|
|
387
|
-
// `###` children, which `section()` already returns inside the parent. Either one could push the
|
|
388
|
-
// scoped figure above the whole one and print a *negative* saving, which is the single direction this
|
|
389
|
-
// measurement must not be able to move (joshuafolkken/kit#1776 review round 1).
|
|
390
|
-
function file_scoped_cost(root: string, file: string, headings: ReadonlyArray<string>): Cost {
|
|
391
|
-
const markdown = read_document(root, file)
|
|
392
|
-
const charged = charged_lines(markdown, headings)
|
|
393
|
-
|
|
394
|
-
if (charged === undefined) return cost_of(markdown)
|
|
395
|
-
|
|
396
|
-
const lines = markdown.split('\n')
|
|
397
|
-
|
|
398
|
-
return total(contiguous_runs(charged).map((run) => cost_of(run_text(lines, run))))
|
|
399
|
-
}
|
|
400
|
-
|
|
317
|
+
// The per-file union that keeps `scoped` at or below `whole` lives in `read-set-cost.ts`.
|
|
401
318
|
function scoped_cost(root: string, sections: ReadonlyArray<SectionReference>): Cost {
|
|
402
319
|
const files = unique(sections.map((reference) => reference.file))
|
|
403
320
|
|
|
404
|
-
return total(
|
|
321
|
+
return total(
|
|
322
|
+
files.map((file) => scoped_file_cost(read_document(root, file), headings_for(sections, file))),
|
|
323
|
+
)
|
|
405
324
|
}
|
|
406
325
|
|
|
407
326
|
function file_costs(root: string, names: ReadonlyArray<string>): Array<FileCost> {
|
|
@@ -491,5 +410,7 @@ const entry_read_set = {
|
|
|
491
410
|
total,
|
|
492
411
|
}
|
|
493
412
|
|
|
494
|
-
export type {
|
|
413
|
+
export type { FileCost, ReadSet, ReadSetCost, SectionCost, SectionReference }
|
|
495
414
|
export { entry_read_set }
|
|
415
|
+
|
|
416
|
+
export { type Cost } from './read-set-cost'
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { cost_tokens } from '#scripts/cost-runtime/cost-tokens'
|
|
2
|
+
import { document_section, type Section } from './document-section'
|
|
3
|
+
|
|
4
|
+
// What a read costs, and the scoped union that charges one file's cited sections once — split out
|
|
5
|
+
// of `entry-read-set.ts` when it reached its line limit (joshuafolkken/kit#3264). `entry_read_set`
|
|
6
|
+
// re-exports `cost_of`, `total` and `Cost` under the names they always had, so the move changed no
|
|
7
|
+
// call site.
|
|
8
|
+
|
|
9
|
+
const NOTHING = 0
|
|
10
|
+
const ONE_LINE = 1
|
|
11
|
+
|
|
12
|
+
interface Cost {
|
|
13
|
+
bytes: number
|
|
14
|
+
tokens: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function cost_of(text: string): Cost {
|
|
18
|
+
return { bytes: Buffer.byteLength(text, 'utf8'), tokens: cost_tokens.estimate(text) }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function total(costs: ReadonlyArray<Cost>): Cost {
|
|
22
|
+
const running = { bytes: NOTHING, tokens: NOTHING }
|
|
23
|
+
|
|
24
|
+
for (const cost of costs) {
|
|
25
|
+
running.bytes += cost.bytes
|
|
26
|
+
running.tokens += cost.tokens
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return running
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function add_range(charged: Set<number>, found: Section): void {
|
|
33
|
+
for (let index = found.first_line; index <= found.last_line; index += ONE_LINE) charged.add(index)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// `undefined` the moment one heading does not resolve, because that reference is charged at its whole
|
|
37
|
+
// file and there is then nothing to union.
|
|
38
|
+
function charged_lines(markdown: string, headings: ReadonlyArray<string>): Set<number> | undefined {
|
|
39
|
+
const charged = new Set<number>()
|
|
40
|
+
|
|
41
|
+
for (const heading of headings) {
|
|
42
|
+
const found = document_section.section(markdown, heading)
|
|
43
|
+
|
|
44
|
+
if (found === undefined) return undefined
|
|
45
|
+
|
|
46
|
+
add_range(charged, found)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return charged
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// **The union's charged lines are split into contiguous runs, and each run is costed on its own
|
|
53
|
+
// natural text.** Joining non-adjacent lines with `\n` into one string fabricates a token boundary at
|
|
54
|
+
// every gap the union skipped, so the estimate could drift a token *above* the per-reference sum even
|
|
55
|
+
// with no real overlap — a false negative saving the measurement must never print
|
|
56
|
+
// (joshuafolkken/kit#1934). A run is a maximal stretch of consecutive charged lines, which is exactly
|
|
57
|
+
// the contiguous text a reader of that section actually reads, so summing the runs both mirrors the
|
|
58
|
+
// real read and keeps `scoped` at or below the per-reference sum by construction.
|
|
59
|
+
function extend_or_start(runs: Array<Array<number>>, index: number): void {
|
|
60
|
+
const current = runs.at(-1)
|
|
61
|
+
|
|
62
|
+
if (current?.at(-1) === index - ONE_LINE) current.push(index)
|
|
63
|
+
else runs.push([index])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function contiguous_runs(charged: ReadonlySet<number>): Array<Array<number>> {
|
|
67
|
+
const runs: Array<Array<number>> = []
|
|
68
|
+
const ascending = [...charged].toSorted((left, right) => left - right)
|
|
69
|
+
|
|
70
|
+
for (const index of ascending) extend_or_start(runs, index)
|
|
71
|
+
|
|
72
|
+
return runs
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function run_text(lines: ReadonlyArray<string>, run: ReadonlyArray<number>): string {
|
|
76
|
+
return run.map((index) => lines[index] ?? '').join('\n')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// **Charged per file over the union of the lines its references cover, never per reference.**
|
|
80
|
+
// Summing the references instead double-counts the two ways one file can be cited twice — two
|
|
81
|
+
// unresolved headings each charged at the whole file, and a `##` section cited beside one of its own
|
|
82
|
+
// `###` children, which `section()` already returns inside the parent. Either one could push the
|
|
83
|
+
// scoped figure above the whole one and print a *negative* saving, which is the single direction this
|
|
84
|
+
// measurement must not be able to move (joshuafolkken/kit#1776 review round 1).
|
|
85
|
+
function scoped_file_cost(markdown: string, headings: ReadonlyArray<string>): Cost {
|
|
86
|
+
const charged = charged_lines(markdown, headings)
|
|
87
|
+
|
|
88
|
+
if (charged === undefined) return cost_of(markdown)
|
|
89
|
+
|
|
90
|
+
const lines = markdown.split('\n')
|
|
91
|
+
|
|
92
|
+
return total(contiguous_runs(charged).map((run) => cost_of(run_text(lines, run))))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const read_set_cost = { cost_of, scoped_file_cost, total }
|
|
96
|
+
|
|
97
|
+
export type { Cost }
|
|
98
|
+
export { read_set_cost }
|
|
@@ -147,6 +147,7 @@ function splice_declaration(
|
|
|
147
147
|
if (index === input.first) return [...input.rendered]
|
|
148
148
|
if (input.dropped.has(index)) return []
|
|
149
149
|
|
|
150
|
+
// eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- one return type (sonarjs/function-return-type)
|
|
150
151
|
return [line]
|
|
151
152
|
})
|
|
152
153
|
}
|
|
@@ -301,7 +301,7 @@ function reported_references(
|
|
|
301
301
|
.flatMap((reference) => {
|
|
302
302
|
const state = reported_state(states, reference)
|
|
303
303
|
|
|
304
|
-
return state === undefined ? [] :
|
|
304
|
+
return state === undefined ? [] : { reference, state }
|
|
305
305
|
})
|
|
306
306
|
}
|
|
307
307
|
|
|
@@ -73,9 +73,10 @@ function order_pairs(
|
|
|
73
73
|
const blocked = find_child(children, { repo: epic_repo, number: link.blocked })
|
|
74
74
|
if (blocker === undefined || blocked === undefined) return []
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
if (!is_open_local(blocker, current_repo) || !is_open_local(blocked, current_repo)) return []
|
|
77
|
+
|
|
78
|
+
// eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- one return type (sonarjs/function-return-type)
|
|
79
|
+
return [{ blocker, blocked }]
|
|
79
80
|
})
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -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 = {
|