@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
package/scripts/git/constants.ts
CHANGED
|
@@ -19,9 +19,7 @@ const UNTRACKED_FILES_FLAG = '--untracked-files=all'
|
|
|
19
19
|
const GIT_BINARY_KEY = 'JOSH_GIT_BINARY'
|
|
20
20
|
|
|
21
21
|
function get_git_command(): string {
|
|
22
|
-
if (platform() === 'win32')
|
|
23
|
-
return String.raw`"C:\Program Files\Git\cmd\git.exe"`
|
|
24
|
-
}
|
|
22
|
+
if (platform() === 'win32') return String.raw`"C:\Program Files\Git\cmd\git.exe"`
|
|
25
23
|
|
|
26
24
|
return GIT_COMMAND_UNIX
|
|
27
25
|
}
|
|
@@ -31,9 +29,7 @@ function get_git_command_for_spawn(): string {
|
|
|
31
29
|
|
|
32
30
|
if (binary !== undefined && binary !== '') return binary
|
|
33
31
|
|
|
34
|
-
if (platform() === 'win32')
|
|
35
|
-
return String.raw`C:\Program Files\Git\cmd\git.exe`
|
|
36
|
-
}
|
|
32
|
+
if (platform() === 'win32') return String.raw`C:\Program Files\Git\cmd\git.exe`
|
|
37
33
|
|
|
38
34
|
return GIT_COMMAND_UNIX
|
|
39
35
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { PORCELAIN_FLAG, UNTRACKED_FILES_FLAG } from './constants'
|
|
2
|
+
import { git_diff_reads } from './git-diff-reads'
|
|
2
3
|
import { create_spawn_error } from './git-execa-error'
|
|
4
|
+
import { git_pre_push_hook } from './git-pre-push-hook'
|
|
3
5
|
import { git_push_transport } from './git-push-transport'
|
|
4
6
|
import { git_spawn } from './git-spawn'
|
|
5
7
|
import { git_spawn_sync } from './git-spawn-sync'
|
|
@@ -73,211 +75,6 @@ async function diff_cached(file_path: string): Promise<string> {
|
|
|
73
75
|
return await git_spawn.read(['diff', '--cached', file_path])
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
const REFS_REMOTES_ORIGIN_PREFIX = 'refs/remotes/origin/'
|
|
77
|
-
const NAME_ONLY_FLAG = '--name-only'
|
|
78
|
-
const DEFAULT_BRANCH_FALLBACK = 'main'
|
|
79
|
-
|
|
80
|
-
async function get_default_branch(): Promise<string> {
|
|
81
|
-
try {
|
|
82
|
-
const output = await git_spawn.read(['symbolic-ref', 'refs/remotes/origin/HEAD'])
|
|
83
|
-
const trimmed = output.trim()
|
|
84
|
-
|
|
85
|
-
if (trimmed.startsWith(REFS_REMOTES_ORIGIN_PREFIX)) {
|
|
86
|
-
return trimmed.slice(REFS_REMOTES_ORIGIN_PREFIX.length)
|
|
87
|
-
}
|
|
88
|
-
} catch {
|
|
89
|
-
// fall through to default
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return DEFAULT_BRANCH_FALLBACK
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const MERGE_BASE_COMMAND = 'merge-base'
|
|
96
|
-
|
|
97
|
-
// **The ref the default branch is actually at**, which is not the same thing as its name
|
|
98
|
-
// (joshuafolkken/kit#1535). `get_default_branch` returns the bare name, and git resolves that to the
|
|
99
|
-
// *local* `refs/heads/<default>` — a ref nothing in this workflow advances, because merges happen on
|
|
100
|
-
// GitHub and the default branch is checked out in no work tree. This is the single source both the
|
|
101
|
-
// lane's start point and `change_base` below resolve through, so a lane cut from
|
|
102
|
-
// `refs/remotes/origin/<default>` is measured against that same commit. Measured without it: a lane
|
|
103
|
-
// cut from a remote-tracking ref three commits ahead reported those three commits' files as its own.
|
|
104
|
-
//
|
|
105
|
-
// It falls back to the bare name where there is no remote-tracking ref — a fresh `git init`, a clone
|
|
106
|
-
// with no `origin` — which is the reading every caller had before. The one case it reads differently
|
|
107
|
-
// is a local default branch *ahead* of the remote, which this workflow does not produce and
|
|
108
|
-
// `prevent-main-commit.ts` exists to stop.
|
|
109
|
-
async function reference_exists(reference: string): Promise<boolean> {
|
|
110
|
-
try {
|
|
111
|
-
await git_spawn.read(['rev-parse', '--verify', '--quiet', reference])
|
|
112
|
-
|
|
113
|
-
return true
|
|
114
|
-
} catch {
|
|
115
|
-
return false
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function default_branch_reference(): Promise<string> {
|
|
120
|
-
const default_branch = await get_default_branch()
|
|
121
|
-
const remote_reference = `${REFS_REMOTES_ORIGIN_PREFIX}${default_branch}`
|
|
122
|
-
|
|
123
|
-
return (await reference_exists(remote_reference)) ? remote_reference : default_branch
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// The commit this branch was cut from — and the base every "changed" reading below measures
|
|
127
|
-
// against, in place of the default branch itself.
|
|
128
|
-
//
|
|
129
|
-
// **A two-dot `git diff <default-branch>` compares a moving *ref* to the working tree, and in a
|
|
130
|
-
// linked work tree that ref is shared with every other lane** (joshuafolkken/kit#1527). So when one
|
|
131
|
-
// lane merges, an unmerged lane's diff picks that lane's files up in reverse: files this branch
|
|
132
|
-
// never touched arrive in `josh review:brief`'s target list, in the digest map `josh gate` stamps,
|
|
133
|
-
// and in `josh review:round2`'s fix delta.
|
|
134
|
-
//
|
|
135
|
-
// **The merge base is the fixed point that removes it**, because it is a commit rather than a ref:
|
|
136
|
-
// an advance of the default branch that this branch is an ancestor of does not move it. It is also
|
|
137
|
-
// the right answer at both points of a run, and a lane passes through both — with the work still
|
|
138
|
-
// uncommitted `HEAD` is the cut commit and the merge base is that same commit, so the diff is the
|
|
139
|
-
// uncommitted work; once the branch commits, `HEAD` moves ahead while the merge base stays put, so
|
|
140
|
-
// the diff is the branch's whole change plus anything still uncommitted. `<default-branch>...HEAD`
|
|
141
|
-
// answers neither, because a three-dot diff ends at `HEAD` and a lane's work is uncommitted for
|
|
142
|
-
// most of its run.
|
|
143
|
-
//
|
|
144
|
-
// **It never reports less than the old reading did.** The new set is the branch's own change; the
|
|
145
|
-
// old one was that same set plus the inverse of whatever the default branch had advanced by — so
|
|
146
|
-
// this narrows the reading and cannot hide a file the branch actually changed.
|
|
147
|
-
//
|
|
148
|
-
// In a checkout sitting on the default branch the merge base *is* that branch's commit, so nothing
|
|
149
|
-
// changes there. A repository with no common ancestor to find falls back to the previous reading
|
|
150
|
-
// rather than failing the callers, all of which treat a throw as "nothing can be reused".
|
|
151
|
-
async function change_base(): Promise<string> {
|
|
152
|
-
const default_branch = await default_branch_reference()
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
return await git_spawn.read([MERGE_BASE_COMMAND, default_branch, 'HEAD'])
|
|
156
|
-
} catch {
|
|
157
|
-
return default_branch
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function diff_main(file_path: string): Promise<string> {
|
|
162
|
-
return await git_spawn.read(['diff', await change_base(), '--', file_path])
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Names only, for callers that classify a change rather than read it — `josh review:level` decides
|
|
166
|
-
// the review depth from the paths alone, and reading the whole diff to get them would be the
|
|
167
|
-
// expensive half of the thing it exists to make cheaper (joshuafolkken/kit#966).
|
|
168
|
-
//
|
|
169
|
-
// `core.quotePath=false` is not cosmetic. With git's default, a path containing any non-ASCII byte
|
|
170
|
-
// comes back C-quoted — `"prompts/\343\202\263.md"` — and a classifier testing `startsWith('prompts/')`
|
|
171
|
-
// against a string that begins with a quote character answers no. `review:level` fails safe there
|
|
172
|
-
// (non-inert wins), but a classifier that answered `skip` would drop a change it is meant to catch,
|
|
173
|
-
// so the quoting is turned off at the source every reader shares (joshuafolkken/kit#907).
|
|
174
|
-
const NO_PATH_QUOTING: ReadonlyArray<string> = ['-c', 'core.quotePath=false']
|
|
175
|
-
|
|
176
|
-
// Repository-root-relative paths, whatever the checkout is configured to prefer. `diff.relative` is
|
|
177
|
-
// a per-repository setting a person may have turned on for their own reading, and with it `git diff
|
|
178
|
-
// --name-only` run from a subdirectory prints cwd-relative paths — which every caller of this
|
|
179
|
-
// reading then joins onto the repository root (joshuafolkken/kit#1257). The flag pins the spelling
|
|
180
|
-
// at the source rather than leaving each caller to discover the configuration.
|
|
181
|
-
const NO_RELATIVE_PATHS = '--no-relative'
|
|
182
|
-
|
|
183
|
-
// **Rename detection is turned off because this listing is a description of a tree, not a summary of
|
|
184
|
-
// a change** (joshuafolkken/kit#1533). With git's default `diff.renames=true`, a rename prints only
|
|
185
|
-
// its *destination*: `git diff --name-only <base>` across a commit that moved `scripts/init-logic.ts`
|
|
186
|
-
// to `scripts/init/init-logic.ts` names the second path and never the first.
|
|
187
|
-
//
|
|
188
|
-
// That omission is what breaks `josh gate`'s reuse. The green record it compares is `base` plus a
|
|
189
|
-
// digest per listed path, and the whole claim that the record still describes the tree rests on one
|
|
190
|
-
// property: **every path that differs from `base` is in the list**, so `base` plus the digests
|
|
191
|
-
// determines the tracked tree completely. A rename's source path differs from `base` — it is gone —
|
|
192
|
-
// and it is not in the list. So a tree in which the file was renamed away and a tree in which it has
|
|
193
|
-
// since come back produce the *same* record: the returned file matches `base`, so it enters no diff,
|
|
194
|
-
// while the destination is an addition either way. The gate then reuses a green taken on the first
|
|
195
|
-
// tree for the second, without running one check on it, and a duplicated module that lint, the type
|
|
196
|
-
// check and the unit suite would all have failed on is committed on a "passed".
|
|
197
|
-
//
|
|
198
|
-
// Off, a rename is listed as its two halves — a delete and an add — and the delete is what makes the
|
|
199
|
-
// two trees compare unequal. `review-tree.ts` already records a listed path the tree does not hold
|
|
200
|
-
// as `ABSENT_DIGEST` rather than dropping it, which is exactly the entry this flag produces.
|
|
201
|
-
//
|
|
202
|
-
// **Every other reader moves in the safe direction.** `josh review:level` sees
|
|
203
|
-
// one more path and can only widen; `josh lint:related` and `josh test:related` drop what the tree
|
|
204
|
-
// no longer holds through `changed-file-scope.ts`, which they already had to do for a plain delete.
|
|
205
|
-
const NO_RENAME_DETECTION = '--no-renames'
|
|
206
|
-
|
|
207
|
-
// The commit `change_base` resolves to. Every "changed" reading below is a diff against it, so a set
|
|
208
|
-
// of changed paths — or a map of their digests — means nothing without it: fetch an advanced default
|
|
209
|
-
// branch and rebase onto it, and each digest can stay identical while the rest of the tree is
|
|
210
|
-
// replaced by code no check has read (joshuafolkken/kit#1328). A rebase still invalidates a stamp
|
|
211
|
-
// taken against it — the rebase moves `HEAD`, and with it the merge base — while another lane
|
|
212
|
-
// merging into the shared default branch no longer does, because it moves neither
|
|
213
|
-
// (joshuafolkken/kit#1527).
|
|
214
|
-
async function change_base_commit(): Promise<string> {
|
|
215
|
-
return await git_spawn.read(['rev-parse', await change_base()])
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function diff_main_names(): Promise<string> {
|
|
219
|
-
return await git_spawn.read([
|
|
220
|
-
...NO_PATH_QUOTING,
|
|
221
|
-
'diff',
|
|
222
|
-
NAME_ONLY_FLAG,
|
|
223
|
-
NO_RELATIVE_PATHS,
|
|
224
|
-
NO_RENAME_DETECTION,
|
|
225
|
-
await change_base(),
|
|
226
|
-
'--',
|
|
227
|
-
])
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function diff_cached_names(): Promise<string> {
|
|
231
|
-
return await git_spawn.read([
|
|
232
|
-
...NO_PATH_QUOTING,
|
|
233
|
-
'diff',
|
|
234
|
-
'--cached',
|
|
235
|
-
NAME_ONLY_FLAG,
|
|
236
|
-
NO_RELATIVE_PATHS,
|
|
237
|
-
NO_RENAME_DETECTION,
|
|
238
|
-
])
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// The branch diff as `added\tdeleted\tpath` rows — the same base and flags as `diff_main_names`, so
|
|
242
|
-
// the size read against and the name read agree on what "changed" is. A binary file prints `-` for
|
|
243
|
-
// both counts; the parser above `josh split:assess` reads it treats those as zero changed lines.
|
|
244
|
-
async function diff_main_numstat(): Promise<string> {
|
|
245
|
-
return await git_spawn.read([
|
|
246
|
-
...NO_PATH_QUOTING,
|
|
247
|
-
'diff',
|
|
248
|
-
'--numstat',
|
|
249
|
-
NO_RELATIVE_PATHS,
|
|
250
|
-
NO_RENAME_DETECTION,
|
|
251
|
-
await change_base(),
|
|
252
|
-
'--',
|
|
253
|
-
])
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Files git is not tracking yet. `git diff` never lists them, so a classifier built on the diff
|
|
257
|
-
// alone sees a change that adds a whole new module as an empty one — which is how a run adding new
|
|
258
|
-
// code could have been handed a reduced review level (joshuafolkken/kit#966).
|
|
259
|
-
//
|
|
260
|
-
// **`--full-name` and the `:/` pathspec are what make this reading agree with the diff beside it**
|
|
261
|
-
// (joshuafolkken/kit#1257). `git diff --name-only` prints repository-root-relative paths for the
|
|
262
|
-
// whole tree wherever it is run; `git ls-files --others` prints *cwd*-relative paths and lists only
|
|
263
|
-
// what is below cwd. Read from a subdirectory, the two halves of `changed_paths` therefore came
|
|
264
|
-
// back in two different coordinate systems, and every caller that resolves a path against the
|
|
265
|
-
// repository root — `review-tree`, `josh test:related` — turned a new file into one that does not
|
|
266
|
-
// exist, or, where the same tail exists elsewhere in the tree, into a different file entirely. The
|
|
267
|
-
// pathspec restores the whole tree; the flag restores the root-relative spelling.
|
|
268
|
-
const WHOLE_TREE_PATHSPEC = ':/'
|
|
269
|
-
|
|
270
|
-
async function untracked_names(): Promise<string> {
|
|
271
|
-
return await git_spawn.read([
|
|
272
|
-
...NO_PATH_QUOTING,
|
|
273
|
-
'ls-files',
|
|
274
|
-
'--others',
|
|
275
|
-
'--exclude-standard',
|
|
276
|
-
'--full-name',
|
|
277
|
-
WHOLE_TREE_PATHSPEC,
|
|
278
|
-
])
|
|
279
|
-
}
|
|
280
|
-
|
|
281
78
|
// One branch from `origin`, fetched by name. `gh pr checkout` did this itself after resolving the
|
|
282
79
|
// head branch through GraphQL, which a cloud session is answered 403 for (joshuafolkken/kit#1022);
|
|
283
80
|
// the resolution moved to REST and the fetch is spelled out here instead of being re-wrapped.
|
|
@@ -380,22 +177,33 @@ function is_upstream_not_set_error(error: unknown): boolean {
|
|
|
380
177
|
return cause !== undefined && is_exit_code_128(cause)
|
|
381
178
|
}
|
|
382
179
|
|
|
180
|
+
const NO_VERIFY_FLAG = '--no-verify'
|
|
181
|
+
|
|
383
182
|
// Both pushes go through `git_push_transport` rather than `git_spawn.with_output`, which is
|
|
384
183
|
// what gives them a timeout and an SSH keepalive the local git commands beside them do not need
|
|
385
184
|
// (joshuafolkken/kit#1251). The thrown error keeps the same `cause.exit_code` shape, so the 128
|
|
386
185
|
// fallback below reads it exactly as it did.
|
|
387
|
-
async function push_with_upstream(
|
|
388
|
-
|
|
186
|
+
async function push_with_upstream(
|
|
187
|
+
branch_name: string,
|
|
188
|
+
verify_flags: ReadonlyArray<string>,
|
|
189
|
+
): Promise<void> {
|
|
190
|
+
await git_push_transport.push([...verify_flags, '--set-upstream', 'origin', branch_name])
|
|
389
191
|
}
|
|
390
192
|
|
|
193
|
+
// **The push budget bounds the transfer alone** (joshuafolkken/kit#3300). The pre-push hook runs once,
|
|
194
|
+
// unbounded, before either push and outside the `try`; both transfers then skip it with `--no-verify`,
|
|
195
|
+
// so neither the timeout retry nor the `--set-upstream` fallback repeats it, and a failed hook is not
|
|
196
|
+
// mistaken for a missing upstream. A git too old to run the hook ahead leaves it to the push, as before.
|
|
391
197
|
async function push(): Promise<void> {
|
|
198
|
+
const verify_flags = (await git_pre_push_hook.run()) ? [NO_VERIFY_FLAG] : []
|
|
199
|
+
|
|
392
200
|
try {
|
|
393
|
-
await git_push_transport.push([])
|
|
201
|
+
await git_push_transport.push([...verify_flags])
|
|
394
202
|
} catch (error) {
|
|
395
203
|
if (is_upstream_not_set_error(error)) {
|
|
396
204
|
const current_branch = await branch()
|
|
397
205
|
|
|
398
|
-
await push_with_upstream(current_branch)
|
|
206
|
+
await push_with_upstream(current_branch, verify_flags)
|
|
399
207
|
|
|
400
208
|
return
|
|
401
209
|
}
|
|
@@ -566,15 +374,9 @@ const git_command = {
|
|
|
566
374
|
head_commit,
|
|
567
375
|
git_directories,
|
|
568
376
|
diff_cached,
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
change_base_commit,
|
|
573
|
-
diff_main_names,
|
|
574
|
-
diff_main_numstat,
|
|
575
|
-
untracked_names,
|
|
576
|
-
get_default_branch,
|
|
577
|
-
default_branch_reference,
|
|
377
|
+
// The default branch, the change base and every reading against it are `git-diff-reads.ts`'s
|
|
378
|
+
// (joshuafolkken/kit#3264), re-exported here so no call site, mock or spy moved with them.
|
|
379
|
+
...git_diff_reads,
|
|
578
380
|
fetch_branch,
|
|
579
381
|
merge_fast_forward,
|
|
580
382
|
fast_forward_local,
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { git_spawn } from './git-spawn'
|
|
2
|
+
|
|
3
|
+
// The default branch, the base a change is measured against, and every "changed" reading taken
|
|
4
|
+
// against that base — split out of `git-command.ts` when it reached its line limit
|
|
5
|
+
// (joshuafolkken/kit#3264). `git_command` re-exports each one under the name it always had, so the
|
|
6
|
+
// move changed no call site and no suite that mocks or spies on `git_command`.
|
|
7
|
+
|
|
8
|
+
const REFS_REMOTES_ORIGIN_PREFIX = 'refs/remotes/origin/'
|
|
9
|
+
const NAME_ONLY_FLAG = '--name-only'
|
|
10
|
+
const DEFAULT_BRANCH_FALLBACK = 'main'
|
|
11
|
+
|
|
12
|
+
async function get_default_branch(): Promise<string> {
|
|
13
|
+
try {
|
|
14
|
+
const output = await git_spawn.read(['symbolic-ref', 'refs/remotes/origin/HEAD'])
|
|
15
|
+
const trimmed = output.trim()
|
|
16
|
+
|
|
17
|
+
if (trimmed.startsWith(REFS_REMOTES_ORIGIN_PREFIX)) {
|
|
18
|
+
return trimmed.slice(REFS_REMOTES_ORIGIN_PREFIX.length)
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
// fall through to default
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return DEFAULT_BRANCH_FALLBACK
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const MERGE_BASE_COMMAND = 'merge-base'
|
|
28
|
+
|
|
29
|
+
// **The ref the default branch is actually at**, which is not the same thing as its name
|
|
30
|
+
// (joshuafolkken/kit#1535). `get_default_branch` returns the bare name, and git resolves that to the
|
|
31
|
+
// *local* `refs/heads/<default>` — a ref nothing in this workflow advances, because merges happen on
|
|
32
|
+
// GitHub and the default branch is checked out in no work tree. This is the single source both the
|
|
33
|
+
// lane's start point and `change_base` below resolve through, so a lane cut from
|
|
34
|
+
// `refs/remotes/origin/<default>` is measured against that same commit. Measured without it: a lane
|
|
35
|
+
// cut from a remote-tracking ref three commits ahead reported those three commits' files as its own.
|
|
36
|
+
//
|
|
37
|
+
// It falls back to the bare name where there is no remote-tracking ref — a fresh `git init`, a clone
|
|
38
|
+
// with no `origin` — which is the reading every caller had before. The one case it reads differently
|
|
39
|
+
// is a local default branch *ahead* of the remote, which this workflow does not produce and
|
|
40
|
+
// `prevent-main-commit.ts` exists to stop.
|
|
41
|
+
async function reference_exists(reference: string): Promise<boolean> {
|
|
42
|
+
try {
|
|
43
|
+
await git_spawn.read(['rev-parse', '--verify', '--quiet', reference])
|
|
44
|
+
|
|
45
|
+
return true
|
|
46
|
+
} catch {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function default_branch_reference(): Promise<string> {
|
|
52
|
+
const default_branch = await get_default_branch()
|
|
53
|
+
const remote_reference = `${REFS_REMOTES_ORIGIN_PREFIX}${default_branch}`
|
|
54
|
+
|
|
55
|
+
return (await reference_exists(remote_reference)) ? remote_reference : default_branch
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The commit this branch was cut from — and the base every "changed" reading below measures
|
|
59
|
+
// against, in place of the default branch itself.
|
|
60
|
+
//
|
|
61
|
+
// **A two-dot `git diff <default-branch>` compares a moving *ref* to the working tree, and in a
|
|
62
|
+
// linked work tree that ref is shared with every other lane** (joshuafolkken/kit#1527). So when one
|
|
63
|
+
// lane merges, an unmerged lane's diff picks that lane's files up in reverse: files this branch
|
|
64
|
+
// never touched arrive in `josh review:brief`'s target list, in the digest map `josh gate` stamps,
|
|
65
|
+
// and in `josh review:round2`'s fix delta.
|
|
66
|
+
//
|
|
67
|
+
// **The merge base is the fixed point that removes it**, because it is a commit rather than a ref:
|
|
68
|
+
// an advance of the default branch that this branch is an ancestor of does not move it. It is also
|
|
69
|
+
// the right answer at both points of a run, and a lane passes through both — with the work still
|
|
70
|
+
// uncommitted `HEAD` is the cut commit and the merge base is that same commit, so the diff is the
|
|
71
|
+
// uncommitted work; once the branch commits, `HEAD` moves ahead while the merge base stays put, so
|
|
72
|
+
// the diff is the branch's whole change plus anything still uncommitted. `<default-branch>...HEAD`
|
|
73
|
+
// answers neither, because a three-dot diff ends at `HEAD` and a lane's work is uncommitted for
|
|
74
|
+
// most of its run.
|
|
75
|
+
//
|
|
76
|
+
// **It never reports less than the old reading did.** The new set is the branch's own change; the
|
|
77
|
+
// old one was that same set plus the inverse of whatever the default branch had advanced by — so
|
|
78
|
+
// this narrows the reading and cannot hide a file the branch actually changed.
|
|
79
|
+
//
|
|
80
|
+
// In a checkout sitting on the default branch the merge base *is* that branch's commit, so nothing
|
|
81
|
+
// changes there. A repository with no common ancestor to find falls back to the previous reading
|
|
82
|
+
// rather than failing the callers, all of which treat a throw as "nothing can be reused".
|
|
83
|
+
async function change_base(): Promise<string> {
|
|
84
|
+
const default_branch = await default_branch_reference()
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
return await git_spawn.read([MERGE_BASE_COMMAND, default_branch, 'HEAD'])
|
|
88
|
+
} catch {
|
|
89
|
+
return default_branch
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function diff_main(file_path: string): Promise<string> {
|
|
94
|
+
return await git_spawn.read(['diff', await change_base(), '--', file_path])
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Names only, for callers that classify a change rather than read it — `josh review:level` decides
|
|
98
|
+
// the review depth from the paths alone, and reading the whole diff to get them would be the
|
|
99
|
+
// expensive half of the thing it exists to make cheaper (joshuafolkken/kit#966).
|
|
100
|
+
//
|
|
101
|
+
// `core.quotePath=false` is not cosmetic. With git's default, a path containing any non-ASCII byte
|
|
102
|
+
// comes back C-quoted — `"prompts/\343\202\263.md"` — and a classifier testing `startsWith('prompts/')`
|
|
103
|
+
// against a string that begins with a quote character answers no. `review:level` fails safe there
|
|
104
|
+
// (non-inert wins), but a classifier that answered `skip` would drop a change it is meant to catch,
|
|
105
|
+
// so the quoting is turned off at the source every reader shares (joshuafolkken/kit#907).
|
|
106
|
+
const NO_PATH_QUOTING: ReadonlyArray<string> = ['-c', 'core.quotePath=false']
|
|
107
|
+
|
|
108
|
+
// Repository-root-relative paths, whatever the checkout is configured to prefer. `diff.relative` is
|
|
109
|
+
// a per-repository setting a person may have turned on for their own reading, and with it `git diff
|
|
110
|
+
// --name-only` run from a subdirectory prints cwd-relative paths — which every caller of this
|
|
111
|
+
// reading then joins onto the repository root (joshuafolkken/kit#1257). The flag pins the spelling
|
|
112
|
+
// at the source rather than leaving each caller to discover the configuration.
|
|
113
|
+
const NO_RELATIVE_PATHS = '--no-relative'
|
|
114
|
+
|
|
115
|
+
// **Rename detection is turned off because this listing is a description of a tree, not a summary of
|
|
116
|
+
// a change** (joshuafolkken/kit#1533). With git's default `diff.renames=true`, a rename prints only
|
|
117
|
+
// its *destination*: `git diff --name-only <base>` across a commit that moved `scripts/init-logic.ts`
|
|
118
|
+
// to `scripts/init/init-logic.ts` names the second path and never the first.
|
|
119
|
+
//
|
|
120
|
+
// That omission is what breaks `josh gate`'s reuse. The green record it compares is `base` plus a
|
|
121
|
+
// digest per listed path, and the whole claim that the record still describes the tree rests on one
|
|
122
|
+
// property: **every path that differs from `base` is in the list**, so `base` plus the digests
|
|
123
|
+
// determines the tracked tree completely. A rename's source path differs from `base` — it is gone —
|
|
124
|
+
// and it is not in the list. So a tree in which the file was renamed away and a tree in which it has
|
|
125
|
+
// since come back produce the *same* record: the returned file matches `base`, so it enters no diff,
|
|
126
|
+
// while the destination is an addition either way. The gate then reuses a green taken on the first
|
|
127
|
+
// tree for the second, without running one check on it, and a duplicated module that lint, the type
|
|
128
|
+
// check and the unit suite would all have failed on is committed on a "passed".
|
|
129
|
+
//
|
|
130
|
+
// Off, a rename is listed as its two halves — a delete and an add — and the delete is what makes the
|
|
131
|
+
// two trees compare unequal. `review-tree.ts` already records a listed path the tree does not hold
|
|
132
|
+
// as `ABSENT_DIGEST` rather than dropping it, which is exactly the entry this flag produces.
|
|
133
|
+
//
|
|
134
|
+
// **Every other reader moves in the safe direction.** `josh review:level` sees
|
|
135
|
+
// one more path and can only widen; `josh lint:related` and `josh test:related` drop what the tree
|
|
136
|
+
// no longer holds through `changed-file-scope.ts`, which they already had to do for a plain delete.
|
|
137
|
+
const NO_RENAME_DETECTION = '--no-renames'
|
|
138
|
+
|
|
139
|
+
// The commit `change_base` resolves to. Every "changed" reading below is a diff against it, so a set
|
|
140
|
+
// of changed paths — or a map of their digests — means nothing without it: fetch an advanced default
|
|
141
|
+
// branch and rebase onto it, and each digest can stay identical while the rest of the tree is
|
|
142
|
+
// replaced by code no check has read (joshuafolkken/kit#1328). A rebase still invalidates a stamp
|
|
143
|
+
// taken against it — the rebase moves `HEAD`, and with it the merge base — while another lane
|
|
144
|
+
// merging into the shared default branch no longer does, because it moves neither
|
|
145
|
+
// (joshuafolkken/kit#1527).
|
|
146
|
+
async function change_base_commit(): Promise<string> {
|
|
147
|
+
return await git_spawn.read(['rev-parse', await change_base()])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function diff_main_names(): Promise<string> {
|
|
151
|
+
return await git_spawn.read([
|
|
152
|
+
...NO_PATH_QUOTING,
|
|
153
|
+
'diff',
|
|
154
|
+
NAME_ONLY_FLAG,
|
|
155
|
+
NO_RELATIVE_PATHS,
|
|
156
|
+
NO_RENAME_DETECTION,
|
|
157
|
+
await change_base(),
|
|
158
|
+
'--',
|
|
159
|
+
])
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function diff_cached_names(): Promise<string> {
|
|
163
|
+
return await git_spawn.read([
|
|
164
|
+
...NO_PATH_QUOTING,
|
|
165
|
+
'diff',
|
|
166
|
+
'--cached',
|
|
167
|
+
NAME_ONLY_FLAG,
|
|
168
|
+
NO_RELATIVE_PATHS,
|
|
169
|
+
NO_RENAME_DETECTION,
|
|
170
|
+
])
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The branch diff as `added\tdeleted\tpath` rows — the same base and flags as `diff_main_names`, so
|
|
174
|
+
// the size read against and the name read agree on what "changed" is. A binary file prints `-` for
|
|
175
|
+
// both counts; the parser above `josh split:assess` reads it treats those as zero changed lines.
|
|
176
|
+
async function diff_main_numstat(): Promise<string> {
|
|
177
|
+
return await git_spawn.read([
|
|
178
|
+
...NO_PATH_QUOTING,
|
|
179
|
+
'diff',
|
|
180
|
+
'--numstat',
|
|
181
|
+
NO_RELATIVE_PATHS,
|
|
182
|
+
NO_RENAME_DETECTION,
|
|
183
|
+
await change_base(),
|
|
184
|
+
'--',
|
|
185
|
+
])
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Files git is not tracking yet. `git diff` never lists them, so a classifier built on the diff
|
|
189
|
+
// alone sees a change that adds a whole new module as an empty one — which is how a run adding new
|
|
190
|
+
// code could have been handed a reduced review level (joshuafolkken/kit#966).
|
|
191
|
+
//
|
|
192
|
+
// **`--full-name` and the `:/` pathspec are what make this reading agree with the diff beside it**
|
|
193
|
+
// (joshuafolkken/kit#1257). `git diff --name-only` prints repository-root-relative paths for the
|
|
194
|
+
// whole tree wherever it is run; `git ls-files --others` prints *cwd*-relative paths and lists only
|
|
195
|
+
// what is below cwd. Read from a subdirectory, the two halves of `changed_paths` therefore came
|
|
196
|
+
// back in two different coordinate systems, and every caller that resolves a path against the
|
|
197
|
+
// repository root — `review-tree`, `josh test:related` — turned a new file into one that does not
|
|
198
|
+
// exist, or, where the same tail exists elsewhere in the tree, into a different file entirely. The
|
|
199
|
+
// pathspec restores the whole tree; the flag restores the root-relative spelling.
|
|
200
|
+
const WHOLE_TREE_PATHSPEC = ':/'
|
|
201
|
+
|
|
202
|
+
async function untracked_names(): Promise<string> {
|
|
203
|
+
return await git_spawn.read([
|
|
204
|
+
...NO_PATH_QUOTING,
|
|
205
|
+
'ls-files',
|
|
206
|
+
'--others',
|
|
207
|
+
'--exclude-standard',
|
|
208
|
+
'--full-name',
|
|
209
|
+
WHOLE_TREE_PATHSPEC,
|
|
210
|
+
])
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const git_diff_reads = {
|
|
214
|
+
change_base,
|
|
215
|
+
change_base_commit,
|
|
216
|
+
default_branch_reference,
|
|
217
|
+
diff_cached_names,
|
|
218
|
+
diff_main,
|
|
219
|
+
diff_main_names,
|
|
220
|
+
diff_main_numstat,
|
|
221
|
+
get_default_branch,
|
|
222
|
+
untracked_names,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export { git_diff_reads }
|
package/scripts/git/git-error.ts
CHANGED
|
@@ -12,21 +12,15 @@ function get_stderr_from_error(cause: Error): string | undefined {
|
|
|
12
12
|
function get_error_message_from_cause(cause: Error): string | undefined {
|
|
13
13
|
const message = cause.message.trim()
|
|
14
14
|
|
|
15
|
-
if (message.length > 0)
|
|
16
|
-
return message
|
|
17
|
-
}
|
|
15
|
+
if (message.length > 0) return message
|
|
18
16
|
|
|
19
17
|
return get_stderr_from_error(cause)
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
function get_cause_message(cause: unknown): string | undefined {
|
|
23
|
-
if (cause instanceof Error)
|
|
24
|
-
return get_error_message_from_cause(cause)
|
|
25
|
-
}
|
|
21
|
+
if (cause instanceof Error) return get_error_message_from_cause(cause)
|
|
26
22
|
|
|
27
|
-
if (typeof cause === 'string')
|
|
28
|
-
return cause.trim()
|
|
29
|
-
}
|
|
23
|
+
if (typeof cause === 'string') return cause.trim()
|
|
30
24
|
|
|
31
25
|
return undefined
|
|
32
26
|
}
|
|
@@ -34,9 +28,7 @@ function get_cause_message(cause: unknown): string | undefined {
|
|
|
34
28
|
function display_error_details(cause: unknown): void {
|
|
35
29
|
const cause_message = get_cause_message(cause)
|
|
36
30
|
|
|
37
|
-
if (cause_message === undefined || cause_message.length === 0)
|
|
38
|
-
return
|
|
39
|
-
}
|
|
31
|
+
if (cause_message === undefined || cause_message.length === 0) return
|
|
40
32
|
|
|
41
33
|
console.error('')
|
|
42
34
|
console.error('💡 Details:', cause_message)
|