@joshuafolkken/kit 1.1166.0 → 1.1229.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/settings.json +2 -2
- package/.claude/skills/dependency-update/SKILL.md +4 -8
- package/.claude/skills/epic-commands/SKILL.md +2 -2
- package/.claude/skills/epic-commands/epic-bundle.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +2 -4
- package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +9 -5
- package/.claude/skills/workflow-commands/backlogrun-park.md +29 -9
- package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun-steps.md +24 -15
- package/.claude/skills/workflow-commands/chain-rule.md +1 -1
- package/.claude/skills/workflow-commands/delegation.md +2 -2
- package/.claude/skills/workflow-commands/followup.md +5 -0
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-scout.md +1 -1
- package/.claude/skills/workflow-commands/kickoff.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
- package/.claude/skills/workflow-commands/pre-gate-cut.md +10 -5
- package/.claude/skills/workflow-commands/prerequisite.md +2 -2
- package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
- package/.codex/hooks.json +2 -2
- package/.cursorrules +6 -41
- package/.github/actions/setup-node/action.yml +1 -1
- package/.github/workflows/auto-tag.yml +3 -4
- package/.github/workflows/ci.yml +47 -33
- package/.github/workflows/dependabot-auto-merge.yml +13 -6
- package/.github/workflows/publish.yml +48 -4
- package/.github/workflows/sonar-qube.yml +3 -0
- package/.pnpmfile.mjs +4 -3
- package/.prettierignore +8 -2
- package/CLAUDE.md +18 -20
- package/README.md +26 -19
- package/SECURITY.md +15 -1
- package/dist/CLAUDE.md +18 -20
- package/dist/commands/document-section-cli.js +734 -0
- package/dist/hooks/audit-provision.js +346 -0
- package/dist/hooks/batch-guard.js +5 -4
- package/dist/hooks/{chunk-DYAVAM6S.js → chunk-55HHG5PX.js} +1405 -631
- package/dist/hooks/{chunk-A6P7PN7P.js → chunk-5VMKCCC4.js} +25 -10
- package/dist/hooks/chunk-5VNDSNG3.js +12 -0
- package/dist/hooks/{chunk-5KTCMCOT.js → chunk-6HJJXRBX.js} +4 -2
- package/dist/hooks/chunk-CDY4NFQG.js +15 -0
- package/dist/hooks/chunk-ELCN5LCH.js +135 -0
- package/dist/hooks/{chunk-ENA7P5FB.js → chunk-F56MFS4B.js} +3 -3
- package/dist/hooks/{chunk-ANJ2WGFD.js → chunk-FIVDHQGT.js} +7 -6
- package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
- package/dist/hooks/{chunk-GU5MATVE.js → chunk-JUDKDCDP.js} +499 -389
- package/dist/hooks/{chunk-ZDZ555YH.js → chunk-NV2JRZZO.js} +743 -909
- package/dist/hooks/{chunk-V2BTOZHT.js → chunk-RNWHN5BP.js} +3 -3
- package/dist/hooks/{chunk-URJA6XY4.js → chunk-TQNPDZNH.js} +4 -4
- package/dist/hooks/{chunk-GYNGP7HI.js → chunk-VC3PZPZO.js} +1 -1
- package/dist/hooks/codex-hook-adapter.js +14 -12
- package/dist/hooks/duplicate-read-guard.js +6 -5
- package/dist/hooks/format-edited.js +4 -3
- package/dist/hooks/inputs.json +232 -210
- package/dist/hooks/investigation-guard.js +6 -5
- package/dist/hooks/pretool-guard.js +12 -10
- package/dist/hooks/session-lang.js +2 -4
- package/dist/hooks/stop-guard.js +81 -59
- package/dist/josh.js +872 -75
- package/dist/version/index.d.ts +7 -2
- package/dist/version/index.js +152 -75
- package/eslint/base.js +23 -1
- package/eslint/rules/code-quality.js +3 -0
- package/eslint/rules/early-return-one-liner.js +98 -0
- package/eslint/rules/unicorn.js +16 -1
- package/package.json +7 -6
- package/pnpm-workspace.yaml +9 -4
- package/prettier/basic.d.ts +3 -1
- package/prettier/index.d.ts +3 -1
- package/prettier/static.d.ts +3 -1
- package/prompts/coding-standards.md +1 -1
- package/prompts/collaboration-workflow/file-edits.md +4 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/operating-rules.md +17 -9
- package/prompts/collaboration-workflow/overview.md +2 -2
- package/prompts/collaboration-workflow/principles.md +14 -12
- package/prompts/collaboration-workflow/report-format.md +1 -1
- package/prompts/collaboration-workflow/residency.md +1 -1
- package/prompts/collaboration-workflow/rule-delivery.md +6 -6
- package/prompts/collaboration-workflow/shell-body.md +6 -6
- package/prompts/collaboration-workflow/turn-batching.md +1 -1
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
- package/prompts/collaboration-workflow/wip-cap.md +10 -10
- package/prompts/review-rubric.md +1 -2
- package/prompts/review.md +1 -1
- package/scripts/agent/agent-headless.ts +29 -0
- package/scripts/agent/agent-launch-environment.ts +6 -2
- package/scripts/agent/codex-agent-argv.ts +2 -1
- package/scripts/auto-ok/auto-ok-cli.ts +1 -1
- package/scripts/backlog/backlog-arrival.ts +3 -3
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-args.ts +135 -0
- package/scripts/backlog/backlog-drive-cli.ts +11 -137
- package/scripts/backlog/backlog-drive-launch.ts +39 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
- package/scripts/backlog/backlog-drive-named.ts +3 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
- package/scripts/backlog/backlog-drive-owner.ts +1 -1
- package/scripts/backlog/backlog-drive-restore.ts +1 -1
- package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
- package/scripts/backlog/backlog-drive.ts +1 -1
- package/scripts/backlog/backlog-next.ts +31 -9
- package/scripts/backlog/backlog-offer-cli.ts +6 -6
- package/scripts/backlog/backlog-offer.ts +1 -1
- package/scripts/backlog/backlog-plan-cli.ts +7 -6
- package/scripts/backlog/backlog-pool.ts +57 -11
- package/scripts/backlog/backlog-rank.ts +18 -9
- package/scripts/backlog/backlog-ready.ts +1 -1
- package/scripts/backlog/backlog-restructure.ts +152 -0
- package/scripts/backlog/backlog-scope.ts +1 -1
- package/scripts/backlog/backlog-stalled-detect.ts +2 -2
- package/scripts/backlog/backlog-stalled.ts +1 -1
- package/scripts/backlog/backlog-waves.ts +15 -17
- package/scripts/build/build-codex-hooks.ts +3 -1
- package/scripts/build/build-commands.ts +66 -0
- package/scripts/build/build-hooks.ts +2 -1
- package/scripts/build/build-library.ts +12 -3
- package/scripts/build/build.ts +70 -0
- package/scripts/build/local-bin.ts +3 -13
- package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
- package/scripts/cost-runtime/cost-verdict.ts +15 -28
- package/scripts/delegation/investigation-reads.ts +3 -1
- package/scripts/discovery/repo-origin.ts +5 -5
- package/scripts/doctor/doctor-consumer.ts +2 -2
- package/scripts/doctor/doctor-io.ts +2 -3
- package/scripts/document/capped-print-part.ts +30 -0
- package/scripts/document/capped-print.ts +3 -4
- package/scripts/document/document-byte-budget.ts +11 -2
- package/scripts/document/document-scan.ts +117 -0
- package/scripts/document/document-section.ts +143 -16
- package/scripts/document/entry-read-budget.ts +5 -3
- package/scripts/document/entry-read-set.ts +10 -89
- package/scripts/document/read-set-cost.ts +98 -0
- package/scripts/epic/epic-add-body.ts +1 -0
- package/scripts/epic/epic-audit-checks.ts +1 -1
- package/scripts/epic/epic-audit-rationale.ts +4 -3
- package/scripts/epic/epic-bundle-cli.ts +14 -3
- package/scripts/epic/epic-bundle-poll.ts +41 -0
- package/scripts/epic/epic-busy.ts +1 -1
- package/scripts/epic/epic-candidate-confirm.ts +3 -2
- package/scripts/epic/epic-classify.ts +1 -1
- package/scripts/epic/epic-cli-argv.ts +78 -46
- package/scripts/epic/epic-cli-remove.ts +11 -19
- package/scripts/epic/epic-cli.ts +74 -73
- package/scripts/epic/epic-close-comment.ts +1 -1
- package/scripts/epic/epic-close.ts +1 -1
- package/scripts/epic/epic-cross-repo.ts +5 -5
- package/scripts/epic/epic-graph.ts +1 -1
- package/scripts/epic/epic-index.ts +1 -1
- package/scripts/epic/epic-issue.ts +2 -6
- package/scripts/epic/epic-lane-offer.ts +2 -2
- package/scripts/epic/epic-next.ts +29 -42
- package/scripts/epic/epic-solo-stale.ts +1 -1
- package/scripts/epic/epic-triage.ts +8 -4
- package/scripts/epic/epic-validate.ts +1 -1
- package/scripts/followup/git-followup-finish.ts +2 -2
- package/scripts/followup/git-followup-label.ts +2 -2
- package/scripts/followup/git-followup-merged.ts +2 -2
- package/scripts/followup/git-followup-stages.ts +9 -0
- package/scripts/followup/git-pr-followup-checks.ts +142 -0
- package/scripts/followup/git-pr-followup.ts +5 -130
- package/scripts/gate/core-budget.ts +69 -1
- package/scripts/gate/gate-plan.ts +47 -7
- package/scripts/gate/gate-skip.ts +38 -7
- package/scripts/gate/hook-gate-reuse.ts +12 -10
- package/scripts/gate/pre-commit-type-check.ts +4 -1
- package/scripts/gate/project-checks.ts +6 -9
- package/scripts/gate/type-check-step.ts +2 -3
- package/scripts/gate/verification-gate.ts +27 -8
- package/scripts/gh/git-gh-api-path.ts +2 -2
- package/scripts/gh/git-gh-issue-rest.ts +3 -3
- package/scripts/gh/git-gh-issue-write.ts +53 -7
- package/scripts/gh/git-gh-issue.ts +5 -3
- package/scripts/gh/git-gh-pr-read.ts +68 -19
- package/scripts/gh/git-gh-pr-rest.ts +1 -1
- package/scripts/gh/git-gh-pr.ts +5 -1
- package/scripts/gh/git-pr-ai-review.ts +1 -1
- package/scripts/gh/git-pr-checks-eval.ts +9 -3
- package/scripts/gh/git-pr-checks-parse.ts +1 -1
- package/scripts/gh/git-pr-checks-watch.ts +6 -5
- package/scripts/gh/git-pr-checks.ts +18 -6
- package/scripts/gh/git-pr-coderabbit.ts +1 -1
- package/scripts/gh/git-pr-error.ts +1 -3
- package/scripts/gh/git-pr.ts +9 -13
- package/scripts/gh/github-issue-url.ts +2 -2
- package/scripts/git/constants.ts +12 -6
- package/scripts/git/git-command.ts +20 -218
- package/scripts/git/git-diff-reads.ts +225 -0
- package/scripts/git/git-error.ts +4 -12
- package/scripts/git/git-pre-push-hook.ts +139 -0
- package/scripts/git/git-prompt.ts +2 -6
- package/scripts/git/{schemas.ts → git-schemas.ts} +2 -0
- package/scripts/git/git-spawn-sync.ts +3 -1
- package/scripts/git/git-spawn.ts +1 -1
- package/scripts/git/git-staging.ts +4 -6
- package/scripts/git/git-status.ts +3 -9
- package/scripts/git/main-merge.ts +57 -21
- package/scripts/hooks/codex-hook-adapter.ts +1 -1
- package/scripts/hooks/edited-cspell.ts +2 -4
- package/scripts/hooks/format-edited-file.ts +3 -12
- package/scripts/hooks/pre-push-unit.ts +31 -9
- package/scripts/hooks/reserved-run.ts +2 -2
- package/scripts/hooks/run-hook.sh +5 -4
- package/scripts/hooks/stop-guard.ts +5 -3
- package/scripts/init/init-actions.ts +0 -5
- package/scripts/init/init-ai-copy.ts +13 -2
- package/scripts/init/init-logic-json-merge.ts +19 -0
- package/scripts/init/init-logic-secretlint.ts +7 -7
- package/scripts/init/init-logic-workspace.ts +41 -2
- package/scripts/init/init-logic.ts +16 -26
- package/scripts/init/init.ts +5 -15
- package/scripts/init/initial-commit.ts +4 -4
- package/scripts/init/kit-development-versions.ts +40 -0
- package/scripts/init/kit-setup-state.ts +2 -2
- package/scripts/init/start-exec.ts +68 -19
- package/scripts/init/start-setup-pr.ts +9 -9
- package/scripts/init/start-steps.ts +6 -10
- package/scripts/issue/defect-rate-cli.ts +1 -1
- package/scripts/issue/git-next-issues.ts +1 -1
- package/scripts/issue/issue-auto-ok.ts +1 -1
- package/scripts/issue/issue-backlinks-cli.ts +2 -2
- package/scripts/issue/issue-backlinks.ts +9 -9
- package/scripts/issue/issue-comment-cli.ts +19 -13
- package/scripts/issue/issue-file-cli.ts +45 -18
- package/scripts/issue/issue-file.ts +24 -1
- package/scripts/issue/issue-scout-closed.ts +1 -1
- package/scripts/issue/issue-state-cli.ts +30 -74
- package/scripts/josh/josh-command-map.ts +2 -0
- package/scripts/josh/josh-command-types.ts +11 -0
- package/scripts/josh/josh-commands-ai.ts +4 -4
- package/scripts/josh/josh-commands-backlog.ts +4 -1
- package/scripts/josh/josh-commands-development.ts +11 -1
- package/scripts/josh/josh-commands-document.ts +1 -0
- package/scripts/josh/josh-commands-lane.ts +22 -0
- package/scripts/josh/josh-commands-maintenance.ts +4 -4
- package/scripts/josh/josh-commands-run.ts +12 -7
- package/scripts/josh/josh-in-process.ts +28 -9
- package/scripts/josh/josh-logic.ts +33 -15
- package/scripts/josh/process-identity.ts +1 -3
- package/scripts/josh/session-language-cli.ts +3 -4
- package/scripts/lane/lane-await.ts +5 -1
- package/scripts/lane/lane-child-invocation.ts +2 -2
- package/scripts/lane/lane-cli.ts +3 -3
- package/scripts/lane/lane-dispatch.ts +14 -10
- package/scripts/lane/lane-environment.ts +5 -1
- package/scripts/lane/lane-handoff.ts +1 -1
- package/scripts/lane/lane-install.ts +2 -13
- package/scripts/lane/lane-launch-cli.ts +35 -3
- package/scripts/lane/lane-ledger.ts +145 -0
- package/scripts/lane/lane-leftover.ts +1 -1
- package/scripts/lane/lane-load.ts +108 -0
- package/scripts/lane/lane-measure-cli.ts +149 -0
- package/scripts/lane/lane-occupancy.ts +3 -0
- package/scripts/lane/lane-reap.ts +1 -3
- package/scripts/lane/lane-relaunch.ts +1 -1
- package/scripts/lane/lane-stats.ts +224 -0
- package/scripts/lane/lane-vacant.ts +36 -0
- package/scripts/lane/openai-lane-ship.ts +2 -2
- package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
- package/scripts/lane/openai-lane-supervisor.ts +7 -165
- package/scripts/lane/openai-review-broker.ts +2 -4
- package/scripts/lib/ancestor-directories.ts +35 -0
- package/scripts/lib/animation-helpers.ts +2 -2
- package/scripts/lib/buffered-process.ts +3 -7
- package/scripts/lib/cli-flags.ts +71 -1
- package/scripts/lib/spawn-exit.ts +2 -2
- package/scripts/lib/timeouts.ts +35 -2
- package/scripts/lines/line-budget.ts +4 -6
- package/scripts/notify/telegram-notify.ts +26 -1
- package/scripts/notify/telegram-test.ts +6 -0
- package/scripts/observations/observation-ledger-line.ts +6 -6
- package/scripts/observations/observation-ledger-migrate.ts +55 -4
- package/scripts/observations/observation-ledger.ts +26 -7
- package/scripts/overrides/lockfile-overrides.ts +1 -1
- package/scripts/overrides/overrides-logic.ts +1 -1
- package/scripts/propagate/propagate-git.ts +1 -1
- package/scripts/propagate/propagate-publish.ts +2 -2
- package/scripts/propagate/propagate-steps.ts +20 -15
- package/scripts/propagate/propagate.ts +36 -27
- package/scripts/refactor/import-graph.ts +18 -1
- package/scripts/refactor/refactor-lint.ts +4 -4
- package/scripts/repo/repository-labels.ts +2 -2
- package/scripts/repo/required-checks-logic.ts +1 -3
- package/scripts/review/live-evidence.ts +2 -4
- package/scripts/review/review-brief-cli.ts +1 -1
- package/scripts/review/review-finding-ledger.ts +1 -1
- package/scripts/review/review-findings-cli.ts +1 -3
- package/scripts/rules/decision-oracle-batch.ts +128 -0
- package/scripts/rules/decision-oracle-tokens.ts +21 -0
- package/scripts/rules/decision-oracle.ts +8 -120
- package/scripts/rules/delivered-rules.ts +26 -182
- package/scripts/rules/direct-filing.ts +1 -1
- package/scripts/rules/early-heartbeat.ts +3 -3
- package/scripts/rules/git-argv.ts +1 -1
- package/scripts/rules/git-force.ts +1 -1
- package/scripts/rules/implementation-cut.ts +2 -2
- package/scripts/rules/index-guard.ts +1 -1
- package/scripts/rules/issue-comments.ts +118 -0
- package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
- package/scripts/rules/lane-background.ts +2 -2
- package/scripts/rules/lane-park.ts +18 -0
- package/scripts/rules/lane-split-park.ts +128 -0
- package/scripts/rules/oracle-firing.ts +1 -1
- package/scripts/rules/poll-loop.ts +1 -1
- package/scripts/rules/pre-gate-cut.ts +3 -3
- package/scripts/rules/protected-files.ts +59 -4
- package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
- package/scripts/rules/shell-body-trigger.ts +1 -1
- package/scripts/rules/stop-rules.ts +18 -7
- package/scripts/rules/test-declared-commit.ts +4 -4
- package/scripts/rules/wip-cap.ts +63 -0
- package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
- package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
- package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
- package/scripts/run/{run-carry.ts → carry/run-carry.ts} +12 -6
- package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
- package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +3 -3
- package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +2 -2
- package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
- package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
- package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
- package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
- package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
- package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
- package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
- package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
- package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
- package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
- package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
- package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
- package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
- package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
- package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
- package/scripts/run/run-ending.ts +2 -2
- package/scripts/run/run-halfrun-resume.ts +6 -1
- package/scripts/run/run-headless.ts +1 -1
- package/scripts/run/run-invocation.ts +85 -84
- package/scripts/run/run-liveness.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +1 -1
- package/scripts/run/run-preflight.ts +6 -6
- package/scripts/run/run-prep-cli.ts +1 -1
- package/scripts/run/run-prrun-resume.ts +1 -1
- package/scripts/run/run-report-cli.ts +4 -4
- package/scripts/run/run-report.ts +2 -2
- package/scripts/run/run-status-cli.ts +1 -1
- package/scripts/run/run-step-cli.ts +3 -3
- package/scripts/run/run-step.ts +2 -2
- package/scripts/run/run-stop-notify.ts +1 -1
- package/scripts/run/run-stranded-detect.ts +4 -4
- package/scripts/run/run-stranded.ts +2 -2
- package/scripts/run/run-watcher-guard-cli.ts +1 -1
- package/scripts/run/run-watcher-guard.ts +5 -3
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +29 -8
- package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
- package/scripts/run/ship/run-ship-lock.ts +50 -0
- package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +13 -0
- package/scripts/run/ship/run-ship-pre-detach.ts +66 -0
- package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
- package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
- package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +10 -6
- package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
- package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
- package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +13 -0
- package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +15 -8
- package/scripts/run/ship/run-ship-stop-text.ts +13 -0
- package/scripts/run/ship/run-ship-sync.ts +128 -0
- package/scripts/run/{run-ship.ts → ship/run-ship.ts} +6 -0
- package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
- package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +31 -9
- package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
- package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +99 -17
- package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
- package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
- package/scripts/run/wake/run-wake-restart.ts +58 -0
- package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
- package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
- package/scripts/safe-chain/preinstall-command.ts +20 -21
- package/scripts/safe-chain/project-config.ts +19 -15
- package/scripts/sync/sync-ai-files.ts +1 -1
- package/scripts/sync/sync-configs.ts +2 -1
- package/scripts/sync/sync.ts +15 -2
- package/scripts/test/test-red-logic.ts +17 -2
- package/scripts/test/test-red.ts +8 -7
- package/scripts/test/unit-worker-share.ts +1 -0
- package/scripts/time-runtime/time-background.ts +5 -1
- package/scripts/time-runtime/time-instant.ts +3 -1
- package/scripts/time-runtime/time-spans.ts +6 -139
- package/scripts/time-runtime/time-tool-call.ts +160 -0
- package/scripts/time-runtime/time-transcript-line.ts +27 -1
- package/scripts/version/effective-upstream.ts +2 -20
- package/scripts/version/latest-corepack.ts +17 -17
- package/scripts/version/latest-regression.ts +1 -1
- package/scripts/version/latest-update.ts +4 -4
- package/scripts/version/npm-registry.ts +20 -5
- package/scripts/version/publishable-range-check.ts +2 -2
- package/scripts/version/publishable-range.ts +1 -1
- package/scripts/version/release-age.ts +48 -38
- package/scripts/version/release-hold.ts +1 -1
- package/scripts/version/safe-chain-version-update.ts +40 -0
- package/scripts/version/upgrade-shell-command.ts +48 -8
- package/scripts/version/version-check-logic.ts +23 -14
- package/scripts/version/version-commands.ts +23 -11
- package/scripts/version/version-remote.ts +28 -13
- package/templates/pnpm-workspace.basic.yaml +4 -0
- package/templates/workflows/dependabot-auto-merge.yml +5 -5
- package/templates/workflows/github-release.yml +2 -0
- package/scripts/build/build-config-merge.ts +0 -27
- package/scripts/build/build-managed-marker.ts +0 -27
- package/scripts/build/build-self-sync-guard.ts +0 -27
- package/scripts/build/build-version.ts +0 -26
- package/scripts/version/preinstall-version-update.ts +0 -86
- /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
- /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
- /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
- /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
- /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
- /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
- /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
- /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
- /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
- /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
- /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
- /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
- /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
- /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
- /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
- /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
- /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
- /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
- /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
- /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { lane_child_marker } from '#scripts/lane/lane-child-marker'
|
|
4
4
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
|
-
import { run_event_stream } from '
|
|
6
|
-
import { run_event_stream_emit } from '
|
|
5
|
+
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
6
|
+
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
7
7
|
import { run_ship, type ShipSection } from './run-ship'
|
|
8
8
|
import { run_ship_detach } from './run-ship-detach'
|
|
9
9
|
import { run_ship_next } from './run-ship-next'
|
|
10
|
+
import { run_ship_pre_detach } from './run-ship-pre-detach'
|
|
10
11
|
import { run_ship_probe } from './run-ship-probe'
|
|
11
12
|
import { run_ship_return } from './run-ship-return'
|
|
12
13
|
import { run_ship_stage, type Phase, type ShipState, type Stage } from './run-ship-stage'
|
|
@@ -171,8 +172,9 @@ function parse(argv: ReadonlyArray<string>): ShipCommand | undefined {
|
|
|
171
172
|
|
|
172
173
|
async function run_step(step: Step, args: ShipArguments, state: ShipState): Promise<ShipSection> {
|
|
173
174
|
const result = await step.run(args, state)
|
|
175
|
+
const section = { header: step.header, body: result.out, code: result.code }
|
|
174
176
|
|
|
175
|
-
return
|
|
177
|
+
return result.conflicts === undefined ? section : { ...section, conflicts: result.conflicts }
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
async function emit_phase(args: ShipArguments, stage: Stage, phase: Phase): Promise<void> {
|
|
@@ -249,7 +251,14 @@ async function stopped(
|
|
|
249
251
|
stage: Stage,
|
|
250
252
|
): Promise<ReadonlyArray<ShipSection>> {
|
|
251
253
|
if (run_ship_detach.is_supervised()) {
|
|
252
|
-
|
|
254
|
+
const conflicts = sections.at(-1)?.conflicts
|
|
255
|
+
const resume = run_ship_next.resume_of(args)
|
|
256
|
+
|
|
257
|
+
await run_ship_return.return_control(
|
|
258
|
+
args.number,
|
|
259
|
+
stage,
|
|
260
|
+
conflicts === undefined ? resume : { ...resume, conflicts },
|
|
261
|
+
)
|
|
253
262
|
}
|
|
254
263
|
|
|
255
264
|
return sections
|
|
@@ -324,14 +333,26 @@ function should_detach(args: ShipArguments): boolean {
|
|
|
324
333
|
// joshuafolkken/kit#2966: the preflight is deterministic and fast, so it runs here, in the agent's own
|
|
325
334
|
// turn, before the hand-off — a stop it finds is fixed by the same session instead of relaunching one
|
|
326
335
|
// from the supervisor. Only the slow stages go to the supervisor, which re-asks the preflight itself.
|
|
336
|
+
// joshuafolkken/kit#3222: the type check and the document tests follow it here for the same reason; they
|
|
337
|
+
// are not a stage, so the supervisor's gate runs them again rather than passing over them.
|
|
338
|
+
async function pre_detach_sections(args: ShipArguments): Promise<ReadonlyArray<ShipSection>> {
|
|
339
|
+
const preflight = await run_stage(run_ship_steps.PREFLIGHT_STEP, args, await open_context(args))
|
|
340
|
+
if (preflight.code !== SUCCESS_EXIT_CODE) return [preflight]
|
|
341
|
+
|
|
342
|
+
const result = await run_ship_pre_detach.checks()
|
|
343
|
+
|
|
344
|
+
return [preflight, { header: run_ship.PRE_DETACH_HEADER, body: result.out, code: result.code }]
|
|
345
|
+
}
|
|
346
|
+
|
|
327
347
|
async function detach_after_preflight(args: ShipArguments): Promise<number> {
|
|
328
|
-
const
|
|
348
|
+
const sections = await pre_detach_sections(args)
|
|
349
|
+
const code = run_ship.exit_code(sections)
|
|
329
350
|
|
|
330
|
-
if (
|
|
351
|
+
if (code === SUCCESS_EXIT_CODE) return await detach(args)
|
|
331
352
|
|
|
332
|
-
console.info(run_ship.format_report(
|
|
353
|
+
console.info(run_ship.format_report(sections))
|
|
333
354
|
|
|
334
|
-
return
|
|
355
|
+
return code
|
|
335
356
|
}
|
|
336
357
|
|
|
337
358
|
async function supervised_repository(): Promise<string | undefined> {
|
|
@@ -3,10 +3,10 @@ import { setTimeout as sleep } from 'node:timers/promises'
|
|
|
3
3
|
import { agent_role_profile } from '#scripts/agent/agent-role-profile'
|
|
4
4
|
import { process_identity } from '#scripts/josh/process-identity'
|
|
5
5
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
6
|
+
import { detached_launch, type LaunchArgv } from '#scripts/run/detached-launch'
|
|
7
|
+
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
8
|
+
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
6
9
|
import { z } from 'zod'
|
|
7
|
-
import { detached_launch, type LaunchArgv } from './detached-launch'
|
|
8
|
-
import { run_event_stream } from './run-event-stream'
|
|
9
|
-
import { run_event_stream_emit } from './run-event-stream-emit'
|
|
10
10
|
|
|
11
11
|
// `josh ship --detach` — hand the post-implementation region to a supervisor that outlives the agent
|
|
12
12
|
// (joshuafolkken/kit#2428). A lane child used to take the pre-gate cut and relaunch a fresh agent session
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { stripVTControlCharacters } from 'node:util'
|
|
4
|
+
import { buffered_process } from '#scripts/lib/buffered-process'
|
|
5
|
+
import { INSTALL_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
6
|
+
|
|
7
|
+
// A lock file that no longer matches its inputs, asked before the gate (joshuafolkken/kit#3307).
|
|
8
|
+
//
|
|
9
|
+
// The pre-push hook's setup runs `pnpm install`, which rewrites a lock that has drifted — a
|
|
10
|
+
// `.pnpmfile.mjs` edit moves `pnpmfileChecksum` — and so leaves the working tree dirty under the push.
|
|
11
|
+
// The hook then refuses the gate's record and re-runs the whole unit suite, long enough under a crowded
|
|
12
|
+
// machine to time the push out. The frozen, lock-only install answers the same question without
|
|
13
|
+
// writing anything or touching `node_modules`, in about a third of a second.
|
|
14
|
+
//
|
|
15
|
+
// A project with no lock file has nothing to drift, so nothing is asked there.
|
|
16
|
+
//
|
|
17
|
+
// **pnpm's own error line rides along**: the install refuses for reasons other than drift too — an
|
|
18
|
+
// engine mismatch, a timeout on a crowded machine — and there "run `pnpm install`" fixes nothing, so
|
|
19
|
+
// the reader needs pnpm's error code to tell the two apart. The line naming an `ERR_PNPM_` code wins
|
|
20
|
+
// over the first line, which in a workspace is pnpm's "Scope: all N workspace projects" banner.
|
|
21
|
+
|
|
22
|
+
const LOCK_FILE = 'pnpm-lock.yaml'
|
|
23
|
+
const PNPM_ERROR_CODE = 'ERR_PNPM_'
|
|
24
|
+
const LOCK_CHECK_ARGUMENTS = ['install', '--frozen-lockfile', '--lockfile-only', '--ignore-scripts']
|
|
25
|
+
const LOCK_DRIFT = `${LOCK_FILE} does not match its inputs (\`pnpm install --frozen-lockfile\` refuses it), so the pre-push hook's install would rewrite it under the push: run \`pnpm install\` and commit ${LOCK_FILE}.`
|
|
26
|
+
|
|
27
|
+
function drift_problem(output: string): string {
|
|
28
|
+
const lines = stripVTControlCharacters(output)
|
|
29
|
+
.split('\n')
|
|
30
|
+
.map((line) => line.trim())
|
|
31
|
+
.filter((line) => line !== '')
|
|
32
|
+
const reason = lines.find((line) => line.includes(PNPM_ERROR_CODE)) ?? lines[0]
|
|
33
|
+
|
|
34
|
+
return reason === undefined ? LOCK_DRIFT : `${LOCK_DRIFT} pnpm said: ${reason}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function lock_problems(directory: string = process.cwd()): Promise<Array<string>> {
|
|
38
|
+
if (!existsSync(path.join(directory, LOCK_FILE))) return []
|
|
39
|
+
|
|
40
|
+
const result = await buffered_process.run_buffered_process(LOCK_CHECK_ARGUMENTS, {
|
|
41
|
+
cwd: directory,
|
|
42
|
+
timeout_ms: INSTALL_TIMEOUT_MS,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return buffered_process.is_process_failed(result) ? [drift_problem(result.output)] : []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const run_ship_lock = { LOCK_CHECK_ARGUMENTS, LOCK_DRIFT, lock_problems }
|
|
49
|
+
|
|
50
|
+
export { run_ship_lock }
|
|
@@ -42,6 +42,8 @@ interface ShipResume {
|
|
|
42
42
|
title: string
|
|
43
43
|
flags: ReadonlyArray<string>
|
|
44
44
|
is_review: boolean
|
|
45
|
+
// The paths a merge of the default branch left unmerged (joshuafolkken/kit#3221).
|
|
46
|
+
conflicts?: ReadonlyArray<string>
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
// Single quotes, so a `$` or a backtick in a title is never expanded by the shell that runs it.
|
|
@@ -90,14 +92,25 @@ const AFTER_FIX: Record<Stage, (resume: ShipResume) => string> = {
|
|
|
90
92
|
[STAGE.PREFLIGHT]: detach_as_started,
|
|
91
93
|
[STAGE.REVIEW]: detach_as_started,
|
|
92
94
|
[STAGE.GATE]: detach_as_started,
|
|
95
|
+
[STAGE.SYNC]: detach_as_started,
|
|
93
96
|
[STAGE.COMMIT]: detach_as_started,
|
|
94
97
|
[STAGE.ROUND_TWO]: detach_again,
|
|
95
98
|
[STAGE.FOLLOWUP]: detach_again,
|
|
96
99
|
[STAGE.REPORT]: detach_again,
|
|
97
100
|
}
|
|
98
101
|
|
|
102
|
+
// A conflict names its paths (joshuafolkken/kit#3221), so the resumed session resolves them without
|
|
103
|
+
// reading the report or asking git which files are unmerged.
|
|
104
|
+
function conflict_step(conflicts: ReadonlyArray<string>): string {
|
|
105
|
+
return `Resolve the conflict with origin/main in ${conflicts.join(', ')} (the merge is left in progress: remove the conflict markers in those files), then`
|
|
106
|
+
}
|
|
107
|
+
|
|
99
108
|
// Two lines' worth: read the stopped report, then the one command that resumes after the fix.
|
|
100
109
|
function next_step(issue: string, stage: Stage, resume: ShipResume): string {
|
|
110
|
+
const conflicts = resume.conflicts ?? []
|
|
111
|
+
|
|
112
|
+
if (conflicts.length > 0) return `${conflict_step(conflicts)} ${AFTER_FIX[stage](resume)}`
|
|
113
|
+
|
|
101
114
|
return `Read the stopped report with \`${COMMAND.LOG} ${issue}\`, fix the ${stage} failure, then ${AFTER_FIX[stage](resume)}`
|
|
102
115
|
}
|
|
103
116
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { type_check_step } from '#scripts/gate/type-check-step'
|
|
4
|
+
import type { JoshResult } from '#scripts/josh/josh-run'
|
|
5
|
+
import { buffered_process, type BufferedProcessResult } from '#scripts/lib/buffered-process'
|
|
6
|
+
|
|
7
|
+
// The fast checks a detached `josh ship` runs in the agent's own turn, after the preflight and before
|
|
8
|
+
// the hand-off (joshuafolkken/kit#3222). In one measured backlogrun about 7 of 31 lane resumes were a
|
|
9
|
+
// gate failure first seen after the detach — a type error in a file the related-only checks never
|
|
10
|
+
// touched, or a document test (a SKILL.md pointer, a byte budget, the command list) that only the full
|
|
11
|
+
// suite runs — each paid for with a relaunched session. These two answer in seconds, so the same
|
|
12
|
+
// session fixes what they find and ships again. **Nothing moves out of the gate**: the supervised gate
|
|
13
|
+
// stage still runs every check, these included.
|
|
14
|
+
//
|
|
15
|
+
// The test targets are the directories those resumes failed in. A consumer has none of them, so only
|
|
16
|
+
// the ones present run, and a consumer meets the type check alone.
|
|
17
|
+
|
|
18
|
+
const SUCCESS_EXIT_CODE = 0
|
|
19
|
+
const FAILURE_EXIT_CODE = 1
|
|
20
|
+
const SECTION_SEPARATOR = '\n\n'
|
|
21
|
+
const PASSED = 'type check and document tests passed'
|
|
22
|
+
const DOCUMENT_TEST_TARGETS: ReadonlyArray<string> = [
|
|
23
|
+
'scripts/document',
|
|
24
|
+
'scripts/claude',
|
|
25
|
+
'scripts/rules',
|
|
26
|
+
'scripts/josh/josh-command-map.test.ts',
|
|
27
|
+
]
|
|
28
|
+
// A present directory need not hold a test the project's Vitest includes — a consumer's own
|
|
29
|
+
// `scripts/claude` of shell helpers, say — and a filter that matches nothing would stop every ship.
|
|
30
|
+
const DOCUMENT_TEST_COMMAND: ReadonlyArray<string> = ['josh', 'test:unit', '--passWithNoTests']
|
|
31
|
+
|
|
32
|
+
function present_targets(directory: string): ReadonlyArray<string> {
|
|
33
|
+
return DOCUMENT_TEST_TARGETS.filter((target) => existsSync(path.join(directory, target)))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function command_lists(directory: string): Promise<Array<ReadonlyArray<string>>> {
|
|
37
|
+
const type_check = await type_check_step.resolve_type_check_args(directory)
|
|
38
|
+
const targets = present_targets(directory)
|
|
39
|
+
|
|
40
|
+
return targets.length === 0 ? [type_check] : [type_check, [...DOCUMENT_TEST_COMMAND, ...targets]]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function run_in(
|
|
44
|
+
directory: string,
|
|
45
|
+
list: ReadonlyArray<string>,
|
|
46
|
+
): Promise<BufferedProcessResult> {
|
|
47
|
+
return await buffered_process.run_buffered_process(list, { cwd: directory })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Both run at once, buffered, and every failure is reported, so one ship shows all there is to fix.
|
|
51
|
+
async function checks(directory: string = process.cwd()): Promise<JoshResult> {
|
|
52
|
+
const lists = await command_lists(directory)
|
|
53
|
+
const results = await Promise.all(lists.map(async (list) => await run_in(directory, list)))
|
|
54
|
+
const failed = results.filter((result) => buffered_process.is_process_failed(result))
|
|
55
|
+
|
|
56
|
+
if (failed.length === 0) return { code: SUCCESS_EXIT_CODE, out: PASSED }
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
code: FAILURE_EXIT_CODE,
|
|
60
|
+
out: failed.map((result) => result.output.trim()).join(SECTION_SEPARATOR),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const run_ship_pre_detach = { DOCUMENT_TEST_TARGETS, PASSED, checks }
|
|
65
|
+
|
|
66
|
+
export { run_ship_pre_detach }
|
|
@@ -7,6 +7,7 @@ import { git_preflight } from '#scripts/git/git-preflight'
|
|
|
7
7
|
import type { JoshResult } from '#scripts/josh/josh-run'
|
|
8
8
|
import { error_text } from '#scripts/lib/error-message'
|
|
9
9
|
import { live_evidence } from '#scripts/review/live-evidence'
|
|
10
|
+
import { run_ship_lock } from './run-ship-lock'
|
|
10
11
|
import { run_ship_scoped } from './run-ship-scoped'
|
|
11
12
|
|
|
12
13
|
// `josh ship`'s first stage (joshuafolkken/kit#2946). A detached ship used to meet the pull request's
|
|
@@ -71,13 +72,16 @@ async function caught(problems: Promise<Array<string>>): Promise<Array<string>>
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// The lock file is asked here too (joshuafolkken/kit#3307), so a drift stops the ship before the gate
|
|
76
|
+
// rather than being rewritten by the pre-push hook's install under the push.
|
|
74
77
|
async function asked_problems(request: PreflightRequest): Promise<Array<string>> {
|
|
75
|
-
const [preflight, evidence] = await Promise.all([
|
|
78
|
+
const [preflight, evidence, lock] = await Promise.all([
|
|
76
79
|
caught(git_preflight.problems_of({ cli_input: request.title, will_open_pr: true })),
|
|
77
80
|
caught(evidence_problems(request.body_path)),
|
|
81
|
+
caught(run_ship_lock.lock_problems()),
|
|
78
82
|
])
|
|
79
83
|
|
|
80
|
-
return [...preflight, ...evidence]
|
|
84
|
+
return [...preflight, ...evidence, ...lock]
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
async function stage(request: PreflightRequest): Promise<JoshResult> {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { git_command } from '#scripts/git/git-command'
|
|
2
2
|
import { git_worktree } from '#scripts/git/git-worktree'
|
|
3
3
|
import { error_text } from '#scripts/lib/error-message'
|
|
4
|
-
import { run_hold } from '
|
|
5
|
-
import { run_preflight } from '
|
|
4
|
+
import { run_hold } from '#scripts/run/hold/run-hold'
|
|
5
|
+
import { run_preflight } from '#scripts/run/run-preflight'
|
|
6
6
|
import { run_ship_stage, type ShipState } from './run-ship-stage'
|
|
7
7
|
|
|
8
8
|
// The read side of a resumed `josh ship` (joshuafolkken/kit#2426): the repository's actual state, asked
|
|
@@ -3,10 +3,11 @@ import { lane_child_invocation } from '#scripts/lane/lane-child-invocation'
|
|
|
3
3
|
import { lane_child_marker } from '#scripts/lane/lane-child-marker'
|
|
4
4
|
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
5
5
|
import { lane_relaunch } from '#scripts/lane/lane-relaunch'
|
|
6
|
-
import { run_event_stream } from '
|
|
7
|
-
import { run_event_stream_emit } from '
|
|
6
|
+
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
7
|
+
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
8
8
|
import type { ShipResume } from './run-ship-next'
|
|
9
|
-
import type
|
|
9
|
+
import { run_ship_stage, type Stage } from './run-ship-stage'
|
|
10
|
+
import { run_ship_stop_text } from './run-ship-stop-text'
|
|
10
11
|
|
|
11
12
|
// How a detached `josh ship` supervisor hands a stopped stage back (joshuafolkken/kit#2428). The agent
|
|
12
13
|
// ended when it handed the region over, so a red gate, a High/Medium review, a failed push or red CI has
|
|
@@ -28,8 +29,8 @@ const OPENAI_PROVIDER = 'openai'
|
|
|
28
29
|
|
|
29
30
|
type ReturnOutcome = 'relaunched' | 'recorded'
|
|
30
31
|
|
|
31
|
-
function stop_text(issue: string, stage: Stage): string {
|
|
32
|
-
return
|
|
32
|
+
function stop_text(issue: string, stage: Stage, resume: ShipResume): string {
|
|
33
|
+
return run_ship_stop_text.format(issue, run_ship_stage.stop_reason(stage, resume.conflicts))
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
function can_relaunch(lane: LaneInfo | undefined): lane is LaneInfo {
|
|
@@ -53,7 +54,10 @@ async function return_control(
|
|
|
53
54
|
stage: Stage,
|
|
54
55
|
resume: ShipResume,
|
|
55
56
|
): Promise<ReturnOutcome> {
|
|
56
|
-
await run_event_stream_emit.emit(
|
|
57
|
+
await run_event_stream_emit.emit(
|
|
58
|
+
run_event_stream.EVENT_KIND.SHIP_STOP,
|
|
59
|
+
stop_text(issue, stage, resume),
|
|
60
|
+
)
|
|
57
61
|
|
|
58
62
|
const lane = await lane_to_relaunch(issue)
|
|
59
63
|
|
|
@@ -6,7 +6,7 @@ import { stamp_file } from '#scripts/josh/stamp-file'
|
|
|
6
6
|
import { lane_child_marker } from '#scripts/lane/lane-child-marker'
|
|
7
7
|
import { openai_review_broker } from '#scripts/lane/openai-review-broker'
|
|
8
8
|
import { review_record } from '#scripts/review/review-record'
|
|
9
|
-
import { detached_launch } from '
|
|
9
|
+
import { detached_launch } from '#scripts/run/detached-launch'
|
|
10
10
|
import { run_ship_review, type RoundOutcome, type ScoredVerdict } from './run-ship-review'
|
|
11
11
|
import { run_ship_scoped, type Phase } from './run-ship-scoped'
|
|
12
12
|
|
|
@@ -59,8 +59,8 @@ function failure(out: string): JoshResult {
|
|
|
59
59
|
return { code: FAILURE_EXIT_CODE, out }
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function paths(
|
|
63
|
-
return openai_review_broker.review_paths(
|
|
62
|
+
function paths(): { brief: string; findings: string; log: string } {
|
|
63
|
+
return openai_review_broker.review_paths(PROJECT_ROOT)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
async function josh(argv: ReadonlyArray<string>): Promise<JoshResult> {
|
|
@@ -39,7 +39,16 @@ async function scoped_pair(): Promise<JoshResult> {
|
|
|
39
39
|
)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
// The scoped pair and then `josh gate` — the gate stage, and the followup's gate over a merged tree
|
|
43
|
+
// before its push (joshuafolkken/kit#3307), so both leave the record the pre-push hook reuses.
|
|
44
|
+
async function scoped_gate(): Promise<JoshResult> {
|
|
45
|
+
return await run_phases([
|
|
46
|
+
scoped_pair,
|
|
47
|
+
async () => await josh_command.josh_run(['gate'], should_forward_stderr),
|
|
48
|
+
])
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const run_ship_scoped = { run_phases, scoped_gate, scoped_pair }
|
|
43
52
|
|
|
44
53
|
export type { Phase }
|
|
45
54
|
export { run_ship_scoped }
|
|
@@ -20,6 +20,7 @@ const STAGE = {
|
|
|
20
20
|
PREFLIGHT: 'preflight',
|
|
21
21
|
REVIEW: 'review',
|
|
22
22
|
GATE: 'gate',
|
|
23
|
+
SYNC: 'sync',
|
|
23
24
|
COMMIT: 'commit',
|
|
24
25
|
ROUND_TWO: 'round-2',
|
|
25
26
|
FOLLOWUP: 'followup',
|
|
@@ -99,6 +100,9 @@ const DONE_BY: Record<Stage, (done: ReadonlySet<string>, state: ShipState) => bo
|
|
|
99
100
|
// the recorded round, and only a commit pins the tree that round read.
|
|
100
101
|
[STAGE.REVIEW]: (_done, state) => state.is_merged || state.is_committed,
|
|
101
102
|
[STAGE.GATE]: (_done, state) => state.is_merged || state.is_committed,
|
|
103
|
+
// The merge of the default branch before the commit (joshuafolkken/kit#3221): once committed, a
|
|
104
|
+
// conflicting pull request is the followup's to merge again.
|
|
105
|
+
[STAGE.SYNC]: (_done, state) => state.is_merged || state.is_committed,
|
|
102
106
|
[STAGE.COMMIT]: (done, state) => state.is_merged || (done.has(STAGE.COMMIT) && is_shipped(state)),
|
|
103
107
|
// The round-2 pass (joshuafolkken/kit#2489) reads the pushed fix delta, so its record is honored only
|
|
104
108
|
// while that push still stands — new work pushed on top reruns it.
|
|
@@ -138,6 +142,14 @@ function event_text(issue: string, stage: Stage, phase: Phase): string {
|
|
|
138
142
|
return `#${issue} ${stage} ${phase}`
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
// A stop on a merge git could not finish is named `conflict` rather than by the stage it happened in
|
|
146
|
+
// (joshuafolkken/kit#3221), so the event stream reads the cause without anyone opening the report.
|
|
147
|
+
const CONFLICT = 'conflict'
|
|
148
|
+
|
|
149
|
+
function stop_reason(stage: Stage, conflicts: ReadonlyArray<string> = []): string {
|
|
150
|
+
return conflicts.length > 0 ? CONFLICT : stage
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
const run_ship_stage = {
|
|
142
154
|
PHASE,
|
|
143
155
|
STAGE,
|
|
@@ -148,6 +160,7 @@ const run_ship_stage = {
|
|
|
148
160
|
mark_done,
|
|
149
161
|
read_done,
|
|
150
162
|
record_path,
|
|
163
|
+
stop_reason,
|
|
151
164
|
}
|
|
152
165
|
|
|
153
166
|
export type { Phase, ShipState, Stage }
|
|
@@ -4,6 +4,7 @@ import { run_ship_preflight } from './run-ship-preflight'
|
|
|
4
4
|
import { run_ship_review_steps } from './run-ship-review-steps'
|
|
5
5
|
import { run_ship_scoped } from './run-ship-scoped'
|
|
6
6
|
import { run_ship_stage, type ShipState, type Stage } from './run-ship-stage'
|
|
7
|
+
import { run_ship_sync, type StepResult } from './run-ship-sync'
|
|
7
8
|
|
|
8
9
|
// The stages `josh ship` runs, in the order a change ships (joshuafolkken/kit#2398), kept apart from the
|
|
9
10
|
// chaining CLI so the order is read in one place.
|
|
@@ -24,7 +25,7 @@ interface ShipArguments {
|
|
|
24
25
|
interface Step {
|
|
25
26
|
stage: Stage
|
|
26
27
|
header: string
|
|
27
|
-
run: (args: ShipArguments, state: ShipState) => Promise<
|
|
28
|
+
run: (args: ShipArguments, state: ShipState) => Promise<StepResult>
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const { STAGE } = run_ship_stage
|
|
@@ -47,14 +48,19 @@ const PREFLIGHT_STEP: Step = {
|
|
|
47
48
|
// with no green record (joshuafolkken/kit#2946). The commit step carries the `--skip-*` flags a resumed
|
|
48
49
|
// ship needs, so an existing commit or push is never made twice.
|
|
49
50
|
const COMMIT_STEPS: ReadonlyArray<Step> = [
|
|
51
|
+
// joshuafolkken/kit#3221: the default branch merged again before the commit, so the pull request
|
|
52
|
+
// opens on a branch that is current; a clean merge goes on without waking anyone. It runs before the
|
|
53
|
+
// gate (joshuafolkken/kit#3307): a merge after it moved the merge base the gate's record pins, so the
|
|
54
|
+
// pre-push hook could never reuse that record and re-ran the whole unit suite on every merged ship.
|
|
55
|
+
{
|
|
56
|
+
stage: STAGE.SYNC,
|
|
57
|
+
header: run_ship.SYNC_HEADER,
|
|
58
|
+
run: async () => await run_ship_sync.sync_stage(),
|
|
59
|
+
},
|
|
50
60
|
{
|
|
51
61
|
stage: STAGE.GATE,
|
|
52
62
|
header: run_ship.GATE_HEADER,
|
|
53
|
-
run: async () =>
|
|
54
|
-
await run_ship_scoped.run_phases([
|
|
55
|
-
run_ship_scoped.scoped_pair,
|
|
56
|
-
async () => await josh(['gate']),
|
|
57
|
-
]),
|
|
63
|
+
run: async () => await run_ship_scoped.scoped_gate(),
|
|
58
64
|
},
|
|
59
65
|
{
|
|
60
66
|
stage: STAGE.COMMIT,
|
|
@@ -69,7 +75,7 @@ const MERGE_STEPS: ReadonlyArray<Step> = [
|
|
|
69
75
|
{
|
|
70
76
|
stage: STAGE.FOLLOWUP,
|
|
71
77
|
header: run_ship.FOLLOWUP_HEADER,
|
|
72
|
-
run: async (args) => await
|
|
78
|
+
run: async (args) => await run_ship_sync.followup_stage(args.title, args.notify),
|
|
73
79
|
},
|
|
74
80
|
{
|
|
75
81
|
stage: STAGE.REPORT,
|
|
@@ -79,7 +85,8 @@ const MERGE_STEPS: ReadonlyArray<Step> = [
|
|
|
79
85
|
]
|
|
80
86
|
|
|
81
87
|
// `--review` (joshuafolkken/kit#2427) puts the supervised round-1 review in front of the gate: it
|
|
82
|
-
// launches the gate itself, so the gate stage that follows reuses that tree's green record
|
|
88
|
+
// launches the gate itself, so the gate stage that follows reuses that tree's green record unless the
|
|
89
|
+
// sync stage between them merged the default branch and changed the tree. It also
|
|
83
90
|
// puts the round-2 pass between the commit and the followup (joshuafolkken/kit#2489), so the PR opens
|
|
84
91
|
// between the rounds and round 2 runs beside CI — a no-op when round 1 left no fix delta.
|
|
85
92
|
const REVIEW_STEP: Step = {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The text a `josh ship` supervisor records when a stage stops, written and read in one place so the
|
|
2
|
+
// retrospective's per-stage count never drifts from the writer (joshuafolkken/kit#3245).
|
|
3
|
+
const STOP_PATTERN = /^#\d+ (\S+) failed — /u
|
|
4
|
+
|
|
5
|
+
function format(issue: string, reason: string): string {
|
|
6
|
+
return `#${issue} ${reason} failed — pnpm josh ship --log ${issue}`
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function reason_of(text: string): string | undefined {
|
|
10
|
+
return STOP_PATTERN.exec(text)?.[1]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const run_ship_stop_text = { format, reason_of }
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { git_gh_pr_read } from '#scripts/gh/git-gh-pr-read'
|
|
2
|
+
import { is_merge_conflict } from '#scripts/gh/git-pr-checks-eval'
|
|
3
|
+
import { git_command } from '#scripts/git/git-command'
|
|
4
|
+
import { main_merge, type MergeOutcome } from '#scripts/git/main-merge'
|
|
5
|
+
import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
|
|
6
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
7
|
+
import { run_ship_scoped } from './run-ship-scoped'
|
|
8
|
+
|
|
9
|
+
// `josh ship` keeps the branch current with the default branch itself (joshuafolkken/kit#3221). The
|
|
10
|
+
// lane merged it once, before the hand-off, and nothing merged it again while the pull request opened
|
|
11
|
+
// and CI ran — so a default branch that moved meanwhile stopped the followup as `interrupted`, and a
|
|
12
|
+
// relaunched session spent its first turns finding out that the cause was a conflict. Most of those
|
|
13
|
+
// conflicts were not conflicts at all to git: merging the default branch again resolved them.
|
|
14
|
+
//
|
|
15
|
+
// So the merge happens twice more, mechanically: once before the commit (`sync_stage`), and once when
|
|
16
|
+
// the followup fails on a pull request GitHub reports `DIRTY` (`followup_stage`). Only a merge git
|
|
17
|
+
// itself cannot finish stops the ship, and it stops as `conflict`, naming the unmerged paths so the
|
|
18
|
+
// resumed session goes straight to them.
|
|
19
|
+
|
|
20
|
+
const should_forward_stderr = true
|
|
21
|
+
const SUCCESS_EXIT_CODE = 0
|
|
22
|
+
const FAILURE_EXIT_CODE = 1
|
|
23
|
+
// A default branch that keeps moving under a pull request is merged again at most this often per ship;
|
|
24
|
+
// past it the followup failure stands, so a busy default branch cannot hold a ship in a loop.
|
|
25
|
+
const MAX_MERGE_RETRIES = 2
|
|
26
|
+
const CONFLICT_NOTE = 'merging the default branch left unmerged paths:'
|
|
27
|
+
const DEFERRED_PREFIX = 'merge deferred to the followup: '
|
|
28
|
+
const CURRENT_SUFFIX = ' brings nothing in'
|
|
29
|
+
const MERGED_PREFIX = 'merged '
|
|
30
|
+
|
|
31
|
+
// A stage's result, plus the paths a stopped merge left unmerged — carried to the stop prompt.
|
|
32
|
+
interface StepResult extends JoshResult {
|
|
33
|
+
conflicts?: ReadonlyArray<string>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function josh(argv: ReadonlyArray<string>): Promise<JoshResult> {
|
|
37
|
+
return await josh_command.josh_run(argv, should_forward_stderr)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A merge that could not even be attempted — a fetch that failed — reads as a refusal: nothing was
|
|
41
|
+
// changed, and the followup still finds a real conflict on GitHub's side.
|
|
42
|
+
async function merge_outcome(): Promise<MergeOutcome> {
|
|
43
|
+
try {
|
|
44
|
+
return await main_merge.merge()
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return { kind: 'refused', message: error_text.message_of(error) }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function conflict_result(files: ReadonlyArray<string>, lead: string): StepResult {
|
|
51
|
+
const out = [lead, CONFLICT_NOTE, ...files].filter((line) => line !== '').join('\n')
|
|
52
|
+
|
|
53
|
+
return { code: FAILURE_EXIT_CODE, out, conflicts: files }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sync_note(outcome: Exclude<MergeOutcome, { kind: 'conflict' }>): string {
|
|
57
|
+
if (outcome.kind === 'refused') return `${DEFERRED_PREFIX}${outcome.message}`
|
|
58
|
+
|
|
59
|
+
return outcome.kind === 'current'
|
|
60
|
+
? `${outcome.branch}${CURRENT_SUFFIX}`
|
|
61
|
+
: `${MERGED_PREFIX}${outcome.branch}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// A refusal does not stop the commit: the guard refuses only uncommitted work the default branch also
|
|
65
|
+
// touches, and once that work is committed the followup merges again on a clean tree. A merge that changed
|
|
66
|
+
// the tree is checked by the gate stage that follows (joshuafolkken/kit#3307), so the commit never carries
|
|
67
|
+
// an unchecked merge and the gate's record names the merge base the push carries.
|
|
68
|
+
async function sync_stage(): Promise<StepResult> {
|
|
69
|
+
const outcome = await merge_outcome()
|
|
70
|
+
|
|
71
|
+
if (outcome.kind === 'conflict') return conflict_result(outcome.files, '')
|
|
72
|
+
|
|
73
|
+
return { code: SUCCESS_EXIT_CODE, out: sync_note(outcome) }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Any read that fails answers "not conflicting": the followup failure then stands as it was.
|
|
77
|
+
async function is_conflicting(): Promise<boolean> {
|
|
78
|
+
try {
|
|
79
|
+
const state = await git_gh_pr_read.pr_get_merge_state(await git_command.branch())
|
|
80
|
+
|
|
81
|
+
return is_merge_conflict(state?.merge_state_status)
|
|
82
|
+
} catch {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The merged tree is gated before it is pushed (joshuafolkken/kit#3307): the push then carries the tree
|
|
88
|
+
// the gate's record describes, so the pre-push hook reuses it rather than re-running the unit suite.
|
|
89
|
+
async function push_merged(title: string): Promise<StepResult | undefined> {
|
|
90
|
+
const gate = await run_ship_scoped.scoped_gate()
|
|
91
|
+
|
|
92
|
+
if (gate.code !== SUCCESS_EXIT_CODE) return gate
|
|
93
|
+
|
|
94
|
+
const push = await josh(['git', '-y', '--skip-commit', '--skip-pr', title])
|
|
95
|
+
|
|
96
|
+
return push.code === SUCCESS_EXIT_CODE ? undefined : push
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The result to stop on, or `undefined` once a clean merge is pushed and the followup can wait again.
|
|
100
|
+
async function merge_again(title: string, failed: JoshResult): Promise<StepResult | undefined> {
|
|
101
|
+
if (!(await is_conflicting())) return failed
|
|
102
|
+
|
|
103
|
+
const outcome = await merge_outcome()
|
|
104
|
+
|
|
105
|
+
if (outcome.kind === 'conflict') return conflict_result(outcome.files, failed.out)
|
|
106
|
+
if (outcome.kind !== 'merged') return failed
|
|
107
|
+
|
|
108
|
+
return await push_merged(title)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function followup_stage(
|
|
112
|
+
title: string,
|
|
113
|
+
notify: ReadonlyArray<string>,
|
|
114
|
+
retries = 0,
|
|
115
|
+
): Promise<StepResult> {
|
|
116
|
+
const result = await josh(['followup', title, ...notify])
|
|
117
|
+
|
|
118
|
+
if (result.code === SUCCESS_EXIT_CODE || retries >= MAX_MERGE_RETRIES) return result
|
|
119
|
+
|
|
120
|
+
const stop = await merge_again(title, result)
|
|
121
|
+
|
|
122
|
+
return stop ?? (await followup_stage(title, notify, retries + 1))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const run_ship_sync = { CONFLICT_NOTE, MAX_MERGE_RETRIES, followup_stage, sync_stage }
|
|
126
|
+
|
|
127
|
+
export type { StepResult }
|
|
128
|
+
export { run_ship_sync }
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
// composite ends at the failure, and the report names the step that failed so the run reads only it.
|
|
11
11
|
|
|
12
12
|
const PREFLIGHT_HEADER = '=== preflight ==='
|
|
13
|
+
const PRE_DETACH_HEADER = '=== pre-detach checks ==='
|
|
13
14
|
const REVIEW_HEADER = '=== review ==='
|
|
14
15
|
const GATE_HEADER = '=== gate ==='
|
|
16
|
+
const SYNC_HEADER = '=== sync origin/main ==='
|
|
15
17
|
const COMMIT_HEADER = '=== commit/push/PR ==='
|
|
16
18
|
const ROUND_TWO_HEADER = '=== round-2 review ==='
|
|
17
19
|
const FOLLOWUP_HEADER = '=== followup ==='
|
|
@@ -29,6 +31,8 @@ interface ShipSection {
|
|
|
29
31
|
header: string
|
|
30
32
|
body: string
|
|
31
33
|
code: number
|
|
34
|
+
// The paths a stopped merge left unmerged (joshuafolkken/kit#3221), handed to the stop prompt.
|
|
35
|
+
conflicts?: ReadonlyArray<string>
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
function section_report(section: ShipSection): string {
|
|
@@ -65,10 +69,12 @@ const run_ship = {
|
|
|
65
69
|
FOLLOWUP_HEADER,
|
|
66
70
|
GATE_HEADER,
|
|
67
71
|
PREFLIGHT_HEADER,
|
|
72
|
+
PRE_DETACH_HEADER,
|
|
68
73
|
REPORT_HEADER,
|
|
69
74
|
REVIEW_HEADER,
|
|
70
75
|
ROUND_TWO_HEADER,
|
|
71
76
|
SKIPPED_BODY,
|
|
77
|
+
SYNC_HEADER,
|
|
72
78
|
STOPPED_PREFIX,
|
|
73
79
|
exit_code,
|
|
74
80
|
failed_section,
|
|
@@ -3,7 +3,7 @@ import { git_stash } from '#scripts/git/stash/git-stash'
|
|
|
3
3
|
import { lane_close } from '#scripts/lane/lane-close'
|
|
4
4
|
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
5
5
|
import { error_text } from '#scripts/lib/error-message'
|
|
6
|
-
import { run_hold } from '
|
|
6
|
+
import { run_hold } from '#scripts/run/hold/run-hold'
|
|
7
7
|
import { run_tidy, type Outcome, type Verdict } from './run-tidy'
|
|
8
8
|
|
|
9
9
|
// The lane half of `josh run:tidy` (joshuafolkken/kit#2701): each lane whose issue merged, read for
|