@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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { freemem, loadavg } from 'node:os'
|
|
3
|
+
import { execa } from 'execa'
|
|
4
|
+
import { lane_await } from './lane-await'
|
|
5
|
+
import type { LoadEntry } from './lane-ledger'
|
|
6
|
+
import { lane_registry, type LaneInfo } from './lane-registry'
|
|
7
|
+
|
|
8
|
+
// One machine-load sample for the lane-limit measurement (joshuafolkken/kit#3355): the one-minute load
|
|
9
|
+
// average, free memory, swap in use and how many lanes have a child working, read at one instant so
|
|
10
|
+
// `josh lane:stats` can set the load beside the lane count that produced it.
|
|
11
|
+
//
|
|
12
|
+
// **Swap is read per platform and is optional.** Node exposes no swap figure, so macOS asks `sysctl` and
|
|
13
|
+
// Linux reads `/proc/meminfo`; any other platform, or an output neither parser recognizes, records the
|
|
14
|
+
// sample without it rather than as a zero nobody measured.
|
|
15
|
+
|
|
16
|
+
const BYTES_PER_MB = 1_048_576
|
|
17
|
+
const MB_PER_GB = 1024
|
|
18
|
+
const KB_PER_MB = 1024
|
|
19
|
+
const DARWIN_SWAP = /used = (?<amount>[\d.]+)(?<unit>[MG])/u
|
|
20
|
+
const LINUX_SWAP_TOTAL = /^SwapTotal:\s+(?<kb>\d+) kB/mu
|
|
21
|
+
const LINUX_SWAP_FREE = /^SwapFree:\s+(?<kb>\d+) kB/mu
|
|
22
|
+
const MEMINFO_PATH = '/proc/meminfo'
|
|
23
|
+
const SYSCTL_ARGUMENTS = ['-n', 'vm.swapusage']
|
|
24
|
+
const GB_UNIT = 'G'
|
|
25
|
+
|
|
26
|
+
// `sysctl -n vm.swapusage` → `total = 5120.00M used = 3993.25M free = 1126.75M (encrypted)`.
|
|
27
|
+
function parse_darwin_swap(output: string): number | undefined {
|
|
28
|
+
const groups = DARWIN_SWAP.exec(output)?.groups
|
|
29
|
+
|
|
30
|
+
if (groups?.['amount'] === undefined) return undefined
|
|
31
|
+
|
|
32
|
+
const scale = groups['unit'] === GB_UNIT ? MB_PER_GB : 1
|
|
33
|
+
|
|
34
|
+
return Number(groups['amount']) * scale
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function kilobytes_of(meminfo: string, pattern: RegExp): number | undefined {
|
|
38
|
+
const kb = pattern.exec(meminfo)?.groups?.['kb']
|
|
39
|
+
|
|
40
|
+
return kb === undefined ? undefined : Number(kb)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// `/proc/meminfo` carries the total and the free swap; what is in use is their difference.
|
|
44
|
+
function parse_linux_swap(meminfo: string): number | undefined {
|
|
45
|
+
const total = kilobytes_of(meminfo, LINUX_SWAP_TOTAL)
|
|
46
|
+
const free = kilobytes_of(meminfo, LINUX_SWAP_FREE)
|
|
47
|
+
|
|
48
|
+
if (total === undefined || free === undefined) return undefined
|
|
49
|
+
|
|
50
|
+
return (total - free) / KB_PER_MB
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function read_swap_mb(
|
|
54
|
+
platform: NodeJS.Platform = process.platform,
|
|
55
|
+
): Promise<number | undefined> {
|
|
56
|
+
try {
|
|
57
|
+
if (platform === 'darwin') {
|
|
58
|
+
const { stdout } = await execa('sysctl', SYSCTL_ARGUMENTS)
|
|
59
|
+
|
|
60
|
+
return parse_darwin_swap(stdout)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return platform === 'linux' ? parse_linux_swap(await readFile(MEMINFO_PATH, 'utf8')) : undefined
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The lanes with a child at work. An open lane is not enough: a stranded lane, or one a park kept, holds
|
|
70
|
+
// a tree but no running child, so it adds no load — the child's process is what `lane:await` polls.
|
|
71
|
+
function count_working(
|
|
72
|
+
lanes: ReadonlyArray<LaneInfo>,
|
|
73
|
+
is_running: (issue: string) => boolean = lane_await.is_process_running_default,
|
|
74
|
+
): number {
|
|
75
|
+
return lanes.filter((lane) => !lane.is_stranded && is_running(lane.issue)).length
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function read_lanes(): Promise<number | undefined> {
|
|
79
|
+
try {
|
|
80
|
+
return count_working(await lane_registry.list_lanes())
|
|
81
|
+
} catch {
|
|
82
|
+
return undefined
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function sample(at: string): Promise<LoadEntry> {
|
|
87
|
+
const [swap_mb, lanes] = await Promise.all([read_swap_mb(), read_lanes()])
|
|
88
|
+
const [load = 0] = loadavg()
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
kind: 'load',
|
|
92
|
+
at,
|
|
93
|
+
load,
|
|
94
|
+
free_mb: Math.round(freemem() / BYTES_PER_MB),
|
|
95
|
+
swap_mb,
|
|
96
|
+
lanes,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const lane_load = {
|
|
101
|
+
count_working,
|
|
102
|
+
parse_darwin_swap,
|
|
103
|
+
parse_linux_swap,
|
|
104
|
+
read_swap_mb,
|
|
105
|
+
sample,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { lane_load }
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { setInterval as every } from 'node:timers/promises'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
|
+
import { lane_capacity } from './lane-capacity'
|
|
6
|
+
import { lane_ledger } from './lane-ledger'
|
|
7
|
+
import { lane_load } from './lane-load'
|
|
8
|
+
import { lane_stats } from './lane-stats'
|
|
9
|
+
|
|
10
|
+
// `josh lane:sample` / `josh lane:stats` — the lane-limit measurement (joshuafolkken/kit#3355).
|
|
11
|
+
//
|
|
12
|
+
// `lane:sample` appends one machine-load sample to the ledger, or one every `--every <seconds>` until it
|
|
13
|
+
// is stopped; `lane:stats` reduces a period of the ledger to the one table row #3347 records per lane
|
|
14
|
+
// limit. The merges and the gates write themselves (`run:merge`, `josh gate`), so a person runs only
|
|
15
|
+
// the sampler beside a `backlogrun` and closes the period with `lane:stats`. The procedure is
|
|
16
|
+
// `docs/maintainers/lane-limit-measurement.md`.
|
|
17
|
+
|
|
18
|
+
const ARGV_OFFSET = 2
|
|
19
|
+
const FAILURE_EXIT_CODE = 1
|
|
20
|
+
const MS_PER_SECOND = 1000
|
|
21
|
+
const SAMPLE_VERB = 'sample'
|
|
22
|
+
const STATS_VERB = 'stats'
|
|
23
|
+
const USAGE = [
|
|
24
|
+
'Usage: josh lane:sample [--every <seconds>]',
|
|
25
|
+
' josh lane:stats --period <days> [--limit <lane-limit>]',
|
|
26
|
+
].join('\n')
|
|
27
|
+
|
|
28
|
+
const SAMPLE_OPTIONS = { every: { type: 'string' } } as const
|
|
29
|
+
const STATS_OPTIONS = { period: { type: 'string' }, limit: { type: 'string' } } as const
|
|
30
|
+
|
|
31
|
+
// Where the ledger is and what "now" is, overridable so a test reads a fixed ledger at a fixed instant.
|
|
32
|
+
interface MeasureContext {
|
|
33
|
+
ledger_path?: string
|
|
34
|
+
now_ms?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function refuse(): number {
|
|
38
|
+
console.error(USAGE)
|
|
39
|
+
|
|
40
|
+
return FAILURE_EXIT_CODE
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A positive finite number, or `undefined` for anything else — a typo must not become a zero period.
|
|
44
|
+
function positive(text: string | undefined): number | undefined {
|
|
45
|
+
const value = Number(text)
|
|
46
|
+
|
|
47
|
+
return text !== undefined && Number.isFinite(value) && value > 0 ? value : undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function take_sample(context: MeasureContext): Promise<void> {
|
|
51
|
+
const at = new Date(context.now_ms ?? Date.now()).toISOString()
|
|
52
|
+
|
|
53
|
+
await lane_ledger.record(await lane_load.sample(at), context.ledger_path)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// The `--every` interval: `{ seconds: undefined }` for a single sample, `undefined` for a refusal.
|
|
57
|
+
function sample_interval(rest: ReadonlyArray<string>): { seconds: number | undefined } | undefined {
|
|
58
|
+
const values = cli_flags.values_of(rest, SAMPLE_OPTIONS)
|
|
59
|
+
|
|
60
|
+
if (values === undefined) return undefined
|
|
61
|
+
|
|
62
|
+
if (values.every === undefined) return { seconds: undefined }
|
|
63
|
+
|
|
64
|
+
const seconds = positive(values.every)
|
|
65
|
+
|
|
66
|
+
return seconds === undefined ? undefined : { seconds }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// A sample every `seconds` until a person stops the process, which is how a measurement period is held
|
|
70
|
+
// open; it never returns.
|
|
71
|
+
async function keep_sampling(context: MeasureContext, seconds: number): Promise<void> {
|
|
72
|
+
const ticks = every(seconds * MS_PER_SECOND, context)
|
|
73
|
+
|
|
74
|
+
for await (const tick_context of ticks) await take_sample(tick_context)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// One sample now, then — with `--every` — one per interval.
|
|
78
|
+
async function sample_command(
|
|
79
|
+
rest: ReadonlyArray<string>,
|
|
80
|
+
context: MeasureContext,
|
|
81
|
+
): Promise<number> {
|
|
82
|
+
const interval = sample_interval(rest)
|
|
83
|
+
|
|
84
|
+
if (interval === undefined) return refuse()
|
|
85
|
+
|
|
86
|
+
await take_sample(context)
|
|
87
|
+
|
|
88
|
+
if (interval.seconds !== undefined) await keep_sampling(context, interval.seconds)
|
|
89
|
+
|
|
90
|
+
return 0
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// The label defaults to the limit this process reads, which is the period's limit only when nobody
|
|
94
|
+
// changed it since — `--limit` names it outright.
|
|
95
|
+
function limit_label(given: string | undefined): string {
|
|
96
|
+
if (given !== undefined) return given
|
|
97
|
+
|
|
98
|
+
const choice = lane_capacity.lane_limit()
|
|
99
|
+
|
|
100
|
+
return choice.kind === 'limit' ? String(choice.limit) : lane_stats.MISSING
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function read_ledger(
|
|
104
|
+
context: MeasureContext,
|
|
105
|
+
): Promise<ReturnType<typeof lane_ledger.read_entries>> {
|
|
106
|
+
const path = context.ledger_path ?? (await lane_ledger.target())
|
|
107
|
+
|
|
108
|
+
return path === undefined ? [] : lane_ledger.read_entries(path)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function stats_command(
|
|
112
|
+
rest: ReadonlyArray<string>,
|
|
113
|
+
context: MeasureContext,
|
|
114
|
+
): Promise<number> {
|
|
115
|
+
const values = cli_flags.values_of(rest, STATS_OPTIONS)
|
|
116
|
+
const period_days = positive(values?.period)
|
|
117
|
+
|
|
118
|
+
if (values === undefined || period_days === undefined) return refuse()
|
|
119
|
+
|
|
120
|
+
const window = lane_stats.window_of(period_days, context.now_ms ?? Date.now())
|
|
121
|
+
const label = { limit: limit_label(values.limit), period_days }
|
|
122
|
+
const row = lane_stats.row(await read_ledger(context), window, label)
|
|
123
|
+
|
|
124
|
+
console.info(`${lane_stats.header()}\n${row}`)
|
|
125
|
+
|
|
126
|
+
return 0
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function run(argv: ReadonlyArray<string>, context: MeasureContext = {}): Promise<number> {
|
|
130
|
+
const [verb, ...rest] = argv
|
|
131
|
+
|
|
132
|
+
if (verb === SAMPLE_VERB) return await sample_command(rest, context)
|
|
133
|
+
|
|
134
|
+
return verb === STATS_VERB ? await stats_command(rest, context) : refuse()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function main(argv: ReadonlyArray<string>): Promise<void> {
|
|
138
|
+
process.exitCode = await run(argv)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const lane_measure_cli = {
|
|
142
|
+
USAGE,
|
|
143
|
+
run,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
|
|
147
|
+
|
|
148
|
+
export type { MeasureContext }
|
|
149
|
+
export { lane_measure_cli }
|
|
@@ -82,7 +82,10 @@ function without_label_line(issue: number): string {
|
|
|
82
82
|
|
|
83
83
|
function labelled_lines(labelled: ReadonlyArray<LabelledIssue>): Array<string> {
|
|
84
84
|
return labelled.flatMap((entry) => {
|
|
85
|
+
// One return type across the branches (sonarjs/function-return-type) keeps the array wrappers.
|
|
86
|
+
// eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- see above
|
|
85
87
|
if (entry.liveness === 'stopped') return [stopped_line(entry.issue)]
|
|
88
|
+
// eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- see above
|
|
86
89
|
if (entry.liveness === 'unknown') return [unknown_line(entry.issue)]
|
|
87
90
|
|
|
88
91
|
return []
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { PROBE_TIMEOUT_MS } from '#scripts/lib/timeouts'
|
|
2
3
|
import { lane_child_invocation } from './lane-child-invocation'
|
|
3
4
|
|
|
4
5
|
// Ending a lane child that did not end itself (joshuafolkken/kit#2421).
|
|
@@ -24,9 +25,6 @@ const CHILDREN_OF_FLAG = '-P'
|
|
|
24
25
|
const PS_COMMAND = '/bin/ps'
|
|
25
26
|
const PARENT_FORMAT = ['-o', 'ppid=', '-p']
|
|
26
27
|
const TERMINATE_SIGNAL = 'SIGTERM'
|
|
27
|
-
// A probe that hung would hang the close it runs inside; failing answers "no process", which leaves
|
|
28
|
-
// the lane exactly as it was before this module existed.
|
|
29
|
-
const PROBE_TIMEOUT_MS = 2000
|
|
30
28
|
// `0` addresses the caller's own process group and `1` is init — neither is ever a lane child.
|
|
31
29
|
const LOWEST_CHILD_PID = 2
|
|
32
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent_argv } from '#scripts/agent/agent-argv'
|
|
2
2
|
import { detached_launch, type LaunchResult } from '#scripts/run/detached-launch'
|
|
3
|
-
import { run_ship_detach } from '#scripts/run/run-ship-detach'
|
|
3
|
+
import { run_ship_detach } from '#scripts/run/ship/run-ship-detach'
|
|
4
4
|
import { lane_child_invocation } from './lane-child-invocation'
|
|
5
5
|
import { lane_child_marker } from './lane-child-marker'
|
|
6
6
|
import { lane_dispatch_log } from './lane-dispatch-log'
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type { GateEntry, LedgerEntry, LoadEntry } from './lane-ledger'
|
|
2
|
+
|
|
3
|
+
// The lane-limit measurement's one table row (joshuafolkken/kit#3355): the ledger entries of one period
|
|
4
|
+
// reduced to throughput, gate duration and machine load, in the column order
|
|
5
|
+
// `docs/maintainers/lane-limit-measurement.md` documents, so the row pastes into #3347's table as is.
|
|
6
|
+
//
|
|
7
|
+
// **A column with no samples prints `—`, never `0`.** A period nobody ran the sampler through has no
|
|
8
|
+
// load, which is not a load of zero; a zero would be read as an idle machine.
|
|
9
|
+
//
|
|
10
|
+
// **Throughput is per active hour** — the time load samples saw a lane child working — rather than per
|
|
11
|
+
// calendar hour, so the nights between runs in a several-day period do not dilute the rate. The gaps
|
|
12
|
+
// between work entries cannot stand in for it: at a low limit the gap from one merge to the next child's
|
|
13
|
+
// first gate is a whole implementation, which a gap cap drops, inflating exactly the low limits' rate.
|
|
14
|
+
// Only a period no sample saw working falls back to the gaps of up to an hour between merges and gates.
|
|
15
|
+
//
|
|
16
|
+
// **The means take working samples only** for the same reason: the sampler runs through the nights,
|
|
17
|
+
// and their idle samples would read four busy lanes as about one. Peaks and minima take every sample.
|
|
18
|
+
|
|
19
|
+
const MS_PER_MINUTE = 60_000
|
|
20
|
+
const MS_PER_HOUR = 3_600_000
|
|
21
|
+
// A busy lane pool finishes a gate well within this; a longer silence is time nobody was running work.
|
|
22
|
+
const ACTIVE_GAP_MS = MS_PER_HOUR
|
|
23
|
+
const MS_PER_DAY = 86_400_000
|
|
24
|
+
const MB_PER_GB = 1024
|
|
25
|
+
const HALF = 2
|
|
26
|
+
const DECIMALS = 1
|
|
27
|
+
const MISSING = '—'
|
|
28
|
+
const CELL_SEPARATOR = ' | '
|
|
29
|
+
|
|
30
|
+
const COLUMNS = [
|
|
31
|
+
'JOSH_LANE_LIMIT',
|
|
32
|
+
'period (days)',
|
|
33
|
+
'merges',
|
|
34
|
+
'merges / hour',
|
|
35
|
+
'effective lanes',
|
|
36
|
+
'gate median (min)',
|
|
37
|
+
'gate max (min)',
|
|
38
|
+
'load peak',
|
|
39
|
+
'load mean',
|
|
40
|
+
'swap peak (GB)',
|
|
41
|
+
'swap mean (GB)',
|
|
42
|
+
'free memory min (GB)',
|
|
43
|
+
'free memory mean (GB)',
|
|
44
|
+
'load samples',
|
|
45
|
+
] as const
|
|
46
|
+
|
|
47
|
+
interface StatsWindow {
|
|
48
|
+
since_ms: number
|
|
49
|
+
until_ms: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface RowLabel {
|
|
53
|
+
limit: string
|
|
54
|
+
period_days: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function window_of(period_days: number, now_ms: number): StatsWindow {
|
|
58
|
+
return { since_ms: now_ms - period_days * MS_PER_DAY, until_ms: now_ms }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function in_window(entries: ReadonlyArray<LedgerEntry>, window: StatsWindow): Array<LedgerEntry> {
|
|
62
|
+
return entries.filter((entry) => {
|
|
63
|
+
const at = Date.parse(entry.at)
|
|
64
|
+
|
|
65
|
+
return at >= window.since_ms && at <= window.until_ms
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function is_gate(entry: LedgerEntry): entry is GateEntry {
|
|
70
|
+
return entry.kind === 'gate'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function is_load(entry: LedgerEntry): entry is LoadEntry {
|
|
74
|
+
return entry.kind === 'load'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function is_number(value: number | undefined): value is number {
|
|
78
|
+
return value !== undefined
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mean(values: ReadonlyArray<number>): number | undefined {
|
|
82
|
+
if (values.length === 0) return undefined
|
|
83
|
+
|
|
84
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function median(values: ReadonlyArray<number>): number | undefined {
|
|
88
|
+
if (values.length === 0) return undefined
|
|
89
|
+
|
|
90
|
+
const sorted = values.toSorted((left, right) => left - right)
|
|
91
|
+
const middle = Math.floor(sorted.length / HALF)
|
|
92
|
+
|
|
93
|
+
return sorted.length % HALF === 0 ? mean(sorted.slice(middle - 1, middle + 1)) : sorted[middle]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function peak(values: ReadonlyArray<number>): number | undefined {
|
|
97
|
+
return values.length === 0 ? undefined : Math.max(...values)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function lowest(values: ReadonlyArray<number>): number | undefined {
|
|
101
|
+
return values.length === 0 ? undefined : Math.min(...values)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type WorkingEntry = LoadEntry & { lanes: number }
|
|
105
|
+
|
|
106
|
+
function is_working(sample: LoadEntry): sample is WorkingEntry {
|
|
107
|
+
return (sample.lanes ?? 0) > 0
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function sum_hours(gaps: ReadonlyArray<number>): number | undefined {
|
|
111
|
+
const active = gaps.filter((gap) => gap <= ACTIVE_GAP_MS).reduce((sum, gap) => sum + gap, 0)
|
|
112
|
+
|
|
113
|
+
return active > 0 ? active / MS_PER_HOUR : undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The fallback for a period no sampler saw working: the gaps between consecutive work entries (merges
|
|
117
|
+
// and gates) no longer than `ACTIVE_GAP_MS`.
|
|
118
|
+
function gap_hours(entries: ReadonlyArray<LedgerEntry>): number | undefined {
|
|
119
|
+
const instants = entries
|
|
120
|
+
.filter((entry) => !is_load(entry))
|
|
121
|
+
.map((entry) => Date.parse(entry.at))
|
|
122
|
+
.toSorted((left, right) => left - right)
|
|
123
|
+
|
|
124
|
+
return sum_hours(
|
|
125
|
+
instants.slice(1).map((instant, index) => instant - (instants[index] ?? instant)),
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// The time from each working sample to the next sample. The cap still applies, so a sampler stopped
|
|
130
|
+
// mid-run does not count its silence as work.
|
|
131
|
+
function sampled_hours(samples: ReadonlyArray<LoadEntry>): number | undefined {
|
|
132
|
+
const sorted = samples.toSorted((left, right) => Date.parse(left.at) - Date.parse(right.at))
|
|
133
|
+
const gaps = sorted
|
|
134
|
+
.slice(1)
|
|
135
|
+
.map((next, index) => ({ sample: sorted[index] ?? next, next }))
|
|
136
|
+
.filter(({ sample }) => is_working(sample))
|
|
137
|
+
.map(({ sample, next }) => Date.parse(next.at) - Date.parse(sample.at))
|
|
138
|
+
|
|
139
|
+
return sum_hours(gaps)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// The hours work was under way; `undefined` when nothing leaves any active time.
|
|
143
|
+
function active_hours(entries: ReadonlyArray<LedgerEntry>): number | undefined {
|
|
144
|
+
const samples = entries.filter(is_load)
|
|
145
|
+
|
|
146
|
+
return samples.some((sample) => is_working(sample)) ? sampled_hours(samples) : gap_hours(entries)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function gigabytes(megabytes: number | undefined): number | undefined {
|
|
150
|
+
return megabytes === undefined ? undefined : megabytes / MB_PER_GB
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function cell(value: number | undefined): string {
|
|
154
|
+
return value === undefined ? MISSING : value.toFixed(DECIMALS)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function throughput_cells(entries: ReadonlyArray<LedgerEntry>): Array<string> {
|
|
158
|
+
const merges = entries.filter((entry) => entry.kind === 'merge').length
|
|
159
|
+
const hours = active_hours(entries)
|
|
160
|
+
const lanes = entries
|
|
161
|
+
.filter(is_load)
|
|
162
|
+
.filter(is_working)
|
|
163
|
+
.map((entry) => entry.lanes)
|
|
164
|
+
const per_hour = hours === undefined ? undefined : merges / hours
|
|
165
|
+
|
|
166
|
+
return [String(merges), cell(per_hour), cell(mean(lanes))]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function gate_cells(entries: ReadonlyArray<LedgerEntry>): Array<string> {
|
|
170
|
+
const minutes = entries.filter(is_gate).map((entry) => entry.elapsed_ms / MS_PER_MINUTE)
|
|
171
|
+
|
|
172
|
+
return [cell(median(minutes)), cell(peak(minutes))]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function load_cells(samples: ReadonlyArray<LoadEntry>): Array<string> {
|
|
176
|
+
const working = samples.filter(is_working)
|
|
177
|
+
const swap = samples.map((sample) => sample.swap_mb).filter(is_number)
|
|
178
|
+
const working_swap = working.map((sample) => sample.swap_mb).filter(is_number)
|
|
179
|
+
const free = samples.map((sample) => sample.free_mb)
|
|
180
|
+
const working_free = working.map((sample) => sample.free_mb)
|
|
181
|
+
|
|
182
|
+
return [
|
|
183
|
+
cell(peak(samples.map((sample) => sample.load))),
|
|
184
|
+
cell(mean(working.map((sample) => sample.load))),
|
|
185
|
+
cell(gigabytes(peak(swap))),
|
|
186
|
+
cell(gigabytes(mean(working_swap))),
|
|
187
|
+
cell(gigabytes(lowest(free))),
|
|
188
|
+
cell(gigabytes(mean(working_free))),
|
|
189
|
+
String(samples.length),
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function to_row(cells: ReadonlyArray<string>): string {
|
|
194
|
+
return `| ${cells.join(CELL_SEPARATOR)} |`
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function header(): string {
|
|
198
|
+
return [to_row(COLUMNS), to_row(COLUMNS.map(() => '---'))].join('\n')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// The period's row: `entries` is the whole ledger, filtered here to `window`.
|
|
202
|
+
function row(entries: ReadonlyArray<LedgerEntry>, window: StatsWindow, label: RowLabel): string {
|
|
203
|
+
const period = in_window(entries, window)
|
|
204
|
+
|
|
205
|
+
return to_row([
|
|
206
|
+
label.limit,
|
|
207
|
+
String(label.period_days),
|
|
208
|
+
...throughput_cells(period),
|
|
209
|
+
...gate_cells(period),
|
|
210
|
+
...load_cells(period.filter(is_load)),
|
|
211
|
+
])
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const lane_stats = {
|
|
215
|
+
COLUMNS,
|
|
216
|
+
MISSING,
|
|
217
|
+
header,
|
|
218
|
+
median,
|
|
219
|
+
row,
|
|
220
|
+
window_of,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export type { RowLabel, StatsWindow }
|
|
224
|
+
export { lane_stats }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { git_command } from '#scripts/git/git-command'
|
|
2
|
+
import { run_hold } from '#scripts/run/hold/run-hold'
|
|
3
|
+
import { lane_await } from './lane-await'
|
|
4
|
+
import type { LaneInfo } from './lane-registry'
|
|
5
|
+
|
|
6
|
+
// Whether an open lane holds nothing worth keeping (joshuafolkken/kit#3289): no commit on its branch
|
|
7
|
+
// beyond the default branch, a clean work tree, and no child or ship supervisor alive for its issue. A
|
|
8
|
+
// `backlogrun` cut between `lane:open` and `lane:dispatch` leaves exactly such a lane, and `lane:open`
|
|
9
|
+
// refuses it as `already-open` — so the next launch of that issue failed on a lane with nothing in it.
|
|
10
|
+
//
|
|
11
|
+
// **Every unreadable answer is "not vacant"**: `is_tree_dirty` already reads an unreadable status as
|
|
12
|
+
// dirty, and a branch whose commits cannot be counted is kept rather than closed, because closing it
|
|
13
|
+
// deletes the branch and anything on it.
|
|
14
|
+
async function has_no_commits(lane: LaneInfo): Promise<boolean> {
|
|
15
|
+
try {
|
|
16
|
+
const base = await git_command.default_branch_reference()
|
|
17
|
+
|
|
18
|
+
return (await git_command.commit_count_beyond(base, lane.branch)) === 0
|
|
19
|
+
} catch {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function is_occupied(lane: LaneInfo): boolean {
|
|
25
|
+
return lane.is_stranded || lane_await.is_process_running_default(lane.issue)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function is_vacant(lane: LaneInfo): Promise<boolean> {
|
|
29
|
+
if (is_occupied(lane) || (await run_hold.is_tree_dirty(lane.directory))) return false
|
|
30
|
+
|
|
31
|
+
return await has_no_commits(lane)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const lane_vacant = { is_vacant }
|
|
35
|
+
|
|
36
|
+
export { lane_vacant }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
2
|
-
import { run_ship_detach } from '#scripts/run/run-ship-detach'
|
|
3
|
-
import { run_ship_probe } from '#scripts/run/run-ship-probe'
|
|
2
|
+
import { run_ship_detach } from '#scripts/run/ship/run-ship-detach'
|
|
3
|
+
import { run_ship_probe } from '#scripts/run/ship/run-ship-probe'
|
|
4
4
|
|
|
5
5
|
const POLL_MS = 250
|
|
6
6
|
type ShipEnding = 'none' | 'success' | 'failed' | 'abnormal'
|