@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
2
3
|
import { pathToFileURL } from 'node:url'
|
|
3
4
|
import type { CommandEntry } from './josh-command-types'
|
|
4
5
|
|
|
@@ -8,26 +9,44 @@ import type { CommandEntry } from './josh-command-types'
|
|
|
8
9
|
// (joshuafolkken/kit#1342). The script is ordinary TypeScript and a TypeScript loader is already
|
|
9
10
|
// installed by the time this file runs, so the dispatcher evaluates the script itself instead.
|
|
10
11
|
const TYPESCRIPT_EXTENSION = '.ts'
|
|
12
|
+
const JAVASCRIPT_EXTENSION = '.js'
|
|
13
|
+
const BUNDLED_COMMAND_DIRECTORY = path.join('dist', 'commands')
|
|
11
14
|
const FAILURE_EXIT_CODE = 1
|
|
12
15
|
|
|
13
16
|
// The dispatcher may only load a `.ts` script in its own process when it was itself loaded as
|
|
14
17
|
// TypeScript — which is exactly the condition under which a loader capable of it is present. A
|
|
15
|
-
// consumer runs the bundled `dist/josh.js` under plain node, whose `import.meta.url` ends in `.js
|
|
16
|
-
// so that install keeps the spawning path unchanged rather than failing on a module node cannot
|
|
17
|
-
// load at all.
|
|
18
|
+
// consumer runs the bundled `dist/josh.js` under plain node, whose `import.meta.url` ends in `.js`.
|
|
18
19
|
function is_typescript_dispatcher(dispatcher_url: string): boolean {
|
|
19
20
|
return dispatcher_url.endsWith(TYPESCRIPT_EXTENSION)
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
// A command marked `is_bundled` is pre-built to `dist/commands/<script basename>.js` by
|
|
24
|
+
// `scripts/build/build-commands.ts` (joshuafolkken/kit#3328). The path is derived here, in a module
|
|
25
|
+
// the dispatcher bundle already carries, so the build and the dispatcher cannot name two locations.
|
|
26
|
+
function bundled_script_path(package_directory: string, script: string): string {
|
|
27
|
+
const name = `${path.basename(script, TYPESCRIPT_EXTENSION)}${JAVASCRIPT_EXTENSION}`
|
|
28
|
+
|
|
29
|
+
return path.join(package_directory, BUNDLED_COMMAND_DIRECTORY, name)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The file this dispatcher imports in its own process, or `undefined` to spawn tsx instead.
|
|
22
33
|
// `tsx_arguments` are node flags the script needs *before* its own code runs — today every one of
|
|
23
34
|
// them a form of `--env-file`, which has no in-process equivalent that reproduces node's own
|
|
24
35
|
// parsing and precedence. Those commands (`doctor`, `latest:scope`, `followup`, `notify`) keep a
|
|
25
36
|
// process of their own; each runs at most a few times per run, so none of them is where the cost
|
|
26
|
-
// this saves accumulates.
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
// this saves accumulates. Under plain node, which cannot load a `.ts` script at all, only a
|
|
38
|
+
// pre-built command runs in-process; every other one keeps spawning tsx.
|
|
39
|
+
function in_process_target(
|
|
40
|
+
entry: CommandEntry,
|
|
41
|
+
package_directory: string,
|
|
42
|
+
dispatcher_url: string,
|
|
43
|
+
): string | undefined {
|
|
44
|
+
if (entry.script === undefined || entry.tsx_arguments !== undefined) return undefined
|
|
45
|
+
if (is_typescript_dispatcher(dispatcher_url)) return path.join(package_directory, entry.script)
|
|
29
46
|
|
|
30
|
-
return
|
|
47
|
+
return entry.is_bundled === true
|
|
48
|
+
? bundled_script_path(package_directory, entry.script)
|
|
49
|
+
: undefined
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
// `process.exitCode` is typed `number | string | undefined`. Only the numeric form is an exit code
|
|
@@ -75,6 +94,6 @@ async function run_in_process(
|
|
|
75
94
|
return read_exit_code()
|
|
76
95
|
}
|
|
77
96
|
|
|
78
|
-
const josh_in_process = {
|
|
97
|
+
const josh_in_process = { bundled_script_path, in_process_target, run_in_process }
|
|
79
98
|
|
|
80
|
-
export { FAILURE_EXIT_CODE, josh_in_process }
|
|
99
|
+
export { BUNDLED_COMMAND_DIRECTORY, FAILURE_EXIT_CODE, josh_in_process }
|
|
@@ -2,6 +2,8 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
import { resolve_local_bin, resolve_package_bin } from '#scripts/build/local-bin'
|
|
5
|
+
import { core_budget } from '#scripts/gate/core-budget'
|
|
6
|
+
import { ancestor_directories } from '#scripts/lib/ancestor-directories'
|
|
5
7
|
import { package_version_schema } from '#scripts/lib/schemas'
|
|
6
8
|
import { resolve_spawn_exit } from '#scripts/lib/spawn-exit'
|
|
7
9
|
import { execaSync } from 'execa'
|
|
@@ -37,15 +39,11 @@ interface TsxRunner {
|
|
|
37
39
|
// from the bundled dist/josh.js (one level under the root) and from the tsx source at
|
|
38
40
|
// scripts/josh/ (two levels), so this file's depth no longer has to be hard-coded.
|
|
39
41
|
function find_package_directory(start_directory: string): string {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
current = parent
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return current
|
|
42
|
+
return (
|
|
43
|
+
ancestor_directories.nearest(start_directory, (directory) =>
|
|
44
|
+
existsSync(path.join(directory, PACKAGE_JSON)),
|
|
45
|
+
) ?? start_directory
|
|
46
|
+
)
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
const PACKAGE_DIR = find_package_directory(path.dirname(fileURLToPath(import.meta.url)))
|
|
@@ -226,14 +224,12 @@ async function run_script_entry(
|
|
|
226
224
|
entry: CommandEntry,
|
|
227
225
|
subcommand_arguments: Array<string>,
|
|
228
226
|
): Promise<number> {
|
|
229
|
-
const script_path = path.join(PACKAGE_DIR, entry.script ?? '')
|
|
230
227
|
const script_arguments = [...(entry.default_script_arguments ?? []), ...subcommand_arguments]
|
|
228
|
+
const target = josh_in_process.in_process_target(entry, PACKAGE_DIR, import.meta.url)
|
|
231
229
|
|
|
232
|
-
if (josh_in_process.
|
|
233
|
-
return await josh_in_process.run_in_process(script_path, script_arguments)
|
|
234
|
-
}
|
|
230
|
+
if (target !== undefined) return await josh_in_process.run_in_process(target, script_arguments)
|
|
235
231
|
|
|
236
|
-
return spawn_script_entry(entry,
|
|
232
|
+
return spawn_script_entry(entry, path.join(PACKAGE_DIR, entry.script ?? ''), script_arguments)
|
|
237
233
|
}
|
|
238
234
|
|
|
239
235
|
function github_prerequisite_exit(resolved: string, is_consumer: boolean): number | undefined {
|
|
@@ -274,7 +270,7 @@ function pre_dispatch_exit(
|
|
|
274
270
|
return USAGE_ERROR_EXIT_CODE
|
|
275
271
|
}
|
|
276
272
|
|
|
277
|
-
async function
|
|
273
|
+
async function run_entry(
|
|
278
274
|
entry: CommandEntry,
|
|
279
275
|
subcommand_arguments: Array<string>,
|
|
280
276
|
): Promise<number> {
|
|
@@ -283,6 +279,28 @@ async function dispatch_entry(
|
|
|
283
279
|
return await run_script_entry(entry, subcommand_arguments)
|
|
284
280
|
}
|
|
285
281
|
|
|
282
|
+
function command_weight(entry: CommandEntry): number | undefined {
|
|
283
|
+
return typeof entry.core_weight === 'function' ? entry.core_weight() : entry.core_weight
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// **Every command passes through here, so this is where a declared weight is reserved**
|
|
287
|
+
// (joshuafolkken/kit#3345). A heavy command run directly, outside `josh gate`, used to start its tools
|
|
288
|
+
// without claiming a place. A command under a parent that already holds its cores claims nothing:
|
|
289
|
+
// `core_budget.with_command_reservation` reads the mark that parent set.
|
|
290
|
+
async function dispatch_entry(
|
|
291
|
+
entry: CommandEntry,
|
|
292
|
+
subcommand_arguments: Array<string>,
|
|
293
|
+
): Promise<number> {
|
|
294
|
+
const weight = command_weight(entry)
|
|
295
|
+
|
|
296
|
+
if (weight === undefined) return await run_entry(entry, subcommand_arguments)
|
|
297
|
+
|
|
298
|
+
return await core_budget.with_command_reservation(
|
|
299
|
+
weight,
|
|
300
|
+
async () => await run_entry(entry, subcommand_arguments),
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
286
304
|
async function run_command(
|
|
287
305
|
cmd: string,
|
|
288
306
|
subcommand_arguments: Array<string>,
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto'
|
|
|
3
3
|
import { readFileSync, rmSync } from 'node:fs'
|
|
4
4
|
import { createServer, type Server } from 'node:net'
|
|
5
5
|
import path from 'node:path'
|
|
6
|
+
import { PROBE_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
6
7
|
import { PLATFORM_TEMP_ROOT } from './platform-temporary'
|
|
7
8
|
|
|
8
9
|
// Whether the process a record names is still the one that wrote it (joshuafolkken/kit#1245).
|
|
@@ -48,9 +49,6 @@ const PID_FLAG = '-p'
|
|
|
48
49
|
// runs in** — nothing is inherited, so the token depends on the process being asked about and on
|
|
49
50
|
// nothing whatever about who is asking.
|
|
50
51
|
const PROBE_ENVIRONMENT = { TZ: 'UTC', LC_ALL: 'C' }
|
|
51
|
-
// A probe that hung would hang `josh review:brief`, `josh bench` and the unit suite's worker share
|
|
52
|
-
// alike. Failing is harmless here: it answers "cannot tell", which every caller already handles.
|
|
53
|
-
const PROBE_TIMEOUT_MS = 2000
|
|
54
52
|
// `process.kill(pid, 0)` runs every permission check and delivers nothing, so it is the standard
|
|
55
53
|
// liveness probe: it throws `ESRCH` where the process is gone.
|
|
56
54
|
const LIVENESS_SIGNAL = 0
|
|
@@ -20,16 +20,16 @@ import { session_language, type Resolution } from './session-language'
|
|
|
20
20
|
|
|
21
21
|
const { DEFAULT_SESSION_LANG, ENV_KEY, resolve_session_lang } = session_language
|
|
22
22
|
const ENGLISH_OPT_IN_LANG = 'en'
|
|
23
|
-
const SCOPE_NOTE =
|
|
24
|
-
'write session dialogue, AskUserQuestion labels/descriptions and artifact prose (Issue bodies, comments, Telegram) in this language. English stays only for Issue/PR titles, code comments/test titles/commit messages, and script-fixed strings.'
|
|
25
23
|
|
|
26
24
|
// The file load happens in `main`, on the command path only; the resolver reads the environment.
|
|
25
|
+
// The line carries the value alone: what the language covers is `CLAUDE.md` → "Output language",
|
|
26
|
+
// already in context every turn, so repeating it here only spent context on each prompt.
|
|
27
27
|
function format_line(resolution: Resolution): string {
|
|
28
28
|
const suffix = resolution.is_default
|
|
29
29
|
? ` (default; set ${ENV_KEY}=${ENGLISH_OPT_IN_LANG} for English)`
|
|
30
30
|
: ''
|
|
31
31
|
|
|
32
|
-
return `Session language (${ENV_KEY}): ${resolution.lang}${suffix}
|
|
32
|
+
return `Session language (${ENV_KEY}): ${resolution.lang}${suffix}`
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function main(): void {
|
|
@@ -42,7 +42,6 @@ const session_language_cli = {
|
|
|
42
42
|
ENV_KEY,
|
|
43
43
|
format_line,
|
|
44
44
|
resolve_session_lang,
|
|
45
|
-
SCOPE_NOTE,
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
if (process.argv[1] === fileURLToPath(import.meta.url)) main()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
2
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
3
3
|
import { parseArgs } from 'node:util'
|
|
4
|
+
import { PROBE_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
4
5
|
import { run_liveness } from '#scripts/run/run-liveness'
|
|
5
6
|
import { lane_child_invocation } from './lane-child-invocation'
|
|
6
7
|
import { lane_handoff } from './lane-handoff'
|
|
@@ -66,7 +67,10 @@ interface RunConfig extends CheckConfig {
|
|
|
66
67
|
|
|
67
68
|
function is_process_running_default(issue: string): boolean {
|
|
68
69
|
const pattern = lane_child_invocation.process_pattern(issue)
|
|
69
|
-
const result = spawnSync('pgrep', ['-f', pattern], {
|
|
70
|
+
const result = spawnSync('pgrep', ['-f', pattern], {
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
73
|
+
})
|
|
70
74
|
|
|
71
75
|
if (result.status === PROCESS_FOUND) return true
|
|
72
76
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { run_issue_number } from '#scripts/run/run-issue-number'
|
|
2
|
-
import { run_ship_next, type ShipResume } from '#scripts/run/run-ship-next'
|
|
3
|
-
import type { Stage } from '#scripts/run/run-ship-stage'
|
|
2
|
+
import { run_ship_next, type ShipResume } from '#scripts/run/ship/run-ship-next'
|
|
3
|
+
import type { Stage } from '#scripts/run/ship/run-ship-stage'
|
|
4
4
|
|
|
5
5
|
interface ShipStop extends ShipResume {
|
|
6
6
|
stage: Stage
|
package/scripts/lane/lane-cli.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { epic_busy } from '#scripts/epic/epic-busy'
|
|
4
4
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
5
|
-
import type { OpenIssueData } from '#scripts/git/schemas'
|
|
5
|
+
import type { OpenIssueData } from '#scripts/git/git-schemas'
|
|
6
6
|
import { issue_cite, type IssueCiter } from '#scripts/issue/issue-cite'
|
|
7
7
|
import { error_text } from '#scripts/lib/error-message'
|
|
8
|
-
import { run_carry } from '#scripts/run/run-carry'
|
|
9
|
-
import { run_carry_reclaim } from '#scripts/run/run-carry-reclaim'
|
|
8
|
+
import { run_carry } from '#scripts/run/carry/run-carry'
|
|
9
|
+
import { run_carry_reclaim } from '#scripts/run/carry/run-carry-reclaim'
|
|
10
10
|
import { lane_await } from './lane-await'
|
|
11
11
|
import { lane_close, type CloseOutcome, type SweepOutcome } from './lane-close'
|
|
12
12
|
import { lane_dispatch, type DispatchOutcome } from './lane-dispatch'
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { agent_argv, type AgentArgv, type AgentArgvResult } from '#scripts/agent/agent-argv'
|
|
2
2
|
import { agent_role_profile, type AgentProfile } from '#scripts/agent/agent-role-profile'
|
|
3
3
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
4
|
+
import type { LabelWrite } from '#scripts/gh/git-gh-issue-write'
|
|
4
5
|
import { IN_PROGRESS_LABEL } from '#scripts/issue/issue-labels'
|
|
5
6
|
import { telegram_notify } from '#scripts/notify/telegram-notify'
|
|
6
7
|
import { detached_launch } from '#scripts/run/detached-launch'
|
|
8
|
+
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
9
|
+
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
7
10
|
import { run_ending } from '#scripts/run/run-ending'
|
|
8
|
-
import { run_event_stream } from '#scripts/run/run-event-stream'
|
|
9
|
-
import { run_event_stream_emit } from '#scripts/run/run-event-stream-emit'
|
|
10
11
|
import { run_issue_number } from '#scripts/run/run-issue-number'
|
|
11
12
|
import { lane_child_invocation } from './lane-child-invocation'
|
|
12
13
|
import { lane_child_marker } from './lane-child-marker'
|
|
@@ -79,7 +80,7 @@ type DispatchOutcome =
|
|
|
79
80
|
// that marks a child as holding its lane — the lane count, the offer filter, `run:progress` and the
|
|
80
81
|
// stale check all read it — so a child that ran without it would be counted as an empty lane and
|
|
81
82
|
// handed to a second session. Refusing the launch keeps "dispatched" and "counted as busy" one state.
|
|
82
|
-
| { kind: typeof LABEL_UNSET_KIND }
|
|
83
|
+
| { kind: typeof LABEL_UNSET_KIND; reason: string }
|
|
83
84
|
|
|
84
85
|
type LogOutcome = { kind: 'ready'; path: string } | { kind: 'unrecordable'; reason: string }
|
|
85
86
|
|
|
@@ -305,9 +306,10 @@ function prepared(lane: LaneInfo): Prepared {
|
|
|
305
306
|
// to its (idempotent) apply. `issue_add_label` creates the label if the repository lacks it — a
|
|
306
307
|
// `POST /issues/{N}/labels` provisions a missing label with a generated color (measured on
|
|
307
308
|
// joshuafolkken/kit#1026, `git-gh-issue-write.ts`) — so no separate `label_ensure` is needed here, and
|
|
308
|
-
// its
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
// its unapplied answer is a real refusal to launch rather than a note — confirmed by a read-back, so
|
|
310
|
+
// a write that landed but answered with an error still launches (joshuafolkken/kit#3312).
|
|
311
|
+
async function mark_in_progress(issue: string): Promise<LabelWrite> {
|
|
312
|
+
return await git_gh_command.issue_apply_label(issue, IN_PROGRESS_LABEL)
|
|
311
313
|
}
|
|
312
314
|
|
|
313
315
|
// The dispatch owns both halves of the marker: it applied it, so a launch that never started takes it
|
|
@@ -324,8 +326,12 @@ async function unmark_in_progress(issue: string): Promise<void> {
|
|
|
324
326
|
|
|
325
327
|
// A started child is recorded on the run's stream, which is where the stall detector ages the last
|
|
326
328
|
// dispatch from (joshuafolkken/kit#2464) — unrecorded, every stall read "hours since the last dispatch"
|
|
327
|
-
// minutes after a launch. A failed start records nothing: no child is running.
|
|
329
|
+
// minutes after a launch. A failed start records nothing: no child is running. The marker is claimed
|
|
330
|
+
// first, so a refused one starts nothing.
|
|
328
331
|
async function finish_dispatch(issue: string, request: StartRequest): Promise<DispatchOutcome> {
|
|
332
|
+
const mark = await mark_in_progress(issue)
|
|
333
|
+
|
|
334
|
+
if (!mark.is_applied) return { kind: LABEL_UNSET_KIND, reason: mark.reason }
|
|
329
335
|
const outcome = await started(request)
|
|
330
336
|
|
|
331
337
|
const is_failed = outcome.kind === 'failed'
|
|
@@ -353,8 +359,6 @@ async function dispatch_child(issue: string): Promise<DispatchOutcome> {
|
|
|
353
359
|
|
|
354
360
|
if (log.kind !== 'ready') return log
|
|
355
361
|
|
|
356
|
-
if (!(await mark_in_progress(issue))) return { kind: LABEL_UNSET_KIND }
|
|
357
|
-
|
|
358
362
|
return await finish_dispatch(issue, { lane, log_path: log.path, ...built })
|
|
359
363
|
}
|
|
360
364
|
|
|
@@ -410,7 +414,7 @@ function describe(outcome: DispatchOutcome, issue: string): string {
|
|
|
410
414
|
if (outcome.kind === 'unrecordable') return outcome.reason
|
|
411
415
|
|
|
412
416
|
if (outcome.kind === LABEL_UNSET_KIND) {
|
|
413
|
-
return `The child for #${issue} was not started: the \`${IN_PROGRESS_LABEL}\` label could not be applied, so the lane would be counted as empty. Nothing was launched.`
|
|
417
|
+
return `The child for #${issue} was not started: the \`${IN_PROGRESS_LABEL}\` label could not be applied (${outcome.reason}), so the lane would be counted as empty. Nothing was launched.`
|
|
414
418
|
}
|
|
415
419
|
|
|
416
420
|
if (outcome.kind === 'failed') {
|
|
@@ -185,9 +185,13 @@ function replace_assignment(
|
|
|
185
185
|
key: string,
|
|
186
186
|
): Array<string> {
|
|
187
187
|
return lines.flatMap((entry, index) => {
|
|
188
|
+
// One return type across the branches (sonarjs/function-return-type) keeps the array wrappers.
|
|
189
|
+
// eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- see above
|
|
188
190
|
if (index === keep) return [assignment]
|
|
191
|
+
if (is_key_line(entry, key)) return []
|
|
189
192
|
|
|
190
|
-
|
|
193
|
+
// eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- see above
|
|
194
|
+
return [entry]
|
|
191
195
|
})
|
|
192
196
|
}
|
|
193
197
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { git_common_directory } from '#scripts/git/git-common-directory'
|
|
2
|
-
import { run_ship_detach } from '#scripts/run/run-ship-detach'
|
|
2
|
+
import { run_ship_detach } from '#scripts/run/ship/run-ship-detach'
|
|
3
3
|
import { lane_child_marker, type MarkerSource } from './lane-child-marker'
|
|
4
4
|
|
|
5
5
|
// Whether a lane has handed its post-implementation region to the detached ship (joshuafolkken/kit#2962).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { buffered_process } from '#scripts/lib/buffered-process'
|
|
4
|
+
import { INSTALL_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
4
5
|
|
|
5
6
|
// Filling the lane, which is the other half of opening one (joshuafolkken/kit#1554).
|
|
6
7
|
//
|
|
@@ -34,12 +35,6 @@ const MANIFEST_FILE_NAME = 'package.json'
|
|
|
34
35
|
// The lane opens with no dependencies installed, and that is the reported state rather than a
|
|
35
36
|
// failure — there was nothing to install, so nothing failed.
|
|
36
37
|
const NO_MANIFEST_OUTPUT = `No ${MANIFEST_FILE_NAME} in the lane, so the dependency install was skipped.`
|
|
37
|
-
// The 3.5 s above is a warm store; a first run, or one after a dependency change, goes to the
|
|
38
|
-
// network, so that figure is not the bound to write here. Ten minutes covers that fetch and still
|
|
39
|
-
// ends `lane:open` when a registry never answers, rather than holding a parallel run open with
|
|
40
|
-
// nothing printed. It is well under the fan-out's own half hour because this one blocks a command a
|
|
41
|
-
// person is waiting on, not a check suite.
|
|
42
|
-
const INSTALL_TIMEOUT_MS = 600_000
|
|
43
38
|
|
|
44
39
|
interface InstallResult {
|
|
45
40
|
is_installed: boolean
|
|
@@ -85,10 +80,4 @@ async function install_dependencies(directory: string): Promise<InstallResult> {
|
|
|
85
80
|
const lane_install = { install_dependencies }
|
|
86
81
|
|
|
87
82
|
export type { InstallResult }
|
|
88
|
-
export {
|
|
89
|
-
lane_install,
|
|
90
|
-
INSTALL_ARGUMENTS,
|
|
91
|
-
INSTALL_TIMEOUT_MS,
|
|
92
|
-
MANIFEST_FILE_NAME,
|
|
93
|
-
NO_MANIFEST_OUTPUT,
|
|
94
|
-
}
|
|
83
|
+
export { lane_install, INSTALL_ARGUMENTS, MANIFEST_FILE_NAME, NO_MANIFEST_OUTPUT }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { josh_command } from '#scripts/josh/josh-run'
|
|
4
|
-
import { lane_registry } from '#scripts/lane/lane-registry'
|
|
4
|
+
import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
|
|
5
|
+
import { lane_vacant } from '#scripts/lane/lane-vacant'
|
|
5
6
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
6
7
|
import { INSTALL_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
7
8
|
import { execa } from 'execa'
|
|
@@ -88,17 +89,30 @@ type LaunchOutcome = { kind: 'launched'; pid: string } | { kind: 'unopened' } |
|
|
|
88
89
|
const UNOPENED: LaunchOutcome = { kind: 'unopened' }
|
|
89
90
|
const FAILED: LaunchOutcome = { kind: 'failed' }
|
|
90
91
|
|
|
92
|
+
async function close_if_vacant(lane: LaneInfo | undefined): Promise<void> {
|
|
93
|
+
if (lane === undefined || !(await lane_vacant.is_vacant(lane))) return
|
|
94
|
+
|
|
95
|
+
await josh_command.josh_run(['lane:close', lane.issue], should_forward_stderr)
|
|
96
|
+
}
|
|
97
|
+
|
|
91
98
|
// **A lane a park kept open is resumed, not reopened** (joshuafolkken/kit#3228). A child parked before
|
|
92
99
|
// its commit leaves its uncommitted work in its lane, and `lane:open` refuses that lane as
|
|
93
100
|
// `already-open` — so the released child is dispatched into the kept tree instead. **Only a lane a
|
|
94
101
|
// child was already dispatched into counts as kept**: `lane:dispatch` records the child's output in the
|
|
95
102
|
// lane's `.env`, and a lane whose install failed never got that far, so it still goes through
|
|
96
103
|
// `lane:open` and its refusal. So does a stranded lane, whose `.env` is gone with its tree.
|
|
104
|
+
//
|
|
105
|
+
// **A lane no child was dispatched into and that holds nothing is closed and opened afresh**
|
|
106
|
+
// (joshuafolkken/kit#3289) — a run cut between the open and the dispatch leaves one. It is reopened
|
|
107
|
+
// rather than reused because nothing recorded whether its install finished. A lane with work or a live
|
|
108
|
+
// process in it still meets `lane:open`'s refusal.
|
|
97
109
|
async function lane_directory(issue: string): Promise<string | undefined> {
|
|
98
110
|
const kept = await lane_registry.find_open_lane(issue)
|
|
99
111
|
|
|
100
112
|
if (kept?.output !== undefined) return kept.directory
|
|
101
113
|
|
|
114
|
+
await close_if_vacant(kept)
|
|
115
|
+
|
|
102
116
|
const opened = await josh_command.josh_run(['lane:open', issue], should_forward_stderr)
|
|
103
117
|
|
|
104
118
|
return opened.code === SUCCESS_EXIT_CODE ? opened.out : undefined
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { closeSync, constants, openSync, writeSync } from 'node:fs'
|
|
2
|
+
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
3
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
4
|
+
import { run_carry } from '#scripts/run/carry/run-carry'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
// The measurement ledger the lane limit is tuned against (joshuafolkken/kit#3355): one JSON line per
|
|
8
|
+
// lane merge, per finished `josh gate`, and per machine-load sample, read back by `josh lane:stats`.
|
|
9
|
+
//
|
|
10
|
+
// **Its own file rather than the run's event stream.** The stream is capped at 500 events and is
|
|
11
|
+
// rewritten whole on every append, which is right for "where is the run" and wrong for a measurement
|
|
12
|
+
// that spans days of one-a-minute load samples. This file is only ever appended to, so concurrent lanes
|
|
13
|
+
// never lose each other's lines to a read-modify-write race.
|
|
14
|
+
//
|
|
15
|
+
// **Keyed on the common git directory, the event stream's key**, so a lane child's gate, the parent's
|
|
16
|
+
// merge and the sampler all land in one ledger per repository. It sits in the temp root beside every
|
|
17
|
+
// other josh record; a reboot clears it, which is why `docs/maintainers/lane-limit-measurement.md`
|
|
18
|
+
// closes a period with `josh lane:stats` before one.
|
|
19
|
+
|
|
20
|
+
const LEDGER_PREFIX = 'josh-lane-ledger-'
|
|
21
|
+
const LEDGER_SUFFIX = '.jsonl'
|
|
22
|
+
const LINE_SEPARATOR = '\n'
|
|
23
|
+
const LEDGER_FILE_MODE = 0o600
|
|
24
|
+
// The flags `detached-launch.ts` appends its log with, for the same deterministic shared-temp path.
|
|
25
|
+
const { O_APPEND, O_CREAT, O_NOFOLLOW, O_WRONLY } = constants
|
|
26
|
+
// eslint-disable-next-line no-bitwise -- POSIX open flags are a bit field; `||` here would be a bug
|
|
27
|
+
const APPEND_FLAGS = O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW
|
|
28
|
+
|
|
29
|
+
const KIND = { MERGE: 'merge', GATE: 'gate', LOAD: 'load' } as const
|
|
30
|
+
|
|
31
|
+
const merge_schema = z.object({ kind: z.literal(KIND.MERGE), at: z.string(), issue: z.number() })
|
|
32
|
+
const gate_schema = z.object({
|
|
33
|
+
kind: z.literal(KIND.GATE),
|
|
34
|
+
at: z.string(),
|
|
35
|
+
elapsed_ms: z.number(),
|
|
36
|
+
is_passed: z.boolean(),
|
|
37
|
+
})
|
|
38
|
+
// `swap_mb` and `lanes` are optional because either read can fail on its own — a platform with no
|
|
39
|
+
// swap reading, a `git worktree` call that did not answer — and a sample is still worth its load.
|
|
40
|
+
const load_schema = z.object({
|
|
41
|
+
kind: z.literal(KIND.LOAD),
|
|
42
|
+
at: z.string(),
|
|
43
|
+
load: z.number(),
|
|
44
|
+
free_mb: z.number(),
|
|
45
|
+
swap_mb: z.number().optional(),
|
|
46
|
+
lanes: z.number().optional(),
|
|
47
|
+
})
|
|
48
|
+
const entry_schema = z.discriminatedUnion('kind', [merge_schema, gate_schema, load_schema])
|
|
49
|
+
|
|
50
|
+
type LedgerEntry = z.infer<typeof entry_schema>
|
|
51
|
+
type MergeEntry = z.infer<typeof merge_schema>
|
|
52
|
+
type GateEntry = z.infer<typeof gate_schema>
|
|
53
|
+
type LoadEntry = z.infer<typeof load_schema>
|
|
54
|
+
|
|
55
|
+
function target_of(repository: string): string {
|
|
56
|
+
return stamp_file.stamp_path(LEDGER_PREFIX, repository, LEDGER_SUFFIX)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The ledger of the repository this checkout belongs to, or `undefined` outside one. `git rev-parse`
|
|
60
|
+
// rejects outside a repository, and `josh gate` resolves this before it runs a check, so the rejection
|
|
61
|
+
// is read as "no ledger" rather than allowed to fail the gate it would only have measured.
|
|
62
|
+
async function target(): Promise<string | undefined> {
|
|
63
|
+
try {
|
|
64
|
+
const repository = await run_carry.repository_directory()
|
|
65
|
+
|
|
66
|
+
return repository === undefined ? undefined : target_of(repository)
|
|
67
|
+
} catch {
|
|
68
|
+
return undefined
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// **Append, never rewrite** — `O_APPEND` keeps each short line whole across concurrent writers, and
|
|
73
|
+
// `O_NOFOLLOW` plus `stamp_file`'s ownership test keep a planted link or another account's file from
|
|
74
|
+
// receiving it.
|
|
75
|
+
function append(path: string, entry: LedgerEntry): void {
|
|
76
|
+
const descriptor = openSync(path, APPEND_FLAGS, LEDGER_FILE_MODE)
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
if (stamp_file.is_own_regular_file(path)) {
|
|
80
|
+
writeSync(descriptor, `${JSON.stringify(entry)}${LINE_SEPARATOR}`)
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
closeSync(descriptor)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Best-effort: a measurement must never fail the gate or the merge it measures, so every error — no
|
|
88
|
+
// repository, an unwritable temp root — is dropped.
|
|
89
|
+
async function record(entry: LedgerEntry, path?: string): Promise<void> {
|
|
90
|
+
try {
|
|
91
|
+
const destination = path ?? (await target())
|
|
92
|
+
|
|
93
|
+
if (destination !== undefined) append(destination, entry)
|
|
94
|
+
} catch {
|
|
95
|
+
// Reporting is best-effort: a failed append is dropped rather than raised into the caller's work.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function now_iso(): string {
|
|
100
|
+
return new Date().toISOString()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// One finished `josh gate`, whether it passed or not — a failing gate cost the lane its time as well.
|
|
104
|
+
async function record_gate(path: string, elapsed_ms: number, is_passed: boolean): Promise<void> {
|
|
105
|
+
await record({ kind: KIND.GATE, at: now_iso(), elapsed_ms, is_passed }, path)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function record_merge(issue: number): Promise<void> {
|
|
109
|
+
await record({ kind: KIND.MERGE, at: now_iso(), issue })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parse_line(line: string): LedgerEntry | undefined {
|
|
113
|
+
return entry_schema.safeParse(json_value.parse_or_undefined(line)).data
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function is_present(entry: LedgerEntry | undefined): entry is LedgerEntry {
|
|
117
|
+
return entry !== undefined
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Every entry, oldest first. A malformed line — a write cut short — is skipped rather than making the
|
|
121
|
+
// whole ledger unreadable, and an absent or unowned file is an empty ledger.
|
|
122
|
+
function read_entries(path: string): ReadonlyArray<LedgerEntry> {
|
|
123
|
+
const raw = stamp_file.read_stamp_text(path)
|
|
124
|
+
|
|
125
|
+
if (raw === undefined) return []
|
|
126
|
+
|
|
127
|
+
return raw
|
|
128
|
+
.split(LINE_SEPARATOR)
|
|
129
|
+
.map((line) => parse_line(line.trim()))
|
|
130
|
+
.filter(is_present)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const lane_ledger = {
|
|
134
|
+
KIND,
|
|
135
|
+
append,
|
|
136
|
+
read_entries,
|
|
137
|
+
record,
|
|
138
|
+
record_gate,
|
|
139
|
+
record_merge,
|
|
140
|
+
target,
|
|
141
|
+
target_of,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type { GateEntry, LedgerEntry, LoadEntry, MergeEntry }
|
|
145
|
+
export { lane_ledger }
|
|
@@ -59,7 +59,7 @@ function walk_files(directory: string, relative: string): Array<string> {
|
|
|
59
59
|
return readdirSync(path.join(directory, relative), { withFileTypes: true }).flatMap((entry) => {
|
|
60
60
|
const child = relative === '' ? entry.name : `${relative}/${entry.name}`
|
|
61
61
|
|
|
62
|
-
return entry.isDirectory() ? walk_files(directory, child) :
|
|
62
|
+
return entry.isDirectory() ? walk_files(directory, child) : child
|
|
63
63
|
})
|
|
64
64
|
}
|
|
65
65
|
|