@joshuafolkken/kit 1.1784.0 → 1.2114.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/agents/reviewer-verification.md +19 -0
- package/.claude/agents/reviewer.md +19 -0
- package/.claude/settings.json +5 -6
- package/.claude/skills/dependency-update/SKILL.md +9 -10
- package/.claude/skills/workflow-commands/SKILL.md +6 -6
- package/.claude/skills/workflow-commands/background-commands.md +8 -5
- package/.claude/skills/workflow-commands/backlogrun-child.md +3 -1
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-park.md +5 -2
- package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -3
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
- package/.claude/skills/workflow-commands/backlogrun.md +1 -1
- package/.claude/skills/workflow-commands/chain-rule.md +41 -32
- package/.claude/skills/workflow-commands/delegation.md +6 -6
- package/.claude/skills/workflow-commands/entry-sequence.md +2 -2
- package/.claude/skills/workflow-commands/followup.md +7 -5
- package/.claude/skills/workflow-commands/fullrun-steps.md +6 -6
- package/.claude/skills/workflow-commands/fullrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +9 -4
- package/.claude/skills/workflow-commands/issue-fold-existing.md +2 -1
- package/.claude/skills/workflow-commands/kickoff.md +2 -2
- package/.claude/skills/workflow-commands/observation-filing.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +6 -4
- package/.claude/skills/workflow-commands/pre-gate-cut.md +9 -7
- package/.claude/skills/workflow-commands/prerequisite.md +3 -2
- package/.claude/skills/workflow-commands/retrospective.md +2 -2
- package/.claude/skills/workflow-commands/split-assessment.md +7 -5
- package/.codex/hooks.json +5 -5
- package/.github/actions/setup-pnpm/action.yml +9 -16
- package/.github/dependabot.yml +16 -0
- package/.github/workflows/auto-tag.yml +45 -0
- package/.github/workflows/ci.yml +155 -15
- package/.github/workflows/dependabot-auto-merge.yml +63 -4
- package/.github/workflows/production.yml +8 -0
- package/.github/workflows/publish.yml +59 -45
- package/.vscode/settings.json +19 -0
- package/CLAUDE.md +5 -12
- package/SECURITY.md +2 -2
- package/dist/CLAUDE.md +5 -12
- package/dist/commands/document-read-cli.js +2 -1
- package/dist/commands/document-section-main.js +2 -1
- package/dist/commands/read-set-cli.js +194 -134
- package/dist/commands/run-prep-main.js +9694 -10173
- package/dist/commands/run-step-cli.js +22138 -18285
- package/dist/hooks/audit-provision.js +23 -5
- package/dist/hooks/chunk-3GKE653K.js +6204 -0
- package/dist/hooks/chunk-45CJJOO7.js +1243 -0
- package/dist/hooks/{chunk-CWMBGKYW.js → chunk-5PM2JRGV.js} +2153 -856
- package/dist/hooks/{chunk-WR2KQAYZ.js → chunk-7SIUNQXK.js} +1207 -1470
- package/dist/hooks/{chunk-O2YBIDSI.js → chunk-CQD4LJE6.js} +1682 -1130
- package/dist/hooks/{chunk-VWWCFGWK.js → chunk-OPSRGWFU.js} +1 -1
- package/dist/hooks/{chunk-XGBNSWEI.js → chunk-UJ3FPMMM.js} +132 -49
- package/dist/hooks/chunk-VVNYJWEI.js +30 -0
- package/dist/hooks/chunk-Z6DQJKH3.js +2806 -0
- package/dist/hooks/codex-hook-adapter.js +8 -8
- package/dist/hooks/format-edited.js +4 -5
- package/dist/hooks/inputs.json +415 -389
- package/dist/hooks/pretool-guard.js +7 -6
- package/dist/hooks/session-lang.js +101 -258
- package/dist/hooks/stop-guard.js +1744 -2576
- package/dist/josh.js +7133 -6368
- package/dist/version/index.js +161 -1
- package/eslint/rules/comment-why-only.js +108 -0
- package/package.json +15 -8
- package/prompts/coding-standards.md +1 -1
- package/prompts/collaboration-workflow/file-edits.md +14 -11
- package/prompts/collaboration-workflow/issue-citation.md +0 -4
- package/prompts/collaboration-workflow/issue-template.md +40 -21
- package/prompts/collaboration-workflow/lane-instructions.md +3 -1
- package/prompts/collaboration-workflow/measurement.md +8 -1
- package/prompts/collaboration-workflow/operating-rules.md +11 -2
- package/prompts/collaboration-workflow/output-bounds.md +6 -2
- package/prompts/collaboration-workflow/plan-comment.md +1 -1
- package/prompts/collaboration-workflow/principles.md +9 -0
- package/prompts/collaboration-workflow/residency.md +3 -2
- package/prompts/collaboration-workflow/rule-delivery.md +13 -1
- package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
- package/prompts/collaboration-workflow/wip-cap.md +3 -1
- package/prompts/review-rubric.md +8 -0
- package/prompts/review.md +31 -22
- package/prompts/testing-guide.md +1 -0
- package/scripts/agent/agent-argv.ts +14 -16
- package/scripts/agent/agent-provider.ts +138 -0
- package/scripts/agent/agent-role-profile.ts +108 -156
- package/scripts/agent/agent-safe-text.ts +20 -5
- package/scripts/agent/api-usage-limit.ts +65 -0
- package/scripts/agent/claude-agent-argv.ts +19 -9
- package/scripts/agent/codex-agent-argv.ts +118 -22
- package/scripts/agent/usage-limit-reset.ts +179 -0
- package/scripts/auth/claude-token-output.ts +2 -2
- package/scripts/auto-ok/auto-ok-author.ts +53 -0
- package/scripts/auto-ok/auto-ok-cli.ts +20 -9
- package/scripts/backlog/backlog-defect-priority.ts +1 -1
- package/scripts/backlog/backlog-drive-cli.ts +6 -1
- package/scripts/backlog/backlog-drive-hold.ts +3 -3
- package/scripts/backlog/backlog-drive-idle.ts +10 -0
- package/scripts/backlog/backlog-drive-launch-pass.ts +90 -0
- package/scripts/backlog/backlog-drive-launch.ts +8 -2
- package/scripts/backlog/backlog-drive-named-offer.ts +73 -30
- package/scripts/backlog/backlog-drive-named.ts +28 -7
- package/scripts/backlog/backlog-drive-paused.ts +5 -4
- package/scripts/backlog/backlog-drive-restore.ts +11 -3
- package/scripts/backlog/backlog-drive.ts +21 -60
- package/scripts/backlog/backlog-gate-scope.ts +61 -0
- package/scripts/backlog/backlog-measure-close.ts +112 -0
- package/scripts/backlog/backlog-next.ts +39 -49
- package/scripts/backlog/backlog-occupied.ts +2 -2
- package/scripts/backlog/backlog-overlap.ts +42 -8
- package/scripts/backlog/backlog-plan-cli.ts +4 -1
- package/scripts/backlog/backlog-plan-comment.ts +81 -0
- package/scripts/backlog/backlog-plan-read.ts +19 -6
- package/scripts/backlog/backlog-pool.ts +50 -12
- package/scripts/backlog/backlog-rank.ts +60 -13
- package/scripts/backlog/backlog-stalled-detect.ts +2 -1
- package/scripts/backlog/backlog-waves.ts +17 -2
- package/scripts/backlog/backlogrun-launch.ts +7 -4
- package/scripts/cost-runtime/context-cut-payback.ts +27 -25
- package/scripts/cost-runtime/cost-cli.ts +37 -16
- package/scripts/cost-runtime/cost-pricing.ts +9 -0
- package/scripts/cost-runtime/cost-verdict.ts +4 -1
- package/scripts/delegation/delegation-policy.ts +2 -2
- package/scripts/delegation/duplicate-read-guard.ts +1 -0
- package/scripts/delegation/investigation-guard.ts +14 -2
- package/scripts/delegation/investigation-reads.ts +45 -23
- package/scripts/dependabot/dependabot-cooldown.ts +3 -2
- package/scripts/discovery/repo-party.ts +80 -20
- package/scripts/discovery/session-owner-pin.ts +51 -0
- package/scripts/document/capped-print.ts +1 -1
- package/scripts/document/command-reference-line-budget.ts +42 -0
- package/scripts/document/document-byte-budget.ts +17 -7
- package/scripts/document/document-checks.ts +20 -0
- package/scripts/document/document-ratchet-check.ts +101 -0
- package/scripts/document/document-section.ts +1 -0
- package/scripts/document/entry-read-budget.ts +7 -3
- package/scripts/document/issue-citation-budget.ts +94 -0
- package/scripts/epic/epic-cli-argv.ts +2 -2
- package/scripts/epic/epic-fetch.ts +4 -2
- package/scripts/epic/epic-graph.ts +7 -2
- package/scripts/epic/epic-issue.ts +3 -1
- package/scripts/epic/epic-rank.ts +5 -0
- package/scripts/exports/unused-members-check.ts +62 -0
- package/scripts/exports/unused-members-cli.ts +6 -51
- package/scripts/followup/followup-ci-path.ts +67 -0
- package/scripts/followup/followup-coderabbit-retrigger.ts +261 -0
- package/scripts/followup/followup-coderabbit-wait.ts +13 -10
- package/scripts/followup/followup-failed-tests.ts +160 -0
- package/scripts/followup/followup-job-needs.ts +100 -0
- package/scripts/followup/followup-job-rerun.ts +303 -0
- package/scripts/followup/followup-main-move.ts +113 -0
- package/scripts/followup/git-followup-finish.ts +43 -11
- package/scripts/followup/git-followup-pending.ts +3 -3
- package/scripts/followup/git-followup-stages.ts +41 -11
- package/scripts/followup/git-pr-followup-checks.ts +55 -16
- package/scripts/followup/git-pr-followup-wrapup.ts +24 -5
- package/scripts/followup/git-pr-followup.ts +62 -27
- package/scripts/fresh-machine/fresh-machine-script.ts +5 -9
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +2 -2
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +2 -2
- package/scripts/gate/busy-average.ts +34 -2
- package/scripts/gate/check-related-pinned.ts +43 -0
- package/scripts/gate/check-related-reuse.ts +64 -0
- package/scripts/gate/check-related-section.ts +57 -0
- package/scripts/gate/check-related-unplanned.ts +34 -0
- package/scripts/gate/check-related.ts +265 -0
- package/scripts/gate/command-memory.ts +1 -1
- package/scripts/gate/core-admission.ts +24 -6
- package/scripts/gate/core-budget-timing.ts +20 -6
- package/scripts/gate/core-budget.ts +40 -11
- package/scripts/gate/core-wait-record.ts +3 -2
- package/scripts/gate/core-wait.ts +25 -2
- package/scripts/gate/core-weights.ts +15 -7
- package/scripts/gate/gate-labels.ts +46 -0
- package/scripts/gate/gate-ledger.ts +14 -6
- package/scripts/gate/gate-plan.ts +103 -58
- package/scripts/gate/gate-report.ts +9 -7
- package/scripts/gate/gate-run-steps.ts +26 -3
- package/scripts/gate/gate-skip.ts +45 -6
- package/scripts/gate/gate-static-group.ts +60 -0
- package/scripts/gate/gate-steps.ts +2 -1
- package/scripts/gate/gate-tree.ts +4 -2
- package/scripts/gate/hook-gate-reuse.ts +19 -2
- package/scripts/gate/metrics-totals.ts +59 -0
- package/scripts/gate/scoped-green.ts +68 -15
- package/scripts/gate/since-green.ts +14 -6
- package/scripts/gate/structural-checks.ts +96 -0
- package/scripts/gate/verification-gate.ts +51 -16
- package/scripts/gate/verification-inputs.ts +21 -6
- package/scripts/gate/verification-tail.ts +52 -0
- package/scripts/gh/gh-author-association.ts +23 -2
- package/scripts/gh/git-ai-review-scan.ts +1 -1
- package/scripts/gh/git-gh-api-path.ts +12 -3
- package/scripts/gh/git-gh-comments-unreadable.ts +36 -0
- package/scripts/gh/git-gh-exec.ts +32 -2
- package/scripts/gh/git-gh-issue-read.ts +4 -2
- package/scripts/gh/git-gh-issue-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +8 -2
- package/scripts/gh/git-gh-issue.ts +12 -5
- package/scripts/gh/git-gh-pr-auto-merge.ts +13 -6
- package/scripts/gh/git-gh-pr-graphql-state.ts +56 -0
- package/scripts/gh/git-gh-pr-read.ts +74 -13
- package/scripts/gh/git-gh-pr-rest.ts +3 -1
- package/scripts/gh/git-gh-pr-snapshot.ts +22 -7
- package/scripts/gh/git-gh-pr-too-large.ts +20 -0
- package/scripts/gh/git-gh-pr.ts +21 -7
- package/scripts/gh/git-gh-retry.ts +141 -0
- package/scripts/gh/git-pr-ai-review.ts +15 -6
- package/scripts/gh/git-pr-checks-eval.ts +3 -0
- package/scripts/gh/git-pr-checks.ts +5 -5
- package/scripts/git/base-tree.ts +30 -0
- package/scripts/git/change-base.ts +4 -10
- package/scripts/git/changed-paths-sync.ts +63 -0
- package/scripts/git/changed-paths.ts +16 -1
- package/scripts/git/git-common-directory.ts +11 -1
- package/scripts/git/git-diff-reads.ts +66 -20
- package/scripts/git/git-error.ts +9 -2
- package/scripts/git/git-schemas.ts +13 -0
- package/scripts/git/git-worktree.ts +13 -0
- package/scripts/git/gone-branch.ts +4 -1
- package/scripts/git/main-merge-budget-conflict.ts +89 -0
- package/scripts/git/main-merge-guard.ts +55 -18
- package/scripts/git/main-merge-regenerate.ts +59 -25
- package/scripts/git/main-merge.ts +53 -5
- package/scripts/git/no-base-checkout.ts +127 -0
- package/scripts/git/stash/git-stash.ts +11 -2
- package/scripts/git/stash/stash-push-cli.ts +10 -0
- package/scripts/hooks/batch-guard.ts +40 -21
- package/scripts/hooks/edited-cspell.ts +37 -11
- package/scripts/hooks/format-edited-file.ts +22 -2
- package/scripts/hooks/lane-plan-guard.ts +50 -0
- package/scripts/hooks/pre-commit-lint-switch.ts +9 -0
- package/scripts/hooks/pre-commit-lint.ts +14 -1
- package/scripts/hooks/pre-push-related-switch.ts +10 -0
- package/scripts/hooks/pre-push-unit.ts +76 -10
- package/scripts/hooks/pretool-guard.ts +2 -0
- package/scripts/hooks/reserved-run.ts +14 -1
- package/scripts/hooks/run-hook.sh +23 -8
- package/scripts/hooks/step-zero-notice.ts +6 -1
- package/scripts/hooks/stop-actions.ts +3 -15
- package/scripts/hooks/stop-guard.ts +7 -6
- package/scripts/init/distributed-paths.ts +1 -0
- package/scripts/init/hook-command-rewrite.ts +5 -1
- package/scripts/init/hook-launch.ts +4 -2
- package/scripts/issue/aggregation-split.ts +52 -0
- package/scripts/issue/defect-origin-line.ts +19 -1
- package/scripts/issue/defect-rate-cli.ts +5 -102
- package/scripts/issue/defect-report.ts +4 -4
- package/scripts/issue/effect-audit-cli.ts +178 -0
- package/scripts/issue/effect-audit.ts +185 -0
- package/scripts/issue/expected-effect-lint.ts +64 -9
- package/scripts/issue/git-next-issues.ts +5 -0
- package/scripts/issue/issue-body-or-comment.ts +38 -4
- package/scripts/issue/issue-cite-cli.ts +1 -3
- package/scripts/issue/issue-classification.ts +1 -0
- package/scripts/issue/issue-closing-pr.ts +8 -1
- package/scripts/issue/issue-evidence-exit.ts +4 -2
- package/scripts/issue/issue-file-cap.ts +58 -0
- package/scripts/issue/issue-file-cli.ts +111 -38
- package/scripts/issue/issue-file-deferral.ts +52 -0
- package/scripts/issue/issue-file-fold.ts +29 -10
- package/scripts/issue/issue-file-interrupt.ts +85 -0
- package/scripts/issue/issue-file-lane-hint.ts +30 -0
- package/scripts/issue/issue-file.ts +43 -6
- package/scripts/issue/issue-labels.ts +19 -2
- package/scripts/issue/issue-lint-cli.ts +27 -2
- package/scripts/issue/issue-measured-cli.ts +13 -35
- package/scripts/issue/issue-measured-close.ts +58 -0
- package/scripts/issue/issue-not-before-label.ts +2 -2
- package/scripts/issue/issue-not-before.ts +4 -1
- package/scripts/issue/issue-priority-cli.ts +60 -0
- package/scripts/issue/issue-priority-raise.ts +55 -0
- package/scripts/issue/issue-priority.ts +174 -43
- package/scripts/issue/issue-rank.ts +21 -4
- package/scripts/issue/issue-read-cli.ts +32 -6
- package/scripts/issue/issue-read.ts +37 -12
- package/scripts/issue/issue-sample-gate.ts +95 -74
- package/scripts/issue/issue-sample-prime.ts +127 -0
- package/scripts/issue/issue-sample-required.ts +57 -0
- package/scripts/issue/issue-sample-source.ts +12 -11
- package/scripts/issue/issue-search.ts +124 -0
- package/scripts/issue/issue-state.ts +13 -1
- package/scripts/issue/issue-template-section.ts +43 -0
- package/scripts/issue/issue-watch-diff.ts +31 -1
- package/scripts/issue/issue-watch-hook.ts +11 -2
- package/scripts/issue/issue-watch.ts +55 -12
- package/scripts/issue/markdown-section.ts +1 -0
- package/scripts/issue/measure-declaration.ts +121 -0
- package/scripts/issue/measure-rerun-cli.ts +7 -70
- package/scripts/issue/plan-consumers-section.ts +22 -1
- package/scripts/issue/reproduction-measure.ts +3 -1
- package/scripts/issue/size-estimate.ts +90 -0
- package/scripts/issue/trusted-measure.ts +84 -0
- package/scripts/josh/command-usage.ts +3 -2
- package/scripts/josh/durable-state-migrate.ts +105 -0
- package/scripts/josh/durable-state.ts +112 -0
- package/scripts/josh/josh-aliases.ts +95 -0
- package/scripts/josh/josh-command-map.ts +1 -82
- package/scripts/josh/josh-commands-agent-hooks.ts +6 -6
- package/scripts/josh/josh-commands-ai.ts +5 -5
- package/scripts/josh/josh-commands-development.ts +59 -58
- package/scripts/josh/josh-commands-issue-write.ts +23 -4
- package/scripts/josh/josh-commands-issue.ts +32 -0
- package/scripts/josh/josh-commands-lane.ts +87 -4
- package/scripts/josh/josh-commands-lint.ts +16 -3
- package/scripts/josh/josh-commands-maintenance.ts +15 -0
- package/scripts/josh/josh-commands-review.ts +41 -6
- package/scripts/josh/josh-commands-ship.ts +56 -8
- package/scripts/josh/josh-commands-test-check.ts +80 -0
- package/scripts/josh/josh-commands-workflow.ts +1 -1
- package/scripts/josh/josh-environment-file.ts +43 -3
- package/scripts/josh/josh-in-process.ts +2 -2
- package/scripts/josh/josh-logic.ts +5 -3
- package/scripts/josh/josh-run.ts +19 -1
- package/scripts/josh/run-record-path.ts +15 -0
- package/scripts/josh/stamp-file.ts +11 -2
- package/scripts/lane/lane-adopt-cli.ts +98 -0
- package/scripts/lane/lane-adopt.ts +208 -0
- package/scripts/lane/lane-await.ts +5 -8
- package/scripts/lane/lane-baseline.ts +123 -0
- package/scripts/lane/lane-breakdown-cli.ts +64 -0
- package/scripts/lane/lane-breakdown-implement.ts +170 -0
- package/scripts/lane/lane-breakdown-laps.ts +107 -0
- package/scripts/lane/lane-breakdown-pinned.ts +76 -0
- package/scripts/lane/lane-breakdown-text.ts +145 -0
- package/scripts/lane/lane-breakdown.ts +239 -0
- package/scripts/lane/lane-check-stats.ts +29 -8
- package/scripts/lane/lane-child-invocation.ts +9 -2
- package/scripts/lane/lane-child-mark-keys.ts +12 -0
- package/scripts/lane/lane-child-marker.ts +20 -1
- package/scripts/lane/lane-ci-path-stats.ts +73 -0
- package/scripts/lane/lane-claim-stats.ts +84 -0
- package/scripts/lane/lane-close.ts +8 -3
- package/scripts/lane/lane-dispatch-log.ts +3 -3
- package/scripts/lane/lane-dispatch.ts +2 -2
- package/scripts/lane/lane-gap-stats.ts +7 -6
- package/scripts/lane/lane-guard-policy.ts +7 -1
- package/scripts/lane/lane-implement-stats.ts +207 -0
- package/scripts/lane/lane-implement-subagents.ts +90 -0
- package/scripts/lane/lane-investigation-switch.ts +16 -0
- package/scripts/lane/lane-ledger-kind.ts +31 -0
- package/scripts/lane/lane-ledger-schema-ship.ts +49 -0
- package/scripts/lane/lane-ledger-schema.ts +119 -36
- package/scripts/lane/lane-ledger-write.ts +257 -0
- package/scripts/lane/lane-ledger.ts +31 -177
- package/scripts/lane/lane-load.ts +1 -1
- package/scripts/lane/lane-measure-cli.ts +176 -59
- package/scripts/lane/lane-net-read.ts +44 -0
- package/scripts/lane/lane-net-time.ts +212 -0
- package/scripts/lane/lane-open.ts +16 -7
- package/scripts/lane/lane-output.ts +2 -4
- package/scripts/lane/lane-overrun-prompt.ts +147 -0
- package/scripts/lane/lane-overrun.ts +274 -0
- package/scripts/lane/lane-paths.ts +24 -1
- package/scripts/lane/lane-pause-cli.ts +4 -1
- package/scripts/lane/lane-paused-spans.ts +97 -0
- package/scripts/lane/lane-plan-check.ts +207 -0
- package/scripts/lane/lane-plan-cli.ts +41 -0
- package/scripts/lane/lane-plan.ts +144 -0
- package/scripts/lane/lane-registry.ts +14 -1
- package/scripts/lane/lane-relaunch.ts +1 -1
- package/scripts/lane/lane-round-one-stats.ts +51 -0
- package/scripts/lane/lane-sampler.ts +23 -4
- package/scripts/lane/lane-session-action.ts +93 -18
- package/scripts/lane/lane-session-broker.ts +49 -21
- package/scripts/lane/lane-session-reread-main.ts +14 -0
- package/scripts/lane/lane-session-reread.ts +51 -0
- package/scripts/lane/lane-ship-lap-stats.ts +74 -0
- package/scripts/lane/lane-sleep.ts +58 -0
- package/scripts/lane/lane-spent.ts +1 -1
- package/scripts/lane/lane-split-stats.ts +28 -0
- package/scripts/lane/lane-stage-stats.ts +13 -1
- package/scripts/lane/lane-state.ts +41 -9
- package/scripts/lane/lane-stats-tables.ts +58 -0
- package/scripts/lane/lane-stats.ts +21 -5
- package/scripts/lane/lane-stop-stats.ts +51 -19
- package/scripts/lane/lane-tail-stats.ts +32 -0
- package/scripts/lane/lane-transcript-calls.ts +67 -10
- package/scripts/lane/lane-transcript-stats.ts +44 -12
- package/scripts/lane/lane-value-cell.ts +72 -0
- package/scripts/lane/lane-value.ts +118 -0
- package/scripts/lane/lane-withdraw-closed.ts +124 -0
- package/scripts/lane/lane-withdraw.ts +108 -31
- package/scripts/lane/lane-yield.ts +85 -0
- package/scripts/lane/openai-lane-ship.ts +12 -11
- package/scripts/lane/openai-lane-supervisor-decision.ts +6 -8
- package/scripts/lane/openai-lane-supervisor-owner.ts +5 -8
- package/scripts/lane/openai-lane-supervisor.ts +5 -4
- package/scripts/lib/poll.ts +38 -9
- package/scripts/lib/settled-together.ts +10 -0
- package/scripts/lib/shell-quote.ts +21 -0
- package/scripts/lib/token-shape.ts +111 -0
- package/scripts/lint/eslint-daemon-sources.ts +79 -0
- package/scripts/lint/eslint-daemon.ts +45 -6
- package/scripts/lint/eslint-warm-cli.ts +7 -0
- package/scripts/lint/eslint-warm.ts +66 -0
- package/scripts/lint/import-graph.ts +141 -11
- package/scripts/lint/lint-gate-scope.ts +2 -2
- package/scripts/lint/lint-parallel.ts +54 -6
- package/scripts/lint/lint-related.ts +154 -33
- package/scripts/lint/rename-cli.ts +179 -0
- package/scripts/lint/rename-identifier.ts +192 -0
- package/scripts/lint/type-only-imports.ts +58 -0
- package/scripts/notify/telegram-notify.ts +12 -3
- package/scripts/observations/observation-record.ts +42 -16
- package/scripts/observations/observations-flush.ts +32 -9
- package/scripts/overrides/overrides-check.ts +9 -1
- package/scripts/release/release-approval-prune.ts +7 -1
- package/scripts/repo/required-checks-logic.ts +4 -1
- package/scripts/repo/ruleset-check.ts +4 -3
- package/scripts/report/markdown-shape.ts +28 -0
- package/scripts/report/report-format-reference.ts +15 -1
- package/scripts/report/report-labels.ts +12 -1
- package/scripts/report/report-lint-child-summary.ts +94 -0
- package/scripts/report/report-lint-cli.ts +26 -7
- package/scripts/report/report-lint.ts +2 -20
- package/scripts/review/prose-stale-cli.ts +88 -0
- package/scripts/review/prose-stale-scan.ts +152 -0
- package/scripts/review/review-attest.ts +73 -23
- package/scripts/review/review-brief-cli.ts +68 -19
- package/scripts/review/review-brief-paths.ts +22 -0
- package/scripts/review/review-brief.ts +14 -7
- package/scripts/review/review-comment-carry.ts +47 -0
- package/scripts/review/review-comment-round.ts +85 -22
- package/scripts/review/review-finding-ledger.ts +109 -7
- package/scripts/review/review-finding-text.ts +43 -0
- package/scripts/review/review-interaction.ts +11 -0
- package/scripts/review/review-lane-handover.ts +68 -0
- package/scripts/review/review-lint-cli.ts +57 -0
- package/scripts/review/review-lint-findings.ts +92 -0
- package/scripts/review/review-lint-sections.ts +80 -0
- package/scripts/review/review-lint.ts +221 -0
- package/scripts/review/review-prior-findings.ts +102 -0
- package/scripts/review/review-readers-scan.ts +4 -2
- package/scripts/review/review-record-cli.ts +177 -54
- package/scripts/review/review-record-code-round.ts +55 -0
- package/scripts/review/review-record-comment.ts +60 -0
- package/scripts/review/review-record-interaction.ts +35 -0
- package/scripts/review/review-record-pinned.ts +108 -0
- package/scripts/review/review-record.ts +25 -1
- package/scripts/review/review-reviewed-tree.ts +307 -0
- package/scripts/review/review-round-one-closure.ts +62 -0
- package/scripts/review/review-round-reuse.ts +106 -0
- package/scripts/review/review-round2-cli.ts +91 -29
- package/scripts/review/review-round2-skip-comment.ts +55 -0
- package/scripts/review/review-round2.ts +24 -8
- package/scripts/review/review-stamps.ts +7 -3
- package/scripts/review/review-stop-log.ts +20 -6
- package/scripts/review/review-stops-cli.ts +2 -1
- package/scripts/review/review-submit-cli.ts +101 -0
- package/scripts/rules/agent-model.ts +1 -1
- package/scripts/rules/bash-triggers.ts +3 -3
- package/scripts/rules/concise-reply.ts +88 -0
- package/scripts/rules/credential-body.ts +77 -0
- package/scripts/rules/decision-oracle.ts +1 -1
- package/scripts/rules/delivered-rules.ts +36 -17
- package/scripts/rules/destructive-command.ts +3 -11
- package/scripts/rules/direct-issue-comment.ts +34 -12
- package/scripts/rules/direct-issue-read.ts +191 -0
- package/scripts/rules/direct-pr-create.ts +4 -24
- package/scripts/rules/early-heartbeat.ts +2 -0
- package/scripts/rules/edit-tool.ts +27 -0
- package/scripts/rules/file-edit-rows.ts +17 -0
- package/scripts/rules/file-rewrite.ts +146 -0
- package/scripts/rules/firing-point.ts +3 -2
- package/scripts/rules/gh-api.ts +128 -7
- package/scripts/rules/gh-repo-selector.ts +84 -0
- package/scripts/rules/git-force.ts +2 -2
- package/scripts/rules/guard-remedy.ts +14 -5
- package/scripts/rules/implementation-cut.ts +61 -28
- package/scripts/rules/investigator-foreground.ts +231 -0
- package/scripts/rules/issue-citation.ts +15 -60
- package/scripts/rules/issue-comments.ts +9 -4
- package/scripts/rules/josh-git-bare.ts +1 -1
- package/scripts/rules/lane-background.ts +41 -13
- package/scripts/rules/lane-direct-tool.ts +107 -9
- package/scripts/rules/lane-interactive-ask.ts +4 -1
- package/scripts/rules/lane-park.ts +4 -2
- package/scripts/rules/lane-spilled-read.ts +90 -0
- package/scripts/rules/lane-split-park.ts +1 -1
- package/scripts/rules/lane-switch-main.ts +4 -4
- package/scripts/rules/last-prompt.ts +20 -47
- package/scripts/rules/oracle-consulted.ts +1 -1
- package/scripts/rules/permission-guards.ts +5 -1
- package/scripts/rules/piped-verification-shapes.ts +110 -0
- package/scripts/rules/piped-verification.ts +67 -17
- package/scripts/rules/poll-loop.ts +72 -9
- package/scripts/rules/pr-merge.ts +5 -5
- package/scripts/rules/pre-gate-cut.ts +3 -3
- package/scripts/rules/protected-files.ts +88 -24
- package/scripts/rules/reply-language.ts +3 -17
- package/scripts/rules/reply-prose.ts +34 -0
- package/scripts/rules/repo-admin.ts +132 -0
- package/scripts/rules/reviewer-foreground.ts +88 -0
- package/scripts/rules/round-one-check-related.ts +120 -0
- package/scripts/rules/rule-body-guard.ts +5 -15
- package/scripts/rules/rule-value-cache.ts +53 -5
- package/scripts/rules/rule-value-candidates.ts +84 -0
- package/scripts/rules/rule-value-cli.ts +59 -11
- package/scripts/rules/rule-value.ts +17 -4
- package/scripts/rules/run-tail-rule.ts +1 -1
- package/scripts/rules/sed-in-place.ts +196 -0
- package/scripts/rules/shell-segments.ts +10 -2
- package/scripts/rules/stop-rule-rows.ts +1 -13
- package/scripts/rules/stop-rules.ts +33 -58
- package/scripts/rules/test-declared-commit.ts +1 -1
- package/scripts/rules/third-party-write.ts +110 -23
- package/scripts/rules/timed-rerun.ts +1 -1
- package/scripts/rules/turn-filings.ts +90 -0
- package/scripts/rules/worktree-guard.ts +22 -7
- package/scripts/run/board/run-board-ending-reason.ts +6 -2
- package/scripts/run/board/run-board-fit.ts +24 -1
- package/scripts/run/board/run-board-found.ts +134 -0
- package/scripts/run/board/run-board-header.ts +8 -0
- package/scripts/run/board/run-board-labels.ts +11 -1
- package/scripts/run/board/run-board-lane-time.ts +1 -0
- package/scripts/run/board/run-board-link.ts +15 -2
- package/scripts/run/board/run-board-notes.ts +7 -1
- package/scripts/run/board/run-board-operation.ts +4 -2
- package/scripts/run/board/run-board-phase.ts +2 -5
- package/scripts/run/board/run-board-plan.ts +1 -1
- package/scripts/run/board/run-board-read.ts +46 -1
- package/scripts/run/board/run-board-reason.ts +20 -8
- package/scripts/run/board/run-board-render-checks.ts +3 -2
- package/scripts/run/board/run-board-render-drawn.ts +11 -0
- package/scripts/run/board/run-board-render-found.ts +157 -0
- package/scripts/run/board/run-board-render-lead.ts +9 -0
- package/scripts/run/board/run-board-render-legend.ts +19 -0
- package/scripts/run/board/run-board-render-tail.ts +1 -1
- package/scripts/run/board/run-board-render.ts +82 -18
- package/scripts/run/board/run-board-spin.ts +1 -3
- package/scripts/run/board/run-board-status.ts +2 -0
- package/scripts/run/board/run-board-tick.ts +39 -10
- package/scripts/run/carry/run-carry-ended.ts +3 -2
- package/scripts/run/carry/run-carry.ts +3 -2
- package/scripts/run/cut/run-cut-cli.ts +33 -133
- package/scripts/run/cut/run-cut-digest.ts +152 -0
- package/scripts/run/cut/run-cut-handoff.ts +52 -8
- package/scripts/run/cut/run-cut-report.ts +24 -3
- package/scripts/run/cut/run-cut-resume.ts +129 -0
- package/scripts/run/cut/run-cut-review.ts +105 -0
- package/scripts/run/cut/run-cut.ts +18 -7
- package/scripts/run/detached-launch.ts +1 -1
- package/scripts/run/entry/run-entry-cli.ts +88 -19
- package/scripts/run/entry/run-entry-spill.ts +83 -0
- package/scripts/run/entry/run-entry-stop.ts +5 -1
- package/scripts/run/entry/run-entry-trust.ts +91 -0
- package/scripts/run/event/run-event-baseline.ts +56 -0
- package/scripts/run/event/run-event-filed.ts +9 -3
- package/scripts/run/event/run-event-overrun.ts +197 -0
- package/scripts/run/event/run-event-render.ts +4 -0
- package/scripts/run/event/run-event-stream.ts +25 -3
- package/scripts/run/hold/run-stop-notify.ts +3 -8
- package/scripts/run/merge/run-merge-cap.ts +57 -0
- package/scripts/run/merge/run-merge-cli.ts +66 -23
- package/scripts/run/merge/run-merge-outcome.ts +2 -0
- package/scripts/run/merge/run-merge-ship.ts +13 -3
- package/scripts/run/merge/run-merge-signals.ts +40 -23
- package/scripts/run/merge/run-merge-steps.ts +56 -36
- package/scripts/run/merge/run-merge-usage-limit.ts +53 -0
- package/scripts/run/merge/run-merge-withdrawn.ts +53 -0
- package/scripts/run/merge/run-merge.ts +41 -14
- package/scripts/run/review/run-review-cli.ts +306 -20
- package/scripts/run/review/run-review-early-push.ts +121 -0
- package/scripts/run/review/run-review-join-read.ts +94 -0
- package/scripts/run/review/run-review-pinned-accepted.ts +126 -0
- package/scripts/run/review/run-review-pinned-join.ts +216 -0
- package/scripts/run/review/run-review-pinned-steps.ts +383 -0
- package/scripts/run/review/run-review-pinned-tree.ts +37 -0
- package/scripts/run/review/run-review-pinned.ts +53 -0
- package/scripts/run/review/run-review-steps.ts +70 -34
- package/scripts/run/review/run-review.ts +26 -6
- package/scripts/run/run-parent-cut-hook.ts +3 -4
- package/scripts/run/run-prep.ts +1 -0
- package/scripts/run/run-stage-read.ts +2 -6
- package/scripts/run/run-step-cli.ts +17 -5
- package/scripts/run/run-step-lane-plan.ts +27 -0
- package/scripts/run/run-step-section.ts +26 -7
- package/scripts/run/run-step.ts +5 -2
- package/scripts/run/ship/run-ship-args.ts +19 -0
- package/scripts/run/ship/run-ship-brokered-review.ts +106 -0
- package/scripts/run/ship/run-ship-cli.ts +131 -39
- package/scripts/run/ship/run-ship-conflict-probe.ts +59 -0
- package/scripts/run/ship/run-ship-detach.ts +10 -4
- package/scripts/run/ship/run-ship-handover.ts +106 -0
- package/scripts/run/ship/run-ship-infra-retry.ts +16 -1
- package/scripts/run/ship/run-ship-lap.ts +63 -0
- package/scripts/run/ship/run-ship-next.ts +27 -26
- package/scripts/run/ship/run-ship-overlap.ts +8 -3
- package/scripts/run/ship/run-ship-place.ts +9 -2
- package/scripts/run/ship/run-ship-planned.ts +92 -0
- package/scripts/run/ship/run-ship-pre-detach-pinned.ts +197 -0
- package/scripts/run/ship/run-ship-pre-detach-timed.ts +7 -8
- package/scripts/run/ship/run-ship-pre-detach.ts +77 -23
- package/scripts/run/ship/run-ship-preflight.ts +16 -4
- package/scripts/run/ship/run-ship-probe.ts +23 -9
- package/scripts/run/ship/run-ship-relaunch-cap.ts +43 -7
- package/scripts/run/ship/run-ship-relaunch.ts +51 -0
- package/scripts/run/ship/run-ship-return.ts +103 -29
- package/scripts/run/ship/run-ship-review-reason.ts +24 -0
- package/scripts/run/ship/run-ship-review-steps.ts +230 -59
- package/scripts/run/ship/run-ship-review.ts +171 -51
- package/scripts/run/ship/run-ship-reviewer.ts +138 -42
- package/scripts/run/ship/run-ship-scoped.ts +39 -10
- package/scripts/run/ship/run-ship-stage.ts +49 -13
- package/scripts/run/ship/run-ship-steps.ts +24 -4
- package/scripts/run/ship/run-ship-stop-cause.ts +43 -2
- package/scripts/run/ship/run-ship-stop-context.ts +50 -13
- package/scripts/run/ship/run-ship-sync.ts +171 -30
- package/scripts/run/ship/run-ship-synced-scope.ts +178 -0
- package/scripts/run/ship/run-ship-wait-cli.ts +145 -0
- package/scripts/run/ship/run-ship-waiter.ts +114 -0
- package/scripts/run/ship/run-ship.ts +16 -2
- package/scripts/run/wake/run-wake-caffeinate.ts +48 -0
- package/scripts/run/wake/run-wake-cli.ts +6 -113
- package/scripts/run/wake/run-wake-code.ts +53 -11
- package/scripts/run/wake/run-wake-driver.ts +5 -3
- package/scripts/run/wake/run-wake-finish.ts +137 -0
- package/scripts/run/wake/run-wake-loop.ts +5 -3
- package/scripts/run/wake/run-wake-ports.ts +11 -3
- package/scripts/run/wake/run-wake-session.ts +2 -0
- package/scripts/run/wake/run-wake.ts +3 -2
- package/scripts/safe-chain/ci-installer-pin.ts +49 -8
- package/scripts/safe-chain/project-config.ts +3 -2
- package/scripts/security/secretlint-scan.ts +42 -9
- package/scripts/security/security-audit-provision-main.ts +9 -1
- package/scripts/security/security-audit-provision.ts +1 -1
- package/scripts/security/security-audit.ts +21 -1
- package/scripts/security/sonar-file.ts +10 -5
- package/scripts/split/split-assess.ts +12 -3
- package/scripts/split/split-early-notice.ts +116 -0
- package/scripts/split/split-measure.ts +20 -2
- package/scripts/split/split-resolution.ts +107 -0
- package/scripts/test/direct-importers.ts +185 -0
- package/scripts/test/pure-files-entries.ts +83 -0
- package/scripts/test/related-heavy-tests.ts +81 -0
- package/scripts/test/test-no-base.ts +88 -0
- package/scripts/test/test-pinned-cache-logic.ts +363 -0
- package/scripts/test/test-pinned-cache.ts +337 -0
- package/scripts/test/test-pinned-emit.ts +49 -0
- package/scripts/test/test-pinned-functions.ts +116 -0
- package/scripts/test/test-pinned-ledger.ts +80 -0
- package/scripts/test/test-pinned-logic.ts +208 -20
- package/scripts/test/test-pinned-main.ts +84 -18
- package/scripts/test/test-pinned-pool.ts +73 -0
- package/scripts/test/test-pinned-probe.ts +254 -0
- package/scripts/test/test-pinned-prune-logic.ts +118 -0
- package/scripts/test/test-pinned-reach.ts +194 -0
- package/scripts/test/test-pinned-record.ts +196 -0
- package/scripts/test/test-pinned-rewire.ts +148 -0
- package/scripts/test/test-pinned-spawn.ts +81 -0
- package/scripts/test/test-pinned-stage.ts +70 -0
- package/scripts/test/test-pinned.ts +258 -47
- package/scripts/test/test-reach-cli.ts +61 -0
- package/scripts/test/test-read-map.ts +35 -11
- package/scripts/test/test-read-recording.ts +17 -5
- package/scripts/test/test-read-trace.ts +26 -8
- package/scripts/test/test-red-logic.ts +46 -7
- package/scripts/test/test-red.ts +34 -14
- package/scripts/test/test-related-claim.ts +37 -0
- package/scripts/test/test-related-main.ts +4 -1
- package/scripts/test/test-related-moved.ts +91 -0
- package/scripts/test/test-related-record.ts +43 -23
- package/scripts/test/test-related-scope.ts +99 -34
- package/scripts/test/test-related-synced.ts +135 -0
- package/scripts/test/test-related-touched.ts +84 -0
- package/scripts/test/test-related.ts +167 -83
- package/scripts/test/test-unit-guard.ts +28 -5
- package/scripts/test/unit-durations.ts +41 -0
- package/scripts/test/unit-worker-share.ts +5 -4
- package/scripts/test/vitest-cache-directory.ts +19 -0
- package/scripts/test/vitest-cli-excludes.ts +14 -0
- package/scripts/test/vitest-failure-summary.ts +111 -0
- package/scripts/test/whole-suite-files.ts +12 -1
- package/scripts/time-runtime/time-batch-guard.ts +42 -4
- package/scripts/time-runtime/time-bundle-call.ts +1 -1
- package/scripts/time-runtime/time-bundles.ts +36 -5
- package/scripts/time-runtime/time-markers.ts +2 -2
- package/scripts/time-runtime/time-search-call.ts +38 -0
- package/scripts/time-runtime/time-shell.ts +1 -1
- package/scripts/time-runtime/time-transcript-line.ts +17 -5
- package/scripts/version/development-engines-verdict.ts +124 -0
- package/scripts/version/latest-corepack.ts +2 -0
- package/templates/gitignore +2 -0
- package/templates/workflows/ci.yml +41 -0
- package/dist/hooks/chunk-AZX4FGQ3.js +0 -207
- package/dist/hooks/chunk-PT5R6UJD.js +0 -7131
- package/dist/hooks/chunk-TUIFS5E7.js +0 -72
- package/dist/hooks/chunk-YCDOIR3I.js +0 -3456
- package/scripts/cost-runtime/context-cut-threshold.ts +0 -11
- package/scripts/rules/filing-cap.ts +0 -139
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import type { Hunk } from './test-pinned-logic'
|
|
5
|
+
import type { RedProbe, RedVerdict } from './test-red-logic'
|
|
6
|
+
|
|
7
|
+
// The pure half of `josh test:pinned`'s verdict cache (joshuafolkken/kit#4706): the key a hunk's verdict
|
|
8
|
+
// is stored under, and whether a stored one still holds. The reads and writes are `test-pinned-cache.ts`.
|
|
9
|
+
//
|
|
10
|
+
// **A verdict is keyed by the hunk's own lines, never its position**, so a hunk that only moved — an
|
|
11
|
+
// edit above it in the same file — keeps its verdict.
|
|
12
|
+
// **A `red` verdict holds while the code change and the test files it failed in are the same**
|
|
13
|
+
// (`change`: the base commit and the content of every non-test path changed since it, a run's own
|
|
14
|
+
// record under `.josh/` left out since no test reads one, joshuafolkken/kit#4856; `tests`: the
|
|
15
|
+
// content digest of each failed test file, joshuafolkken/kit#4790). A hunk goes red on the tree with
|
|
16
|
+
// only it reverted, so a fixture, a config or another source file can decide it — a test that already
|
|
17
|
+
// failed before the revert reads red too — and a red kept past any of those, or past an edit to the test
|
|
18
|
+
// that failed, would pass an unpinned hunk. A test added or edited elsewhere cannot make that test
|
|
19
|
+
// pass, so it keeps the red. A red with no failed file recorded holds only while no test file changed either.
|
|
20
|
+
// **A `green` or `no-test` one holds while no test file's path or content changed** (`test_contents`):
|
|
21
|
+
// it is only rechecked when a test could have come to reach it, and a stale one stops a lane rather
|
|
22
|
+
// than passing it.
|
|
23
|
+
// **Past those tree-wide digests, a verdict also holds while its own `basis` is the same**
|
|
24
|
+
// (joshuafolkken/kit#4873): for a `red`, the base commit, the changed non-code and whole-suite files,
|
|
25
|
+
// and each failed test's import closure; for any other, the tests whose imports reach the hunk's file,
|
|
26
|
+
// with their content. An edit outside them keeps the verdict; with no basis — no import graph, no
|
|
27
|
+
// failed file recorded — only the tree-wide digests hold it, as before.
|
|
28
|
+
// **A `red` verdict also holds while `pruned` reads the same** (joshuafolkken/kit#5004): the code
|
|
29
|
+
// change since differs only by lines removed from `green` hunks (`test-pinned-prune-logic.ts`).
|
|
30
|
+
// **Past all of those, a verdict is rechecked rather than probed whole** (`recheck_of`).
|
|
31
|
+
|
|
32
|
+
const DIGEST_ALGORITHM = 'sha256'
|
|
33
|
+
const LINE_BREAK = '\n'
|
|
34
|
+
const VERDICTS = ['red', 'green', 'no-test', 'test-only'] as const
|
|
35
|
+
|
|
36
|
+
// A hunk's verdict, with the content digest of each test file that failed on it and what it rests on.
|
|
37
|
+
// `related` is a `green` or `no-test` basis's own tests with their digests, so a later run can tell
|
|
38
|
+
// which of them were added or edited (joshuafolkken/kit#4936).
|
|
39
|
+
interface StoredVerdict {
|
|
40
|
+
verdict: RedVerdict
|
|
41
|
+
tests: Record<string, string>
|
|
42
|
+
basis?: string | undefined
|
|
43
|
+
related?: Record<string, string> | undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// What a verdict rests on now: its `basis` digest, and for a `green` or `no-test` one its tests.
|
|
47
|
+
interface Basis {
|
|
48
|
+
digest: string
|
|
49
|
+
related?: Record<string, string> | undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A `green` or `no-test` verdict that holds once `recheck` — the tests added or edited since, among
|
|
53
|
+
// those reaching its hunk — stays green with the hunk reverted (joshuafolkken/kit#4936), or a `red`
|
|
54
|
+
// one once the tests it failed in still fail (joshuafolkken/kit#5129).
|
|
55
|
+
interface Recheck {
|
|
56
|
+
verdict: RedVerdict
|
|
57
|
+
recheck: ReadonlyArray<string>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// What the cache knows of a hunk: a verdict, one to recheck against a few tests, or nothing.
|
|
61
|
+
type Known = RedProbe | Recheck | undefined
|
|
62
|
+
|
|
63
|
+
// A `green` hunk as the cache records it: its file, its base lines and the lines it held.
|
|
64
|
+
interface GreenHunk {
|
|
65
|
+
file: string
|
|
66
|
+
old_lines: Array<string>
|
|
67
|
+
new_lines: Array<string>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// `pruned` is the code change with every `green` hunk in `green` reverted.
|
|
71
|
+
interface VerdictCache {
|
|
72
|
+
test_contents: string
|
|
73
|
+
change: string
|
|
74
|
+
entries: Record<string, StoredVerdict>
|
|
75
|
+
pruned?: string | undefined
|
|
76
|
+
green?: Array<GreenHunk> | undefined
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// What the work tree holds now, set against what the cache was written under.
|
|
80
|
+
interface Freshness {
|
|
81
|
+
is_same_change: boolean
|
|
82
|
+
is_unchanged: boolean
|
|
83
|
+
is_pruned: boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const STORED_SCHEMA = z.object({
|
|
87
|
+
verdict: z.enum(VERDICTS),
|
|
88
|
+
tests: z.record(z.string(), z.string()),
|
|
89
|
+
basis: z.string().optional(),
|
|
90
|
+
related: z.record(z.string(), z.string()).optional(),
|
|
91
|
+
})
|
|
92
|
+
const GREEN_SCHEMA = z.object({
|
|
93
|
+
file: z.string(),
|
|
94
|
+
old_lines: z.array(z.string()),
|
|
95
|
+
new_lines: z.array(z.string()),
|
|
96
|
+
})
|
|
97
|
+
const CACHE_SCHEMA = z.object({
|
|
98
|
+
test_contents: z.string(),
|
|
99
|
+
change: z.string(),
|
|
100
|
+
entries: z.record(z.string(), STORED_SCHEMA),
|
|
101
|
+
pruned: z.string().optional(),
|
|
102
|
+
green: z.array(GREEN_SCHEMA).optional(),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
function digest_of(text: string): string {
|
|
106
|
+
return createHash(DIGEST_ALGORITHM).update(text).digest('hex')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The hunk's file, its old lines, the lines it holds now in `content` and the probed function's name.
|
|
110
|
+
function hunk_key(hunk: Hunk, content: string): string {
|
|
111
|
+
const start = hunk.new_start - 1
|
|
112
|
+
const lines = content.split(LINE_BREAK).slice(start, start + hunk.new_count)
|
|
113
|
+
|
|
114
|
+
return digest_of(JSON.stringify([hunk.file, hunk.function_name ?? '', hunk.old_lines, lines]))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A key an identical hunk already took gets its occurrence number, so each keeps a verdict of its own.
|
|
118
|
+
function distinct(keys: ReadonlyArray<string>): Array<string> {
|
|
119
|
+
return keys.map((key, index) => {
|
|
120
|
+
const seen = keys.slice(0, index).filter((earlier) => earlier === key).length
|
|
121
|
+
|
|
122
|
+
return seen === 0 ? key : `${key}:${String(seen)}`
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sorted(names: ReadonlyArray<string>): Array<string> {
|
|
127
|
+
return names.toSorted((left, right) => left.localeCompare(right))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// The sorted test file paths, so the set reads the same whatever order git listed them in.
|
|
131
|
+
function test_set_of(test_files: ReadonlyArray<string>): string {
|
|
132
|
+
return digest_of(sorted(test_files).join(LINE_BREAK))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The base commit and each changed path with its content's digest — a deleted path has none.
|
|
136
|
+
function change_of(
|
|
137
|
+
commit: string,
|
|
138
|
+
paths: ReadonlyArray<string>,
|
|
139
|
+
digests: ReadonlyMap<string, string>,
|
|
140
|
+
): string {
|
|
141
|
+
return digest_of(JSON.stringify([commit, sorted(paths).map((file) => [file, digests.get(file)])]))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// An unreadable cache holds nothing.
|
|
145
|
+
function entries_of(text: string): Record<string, StoredVerdict> {
|
|
146
|
+
return json_value.parse_with(text, CACHE_SCHEMA)?.entries ?? {}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// The `green` hunks the cache recorded; an unreadable cache recorded none.
|
|
150
|
+
function greens_of(text: string): Array<GreenHunk> {
|
|
151
|
+
return json_value.parse_with(text, CACHE_SCHEMA)?.green ?? []
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// How `text` was written against the work tree now; an unreadable cache was written under nothing, and
|
|
155
|
+
// one with no `pruned` recorded never reads the same as one pruned now.
|
|
156
|
+
function freshness_of(
|
|
157
|
+
text: string,
|
|
158
|
+
now: Pick<VerdictCache, 'change' | 'pruned' | 'test_contents'>,
|
|
159
|
+
): Freshness {
|
|
160
|
+
const cache = json_value.parse_with(text, CACHE_SCHEMA)
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
is_same_change: cache?.change === now.change,
|
|
164
|
+
is_unchanged: cache?.test_contents === now.test_contents,
|
|
165
|
+
is_pruned: cache?.pruned !== undefined && cache.pruned === now.pruned,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Each failed test file still holds the content it failed with; with none recorded, no test file changed.
|
|
170
|
+
function holds_tests(
|
|
171
|
+
tests: Record<string, string>,
|
|
172
|
+
freshness: Freshness,
|
|
173
|
+
digests: ReadonlyMap<string, string>,
|
|
174
|
+
): boolean {
|
|
175
|
+
const files = Object.entries(tests)
|
|
176
|
+
if (files.length === 0) return freshness.is_unchanged
|
|
177
|
+
|
|
178
|
+
return files.every(([file, digest]) => digests.get(file) === digest)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// The entry was stored with a basis, and what it rests on reads the same now.
|
|
182
|
+
function is_based(entry: StoredVerdict, basis: string | undefined): boolean {
|
|
183
|
+
return entry.basis !== undefined && entry.basis === basis
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// The code a `red` verdict was reached on reads the same, or differs only by lines removed from `green`
|
|
187
|
+
// hunks.
|
|
188
|
+
function is_same_code(freshness: Freshness): boolean {
|
|
189
|
+
return freshness.is_same_change || freshness.is_pruned
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function holds(
|
|
193
|
+
entry: StoredVerdict,
|
|
194
|
+
freshness: Freshness,
|
|
195
|
+
digests: ReadonlyMap<string, string>,
|
|
196
|
+
basis: string | undefined,
|
|
197
|
+
): boolean {
|
|
198
|
+
if (entry.verdict !== 'red') return freshness.is_unchanged || is_based(entry, basis)
|
|
199
|
+
const is_same = is_same_code(freshness) || is_based(entry, basis)
|
|
200
|
+
|
|
201
|
+
return is_same && holds_tests(entry.tests, freshness, digests)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// The stored verdict while what it rests on is unchanged: the code change and the failed test files for
|
|
205
|
+
// `red`, every test file for any other — or, past either, its own `basis` read now. `digests` holds each
|
|
206
|
+
// test file's content digest now.
|
|
207
|
+
function fresh_verdict(
|
|
208
|
+
entry: StoredVerdict | undefined,
|
|
209
|
+
freshness: Freshness,
|
|
210
|
+
digests: ReadonlyMap<string, string>,
|
|
211
|
+
basis?: string,
|
|
212
|
+
): RedProbe | undefined {
|
|
213
|
+
if (entry === undefined || !holds(entry, freshness, digests, basis)) return undefined
|
|
214
|
+
|
|
215
|
+
return { verdict: entry.verdict, failed: Object.keys(entry.tests) }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// A `green` or `no-test` verdict's basis: each test whose imports reach the hunk's file, with its content.
|
|
219
|
+
function related_basis(tests: ReadonlyArray<string>, digests: ReadonlyMap<string, string>): Basis {
|
|
220
|
+
const pairs = sorted(tests).map((file) => [file, digests.get(file) ?? ''] as const)
|
|
221
|
+
|
|
222
|
+
return { digest: digest_of(JSON.stringify(pairs)), related: Object.fromEntries(pairs) }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// The tests added or edited since `before` was read; `undefined` once one of its tests is gone or no
|
|
226
|
+
// longer reaches the hunk, which only a run over every reaching test can answer.
|
|
227
|
+
function changed_tests(
|
|
228
|
+
before: Record<string, string>,
|
|
229
|
+
now: Record<string, string>,
|
|
230
|
+
): Array<string> | undefined {
|
|
231
|
+
if (Object.keys(before).some((file) => now[file] === undefined)) return undefined
|
|
232
|
+
|
|
233
|
+
return Object.keys(now).filter((file) => before[file] !== now[file])
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function recheck_tests(entry: StoredVerdict, basis: Basis | undefined): Array<string> | undefined {
|
|
237
|
+
if (entry.related === undefined || basis?.related === undefined) return undefined
|
|
238
|
+
|
|
239
|
+
return changed_tests(entry.related, basis.related)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// A `red` verdict rechecked against the test files it failed in, while each is still there
|
|
243
|
+
// (joshuafolkken/kit#5129); none where no failed file was recorded.
|
|
244
|
+
function failed_recheck(
|
|
245
|
+
entry: StoredVerdict,
|
|
246
|
+
digests: ReadonlyMap<string, string>,
|
|
247
|
+
): Recheck | undefined {
|
|
248
|
+
const failed = Object.keys(entry.tests)
|
|
249
|
+
if (failed.length === 0 || failed.some((file) => !digests.has(file))) return undefined
|
|
250
|
+
|
|
251
|
+
return { verdict: entry.verdict, recheck: failed }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// A `red` verdict past its basis is rechecked against its failed tests: one still failing pins the hunk
|
|
255
|
+
// on the tree now, whatever moved (`rechecked`), so an edit to a doc no import names re-runs a few tests,
|
|
256
|
+
// not every test reaching a hub file. A `green` or `no-test` one whose code change is the same is
|
|
257
|
+
// rechecked against only the tests added or edited since; a source change, a removed test or no recorded
|
|
258
|
+
// tests reruns every reaching test. `digests` holds each test file's content digest now.
|
|
259
|
+
function recheck_of(
|
|
260
|
+
entry: StoredVerdict,
|
|
261
|
+
freshness: Freshness,
|
|
262
|
+
basis: Basis | undefined,
|
|
263
|
+
digests: ReadonlyMap<string, string>,
|
|
264
|
+
): Recheck | undefined {
|
|
265
|
+
if (entry.verdict === 'red') return failed_recheck(entry, digests)
|
|
266
|
+
if (!freshness.is_same_change) return undefined
|
|
267
|
+
const recheck = recheck_tests(entry, basis) ?? []
|
|
268
|
+
|
|
269
|
+
return recheck.length === 0 ? undefined : { verdict: entry.verdict, recheck }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// The verdict a recheck answers: a red test pins the hunk; otherwise the stored verdict stands, raised
|
|
273
|
+
// to `green` when a stored `no-test` now has a test that ran. A `red` one whose tests no longer fail
|
|
274
|
+
// answers nothing — only a run over every reaching test can — and a stopped run answers as it stopped.
|
|
275
|
+
function rechecked(known: Recheck, probe: RedProbe): RedProbe | undefined {
|
|
276
|
+
if (known.verdict !== 'red') {
|
|
277
|
+
return probe.verdict === 'no-test' ? { verdict: known.verdict, failed: [] } : probe
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return probe.verdict === 'green' || probe.verdict === 'no-test' ? undefined : probe
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function is_recheck(known: Known): known is Recheck {
|
|
284
|
+
return known !== undefined && 'recheck' in known
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// A `red` verdict's basis: the base commit with the changed files no import graph follows (`assets`,
|
|
288
|
+
// a `change_of` digest), and each failed test's import closure.
|
|
289
|
+
function red_basis(assets: string, closures: ReadonlyArray<readonly [string, string]>): string {
|
|
290
|
+
const named = closures.toSorted(([left], [right]) => left.localeCompare(right))
|
|
291
|
+
|
|
292
|
+
return digest_of(JSON.stringify([assets, named]))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Each failed file with its digest now; a failed file with none — not a test file this cache digests —
|
|
296
|
+
// records none, so the verdict rests on every test file instead.
|
|
297
|
+
function tests_of(
|
|
298
|
+
failed: ReadonlyArray<string>,
|
|
299
|
+
digests: ReadonlyMap<string, string>,
|
|
300
|
+
): Record<string, string> {
|
|
301
|
+
const pairs = failed.flatMap((file) => {
|
|
302
|
+
const digest = digests.get(file)
|
|
303
|
+
|
|
304
|
+
return digest === undefined ? [] : [[file, digest] as const]
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
return pairs.length === failed.length ? Object.fromEntries(pairs) : {}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function stored_of(
|
|
311
|
+
probe: RedProbe,
|
|
312
|
+
digests: ReadonlyMap<string, string>,
|
|
313
|
+
basis: Basis | undefined,
|
|
314
|
+
): StoredVerdict {
|
|
315
|
+
const entry = { verdict: probe.verdict, tests: tests_of(probe.failed, digests) }
|
|
316
|
+
if (basis === undefined) return entry
|
|
317
|
+
|
|
318
|
+
return basis.related === undefined
|
|
319
|
+
? { ...entry, basis: basis.digest }
|
|
320
|
+
: { ...entry, basis: basis.digest, related: basis.related }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// The verdicts to write back, keyed, each with its basis where one was read; a key with no verdict is
|
|
324
|
+
// left out. A `stopped` verdict is left out too: the config that decides it is not a test file the
|
|
325
|
+
// freshness rules watch, so a stale one would set the hunk aside after the config is fixed — it is
|
|
326
|
+
// probed again on every run (#5043).
|
|
327
|
+
function stored(
|
|
328
|
+
keys: ReadonlyArray<string>,
|
|
329
|
+
probes: ReadonlyArray<RedProbe | undefined>,
|
|
330
|
+
digests: ReadonlyMap<string, string>,
|
|
331
|
+
bases: ReadonlyArray<Basis | undefined> = [],
|
|
332
|
+
): Record<string, StoredVerdict> {
|
|
333
|
+
const pairs = keys.flatMap((key, index) => {
|
|
334
|
+
const probe = probes[index]
|
|
335
|
+
if (probe === undefined || probe.verdict === 'stopped') return []
|
|
336
|
+
|
|
337
|
+
return [[key, stored_of(probe, digests, bases[index])] as const]
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
return Object.fromEntries(pairs)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const test_pinned_cache_logic = {
|
|
344
|
+
change_of,
|
|
345
|
+
digest_of,
|
|
346
|
+
distinct,
|
|
347
|
+
entries_of,
|
|
348
|
+
fresh_verdict,
|
|
349
|
+
freshness_of,
|
|
350
|
+
greens_of,
|
|
351
|
+
hunk_key,
|
|
352
|
+
is_recheck,
|
|
353
|
+
recheck_of,
|
|
354
|
+
rechecked,
|
|
355
|
+
red_basis,
|
|
356
|
+
related_basis,
|
|
357
|
+
stored,
|
|
358
|
+
tests_of,
|
|
359
|
+
test_set_of,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export type { Basis, Freshness, GreenHunk, Known, Recheck, StoredVerdict, VerdictCache }
|
|
363
|
+
export { test_pinned_cache_logic }
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { changed_paths } from '#scripts/git/changed-paths'
|
|
5
|
+
import { git_spawn } from '#scripts/git/git-spawn'
|
|
6
|
+
import { run_record_path } from '#scripts/josh/run-record-path'
|
|
7
|
+
import { atomic_write } from '#scripts/lib/atomic-write'
|
|
8
|
+
import { test_declared_logic } from './test-declared-logic'
|
|
9
|
+
import {
|
|
10
|
+
test_pinned_cache_logic,
|
|
11
|
+
type Basis,
|
|
12
|
+
type Freshness,
|
|
13
|
+
type Known,
|
|
14
|
+
type StoredVerdict,
|
|
15
|
+
type VerdictCache,
|
|
16
|
+
} from './test-pinned-cache-logic'
|
|
17
|
+
import type { Hunk } from './test-pinned-logic'
|
|
18
|
+
import { test_pinned_prune_logic, type CodeChange } from './test-pinned-prune-logic'
|
|
19
|
+
import { test_pinned_reach, type Reach } from './test-pinned-reach'
|
|
20
|
+
import type { RedProbe } from './test-red-logic'
|
|
21
|
+
|
|
22
|
+
// The reads and writes of `josh test:pinned`'s verdict cache (joshuafolkken/kit#4706); what a key is and
|
|
23
|
+
// when an entry holds is `test-pinned-cache-logic.ts`. `josh run:review` calls the same `test_pinned.run`,
|
|
24
|
+
// so the two share it.
|
|
25
|
+
//
|
|
26
|
+
// **The cache sits in the checkout's own git directory** (`git rev-parse --git-path`, per worktree), so
|
|
27
|
+
// a lane never reads another lane's verdicts, and nothing is written into the work tree.
|
|
28
|
+
|
|
29
|
+
const CACHE_NAME = 'josh-test-pinned-verdicts.json'
|
|
30
|
+
const LINE_BREAK = '\n'
|
|
31
|
+
// Peels a ref or tag to the commit it names, so a branch name and its SHA key the same change.
|
|
32
|
+
const AS_COMMIT = '^{commit}'
|
|
33
|
+
|
|
34
|
+
interface TreeRead {
|
|
35
|
+
test_digests: ReadonlyMap<string, string>
|
|
36
|
+
code: CodeChange
|
|
37
|
+
test_contents: string
|
|
38
|
+
change: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// `hunks` are the run's own, and `contents` each of their files and each recorded `green` hunk's file
|
|
42
|
+
// as `load` read them, so `pruned` is written off the tree the verdicts were reached on.
|
|
43
|
+
interface LoadedCache extends VerdictCache {
|
|
44
|
+
file: string
|
|
45
|
+
base: string
|
|
46
|
+
freshness: Freshness
|
|
47
|
+
test_digests: ReadonlyMap<string, string>
|
|
48
|
+
reach: Reach
|
|
49
|
+
code: CodeChange
|
|
50
|
+
hunks: ReadonlyArray<Hunk>
|
|
51
|
+
contents: ReadonlyMap<string, string>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A verdict reached by a run, with the index of the key it answers.
|
|
55
|
+
type Reached = readonly [number, RedProbe]
|
|
56
|
+
|
|
57
|
+
interface CacheWriter {
|
|
58
|
+
record: (reached: ReadonlyArray<Reached>) => Promise<void>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The verdicts a run reached so far, each with the basis read when it was reached.
|
|
62
|
+
interface WriterState {
|
|
63
|
+
cache: LoadedCache
|
|
64
|
+
keys: ReadonlyArray<string>
|
|
65
|
+
files: ReadonlyArray<string>
|
|
66
|
+
probes: Array<RedProbe | undefined>
|
|
67
|
+
bases: Array<Basis | undefined>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function read_text(file: string): Promise<string> {
|
|
71
|
+
return existsSync(file) ? await readFile(file, 'utf8') : ''
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The content of each named file the work tree still holds.
|
|
75
|
+
async function contents_of(files: ReadonlyArray<string>): Promise<Map<string, string>> {
|
|
76
|
+
const existing = [...new Set(files)].filter((file) => existsSync(file))
|
|
77
|
+
const texts = await Promise.all(existing.map(async (file) => await readFile(file, 'utf8')))
|
|
78
|
+
|
|
79
|
+
return new Map(existing.map((file, index) => [file, texts[index] ?? '']))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Every test file in the work tree, tracked or untracked but not ignored.
|
|
83
|
+
async function test_files(): Promise<Array<string>> {
|
|
84
|
+
const listed = await test_pinned_reach.work_tree_files()
|
|
85
|
+
|
|
86
|
+
return listed.filter((file) => test_declared_logic.is_test_file(file))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The digest of each named file the work tree still holds.
|
|
90
|
+
async function digests_of(files: ReadonlyArray<string>): Promise<Map<string, string>> {
|
|
91
|
+
const contents = await contents_of(files)
|
|
92
|
+
|
|
93
|
+
return new Map(
|
|
94
|
+
[...contents].map(([file, text]) => [file, test_pinned_cache_logic.digest_of(text)]),
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Each test file's content digest, keyed by its path.
|
|
99
|
+
async function test_digests_of(): Promise<Map<string, string>> {
|
|
100
|
+
return await digests_of(await test_files())
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// One digest over every test file's path and content.
|
|
104
|
+
function contents_digest(digests: ReadonlyMap<string, string>): string {
|
|
105
|
+
return test_pinned_cache_logic.test_set_of(
|
|
106
|
+
[...digests].map(([file, digest]) => `${file} ${digest}`),
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The code change: `base` as a commit, and every non-test path changed since it with its content. A
|
|
111
|
+
// test file is left out — a `red` rests on the test files it failed in, not on every test — and so is
|
|
112
|
+
// a run record, which no test reads, so one written after a probe keeps its `red`
|
|
113
|
+
// (joshuafolkken/kit#4856, `run-record-path.ts`).
|
|
114
|
+
async function code_change(base: string): Promise<CodeChange> {
|
|
115
|
+
const [commit, paths] = await Promise.all([
|
|
116
|
+
git_spawn.read(['rev-parse', '--verify', `${base}${AS_COMMIT}`]),
|
|
117
|
+
changed_paths.read_changed_since(base),
|
|
118
|
+
])
|
|
119
|
+
const code = paths.filter(
|
|
120
|
+
(file) => !test_declared_logic.is_test_file(file) && !run_record_path.is_run_record(file),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return { commit: commit.trim(), code, digests: await digests_of(code) }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The work tree's tree-wide digests, with what they were read off.
|
|
127
|
+
async function read_tree(base: string): Promise<TreeRead> {
|
|
128
|
+
const [test_digests, code] = await Promise.all([test_digests_of(), code_change(base)])
|
|
129
|
+
const test_contents = contents_digest(test_digests)
|
|
130
|
+
const change = test_pinned_cache_logic.change_of(code.commit, code.code, code.digests)
|
|
131
|
+
|
|
132
|
+
return { test_digests, code, test_contents, change }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The cache file's text and what the work tree holds now, with the files `hunks` and the recorded
|
|
136
|
+
// `green` hunks sit in.
|
|
137
|
+
async function read_all(
|
|
138
|
+
file: string,
|
|
139
|
+
base: string,
|
|
140
|
+
hunks: ReadonlyArray<Hunk>,
|
|
141
|
+
): Promise<[string, TreeRead, Map<string, string>]> {
|
|
142
|
+
const text = await read_text(file)
|
|
143
|
+
const greens = test_pinned_cache_logic.greens_of(text)
|
|
144
|
+
const files = [...hunks, ...greens].map((hunk) => hunk.file)
|
|
145
|
+
|
|
146
|
+
return [text, ...(await Promise.all([read_tree(base), contents_of(files)]))]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// `hunks` are the ones this run checks; `pruned` is read now against the `green` hunks the cache recorded.
|
|
150
|
+
async function load(base: string, hunks: ReadonlyArray<Hunk>): Promise<LoadedCache> {
|
|
151
|
+
const file = path.resolve(await git_spawn.read(['rev-parse', '--git-path', CACHE_NAME]))
|
|
152
|
+
const [text, tree, contents] = await read_all(file, base, hunks)
|
|
153
|
+
const { test_digests, code, test_contents, change } = tree
|
|
154
|
+
const greens = test_pinned_cache_logic.greens_of(text)
|
|
155
|
+
const remnants = test_pinned_prune_logic.remnants_of(hunks, greens)
|
|
156
|
+
const pruned = test_pinned_prune_logic.pruned_of(code, contents, remnants, greens)
|
|
157
|
+
const freshness = test_pinned_cache_logic.freshness_of(text, { change, pruned, test_contents })
|
|
158
|
+
const reach = test_pinned_reach.create({ ...code, test_digests })
|
|
159
|
+
const entries = test_pinned_cache_logic.entries_of(text)
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
file,
|
|
163
|
+
base,
|
|
164
|
+
test_contents,
|
|
165
|
+
change,
|
|
166
|
+
entries,
|
|
167
|
+
freshness,
|
|
168
|
+
test_digests,
|
|
169
|
+
reach,
|
|
170
|
+
code,
|
|
171
|
+
hunks,
|
|
172
|
+
contents,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// What a verdict on a hunk of `file` rests on now: the failed tests' closures for a `red`, the tests
|
|
177
|
+
// reaching `file` for any other.
|
|
178
|
+
async function basis_of(
|
|
179
|
+
cache: LoadedCache,
|
|
180
|
+
probe: RedProbe,
|
|
181
|
+
file: string,
|
|
182
|
+
): Promise<Basis | undefined> {
|
|
183
|
+
if (probe.verdict !== 'red') {
|
|
184
|
+
const tests = await cache.reach.related(file)
|
|
185
|
+
|
|
186
|
+
return tests && test_pinned_cache_logic.related_basis(tests, cache.test_digests)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const digest = await cache.reach.failed(probe.failed)
|
|
190
|
+
|
|
191
|
+
return digest === undefined ? undefined : { digest }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The tree-wide digests first; only an entry they drop has its basis read, so an unchanged tree builds
|
|
195
|
+
// no import graph. Past the basis, it may still be rechecked against a few tests (`recheck_of`).
|
|
196
|
+
async function known_by_basis(
|
|
197
|
+
cache: LoadedCache,
|
|
198
|
+
entry: StoredVerdict,
|
|
199
|
+
file: string,
|
|
200
|
+
): Promise<Known> {
|
|
201
|
+
const { freshness, test_digests } = cache
|
|
202
|
+
const stored = { verdict: entry.verdict, failed: Object.keys(entry.tests) }
|
|
203
|
+
const basis = await basis_of(cache, stored, file)
|
|
204
|
+
const held = test_pinned_cache_logic.fresh_verdict(entry, freshness, test_digests, basis?.digest)
|
|
205
|
+
|
|
206
|
+
return held ?? test_pinned_cache_logic.recheck_of(entry, freshness, basis, test_digests)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// An entry stored with no basis is not held past the tree-wide digests, but a `red` one is still
|
|
210
|
+
// rechecked against its failed tests.
|
|
211
|
+
async function known_one(cache: LoadedCache, key: string, file: string): Promise<Known> {
|
|
212
|
+
const { freshness, test_digests } = cache
|
|
213
|
+
const entry = cache.entries[key]
|
|
214
|
+
const kept = test_pinned_cache_logic.fresh_verdict(entry, freshness, test_digests)
|
|
215
|
+
if (kept !== undefined || entry === undefined) return kept
|
|
216
|
+
|
|
217
|
+
if (entry.basis === undefined) {
|
|
218
|
+
return test_pinned_cache_logic.recheck_of(entry, freshness, undefined, test_digests)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return await known_by_basis(cache, entry, file)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// What the cache knows of each key: its stored verdict, one to recheck, or `undefined` where none is
|
|
225
|
+
// stored or what it rests on changed; `files` names each key's hunk file.
|
|
226
|
+
async function known(
|
|
227
|
+
cache: LoadedCache,
|
|
228
|
+
keys: ReadonlyArray<string>,
|
|
229
|
+
files: ReadonlyArray<string>,
|
|
230
|
+
): Promise<Array<Known>> {
|
|
231
|
+
return await Promise.all(
|
|
232
|
+
keys.map(async (key, index) => await known_one(cache, key, files[index] ?? '')),
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// A verdict the tree-wide digests still hold keeps its stored basis; any other has its own read now.
|
|
237
|
+
async function stored_basis(
|
|
238
|
+
cache: LoadedCache,
|
|
239
|
+
key: string,
|
|
240
|
+
probe: RedProbe,
|
|
241
|
+
file: string,
|
|
242
|
+
): Promise<Basis | undefined> {
|
|
243
|
+
const entry = cache.entries[key]
|
|
244
|
+
const kept = test_pinned_cache_logic.fresh_verdict(entry, cache.freshness, cache.test_digests)
|
|
245
|
+
|
|
246
|
+
if (kept?.verdict === probe.verdict && entry?.basis !== undefined) {
|
|
247
|
+
return { digest: entry.basis, related: entry.related }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const failed = Object.keys(test_pinned_cache_logic.tests_of(probe.failed, cache.test_digests))
|
|
251
|
+
|
|
252
|
+
return await basis_of(cache, { ...probe, failed }, file)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Whether the tree still reads as it did at `load`.
|
|
256
|
+
async function is_steady(cache: LoadedCache): Promise<boolean> {
|
|
257
|
+
const now = await read_tree(cache.base)
|
|
258
|
+
|
|
259
|
+
return now.change === cache.change && now.test_contents === cache.test_contents
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Each `green` hunk among `probes` is recorded, with the code change those hunks reverted.
|
|
263
|
+
function write(
|
|
264
|
+
cache: LoadedCache,
|
|
265
|
+
entries: Record<string, StoredVerdict>,
|
|
266
|
+
probes: ReadonlyArray<RedProbe | undefined>,
|
|
267
|
+
): void {
|
|
268
|
+
const { code, contents, hunks } = cache
|
|
269
|
+
const reached = test_pinned_prune_logic.greens_reached(
|
|
270
|
+
hunks,
|
|
271
|
+
probes.map((probe) => probe?.verdict),
|
|
272
|
+
)
|
|
273
|
+
const green = reached.map((hunk) => test_pinned_prune_logic.record_of(hunk))
|
|
274
|
+
const pruned = test_pinned_prune_logic.pruned_of(code, contents, reached, green)
|
|
275
|
+
const content: VerdictCache = {
|
|
276
|
+
test_contents: cache.test_contents,
|
|
277
|
+
change: cache.change,
|
|
278
|
+
entries,
|
|
279
|
+
pruned,
|
|
280
|
+
green,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
atomic_write.replace_file(cache.file, `${JSON.stringify(content)}${LINE_BREAK}`)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// **Each verdict is written as soon as it is reached** (joshuafolkken/kit#4936), so a run stopped
|
|
287
|
+
// mid-way — `josh run:review` stops one a round-1 fix moved the tree under — leaves the verdicts it
|
|
288
|
+
// already had for the next run to reuse. The writes are queued one after another, and each replaces the
|
|
289
|
+
// file whole, so a stopped run leaves either write, never half of one.
|
|
290
|
+
//
|
|
291
|
+
// **A basis is read once its verdict is reached, and kept only while the tree still reads as it did at
|
|
292
|
+
// `load`**: one read off an edit made mid-run would hold a verdict the probes never saw. A verdict
|
|
293
|
+
// reached before the edit keeps the basis it was written with.
|
|
294
|
+
//
|
|
295
|
+
// Only the verdicts this run used are written, so the file never holds a hunk the branch has left behind.
|
|
296
|
+
async function settle(state: WriterState, reached: ReadonlyArray<Reached>): Promise<void> {
|
|
297
|
+
const { cache, keys, files, probes, bases } = state
|
|
298
|
+
const read = await Promise.all(
|
|
299
|
+
reached.map(
|
|
300
|
+
async ([index, probe]) =>
|
|
301
|
+
await stored_basis(cache, keys[index] ?? '', probe, files[index] ?? ''),
|
|
302
|
+
),
|
|
303
|
+
)
|
|
304
|
+
const is_kept = await is_steady(cache)
|
|
305
|
+
|
|
306
|
+
for (const [position, [index, probe]] of reached.entries()) {
|
|
307
|
+
probes[index] = probe
|
|
308
|
+
bases[index] = is_kept ? read[position] : undefined
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
write(cache, test_pinned_cache_logic.stored(keys, probes, cache.test_digests, bases), probes)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// `keys` name the hunks `load` was given, in their order.
|
|
315
|
+
function writer(cache: LoadedCache, keys: ReadonlyArray<string>): CacheWriter {
|
|
316
|
+
const files = cache.hunks.map((hunk) => hunk.file)
|
|
317
|
+
const state: WriterState = { cache, keys, files, probes: [], bases: [] }
|
|
318
|
+
const queue = { last: Promise.resolve() }
|
|
319
|
+
|
|
320
|
+
async function record(reached: ReadonlyArray<Reached>): Promise<void> {
|
|
321
|
+
const previous = queue.last
|
|
322
|
+
const current = (async (): Promise<void> => {
|
|
323
|
+
await previous
|
|
324
|
+
await settle(state, reached)
|
|
325
|
+
})()
|
|
326
|
+
|
|
327
|
+
queue.last = current
|
|
328
|
+
await current
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return { record }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const test_pinned_cache = { known, load, writer }
|
|
335
|
+
|
|
336
|
+
export type { CacheWriter, LoadedCache, Reached }
|
|
337
|
+
export { test_pinned_cache }
|